aicomputer 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +7 -0
  2. package/dist/index.js +1436 -305
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,16 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command9 } from "commander";
5
- import chalk8 from "chalk";
4
+ import { Command as Command11 } from "commander";
5
+ import chalk11 from "chalk";
6
6
  import { readFileSync as readFileSync3 } from "fs";
7
7
  import { basename as basename2 } from "path";
8
8
 
9
9
  // src/commands/access.ts
10
10
  import { spawn } from "child_process";
11
- import { select } from "@inquirer/prompts";
12
11
  import { Command } from "commander";
13
- import chalk2 from "chalk";
12
+ import chalk3 from "chalk";
14
13
  import ora from "ora";
15
14
 
16
15
  // src/lib/config.ts
@@ -159,6 +158,11 @@ async function createComputer(input) {
159
158
  body: JSON.stringify(input)
160
159
  });
161
160
  }
161
+ async function deleteComputer(computerID) {
162
+ return api(`/v1/computers/${computerID}`, {
163
+ method: "DELETE"
164
+ });
165
+ }
162
166
  async function getFilesystemSettings() {
163
167
  return api("/v1/me/filesystem");
164
168
  }
@@ -236,6 +240,79 @@ function normalizePrimaryPath(primaryPath) {
236
240
  return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
237
241
  }
238
242
 
243
+ // src/lib/computer-picker.ts
244
+ import { select } from "@inquirer/prompts";
245
+ import chalk2 from "chalk";
246
+
247
+ // src/lib/format.ts
248
+ import chalk from "chalk";
249
+ function padEnd(str, len) {
250
+ const visible = str.replace(/\u001b\[[0-9;]*m/g, "");
251
+ return str + " ".repeat(Math.max(0, len - visible.length));
252
+ }
253
+ function timeAgo(dateStr) {
254
+ const seconds = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1e3);
255
+ if (seconds < 60) return `${seconds}s ago`;
256
+ const minutes = Math.floor(seconds / 60);
257
+ if (minutes < 60) return `${minutes}m ago`;
258
+ const hours = Math.floor(minutes / 60);
259
+ if (hours < 24) return `${hours}h ago`;
260
+ const days = Math.floor(hours / 24);
261
+ return `${days}d ago`;
262
+ }
263
+ function formatStatus(status) {
264
+ switch (status) {
265
+ case "running":
266
+ return chalk.green(status);
267
+ case "pending":
268
+ case "provisioning":
269
+ case "starting":
270
+ return chalk.yellow(status);
271
+ case "stopping":
272
+ case "stopped":
273
+ case "deleted":
274
+ return chalk.gray(status);
275
+ case "error":
276
+ return chalk.red(status);
277
+ default:
278
+ return status;
279
+ }
280
+ }
281
+
282
+ // src/lib/computer-picker.ts
283
+ async function promptForSSHComputer(computers, message) {
284
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
285
+ throw new Error("computer id or handle is required when not running interactively");
286
+ }
287
+ const available = computers.filter(isSSHSelectable);
288
+ if (available.length === 0) {
289
+ if (computers.length === 0) {
290
+ throw new Error("no computers found");
291
+ }
292
+ throw new Error("no running computers with SSH enabled");
293
+ }
294
+ const handleWidth = Math.max(6, ...available.map((computer) => computer.handle.length));
295
+ const selectedID = await select({
296
+ message,
297
+ pageSize: Math.min(available.length, 10),
298
+ choices: available.map((computer) => ({
299
+ name: `${padEnd(chalk2.white(computer.handle), handleWidth + 2)}${padEnd(formatStatus(computer.status), 12)}${chalk2.dim(describeSSHChoice(computer))}`,
300
+ value: computer.id
301
+ }))
302
+ });
303
+ return available.find((computer) => computer.id === selectedID) ?? available[0];
304
+ }
305
+ function isSSHSelectable(computer) {
306
+ return computer.ssh_enabled && computer.status === "running";
307
+ }
308
+ function describeSSHChoice(computer) {
309
+ const displayName = computer.display_name.trim();
310
+ if (displayName && displayName !== computer.handle) {
311
+ return `${displayName} ${timeAgo(computer.updated_at)}`;
312
+ }
313
+ return `${computer.runtime_family} ${timeAgo(computer.updated_at)}`;
314
+ }
315
+
239
316
  // src/lib/ssh-config.ts
240
317
  import { homedir as homedir2 } from "os";
241
318
  import { join as join2 } from "path";
@@ -370,41 +447,6 @@ async function generateSSHKey() {
370
447
  return { publicKeyPath, privateKeyPath };
371
448
  }
372
449
 
373
- // src/lib/format.ts
374
- import chalk from "chalk";
375
- function padEnd(str, len) {
376
- const visible = str.replace(/\u001b\[[0-9;]*m/g, "");
377
- return str + " ".repeat(Math.max(0, len - visible.length));
378
- }
379
- function timeAgo(dateStr) {
380
- const seconds = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1e3);
381
- if (seconds < 60) return `${seconds}s ago`;
382
- const minutes = Math.floor(seconds / 60);
383
- if (minutes < 60) return `${minutes}m ago`;
384
- const hours = Math.floor(minutes / 60);
385
- if (hours < 24) return `${hours}h ago`;
386
- const days = Math.floor(hours / 24);
387
- return `${days}d ago`;
388
- }
389
- function formatStatus(status) {
390
- switch (status) {
391
- case "running":
392
- return chalk.green(status);
393
- case "pending":
394
- case "provisioning":
395
- case "starting":
396
- return chalk.yellow(status);
397
- case "stopping":
398
- case "stopped":
399
- case "deleted":
400
- return chalk.gray(status);
401
- case "error":
402
- return chalk.red(status);
403
- default:
404
- return status;
405
- }
406
- }
407
-
408
450
  // src/lib/open-browser.ts
409
451
  import { constants } from "fs";
410
452
  import { access } from "fs/promises";
@@ -466,9 +508,9 @@ var openCommand = new Command("open").description("Open a computer in your brows
466
508
  throw new Error("VNC is not available for this computer");
467
509
  }
468
510
  const url = info.connection.vnc_url;
469
- spinner.succeed(`Opening VNC for ${chalk2.bold(computer.handle)}`);
511
+ spinner.succeed(`Opening VNC for ${chalk3.bold(computer.handle)}`);
470
512
  await openBrowserURL(url);
471
- console.log(chalk2.dim(` ${url}`));
513
+ console.log(chalk3.dim(` ${url}`));
472
514
  return;
473
515
  }
474
516
  if (options.terminal) {
@@ -476,15 +518,15 @@ var openCommand = new Command("open").description("Open a computer in your brows
476
518
  throw new Error("Terminal access is not available for this computer");
477
519
  }
478
520
  const access3 = await createTerminalAccess(computer.id);
479
- spinner.succeed(`Opening terminal for ${chalk2.bold(computer.handle)}`);
521
+ spinner.succeed(`Opening terminal for ${chalk3.bold(computer.handle)}`);
480
522
  await openBrowserURL(access3.access_url);
481
- console.log(chalk2.dim(` ${access3.access_url}`));
523
+ console.log(chalk3.dim(` ${access3.access_url}`));
482
524
  return;
483
525
  }
484
526
  const access2 = await createBrowserAccess(computer.id);
485
- spinner.succeed(`Opening ${chalk2.bold(computer.handle)}`);
527
+ spinner.succeed(`Opening ${chalk3.bold(computer.handle)}`);
486
528
  await openBrowserURL(access2.access_url);
487
- console.log(chalk2.dim(` ${access2.access_url}`));
529
+ console.log(chalk3.dim(` ${access2.access_url}`));
488
530
  } catch (error) {
489
531
  spinner.fail(error instanceof Error ? error.message : "Failed to open computer");
490
532
  process.exit(1);
@@ -505,8 +547,8 @@ var sshCommand = new Command("ssh").description("Open an SSH session to a comput
505
547
  throw new Error("SSH is not available for this computer");
506
548
  }
507
549
  const registered = await ensureDefaultSSHKeyRegistered();
508
- spinner.succeed(`Connecting to ${chalk2.bold(computer.handle)}`);
509
- console.log(chalk2.dim(` ${formatSSHCommand(info.connection.ssh_user, info.connection.ssh_host, info.connection.ssh_port)}`));
550
+ spinner.succeed(`Connecting to ${chalk3.bold(computer.handle)}`);
551
+ console.log(chalk3.dim(` ${formatSSHCommand(info.connection.ssh_user, info.connection.ssh_host, info.connection.ssh_port)}`));
510
552
  console.log();
511
553
  await runSSH([
512
554
  "-i",
@@ -529,17 +571,17 @@ portsCommand.command("ls").description("List published ports for a computer").ar
529
571
  spinner.stop();
530
572
  if (ports.length === 0) {
531
573
  console.log();
532
- console.log(chalk2.dim(" No published ports."));
574
+ console.log(chalk3.dim(" No published ports."));
533
575
  console.log();
534
576
  return;
535
577
  }
536
578
  const subWidth = Math.max(10, ...ports.map((p) => p.subdomain.length));
537
579
  console.log();
538
580
  console.log(
539
- ` ${chalk2.dim(padEnd("Subdomain", subWidth + 2))}${chalk2.dim(padEnd("Port", 8))}${chalk2.dim("Protocol")}`
581
+ ` ${chalk3.dim(padEnd("Subdomain", subWidth + 2))}${chalk3.dim(padEnd("Port", 8))}${chalk3.dim("Protocol")}`
540
582
  );
541
583
  console.log(
542
- ` ${chalk2.dim("-".repeat(subWidth + 2))}${chalk2.dim("-".repeat(8))}${chalk2.dim("-".repeat(8))}`
584
+ ` ${chalk3.dim("-".repeat(subWidth + 2))}${chalk3.dim("-".repeat(8))}${chalk3.dim("-".repeat(8))}`
543
585
  );
544
586
  for (const port of ports) {
545
587
  const url = `https://${port.subdomain}--${computer.handle}.computer.agentcomputer.ai`;
@@ -547,7 +589,7 @@ portsCommand.command("ls").description("List published ports for a computer").ar
547
589
  ` ${padEnd(port.subdomain, subWidth + 2)}${padEnd(String(port.target_port), 8)}${port.protocol}`
548
590
  );
549
591
  console.log(
550
- ` ${chalk2.dim(url)}`
592
+ ` ${chalk3.dim(url)}`
551
593
  );
552
594
  }
553
595
  console.log();
@@ -570,8 +612,8 @@ portsCommand.command("publish").description("Publish an HTTP app port").argument
570
612
  protocol: options.protocol
571
613
  });
572
614
  const url = `https://${published.subdomain}--${computer.handle}.computer.agentcomputer.ai`;
573
- spinner.succeed(`Published port ${chalk2.bold(String(published.target_port))}`);
574
- console.log(chalk2.dim(` ${url}`));
615
+ spinner.succeed(`Published port ${chalk3.bold(String(published.target_port))}`);
616
+ console.log(chalk3.dim(` ${url}`));
575
617
  } catch (error) {
576
618
  spinner.fail(error instanceof Error ? error.message : "Failed to publish port");
577
619
  process.exit(1);
@@ -586,7 +628,7 @@ portsCommand.command("rm").description("Unpublish an app port").argument("<id-or
586
628
  }
587
629
  const computer = await resolveComputer(identifier);
588
630
  await deletePublishedPort(computer.id, targetPort);
589
- spinner.succeed(`Removed port ${chalk2.bold(String(targetPort))}`);
631
+ spinner.succeed(`Removed port ${chalk3.bold(String(targetPort))}`);
590
632
  } catch (error) {
591
633
  spinner.fail(error instanceof Error ? error.message : "Failed to remove port");
592
634
  process.exit(1);
@@ -623,11 +665,11 @@ async function setupSSHAlias(options) {
623
665
  });
624
666
  spinner.succeed(`SSH alias '${alias}' is ready`);
625
667
  console.log();
626
- console.log(chalk2.dim(` SSH config: ${configResult.configPath}`));
627
- console.log(chalk2.dim(` Identity: ${registered.privateKeyPath}`));
668
+ console.log(chalk3.dim(` SSH config: ${configResult.configPath}`));
669
+ console.log(chalk3.dim(` Identity: ${registered.privateKeyPath}`));
628
670
  console.log();
629
- console.log(` ${chalk2.bold("Shell:")} ssh ${alias}`);
630
- console.log(` ${chalk2.bold("Direct:")} ssh <handle>@${alias}`);
671
+ console.log(` ${chalk3.bold("Shell:")} ssh ${alias}`);
672
+ console.log(` ${chalk3.bold("Direct:")} ssh <handle>@${alias}`);
631
673
  console.log();
632
674
  } catch (error) {
633
675
  spinner.fail(error instanceof Error ? error.message : "Failed to configure SSH alias");
@@ -675,43 +717,13 @@ async function resolveSSHComputer(identifier, spinner) {
675
717
  if (trimmed) {
676
718
  return resolveComputer(trimmed);
677
719
  }
678
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
679
- throw new Error("computer id or handle is required when not running interactively");
680
- }
681
720
  const computers = await listComputers();
682
- const available = computers.filter(isSSHSelectable);
683
- if (available.length === 0) {
684
- if (computers.length === 0) {
685
- throw new Error("no computers found");
686
- }
687
- throw new Error("no running computers with SSH enabled");
688
- }
689
- const handleWidth = Math.max(6, ...available.map((computer) => computer.handle.length));
690
721
  spinner.stop();
691
- let selectedID;
692
722
  try {
693
- selectedID = await select({
694
- message: "Select a computer to SSH into",
695
- pageSize: Math.min(available.length, 10),
696
- choices: available.map((computer) => ({
697
- name: `${padEnd(chalk2.white(computer.handle), handleWidth + 2)}${padEnd(formatStatus(computer.status), 12)}${chalk2.dim(describeSSHChoice(computer))}`,
698
- value: computer.id
699
- }))
700
- });
723
+ return await promptForSSHComputer(computers, "Select a computer to SSH into");
701
724
  } finally {
702
725
  spinner.start("Preparing SSH access...");
703
726
  }
704
- return available.find((computer) => computer.id === selectedID) ?? available[0];
705
- }
706
- function isSSHSelectable(computer) {
707
- return computer.ssh_enabled && computer.status === "running";
708
- }
709
- function describeSSHChoice(computer) {
710
- const displayName = computer.display_name.trim();
711
- if (displayName && displayName !== computer.handle) {
712
- return `${displayName} ${timeAgo(computer.updated_at)}`;
713
- }
714
- return `${computer.runtime_family} ${timeAgo(computer.updated_at)}`;
715
727
  }
716
728
 
717
729
  // src/commands/acp.ts
@@ -857,8 +869,28 @@ function emitError(id, code, message) {
857
869
  }
858
870
  });
859
871
  }
860
- async function forwardRawEventStream(computerID, sessionID, signal) {
861
- const response = await openAgentSessionEventsStream(computerID, sessionID, { signal });
872
+ function forwardBridgePayload(payload, publicSessionID, backendSessionID) {
873
+ let message;
874
+ try {
875
+ message = JSON.parse(payload);
876
+ } catch {
877
+ return;
878
+ }
879
+ const method = typeof message.method === "string" ? message.method : "";
880
+ if (method !== "session/update" && method !== "session/request_permission") {
881
+ return;
882
+ }
883
+ const params = typeof message.params === "object" && message.params !== null ? { ...message.params } : {};
884
+ const payloadSessionID = typeof params.sessionId === "string" ? params.sessionId.trim() : "";
885
+ if (backendSessionID?.trim() && payloadSessionID && payloadSessionID !== backendSessionID.trim()) {
886
+ return;
887
+ }
888
+ params.sessionId = publicSessionID;
889
+ process.stdout.write(`${JSON.stringify({ ...message, params })}
890
+ `);
891
+ }
892
+ async function forwardRawEventStream(computerID, publicSessionID, backendSessionID, signal) {
893
+ const response = await openAgentSessionEventsStream(computerID, publicSessionID, { signal });
862
894
  if (!response.body) {
863
895
  return;
864
896
  }
@@ -882,8 +914,7 @@ async function forwardRawEventStream(computerID, sessionID, signal) {
882
914
  if (!payload || payload === "heartbeat") {
883
915
  continue;
884
916
  }
885
- process.stdout.write(`${payload}
886
- `);
917
+ forwardBridgePayload(payload, publicSessionID, backendSessionID);
887
918
  }
888
919
  }
889
920
  }
@@ -904,15 +935,21 @@ async function handlePromptRequest(computerID, sessionID, id, params) {
904
935
  if (prompt.length === 0) {
905
936
  throw new Error("session/prompt requires prompt content");
906
937
  }
938
+ const accepted = await promptAgentSession(computerID, sessionID, { prompt });
939
+ const current = await getAgentSession(computerID, sessionID);
907
940
  const controller = new AbortController();
908
- const streamPromise = forwardRawEventStream(computerID, sessionID, controller.signal).catch((error) => {
941
+ const streamPromise = forwardRawEventStream(
942
+ computerID,
943
+ sessionID,
944
+ current.backend_session_id,
945
+ controller.signal
946
+ ).catch((error) => {
909
947
  if (error instanceof Error && error.name === "AbortError") {
910
948
  return;
911
949
  }
912
- throw error;
950
+ return;
913
951
  });
914
952
  try {
915
- const accepted = await promptAgentSession(computerID, sessionID, { prompt });
916
953
  const settled = await waitForSessionToSettle(computerID, sessionID, accepted.prompt_id);
917
954
  emitResult(id, {
918
955
  stopReason: settled.last_stop_reason || "end_turn"
@@ -1037,22 +1074,22 @@ acpCommand.command("serve").description("Serve a local stdio ACP bridge backed b
1037
1074
 
1038
1075
  // src/commands/agent.ts
1039
1076
  import { Command as Command3 } from "commander";
1040
- import chalk3 from "chalk";
1077
+ import chalk4 from "chalk";
1041
1078
  import ora2 from "ora";
1042
1079
  function formatAgentSessionStatus(status) {
1043
1080
  switch (status) {
1044
1081
  case "idle":
1045
- return chalk3.green(status);
1082
+ return chalk4.green(status);
1046
1083
  case "running":
1047
- return chalk3.blue(status);
1084
+ return chalk4.blue(status);
1048
1085
  case "cancelling":
1049
- return chalk3.yellow(status);
1086
+ return chalk4.yellow(status);
1050
1087
  case "interrupted":
1051
- return chalk3.yellow(status);
1088
+ return chalk4.yellow(status);
1052
1089
  case "failed":
1053
- return chalk3.red(status);
1090
+ return chalk4.red(status);
1054
1091
  case "closed":
1055
- return chalk3.gray(status);
1092
+ return chalk4.gray(status);
1056
1093
  default:
1057
1094
  return status;
1058
1095
  }
@@ -1061,14 +1098,14 @@ function printAgents(agents) {
1061
1098
  const idWidth = Math.max(5, ...agents.map((agent) => agent.id.length));
1062
1099
  console.log();
1063
1100
  console.log(
1064
- ` ${chalk3.dim(padEnd("Agent", idWidth + 2))}${chalk3.dim(padEnd("Installed", 12))}${chalk3.dim(padEnd("Creds", 8))}${chalk3.dim("Version")}`
1101
+ ` ${chalk4.dim(padEnd("Agent", idWidth + 2))}${chalk4.dim(padEnd("Installed", 12))}${chalk4.dim(padEnd("Creds", 8))}${chalk4.dim("Version")}`
1065
1102
  );
1066
1103
  console.log(
1067
- ` ${chalk3.dim("-".repeat(idWidth + 2))}${chalk3.dim("-".repeat(12))}${chalk3.dim("-".repeat(8))}${chalk3.dim("-".repeat(12))}`
1104
+ ` ${chalk4.dim("-".repeat(idWidth + 2))}${chalk4.dim("-".repeat(12))}${chalk4.dim("-".repeat(8))}${chalk4.dim("-".repeat(12))}`
1068
1105
  );
1069
1106
  for (const agent of agents) {
1070
1107
  console.log(
1071
- ` ${padEnd(agent.id, idWidth + 2)}${padEnd(agent.installed ? chalk3.green("yes") : chalk3.gray("no"), 12)}${padEnd(agent.credentialsAvailable ? chalk3.green("yes") : chalk3.yellow("no"), 8)}${agent.version ?? chalk3.dim("unknown")}`
1108
+ ` ${padEnd(agent.id, idWidth + 2)}${padEnd(agent.installed ? chalk4.green("yes") : chalk4.gray("no"), 12)}${padEnd(agent.credentialsAvailable ? chalk4.green("yes") : chalk4.yellow("no"), 8)}${agent.version ?? chalk4.dim("unknown")}`
1072
1109
  );
1073
1110
  }
1074
1111
  console.log();
@@ -1076,7 +1113,7 @@ function printAgents(agents) {
1076
1113
  function printSessions(sessions, handleByComputerID = /* @__PURE__ */ new Map()) {
1077
1114
  if (sessions.length === 0) {
1078
1115
  console.log();
1079
- console.log(chalk3.dim(" No agent sessions found."));
1116
+ console.log(chalk4.dim(" No agent sessions found."));
1080
1117
  console.log();
1081
1118
  return;
1082
1119
  }
@@ -1085,19 +1122,19 @@ function printSessions(sessions, handleByComputerID = /* @__PURE__ */ new Map())
1085
1122
  const statusWidth = 13;
1086
1123
  console.log();
1087
1124
  console.log(
1088
- ` ${chalk3.dim(padEnd("Session", 14))}${chalk3.dim(padEnd("Name", nameWidth + 2))}${chalk3.dim(padEnd("Agent", agentWidth + 2))}${chalk3.dim(padEnd("Status", statusWidth + 2))}${chalk3.dim("Location")}`
1125
+ ` ${chalk4.dim(padEnd("Session", 14))}${chalk4.dim(padEnd("Name", nameWidth + 2))}${chalk4.dim(padEnd("Agent", agentWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim("Location")}`
1089
1126
  );
1090
1127
  console.log(
1091
- ` ${chalk3.dim("-".repeat(14))}${chalk3.dim("-".repeat(nameWidth + 2))}${chalk3.dim("-".repeat(agentWidth + 2))}${chalk3.dim("-".repeat(statusWidth + 2))}${chalk3.dim("-".repeat(20))}`
1128
+ ` ${chalk4.dim("-".repeat(14))}${chalk4.dim("-".repeat(nameWidth + 2))}${chalk4.dim("-".repeat(agentWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(20))}`
1092
1129
  );
1093
1130
  for (const session of sessions) {
1094
1131
  const location = handleByComputerID.get(session.computer_id) ?? session.computer_id;
1095
1132
  console.log(
1096
1133
  ` ${padEnd(session.id.slice(0, 12), 14)}${padEnd(session.name || "default", nameWidth + 2)}${padEnd(session.agent, agentWidth + 2)}${padEnd(formatAgentSessionStatus(session.status), statusWidth + 2)}${location}`
1097
1134
  );
1098
- console.log(` ${chalk3.dim(` cwd=${session.cwd} updated=${timeAgo(session.updated_at)}`)}`);
1135
+ console.log(` ${chalk4.dim(` cwd=${session.cwd} updated=${timeAgo(session.updated_at)}`)}`);
1099
1136
  if (session.last_stop_reason || session.last_error) {
1100
- console.log(` ${chalk3.dim(` ${session.last_error ? `error=${session.last_error}` : `stop=${session.last_stop_reason}`}`)}`);
1137
+ console.log(` ${chalk4.dim(` ${session.last_error ? `error=${session.last_error}` : `stop=${session.last_stop_reason}`}`)}`);
1101
1138
  }
1102
1139
  }
1103
1140
  console.log();
@@ -1111,7 +1148,7 @@ var StreamPrinter = class {
1111
1148
  }
1112
1149
  writeDim(text) {
1113
1150
  this.ensureBreak();
1114
- process.stdout.write(chalk3.dim(text));
1151
+ process.stdout.write(chalk4.dim(text));
1115
1152
  this.inlineOpen = !text.endsWith("\n");
1116
1153
  }
1117
1154
  writeLine(text) {
@@ -1129,7 +1166,7 @@ var StreamPrinter = class {
1129
1166
  };
1130
1167
  async function streamSessionEvents(computerID, sessionID, options = {}) {
1131
1168
  const response = await openAgentSessionEventsStream(computerID, sessionID, {
1132
- lastEventId: options.replay ? "0" : void 0,
1169
+ lastEventId: options.lastEventId?.trim() || void 0,
1133
1170
  signal: options.signal
1134
1171
  });
1135
1172
  if (!response.body) {
@@ -1179,7 +1216,7 @@ function renderSSEChunk(chunk, printer, asJson) {
1179
1216
  try {
1180
1217
  envelope = JSON.parse(payload);
1181
1218
  } catch {
1182
- printer.writeLine(chalk3.dim(payload));
1219
+ printer.writeLine(chalk4.dim(payload));
1183
1220
  return;
1184
1221
  }
1185
1222
  const method = typeof envelope.method === "string" ? envelope.method : "";
@@ -1206,14 +1243,14 @@ function renderSSEChunk(chunk, printer, asJson) {
1206
1243
  case "tool_call_update": {
1207
1244
  const title = typeof update?.title === "string" && update.title ? update.title : "tool";
1208
1245
  const status = typeof update?.status === "string" && update.status ? update.status : "in_progress";
1209
- printer.writeLine(chalk3.dim(`[tool:${status}] ${title}`));
1246
+ printer.writeLine(chalk4.dim(`[tool:${status}] ${title}`));
1210
1247
  return;
1211
1248
  }
1212
1249
  case "plan": {
1213
1250
  const entries = Array.isArray(update?.entries) ? update.entries : [];
1214
1251
  const detail = entries.filter((entry) => typeof entry === "object" && entry !== null).map((entry) => `- [${entry.status ?? "pending"}] ${entry.content ?? ""}`).join("\n");
1215
1252
  if (detail) {
1216
- printer.writeLine(chalk3.dim(`Plan
1253
+ printer.writeLine(chalk4.dim(`Plan
1217
1254
  ${detail}`));
1218
1255
  }
1219
1256
  return;
@@ -1223,7 +1260,7 @@ ${detail}`));
1223
1260
  }
1224
1261
  }
1225
1262
  if (method === "session/request_permission") {
1226
- printer.writeLine(chalk3.yellow("Permission requested by remote agent"));
1263
+ printer.writeLine(chalk4.yellow("Permission requested by remote agent"));
1227
1264
  return;
1228
1265
  }
1229
1266
  }
@@ -1314,22 +1351,33 @@ agentCommand.command("prompt").description("Send a prompt to a machine agent ses
1314
1351
  const computer = await resolveComputer(identifier);
1315
1352
  const session = await resolvePromptSession(computer.id, options);
1316
1353
  spinner?.stop();
1317
- if (!options.noStream) {
1354
+ const canStreamImmediately = !options.noStream && Boolean(session.backend_session_id?.trim());
1355
+ if (canStreamImmediately) {
1318
1356
  streamPromise = streamSessionEvents(computer.id, session.id, {
1319
- replay: false,
1320
1357
  json: options.json,
1321
1358
  signal: controller.signal
1322
1359
  }).catch((error) => {
1323
1360
  if (error instanceof Error && error.name === "AbortError") {
1324
1361
  return;
1325
1362
  }
1326
- throw error;
1363
+ return;
1327
1364
  });
1328
1365
  await new Promise((resolve) => setTimeout(resolve, 150));
1329
1366
  }
1330
1367
  const promptResponse = await promptAgentSession(computer.id, session.id, {
1331
1368
  prompt: [{ type: "text", text }]
1332
1369
  });
1370
+ if (!options.noStream && !canStreamImmediately) {
1371
+ streamPromise = streamSessionEvents(computer.id, session.id, {
1372
+ json: options.json,
1373
+ signal: controller.signal
1374
+ }).catch((error) => {
1375
+ if (error instanceof Error && error.name === "AbortError") {
1376
+ return;
1377
+ }
1378
+ return;
1379
+ });
1380
+ }
1333
1381
  const settled = await waitForSessionToSettle(computer.id, session.id, promptResponse.prompt_id);
1334
1382
  controller.abort();
1335
1383
  if (streamPromise) {
@@ -1340,12 +1388,12 @@ agentCommand.command("prompt").description("Send a prompt to a machine agent ses
1340
1388
  return;
1341
1389
  }
1342
1390
  console.log();
1343
- console.log(` ${chalk3.bold(session.name || "default")} ${formatAgentSessionStatus(settled.status)}`);
1391
+ console.log(` ${chalk4.bold(session.name || "default")} ${formatAgentSessionStatus(settled.status)}`);
1344
1392
  if (settled.last_stop_reason) {
1345
- console.log(chalk3.dim(` stop_reason=${settled.last_stop_reason}`));
1393
+ console.log(chalk4.dim(` stop_reason=${settled.last_stop_reason}`));
1346
1394
  }
1347
1395
  if (settled.last_error) {
1348
- console.log(chalk3.red(` error=${settled.last_error}`));
1396
+ console.log(chalk4.red(` error=${settled.last_error}`));
1349
1397
  }
1350
1398
  console.log();
1351
1399
  } catch (error) {
@@ -1358,11 +1406,14 @@ agentCommand.command("prompt").description("Send a prompt to a machine agent ses
1358
1406
  process.exit(1);
1359
1407
  }
1360
1408
  });
1361
- agentCommand.command("watch").description("Watch a machine agent session event stream").argument("<machine>", "Computer id or handle").requiredOption("--session <id>", "Session id").option("--no-replay", "Do not replay buffered events before following live output").option("--json", "Print raw event envelopes").action(async (identifier, options) => {
1409
+ agentCommand.command("watch").description("Watch a machine agent session event stream").argument("<machine>", "Computer id or handle").requiredOption("--session <id>", "Session id").option("--last-event-id <id>", "Replay buffered events after this event id").option("--json", "Print raw event envelopes").action(async (identifier, options) => {
1362
1410
  try {
1363
1411
  const computer = await resolveComputer(identifier);
1412
+ if (options.lastEventId && !/^[1-9]\d*$/.test(options.lastEventId.trim())) {
1413
+ throw new Error("--last-event-id must be a positive integer");
1414
+ }
1364
1415
  await streamSessionEvents(computer.id, options.session.trim(), {
1365
- replay: options.replay,
1416
+ lastEventId: options.lastEventId,
1366
1417
  json: options.json
1367
1418
  });
1368
1419
  } catch (error) {
@@ -1472,105 +1523,677 @@ fleetCommand.command("status").description("List open agent sessions across all
1472
1523
  }
1473
1524
  });
1474
1525
 
1475
- // src/commands/computers.ts
1526
+ // src/commands/claude-auth.ts
1527
+ import { randomBytes, createHash } from "crypto";
1528
+ import { spawn as spawn2 } from "child_process";
1529
+ import { input as textInput } from "@inquirer/prompts";
1476
1530
  import { Command as Command4 } from "commander";
1477
- import chalk4 from "chalk";
1531
+ import chalk5 from "chalk";
1478
1532
  import ora3 from "ora";
1479
- import { select as select2, input as textInput, confirm } from "@inquirer/prompts";
1480
- function isInternalCondition(value) {
1481
- return /^[A-Z][a-zA-Z]+$/.test(value);
1482
- }
1483
- function printComputer(computer) {
1484
- const vnc = vncURL(computer);
1485
- const terminal = terminalURL(computer);
1486
- const ssh = computer.ssh_enabled ? formatSSHCommand2(computer.handle, computer.ssh_host, computer.ssh_port) : "disabled";
1487
- const isCustom = computer.runtime_family === "custom-machine";
1488
- console.log();
1489
- console.log(` ${chalk4.bold.white(computer.handle)} ${formatStatus(computer.status)}`);
1533
+ var CLAUDE_OAUTH_CLIENT_ID = process.env.CLAUDE_OAUTH_CLIENT_ID ?? "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
1534
+ var CLAUDE_OAUTH_AUTHORIZE_URL = process.env.CLAUDE_OAUTH_AUTHORIZE_URL ?? "https://claude.ai/oauth/authorize";
1535
+ var CLAUDE_OAUTH_TOKEN_URL = process.env.CLAUDE_OAUTH_TOKEN_URL ?? "https://platform.claude.com/v1/oauth/token";
1536
+ var CLAUDE_OAUTH_REDIRECT_URL = process.env.CLAUDE_OAUTH_REDIRECT_URL ?? "https://platform.claude.com/oauth/code/callback";
1537
+ var CLAUDE_OAUTH_SCOPES = (process.env.CLAUDE_OAUTH_SCOPES ?? "user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload").split(/\s+/).filter(Boolean);
1538
+ var readyPollIntervalMs = 2e3;
1539
+ var readyPollTimeoutMs = 18e4;
1540
+ var claudeAuthCommand = new Command4("claude-auth").alias("claude-login").description("Authenticate Claude Code on a computer").option("--machine <id-or-handle>", "Use a specific computer").option("--keep-helper", "Keep a temporary helper machine if one is created").option("--skip-cross-check", "Skip verification on a second shared machine").action(async (options) => {
1541
+ const todos = createTodoList();
1542
+ let target = null;
1543
+ let helperCreated = false;
1544
+ let sharedInstall = false;
1545
+ let activeTodoID = "target";
1546
+ let failureMessage = null;
1490
1547
  console.log();
1491
- console.log(` ${chalk4.dim("ID")} ${computer.id}`);
1492
- console.log(` ${chalk4.dim("Tier")} ${computer.tier}`);
1493
- if (isCustom) {
1494
- console.log(` ${chalk4.dim("Runtime")} ${computer.runtime_family}`);
1495
- console.log(` ${chalk4.dim("Source")} ${computer.source_kind}`);
1496
- console.log(` ${chalk4.dim("Image")} ${computer.image_family}`);
1497
- }
1498
- console.log(` ${chalk4.dim("Primary")} :${computer.primary_port}${computer.primary_path}`);
1499
- console.log(` ${chalk4.dim("Health")} ${computer.healthcheck_type}${computer.healthcheck_value ? ` (${computer.healthcheck_value})` : ""}`);
1500
- console.log();
1501
- console.log(` ${chalk4.dim("Gateway")} ${chalk4.cyan(webURL(computer))}`);
1502
- console.log(` ${chalk4.dim("VNC")} ${vnc ? chalk4.cyan(vnc) : chalk4.dim("not available")}`);
1503
- console.log(` ${chalk4.dim("Terminal")} ${terminal ? chalk4.cyan(terminal) : chalk4.dim("not available")}`);
1504
- console.log(` ${chalk4.dim("SSH")} ${computer.ssh_enabled ? chalk4.white(ssh) : chalk4.dim(ssh)}`);
1505
- if (computer.last_error) {
1506
- console.log();
1507
- if (isInternalCondition(computer.last_error)) {
1508
- console.log(` ${chalk4.dim("Condition")} ${chalk4.dim(computer.last_error)}`);
1548
+ console.log(chalk5.cyan("Authenticating with Claude Code...\n"));
1549
+ try {
1550
+ const prepared = await prepareTargetMachine(options);
1551
+ target = prepared.computer;
1552
+ helperCreated = prepared.helperCreated;
1553
+ sharedInstall = prepared.sharedInstall;
1554
+ markTodo(
1555
+ todos,
1556
+ "target",
1557
+ "done",
1558
+ prepared.detail
1559
+ );
1560
+ activeTodoID = "ready";
1561
+ target = await waitForRunning(target);
1562
+ markTodo(todos, "ready", "done", `${target.handle} is running`);
1563
+ activeTodoID = "oauth";
1564
+ const oauth = await runManualOAuthFlow();
1565
+ markTodo(todos, "oauth", "done", "browser flow completed");
1566
+ activeTodoID = "install";
1567
+ const sshTarget = await resolveSSHTarget(target);
1568
+ await installClaudeAuth(sshTarget, oauth);
1569
+ markTodo(
1570
+ todos,
1571
+ "install",
1572
+ "done",
1573
+ sharedInstall ? `installed Claude login on shared home via ${target.handle}` : `installed Claude login on ${target.handle}`
1574
+ );
1575
+ activeTodoID = "verify-primary";
1576
+ await verifyStoredAuth(sshTarget);
1577
+ markTodo(
1578
+ todos,
1579
+ "verify-primary",
1580
+ "done",
1581
+ `${target.handle} fresh login shell sees Claude auth`
1582
+ );
1583
+ activeTodoID = "verify-shared";
1584
+ const sharedCheck = await verifySecondaryMachine(
1585
+ target.id,
1586
+ sharedInstall,
1587
+ Boolean(options.skipCrossCheck)
1588
+ );
1589
+ if (sharedCheck.status === "verified") {
1590
+ markTodo(
1591
+ todos,
1592
+ "verify-shared",
1593
+ "done",
1594
+ `${sharedCheck.handle} also sees stored Claude auth`
1595
+ );
1596
+ } else {
1597
+ markTodo(todos, "verify-shared", "skipped", sharedCheck.reason);
1598
+ }
1599
+ } catch (error) {
1600
+ failureMessage = error instanceof Error ? error.message : "Failed to authenticate Claude";
1601
+ markTodo(todos, activeTodoID, "failed", failureMessage);
1602
+ } finally {
1603
+ if (helperCreated && target && !options.keepHelper) {
1604
+ try {
1605
+ await deleteComputer(target.id);
1606
+ markTodo(
1607
+ todos,
1608
+ "cleanup",
1609
+ "done",
1610
+ `removed temporary helper ${target.handle}`
1611
+ );
1612
+ } catch (error) {
1613
+ const message = error instanceof Error ? error.message : "failed to remove helper";
1614
+ markTodo(todos, "cleanup", "failed", message);
1615
+ }
1616
+ } else if (helperCreated && target && options.keepHelper) {
1617
+ markTodo(
1618
+ todos,
1619
+ "cleanup",
1620
+ "skipped",
1621
+ `kept helper ${target.handle}`
1622
+ );
1509
1623
  } else {
1510
- console.log(` ${chalk4.dim("Error")} ${chalk4.red(computer.last_error)}`);
1624
+ markTodo(todos, "cleanup", "skipped", "no helper created");
1511
1625
  }
1626
+ printTodoList(todos);
1627
+ }
1628
+ if (failureMessage) {
1629
+ console.error(chalk5.red(`
1630
+ ${failureMessage}`));
1631
+ process.exit(1);
1632
+ }
1633
+ if (target) {
1634
+ console.log(chalk5.green(`Claude is ready on ${chalk5.bold(target.handle)}.`));
1635
+ console.log();
1636
+ }
1637
+ });
1638
+ function createTodoList() {
1639
+ return [
1640
+ { id: "target", label: "Pick target computer", state: "pending" },
1641
+ { id: "ready", label: "Wait for machine readiness", state: "pending" },
1642
+ { id: "oauth", label: "Complete Claude browser auth", state: "pending" },
1643
+ { id: "install", label: "Install stored Claude login", state: "pending" },
1644
+ { id: "verify-primary", label: "Verify on target machine", state: "pending" },
1645
+ { id: "verify-shared", label: "Verify shared-home propagation", state: "pending" },
1646
+ { id: "cleanup", label: "Clean up temporary helper", state: "pending" }
1647
+ ];
1648
+ }
1649
+ function markTodo(items, id, state, detail) {
1650
+ const item = items.find((entry) => entry.id === id);
1651
+ if (!item) {
1652
+ return;
1512
1653
  }
1654
+ item.state = state;
1655
+ item.detail = detail;
1656
+ }
1657
+ function printTodoList(items) {
1658
+ console.log();
1659
+ console.log(chalk5.dim("TODO"));
1513
1660
  console.log();
1514
- console.log(` ${chalk4.dim("Created")} ${timeAgo(computer.created_at)}`);
1661
+ for (const item of items) {
1662
+ const marker = item.state === "done" ? chalk5.green("[x]") : item.state === "skipped" ? chalk5.yellow("[-]") : item.state === "failed" ? chalk5.red("[!]") : chalk5.dim("[ ]");
1663
+ const detail = item.detail ? chalk5.dim(` ${item.detail}`) : "";
1664
+ console.log(` ${marker} ${item.label}${detail ? ` ${detail}` : ""}`);
1665
+ }
1515
1666
  console.log();
1516
1667
  }
1517
- function formatSSHCommand2(user, host, port) {
1518
- if (!user.trim() || !host.trim()) {
1519
- return "ssh unavailable";
1668
+ async function prepareTargetMachine(options) {
1669
+ if (options.machine?.trim()) {
1670
+ const computer2 = await resolveComputer(options.machine.trim());
1671
+ assertClaudeAuthTarget(computer2);
1672
+ return {
1673
+ computer: computer2,
1674
+ helperCreated: false,
1675
+ sharedInstall: isSharedInstallTarget(computer2),
1676
+ detail: describeTarget(computer2, false)
1677
+ };
1520
1678
  }
1521
- if (port <= 0 || port === 22) {
1522
- return `ssh ${user}@${host}`;
1679
+ const computers = await listComputers();
1680
+ const filesystemSettings = await getFilesystemSettings().catch(() => null);
1681
+ if (filesystemSettings?.shared_enabled) {
1682
+ const existing = pickSharedRunningComputer(computers);
1683
+ if (existing) {
1684
+ return {
1685
+ computer: existing,
1686
+ helperCreated: false,
1687
+ sharedInstall: true,
1688
+ detail: describeTarget(existing, false)
1689
+ };
1690
+ }
1691
+ const spinner = ora3("Creating temporary shared helper...").start();
1692
+ try {
1693
+ const helper = await createComputer({
1694
+ handle: `claude-auth-${randomSuffix(6)}`,
1695
+ display_name: "Claude Auth Helper",
1696
+ runtime_family: "managed-worker",
1697
+ use_platform_default: true,
1698
+ ssh_enabled: true,
1699
+ vnc_enabled: false
1700
+ });
1701
+ spinner.succeed(`Created temporary helper ${chalk5.bold(helper.handle)}`);
1702
+ return {
1703
+ computer: helper,
1704
+ helperCreated: true,
1705
+ sharedInstall: true,
1706
+ detail: describeTarget(helper, true)
1707
+ };
1708
+ } catch (error) {
1709
+ spinner.fail(
1710
+ error instanceof Error ? error.message : "Failed to create temporary helper"
1711
+ );
1712
+ throw error;
1713
+ }
1523
1714
  }
1524
- return `ssh -p ${port} ${user}@${host}`;
1525
- }
1526
- function printComputerTable(computers) {
1527
- const handleWidth = Math.max(6, ...computers.map((c) => c.handle.length));
1528
- const statusWidth = 12;
1529
- const createdWidth = 10;
1530
- console.log();
1531
- console.log(
1532
- ` ${chalk4.dim(padEnd("Handle", handleWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim(padEnd("Created", createdWidth + 2))}${chalk4.dim("URL")}`
1715
+ const computer = await promptForSSHComputer(
1716
+ computers,
1717
+ "Select a computer for Claude auth"
1533
1718
  );
1534
- console.log(
1535
- ` ${chalk4.dim("-".repeat(handleWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(createdWidth + 2))}${chalk4.dim("-".repeat(20))}`
1719
+ return {
1720
+ computer,
1721
+ helperCreated: false,
1722
+ sharedInstall: isSharedInstallTarget(computer),
1723
+ detail: describeTarget(computer, false)
1724
+ };
1725
+ }
1726
+ function pickSharedRunningComputer(computers) {
1727
+ const candidates = computers.filter(
1728
+ (computer) => computer.runtime_family === "managed-worker" && computer.filesystem_mode === "shared" && computer.ssh_enabled && computer.status === "running"
1729
+ ).sort(
1730
+ (left, right) => new Date(right.updated_at).getTime() - new Date(left.updated_at).getTime()
1536
1731
  );
1537
- for (const computer of computers) {
1538
- const status = formatStatus(computer.status);
1539
- const created = chalk4.dim(timeAgo(computer.created_at));
1540
- console.log(
1541
- ` ${chalk4.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${padEnd(created, createdWidth + 2)}${chalk4.cyan(webURL(computer))}`
1542
- );
1732
+ return candidates[0] ?? null;
1733
+ }
1734
+ function assertClaudeAuthTarget(computer) {
1735
+ if (!computer.ssh_enabled) {
1736
+ throw new Error(`${computer.handle} does not have SSH enabled`);
1543
1737
  }
1544
- console.log();
1545
1738
  }
1546
- function printComputerTableVerbose(computers) {
1547
- const handleWidth = Math.max(6, ...computers.map((c) => c.handle.length));
1548
- const statusWidth = 10;
1739
+ function isSharedInstallTarget(computer) {
1740
+ return computer.runtime_family === "managed-worker" && computer.filesystem_mode === "shared";
1741
+ }
1742
+ function describeTarget(computer, helperCreated) {
1743
+ if (helperCreated) {
1744
+ return `created temporary helper ${computer.handle}`;
1745
+ }
1746
+ if (isSharedInstallTarget(computer)) {
1747
+ return `using shared machine ${computer.handle}`;
1748
+ }
1749
+ return `using ${computer.handle}`;
1750
+ }
1751
+ async function waitForRunning(initial) {
1752
+ if (initial.status === "running") {
1753
+ return initial;
1754
+ }
1755
+ const spinner = ora3(`Waiting for ${chalk5.bold(initial.handle)} to be ready...`).start();
1756
+ const deadline = Date.now() + readyPollTimeoutMs;
1757
+ let lastStatus = initial.status;
1758
+ while (Date.now() < deadline) {
1759
+ const current = await getComputerByID(initial.id);
1760
+ if (current.status === "running") {
1761
+ spinner.succeed(`${chalk5.bold(current.handle)} is ready`);
1762
+ return current;
1763
+ }
1764
+ if (current.status !== lastStatus) {
1765
+ lastStatus = current.status;
1766
+ spinner.text = `Waiting for ${chalk5.bold(current.handle)}... ${chalk5.dim(current.status)}`;
1767
+ }
1768
+ if (current.status === "error" || current.status === "deleted" || current.status === "stopped") {
1769
+ spinner.fail(`${current.handle} entered ${current.status}`);
1770
+ throw new Error(current.last_error || `${current.handle} entered ${current.status}`);
1771
+ }
1772
+ await delay(readyPollIntervalMs);
1773
+ }
1774
+ spinner.fail(`Timed out waiting for ${initial.handle}`);
1775
+ throw new Error(`timed out waiting for ${initial.handle} to be ready`);
1776
+ }
1777
+ async function runManualOAuthFlow() {
1778
+ const codeVerifier = base64url(randomBytes(32));
1779
+ const state = randomBytes(16).toString("hex");
1780
+ const codeChallenge = base64url(createHash("sha256").update(codeVerifier).digest());
1781
+ const url = buildAuthorizationURL(codeChallenge, state);
1782
+ console.log("We will open your browser so you can authenticate with Claude.");
1783
+ console.log("If the browser does not open automatically, use the URL below:\n");
1784
+ console.log(url);
1549
1785
  console.log();
1786
+ try {
1787
+ await openBrowserURL(url);
1788
+ } catch {
1789
+ console.log(chalk5.yellow("Unable to open the browser automatically."));
1790
+ }
1550
1791
  console.log(
1551
- ` ${chalk4.dim(padEnd("Handle", handleWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim("URLs")}`
1552
- );
1553
- console.log(
1554
- ` ${chalk4.dim("-".repeat(handleWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(20))}`
1792
+ "After completing authentication, copy the code shown on the success page."
1555
1793
  );
1556
- for (const computer of computers) {
1794
+ console.log("You can paste either the full URL, or a value formatted as CODE#STATE.\n");
1795
+ const pasted = (await textInput({
1796
+ message: "Paste the authorization code (or URL) here:"
1797
+ })).trim();
1798
+ if (!pasted) {
1799
+ throw new Error("no authorization code provided");
1800
+ }
1801
+ const parsed = parseAuthorizationInput(pasted, state);
1802
+ const spinner = ora3("Exchanging authorization code...").start();
1803
+ try {
1804
+ const response = await fetch(CLAUDE_OAUTH_TOKEN_URL, {
1805
+ method: "POST",
1806
+ headers: {
1807
+ "Content-Type": "application/json"
1808
+ },
1809
+ body: JSON.stringify({
1810
+ grant_type: "authorization_code",
1811
+ code: parsed.code,
1812
+ state: parsed.state,
1813
+ redirect_uri: CLAUDE_OAUTH_REDIRECT_URL,
1814
+ client_id: CLAUDE_OAUTH_CLIENT_ID,
1815
+ code_verifier: codeVerifier
1816
+ })
1817
+ });
1818
+ if (!response.ok) {
1819
+ throw new Error(
1820
+ `token exchange failed: ${response.status} ${await response.text()}`
1821
+ );
1822
+ }
1823
+ const payload = await response.json();
1824
+ if (!payload.refresh_token || !payload.scope) {
1825
+ throw new Error("token exchange returned an incomplete response");
1826
+ }
1827
+ spinner.succeed("Authorization code exchanged");
1828
+ return {
1829
+ refreshToken: payload.refresh_token,
1830
+ scope: payload.scope
1831
+ };
1832
+ } catch (error) {
1833
+ spinner.fail(
1834
+ error instanceof Error ? error.message : "Failed to exchange authorization code"
1835
+ );
1836
+ throw error;
1837
+ }
1838
+ }
1839
+ function buildAuthorizationURL(codeChallenge, state) {
1840
+ const params = new URLSearchParams({
1841
+ code: "true",
1842
+ client_id: CLAUDE_OAUTH_CLIENT_ID,
1843
+ response_type: "code",
1844
+ redirect_uri: CLAUDE_OAUTH_REDIRECT_URL,
1845
+ scope: CLAUDE_OAUTH_SCOPES.join(" "),
1846
+ code_challenge: codeChallenge,
1847
+ code_challenge_method: "S256",
1848
+ state
1849
+ });
1850
+ return `${CLAUDE_OAUTH_AUTHORIZE_URL}?${params.toString()}`;
1851
+ }
1852
+ function parseAuthorizationInput(value, expectedState) {
1853
+ if (value.startsWith("http://") || value.startsWith("https://")) {
1854
+ const parsed = new URL(value);
1855
+ const code2 = parsed.searchParams.get("code");
1856
+ const state2 = parsed.searchParams.get("state");
1857
+ if (!code2 || !state2) {
1858
+ throw new Error("pasted URL is missing code or state");
1859
+ }
1860
+ if (state2 !== expectedState) {
1861
+ throw new Error("state mismatch detected; restart the authentication flow");
1862
+ }
1863
+ return { code: code2, state: state2 };
1864
+ }
1865
+ const [code, state] = value.split("#", 2).map((part) => part?.trim() ?? "");
1866
+ if (!code || !state) {
1867
+ throw new Error("expected a full URL or a CODE#STATE value");
1868
+ }
1869
+ if (state !== expectedState) {
1870
+ throw new Error("state mismatch detected; restart the authentication flow");
1871
+ }
1872
+ return { code, state };
1873
+ }
1874
+ async function resolveSSHTarget(computer) {
1875
+ const registered = await ensureDefaultSSHKeyRegistered();
1876
+ const info = await getConnectionInfo(computer.id);
1877
+ if (!info.connection.ssh_available) {
1878
+ throw new Error(`SSH is not available for ${computer.handle}`);
1879
+ }
1880
+ return {
1881
+ handle: computer.handle,
1882
+ host: info.connection.ssh_host,
1883
+ port: info.connection.ssh_port,
1884
+ user: info.connection.ssh_user,
1885
+ identityFile: registered.privateKeyPath
1886
+ };
1887
+ }
1888
+ async function installClaudeAuth(target, oauth) {
1889
+ const spinner = ora3(`Installing Claude auth on ${chalk5.bold(target.handle)}...`).start();
1890
+ try {
1891
+ const installScript = buildInstallScript(oauth.refreshToken, oauth.scope);
1892
+ const result = await runRemoteBash(target, ["-s"], installScript);
1893
+ if (result.stdout.trim()) {
1894
+ spinner.succeed(`Installed Claude auth on ${chalk5.bold(target.handle)}`);
1895
+ return;
1896
+ }
1897
+ spinner.succeed(`Installed Claude auth on ${chalk5.bold(target.handle)}`);
1898
+ } catch (error) {
1899
+ spinner.fail(
1900
+ error instanceof Error ? error.message : `Failed to install Claude auth on ${target.handle}`
1901
+ );
1902
+ throw error;
1903
+ }
1904
+ }
1905
+ function buildInstallScript(refreshToken, scopes) {
1906
+ const tokenMarker = `TOKEN_${randomSuffix(12)}`;
1907
+ const scopeMarker = `SCOPES_${randomSuffix(12)}`;
1908
+ return [
1909
+ "set -euo pipefail",
1910
+ 'command -v claude >/dev/null 2>&1 || { echo "claude is not installed on this computer" >&2; exit 1; }',
1911
+ `export CLAUDE_CODE_OAUTH_REFRESH_TOKEN="$(cat <<'` + tokenMarker + "'",
1912
+ refreshToken,
1913
+ tokenMarker,
1914
+ ')"',
1915
+ `export CLAUDE_CODE_OAUTH_SCOPES="$(cat <<'` + scopeMarker + "'",
1916
+ scopes,
1917
+ scopeMarker,
1918
+ ')"',
1919
+ "claude auth login",
1920
+ "unset CLAUDE_CODE_OAUTH_REFRESH_TOKEN",
1921
+ "unset CLAUDE_CODE_OAUTH_SCOPES"
1922
+ ].join("\n");
1923
+ }
1924
+ async function verifyStoredAuth(target) {
1925
+ const command = freshShellCommand("claude auth status --json");
1926
+ const result = await runRemoteBash(target, ["-lc", command]);
1927
+ const payload = parseStatusOutput(result.stdout);
1928
+ if (!payload.loggedIn) {
1929
+ throw new Error(`Claude auth is not visible on ${target.handle}`);
1930
+ }
1931
+ }
1932
+ function freshShellCommand(inner) {
1933
+ return [
1934
+ "env -i",
1935
+ 'HOME="$HOME"',
1936
+ 'USER="${USER:-node}"',
1937
+ 'SHELL="/bin/bash"',
1938
+ 'PATH="$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin"',
1939
+ "bash -lc",
1940
+ shellQuote(inner)
1941
+ ].join(" ");
1942
+ }
1943
+ function parseStatusOutput(stdout) {
1944
+ const start = stdout.indexOf("{");
1945
+ const end = stdout.lastIndexOf("}");
1946
+ if (start === -1 || end === -1 || end <= start) {
1947
+ throw new Error("could not parse claude auth status output");
1948
+ }
1949
+ const parsed = JSON.parse(stdout.slice(start, end + 1));
1950
+ return { loggedIn: parsed.loggedIn === true };
1951
+ }
1952
+ async function verifySecondaryMachine(primaryComputerID, sharedInstall, skip) {
1953
+ if (!sharedInstall) {
1954
+ return { status: "skipped", reason: "target uses isolated filesystem" };
1955
+ }
1956
+ if (skip) {
1957
+ return { status: "skipped", reason: "cross-check skipped by flag" };
1958
+ }
1959
+ const secondary = (await listComputers()).filter(
1960
+ (computer) => computer.id !== primaryComputerID && computer.runtime_family === "managed-worker" && computer.filesystem_mode === "shared" && computer.ssh_enabled && computer.status === "running"
1961
+ ).sort(
1962
+ (left, right) => new Date(right.updated_at).getTime() - new Date(left.updated_at).getTime()
1963
+ )[0];
1964
+ if (!secondary) {
1965
+ return {
1966
+ status: "skipped",
1967
+ reason: "no second running shared managed-worker was available"
1968
+ };
1969
+ }
1970
+ const sshTarget = await resolveSSHTarget(secondary);
1971
+ await verifyStoredAuth(sshTarget);
1972
+ return { status: "verified", handle: secondary.handle };
1973
+ }
1974
+ async function runRemoteBash(target, bashArgs, script) {
1975
+ const args = [
1976
+ "-T",
1977
+ "-i",
1978
+ target.identityFile,
1979
+ "-p",
1980
+ String(target.port),
1981
+ `${target.user}@${target.host}`,
1982
+ "bash",
1983
+ ...bashArgs
1984
+ ];
1985
+ return new Promise((resolve, reject) => {
1986
+ const child = spawn2("ssh", args, {
1987
+ stdio: ["pipe", "pipe", "pipe"]
1988
+ });
1989
+ let stdout = "";
1990
+ let stderr = "";
1991
+ child.stdout.on("data", (chunk) => {
1992
+ stdout += chunk.toString();
1993
+ });
1994
+ child.stderr.on("data", (chunk) => {
1995
+ stderr += chunk.toString();
1996
+ });
1997
+ child.on("error", reject);
1998
+ child.on("exit", (code) => {
1999
+ if (code === 0) {
2000
+ resolve({ stdout, stderr });
2001
+ return;
2002
+ }
2003
+ const message = stderr.trim() || stdout.trim() || `ssh exited with code ${code ?? 1}`;
2004
+ reject(new Error(message));
2005
+ });
2006
+ if (script !== void 0) {
2007
+ child.stdin.end(script);
2008
+ } else {
2009
+ child.stdin.end();
2010
+ }
2011
+ });
2012
+ }
2013
+ function shellQuote(value) {
2014
+ return `'${value.replace(/'/g, `'\\''`)}'`;
2015
+ }
2016
+ function base64url(buffer) {
2017
+ return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
2018
+ }
2019
+ function randomSuffix(length) {
2020
+ return randomBytes(Math.ceil(length / 2)).toString("hex").slice(0, length);
2021
+ }
2022
+ function delay(ms) {
2023
+ return new Promise((resolve) => {
2024
+ setTimeout(resolve, ms);
2025
+ });
2026
+ }
2027
+
2028
+ // src/commands/computers.ts
2029
+ import { Command as Command5 } from "commander";
2030
+ import chalk6 from "chalk";
2031
+ import ora4 from "ora";
2032
+ import { select as select2, input as textInput2, confirm } from "@inquirer/prompts";
2033
+
2034
+ // src/lib/machine-sources.ts
2035
+ async function getMachineSourceSettings() {
2036
+ return api("/v1/me/machine-source");
2037
+ }
2038
+ async function upsertMachineSource(input) {
2039
+ return api("/v1/me/machine-source", {
2040
+ method: "PUT",
2041
+ body: JSON.stringify(input)
2042
+ });
2043
+ }
2044
+ async function clearMachineSourceDefault() {
2045
+ return api("/v1/me/machine-source", {
2046
+ method: "DELETE"
2047
+ });
2048
+ }
2049
+ async function rebuildMachineSource(sourceID) {
2050
+ return api(`/v1/me/machine-source/${encodeURIComponent(sourceID)}/rebuild`, {
2051
+ method: "POST"
2052
+ });
2053
+ }
2054
+ async function deleteMachineSource(sourceID) {
2055
+ return api(`/v1/me/machine-source/${encodeURIComponent(sourceID)}`, {
2056
+ method: "DELETE"
2057
+ });
2058
+ }
2059
+ function summarizeMachineSourceSelection(settings) {
2060
+ if (settings.platform_default || !settings.default_machine_source) {
2061
+ return "AgentComputer platform default image";
2062
+ }
2063
+ return summarizeMachineSource(settings.default_machine_source);
2064
+ }
2065
+ function summarizeMachineSource(source) {
2066
+ if (source.kind === "oci-image") {
2067
+ return source.requested_ref || "OCI image";
2068
+ }
2069
+ const parts = [
2070
+ source.git_url || "Nix git source",
2071
+ source.git_ref ? `ref ${source.git_ref}` : "",
2072
+ source.git_subpath ? `subpath ${source.git_subpath}` : ""
2073
+ ].filter(Boolean);
2074
+ return parts.join(" | ");
2075
+ }
2076
+
2077
+ // src/commands/computers.ts
2078
+ function isInternalCondition(value) {
2079
+ return /^[A-Z][a-zA-Z]+$/.test(value);
2080
+ }
2081
+ function printComputer(computer) {
2082
+ const vnc = vncURL(computer);
2083
+ const terminal = terminalURL(computer);
2084
+ const ssh = computer.ssh_enabled ? formatSSHCommand2(computer.handle, computer.ssh_host, computer.ssh_port) : "disabled";
2085
+ const isCustom = computer.runtime_family === "custom-machine";
2086
+ console.log();
2087
+ console.log(` ${chalk6.bold.white(computer.handle)} ${formatStatus(computer.status)}`);
2088
+ console.log();
2089
+ console.log(` ${chalk6.dim("ID")} ${computer.id}`);
2090
+ console.log(` ${chalk6.dim("Tier")} ${computer.tier}`);
2091
+ if (isCustom) {
2092
+ console.log(` ${chalk6.dim("Runtime")} ${computer.runtime_family}`);
2093
+ console.log(` ${chalk6.dim("Source")} ${computer.source_kind}`);
2094
+ console.log(` ${chalk6.dim("Image")} ${computer.image_family}`);
2095
+ } else {
2096
+ console.log(` ${chalk6.dim("Runtime")} ${computer.runtime_family}`);
2097
+ console.log(` ${chalk6.dim("Launch")} ${formatManagedWorkerLaunchSource(computer)}`);
2098
+ if (computer.user_source_id && computer.resolved_image_ref) {
2099
+ console.log(` ${chalk6.dim("Image")} ${computer.resolved_image_ref}`);
2100
+ }
2101
+ }
2102
+ console.log(` ${chalk6.dim("Primary")} :${computer.primary_port}${computer.primary_path}`);
2103
+ console.log(` ${chalk6.dim("Health")} ${computer.healthcheck_type}${computer.healthcheck_value ? ` (${computer.healthcheck_value})` : ""}`);
2104
+ console.log();
2105
+ console.log(` ${chalk6.dim("Gateway")} ${chalk6.cyan(webURL(computer))}`);
2106
+ console.log(` ${chalk6.dim("VNC")} ${vnc ? chalk6.cyan(vnc) : chalk6.dim("not available")}`);
2107
+ console.log(` ${chalk6.dim("Terminal")} ${terminal ? chalk6.cyan(terminal) : chalk6.dim("not available")}`);
2108
+ console.log(` ${chalk6.dim("SSH")} ${computer.ssh_enabled ? chalk6.white(ssh) : chalk6.dim(ssh)}`);
2109
+ if (computer.last_error) {
2110
+ console.log();
2111
+ if (isInternalCondition(computer.last_error)) {
2112
+ console.log(` ${chalk6.dim("Condition")} ${chalk6.dim(computer.last_error)}`);
2113
+ } else {
2114
+ console.log(` ${chalk6.dim("Error")} ${chalk6.red(computer.last_error)}`);
2115
+ }
2116
+ }
2117
+ console.log();
2118
+ console.log(` ${chalk6.dim("Created")} ${timeAgo(computer.created_at)}`);
2119
+ console.log();
2120
+ }
2121
+ function formatSSHCommand2(user, host, port) {
2122
+ if (!user.trim() || !host.trim()) {
2123
+ return "ssh unavailable";
2124
+ }
2125
+ if (port <= 0 || port === 22) {
2126
+ return `ssh ${user}@${host}`;
2127
+ }
2128
+ return `ssh -p ${port} ${user}@${host}`;
2129
+ }
2130
+ function formatManagedWorkerLaunchSource(computer) {
2131
+ if (!computer.user_source_id) {
2132
+ return "AgentComputer managed-worker platform default";
2133
+ }
2134
+ if (computer.source_repo_url) {
2135
+ let value = computer.source_repo_url;
2136
+ if (computer.source_ref) {
2137
+ value += ` @ ${computer.source_ref}`;
2138
+ }
2139
+ if (computer.source_subpath) {
2140
+ value += ` # ${computer.source_subpath}`;
2141
+ }
2142
+ return `saved nix-git source ${computer.user_source_id} (${value})`;
2143
+ }
2144
+ if (computer.source_ref) {
2145
+ return `saved oci-image source ${computer.user_source_id} (${computer.source_ref})`;
2146
+ }
2147
+ return `saved custom source ${computer.user_source_id}`;
2148
+ }
2149
+ function printComputerTable(computers) {
2150
+ const handleWidth = Math.max(6, ...computers.map((c) => c.handle.length));
2151
+ const statusWidth = 12;
2152
+ const createdWidth = 10;
2153
+ console.log();
2154
+ console.log(
2155
+ ` ${chalk6.dim(padEnd("Handle", handleWidth + 2))}${chalk6.dim(padEnd("Status", statusWidth + 2))}${chalk6.dim(padEnd("Created", createdWidth + 2))}${chalk6.dim("URL")}`
2156
+ );
2157
+ console.log(
2158
+ ` ${chalk6.dim("-".repeat(handleWidth + 2))}${chalk6.dim("-".repeat(statusWidth + 2))}${chalk6.dim("-".repeat(createdWidth + 2))}${chalk6.dim("-".repeat(20))}`
2159
+ );
2160
+ for (const computer of computers) {
2161
+ const status = formatStatus(computer.status);
2162
+ const created = chalk6.dim(timeAgo(computer.created_at));
2163
+ console.log(
2164
+ ` ${chalk6.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${padEnd(created, createdWidth + 2)}${chalk6.cyan(webURL(computer))}`
2165
+ );
2166
+ }
2167
+ console.log();
2168
+ }
2169
+ function printComputerTableVerbose(computers) {
2170
+ const handleWidth = Math.max(6, ...computers.map((c) => c.handle.length));
2171
+ const statusWidth = 10;
2172
+ console.log();
2173
+ console.log(
2174
+ ` ${chalk6.dim(padEnd("Handle", handleWidth + 2))}${chalk6.dim(padEnd("Status", statusWidth + 2))}${chalk6.dim("URLs")}`
2175
+ );
2176
+ console.log(
2177
+ ` ${chalk6.dim("-".repeat(handleWidth + 2))}${chalk6.dim("-".repeat(statusWidth + 2))}${chalk6.dim("-".repeat(20))}`
2178
+ );
2179
+ for (const computer of computers) {
1557
2180
  const status = formatStatus(computer.status);
1558
2181
  const vnc = vncURL(computer);
1559
2182
  const terminal = terminalURL(computer);
1560
2183
  console.log(
1561
- ` ${chalk4.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${chalk4.cyan(webURL(computer))}`
2184
+ ` ${chalk6.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${chalk6.cyan(webURL(computer))}`
1562
2185
  );
1563
2186
  console.log(
1564
- ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk4.dim(vnc ?? "VNC not available")}`
2187
+ ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk6.dim(vnc ?? "VNC not available")}`
1565
2188
  );
1566
2189
  console.log(
1567
- ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk4.dim(terminal ?? "Terminal not available")}`
2190
+ ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk6.dim(terminal ?? "Terminal not available")}`
1568
2191
  );
1569
2192
  }
1570
2193
  console.log();
1571
2194
  }
1572
- var lsCommand = new Command4("ls").description("List computers").option("--json", "Print raw JSON").option("-v, --verbose", "Show all URLs for each computer").action(async (options) => {
1573
- const spinner = options.json ? null : ora3("Fetching computers...").start();
2195
+ var lsCommand = new Command5("ls").description("List computers").option("--json", "Print raw JSON").option("-v, --verbose", "Show all URLs for each computer").action(async (options) => {
2196
+ const spinner = options.json ? null : ora4("Fetching computers...").start();
1574
2197
  try {
1575
2198
  const computers = await listComputers();
1576
2199
  spinner?.stop();
@@ -1580,7 +2203,7 @@ var lsCommand = new Command4("ls").description("List computers").option("--json"
1580
2203
  }
1581
2204
  if (computers.length === 0) {
1582
2205
  console.log();
1583
- console.log(chalk4.dim(" No computers found."));
2206
+ console.log(chalk6.dim(" No computers found."));
1584
2207
  console.log();
1585
2208
  return;
1586
2209
  }
@@ -1600,8 +2223,8 @@ var lsCommand = new Command4("ls").description("List computers").option("--json"
1600
2223
  process.exit(1);
1601
2224
  }
1602
2225
  });
1603
- var getCommand = new Command4("get").description("Show computer details").argument("<id-or-handle>", "Computer id or handle").option("--json", "Print raw JSON").action(async (identifier, options) => {
1604
- const spinner = options.json ? null : ora3("Fetching computer...").start();
2226
+ var getCommand = new Command5("get").description("Show computer details").argument("<id-or-handle>", "Computer id or handle").option("--json", "Print raw JSON").action(async (identifier, options) => {
2227
+ const spinner = options.json ? null : ora4("Fetching computer...").start();
1605
2228
  try {
1606
2229
  const computer = await resolveComputer(identifier);
1607
2230
  spinner?.stop();
@@ -1621,19 +2244,28 @@ var getCommand = new Command4("get").description("Show computer details").argume
1621
2244
  process.exit(1);
1622
2245
  }
1623
2246
  });
1624
- var createCommand = new Command4("create").description("Create a computer").argument("[handle]", "Optional computer handle").option("--name <display-name>", "Display name").option("--tier <tier>", "Tier override").option("--interactive", "Prompt for runtime choices").option("--runtime-family <runtime-family>", "managed-worker or custom-machine").option("--source-kind <source-kind>", "none or oci-image").option("--image-family <family>", "Image family override").option("--image-ref <image>", "Resolved image override").option("--primary-port <port>", "Primary app port").option("--primary-path <path>", "Primary app path").option("--healthcheck-type <type>", "http or tcp").option("--healthcheck-value <value>", "Health check path or port").option("--ssh-enabled", "Enable SSH access").option("--ssh-disabled", "Disable SSH access").option("--vnc-enabled", "Enable VNC access").option("--vnc-disabled", "Disable VNC access").action(async (handle, options) => {
2247
+ var createCommand = new Command5("create").description("Create a computer").argument("[handle]", "Optional computer handle").option("--name <display-name>", "Display name").option("--tier <tier>", "Tier override").option("--interactive", "Prompt for runtime choices").option("--runtime-family <runtime-family>", "managed-worker or custom-machine").option("--source-kind <source-kind>", "none or oci-image").option("--image-family <family>", "Image family override").option("--image-ref <image>", "Resolved image override").option("--use-platform-default", "Use the AgentComputer platform default image").option("--primary-port <port>", "Primary app port").option("--primary-path <path>", "Primary app path").option("--healthcheck-type <type>", "http or tcp").option("--healthcheck-value <value>", "Health check path or port").option("--ssh-enabled", "Enable SSH access").option("--ssh-disabled", "Disable SSH access").option("--vnc-enabled", "Enable VNC access").option("--vnc-disabled", "Disable VNC access").action(async (handle, options) => {
1625
2248
  let spinner;
1626
2249
  let timer;
1627
2250
  let startTime = 0;
1628
2251
  try {
1629
2252
  const selectedOptions = await resolveCreateOptions(options);
1630
2253
  const runtimeFamily = effectiveRuntimeFamily(selectedOptions.runtimeFamily);
2254
+ const machineSourceSettings = runtimeFamily === "managed-worker" ? await getMachineSourceSettings().catch(() => null) : null;
1631
2255
  const filesystemSettings = await loadFilesystemSettingsForCreate(runtimeFamily);
2256
+ const machineSourceNote = createMachineSourceNote(
2257
+ runtimeFamily,
2258
+ machineSourceSettings,
2259
+ Boolean(selectedOptions.usePlatformDefault)
2260
+ );
2261
+ if (machineSourceNote) {
2262
+ console.log(chalk6.dim(machineSourceNote));
2263
+ }
1632
2264
  const provisioningNote = createProvisioningNote(runtimeFamily, filesystemSettings);
1633
2265
  if (provisioningNote) {
1634
- console.log(chalk4.dim(provisioningNote));
2266
+ console.log(chalk6.dim(provisioningNote));
1635
2267
  }
1636
- spinner = ora3(createSpinnerText(runtimeFamily, filesystemSettings, 0)).start();
2268
+ spinner = ora4(createSpinnerText(runtimeFamily, filesystemSettings, 0)).start();
1637
2269
  startTime = Date.now();
1638
2270
  timer = setInterval(() => {
1639
2271
  const elapsed2 = (Date.now() - startTime) / 1e3;
@@ -1649,6 +2281,7 @@ var createCommand = new Command4("create").description("Create a computer").argu
1649
2281
  source_kind: parseSourceKindOption(selectedOptions.sourceKind),
1650
2282
  image_family: selectedOptions.imageFamily,
1651
2283
  image_ref: selectedOptions.imageRef,
2284
+ use_platform_default: selectedOptions.usePlatformDefault,
1652
2285
  primary_port: parseOptionalPort(selectedOptions.primaryPort),
1653
2286
  primary_path: selectedOptions.primaryPath,
1654
2287
  healthcheck_type: parseHealthcheckTypeOption(selectedOptions.healthcheckType),
@@ -1669,7 +2302,7 @@ var createCommand = new Command4("create").description("Create a computer").argu
1669
2302
  }
1670
2303
  const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
1671
2304
  spinner.succeed(
1672
- chalk4.green(`Created ${chalk4.bold(computer.handle)} ${chalk4.dim(`[${elapsed}s]`)}`)
2305
+ chalk6.green(`Created ${chalk6.bold(computer.handle)} ${chalk6.dim(`[${elapsed}s]`)}`)
1673
2306
  );
1674
2307
  printComputer(computer);
1675
2308
  } catch (error) {
@@ -1678,18 +2311,17 @@ var createCommand = new Command4("create").description("Create a computer").argu
1678
2311
  }
1679
2312
  const message = error instanceof Error ? error.message : "Failed to create computer";
1680
2313
  if (spinner) {
1681
- const suffix = startTime ? ` ${chalk4.dim(`[${((Date.now() - startTime) / 1e3).toFixed(1)}s]`)}` : "";
2314
+ const suffix = startTime ? ` ${chalk6.dim(`[${((Date.now() - startTime) / 1e3).toFixed(1)}s]`)}` : "";
1682
2315
  spinner.fail(`${message}${suffix}`);
1683
2316
  } else {
1684
- console.error(chalk4.red(message));
2317
+ console.error(chalk6.red(message));
1685
2318
  }
1686
2319
  process.exit(1);
1687
2320
  }
1688
2321
  });
1689
2322
  async function resolveCreateOptions(options) {
1690
2323
  const selectedOptions = { ...options };
1691
- const hasExplicitRuntimeSelection = Boolean(selectedOptions.runtimeFamily) || Boolean(selectedOptions.imageRef) || Boolean(selectedOptions.sourceKind);
1692
- const shouldPrompt = Boolean(selectedOptions.interactive) || !hasExplicitRuntimeSelection;
2324
+ const shouldPrompt = Boolean(selectedOptions.interactive);
1693
2325
  if (!process.stdin.isTTY || !process.stdout.isTTY || !shouldPrompt) {
1694
2326
  validateCreateOptions(selectedOptions);
1695
2327
  return selectedOptions;
@@ -1711,9 +2343,9 @@ async function resolveCreateOptions(options) {
1711
2343
  if (runtimeChoice === "custom-machine") {
1712
2344
  selectedOptions.runtimeFamily = "custom-machine";
1713
2345
  selectedOptions.sourceKind = "oci-image";
1714
- selectedOptions.imageRef = (await textInput({ message: "OCI image ref (required):" })).trim();
1715
- selectedOptions.primaryPort = (await textInput({ message: "Primary port:", default: "3000" })).trim();
1716
- selectedOptions.primaryPath = (await textInput({ message: "Primary path:", default: "/" })).trim();
2346
+ selectedOptions.imageRef = (await textInput2({ message: "OCI image ref (required):" })).trim();
2347
+ selectedOptions.primaryPort = (await textInput2({ message: "Primary port:", default: "3000" })).trim();
2348
+ selectedOptions.primaryPath = (await textInput2({ message: "Primary path:", default: "/" })).trim();
1717
2349
  selectedOptions.healthcheckType = await select2({
1718
2350
  message: "Healthcheck type",
1719
2351
  choices: [
@@ -1722,7 +2354,7 @@ async function resolveCreateOptions(options) {
1722
2354
  ],
1723
2355
  default: "tcp"
1724
2356
  });
1725
- selectedOptions.healthcheckValue = (await textInput({ message: "Healthcheck value (optional):" })).trim();
2357
+ selectedOptions.healthcheckValue = (await textInput2({ message: "Healthcheck value (optional):" })).trim();
1726
2358
  selectedOptions.sshEnabled = await confirm({
1727
2359
  message: "Enable SSH?",
1728
2360
  default: false
@@ -1736,28 +2368,28 @@ async function resolveCreateOptions(options) {
1736
2368
  validateCreateOptions(selectedOptions);
1737
2369
  return selectedOptions;
1738
2370
  }
1739
- var removeCommand = new Command4("rm").description("Delete a computer").argument("<id-or-handle>", "Computer id or handle").option("-y, --yes", "Skip confirmation prompt").action(async (identifier, options, cmd) => {
2371
+ var removeCommand = new Command5("rm").description("Delete a computer").argument("<id-or-handle>", "Computer id or handle").option("-y, --yes", "Skip confirmation prompt").action(async (identifier, options, cmd) => {
1740
2372
  const globalYes = cmd.parent?.opts()?.yes;
1741
2373
  const skipConfirm = Boolean(options.yes || globalYes);
1742
- const spinner = ora3("Resolving computer...").start();
2374
+ const spinner = ora4("Resolving computer...").start();
1743
2375
  try {
1744
2376
  const computer = await resolveComputer(identifier);
1745
2377
  spinner.stop();
1746
2378
  if (!skipConfirm && process.stdin.isTTY) {
1747
2379
  const confirmed = await confirm({
1748
- message: `Delete computer ${chalk4.bold(computer.handle)}?`,
2380
+ message: `Delete computer ${chalk6.bold(computer.handle)}?`,
1749
2381
  default: false
1750
2382
  });
1751
2383
  if (!confirmed) {
1752
- console.log(chalk4.dim(" Cancelled."));
2384
+ console.log(chalk6.dim(" Cancelled."));
1753
2385
  return;
1754
2386
  }
1755
2387
  }
1756
- const deleteSpinner = ora3("Deleting computer...").start();
2388
+ const deleteSpinner = ora4("Deleting computer...").start();
1757
2389
  await api(`/v1/computers/${computer.id}`, {
1758
2390
  method: "DELETE"
1759
2391
  });
1760
- deleteSpinner.succeed(chalk4.green(`Deleted ${chalk4.bold(computer.handle)}`));
2392
+ deleteSpinner.succeed(chalk6.green(`Deleted ${chalk6.bold(computer.handle)}`));
1761
2393
  } catch (error) {
1762
2394
  spinner.fail(
1763
2395
  error instanceof Error ? error.message : "Failed to delete computer"
@@ -1797,17 +2429,33 @@ function createProvisioningNote(runtimeFamily, filesystemSettings) {
1797
2429
  }
1798
2430
  return "Using isolated storage for this desktop.";
1799
2431
  }
2432
+ function createMachineSourceNote(runtimeFamily, machineSourceSettings, usePlatformDefault) {
2433
+ if (runtimeFamily !== "managed-worker") {
2434
+ return null;
2435
+ }
2436
+ if (usePlatformDefault) {
2437
+ return "Using the AgentComputer platform default image for this machine.";
2438
+ }
2439
+ if (!machineSourceSettings) {
2440
+ return null;
2441
+ }
2442
+ if (machineSourceSettings.platform_default || !machineSourceSettings.default_machine_source) {
2443
+ return "Using the AgentComputer platform default image.";
2444
+ }
2445
+ return `Using managed-worker image source: ${summarizeMachineSourceSelection(machineSourceSettings)}.`;
2446
+ }
1800
2447
  function createSpinnerText(runtimeFamily, filesystemSettings, elapsedSeconds) {
1801
- const elapsedLabel = chalk4.dim(`${elapsedSeconds.toFixed(1)}s`);
2448
+ const elapsedLabel = chalk6.dim(`${elapsedSeconds.toFixed(1)}s`);
1802
2449
  if (runtimeFamily === "managed-worker" && filesystemSettings?.shared_enabled && elapsedSeconds >= 5) {
1803
- return `Creating computer... ${elapsedLabel} ${chalk4.dim("mounting shared home")}`;
2450
+ return `Creating computer... ${elapsedLabel} ${chalk6.dim("mounting shared home")}`;
1804
2451
  }
1805
2452
  return `Creating computer... ${elapsedLabel}`;
1806
2453
  }
1807
2454
  function validateCreateOptions(options) {
1808
2455
  const runtimeFamily = parseRuntimeFamilyOption(options.runtimeFamily);
1809
2456
  const sourceKind = parseSourceKindOption(options.sourceKind);
1810
- if (runtimeFamily === "custom-machine" && (typeof options.imageRef !== "string" || options.imageRef.trim() === "")) {
2457
+ const imageRef = options.imageRef?.trim();
2458
+ if (runtimeFamily === "custom-machine" && !imageRef) {
1811
2459
  throw new Error("--image-ref is required for --runtime-family custom-machine");
1812
2460
  }
1813
2461
  if (runtimeFamily === "custom-machine" && sourceKind === "none") {
@@ -1816,6 +2464,12 @@ function validateCreateOptions(options) {
1816
2464
  if (runtimeFamily === "custom-machine" && options.vncEnabled) {
1817
2465
  throw new Error("custom-machine does not support platform VNC");
1818
2466
  }
2467
+ if (runtimeFamily === "custom-machine" && options.usePlatformDefault) {
2468
+ throw new Error("--use-platform-default cannot be used with --runtime-family custom-machine");
2469
+ }
2470
+ if (imageRef && options.usePlatformDefault) {
2471
+ throw new Error("choose either --image-ref or --use-platform-default");
2472
+ }
1819
2473
  }
1820
2474
  function parseSourceKindOption(value) {
1821
2475
  switch (value) {
@@ -1861,7 +2515,7 @@ function resolveOptionalToggle(enabled, disabled, label) {
1861
2515
  }
1862
2516
 
1863
2517
  // src/commands/completion.ts
1864
- import { Command as Command5 } from "commander";
2518
+ import { Command as Command6 } from "commander";
1865
2519
  var ZSH_SCRIPT = `#compdef computer agentcomputer aicomputer
1866
2520
 
1867
2521
  _computer() {
@@ -1870,12 +2524,18 @@ _computer() {
1870
2524
  'login:Authenticate the CLI'
1871
2525
  'logout:Remove stored API key'
1872
2526
  'whoami:Show current user'
2527
+ 'claude-auth:Authenticate Claude Code on a computer'
2528
+ 'claude-login:Alias for claude-auth'
1873
2529
  'create:Create a computer'
1874
2530
  'ls:List computers'
1875
2531
  'get:Show computer details'
2532
+ 'image:Manage machine image sources'
1876
2533
  'open:Open in browser'
1877
2534
  'ssh:SSH into a computer'
1878
2535
  'ports:Manage published ports'
2536
+ 'agent:Manage cloud agent sessions'
2537
+ 'fleet:View agent activity across your fleet'
2538
+ 'acp:Run a local ACP bridge for remote agent sessions'
1879
2539
  'rm:Delete a computer'
1880
2540
  'completion:Generate shell completions'
1881
2541
  'help:Display help'
@@ -1888,6 +2548,15 @@ _computer() {
1888
2548
  'rm:Unpublish an app port'
1889
2549
  )
1890
2550
 
2551
+ local -a image_commands
2552
+ image_commands=(
2553
+ 'ls:List machine image sources'
2554
+ 'save:Create or update a machine image source'
2555
+ 'default:Set the default machine image source'
2556
+ 'rebuild:Rebuild a machine image source'
2557
+ 'rm:Delete a machine image source'
2558
+ )
2559
+
1891
2560
  _arguments -C \\
1892
2561
  '(-h --help)'{-h,--help}'[Display help]' \\
1893
2562
  '(-V --version)'{-V,--version}'[Show version]' \\
@@ -1909,6 +2578,12 @@ _computer() {
1909
2578
  whoami)
1910
2579
  _arguments '--json[Print raw JSON]'
1911
2580
  ;;
2581
+ claude-auth|claude-login)
2582
+ _arguments \\
2583
+ '--machine[Use a specific computer]:computer:_computer_handles' \\
2584
+ '--keep-helper[Keep a temporary helper machine]' \\
2585
+ '--skip-cross-check[Skip second-machine verification]'
2586
+ ;;
1912
2587
  create)
1913
2588
  _arguments \\
1914
2589
  '--name[Display name]:name:' \\
@@ -1918,6 +2593,7 @@ _computer() {
1918
2593
  '--source-kind[Source kind]:kind:(none oci-image)' \\
1919
2594
  '--image-family[Image family]:family:' \\
1920
2595
  '--image-ref[Image reference]:image:' \\
2596
+ '--use-platform-default[Use platform default image]' \\
1921
2597
  '--primary-port[Primary app port]:port:' \\
1922
2598
  '--primary-path[Primary app path]:path:' \\
1923
2599
  '--healthcheck-type[Healthcheck type]:type:(http tcp)' \\
@@ -1938,6 +2614,50 @@ _computer() {
1938
2614
  '--json[Print raw JSON]' \\
1939
2615
  '1:computer:_computer_handles'
1940
2616
  ;;
2617
+ image)
2618
+ _arguments -C \\
2619
+ '1:command:->image_command' \\
2620
+ '*::arg:->image_args'
2621
+ case "$state" in
2622
+ image_command)
2623
+ _describe -t commands 'image command' image_commands
2624
+ ;;
2625
+ image_args)
2626
+ case "$words[2]" in
2627
+ ls)
2628
+ _arguments '--json[Print raw JSON]'
2629
+ ;;
2630
+ save)
2631
+ _arguments \\
2632
+ '--id[source id]:id:' \\
2633
+ '--kind[source kind]:kind:(oci-image nix-git)' \\
2634
+ '--requested-ref[OCI image ref or resolved ref]:ref:' \\
2635
+ '--git-url[Git URL]:url:' \\
2636
+ '--git-ref[Git ref]:ref:' \\
2637
+ '--git-subpath[Git subpath]:path:' \\
2638
+ '--set-as-default[Select as default after saving]' \\
2639
+ '--json[Print raw JSON]'
2640
+ ;;
2641
+ default)
2642
+ _arguments \\
2643
+ '--json[Print raw JSON]' \\
2644
+ '1:source id:_machine_source_ids'
2645
+ ;;
2646
+ rebuild)
2647
+ _arguments \\
2648
+ '--json[Print raw JSON]' \\
2649
+ '1:source id:_machine_source_ids'
2650
+ ;;
2651
+ rm)
2652
+ _arguments \\
2653
+ '--json[Print raw JSON]' \\
2654
+ '(-y --yes)'{-y,--yes}'[Skip confirmation]' \\
2655
+ '1:source id:_machine_source_ids'
2656
+ ;;
2657
+ esac
2658
+ ;;
2659
+ esac
2660
+ ;;
1941
2661
  open)
1942
2662
  _arguments \\
1943
2663
  '--vnc[Open VNC desktop]' \\
@@ -1997,13 +2717,22 @@ _computer_handles() {
1997
2717
  fi
1998
2718
  }
1999
2719
 
2720
+ _machine_source_ids() {
2721
+ local -a ids
2722
+ local cli="\${words[1]:-computer}"
2723
+ if ids=(\${(f)"$(\${cli} image ls --json 2>/dev/null | grep '"id"' | sed 's/.*"id": "\\([^"]*\\)".*/\\1/')"}); then
2724
+ _describe -t ids 'machine image source' ids
2725
+ fi
2726
+ }
2727
+
2000
2728
  _computer "$@"`;
2001
2729
  var BASH_SCRIPT = `_computer() {
2002
2730
  local cur prev words cword
2003
2731
  _init_completion || return
2004
2732
 
2005
- local commands="login logout whoami create ls get open ssh ports rm completion help"
2733
+ local commands="login logout whoami claude-auth claude-login create ls get image open ssh ports agent fleet acp rm completion help"
2006
2734
  local ports_commands="ls publish rm"
2735
+ local image_commands="ls save default rebuild rm"
2007
2736
 
2008
2737
  if [[ $cword -eq 1 ]]; then
2009
2738
  COMPREPLY=($(compgen -W "$commands" -- "$cur"))
@@ -2019,12 +2748,60 @@ var BASH_SCRIPT = `_computer() {
2019
2748
  whoami)
2020
2749
  COMPREPLY=($(compgen -W "--json" -- "$cur"))
2021
2750
  ;;
2751
+ claude-auth|claude-login)
2752
+ COMPREPLY=($(compgen -W "--machine --keep-helper --skip-cross-check" -- "$cur"))
2753
+ ;;
2022
2754
  create)
2023
- COMPREPLY=($(compgen -W "--name --tier --interactive --runtime-family --source-kind --image-family --image-ref --primary-port --primary-path --healthcheck-type --healthcheck-value --ssh-enabled --ssh-disabled --vnc-enabled --vnc-disabled" -- "$cur"))
2755
+ COMPREPLY=($(compgen -W "--name --tier --interactive --runtime-family --source-kind --image-family --image-ref --use-platform-default --primary-port --primary-path --healthcheck-type --healthcheck-value --ssh-enabled --ssh-disabled --vnc-enabled --vnc-disabled" -- "$cur"))
2024
2756
  ;;
2025
2757
  ls)
2026
2758
  COMPREPLY=($(compgen -W "--json --verbose -v" -- "$cur"))
2027
2759
  ;;
2760
+ image)
2761
+ if [[ $cword -eq 2 ]]; then
2762
+ COMPREPLY=($(compgen -W "$image_commands" -- "$cur"))
2763
+ else
2764
+ case "\${words[2]}" in
2765
+ ls)
2766
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
2767
+ ;;
2768
+ save)
2769
+ COMPREPLY=($(compgen -W "--id --kind --requested-ref --git-url --git-ref --git-subpath --set-as-default --json" -- "$cur"))
2770
+ ;;
2771
+ default|rebuild|rm)
2772
+ case "\${words[2]}" in
2773
+ default)
2774
+ if [[ "$cur" == -* ]]; then
2775
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
2776
+ else
2777
+ local source_ids cli="\${words[0]:-computer}"
2778
+ source_ids=$(\${cli} image ls --json 2>/dev/null | grep '"id"' | sed 's/.*"id": "([^"]*)".*/\\1/')
2779
+ COMPREPLY=($(compgen -W "$source_ids" -- "$cur"))
2780
+ fi
2781
+ ;;
2782
+ rebuild)
2783
+ if [[ "$cur" == -* ]]; then
2784
+ COMPREPLY=($(compgen -W "--json" -- "$cur"))
2785
+ else
2786
+ local source_ids cli="\${words[0]:-computer}"
2787
+ source_ids=$(\${cli} image ls --json 2>/dev/null | grep '"id"' | sed 's/.*"id": "([^"]*)".*/\\1/')
2788
+ COMPREPLY=($(compgen -W "$source_ids" -- "$cur"))
2789
+ fi
2790
+ ;;
2791
+ rm)
2792
+ if [[ "$cur" == -* ]]; then
2793
+ COMPREPLY=($(compgen -W "--json --yes -y" -- "$cur"))
2794
+ else
2795
+ local source_ids cli="\${words[0]:-computer}"
2796
+ source_ids=$(\${cli} image ls --json 2>/dev/null | grep '"id"' | sed 's/.*"id": "([^"]*)".*/\\1/')
2797
+ COMPREPLY=($(compgen -W "$source_ids" -- "$cur"))
2798
+ fi
2799
+ ;;
2800
+ esac
2801
+ ;;
2802
+ esac
2803
+ fi
2804
+ ;;
2028
2805
  get|open|ssh|rm)
2029
2806
  if [[ $cword -eq 2 ]]; then
2030
2807
  local handles cli="\${words[0]:-computer}"
@@ -2056,7 +2833,7 @@ var BASH_SCRIPT = `_computer() {
2056
2833
  }
2057
2834
 
2058
2835
  complete -F _computer computer agentcomputer aicomputer`;
2059
- var completionCommand = new Command5("completion").description("Generate shell completions").argument("<shell>", "Shell type (bash or zsh)").action((shell) => {
2836
+ var completionCommand = new Command6("completion").description("Generate shell completions").argument("<shell>", "Shell type (bash or zsh)").action((shell) => {
2060
2837
  switch (shell) {
2061
2838
  case "zsh":
2062
2839
  console.log(ZSH_SCRIPT);
@@ -2070,19 +2847,299 @@ var completionCommand = new Command5("completion").description("Generate shell c
2070
2847
  }
2071
2848
  });
2072
2849
 
2850
+ // src/commands/images.ts
2851
+ import { confirm as confirm2, input as textInput3, select as select3 } from "@inquirer/prompts";
2852
+ import { Command as Command7 } from "commander";
2853
+ import chalk7 from "chalk";
2854
+ import ora5 from "ora";
2855
+ var imageCommand = new Command7("image").description("Manage machine image sources");
2856
+ imageCommand.command("ls").description("List machine image sources").option("--json", "Print raw JSON").action(async (options) => {
2857
+ const spinner = options.json ? null : ora5("Fetching machine images...").start();
2858
+ try {
2859
+ const settings = await getMachineSourceSettings();
2860
+ spinner?.stop();
2861
+ if (options.json) {
2862
+ console.log(JSON.stringify(settings, null, 2));
2863
+ return;
2864
+ }
2865
+ printMachineSourceSettings(settings);
2866
+ } catch (error) {
2867
+ if (spinner) {
2868
+ spinner.fail(error instanceof Error ? error.message : "Failed to fetch machine images");
2869
+ } else {
2870
+ console.error(error instanceof Error ? error.message : "Failed to fetch machine images");
2871
+ }
2872
+ process.exit(1);
2873
+ }
2874
+ });
2875
+ imageCommand.command("save").description("Create or update a machine image source").option("--id <id>", "Existing source id to update").option("--kind <kind>", "oci-image or nix-git").option("--requested-ref <ref>", "OCI image ref or explicit resolved ref").option("--git-url <url>", "Repository URL for a Nix git source").option("--git-ref <ref>", "Git ref for a Nix git source").option("--git-subpath <path>", "Subdirectory for a Nix git source").option("--set-as-default", "Select this source as the default after saving").option("--json", "Print raw JSON").action(async (options) => {
2876
+ const spinner = options.json ? null : ora5("Saving machine image source...").start();
2877
+ try {
2878
+ const input = await resolveSaveInput(options);
2879
+ const settings = await upsertMachineSource(input);
2880
+ spinner?.stop();
2881
+ if (options.json) {
2882
+ console.log(JSON.stringify(settings, null, 2));
2883
+ return;
2884
+ }
2885
+ console.log();
2886
+ console.log(chalk7.green("Saved machine image source."));
2887
+ printMachineSourceSettings(settings);
2888
+ } catch (error) {
2889
+ if (spinner) {
2890
+ spinner.fail(error instanceof Error ? error.message : "Failed to save machine image source");
2891
+ } else {
2892
+ console.error(error instanceof Error ? error.message : "Failed to save machine image source");
2893
+ }
2894
+ process.exit(1);
2895
+ }
2896
+ });
2897
+ imageCommand.command("default").description("Set the default machine image source").argument("[source-id]", "Source id to select; use 'platform' or omit for the platform default").option("--json", "Print raw JSON").action(async (sourceID, options) => {
2898
+ const usePlatformDefault = !sourceID || sourceID === "platform";
2899
+ const spinner = options.json ? null : ora5("Updating machine image default...").start();
2900
+ try {
2901
+ let settings;
2902
+ if (usePlatformDefault) {
2903
+ settings = await clearMachineSourceDefault();
2904
+ } else {
2905
+ const current = await getMachineSourceSettings();
2906
+ const source = current.sources.find((entry) => entry.id === sourceID);
2907
+ if (!source) {
2908
+ throw new Error(`machine image source '${sourceID}' not found`);
2909
+ }
2910
+ settings = await upsertMachineSource({
2911
+ id: source.id,
2912
+ kind: source.kind,
2913
+ set_as_default: true
2914
+ });
2915
+ }
2916
+ spinner?.stop();
2917
+ if (options.json) {
2918
+ console.log(JSON.stringify(settings, null, 2));
2919
+ return;
2920
+ }
2921
+ console.log();
2922
+ if (!usePlatformDefault) {
2923
+ const selected = settings.default_machine_source ?? void 0;
2924
+ const label = selected ? summarizeMachineSource(selected) : sourceID;
2925
+ console.log(chalk7.green(`Selected ${chalk7.bold(label)} as the default machine image.`));
2926
+ } else {
2927
+ console.log(chalk7.green("Using the AgentComputer platform default image."));
2928
+ }
2929
+ printMachineSourceSettings(settings);
2930
+ } catch (error) {
2931
+ if (spinner) {
2932
+ spinner.fail(error instanceof Error ? error.message : "Failed to update machine image default");
2933
+ } else {
2934
+ console.error(error instanceof Error ? error.message : "Failed to update machine image default");
2935
+ }
2936
+ process.exit(1);
2937
+ }
2938
+ });
2939
+ imageCommand.command("rebuild").description("Rebuild a machine image source").argument("<source-id>", "Source id to rebuild").option("--json", "Print raw JSON").action(async (sourceID, options) => {
2940
+ const spinner = options.json ? null : ora5("Queueing machine image rebuild...").start();
2941
+ try {
2942
+ const settings = await rebuildMachineSource(sourceID);
2943
+ spinner?.stop();
2944
+ if (options.json) {
2945
+ console.log(JSON.stringify(settings, null, 2));
2946
+ return;
2947
+ }
2948
+ console.log();
2949
+ console.log(chalk7.green(`Queued rebuild for ${chalk7.bold(sourceID)}.`));
2950
+ printMachineSourceSettings(settings);
2951
+ } catch (error) {
2952
+ if (spinner) {
2953
+ spinner.fail(error instanceof Error ? error.message : "Failed to rebuild machine image source");
2954
+ } else {
2955
+ console.error(error instanceof Error ? error.message : "Failed to rebuild machine image source");
2956
+ }
2957
+ process.exit(1);
2958
+ }
2959
+ });
2960
+ imageCommand.command("rm").description("Delete a machine image source").argument("<source-id>", "Source id to delete").option("-y, --yes", "Skip confirmation prompt").option("--json", "Print raw JSON").action(async (sourceID, options, cmd) => {
2961
+ const globalYes = cmd.parent?.parent?.opts()?.yes;
2962
+ const skipConfirm = Boolean(options.yes || globalYes);
2963
+ let spinner = null;
2964
+ try {
2965
+ if (!skipConfirm && process.stdin.isTTY) {
2966
+ const confirmed = await confirmDeletion(sourceID);
2967
+ if (!confirmed) {
2968
+ console.log(chalk7.dim(" Cancelled."));
2969
+ return;
2970
+ }
2971
+ }
2972
+ spinner = options.json ? null : ora5("Deleting machine image source...").start();
2973
+ const settings = await deleteMachineSource(sourceID);
2974
+ spinner?.stop();
2975
+ if (options.json) {
2976
+ console.log(JSON.stringify(settings, null, 2));
2977
+ return;
2978
+ }
2979
+ console.log();
2980
+ console.log(chalk7.green(`Deleted machine image source ${chalk7.bold(sourceID)}.`));
2981
+ printMachineSourceSettings(settings);
2982
+ } catch (error) {
2983
+ if (spinner) {
2984
+ spinner.fail(error instanceof Error ? error.message : "Failed to delete machine image source");
2985
+ } else {
2986
+ console.error(error instanceof Error ? error.message : "Failed to delete machine image source");
2987
+ }
2988
+ process.exit(1);
2989
+ }
2990
+ });
2991
+ function printMachineSourceSettings(settings) {
2992
+ console.log(` ${chalk7.dim("Default")} ${chalk7.white(summarizeMachineSourceSelection(settings))}`);
2993
+ console.log();
2994
+ if (settings.sources.length === 0) {
2995
+ console.log(chalk7.dim(" No custom machine images configured yet."));
2996
+ console.log();
2997
+ return;
2998
+ }
2999
+ for (const source of settings.sources) {
3000
+ printMachineSourceCard(source, settings.default_machine_source_id === source.id);
3001
+ }
3002
+ }
3003
+ function printMachineSourceCard(source, isDefault) {
3004
+ console.log(` ${chalk7.bold(machineSourceTitle(source))}${isDefault ? chalk7.green(" (default)") : ""}`);
3005
+ console.log(` ${chalk7.dim(" ID")} ${source.id}`);
3006
+ console.log(` ${chalk7.dim(" Kind")} ${source.kind}`);
3007
+ console.log(` ${chalk7.dim(" Status")} ${source.status}${source.latest_build ? ` | latest build ${source.latest_build.status}` : ""}`);
3008
+ if (source.resolved_image_ref) {
3009
+ console.log(` ${chalk7.dim(" Resolved")} ${source.resolved_image_ref}`);
3010
+ }
3011
+ if (source.error) {
3012
+ console.log(` ${chalk7.dim(" Error")} ${chalk7.red(source.error)}`);
3013
+ }
3014
+ console.log(` ${chalk7.dim(" Source")} ${summarizeMachineSource(source)}`);
3015
+ console.log();
3016
+ }
3017
+ function machineSourceTitle(source) {
3018
+ if (source.kind === "oci-image") {
3019
+ return source.requested_ref || "OCI image";
3020
+ }
3021
+ return source.git_url || "Nix git source";
3022
+ }
3023
+ function parseMachineSourceKind(value) {
3024
+ switch (value) {
3025
+ case void 0:
3026
+ case "oci-image":
3027
+ case "nix-git":
3028
+ return value;
3029
+ default:
3030
+ throw new Error("--kind must be oci-image or nix-git");
3031
+ }
3032
+ }
3033
+ function hasTTY() {
3034
+ return process.stdin.isTTY && process.stdout.isTTY;
3035
+ }
3036
+ async function resolveSaveInput(options) {
3037
+ const existing = await loadExistingSource(options.id);
3038
+ const selectedKind = parseMachineSourceKind(options.kind) ?? existing?.kind;
3039
+ let kind = selectedKind;
3040
+ if (!kind) {
3041
+ if (!hasTTY()) {
3042
+ throw new Error("--kind is required");
3043
+ }
3044
+ kind = await selectMachineSourceKind();
3045
+ }
3046
+ const input = {
3047
+ id: options.id,
3048
+ kind,
3049
+ set_as_default: options.setAsDefault
3050
+ };
3051
+ switch (kind) {
3052
+ case "oci-image": {
3053
+ const existingSameKind = existing?.kind === kind;
3054
+ const requestedRef = normalizeValue(options.requestedRef) ?? (existingSameKind ? existing?.requested_ref : void 0);
3055
+ if (!requestedRef) {
3056
+ if (!hasTTY()) {
3057
+ throw new Error("--requested-ref is required for oci-image sources");
3058
+ }
3059
+ const promptedRef = normalizeValue(await textInput3({ message: "OCI image ref:" }));
3060
+ if (!promptedRef) {
3061
+ throw new Error("OCI image ref is required");
3062
+ }
3063
+ input.requested_ref = promptedRef;
3064
+ break;
3065
+ }
3066
+ input.requested_ref = requestedRef;
3067
+ break;
3068
+ }
3069
+ case "nix-git": {
3070
+ const existingSameKind = existing?.kind === kind;
3071
+ const gitUrl = normalizeValue(options.gitUrl) ?? (existingSameKind ? existing?.git_url : void 0);
3072
+ const gitRef = normalizeValue(options.gitRef) ?? (existingSameKind ? existing?.git_ref : void 0);
3073
+ const gitSubpath = normalizeValue(options.gitSubpath) ?? (existingSameKind ? existing?.git_subpath : void 0);
3074
+ if (!gitUrl) {
3075
+ if (!hasTTY()) {
3076
+ throw new Error("--git-url is required for nix-git sources");
3077
+ }
3078
+ const promptedGitURL = normalizeValue(await textInput3({ message: "Git URL:" }));
3079
+ if (!promptedGitURL) {
3080
+ throw new Error("Git URL is required");
3081
+ }
3082
+ input.git_url = promptedGitURL;
3083
+ break;
3084
+ }
3085
+ input.git_url = gitUrl;
3086
+ if (gitRef) {
3087
+ input.git_ref = gitRef;
3088
+ }
3089
+ if (gitSubpath) {
3090
+ input.git_subpath = gitSubpath;
3091
+ }
3092
+ break;
3093
+ }
3094
+ }
3095
+ return input;
3096
+ }
3097
+ async function selectMachineSourceKind() {
3098
+ const kind = await select3({
3099
+ message: "Select machine image kind",
3100
+ choices: [
3101
+ { name: "oci-image - resolve an OCI image digest", value: "oci-image" },
3102
+ { name: "nix-git - build a Nix source into OCI", value: "nix-git" }
3103
+ ],
3104
+ default: "oci-image"
3105
+ });
3106
+ return kind;
3107
+ }
3108
+ async function loadExistingSource(sourceID) {
3109
+ if (!sourceID) {
3110
+ return void 0;
3111
+ }
3112
+ const settings = await getMachineSourceSettings();
3113
+ const source = settings.sources.find((entry) => entry.id === sourceID);
3114
+ if (!source) {
3115
+ throw new Error(`machine image source '${sourceID}' not found`);
3116
+ }
3117
+ return source;
3118
+ }
3119
+ function normalizeValue(value) {
3120
+ const trimmed = value?.trim();
3121
+ return trimmed ? trimmed : void 0;
3122
+ }
3123
+ async function confirmDeletion(sourceID) {
3124
+ return confirm2({
3125
+ message: `Delete machine image source ${sourceID}?`,
3126
+ default: false
3127
+ });
3128
+ }
3129
+
2073
3130
  // src/commands/login.ts
2074
- import { Command as Command6 } from "commander";
2075
- import chalk5 from "chalk";
2076
- import ora4 from "ora";
3131
+ import { Command as Command8 } from "commander";
3132
+ import chalk8 from "chalk";
3133
+ import ora6 from "ora";
2077
3134
 
2078
3135
  // src/lib/browser-login.ts
2079
- import { randomBytes } from "crypto";
3136
+ import { randomBytes as randomBytes2 } from "crypto";
2080
3137
  import { createServer } from "http";
2081
3138
  var CALLBACK_HOST = "127.0.0.1";
2082
3139
  var CALLBACK_PATH = "/callback";
2083
3140
  var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
2084
3141
  async function createBrowserLoginAttempt() {
2085
- const state = randomBytes(16).toString("hex");
3142
+ const state = randomBytes2(16).toString("hex");
2086
3143
  const deferred = createDeferred();
2087
3144
  let callbackURL = "";
2088
3145
  let closed = false;
@@ -2301,11 +3358,11 @@ function escapeHTML(value) {
2301
3358
  }
2302
3359
 
2303
3360
  // src/commands/login.ts
2304
- var loginCommand = new Command6("login").description("Authenticate the CLI").option("--api-key <key>", "API key starting with ac_live_").option("--stdin", "Read the API key from stdin").option("-f, --force", "Overwrite an existing stored API key").action(async (options) => {
3361
+ var loginCommand = new Command8("login").description("Authenticate the CLI").option("--api-key <key>", "API key starting with ac_live_").option("--stdin", "Read the API key from stdin").option("-f, --force", "Overwrite an existing stored API key").action(async (options) => {
2305
3362
  const existingKey = getStoredAPIKey();
2306
3363
  if (existingKey && !options.force) {
2307
3364
  console.log();
2308
- console.log(chalk5.yellow(" Already logged in. Use --force to overwrite."));
3365
+ console.log(chalk8.yellow(" Already logged in. Use --force to overwrite."));
2309
3366
  console.log();
2310
3367
  return;
2311
3368
  }
@@ -2313,8 +3370,8 @@ var loginCommand = new Command6("login").description("Authenticate the CLI").opt
2313
3370
  const apiKey = await resolveAPIKeyInput(options.apiKey, options.stdin);
2314
3371
  if (!apiKey && wantsManualLogin) {
2315
3372
  console.log();
2316
- console.log(chalk5.dim(" Usage: computer login --api-key <ac_live_...>"));
2317
- console.log(chalk5.dim(` API: ${getBaseURL()}`));
3373
+ console.log(chalk8.dim(" Usage: computer login --api-key <ac_live_...>"));
3374
+ console.log(chalk8.dim(` API: ${getBaseURL()}`));
2318
3375
  console.log();
2319
3376
  process.exit(1);
2320
3377
  }
@@ -2324,15 +3381,15 @@ var loginCommand = new Command6("login").description("Authenticate the CLI").opt
2324
3381
  }
2325
3382
  if (!apiKey.startsWith("ac_live_")) {
2326
3383
  console.log();
2327
- console.log(chalk5.red(" API key must start with ac_live_"));
3384
+ console.log(chalk8.red(" API key must start with ac_live_"));
2328
3385
  console.log();
2329
3386
  process.exit(1);
2330
3387
  }
2331
- const spinner = ora4("Authenticating...").start();
3388
+ const spinner = ora6("Authenticating...").start();
2332
3389
  try {
2333
3390
  const me = await apiWithKey(apiKey, "/v1/me");
2334
3391
  setAPIKey(apiKey);
2335
- spinner.succeed(`Logged in as ${chalk5.bold(me.user.email)}`);
3392
+ spinner.succeed(`Logged in as ${chalk8.bold(me.user.email)}`);
2336
3393
  } catch (error) {
2337
3394
  spinner.fail(
2338
3395
  error instanceof Error ? error.message : "Failed to validate API key"
@@ -2341,7 +3398,7 @@ var loginCommand = new Command6("login").description("Authenticate the CLI").opt
2341
3398
  }
2342
3399
  });
2343
3400
  async function runBrowserLogin() {
2344
- const spinner = ora4("Starting browser login...").start();
3401
+ const spinner = ora6("Starting browser login...").start();
2345
3402
  let attempt = null;
2346
3403
  try {
2347
3404
  attempt = await createBrowserLoginAttempt();
@@ -2351,14 +3408,14 @@ async function runBrowserLogin() {
2351
3408
  } catch {
2352
3409
  spinner.stop();
2353
3410
  console.log();
2354
- console.log(chalk5.yellow(" Browser auto-open failed. Open this URL to continue:"));
2355
- console.log(chalk5.dim(` ${attempt.loginURL}`));
3411
+ console.log(chalk8.yellow(" Browser auto-open failed. Open this URL to continue:"));
3412
+ console.log(chalk8.dim(` ${attempt.loginURL}`));
2356
3413
  console.log();
2357
3414
  spinner.start("Waiting for browser login...");
2358
3415
  }
2359
3416
  spinner.text = "Waiting for browser login...";
2360
3417
  const result = await attempt.waitForResult();
2361
- spinner.succeed(`Logged in as ${chalk5.bold(result.me.user.email)}`);
3418
+ spinner.succeed(`Logged in as ${chalk8.bold(result.me.user.email)}`);
2362
3419
  } catch (error) {
2363
3420
  spinner.fail(error instanceof Error ? error.message : "Browser login failed");
2364
3421
  process.exit(1);
@@ -2384,33 +3441,33 @@ async function resolveAPIKeyInput(flagValue, readFromStdin) {
2384
3441
  }
2385
3442
 
2386
3443
  // src/commands/logout.ts
2387
- import { Command as Command7 } from "commander";
2388
- import chalk6 from "chalk";
2389
- var logoutCommand = new Command7("logout").description("Remove stored API key").action(() => {
3444
+ import { Command as Command9 } from "commander";
3445
+ import chalk9 from "chalk";
3446
+ var logoutCommand = new Command9("logout").description("Remove stored API key").action(() => {
2390
3447
  if (!getStoredAPIKey()) {
2391
3448
  console.log();
2392
- console.log(chalk6.dim(" Not logged in."));
3449
+ console.log(chalk9.dim(" Not logged in."));
2393
3450
  if (hasEnvAPIKey()) {
2394
- console.log(chalk6.dim(" Environment API key is still active in this shell."));
3451
+ console.log(chalk9.dim(" Environment API key is still active in this shell."));
2395
3452
  }
2396
3453
  console.log();
2397
3454
  return;
2398
3455
  }
2399
3456
  clearAPIKey();
2400
3457
  console.log();
2401
- console.log(chalk6.green(" Logged out."));
3458
+ console.log(chalk9.green(" Logged out."));
2402
3459
  if (hasEnvAPIKey()) {
2403
- console.log(chalk6.dim(" Environment API key is still active in this shell."));
3460
+ console.log(chalk9.dim(" Environment API key is still active in this shell."));
2404
3461
  }
2405
3462
  console.log();
2406
3463
  });
2407
3464
 
2408
3465
  // src/commands/whoami.ts
2409
- import { Command as Command8 } from "commander";
2410
- import chalk7 from "chalk";
2411
- import ora5 from "ora";
2412
- var whoamiCommand = new Command8("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
2413
- const spinner = options.json ? null : ora5("Loading user...").start();
3466
+ import { Command as Command10 } from "commander";
3467
+ import chalk10 from "chalk";
3468
+ import ora7 from "ora";
3469
+ var whoamiCommand = new Command10("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
3470
+ const spinner = options.json ? null : ora7("Loading user...").start();
2414
3471
  try {
2415
3472
  const me = await api("/v1/me");
2416
3473
  spinner?.stop();
@@ -2419,14 +3476,14 @@ var whoamiCommand = new Command8("whoami").description("Show current user").opti
2419
3476
  return;
2420
3477
  }
2421
3478
  console.log();
2422
- console.log(` ${chalk7.bold.white(me.user.display_name || me.user.email)}`);
3479
+ console.log(` ${chalk10.bold.white(me.user.display_name || me.user.email)}`);
2423
3480
  if (me.user.display_name) {
2424
- console.log(` ${chalk7.dim(me.user.email)}`);
3481
+ console.log(` ${chalk10.dim(me.user.email)}`);
2425
3482
  }
2426
3483
  if (me.api_key.name) {
2427
- console.log(` ${chalk7.dim("Key:")} ${me.api_key.name}`);
3484
+ console.log(` ${chalk10.dim("Key:")} ${me.api_key.name}`);
2428
3485
  }
2429
- console.log(` ${chalk7.dim("API:")} ${chalk7.dim(getBaseURL())}`);
3486
+ console.log(` ${chalk10.dim("API:")} ${chalk10.dim(getBaseURL())}`);
2430
3487
  console.log();
2431
3488
  } catch (error) {
2432
3489
  if (spinner) {
@@ -2443,67 +3500,140 @@ var pkg2 = JSON.parse(
2443
3500
  readFileSync3(new URL("../package.json", import.meta.url), "utf8")
2444
3501
  );
2445
3502
  var cliName = process.argv[1] ? basename2(process.argv[1]) : "agentcomputer";
2446
- var program = new Command9();
2447
- program.name(cliName).description("Agent Computer CLI").version(pkg2.version ?? "0.0.0").option("-y, --yes", "Skip confirmation prompts").configureHelp({
2448
- formatHelp(cmd, helper) {
2449
- const version = pkg2.version ?? "0.0.0";
2450
- const lines = [];
2451
- lines.push(`${chalk8.bold(cliName)} ${chalk8.dim(`v${version}`)}`);
3503
+ var program = new Command11();
3504
+ function appendTextSection(lines, title, values) {
3505
+ if (values.length === 0) {
3506
+ return;
3507
+ }
3508
+ lines.push(` ${chalk11.dim(title)}`);
3509
+ lines.push("");
3510
+ for (const value of values) {
3511
+ lines.push(` ${chalk11.white(value)}`);
3512
+ }
3513
+ lines.push("");
3514
+ }
3515
+ function appendTableSection(lines, title, entries) {
3516
+ if (entries.length === 0) {
3517
+ return;
3518
+ }
3519
+ const width = Math.max(...entries.map((entry) => entry.term.length), 0) + 2;
3520
+ lines.push(` ${chalk11.dim(title)}`);
3521
+ lines.push("");
3522
+ for (const entry of entries) {
3523
+ lines.push(` ${chalk11.white(padEnd(entry.term, width))}${chalk11.dim(entry.desc)}`);
3524
+ }
3525
+ lines.push("");
3526
+ }
3527
+ function commandPath(cmd) {
3528
+ const parts = [];
3529
+ let current = cmd;
3530
+ while (current) {
3531
+ parts.unshift(current.name());
3532
+ current = current.parent ?? null;
3533
+ }
3534
+ return parts.join(" ");
3535
+ }
3536
+ function formatRootHelp(cmd) {
3537
+ const version = pkg2.version ?? "0.0.0";
3538
+ const lines = [];
3539
+ const groups = [
3540
+ ["Auth", []],
3541
+ ["Computers", []],
3542
+ ["Images", []],
3543
+ ["Access", []],
3544
+ ["Agents", []],
3545
+ ["Other", []]
3546
+ ];
3547
+ const otherGroup = groups.find(([name]) => name === "Other")[1];
3548
+ lines.push(`${chalk11.bold(cliName)} ${chalk11.dim(`v${version}`)}`);
3549
+ lines.push("");
3550
+ if (cmd.description()) {
3551
+ lines.push(` ${chalk11.dim(cmd.description())}`);
2452
3552
  lines.push("");
2453
- if (cmd.commands.length > 0) {
2454
- const groups = {
2455
- Auth: [],
2456
- Computers: [],
2457
- Agents: [],
2458
- Access: [],
2459
- Other: []
2460
- };
2461
- for (const sub of cmd.commands) {
2462
- const name = sub.name();
2463
- const desc = sub.description();
2464
- const entry = { name, desc };
2465
- if (["login", "logout", "whoami"].includes(name)) {
2466
- groups.Auth.push(entry);
2467
- } else if (["create", "ls", "get", "rm"].includes(name)) {
2468
- groups.Computers.push(entry);
2469
- } else if (["agent", "fleet", "acp"].includes(name)) {
2470
- groups.Agents.push(entry);
2471
- } else if (["open", "ssh", "ports"].includes(name)) {
2472
- groups.Access.push(entry);
2473
- } else {
2474
- groups.Other.push(entry);
2475
- }
2476
- }
2477
- for (const [groupName, entries] of Object.entries(groups)) {
2478
- if (entries.length === 0) continue;
2479
- lines.push(` ${chalk8.dim(groupName)}`);
2480
- for (const entry of entries) {
2481
- const padded = entry.name.padEnd(14);
2482
- lines.push(` ${chalk8.white(padded)}${chalk8.dim(entry.desc)}`);
2483
- }
2484
- lines.push("");
2485
- }
2486
- }
2487
- const globalOpts = [
2488
- { flags: "-y, --yes", desc: "Skip confirmation prompts" },
2489
- { flags: "-V, --version", desc: "Show version" },
2490
- { flags: "-h, --help", desc: "Show help" }
2491
- ];
2492
- lines.push(` ${chalk8.dim("Options")}`);
2493
- for (const opt of globalOpts) {
2494
- const padded = opt.flags.padEnd(14);
2495
- lines.push(` ${chalk8.white(padded)}${chalk8.dim(opt.desc)}`);
3553
+ }
3554
+ appendTextSection(lines, "Usage", [`${cliName} <command> [options]`]);
3555
+ for (const sub of cmd.commands) {
3556
+ const name = sub.name();
3557
+ const entry = { term: name, desc: sub.description() };
3558
+ if (["login", "logout", "whoami", "claude-auth"].includes(name)) {
3559
+ groups[0][1].push(entry);
3560
+ } else if (["create", "ls", "get", "rm"].includes(name)) {
3561
+ groups[1][1].push(entry);
3562
+ } else if (name === "image") {
3563
+ groups[2][1].push(entry);
3564
+ } else if (["open", "ssh", "ports"].includes(name)) {
3565
+ groups[3][1].push(entry);
3566
+ } else if (["agent", "fleet", "acp"].includes(name)) {
3567
+ groups[4][1].push(entry);
3568
+ } else {
3569
+ otherGroup.push(entry);
2496
3570
  }
3571
+ }
3572
+ for (const [groupName, entries] of groups) {
3573
+ appendTableSection(
3574
+ lines,
3575
+ groupName,
3576
+ entries
3577
+ );
3578
+ }
3579
+ appendTableSection(lines, "Options", [
3580
+ { term: "-y, --yes", desc: "Skip confirmation prompts" },
3581
+ { term: "-V, --version", desc: "Show version" },
3582
+ { term: "-h, --help", desc: "Show help" }
3583
+ ]);
3584
+ return `${lines.join("\n").trimEnd()}
3585
+ `;
3586
+ }
3587
+ function formatSubcommandHelp(cmd, helper) {
3588
+ const lines = [];
3589
+ const description = helper.commandDescription(cmd);
3590
+ const argumentsList = helper.visibleArguments(cmd).map((argument) => ({
3591
+ term: helper.argumentTerm(argument),
3592
+ desc: helper.argumentDescription(argument)
3593
+ }));
3594
+ const commandList = helper.visibleCommands(cmd).map((subcommand) => ({
3595
+ term: helper.subcommandTerm(subcommand),
3596
+ desc: helper.subcommandDescription(subcommand)
3597
+ }));
3598
+ const optionList = helper.visibleOptions(cmd).map((option) => ({
3599
+ term: helper.optionTerm(option),
3600
+ desc: helper.optionDescription(option)
3601
+ }));
3602
+ lines.push(chalk11.bold(commandPath(cmd)));
3603
+ lines.push("");
3604
+ if (description) {
3605
+ lines.push(` ${chalk11.dim(description)}`);
2497
3606
  lines.push("");
2498
- return lines.join("\n");
2499
3607
  }
2500
- });
3608
+ appendTextSection(lines, "Usage", [helper.commandUsage(cmd)]);
3609
+ appendTableSection(lines, "Arguments", argumentsList);
3610
+ appendTableSection(lines, "Commands", commandList);
3611
+ appendTableSection(lines, "Options", optionList);
3612
+ return `${lines.join("\n").trimEnd()}
3613
+ `;
3614
+ }
3615
+ function applyHelpFormatting(cmd) {
3616
+ cmd.configureHelp({
3617
+ formatHelp(current, helper) {
3618
+ if (!current.parent) {
3619
+ return formatRootHelp(current);
3620
+ }
3621
+ return formatSubcommandHelp(current, helper);
3622
+ }
3623
+ });
3624
+ for (const subcommand of cmd.commands) {
3625
+ applyHelpFormatting(subcommand);
3626
+ }
3627
+ }
3628
+ program.name(cliName).description("Agent Computer CLI").version(pkg2.version ?? "0.0.0").option("-y, --yes", "Skip confirmation prompts");
2501
3629
  program.addCommand(loginCommand);
2502
3630
  program.addCommand(logoutCommand);
2503
3631
  program.addCommand(whoamiCommand);
3632
+ program.addCommand(claudeAuthCommand);
2504
3633
  program.addCommand(createCommand);
2505
3634
  program.addCommand(lsCommand);
2506
3635
  program.addCommand(getCommand);
3636
+ program.addCommand(imageCommand);
2507
3637
  program.addCommand(agentCommand);
2508
3638
  program.addCommand(fleetCommand);
2509
3639
  program.addCommand(acpCommand);
@@ -2512,4 +3642,5 @@ program.addCommand(sshCommand);
2512
3642
  program.addCommand(portsCommand);
2513
3643
  program.addCommand(removeCommand);
2514
3644
  program.addCommand(completionCommand);
3645
+ applyHelpFormatting(program);
2515
3646
  program.parse();