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