agent-console-server 3.7.0 → 3.8.1
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/dist/{HostCloseCode-eTYq6aIa.js → EphemeralAgentEventTypes-Ctmqlyvj.js} +15 -15
- package/dist/{spawnPtyShell-CNDdpI2T.js → agentConsoleServerCommand-D-Uv3ME1.js} +991 -323
- package/dist/cli.js +2 -112
- package/dist/{contracts-DQwqr8CH.d.ts → contracts-CDeSer-r.d.ts} +1 -1
- package/dist/contracts.d.ts +1 -1
- package/dist/contracts.js +1 -1
- package/dist/index.d.ts +126 -8
- package/dist/index.js +3 -3
- package/package.json +11 -7
|
@@ -1,19 +1,112 @@
|
|
|
1
|
-
import { B as getSignedText, V as serverMessageSchema, _ as driverCommandSchema, et as agentEventSchema, ft as todoStatusSchema,
|
|
1
|
+
import { B as getSignedText, V as serverMessageSchema, _ as driverCommandSchema, an as DEFAULT_APP_ORIGIN, cn as HOST_SCHEME, dn as SCHEME_PAIRING_CODE_DURATION, et as agentEventSchema, fn as SHELL_OUTPUT_LENGTH, ft as todoStatusSchema, ln as PAIRING_CODE_PARAMETER, on as DEFAULT_HOSTNAME, r as handshakeMessageSchema, sn as DEFAULT_PORT, t as EphemeralAgentEventTypes, u as commandSchema, un as PAIRING_HASH_PARAMETER, wt as permissionModeSchema } from "./EphemeralAgentEventTypes-Ctmqlyvj.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import {
|
|
4
|
-
import { ID_SEPARATOR, InvalidOperationError, Operation, SITE_NAME, createUniqueArraySchema, exhaustiveGuard, getOrCreate, getResult, getResultAsync, noop, withFinalizerAsync } from "@esposter/shared";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { homedir, hostname } from "node:os";
|
|
4
|
+
import { ID_SEPARATOR, InvalidOperationError, Operation, RoutePath, SITE_NAME, createUniqueArraySchema, exhaustiveGuard, getOrCreate, getResult, getResultAsync, noop, withFinalizerAsync } from "@esposter/shared";
|
|
5
|
+
import { basename, delimiter, dirname, join, relative, win32 } from "node:path";
|
|
6
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
7
7
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { getAssetKeys, getRawAsset, isSea } from "node:sea";
|
|
8
9
|
import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, randomBytes, sign, timingSafeEqual, verify } from "node:crypto";
|
|
10
|
+
import { z } from "zod";
|
|
9
11
|
import { once } from "node:events";
|
|
10
12
|
import { WebSocket, WebSocketServer } from "ws";
|
|
13
|
+
import { parseArgs } from "node:util";
|
|
11
14
|
import { getSessionInfo, getSessionMessages, listSessions, query } from "@anthropic-ai/claude-agent-sdk";
|
|
12
15
|
import { readFile, readdir } from "node:fs/promises";
|
|
13
|
-
import { getAssetKeys, getRawAsset, isSea } from "node:sea";
|
|
14
16
|
import { createServer } from "node:http";
|
|
15
|
-
import { execFileSync, spawn } from "node:child_process";
|
|
16
17
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
18
|
+
//#region src/services/cli/getReachableHostname.ts
|
|
19
|
+
const getReachableHostname = (hostname$1) => hostname$1 === "0.0.0.0" ? hostname() : hostname$1;
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/services/cli/hostArgs.ts
|
|
22
|
+
const hostArgs = {
|
|
23
|
+
hostname: {
|
|
24
|
+
default: DEFAULT_HOSTNAME,
|
|
25
|
+
description: "The address the host listens on",
|
|
26
|
+
type: "string"
|
|
27
|
+
},
|
|
28
|
+
origin: {
|
|
29
|
+
default: DEFAULT_APP_ORIGIN,
|
|
30
|
+
description: "The app's origin the printed link opens",
|
|
31
|
+
type: "string"
|
|
32
|
+
},
|
|
33
|
+
port: {
|
|
34
|
+
default: String(DEFAULT_PORT),
|
|
35
|
+
description: "The port the host listens on",
|
|
36
|
+
type: "string"
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/services/installer/constants.ts
|
|
41
|
+
const HOST_EXECUTABLE_FILENAME = "agent-console-host.exe";
|
|
42
|
+
const CLAUDE_CODE_EXECUTABLE_FILENAME = "claude.exe";
|
|
43
|
+
const HOST_SCHEME_REGISTRY_KEY = win32.join("HKCU", "Software", "Classes", HOST_SCHEME);
|
|
44
|
+
const HOST_DIRECTORY_ENVIRONMENT_VARIABLE = `${SITE_NAME.toUpperCase()}_HOST_DIRECTORY`;
|
|
45
|
+
const HOST_INSTALL_DIRECTORY_SEGMENTS = [SITE_NAME, "Host"];
|
|
46
|
+
const NODE_PTY_DIRECTORY_NAME = "node-pty";
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/services/installer/getHostInstallDirectory.ts
|
|
49
|
+
const getHostInstallDirectory = () => join(process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"), ...HOST_INSTALL_DIRECTORY_SEGMENTS);
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/services/installer/checkIsHostInstalled.ts
|
|
52
|
+
const checkIsHostInstalled = () => relative(getHostInstallDirectory(), dirname(process.execPath)) === "";
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/services/installer/installHost.ts
|
|
55
|
+
const installHost = () => {
|
|
56
|
+
const installDirectory = getHostInstallDirectory();
|
|
57
|
+
const executablePath = join(installDirectory, HOST_EXECUTABLE_FILENAME);
|
|
58
|
+
mkdirSync(installDirectory, { recursive: true });
|
|
59
|
+
copyFileSync(process.execPath, executablePath);
|
|
60
|
+
writeFileSync(join(installDirectory, CLAUDE_CODE_EXECUTABLE_FILENAME), new Uint8Array(getRawAsset(CLAUDE_CODE_EXECUTABLE_FILENAME)));
|
|
61
|
+
for (const assetKey of getAssetKeys().filter((key) => key.startsWith(`${NODE_PTY_DIRECTORY_NAME}/`))) {
|
|
62
|
+
const assetPath = join(installDirectory, ...assetKey.split("/"));
|
|
63
|
+
mkdirSync(dirname(assetPath), { recursive: true });
|
|
64
|
+
writeFileSync(assetPath, new Uint8Array(getRawAsset(assetKey)));
|
|
65
|
+
}
|
|
66
|
+
for (const registryArguments of [
|
|
67
|
+
[
|
|
68
|
+
HOST_SCHEME_REGISTRY_KEY,
|
|
69
|
+
"/ve",
|
|
70
|
+
"/d",
|
|
71
|
+
`URL:${SITE_NAME} Host`
|
|
72
|
+
],
|
|
73
|
+
[
|
|
74
|
+
HOST_SCHEME_REGISTRY_KEY,
|
|
75
|
+
"/v",
|
|
76
|
+
"URL Protocol",
|
|
77
|
+
"/d",
|
|
78
|
+
""
|
|
79
|
+
],
|
|
80
|
+
[
|
|
81
|
+
win32.join(HOST_SCHEME_REGISTRY_KEY, "shell", "open", "command"),
|
|
82
|
+
"/ve",
|
|
83
|
+
"/d",
|
|
84
|
+
`"${executablePath}" "%1"`
|
|
85
|
+
]
|
|
86
|
+
]) execFileSync("reg", [
|
|
87
|
+
"add",
|
|
88
|
+
...registryArguments,
|
|
89
|
+
"/f"
|
|
90
|
+
], { stdio: "ignore" });
|
|
91
|
+
return installDirectory;
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/services/cli/installDownloadedHost.ts
|
|
95
|
+
const installDownloadedHost = () => {
|
|
96
|
+
if (!isSea() || checkIsHostInstalled()) return;
|
|
97
|
+
getResult(() => installHost()).match((installDirectory) => {
|
|
98
|
+
process.stdout.write(`Installed the host in ${installDirectory}. The page's Connect starts it from now on.\n\n`);
|
|
99
|
+
}, (error) => {
|
|
100
|
+
process.stderr.write(`Could not install the host: ${error.message}\nClose any other host window, then open this file again.\n`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/services/cli/writeLine.ts
|
|
106
|
+
const writeLine = (line) => {
|
|
107
|
+
process.stdout.write(`${line}\n`);
|
|
108
|
+
};
|
|
109
|
+
//#endregion
|
|
17
110
|
//#region src/services/device/constants.ts
|
|
18
111
|
const STATE_DIRECTORY_NAME = ".agent-console-server";
|
|
19
112
|
const DEVICES_FILENAME = "devices.json";
|
|
@@ -135,6 +228,506 @@ const runDevicesCommand = async ({ hostKey, hostname, port, revokeDeviceId, stat
|
|
|
135
228
|
return true;
|
|
136
229
|
};
|
|
137
230
|
//#endregion
|
|
231
|
+
//#region ../../node_modules/.pnpm/citty@0.2.2/node_modules/citty/dist/_chunks/libs/scule.mjs
|
|
232
|
+
const NUMBER_CHAR_RE = /\d/;
|
|
233
|
+
const STR_SPLITTERS = [
|
|
234
|
+
"-",
|
|
235
|
+
"_",
|
|
236
|
+
"/",
|
|
237
|
+
"."
|
|
238
|
+
];
|
|
239
|
+
function isUppercase(char = "") {
|
|
240
|
+
if (NUMBER_CHAR_RE.test(char)) return;
|
|
241
|
+
return char !== char.toLowerCase();
|
|
242
|
+
}
|
|
243
|
+
function splitByCase(str, separators) {
|
|
244
|
+
const splitters = separators ?? STR_SPLITTERS;
|
|
245
|
+
const parts = [];
|
|
246
|
+
if (!str || typeof str !== "string") return parts;
|
|
247
|
+
let buff = "";
|
|
248
|
+
let previousUpper;
|
|
249
|
+
let previousSplitter;
|
|
250
|
+
for (const char of str) {
|
|
251
|
+
const isSplitter = splitters.includes(char);
|
|
252
|
+
if (isSplitter === true) {
|
|
253
|
+
parts.push(buff);
|
|
254
|
+
buff = "";
|
|
255
|
+
previousUpper = void 0;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const isUpper = isUppercase(char);
|
|
259
|
+
if (previousSplitter === false) {
|
|
260
|
+
if (previousUpper === false && isUpper === true) {
|
|
261
|
+
parts.push(buff);
|
|
262
|
+
buff = char;
|
|
263
|
+
previousUpper = isUpper;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (previousUpper === true && isUpper === false && buff.length > 1) {
|
|
267
|
+
const lastChar = buff.at(-1);
|
|
268
|
+
parts.push(buff.slice(0, Math.max(0, buff.length - 1)));
|
|
269
|
+
buff = lastChar + char;
|
|
270
|
+
previousUpper = isUpper;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
buff += char;
|
|
275
|
+
previousUpper = isUpper;
|
|
276
|
+
previousSplitter = isSplitter;
|
|
277
|
+
}
|
|
278
|
+
parts.push(buff);
|
|
279
|
+
return parts;
|
|
280
|
+
}
|
|
281
|
+
function upperFirst(str) {
|
|
282
|
+
return str ? str[0].toUpperCase() + str.slice(1) : "";
|
|
283
|
+
}
|
|
284
|
+
function lowerFirst(str) {
|
|
285
|
+
return str ? str[0].toLowerCase() + str.slice(1) : "";
|
|
286
|
+
}
|
|
287
|
+
function pascalCase(str, opts) {
|
|
288
|
+
return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => upperFirst(opts?.normalize ? p.toLowerCase() : p)).join("") : "";
|
|
289
|
+
}
|
|
290
|
+
function camelCase(str, opts) {
|
|
291
|
+
return lowerFirst(pascalCase(str || "", opts));
|
|
292
|
+
}
|
|
293
|
+
function kebabCase(str, joiner) {
|
|
294
|
+
return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => p.toLowerCase()).join(joiner ?? "-") : "";
|
|
295
|
+
}
|
|
296
|
+
function snakeCase(str) {
|
|
297
|
+
return kebabCase(str || "", "_");
|
|
298
|
+
}
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region ../../node_modules/.pnpm/citty@0.2.2/node_modules/citty/dist/index.mjs
|
|
301
|
+
function toArray(val) {
|
|
302
|
+
if (Array.isArray(val)) return val;
|
|
303
|
+
return val === void 0 ? [] : [val];
|
|
304
|
+
}
|
|
305
|
+
function formatLineColumns(lines, linePrefix = "") {
|
|
306
|
+
const maxLength = [];
|
|
307
|
+
for (const line of lines) for (const [i, element] of line.entries()) maxLength[i] = Math.max(maxLength[i] || 0, element.length);
|
|
308
|
+
return lines.map((l) => l.map((c, i) => linePrefix + c[i === 0 ? "padStart" : "padEnd"](maxLength[i])).join(" ")).join("\n");
|
|
309
|
+
}
|
|
310
|
+
function resolveValue(input) {
|
|
311
|
+
return typeof input === "function" ? input() : input;
|
|
312
|
+
}
|
|
313
|
+
var CLIError = class extends Error {
|
|
314
|
+
code;
|
|
315
|
+
constructor(message, code) {
|
|
316
|
+
super(message);
|
|
317
|
+
this.name = "CLIError";
|
|
318
|
+
this.code = code;
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
function parseRawArgs(args = [], opts = {}) {
|
|
322
|
+
const booleans = new Set(opts.boolean || []);
|
|
323
|
+
const strings = new Set(opts.string || []);
|
|
324
|
+
const aliasMap = opts.alias || {};
|
|
325
|
+
const defaults = opts.default || {};
|
|
326
|
+
const aliasToMain = /* @__PURE__ */ new Map();
|
|
327
|
+
const mainToAliases = /* @__PURE__ */ new Map();
|
|
328
|
+
for (const [key, value] of Object.entries(aliasMap)) {
|
|
329
|
+
const targets = value;
|
|
330
|
+
for (const target of targets) {
|
|
331
|
+
aliasToMain.set(key, target);
|
|
332
|
+
if (!mainToAliases.has(target)) mainToAliases.set(target, []);
|
|
333
|
+
mainToAliases.get(target).push(key);
|
|
334
|
+
aliasToMain.set(target, key);
|
|
335
|
+
if (!mainToAliases.has(key)) mainToAliases.set(key, []);
|
|
336
|
+
mainToAliases.get(key).push(target);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const options = {};
|
|
340
|
+
function getType(name) {
|
|
341
|
+
if (booleans.has(name)) return "boolean";
|
|
342
|
+
const aliases = mainToAliases.get(name) || [];
|
|
343
|
+
for (const alias of aliases) if (booleans.has(alias)) return "boolean";
|
|
344
|
+
return "string";
|
|
345
|
+
}
|
|
346
|
+
function isStringType(name) {
|
|
347
|
+
if (strings.has(name)) return true;
|
|
348
|
+
const aliases = mainToAliases.get(name) || [];
|
|
349
|
+
for (const alias of aliases) if (strings.has(alias)) return true;
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
const allOptions = /* @__PURE__ */ new Set([
|
|
353
|
+
...booleans,
|
|
354
|
+
...strings,
|
|
355
|
+
...Object.keys(aliasMap),
|
|
356
|
+
...Object.values(aliasMap).flat(),
|
|
357
|
+
...Object.keys(defaults)
|
|
358
|
+
]);
|
|
359
|
+
for (const name of allOptions) if (!options[name]) options[name] = {
|
|
360
|
+
type: getType(name),
|
|
361
|
+
default: defaults[name]
|
|
362
|
+
};
|
|
363
|
+
for (const [alias, main] of aliasToMain.entries()) if (alias.length === 1 && options[main] && !options[main].short) options[main].short = alias;
|
|
364
|
+
const processedArgs = [];
|
|
365
|
+
const negatedFlags = {};
|
|
366
|
+
for (let i = 0; i < args.length; i++) {
|
|
367
|
+
const arg = args[i];
|
|
368
|
+
if (arg === "--") {
|
|
369
|
+
processedArgs.push(...args.slice(i));
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
if (arg.startsWith("--no-")) {
|
|
373
|
+
const flagName = arg.slice(5);
|
|
374
|
+
negatedFlags[flagName] = true;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
processedArgs.push(arg);
|
|
378
|
+
}
|
|
379
|
+
let parsed;
|
|
380
|
+
try {
|
|
381
|
+
parsed = parseArgs({
|
|
382
|
+
args: processedArgs,
|
|
383
|
+
options: Object.keys(options).length > 0 ? options : void 0,
|
|
384
|
+
allowPositionals: true,
|
|
385
|
+
strict: false
|
|
386
|
+
});
|
|
387
|
+
} catch {
|
|
388
|
+
parsed = {
|
|
389
|
+
values: {},
|
|
390
|
+
positionals: processedArgs
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
const out = { _: [] };
|
|
394
|
+
out._ = parsed.positionals;
|
|
395
|
+
for (const [key, value] of Object.entries(parsed.values)) {
|
|
396
|
+
let coerced = value;
|
|
397
|
+
if (getType(key) === "boolean" && typeof value === "string") coerced = value !== "false";
|
|
398
|
+
else if (isStringType(key) && typeof value === "boolean") coerced = "";
|
|
399
|
+
out[key] = coerced;
|
|
400
|
+
}
|
|
401
|
+
for (const [name] of Object.entries(negatedFlags)) {
|
|
402
|
+
out[name] = false;
|
|
403
|
+
const mainName = aliasToMain.get(name);
|
|
404
|
+
if (mainName) out[mainName] = false;
|
|
405
|
+
const aliases = mainToAliases.get(name);
|
|
406
|
+
if (aliases) for (const alias of aliases) out[alias] = false;
|
|
407
|
+
}
|
|
408
|
+
for (const [alias, main] of aliasToMain.entries()) {
|
|
409
|
+
if (out[alias] !== void 0 && out[main] === void 0) out[main] = out[alias];
|
|
410
|
+
if (out[main] !== void 0 && out[alias] === void 0) out[alias] = out[main];
|
|
411
|
+
if (out[alias] !== out[main] && defaults[main] === out[main]) out[main] = out[alias];
|
|
412
|
+
}
|
|
413
|
+
return out;
|
|
414
|
+
}
|
|
415
|
+
const noColor = /* @__PURE__ */ (() => {
|
|
416
|
+
const env = globalThis.process?.env ?? {};
|
|
417
|
+
return env.NO_COLOR === "1" || env.TERM === "dumb" || env.TEST || env.CI;
|
|
418
|
+
})();
|
|
419
|
+
const _c = (c, r = 39) => (t) => noColor ? t : `\u001B[${c}m${t}\u001B[${r}m`;
|
|
420
|
+
const bold = /* @__PURE__ */ _c(1, 22);
|
|
421
|
+
const cyan = /* @__PURE__ */ _c(36);
|
|
422
|
+
const gray = /* @__PURE__ */ _c(90);
|
|
423
|
+
const underline = /* @__PURE__ */ _c(4, 24);
|
|
424
|
+
function parseArgs$1(rawArgs, argsDef) {
|
|
425
|
+
const parseOptions = {
|
|
426
|
+
boolean: [],
|
|
427
|
+
string: [],
|
|
428
|
+
alias: {},
|
|
429
|
+
default: {}
|
|
430
|
+
};
|
|
431
|
+
const args = resolveArgs(argsDef);
|
|
432
|
+
for (const arg of args) {
|
|
433
|
+
if (arg.type === "positional") continue;
|
|
434
|
+
if (arg.type === "string" || arg.type === "enum") parseOptions.string.push(arg.name);
|
|
435
|
+
else if (arg.type === "boolean") parseOptions.boolean.push(arg.name);
|
|
436
|
+
if (arg.default !== void 0) parseOptions.default[arg.name] = arg.default;
|
|
437
|
+
if (arg.alias) parseOptions.alias[arg.name] = arg.alias;
|
|
438
|
+
const camelName = camelCase(arg.name);
|
|
439
|
+
const kebabName = kebabCase(arg.name);
|
|
440
|
+
if (camelName !== arg.name || kebabName !== arg.name) {
|
|
441
|
+
const existingAliases = toArray(parseOptions.alias[arg.name] || []);
|
|
442
|
+
if (camelName !== arg.name && !existingAliases.includes(camelName)) existingAliases.push(camelName);
|
|
443
|
+
if (kebabName !== arg.name && !existingAliases.includes(kebabName)) existingAliases.push(kebabName);
|
|
444
|
+
if (existingAliases.length > 0) parseOptions.alias[arg.name] = existingAliases;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const parsed = parseRawArgs(rawArgs, parseOptions);
|
|
448
|
+
const [ ...positionalArguments] = parsed._;
|
|
449
|
+
const parsedArgsProxy = new Proxy(parsed, { get(target, prop) {
|
|
450
|
+
return target[prop] ?? target[camelCase(prop)] ?? target[kebabCase(prop)];
|
|
451
|
+
} });
|
|
452
|
+
for (const [, arg] of args.entries()) if (arg.type === "positional") {
|
|
453
|
+
const nextPositionalArgument = positionalArguments.shift();
|
|
454
|
+
if (nextPositionalArgument !== void 0) parsedArgsProxy[arg.name] = nextPositionalArgument;
|
|
455
|
+
else if (arg.default === void 0 && arg.required !== false) throw new CLIError(`Missing required positional argument: ${arg.name.toUpperCase()}`, "EARG");
|
|
456
|
+
else parsedArgsProxy[arg.name] = arg.default;
|
|
457
|
+
} else if (arg.type === "enum") {
|
|
458
|
+
const argument = parsedArgsProxy[arg.name];
|
|
459
|
+
const options = arg.options || [];
|
|
460
|
+
if (argument !== void 0 && options.length > 0 && !options.includes(argument)) throw new CLIError(`Invalid value for argument: ${cyan(`--${arg.name}`)} (${cyan(argument)}). Expected one of: ${options.map((o) => cyan(o)).join(", ")}.`, "EARG");
|
|
461
|
+
} else if (arg.required && parsedArgsProxy[arg.name] === void 0) throw new CLIError(`Missing required argument: --${arg.name}`, "EARG");
|
|
462
|
+
return parsedArgsProxy;
|
|
463
|
+
}
|
|
464
|
+
function resolveArgs(argsDef) {
|
|
465
|
+
const args = [];
|
|
466
|
+
for (const [name, argDef] of Object.entries(argsDef || {})) args.push({
|
|
467
|
+
...argDef,
|
|
468
|
+
name,
|
|
469
|
+
alias: toArray(argDef.alias)
|
|
470
|
+
});
|
|
471
|
+
return args;
|
|
472
|
+
}
|
|
473
|
+
async function resolvePlugins(plugins) {
|
|
474
|
+
return Promise.all(plugins.map((p) => resolveValue(p)));
|
|
475
|
+
}
|
|
476
|
+
function defineCommand(def) {
|
|
477
|
+
return def;
|
|
478
|
+
}
|
|
479
|
+
async function runCommand(cmd, opts) {
|
|
480
|
+
const cmdArgs = await resolveValue(cmd.args || {});
|
|
481
|
+
const parsedArgs = parseArgs$1(opts.rawArgs, cmdArgs);
|
|
482
|
+
const context = {
|
|
483
|
+
rawArgs: opts.rawArgs,
|
|
484
|
+
args: parsedArgs,
|
|
485
|
+
data: opts.data,
|
|
486
|
+
cmd
|
|
487
|
+
};
|
|
488
|
+
const plugins = await resolvePlugins(cmd.plugins ?? []);
|
|
489
|
+
let result;
|
|
490
|
+
let runError;
|
|
491
|
+
try {
|
|
492
|
+
for (const plugin of plugins) await plugin.setup?.(context);
|
|
493
|
+
if (typeof cmd.setup === "function") await cmd.setup(context);
|
|
494
|
+
const subCommands = await resolveValue(cmd.subCommands);
|
|
495
|
+
if (subCommands && Object.keys(subCommands).length > 0) {
|
|
496
|
+
const subCommandArgIndex = findSubCommandIndex(opts.rawArgs, cmdArgs);
|
|
497
|
+
const explicitName = opts.rawArgs[subCommandArgIndex];
|
|
498
|
+
if (explicitName) {
|
|
499
|
+
const subCommand = await _findSubCommand(subCommands, explicitName);
|
|
500
|
+
if (!subCommand) throw new CLIError(`Unknown command ${cyan(explicitName)}`, "E_UNKNOWN_COMMAND");
|
|
501
|
+
await runCommand(subCommand, { rawArgs: opts.rawArgs.slice(subCommandArgIndex + 1) });
|
|
502
|
+
} else {
|
|
503
|
+
const defaultSubCommand = await resolveValue(cmd.default);
|
|
504
|
+
if (defaultSubCommand) {
|
|
505
|
+
if (cmd.run) throw new CLIError(`Cannot specify both 'run' and 'default' on the same command.`, "E_DEFAULT_CONFLICT");
|
|
506
|
+
const subCommand = await _findSubCommand(subCommands, defaultSubCommand);
|
|
507
|
+
if (!subCommand) throw new CLIError(`Default sub command ${cyan(defaultSubCommand)} not found in subCommands.`, "E_UNKNOWN_COMMAND");
|
|
508
|
+
await runCommand(subCommand, { rawArgs: opts.rawArgs });
|
|
509
|
+
} else if (!cmd.run) throw new CLIError(`No command specified.`, "E_NO_COMMAND");
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (typeof cmd.run === "function") result = await cmd.run(context);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
runError = error;
|
|
515
|
+
}
|
|
516
|
+
const cleanupErrors = [];
|
|
517
|
+
if (typeof cmd.cleanup === "function") try {
|
|
518
|
+
await cmd.cleanup(context);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
cleanupErrors.push(error);
|
|
521
|
+
}
|
|
522
|
+
for (const plugin of [...plugins].reverse()) try {
|
|
523
|
+
await plugin.cleanup?.(context);
|
|
524
|
+
} catch (error) {
|
|
525
|
+
cleanupErrors.push(error);
|
|
526
|
+
}
|
|
527
|
+
if (runError) throw runError;
|
|
528
|
+
if (cleanupErrors.length === 1) throw cleanupErrors[0];
|
|
529
|
+
if (cleanupErrors.length > 1) throw new Error("Multiple cleanup errors", { cause: cleanupErrors });
|
|
530
|
+
return { result };
|
|
531
|
+
}
|
|
532
|
+
async function resolveSubCommand(cmd, rawArgs, parent) {
|
|
533
|
+
const subCommands = await resolveValue(cmd.subCommands);
|
|
534
|
+
if (subCommands && Object.keys(subCommands).length > 0) {
|
|
535
|
+
const subCommandArgIndex = findSubCommandIndex(rawArgs, await resolveValue(cmd.args || {}));
|
|
536
|
+
const subCommandName = rawArgs[subCommandArgIndex];
|
|
537
|
+
const subCommand = await _findSubCommand(subCommands, subCommandName);
|
|
538
|
+
if (subCommand) return resolveSubCommand(subCommand, rawArgs.slice(subCommandArgIndex + 1), cmd);
|
|
539
|
+
}
|
|
540
|
+
return [cmd, parent];
|
|
541
|
+
}
|
|
542
|
+
async function _findSubCommand(subCommands, name) {
|
|
543
|
+
if (name in subCommands) return resolveValue(subCommands[name]);
|
|
544
|
+
for (const sub of Object.values(subCommands)) {
|
|
545
|
+
const resolved = await resolveValue(sub);
|
|
546
|
+
const meta = await resolveValue(resolved?.meta);
|
|
547
|
+
if (meta?.alias) {
|
|
548
|
+
if (toArray(meta.alias).includes(name)) return resolved;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
function findSubCommandIndex(rawArgs, argsDef) {
|
|
553
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
554
|
+
const arg = rawArgs[i];
|
|
555
|
+
if (arg === "--") return -1;
|
|
556
|
+
if (arg.startsWith("-")) {
|
|
557
|
+
if (!arg.includes("=") && _isValueFlag(arg, argsDef)) i++;
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
return i;
|
|
561
|
+
}
|
|
562
|
+
return -1;
|
|
563
|
+
}
|
|
564
|
+
function _isValueFlag(flag, argsDef) {
|
|
565
|
+
const name = flag.replace(/^-{1,2}/, "");
|
|
566
|
+
const normalized = camelCase(name);
|
|
567
|
+
for (const [key, def] of Object.entries(argsDef)) {
|
|
568
|
+
if (def.type !== "string" && def.type !== "enum") continue;
|
|
569
|
+
if (normalized === camelCase(key)) return true;
|
|
570
|
+
if ((Array.isArray(def.alias) ? def.alias : def.alias ? [def.alias] : []).includes(name)) return true;
|
|
571
|
+
}
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
async function showUsage(cmd, parent) {
|
|
575
|
+
try {
|
|
576
|
+
console.log(await renderUsage(cmd, parent) + "\n");
|
|
577
|
+
} catch (error) {
|
|
578
|
+
console.error(error);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const negativePrefixRe = /^no[-A-Z]/;
|
|
582
|
+
async function renderUsage(cmd, parent) {
|
|
583
|
+
const cmdMeta = await resolveValue(cmd.meta || {});
|
|
584
|
+
const cmdArgs = resolveArgs(await resolveValue(cmd.args || {}));
|
|
585
|
+
const parentMeta = await resolveValue(parent?.meta || {});
|
|
586
|
+
const commandName = `${parentMeta.name ? `${parentMeta.name} ` : ""}` + (cmdMeta.name || process.argv[1]);
|
|
587
|
+
const argLines = [];
|
|
588
|
+
const posLines = [];
|
|
589
|
+
const commandsLines = [];
|
|
590
|
+
const usageLine = [];
|
|
591
|
+
for (const arg of cmdArgs) if (arg.type === "positional") {
|
|
592
|
+
const name = arg.name.toUpperCase();
|
|
593
|
+
const isRequired = arg.required !== false && arg.default === void 0;
|
|
594
|
+
posLines.push([cyan(name + renderValueHint(arg)), renderDescription(arg, isRequired)]);
|
|
595
|
+
usageLine.push(isRequired ? `<${name}>` : `[${name}]`);
|
|
596
|
+
} else {
|
|
597
|
+
const isRequired = arg.required === true && arg.default === void 0;
|
|
598
|
+
const argStr = [...(arg.alias || []).map((a) => `-${a}`), `--${arg.name}`].join(", ") + renderValueHint(arg);
|
|
599
|
+
argLines.push([cyan(argStr), renderDescription(arg, isRequired)]);
|
|
600
|
+
/**
|
|
601
|
+
* print negative boolean arg variant usage when
|
|
602
|
+
* - enabled by default or has `negativeDescription`
|
|
603
|
+
* - not prefixed with `no-` or `no[A-Z]`
|
|
604
|
+
*/
|
|
605
|
+
if (arg.type === "boolean" && (arg.default === true || arg.negativeDescription) && !negativePrefixRe.test(arg.name)) {
|
|
606
|
+
const negativeArgStr = [...(arg.alias || []).map((a) => `--no-${a}`), `--no-${arg.name}`].join(", ");
|
|
607
|
+
argLines.push([cyan(negativeArgStr), [arg.negativeDescription, isRequired ? gray("(Required)") : ""].filter(Boolean).join(" ")]);
|
|
608
|
+
}
|
|
609
|
+
if (isRequired) usageLine.push(`--${arg.name}` + renderValueHint(arg));
|
|
610
|
+
}
|
|
611
|
+
if (cmd.subCommands) {
|
|
612
|
+
const commandNames = [];
|
|
613
|
+
const subCommands = await resolveValue(cmd.subCommands);
|
|
614
|
+
for (const [name, sub] of Object.entries(subCommands)) {
|
|
615
|
+
const meta = await resolveValue((await resolveValue(sub))?.meta);
|
|
616
|
+
if (meta?.hidden) continue;
|
|
617
|
+
const aliases = toArray(meta?.alias);
|
|
618
|
+
const label = [name, ...aliases].join(", ");
|
|
619
|
+
commandsLines.push([cyan(label), meta?.description || ""]);
|
|
620
|
+
commandNames.push(name, ...aliases);
|
|
621
|
+
}
|
|
622
|
+
usageLine.push(commandNames.join("|"));
|
|
623
|
+
}
|
|
624
|
+
const usageLines = [];
|
|
625
|
+
const version = cmdMeta.version || parentMeta.version;
|
|
626
|
+
usageLines.push(gray(`${cmdMeta.description} (${commandName + (version ? ` v${version}` : "")})`), "");
|
|
627
|
+
const hasOptions = argLines.length > 0 || posLines.length > 0;
|
|
628
|
+
usageLines.push(`${underline(bold("USAGE"))} ${cyan(`${commandName}${hasOptions ? " [OPTIONS]" : ""} ${usageLine.join(" ")}`)}`, "");
|
|
629
|
+
if (posLines.length > 0) {
|
|
630
|
+
usageLines.push(underline(bold("ARGUMENTS")), "");
|
|
631
|
+
usageLines.push(formatLineColumns(posLines, " "));
|
|
632
|
+
usageLines.push("");
|
|
633
|
+
}
|
|
634
|
+
if (argLines.length > 0) {
|
|
635
|
+
usageLines.push(underline(bold("OPTIONS")), "");
|
|
636
|
+
usageLines.push(formatLineColumns(argLines, " "));
|
|
637
|
+
usageLines.push("");
|
|
638
|
+
}
|
|
639
|
+
if (commandsLines.length > 0) {
|
|
640
|
+
usageLines.push(underline(bold("COMMANDS")), "");
|
|
641
|
+
usageLines.push(formatLineColumns(commandsLines, " "));
|
|
642
|
+
usageLines.push("", `Use ${cyan(`${commandName} <command> --help`)} for more information about a command.`);
|
|
643
|
+
}
|
|
644
|
+
return usageLines.filter((l) => typeof l === "string").join("\n");
|
|
645
|
+
}
|
|
646
|
+
function renderValueHint(arg) {
|
|
647
|
+
const valueHint = arg.valueHint ? `=<${arg.valueHint}>` : "";
|
|
648
|
+
const fallbackValueHint = valueHint || `=<${snakeCase(arg.name)}>`;
|
|
649
|
+
if (!arg.type || arg.type === "positional" || arg.type === "boolean") return valueHint;
|
|
650
|
+
if (arg.type === "enum" && arg.options?.length) return `=<${arg.options.join("|")}>`;
|
|
651
|
+
return fallbackValueHint;
|
|
652
|
+
}
|
|
653
|
+
function renderDescription(arg, required) {
|
|
654
|
+
const requiredHint = required ? gray("(Required)") : "";
|
|
655
|
+
const defaultHint = arg.default === void 0 ? "" : gray(`(Default: ${arg.default})`);
|
|
656
|
+
return [
|
|
657
|
+
arg.description,
|
|
658
|
+
requiredHint,
|
|
659
|
+
defaultHint
|
|
660
|
+
].filter(Boolean).join(" ");
|
|
661
|
+
}
|
|
662
|
+
async function runMain(cmd, opts = {}) {
|
|
663
|
+
const rawArgs = opts.rawArgs || process.argv.slice(2);
|
|
664
|
+
const showUsage$1 = opts.showUsage || showUsage;
|
|
665
|
+
try {
|
|
666
|
+
const builtinFlags = await _resolveBuiltinFlags(cmd);
|
|
667
|
+
if (builtinFlags.help.length > 0 && rawArgs.some((arg) => builtinFlags.help.includes(arg))) {
|
|
668
|
+
await showUsage$1(...await resolveSubCommand(cmd, rawArgs));
|
|
669
|
+
process.exit(0);
|
|
670
|
+
} else if (rawArgs.length === 1 && builtinFlags.version.includes(rawArgs[0])) {
|
|
671
|
+
const meta = typeof cmd.meta === "function" ? await cmd.meta() : await cmd.meta;
|
|
672
|
+
if (!meta?.version) throw new CLIError("No version specified", "E_NO_VERSION");
|
|
673
|
+
console.log(meta.version);
|
|
674
|
+
} else await runCommand(cmd, { rawArgs });
|
|
675
|
+
} catch (error) {
|
|
676
|
+
if (error instanceof CLIError) {
|
|
677
|
+
await showUsage$1(...await resolveSubCommand(cmd, rawArgs));
|
|
678
|
+
console.error(error.message);
|
|
679
|
+
} else console.error(error, "\n");
|
|
680
|
+
process.exit(1);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
async function _resolveBuiltinFlags(cmd) {
|
|
684
|
+
const argsDef = await resolveValue(cmd.args || {});
|
|
685
|
+
const userNames = /* @__PURE__ */ new Set();
|
|
686
|
+
const userAliases = /* @__PURE__ */ new Set();
|
|
687
|
+
for (const [name, def] of Object.entries(argsDef)) {
|
|
688
|
+
userNames.add(name);
|
|
689
|
+
for (const alias of toArray(def.alias)) userAliases.add(alias);
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
help: _getBuiltinFlags("help", "h", userNames, userAliases),
|
|
693
|
+
version: _getBuiltinFlags("version", "v", userNames, userAliases)
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
function _getBuiltinFlags(long, short, userNames, userAliases) {
|
|
697
|
+
if (userNames.has(long) || userAliases.has(long)) return [];
|
|
698
|
+
if (userNames.has(short) || userAliases.has(short)) return [`--${long}`];
|
|
699
|
+
return [`--${long}`, `-${short}`];
|
|
700
|
+
}
|
|
701
|
+
//#endregion
|
|
702
|
+
//#region src/services/cli/commands/devicesCommand.ts
|
|
703
|
+
const devicesCommand = defineCommand({
|
|
704
|
+
args: {
|
|
705
|
+
hostname: hostArgs.hostname,
|
|
706
|
+
port: hostArgs.port,
|
|
707
|
+
revoke: {
|
|
708
|
+
default: "",
|
|
709
|
+
description: "A paired page's id, to remove it",
|
|
710
|
+
type: "string"
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
meta: {
|
|
714
|
+
description: "List the pages paired with this host, or remove one",
|
|
715
|
+
name: "devices"
|
|
716
|
+
},
|
|
717
|
+
run: async ({ args }) => {
|
|
718
|
+
installDownloadedHost();
|
|
719
|
+
const stateDirectory = getStateDirectory();
|
|
720
|
+
const isDone = await runDevicesCommand({
|
|
721
|
+
hostKey: readHostKey(stateDirectory),
|
|
722
|
+
hostname: getReachableHostname(args.hostname),
|
|
723
|
+
port: Number(args.port),
|
|
724
|
+
revokeDeviceId: args.revoke,
|
|
725
|
+
stateDirectory
|
|
726
|
+
}, writeLine);
|
|
727
|
+
process.exit(isDone ? 0 : 1);
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
//#endregion
|
|
138
731
|
//#region src/services/drivers/claudeAgentSdk/getEventId.ts
|
|
139
732
|
const getEventId = (sourceId, suffix) => `${sourceId}${ID_SEPARATOR}${suffix}`;
|
|
140
733
|
//#endregion
|
|
@@ -980,17 +1573,6 @@ const watchSession = async (sessionId, openSession, { emit, onSessionsChange, op
|
|
|
980
1573
|
]);
|
|
981
1574
|
};
|
|
982
1575
|
//#endregion
|
|
983
|
-
//#region src/services/installer/constants.ts
|
|
984
|
-
const HOST_EXECUTABLE_FILENAME = "agent-console-host.exe";
|
|
985
|
-
const CLAUDE_CODE_EXECUTABLE_FILENAME = "claude.exe";
|
|
986
|
-
const HOST_SCHEME_REGISTRY_KEY = win32.join("HKCU", "Software", "Classes", HOST_SCHEME);
|
|
987
|
-
const HOST_DIRECTORY_ENVIRONMENT_VARIABLE = `${SITE_NAME.toUpperCase()}_HOST_DIRECTORY`;
|
|
988
|
-
const HOST_INSTALL_DIRECTORY_SEGMENTS = [SITE_NAME, "Host"];
|
|
989
|
-
const NODE_PTY_DIRECTORY_NAME = "node-pty";
|
|
990
|
-
//#endregion
|
|
991
|
-
//#region src/services/installer/getHostInstallDirectory.ts
|
|
992
|
-
const getHostInstallDirectory = () => join(process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"), ...HOST_INSTALL_DIRECTORY_SEGMENTS);
|
|
993
|
-
//#endregion
|
|
994
1576
|
//#region src/services/installer/getClaudeCodeExecutablePath.ts
|
|
995
1577
|
const getClaudeCodeExecutablePath = () => isSea() ? join(getHostInstallDirectory(), CLAUDE_CODE_EXECUTABLE_FILENAME) : void 0;
|
|
996
1578
|
//#endregion
|
|
@@ -1308,15 +1890,6 @@ const createClaudeAgentSdkDriver = ({ onEvents, onSessionOpen, onSessionsChange
|
|
|
1308
1890
|
};
|
|
1309
1891
|
};
|
|
1310
1892
|
//#endregion
|
|
1311
|
-
//#region src/services/drivers/window/constants.ts
|
|
1312
|
-
const SESSION_SUBCOMMAND = "session";
|
|
1313
|
-
const SESSION_SECRET_ENVIRONMENT_VARIABLE = `${SITE_NAME.toUpperCase()}_SESSION_SECRET`;
|
|
1314
|
-
const SESSION_SECRET_BYTE_LENGTH = 32;
|
|
1315
|
-
const SESSION_WINDOW_CONNECT_TIMEOUT = Temporal.Duration.from({ seconds: 30 }).total("milliseconds");
|
|
1316
|
-
const SESSION_WINDOWS_FILENAME = "session-windows.json";
|
|
1317
|
-
const SESSION_WINDOW_REJOIN_DURATION = Temporal.Duration.from({ minutes: 1 }).total("milliseconds");
|
|
1318
|
-
const SESSION_WINDOW_RECONNECT_DELAY = Temporal.Duration.from({ seconds: 1 }).total("milliseconds");
|
|
1319
|
-
//#endregion
|
|
1320
1893
|
//#region src/models/window/BaseChildMessage.ts
|
|
1321
1894
|
const createBaseChildMessageSchema = (typeSchema) => z.object({ type: typeSchema });
|
|
1322
1895
|
//#endregion
|
|
@@ -1391,6 +1964,15 @@ let SessionWindowCloseCode = /* @__PURE__ */ function(SessionWindowCloseCode) {
|
|
|
1391
1964
|
//#region src/services/device/hashCredential.ts
|
|
1392
1965
|
const hashCredential = (credential) => createHash("sha256").update(credential).digest("base64url");
|
|
1393
1966
|
//#endregion
|
|
1967
|
+
//#region src/services/drivers/window/constants.ts
|
|
1968
|
+
const SESSION_SUBCOMMAND = "session";
|
|
1969
|
+
const SESSION_SECRET_ENVIRONMENT_VARIABLE = `${SITE_NAME.toUpperCase()}_SESSION_SECRET`;
|
|
1970
|
+
const SESSION_SECRET_BYTE_LENGTH = 32;
|
|
1971
|
+
const SESSION_WINDOW_CONNECT_TIMEOUT = Temporal.Duration.from({ seconds: 30 }).total("milliseconds");
|
|
1972
|
+
const SESSION_WINDOWS_FILENAME = "session-windows.json";
|
|
1973
|
+
const SESSION_WINDOW_REJOIN_DURATION = Temporal.Duration.from({ minutes: 1 }).total("milliseconds");
|
|
1974
|
+
const SESSION_WINDOW_RECONNECT_DELAY = Temporal.Duration.from({ seconds: 1 }).total("milliseconds");
|
|
1975
|
+
//#endregion
|
|
1394
1976
|
//#region src/models/window/SessionWindows.ts
|
|
1395
1977
|
const sessionWindowsSchema = z.object({
|
|
1396
1978
|
port: z.int().min(0),
|
|
@@ -1762,296 +2344,51 @@ const launchSessionWindow = ({ port, secret }) => {
|
|
|
1762
2344
|
}).on("error", console.error);
|
|
1763
2345
|
};
|
|
1764
2346
|
//#endregion
|
|
1765
|
-
//#region src/services/
|
|
1766
|
-
const
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
case "HostError": return `Error: ${event.message}`;
|
|
1770
|
-
case "PermissionRequest": return `Claude asks to use ${event.toolName}. Answer in the page.`;
|
|
1771
|
-
case "ToolUse": return event.parentToolUseId ? "" : `Claude uses ${event.name}`;
|
|
1772
|
-
case "UserMessage": return event.parentToolUseId ? "" : `You: ${event.text}`;
|
|
1773
|
-
default: return "";
|
|
1774
|
-
}
|
|
2347
|
+
//#region src/services/device/findDevice.ts
|
|
2348
|
+
const findDevice = (devices, credential) => {
|
|
2349
|
+
const credentialHash = hashCredential(credential);
|
|
2350
|
+
return devices.find((device) => checkIsTokenValid(credentialHash, device.credentialHash));
|
|
1775
2351
|
};
|
|
1776
2352
|
//#endregion
|
|
1777
|
-
//#region src/services/
|
|
1778
|
-
const
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
return "";
|
|
1806
|
-
case "SetPermissionMode":
|
|
1807
|
-
await driver.setPermissionMode(command.sessionId, command.permissionMode);
|
|
1808
|
-
return "";
|
|
1809
|
-
case "SlashCommand":
|
|
1810
|
-
driver.runSlashCommand(command.sessionId, command.name, command.arguments);
|
|
1811
|
-
return "";
|
|
1812
|
-
case "StopTask":
|
|
1813
|
-
await driver.stopTask(command.sessionId, command.taskId);
|
|
1814
|
-
return "";
|
|
1815
|
-
default: return exhaustiveGuard(command);
|
|
2353
|
+
//#region src/services/device/getDeviceName.ts
|
|
2354
|
+
const BrowserNames = [
|
|
2355
|
+
[/Edg\//u, "Edge"],
|
|
2356
|
+
[/Firefox\//u, "Firefox"],
|
|
2357
|
+
[/Chrome\//u, "Chrome"],
|
|
2358
|
+
[/Safari\//u, "Safari"]
|
|
2359
|
+
];
|
|
2360
|
+
const SystemNames = [
|
|
2361
|
+
[/Windows/u, "Windows"],
|
|
2362
|
+
[/Android/u, "Android"],
|
|
2363
|
+
[/iPhone|iPad/u, "iOS"],
|
|
2364
|
+
[/Mac OS X/u, "macOS"],
|
|
2365
|
+
[/Linux/u, "Linux"]
|
|
2366
|
+
];
|
|
2367
|
+
const getDeviceName = (userAgent) => {
|
|
2368
|
+
const browserName = BrowserNames.find(([pattern]) => pattern.test(userAgent))?.[1] ?? "A browser";
|
|
2369
|
+
const systemName = SystemNames.find(([pattern]) => pattern.test(userAgent))?.[1];
|
|
2370
|
+
return systemName ? `${browserName} on ${systemName}` : browserName;
|
|
2371
|
+
};
|
|
2372
|
+
//#endregion
|
|
2373
|
+
//#region src/services/handshake/getPublicKeyText.ts
|
|
2374
|
+
const getPublicKeyText = (privateKey) => createPublicKey(privateKey).export({ format: "jwk" }).x ?? "";
|
|
2375
|
+
//#endregion
|
|
2376
|
+
//#region src/services/server/answerHttpRequest.ts
|
|
2377
|
+
const answerHttpRequest = ({ headers, method }, response) => {
|
|
2378
|
+
if (method !== "OPTIONS") {
|
|
2379
|
+
response.writeHead(405).end();
|
|
2380
|
+
return;
|
|
1816
2381
|
}
|
|
2382
|
+
response.setHeader("Access-Control-Allow-Origin", headers.origin ?? "*");
|
|
2383
|
+
response.setHeader("Access-Control-Allow-Private-Network", "true");
|
|
2384
|
+
response.setHeader("Access-Control-Allow-Methods", "GET");
|
|
2385
|
+
response.setHeader("Access-Control-Allow-Headers", headers["access-control-request-headers"] ?? "*");
|
|
2386
|
+
response.setHeader("Vary", "Origin");
|
|
2387
|
+
response.writeHead(204).end();
|
|
1817
2388
|
};
|
|
1818
2389
|
//#endregion
|
|
1819
|
-
//#region src/services/
|
|
1820
|
-
const
|
|
1821
|
-
const taskRegistry = createTaskRegistry();
|
|
1822
|
-
const sessionEventMap = /* @__PURE__ */ new Map();
|
|
1823
|
-
let webSocket;
|
|
1824
|
-
const send = (childMessage) => {
|
|
1825
|
-
if (webSocket?.readyState === WebSocket.OPEN) webSocket.send(JSON.stringify(childMessage));
|
|
1826
|
-
};
|
|
1827
|
-
const driver = createDriver({
|
|
1828
|
-
onEvents: (sessionId, events) => {
|
|
1829
|
-
for (const event of events) {
|
|
1830
|
-
const line = formatSessionLogLine(event);
|
|
1831
|
-
if (line) writeLine(line);
|
|
1832
|
-
}
|
|
1833
|
-
getOrCreate(sessionEventMap, sessionId, () => []).push(...events.filter(({ type }) => !EphemeralAgentEventTypes.includes(type)));
|
|
1834
|
-
if (events.some((event) => event.type === "SessionState" && event.state === "Closed")) sessionEventMap.delete(sessionId);
|
|
1835
|
-
send({
|
|
1836
|
-
events,
|
|
1837
|
-
sessionId,
|
|
1838
|
-
type: "Events"
|
|
1839
|
-
});
|
|
1840
|
-
},
|
|
1841
|
-
onSessionOpen: (sessionId) => {
|
|
1842
|
-
sessionEventMap.set(sessionId, []);
|
|
1843
|
-
send({
|
|
1844
|
-
sessionId,
|
|
1845
|
-
type: "SessionOpen"
|
|
1846
|
-
});
|
|
1847
|
-
},
|
|
1848
|
-
onSessionsChange: () => {
|
|
1849
|
-
send({ type: "SessionsChange" });
|
|
1850
|
-
}
|
|
1851
|
-
});
|
|
1852
|
-
const receive = async (text) => {
|
|
1853
|
-
const command = getResult(() => driverCommandSchema.parse(JSON.parse(text))).orTee((error) => {
|
|
1854
|
-
send({
|
|
1855
|
-
commandId: "",
|
|
1856
|
-
message: error.message,
|
|
1857
|
-
type: "CommandError"
|
|
1858
|
-
});
|
|
1859
|
-
}).unwrapOr(void 0);
|
|
1860
|
-
if (!command) return;
|
|
1861
|
-
await getResultAsync(() => handleCommand(driver, command)).match((sessionId) => {
|
|
1862
|
-
send({
|
|
1863
|
-
commandId: command.id,
|
|
1864
|
-
sessionId,
|
|
1865
|
-
type: "CommandResult"
|
|
1866
|
-
});
|
|
1867
|
-
}, (error) => {
|
|
1868
|
-
send({
|
|
1869
|
-
commandId: command.id,
|
|
1870
|
-
message: error.message,
|
|
1871
|
-
type: "CommandError"
|
|
1872
|
-
});
|
|
1873
|
-
});
|
|
1874
|
-
};
|
|
1875
|
-
signal.addEventListener("abort", () => {
|
|
1876
|
-
webSocket?.close();
|
|
1877
|
-
});
|
|
1878
|
-
let isRejoining = false;
|
|
1879
|
-
while (!signal.aborted) {
|
|
1880
|
-
const currentWebSocket = connect();
|
|
1881
|
-
webSocket = currentWebSocket;
|
|
1882
|
-
let isRefused = false;
|
|
1883
|
-
currentWebSocket.on("message", (data) => {
|
|
1884
|
-
taskRegistry.run(() => receive(readMessageText(data)));
|
|
1885
|
-
});
|
|
1886
|
-
currentWebSocket.on("error", noop);
|
|
1887
|
-
currentWebSocket.on("unexpected-response", () => {
|
|
1888
|
-
isRefused = true;
|
|
1889
|
-
currentWebSocket.terminate();
|
|
1890
|
-
});
|
|
1891
|
-
if (isRejoining) currentWebSocket.once("open", () => {
|
|
1892
|
-
writeLine("The host is back, and has this session again.");
|
|
1893
|
-
send({
|
|
1894
|
-
sessions: Array.from(sessionEventMap, ([sessionId, events]) => ({
|
|
1895
|
-
events,
|
|
1896
|
-
sessionId
|
|
1897
|
-
})),
|
|
1898
|
-
type: "Rejoin"
|
|
1899
|
-
});
|
|
1900
|
-
});
|
|
1901
|
-
const [code] = await once(currentWebSocket, "close");
|
|
1902
|
-
if (isRefused && isRejoining) writeLine("The host did not take this session back, so it ends here.");
|
|
1903
|
-
if (signal.aborted || isRefused || code === 4e3 || sessionEventMap.size === 0) break;
|
|
1904
|
-
if (!isRejoining) writeLine("The host went away. This session keeps running, and comes back when the host is started again.");
|
|
1905
|
-
isRejoining = true;
|
|
1906
|
-
await setTimeout$1(SESSION_WINDOW_RECONNECT_DELAY);
|
|
1907
|
-
}
|
|
1908
|
-
await driver.close();
|
|
1909
|
-
await taskRegistry.drain();
|
|
1910
|
-
};
|
|
1911
|
-
//#endregion
|
|
1912
|
-
//#region src/services/drivers/window/runSessionChild.ts
|
|
1913
|
-
const runSessionChild = async (port, writeLine) => {
|
|
1914
|
-
const secret = process.env[SESSION_SECRET_ENVIRONMENT_VARIABLE] ?? "";
|
|
1915
|
-
Reflect.deleteProperty(process.env, SESSION_SECRET_ENVIRONMENT_VARIABLE);
|
|
1916
|
-
process.title = `${SITE_NAME} session`;
|
|
1917
|
-
const stateDirectory = getStateDirectory();
|
|
1918
|
-
let isFirstConnection = true;
|
|
1919
|
-
const connect = () => {
|
|
1920
|
-
const hostPort = isFirstConnection ? port : readSessionWindows(stateDirectory).port;
|
|
1921
|
-
isFirstConnection = false;
|
|
1922
|
-
return new WebSocket(`ws://${DEFAULT_HOSTNAME}:${hostPort}`, { headers: { authorization: `Bearer ${secret}` } });
|
|
1923
|
-
};
|
|
1924
|
-
const abortController = new AbortController();
|
|
1925
|
-
const stop = () => {
|
|
1926
|
-
abortController.abort();
|
|
1927
|
-
};
|
|
1928
|
-
process.once("SIGINT", stop);
|
|
1929
|
-
process.once("SIGHUP", stop);
|
|
1930
|
-
writeLine("This window shows one session as it runs. Type to it in the page.\nTo stop it, close this window.\n");
|
|
1931
|
-
await serveSessionChild(connect, createClaudeAgentSdkDriver, writeLine, abortController.signal);
|
|
1932
|
-
};
|
|
1933
|
-
//#endregion
|
|
1934
|
-
//#region src/services/installer/checkIsHostInstalled.ts
|
|
1935
|
-
const checkIsHostInstalled = () => relative(getHostInstallDirectory(), dirname(process.execPath)) === "";
|
|
1936
|
-
//#endregion
|
|
1937
|
-
//#region src/services/installer/checkIsSchemeLaunchTampered.ts
|
|
1938
|
-
const checkIsSchemeLaunchTampered = (commandLineArguments) => commandLineArguments.length > 1 && commandLineArguments.some((commandLineArgument) => commandLineArgument.startsWith(`${HOST_SCHEME}:`));
|
|
1939
|
-
//#endregion
|
|
1940
|
-
//#region src/services/installer/getSchemeLaunch.ts
|
|
1941
|
-
const getSchemeLaunch = (argument) => {
|
|
1942
|
-
if (!URL.canParse(argument)) return void 0;
|
|
1943
|
-
const url = new URL(argument);
|
|
1944
|
-
if (url.protocol !== `${HOST_SCHEME}:`) return void 0;
|
|
1945
|
-
return { code: url.searchParams.get("code") ?? "" };
|
|
1946
|
-
};
|
|
1947
|
-
//#endregion
|
|
1948
|
-
//#region src/services/installer/installHost.ts
|
|
1949
|
-
const installHost = () => {
|
|
1950
|
-
const installDirectory = getHostInstallDirectory();
|
|
1951
|
-
const executablePath = join(installDirectory, HOST_EXECUTABLE_FILENAME);
|
|
1952
|
-
mkdirSync(installDirectory, { recursive: true });
|
|
1953
|
-
copyFileSync(process.execPath, executablePath);
|
|
1954
|
-
writeFileSync(join(installDirectory, CLAUDE_CODE_EXECUTABLE_FILENAME), new Uint8Array(getRawAsset(CLAUDE_CODE_EXECUTABLE_FILENAME)));
|
|
1955
|
-
for (const assetKey of getAssetKeys().filter((key) => key.startsWith(`${NODE_PTY_DIRECTORY_NAME}/`))) {
|
|
1956
|
-
const assetPath = join(installDirectory, ...assetKey.split("/"));
|
|
1957
|
-
mkdirSync(dirname(assetPath), { recursive: true });
|
|
1958
|
-
writeFileSync(assetPath, new Uint8Array(getRawAsset(assetKey)));
|
|
1959
|
-
}
|
|
1960
|
-
for (const registryArguments of [
|
|
1961
|
-
[
|
|
1962
|
-
HOST_SCHEME_REGISTRY_KEY,
|
|
1963
|
-
"/ve",
|
|
1964
|
-
"/d",
|
|
1965
|
-
`URL:${SITE_NAME} Host`
|
|
1966
|
-
],
|
|
1967
|
-
[
|
|
1968
|
-
HOST_SCHEME_REGISTRY_KEY,
|
|
1969
|
-
"/v",
|
|
1970
|
-
"URL Protocol",
|
|
1971
|
-
"/d",
|
|
1972
|
-
""
|
|
1973
|
-
],
|
|
1974
|
-
[
|
|
1975
|
-
win32.join(HOST_SCHEME_REGISTRY_KEY, "shell", "open", "command"),
|
|
1976
|
-
"/ve",
|
|
1977
|
-
"/d",
|
|
1978
|
-
`"${executablePath}" "%1"`
|
|
1979
|
-
]
|
|
1980
|
-
]) execFileSync("reg", [
|
|
1981
|
-
"add",
|
|
1982
|
-
...registryArguments,
|
|
1983
|
-
"/f"
|
|
1984
|
-
], { stdio: "ignore" });
|
|
1985
|
-
return installDirectory;
|
|
1986
|
-
};
|
|
1987
|
-
//#endregion
|
|
1988
|
-
//#region src/services/installer/uninstallHost.ts
|
|
1989
|
-
const uninstallHost = () => {
|
|
1990
|
-
execFileSync("reg", [
|
|
1991
|
-
"delete",
|
|
1992
|
-
HOST_SCHEME_REGISTRY_KEY,
|
|
1993
|
-
"/f"
|
|
1994
|
-
], { stdio: "ignore" });
|
|
1995
|
-
spawn("powershell.exe", [
|
|
1996
|
-
"-NoProfile",
|
|
1997
|
-
"-Command",
|
|
1998
|
-
`Start-Sleep -Seconds 2; Remove-Item -LiteralPath $env:${HOST_DIRECTORY_ENVIRONMENT_VARIABLE} -Recurse -Force`
|
|
1999
|
-
], {
|
|
2000
|
-
detached: true,
|
|
2001
|
-
env: {
|
|
2002
|
-
...process.env,
|
|
2003
|
-
[HOST_DIRECTORY_ENVIRONMENT_VARIABLE]: getHostInstallDirectory()
|
|
2004
|
-
},
|
|
2005
|
-
stdio: "ignore",
|
|
2006
|
-
windowsHide: true
|
|
2007
|
-
}).unref();
|
|
2008
|
-
};
|
|
2009
|
-
//#endregion
|
|
2010
|
-
//#region src/services/device/findDevice.ts
|
|
2011
|
-
const findDevice = (devices, credential) => {
|
|
2012
|
-
const credentialHash = hashCredential(credential);
|
|
2013
|
-
return devices.find((device) => checkIsTokenValid(credentialHash, device.credentialHash));
|
|
2014
|
-
};
|
|
2015
|
-
//#endregion
|
|
2016
|
-
//#region src/services/device/getDeviceName.ts
|
|
2017
|
-
const BrowserNames = [
|
|
2018
|
-
[/Edg\//u, "Edge"],
|
|
2019
|
-
[/Firefox\//u, "Firefox"],
|
|
2020
|
-
[/Chrome\//u, "Chrome"],
|
|
2021
|
-
[/Safari\//u, "Safari"]
|
|
2022
|
-
];
|
|
2023
|
-
const SystemNames = [
|
|
2024
|
-
[/Windows/u, "Windows"],
|
|
2025
|
-
[/Android/u, "Android"],
|
|
2026
|
-
[/iPhone|iPad/u, "iOS"],
|
|
2027
|
-
[/Mac OS X/u, "macOS"],
|
|
2028
|
-
[/Linux/u, "Linux"]
|
|
2029
|
-
];
|
|
2030
|
-
const getDeviceName = (userAgent) => {
|
|
2031
|
-
const browserName = BrowserNames.find(([pattern]) => pattern.test(userAgent))?.[1] ?? "A browser";
|
|
2032
|
-
const systemName = SystemNames.find(([pattern]) => pattern.test(userAgent))?.[1];
|
|
2033
|
-
return systemName ? `${browserName} on ${systemName}` : browserName;
|
|
2034
|
-
};
|
|
2035
|
-
//#endregion
|
|
2036
|
-
//#region src/services/handshake/getPublicKeyText.ts
|
|
2037
|
-
const getPublicKeyText = (privateKey) => createPublicKey(privateKey).export({ format: "jwk" }).x ?? "";
|
|
2038
|
-
//#endregion
|
|
2039
|
-
//#region src/services/server/answerHttpRequest.ts
|
|
2040
|
-
const answerHttpRequest = ({ headers, method }, response) => {
|
|
2041
|
-
if (method !== "OPTIONS") {
|
|
2042
|
-
response.writeHead(405).end();
|
|
2043
|
-
return;
|
|
2044
|
-
}
|
|
2045
|
-
response.setHeader("Access-Control-Allow-Origin", headers.origin ?? "*");
|
|
2046
|
-
response.setHeader("Access-Control-Allow-Private-Network", "true");
|
|
2047
|
-
response.setHeader("Access-Control-Allow-Methods", "GET");
|
|
2048
|
-
response.setHeader("Access-Control-Allow-Headers", headers["access-control-request-headers"] ?? "*");
|
|
2049
|
-
response.setHeader("Vary", "Origin");
|
|
2050
|
-
response.writeHead(204).end();
|
|
2051
|
-
};
|
|
2052
|
-
//#endregion
|
|
2053
|
-
//#region src/services/server/createEventLog.ts
|
|
2054
|
-
const createEventLog = () => {
|
|
2390
|
+
//#region src/services/server/createEventLog.ts
|
|
2391
|
+
const createEventLog = () => {
|
|
2055
2392
|
const sessionEventMap = /* @__PURE__ */ new Map();
|
|
2056
2393
|
return {
|
|
2057
2394
|
append: (sessionId, events) => {
|
|
@@ -2103,6 +2440,48 @@ const createPairingCodes = () => {
|
|
|
2103
2440
|
};
|
|
2104
2441
|
};
|
|
2105
2442
|
//#endregion
|
|
2443
|
+
//#region src/services/server/handleCommand.ts
|
|
2444
|
+
const handleCommand = async (driver, command) => {
|
|
2445
|
+
switch (command.type) {
|
|
2446
|
+
case "BackgroundTasks":
|
|
2447
|
+
await driver.backgroundTasks(command.sessionId);
|
|
2448
|
+
return "";
|
|
2449
|
+
case "CloseSession":
|
|
2450
|
+
driver.closeSession(command.sessionId);
|
|
2451
|
+
return "";
|
|
2452
|
+
case "CreateSession": return driver.createSession(command.cwd);
|
|
2453
|
+
case "Fork": return driver.forkSession(command.sessionId, command.messageUuid);
|
|
2454
|
+
case "Interrupt":
|
|
2455
|
+
await driver.interrupt(command.sessionId);
|
|
2456
|
+
return "";
|
|
2457
|
+
case "ListSessions": return "";
|
|
2458
|
+
case "PermissionVerdict":
|
|
2459
|
+
driver.resolvePermission(command.sessionId, command.requestId, command.behavior, command.message);
|
|
2460
|
+
return "";
|
|
2461
|
+
case "Prompt":
|
|
2462
|
+
driver.prompt(command.sessionId, command.text, command.attachments);
|
|
2463
|
+
return "";
|
|
2464
|
+
case "Resume": return driver.resumeSession(command.sessionId);
|
|
2465
|
+
case "ResumeAt": return driver.resumeAt(command.sessionId, command.messageUuid);
|
|
2466
|
+
case "RewindFiles":
|
|
2467
|
+
await driver.rewindFiles(command.sessionId, command.messageUuid);
|
|
2468
|
+
return "";
|
|
2469
|
+
case "SetModel":
|
|
2470
|
+
await driver.setModel(command.sessionId, command.model);
|
|
2471
|
+
return "";
|
|
2472
|
+
case "SetPermissionMode":
|
|
2473
|
+
await driver.setPermissionMode(command.sessionId, command.permissionMode);
|
|
2474
|
+
return "";
|
|
2475
|
+
case "SlashCommand":
|
|
2476
|
+
driver.runSlashCommand(command.sessionId, command.name, command.arguments);
|
|
2477
|
+
return "";
|
|
2478
|
+
case "StopTask":
|
|
2479
|
+
await driver.stopTask(command.sessionId, command.taskId);
|
|
2480
|
+
return "";
|
|
2481
|
+
default: return exhaustiveGuard(command);
|
|
2482
|
+
}
|
|
2483
|
+
};
|
|
2484
|
+
//#endregion
|
|
2106
2485
|
//#region src/services/server/handleShellCommand.ts
|
|
2107
2486
|
const handleShellCommand = async (shellRegistry, command) => {
|
|
2108
2487
|
switch (command.type) {
|
|
@@ -2455,16 +2834,6 @@ const createAgentConsoleServer = async ({ createDriver, hostKey, hostname, origi
|
|
|
2455
2834
|
};
|
|
2456
2835
|
};
|
|
2457
2836
|
//#endregion
|
|
2458
|
-
//#region src/services/shell/constants.ts
|
|
2459
|
-
const CONSOLE_LIST_AGENT_NAME = "conpty_console_list_agent";
|
|
2460
|
-
//#endregion
|
|
2461
|
-
//#region src/services/shell/runConsoleListAgent.ts
|
|
2462
|
-
const runConsoleListAgent = (shellPid) => {
|
|
2463
|
-
const installDirectory = getHostInstallDirectory();
|
|
2464
|
-
const { getConsoleProcessList } = createRequire(join(installDirectory, "package.json"))(join(installDirectory, NODE_PTY_DIRECTORY_NAME, "prebuilds", "win32-x64", "conpty_console_list.node"));
|
|
2465
|
-
process.send?.({ consoleProcessList: getConsoleProcessList(shellPid) });
|
|
2466
|
-
};
|
|
2467
|
-
//#endregion
|
|
2468
2837
|
//#region src/services/shell/getShellFile.ts
|
|
2469
2838
|
const POWERSHELL_FILE = "pwsh.exe";
|
|
2470
2839
|
const WINDOWS_POWERSHELL_FILE = "powershell.exe";
|
|
@@ -2495,4 +2864,303 @@ const spawnPtyShell = async ({ cols, cwd, rows }) => {
|
|
|
2495
2864
|
});
|
|
2496
2865
|
};
|
|
2497
2866
|
//#endregion
|
|
2498
|
-
|
|
2867
|
+
//#region src/services/cli/serveHost.ts
|
|
2868
|
+
const serveHost = async ({ hostname, origin, port, schemeLaunch }) => {
|
|
2869
|
+
installDownloadedHost();
|
|
2870
|
+
const stateDirectory = getStateDirectory();
|
|
2871
|
+
const hostKey = readHostKey(stateDirectory);
|
|
2872
|
+
const reachableHostname = getReachableHostname(hostname);
|
|
2873
|
+
const server = await getResultAsync(() => createAgentConsoleServer({
|
|
2874
|
+
createDriver: (callbacks) => process.platform === "win32" && hostname === "127.0.0.1" ? createWindowDriver(callbacks, {
|
|
2875
|
+
launchSessionWindow,
|
|
2876
|
+
stateDirectory,
|
|
2877
|
+
writeLine
|
|
2878
|
+
}) : createClaudeAgentSdkDriver(callbacks),
|
|
2879
|
+
hostKey,
|
|
2880
|
+
hostname,
|
|
2881
|
+
origin,
|
|
2882
|
+
port,
|
|
2883
|
+
spawnShell: spawnPtyShell,
|
|
2884
|
+
stateDirectory,
|
|
2885
|
+
writeLine
|
|
2886
|
+
})).match((agentConsoleServer) => agentConsoleServer, async (error) => {
|
|
2887
|
+
if (!schemeLaunch || !("code" in error) || error.code !== "EADDRINUSE") throw error;
|
|
2888
|
+
const { code } = schemeLaunch;
|
|
2889
|
+
if (await sendToRunningHost(reachableHostname, port, hostKey, code ? (signature) => ({
|
|
2890
|
+
code,
|
|
2891
|
+
signature,
|
|
2892
|
+
type: "HandOff"
|
|
2893
|
+
}) : void 0)) {
|
|
2894
|
+
process.stdout.write("The host is already running in another window.\n");
|
|
2895
|
+
process.exit(0);
|
|
2896
|
+
}
|
|
2897
|
+
process.stderr.write(`Another program is using port ${port}, so the host cannot start. Close it and connect again.\n`);
|
|
2898
|
+
process.exit(1);
|
|
2899
|
+
});
|
|
2900
|
+
if (schemeLaunch) {
|
|
2901
|
+
if (schemeLaunch.code) server.addPairingCode(schemeLaunch.code, SCHEME_PAIRING_CODE_DURATION);
|
|
2902
|
+
process.stdout.write("The host is running. Keep this window open.\n\nTo stop, close this window.\n");
|
|
2903
|
+
} else {
|
|
2904
|
+
const code = randomBytes(32).toString("base64url");
|
|
2905
|
+
server.addPairingCode(code, PRINTED_PAIRING_CODE_DURATION);
|
|
2906
|
+
const hostAddress = `ws://${reachableHostname}:${server.port}`;
|
|
2907
|
+
const pairingUrl = `${origin}${RoutePath.Genshin}#${new URLSearchParams({
|
|
2908
|
+
[PAIRING_CODE_PARAMETER]: code,
|
|
2909
|
+
[PAIRING_HASH_PARAMETER]: hostAddress
|
|
2910
|
+
})}`;
|
|
2911
|
+
process.stdout.write(`The host is running. Keep this window open.\n\nTo connect, press Connect on the page, or hold Ctrl and click this link in the next ten minutes:\n ${pairingUrl}\n\nTo add this machine from another computer, enter its address and this code in the page's sessions tab:\n ${code}\n\nTo stop, close this window.\n`);
|
|
2912
|
+
}
|
|
2913
|
+
await Promise.race([once(process, "SIGINT"), once(process, "SIGHUP")]);
|
|
2914
|
+
await server.close();
|
|
2915
|
+
};
|
|
2916
|
+
//#endregion
|
|
2917
|
+
//#region src/services/cli/commands/serveCommand.ts
|
|
2918
|
+
const serveCommand = defineCommand({
|
|
2919
|
+
args: { ...hostArgs },
|
|
2920
|
+
meta: {
|
|
2921
|
+
description: "Start the host and print the link that pairs a page with it",
|
|
2922
|
+
name: "serve"
|
|
2923
|
+
},
|
|
2924
|
+
run: ({ args }) => serveHost({
|
|
2925
|
+
hostname: args.hostname,
|
|
2926
|
+
origin: args.origin,
|
|
2927
|
+
port: Number(args.port)
|
|
2928
|
+
})
|
|
2929
|
+
});
|
|
2930
|
+
//#endregion
|
|
2931
|
+
//#region src/services/drivers/window/formatSessionLogLine.ts
|
|
2932
|
+
const formatSessionLogLine = (event) => {
|
|
2933
|
+
switch (event.type) {
|
|
2934
|
+
case "AssistantMessage": return event.parentToolUseId ? "" : `Claude: ${event.text}`;
|
|
2935
|
+
case "HostError": return `Error: ${event.message}`;
|
|
2936
|
+
case "PermissionRequest": return `Claude asks to use ${event.toolName}. Answer in the page.`;
|
|
2937
|
+
case "ToolUse": return event.parentToolUseId ? "" : `Claude uses ${event.name}`;
|
|
2938
|
+
case "UserMessage": return event.parentToolUseId ? "" : `You: ${event.text}`;
|
|
2939
|
+
default: return "";
|
|
2940
|
+
}
|
|
2941
|
+
};
|
|
2942
|
+
//#endregion
|
|
2943
|
+
//#region src/services/drivers/window/serveSessionChild.ts
|
|
2944
|
+
const serveSessionChild = async (connect, createDriver, writeLine, signal) => {
|
|
2945
|
+
const taskRegistry = createTaskRegistry();
|
|
2946
|
+
const sessionEventMap = /* @__PURE__ */ new Map();
|
|
2947
|
+
let webSocket;
|
|
2948
|
+
const send = (childMessage) => {
|
|
2949
|
+
if (webSocket?.readyState === WebSocket.OPEN) webSocket.send(JSON.stringify(childMessage));
|
|
2950
|
+
};
|
|
2951
|
+
const driver = createDriver({
|
|
2952
|
+
onEvents: (sessionId, events) => {
|
|
2953
|
+
for (const event of events) {
|
|
2954
|
+
const line = formatSessionLogLine(event);
|
|
2955
|
+
if (line) writeLine(line);
|
|
2956
|
+
}
|
|
2957
|
+
getOrCreate(sessionEventMap, sessionId, () => []).push(...events.filter(({ type }) => !EphemeralAgentEventTypes.includes(type)));
|
|
2958
|
+
if (events.some((event) => event.type === "SessionState" && event.state === "Closed")) sessionEventMap.delete(sessionId);
|
|
2959
|
+
send({
|
|
2960
|
+
events,
|
|
2961
|
+
sessionId,
|
|
2962
|
+
type: "Events"
|
|
2963
|
+
});
|
|
2964
|
+
},
|
|
2965
|
+
onSessionOpen: (sessionId) => {
|
|
2966
|
+
sessionEventMap.set(sessionId, []);
|
|
2967
|
+
send({
|
|
2968
|
+
sessionId,
|
|
2969
|
+
type: "SessionOpen"
|
|
2970
|
+
});
|
|
2971
|
+
},
|
|
2972
|
+
onSessionsChange: () => {
|
|
2973
|
+
send({ type: "SessionsChange" });
|
|
2974
|
+
}
|
|
2975
|
+
});
|
|
2976
|
+
const receive = async (text) => {
|
|
2977
|
+
const command = getResult(() => driverCommandSchema.parse(JSON.parse(text))).orTee((error) => {
|
|
2978
|
+
send({
|
|
2979
|
+
commandId: "",
|
|
2980
|
+
message: error.message,
|
|
2981
|
+
type: "CommandError"
|
|
2982
|
+
});
|
|
2983
|
+
}).unwrapOr(void 0);
|
|
2984
|
+
if (!command) return;
|
|
2985
|
+
await getResultAsync(() => handleCommand(driver, command)).match((sessionId) => {
|
|
2986
|
+
send({
|
|
2987
|
+
commandId: command.id,
|
|
2988
|
+
sessionId,
|
|
2989
|
+
type: "CommandResult"
|
|
2990
|
+
});
|
|
2991
|
+
}, (error) => {
|
|
2992
|
+
send({
|
|
2993
|
+
commandId: command.id,
|
|
2994
|
+
message: error.message,
|
|
2995
|
+
type: "CommandError"
|
|
2996
|
+
});
|
|
2997
|
+
});
|
|
2998
|
+
};
|
|
2999
|
+
signal.addEventListener("abort", () => {
|
|
3000
|
+
webSocket?.close();
|
|
3001
|
+
});
|
|
3002
|
+
let isRejoining = false;
|
|
3003
|
+
while (!signal.aborted) {
|
|
3004
|
+
const currentWebSocket = connect();
|
|
3005
|
+
webSocket = currentWebSocket;
|
|
3006
|
+
let isRefused = false;
|
|
3007
|
+
currentWebSocket.on("message", (data) => {
|
|
3008
|
+
taskRegistry.run(() => receive(readMessageText(data)));
|
|
3009
|
+
});
|
|
3010
|
+
currentWebSocket.on("error", noop);
|
|
3011
|
+
currentWebSocket.on("unexpected-response", () => {
|
|
3012
|
+
isRefused = true;
|
|
3013
|
+
currentWebSocket.terminate();
|
|
3014
|
+
});
|
|
3015
|
+
if (isRejoining) currentWebSocket.once("open", () => {
|
|
3016
|
+
writeLine("The host is back, and has this session again.");
|
|
3017
|
+
send({
|
|
3018
|
+
sessions: Array.from(sessionEventMap, ([sessionId, events]) => ({
|
|
3019
|
+
events,
|
|
3020
|
+
sessionId
|
|
3021
|
+
})),
|
|
3022
|
+
type: "Rejoin"
|
|
3023
|
+
});
|
|
3024
|
+
});
|
|
3025
|
+
const [code] = await once(currentWebSocket, "close");
|
|
3026
|
+
if (isRefused && isRejoining) writeLine("The host did not take this session back, so it ends here.");
|
|
3027
|
+
if (signal.aborted || isRefused || code === 4e3 || sessionEventMap.size === 0) break;
|
|
3028
|
+
if (!isRejoining) writeLine("The host went away. This session keeps running, and comes back when the host is started again.");
|
|
3029
|
+
isRejoining = true;
|
|
3030
|
+
await setTimeout$1(SESSION_WINDOW_RECONNECT_DELAY);
|
|
3031
|
+
}
|
|
3032
|
+
await driver.close();
|
|
3033
|
+
await taskRegistry.drain();
|
|
3034
|
+
};
|
|
3035
|
+
//#endregion
|
|
3036
|
+
//#region src/services/drivers/window/runSessionChild.ts
|
|
3037
|
+
const runSessionChild = async (port, writeLine) => {
|
|
3038
|
+
const secret = process.env[SESSION_SECRET_ENVIRONMENT_VARIABLE] ?? "";
|
|
3039
|
+
Reflect.deleteProperty(process.env, SESSION_SECRET_ENVIRONMENT_VARIABLE);
|
|
3040
|
+
process.title = `${SITE_NAME} session`;
|
|
3041
|
+
const stateDirectory = getStateDirectory();
|
|
3042
|
+
let isFirstConnection = true;
|
|
3043
|
+
const connect = () => {
|
|
3044
|
+
const hostPort = isFirstConnection ? port : readSessionWindows(stateDirectory).port;
|
|
3045
|
+
isFirstConnection = false;
|
|
3046
|
+
return new WebSocket(`ws://${DEFAULT_HOSTNAME}:${hostPort}`, { headers: { authorization: `Bearer ${secret}` } });
|
|
3047
|
+
};
|
|
3048
|
+
const abortController = new AbortController();
|
|
3049
|
+
const stop = () => {
|
|
3050
|
+
abortController.abort();
|
|
3051
|
+
};
|
|
3052
|
+
process.once("SIGINT", stop);
|
|
3053
|
+
process.once("SIGHUP", stop);
|
|
3054
|
+
writeLine("This window shows one session as it runs. Type to it in the page.\nTo stop it, close this window.\n");
|
|
3055
|
+
await serveSessionChild(connect, createClaudeAgentSdkDriver, writeLine, abortController.signal);
|
|
3056
|
+
};
|
|
3057
|
+
//#endregion
|
|
3058
|
+
//#region src/services/cli/commands/sessionCommand.ts
|
|
3059
|
+
const sessionCommand = defineCommand({
|
|
3060
|
+
args: { port: {
|
|
3061
|
+
description: "The port of the host that started this window",
|
|
3062
|
+
required: true,
|
|
3063
|
+
type: "string"
|
|
3064
|
+
} },
|
|
3065
|
+
meta: {
|
|
3066
|
+
description: "Serve one session in a window of its own, for the host that started it",
|
|
3067
|
+
name: SESSION_SUBCOMMAND
|
|
3068
|
+
},
|
|
3069
|
+
run: async ({ args }) => {
|
|
3070
|
+
await runSessionChild(Number(args.port), writeLine);
|
|
3071
|
+
process.exit(0);
|
|
3072
|
+
}
|
|
3073
|
+
});
|
|
3074
|
+
//#endregion
|
|
3075
|
+
//#region src/services/installer/uninstallHost.ts
|
|
3076
|
+
const uninstallHost = () => {
|
|
3077
|
+
execFileSync("reg", [
|
|
3078
|
+
"delete",
|
|
3079
|
+
HOST_SCHEME_REGISTRY_KEY,
|
|
3080
|
+
"/f"
|
|
3081
|
+
], { stdio: "ignore" });
|
|
3082
|
+
spawn("powershell.exe", [
|
|
3083
|
+
"-NoProfile",
|
|
3084
|
+
"-Command",
|
|
3085
|
+
`Start-Sleep -Seconds 2; Remove-Item -LiteralPath $env:${HOST_DIRECTORY_ENVIRONMENT_VARIABLE} -Recurse -Force`
|
|
3086
|
+
], {
|
|
3087
|
+
detached: true,
|
|
3088
|
+
env: {
|
|
3089
|
+
...process.env,
|
|
3090
|
+
[HOST_DIRECTORY_ENVIRONMENT_VARIABLE]: getHostInstallDirectory()
|
|
3091
|
+
},
|
|
3092
|
+
stdio: "ignore",
|
|
3093
|
+
windowsHide: true
|
|
3094
|
+
}).unref();
|
|
3095
|
+
};
|
|
3096
|
+
//#endregion
|
|
3097
|
+
//#region src/services/cli/commands/uninstallCommand.ts
|
|
3098
|
+
const uninstallCommand = defineCommand({
|
|
3099
|
+
meta: {
|
|
3100
|
+
description: "Remove the installed host, its link and its folder",
|
|
3101
|
+
name: "uninstall"
|
|
3102
|
+
},
|
|
3103
|
+
run: () => {
|
|
3104
|
+
uninstallHost();
|
|
3105
|
+
process.stdout.write("The host is uninstalled, and its folder is removed once this window closes.\n");
|
|
3106
|
+
process.exit(0);
|
|
3107
|
+
}
|
|
3108
|
+
});
|
|
3109
|
+
//#endregion
|
|
3110
|
+
//#region src/services/installer/checkIsSchemeLaunchTampered.ts
|
|
3111
|
+
const checkIsSchemeLaunchTampered = (commandLineArguments) => commandLineArguments.length > 1 && commandLineArguments.some((commandLineArgument) => commandLineArgument.startsWith(`${HOST_SCHEME}:`));
|
|
3112
|
+
//#endregion
|
|
3113
|
+
//#region src/services/installer/getSchemeLaunch.ts
|
|
3114
|
+
const getSchemeLaunch = (argument) => {
|
|
3115
|
+
if (!URL.canParse(argument)) return void 0;
|
|
3116
|
+
const url = new URL(argument);
|
|
3117
|
+
if (url.protocol !== `${HOST_SCHEME}:`) return void 0;
|
|
3118
|
+
return { code: url.searchParams.get("code") ?? "" };
|
|
3119
|
+
};
|
|
3120
|
+
//#endregion
|
|
3121
|
+
//#region src/services/shell/constants.ts
|
|
3122
|
+
const CONSOLE_LIST_AGENT_NAME = "conpty_console_list_agent";
|
|
3123
|
+
//#endregion
|
|
3124
|
+
//#region src/services/shell/runConsoleListAgent.ts
|
|
3125
|
+
const runConsoleListAgent = (shellPid) => {
|
|
3126
|
+
const installDirectory = getHostInstallDirectory();
|
|
3127
|
+
const { getConsoleProcessList } = createRequire(join(installDirectory, "package.json"))(join(installDirectory, NODE_PTY_DIRECTORY_NAME, "prebuilds", "win32-x64", "conpty_console_list.node"));
|
|
3128
|
+
process.send?.({ consoleProcessList: getConsoleProcessList(shellPid) });
|
|
3129
|
+
};
|
|
3130
|
+
//#endregion
|
|
3131
|
+
//#region src/services/cli/commands/agentConsoleServerCommand.ts
|
|
3132
|
+
const agentConsoleServerCommand = defineCommand({
|
|
3133
|
+
default: "serve",
|
|
3134
|
+
meta: {
|
|
3135
|
+
description: "The agent console's host: Claude Code sessions for a paired page",
|
|
3136
|
+
name: "agent-console-server"
|
|
3137
|
+
},
|
|
3138
|
+
setup: async ({ rawArgs }) => {
|
|
3139
|
+
if (checkIsSchemeLaunchTampered(rawArgs)) {
|
|
3140
|
+
process.stderr.write("This link tried to start the host with settings of its own, so it was not started.\n");
|
|
3141
|
+
process.exit(1);
|
|
3142
|
+
}
|
|
3143
|
+
const [firstArgument = "", secondArgument = ""] = rawArgs;
|
|
3144
|
+
if (basename(firstArgument) === "conpty_console_list_agent") {
|
|
3145
|
+
runConsoleListAgent(Number(secondArgument));
|
|
3146
|
+
process.exit(0);
|
|
3147
|
+
}
|
|
3148
|
+
const schemeLaunch = getSchemeLaunch(firstArgument);
|
|
3149
|
+
if (!schemeLaunch) return;
|
|
3150
|
+
await serveHost({
|
|
3151
|
+
hostname: DEFAULT_HOSTNAME,
|
|
3152
|
+
origin: DEFAULT_APP_ORIGIN,
|
|
3153
|
+
port: DEFAULT_PORT,
|
|
3154
|
+
schemeLaunch
|
|
3155
|
+
});
|
|
3156
|
+
process.exit(0);
|
|
3157
|
+
},
|
|
3158
|
+
subCommands: {
|
|
3159
|
+
devices: devicesCommand,
|
|
3160
|
+
serve: serveCommand,
|
|
3161
|
+
[SESSION_SUBCOMMAND]: sessionCommand,
|
|
3162
|
+
uninstall: uninstallCommand
|
|
3163
|
+
}
|
|
3164
|
+
});
|
|
3165
|
+
//#endregion
|
|
3166
|
+
export { createBaseChildMessageSchema as $, DEVICES_FILENAME as $t, checkIsTokenValid as A, toPermissionRequestEvent as At, SESSION_WINDOW_RECONNECT_DELAY as B, runMain as Bt, createEventLog as C, toUnknownEvent as Ct, findDevice as D, createFileOriginTracker as Dt, getDeviceName as E, createStreamTracker as Et, SESSION_SECRET_BYTE_LENGTH as F, SETTING_SOURCES as Ft, childSessionsChangeMessageSchema as G, signNonce as Gt, hashCredential as H, sendToRunningHost as Ht, SESSION_SECRET_ENVIRONMENT_VARIABLE as I, closeOpenSession as It, childEventsMessageSchema as J, readDevices as Jt, childSessionOpenMessageSchema as K, checkIsSignatureValid as Kt, SESSION_SUBCOMMAND as L, toSessionClosedEvents as Lt, sendChildCommand as M, DEFAULT_DENY_MESSAGE as Mt, readSessionWindows as N, IGNORED_SDK_MESSAGE_TYPES as Nt, launchSessionWindow as O, createPermissionBridge as Ot, sessionWindowsSchema as P, SESSION_LIST_LIMIT as Pt, childMessageTypeSchema as Q, getStateDirectory as Qt, SESSION_WINDOWS_FILENAME as R, getEventId as Rt, createPairingCodes as S, mapAssistantContent as St, getPublicKeyText as T, TodoToolName as Tt, SessionWindowCloseCode as U, readMessageText as Ut, SESSION_WINDOW_REJOIN_DURATION as V, runDevicesCommand as Vt, childMessageSchema as W, HOST_PROBE_TIMEOUT_MS as Wt, childCommandErrorMessageSchema as X, readHostKey as Xt, childCommandResultMessageSchema as Y, deviceSchema as Yt, ChildMessageType as Z, writeStateFile as Zt, createAgentConsoleServer as _, mapUserContent as _t, checkIsSchemeLaunchTampered as a, installDownloadedHost as an, createSessionOpener as at, handleShellCommand as b, mapTaskMessage as bt, sessionCommand as c, getHostInstallDirectory as cn, toContextUsageEvent as ct, formatSessionLogLine as d, HOST_EXECUTABLE_FILENAME as dn, readToolUseResultMap as dt, HOST_KEY_FILENAME as en, createClaudeAgentSdkDriver as et, serveCommand as f, HOST_INSTALL_DIRECTORY_SEGMENTS as fn, readSessionHistory as ft, getShellFile as g, getReachableHostname as gn, parseMessageBody as gt, loadNodePty as h, hostArgs as hn, toSessionInitEvent as ht, getSchemeLaunch as i, writeLine as in, listSessionSummaries as it, takeChildReply as j, createInputQueue as jt, createWindowDriver as k, toPermissionResult as kt, runSessionChild as l, CLAUDE_CODE_EXECUTABLE_FILENAME as ln, toCapabilitiesEvent as lt, spawnPtyShell as m, NODE_PTY_DIRECTORY_NAME as mn, toTurnResultEvents as mt, runConsoleListAgent as n, SECRET_BYTE_LENGTH as nn, toContentBlockParam as nt, uninstallCommand as o, installHost as on, getClaudeCodeExecutablePath as ot, serveHost as p, HOST_SCHEME_REGISTRY_KEY as pn, createSdkMessageMapper as pt, childRejoinMessageSchema as q, writeDevices as qt, CONSOLE_LIST_AGENT_NAME as r, STATE_DIRECTORY_NAME as rn, readSessionCwd as rt, uninstallHost as s, checkIsHostInstalled as sn, watchSession as st, agentConsoleServerCommand as t, PRINTED_PAIRING_CODE_DURATION as tn, createTaskRegistry as tt, serveSessionChild as u, HOST_DIRECTORY_ENVIRONMENT_VARIABLE as un, getSessionTitle as ut, createShellRegistry as v, toToolResultText as vt, answerHttpRequest as w, createTodoTracker as wt, handleCommand as x, mapHookMessage as xt, sendServerMessage as y, mapSystemMessage as yt, SESSION_WINDOW_CONNECT_TIMEOUT as z, devicesCommand as zt };
|