@kortix/agent-tunnel 0.1.4 → 0.12.8
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 +202 -493
- 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 -539
- 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/cli.ts
CHANGED
|
@@ -1,115 +1,72 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agent Tunnel CLI
|
|
2
|
+
* Agent Tunnel CLI: filesystem, shell, and CUA Driver-backed computer control.
|
|
3
3
|
*
|
|
4
4
|
* Usage: agent-tunnel-cli <command> [args as JSON]
|
|
5
|
-
*
|
|
6
|
-
* Commands:
|
|
7
|
-
* status — list all tunnel connections
|
|
8
|
-
* fs_read '{"path":"/Users/me/file.txt"}' — read a file
|
|
9
|
-
* fs_write '{"path":"/tmp/out.txt","content":"hello"}' — write a file
|
|
10
|
-
* fs_list '{"path":"/Users/me"}' — list directory
|
|
11
|
-
* shell '{"command":"git","args":["status"]}' — run a command
|
|
12
|
-
* screenshot — take a screenshot
|
|
13
|
-
* click '{"x":100,"y":200}' — click at coordinates
|
|
14
|
-
* mouse_move '{"x":100,"y":200}' — move mouse
|
|
15
|
-
* mouse_drag '{"fromX":0,"fromY":0,"toX":100,"toY":100}' — drag
|
|
16
|
-
* mouse_scroll '{"x":500,"y":500,"deltaY":3}' — scroll
|
|
17
|
-
* type '{"text":"hello world"}' — type text
|
|
18
|
-
* key '{"keys":["cmd","s"]}' — press key combo
|
|
19
|
-
* window_list — list windows
|
|
20
|
-
* window_focus '{"windowId":123}' — focus a window
|
|
21
|
-
* app_launch '{"app":"Safari"}' — launch app
|
|
22
|
-
* app_quit '{"app":"Safari"}' — quit app
|
|
23
|
-
* clipboard_read — read clipboard
|
|
24
|
-
* clipboard_write '{"text":"copied"}' — write clipboard
|
|
25
|
-
* screen_info — get screen resolution
|
|
26
|
-
* cursor_image — screenshot around cursor
|
|
27
|
-
* ax_tree '{"pid":1234}' — accessibility tree
|
|
28
|
-
* ax_action '{"elementId":"0.3.1","action":"AXPress"}' — perform AX action
|
|
29
|
-
* ax_set_value '{"elementId":"0.3.1","value":"hello"}' — set element value
|
|
30
|
-
* ax_focus '{"elementId":"0.3.1"}' — focus element
|
|
31
|
-
* ax_search '{"query":"Submit"}' — search AX tree
|
|
32
5
|
*/
|
|
33
6
|
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import { tmpdir } from "os";
|
|
37
|
-
import { randomBytes } from "crypto";
|
|
38
|
-
import { TunnelClient, TunnelClientError } from "./tunnel-client";
|
|
39
|
-
import type { AXElement } from "./tunnel-client";
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
8
|
+
import { TunnelClient, TunnelClientError } from './tunnel-client';
|
|
40
9
|
|
|
41
|
-
|
|
10
|
+
const S6_ENV_DIR = process.env.S6_ENV_DIR || '/run/s6/container_environment';
|
|
11
|
+
const FALLBACK_API_URL = 'http://localhost:8008';
|
|
42
12
|
|
|
43
|
-
const
|
|
13
|
+
const ALL_COMMANDS = [
|
|
14
|
+
'status',
|
|
15
|
+
'fs_read',
|
|
16
|
+
'fs_write',
|
|
17
|
+
'fs_list',
|
|
18
|
+
'shell',
|
|
19
|
+
'cua_ensure',
|
|
20
|
+
'cua_start_daemon',
|
|
21
|
+
'cua_status',
|
|
22
|
+
'cua_version',
|
|
23
|
+
'cua_list_tools',
|
|
24
|
+
'cua_describe',
|
|
25
|
+
'cua_call',
|
|
26
|
+
'cua_list_apps',
|
|
27
|
+
'cua_list_windows',
|
|
28
|
+
'cua_get_window_state',
|
|
29
|
+
'cua_click',
|
|
30
|
+
'cua_type_text',
|
|
31
|
+
'cua_hotkey',
|
|
32
|
+
];
|
|
44
33
|
|
|
45
34
|
function getEnv(key: string): string | undefined {
|
|
46
35
|
try {
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
36
|
+
const value = readFileSync(`${S6_ENV_DIR}/${key}`, 'utf-8').trim();
|
|
37
|
+
if (value) return value;
|
|
49
38
|
} catch {}
|
|
50
39
|
return process.env[key];
|
|
51
40
|
}
|
|
52
41
|
|
|
53
|
-
// ── Client setup ──────────────────────────────────────────────────────────
|
|
54
|
-
|
|
55
|
-
const FALLBACK_API_URL = "http://localhost:8008";
|
|
56
|
-
|
|
57
42
|
function getApiBase(): string {
|
|
58
|
-
const raw = getEnv(
|
|
59
|
-
const url = raw.startsWith(
|
|
60
|
-
|
|
61
|
-
// but the CLI appends its own /v1/tunnel path.
|
|
62
|
-
return url.replace(/\/+$/, "").replace(/\/v1\/router\/?$/, "");
|
|
43
|
+
const raw = getEnv('TUNNEL_API_URL') || FALLBACK_API_URL;
|
|
44
|
+
const url = raw.startsWith('http') ? raw : FALLBACK_API_URL;
|
|
45
|
+
return url.replace(/\/+$/, '').replace(/\/v1\/router\/?$/, '');
|
|
63
46
|
}
|
|
64
47
|
|
|
65
48
|
const client = new TunnelClient({
|
|
66
49
|
apiUrl: `${getApiBase()}/v1/tunnel`,
|
|
67
|
-
token: getEnv(
|
|
68
|
-
tunnelId: getEnv(
|
|
50
|
+
token: getEnv('TUNNEL_TOKEN') || '',
|
|
51
|
+
tunnelId: getEnv('TUNNEL_ID'),
|
|
69
52
|
});
|
|
70
53
|
|
|
71
|
-
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
72
|
-
|
|
73
54
|
function out(data: unknown): void {
|
|
74
55
|
console.log(JSON.stringify(data, null, 2));
|
|
75
56
|
}
|
|
76
57
|
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
mkdirSync(dir, { recursive: true });
|
|
81
|
-
const path = join(
|
|
82
|
-
dir,
|
|
83
|
-
`screenshot-${randomBytes(4).toString("hex")}.${ext}`
|
|
84
|
-
);
|
|
85
|
-
writeFileSync(path, Buffer.from(base64, "base64"));
|
|
86
|
-
return path;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function formatAXTree(el: AXElement, indent = 0): string {
|
|
90
|
-
const pad = " ".repeat(indent);
|
|
91
|
-
const parts: string[] = [];
|
|
92
|
-
|
|
93
|
-
const label = el.title || el.value || el.description || "(unnamed)";
|
|
94
|
-
const flags: string[] = [];
|
|
95
|
-
if (!el.enabled) flags.push("disabled");
|
|
96
|
-
if (el.focused) flags.push("focused");
|
|
97
|
-
if (el.actions.length > 0) flags.push(`actions: ${el.actions.join(",")}`);
|
|
98
|
-
const flagStr = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
|
|
99
|
-
|
|
100
|
-
parts.push(`${pad}[${el.role}] ${label} (id: ${el.id})${flagStr}`);
|
|
101
|
-
|
|
102
|
-
for (const child of el.children) {
|
|
103
|
-
parts.push(formatAXTree(child, indent + 1));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return parts.join("\n");
|
|
58
|
+
function fail(message: string): void {
|
|
59
|
+
out({ success: false, error: message });
|
|
60
|
+
process.exitCode = 1;
|
|
107
61
|
}
|
|
108
62
|
|
|
109
63
|
async function rpcSafe(
|
|
110
64
|
method: string,
|
|
111
|
-
params: Record<string, unknown> = {}
|
|
112
|
-
): Promise<
|
|
65
|
+
params: Record<string, unknown> = {},
|
|
66
|
+
): Promise<
|
|
67
|
+
| { result: unknown; permissionRequired: false }
|
|
68
|
+
| { result: null; permissionRequired: true; requestId: string; message: string }
|
|
69
|
+
> {
|
|
113
70
|
try {
|
|
114
71
|
const result = await client.rpc(method, params);
|
|
115
72
|
return { result, permissionRequired: false };
|
|
@@ -118,45 +75,36 @@ async function rpcSafe(
|
|
|
118
75
|
return {
|
|
119
76
|
result: null,
|
|
120
77
|
permissionRequired: true,
|
|
121
|
-
requestId: err.requestId ||
|
|
122
|
-
message: `Permission required. A permission request (${err.requestId}) has been sent to the user for approval
|
|
78
|
+
requestId: err.requestId || 'unknown',
|
|
79
|
+
message: `Permission required. A permission request (${err.requestId}) has been sent to the user for approval.`,
|
|
123
80
|
};
|
|
124
81
|
}
|
|
125
82
|
throw err;
|
|
126
83
|
}
|
|
127
84
|
}
|
|
128
85
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
params: Record<string, unknown> = {}
|
|
133
|
-
): Promise<unknown | null> {
|
|
134
|
-
const r = await rpcSafe(method, params);
|
|
135
|
-
if (r.permissionRequired) {
|
|
86
|
+
async function call(method: string, params: Record<string, unknown> = {}): Promise<unknown | null> {
|
|
87
|
+
const response = await rpcSafe(method, params);
|
|
88
|
+
if (response.permissionRequired) {
|
|
136
89
|
out({
|
|
137
90
|
success: false,
|
|
138
91
|
permissionRequired: true,
|
|
139
|
-
requestId:
|
|
140
|
-
message:
|
|
92
|
+
requestId: response.requestId,
|
|
93
|
+
message: response.message,
|
|
141
94
|
});
|
|
142
95
|
return null;
|
|
143
96
|
}
|
|
144
|
-
return
|
|
97
|
+
return response.result;
|
|
145
98
|
}
|
|
146
99
|
|
|
147
|
-
// ── Commands ──────────────────────────────────────────────────────────────
|
|
148
|
-
|
|
149
100
|
async function status() {
|
|
150
|
-
const connections = (await client.getConnections()) as Array<
|
|
151
|
-
Record<string, unknown>
|
|
152
|
-
>;
|
|
101
|
+
const connections = (await client.getConnections()) as Array<Record<string, unknown>>;
|
|
153
102
|
|
|
154
103
|
if (connections.length === 0) {
|
|
155
104
|
return out({
|
|
156
105
|
success: true,
|
|
157
106
|
connections: [],
|
|
158
|
-
message:
|
|
159
|
-
"No tunnel connections found. The user needs to set up Agent Tunnel first.",
|
|
107
|
+
message: 'No tunnel connections found. The user needs to set up Agent Tunnel first.',
|
|
160
108
|
});
|
|
161
109
|
}
|
|
162
110
|
|
|
@@ -164,9 +112,9 @@ async function status() {
|
|
|
164
112
|
const mapped = connections.map((data) => {
|
|
165
113
|
if (data.isLive) hasOnline = true;
|
|
166
114
|
return {
|
|
167
|
-
name: data.name ||
|
|
115
|
+
name: data.name || 'Unnamed',
|
|
168
116
|
tunnelId: data.tunnelId,
|
|
169
|
-
status: data.isLive ?
|
|
117
|
+
status: data.isLive ? 'ONLINE' : 'OFFLINE',
|
|
170
118
|
capabilities: (data.capabilities as string[]) || [],
|
|
171
119
|
machineInfo: data.machineInfo || {},
|
|
172
120
|
};
|
|
@@ -178,30 +126,25 @@ async function status() {
|
|
|
178
126
|
hasOnline,
|
|
179
127
|
message: hasOnline
|
|
180
128
|
? undefined
|
|
181
|
-
:
|
|
129
|
+
: 'No tunnel is currently online. Ask the user to run `npx --yes @kortix/agent-tunnel@latest connect` on their local machine.',
|
|
182
130
|
});
|
|
183
131
|
}
|
|
184
132
|
|
|
185
133
|
async function fsRead(args: Record<string, unknown>) {
|
|
186
|
-
const result = await call(
|
|
134
|
+
const result = await call('fs.read', {
|
|
187
135
|
path: args.path,
|
|
188
|
-
encoding: (args.encoding as string) ||
|
|
136
|
+
encoding: (args.encoding as string) || 'utf-8',
|
|
189
137
|
});
|
|
190
138
|
if (result === null) return;
|
|
191
139
|
const data = result as Record<string, unknown>;
|
|
192
|
-
out({
|
|
193
|
-
success: true,
|
|
194
|
-
path: data.path || args.path,
|
|
195
|
-
size: data.size,
|
|
196
|
-
content: data.content,
|
|
197
|
-
});
|
|
140
|
+
out({ success: true, path: data.path || args.path, size: data.size, content: data.content });
|
|
198
141
|
}
|
|
199
142
|
|
|
200
143
|
async function fsWrite(args: Record<string, unknown>) {
|
|
201
|
-
const result = await call(
|
|
144
|
+
const result = await call('fs.write', {
|
|
202
145
|
path: args.path,
|
|
203
146
|
content: args.content,
|
|
204
|
-
encoding: (args.encoding as string) ||
|
|
147
|
+
encoding: (args.encoding as string) || 'utf-8',
|
|
205
148
|
});
|
|
206
149
|
if (result === null) return;
|
|
207
150
|
const data = result as Record<string, unknown>;
|
|
@@ -209,30 +152,17 @@ async function fsWrite(args: Record<string, unknown>) {
|
|
|
209
152
|
}
|
|
210
153
|
|
|
211
154
|
async function fsList(args: Record<string, unknown>) {
|
|
212
|
-
const result = await call(
|
|
155
|
+
const result = await call('fs.list', {
|
|
213
156
|
path: args.path,
|
|
214
157
|
recursive: args.recursive || false,
|
|
215
158
|
});
|
|
216
159
|
if (result === null) return;
|
|
217
|
-
const data = result as {
|
|
218
|
-
|
|
219
|
-
name: string;
|
|
220
|
-
path: string;
|
|
221
|
-
isDirectory: boolean;
|
|
222
|
-
isFile: boolean;
|
|
223
|
-
}>;
|
|
224
|
-
count: number;
|
|
225
|
-
};
|
|
226
|
-
out({
|
|
227
|
-
success: true,
|
|
228
|
-
path: args.path,
|
|
229
|
-
count: data.count,
|
|
230
|
-
entries: data.entries,
|
|
231
|
-
});
|
|
160
|
+
const data = result as { entries: unknown[]; count: number };
|
|
161
|
+
out({ success: true, path: args.path, count: data.count, entries: data.entries });
|
|
232
162
|
}
|
|
233
163
|
|
|
234
164
|
async function shell(args: Record<string, unknown>) {
|
|
235
|
-
const result = await call(
|
|
165
|
+
const result = await call('shell.exec', {
|
|
236
166
|
command: args.command,
|
|
237
167
|
args: (args.args as string[]) || [],
|
|
238
168
|
cwd: args.cwd,
|
|
@@ -258,456 +188,103 @@ async function shell(args: Record<string, unknown>) {
|
|
|
258
188
|
});
|
|
259
189
|
}
|
|
260
190
|
|
|
261
|
-
async function
|
|
262
|
-
const
|
|
263
|
-
if (
|
|
264
|
-
args.x !== undefined &&
|
|
265
|
-
args.y !== undefined &&
|
|
266
|
-
args.width !== undefined &&
|
|
267
|
-
args.height !== undefined
|
|
268
|
-
) {
|
|
269
|
-
params.region = {
|
|
270
|
-
x: args.x,
|
|
271
|
-
y: args.y,
|
|
272
|
-
width: args.width,
|
|
273
|
-
height: args.height,
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
if (args.windowId !== undefined) params.windowId = args.windowId;
|
|
277
|
-
|
|
278
|
-
const result = await call("desktop.screenshot", params);
|
|
279
|
-
if (result === null) return;
|
|
280
|
-
const data = result as {
|
|
281
|
-
image: string;
|
|
282
|
-
width: number;
|
|
283
|
-
height: number;
|
|
284
|
-
format?: string;
|
|
285
|
-
};
|
|
286
|
-
const format = data.format || "png";
|
|
287
|
-
const sizeKB = Math.round((data.image.length * 0.75) / 1024);
|
|
288
|
-
const path = saveImage(data.image, format);
|
|
289
|
-
out({
|
|
290
|
-
success: true,
|
|
291
|
-
path,
|
|
292
|
-
width: data.width,
|
|
293
|
-
height: data.height,
|
|
294
|
-
format,
|
|
295
|
-
sizeKB,
|
|
296
|
-
message: `Screenshot saved: ${path} (${data.width}x${data.height} ${format.toUpperCase()}, ${sizeKB}KB). Use the Read tool to view.`,
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
async function click(args: Record<string, unknown>) {
|
|
301
|
-
const result = await call("desktop.mouse.click", {
|
|
302
|
-
x: args.x,
|
|
303
|
-
y: args.y,
|
|
304
|
-
button: args.button,
|
|
305
|
-
clicks: args.clicks,
|
|
306
|
-
modifiers: args.modifiers,
|
|
307
|
-
});
|
|
308
|
-
if (result === null) return;
|
|
309
|
-
out({
|
|
310
|
-
success: true,
|
|
311
|
-
x: args.x,
|
|
312
|
-
y: args.y,
|
|
313
|
-
button: args.button || "left",
|
|
314
|
-
clicks: args.clicks || 1,
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
async function mouseMove(args: Record<string, unknown>) {
|
|
319
|
-
const result = await call("desktop.mouse.move", { x: args.x, y: args.y });
|
|
320
|
-
if (result === null) return;
|
|
321
|
-
out({ success: true, x: args.x, y: args.y });
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
async function mouseDrag(args: Record<string, unknown>) {
|
|
325
|
-
const result = await call("desktop.mouse.drag", {
|
|
326
|
-
fromX: args.fromX,
|
|
327
|
-
fromY: args.fromY,
|
|
328
|
-
toX: args.toX,
|
|
329
|
-
toY: args.toY,
|
|
330
|
-
button: args.button,
|
|
331
|
-
});
|
|
332
|
-
if (result === null) return;
|
|
333
|
-
out({
|
|
334
|
-
success: true,
|
|
335
|
-
fromX: args.fromX,
|
|
336
|
-
fromY: args.fromY,
|
|
337
|
-
toX: args.toX,
|
|
338
|
-
toY: args.toY,
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
async function mouseScroll(args: Record<string, unknown>) {
|
|
343
|
-
const result = await call("desktop.mouse.scroll", {
|
|
344
|
-
x: args.x,
|
|
345
|
-
y: args.y,
|
|
346
|
-
deltaX: args.deltaX,
|
|
347
|
-
deltaY: args.deltaY,
|
|
348
|
-
});
|
|
349
|
-
if (result === null) return;
|
|
350
|
-
out({
|
|
351
|
-
success: true,
|
|
352
|
-
x: args.x,
|
|
353
|
-
y: args.y,
|
|
354
|
-
deltaX: args.deltaX || 0,
|
|
355
|
-
deltaY: args.deltaY || 0,
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
async function typeText(args: Record<string, unknown>) {
|
|
360
|
-
const result = await call("desktop.keyboard.type", {
|
|
361
|
-
text: args.text,
|
|
362
|
-
delay: args.delay,
|
|
363
|
-
});
|
|
364
|
-
if (result === null) return;
|
|
365
|
-
out({ success: true, chars: (args.text as string).length });
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async function pressKey(args: Record<string, unknown>) {
|
|
369
|
-
const result = await call("desktop.keyboard.key", { keys: args.keys });
|
|
370
|
-
if (result === null) return;
|
|
371
|
-
out({ success: true, keys: args.keys });
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
async function windowList() {
|
|
375
|
-
const result = await call("desktop.window.list", {});
|
|
376
|
-
if (result === null) return;
|
|
377
|
-
const data = result as {
|
|
378
|
-
windows: Array<{
|
|
379
|
-
id: number;
|
|
380
|
-
app: string;
|
|
381
|
-
title: string;
|
|
382
|
-
bounds: { x: number; y: number; width: number; height: number };
|
|
383
|
-
minimized: boolean;
|
|
384
|
-
}>;
|
|
385
|
-
};
|
|
386
|
-
out({ success: true, windows: data.windows });
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
async function windowFocus(args: Record<string, unknown>) {
|
|
390
|
-
const result = await call("desktop.window.focus", {
|
|
391
|
-
windowId: args.windowId,
|
|
392
|
-
});
|
|
393
|
-
if (result === null) return;
|
|
394
|
-
out({ success: true, windowId: args.windowId });
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
async function appLaunch(args: Record<string, unknown>) {
|
|
398
|
-
const result = await call("desktop.app.launch", { app: args.app });
|
|
399
|
-
if (result === null) return;
|
|
400
|
-
out({ success: true, app: args.app });
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
async function appQuit(args: Record<string, unknown>) {
|
|
404
|
-
const result = await call("desktop.app.quit", { app: args.app });
|
|
405
|
-
if (result === null) return;
|
|
406
|
-
out({ success: true, app: args.app });
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
async function clipboardRead() {
|
|
410
|
-
const result = await call("desktop.clipboard.read", {});
|
|
411
|
-
if (result === null) return;
|
|
412
|
-
const data = result as { text: string };
|
|
413
|
-
out({ success: true, text: data.text || "" });
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
async function clipboardWrite(args: Record<string, unknown>) {
|
|
417
|
-
const result = await call("desktop.clipboard.write", { text: args.text });
|
|
418
|
-
if (result === null) return;
|
|
419
|
-
out({ success: true, chars: (args.text as string).length });
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
async function screenInfo() {
|
|
423
|
-
const result = await call("desktop.screen.info", {});
|
|
424
|
-
if (result === null) return;
|
|
425
|
-
const data = result as {
|
|
426
|
-
width: number;
|
|
427
|
-
height: number;
|
|
428
|
-
scaleFactor: number;
|
|
429
|
-
};
|
|
430
|
-
out({ success: true, ...data });
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
async function cursorImage(args: Record<string, unknown>) {
|
|
434
|
-
const result = await call("desktop.cursor.image", { radius: args.radius });
|
|
435
|
-
if (result === null) return;
|
|
436
|
-
const data = result as {
|
|
437
|
-
image: string;
|
|
438
|
-
width: number;
|
|
439
|
-
height: number;
|
|
440
|
-
format?: string;
|
|
441
|
-
};
|
|
442
|
-
const format = data.format || "png";
|
|
443
|
-
const sizeKB = Math.round((data.image.length * 0.75) / 1024);
|
|
444
|
-
const path = saveImage(data.image, format);
|
|
445
|
-
out({
|
|
446
|
-
success: true,
|
|
447
|
-
path,
|
|
448
|
-
width: data.width,
|
|
449
|
-
height: data.height,
|
|
450
|
-
format,
|
|
451
|
-
sizeKB,
|
|
452
|
-
message: `Cursor area saved: ${path} (${data.width}x${data.height}). Use the Read tool to view.`,
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
async function axTree(args: Record<string, unknown>) {
|
|
457
|
-
const params: Record<string, unknown> = {};
|
|
458
|
-
if (args.pid !== undefined) params.pid = args.pid;
|
|
459
|
-
if (args.maxDepth !== undefined) params.maxDepth = args.maxDepth;
|
|
460
|
-
if (args.roles !== undefined) params.roles = args.roles;
|
|
461
|
-
|
|
462
|
-
const result = await call("desktop.ax.tree", params);
|
|
191
|
+
async function cuaRpc(method: string, args: Record<string, unknown> = {}) {
|
|
192
|
+
const result = await call(method, args);
|
|
463
193
|
if (result === null) return;
|
|
464
|
-
|
|
465
|
-
if (!data.root)
|
|
466
|
-
return out({
|
|
467
|
-
success: true,
|
|
468
|
-
tree: null,
|
|
469
|
-
message: "No accessibility tree available",
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
out({
|
|
473
|
-
success: true,
|
|
474
|
-
elementCount: data.elementCount,
|
|
475
|
-
tree: formatAXTree(data.root),
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
async function axAction(args: Record<string, unknown>) {
|
|
480
|
-
const result = await call("desktop.ax.action", {
|
|
481
|
-
elementId: args.elementId,
|
|
482
|
-
action: args.action,
|
|
483
|
-
pid: args.pid,
|
|
484
|
-
});
|
|
485
|
-
if (result === null) return;
|
|
486
|
-
const data = result as {
|
|
487
|
-
ok: boolean;
|
|
488
|
-
action: string;
|
|
489
|
-
elementId: string;
|
|
490
|
-
before: { focused: boolean; value: string };
|
|
491
|
-
after: { focused: boolean; value: string };
|
|
492
|
-
stateChanged: boolean;
|
|
493
|
-
role: string;
|
|
494
|
-
title: string;
|
|
495
|
-
};
|
|
496
|
-
out({
|
|
497
|
-
success: data.ok,
|
|
498
|
-
action: data.action,
|
|
499
|
-
elementId: data.elementId,
|
|
500
|
-
role: data.role,
|
|
501
|
-
title: data.title,
|
|
502
|
-
stateChanged: data.stateChanged,
|
|
503
|
-
before: data.before,
|
|
504
|
-
after: data.after,
|
|
505
|
-
});
|
|
194
|
+
out({ success: true, result });
|
|
506
195
|
}
|
|
507
196
|
|
|
508
|
-
async function
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
value: args.value,
|
|
512
|
-
pid: args.pid,
|
|
513
|
-
});
|
|
514
|
-
if (result === null) return;
|
|
515
|
-
const data = result as {
|
|
516
|
-
ok: boolean;
|
|
517
|
-
elementId: string;
|
|
518
|
-
requestedValue: string;
|
|
519
|
-
actualValue: string;
|
|
520
|
-
error?: string;
|
|
521
|
-
};
|
|
522
|
-
out({
|
|
523
|
-
success: data.ok,
|
|
524
|
-
elementId: data.elementId,
|
|
525
|
-
requestedValue: data.requestedValue,
|
|
526
|
-
actualValue: data.actualValue,
|
|
527
|
-
error: data.error,
|
|
528
|
-
});
|
|
529
|
-
}
|
|
197
|
+
async function cuaCall(args: Record<string, unknown>) {
|
|
198
|
+
const tool = args.tool as string;
|
|
199
|
+
if (!tool) return fail('tool is required');
|
|
530
200
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
pid: args.pid,
|
|
201
|
+
const result = await call('desktop.cua.call', {
|
|
202
|
+
tool,
|
|
203
|
+
args: (args.args || {}) as Record<string, unknown>,
|
|
535
204
|
});
|
|
536
205
|
if (result === null) return;
|
|
537
|
-
|
|
538
|
-
ok: boolean;
|
|
539
|
-
elementId: string;
|
|
540
|
-
role: string;
|
|
541
|
-
title: string;
|
|
542
|
-
before: { focused: boolean };
|
|
543
|
-
after: { focused: boolean };
|
|
544
|
-
error?: string;
|
|
545
|
-
};
|
|
546
|
-
out({
|
|
547
|
-
success: data.ok,
|
|
548
|
-
elementId: data.elementId,
|
|
549
|
-
role: data.role,
|
|
550
|
-
title: data.title,
|
|
551
|
-
before: data.before,
|
|
552
|
-
after: data.after,
|
|
553
|
-
error: data.error,
|
|
554
|
-
});
|
|
206
|
+
out({ success: true, tool, result });
|
|
555
207
|
}
|
|
556
208
|
|
|
557
|
-
async function
|
|
558
|
-
const
|
|
559
|
-
if (
|
|
560
|
-
|
|
561
|
-
if (args.maxResults !== undefined) params.maxResults = args.maxResults;
|
|
562
|
-
|
|
563
|
-
const result = await call("desktop.ax.search", params);
|
|
564
|
-
if (result === null) return;
|
|
565
|
-
const data = result as { elements: AXElement[] };
|
|
566
|
-
|
|
567
|
-
if (!data.elements || data.elements.length === 0) {
|
|
568
|
-
return out({
|
|
569
|
-
success: true,
|
|
570
|
-
query: args.query,
|
|
571
|
-
elements: [],
|
|
572
|
-
message: `No elements found matching "${args.query}"`,
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
const elements = data.elements.map((el) => ({
|
|
577
|
-
id: el.id,
|
|
578
|
-
role: el.role,
|
|
579
|
-
title: el.title || el.value || el.description || "(unnamed)",
|
|
580
|
-
bounds: el.bounds,
|
|
581
|
-
enabled: el.enabled,
|
|
582
|
-
focused: el.focused,
|
|
583
|
-
actions: el.actions,
|
|
584
|
-
}));
|
|
585
|
-
|
|
586
|
-
out({ success: true, query: args.query, count: elements.length, elements });
|
|
209
|
+
async function cuaDescribe(args: Record<string, unknown>) {
|
|
210
|
+
const tool = args.tool as string;
|
|
211
|
+
if (!tool) return fail('tool is required');
|
|
212
|
+
await cuaRpc('desktop.cua.describe', { tool });
|
|
587
213
|
}
|
|
588
214
|
|
|
589
|
-
// ── Dispatch ──────────────────────────────────────────────────────────────
|
|
590
|
-
|
|
591
|
-
const ALL_COMMANDS = [
|
|
592
|
-
"status",
|
|
593
|
-
"fs_read",
|
|
594
|
-
"fs_write",
|
|
595
|
-
"fs_list",
|
|
596
|
-
"shell",
|
|
597
|
-
"screenshot",
|
|
598
|
-
"click",
|
|
599
|
-
"mouse_move",
|
|
600
|
-
"mouse_drag",
|
|
601
|
-
"mouse_scroll",
|
|
602
|
-
"type",
|
|
603
|
-
"key",
|
|
604
|
-
"window_list",
|
|
605
|
-
"window_focus",
|
|
606
|
-
"app_launch",
|
|
607
|
-
"app_quit",
|
|
608
|
-
"clipboard_read",
|
|
609
|
-
"clipboard_write",
|
|
610
|
-
"screen_info",
|
|
611
|
-
"cursor_image",
|
|
612
|
-
"ax_tree",
|
|
613
|
-
"ax_action",
|
|
614
|
-
"ax_set_value",
|
|
615
|
-
"ax_focus",
|
|
616
|
-
"ax_search",
|
|
617
|
-
];
|
|
618
|
-
|
|
619
215
|
const [cmd, rawArgs] = process.argv.slice(2);
|
|
620
216
|
|
|
621
217
|
if (!cmd) {
|
|
622
|
-
console.error(
|
|
623
|
-
`Usage: agent-tunnel-cli <command> [args as JSON]\n\nAvailable: ${ALL_COMMANDS.join(" | ")}`
|
|
624
|
-
);
|
|
218
|
+
console.error(`Usage: agent-tunnel-cli <command> [args as JSON]\n\nAvailable: ${ALL_COMMANDS.join(' | ')}`);
|
|
625
219
|
process.exit(1);
|
|
626
220
|
}
|
|
627
221
|
|
|
628
|
-
|
|
222
|
+
let args: Record<string, unknown> = {};
|
|
223
|
+
try {
|
|
224
|
+
args = rawArgs ? JSON.parse(rawArgs) : {};
|
|
225
|
+
} catch (err) {
|
|
226
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
629
229
|
|
|
630
230
|
try {
|
|
631
231
|
switch (cmd) {
|
|
632
|
-
case
|
|
232
|
+
case 'status':
|
|
633
233
|
await status();
|
|
634
234
|
break;
|
|
635
|
-
case
|
|
235
|
+
case 'fs_read':
|
|
636
236
|
await fsRead(args);
|
|
637
237
|
break;
|
|
638
|
-
case
|
|
238
|
+
case 'fs_write':
|
|
639
239
|
await fsWrite(args);
|
|
640
240
|
break;
|
|
641
|
-
case
|
|
241
|
+
case 'fs_list':
|
|
642
242
|
await fsList(args);
|
|
643
243
|
break;
|
|
644
|
-
case
|
|
244
|
+
case 'shell':
|
|
645
245
|
await shell(args);
|
|
646
246
|
break;
|
|
647
|
-
case
|
|
648
|
-
await
|
|
649
|
-
break;
|
|
650
|
-
case "click":
|
|
651
|
-
await click(args);
|
|
652
|
-
break;
|
|
653
|
-
case "mouse_move":
|
|
654
|
-
await mouseMove(args);
|
|
655
|
-
break;
|
|
656
|
-
case "mouse_drag":
|
|
657
|
-
await mouseDrag(args);
|
|
658
|
-
break;
|
|
659
|
-
case "mouse_scroll":
|
|
660
|
-
await mouseScroll(args);
|
|
661
|
-
break;
|
|
662
|
-
case "type":
|
|
663
|
-
await typeText(args);
|
|
664
|
-
break;
|
|
665
|
-
case "key":
|
|
666
|
-
await pressKey(args);
|
|
667
|
-
break;
|
|
668
|
-
case "window_list":
|
|
669
|
-
await windowList();
|
|
247
|
+
case 'cua_ensure':
|
|
248
|
+
await cuaRpc('desktop.cua.ensure');
|
|
670
249
|
break;
|
|
671
|
-
case
|
|
672
|
-
await
|
|
250
|
+
case 'cua_start_daemon':
|
|
251
|
+
await cuaRpc('desktop.cua.start_daemon');
|
|
673
252
|
break;
|
|
674
|
-
case
|
|
675
|
-
await
|
|
253
|
+
case 'cua_status':
|
|
254
|
+
await cuaRpc('desktop.cua.status');
|
|
676
255
|
break;
|
|
677
|
-
case
|
|
678
|
-
await
|
|
256
|
+
case 'cua_version':
|
|
257
|
+
await cuaRpc('desktop.cua.version');
|
|
679
258
|
break;
|
|
680
|
-
case
|
|
681
|
-
await
|
|
259
|
+
case 'cua_list_tools':
|
|
260
|
+
await cuaRpc('desktop.cua.list_tools');
|
|
682
261
|
break;
|
|
683
|
-
case
|
|
684
|
-
await
|
|
262
|
+
case 'cua_describe':
|
|
263
|
+
await cuaDescribe(args);
|
|
685
264
|
break;
|
|
686
|
-
case
|
|
687
|
-
await
|
|
265
|
+
case 'cua_call':
|
|
266
|
+
await cuaCall(args);
|
|
688
267
|
break;
|
|
689
|
-
case
|
|
690
|
-
await
|
|
268
|
+
case 'cua_list_apps':
|
|
269
|
+
await cuaRpc('desktop.cua.list_apps', args);
|
|
691
270
|
break;
|
|
692
|
-
case
|
|
693
|
-
await
|
|
271
|
+
case 'cua_list_windows':
|
|
272
|
+
await cuaRpc('desktop.cua.list_windows', args);
|
|
694
273
|
break;
|
|
695
|
-
case
|
|
696
|
-
await
|
|
274
|
+
case 'cua_get_window_state':
|
|
275
|
+
await cuaRpc('desktop.cua.get_window_state', args);
|
|
697
276
|
break;
|
|
698
|
-
case
|
|
699
|
-
await
|
|
277
|
+
case 'cua_click':
|
|
278
|
+
await cuaRpc('desktop.cua.click', args);
|
|
700
279
|
break;
|
|
701
|
-
case
|
|
702
|
-
await
|
|
280
|
+
case 'cua_type_text':
|
|
281
|
+
await cuaRpc('desktop.cua.type_text', args);
|
|
703
282
|
break;
|
|
704
|
-
case
|
|
705
|
-
await
|
|
283
|
+
case 'cua_hotkey':
|
|
284
|
+
await cuaRpc('desktop.cua.hotkey', args);
|
|
706
285
|
break;
|
|
707
286
|
default:
|
|
708
|
-
console.error(
|
|
709
|
-
`Unknown command: ${cmd}\n\nAvailable: ${ALL_COMMANDS.join(" | ")}`
|
|
710
|
-
);
|
|
287
|
+
console.error(`Unknown command: ${cmd}\n\nAvailable: ${ALL_COMMANDS.join(' | ')}`);
|
|
711
288
|
process.exit(1);
|
|
712
289
|
}
|
|
713
290
|
} catch (err) {
|