@kortix/agent-tunnel 0.1.3 → 0.12.7
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.
- package/README.md +65 -0
- package/dist/agent-cli.js +4676 -3174
- package/dist/client-cli.js +203 -494
- package/package.json +24 -29
- package/src/agent/agent.ts +67 -17
- package/src/agent/capabilities/desktop/cua-driver.ts +284 -0
- package/src/agent/capabilities/desktop.ts +100 -167
- package/src/agent/capabilities/enabled-registry.ts +24 -0
- package/src/agent/capabilities/filesystem.ts +136 -32
- package/src/agent/capabilities/index.ts +1 -1
- package/src/agent/capabilities/security.test.ts +204 -0
- package/src/agent/capabilities/shell.ts +37 -3
- package/src/agent/cli-device-auth.test.ts +179 -0
- package/src/agent/cli-help.test.ts +25 -0
- package/src/agent/cli.ts +475 -38
- package/src/agent/config.test.ts +53 -0
- package/src/agent/config.ts +169 -7
- package/src/agent/index.ts +1 -0
- package/src/agent/security/command-validator.ts +4 -2
- package/src/agent/security/path-validator.ts +73 -18
- package/src/agent/security/permission-guard.test.ts +52 -0
- package/src/agent/security/permission-guard.ts +35 -8
- package/src/agent/service.test.ts +63 -0
- package/src/agent/service.ts +410 -0
- package/src/client/cli.test.ts +150 -547
- package/src/client/cli.ts +116 -537
- package/src/client/index.ts +1 -1
- package/src/client/tools.ts +95 -356
- package/src/client/tunnel-client.ts +50 -80
- package/src/index.ts +7 -1
- package/src/node-ws-polyfill.test.ts +18 -0
- package/src/node-ws-polyfill.ts +5 -3
- package/src/server/heartbeat.ts +13 -6
- package/src/server/relay.test.ts +72 -0
- package/src/server/relay.ts +50 -9
- package/src/server/server.test.ts +33 -0
- package/src/server/server.ts +26 -6
- package/src/server/ws-handler.test.ts +158 -0
- package/src/server/ws-handler.ts +94 -36
- package/src/shared/crypto.ts +2 -3
- package/src/shared/index.ts +8 -0
- package/src/shared/permissions.ts +292 -0
- package/src/shared/types.ts +70 -41
- package/dist/agent/index.d.ts +0 -140
- package/dist/agent/index.js +0 -21
- package/dist/agent/index.js.map +0 -1
- package/dist/chunk-7N7GSU6K.js +0 -34
- package/dist/client/index.d.ts +0 -183
- package/dist/client/index.js +0 -8
- package/dist/client/index.js.map +0 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -55
- package/dist/index.js.map +0 -1
- package/dist/server/index.d.ts +0 -89
- package/dist/server/index.js +0 -14
- package/dist/server/index.js.map +0 -1
- package/dist/shared/index.d.ts +0 -10
- package/dist/shared/index.js +0 -20
- package/dist/shared/index.js.map +0 -1
- package/dist/types-Dpwrd8Ai.d.ts +0 -194
- package/src/agent/capabilities/desktop/atspi-helper.ts +0 -345
- package/src/agent/capabilities/desktop/csharp-helper.ts +0 -914
- package/src/agent/capabilities/desktop/linux-driver.ts +0 -368
- package/src/agent/capabilities/desktop/macos-driver.ts +0 -601
- package/src/agent/capabilities/desktop/swift-helper.ts +0 -736
- package/src/agent/capabilities/desktop/types.ts +0 -201
- package/src/agent/capabilities/desktop/windows-driver.ts +0 -220
package/src/client/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { TunnelClient, TunnelClientError } from './tunnel-client';
|
|
2
|
-
export type { TunnelClientConfig
|
|
2
|
+
export type { TunnelClientConfig } from './tunnel-client';
|
|
3
3
|
export { createTunnelTools } from './tools';
|
|
4
4
|
export type { TunnelToolDefinition, TunnelToolParameter } from './tools';
|
|
5
5
|
// CLI entrypoint: src/client/cli.ts (run via `bun run cli.ts <command> [json]`)
|
package/src/client/tools.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { tmpdir } from 'os';
|
|
4
|
-
import { randomBytes } from 'crypto';
|
|
5
|
-
import type { TunnelClient, AXElement } from './tunnel-client';
|
|
1
|
+
import type { TunnelClient } from './tunnel-client';
|
|
6
2
|
|
|
7
3
|
export interface TunnelToolParameter {
|
|
8
4
|
type: string;
|
|
@@ -19,52 +15,28 @@ export interface TunnelToolDefinition {
|
|
|
19
15
|
execute: (args: Record<string, unknown>) => Promise<string>;
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
function saveImage(base64: string, format: string): string {
|
|
23
|
-
const ext = format === 'jpeg' || format === 'jpg' ? 'jpg' : 'png';
|
|
24
|
-
const dir = join(tmpdir(), 'tunnel-screenshots');
|
|
25
|
-
mkdirSync(dir, { recursive: true });
|
|
26
|
-
const path = join(dir, `screenshot-${randomBytes(4).toString('hex')}.${ext}`);
|
|
27
|
-
writeFileSync(path, Buffer.from(base64, 'base64'));
|
|
28
|
-
return path;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function formatAXTree(el: AXElement, indent: number = 0): string {
|
|
32
|
-
const pad = ' '.repeat(indent);
|
|
33
|
-
const parts: string[] = [];
|
|
34
|
-
|
|
35
|
-
const label = el.title || el.value || el.description || '(unnamed)';
|
|
36
|
-
const flags: string[] = [];
|
|
37
|
-
if (!el.enabled) flags.push('disabled');
|
|
38
|
-
if (el.focused) flags.push('focused');
|
|
39
|
-
if (el.actions.length > 0) flags.push(`actions: ${el.actions.join(',')}`);
|
|
40
|
-
const flagStr = flags.length > 0 ? ` [${flags.join(', ')}]` : '';
|
|
41
|
-
|
|
42
|
-
parts.push(`${pad}[${el.role}] ${label} (id: ${el.id})${flagStr}`);
|
|
43
|
-
|
|
44
|
-
for (const child of el.children) {
|
|
45
|
-
parts.push(formatAXTree(child, indent + 1));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return parts.join('\n');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
18
|
const tunnelIdParam: TunnelToolParameter = {
|
|
52
19
|
type: 'string',
|
|
53
20
|
description: 'Tunnel connection ID (auto-discovered if omitted)',
|
|
54
21
|
required: false,
|
|
55
22
|
};
|
|
56
23
|
|
|
24
|
+
function stringifyResult(result: unknown): string {
|
|
25
|
+
if (typeof result === 'string') return result;
|
|
26
|
+
return JSON.stringify(result, null, 2);
|
|
27
|
+
}
|
|
28
|
+
|
|
57
29
|
export function createTunnelTools(client: TunnelClient): TunnelToolDefinition[] {
|
|
58
30
|
return [
|
|
59
31
|
{
|
|
60
32
|
name: 'tunnel_status',
|
|
61
|
-
description: `Check the status of all Agent Tunnel connections to
|
|
33
|
+
description: `Check the status of all Agent Tunnel connections to local computers. Lists every registered machine with live/offline status, capabilities, and machine info.`,
|
|
62
34
|
parameters: {},
|
|
63
35
|
async execute() {
|
|
64
36
|
const connections = (await client.getConnections()) as Array<Record<string, unknown>>;
|
|
65
37
|
|
|
66
38
|
if (connections.length === 0) {
|
|
67
|
-
return 'No tunnel connections found.
|
|
39
|
+
return 'No tunnel connections found. Connect this computer from the Kortix desktop app or run the tunnel connect command on another computer.';
|
|
68
40
|
}
|
|
69
41
|
|
|
70
42
|
const sections: string[] = [];
|
|
@@ -77,7 +49,7 @@ export function createTunnelTools(client: TunnelClient): TunnelToolDefinition[]
|
|
|
77
49
|
const machineInfo = (data.machineInfo as Record<string, unknown>) || {};
|
|
78
50
|
|
|
79
51
|
const lines = [
|
|
80
|
-
`===
|
|
52
|
+
`=== Computer: ${data.name || 'Unnamed'} — ${status} ===`,
|
|
81
53
|
`ID: ${data.tunnelId}`,
|
|
82
54
|
`Capabilities: ${capabilities.length > 0 ? capabilities.join(', ') : '(none registered)'}`,
|
|
83
55
|
];
|
|
@@ -90,7 +62,7 @@ export function createTunnelTools(client: TunnelClient): TunnelToolDefinition[]
|
|
|
90
62
|
}
|
|
91
63
|
|
|
92
64
|
if (!hasOnline) {
|
|
93
|
-
sections.push('\nNo tunnel is currently online.
|
|
65
|
+
sections.push('\nNo tunnel is currently online. Connect this computer from the Kortix desktop app or run the tunnel connect command on the target computer.');
|
|
94
66
|
}
|
|
95
67
|
|
|
96
68
|
return sections.join('\n\n');
|
|
@@ -98,10 +70,10 @@ export function createTunnelTools(client: TunnelClient): TunnelToolDefinition[]
|
|
|
98
70
|
},
|
|
99
71
|
{
|
|
100
72
|
name: 'tunnel_fs_read',
|
|
101
|
-
description: `Read a file from
|
|
73
|
+
description: `Read a file from a connected computer via Agent Tunnel. Requires filesystem permission.`,
|
|
102
74
|
parameters: {
|
|
103
75
|
tunnel_id: tunnelIdParam,
|
|
104
|
-
path: { type: 'string', description: 'Absolute path to the file on the
|
|
76
|
+
path: { type: 'string', description: 'Absolute path to the file on the connected computer', required: true },
|
|
105
77
|
encoding: { type: 'string', description: 'File encoding (default: utf-8)', required: false },
|
|
106
78
|
},
|
|
107
79
|
async execute(args) {
|
|
@@ -116,10 +88,10 @@ export function createTunnelTools(client: TunnelClient): TunnelToolDefinition[]
|
|
|
116
88
|
},
|
|
117
89
|
{
|
|
118
90
|
name: 'tunnel_fs_write',
|
|
119
|
-
description: `Write a file to
|
|
91
|
+
description: `Write a file to a connected computer via Agent Tunnel. Creates parent directories if needed. Requires filesystem write permission.`,
|
|
120
92
|
parameters: {
|
|
121
93
|
tunnel_id: tunnelIdParam,
|
|
122
|
-
path: { type: 'string', description: 'Absolute path for the file on the
|
|
94
|
+
path: { type: 'string', description: 'Absolute path for the file on the connected computer', required: true },
|
|
123
95
|
content: { type: 'string', description: 'File content to write', required: true },
|
|
124
96
|
encoding: { type: 'string', description: 'File encoding (default: utf-8)', required: false },
|
|
125
97
|
},
|
|
@@ -136,10 +108,10 @@ export function createTunnelTools(client: TunnelClient): TunnelToolDefinition[]
|
|
|
136
108
|
},
|
|
137
109
|
{
|
|
138
110
|
name: 'tunnel_fs_list',
|
|
139
|
-
description: `List directory contents on
|
|
111
|
+
description: `List directory contents on a connected computer via Agent Tunnel. Requires filesystem permission.`,
|
|
140
112
|
parameters: {
|
|
141
113
|
tunnel_id: tunnelIdParam,
|
|
142
|
-
path: { type: 'string', description: 'Absolute path to the directory on the
|
|
114
|
+
path: { type: 'string', description: 'Absolute path to the directory on the connected computer', required: true },
|
|
143
115
|
recursive: { type: 'boolean', description: 'Include subdirectory contents (default: false)', required: false },
|
|
144
116
|
},
|
|
145
117
|
async execute(args) {
|
|
@@ -162,10 +134,10 @@ export function createTunnelTools(client: TunnelClient): TunnelToolDefinition[]
|
|
|
162
134
|
},
|
|
163
135
|
{
|
|
164
136
|
name: 'tunnel_shell_exec',
|
|
165
|
-
description: `Execute a command on
|
|
137
|
+
description: `Execute a command on a connected computer via Agent Tunnel. Commands are executed without shell interpolation (array args) for security. Requires shell permission.`,
|
|
166
138
|
parameters: {
|
|
167
139
|
tunnel_id: tunnelIdParam,
|
|
168
|
-
command: { type: 'string', description: "Command executable name (e.g
|
|
140
|
+
command: { type: 'string', description: "Command executable name (e.g. 'ls', 'git', 'python')", required: true },
|
|
169
141
|
args: { type: 'array', description: 'Command arguments as separate strings (no shell interpolation)', required: false, items: { type: 'string' } },
|
|
170
142
|
cwd: { type: 'string', description: 'Working directory for the command', required: false },
|
|
171
143
|
timeout: { type: 'number', description: 'Timeout in milliseconds (default: 30000, max: 120000)', required: false },
|
|
@@ -202,401 +174,168 @@ export function createTunnelTools(client: TunnelClient): TunnelToolDefinition[]
|
|
|
202
174
|
},
|
|
203
175
|
},
|
|
204
176
|
{
|
|
205
|
-
name: '
|
|
206
|
-
description: `
|
|
207
|
-
parameters: {
|
|
208
|
-
tunnel_id: tunnelIdParam,
|
|
209
|
-
x: { type: 'number', description: 'Region X coordinate', required: false },
|
|
210
|
-
y: { type: 'number', description: 'Region Y coordinate', required: false },
|
|
211
|
-
width: { type: 'number', description: 'Region width', required: false },
|
|
212
|
-
height: { type: 'number', description: 'Region height', required: false },
|
|
213
|
-
windowId: { type: 'number', description: 'Capture a specific window by ID', required: false },
|
|
214
|
-
},
|
|
215
|
-
async execute(args) {
|
|
216
|
-
const params: Record<string, unknown> = {};
|
|
217
|
-
if (args.x !== undefined && args.y !== undefined && args.width !== undefined && args.height !== undefined) {
|
|
218
|
-
params.region = { x: args.x, y: args.y, width: args.width, height: args.height };
|
|
219
|
-
}
|
|
220
|
-
if (args.windowId !== undefined) params.windowId = args.windowId;
|
|
221
|
-
|
|
222
|
-
const result = await client.rpcWithPermissionFlow('desktop.screenshot', params);
|
|
223
|
-
if (typeof result === 'string') return result;
|
|
224
|
-
|
|
225
|
-
const data = result as { image: string; width: number; height: number; format?: string };
|
|
226
|
-
const format = data.format || 'png';
|
|
227
|
-
const sizeKB = Math.round(data.image.length * 0.75 / 1024);
|
|
228
|
-
const path = saveImage(data.image, format);
|
|
229
|
-
return `Screenshot saved: ${path}\nDimensions: ${data.width}x${data.height} ${format.toUpperCase()} (${sizeKB}KB)\n\nUse the Read tool to view this image.`;
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
name: 'tunnel_click',
|
|
234
|
-
description: `Click at a specific screen coordinate on the user's machine via Agent Tunnel.`,
|
|
235
|
-
parameters: {
|
|
236
|
-
tunnel_id: tunnelIdParam,
|
|
237
|
-
x: { type: 'number', description: 'X coordinate to click', required: true },
|
|
238
|
-
y: { type: 'number', description: 'Y coordinate to click', required: true },
|
|
239
|
-
button: { type: 'string', description: 'Mouse button (default: left)', required: false, enum: ['left', 'right', 'middle'] },
|
|
240
|
-
clicks: { type: 'number', description: 'Number of clicks (default: 1, use 2 for double-click)', required: false },
|
|
241
|
-
modifiers: { type: 'array', description: 'Modifier keys held during click: cmd, shift, alt, ctrl', required: false, items: { type: 'string' } },
|
|
242
|
-
},
|
|
243
|
-
async execute(args) {
|
|
244
|
-
const result = await client.rpcWithPermissionFlow('desktop.mouse.click', {
|
|
245
|
-
x: args.x, y: args.y, button: args.button, clicks: args.clicks, modifiers: args.modifiers,
|
|
246
|
-
});
|
|
247
|
-
if (typeof result === 'string') return result;
|
|
248
|
-
return `Clicked at (${args.x}, ${args.y}) [${args.button || 'left'}]${args.clicks && (args.clicks as number) > 1 ? ` x${args.clicks}` : ''}`;
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
name: 'tunnel_mouse_move',
|
|
253
|
-
description: `Move the mouse cursor to a specific position on the user's screen via Agent Tunnel.`,
|
|
254
|
-
parameters: {
|
|
255
|
-
tunnel_id: tunnelIdParam,
|
|
256
|
-
x: { type: 'number', description: 'Target X coordinate', required: true },
|
|
257
|
-
y: { type: 'number', description: 'Target Y coordinate', required: true },
|
|
258
|
-
},
|
|
259
|
-
async execute(args) {
|
|
260
|
-
const result = await client.rpcWithPermissionFlow('desktop.mouse.move', { x: args.x, y: args.y });
|
|
261
|
-
if (typeof result === 'string') return result;
|
|
262
|
-
return `Mouse moved to (${args.x}, ${args.y})`;
|
|
263
|
-
},
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
name: 'tunnel_mouse_drag',
|
|
267
|
-
description: `Drag from one point to another on the user's screen via Agent Tunnel.`,
|
|
268
|
-
parameters: {
|
|
269
|
-
tunnel_id: tunnelIdParam,
|
|
270
|
-
fromX: { type: 'number', description: 'Start X coordinate', required: true },
|
|
271
|
-
fromY: { type: 'number', description: 'Start Y coordinate', required: true },
|
|
272
|
-
toX: { type: 'number', description: 'End X coordinate', required: true },
|
|
273
|
-
toY: { type: 'number', description: 'End Y coordinate', required: true },
|
|
274
|
-
button: { type: 'string', description: 'Mouse button (default: left)', required: false, enum: ['left', 'right'] },
|
|
275
|
-
},
|
|
276
|
-
async execute(args) {
|
|
277
|
-
const result = await client.rpcWithPermissionFlow('desktop.mouse.drag', {
|
|
278
|
-
fromX: args.fromX, fromY: args.fromY, toX: args.toX, toY: args.toY, button: args.button,
|
|
279
|
-
});
|
|
280
|
-
if (typeof result === 'string') return result;
|
|
281
|
-
return `Dragged from (${args.fromX}, ${args.fromY}) to (${args.toX}, ${args.toY})`;
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
name: 'tunnel_mouse_scroll',
|
|
286
|
-
description: `Scroll the mouse wheel at a specific position on the user's screen via Agent Tunnel.`,
|
|
287
|
-
parameters: {
|
|
288
|
-
tunnel_id: tunnelIdParam,
|
|
289
|
-
x: { type: 'number', description: 'X coordinate to scroll at', required: true },
|
|
290
|
-
y: { type: 'number', description: 'Y coordinate to scroll at', required: true },
|
|
291
|
-
deltaX: { type: 'number', description: 'Horizontal scroll amount (positive=right)', required: false },
|
|
292
|
-
deltaY: { type: 'number', description: 'Vertical scroll amount (positive=down)', required: false },
|
|
293
|
-
},
|
|
294
|
-
async execute(args) {
|
|
295
|
-
const result = await client.rpcWithPermissionFlow('desktop.mouse.scroll', {
|
|
296
|
-
x: args.x, y: args.y, deltaX: args.deltaX, deltaY: args.deltaY,
|
|
297
|
-
});
|
|
298
|
-
if (typeof result === 'string') return result;
|
|
299
|
-
return `Scrolled at (${args.x}, ${args.y}) [dx=${args.deltaX || 0}, dy=${args.deltaY || 0}]`;
|
|
300
|
-
},
|
|
301
|
-
},
|
|
302
|
-
|
|
303
|
-
{
|
|
304
|
-
name: 'tunnel_type',
|
|
305
|
-
description: `Type text into the currently focused application on the user's machine via Agent Tunnel.`,
|
|
306
|
-
parameters: {
|
|
307
|
-
tunnel_id: tunnelIdParam,
|
|
308
|
-
text: { type: 'string', description: 'Text to type', required: true },
|
|
309
|
-
delay: { type: 'number', description: 'Delay between characters in ms', required: false },
|
|
310
|
-
},
|
|
311
|
-
async execute(args) {
|
|
312
|
-
const result = await client.rpcWithPermissionFlow('desktop.keyboard.type', {
|
|
313
|
-
text: args.text, delay: args.delay,
|
|
314
|
-
});
|
|
315
|
-
if (typeof result === 'string') return result;
|
|
316
|
-
return `Typed ${(args.text as string).length} characters`;
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
name: 'tunnel_key',
|
|
321
|
-
description: `Press a key combination on the user's machine via Agent Tunnel. Use for keyboard shortcuts like cmd+s, ctrl+c, enter, tab, etc.`,
|
|
177
|
+
name: 'tunnel_cua_ensure',
|
|
178
|
+
description: `Ensure CUA Driver is installed on the connected computer and return its local binary path/version. Requires desktop computer_use permission.`,
|
|
322
179
|
parameters: {
|
|
323
180
|
tunnel_id: tunnelIdParam,
|
|
324
|
-
keys: { type: 'array', description: "Keys to press simultaneously. Examples: ['cmd', 's'] for save, ['enter'] for enter", required: true, items: { type: 'string' } },
|
|
325
181
|
},
|
|
326
|
-
async execute(
|
|
327
|
-
|
|
328
|
-
if (typeof result === 'string') return result;
|
|
329
|
-
return `Pressed: ${(args.keys as string[]).join('+')}`;
|
|
182
|
+
async execute() {
|
|
183
|
+
return stringifyResult(await client.rpcWithPermissionFlow('desktop.cua.ensure', {}));
|
|
330
184
|
},
|
|
331
185
|
},
|
|
332
|
-
|
|
333
186
|
{
|
|
334
|
-
name: '
|
|
335
|
-
description: `
|
|
187
|
+
name: 'tunnel_cua_start_daemon',
|
|
188
|
+
description: `Start the CUA Driver daemon/background service on the connected computer so CUA sessions and element indices remain stable.`,
|
|
336
189
|
parameters: {
|
|
337
190
|
tunnel_id: tunnelIdParam,
|
|
338
191
|
},
|
|
339
192
|
async execute() {
|
|
340
|
-
|
|
341
|
-
if (typeof result === 'string') return result;
|
|
342
|
-
|
|
343
|
-
const data = result as { windows: Array<{ id: number; app: string; title: string; bounds: { x: number; y: number; width: number; height: number }; minimized: boolean }> };
|
|
344
|
-
if (data.windows.length === 0) return 'No windows found';
|
|
345
|
-
|
|
346
|
-
const lines = [`=== Windows (${data.windows.length}) ===`];
|
|
347
|
-
for (const w of data.windows) {
|
|
348
|
-
const min = w.minimized ? ' [minimized]' : '';
|
|
349
|
-
lines.push(` #${w.id} | ${w.app} — "${w.title}" | ${w.bounds.x},${w.bounds.y} ${w.bounds.width}x${w.bounds.height}${min}`);
|
|
350
|
-
}
|
|
351
|
-
return lines.join('\n');
|
|
193
|
+
return stringifyResult(await client.rpcWithPermissionFlow('desktop.cua.start_daemon', {}));
|
|
352
194
|
},
|
|
353
195
|
},
|
|
354
196
|
{
|
|
355
|
-
name: '
|
|
356
|
-
description: `
|
|
197
|
+
name: 'tunnel_cua_status',
|
|
198
|
+
description: `Read CUA Driver daemon status from the connected computer.`,
|
|
357
199
|
parameters: {
|
|
358
200
|
tunnel_id: tunnelIdParam,
|
|
359
|
-
windowId: { type: 'number', description: 'Window ID from tunnel_window_list', required: true },
|
|
360
201
|
},
|
|
361
|
-
async execute(
|
|
362
|
-
|
|
363
|
-
if (typeof result === 'string') return result;
|
|
364
|
-
return `Window #${args.windowId} focused`;
|
|
365
|
-
},
|
|
366
|
-
},
|
|
367
|
-
|
|
368
|
-
{
|
|
369
|
-
name: 'tunnel_app_launch',
|
|
370
|
-
description: `Launch an application on the user's machine via Agent Tunnel.`,
|
|
371
|
-
parameters: {
|
|
372
|
-
tunnel_id: tunnelIdParam,
|
|
373
|
-
app: { type: 'string', description: 'Application name to launch', required: true },
|
|
374
|
-
},
|
|
375
|
-
async execute(args) {
|
|
376
|
-
const result = await client.rpcWithPermissionFlow('desktop.app.launch', { app: args.app });
|
|
377
|
-
if (typeof result === 'string') return result;
|
|
378
|
-
return `Launched: ${args.app}`;
|
|
202
|
+
async execute() {
|
|
203
|
+
return stringifyResult(await client.rpcWithPermissionFlow('desktop.cua.status', {}));
|
|
379
204
|
},
|
|
380
205
|
},
|
|
381
206
|
{
|
|
382
|
-
name: '
|
|
383
|
-
description: `
|
|
207
|
+
name: 'tunnel_cua_version',
|
|
208
|
+
description: `Read the installed CUA Driver version on the connected computer.`,
|
|
384
209
|
parameters: {
|
|
385
210
|
tunnel_id: tunnelIdParam,
|
|
386
|
-
app: { type: 'string', description: 'Application name to quit', required: true },
|
|
387
211
|
},
|
|
388
|
-
async execute(
|
|
389
|
-
|
|
390
|
-
if (typeof result === 'string') return result;
|
|
391
|
-
return `Quit: ${args.app}`;
|
|
212
|
+
async execute() {
|
|
213
|
+
return stringifyResult(await client.rpcWithPermissionFlow('desktop.cua.version', {}));
|
|
392
214
|
},
|
|
393
215
|
},
|
|
394
|
-
|
|
395
216
|
{
|
|
396
|
-
name: '
|
|
397
|
-
description: `
|
|
217
|
+
name: 'tunnel_cua_list_tools',
|
|
218
|
+
description: `List every CUA Driver tool exposed by the connected computer's installed driver.`,
|
|
398
219
|
parameters: {
|
|
399
220
|
tunnel_id: tunnelIdParam,
|
|
400
221
|
},
|
|
401
222
|
async execute() {
|
|
402
|
-
|
|
403
|
-
if (typeof result === 'string') return result;
|
|
404
|
-
const data = result as { text: string };
|
|
405
|
-
if (!data.text) return '(clipboard is empty)';
|
|
406
|
-
return `=== Clipboard ===\n${data.text}`;
|
|
223
|
+
return stringifyResult(await client.rpcWithPermissionFlow('desktop.cua.list_tools', {}));
|
|
407
224
|
},
|
|
408
225
|
},
|
|
409
226
|
{
|
|
410
|
-
name: '
|
|
411
|
-
description: `
|
|
227
|
+
name: 'tunnel_cua_describe',
|
|
228
|
+
description: `Describe one CUA Driver tool before calling it.`,
|
|
412
229
|
parameters: {
|
|
413
230
|
tunnel_id: tunnelIdParam,
|
|
414
|
-
|
|
231
|
+
tool: { type: 'string', description: 'CUA Driver tool name', required: true },
|
|
415
232
|
},
|
|
416
233
|
async execute(args) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
234
|
+
return stringifyResult(await client.rpcWithPermissionFlow('desktop.cua.describe', {
|
|
235
|
+
tool: args.tool,
|
|
236
|
+
}));
|
|
420
237
|
},
|
|
421
238
|
},
|
|
422
|
-
|
|
423
239
|
{
|
|
424
|
-
name: '
|
|
425
|
-
description: `
|
|
240
|
+
name: 'tunnel_cua_list_apps',
|
|
241
|
+
description: `List installed and running desktop apps through CUA Driver, including bundle IDs and PIDs.`,
|
|
426
242
|
parameters: {
|
|
427
243
|
tunnel_id: tunnelIdParam,
|
|
428
244
|
},
|
|
429
245
|
async execute() {
|
|
430
|
-
|
|
431
|
-
if (typeof result === 'string') return result;
|
|
432
|
-
const data = result as { width: number; height: number; scaleFactor: number };
|
|
433
|
-
return `Screen: ${data.width}x${data.height} @ ${data.scaleFactor}x scale`;
|
|
246
|
+
return stringifyResult(await client.rpcWithPermissionFlow('desktop.cua.list_apps', {}));
|
|
434
247
|
},
|
|
435
248
|
},
|
|
436
|
-
|
|
437
249
|
{
|
|
438
|
-
name: '
|
|
439
|
-
description: `
|
|
250
|
+
name: 'tunnel_cua_list_windows',
|
|
251
|
+
description: `List top-level local desktop windows through CUA Driver. Use this before tunnel_cua_get_window_state.`,
|
|
440
252
|
parameters: {
|
|
441
253
|
tunnel_id: tunnelIdParam,
|
|
442
|
-
|
|
254
|
+
pid: { type: 'number', description: 'Optional process ID filter', required: false },
|
|
255
|
+
on_screen_only: { type: 'boolean', description: 'Only include windows on the current Space', required: false },
|
|
443
256
|
},
|
|
444
257
|
async execute(args) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const format = data.format || 'png';
|
|
450
|
-
const sizeKB = Math.round(data.image.length * 0.75 / 1024);
|
|
451
|
-
const path = saveImage(data.image, format);
|
|
452
|
-
return `Cursor area saved: ${path}\nDimensions: ${data.width}x${data.height} ${format.toUpperCase()} (${sizeKB}KB)\n\nUse the Read tool to view this image.`;
|
|
258
|
+
return stringifyResult(await client.rpcWithPermissionFlow('desktop.cua.list_windows', {
|
|
259
|
+
pid: args.pid,
|
|
260
|
+
on_screen_only: args.on_screen_only,
|
|
261
|
+
}));
|
|
453
262
|
},
|
|
454
263
|
},
|
|
455
|
-
|
|
456
264
|
{
|
|
457
|
-
name: '
|
|
458
|
-
description: `Get
|
|
265
|
+
name: 'tunnel_cua_get_window_state',
|
|
266
|
+
description: `Get a CUA window snapshot: screenshot plus accessibility tree markdown with element_index values. Call this once per turn before element-indexed actions.`,
|
|
459
267
|
parameters: {
|
|
460
268
|
tunnel_id: tunnelIdParam,
|
|
461
|
-
pid: { type: 'number', description: '
|
|
462
|
-
|
|
463
|
-
|
|
269
|
+
pid: { type: 'number', description: 'Target process ID', required: true },
|
|
270
|
+
window_id: { type: 'number', description: 'Target window ID from tunnel_cua_list_windows', required: true },
|
|
271
|
+
query: { type: 'string', description: 'Optional filter for tree markdown', required: false },
|
|
272
|
+
capture_mode: { type: 'string', description: 'som, vision, or ax', required: false, enum: ['som', 'vision', 'ax'] },
|
|
464
273
|
},
|
|
465
274
|
async execute(args) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if (typeof result === 'string') return result;
|
|
473
|
-
|
|
474
|
-
const data = result as { root: AXElement; elementCount: number };
|
|
475
|
-
if (!data.root) return 'No accessibility tree available';
|
|
476
|
-
|
|
477
|
-
const tree = formatAXTree(data.root);
|
|
478
|
-
return `=== Accessibility Tree (${data.elementCount} elements) ===\n${tree}`;
|
|
275
|
+
return stringifyResult(await client.rpcWithPermissionFlow('desktop.cua.get_window_state', {
|
|
276
|
+
pid: args.pid,
|
|
277
|
+
window_id: args.window_id,
|
|
278
|
+
query: args.query,
|
|
279
|
+
capture_mode: args.capture_mode,
|
|
280
|
+
}));
|
|
479
281
|
},
|
|
480
282
|
},
|
|
481
283
|
{
|
|
482
|
-
name: '
|
|
483
|
-
description: `
|
|
284
|
+
name: 'tunnel_cua_click',
|
|
285
|
+
description: `Click with CUA Driver by element_index from the last window state, or by window-local screenshot coordinates.`,
|
|
484
286
|
parameters: {
|
|
485
287
|
tunnel_id: tunnelIdParam,
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
288
|
+
pid: { type: 'number', description: 'Target process ID', required: true },
|
|
289
|
+
window_id: { type: 'number', description: 'Target window ID; required for element_index', required: false },
|
|
290
|
+
element_index: { type: 'number', description: 'Element index from tunnel_cua_get_window_state', required: false },
|
|
291
|
+
x: { type: 'number', description: 'Window-local screenshot X coordinate', required: false },
|
|
292
|
+
y: { type: 'number', description: 'Window-local screenshot Y coordinate', required: false },
|
|
293
|
+
action: { type: 'string', description: 'AX action: press, show_menu, pick, confirm, cancel, open', required: false },
|
|
489
294
|
},
|
|
490
295
|
async execute(args) {
|
|
491
|
-
|
|
492
|
-
elementId: args.elementId, action: args.action, pid: args.pid,
|
|
493
|
-
});
|
|
494
|
-
if (typeof result === 'string') return result;
|
|
495
|
-
|
|
496
|
-
const data = result as {
|
|
497
|
-
ok: boolean; action: string; elementId: string;
|
|
498
|
-
before: { focused: boolean; value: string };
|
|
499
|
-
after: { focused: boolean; value: string };
|
|
500
|
-
stateChanged: boolean; role: string; title: string;
|
|
501
|
-
};
|
|
502
|
-
|
|
503
|
-
const lines = [`Action "${data.action}" on [${data.role}] "${data.title}" (${data.elementId})`];
|
|
504
|
-
lines.push(`State changed: ${data.stateChanged ? 'YES' : 'NO'}`);
|
|
505
|
-
lines.push(`Before: focused=${data.before.focused}, value="${data.before.value}"`);
|
|
506
|
-
lines.push(`After: focused=${data.after.focused}, value="${data.after.value}"`);
|
|
507
|
-
if (!data.stateChanged) {
|
|
508
|
-
lines.push('WARNING: No state change detected. The action may not have had any effect.');
|
|
509
|
-
}
|
|
510
|
-
return lines.join('\n');
|
|
296
|
+
return stringifyResult(await client.rpcWithPermissionFlow('desktop.cua.click', args));
|
|
511
297
|
},
|
|
512
298
|
},
|
|
513
299
|
{
|
|
514
|
-
name: '
|
|
515
|
-
description: `
|
|
300
|
+
name: 'tunnel_cua_type_text',
|
|
301
|
+
description: `Type text with CUA Driver into a target PID, optionally directed to an element_index from the last window state.`,
|
|
516
302
|
parameters: {
|
|
517
303
|
tunnel_id: tunnelIdParam,
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
304
|
+
pid: { type: 'number', description: 'Target process ID', required: true },
|
|
305
|
+
text: { type: 'string', description: 'Text to type', required: true },
|
|
306
|
+
window_id: { type: 'number', description: 'Target window ID; required for element_index', required: false },
|
|
307
|
+
element_index: { type: 'number', description: 'Element index from tunnel_cua_get_window_state', required: false },
|
|
521
308
|
},
|
|
522
309
|
async execute(args) {
|
|
523
|
-
|
|
524
|
-
elementId: args.elementId, value: args.value, pid: args.pid,
|
|
525
|
-
});
|
|
526
|
-
if (typeof result === 'string') return result;
|
|
527
|
-
|
|
528
|
-
const data = result as {
|
|
529
|
-
ok: boolean; elementId: string;
|
|
530
|
-
requestedValue: string; actualValue: string; error?: string;
|
|
531
|
-
};
|
|
532
|
-
|
|
533
|
-
if (data.ok) {
|
|
534
|
-
return `Value set successfully on ${data.elementId}\nRequested: "${data.requestedValue}"\nVerified: "${data.actualValue}"`;
|
|
535
|
-
} else {
|
|
536
|
-
return `FAILED to set value on ${data.elementId}\nRequested: "${data.requestedValue}"\nActual: "${data.actualValue}"\nError: ${data.error || 'unknown'}`;
|
|
537
|
-
}
|
|
310
|
+
return stringifyResult(await client.rpcWithPermissionFlow('desktop.cua.type_text', args));
|
|
538
311
|
},
|
|
539
312
|
},
|
|
540
313
|
{
|
|
541
|
-
name: '
|
|
542
|
-
description: `
|
|
314
|
+
name: 'tunnel_cua_hotkey',
|
|
315
|
+
description: `Press a CUA Driver hotkey against a target PID, for example ["cmd","c"].`,
|
|
543
316
|
parameters: {
|
|
544
317
|
tunnel_id: tunnelIdParam,
|
|
545
|
-
|
|
546
|
-
|
|
318
|
+
pid: { type: 'number', description: 'Target process ID', required: true },
|
|
319
|
+
keys: { type: 'array', description: 'Modifier(s) and one key, e.g. ["cmd","c"]', required: true, items: { type: 'string' } },
|
|
320
|
+
window_id: { type: 'number', description: 'Optional target window ID for native menu dispatch', required: false },
|
|
547
321
|
},
|
|
548
322
|
async execute(args) {
|
|
549
|
-
|
|
550
|
-
elementId: args.elementId, pid: args.pid,
|
|
551
|
-
});
|
|
552
|
-
if (typeof result === 'string') return result;
|
|
553
|
-
|
|
554
|
-
const data = result as {
|
|
555
|
-
ok: boolean; elementId: string; role: string; title: string;
|
|
556
|
-
before: { focused: boolean }; after: { focused: boolean }; error?: string;
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
if (data.ok) {
|
|
560
|
-
return `Focused [${data.role}] "${data.title}" (${data.elementId})\nBefore: focused=${data.before.focused}\nAfter: focused=${data.after.focused}`;
|
|
561
|
-
} else {
|
|
562
|
-
return `FAILED to focus [${data.role}] "${data.title}" (${data.elementId})\nError: ${data.error || 'unknown'}\nBefore: focused=${data.before.focused}\nAfter: focused=${data.after.focused}`;
|
|
563
|
-
}
|
|
323
|
+
return stringifyResult(await client.rpcWithPermissionFlow('desktop.cua.hotkey', args));
|
|
564
324
|
},
|
|
565
325
|
},
|
|
566
326
|
{
|
|
567
|
-
name: '
|
|
568
|
-
description: `
|
|
327
|
+
name: 'tunnel_cua_call',
|
|
328
|
+
description: `Call any CUA Driver tool by name with raw JSON args. Prefer the specific tunnel_cua_* tools when available.`,
|
|
569
329
|
parameters: {
|
|
570
330
|
tunnel_id: tunnelIdParam,
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
pid: { type: 'number', description: 'Process ID of the target application', required: false },
|
|
574
|
-
maxResults: { type: 'number', description: 'Maximum results to return (default: 20)', required: false },
|
|
331
|
+
tool: { type: 'string', description: 'CUA Driver tool name', required: true },
|
|
332
|
+
args: { type: 'object', description: 'Raw CUA Driver tool arguments', required: false },
|
|
575
333
|
},
|
|
576
334
|
async execute(args) {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
const result = await client.rpcWithPermissionFlow('desktop.ax.search', params);
|
|
583
|
-
if (typeof result === 'string') return result;
|
|
584
|
-
|
|
585
|
-
const data = result as { elements: AXElement[] };
|
|
586
|
-
if (!data.elements || data.elements.length === 0) return `No elements found matching "${args.query}"`;
|
|
587
|
-
|
|
588
|
-
const lines = [`=== AX Search: "${args.query}" (${data.elements.length} results) ===`];
|
|
589
|
-
for (const el of data.elements) {
|
|
590
|
-
const label = el.title || el.value || el.description || '(unnamed)';
|
|
591
|
-
const flags: string[] = [];
|
|
592
|
-
if (!el.enabled) flags.push('disabled');
|
|
593
|
-
if (el.focused) flags.push('focused');
|
|
594
|
-
if (el.actions.length > 0) flags.push(`actions: ${el.actions.join(',')}`);
|
|
595
|
-
const flagStr = flags.length > 0 ? ` [${flags.join(', ')}]` : '';
|
|
596
|
-
const b = el.bounds;
|
|
597
|
-
lines.push(` [${el.role}] ${label} (id: ${el.id}) @ ${b.x},${b.y} ${b.width}x${b.height}${flagStr}`);
|
|
598
|
-
}
|
|
599
|
-
return lines.join('\n');
|
|
335
|
+
return stringifyResult(await client.rpcWithPermissionFlow('desktop.cua.call', {
|
|
336
|
+
tool: args.tool,
|
|
337
|
+
args: args.args || {},
|
|
338
|
+
}));
|
|
600
339
|
},
|
|
601
340
|
},
|
|
602
341
|
];
|