@projectservan8n/cnapse 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,454 @@
1
+ /**
2
+ * Computer Control Tools - Mouse, keyboard, and window automation
3
+ * Uses native platform APIs via shell commands
4
+ */
5
+
6
+ import { exec } from 'child_process';
7
+ import { promisify } from 'util';
8
+ import { ToolResult, ok, err } from './index.js';
9
+
10
+ const execAsync = promisify(exec);
11
+
12
+ /**
13
+ * Move mouse to coordinates
14
+ */
15
+ export async function moveMouse(x: number, y: number): Promise<ToolResult> {
16
+ try {
17
+ if (process.platform === 'win32') {
18
+ await execAsync(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(${x}, ${y})"`, { shell: 'cmd.exe' });
19
+ } else if (process.platform === 'darwin') {
20
+ // macOS - use cliclick if available
21
+ await execAsync(`cliclick m:${x},${y}`);
22
+ } else {
23
+ // Linux - use xdotool
24
+ await execAsync(`xdotool mousemove ${x} ${y}`);
25
+ }
26
+ return ok(`Mouse moved to (${x}, ${y})`);
27
+ } catch (error) {
28
+ return err(`Failed to move mouse: ${error instanceof Error ? error.message : 'Unknown error'}`);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Click mouse button
34
+ */
35
+ export async function clickMouse(button: 'left' | 'right' | 'middle' = 'left'): Promise<ToolResult> {
36
+ try {
37
+ if (process.platform === 'win32') {
38
+ const script = `
39
+ Add-Type -MemberDefinition @"
40
+ [DllImport("user32.dll",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
41
+ public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
42
+ "@ -Name Mouse -Namespace Win32
43
+ ${button === 'left' ? '[Win32.Mouse]::mouse_event(0x02, 0, 0, 0, 0); [Win32.Mouse]::mouse_event(0x04, 0, 0, 0, 0)' :
44
+ button === 'right' ? '[Win32.Mouse]::mouse_event(0x08, 0, 0, 0, 0); [Win32.Mouse]::mouse_event(0x10, 0, 0, 0, 0)' :
45
+ '[Win32.Mouse]::mouse_event(0x20, 0, 0, 0, 0); [Win32.Mouse]::mouse_event(0x40, 0, 0, 0, 0)'}`;
46
+ await execAsync(`powershell -Command "${script.replace(/\n/g, ' ')}"`, { shell: 'cmd.exe' });
47
+ } else if (process.platform === 'darwin') {
48
+ await execAsync(`cliclick c:.`);
49
+ } else {
50
+ const btn = button === 'left' ? '1' : button === 'right' ? '3' : '2';
51
+ await execAsync(`xdotool click ${btn}`);
52
+ }
53
+ return ok(`Clicked ${button} button`);
54
+ } catch (error) {
55
+ return err(`Failed to click: ${error instanceof Error ? error.message : 'Unknown error'}`);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Double click
61
+ */
62
+ export async function doubleClick(): Promise<ToolResult> {
63
+ try {
64
+ if (process.platform === 'win32') {
65
+ const script = `
66
+ Add-Type -MemberDefinition @"
67
+ [DllImport("user32.dll",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
68
+ public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
69
+ "@ -Name Mouse -Namespace Win32
70
+ [Win32.Mouse]::mouse_event(0x02, 0, 0, 0, 0); [Win32.Mouse]::mouse_event(0x04, 0, 0, 0, 0)
71
+ Start-Sleep -Milliseconds 50
72
+ [Win32.Mouse]::mouse_event(0x02, 0, 0, 0, 0); [Win32.Mouse]::mouse_event(0x04, 0, 0, 0, 0)`;
73
+ await execAsync(`powershell -Command "${script.replace(/\n/g, ' ')}"`, { shell: 'cmd.exe' });
74
+ } else if (process.platform === 'darwin') {
75
+ await execAsync(`cliclick dc:.`);
76
+ } else {
77
+ await execAsync(`xdotool click --repeat 2 --delay 50 1`);
78
+ }
79
+ return ok('Double clicked');
80
+ } catch (error) {
81
+ return err(`Failed to double click: ${error instanceof Error ? error.message : 'Unknown error'}`);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Type text
87
+ */
88
+ export async function typeText(text: string): Promise<ToolResult> {
89
+ try {
90
+ if (process.platform === 'win32') {
91
+ // Use PowerShell SendKeys
92
+ const escapedText = text.replace(/'/g, "''").replace(/[+^%~(){}[\]]/g, '{$&}');
93
+ await execAsync(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${escapedText}')"`, { shell: 'cmd.exe' });
94
+ } else if (process.platform === 'darwin') {
95
+ const escaped = text.replace(/'/g, "'\\''");
96
+ await execAsync(`osascript -e 'tell application "System Events" to keystroke "${escaped}"'`);
97
+ } else {
98
+ const escaped = text.replace(/'/g, "'\\''");
99
+ await execAsync(`xdotool type '${escaped}'`);
100
+ }
101
+ return ok(`Typed: ${text}`);
102
+ } catch (error) {
103
+ return err(`Failed to type: ${error instanceof Error ? error.message : 'Unknown error'}`);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Press a key
109
+ */
110
+ export async function pressKey(key: string): Promise<ToolResult> {
111
+ try {
112
+ if (process.platform === 'win32') {
113
+ // Windows SendKeys format
114
+ const winKeyMap: Record<string, string> = {
115
+ 'enter': '{ENTER}', 'return': '{ENTER}',
116
+ 'escape': '{ESC}', 'esc': '{ESC}',
117
+ 'tab': '{TAB}',
118
+ 'space': ' ',
119
+ 'backspace': '{BACKSPACE}',
120
+ 'delete': '{DELETE}',
121
+ 'up': '{UP}',
122
+ 'down': '{DOWN}',
123
+ 'left': '{LEFT}',
124
+ 'right': '{RIGHT}',
125
+ 'home': '{HOME}',
126
+ 'end': '{END}',
127
+ 'pageup': '{PGUP}',
128
+ 'pagedown': '{PGDN}',
129
+ 'f1': '{F1}', 'f2': '{F2}', 'f3': '{F3}', 'f4': '{F4}',
130
+ 'f5': '{F5}', 'f6': '{F6}', 'f7': '{F7}', 'f8': '{F8}',
131
+ 'f9': '{F9}', 'f10': '{F10}', 'f11': '{F11}', 'f12': '{F12}',
132
+ };
133
+ const winKey = winKeyMap[key.toLowerCase()] || key;
134
+ await execAsync(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${winKey}')"`, { shell: 'cmd.exe' });
135
+ } else if (process.platform === 'darwin') {
136
+ const macKeyMap: Record<string, number> = {
137
+ 'return': 36, 'enter': 36, 'escape': 53, 'esc': 53,
138
+ 'tab': 48, 'space': 49, 'backspace': 51, 'delete': 117,
139
+ 'up': 126, 'down': 125, 'left': 123, 'right': 124,
140
+ };
141
+ const keyCode = macKeyMap[key.toLowerCase()];
142
+ if (keyCode) {
143
+ await execAsync(`osascript -e 'tell application "System Events" to key code ${keyCode}'`);
144
+ } else {
145
+ await execAsync(`osascript -e 'tell application "System Events" to keystroke "${key}"'`);
146
+ }
147
+ } else {
148
+ await execAsync(`xdotool key ${key}`);
149
+ }
150
+ return ok(`Pressed: ${key}`);
151
+ } catch (error) {
152
+ return err(`Failed to press key: ${error instanceof Error ? error.message : 'Unknown error'}`);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Press key combination
158
+ */
159
+ export async function keyCombo(keys: string[]): Promise<ToolResult> {
160
+ try {
161
+ if (process.platform === 'win32') {
162
+ // Handle Win+R specially for opening Run dialog
163
+ const hasWin = keys.some(k => k.toLowerCase() === 'meta' || k.toLowerCase() === 'win');
164
+ const hasR = keys.some(k => k.toLowerCase() === 'r');
165
+
166
+ if (hasWin && hasR) {
167
+ // Use PowerShell to open Run dialog
168
+ await execAsync(`powershell -Command "$shell = New-Object -ComObject WScript.Shell; $shell.Run('explorer shell:::{2559a1f3-21d7-11d4-bdaf-00c04f60b9f0}')"`, { shell: 'cmd.exe' });
169
+ return ok(`Pressed: ${keys.join('+')}`);
170
+ }
171
+
172
+ // Build SendKeys combo
173
+ const modifierMap: Record<string, string> = {
174
+ 'control': '^', 'ctrl': '^',
175
+ 'alt': '%',
176
+ 'shift': '+',
177
+ };
178
+
179
+ let combo = '';
180
+ const regularKeys: string[] = [];
181
+
182
+ for (const key of keys) {
183
+ const lower = key.toLowerCase();
184
+ if (modifierMap[lower]) {
185
+ combo += modifierMap[lower];
186
+ } else if (lower !== 'meta' && lower !== 'win') {
187
+ regularKeys.push(key.toLowerCase());
188
+ }
189
+ }
190
+
191
+ combo += regularKeys.join('');
192
+ await execAsync(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${combo}')"`, { shell: 'cmd.exe' });
193
+ } else if (process.platform === 'darwin') {
194
+ const modifiers = keys.filter(k => ['control', 'ctrl', 'alt', 'shift', 'command', 'meta'].includes(k.toLowerCase()));
195
+ const regular = keys.filter(k => !['control', 'ctrl', 'alt', 'shift', 'command', 'meta'].includes(k.toLowerCase()));
196
+
197
+ let cmd = 'tell application "System Events" to keystroke "' + regular.join('') + '"';
198
+ if (modifiers.length > 0) {
199
+ const modMap: Record<string, string> = {
200
+ 'control': 'control down', 'ctrl': 'control down',
201
+ 'alt': 'option down', 'shift': 'shift down',
202
+ 'command': 'command down', 'meta': 'command down',
203
+ };
204
+ cmd += ' using {' + modifiers.map(m => modMap[m.toLowerCase()]).join(', ') + '}';
205
+ }
206
+ await execAsync(`osascript -e '${cmd}'`);
207
+ } else {
208
+ await execAsync(`xdotool key ${keys.join('+')}`);
209
+ }
210
+ return ok(`Pressed: ${keys.join('+')}`);
211
+ } catch (error) {
212
+ return err(`Failed to press combo: ${error instanceof Error ? error.message : 'Unknown error'}`);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Get active window info
218
+ */
219
+ export async function getActiveWindow(): Promise<ToolResult> {
220
+ try {
221
+ if (process.platform === 'win32') {
222
+ const script = `
223
+ Add-Type @"
224
+ using System;
225
+ using System.Runtime.InteropServices;
226
+ using System.Text;
227
+ public class Win32 {
228
+ [DllImport("user32.dll")]
229
+ public static extern IntPtr GetForegroundWindow();
230
+ [DllImport("user32.dll")]
231
+ public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
232
+ }
233
+ "@
234
+ $hwnd = [Win32]::GetForegroundWindow()
235
+ $sb = New-Object System.Text.StringBuilder 256
236
+ [Win32]::GetWindowText($hwnd, $sb, 256)
237
+ $sb.ToString()`;
238
+ const { stdout } = await execAsync(`powershell -Command "${script.replace(/\n/g, ' ')}"`, { shell: 'cmd.exe' });
239
+ return ok(stdout.trim() || 'Unknown window');
240
+ } else if (process.platform === 'darwin') {
241
+ const { stdout } = await execAsync(`osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true'`);
242
+ return ok(stdout.trim());
243
+ } else {
244
+ const { stdout } = await execAsync(`xdotool getactivewindow getwindowname`);
245
+ return ok(stdout.trim());
246
+ }
247
+ } catch (error) {
248
+ return err(`Failed to get active window: ${error instanceof Error ? error.message : 'Unknown error'}`);
249
+ }
250
+ }
251
+
252
+ /**
253
+ * List all windows
254
+ */
255
+ export async function listWindows(): Promise<ToolResult> {
256
+ try {
257
+ if (process.platform === 'win32') {
258
+ const { stdout } = await execAsync(`powershell -Command "Get-Process | Where-Object {$_.MainWindowTitle} | Select-Object ProcessName, MainWindowTitle | Format-Table -AutoSize"`, { shell: 'cmd.exe' });
259
+ return ok(stdout);
260
+ } else if (process.platform === 'darwin') {
261
+ const { stdout } = await execAsync(`osascript -e 'tell application "System Events" to get name of every application process whose visible is true'`);
262
+ return ok(stdout);
263
+ } else {
264
+ const { stdout } = await execAsync(`wmctrl -l`);
265
+ return ok(stdout);
266
+ }
267
+ } catch (error) {
268
+ return err(`Failed to list windows: ${error instanceof Error ? error.message : 'Unknown error'}`);
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Focus a window by title
274
+ */
275
+ export async function focusWindow(title: string): Promise<ToolResult> {
276
+ try {
277
+ if (process.platform === 'win32') {
278
+ const escaped = title.replace(/'/g, "''");
279
+ await execAsync(`powershell -Command "$wshell = New-Object -ComObject wscript.shell; $wshell.AppActivate('${escaped}')"`, { shell: 'cmd.exe' });
280
+ } else if (process.platform === 'darwin') {
281
+ await execAsync(`osascript -e 'tell application "${title}" to activate'`);
282
+ } else {
283
+ await execAsync(`wmctrl -a "${title}"`);
284
+ }
285
+ return ok(`Focused window: ${title}`);
286
+ } catch (error) {
287
+ return err(`Failed to focus window: ${error instanceof Error ? error.message : 'Unknown error'}`);
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Scroll mouse wheel
293
+ */
294
+ export async function scrollMouse(amount: number): Promise<ToolResult> {
295
+ try {
296
+ if (process.platform === 'win32') {
297
+ const direction = amount > 0 ? 120 * Math.abs(amount) : -120 * Math.abs(amount);
298
+ const script = `
299
+ Add-Type -MemberDefinition @"
300
+ [DllImport("user32.dll",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
301
+ public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
302
+ "@ -Name Mouse -Namespace Win32
303
+ [Win32.Mouse]::mouse_event(0x0800, 0, 0, ${direction}, 0)`;
304
+ await execAsync(`powershell -Command "${script.replace(/\n/g, ' ')}"`, { shell: 'cmd.exe' });
305
+ } else if (process.platform === 'darwin') {
306
+ const dir = amount > 0 ? 'u' : 'd';
307
+ await execAsync(`cliclick -r ${dir}:${Math.abs(amount)}`);
308
+ } else {
309
+ const btn = amount > 0 ? '4' : '5';
310
+ await execAsync(`xdotool click --repeat ${Math.abs(amount)} ${btn}`);
311
+ }
312
+ return ok(`Scrolled ${amount > 0 ? 'up' : 'down'} by ${Math.abs(amount)}`);
313
+ } catch (error) {
314
+ return err(`Failed to scroll: ${error instanceof Error ? error.message : 'Unknown error'}`);
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Drag mouse from one point to another
320
+ */
321
+ export async function dragMouse(startX: number, startY: number, endX: number, endY: number): Promise<ToolResult> {
322
+ try {
323
+ await moveMouse(startX, startY);
324
+ await new Promise(r => setTimeout(r, 100));
325
+
326
+ if (process.platform === 'win32') {
327
+ // Mouse down
328
+ await execAsync(`powershell -Command "Add-Type -MemberDefinition @'
329
+ [DllImport(\\"user32.dll\\",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
330
+ public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
331
+ '@ -Name Mouse -Namespace Win32; [Win32.Mouse]::mouse_event(0x02, 0, 0, 0, 0)"`, { shell: 'cmd.exe' });
332
+
333
+ await moveMouse(endX, endY);
334
+ await new Promise(r => setTimeout(r, 100));
335
+
336
+ // Mouse up
337
+ await execAsync(`powershell -Command "Add-Type -MemberDefinition @'
338
+ [DllImport(\\"user32.dll\\",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
339
+ public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
340
+ '@ -Name Mouse -Namespace Win32; [Win32.Mouse]::mouse_event(0x04, 0, 0, 0, 0)"`, { shell: 'cmd.exe' });
341
+ } else if (process.platform === 'darwin') {
342
+ await execAsync(`cliclick dd:${startX},${startY} du:${endX},${endY}`);
343
+ } else {
344
+ await execAsync(`xdotool mousemove ${startX} ${startY} mousedown 1 mousemove ${endX} ${endY} mouseup 1`);
345
+ }
346
+ return ok(`Dragged from (${startX},${startY}) to (${endX},${endY})`);
347
+ } catch (error) {
348
+ return err(`Failed to drag: ${error instanceof Error ? error.message : 'Unknown error'}`);
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Get mouse position
354
+ */
355
+ export async function getMousePosition(): Promise<ToolResult> {
356
+ try {
357
+ if (process.platform === 'win32') {
358
+ const { stdout } = await execAsync(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; $p = [System.Windows.Forms.Cursor]::Position; Write-Output \\"$($p.X),$($p.Y)\\""`, { shell: 'cmd.exe' });
359
+ return ok(`Mouse position: ${stdout.trim()}`);
360
+ } else if (process.platform === 'darwin') {
361
+ const { stdout } = await execAsync(`cliclick p`);
362
+ return ok(`Mouse position: ${stdout.trim()}`);
363
+ } else {
364
+ const { stdout } = await execAsync(`xdotool getmouselocation --shell`);
365
+ return ok(stdout);
366
+ }
367
+ } catch (error) {
368
+ return err(`Failed to get mouse position: ${error instanceof Error ? error.message : 'Unknown error'}`);
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Get all computer control tools
374
+ */
375
+ export function getComputerTools() {
376
+ return {
377
+ moveMouse,
378
+ clickMouse,
379
+ doubleClick,
380
+ typeText,
381
+ pressKey,
382
+ keyCombo,
383
+ getActiveWindow,
384
+ listWindows,
385
+ focusWindow,
386
+ scrollMouse,
387
+ dragMouse,
388
+ getMousePosition,
389
+ };
390
+ }
391
+
392
+ // Tool definitions for executor
393
+ export const computerTools = [
394
+ {
395
+ name: 'moveMouse',
396
+ description: 'Move mouse to screen coordinates',
397
+ parameters: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, required: ['x', 'y'] },
398
+ },
399
+ {
400
+ name: 'clickMouse',
401
+ description: 'Click mouse button',
402
+ parameters: { type: 'object', properties: { button: { type: 'string', enum: ['left', 'right', 'middle'] } } },
403
+ },
404
+ {
405
+ name: 'doubleClick',
406
+ description: 'Double click at current position',
407
+ parameters: { type: 'object', properties: {} },
408
+ },
409
+ {
410
+ name: 'typeText',
411
+ description: 'Type text using keyboard',
412
+ parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] },
413
+ },
414
+ {
415
+ name: 'pressKey',
416
+ description: 'Press a single key',
417
+ parameters: { type: 'object', properties: { key: { type: 'string' } }, required: ['key'] },
418
+ },
419
+ {
420
+ name: 'keyCombo',
421
+ description: 'Press key combination',
422
+ parameters: { type: 'object', properties: { keys: { type: 'array', items: { type: 'string' } } }, required: ['keys'] },
423
+ },
424
+ {
425
+ name: 'getActiveWindow',
426
+ description: 'Get active window info',
427
+ parameters: { type: 'object', properties: {} },
428
+ },
429
+ {
430
+ name: 'listWindows',
431
+ description: 'List all open windows',
432
+ parameters: { type: 'object', properties: {} },
433
+ },
434
+ {
435
+ name: 'focusWindow',
436
+ description: 'Focus a window by title',
437
+ parameters: { type: 'object', properties: { title: { type: 'string' } }, required: ['title'] },
438
+ },
439
+ {
440
+ name: 'scrollMouse',
441
+ description: 'Scroll mouse wheel (positive=up, negative=down)',
442
+ parameters: { type: 'object', properties: { amount: { type: 'number' } }, required: ['amount'] },
443
+ },
444
+ {
445
+ name: 'dragMouse',
446
+ description: 'Drag mouse from one point to another',
447
+ parameters: { type: 'object', properties: { startX: { type: 'number' }, startY: { type: 'number' }, endX: { type: 'number' }, endY: { type: 'number' } }, required: ['startX', 'startY', 'endX', 'endY'] },
448
+ },
449
+ {
450
+ name: 'getMousePosition',
451
+ description: 'Get current mouse position',
452
+ parameters: { type: 'object', properties: {} },
453
+ },
454
+ ];