@agentconnect/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3020 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import path12 from "path";
5
+ import { promises as fs7 } from "fs";
6
+ import { createPrivateKey, createPublicKey as createPublicKey2, sign as signData } from "crypto";
7
+
8
+ // src/host.ts
9
+ import http from "http";
10
+ import { WebSocketServer } from "ws";
11
+ import { spawn as spawn4 } from "child_process";
12
+ import fs2 from "fs";
13
+ import { promises as fsp } from "fs";
14
+ import net from "net";
15
+ import path5 from "path";
16
+
17
+ // src/providers/claude.ts
18
+ import { spawn as spawn2 } from "child_process";
19
+ import { access, mkdir, readFile, rm, writeFile } from "fs/promises";
20
+ import os2 from "os";
21
+ import path2 from "path";
22
+
23
+ // src/providers/utils.ts
24
+ import { spawn } from "child_process";
25
+ import { existsSync } from "fs";
26
+ import os from "os";
27
+ import path from "path";
28
+ var DEBUG_ENABLED = Boolean(process.env.AGENTCONNECT_DEBUG?.trim());
29
+ function debugLog(scope, message, details) {
30
+ if (!DEBUG_ENABLED) return;
31
+ let suffix = "";
32
+ if (details) {
33
+ try {
34
+ suffix = ` ${JSON.stringify(details)}`;
35
+ } catch {
36
+ suffix = "";
37
+ }
38
+ }
39
+ console.log(`[AgentConnect][${scope}] ${message}${suffix}`);
40
+ }
41
+ function splitCommand(value) {
42
+ if (!value) return { command: "", args: [] };
43
+ if (Array.isArray(value)) return { command: value[0] ?? "", args: value.slice(1) };
44
+ const input = String(value).trim();
45
+ const parts = [];
46
+ let current = "";
47
+ let quote = null;
48
+ for (let i = 0; i < input.length; i += 1) {
49
+ const char = input[i];
50
+ if (quote) {
51
+ if (char === quote) {
52
+ quote = null;
53
+ } else {
54
+ current += char;
55
+ }
56
+ continue;
57
+ }
58
+ if (char === '"' || char === "'") {
59
+ quote = char;
60
+ continue;
61
+ }
62
+ if (char === " ") {
63
+ if (current) {
64
+ parts.push(current);
65
+ current = "";
66
+ }
67
+ continue;
68
+ }
69
+ current += char;
70
+ }
71
+ if (current) parts.push(current);
72
+ return { command: parts[0] ?? "", args: parts.slice(1) };
73
+ }
74
+ function resolveWindowsCommand(command2) {
75
+ if (process.platform !== "win32") return command2;
76
+ if (!command2) return command2;
77
+ if (command2.endsWith(".cmd") || command2.endsWith(".exe") || command2.includes("\\")) {
78
+ return command2;
79
+ }
80
+ return `${command2}.cmd`;
81
+ }
82
+ function getCommonBinPaths() {
83
+ const home = os.homedir();
84
+ const bunInstall = process.env.BUN_INSTALL || path.join(home, ".bun");
85
+ const pnpmHome = process.env.PNPM_HOME || path.join(home, "Library", "pnpm");
86
+ const npmPrefix = process.env.NPM_CONFIG_PREFIX;
87
+ const npmBin = npmPrefix ? path.join(npmPrefix, "bin") : "";
88
+ const claudeLocal = process.env.CLAUDE_CONFIG_DIR ? path.join(process.env.CLAUDE_CONFIG_DIR, "local") : path.join(home, ".claude", "local");
89
+ return [
90
+ path.join(bunInstall, "bin"),
91
+ pnpmHome,
92
+ path.join(home, ".local", "bin"),
93
+ claudeLocal,
94
+ path.join(home, ".claude", "bin"),
95
+ npmBin,
96
+ "/opt/homebrew/bin",
97
+ "/usr/local/bin",
98
+ "/usr/bin",
99
+ "/bin"
100
+ ].filter(Boolean);
101
+ }
102
+ function getCommandCandidates(command2) {
103
+ if (process.platform !== "win32") return [command2];
104
+ if (command2.endsWith(".cmd") || command2.endsWith(".exe") || command2.endsWith(".bat")) {
105
+ return [command2];
106
+ }
107
+ return [command2, `${command2}.cmd`, `${command2}.exe`, `${command2}.bat`];
108
+ }
109
+ function resolveCommandPath(command2) {
110
+ if (!command2) return null;
111
+ if (command2.includes("/") || command2.includes("\\")) {
112
+ return existsSync(command2) ? command2 : null;
113
+ }
114
+ const candidates = getCommandCandidates(command2);
115
+ const searchPaths = /* @__PURE__ */ new Set();
116
+ const pathEntries = process.env.PATH ? process.env.PATH.split(path.delimiter) : [];
117
+ for (const entry of pathEntries) {
118
+ if (entry) searchPaths.add(entry);
119
+ }
120
+ for (const entry of getCommonBinPaths()) {
121
+ if (entry) searchPaths.add(entry);
122
+ }
123
+ for (const dir of searchPaths) {
124
+ for (const candidate of candidates) {
125
+ const fullPath = path.join(dir, candidate);
126
+ if (existsSync(fullPath)) return fullPath;
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+ function commandExists(command2) {
132
+ return Boolean(resolveCommandPath(command2));
133
+ }
134
+ function runCommand(command2, args2, options = {}) {
135
+ return new Promise((resolve) => {
136
+ const { input, timeoutMs, ...spawnOptions } = options;
137
+ if (!command2) {
138
+ resolve({ code: -1, stdout: "", stderr: "Command is empty" });
139
+ return;
140
+ }
141
+ const resolved = resolveCommandPath(command2);
142
+ if (!resolved) {
143
+ resolve({ code: 127, stdout: "", stderr: `Executable not found in PATH: "${command2}"` });
144
+ return;
145
+ }
146
+ const child = spawn(resolved, args2, {
147
+ ...spawnOptions,
148
+ stdio: ["pipe", "pipe", "pipe"]
149
+ });
150
+ let stdout = "";
151
+ let stderr = "";
152
+ let timeout;
153
+ if (typeof timeoutMs === "number" && timeoutMs > 0) {
154
+ timeout = setTimeout(() => {
155
+ child.kill();
156
+ resolve({ code: -1, stdout, stderr: `${stderr}Command timed out` });
157
+ }, timeoutMs);
158
+ }
159
+ if (input) {
160
+ child.stdin?.write(input);
161
+ }
162
+ child.stdin?.end();
163
+ child.stdout?.on("data", (chunk) => {
164
+ stdout += chunk.toString("utf8");
165
+ });
166
+ child.stderr?.on("data", (chunk) => {
167
+ stderr += chunk.toString("utf8");
168
+ });
169
+ child.on("error", (err) => {
170
+ if (timeout) clearTimeout(timeout);
171
+ resolve({ code: -1, stdout, stderr: `${stderr}${err.message}` });
172
+ });
173
+ child.on("close", (code) => {
174
+ if (timeout) clearTimeout(timeout);
175
+ resolve({ code: code ?? 0, stdout, stderr });
176
+ });
177
+ });
178
+ }
179
+ function createLineParser(onLine) {
180
+ let buffer = "";
181
+ return (chunk) => {
182
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
183
+ buffer += text;
184
+ let idx;
185
+ while ((idx = buffer.indexOf("\n")) !== -1) {
186
+ const line = buffer.slice(0, idx).trim();
187
+ buffer = buffer.slice(idx + 1);
188
+ if (line) onLine(line);
189
+ }
190
+ };
191
+ }
192
+ async function checkCommandVersion(command2, argsList) {
193
+ for (const args2 of argsList) {
194
+ const result = await runCommand(command2, args2);
195
+ if (result.code === 0) {
196
+ const version = result.stdout.trim().split("\n")[0] ?? "";
197
+ return { ok: true, version };
198
+ }
199
+ }
200
+ return { ok: false, version: "" };
201
+ }
202
+ var packageManagerCache = { detected: null };
203
+ async function isCommandAvailable(command2) {
204
+ const result = await runCommand(command2, ["--version"]);
205
+ return result.code === 0;
206
+ }
207
+ async function detectPackageManager() {
208
+ if (packageManagerCache.detected) {
209
+ return packageManagerCache.detected;
210
+ }
211
+ if (await isCommandAvailable("bun")) {
212
+ packageManagerCache.detected = "bun";
213
+ return "bun";
214
+ }
215
+ if (await isCommandAvailable("pnpm")) {
216
+ packageManagerCache.detected = "pnpm";
217
+ return "pnpm";
218
+ }
219
+ if (await isCommandAvailable("npm")) {
220
+ packageManagerCache.detected = "npm";
221
+ return "npm";
222
+ }
223
+ if (process.platform === "darwin" && await isCommandAvailable("brew")) {
224
+ packageManagerCache.detected = "brew";
225
+ return "brew";
226
+ }
227
+ packageManagerCache.detected = "unknown";
228
+ return "unknown";
229
+ }
230
+ function getInstallCommand(packageManager, packageName) {
231
+ switch (packageManager) {
232
+ case "bun":
233
+ return { command: "bun", args: ["add", "-g", packageName] };
234
+ case "pnpm":
235
+ return { command: "pnpm", args: ["add", "-g", packageName] };
236
+ case "npm":
237
+ return { command: "npm", args: ["install", "-g", packageName] };
238
+ case "brew":
239
+ return { command: "brew", args: ["install", packageName] };
240
+ default:
241
+ return { command: "", args: [] };
242
+ }
243
+ }
244
+ async function buildInstallCommandAuto(packageName) {
245
+ const pm = await detectPackageManager();
246
+ const cmd = getInstallCommand(pm, packageName);
247
+ return { ...cmd, packageManager: pm };
248
+ }
249
+ function buildInstallCommand(envVar, fallback) {
250
+ const value = process.env[envVar] || fallback;
251
+ return splitCommand(value);
252
+ }
253
+ function buildLoginCommand(envVar, fallback) {
254
+ const value = process.env[envVar] || fallback;
255
+ return splitCommand(value);
256
+ }
257
+ function buildStatusCommand(envVar, fallback) {
258
+ const value = process.env[envVar] || fallback;
259
+ return splitCommand(value);
260
+ }
261
+
262
+ // src/providers/claude.ts
263
+ var CLAUDE_PACKAGE = "@anthropic-ai/claude-code";
264
+ var INSTALL_UNIX = "curl -fsSL https://claude.ai/install.sh | bash";
265
+ var INSTALL_WINDOWS_PS = "irm https://claude.ai/install.ps1 | iex";
266
+ var INSTALL_WINDOWS_CMD = "curl -fsSL https://claude.ai/install.cmd -o install.cmd && install.cmd && del install.cmd";
267
+ var DEFAULT_LOGIN = "";
268
+ var DEFAULT_STATUS = "";
269
+ var CLAUDE_MODELS_CACHE_TTL_MS = 6e4;
270
+ var CLAUDE_RECENT_MODELS_CACHE_TTL_MS = 6e4;
271
+ var claudeModelsCache = null;
272
+ var claudeModelsCacheAt = 0;
273
+ var claudeRecentModelsCache = null;
274
+ var claudeRecentModelsCacheAt = 0;
275
+ var DEFAULT_CLAUDE_MODELS = [
276
+ {
277
+ id: "default",
278
+ displayName: "Default \xB7 Opus 4.5"
279
+ },
280
+ {
281
+ id: "sonnet",
282
+ displayName: "Sonnet 4.5"
283
+ },
284
+ {
285
+ id: "haiku",
286
+ displayName: "Haiku 4.5"
287
+ },
288
+ {
289
+ id: "opus",
290
+ displayName: "Opus"
291
+ }
292
+ ];
293
+ function getClaudeCommand() {
294
+ const override = process.env.AGENTCONNECT_CLAUDE_COMMAND;
295
+ const base = override || "claude";
296
+ const resolved = resolveCommandPath(base);
297
+ return resolved || resolveWindowsCommand(base);
298
+ }
299
+ function getClaudeConfigDir() {
300
+ return process.env.CLAUDE_CONFIG_DIR || path2.join(os2.homedir(), ".claude");
301
+ }
302
+ function formatClaudeDisplayName(modelId) {
303
+ const value = modelId.trim();
304
+ if (!value.startsWith("claude-")) return value;
305
+ const parts = value.replace(/^claude-/, "").split("-").filter(Boolean);
306
+ if (!parts.length) return value;
307
+ const family = parts[0];
308
+ const numeric = parts.slice(1).filter((entry) => /^\d+$/.test(entry));
309
+ let version = "";
310
+ if (numeric.length >= 2) {
311
+ version = `${numeric[0]}.${numeric[1]}`;
312
+ } else if (numeric.length === 1) {
313
+ version = numeric[0];
314
+ }
315
+ const familyLabel = family.charAt(0).toUpperCase() + family.slice(1);
316
+ return `Claude ${familyLabel}${version ? ` ${version}` : ""}`;
317
+ }
318
+ async function readClaudeStatsModels() {
319
+ const statsPath = path2.join(getClaudeConfigDir(), "stats-cache.json");
320
+ try {
321
+ const raw = await readFile(statsPath, "utf8");
322
+ const parsed = JSON.parse(raw);
323
+ const usage = parsed?.modelUsage;
324
+ if (!usage || typeof usage !== "object") return [];
325
+ return Object.keys(usage).filter(Boolean);
326
+ } catch {
327
+ return [];
328
+ }
329
+ }
330
+ async function listClaudeModels() {
331
+ if (claudeModelsCache && Date.now() - claudeModelsCacheAt < CLAUDE_MODELS_CACHE_TTL_MS) {
332
+ return claudeModelsCache;
333
+ }
334
+ const list = DEFAULT_CLAUDE_MODELS.map((entry) => ({
335
+ id: entry.id,
336
+ provider: "claude",
337
+ displayName: entry.displayName
338
+ }));
339
+ claudeModelsCache = list;
340
+ claudeModelsCacheAt = Date.now();
341
+ return list;
342
+ }
343
+ async function listClaudeRecentModels() {
344
+ if (claudeRecentModelsCache && Date.now() - claudeRecentModelsCacheAt < CLAUDE_RECENT_MODELS_CACHE_TTL_MS) {
345
+ return claudeRecentModelsCache;
346
+ }
347
+ const discovered = await readClaudeStatsModels();
348
+ const mapped = [];
349
+ const seen = /* @__PURE__ */ new Set();
350
+ for (const modelId of discovered) {
351
+ const id = modelId.trim();
352
+ if (!id || seen.has(id)) continue;
353
+ seen.add(id);
354
+ mapped.push({
355
+ id,
356
+ provider: "claude",
357
+ displayName: formatClaudeDisplayName(id)
358
+ });
359
+ }
360
+ claudeRecentModelsCache = mapped;
361
+ claudeRecentModelsCacheAt = Date.now();
362
+ return mapped;
363
+ }
364
+ function getClaudeAuthPaths() {
365
+ const home = os2.homedir();
366
+ return [
367
+ path2.join(home, ".claude.json"),
368
+ path2.join(home, ".claude", "settings.json"),
369
+ path2.join(home, ".config", "claude", "auth.json")
370
+ ];
371
+ }
372
+ function resolveClaudeTheme() {
373
+ const raw = process.env.AGENTCONNECT_CLAUDE_THEME;
374
+ return raw && raw.trim() ? raw.trim() : "dark";
375
+ }
376
+ function resolveClaudeLoginMethod(options) {
377
+ const raw = options?.loginMethod ?? process.env.AGENTCONNECT_CLAUDE_LOGIN_METHOD;
378
+ if (!raw) return "claudeai";
379
+ const normalized = String(raw).trim().toLowerCase();
380
+ if (normalized === "console") return "console";
381
+ if (normalized === "claudeai" || normalized === "claude") return "claudeai";
382
+ return "claudeai";
383
+ }
384
+ function resolveClaudeLoginExperience(options) {
385
+ const raw = options?.loginExperience ?? process.env.AGENTCONNECT_CLAUDE_LOGIN_EXPERIENCE ?? process.env.AGENTCONNECT_LOGIN_EXPERIENCE;
386
+ if (raw) {
387
+ const normalized = String(raw).trim().toLowerCase();
388
+ if (normalized === "terminal" || normalized === "manual") return "terminal";
389
+ if (normalized === "embedded" || normalized === "pty") return "embedded";
390
+ }
391
+ if (process.env.AGENTCONNECT_HOST_MODE === "dev") return "terminal";
392
+ return "embedded";
393
+ }
394
+ async function createClaudeLoginSettingsFile(loginMethod) {
395
+ if (!loginMethod) return null;
396
+ const fileName = `agentconnect-claude-login-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`;
397
+ const filePath = path2.join(os2.tmpdir(), fileName);
398
+ const theme = resolveClaudeTheme();
399
+ const payload = {
400
+ forceLoginMethod: loginMethod,
401
+ theme,
402
+ hasCompletedOnboarding: true
403
+ };
404
+ await writeFile(filePath, JSON.stringify(payload), "utf8");
405
+ return filePath;
406
+ }
407
+ async function ensureClaudeOnboardingSettings() {
408
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path2.join(os2.homedir(), ".claude");
409
+ const settingsPath = path2.join(configDir, "settings.json");
410
+ let settings = {};
411
+ try {
412
+ const raw = await readFile(settingsPath, "utf8");
413
+ try {
414
+ settings = JSON.parse(raw);
415
+ } catch {
416
+ return;
417
+ }
418
+ } catch (err) {
419
+ const code = err?.code;
420
+ if (code && code !== "ENOENT") return;
421
+ }
422
+ let updated = false;
423
+ if (!settings.theme) {
424
+ settings.theme = resolveClaudeTheme();
425
+ updated = true;
426
+ }
427
+ if (settings.hasCompletedOnboarding !== true) {
428
+ settings.hasCompletedOnboarding = true;
429
+ updated = true;
430
+ }
431
+ if (!updated) return;
432
+ await mkdir(configDir, { recursive: true });
433
+ await writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}
434
+ `, "utf8");
435
+ }
436
+ async function loadPtyModule() {
437
+ try {
438
+ const mod = await import("node-pty");
439
+ if (typeof mod.spawn === "function") return mod;
440
+ if (mod.default && typeof mod.default.spawn === "function") return mod.default;
441
+ return null;
442
+ } catch {
443
+ return null;
444
+ }
445
+ }
446
+ function shellEscape(value) {
447
+ return `'${value.replace(/'/g, `'\\''`)}'`;
448
+ }
449
+ function cmdEscape(value) {
450
+ if (!value) return '""';
451
+ const escaped = value.replace(/"/g, '""');
452
+ return `"${escaped}"`;
453
+ }
454
+ function buildClaudeCommand(command2, args2, includeLogin = false) {
455
+ const parts = includeLogin ? [command2, ...args2, "/login"] : [command2, ...args2];
456
+ return parts.map(shellEscape).join(" ");
457
+ }
458
+ function buildClaudeCmd(command2, args2, includeLogin = false) {
459
+ const parts = includeLogin ? [command2, ...args2, "/login"] : [command2, ...args2];
460
+ return parts.map(cmdEscape).join(" ");
461
+ }
462
+ async function openClaudeLoginTerminal(command2, args2, includeLogin = false) {
463
+ const message = "AgentConnect: complete Claude login in this terminal. If login does not start automatically, run /login.";
464
+ if (process.platform === "win32") {
465
+ const cmdLine = `echo ${message} & ${buildClaudeCmd(command2, args2, includeLogin)}`;
466
+ await runCommand("cmd", ["/c", "start", "", "cmd", "/k", cmdLine], { shell: true });
467
+ return;
468
+ }
469
+ const loginCommand = buildClaudeCommand(command2, args2, includeLogin);
470
+ const shellCommand = `printf "%s\\n\\n" ${shellEscape(message)}; ${loginCommand}`;
471
+ if (process.platform === "darwin") {
472
+ const script = `tell application "Terminal"
473
+ activate
474
+ do script "${shellCommand.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"
475
+ end tell`;
476
+ await runCommand("osascript", ["-e", script]);
477
+ return;
478
+ }
479
+ if (commandExists("x-terminal-emulator")) {
480
+ await runCommand("x-terminal-emulator", ["-e", "bash", "-lc", shellCommand]);
481
+ return;
482
+ }
483
+ if (commandExists("gnome-terminal")) {
484
+ await runCommand("gnome-terminal", ["--", "bash", "-lc", shellCommand]);
485
+ return;
486
+ }
487
+ if (commandExists("konsole")) {
488
+ await runCommand("konsole", ["-e", "bash", "-lc", shellCommand]);
489
+ return;
490
+ }
491
+ if (commandExists("xterm")) {
492
+ await runCommand("xterm", ["-e", "bash", "-lc", shellCommand]);
493
+ return;
494
+ }
495
+ throw new Error("No terminal emulator found to launch Claude login.");
496
+ }
497
+ function maybeAdvanceClaudeOnboarding(output, loginMethod, write) {
498
+ const text = output.toLowerCase();
499
+ if (text.includes("select login method") || text.includes("claude account with subscription")) {
500
+ if (loginMethod === "console") {
501
+ write("\x1B[B");
502
+ }
503
+ write("\r");
504
+ return true;
505
+ }
506
+ if (text.includes("choose the text style") || text.includes("text style that looks best")) {
507
+ write("\r");
508
+ return true;
509
+ }
510
+ if (text.includes("press enter") || text.includes("enter to confirm")) {
511
+ write("\r");
512
+ return true;
513
+ }
514
+ return false;
515
+ }
516
+ async function hasClaudeAuth() {
517
+ if (typeof process.env.CLAUDE_CODE_OAUTH_TOKEN === "string") {
518
+ return process.env.CLAUDE_CODE_OAUTH_TOKEN.trim().length > 0;
519
+ }
520
+ for (const filePath of getClaudeAuthPaths()) {
521
+ try {
522
+ await access(filePath);
523
+ const raw = await readFile(filePath, "utf8");
524
+ const parsed = JSON.parse(raw);
525
+ const auth = parsed?.claudeAiOauth;
526
+ if (typeof auth?.accessToken === "string" && auth.accessToken.trim()) {
527
+ return true;
528
+ }
529
+ if (typeof parsed.primaryApiKey === "string" && parsed.primaryApiKey.trim()) {
530
+ return true;
531
+ }
532
+ if (typeof parsed.accessToken === "string" && parsed.accessToken.trim()) {
533
+ return true;
534
+ }
535
+ if (typeof parsed.token === "string" && parsed.token.trim()) {
536
+ return true;
537
+ }
538
+ const oauthAccount = parsed.oauthAccount;
539
+ if (typeof oauthAccount?.emailAddress === "string" && oauthAccount.emailAddress.trim()) {
540
+ return true;
541
+ }
542
+ if (typeof oauthAccount?.accountUuid === "string" && oauthAccount.accountUuid.trim()) {
543
+ return true;
544
+ }
545
+ const oauth = parsed.oauth;
546
+ if (typeof oauth?.access_token === "string" && oauth.access_token.trim()) {
547
+ return true;
548
+ }
549
+ } catch {
550
+ }
551
+ }
552
+ return false;
553
+ }
554
+ async function checkClaudeCliStatus() {
555
+ const command2 = resolveWindowsCommand(getClaudeCommand());
556
+ const result = await runCommand(command2, ["--print"], {
557
+ env: { ...process.env, CI: "1" },
558
+ input: "/status\n",
559
+ timeoutMs: 4e3
560
+ });
561
+ const output = `${result.stdout}
562
+ ${result.stderr}`.toLowerCase();
563
+ if (!output.trim()) {
564
+ return null;
565
+ }
566
+ if (output.includes("not logged in") || output.includes("not authenticated") || output.includes("please log in") || output.includes("please login") || output.includes("run /login") || output.includes("sign in") || output.includes("invalid api key")) {
567
+ return false;
568
+ }
569
+ if (output.includes("logged in") || output.includes("authenticated") || output.includes("signed in") || output.includes("account") || output.includes("@")) {
570
+ return true;
571
+ }
572
+ return null;
573
+ }
574
+ async function ensureClaudeInstalled() {
575
+ const command2 = getClaudeCommand();
576
+ const versionCheck = await checkCommandVersion(command2, [["--version"], ["-V"]]);
577
+ if (versionCheck.ok) {
578
+ return { installed: true, version: versionCheck.version || void 0 };
579
+ }
580
+ if (commandExists(command2)) {
581
+ return { installed: true, version: void 0 };
582
+ }
583
+ const override = buildInstallCommand("AGENTCONNECT_CLAUDE_INSTALL", "");
584
+ let install = override;
585
+ let packageManager = override.command ? "unknown" : "unknown";
586
+ if (!install.command) {
587
+ if (process.platform === "win32") {
588
+ if (commandExists("powershell")) {
589
+ install = {
590
+ command: "powershell",
591
+ args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", INSTALL_WINDOWS_PS]
592
+ };
593
+ packageManager = "script";
594
+ } else if (commandExists("pwsh")) {
595
+ install = {
596
+ command: "pwsh",
597
+ args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", INSTALL_WINDOWS_PS]
598
+ };
599
+ packageManager = "script";
600
+ } else if (commandExists("cmd") && commandExists("curl")) {
601
+ install = { command: "cmd", args: ["/c", INSTALL_WINDOWS_CMD] };
602
+ packageManager = "script";
603
+ }
604
+ } else if (commandExists("bash") && commandExists("curl")) {
605
+ install = { command: "bash", args: ["-lc", INSTALL_UNIX] };
606
+ packageManager = "script";
607
+ } else {
608
+ const auto = await buildInstallCommandAuto(CLAUDE_PACKAGE);
609
+ install = { command: auto.command, args: auto.args };
610
+ packageManager = auto.packageManager;
611
+ }
612
+ }
613
+ if (!install.command) {
614
+ return { installed: false, version: void 0, packageManager };
615
+ }
616
+ await runCommand(install.command, install.args, { shell: process.platform === "win32" });
617
+ const after = await checkCommandVersion(command2, [["--version"], ["-V"]]);
618
+ return {
619
+ installed: after.ok,
620
+ version: after.version || void 0,
621
+ packageManager
622
+ };
623
+ }
624
+ async function getClaudeStatus() {
625
+ const command2 = getClaudeCommand();
626
+ const versionCheck = await checkCommandVersion(command2, [["--version"], ["-V"]]);
627
+ const installed = versionCheck.ok || commandExists(command2);
628
+ let loggedIn = false;
629
+ if (installed) {
630
+ const status = buildStatusCommand("AGENTCONNECT_CLAUDE_STATUS", DEFAULT_STATUS);
631
+ if (status.command) {
632
+ const statusCommand = resolveWindowsCommand(status.command);
633
+ const result = await runCommand(statusCommand, status.args);
634
+ loggedIn = result.code === 0;
635
+ } else {
636
+ const hasAuth = await hasClaudeAuth();
637
+ const cliStatus = await checkClaudeCliStatus();
638
+ if (cliStatus === false) {
639
+ loggedIn = false;
640
+ } else if (cliStatus === true) {
641
+ loggedIn = true;
642
+ } else {
643
+ loggedIn = hasAuth;
644
+ }
645
+ }
646
+ }
647
+ return { installed, loggedIn, version: versionCheck.version || void 0 };
648
+ }
649
+ async function loginClaude(options) {
650
+ const login = buildLoginCommand("AGENTCONNECT_CLAUDE_LOGIN", DEFAULT_LOGIN);
651
+ if (login.command) {
652
+ const command2 = resolveWindowsCommand(login.command);
653
+ await runCommand(command2, login.args);
654
+ } else {
655
+ await runClaudeLoginFlow(options);
656
+ }
657
+ const status = await getClaudeStatus();
658
+ return { loggedIn: status.loggedIn };
659
+ }
660
+ async function runClaudeLoginFlow(options) {
661
+ const command2 = resolveWindowsCommand(getClaudeCommand());
662
+ const loginMethod = resolveClaudeLoginMethod(options);
663
+ const loginExperience = resolveClaudeLoginExperience(options);
664
+ await ensureClaudeOnboardingSettings();
665
+ const settingsPath = await createClaudeLoginSettingsFile(loginMethod);
666
+ const loginTimeoutMs = Number(process.env.AGENTCONNECT_CLAUDE_LOGIN_TIMEOUT_MS || 18e4);
667
+ const loginArgs = settingsPath ? ["--settings", settingsPath] : [];
668
+ let ptyProcess = null;
669
+ let childExited = false;
670
+ const cleanup = async () => {
671
+ if (ptyProcess) {
672
+ try {
673
+ ptyProcess.kill();
674
+ } catch {
675
+ }
676
+ }
677
+ if (settingsPath) {
678
+ try {
679
+ await rm(settingsPath, { force: true });
680
+ } catch {
681
+ }
682
+ }
683
+ };
684
+ try {
685
+ if (loginExperience === "terminal") {
686
+ await openClaudeLoginTerminal(command2, loginArgs, false);
687
+ } else {
688
+ const ptyModule = await loadPtyModule();
689
+ if (!ptyModule) {
690
+ throw new Error(
691
+ "Claude login requires node-pty. Reinstall AgentConnect or run `claude /login` manually."
692
+ );
693
+ }
694
+ ptyProcess = ptyModule.spawn(command2, [...loginArgs, "/login"], {
695
+ name: "xterm-256color",
696
+ cols: 100,
697
+ rows: 30,
698
+ cwd: os2.homedir(),
699
+ env: { ...process.env, TERM: "xterm-256color" }
700
+ });
701
+ let outputBuffer = "";
702
+ ptyProcess.onData((data) => {
703
+ outputBuffer += data;
704
+ if (outputBuffer.length > 6e3) {
705
+ outputBuffer = outputBuffer.slice(-3e3);
706
+ }
707
+ const advanced = maybeAdvanceClaudeOnboarding(outputBuffer, loginMethod, (input) => {
708
+ ptyProcess?.write(input);
709
+ });
710
+ if (advanced) outputBuffer = "";
711
+ });
712
+ ptyProcess.onExit(() => {
713
+ childExited = true;
714
+ });
715
+ }
716
+ const start = Date.now();
717
+ while (Date.now() - start < loginTimeoutMs) {
718
+ const authed = await hasClaudeAuth();
719
+ if (authed) {
720
+ return;
721
+ }
722
+ if (childExited) {
723
+ break;
724
+ }
725
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
726
+ }
727
+ throw new Error(
728
+ "Claude login timed out. Finish login in your browser or run `claude` manually to authenticate."
729
+ );
730
+ } finally {
731
+ await cleanup();
732
+ }
733
+ }
734
+ function safeJsonParse(line) {
735
+ try {
736
+ return JSON.parse(line);
737
+ } catch {
738
+ return null;
739
+ }
740
+ }
741
+ function mapClaudeModel(model) {
742
+ if (!model) return null;
743
+ const value = String(model).toLowerCase();
744
+ if (value === "default" || value === "recommended") return null;
745
+ if (value === "claude-default" || value === "claude-recommended") return null;
746
+ if (value.includes("opus")) return "opus";
747
+ if (value.includes("sonnet")) return "sonnet";
748
+ if (value.includes("haiku")) return "haiku";
749
+ if (value.startsWith("claude-")) return value.replace("claude-", "");
750
+ return model;
751
+ }
752
+ function extractSessionId(msg) {
753
+ const direct = msg.session_id ?? msg.sessionId;
754
+ if (typeof direct === "string" && direct) return direct;
755
+ const nested = msg.message?.session_id ?? msg.message?.sessionId;
756
+ return typeof nested === "string" && nested ? nested : null;
757
+ }
758
+ function extractAssistantDelta(msg) {
759
+ const type = String(msg.type ?? "");
760
+ if (type === "stream_event") {
761
+ const ev = msg.event ?? {};
762
+ if (ev.type === "content_block_delta") {
763
+ const text2 = ev.delta?.text;
764
+ return typeof text2 === "string" && text2 ? text2 : null;
765
+ }
766
+ }
767
+ if (type === "content_block_delta") {
768
+ const text2 = msg.delta?.text;
769
+ return typeof text2 === "string" && text2 ? text2 : null;
770
+ }
771
+ const text = msg.delta?.text;
772
+ return typeof text === "string" && text ? text : null;
773
+ }
774
+ function extractAssistantText(msg) {
775
+ if (String(msg.type ?? "") !== "assistant") return null;
776
+ const content = msg.message?.content;
777
+ if (!Array.isArray(content)) return null;
778
+ const textBlock = content.find((c) => c && typeof c === "object" && c.type === "text");
779
+ const text = textBlock?.text;
780
+ return typeof text === "string" && text ? text : null;
781
+ }
782
+ function extractResultText(msg) {
783
+ if (String(msg.type ?? "") !== "result") return null;
784
+ const text = msg.result;
785
+ return typeof text === "string" && text ? text : null;
786
+ }
787
+ function runClaudePrompt({
788
+ prompt,
789
+ resumeSessionId,
790
+ model,
791
+ cwd,
792
+ onEvent,
793
+ signal
794
+ }) {
795
+ return new Promise((resolve) => {
796
+ const command2 = getClaudeCommand();
797
+ const args2 = [
798
+ "-p",
799
+ "--output-format=stream-json",
800
+ "--verbose",
801
+ "--permission-mode",
802
+ "bypassPermissions"
803
+ ];
804
+ const modelValue = mapClaudeModel(model);
805
+ if (modelValue) {
806
+ args2.push("--model", modelValue);
807
+ }
808
+ if (resumeSessionId) args2.push("--resume", resumeSessionId);
809
+ args2.push(prompt);
810
+ const child = spawn2(command2, args2, {
811
+ cwd,
812
+ env: { ...process.env },
813
+ stdio: ["ignore", "pipe", "pipe"]
814
+ });
815
+ if (signal) {
816
+ signal.addEventListener("abort", () => {
817
+ child.kill("SIGTERM");
818
+ });
819
+ }
820
+ let aggregated = "";
821
+ let finalSessionId = null;
822
+ let didFinalize = false;
823
+ let sawError = false;
824
+ const emitError = (message) => {
825
+ if (sawError) return;
826
+ sawError = true;
827
+ onEvent({ type: "error", message });
828
+ };
829
+ const emitFinal = (text) => {
830
+ if (finalSessionId) {
831
+ onEvent({ type: "final", text, providerSessionId: finalSessionId });
832
+ } else {
833
+ onEvent({ type: "final", text });
834
+ }
835
+ };
836
+ const handleLine = (line) => {
837
+ const parsed = safeJsonParse(line);
838
+ if (!parsed || typeof parsed !== "object") {
839
+ if (line.trim()) {
840
+ onEvent({ type: "raw_line", line });
841
+ }
842
+ return;
843
+ }
844
+ const msg = parsed;
845
+ const sid = extractSessionId(msg);
846
+ if (sid) finalSessionId = sid;
847
+ const delta = extractAssistantDelta(msg);
848
+ if (delta) {
849
+ aggregated += delta;
850
+ onEvent({ type: "delta", text: delta });
851
+ return;
852
+ }
853
+ const assistant = extractAssistantText(msg);
854
+ if (assistant && !aggregated) {
855
+ aggregated = assistant;
856
+ onEvent({ type: "delta", text: assistant });
857
+ return;
858
+ }
859
+ const result = extractResultText(msg);
860
+ if (result && !didFinalize && !sawError) {
861
+ didFinalize = true;
862
+ emitFinal(aggregated || result);
863
+ }
864
+ };
865
+ const stdoutParser = createLineParser(handleLine);
866
+ const stderrParser = createLineParser(handleLine);
867
+ child.stdout?.on("data", stdoutParser);
868
+ child.stderr?.on("data", stderrParser);
869
+ child.on("close", (code) => {
870
+ if (!didFinalize) {
871
+ if (code && code !== 0) {
872
+ emitError(`Claude exited with code ${code}`);
873
+ } else if (!sawError) {
874
+ emitFinal(aggregated);
875
+ }
876
+ }
877
+ resolve({ sessionId: finalSessionId });
878
+ });
879
+ child.on("error", (err) => {
880
+ emitError(err?.message ?? "Claude failed to start");
881
+ resolve({ sessionId: finalSessionId });
882
+ });
883
+ });
884
+ }
885
+
886
+ // src/providers/codex.ts
887
+ import { spawn as spawn3 } from "child_process";
888
+ import path3 from "path";
889
+ var CODEX_PACKAGE = "@openai/codex";
890
+ var DEFAULT_LOGIN2 = "codex login";
891
+ var DEFAULT_STATUS2 = "codex login status";
892
+ var CODEX_MODELS_CACHE_TTL_MS = 6e4;
893
+ var codexModelsCache = null;
894
+ var codexModelsCacheAt = 0;
895
+ function trimOutput(value, limit = 400) {
896
+ const cleaned = value.trim();
897
+ if (!cleaned) return "";
898
+ if (cleaned.length <= limit) return cleaned;
899
+ return `${cleaned.slice(0, limit)}...`;
900
+ }
901
+ function buildCodexExecArgs(options) {
902
+ const { prompt, cdTarget, resumeSessionId, model, reasoningEffort, mode } = options;
903
+ const args2 = ["exec", "--skip-git-repo-check"];
904
+ if (mode === "legacy") {
905
+ args2.push("--json", "-C", cdTarget);
906
+ } else {
907
+ args2.push("--experimental-json", "--cd", cdTarget);
908
+ }
909
+ args2.push("--yolo");
910
+ if (model) {
911
+ args2.push("--model", String(model));
912
+ }
913
+ if (reasoningEffort) {
914
+ args2.push("--config", `model_reasoning_effort=${reasoningEffort}`);
915
+ }
916
+ if (resumeSessionId) {
917
+ args2.push("resume", resumeSessionId);
918
+ }
919
+ args2.push(prompt);
920
+ return args2;
921
+ }
922
+ function shouldFallbackToLegacy(lines) {
923
+ const combined = lines.join(" ").toLowerCase();
924
+ if (!(combined.includes("unknown flag") || combined.includes("unknown option") || combined.includes("unrecognized option") || combined.includes("unknown argument") || combined.includes("unexpected argument") || combined.includes("invalid option") || combined.includes("invalid argument"))) {
925
+ return false;
926
+ }
927
+ return combined.includes("experimental-json") || combined.includes("--cd");
928
+ }
929
+ function getCodexCommand() {
930
+ const override = process.env.AGENTCONNECT_CODEX_COMMAND;
931
+ const base = override || "codex";
932
+ const resolved = resolveCommandPath(base);
933
+ return resolved || resolveWindowsCommand(base);
934
+ }
935
+ async function ensureCodexInstalled() {
936
+ const command2 = getCodexCommand();
937
+ const versionCheck = await checkCommandVersion(command2, [["--version"], ["-V"]]);
938
+ if (versionCheck.ok) {
939
+ return { installed: true, version: versionCheck.version || void 0 };
940
+ }
941
+ if (commandExists(command2)) {
942
+ return { installed: true, version: void 0 };
943
+ }
944
+ const install = await buildInstallCommandAuto(CODEX_PACKAGE);
945
+ if (!install.command) {
946
+ return { installed: false, version: void 0, packageManager: install.packageManager };
947
+ }
948
+ debugLog("Codex", "install", { command: install.command, args: install.args });
949
+ const installResult = await runCommand(install.command, install.args, {
950
+ shell: process.platform === "win32"
951
+ });
952
+ debugLog("Codex", "install-result", {
953
+ code: installResult.code,
954
+ stderr: trimOutput(installResult.stderr)
955
+ });
956
+ const after = await checkCommandVersion(command2, [["--version"], ["-V"]]);
957
+ codexModelsCache = null;
958
+ codexModelsCacheAt = 0;
959
+ return {
960
+ installed: after.ok,
961
+ version: after.version || void 0,
962
+ packageManager: install.packageManager
963
+ };
964
+ }
965
+ async function getCodexStatus() {
966
+ const command2 = getCodexCommand();
967
+ const versionCheck = await checkCommandVersion(command2, [["--version"], ["-V"]]);
968
+ const installed = versionCheck.ok || commandExists(command2);
969
+ let loggedIn = false;
970
+ if (installed) {
971
+ const status = buildStatusCommand("AGENTCONNECT_CODEX_STATUS", DEFAULT_STATUS2);
972
+ if (status.command) {
973
+ const statusCommand = resolveWindowsCommand(status.command);
974
+ const result = await runCommand(statusCommand, status.args);
975
+ loggedIn = result.code === 0;
976
+ }
977
+ }
978
+ return { installed, loggedIn, version: versionCheck.version || void 0 };
979
+ }
980
+ async function loginCodex() {
981
+ const login = buildLoginCommand("AGENTCONNECT_CODEX_LOGIN", DEFAULT_LOGIN2);
982
+ if (!login.command) {
983
+ return { loggedIn: false };
984
+ }
985
+ const command2 = resolveWindowsCommand(login.command);
986
+ debugLog("Codex", "login", { command: command2, args: login.args });
987
+ const result = await runCommand(command2, login.args, { env: { ...process.env, CI: "1" } });
988
+ debugLog("Codex", "login-result", { code: result.code, stderr: trimOutput(result.stderr) });
989
+ const status = await getCodexStatus();
990
+ codexModelsCache = null;
991
+ codexModelsCacheAt = 0;
992
+ return { loggedIn: status.loggedIn };
993
+ }
994
+ function safeJsonParse2(line) {
995
+ try {
996
+ return JSON.parse(line);
997
+ } catch {
998
+ return null;
999
+ }
1000
+ }
1001
+ function extractSessionId2(ev) {
1002
+ const t = String(ev.type ?? "");
1003
+ if (t === "thread.started") {
1004
+ return typeof ev.thread_id === "string" ? ev.thread_id : null;
1005
+ }
1006
+ const id = ev.thread_id ?? ev.threadId ?? ev.session_id ?? ev.sessionId;
1007
+ return typeof id === "string" ? id : null;
1008
+ }
1009
+ function extractUsage(ev) {
1010
+ const usage = ev.usage ?? ev.token_usage ?? ev.tokens ?? ev.tokenUsage;
1011
+ if (!usage || typeof usage !== "object") return null;
1012
+ const toNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
1013
+ const input = toNumber(
1014
+ usage.input_tokens ?? usage.prompt_tokens ?? usage.inputTokens ?? usage.promptTokens
1015
+ );
1016
+ const output = toNumber(
1017
+ usage.output_tokens ?? usage.completion_tokens ?? usage.outputTokens ?? usage.completionTokens
1018
+ );
1019
+ const total = toNumber(usage.total_tokens ?? usage.totalTokens);
1020
+ const cached = toNumber(usage.cached_input_tokens ?? usage.cachedInputTokens);
1021
+ const out = {};
1022
+ if (input !== void 0) out.input_tokens = input;
1023
+ if (output !== void 0) out.output_tokens = output;
1024
+ if (total !== void 0) out.total_tokens = total;
1025
+ if (cached !== void 0) out.cached_input_tokens = cached;
1026
+ return Object.keys(out).length ? out : null;
1027
+ }
1028
+ function normalizeItem(raw) {
1029
+ if (!raw || typeof raw !== "object") return raw;
1030
+ const item = raw;
1031
+ const type = item.type;
1032
+ const id = item.id;
1033
+ if (type === "command_execution" && id) {
1034
+ return {
1035
+ id,
1036
+ type,
1037
+ command: item.command || "",
1038
+ aggregated_output: item.aggregated_output,
1039
+ exit_code: item.exit_code,
1040
+ status: item.status
1041
+ };
1042
+ }
1043
+ if (type === "reasoning" && id) {
1044
+ return { id, type, text: item.text || "" };
1045
+ }
1046
+ if (type === "agent_message" && id) {
1047
+ return { id, type, text: item.text || "" };
1048
+ }
1049
+ return raw;
1050
+ }
1051
+ function normalizeEvent(raw) {
1052
+ const type = typeof raw.type === "string" ? raw.type : "unknown";
1053
+ if (type === "item.started" || type === "item.completed") {
1054
+ return { type, item: normalizeItem(raw.item) };
1055
+ }
1056
+ if (type === "agent_message") {
1057
+ return { type, text: raw.text || "" };
1058
+ }
1059
+ if (type === "error") {
1060
+ return { type, message: raw.message || "Unknown error" };
1061
+ }
1062
+ return { ...raw, type };
1063
+ }
1064
+ function isTerminalEvent(ev) {
1065
+ const t = String(ev.type ?? "");
1066
+ return t === "turn.completed" || t === "turn.failed";
1067
+ }
1068
+ function normalizeEffortId(raw) {
1069
+ if (!raw) return null;
1070
+ return String(raw).trim().toLowerCase();
1071
+ }
1072
+ function formatEffortLabel(id) {
1073
+ if (!id) return "";
1074
+ const normalized = String(id).trim().toLowerCase();
1075
+ if (normalized === "xhigh") return "X-High";
1076
+ if (normalized === "none") return "None";
1077
+ if (normalized === "minimal") return "Minimal";
1078
+ if (normalized === "low") return "Low";
1079
+ if (normalized === "medium") return "Medium";
1080
+ if (normalized === "high") return "High";
1081
+ return normalized.toUpperCase();
1082
+ }
1083
+ function normalizeEffortOptions(raw) {
1084
+ if (!Array.isArray(raw)) return [];
1085
+ const options = raw.map((entry) => {
1086
+ if (!entry || typeof entry !== "object") return null;
1087
+ const opt = entry;
1088
+ const id = normalizeEffortId(
1089
+ opt.reasoning_effort ?? opt.reasoningEffort ?? opt.effort ?? opt.level
1090
+ );
1091
+ if (!id) return null;
1092
+ const label = formatEffortLabel(id);
1093
+ return { id, label };
1094
+ }).filter((x) => x !== null);
1095
+ const seen = /* @__PURE__ */ new Set();
1096
+ return options.filter((option) => {
1097
+ if (seen.has(option.id)) return false;
1098
+ seen.add(option.id);
1099
+ return true;
1100
+ });
1101
+ }
1102
+ function normalizeCodexModels(raw) {
1103
+ const response = raw;
1104
+ const list = Array.isArray(response?.data) ? response.data : Array.isArray(response?.items) ? response.items : [];
1105
+ const mapped = [];
1106
+ for (const item of list) {
1107
+ if (!item || typeof item !== "object") continue;
1108
+ const id = item.id || item.model;
1109
+ if (!id) continue;
1110
+ const displayName = item.displayName || item.display_name || item.name || item.title || String(id);
1111
+ const reasoningEfforts = normalizeEffortOptions(
1112
+ item.supportedReasoningEfforts || item.supported_reasoning_efforts || item.supportedReasoningLevels || item.supported_reasoning_levels
1113
+ );
1114
+ const defaultReasoningEffort = normalizeEffortId(
1115
+ item.defaultReasoningEffort || item.default_reasoning_effort
1116
+ );
1117
+ mapped.push({
1118
+ id: String(id),
1119
+ provider: "codex",
1120
+ displayName: String(displayName),
1121
+ reasoningEfforts: reasoningEfforts.length ? reasoningEfforts : void 0,
1122
+ defaultReasoningEffort: defaultReasoningEffort || void 0
1123
+ });
1124
+ }
1125
+ if (!mapped.length) return [];
1126
+ const seen = /* @__PURE__ */ new Set();
1127
+ return mapped.filter((model) => {
1128
+ const key = model.id;
1129
+ if (seen.has(key)) return false;
1130
+ seen.add(key);
1131
+ return true;
1132
+ });
1133
+ }
1134
+ async function fetchCodexModels(command2) {
1135
+ return new Promise((resolve) => {
1136
+ const child = spawn3(command2, ["app-server"], {
1137
+ env: { ...process.env },
1138
+ stdio: ["pipe", "pipe", "pipe"]
1139
+ });
1140
+ let settled = false;
1141
+ const initId = 1;
1142
+ const listId = 2;
1143
+ const timeout = setTimeout(() => {
1144
+ finalize(null);
1145
+ }, 8e3);
1146
+ const finalize = (models) => {
1147
+ if (settled) return;
1148
+ settled = true;
1149
+ clearTimeout(timeout);
1150
+ child.kill("SIGTERM");
1151
+ resolve(models);
1152
+ };
1153
+ const handleLine = (line) => {
1154
+ const parsed = safeJsonParse2(line);
1155
+ if (!parsed || typeof parsed !== "object") return;
1156
+ if (parsed.id === listId && parsed.result) {
1157
+ finalize(normalizeCodexModels(parsed.result));
1158
+ }
1159
+ if (parsed.id === listId && parsed.error) {
1160
+ finalize(null);
1161
+ }
1162
+ };
1163
+ const stdoutParser = createLineParser(handleLine);
1164
+ child.stdout?.on("data", stdoutParser);
1165
+ child.stderr?.on("data", () => {
1166
+ });
1167
+ child.on("error", () => finalize(null));
1168
+ child.on("close", () => finalize(null));
1169
+ if (!child.stdin) {
1170
+ finalize(null);
1171
+ return;
1172
+ }
1173
+ const initialize = {
1174
+ method: "initialize",
1175
+ id: initId,
1176
+ params: {
1177
+ clientInfo: { name: "agentconnect", title: "AgentConnect", version: "0.1.0" }
1178
+ }
1179
+ };
1180
+ const initialized = { method: "initialized" };
1181
+ const listRequest = { method: "model/list", id: listId, params: { cursor: null, limit: null } };
1182
+ const payload = `${JSON.stringify(initialize)}
1183
+ ${JSON.stringify(initialized)}
1184
+ ${JSON.stringify(
1185
+ listRequest
1186
+ )}
1187
+ `;
1188
+ child.stdin.write(payload);
1189
+ });
1190
+ }
1191
+ async function listCodexModels() {
1192
+ if (codexModelsCache && Date.now() - codexModelsCacheAt < CODEX_MODELS_CACHE_TTL_MS) {
1193
+ return codexModelsCache;
1194
+ }
1195
+ const command2 = getCodexCommand();
1196
+ const versionCheck = await checkCommandVersion(command2, [["--version"], ["-V"]]);
1197
+ if (!versionCheck.ok) {
1198
+ return [];
1199
+ }
1200
+ const models = await fetchCodexModels(command2);
1201
+ if (models && models.length) {
1202
+ codexModelsCache = models;
1203
+ codexModelsCacheAt = Date.now();
1204
+ return models;
1205
+ }
1206
+ return [];
1207
+ }
1208
+ function runCodexPrompt({
1209
+ prompt,
1210
+ resumeSessionId,
1211
+ model,
1212
+ reasoningEffort,
1213
+ repoRoot,
1214
+ cwd,
1215
+ onEvent,
1216
+ signal
1217
+ }) {
1218
+ return new Promise((resolve) => {
1219
+ const command2 = getCodexCommand();
1220
+ const resolvedRepoRoot = repoRoot ? path3.resolve(repoRoot) : null;
1221
+ const resolvedCwd = cwd ? path3.resolve(cwd) : null;
1222
+ const runDir = resolvedCwd || resolvedRepoRoot || process.cwd();
1223
+ const cdTarget = resolvedRepoRoot || resolvedCwd || ".";
1224
+ const runAttempt = (mode) => new Promise((attemptResolve) => {
1225
+ const args2 = buildCodexExecArgs({
1226
+ prompt,
1227
+ cdTarget,
1228
+ resumeSessionId,
1229
+ model,
1230
+ reasoningEffort,
1231
+ mode
1232
+ });
1233
+ const argsPreview = [...args2];
1234
+ if (argsPreview.length > 0) {
1235
+ argsPreview[argsPreview.length - 1] = "[prompt]";
1236
+ }
1237
+ debugLog("Codex", "spawn", { command: command2, args: argsPreview, cwd: runDir, mode });
1238
+ const child = spawn3(command2, args2, {
1239
+ cwd: runDir,
1240
+ env: { ...process.env },
1241
+ stdio: ["ignore", "pipe", "pipe"]
1242
+ });
1243
+ if (signal) {
1244
+ signal.addEventListener("abort", () => {
1245
+ child.kill("SIGTERM");
1246
+ });
1247
+ }
1248
+ let aggregated = "";
1249
+ let finalSessionId = null;
1250
+ let didFinalize = false;
1251
+ let sawError = false;
1252
+ const emitError = (message) => {
1253
+ if (sawError) return;
1254
+ sawError = true;
1255
+ onEvent({ type: "error", message });
1256
+ };
1257
+ let sawJson = false;
1258
+ const stdoutLines = [];
1259
+ const stderrLines = [];
1260
+ const pushLine = (list, line) => {
1261
+ if (!line) return;
1262
+ list.push(line);
1263
+ if (list.length > 12) list.shift();
1264
+ };
1265
+ const emitFinal = (text) => {
1266
+ if (finalSessionId) {
1267
+ onEvent({ type: "final", text, providerSessionId: finalSessionId });
1268
+ } else {
1269
+ onEvent({ type: "final", text });
1270
+ }
1271
+ };
1272
+ const handleLine = (line, source) => {
1273
+ const parsed = safeJsonParse2(line);
1274
+ if (!parsed || typeof parsed !== "object") {
1275
+ if (line.trim()) {
1276
+ onEvent({ type: "raw_line", line });
1277
+ }
1278
+ if (source === "stdout") {
1279
+ pushLine(stdoutLines, line);
1280
+ } else {
1281
+ pushLine(stderrLines, line);
1282
+ }
1283
+ return;
1284
+ }
1285
+ sawJson = true;
1286
+ const ev = parsed;
1287
+ const normalized = normalizeEvent(ev);
1288
+ onEvent({ type: "provider_event", provider: "codex", event: normalized });
1289
+ const sid = extractSessionId2(ev);
1290
+ if (sid) finalSessionId = sid;
1291
+ const usage = extractUsage(ev);
1292
+ if (usage) {
1293
+ onEvent({
1294
+ type: "usage",
1295
+ inputTokens: usage.input_tokens,
1296
+ outputTokens: usage.output_tokens
1297
+ });
1298
+ }
1299
+ if (normalized.type === "agent_message") {
1300
+ const text = normalized.text;
1301
+ if (typeof text === "string" && text) {
1302
+ aggregated += text;
1303
+ onEvent({ type: "delta", text });
1304
+ }
1305
+ } else if (normalized.type === "item.completed") {
1306
+ const item = normalized.item;
1307
+ if (item && typeof item === "object") {
1308
+ if (item.type === "command_execution" && typeof item.aggregated_output === "string") {
1309
+ onEvent({ type: "delta", text: item.aggregated_output });
1310
+ }
1311
+ if (item.type === "agent_message" && typeof item.text === "string") {
1312
+ aggregated += item.text;
1313
+ onEvent({ type: "delta", text: item.text });
1314
+ }
1315
+ }
1316
+ }
1317
+ if (isTerminalEvent(ev) && !didFinalize) {
1318
+ if (ev.type === "turn.failed") {
1319
+ const message = ev.error?.message;
1320
+ emitError(typeof message === "string" ? message : "Codex run failed");
1321
+ didFinalize = true;
1322
+ return;
1323
+ }
1324
+ if (!sawError) {
1325
+ didFinalize = true;
1326
+ emitFinal(aggregated);
1327
+ }
1328
+ }
1329
+ };
1330
+ const stdoutParser = createLineParser((line) => handleLine(line, "stdout"));
1331
+ const stderrParser = createLineParser((line) => handleLine(line, "stderr"));
1332
+ child.stdout?.on("data", stdoutParser);
1333
+ child.stderr?.on("data", stderrParser);
1334
+ child.on("close", (code) => {
1335
+ if (!didFinalize) {
1336
+ if (code && code !== 0) {
1337
+ const hint = stderrLines.at(-1) || stdoutLines.at(-1) || "";
1338
+ const suffix = hint ? `: ${hint}` : "";
1339
+ const fallback = mode === "modern" && !sawJson && shouldFallbackToLegacy([
1340
+ ...stderrLines,
1341
+ ...stdoutLines
1342
+ ]);
1343
+ debugLog("Codex", "exit", {
1344
+ code,
1345
+ stderr: stderrLines,
1346
+ stdout: stdoutLines,
1347
+ fallback
1348
+ });
1349
+ if (fallback) {
1350
+ attemptResolve({ sessionId: finalSessionId, fallback: true });
1351
+ return;
1352
+ }
1353
+ emitError(`Codex exited with code ${code}${suffix}`);
1354
+ } else if (!sawError) {
1355
+ emitFinal(aggregated);
1356
+ }
1357
+ }
1358
+ attemptResolve({ sessionId: finalSessionId, fallback: false });
1359
+ });
1360
+ child.on("error", (err) => {
1361
+ debugLog("Codex", "spawn-error", { message: err?.message });
1362
+ emitError(err?.message ?? "Codex failed to start");
1363
+ attemptResolve({ sessionId: finalSessionId, fallback: false });
1364
+ });
1365
+ });
1366
+ void (async () => {
1367
+ const primary = await runAttempt("modern");
1368
+ if (primary.fallback) {
1369
+ debugLog("Codex", "fallback", { from: "modern", to: "legacy" });
1370
+ const legacy = await runAttempt("legacy");
1371
+ resolve({ sessionId: legacy.sessionId });
1372
+ return;
1373
+ }
1374
+ resolve({ sessionId: primary.sessionId });
1375
+ })();
1376
+ });
1377
+ }
1378
+
1379
+ // src/providers/local.ts
1380
+ function getLocalBaseUrl() {
1381
+ const base = process.env.AGENTCONNECT_LOCAL_BASE_URL || "http://localhost:11434/v1";
1382
+ return base.replace(/\/+$/, "");
1383
+ }
1384
+ function getLocalApiKey() {
1385
+ return process.env.AGENTCONNECT_LOCAL_API_KEY || "";
1386
+ }
1387
+ function resolveLocalModel(model, fallback) {
1388
+ if (!model) return fallback;
1389
+ const raw = String(model);
1390
+ if (raw === "local") return fallback;
1391
+ if (raw.startsWith("local:")) return raw.slice("local:".length);
1392
+ if (raw.startsWith("local/")) return raw.slice("local/".length);
1393
+ return raw;
1394
+ }
1395
+ async function fetchJson(url, options = {}) {
1396
+ const controller = new AbortController();
1397
+ const timer = setTimeout(() => controller.abort(), 4e3);
1398
+ try {
1399
+ const res = await fetch(url, { ...options, signal: controller.signal });
1400
+ if (!res.ok) {
1401
+ return { ok: false, status: res.status, data: null };
1402
+ }
1403
+ const data = await res.json();
1404
+ return { ok: true, status: res.status, data };
1405
+ } catch {
1406
+ return { ok: false, status: 0, data: null };
1407
+ } finally {
1408
+ clearTimeout(timer);
1409
+ }
1410
+ }
1411
+ async function ensureLocalInstalled() {
1412
+ const base = getLocalBaseUrl();
1413
+ const res = await fetchJson(`${base}/models`);
1414
+ return { installed: res.ok };
1415
+ }
1416
+ async function getLocalStatus() {
1417
+ const base = getLocalBaseUrl();
1418
+ const res = await fetchJson(`${base}/models`);
1419
+ if (!res.ok) return { installed: false, loggedIn: false };
1420
+ return { installed: true, loggedIn: true };
1421
+ }
1422
+ async function loginLocal(options = {}) {
1423
+ if (typeof options.baseUrl === "string") {
1424
+ process.env.AGENTCONNECT_LOCAL_BASE_URL = options.baseUrl;
1425
+ }
1426
+ if (typeof options.apiKey === "string") {
1427
+ process.env.AGENTCONNECT_LOCAL_API_KEY = options.apiKey;
1428
+ }
1429
+ if (typeof options.model === "string") {
1430
+ process.env.AGENTCONNECT_LOCAL_MODEL = options.model;
1431
+ }
1432
+ if (Array.isArray(options.models)) {
1433
+ process.env.AGENTCONNECT_LOCAL_MODELS = JSON.stringify(options.models.filter(Boolean));
1434
+ }
1435
+ const status = await getLocalStatus();
1436
+ return { loggedIn: status.installed };
1437
+ }
1438
+ async function listLocalModels() {
1439
+ const base = getLocalBaseUrl();
1440
+ const res = await fetchJson(`${base}/models`);
1441
+ if (!res.ok || !res.data || !Array.isArray(res.data.data)) return [];
1442
+ return res.data.data.map((entry) => ({ id: entry.id, provider: "local", displayName: entry.id })).filter((entry) => entry.id);
1443
+ }
1444
+ async function runLocalPrompt({
1445
+ prompt,
1446
+ model,
1447
+ onEvent
1448
+ }) {
1449
+ const base = getLocalBaseUrl();
1450
+ const fallback = process.env.AGENTCONNECT_LOCAL_MODEL || "";
1451
+ const resolvedModel = resolveLocalModel(model, fallback);
1452
+ if (!resolvedModel) {
1453
+ onEvent({ type: "error", message: "Local provider model is not configured." });
1454
+ return { sessionId: null };
1455
+ }
1456
+ const payload = {
1457
+ model: resolvedModel,
1458
+ messages: [{ role: "user", content: prompt }],
1459
+ stream: false
1460
+ };
1461
+ const headers = { "Content-Type": "application/json" };
1462
+ const apiKey = getLocalApiKey();
1463
+ if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
1464
+ const res = await fetchJson(`${base}/chat/completions`, {
1465
+ method: "POST",
1466
+ headers,
1467
+ body: JSON.stringify(payload)
1468
+ });
1469
+ if (!res.ok) {
1470
+ onEvent({ type: "error", message: "Local provider request failed." });
1471
+ return { sessionId: null };
1472
+ }
1473
+ const message = res.data?.choices?.[0]?.message?.content;
1474
+ const text = typeof message === "string" ? message : "";
1475
+ if (text) {
1476
+ onEvent({ type: "delta", text });
1477
+ onEvent({ type: "final", text });
1478
+ } else {
1479
+ onEvent({ type: "error", message: "Local provider returned no content." });
1480
+ }
1481
+ return { sessionId: null };
1482
+ }
1483
+
1484
+ // src/providers/index.ts
1485
+ var providers = {
1486
+ claude: {
1487
+ id: "claude",
1488
+ name: "Claude",
1489
+ ensureInstalled: ensureClaudeInstalled,
1490
+ status: getClaudeStatus,
1491
+ login: loginClaude,
1492
+ logout: async () => {
1493
+ },
1494
+ runPrompt: runClaudePrompt
1495
+ },
1496
+ codex: {
1497
+ id: "codex",
1498
+ name: "Codex",
1499
+ ensureInstalled: ensureCodexInstalled,
1500
+ status: getCodexStatus,
1501
+ login: loginCodex,
1502
+ logout: async () => {
1503
+ },
1504
+ runPrompt: runCodexPrompt
1505
+ },
1506
+ local: {
1507
+ id: "local",
1508
+ name: "Local",
1509
+ ensureInstalled: ensureLocalInstalled,
1510
+ status: getLocalStatus,
1511
+ login: loginLocal,
1512
+ logout: async () => {
1513
+ },
1514
+ runPrompt: runLocalPrompt
1515
+ }
1516
+ };
1517
+ async function listModels() {
1518
+ const claudeModels = await listClaudeModels();
1519
+ const codexModels = await listCodexModels();
1520
+ const base = [
1521
+ ...claudeModels,
1522
+ { id: "local", provider: "local", displayName: "Local Model" }
1523
+ ];
1524
+ const envModels = process.env.AGENTCONNECT_LOCAL_MODELS;
1525
+ if (envModels) {
1526
+ try {
1527
+ const parsed = JSON.parse(envModels);
1528
+ if (Array.isArray(parsed)) {
1529
+ for (const entry of parsed) {
1530
+ if (typeof entry === "string" && entry) {
1531
+ base.push({ id: entry, provider: "local", displayName: entry });
1532
+ }
1533
+ }
1534
+ }
1535
+ } catch {
1536
+ }
1537
+ }
1538
+ const discovered = await listLocalModels();
1539
+ const all = [...base, ...codexModels, ...discovered.filter((entry) => entry.id !== "local")];
1540
+ const seen = /* @__PURE__ */ new Set();
1541
+ return all.filter((entry) => {
1542
+ const key = `${entry.provider}:${entry.id}`;
1543
+ if (seen.has(key)) return false;
1544
+ seen.add(key);
1545
+ return true;
1546
+ });
1547
+ }
1548
+ async function listRecentModels(providerId) {
1549
+ if (providerId && providerId !== "claude") return [];
1550
+ const recent = await listClaudeRecentModels();
1551
+ return recent.filter((entry) => entry.provider === "claude");
1552
+ }
1553
+ function resolveProviderForModel(model) {
1554
+ const lower = String(model || "").toLowerCase();
1555
+ if (lower.includes("codex")) return "codex";
1556
+ if (lower.startsWith("gpt") || lower.startsWith("o1") || lower.startsWith("o3") || lower.startsWith("o4")) {
1557
+ return "codex";
1558
+ }
1559
+ if (lower.includes("local")) return "local";
1560
+ if (lower.includes("opus") || lower.includes("sonnet") || lower.includes("haiku"))
1561
+ return "claude";
1562
+ if (lower.includes("claude")) return "claude";
1563
+ return "claude";
1564
+ }
1565
+
1566
+ // src/observed.ts
1567
+ import fs from "fs";
1568
+ import path4 from "path";
1569
+ function createObservedTracker({
1570
+ basePath,
1571
+ appId,
1572
+ requested = []
1573
+ }) {
1574
+ const requestedList = Array.isArray(requested) ? requested.filter(Boolean) : [];
1575
+ const dirPath = path4.join(basePath, ".agentconnect");
1576
+ const filePath = path4.join(dirPath, "observed-capabilities.json");
1577
+ const observed = /* @__PURE__ */ new Set();
1578
+ let writeTimer = null;
1579
+ function load() {
1580
+ if (!fs.existsSync(filePath)) return;
1581
+ try {
1582
+ const raw = fs.readFileSync(filePath, "utf8");
1583
+ const parsed = JSON.parse(raw);
1584
+ if (Array.isArray(parsed?.observed)) {
1585
+ for (const entry of parsed.observed) {
1586
+ if (typeof entry === "string" && entry) observed.add(entry);
1587
+ }
1588
+ }
1589
+ } catch {
1590
+ }
1591
+ }
1592
+ function snapshot() {
1593
+ return {
1594
+ appId,
1595
+ requested: requestedList,
1596
+ observed: Array.from(observed).sort(),
1597
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1598
+ };
1599
+ }
1600
+ function flush() {
1601
+ if (writeTimer) {
1602
+ clearTimeout(writeTimer);
1603
+ writeTimer = null;
1604
+ }
1605
+ const payload = JSON.stringify(snapshot(), null, 2);
1606
+ fs.mkdirSync(dirPath, { recursive: true });
1607
+ fs.writeFileSync(filePath, payload);
1608
+ }
1609
+ function scheduleFlush() {
1610
+ if (writeTimer) return;
1611
+ writeTimer = setTimeout(() => {
1612
+ flush();
1613
+ }, 400);
1614
+ }
1615
+ function record(capability) {
1616
+ const value = typeof capability === "string" ? capability.trim() : "";
1617
+ if (!value) return;
1618
+ if (observed.has(value)) return;
1619
+ observed.add(value);
1620
+ scheduleFlush();
1621
+ }
1622
+ function list() {
1623
+ return Array.from(observed).sort();
1624
+ }
1625
+ load();
1626
+ return {
1627
+ record,
1628
+ list,
1629
+ snapshot,
1630
+ flush
1631
+ };
1632
+ }
1633
+
1634
+ // src/host.ts
1635
+ function send(socket, payload) {
1636
+ socket.send(JSON.stringify(payload));
1637
+ }
1638
+ function reply(socket, id, result) {
1639
+ send(socket, { jsonrpc: "2.0", id, result });
1640
+ }
1641
+ function replyError(socket, id, code, message) {
1642
+ send(socket, {
1643
+ jsonrpc: "2.0",
1644
+ id,
1645
+ error: { code, message }
1646
+ });
1647
+ }
1648
+ function sessionEvent(socket, sessionId, type, data) {
1649
+ send(socket, {
1650
+ jsonrpc: "2.0",
1651
+ method: "acp.session.event",
1652
+ params: { sessionId, type, data }
1653
+ });
1654
+ }
1655
+ function buildProviderList(statuses) {
1656
+ return Object.values(providers).map((provider) => {
1657
+ const info = statuses[provider.id] || {};
1658
+ return {
1659
+ id: provider.id,
1660
+ name: provider.name,
1661
+ installed: info.installed ?? false,
1662
+ loggedIn: info.loggedIn ?? false,
1663
+ version: info.version
1664
+ };
1665
+ });
1666
+ }
1667
+ function startDevHost({
1668
+ host = "127.0.0.1",
1669
+ port = 9630,
1670
+ appPath,
1671
+ uiUrl
1672
+ } = {}) {
1673
+ process.env.AGENTCONNECT_HOST_MODE ||= "dev";
1674
+ const server = http.createServer();
1675
+ const wss = new WebSocketServer({ server });
1676
+ const sessions = /* @__PURE__ */ new Map();
1677
+ const activeRuns = /* @__PURE__ */ new Map();
1678
+ const processTable = /* @__PURE__ */ new Map();
1679
+ const backendState = /* @__PURE__ */ new Map();
1680
+ const statusCache = /* @__PURE__ */ new Map();
1681
+ const statusCacheTtlMs = 2e3;
1682
+ const basePath = appPath || process.cwd();
1683
+ const manifest = readManifest(basePath);
1684
+ const appId = manifest?.id || "agentconnect-dev-app";
1685
+ const requestedCapabilities = Array.isArray(manifest?.capabilities) ? manifest.capabilities : [];
1686
+ const observedTracker = createObservedTracker({
1687
+ basePath,
1688
+ appId,
1689
+ requested: requestedCapabilities
1690
+ });
1691
+ function resolveAppPathInternal(input) {
1692
+ if (!input) return basePath;
1693
+ const value = String(input);
1694
+ return path5.isAbsolute(value) ? value : path5.resolve(basePath, value);
1695
+ }
1696
+ function mapFileType(stat) {
1697
+ if (stat.isFile()) return "file";
1698
+ if (stat.isDirectory()) return "dir";
1699
+ if (stat.isSymbolicLink()) return "link";
1700
+ return "other";
1701
+ }
1702
+ async function allocatePort() {
1703
+ return new Promise((resolve, reject) => {
1704
+ const socket = net.createServer();
1705
+ socket.listen(0, host, () => {
1706
+ const address = socket.address();
1707
+ if (!address || typeof address === "string") {
1708
+ socket.close();
1709
+ reject(new Error("Failed to allocate port."));
1710
+ return;
1711
+ }
1712
+ const portValue = address.port;
1713
+ socket.close(() => resolve(portValue));
1714
+ });
1715
+ socket.on("error", reject);
1716
+ });
1717
+ }
1718
+ async function waitForHealthcheck(url, timeoutMs = 15e3) {
1719
+ const start = Date.now();
1720
+ while (Date.now() - start < timeoutMs) {
1721
+ try {
1722
+ const res = await fetch(url, { method: "GET" });
1723
+ if (res.ok) return true;
1724
+ } catch {
1725
+ }
1726
+ await new Promise((resolve) => setTimeout(resolve, 500));
1727
+ }
1728
+ return false;
1729
+ }
1730
+ function readManifest(root) {
1731
+ try {
1732
+ const raw = fs2.readFileSync(path5.join(root, "agentconnect.app.json"), "utf8");
1733
+ return JSON.parse(raw);
1734
+ } catch {
1735
+ return null;
1736
+ }
1737
+ }
1738
+ function recordCapability(capability) {
1739
+ observedTracker.record(capability);
1740
+ }
1741
+ function recordModelCapability(model) {
1742
+ const providerId = resolveProviderForModel(model);
1743
+ if (!providerId) return;
1744
+ recordCapability(`model.${providerId}`);
1745
+ }
1746
+ async function getCachedStatus(provider) {
1747
+ const cached = statusCache.get(provider.id);
1748
+ const now = Date.now();
1749
+ if (cached && now - cached.at < statusCacheTtlMs) {
1750
+ return cached.status;
1751
+ }
1752
+ const status = await provider.status();
1753
+ statusCache.set(provider.id, { status, at: now });
1754
+ return status;
1755
+ }
1756
+ function invalidateStatus(providerId) {
1757
+ if (!providerId) return;
1758
+ statusCache.delete(providerId);
1759
+ }
1760
+ wss.on("connection", (socket) => {
1761
+ socket.on("message", async (raw) => {
1762
+ let payload;
1763
+ try {
1764
+ payload = JSON.parse(String(raw));
1765
+ } catch {
1766
+ return;
1767
+ }
1768
+ if (!payload || payload.jsonrpc !== "2.0" || payload.id === void 0) {
1769
+ return;
1770
+ }
1771
+ const id = payload.id;
1772
+ const method = payload.method;
1773
+ const params = payload.params ?? {};
1774
+ if (typeof method === "string" && method.startsWith("acp.")) {
1775
+ recordCapability("agent.connect");
1776
+ }
1777
+ if (method === "acp.hello") {
1778
+ const loginExperience = process.env.AGENTCONNECT_LOGIN_EXPERIENCE || process.env.AGENTCONNECT_CLAUDE_LOGIN_EXPERIENCE || (process.env.AGENTCONNECT_HOST_MODE === "dev" ? "terminal" : "embedded");
1779
+ reply(socket, id, {
1780
+ hostId: "agentconnect-dev",
1781
+ hostName: "AgentConnect Dev Host",
1782
+ hostVersion: "0.1.0",
1783
+ protocolVersion: "0.1",
1784
+ mode: "local",
1785
+ capabilities: [],
1786
+ providers: Object.keys(providers),
1787
+ loginExperience
1788
+ });
1789
+ return;
1790
+ }
1791
+ if (method === "acp.providers.list") {
1792
+ const statusEntries = await Promise.all(
1793
+ Object.values(providers).map(async (provider) => {
1794
+ try {
1795
+ return [provider.id, await getCachedStatus(provider)];
1796
+ } catch {
1797
+ return [provider.id, { installed: false, loggedIn: false }];
1798
+ }
1799
+ })
1800
+ );
1801
+ const statuses = Object.fromEntries(statusEntries);
1802
+ reply(socket, id, { providers: buildProviderList(statuses) });
1803
+ return;
1804
+ }
1805
+ if (method === "acp.providers.status") {
1806
+ const providerId = params.provider;
1807
+ const provider = providers[providerId];
1808
+ if (!provider) {
1809
+ replyError(socket, id, "AC_ERR_UNSUPPORTED", "Unknown provider");
1810
+ return;
1811
+ }
1812
+ const status = await getCachedStatus(provider);
1813
+ reply(socket, id, {
1814
+ provider: {
1815
+ id: provider.id,
1816
+ name: provider.name,
1817
+ installed: status.installed,
1818
+ loggedIn: status.loggedIn,
1819
+ version: status.version
1820
+ }
1821
+ });
1822
+ return;
1823
+ }
1824
+ if (method === "acp.providers.ensureInstalled") {
1825
+ const providerId = params.provider;
1826
+ const provider = providers[providerId];
1827
+ if (!provider) {
1828
+ replyError(socket, id, "AC_ERR_UNSUPPORTED", "Unknown provider");
1829
+ return;
1830
+ }
1831
+ const result = await provider.ensureInstalled();
1832
+ invalidateStatus(provider.id);
1833
+ reply(socket, id, result);
1834
+ return;
1835
+ }
1836
+ if (method === "acp.providers.login") {
1837
+ const providerId = params.provider;
1838
+ const provider = providers[providerId];
1839
+ if (!provider) {
1840
+ replyError(socket, id, "AC_ERR_UNSUPPORTED", "Unknown provider");
1841
+ return;
1842
+ }
1843
+ try {
1844
+ const result = await provider.login(params.options);
1845
+ invalidateStatus(provider.id);
1846
+ reply(socket, id, result);
1847
+ } catch (err) {
1848
+ replyError(
1849
+ socket,
1850
+ id,
1851
+ "AC_ERR_INTERNAL",
1852
+ err?.message || "Provider login failed."
1853
+ );
1854
+ }
1855
+ return;
1856
+ }
1857
+ if (method === "acp.providers.logout") {
1858
+ const providerId = params.provider;
1859
+ const provider = providers[providerId];
1860
+ if (!provider) {
1861
+ replyError(socket, id, "AC_ERR_UNSUPPORTED", "Unknown provider");
1862
+ return;
1863
+ }
1864
+ await provider.logout();
1865
+ invalidateStatus(provider.id);
1866
+ reply(socket, id, {});
1867
+ return;
1868
+ }
1869
+ if (method === "acp.models.list") {
1870
+ const models = await listModels();
1871
+ const providerId = params.provider;
1872
+ if (providerId) {
1873
+ reply(socket, id, { models: models.filter((m) => m.provider === providerId) });
1874
+ } else {
1875
+ reply(socket, id, { models });
1876
+ }
1877
+ return;
1878
+ }
1879
+ if (method === "acp.models.recent") {
1880
+ const providerId = params.provider;
1881
+ const models = await listRecentModels(providerId);
1882
+ reply(socket, id, { models });
1883
+ return;
1884
+ }
1885
+ if (method === "acp.models.info") {
1886
+ const modelId = params.model;
1887
+ const model = (await listModels()).find((m) => m.id === modelId);
1888
+ if (!model) {
1889
+ replyError(socket, id, "AC_ERR_INVALID_ARGS", "Unknown model");
1890
+ return;
1891
+ }
1892
+ reply(socket, id, { model });
1893
+ return;
1894
+ }
1895
+ if (method === "acp.sessions.create") {
1896
+ const sessionId = `sess_${Math.random().toString(36).slice(2, 10)}`;
1897
+ const model = params.model || "claude-opus";
1898
+ const reasoningEffort = params.reasoningEffort || null;
1899
+ const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : void 0;
1900
+ const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : void 0;
1901
+ const providerId = resolveProviderForModel(model);
1902
+ recordModelCapability(model);
1903
+ sessions.set(sessionId, {
1904
+ id: sessionId,
1905
+ providerId,
1906
+ model,
1907
+ providerSessionId: null,
1908
+ reasoningEffort,
1909
+ cwd,
1910
+ repoRoot
1911
+ });
1912
+ reply(socket, id, { sessionId });
1913
+ return;
1914
+ }
1915
+ if (method === "acp.sessions.resume") {
1916
+ const sessionId = params.sessionId;
1917
+ const existing = sessions.get(sessionId);
1918
+ if (!existing) {
1919
+ const model = params.model || "claude-opus";
1920
+ const reasoningEffort = params.reasoningEffort || null;
1921
+ const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : void 0;
1922
+ const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : void 0;
1923
+ recordModelCapability(model);
1924
+ sessions.set(sessionId, {
1925
+ id: sessionId,
1926
+ providerId: resolveProviderForModel(model),
1927
+ model,
1928
+ providerSessionId: params.providerSessionId || null,
1929
+ reasoningEffort,
1930
+ cwd,
1931
+ repoRoot
1932
+ });
1933
+ } else {
1934
+ if (params.providerSessionId) {
1935
+ existing.providerSessionId = String(params.providerSessionId);
1936
+ }
1937
+ if (params.cwd) {
1938
+ existing.cwd = resolveAppPathInternal(params.cwd);
1939
+ }
1940
+ if (params.repoRoot) {
1941
+ existing.repoRoot = resolveAppPathInternal(params.repoRoot);
1942
+ }
1943
+ recordModelCapability(existing.model);
1944
+ }
1945
+ reply(socket, id, { sessionId });
1946
+ return;
1947
+ }
1948
+ if (method === "acp.sessions.send") {
1949
+ const sessionId = params.sessionId;
1950
+ const message = params.message?.content || "";
1951
+ const session = sessions.get(sessionId);
1952
+ if (!session) {
1953
+ replyError(socket, id, "AC_ERR_INVALID_ARGS", "Unknown session");
1954
+ return;
1955
+ }
1956
+ recordModelCapability(session.model);
1957
+ const provider = providers[session.providerId];
1958
+ if (!provider) {
1959
+ replyError(socket, id, "AC_ERR_UNSUPPORTED", "Unknown provider");
1960
+ return;
1961
+ }
1962
+ const status = await provider.status();
1963
+ if (!status.installed) {
1964
+ const installed = await provider.ensureInstalled();
1965
+ if (!installed.installed) {
1966
+ replyError(socket, id, "AC_ERR_NOT_INSTALLED", "Provider CLI is not installed.");
1967
+ return;
1968
+ }
1969
+ }
1970
+ const controller = new AbortController();
1971
+ const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : session.cwd || basePath;
1972
+ const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : session.repoRoot || basePath;
1973
+ activeRuns.set(sessionId, controller);
1974
+ let sawError = false;
1975
+ provider.runPrompt({
1976
+ prompt: message,
1977
+ resumeSessionId: session.providerSessionId,
1978
+ model: session.model,
1979
+ reasoningEffort: session.reasoningEffort,
1980
+ repoRoot,
1981
+ cwd,
1982
+ signal: controller.signal,
1983
+ onEvent: (event) => {
1984
+ if (event.type === "error") {
1985
+ sawError = true;
1986
+ }
1987
+ if (sawError && event.type === "final") {
1988
+ return;
1989
+ }
1990
+ sessionEvent(socket, sessionId, event.type, { ...event });
1991
+ }
1992
+ }).then((result) => {
1993
+ if (result?.sessionId) {
1994
+ session.providerSessionId = result.sessionId;
1995
+ }
1996
+ }).catch((err) => {
1997
+ if (!sawError) {
1998
+ sessionEvent(socket, sessionId, "error", {
1999
+ message: err?.message || "Provider error"
2000
+ });
2001
+ }
2002
+ }).finally(() => {
2003
+ activeRuns.delete(sessionId);
2004
+ });
2005
+ reply(socket, id, { accepted: true });
2006
+ return;
2007
+ }
2008
+ if (method === "acp.sessions.cancel") {
2009
+ const sessionId = params.sessionId;
2010
+ const controller = activeRuns.get(sessionId);
2011
+ if (controller) {
2012
+ controller.abort();
2013
+ activeRuns.delete(sessionId);
2014
+ }
2015
+ reply(socket, id, { cancelled: true });
2016
+ return;
2017
+ }
2018
+ if (method === "acp.sessions.close") {
2019
+ const sessionId = params.sessionId;
2020
+ sessions.delete(sessionId);
2021
+ reply(socket, id, { closed: true });
2022
+ return;
2023
+ }
2024
+ if (method === "acp.fs.read") {
2025
+ recordCapability("fs.read");
2026
+ try {
2027
+ const filePath = resolveAppPathInternal(params.path);
2028
+ const encoding = params.encoding || "utf8";
2029
+ const content = await fsp.readFile(filePath, encoding);
2030
+ reply(socket, id, { content, encoding });
2031
+ } catch (err) {
2032
+ replyError(
2033
+ socket,
2034
+ id,
2035
+ "AC_ERR_FS_READ",
2036
+ err?.message || "Failed to read file."
2037
+ );
2038
+ }
2039
+ return;
2040
+ }
2041
+ if (method === "acp.fs.write") {
2042
+ recordCapability("fs.write");
2043
+ try {
2044
+ const filePath = resolveAppPathInternal(params.path);
2045
+ const encoding = params.encoding || "utf8";
2046
+ const content = params.content ?? "";
2047
+ await fsp.mkdir(path5.dirname(filePath), { recursive: true });
2048
+ await fsp.writeFile(filePath, content, {
2049
+ encoding,
2050
+ mode: params.mode
2051
+ });
2052
+ const bytes = Buffer.byteLength(String(content), encoding);
2053
+ reply(socket, id, { bytes });
2054
+ } catch (err) {
2055
+ replyError(
2056
+ socket,
2057
+ id,
2058
+ "AC_ERR_FS_WRITE",
2059
+ err?.message || "Failed to write file."
2060
+ );
2061
+ }
2062
+ return;
2063
+ }
2064
+ if (method === "acp.fs.list") {
2065
+ recordCapability("fs.read");
2066
+ try {
2067
+ const dirPath = resolveAppPathInternal(params.path);
2068
+ const entries = await fsp.readdir(dirPath, { withFileTypes: true });
2069
+ const results = [];
2070
+ for (const entry of entries) {
2071
+ const entryPath = path5.join(dirPath, entry.name);
2072
+ let size = 0;
2073
+ let type = "other";
2074
+ try {
2075
+ const stat = await fsp.lstat(entryPath);
2076
+ type = mapFileType(stat);
2077
+ if (type === "file") size = stat.size;
2078
+ } catch {
2079
+ type = entry.isDirectory() ? "dir" : entry.isFile() ? "file" : "other";
2080
+ }
2081
+ results.push({
2082
+ name: entry.name,
2083
+ path: entryPath,
2084
+ type,
2085
+ size
2086
+ });
2087
+ }
2088
+ reply(socket, id, { entries: results });
2089
+ } catch (err) {
2090
+ replyError(
2091
+ socket,
2092
+ id,
2093
+ "AC_ERR_FS_LIST",
2094
+ err?.message || "Failed to list directory."
2095
+ );
2096
+ }
2097
+ return;
2098
+ }
2099
+ if (method === "acp.fs.stat") {
2100
+ recordCapability("fs.read");
2101
+ try {
2102
+ const filePath = resolveAppPathInternal(params.path);
2103
+ const stat = await fsp.lstat(filePath);
2104
+ reply(socket, id, {
2105
+ type: mapFileType(stat),
2106
+ size: stat.size,
2107
+ mtime: stat.mtime.toISOString()
2108
+ });
2109
+ } catch (err) {
2110
+ replyError(
2111
+ socket,
2112
+ id,
2113
+ "AC_ERR_FS_STAT",
2114
+ err?.message || "Failed to stat file."
2115
+ );
2116
+ }
2117
+ return;
2118
+ }
2119
+ if (method === "acp.process.spawn") {
2120
+ recordCapability("process.spawn");
2121
+ try {
2122
+ const command2 = String(params.command || "");
2123
+ const args2 = Array.isArray(params.args) ? params.args.map(String) : [];
2124
+ const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : basePath;
2125
+ const env = { ...process.env, ...params.env || {} };
2126
+ const useTty = Boolean(params.tty);
2127
+ const child = spawn4(command2, args2, {
2128
+ cwd,
2129
+ env,
2130
+ stdio: useTty ? "inherit" : ["pipe", "pipe", "pipe"]
2131
+ });
2132
+ if (!useTty) {
2133
+ child.stdout?.on("data", () => void 0);
2134
+ child.stderr?.on("data", () => void 0);
2135
+ }
2136
+ if (typeof params.stdin === "string" && child.stdin) {
2137
+ child.stdin.write(params.stdin);
2138
+ child.stdin.end();
2139
+ }
2140
+ if (child.pid) {
2141
+ processTable.set(child.pid, child);
2142
+ child.on("close", () => {
2143
+ if (child.pid) processTable.delete(child.pid);
2144
+ });
2145
+ }
2146
+ reply(socket, id, { pid: child.pid });
2147
+ } catch (err) {
2148
+ replyError(
2149
+ socket,
2150
+ id,
2151
+ "AC_ERR_PROCESS",
2152
+ err?.message || "Failed to spawn process."
2153
+ );
2154
+ }
2155
+ return;
2156
+ }
2157
+ if (method === "acp.process.kill") {
2158
+ recordCapability("process.kill");
2159
+ const pid = Number(params.pid);
2160
+ const signal = params.signal || "SIGTERM";
2161
+ const child = processTable.get(pid);
2162
+ try {
2163
+ const success = child ? child.kill(signal) : process.kill(pid, signal);
2164
+ reply(socket, id, { success: Boolean(success) });
2165
+ } catch (err) {
2166
+ replyError(
2167
+ socket,
2168
+ id,
2169
+ "AC_ERR_PROCESS",
2170
+ err?.message || "Failed to kill process."
2171
+ );
2172
+ }
2173
+ return;
2174
+ }
2175
+ if (method === "acp.net.request") {
2176
+ recordCapability("network.request");
2177
+ try {
2178
+ if (typeof fetch !== "function") {
2179
+ replyError(socket, id, "AC_ERR_NET", "Fetch is not available.");
2180
+ return;
2181
+ }
2182
+ const controller = new AbortController();
2183
+ const timeout = params.timeoutMs;
2184
+ let timer = null;
2185
+ if (timeout) {
2186
+ timer = setTimeout(() => controller.abort(), Number(timeout));
2187
+ }
2188
+ const res = await fetch(String(params.url), {
2189
+ method: params.method || "GET",
2190
+ headers: params.headers || {},
2191
+ body: params.body,
2192
+ signal: controller.signal
2193
+ });
2194
+ if (timer) clearTimeout(timer);
2195
+ const body = await res.text();
2196
+ const headers = {};
2197
+ res.headers.forEach((value, key) => {
2198
+ headers[key] = value;
2199
+ });
2200
+ reply(socket, id, { status: res.status, headers, body });
2201
+ } catch (err) {
2202
+ replyError(
2203
+ socket,
2204
+ id,
2205
+ "AC_ERR_NET",
2206
+ err?.message || "Network request failed."
2207
+ );
2208
+ }
2209
+ return;
2210
+ }
2211
+ if (method === "acp.backend.start") {
2212
+ recordCapability("backend.run");
2213
+ if (!manifest?.backend) {
2214
+ reply(socket, id, { status: "disabled" });
2215
+ return;
2216
+ }
2217
+ const backendConfig = manifest.backend;
2218
+ const existing = backendState.get(appId);
2219
+ if (existing?.status === "running") {
2220
+ reply(socket, id, { status: "running", url: existing.url });
2221
+ return;
2222
+ }
2223
+ try {
2224
+ let assignedPort = null;
2225
+ const declaredPort = backendConfig.env?.PORT;
2226
+ if (declaredPort === void 0 || String(declaredPort) === "0") {
2227
+ assignedPort = await allocatePort();
2228
+ } else if (!Number.isNaN(Number(declaredPort))) {
2229
+ assignedPort = Number(declaredPort);
2230
+ }
2231
+ const env = {
2232
+ ...process.env,
2233
+ ...backendConfig.env || {},
2234
+ AGENTCONNECT_HOST: `ws://${host}:${port}`,
2235
+ AGENTCONNECT_APP_ID: appId
2236
+ };
2237
+ if (assignedPort) {
2238
+ env.PORT = String(assignedPort);
2239
+ env.AGENTCONNECT_APP_PORT = String(assignedPort);
2240
+ }
2241
+ const cwd = backendConfig.cwd ? resolveAppPathInternal(backendConfig.cwd) : basePath;
2242
+ const args2 = backendConfig.args || [];
2243
+ const child = spawn4(backendConfig.command, args2, {
2244
+ cwd,
2245
+ env,
2246
+ stdio: ["ignore", "pipe", "pipe"]
2247
+ });
2248
+ child.stdout?.on("data", () => void 0);
2249
+ child.stderr?.on("data", () => void 0);
2250
+ const url = assignedPort ? `http://${host}:${assignedPort}` : void 0;
2251
+ const record = { status: "starting", pid: child.pid, url };
2252
+ backendState.set(appId, record);
2253
+ child.on("exit", () => {
2254
+ backendState.set(appId, { status: "stopped" });
2255
+ });
2256
+ if (backendConfig.healthcheck?.type === "http" && assignedPort) {
2257
+ const healthUrl = `http://${host}:${assignedPort}${backendConfig.healthcheck.path}`;
2258
+ const ok = await waitForHealthcheck(healthUrl);
2259
+ if (!ok) {
2260
+ child.kill("SIGTERM");
2261
+ backendState.set(appId, { status: "error" });
2262
+ reply(socket, id, { status: "error" });
2263
+ return;
2264
+ }
2265
+ }
2266
+ backendState.set(appId, { status: "running", pid: child.pid, url });
2267
+ reply(socket, id, { status: "running", url });
2268
+ } catch (err) {
2269
+ replyError(
2270
+ socket,
2271
+ id,
2272
+ "AC_ERR_BACKEND",
2273
+ err?.message || "Failed to start backend."
2274
+ );
2275
+ }
2276
+ return;
2277
+ }
2278
+ if (method === "acp.backend.stop") {
2279
+ recordCapability("backend.run");
2280
+ const current = backendState.get(appId);
2281
+ if (!current?.pid) {
2282
+ backendState.set(appId, { status: "stopped" });
2283
+ reply(socket, id, { status: "stopped" });
2284
+ return;
2285
+ }
2286
+ try {
2287
+ process.kill(current.pid, "SIGTERM");
2288
+ } catch {
2289
+ }
2290
+ backendState.set(appId, { status: "stopped" });
2291
+ reply(socket, id, { status: "stopped" });
2292
+ return;
2293
+ }
2294
+ if (method === "acp.backend.status") {
2295
+ recordCapability("backend.run");
2296
+ const current = backendState.get(appId) || { status: "stopped" };
2297
+ reply(socket, id, { status: current.status, url: current.url });
2298
+ return;
2299
+ }
2300
+ if (method === "acp.capabilities.observed") {
2301
+ reply(socket, id, { ...observedTracker.snapshot() });
2302
+ return;
2303
+ }
2304
+ reply(socket, id, {});
2305
+ });
2306
+ });
2307
+ server.listen(port, host, () => {
2308
+ console.log(`AgentConnect dev host running at ws://${host}:${port}`);
2309
+ if (appPath) console.log(`App path: ${appPath}`);
2310
+ if (uiUrl) console.log(`UI dev server: ${uiUrl}`);
2311
+ });
2312
+ process.on("SIGINT", () => {
2313
+ try {
2314
+ observedTracker.flush();
2315
+ } catch {
2316
+ }
2317
+ server.close(() => process.exit(0));
2318
+ });
2319
+ }
2320
+
2321
+ // src/paths.ts
2322
+ import path6 from "path";
2323
+ import { fileURLToPath } from "url";
2324
+ import { promises as fs3 } from "fs";
2325
+ async function exists(target) {
2326
+ try {
2327
+ await fs3.stat(target);
2328
+ return true;
2329
+ } catch {
2330
+ return false;
2331
+ }
2332
+ }
2333
+ function resolveAppPath(input) {
2334
+ const candidate = input ? path6.resolve(input) : process.cwd();
2335
+ return candidate;
2336
+ }
2337
+ async function findSchemaDir() {
2338
+ const envDir = process.env.AGENTCONNECT_SCHEMA_DIR;
2339
+ if (envDir && await exists(envDir)) return envDir;
2340
+ const start = path6.dirname(fileURLToPath(import.meta.url));
2341
+ const roots = [start, process.cwd()];
2342
+ for (const root of roots) {
2343
+ let current = root;
2344
+ for (let i = 0; i < 8; i += 1) {
2345
+ const candidate = path6.join(current, "schemas");
2346
+ if (await exists(candidate)) return candidate;
2347
+ const parent = path6.dirname(current);
2348
+ if (parent === current) break;
2349
+ current = parent;
2350
+ }
2351
+ }
2352
+ return null;
2353
+ }
2354
+
2355
+ // src/zip.ts
2356
+ import fs5 from "fs";
2357
+ import path8 from "path";
2358
+ import { createHash } from "crypto";
2359
+ import yazl from "yazl";
2360
+ import yauzl from "yauzl";
2361
+
2362
+ // src/fs-utils.ts
2363
+ import path7 from "path";
2364
+ import { promises as fs4 } from "fs";
2365
+ async function collectFiles(root, options = {}) {
2366
+ const ignoreNames = new Set(options.ignoreNames || []);
2367
+ const ignorePaths = new Set(options.ignorePaths || []);
2368
+ const files = [];
2369
+ async function walk(dir) {
2370
+ const entries = await fs4.readdir(dir, { withFileTypes: true });
2371
+ for (const entry of entries) {
2372
+ if (ignoreNames.has(entry.name)) continue;
2373
+ const fullPath = path7.join(dir, entry.name);
2374
+ const rel = path7.relative(root, fullPath);
2375
+ if (ignorePaths.has(rel)) continue;
2376
+ if (entry.isDirectory()) {
2377
+ await walk(fullPath);
2378
+ } else if (entry.isFile()) {
2379
+ files.push({ fullPath, rel });
2380
+ }
2381
+ }
2382
+ }
2383
+ await walk(root);
2384
+ return files.sort((a, b) => a.rel.localeCompare(b.rel));
2385
+ }
2386
+ async function readJson(filePath) {
2387
+ const raw = await fs4.readFile(filePath, "utf8");
2388
+ return JSON.parse(raw);
2389
+ }
2390
+ async function writeJson(filePath, data) {
2391
+ await fs4.mkdir(path7.dirname(filePath), { recursive: true });
2392
+ await fs4.writeFile(filePath, JSON.stringify(data, null, 2), "utf8");
2393
+ }
2394
+ async function fileExists(filePath) {
2395
+ try {
2396
+ await fs4.stat(filePath);
2397
+ return true;
2398
+ } catch {
2399
+ return false;
2400
+ }
2401
+ }
2402
+
2403
+ // src/zip.ts
2404
+ async function hashFile(filePath) {
2405
+ return new Promise((resolve, reject) => {
2406
+ const hash = createHash("sha256");
2407
+ const stream = fs5.createReadStream(filePath);
2408
+ stream.on("data", (chunk) => hash.update(chunk));
2409
+ stream.on("error", reject);
2410
+ stream.on("end", () => resolve(hash.digest("hex")));
2411
+ });
2412
+ }
2413
+ async function zipDirectory({
2414
+ inputDir,
2415
+ outputPath,
2416
+ ignoreNames = [],
2417
+ ignorePaths = []
2418
+ }) {
2419
+ await fs5.promises.mkdir(path8.dirname(outputPath), { recursive: true });
2420
+ const zipfile = new yazl.ZipFile();
2421
+ const files = await collectFiles(inputDir, { ignoreNames, ignorePaths });
2422
+ for (const file of files) {
2423
+ zipfile.addFile(file.fullPath, file.rel);
2424
+ }
2425
+ return new Promise((resolve, reject) => {
2426
+ const outStream = fs5.createWriteStream(outputPath);
2427
+ zipfile.outputStream.pipe(outStream);
2428
+ outStream.on("close", () => resolve());
2429
+ outStream.on("error", reject);
2430
+ zipfile.end();
2431
+ });
2432
+ }
2433
+ async function readZipEntry(zipPath, entryName) {
2434
+ return new Promise((resolve, reject) => {
2435
+ yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
2436
+ if (err || !zipfile) {
2437
+ reject(err || new Error("Unable to open zip"));
2438
+ return;
2439
+ }
2440
+ let found = false;
2441
+ zipfile.readEntry();
2442
+ zipfile.on("entry", (entry) => {
2443
+ if (entry.fileName === entryName || entry.fileName.endsWith(`/${entryName}`)) {
2444
+ found = true;
2445
+ zipfile.openReadStream(entry, (streamErr, stream) => {
2446
+ if (streamErr || !stream) {
2447
+ zipfile.close();
2448
+ reject(streamErr || new Error("Unable to read zip entry"));
2449
+ return;
2450
+ }
2451
+ const chunks = [];
2452
+ stream.on("data", (chunk) => chunks.push(chunk));
2453
+ stream.on("end", () => {
2454
+ const content = Buffer.concat(chunks).toString("utf8");
2455
+ zipfile.close();
2456
+ resolve(content);
2457
+ });
2458
+ stream.on("error", reject);
2459
+ });
2460
+ } else {
2461
+ zipfile.readEntry();
2462
+ }
2463
+ });
2464
+ zipfile.on("end", () => {
2465
+ if (!found) {
2466
+ zipfile.close();
2467
+ resolve(null);
2468
+ }
2469
+ });
2470
+ });
2471
+ });
2472
+ }
2473
+
2474
+ // src/manifest.ts
2475
+ import path9 from "path";
2476
+ import Ajv from "ajv";
2477
+ async function readManifestFromDir(appPath) {
2478
+ const manifestPath = path9.join(appPath, "agentconnect.app.json");
2479
+ if (!await fileExists(manifestPath)) {
2480
+ throw new Error("agentconnect.app.json not found in app directory.");
2481
+ }
2482
+ return readJson(manifestPath);
2483
+ }
2484
+ async function readManifestFromZip(zipPath) {
2485
+ const content = await readZipEntry(zipPath, "agentconnect.app.json");
2486
+ if (!content) {
2487
+ throw new Error("agentconnect.app.json not found in zip.");
2488
+ }
2489
+ return JSON.parse(content);
2490
+ }
2491
+ async function loadManifestSchema() {
2492
+ const schemaDir = await findSchemaDir();
2493
+ if (!schemaDir) {
2494
+ throw new Error("Unable to locate schemas directory.");
2495
+ }
2496
+ const schemaPath = path9.join(schemaDir, "app-manifest.json");
2497
+ return readJson(schemaPath);
2498
+ }
2499
+ async function validateManifest(manifest) {
2500
+ const schema = await loadManifestSchema();
2501
+ const ajv = new Ajv({ allErrors: true, strict: false });
2502
+ const validate = ajv.compile(schema);
2503
+ const valid = validate(manifest);
2504
+ return { valid: Boolean(valid), errors: validate.errors || [] };
2505
+ }
2506
+
2507
+ // src/registry.ts
2508
+ import path10 from "path";
2509
+ import { promises as fs6 } from "fs";
2510
+ function compareSemver(a, b) {
2511
+ const parse = (v) => v.split("-")[0].split(".").map((n) => Number(n));
2512
+ const pa = parse(a);
2513
+ const pb = parse(b);
2514
+ for (let i = 0; i < 3; i += 1) {
2515
+ const diff = (pa[i] || 0) - (pb[i] || 0);
2516
+ if (diff !== 0) return diff;
2517
+ }
2518
+ return 0;
2519
+ }
2520
+ async function publishPackage({
2521
+ zipPath,
2522
+ signaturePath,
2523
+ registryPath,
2524
+ manifest
2525
+ }) {
2526
+ const resolvedManifest = manifest || await readManifestFromZip(zipPath);
2527
+ const appId = resolvedManifest.id;
2528
+ const version = resolvedManifest.version;
2529
+ if (!appId || !version) {
2530
+ throw new Error("Manifest must include id and version.");
2531
+ }
2532
+ const entryDir = path10.join(registryPath, "apps", appId, version);
2533
+ await fs6.mkdir(entryDir, { recursive: true });
2534
+ const targetZip = path10.join(entryDir, "app.zip");
2535
+ await fs6.copyFile(zipPath, targetZip);
2536
+ const manifestPath = path10.join(entryDir, "manifest.json");
2537
+ await writeJson(manifestPath, resolvedManifest);
2538
+ let signatureOut = null;
2539
+ if (signaturePath) {
2540
+ signatureOut = path10.join(entryDir, "signature.json");
2541
+ await fs6.copyFile(signaturePath, signatureOut);
2542
+ }
2543
+ const hash = await hashFile(zipPath);
2544
+ const indexPath = path10.join(registryPath, "index.json");
2545
+ const index = await fileExists(indexPath) ? await readJson(indexPath) : { apps: {} };
2546
+ if (!index.apps) index.apps = {};
2547
+ if (!index.apps[appId]) {
2548
+ index.apps[appId] = { latest: version, versions: {} };
2549
+ }
2550
+ index.apps[appId].versions[version] = {
2551
+ path: path10.relative(registryPath, targetZip).replace(/\\/g, "/"),
2552
+ manifest: resolvedManifest,
2553
+ signature: signatureOut ? {
2554
+ algorithm: "unknown",
2555
+ publicKey: "",
2556
+ signature: ""
2557
+ } : void 0,
2558
+ hash
2559
+ };
2560
+ const currentLatest = index.apps[appId].latest;
2561
+ if (!currentLatest || compareSemver(version, currentLatest) > 0) {
2562
+ index.apps[appId].latest = version;
2563
+ }
2564
+ await writeJson(indexPath, index);
2565
+ return { appId, version, hash, targetZip, manifestPath, signaturePath: signatureOut, indexPath };
2566
+ }
2567
+
2568
+ // src/registry-validate.ts
2569
+ import path11 from "path";
2570
+ import { createPublicKey, verify as verifySignature } from "crypto";
2571
+ function compareSemver2(a, b) {
2572
+ const parse = (v) => v.split("-")[0].split(".").map((n) => Number(n));
2573
+ const pa = parse(a);
2574
+ const pb = parse(b);
2575
+ for (let i = 0; i < 3; i += 1) {
2576
+ const diff = (pa[i] || 0) - (pb[i] || 0);
2577
+ if (diff !== 0) return diff;
2578
+ }
2579
+ return 0;
2580
+ }
2581
+ function resolveEntry(registryPath, entryPath) {
2582
+ if (!entryPath || typeof entryPath !== "string") return null;
2583
+ return path11.resolve(registryPath, entryPath);
2584
+ }
2585
+ function normalizeSignatureAlg(signatureAlg) {
2586
+ const value = String(signatureAlg || "").toLowerCase();
2587
+ if (value === "ed25519") return null;
2588
+ if (value === "rsa-sha256") return "sha256";
2589
+ if (value === "ecdsa-sha256") return "sha256";
2590
+ return "sha256";
2591
+ }
2592
+ async function verifySignatureFile({
2593
+ signaturePath,
2594
+ hash
2595
+ }) {
2596
+ const signature = await readJson(signaturePath);
2597
+ if (!signature || typeof signature !== "object") {
2598
+ return { ok: false, message: "Signature payload is not valid JSON." };
2599
+ }
2600
+ if (signature.hash !== hash) {
2601
+ return { ok: false, message: "Signature hash does not match app hash." };
2602
+ }
2603
+ const publicKey = signature.publicKey;
2604
+ if (!publicKey || typeof publicKey !== "string") {
2605
+ return { ok: false, message: "Signature is missing public key." };
2606
+ }
2607
+ const signatureValue = signature.signature;
2608
+ if (!signatureValue || typeof signatureValue !== "string") {
2609
+ return { ok: false, message: "Signature is missing signature bytes." };
2610
+ }
2611
+ const algorithm = normalizeSignatureAlg(signature.signatureAlg);
2612
+ const key = createPublicKey(publicKey);
2613
+ const payload = Buffer.from(hash, "hex");
2614
+ const sigBuffer = Buffer.from(signatureValue, "base64");
2615
+ const ok = verifySignature(algorithm, payload, key, sigBuffer);
2616
+ return { ok, message: ok ? "" : "Signature verification failed." };
2617
+ }
2618
+ async function validateRegistry({
2619
+ registryPath,
2620
+ requireSignature = false
2621
+ }) {
2622
+ const errors = [];
2623
+ const warnings = [];
2624
+ const indexPath = path11.join(registryPath, "index.json");
2625
+ if (!await fileExists(indexPath)) {
2626
+ return {
2627
+ valid: false,
2628
+ errors: [{ path: "index.json", message: "index.json not found." }],
2629
+ warnings
2630
+ };
2631
+ }
2632
+ const index = await readJson(indexPath).catch(() => null);
2633
+ if (!index || typeof index !== "object") {
2634
+ return {
2635
+ valid: false,
2636
+ errors: [{ path: "index.json", message: "index.json is not valid JSON." }],
2637
+ warnings
2638
+ };
2639
+ }
2640
+ const apps = index.apps;
2641
+ if (!apps || typeof apps !== "object") {
2642
+ return {
2643
+ valid: false,
2644
+ errors: [{ path: "index.json", message: "index.json missing apps map." }],
2645
+ warnings
2646
+ };
2647
+ }
2648
+ for (const [appId, appEntry] of Object.entries(apps)) {
2649
+ if (!appEntry || typeof appEntry !== "object") {
2650
+ errors.push({
2651
+ path: `apps.${appId}`,
2652
+ message: `App entry for ${appId} is invalid.`
2653
+ });
2654
+ continue;
2655
+ }
2656
+ const versions = appEntry.versions;
2657
+ if (!versions || typeof versions !== "object") {
2658
+ errors.push({
2659
+ path: `apps.${appId}`,
2660
+ message: `App entry for ${appId} missing versions.`
2661
+ });
2662
+ continue;
2663
+ }
2664
+ const versionKeys = Object.keys(versions);
2665
+ if (!versionKeys.length) {
2666
+ errors.push({
2667
+ path: `apps.${appId}`,
2668
+ message: `App entry for ${appId} has no versions.`
2669
+ });
2670
+ continue;
2671
+ }
2672
+ const latest = appEntry.latest;
2673
+ if (latest && !versions[latest]) {
2674
+ errors.push({
2675
+ path: `apps.${appId}`,
2676
+ message: `App ${appId} latest (${latest}) not found in versions.`
2677
+ });
2678
+ }
2679
+ const sorted = [...versionKeys].sort(compareSemver2);
2680
+ const expectedLatest = sorted[sorted.length - 1];
2681
+ if (latest && expectedLatest && compareSemver2(latest, expectedLatest) !== 0) {
2682
+ warnings.push({
2683
+ path: `apps.${appId}`,
2684
+ message: `App ${appId} latest (${latest}) is not the newest (${expectedLatest}).`
2685
+ });
2686
+ }
2687
+ for (const [version, entry] of Object.entries(versions)) {
2688
+ if (!entry || typeof entry !== "object") {
2689
+ errors.push({
2690
+ path: `apps.${appId}.versions.${version}`,
2691
+ message: `App ${appId}@${version} entry is invalid.`
2692
+ });
2693
+ continue;
2694
+ }
2695
+ const zipPath = resolveEntry(registryPath, entry.path);
2696
+ const manifestPath = resolveEntry(registryPath, entry.manifest);
2697
+ const signaturePath = entry.signature ? resolveEntry(registryPath, entry.signature) : null;
2698
+ if (!zipPath || !await fileExists(zipPath)) {
2699
+ errors.push({
2700
+ path: `apps.${appId}.versions.${version}`,
2701
+ message: `App ${appId}@${version} app.zip missing.`
2702
+ });
2703
+ continue;
2704
+ }
2705
+ if (!manifestPath || !await fileExists(manifestPath)) {
2706
+ errors.push({
2707
+ path: `apps.${appId}.versions.${version}`,
2708
+ message: `App ${appId}@${version} manifest missing.`
2709
+ });
2710
+ continue;
2711
+ }
2712
+ const hash = await hashFile(zipPath);
2713
+ if (entry.hash && entry.hash !== hash) {
2714
+ errors.push({
2715
+ path: `apps.${appId}.versions.${version}`,
2716
+ message: `App ${appId}@${version} hash mismatch.`
2717
+ });
2718
+ }
2719
+ const manifest = await readJson(manifestPath).catch(
2720
+ () => null
2721
+ );
2722
+ if (!manifest) {
2723
+ errors.push({
2724
+ path: `apps.${appId}.versions.${version}`,
2725
+ message: `App ${appId}@${version} manifest invalid JSON.`
2726
+ });
2727
+ continue;
2728
+ }
2729
+ const manifestValidation = await validateManifest(manifest);
2730
+ if (!manifestValidation.valid) {
2731
+ errors.push({
2732
+ path: `apps.${appId}.versions.${version}`,
2733
+ message: `App ${appId}@${version} manifest failed schema validation.`
2734
+ });
2735
+ }
2736
+ if (manifest.id !== appId || manifest.version !== version) {
2737
+ errors.push({
2738
+ path: `apps.${appId}.versions.${version}`,
2739
+ message: `App ${appId}@${version} manifest id/version mismatch.`
2740
+ });
2741
+ }
2742
+ if (signaturePath) {
2743
+ if (!await fileExists(signaturePath)) {
2744
+ errors.push({
2745
+ path: `apps.${appId}.versions.${version}`,
2746
+ message: `App ${appId}@${version} signature file missing.`
2747
+ });
2748
+ } else {
2749
+ const verification = await verifySignatureFile({ signaturePath, hash });
2750
+ if (!verification.ok) {
2751
+ errors.push({
2752
+ path: `apps.${appId}.versions.${version}`,
2753
+ message: `App ${appId}@${version} signature invalid: ${verification.message}`
2754
+ });
2755
+ }
2756
+ }
2757
+ } else if (requireSignature) {
2758
+ errors.push({
2759
+ path: `apps.${appId}.versions.${version}`,
2760
+ message: `App ${appId}@${version} is missing a signature.`
2761
+ });
2762
+ } else {
2763
+ warnings.push({
2764
+ path: `apps.${appId}.versions.${version}`,
2765
+ message: `App ${appId}@${version} has no signature.`
2766
+ });
2767
+ }
2768
+ }
2769
+ }
2770
+ return { valid: errors.length === 0, errors, warnings };
2771
+ }
2772
+
2773
+ // src/index.ts
2774
+ var args = process.argv.slice(2);
2775
+ var command = args[0];
2776
+ var helpText = `agentconnect <command>
2777
+
2778
+ Commands:
2779
+ dev Start a local AgentConnect host
2780
+ pack Package an app
2781
+ verify Verify an app package
2782
+ sign Sign an app package
2783
+ publish Publish to registry
2784
+ registry-verify Validate a registry
2785
+
2786
+ Dev options:
2787
+ --host <host> Host to bind (default: 127.0.0.1)
2788
+ --port <port> Port to bind (default: 9630)
2789
+ --app <path> App path (optional)
2790
+ --ui <url> UI dev server URL (optional)
2791
+
2792
+ Pack options:
2793
+ --app <path> App directory (default: cwd)
2794
+ --out <path> Output zip path (default: dist/app.zip)
2795
+
2796
+ Verify options:
2797
+ --app <path> App directory or zip
2798
+ --json Output JSON
2799
+
2800
+ Sign options:
2801
+ --app <path> Zip path
2802
+ --key <path> Private key path (PEM)
2803
+ --out <path> Signature output path (default: dist/app.sig.json)
2804
+
2805
+ Publish options:
2806
+ --app <path> Zip path
2807
+ --registry <path> Registry directory
2808
+ --signature <path> Signature file (optional)
2809
+
2810
+ Registry-verify options:
2811
+ --registry <path> Registry directory
2812
+ --require-signature Fail if any entry lacks a signature
2813
+ --json Output JSON
2814
+ `;
2815
+ function getFlag(name, alias) {
2816
+ const idx = args.findIndex((arg) => arg === name || arg === alias);
2817
+ if (idx === -1) return null;
2818
+ return args[idx + 1] ?? null;
2819
+ }
2820
+ function parsePort(value, fallback) {
2821
+ if (!value) return fallback;
2822
+ const parsed = Number(value);
2823
+ return Number.isFinite(parsed) ? parsed : fallback;
2824
+ }
2825
+ async function main() {
2826
+ if (!command || command === "--help" || command === "-h") {
2827
+ console.log(helpText);
2828
+ return 0;
2829
+ }
2830
+ if (command === "dev") {
2831
+ const host = getFlag("--host", "-H") ?? "127.0.0.1";
2832
+ const port = parsePort(getFlag("--port", "-p"), 9630);
2833
+ const appPath = getFlag("--app", "-a") ?? void 0;
2834
+ const uiUrl = getFlag("--ui", "-u") ?? void 0;
2835
+ startDevHost({ host, port, appPath, uiUrl });
2836
+ return null;
2837
+ }
2838
+ if (command === "pack") {
2839
+ const appPath = resolveAppPath(getFlag("--app", "-a") ?? void 0);
2840
+ const outPath = getFlag("--out", "-o") || path12.join(appPath, "dist", "app.zip");
2841
+ const manifest = await readManifestFromDir(appPath);
2842
+ const validation = await validateManifest(manifest);
2843
+ if (!validation.valid) {
2844
+ console.error("Manifest validation failed.");
2845
+ console.error(validation.errors);
2846
+ return 1;
2847
+ }
2848
+ const ignoreNames = ["node_modules", ".git", ".DS_Store"];
2849
+ const ignorePaths = [];
2850
+ const outputRel = path12.relative(appPath, outPath);
2851
+ if (!outputRel.startsWith("..") && outputRel !== "") {
2852
+ ignorePaths.push(outputRel);
2853
+ }
2854
+ await zipDirectory({ inputDir: appPath, outputPath: outPath, ignoreNames, ignorePaths });
2855
+ console.log(`Packed ${appPath} -> ${outPath}`);
2856
+ return 0;
2857
+ }
2858
+ if (command === "verify") {
2859
+ const appArg = getFlag("--app", "-a");
2860
+ const jsonOut = args.includes("--json");
2861
+ const appPath = resolveAppPath(appArg ?? void 0);
2862
+ const stats = await fs7.stat(appPath).catch(() => null);
2863
+ if (!stats) {
2864
+ console.error("App path not found.");
2865
+ return 1;
2866
+ }
2867
+ let manifest;
2868
+ let hash = null;
2869
+ if (stats.isDirectory()) {
2870
+ manifest = await readManifestFromDir(appPath);
2871
+ } else {
2872
+ manifest = await readManifestFromZip(appPath);
2873
+ hash = await hashFile(appPath);
2874
+ }
2875
+ const validation = await validateManifest(manifest);
2876
+ const result = {
2877
+ valid: validation.valid,
2878
+ errors: validation.errors,
2879
+ hash,
2880
+ manifest
2881
+ };
2882
+ if (jsonOut) {
2883
+ console.log(JSON.stringify(result, null, 2));
2884
+ return validation.valid ? 0 : 1;
2885
+ }
2886
+ if (validation.valid) {
2887
+ console.log("Manifest valid.");
2888
+ if (hash) console.log(`SHA256: ${hash}`);
2889
+ return 0;
2890
+ }
2891
+ console.error("Manifest validation failed.");
2892
+ console.error(validation.errors);
2893
+ return 1;
2894
+ }
2895
+ if (command === "sign") {
2896
+ const appArg = getFlag("--app", "-a");
2897
+ const keyPath = getFlag("--key", "-k");
2898
+ if (!appArg || !keyPath) {
2899
+ console.error("Usage: agentconnect sign --app <zip> --key <pem> [--out <path>]");
2900
+ return 1;
2901
+ }
2902
+ const appPath = resolveAppPath(appArg);
2903
+ const appStats = await fs7.stat(appPath).catch(() => null);
2904
+ if (!appStats || !appStats.isFile()) {
2905
+ console.error("Sign requires a zip file path.");
2906
+ return 1;
2907
+ }
2908
+ const outPath = getFlag("--out", "-o") || path12.join(path12.dirname(appPath), "app.sig.json");
2909
+ const manifest = await readManifestFromZip(appPath);
2910
+ const hash = await hashFile(appPath);
2911
+ const hashBuffer = Buffer.from(hash, "hex");
2912
+ const privateKeyPem = await fs7.readFile(keyPath, "utf8");
2913
+ const privateKey = createPrivateKey(privateKeyPem);
2914
+ const keyType = privateKey.asymmetricKeyType;
2915
+ const algorithm = keyType === "ed25519" ? null : "sha256";
2916
+ const signatureAlg = keyType === "ed25519" ? "ed25519" : keyType === "rsa" ? "rsa-sha256" : keyType === "ec" ? "ecdsa-sha256" : "sha256";
2917
+ const signature = signData(algorithm, hashBuffer, privateKey);
2918
+ const publicKeyPem = createPublicKey2(privateKey).export({
2919
+ type: "spki",
2920
+ format: "pem"
2921
+ });
2922
+ const signaturePayload = {
2923
+ appId: manifest.id,
2924
+ version: manifest.version,
2925
+ hash,
2926
+ hashAlg: "sha256",
2927
+ signature: signature.toString("base64"),
2928
+ signatureAlg,
2929
+ publicKey: publicKeyPem,
2930
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2931
+ };
2932
+ await fs7.mkdir(path12.dirname(outPath), { recursive: true });
2933
+ await fs7.writeFile(outPath, JSON.stringify(signaturePayload, null, 2), "utf8");
2934
+ console.log(`Signature written to ${outPath}`);
2935
+ return 0;
2936
+ }
2937
+ if (command === "publish") {
2938
+ const appArg = getFlag("--app", "-a");
2939
+ const registry = getFlag("--registry", "-r");
2940
+ const signaturePath = getFlag("--signature", "-s") ?? void 0;
2941
+ if (!appArg || !registry) {
2942
+ console.error(
2943
+ "Usage: agentconnect publish --app <zip> --registry <path> [--signature <path>]"
2944
+ );
2945
+ return 1;
2946
+ }
2947
+ const appPath = resolveAppPath(appArg);
2948
+ const appStats = await fs7.stat(appPath).catch(() => null);
2949
+ if (!appStats || !appStats.isFile()) {
2950
+ console.error("Publish requires a zip file path.");
2951
+ return 1;
2952
+ }
2953
+ const registryPath = resolveAppPath(registry);
2954
+ const manifest = await readManifestFromZip(appPath);
2955
+ const validation = await validateManifest(manifest);
2956
+ if (!validation.valid) {
2957
+ console.error("Manifest validation failed.");
2958
+ console.error(validation.errors);
2959
+ return 1;
2960
+ }
2961
+ const result = await publishPackage({
2962
+ zipPath: appPath,
2963
+ signaturePath,
2964
+ registryPath,
2965
+ manifest
2966
+ });
2967
+ console.log(`Published ${result.appId}@${result.version}`);
2968
+ console.log(`Registry entry: ${result.indexPath}`);
2969
+ return 0;
2970
+ }
2971
+ if (command === "registry-verify") {
2972
+ const registry = getFlag("--registry", "-r");
2973
+ const jsonOut = args.includes("--json");
2974
+ const requireSignature = args.includes("--require-signature");
2975
+ if (!registry) {
2976
+ console.error(
2977
+ "Usage: agentconnect registry-verify --registry <path> [--require-signature] [--json]"
2978
+ );
2979
+ return 1;
2980
+ }
2981
+ const registryPath = resolveAppPath(registry);
2982
+ const result = await validateRegistry({ registryPath, requireSignature });
2983
+ if (jsonOut) {
2984
+ console.log(JSON.stringify(result, null, 2));
2985
+ return result.valid ? 0 : 1;
2986
+ }
2987
+ if (result.valid) {
2988
+ console.log("Registry valid.");
2989
+ if (result.warnings.length) {
2990
+ console.log("Warnings:");
2991
+ for (const warning of result.warnings) {
2992
+ console.log(`- ${warning.message}`);
2993
+ }
2994
+ }
2995
+ return 0;
2996
+ }
2997
+ console.error("Registry validation failed.");
2998
+ for (const error of result.errors) {
2999
+ console.error(`- ${error.message}`);
3000
+ }
3001
+ if (result.warnings.length) {
3002
+ console.error("Warnings:");
3003
+ for (const warning of result.warnings) {
3004
+ console.error(`- ${warning.message}`);
3005
+ }
3006
+ }
3007
+ return 1;
3008
+ }
3009
+ console.error(`Unknown command: ${command}`);
3010
+ console.log(helpText);
3011
+ return 1;
3012
+ }
3013
+ main().then((code) => {
3014
+ if (typeof code === "number") {
3015
+ process.exitCode = code;
3016
+ }
3017
+ }).catch((err) => {
3018
+ console.error(err instanceof Error ? err.message : String(err));
3019
+ process.exitCode = 1;
3020
+ });