| 246 | argsSchema = KeyArgs; |
| 247 | |
| 248 | async execute(args: z.infer<typeof KeyArgs>, _ctx: ToolContext): Promise<ToolResult> { |
| 249 | if (!isMacos()) return macosOnly(this.name); |
| 250 | try { |
| 251 | const parts = args.combo.toLowerCase().split('+').map(s => s.trim()); |
| 252 | const modifiers: string[] = []; |
| 253 | let keyName: string | null = null; |
| 254 | for (const part of parts) { |
| 255 | const norm = KEY_ALIASES[part] ?? part; |
| 256 | if (['command', 'control', 'option', 'shift'].includes(norm)) modifiers.push(norm + ' down'); |
| 257 | else keyName = norm; |
| 258 | } |
| 259 | if (!keyName) return { content: '[COMPUTER_USE_ERROR] No primary key in combo. Example: "cmd+s".', isError: true }; |
| 260 | |
| 261 | // Special keys via key code; printable chars via keystroke |
| 262 | const specialKeys: Record<string, number> = { |
| 263 | return: 36, escape: 53, tab: 48, space: 49, delete: 51, |
| 264 | left: 123, right: 124, down: 125, up: 126, |
| 265 | f1: 122, f2: 120, f3: 99, f4: 118, f5: 96, f6: 97, f7: 98, f8: 100, f9: 101, f10: 109, f11: 103, f12: 111, |
| 266 | }; |
| 267 | const usingModifiers = modifiers.length > 0; |
| 268 | const modClause = usingModifiers ? ` using {${modifiers.join(', ')}}` : ''; |
| 269 | |
| 270 | let script: string; |
| 271 | if (keyName in specialKeys) { |
| 272 | script = `tell application "System Events" to key code ${specialKeys[keyName]}${modClause}`; |
| 273 | } else { |
| 274 | // Keystroke a single character (e.g. cmd+s → "s") |
| 275 | script = `tell application "System Events" to keystroke "${keyName.replace(/"/g, '\\"')}"${modClause}`; |
| 276 | } |
| 277 | await runCmd('osascript', ['-e', script], { timeoutMs: 5000 }); |
| 278 | return { content: `Pressed ${args.combo}` }; |
| 279 | } catch (e: any) { |
| 280 | return { content: `[COMPUTER_USE_ERROR] key press failed: ${e?.message ?? e}`, isError: true }; |
| 281 | } |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | // ───────────────────────────────────────────────────────────────────────────── |