aicomputer 0.1.4 → 0.1.6

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 +15 -0
  2. package/dist/index.js +1201 -145
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command7 } from "commander";
5
- import chalk7 from "chalk";
6
- import { createRequire } from "module";
4
+ import { Command as Command9 } from "commander";
5
+ import chalk8 from "chalk";
6
+ import { readFileSync as readFileSync3 } from "fs";
7
7
  import { basename as basename2 } from "path";
8
8
 
9
9
  // src/commands/access.ts
@@ -159,6 +159,9 @@ async function createComputer(input) {
159
159
  body: JSON.stringify(input)
160
160
  });
161
161
  }
162
+ async function getFilesystemSettings() {
163
+ return api("/v1/me/filesystem");
164
+ }
162
165
  async function getConnectionInfo(computerID) {
163
166
  return api(`/v1/computers/${computerID}/connection`);
164
167
  }
@@ -216,7 +219,7 @@ function vncURL(computer) {
216
219
  return null;
217
220
  }
218
221
  const domain = computer.primary_web_host.replace(/^[^.]+\./, "");
219
- return `https://6080--${computer.handle}.${domain}/vnc.html?autoconnect=1&resize=remote`;
222
+ return `https://6080--${computer.handle}.${domain}`;
220
223
  }
221
224
  function terminalURL(computer) {
222
225
  if (computer.runtime_family !== "managed-worker") {
@@ -233,21 +236,89 @@ function normalizePrimaryPath(primaryPath) {
233
236
  return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
234
237
  }
235
238
 
239
+ // src/lib/ssh-config.ts
240
+ import { homedir as homedir2 } from "os";
241
+ import { join as join2 } from "path";
242
+ import { mkdir, readFile, writeFile } from "fs/promises";
243
+ var MANAGED_BLOCK_START = "# >>> agentcomputer ssh setup >>>";
244
+ var MANAGED_BLOCK_END = "# <<< agentcomputer ssh setup <<<";
245
+ async function ensureSSHAliasConfig(options) {
246
+ const sshDir = join2(homedir2(), ".ssh");
247
+ const configPath = join2(sshDir, "config");
248
+ await mkdir(sshDir, { recursive: true, mode: 448 });
249
+ let existing = "";
250
+ try {
251
+ existing = await readFile(configPath, "utf8");
252
+ } catch (error) {
253
+ if (error.code !== "ENOENT") {
254
+ throw error;
255
+ }
256
+ }
257
+ const nextBlock = renderManagedBlock(options);
258
+ const managedBlockPattern = new RegExp(
259
+ `${escapeRegex(MANAGED_BLOCK_START)}[\\s\\S]*?${escapeRegex(MANAGED_BLOCK_END)}\\n?`,
260
+ "m"
261
+ );
262
+ let nextContents;
263
+ if (managedBlockPattern.test(existing)) {
264
+ nextContents = existing.replace(managedBlockPattern, nextBlock);
265
+ } else {
266
+ const normalized = existing.length === 0 ? "" : existing.endsWith("\n") ? existing : `${existing}
267
+ `;
268
+ nextContents = normalized.length === 0 ? nextBlock : `${normalized}
269
+ ${nextBlock}`;
270
+ }
271
+ const changed = nextContents !== existing;
272
+ if (changed) {
273
+ await writeFile(configPath, nextContents, { mode: 384 });
274
+ }
275
+ return {
276
+ configPath,
277
+ changed
278
+ };
279
+ }
280
+ function renderManagedBlock(options) {
281
+ const user = options.user?.trim() || "agentcomputer";
282
+ const identityFile = formatIdentityFilePath(options.identityFilePath);
283
+ return `${MANAGED_BLOCK_START}
284
+ Host ${options.alias}
285
+ HostName ${options.host}
286
+ Port ${options.port}
287
+ User ${user}
288
+ IdentityFile ${identityFile}
289
+ IdentitiesOnly yes
290
+ ServerAliveInterval 30
291
+ ServerAliveCountMax 4
292
+ ${MANAGED_BLOCK_END}
293
+ `;
294
+ }
295
+ function formatIdentityFilePath(path) {
296
+ const normalized = path.trim();
297
+ const homePath = `${homedir2()}/`;
298
+ if (normalized.startsWith(homePath)) {
299
+ return `~/${normalized.slice(homePath.length)}`;
300
+ }
301
+ return normalized.replaceAll(" ", "\\ ");
302
+ }
303
+ function escapeRegex(value) {
304
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
305
+ }
306
+
236
307
  // src/lib/ssh-keys.ts
237
308
  import { basename } from "path";
238
- import { homedir as homedir2 } from "os";
239
- import { readFile, mkdir } from "fs/promises";
309
+ import { homedir as homedir3 } from "os";
310
+ import { readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
240
311
  import { execFileSync } from "child_process";
241
312
  import { existsSync as existsSync2 } from "fs";
242
313
  var DEFAULT_PUBLIC_KEY_PATHS = [
243
- `${homedir2()}/.ssh/id_ed25519.pub`,
244
- `${homedir2()}/.ssh/id_ecdsa.pub`,
245
- `${homedir2()}/.ssh/id_rsa.pub`
314
+ `${homedir3()}/.ssh/id_ed25519.pub`,
315
+ `${homedir3()}/.ssh/id_ecdsa.pub`,
316
+ `${homedir3()}/.ssh/id_rsa.pub`
246
317
  ];
247
318
  async function ensureDefaultSSHKeyRegistered() {
248
319
  for (const path of DEFAULT_PUBLIC_KEY_PATHS) {
249
320
  try {
250
- const publicKey2 = (await readFile(path, "utf8")).trim();
321
+ const publicKey2 = (await readFile2(path, "utf8")).trim();
251
322
  if (!publicKey2) {
252
323
  continue;
253
324
  }
@@ -271,7 +342,7 @@ async function ensureDefaultSSHKeyRegistered() {
271
342
  }
272
343
  }
273
344
  const generated = await generateSSHKey();
274
- const publicKey = (await readFile(generated.publicKeyPath, "utf8")).trim();
345
+ const publicKey = (await readFile2(generated.publicKeyPath, "utf8")).trim();
275
346
  const key = await api("/v1/ssh-keys", {
276
347
  method: "POST",
277
348
  body: JSON.stringify({
@@ -286,9 +357,9 @@ async function ensureDefaultSSHKeyRegistered() {
286
357
  };
287
358
  }
288
359
  async function generateSSHKey() {
289
- const sshDir = `${homedir2()}/.ssh`;
360
+ const sshDir = `${homedir3()}/.ssh`;
290
361
  if (!existsSync2(sshDir)) {
291
- await mkdir(sshDir, { mode: 448 });
362
+ await mkdir2(sshDir, { mode: 448 });
292
363
  }
293
364
  const privateKeyPath = `${sshDir}/id_ed25519`;
294
365
  const publicKeyPath = `${privateKeyPath}.pub`;
@@ -419,7 +490,11 @@ var openCommand = new Command("open").description("Open a computer in your brows
419
490
  process.exit(1);
420
491
  }
421
492
  });
422
- var sshCommand = new Command("ssh").description("Open an SSH session to a computer").argument("[id-or-handle]", "Computer id or handle").action(async (identifier) => {
493
+ var sshCommand = new Command("ssh").description("Open an SSH session to a computer").argument("[id-or-handle]", "Computer id or handle").option("--setup", "Register key and configure a global SSH alias").option("--alias <alias>", "SSH host alias", "agentcomputer.ai").option("--host <host>", "SSH gateway host", "ssh.agentcomputer.ai").option("--port <port>", "SSH gateway port", "22").action(async (identifier, options) => {
494
+ if (options.setup) {
495
+ await setupSSHAlias(options);
496
+ return;
497
+ }
423
498
  const spinner = ora(
424
499
  identifier ? "Preparing SSH access..." : "Fetching computers..."
425
500
  ).start();
@@ -431,9 +506,7 @@ var sshCommand = new Command("ssh").description("Open an SSH session to a comput
431
506
  }
432
507
  const registered = await ensureDefaultSSHKeyRegistered();
433
508
  spinner.succeed(`Connecting to ${chalk2.bold(computer.handle)}`);
434
- console.log(
435
- chalk2.dim(` ssh -p ${info.connection.ssh_port} ${info.connection.ssh_user}@${info.connection.ssh_host}`)
436
- );
509
+ console.log(chalk2.dim(` ${formatSSHCommand(info.connection.ssh_user, info.connection.ssh_host, info.connection.ssh_port)}`));
437
510
  console.log();
438
511
  await runSSH([
439
512
  "-i",
@@ -534,6 +607,69 @@ async function runSSH(args) {
534
607
  });
535
608
  });
536
609
  }
610
+ async function setupSSHAlias(options) {
611
+ const spinner = ora("Configuring global SSH access...").start();
612
+ try {
613
+ const alias = normalizeSSHAlias(options.alias ?? "agentcomputer.ai");
614
+ const host = normalizeSSHHost(options.host ?? "ssh.agentcomputer.ai");
615
+ const port = parseSSHPort(options.port ?? "22");
616
+ const registered = await ensureDefaultSSHKeyRegistered();
617
+ const configResult = await ensureSSHAliasConfig({
618
+ alias,
619
+ host,
620
+ port,
621
+ user: "agentcomputer",
622
+ identityFilePath: registered.privateKeyPath
623
+ });
624
+ spinner.succeed(`SSH alias '${alias}' is ready`);
625
+ console.log();
626
+ console.log(chalk2.dim(` SSH config: ${configResult.configPath}`));
627
+ console.log(chalk2.dim(` Identity: ${registered.privateKeyPath}`));
628
+ console.log();
629
+ console.log(` ${chalk2.bold("Shell:")} ssh ${alias}`);
630
+ console.log(` ${chalk2.bold("Direct:")} ssh <handle>@${alias}`);
631
+ console.log();
632
+ } catch (error) {
633
+ spinner.fail(error instanceof Error ? error.message : "Failed to configure SSH alias");
634
+ process.exit(1);
635
+ }
636
+ }
637
+ function normalizeSSHAlias(value) {
638
+ const alias = value.trim();
639
+ if (!alias) {
640
+ throw new Error("ssh alias cannot be empty");
641
+ }
642
+ if (!/^[A-Za-z0-9._-]+$/.test(alias)) {
643
+ throw new Error("ssh alias may contain only letters, numbers, dot, dash, or underscore");
644
+ }
645
+ return alias;
646
+ }
647
+ function normalizeSSHHost(value) {
648
+ const host = value.trim();
649
+ if (!host) {
650
+ throw new Error("ssh host cannot be empty");
651
+ }
652
+ if (host.includes(" ")) {
653
+ throw new Error("ssh host cannot contain spaces");
654
+ }
655
+ return host;
656
+ }
657
+ function parseSSHPort(value) {
658
+ const parsed = Number.parseInt(value, 10);
659
+ if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {
660
+ throw new Error("ssh port must be between 1 and 65535");
661
+ }
662
+ return parsed;
663
+ }
664
+ function formatSSHCommand(user, host, port) {
665
+ if (!user.trim() || !host.trim()) {
666
+ return "ssh unavailable";
667
+ }
668
+ if (port <= 0 || port === 22) {
669
+ return `ssh ${user}@${host}`;
670
+ }
671
+ return `ssh -p ${port} ${user}@${host}`;
672
+ }
537
673
  async function resolveSSHComputer(identifier, spinner) {
538
674
  const trimmed = identifier?.trim();
539
675
  if (trimmed) {
@@ -578,10 +714,807 @@ function describeSSHChoice(computer) {
578
714
  return `${computer.runtime_family} ${timeAgo(computer.updated_at)}`;
579
715
  }
580
716
 
581
- // src/commands/computers.ts
717
+ // src/commands/acp.ts
718
+ import { readFileSync as readFileSync2 } from "fs";
719
+ import readline from "readline";
582
720
  import { Command as Command2 } from "commander";
721
+
722
+ // src/lib/agents.ts
723
+ async function listComputerAgents(computerID) {
724
+ const response = await api(`/v1/computers/${computerID}/agents`);
725
+ return response.agents;
726
+ }
727
+ async function listAgentSessions(computerID) {
728
+ const response = await api(`/v1/computers/${computerID}/agent-sessions`);
729
+ return response.sessions;
730
+ }
731
+ async function listFleetAgentSessions() {
732
+ const response = await api("/v1/fleet/agent-sessions");
733
+ return response.sessions;
734
+ }
735
+ async function createAgentSession(computerID, input) {
736
+ const response = await api(`/v1/computers/${computerID}/agent-sessions`, {
737
+ method: "POST",
738
+ body: JSON.stringify(input)
739
+ });
740
+ return response.session;
741
+ }
742
+ async function getAgentSession(computerID, sessionID) {
743
+ const response = await api(
744
+ `/v1/computers/${computerID}/agent-sessions/${sessionID}`
745
+ );
746
+ return response.session;
747
+ }
748
+ async function promptAgentSession(computerID, sessionID, input) {
749
+ return api(
750
+ `/v1/computers/${computerID}/agent-sessions/${sessionID}/prompt`,
751
+ {
752
+ method: "POST",
753
+ body: JSON.stringify(input)
754
+ }
755
+ );
756
+ }
757
+ async function cancelAgentSession(computerID, sessionID) {
758
+ const response = await api(
759
+ `/v1/computers/${computerID}/agent-sessions/${sessionID}/cancel`,
760
+ {
761
+ method: "POST"
762
+ }
763
+ );
764
+ return response.session;
765
+ }
766
+ async function interruptAgentSession(computerID, sessionID) {
767
+ const response = await api(
768
+ `/v1/computers/${computerID}/agent-sessions/${sessionID}/interrupt`,
769
+ {
770
+ method: "POST"
771
+ }
772
+ );
773
+ return response.session;
774
+ }
775
+ async function deleteAgentSession(computerID, sessionID) {
776
+ return api(`/v1/computers/${computerID}/agent-sessions/${sessionID}`, {
777
+ method: "DELETE"
778
+ });
779
+ }
780
+ async function openAgentSessionEventsStream(computerID, sessionID, options = {}) {
781
+ const apiKey = getAPIKey();
782
+ if (!apiKey) {
783
+ throw new ApiError(401, "not logged in; run 'computer login' first");
784
+ }
785
+ const endpoint = new URL(`${getBaseURL()}/v1/computers/${computerID}/agent-sessions/${sessionID}/events`);
786
+ if (options.lastEventId?.trim()) {
787
+ endpoint.searchParams.set("last_event_id", options.lastEventId.trim());
788
+ }
789
+ const response = await fetch(endpoint, {
790
+ method: "GET",
791
+ headers: {
792
+ Accept: "text/event-stream",
793
+ Authorization: `Bearer ${apiKey}`
794
+ },
795
+ signal: options.signal
796
+ });
797
+ if (!response.ok) {
798
+ const message = await readErrorMessage2(response);
799
+ throw new ApiError(response.status, message);
800
+ }
801
+ return response;
802
+ }
803
+ async function waitForSessionToSettle(computerID, sessionID, promptID, options = {}) {
804
+ const intervalMs = options.intervalMs ?? 1e3;
805
+ const timeoutMs = options.timeoutMs ?? 30 * 60 * 1e3;
806
+ const deadline = Date.now() + timeoutMs;
807
+ while (true) {
808
+ const session = await getAgentSession(computerID, sessionID);
809
+ if (session.status !== "running" && session.status !== "cancelling" && (!promptID || session.active_prompt_id !== promptID)) {
810
+ return session;
811
+ }
812
+ if (Date.now() >= deadline) {
813
+ throw new Error("timed out waiting for agent session to finish");
814
+ }
815
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
816
+ }
817
+ }
818
+ async function readErrorMessage2(response) {
819
+ const contentType = response.headers.get("content-type") ?? "";
820
+ if (contentType.includes("application/json")) {
821
+ try {
822
+ const payload = await response.json();
823
+ return payload.error ?? JSON.stringify(payload);
824
+ } catch {
825
+ return response.statusText || "request failed";
826
+ }
827
+ }
828
+ const body = await response.text();
829
+ return body || response.statusText || "request failed";
830
+ }
831
+
832
+ // src/commands/acp.ts
833
+ var pkg = JSON.parse(
834
+ readFileSync2(new URL("../package.json", import.meta.url), "utf8")
835
+ );
836
+ function emit(message) {
837
+ process.stdout.write(`${JSON.stringify(message)}
838
+ `);
839
+ }
840
+ function emitResult(id, result) {
841
+ if (id === void 0 || id === null) {
842
+ return;
843
+ }
844
+ emit({
845
+ jsonrpc: "2.0",
846
+ id,
847
+ result
848
+ });
849
+ }
850
+ function emitError(id, code, message) {
851
+ emit({
852
+ jsonrpc: "2.0",
853
+ id: id ?? null,
854
+ error: {
855
+ code,
856
+ message
857
+ }
858
+ });
859
+ }
860
+ function forwardBridgePayload(payload, publicSessionID, backendSessionID) {
861
+ let message;
862
+ try {
863
+ message = JSON.parse(payload);
864
+ } catch {
865
+ return;
866
+ }
867
+ const method = typeof message.method === "string" ? message.method : "";
868
+ if (method !== "session/update" && method !== "session/request_permission") {
869
+ return;
870
+ }
871
+ const params = typeof message.params === "object" && message.params !== null ? { ...message.params } : {};
872
+ const payloadSessionID = typeof params.sessionId === "string" ? params.sessionId.trim() : "";
873
+ if (backendSessionID?.trim() && payloadSessionID && payloadSessionID !== backendSessionID.trim()) {
874
+ return;
875
+ }
876
+ params.sessionId = publicSessionID;
877
+ process.stdout.write(`${JSON.stringify({ ...message, params })}
878
+ `);
879
+ }
880
+ async function forwardRawEventStream(computerID, publicSessionID, backendSessionID, signal) {
881
+ const response = await openAgentSessionEventsStream(computerID, publicSessionID, { signal });
882
+ if (!response.body) {
883
+ return;
884
+ }
885
+ const decoder = new TextDecoder();
886
+ const reader = response.body.getReader();
887
+ let buffer = "";
888
+ while (true) {
889
+ const { done, value } = await reader.read();
890
+ if (done) {
891
+ return;
892
+ }
893
+ buffer += decoder.decode(value, { stream: true }).replaceAll("\r\n", "\n");
894
+ while (true) {
895
+ const splitIndex = buffer.indexOf("\n\n");
896
+ if (splitIndex === -1) {
897
+ break;
898
+ }
899
+ const chunk = buffer.slice(0, splitIndex);
900
+ buffer = buffer.slice(splitIndex + 2);
901
+ const payload = chunk.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n").trim();
902
+ if (!payload || payload === "heartbeat") {
903
+ continue;
904
+ }
905
+ forwardBridgePayload(payload, publicSessionID, backendSessionID);
906
+ }
907
+ }
908
+ }
909
+ async function ensureBridgeSession(computerID, agent, baseName, cwd, requestCwd, counter) {
910
+ const resolvedCwd = typeof requestCwd === "string" && requestCwd.trim() ? requestCwd.trim() : cwd;
911
+ return createAgentSession(computerID, {
912
+ agent,
913
+ name: `${baseName}-${counter}`,
914
+ cwd: resolvedCwd,
915
+ resume: false
916
+ });
917
+ }
918
+ async function handlePromptRequest(computerID, sessionID, id, params) {
919
+ if (id === void 0 || id === null) {
920
+ throw new Error("session/prompt requires a request id");
921
+ }
922
+ const prompt = Array.isArray(params?.prompt) ? params?.prompt : [];
923
+ if (prompt.length === 0) {
924
+ throw new Error("session/prompt requires prompt content");
925
+ }
926
+ const accepted = await promptAgentSession(computerID, sessionID, { prompt });
927
+ const current = await getAgentSession(computerID, sessionID);
928
+ const controller = new AbortController();
929
+ const streamPromise = forwardRawEventStream(
930
+ computerID,
931
+ sessionID,
932
+ current.backend_session_id,
933
+ controller.signal
934
+ ).catch((error) => {
935
+ if (error instanceof Error && error.name === "AbortError") {
936
+ return;
937
+ }
938
+ return;
939
+ });
940
+ try {
941
+ const settled = await waitForSessionToSettle(computerID, sessionID, accepted.prompt_id);
942
+ emitResult(id, {
943
+ stopReason: settled.last_stop_reason || "end_turn"
944
+ });
945
+ } finally {
946
+ controller.abort();
947
+ await streamPromise.catch(() => void 0);
948
+ }
949
+ }
950
+ var acpCommand = new Command2("acp").description("Expose Agent Computer sessions through a local ACP bridge");
951
+ acpCommand.command("serve").description("Serve a local stdio ACP bridge backed by one machine").argument("<machine>", "Computer id or handle").requiredOption("--agent <agent>", "Agent id to use for remote sessions").option("--name <name>", "Base session name prefix", "acp").option("--cwd <cwd>", "Default working directory").action(async (identifier, options) => {
952
+ try {
953
+ const computer = await resolveComputer(identifier);
954
+ const rl = readline.createInterface({
955
+ input: process.stdin,
956
+ crlfDelay: Infinity
957
+ });
958
+ let nextSession = 0;
959
+ for await (const line of rl) {
960
+ const trimmed = line.trim();
961
+ if (!trimmed) {
962
+ continue;
963
+ }
964
+ let message;
965
+ try {
966
+ message = JSON.parse(trimmed);
967
+ } catch {
968
+ emitError(null, -32700, "invalid JSON");
969
+ continue;
970
+ }
971
+ try {
972
+ switch (message.method) {
973
+ case "initialize": {
974
+ emitResult(message.id, {
975
+ protocolVersion: 1,
976
+ agentInfo: {
977
+ name: "computer-acp-bridge",
978
+ version: pkg.version ?? "0.0.0"
979
+ },
980
+ serverInfo: {
981
+ name: "computer-acp-bridge",
982
+ version: pkg.version ?? "0.0.0"
983
+ },
984
+ agentCapabilities: {
985
+ loadSession: true,
986
+ sessionCapabilities: {
987
+ list: {}
988
+ }
989
+ },
990
+ capabilities: {}
991
+ });
992
+ break;
993
+ }
994
+ case "session/list": {
995
+ const sessions = await listAgentSessions(computer.id);
996
+ const requestedCwd = typeof message.params?.cwd === "string" ? message.params.cwd.trim() : "";
997
+ emitResult(message.id, {
998
+ sessions: sessions.filter((session) => session.agent === options.agent).filter((session) => !requestedCwd || session.cwd === requestedCwd).map((session) => ({
999
+ sessionId: session.id,
1000
+ cwd: session.cwd,
1001
+ title: session.name || void 0,
1002
+ updatedAt: session.updated_at
1003
+ }))
1004
+ });
1005
+ break;
1006
+ }
1007
+ case "session/new": {
1008
+ nextSession += 1;
1009
+ const session = await ensureBridgeSession(
1010
+ computer.id,
1011
+ options.agent,
1012
+ options.name?.trim() || "acp",
1013
+ options.cwd,
1014
+ message.params?.cwd,
1015
+ nextSession
1016
+ );
1017
+ emitResult(message.id, {
1018
+ sessionId: session.id
1019
+ });
1020
+ break;
1021
+ }
1022
+ case "session/load": {
1023
+ if (typeof message.params?.sessionId !== "string" || !message.params.sessionId.trim()) {
1024
+ throw new Error("session/load requires sessionId");
1025
+ }
1026
+ await getAgentSession(computer.id, message.params.sessionId.trim());
1027
+ emitResult(message.id, {});
1028
+ break;
1029
+ }
1030
+ case "session/prompt": {
1031
+ if (typeof message.params?.sessionId !== "string" || !message.params.sessionId.trim()) {
1032
+ throw new Error("session/prompt requires sessionId");
1033
+ }
1034
+ await handlePromptRequest(computer.id, message.params.sessionId.trim(), message.id, message.params);
1035
+ break;
1036
+ }
1037
+ case "session/cancel": {
1038
+ if (typeof message.params?.sessionId !== "string" || !message.params.sessionId.trim()) {
1039
+ throw new Error("session/cancel requires sessionId");
1040
+ }
1041
+ await cancelAgentSession(computer.id, message.params.sessionId.trim());
1042
+ emitResult(message.id, {});
1043
+ break;
1044
+ }
1045
+ default:
1046
+ emitError(message.id, -32601, `method not found: ${message.method ?? "<unknown>"}`);
1047
+ break;
1048
+ }
1049
+ } catch (error) {
1050
+ emitError(
1051
+ message.id,
1052
+ -32e3,
1053
+ error instanceof Error ? error.message : "request failed"
1054
+ );
1055
+ }
1056
+ }
1057
+ } catch (error) {
1058
+ console.error(error instanceof Error ? error.message : "Failed to start ACP bridge");
1059
+ process.exit(1);
1060
+ }
1061
+ });
1062
+
1063
+ // src/commands/agent.ts
1064
+ import { Command as Command3 } from "commander";
583
1065
  import chalk3 from "chalk";
584
1066
  import ora2 from "ora";
1067
+ function formatAgentSessionStatus(status) {
1068
+ switch (status) {
1069
+ case "idle":
1070
+ return chalk3.green(status);
1071
+ case "running":
1072
+ return chalk3.blue(status);
1073
+ case "cancelling":
1074
+ return chalk3.yellow(status);
1075
+ case "interrupted":
1076
+ return chalk3.yellow(status);
1077
+ case "failed":
1078
+ return chalk3.red(status);
1079
+ case "closed":
1080
+ return chalk3.gray(status);
1081
+ default:
1082
+ return status;
1083
+ }
1084
+ }
1085
+ function printAgents(agents) {
1086
+ const idWidth = Math.max(5, ...agents.map((agent) => agent.id.length));
1087
+ console.log();
1088
+ console.log(
1089
+ ` ${chalk3.dim(padEnd("Agent", idWidth + 2))}${chalk3.dim(padEnd("Installed", 12))}${chalk3.dim(padEnd("Creds", 8))}${chalk3.dim("Version")}`
1090
+ );
1091
+ console.log(
1092
+ ` ${chalk3.dim("-".repeat(idWidth + 2))}${chalk3.dim("-".repeat(12))}${chalk3.dim("-".repeat(8))}${chalk3.dim("-".repeat(12))}`
1093
+ );
1094
+ for (const agent of agents) {
1095
+ console.log(
1096
+ ` ${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")}`
1097
+ );
1098
+ }
1099
+ console.log();
1100
+ }
1101
+ function printSessions(sessions, handleByComputerID = /* @__PURE__ */ new Map()) {
1102
+ if (sessions.length === 0) {
1103
+ console.log();
1104
+ console.log(chalk3.dim(" No agent sessions found."));
1105
+ console.log();
1106
+ return;
1107
+ }
1108
+ const nameWidth = Math.max(7, ...sessions.map((session) => (session.name || "default").length));
1109
+ const agentWidth = Math.max(5, ...sessions.map((session) => session.agent.length));
1110
+ const statusWidth = 13;
1111
+ console.log();
1112
+ console.log(
1113
+ ` ${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")}`
1114
+ );
1115
+ console.log(
1116
+ ` ${chalk3.dim("-".repeat(14))}${chalk3.dim("-".repeat(nameWidth + 2))}${chalk3.dim("-".repeat(agentWidth + 2))}${chalk3.dim("-".repeat(statusWidth + 2))}${chalk3.dim("-".repeat(20))}`
1117
+ );
1118
+ for (const session of sessions) {
1119
+ const location = handleByComputerID.get(session.computer_id) ?? session.computer_id;
1120
+ console.log(
1121
+ ` ${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}`
1122
+ );
1123
+ console.log(` ${chalk3.dim(` cwd=${session.cwd} updated=${timeAgo(session.updated_at)}`)}`);
1124
+ if (session.last_stop_reason || session.last_error) {
1125
+ console.log(` ${chalk3.dim(` ${session.last_error ? `error=${session.last_error}` : `stop=${session.last_stop_reason}`}`)}`);
1126
+ }
1127
+ }
1128
+ console.log();
1129
+ }
1130
+ var StreamPrinter = class {
1131
+ inlineOpen = false;
1132
+ writeText(text) {
1133
+ if (!text) return;
1134
+ process.stdout.write(text);
1135
+ this.inlineOpen = !text.endsWith("\n");
1136
+ }
1137
+ writeDim(text) {
1138
+ this.ensureBreak();
1139
+ process.stdout.write(chalk3.dim(text));
1140
+ this.inlineOpen = !text.endsWith("\n");
1141
+ }
1142
+ writeLine(text) {
1143
+ this.ensureBreak();
1144
+ console.log(text);
1145
+ this.inlineOpen = false;
1146
+ }
1147
+ ensureBreak() {
1148
+ if (!this.inlineOpen) {
1149
+ return;
1150
+ }
1151
+ process.stdout.write("\n");
1152
+ this.inlineOpen = false;
1153
+ }
1154
+ };
1155
+ async function streamSessionEvents(computerID, sessionID, options = {}) {
1156
+ const response = await openAgentSessionEventsStream(computerID, sessionID, {
1157
+ lastEventId: options.lastEventId?.trim() || void 0,
1158
+ signal: options.signal
1159
+ });
1160
+ if (!response.body) {
1161
+ return;
1162
+ }
1163
+ const decoder = new TextDecoder();
1164
+ const reader = response.body.getReader();
1165
+ const printer = new StreamPrinter();
1166
+ let buffer = "";
1167
+ while (true) {
1168
+ const { done, value } = await reader.read();
1169
+ if (done) {
1170
+ break;
1171
+ }
1172
+ buffer += decoder.decode(value, { stream: true }).replaceAll("\r\n", "\n");
1173
+ while (true) {
1174
+ const splitIndex = buffer.indexOf("\n\n");
1175
+ if (splitIndex === -1) {
1176
+ break;
1177
+ }
1178
+ const chunk = buffer.slice(0, splitIndex);
1179
+ buffer = buffer.slice(splitIndex + 2);
1180
+ renderSSEChunk(chunk, printer, options.json === true);
1181
+ }
1182
+ }
1183
+ printer.ensureBreak();
1184
+ }
1185
+ function renderSSEChunk(chunk, printer, asJson) {
1186
+ const dataLines = chunk.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
1187
+ if (dataLines.length === 0) {
1188
+ return;
1189
+ }
1190
+ const payload = dataLines.join("\n").trim();
1191
+ if (!payload || payload === "heartbeat") {
1192
+ return;
1193
+ }
1194
+ if (asJson) {
1195
+ try {
1196
+ const parsed = JSON.parse(payload);
1197
+ console.log(JSON.stringify(parsed));
1198
+ } catch {
1199
+ console.log(payload);
1200
+ }
1201
+ return;
1202
+ }
1203
+ let envelope;
1204
+ try {
1205
+ envelope = JSON.parse(payload);
1206
+ } catch {
1207
+ printer.writeLine(chalk3.dim(payload));
1208
+ return;
1209
+ }
1210
+ const method = typeof envelope.method === "string" ? envelope.method : "";
1211
+ if (method === "session/update") {
1212
+ const params = envelope.params;
1213
+ const update = params?.update;
1214
+ const sessionUpdate = typeof update?.sessionUpdate === "string" ? update.sessionUpdate : "";
1215
+ switch (sessionUpdate) {
1216
+ case "agent_message_chunk": {
1217
+ const content = update?.content;
1218
+ if (content?.type === "text" && content.text) {
1219
+ printer.writeText(content.text);
1220
+ }
1221
+ return;
1222
+ }
1223
+ case "agent_thought_chunk": {
1224
+ const content = update?.content;
1225
+ if (content?.type === "text" && content.text) {
1226
+ printer.writeDim(content.text);
1227
+ }
1228
+ return;
1229
+ }
1230
+ case "tool_call":
1231
+ case "tool_call_update": {
1232
+ const title = typeof update?.title === "string" && update.title ? update.title : "tool";
1233
+ const status = typeof update?.status === "string" && update.status ? update.status : "in_progress";
1234
+ printer.writeLine(chalk3.dim(`[tool:${status}] ${title}`));
1235
+ return;
1236
+ }
1237
+ case "plan": {
1238
+ const entries = Array.isArray(update?.entries) ? update.entries : [];
1239
+ const detail = entries.filter((entry) => typeof entry === "object" && entry !== null).map((entry) => `- [${entry.status ?? "pending"}] ${entry.content ?? ""}`).join("\n");
1240
+ if (detail) {
1241
+ printer.writeLine(chalk3.dim(`Plan
1242
+ ${detail}`));
1243
+ }
1244
+ return;
1245
+ }
1246
+ default:
1247
+ return;
1248
+ }
1249
+ }
1250
+ if (method === "session/request_permission") {
1251
+ printer.writeLine(chalk3.yellow("Permission requested by remote agent"));
1252
+ return;
1253
+ }
1254
+ }
1255
+ async function resolvePromptSession(computerID, options) {
1256
+ if (options.session?.trim()) {
1257
+ return getAgentSession(computerID, options.session.trim());
1258
+ }
1259
+ if (!options.agent?.trim()) {
1260
+ throw new Error("either --session or --agent is required");
1261
+ }
1262
+ return createAgentSession(computerID, {
1263
+ agent: options.agent.trim(),
1264
+ name: options.name?.trim() || "default",
1265
+ cwd: options.cwd?.trim(),
1266
+ resume: true
1267
+ });
1268
+ }
1269
+ var agentCommand = new Command3("agent").description("Manage cloud agent sessions");
1270
+ agentCommand.command("agents").description("List available agents on a machine").argument("<machine>", "Computer id or handle").option("--json", "Print raw JSON").action(async (identifier, options) => {
1271
+ const spinner = options.json ? null : ora2("Fetching machine agents...").start();
1272
+ try {
1273
+ const computer = await resolveComputer(identifier);
1274
+ const agents = await listComputerAgents(computer.id);
1275
+ spinner?.stop();
1276
+ if (options.json) {
1277
+ console.log(JSON.stringify({ agents }, null, 2));
1278
+ return;
1279
+ }
1280
+ printAgents(agents);
1281
+ } catch (error) {
1282
+ spinner?.fail(error instanceof Error ? error.message : "Failed to fetch agents");
1283
+ if (!spinner) {
1284
+ console.error(error instanceof Error ? error.message : "Failed to fetch agents");
1285
+ }
1286
+ process.exit(1);
1287
+ }
1288
+ });
1289
+ var sessionsCommand = new Command3("sessions").description("Manage agent sessions on one machine");
1290
+ sessionsCommand.command("list").description("List agent sessions for a machine").argument("<machine>", "Computer id or handle").option("--json", "Print raw JSON").action(async (identifier, options) => {
1291
+ const spinner = options.json ? null : ora2("Fetching agent sessions...").start();
1292
+ try {
1293
+ const computer = await resolveComputer(identifier);
1294
+ const sessions = await listAgentSessions(computer.id);
1295
+ spinner?.stop();
1296
+ if (options.json) {
1297
+ console.log(JSON.stringify({ sessions }, null, 2));
1298
+ return;
1299
+ }
1300
+ printSessions(sessions, /* @__PURE__ */ new Map([[computer.id, computer.handle]]));
1301
+ } catch (error) {
1302
+ spinner?.fail(error instanceof Error ? error.message : "Failed to fetch sessions");
1303
+ if (!spinner) {
1304
+ console.error(error instanceof Error ? error.message : "Failed to fetch sessions");
1305
+ }
1306
+ process.exit(1);
1307
+ }
1308
+ });
1309
+ sessionsCommand.command("new").description("Create or resume an agent session on a machine").argument("<machine>", "Computer id or handle").requiredOption("--agent <agent>", "Agent id").option("--name <name>", "Session name", "default").option("--cwd <cwd>", "Session working directory").option("--no-resume", "Always create a new session").option("--json", "Print raw JSON").action(async (identifier, options) => {
1310
+ const spinner = options.json ? null : ora2("Creating agent session...").start();
1311
+ try {
1312
+ const computer = await resolveComputer(identifier);
1313
+ const session = await createAgentSession(computer.id, {
1314
+ agent: options.agent,
1315
+ name: options.name,
1316
+ cwd: options.cwd,
1317
+ resume: options.resume
1318
+ });
1319
+ spinner?.stop();
1320
+ if (options.json) {
1321
+ console.log(JSON.stringify({ session }, null, 2));
1322
+ return;
1323
+ }
1324
+ printSessions([session], /* @__PURE__ */ new Map([[computer.id, computer.handle]]));
1325
+ } catch (error) {
1326
+ spinner?.fail(error instanceof Error ? error.message : "Failed to create session");
1327
+ if (!spinner) {
1328
+ console.error(error instanceof Error ? error.message : "Failed to create session");
1329
+ }
1330
+ process.exit(1);
1331
+ }
1332
+ });
1333
+ agentCommand.addCommand(sessionsCommand);
1334
+ agentCommand.command("prompt").description("Send a prompt to a machine agent session").argument("<machine>", "Computer id or handle").argument("<text>", "Prompt text").option("--session <id>", "Existing session id").option("--agent <agent>", "Agent to use when creating/resuming a session").option("--name <name>", "Session name when creating/resuming", "default").option("--cwd <cwd>", "Session working directory when creating/resuming").option("--no-stream", "Do not stream session events while waiting").option("--json", "Print raw JSON").action(async (identifier, text, options) => {
1335
+ const spinner = options.json ? null : ora2("Preparing agent prompt...").start();
1336
+ const controller = new AbortController();
1337
+ let streamPromise;
1338
+ try {
1339
+ const computer = await resolveComputer(identifier);
1340
+ const session = await resolvePromptSession(computer.id, options);
1341
+ spinner?.stop();
1342
+ const canStreamImmediately = !options.noStream && Boolean(session.backend_session_id?.trim());
1343
+ if (canStreamImmediately) {
1344
+ streamPromise = streamSessionEvents(computer.id, session.id, {
1345
+ json: options.json,
1346
+ signal: controller.signal
1347
+ }).catch((error) => {
1348
+ if (error instanceof Error && error.name === "AbortError") {
1349
+ return;
1350
+ }
1351
+ return;
1352
+ });
1353
+ await new Promise((resolve) => setTimeout(resolve, 150));
1354
+ }
1355
+ const promptResponse = await promptAgentSession(computer.id, session.id, {
1356
+ prompt: [{ type: "text", text }]
1357
+ });
1358
+ if (!options.noStream && !canStreamImmediately) {
1359
+ streamPromise = streamSessionEvents(computer.id, session.id, {
1360
+ json: options.json,
1361
+ signal: controller.signal
1362
+ }).catch((error) => {
1363
+ if (error instanceof Error && error.name === "AbortError") {
1364
+ return;
1365
+ }
1366
+ return;
1367
+ });
1368
+ }
1369
+ const settled = await waitForSessionToSettle(computer.id, session.id, promptResponse.prompt_id);
1370
+ controller.abort();
1371
+ if (streamPromise) {
1372
+ await streamPromise;
1373
+ }
1374
+ if (options.json) {
1375
+ console.log(JSON.stringify({ prompt: promptResponse, session: settled }, null, 2));
1376
+ return;
1377
+ }
1378
+ console.log();
1379
+ console.log(` ${chalk3.bold(session.name || "default")} ${formatAgentSessionStatus(settled.status)}`);
1380
+ if (settled.last_stop_reason) {
1381
+ console.log(chalk3.dim(` stop_reason=${settled.last_stop_reason}`));
1382
+ }
1383
+ if (settled.last_error) {
1384
+ console.log(chalk3.red(` error=${settled.last_error}`));
1385
+ }
1386
+ console.log();
1387
+ } catch (error) {
1388
+ controller.abort();
1389
+ if (spinner) {
1390
+ spinner.fail(error instanceof Error ? error.message : "Failed to prompt agent session");
1391
+ } else {
1392
+ console.error(error instanceof Error ? error.message : "Failed to prompt agent session");
1393
+ }
1394
+ process.exit(1);
1395
+ }
1396
+ });
1397
+ 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) => {
1398
+ try {
1399
+ const computer = await resolveComputer(identifier);
1400
+ if (options.lastEventId && !/^[1-9]\d*$/.test(options.lastEventId.trim())) {
1401
+ throw new Error("--last-event-id must be a positive integer");
1402
+ }
1403
+ await streamSessionEvents(computer.id, options.session.trim(), {
1404
+ lastEventId: options.lastEventId,
1405
+ json: options.json
1406
+ });
1407
+ } catch (error) {
1408
+ console.error(error instanceof Error ? error.message : "Failed to watch session");
1409
+ process.exit(1);
1410
+ }
1411
+ });
1412
+ agentCommand.command("status").description("Show one session or list sessions on a machine").argument("<machine>", "Computer id or handle").option("--session <id>", "Session id").option("--json", "Print raw JSON").action(async (identifier, options) => {
1413
+ const spinner = options.json ? null : ora2("Fetching session status...").start();
1414
+ try {
1415
+ const computer = await resolveComputer(identifier);
1416
+ if (options.session) {
1417
+ const session = await getAgentSession(computer.id, options.session.trim());
1418
+ spinner?.stop();
1419
+ if (options.json) {
1420
+ console.log(JSON.stringify({ session }, null, 2));
1421
+ return;
1422
+ }
1423
+ printSessions([session], /* @__PURE__ */ new Map([[computer.id, computer.handle]]));
1424
+ return;
1425
+ }
1426
+ const sessions = await listAgentSessions(computer.id);
1427
+ spinner?.stop();
1428
+ if (options.json) {
1429
+ console.log(JSON.stringify({ sessions }, null, 2));
1430
+ return;
1431
+ }
1432
+ printSessions(sessions, /* @__PURE__ */ new Map([[computer.id, computer.handle]]));
1433
+ } catch (error) {
1434
+ spinner?.fail(error instanceof Error ? error.message : "Failed to fetch session status");
1435
+ if (!spinner) {
1436
+ console.error(error instanceof Error ? error.message : "Failed to fetch session status");
1437
+ }
1438
+ process.exit(1);
1439
+ }
1440
+ });
1441
+ agentCommand.command("cancel").description("Send session/cancel to a machine agent session").argument("<machine>", "Computer id or handle").requiredOption("--session <id>", "Session id").option("--json", "Print raw JSON").action(async (identifier, options) => {
1442
+ const spinner = options.json ? null : ora2("Cancelling agent session...").start();
1443
+ try {
1444
+ const computer = await resolveComputer(identifier);
1445
+ const session = await cancelAgentSession(computer.id, options.session.trim());
1446
+ spinner?.stop();
1447
+ if (options.json) {
1448
+ console.log(JSON.stringify({ session }, null, 2));
1449
+ return;
1450
+ }
1451
+ printSessions([session], /* @__PURE__ */ new Map([[computer.id, computer.handle]]));
1452
+ } catch (error) {
1453
+ spinner?.fail(error instanceof Error ? error.message : "Failed to cancel session");
1454
+ if (!spinner) {
1455
+ console.error(error instanceof Error ? error.message : "Failed to cancel session");
1456
+ }
1457
+ process.exit(1);
1458
+ }
1459
+ });
1460
+ agentCommand.command("interrupt").description("Force-stop a machine agent session runtime").argument("<machine>", "Computer id or handle").requiredOption("--session <id>", "Session id").option("--json", "Print raw JSON").action(async (identifier, options) => {
1461
+ const spinner = options.json ? null : ora2("Interrupting agent session...").start();
1462
+ try {
1463
+ const computer = await resolveComputer(identifier);
1464
+ const session = await interruptAgentSession(computer.id, options.session.trim());
1465
+ spinner?.stop();
1466
+ if (options.json) {
1467
+ console.log(JSON.stringify({ session }, null, 2));
1468
+ return;
1469
+ }
1470
+ printSessions([session], /* @__PURE__ */ new Map([[computer.id, computer.handle]]));
1471
+ } catch (error) {
1472
+ spinner?.fail(error instanceof Error ? error.message : "Failed to interrupt session");
1473
+ if (!spinner) {
1474
+ console.error(error instanceof Error ? error.message : "Failed to interrupt session");
1475
+ }
1476
+ process.exit(1);
1477
+ }
1478
+ });
1479
+ agentCommand.command("close").description("Close and delete a machine agent session").argument("<machine>", "Computer id or handle").requiredOption("--session <id>", "Session id").action(async (identifier, options) => {
1480
+ const spinner = ora2("Closing agent session...").start();
1481
+ try {
1482
+ const computer = await resolveComputer(identifier);
1483
+ await deleteAgentSession(computer.id, options.session.trim());
1484
+ spinner.succeed("Agent session closed");
1485
+ } catch (error) {
1486
+ spinner.fail(error instanceof Error ? error.message : "Failed to close session");
1487
+ process.exit(1);
1488
+ }
1489
+ });
1490
+ var fleetCommand = new Command3("fleet").description("View agent activity across your fleet");
1491
+ fleetCommand.command("status").description("List open agent sessions across all machines").option("--json", "Print raw JSON").action(async (options) => {
1492
+ const spinner = options.json ? null : ora2("Fetching fleet status...").start();
1493
+ try {
1494
+ const [sessions, computers] = await Promise.all([
1495
+ listFleetAgentSessions(),
1496
+ listComputers()
1497
+ ]);
1498
+ spinner?.stop();
1499
+ if (options.json) {
1500
+ console.log(JSON.stringify({ sessions }, null, 2));
1501
+ return;
1502
+ }
1503
+ const handleByComputerID = new Map(computers.map((computer) => [computer.id, computer.handle]));
1504
+ printSessions(sessions, handleByComputerID);
1505
+ } catch (error) {
1506
+ spinner?.fail(error instanceof Error ? error.message : "Failed to fetch fleet status");
1507
+ if (!spinner) {
1508
+ console.error(error instanceof Error ? error.message : "Failed to fetch fleet status");
1509
+ }
1510
+ process.exit(1);
1511
+ }
1512
+ });
1513
+
1514
+ // src/commands/computers.ts
1515
+ import { Command as Command4 } from "commander";
1516
+ import chalk4 from "chalk";
1517
+ import ora3 from "ora";
585
1518
  import { select as select2, input as textInput, confirm } from "@inquirer/prompts";
586
1519
  function isInternalCondition(value) {
587
1520
  return /^[A-Z][a-zA-Z]+$/.test(value);
@@ -589,53 +1522,62 @@ function isInternalCondition(value) {
589
1522
  function printComputer(computer) {
590
1523
  const vnc = vncURL(computer);
591
1524
  const terminal = terminalURL(computer);
592
- const ssh = computer.ssh_enabled ? `ssh -p ${computer.ssh_port} ${computer.handle}@${computer.ssh_host}` : "disabled";
1525
+ const ssh = computer.ssh_enabled ? formatSSHCommand2(computer.handle, computer.ssh_host, computer.ssh_port) : "disabled";
593
1526
  const isCustom = computer.runtime_family === "custom-machine";
594
1527
  console.log();
595
- console.log(` ${chalk3.bold.white(computer.handle)} ${formatStatus(computer.status)}`);
1528
+ console.log(` ${chalk4.bold.white(computer.handle)} ${formatStatus(computer.status)}`);
596
1529
  console.log();
597
- console.log(` ${chalk3.dim("ID")} ${computer.id}`);
598
- console.log(` ${chalk3.dim("Tier")} ${computer.tier}`);
1530
+ console.log(` ${chalk4.dim("ID")} ${computer.id}`);
1531
+ console.log(` ${chalk4.dim("Tier")} ${computer.tier}`);
599
1532
  if (isCustom) {
600
- console.log(` ${chalk3.dim("Runtime")} ${computer.runtime_family}`);
601
- console.log(` ${chalk3.dim("Source")} ${computer.source_kind}`);
602
- console.log(` ${chalk3.dim("Image")} ${computer.image_family}`);
1533
+ console.log(` ${chalk4.dim("Runtime")} ${computer.runtime_family}`);
1534
+ console.log(` ${chalk4.dim("Source")} ${computer.source_kind}`);
1535
+ console.log(` ${chalk4.dim("Image")} ${computer.image_family}`);
603
1536
  }
604
- console.log(` ${chalk3.dim("Primary")} :${computer.primary_port}${computer.primary_path}`);
605
- console.log(` ${chalk3.dim("Health")} ${computer.healthcheck_type}${computer.healthcheck_value ? ` (${computer.healthcheck_value})` : ""}`);
1537
+ console.log(` ${chalk4.dim("Primary")} :${computer.primary_port}${computer.primary_path}`);
1538
+ console.log(` ${chalk4.dim("Health")} ${computer.healthcheck_type}${computer.healthcheck_value ? ` (${computer.healthcheck_value})` : ""}`);
606
1539
  console.log();
607
- console.log(` ${chalk3.dim("Gateway")} ${chalk3.cyan(webURL(computer))}`);
608
- console.log(` ${chalk3.dim("VNC")} ${vnc ? chalk3.cyan(vnc) : chalk3.dim("not available")}`);
609
- console.log(` ${chalk3.dim("Terminal")} ${terminal ? chalk3.cyan(terminal) : chalk3.dim("not available")}`);
610
- console.log(` ${chalk3.dim("SSH")} ${computer.ssh_enabled ? chalk3.white(ssh) : chalk3.dim(ssh)}`);
1540
+ console.log(` ${chalk4.dim("Gateway")} ${chalk4.cyan(webURL(computer))}`);
1541
+ console.log(` ${chalk4.dim("VNC")} ${vnc ? chalk4.cyan(vnc) : chalk4.dim("not available")}`);
1542
+ console.log(` ${chalk4.dim("Terminal")} ${terminal ? chalk4.cyan(terminal) : chalk4.dim("not available")}`);
1543
+ console.log(` ${chalk4.dim("SSH")} ${computer.ssh_enabled ? chalk4.white(ssh) : chalk4.dim(ssh)}`);
611
1544
  if (computer.last_error) {
612
1545
  console.log();
613
1546
  if (isInternalCondition(computer.last_error)) {
614
- console.log(` ${chalk3.dim("Condition")} ${chalk3.dim(computer.last_error)}`);
1547
+ console.log(` ${chalk4.dim("Condition")} ${chalk4.dim(computer.last_error)}`);
615
1548
  } else {
616
- console.log(` ${chalk3.dim("Error")} ${chalk3.red(computer.last_error)}`);
1549
+ console.log(` ${chalk4.dim("Error")} ${chalk4.red(computer.last_error)}`);
617
1550
  }
618
1551
  }
619
1552
  console.log();
620
- console.log(` ${chalk3.dim("Created")} ${timeAgo(computer.created_at)}`);
1553
+ console.log(` ${chalk4.dim("Created")} ${timeAgo(computer.created_at)}`);
621
1554
  console.log();
622
1555
  }
1556
+ function formatSSHCommand2(user, host, port) {
1557
+ if (!user.trim() || !host.trim()) {
1558
+ return "ssh unavailable";
1559
+ }
1560
+ if (port <= 0 || port === 22) {
1561
+ return `ssh ${user}@${host}`;
1562
+ }
1563
+ return `ssh -p ${port} ${user}@${host}`;
1564
+ }
623
1565
  function printComputerTable(computers) {
624
1566
  const handleWidth = Math.max(6, ...computers.map((c) => c.handle.length));
625
1567
  const statusWidth = 12;
626
1568
  const createdWidth = 10;
627
1569
  console.log();
628
1570
  console.log(
629
- ` ${chalk3.dim(padEnd("Handle", handleWidth + 2))}${chalk3.dim(padEnd("Status", statusWidth + 2))}${chalk3.dim(padEnd("Created", createdWidth + 2))}${chalk3.dim("URL")}`
1571
+ ` ${chalk4.dim(padEnd("Handle", handleWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim(padEnd("Created", createdWidth + 2))}${chalk4.dim("URL")}`
630
1572
  );
631
1573
  console.log(
632
- ` ${chalk3.dim("-".repeat(handleWidth + 2))}${chalk3.dim("-".repeat(statusWidth + 2))}${chalk3.dim("-".repeat(createdWidth + 2))}${chalk3.dim("-".repeat(20))}`
1574
+ ` ${chalk4.dim("-".repeat(handleWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(createdWidth + 2))}${chalk4.dim("-".repeat(20))}`
633
1575
  );
634
1576
  for (const computer of computers) {
635
1577
  const status = formatStatus(computer.status);
636
- const created = chalk3.dim(timeAgo(computer.created_at));
1578
+ const created = chalk4.dim(timeAgo(computer.created_at));
637
1579
  console.log(
638
- ` ${chalk3.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${padEnd(created, createdWidth + 2)}${chalk3.cyan(webURL(computer))}`
1580
+ ` ${chalk4.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${padEnd(created, createdWidth + 2)}${chalk4.cyan(webURL(computer))}`
639
1581
  );
640
1582
  }
641
1583
  console.log();
@@ -645,29 +1587,29 @@ function printComputerTableVerbose(computers) {
645
1587
  const statusWidth = 10;
646
1588
  console.log();
647
1589
  console.log(
648
- ` ${chalk3.dim(padEnd("Handle", handleWidth + 2))}${chalk3.dim(padEnd("Status", statusWidth + 2))}${chalk3.dim("URLs")}`
1590
+ ` ${chalk4.dim(padEnd("Handle", handleWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim("URLs")}`
649
1591
  );
650
1592
  console.log(
651
- ` ${chalk3.dim("-".repeat(handleWidth + 2))}${chalk3.dim("-".repeat(statusWidth + 2))}${chalk3.dim("-".repeat(20))}`
1593
+ ` ${chalk4.dim("-".repeat(handleWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(20))}`
652
1594
  );
653
1595
  for (const computer of computers) {
654
1596
  const status = formatStatus(computer.status);
655
1597
  const vnc = vncURL(computer);
656
1598
  const terminal = terminalURL(computer);
657
1599
  console.log(
658
- ` ${chalk3.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${chalk3.cyan(webURL(computer))}`
1600
+ ` ${chalk4.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${chalk4.cyan(webURL(computer))}`
659
1601
  );
660
1602
  console.log(
661
- ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk3.dim(vnc ?? "VNC not available")}`
1603
+ ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk4.dim(vnc ?? "VNC not available")}`
662
1604
  );
663
1605
  console.log(
664
- ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk3.dim(terminal ?? "Terminal not available")}`
1606
+ ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk4.dim(terminal ?? "Terminal not available")}`
665
1607
  );
666
1608
  }
667
1609
  console.log();
668
1610
  }
669
- var lsCommand = new Command2("ls").description("List computers").option("--json", "Print raw JSON").option("-v, --verbose", "Show all URLs for each computer").action(async (options) => {
670
- const spinner = options.json ? null : ora2("Fetching computers...").start();
1611
+ 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) => {
1612
+ const spinner = options.json ? null : ora3("Fetching computers...").start();
671
1613
  try {
672
1614
  const computers = await listComputers();
673
1615
  spinner?.stop();
@@ -677,7 +1619,7 @@ var lsCommand = new Command2("ls").description("List computers").option("--json"
677
1619
  }
678
1620
  if (computers.length === 0) {
679
1621
  console.log();
680
- console.log(chalk3.dim(" No computers found."));
1622
+ console.log(chalk4.dim(" No computers found."));
681
1623
  console.log();
682
1624
  return;
683
1625
  }
@@ -697,8 +1639,8 @@ var lsCommand = new Command2("ls").description("List computers").option("--json"
697
1639
  process.exit(1);
698
1640
  }
699
1641
  });
700
- var getCommand = new Command2("get").description("Show computer details").argument("<id-or-handle>", "Computer id or handle").option("--json", "Print raw JSON").action(async (identifier, options) => {
701
- const spinner = options.json ? null : ora2("Fetching computer...").start();
1642
+ 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) => {
1643
+ const spinner = options.json ? null : ora3("Fetching computer...").start();
702
1644
  try {
703
1645
  const computer = await resolveComputer(identifier);
704
1646
  spinner?.stop();
@@ -718,25 +1660,31 @@ var getCommand = new Command2("get").description("Show computer details").argume
718
1660
  process.exit(1);
719
1661
  }
720
1662
  });
721
- var createCommand = new Command2("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) => {
1663
+ 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) => {
722
1664
  let spinner;
723
1665
  let timer;
724
1666
  let startTime = 0;
725
1667
  try {
726
1668
  const selectedOptions = await resolveCreateOptions(options);
727
- spinner = ora2("Creating computer...").start();
1669
+ const runtimeFamily = effectiveRuntimeFamily(selectedOptions.runtimeFamily);
1670
+ const filesystemSettings = await loadFilesystemSettingsForCreate(runtimeFamily);
1671
+ const provisioningNote = createProvisioningNote(runtimeFamily, filesystemSettings);
1672
+ if (provisioningNote) {
1673
+ console.log(chalk4.dim(provisioningNote));
1674
+ }
1675
+ spinner = ora3(createSpinnerText(runtimeFamily, filesystemSettings, 0)).start();
728
1676
  startTime = Date.now();
729
1677
  timer = setInterval(() => {
730
- const elapsed2 = ((Date.now() - startTime) / 1e3).toFixed(1);
1678
+ const elapsed2 = (Date.now() - startTime) / 1e3;
731
1679
  if (spinner) {
732
- spinner.text = `Creating computer... ${chalk3.dim(`${elapsed2}s`)}`;
1680
+ spinner.text = createSpinnerText(runtimeFamily, filesystemSettings, elapsed2);
733
1681
  }
734
1682
  }, 100);
735
1683
  const computer = await createComputer({
736
1684
  handle,
737
1685
  display_name: selectedOptions.name,
738
1686
  tier: selectedOptions.tier,
739
- runtime_family: parseRuntimeFamilyOption(selectedOptions.runtimeFamily),
1687
+ runtime_family: runtimeFamily,
740
1688
  source_kind: parseSourceKindOption(selectedOptions.sourceKind),
741
1689
  image_family: selectedOptions.imageFamily,
742
1690
  image_ref: selectedOptions.imageRef,
@@ -760,7 +1708,7 @@ var createCommand = new Command2("create").description("Create a computer").argu
760
1708
  }
761
1709
  const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
762
1710
  spinner.succeed(
763
- chalk3.green(`Created ${chalk3.bold(computer.handle)} ${chalk3.dim(`[${elapsed}s]`)}`)
1711
+ chalk4.green(`Created ${chalk4.bold(computer.handle)} ${chalk4.dim(`[${elapsed}s]`)}`)
764
1712
  );
765
1713
  printComputer(computer);
766
1714
  } catch (error) {
@@ -769,10 +1717,10 @@ var createCommand = new Command2("create").description("Create a computer").argu
769
1717
  }
770
1718
  const message = error instanceof Error ? error.message : "Failed to create computer";
771
1719
  if (spinner) {
772
- const suffix = startTime ? ` ${chalk3.dim(`[${((Date.now() - startTime) / 1e3).toFixed(1)}s]`)}` : "";
1720
+ const suffix = startTime ? ` ${chalk4.dim(`[${((Date.now() - startTime) / 1e3).toFixed(1)}s]`)}` : "";
773
1721
  spinner.fail(`${message}${suffix}`);
774
1722
  } else {
775
- console.error(chalk3.red(message));
1723
+ console.error(chalk4.red(message));
776
1724
  }
777
1725
  process.exit(1);
778
1726
  }
@@ -827,28 +1775,28 @@ async function resolveCreateOptions(options) {
827
1775
  validateCreateOptions(selectedOptions);
828
1776
  return selectedOptions;
829
1777
  }
830
- var removeCommand = new Command2("rm").description("Delete a computer").argument("<id-or-handle>", "Computer id or handle").option("-y, --yes", "Skip confirmation prompt").action(async (identifier, options, cmd) => {
1778
+ 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) => {
831
1779
  const globalYes = cmd.parent?.opts()?.yes;
832
1780
  const skipConfirm = Boolean(options.yes || globalYes);
833
- const spinner = ora2("Resolving computer...").start();
1781
+ const spinner = ora3("Resolving computer...").start();
834
1782
  try {
835
1783
  const computer = await resolveComputer(identifier);
836
1784
  spinner.stop();
837
1785
  if (!skipConfirm && process.stdin.isTTY) {
838
1786
  const confirmed = await confirm({
839
- message: `Delete computer ${chalk3.bold(computer.handle)}?`,
1787
+ message: `Delete computer ${chalk4.bold(computer.handle)}?`,
840
1788
  default: false
841
1789
  });
842
1790
  if (!confirmed) {
843
- console.log(chalk3.dim(" Cancelled."));
1791
+ console.log(chalk4.dim(" Cancelled."));
844
1792
  return;
845
1793
  }
846
1794
  }
847
- const deleteSpinner = ora2("Deleting computer...").start();
1795
+ const deleteSpinner = ora3("Deleting computer...").start();
848
1796
  await api(`/v1/computers/${computer.id}`, {
849
1797
  method: "DELETE"
850
1798
  });
851
- deleteSpinner.succeed(chalk3.green(`Deleted ${chalk3.bold(computer.handle)}`));
1799
+ deleteSpinner.succeed(chalk4.green(`Deleted ${chalk4.bold(computer.handle)}`));
852
1800
  } catch (error) {
853
1801
  spinner.fail(
854
1802
  error instanceof Error ? error.message : "Failed to delete computer"
@@ -866,6 +1814,35 @@ function parseRuntimeFamilyOption(value) {
866
1814
  throw new Error("--runtime-family must be managed-worker or custom-machine");
867
1815
  }
868
1816
  }
1817
+ function effectiveRuntimeFamily(value) {
1818
+ return parseRuntimeFamilyOption(value) ?? "managed-worker";
1819
+ }
1820
+ async function loadFilesystemSettingsForCreate(runtimeFamily) {
1821
+ if (runtimeFamily !== "managed-worker") {
1822
+ return null;
1823
+ }
1824
+ try {
1825
+ return await getFilesystemSettings();
1826
+ } catch {
1827
+ return null;
1828
+ }
1829
+ }
1830
+ function createProvisioningNote(runtimeFamily, filesystemSettings) {
1831
+ if (runtimeFamily !== "managed-worker") {
1832
+ return null;
1833
+ }
1834
+ if (filesystemSettings?.shared_enabled) {
1835
+ return "Shared home is enabled. Mounting /home/node from EFS can take around 20s.";
1836
+ }
1837
+ return "Using isolated storage for this desktop.";
1838
+ }
1839
+ function createSpinnerText(runtimeFamily, filesystemSettings, elapsedSeconds) {
1840
+ const elapsedLabel = chalk4.dim(`${elapsedSeconds.toFixed(1)}s`);
1841
+ if (runtimeFamily === "managed-worker" && filesystemSettings?.shared_enabled && elapsedSeconds >= 5) {
1842
+ return `Creating computer... ${elapsedLabel} ${chalk4.dim("mounting shared home")}`;
1843
+ }
1844
+ return `Creating computer... ${elapsedLabel}`;
1845
+ }
869
1846
  function validateCreateOptions(options) {
870
1847
  const runtimeFamily = parseRuntimeFamilyOption(options.runtimeFamily);
871
1848
  const sourceKind = parseSourceKindOption(options.sourceKind);
@@ -923,7 +1900,7 @@ function resolveOptionalToggle(enabled, disabled, label) {
923
1900
  }
924
1901
 
925
1902
  // src/commands/completion.ts
926
- import { Command as Command3 } from "commander";
1903
+ import { Command as Command5 } from "commander";
927
1904
  var ZSH_SCRIPT = `#compdef computer agentcomputer aicomputer
928
1905
 
929
1906
  _computer() {
@@ -938,6 +1915,9 @@ _computer() {
938
1915
  'open:Open in browser'
939
1916
  'ssh:SSH into a computer'
940
1917
  'ports:Manage published ports'
1918
+ 'agent:Manage cloud agent sessions'
1919
+ 'fleet:View agent activity across your fleet'
1920
+ 'acp:Run a local ACP bridge for remote agent sessions'
941
1921
  'rm:Delete a computer'
942
1922
  'completion:Generate shell completions'
943
1923
  'help:Display help'
@@ -1064,7 +2044,7 @@ var BASH_SCRIPT = `_computer() {
1064
2044
  local cur prev words cword
1065
2045
  _init_completion || return
1066
2046
 
1067
- local commands="login logout whoami create ls get open ssh ports rm completion help"
2047
+ local commands="login logout whoami create ls get open ssh ports agent fleet acp rm completion help"
1068
2048
  local ports_commands="ls publish rm"
1069
2049
 
1070
2050
  if [[ $cword -eq 1 ]]; then
@@ -1118,7 +2098,7 @@ var BASH_SCRIPT = `_computer() {
1118
2098
  }
1119
2099
 
1120
2100
  complete -F _computer computer agentcomputer aicomputer`;
1121
- var completionCommand = new Command3("completion").description("Generate shell completions").argument("<shell>", "Shell type (bash or zsh)").action((shell) => {
2101
+ var completionCommand = new Command5("completion").description("Generate shell completions").argument("<shell>", "Shell type (bash or zsh)").action((shell) => {
1122
2102
  switch (shell) {
1123
2103
  case "zsh":
1124
2104
  console.log(ZSH_SCRIPT);
@@ -1133,9 +2113,9 @@ var completionCommand = new Command3("completion").description("Generate shell c
1133
2113
  });
1134
2114
 
1135
2115
  // src/commands/login.ts
1136
- import { Command as Command4 } from "commander";
1137
- import chalk4 from "chalk";
1138
- import ora3 from "ora";
2116
+ import { Command as Command6 } from "commander";
2117
+ import chalk5 from "chalk";
2118
+ import ora4 from "ora";
1139
2119
 
1140
2120
  // src/lib/browser-login.ts
1141
2121
  import { randomBytes } from "crypto";
@@ -1363,11 +2343,11 @@ function escapeHTML(value) {
1363
2343
  }
1364
2344
 
1365
2345
  // src/commands/login.ts
1366
- var loginCommand = new Command4("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) => {
2346
+ 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) => {
1367
2347
  const existingKey = getStoredAPIKey();
1368
2348
  if (existingKey && !options.force) {
1369
2349
  console.log();
1370
- console.log(chalk4.yellow(" Already logged in. Use --force to overwrite."));
2350
+ console.log(chalk5.yellow(" Already logged in. Use --force to overwrite."));
1371
2351
  console.log();
1372
2352
  return;
1373
2353
  }
@@ -1375,8 +2355,8 @@ var loginCommand = new Command4("login").description("Authenticate the CLI").opt
1375
2355
  const apiKey = await resolveAPIKeyInput(options.apiKey, options.stdin);
1376
2356
  if (!apiKey && wantsManualLogin) {
1377
2357
  console.log();
1378
- console.log(chalk4.dim(" Usage: computer login --api-key <ac_live_...>"));
1379
- console.log(chalk4.dim(` API: ${getBaseURL()}`));
2358
+ console.log(chalk5.dim(" Usage: computer login --api-key <ac_live_...>"));
2359
+ console.log(chalk5.dim(` API: ${getBaseURL()}`));
1380
2360
  console.log();
1381
2361
  process.exit(1);
1382
2362
  }
@@ -1386,15 +2366,15 @@ var loginCommand = new Command4("login").description("Authenticate the CLI").opt
1386
2366
  }
1387
2367
  if (!apiKey.startsWith("ac_live_")) {
1388
2368
  console.log();
1389
- console.log(chalk4.red(" API key must start with ac_live_"));
2369
+ console.log(chalk5.red(" API key must start with ac_live_"));
1390
2370
  console.log();
1391
2371
  process.exit(1);
1392
2372
  }
1393
- const spinner = ora3("Authenticating...").start();
2373
+ const spinner = ora4("Authenticating...").start();
1394
2374
  try {
1395
2375
  const me = await apiWithKey(apiKey, "/v1/me");
1396
2376
  setAPIKey(apiKey);
1397
- spinner.succeed(`Logged in as ${chalk4.bold(me.user.email)}`);
2377
+ spinner.succeed(`Logged in as ${chalk5.bold(me.user.email)}`);
1398
2378
  } catch (error) {
1399
2379
  spinner.fail(
1400
2380
  error instanceof Error ? error.message : "Failed to validate API key"
@@ -1403,7 +2383,7 @@ var loginCommand = new Command4("login").description("Authenticate the CLI").opt
1403
2383
  }
1404
2384
  });
1405
2385
  async function runBrowserLogin() {
1406
- const spinner = ora3("Starting browser login...").start();
2386
+ const spinner = ora4("Starting browser login...").start();
1407
2387
  let attempt = null;
1408
2388
  try {
1409
2389
  attempt = await createBrowserLoginAttempt();
@@ -1413,14 +2393,14 @@ async function runBrowserLogin() {
1413
2393
  } catch {
1414
2394
  spinner.stop();
1415
2395
  console.log();
1416
- console.log(chalk4.yellow(" Browser auto-open failed. Open this URL to continue:"));
1417
- console.log(chalk4.dim(` ${attempt.loginURL}`));
2396
+ console.log(chalk5.yellow(" Browser auto-open failed. Open this URL to continue:"));
2397
+ console.log(chalk5.dim(` ${attempt.loginURL}`));
1418
2398
  console.log();
1419
2399
  spinner.start("Waiting for browser login...");
1420
2400
  }
1421
2401
  spinner.text = "Waiting for browser login...";
1422
2402
  const result = await attempt.waitForResult();
1423
- spinner.succeed(`Logged in as ${chalk4.bold(result.me.user.email)}`);
2403
+ spinner.succeed(`Logged in as ${chalk5.bold(result.me.user.email)}`);
1424
2404
  } catch (error) {
1425
2405
  spinner.fail(error instanceof Error ? error.message : "Browser login failed");
1426
2406
  process.exit(1);
@@ -1446,33 +2426,33 @@ async function resolveAPIKeyInput(flagValue, readFromStdin) {
1446
2426
  }
1447
2427
 
1448
2428
  // src/commands/logout.ts
1449
- import { Command as Command5 } from "commander";
1450
- import chalk5 from "chalk";
1451
- var logoutCommand = new Command5("logout").description("Remove stored API key").action(() => {
2429
+ import { Command as Command7 } from "commander";
2430
+ import chalk6 from "chalk";
2431
+ var logoutCommand = new Command7("logout").description("Remove stored API key").action(() => {
1452
2432
  if (!getStoredAPIKey()) {
1453
2433
  console.log();
1454
- console.log(chalk5.dim(" Not logged in."));
2434
+ console.log(chalk6.dim(" Not logged in."));
1455
2435
  if (hasEnvAPIKey()) {
1456
- console.log(chalk5.dim(" Environment API key is still active in this shell."));
2436
+ console.log(chalk6.dim(" Environment API key is still active in this shell."));
1457
2437
  }
1458
2438
  console.log();
1459
2439
  return;
1460
2440
  }
1461
2441
  clearAPIKey();
1462
2442
  console.log();
1463
- console.log(chalk5.green(" Logged out."));
2443
+ console.log(chalk6.green(" Logged out."));
1464
2444
  if (hasEnvAPIKey()) {
1465
- console.log(chalk5.dim(" Environment API key is still active in this shell."));
2445
+ console.log(chalk6.dim(" Environment API key is still active in this shell."));
1466
2446
  }
1467
2447
  console.log();
1468
2448
  });
1469
2449
 
1470
2450
  // src/commands/whoami.ts
1471
- import { Command as Command6 } from "commander";
1472
- import chalk6 from "chalk";
1473
- import ora4 from "ora";
1474
- var whoamiCommand = new Command6("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
1475
- const spinner = options.json ? null : ora4("Loading user...").start();
2451
+ import { Command as Command8 } from "commander";
2452
+ import chalk7 from "chalk";
2453
+ import ora5 from "ora";
2454
+ var whoamiCommand = new Command8("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
2455
+ const spinner = options.json ? null : ora5("Loading user...").start();
1476
2456
  try {
1477
2457
  const me = await api("/v1/me");
1478
2458
  spinner?.stop();
@@ -1481,14 +2461,14 @@ var whoamiCommand = new Command6("whoami").description("Show current user").opti
1481
2461
  return;
1482
2462
  }
1483
2463
  console.log();
1484
- console.log(` ${chalk6.bold.white(me.user.display_name || me.user.email)}`);
2464
+ console.log(` ${chalk7.bold.white(me.user.display_name || me.user.email)}`);
1485
2465
  if (me.user.display_name) {
1486
- console.log(` ${chalk6.dim(me.user.email)}`);
2466
+ console.log(` ${chalk7.dim(me.user.email)}`);
1487
2467
  }
1488
2468
  if (me.api_key.name) {
1489
- console.log(` ${chalk6.dim("Key:")} ${me.api_key.name}`);
2469
+ console.log(` ${chalk7.dim("Key:")} ${me.api_key.name}`);
1490
2470
  }
1491
- console.log(` ${chalk6.dim("API:")} ${chalk6.dim(getBaseURL())}`);
2471
+ console.log(` ${chalk7.dim("API:")} ${chalk7.dim(getBaseURL())}`);
1492
2472
  console.log();
1493
2473
  } catch (error) {
1494
2474
  if (spinner) {
@@ -1501,70 +2481,146 @@ var whoamiCommand = new Command6("whoami").description("Show current user").opti
1501
2481
  });
1502
2482
 
1503
2483
  // src/index.ts
1504
- var require2 = createRequire(import.meta.url);
1505
- var pkg = require2("../package.json");
2484
+ var pkg2 = JSON.parse(
2485
+ readFileSync3(new URL("../package.json", import.meta.url), "utf8")
2486
+ );
1506
2487
  var cliName = process.argv[1] ? basename2(process.argv[1]) : "agentcomputer";
1507
- var program = new Command7();
1508
- program.name(cliName).description("Agent Computer CLI").version(pkg.version ?? "0.0.0").option("-y, --yes", "Skip confirmation prompts").configureHelp({
1509
- formatHelp(cmd, helper) {
1510
- const version = pkg.version ?? "0.0.0";
1511
- const lines = [];
1512
- lines.push(`${chalk7.bold(cliName)} ${chalk7.dim(`v${version}`)}`);
2488
+ var program = new Command9();
2489
+ function appendTextSection(lines, title, values) {
2490
+ if (values.length === 0) {
2491
+ return;
2492
+ }
2493
+ lines.push(` ${chalk8.dim(title)}`);
2494
+ lines.push("");
2495
+ for (const value of values) {
2496
+ lines.push(` ${chalk8.white(value)}`);
2497
+ }
2498
+ lines.push("");
2499
+ }
2500
+ function appendTableSection(lines, title, entries) {
2501
+ if (entries.length === 0) {
2502
+ return;
2503
+ }
2504
+ const width = Math.max(...entries.map((entry) => entry.term.length), 0) + 2;
2505
+ lines.push(` ${chalk8.dim(title)}`);
2506
+ lines.push("");
2507
+ for (const entry of entries) {
2508
+ lines.push(` ${chalk8.white(padEnd(entry.term, width))}${chalk8.dim(entry.desc)}`);
2509
+ }
2510
+ lines.push("");
2511
+ }
2512
+ function commandPath(cmd) {
2513
+ const parts = [];
2514
+ let current = cmd;
2515
+ while (current) {
2516
+ parts.unshift(current.name());
2517
+ current = current.parent ?? null;
2518
+ }
2519
+ return parts.join(" ");
2520
+ }
2521
+ function formatRootHelp(cmd) {
2522
+ const version = pkg2.version ?? "0.0.0";
2523
+ const lines = [];
2524
+ const groups = [
2525
+ ["Auth", []],
2526
+ ["Computers", []],
2527
+ ["Access", []],
2528
+ ["Agents", []],
2529
+ ["Other", []]
2530
+ ];
2531
+ const otherGroup = groups.find(([name]) => name === "Other")[1];
2532
+ lines.push(`${chalk8.bold(cliName)} ${chalk8.dim(`v${version}`)}`);
2533
+ lines.push("");
2534
+ if (cmd.description()) {
2535
+ lines.push(` ${chalk8.dim(cmd.description())}`);
1513
2536
  lines.push("");
1514
- if (cmd.commands.length > 0) {
1515
- const groups = {
1516
- Auth: [],
1517
- Computers: [],
1518
- Access: [],
1519
- Other: []
1520
- };
1521
- for (const sub of cmd.commands) {
1522
- const name = sub.name();
1523
- const desc = sub.description();
1524
- const entry = { name, desc };
1525
- if (["login", "logout", "whoami"].includes(name)) {
1526
- groups.Auth.push(entry);
1527
- } else if (["create", "ls", "get", "rm"].includes(name)) {
1528
- groups.Computers.push(entry);
1529
- } else if (["open", "ssh", "ports"].includes(name)) {
1530
- groups.Access.push(entry);
1531
- } else {
1532
- groups.Other.push(entry);
1533
- }
1534
- }
1535
- for (const [groupName, entries] of Object.entries(groups)) {
1536
- if (entries.length === 0) continue;
1537
- lines.push(` ${chalk7.dim(groupName)}`);
1538
- for (const entry of entries) {
1539
- const padded = entry.name.padEnd(14);
1540
- lines.push(` ${chalk7.white(padded)}${chalk7.dim(entry.desc)}`);
1541
- }
1542
- lines.push("");
1543
- }
1544
- }
1545
- const globalOpts = [
1546
- { flags: "-y, --yes", desc: "Skip confirmation prompts" },
1547
- { flags: "-V, --version", desc: "Show version" },
1548
- { flags: "-h, --help", desc: "Show help" }
1549
- ];
1550
- lines.push(` ${chalk7.dim("Options")}`);
1551
- for (const opt of globalOpts) {
1552
- const padded = opt.flags.padEnd(14);
1553
- lines.push(` ${chalk7.white(padded)}${chalk7.dim(opt.desc)}`);
2537
+ }
2538
+ appendTextSection(lines, "Usage", [`${cliName} <command> [options]`]);
2539
+ for (const sub of cmd.commands) {
2540
+ const name = sub.name();
2541
+ const entry = { term: name, desc: sub.description() };
2542
+ if (["login", "logout", "whoami"].includes(name)) {
2543
+ groups[0][1].push(entry);
2544
+ } else if (["create", "ls", "get", "rm"].includes(name)) {
2545
+ groups[1][1].push(entry);
2546
+ } else if (["open", "ssh", "ports"].includes(name)) {
2547
+ groups[2][1].push(entry);
2548
+ } else if (["agent", "fleet", "acp"].includes(name)) {
2549
+ groups[3][1].push(entry);
2550
+ } else {
2551
+ otherGroup.push(entry);
1554
2552
  }
2553
+ }
2554
+ for (const [groupName, entries] of groups) {
2555
+ appendTableSection(
2556
+ lines,
2557
+ groupName,
2558
+ entries
2559
+ );
2560
+ }
2561
+ appendTableSection(lines, "Options", [
2562
+ { term: "-y, --yes", desc: "Skip confirmation prompts" },
2563
+ { term: "-V, --version", desc: "Show version" },
2564
+ { term: "-h, --help", desc: "Show help" }
2565
+ ]);
2566
+ return `${lines.join("\n").trimEnd()}
2567
+ `;
2568
+ }
2569
+ function formatSubcommandHelp(cmd, helper) {
2570
+ const lines = [];
2571
+ const description = helper.commandDescription(cmd);
2572
+ const argumentsList = helper.visibleArguments(cmd).map((argument) => ({
2573
+ term: helper.argumentTerm(argument),
2574
+ desc: helper.argumentDescription(argument)
2575
+ }));
2576
+ const commandList = helper.visibleCommands(cmd).map((subcommand) => ({
2577
+ term: helper.subcommandTerm(subcommand),
2578
+ desc: helper.subcommandDescription(subcommand)
2579
+ }));
2580
+ const optionList = helper.visibleOptions(cmd).map((option) => ({
2581
+ term: helper.optionTerm(option),
2582
+ desc: helper.optionDescription(option)
2583
+ }));
2584
+ lines.push(chalk8.bold(commandPath(cmd)));
2585
+ lines.push("");
2586
+ if (description) {
2587
+ lines.push(` ${chalk8.dim(description)}`);
1555
2588
  lines.push("");
1556
- return lines.join("\n");
1557
2589
  }
1558
- });
2590
+ appendTextSection(lines, "Usage", [helper.commandUsage(cmd)]);
2591
+ appendTableSection(lines, "Arguments", argumentsList);
2592
+ appendTableSection(lines, "Commands", commandList);
2593
+ appendTableSection(lines, "Options", optionList);
2594
+ return `${lines.join("\n").trimEnd()}
2595
+ `;
2596
+ }
2597
+ function applyHelpFormatting(cmd) {
2598
+ cmd.configureHelp({
2599
+ formatHelp(current, helper) {
2600
+ if (!current.parent) {
2601
+ return formatRootHelp(current);
2602
+ }
2603
+ return formatSubcommandHelp(current, helper);
2604
+ }
2605
+ });
2606
+ for (const subcommand of cmd.commands) {
2607
+ applyHelpFormatting(subcommand);
2608
+ }
2609
+ }
2610
+ program.name(cliName).description("Agent Computer CLI").version(pkg2.version ?? "0.0.0").option("-y, --yes", "Skip confirmation prompts");
1559
2611
  program.addCommand(loginCommand);
1560
2612
  program.addCommand(logoutCommand);
1561
2613
  program.addCommand(whoamiCommand);
1562
2614
  program.addCommand(createCommand);
1563
2615
  program.addCommand(lsCommand);
1564
2616
  program.addCommand(getCommand);
2617
+ program.addCommand(agentCommand);
2618
+ program.addCommand(fleetCommand);
2619
+ program.addCommand(acpCommand);
1565
2620
  program.addCommand(openCommand);
1566
2621
  program.addCommand(sshCommand);
1567
2622
  program.addCommand(portsCommand);
1568
2623
  program.addCommand(removeCommand);
1569
2624
  program.addCommand(completionCommand);
2625
+ applyHelpFormatting(program);
1570
2626
  program.parse();