Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add macos-clipboard tool #130

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added tools/macos-clipboard/banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tools/macos-clipboard/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 71 additions & 0 deletions tools/macos-clipboard/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { expect } from '@jest/globals';
import { getToolTestClient } from '../../src/test/utils';
import * as path from 'path';

describe('macos-clipboard Tool', () => {
const toolPath = path.join(__dirname, 'tool.py');
const client = getToolTestClient();

it('sets and gets clipboard text', async () => {
const testText = 'Test clipboard text ' + Date.now();

// Set clipboard
const setResponse = await client.executeToolFromFile(toolPath, {
command: 'setClipboard',
content: testText
});
expect(setResponse).toHaveProperty('result');
expect(setResponse.result).toBe('Clipboard set successfully');

// Get clipboard
const getResponse = await client.executeToolFromFile(toolPath, {
command: 'getClipboard',
content_type: 'text'
});
expect(getResponse).toHaveProperty('result');
expect(getResponse.result).toBe(testText);
});

it('clears clipboard', async () => {
// First set some content
await client.executeToolFromFile(toolPath, {
command: 'setClipboard',
content: 'Content to clear'
});

// Then clear it
const clearResponse = await client.executeToolFromFile(toolPath, {
command: 'clearClipboard'
});
expect(clearResponse).toHaveProperty('result');
expect(clearResponse.result).toBe('Clipboard cleared');

// Verify it's cleared
const getResponse = await client.executeToolFromFile(toolPath, {
command: 'getClipboard'
});
expect(getResponse).toHaveProperty('result');
expect(getResponse.result).toBe('');
});

it('gets clipboard as file paths when empty', async () => {
const response = await client.executeToolFromFile(toolPath, {
command: 'getClipboard',
content_type: 'filePaths'
});
expect(response).toHaveProperty('result');
expect(response.result).toBe('No file paths in clipboard');
});

it('fails with invalid command', async () => {
await expect(client.executeToolFromFile(toolPath, {
command: 'invalidCommand'
})).rejects.toThrow();
});

it('fails setting clipboard without content', async () => {
await expect(client.executeToolFromFile(toolPath, {
command: 'setClipboard'
})).rejects.toThrow('Missing "content" for setClipboard');
});
});
53 changes: 53 additions & 0 deletions tools/macos-clipboard/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"id": "macos-clipboard",
"name": "macos-clipboard",
"version": "1.0.0",
"description": "Manage macOS clipboard via AppleScript",
"author": "Example",
"keywords": [
"macos",
"clipboard",
"automation",
"applescript"
],
"configurations": {
"type": "object",
"properties": {},
"required": []
},
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"enum": [
"getClipboard",
"setClipboard",
"clearClipboard"
]
},
"content": {
"type": "string",
"description": "Text to put in clipboard for setClipboard"
},
"content_type": {
"type": "string",
"enum": ["text", "filePaths"],
"description": "When getClipboard, specify if it's text or file paths"
}
},
"required": [
"command"
]
},
"result": {
"type": "object",
"properties": {
"result": {
"type": "string",
"description": "Resulting text from AppleScript"
}
},
"required": ["result"]
}
}
3 changes: 3 additions & 0 deletions tools/macos-clipboard/store.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"categoryId": "5f10d0b4-6acd-477a-96e1-be35634465b2"
}
96 changes: 96 additions & 0 deletions tools/macos-clipboard/tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# /// script
# dependencies = [
# "requests",
# ]
# ///

from typing import Dict, Any, Optional, List
import subprocess

class CONFIG:
pass

class INPUTS:
command: str
content: Optional[str] = None # for setClipboard
content_type: Optional[str] = "text" # for getClipboard

class OUTPUT:
result: str

async def run_applescript(script: str) -> str:
"""Helper function to run AppleScript and return its output."""
try:
result = subprocess.run(['osascript', '-e', script],
capture_output=True,
text=True,
check=True)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
return f"Error: {e.stderr.strip()}"

async def run(config: CONFIG, inputs: INPUTS) -> OUTPUT:
output = OUTPUT()
script = ""

if inputs.command == "getClipboard":
if inputs.content_type == "filePaths":
script = """
tell application "System Events"
try
set theClipboard to the clipboard
if theClipboard starts with "file://" then
set AppleScript's text item delimiters to linefeed
set filePaths to {}
repeat with aLine in paragraphs of (the clipboard as string)
if aLine starts with "file://" then
set end of filePaths to (POSIX path of (aLine as alias))
end if
end repeat
return filePaths as string
else
return "No file paths in clipboard"
end if
on error errMsg
return "Failed to get file paths: " & errMsg
end try
end tell
"""
else:
script = """
tell application "System Events"
try
return (the clipboard as text)
on error errMsg
return "Failed to get clipboard: " & errMsg
end try
end tell
"""

elif inputs.command == "setClipboard":
if not inputs.content:
raise ValueError('Missing "content" for setClipboard')
script = f"""
try
set the clipboard to "{inputs.content}"
return "Clipboard set successfully"
on error errMsg
return "Failed to set clipboard: " & errMsg
end try
"""

elif inputs.command == "clearClipboard":
script = """
try
set the clipboard to ""
return "Clipboard cleared"
on error errMsg
return "Failed to clear clipboard: " & errMsg
end try
"""

else:
raise ValueError(f"Unknown command: {inputs.command}")

output.result = await run_applescript(script)
return output
Loading