aicomputer 0.1.18 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,52 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ensureSSHAliasConfig
4
+ } from "./chunk-D3SAFNSI.js";
5
+ import {
6
+ compareVersions,
7
+ resolveLatestPublishedVersion
8
+ } from "./chunk-GGBVVRLL.js";
2
9
  import {
3
10
  formatMountHostInstallGuidance,
4
11
  getMountHostValidationIssues
5
12
  } from "./chunk-JMRAYXUO.js";
13
+ import {
14
+ formatStatus,
15
+ padEnd,
16
+ promptForSSHComputer,
17
+ reconcileMounts,
18
+ teardownManagedSessions,
19
+ timeAgo
20
+ } from "./chunk-36BZXAAX.js";
21
+ import {
22
+ isAbortError
23
+ } from "./chunk-TPFE3CC6.js";
24
+ import {
25
+ defaultMountServiceConfig,
26
+ ensureMountDirectories,
27
+ getMountPaths,
28
+ readMountConfig,
29
+ readMountControllerLock,
30
+ readMountStatusSnapshot,
31
+ removeMountControllerLock,
32
+ writeMountConfig,
33
+ writeMountControllerLock,
34
+ writeMountStatusSnapshot
35
+ } from "./chunk-KXLTHWW3.js";
36
+ import {
37
+ AGENTCOMPUTER_MUTAGEN_PATH_ENV,
38
+ ensureBundledMutagenInstalled,
39
+ ensureMutagenCommandPath
40
+ } from "./chunk-GD42GHW3.js";
41
+ import {
42
+ ensureDefaultSSHKeyRegistered,
43
+ openSSHConnection,
44
+ prepareSSHConnection,
45
+ prepareSSHConnectionByIdentifier
46
+ } from "./chunk-4TE5XTYE.js";
47
+ import {
48
+ ensureBundledAutosshInstalled
49
+ } from "./chunk-3ZF7JRBW.js";
6
50
  import {
7
51
  ApiError,
8
52
  api,
@@ -12,7 +56,6 @@ import {
12
56
  createComputer,
13
57
  deleteComputer,
14
58
  deletePublishedPort,
15
- formatStatus,
16
59
  getAPIKey,
17
60
  getBaseURL,
18
61
  getComputerByID,
@@ -23,168 +66,27 @@ import {
23
66
  hasEnvAPIKey,
24
67
  listComputers,
25
68
  listPublishedPorts,
26
- padEnd,
27
- promptForSSHComputer,
69
+ powerOffComputer,
70
+ powerOnComputer,
28
71
  publishPort,
29
- reconcileMounts,
30
72
  resolveComputer,
31
73
  setAPIKey,
32
- teardownManagedSessions,
33
- timeAgo,
34
74
  vncURL,
35
75
  webURL
36
- } from "./chunk-3ZUTAUUD.js";
37
- import {
38
- AGENTCOMPUTER_MUTAGEN_PATH_ENV,
39
- ensureBundledMutagenInstalled,
40
- ensureMutagenCommandPath,
41
- isAbortError
42
- } from "./chunk-F2U4SFJ4.js";
43
- import {
44
- defaultMountServiceConfig,
45
- ensureMountDirectories,
46
- getMountPaths,
47
- readMountConfig,
48
- readMountControllerLock,
49
- readMountStatusSnapshot,
50
- removeMountControllerLock,
51
- writeMountConfig,
52
- writeMountControllerLock,
53
- writeMountStatusSnapshot
54
- } from "./chunk-KXLTHWW3.js";
76
+ } from "./chunk-LOGK7YYJ.js";
77
+ import "./chunk-5Y2NWK5I.js";
55
78
 
56
79
  // src/index.ts
57
- import { Command as Command15 } from "commander";
58
- import chalk13 from "chalk";
80
+ import { Command as Command16 } from "commander";
81
+ import chalk14 from "chalk";
59
82
  import { readFileSync as readFileSync3 } from "fs";
60
- import { basename as basename2 } from "path";
83
+ import { basename } from "path";
61
84
 
62
85
  // src/commands/access.ts
63
86
  import { Command } from "commander";
64
87
  import chalk from "chalk";
65
88
  import ora from "ora";
66
89
 
67
- // src/lib/ssh-access.ts
68
- import { spawn } from "child_process";
69
-
70
- // src/lib/ssh-keys.ts
71
- import { basename } from "path";
72
- import { homedir } from "os";
73
- import { readFile, mkdir } from "fs/promises";
74
- import { execFileSync } from "child_process";
75
- import { existsSync } from "fs";
76
- var DEFAULT_PUBLIC_KEY_PATHS = [
77
- `${homedir()}/.ssh/id_ed25519.pub`,
78
- `${homedir()}/.ssh/id_ecdsa.pub`,
79
- `${homedir()}/.ssh/id_rsa.pub`
80
- ];
81
- async function ensureDefaultSSHKeyRegistered() {
82
- for (const path of DEFAULT_PUBLIC_KEY_PATHS) {
83
- try {
84
- const publicKey2 = (await readFile(path, "utf8")).trim();
85
- if (!publicKey2) {
86
- continue;
87
- }
88
- const key2 = await api("/v1/ssh-keys", {
89
- method: "POST",
90
- body: JSON.stringify({
91
- name: basename(path),
92
- public_key: publicKey2
93
- })
94
- });
95
- return {
96
- key: key2,
97
- publicKeyPath: path,
98
- privateKeyPath: path.replace(/\.pub$/, "")
99
- };
100
- } catch (error) {
101
- if (error?.code === "ENOENT") {
102
- continue;
103
- }
104
- throw error;
105
- }
106
- }
107
- const generated = await generateSSHKey();
108
- const publicKey = (await readFile(generated.publicKeyPath, "utf8")).trim();
109
- const key = await api("/v1/ssh-keys", {
110
- method: "POST",
111
- body: JSON.stringify({
112
- name: basename(generated.publicKeyPath),
113
- public_key: publicKey
114
- })
115
- });
116
- return {
117
- key,
118
- publicKeyPath: generated.publicKeyPath,
119
- privateKeyPath: generated.privateKeyPath
120
- };
121
- }
122
- async function generateSSHKey() {
123
- const sshDir = `${homedir()}/.ssh`;
124
- if (!existsSync(sshDir)) {
125
- await mkdir(sshDir, { mode: 448 });
126
- }
127
- const privateKeyPath = `${sshDir}/id_ed25519`;
128
- const publicKeyPath = `${privateKeyPath}.pub`;
129
- console.log("No SSH key found \u2014 generating one at", publicKeyPath);
130
- execFileSync("ssh-keygen", ["-t", "ed25519", "-f", privateKeyPath, "-N", ""], {
131
- stdio: "inherit"
132
- });
133
- return { publicKeyPath, privateKeyPath };
134
- }
135
-
136
- // src/lib/ssh-access.ts
137
- async function prepareSSHConnection(computer) {
138
- const registered = await ensureDefaultSSHKeyRegistered();
139
- const info = await getConnectionInfo(computer.id);
140
- if (!info.connection.ssh_available) {
141
- throw new Error("SSH is not available for this computer");
142
- }
143
- return {
144
- computer,
145
- command: formatSSHCommand(
146
- info.connection.ssh_user,
147
- info.connection.ssh_host,
148
- info.connection.ssh_port
149
- ),
150
- args: [
151
- "-i",
152
- registered.privateKeyPath,
153
- "-p",
154
- String(info.connection.ssh_port),
155
- `${info.connection.ssh_user}@${info.connection.ssh_host}`
156
- ]
157
- };
158
- }
159
- async function prepareSSHConnectionByIdentifier(identifier) {
160
- const computer = await resolveComputer(identifier);
161
- return prepareSSHConnection(computer);
162
- }
163
- async function openSSHConnection(connection) {
164
- await new Promise((resolve, reject) => {
165
- const child = spawn("ssh", connection.args, {
166
- stdio: "inherit"
167
- });
168
- child.on("error", reject);
169
- child.on("exit", (code) => {
170
- if (code === 0) {
171
- resolve();
172
- return;
173
- }
174
- reject(new Error(`ssh exited with code ${code ?? 1}`));
175
- });
176
- });
177
- }
178
- function formatSSHCommand(user, host, port) {
179
- if (!user.trim() || !host.trim()) {
180
- return "ssh unavailable";
181
- }
182
- if (port <= 0 || port === 22) {
183
- return `ssh ${user}@${host}`;
184
- }
185
- return `ssh -p ${port} ${user}@${host}`;
186
- }
187
-
188
90
  // src/lib/open-browser.ts
189
91
  import { constants } from "fs";
190
92
  import { access } from "fs/promises";
@@ -242,74 +144,6 @@ async function openBrowserURL(url) {
242
144
  await open(url);
243
145
  }
244
146
 
245
- // src/lib/ssh-config.ts
246
- import { homedir as homedir2 } from "os";
247
- import { join } from "path";
248
- import { mkdir as mkdir2, readFile as readFile2, writeFile } from "fs/promises";
249
- var MANAGED_BLOCK_START = "# >>> agentcomputer ssh setup >>>";
250
- var MANAGED_BLOCK_END = "# <<< agentcomputer ssh setup <<<";
251
- async function ensureSSHAliasConfig(options) {
252
- const sshDir = join(homedir2(), ".ssh");
253
- const configPath = join(sshDir, "config");
254
- await mkdir2(sshDir, { recursive: true, mode: 448 });
255
- let existing = "";
256
- try {
257
- existing = await readFile2(configPath, "utf8");
258
- } catch (error) {
259
- if (error.code !== "ENOENT") {
260
- throw error;
261
- }
262
- }
263
- const nextBlock = renderManagedBlock(options);
264
- const managedBlockPattern = new RegExp(
265
- `${escapeRegex(MANAGED_BLOCK_START)}[\\s\\S]*?${escapeRegex(MANAGED_BLOCK_END)}\\n?`,
266
- "m"
267
- );
268
- let nextContents;
269
- if (managedBlockPattern.test(existing)) {
270
- nextContents = existing.replace(managedBlockPattern, nextBlock);
271
- } else {
272
- const normalized = existing.length === 0 ? "" : existing.endsWith("\n") ? existing : `${existing}
273
- `;
274
- nextContents = normalized.length === 0 ? nextBlock : `${normalized}
275
- ${nextBlock}`;
276
- }
277
- const changed = nextContents !== existing;
278
- if (changed) {
279
- await writeFile(configPath, nextContents, { mode: 384 });
280
- }
281
- return {
282
- configPath,
283
- changed
284
- };
285
- }
286
- function renderManagedBlock(options) {
287
- const user = options.user?.trim() || "agentcomputer";
288
- const identityFile = formatIdentityFilePath(options.identityFilePath);
289
- return `${MANAGED_BLOCK_START}
290
- Host ${options.alias}
291
- HostName ${options.host}
292
- Port ${options.port}
293
- User ${user}
294
- IdentityFile ${identityFile}
295
- IdentitiesOnly yes
296
- ServerAliveInterval 30
297
- ServerAliveCountMax 4
298
- ${MANAGED_BLOCK_END}
299
- `;
300
- }
301
- function formatIdentityFilePath(path) {
302
- const normalized = path.trim();
303
- const homePath = `${homedir2()}/`;
304
- if (normalized.startsWith(homePath)) {
305
- return `~/${normalized.slice(homePath.length)}`;
306
- }
307
- return normalized.replaceAll(" ", "\\ ");
308
- }
309
- function escapeRegex(value) {
310
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
311
- }
312
-
313
147
  // src/lib/ssh-setup.ts
314
148
  async function ensureSSHAccessConfigured(options = {}) {
315
149
  const alias = normalizeSSHAlias(options.alias ?? "agentcomputer.ai");
@@ -389,8 +223,8 @@ var openCommand = new Command("open").description("Open a computer in your brows
389
223
  process.exit(1);
390
224
  }
391
225
  });
392
- 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", "443").action(
393
- async (identifier, options) => {
226
+ var sshCommand = new Command("ssh").description("Open an SSH session to a computer").allowUnknownOption(true).argument("[id-or-handle]", "Computer id or handle").argument("[ssh-args...]", "SSH arguments passed through after the machine handle").option("--setup", "Register key and configure a global SSH alias").option("--tmux", "Attach or create a persistent tmux session on connect").option("--alias <alias>", "SSH host alias", "agentcomputer.ai").option("--host <host>", "SSH gateway host", "ssh.agentcomputer.ai").option("--port <port>", "SSH gateway port", "443").action(
227
+ async (identifier, sshArgs, options) => {
394
228
  if (options.setup) {
395
229
  await setupSSHAlias(options);
396
230
  return;
@@ -400,7 +234,10 @@ var sshCommand = new Command("ssh").description("Open an SSH session to a comput
400
234
  ).start();
401
235
  try {
402
236
  const computer = await resolveSSHComputer(identifier, spinner);
403
- const connection = await prepareSSHConnection(computer);
237
+ const connection = await prepareSSHConnection(computer, {
238
+ extraArgs: sshArgs,
239
+ tmux: options.tmux
240
+ });
404
241
  spinner.succeed(`Connecting to ${chalk.bold(computer.handle)}`);
405
242
  console.log(chalk.dim(` ${connection.command}`));
406
243
  console.log();
@@ -1311,7 +1148,7 @@ import ora4 from "ora";
1311
1148
 
1312
1149
  // src/lib/remote-auth.ts
1313
1150
  import { randomBytes } from "crypto";
1314
- import { spawn as spawn2 } from "child_process";
1151
+ import { spawn } from "child_process";
1315
1152
  import ora3 from "ora";
1316
1153
  var readyPollIntervalMs = 2e3;
1317
1154
  var readyPollTimeoutMs = 18e4;
@@ -1447,7 +1284,7 @@ async function runRemoteCommand(target, remoteArgs, script) {
1447
1284
  ...remoteArgs
1448
1285
  ];
1449
1286
  return new Promise((resolve, reject) => {
1450
- const child = spawn2("ssh", args, {
1287
+ const child = spawn("ssh", args, {
1451
1288
  stdio: ["pipe", "pipe", "pipe"]
1452
1289
  });
1453
1290
  let stdout = "";
@@ -2007,13 +1844,14 @@ function isInternalCondition(value) {
2007
1844
  }
2008
1845
  function printComputer(computer) {
2009
1846
  const vnc = vncURL(computer);
2010
- const ssh = computer.ssh_enabled ? formatSSHCommand2(computer.handle, computer.ssh_host, computer.ssh_port) : "disabled";
1847
+ const ssh = computer.ssh_enabled ? formatSSHCommand(computer.handle, computer.ssh_host, computer.ssh_port) : "disabled";
2011
1848
  const isCustom = computer.runtime_family === "custom-machine";
2012
1849
  console.log();
2013
1850
  console.log(` ${chalk4.bold.white(computer.handle)} ${formatStatus(computer.status)}`);
2014
1851
  console.log();
2015
1852
  console.log(` ${chalk4.dim("ID")} ${computer.id}`);
2016
1853
  console.log(` ${chalk4.dim("Tier")} ${computer.tier}`);
1854
+ console.log(` ${chalk4.dim("Power")} ${formatDesiredPowerState(computer.desired_power_state)}`);
2017
1855
  if (isCustom) {
2018
1856
  console.log(` ${chalk4.dim("Runtime")} ${computer.runtime_family}`);
2019
1857
  console.log(` ${chalk4.dim("Source")} ${computer.source_kind}`);
@@ -2043,7 +1881,7 @@ function printComputer(computer) {
2043
1881
  console.log(` ${chalk4.dim("Created")} ${timeAgo(computer.created_at)}`);
2044
1882
  console.log();
2045
1883
  }
2046
- function formatSSHCommand2(user, host, port) {
1884
+ function formatSSHCommand(user, host, port) {
2047
1885
  if (!user.trim() || !host.trim()) {
2048
1886
  return "ssh unavailable";
2049
1887
  }
@@ -2071,6 +1909,12 @@ function formatManagedWorkerLaunchSource(computer) {
2071
1909
  }
2072
1910
  return `saved custom source ${computer.user_source_id}`;
2073
1911
  }
1912
+ function formatDesiredPowerState(state) {
1913
+ return state === "off" ? chalk4.yellow("off") : chalk4.green("on");
1914
+ }
1915
+ function supportsPowerLifecycle(computer) {
1916
+ return computer.runtime_family === "managed-worker";
1917
+ }
2074
1918
  function printComputerTable(computers) {
2075
1919
  const handleWidth = Math.max(6, ...computers.map((c) => c.handle.length));
2076
1920
  const statusWidth = 12;
@@ -2327,6 +2171,72 @@ var removeCommand = new Command5("rm").description("Delete a computer").argument
2327
2171
  process.exit(1);
2328
2172
  }
2329
2173
  });
2174
+ var powerOffCommand = new Command5("power-off").description("Power off a managed worker without deleting its storage").argument("<id-or-handle>", "Computer id or handle").option("--json", "Print raw JSON").action(async (identifier, options) => {
2175
+ const spinner = options.json ? null : ora5("Resolving computer...").start();
2176
+ try {
2177
+ const computer = await resolveComputer(identifier);
2178
+ if (!supportsPowerLifecycle(computer)) {
2179
+ throw new Error("power lifecycle is only available for managed-worker machines");
2180
+ }
2181
+ spinner?.start("Powering off computer...");
2182
+ const poweredOff = await powerOffComputer(computer.id);
2183
+ spinner?.succeed(chalk4.green(`Powered off ${chalk4.bold(poweredOff.handle)}`));
2184
+ await notifyMountDaemon(
2185
+ getMountPaths((readMountConfig() ?? defaultMountServiceConfig()).rootPath)
2186
+ ).catch(
2187
+ () => false
2188
+ );
2189
+ if (options.json) {
2190
+ console.log(JSON.stringify(poweredOff, null, 2));
2191
+ return;
2192
+ }
2193
+ printComputer(poweredOff);
2194
+ } catch (error) {
2195
+ if (spinner) {
2196
+ spinner.fail(
2197
+ error instanceof Error ? error.message : "Failed to power off computer"
2198
+ );
2199
+ } else {
2200
+ console.error(
2201
+ error instanceof Error ? error.message : "Failed to power off computer"
2202
+ );
2203
+ }
2204
+ process.exit(1);
2205
+ }
2206
+ });
2207
+ var powerOnCommand = new Command5("power-on").description("Power on a managed worker and recreate its runtime").argument("<id-or-handle>", "Computer id or handle").option("--json", "Print raw JSON").action(async (identifier, options) => {
2208
+ const spinner = options.json ? null : ora5("Resolving computer...").start();
2209
+ try {
2210
+ const computer = await resolveComputer(identifier);
2211
+ if (!supportsPowerLifecycle(computer)) {
2212
+ throw new Error("power lifecycle is only available for managed-worker machines");
2213
+ }
2214
+ spinner?.start("Powering on computer...");
2215
+ const poweredOn = await powerOnComputer(computer.id);
2216
+ spinner?.succeed(chalk4.green(`Powered on ${chalk4.bold(poweredOn.handle)}`));
2217
+ await notifyMountDaemon(
2218
+ getMountPaths((readMountConfig() ?? defaultMountServiceConfig()).rootPath)
2219
+ ).catch(
2220
+ () => false
2221
+ );
2222
+ if (options.json) {
2223
+ console.log(JSON.stringify(poweredOn, null, 2));
2224
+ return;
2225
+ }
2226
+ printComputer(poweredOn);
2227
+ } catch (error) {
2228
+ if (spinner) {
2229
+ spinner.fail(
2230
+ error instanceof Error ? error.message : "Failed to power on computer"
2231
+ );
2232
+ } else {
2233
+ console.error(
2234
+ error instanceof Error ? error.message : "Failed to power on computer"
2235
+ );
2236
+ }
2237
+ process.exit(1);
2238
+ }
2239
+ });
2330
2240
  function parseRuntimeFamilyOption(value) {
2331
2241
  switch (value) {
2332
2242
  case void 0:
@@ -2462,6 +2372,8 @@ _computer() {
2462
2372
  'create:Create a computer'
2463
2373
  'ls:List computers'
2464
2374
  'get:Show computer details'
2375
+ 'power-on:Power on a managed worker'
2376
+ 'power-off:Power off a managed worker'
2465
2377
  'image:Manage machine image sources'
2466
2378
  'open:Open in browser'
2467
2379
  'ssh:SSH into a computer'
@@ -2552,6 +2464,11 @@ _computer() {
2552
2464
  '--json[Print raw JSON]' \\
2553
2465
  '1:computer:_computer_handles'
2554
2466
  ;;
2467
+ power-on|power-off)
2468
+ _arguments \\
2469
+ '--json[Print raw JSON]' \\
2470
+ '1:computer:_computer_handles'
2471
+ ;;
2555
2472
  image)
2556
2473
  _arguments -C \\
2557
2474
  '1:command:->image_command' \\
@@ -2816,10 +2733,10 @@ var completionCommand = new Command6("completion").description("Generate shell c
2816
2733
  });
2817
2734
 
2818
2735
  // src/commands/codex-login.ts
2819
- import { spawn as spawn3 } from "child_process";
2820
- import { readFile as readFile3 } from "fs/promises";
2821
- import { homedir as homedir3 } from "os";
2822
- import { join as join2 } from "path";
2736
+ import { spawn as spawn2 } from "child_process";
2737
+ import { readFile } from "fs/promises";
2738
+ import { homedir } from "os";
2739
+ import { join } from "path";
2823
2740
  import { Command as Command7 } from "commander";
2824
2741
  import chalk5 from "chalk";
2825
2742
  import ora6 from "ora";
@@ -3019,10 +2936,10 @@ async function getLocalCodexStatus() {
3019
2936
  return parseCodexStatusOutput(result.stdout, result.stderr);
3020
2937
  }
3021
2938
  async function readLocalCodexAuthFile() {
3022
- const authPath = join2(homedir3(), ".codex", "auth.json");
2939
+ const authPath = join(homedir(), ".codex", "auth.json");
3023
2940
  let raw;
3024
2941
  try {
3025
- raw = await readFile3(authPath, "utf8");
2942
+ raw = await readFile(authPath, "utf8");
3026
2943
  } catch (error) {
3027
2944
  throw new Error(
3028
2945
  error instanceof Error ? `failed to read ${authPath}: ${error.message}` : `failed to read ${authPath}`
@@ -3040,7 +2957,7 @@ async function readLocalCodexAuthFile() {
3040
2957
  }
3041
2958
  async function runInteractiveCodexLogin() {
3042
2959
  await new Promise((resolve, reject) => {
3043
- const child = spawn3("codex", ["login"], {
2960
+ const child = spawn2("codex", ["login"], {
3044
2961
  stdio: "inherit"
3045
2962
  });
3046
2963
  child.on("error", (error) => {
@@ -3063,7 +2980,7 @@ async function runInteractiveCodexLogin() {
3063
2980
  }
3064
2981
  async function captureLocalCommand(command, args) {
3065
2982
  return new Promise((resolve, reject) => {
3066
- const child = spawn3(command, args, {
2983
+ const child = spawn2(command, args, {
3067
2984
  stdio: ["ignore", "pipe", "pipe"]
3068
2985
  });
3069
2986
  let stdout = "";
@@ -3567,16 +3484,42 @@ async function confirmDeletion(sourceID) {
3567
3484
  });
3568
3485
  }
3569
3486
 
3570
- // src/commands/internal-install-mutagen.ts
3487
+ // src/commands/internal-install-autossh.ts
3571
3488
  import { Command as Command9 } from "commander";
3572
3489
  import chalk7 from "chalk";
3573
- var internalInstallMutagenCommand = new Command9("internal-install-mutagen").option("--quiet", "Suppress output unless installation fails").action(async (options) => {
3490
+ var internalInstallAutosshCommand = new Command9(
3491
+ "internal-install-autossh"
3492
+ ).option("--quiet", "Suppress output unless installation fails").action(async (options) => {
3493
+ try {
3494
+ const executablePath = await ensureBundledAutosshInstalled();
3495
+ if (!options.quiet) {
3496
+ console.log();
3497
+ console.log(
3498
+ chalk7.green(` Bundled autossh ready at ${executablePath}.`)
3499
+ );
3500
+ console.log();
3501
+ }
3502
+ } catch (error) {
3503
+ if (!options.quiet) {
3504
+ const message = error instanceof Error ? error.message : "failed to install bundled autossh";
3505
+ console.error();
3506
+ console.error(chalk7.red(` ${message}`));
3507
+ console.error();
3508
+ }
3509
+ process.exit(1);
3510
+ }
3511
+ });
3512
+
3513
+ // src/commands/internal-install-mutagen.ts
3514
+ import { Command as Command10 } from "commander";
3515
+ import chalk8 from "chalk";
3516
+ var internalInstallMutagenCommand = new Command10("internal-install-mutagen").option("--quiet", "Suppress output unless installation fails").action(async (options) => {
3574
3517
  try {
3575
3518
  const executablePath = await ensureBundledMutagenInstalled();
3576
3519
  if (!options.quiet) {
3577
3520
  console.log();
3578
3521
  console.log(
3579
- chalk7.green(
3522
+ chalk8.green(
3580
3523
  ` Bundled Mutagen ready at ${executablePath}.`
3581
3524
  )
3582
3525
  );
@@ -3586,7 +3529,7 @@ var internalInstallMutagenCommand = new Command9("internal-install-mutagen").opt
3586
3529
  if (!options.quiet) {
3587
3530
  const message = error instanceof Error ? error.message : "failed to install bundled Mutagen";
3588
3531
  console.error();
3589
- console.error(chalk7.red(` ${message}`));
3532
+ console.error(chalk8.red(` ${message}`));
3590
3533
  console.error();
3591
3534
  }
3592
3535
  process.exit(1);
@@ -3594,8 +3537,8 @@ var internalInstallMutagenCommand = new Command9("internal-install-mutagen").opt
3594
3537
  });
3595
3538
 
3596
3539
  // src/commands/login.ts
3597
- import { Command as Command10 } from "commander";
3598
- import chalk8 from "chalk";
3540
+ import { Command as Command11 } from "commander";
3541
+ import chalk9 from "chalk";
3599
3542
  import ora8 from "ora";
3600
3543
 
3601
3544
  // src/lib/browser-login.ts
@@ -3863,12 +3806,12 @@ function escapeHTML(value) {
3863
3806
  }
3864
3807
 
3865
3808
  // src/commands/login.ts
3866
- var loginCommand = new Command10("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) => {
3809
+ var loginCommand = new Command11("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) => {
3867
3810
  const existingKey = getStoredAPIKey();
3868
3811
  if (existingKey && !options.force) {
3869
3812
  console.log();
3870
3813
  console.log(
3871
- chalk8.yellow(" Already logged in. Use --force to overwrite.")
3814
+ chalk9.yellow(" Already logged in. Use --force to overwrite.")
3872
3815
  );
3873
3816
  console.log();
3874
3817
  return;
@@ -3877,8 +3820,8 @@ var loginCommand = new Command10("login").description("Authenticate the CLI").op
3877
3820
  const apiKey = await resolveAPIKeyInput(options.apiKey, options.stdin);
3878
3821
  if (!apiKey && wantsManualLogin) {
3879
3822
  console.log();
3880
- console.log(chalk8.dim(" Usage: computer login --api-key <ac_live_...>"));
3881
- console.log(chalk8.dim(` API: ${getBaseURL()}`));
3823
+ console.log(chalk9.dim(" Usage: computer login --api-key <ac_live_...>"));
3824
+ console.log(chalk9.dim(` API: ${getBaseURL()}`));
3882
3825
  console.log();
3883
3826
  process.exit(1);
3884
3827
  }
@@ -3888,7 +3831,7 @@ var loginCommand = new Command10("login").description("Authenticate the CLI").op
3888
3831
  }
3889
3832
  if (!apiKey.startsWith("ac_live_")) {
3890
3833
  console.log();
3891
- console.log(chalk8.red(" API key must start with ac_live_"));
3834
+ console.log(chalk9.red(" API key must start with ac_live_"));
3892
3835
  console.log();
3893
3836
  process.exit(1);
3894
3837
  }
@@ -3896,7 +3839,7 @@ var loginCommand = new Command10("login").description("Authenticate the CLI").op
3896
3839
  try {
3897
3840
  const me = await apiWithKey(apiKey, "/v1/me");
3898
3841
  setAPIKey(apiKey);
3899
- spinner.succeed(`Logged in as ${chalk8.bold(me.user.email)}`);
3842
+ spinner.succeed(`Logged in as ${chalk9.bold(me.user.email)}`);
3900
3843
  } catch (error) {
3901
3844
  spinner.fail(
3902
3845
  error instanceof Error ? error.message : "Failed to validate API key"
@@ -3916,15 +3859,15 @@ async function runBrowserLogin() {
3916
3859
  spinner.stop();
3917
3860
  console.log();
3918
3861
  console.log(
3919
- chalk8.yellow(" Browser auto-open failed. Open this URL to continue:")
3862
+ chalk9.yellow(" Browser auto-open failed. Open this URL to continue:")
3920
3863
  );
3921
- console.log(chalk8.dim(` ${attempt.loginURL}`));
3864
+ console.log(chalk9.dim(` ${attempt.loginURL}`));
3922
3865
  console.log();
3923
3866
  spinner.start("Waiting for browser login...");
3924
3867
  }
3925
3868
  spinner.text = "Waiting for browser login...";
3926
3869
  const result = await attempt.waitForResult();
3927
- spinner.succeed(`Logged in as ${chalk8.bold(result.me.user.email)}`);
3870
+ spinner.succeed(`Logged in as ${chalk9.bold(result.me.user.email)}`);
3928
3871
  await continueFirstLoginFlow(result);
3929
3872
  } catch (error) {
3930
3873
  spinner.fail(
@@ -3958,8 +3901,8 @@ async function continueFirstLoginFlow(result) {
3958
3901
  }
3959
3902
  console.log();
3960
3903
  console.log(
3961
- chalk8.cyan(
3962
- `Continuing first-time setup for ${chalk8.bold(machineHandle)}...
3904
+ chalk9.cyan(
3905
+ `Continuing first-time setup for ${chalk9.bold(machineHandle)}...
3963
3906
  `
3964
3907
  )
3965
3908
  );
@@ -3972,8 +3915,8 @@ async function continueFirstLoginFlow(result) {
3972
3915
  const spinner = ora8(`Preparing SSH access for ${machineHandle}...`).start();
3973
3916
  try {
3974
3917
  const connection = await prepareSSHConnectionByIdentifier(machineHandle);
3975
- spinner.succeed(`Connecting to ${chalk8.bold(machineHandle)}`);
3976
- console.log(chalk8.dim(` ${connection.command}`));
3918
+ spinner.succeed(`Connecting to ${chalk9.bold(machineHandle)}`);
3919
+ console.log(chalk9.dim(` ${connection.command}`));
3977
3920
  console.log();
3978
3921
  await openSSHConnection(connection);
3979
3922
  } catch (error) {
@@ -3984,19 +3927,19 @@ async function continueFirstLoginFlow(result) {
3984
3927
  }
3985
3928
  } catch (error) {
3986
3929
  const message = error instanceof Error ? error.message : "Failed to finish first-time setup";
3987
- console.error(chalk8.red(`
3930
+ console.error(chalk9.red(`
3988
3931
  ${message}`));
3989
3932
  console.log();
3990
3933
  if (result.provider === "claude") {
3991
3934
  console.log(
3992
- chalk8.dim(` computer claude-login --machine ${machineHandle}`)
3935
+ chalk9.dim(` computer claude-login --machine ${machineHandle}`)
3993
3936
  );
3994
3937
  } else if (result.provider === "codex") {
3995
3938
  console.log(
3996
- chalk8.dim(` computer codex-login --machine ${machineHandle}`)
3939
+ chalk9.dim(` computer codex-login --machine ${machineHandle}`)
3997
3940
  );
3998
3941
  }
3999
- console.log(chalk8.dim(` computer ssh ${machineHandle}`));
3942
+ console.log(chalk9.dim(` computer ssh ${machineHandle}`));
4000
3943
  console.log();
4001
3944
  process.exit(1);
4002
3945
  }
@@ -4010,23 +3953,23 @@ async function runSelectedProvider(provider, machineHandle) {
4010
3953
  await runCodexLogin({ machine: machineHandle });
4011
3954
  return;
4012
3955
  }
4013
- console.log(chalk8.green(`Sandbox ${chalk8.bold(machineHandle)} is ready.`));
3956
+ console.log(chalk9.green(`Sandbox ${chalk9.bold(machineHandle)} is ready.`));
4014
3957
  console.log();
4015
3958
  }
4016
3959
  function printNextStep(machineHandle) {
4017
- console.log(chalk8.green(`Sandbox ${chalk8.bold(machineHandle)} is ready.`));
4018
- console.log(chalk8.dim(` computer ssh ${machineHandle}`));
3960
+ console.log(chalk9.green(`Sandbox ${chalk9.bold(machineHandle)} is ready.`));
3961
+ console.log(chalk9.dim(` computer ssh ${machineHandle}`));
4019
3962
  console.log();
4020
3963
  }
4021
3964
 
4022
3965
  // src/commands/mount.ts
4023
- import { spawn as spawn4 } from "child_process";
4024
- import { Command as Command11, Option } from "commander";
4025
- import chalk9 from "chalk";
3966
+ import { spawn as spawn3 } from "child_process";
3967
+ import { Command as Command12, Option } from "commander";
3968
+ import chalk10 from "chalk";
4026
3969
  import ora9 from "ora";
4027
3970
 
4028
3971
  // src/lib/mount-daemon.ts
4029
- import { mkdir as mkdir3 } from "fs/promises";
3972
+ import { mkdir } from "fs/promises";
4030
3973
  function getMountControllerState(rootPath = defaultMountServiceConfig().rootPath) {
4031
3974
  const lock = readMountControllerLock(rootPath);
4032
3975
  if (!lock) {
@@ -4042,7 +3985,7 @@ async function runMountDaemon(config, options = {}) {
4042
3985
  const { onReady, onStarted } = options;
4043
3986
  const paths = getMountPaths(config.rootPath);
4044
3987
  ensureMountDirectories(paths);
4045
- await mkdir3(paths.rootPath, { recursive: true });
3988
+ await mkdir(paths.rootPath, { recursive: true });
4046
3989
  await acquireControllerLock(config.rootPath);
4047
3990
  writeMountStatusSnapshot(
4048
3991
  {
@@ -4072,10 +4015,11 @@ async function runMountDaemon(config, options = {}) {
4072
4015
  config.rootPath
4073
4016
  );
4074
4017
  await teardownManagedSessions(config, paths);
4075
- await mkdir3(paths.rootPath, { recursive: true });
4018
+ await mkdir(paths.rootPath, { recursive: true });
4076
4019
  let running = false;
4077
4020
  let queued = false;
4078
4021
  let shuttingDown = false;
4022
+ let readyNotified = false;
4079
4023
  let activeRun = null;
4080
4024
  let activeRunController = null;
4081
4025
  const runOnce = async () => {
@@ -4097,6 +4041,11 @@ async function runMountDaemon(config, options = {}) {
4097
4041
  process.pid,
4098
4042
  controller.signal
4099
4043
  );
4044
+ const snapshot = readMountStatusSnapshot(config.rootPath);
4045
+ if (!readyNotified && snapshot?.startupPhase === "ready") {
4046
+ readyNotified = true;
4047
+ onReady?.();
4048
+ }
4100
4049
  } catch (error) {
4101
4050
  if (shuttingDown && isAbortError(error)) {
4102
4051
  return;
@@ -4198,8 +4147,6 @@ async function runMountDaemon(config, options = {}) {
4198
4147
  void shutdown();
4199
4148
  });
4200
4149
  await runOnce();
4201
- writeMountStatusSnapshot(markMountStartupReady(readMountStatusSnapshot(config.rootPath), process.pid), config.rootPath);
4202
- onReady?.();
4203
4150
  await new Promise(() => {
4204
4151
  });
4205
4152
  }
@@ -4228,28 +4175,13 @@ function processExists(pid) {
4228
4175
  return false;
4229
4176
  }
4230
4177
  }
4231
- function markMountStartupReady(snapshot, controllerPid) {
4232
- return {
4233
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4234
- controllerPid,
4235
- running: true,
4236
- startupPhase: "ready",
4237
- startupMessage: void 0,
4238
- lastHealthySyncAt: snapshot?.lastHealthySyncAt,
4239
- lastSuccessfulSyncAt: snapshot?.lastSuccessfulSyncAt,
4240
- lastIssueAt: snapshot?.lastIssueAt,
4241
- lastIssue: snapshot?.lastIssue,
4242
- lastError: snapshot?.lastError,
4243
- mounts: snapshot?.mounts ?? []
4244
- };
4245
- }
4246
4178
 
4247
4179
  // src/commands/mount.ts
4248
4180
  var MOUNT_START_SPINNER = {
4249
4181
  interval: 90,
4250
4182
  frames: ["\u25F0", "\u25F3", "\u25F2", "\u25F1"]
4251
4183
  };
4252
- var mountCommand = new Command11("mount").description("Mirror SSH-ready machines under ~/agentcomputer with a local mount controller").option("--alias <alias>", "SSH host alias", "agentcomputer.ai").option("--host <host>", "SSH gateway host", "ssh.agentcomputer.ai").option("--port <port>", "SSH gateway port", "443").option("--poll-interval <ms>", "Reconcile interval in milliseconds", "5000").option("--connect-timeout <seconds>", "SSH connect timeout for Mutagen", "5").option(
4184
+ var mountCommand = new Command12("mount").description("Mirror SSH-ready machines under ~/agentcomputer with a local mount controller").option("--alias <alias>", "SSH host alias", "agentcomputer.ai").option("--host <host>", "SSH gateway host", "ssh.agentcomputer.ai").option("--port <port>", "SSH gateway port", "443").option("--poll-interval <ms>", "Reconcile interval in milliseconds", "5000").option("--connect-timeout <seconds>", "SSH connect timeout for Mutagen", "5").option(
4253
4185
  "--background",
4254
4186
  "Run the mount controller in the background and print its PID"
4255
4187
  ).addOption(new Option("--daemonized").hideHelp()).addOption(new Option("--open-root-when-ready").hideHelp()).action(async (options) => {
@@ -4283,14 +4215,22 @@ var mountCommand = new Command11("mount").description("Mirror SSH-ready machines
4283
4215
  return;
4284
4216
  }
4285
4217
  const child = await startMountControllerInForeground(resolved.config.rootPath);
4286
- await waitForMountControllerReady(resolved.config.rootPath, child.pid, spinner);
4218
+ const controls = attachForegroundControls(child);
4287
4219
  spinner.succeed("Machine mount controller running");
4288
4220
  printMountStartSummary(
4289
4221
  resolved.config,
4290
4222
  child.pid ?? process.pid,
4291
4223
  "foreground"
4292
4224
  );
4293
- await superviseForegroundMountController(child);
4225
+ try {
4226
+ await superviseForegroundMountController(
4227
+ resolved.config.rootPath,
4228
+ child,
4229
+ controls
4230
+ );
4231
+ } finally {
4232
+ controls.close();
4233
+ }
4294
4234
  } catch (error) {
4295
4235
  spinner.fail(
4296
4236
  error instanceof Error ? error.message : "Failed to start machine mount controller"
@@ -4298,42 +4238,9 @@ var mountCommand = new Command11("mount").description("Mirror SSH-ready machines
4298
4238
  process.exit(1);
4299
4239
  }
4300
4240
  }).addCommand(
4301
- new Command11("status").description("Show machine mount controller status").action(() => {
4241
+ new Command12("status").description("Show machine mount controller status").action(() => {
4302
4242
  const config = readMountConfig() ?? defaultMountServiceConfig();
4303
- const controller = getMountControllerState(config.rootPath);
4304
- const snapshot = readMountStatusSnapshot(config.rootPath);
4305
- console.log();
4306
- console.log(` ${chalk9.bold("Machine Mounts")}`);
4307
- console.log();
4308
- console.log(
4309
- ` ${chalk9.dim("Running")} ${controller.running ? chalk9.green("yes") : chalk9.dim("no")}`
4310
- );
4311
- if (controller.pid) {
4312
- console.log(` ${chalk9.dim("PID")} ${controller.pid}`);
4313
- }
4314
- console.log(` ${chalk9.dim("Root")} ${config.rootPath}`);
4315
- console.log(` ${chalk9.dim("Alias")} ${config.alias}`);
4316
- console.log(
4317
- ` ${chalk9.dim("Updated")} ${snapshot?.updatedAt ? timeAgo(snapshot.updatedAt) : chalk9.dim("never")}`
4318
- );
4319
- console.log(
4320
- ` ${chalk9.dim("Healthy")} ${snapshot?.lastHealthySyncAt ? timeAgo(snapshot.lastHealthySyncAt) : chalk9.dim("never")}`
4321
- );
4322
- if (controller.running && snapshot?.mounts.length) {
4323
- console.log();
4324
- for (const mount of snapshot.mounts) {
4325
- const state = formatMountState(mount.state);
4326
- console.log(` ${chalk9.white(mount.handle)} ${state} ${chalk9.dim(mount.mountPath)}`);
4327
- if (mount.message) {
4328
- console.log(` ${chalk9.dim(mount.message)}`);
4329
- }
4330
- }
4331
- }
4332
- if (snapshot?.lastIssue) {
4333
- console.log();
4334
- console.log(` ${chalk9.dim("Last issue")} ${chalk9.yellow(snapshot.lastIssue)}`);
4335
- }
4336
- console.log();
4243
+ renderMountStatus(config);
4337
4244
  })
4338
4245
  );
4339
4246
  function parsePositiveInt(raw, label) {
@@ -4377,7 +4284,7 @@ async function startMountControllerInBackground(rootPath) {
4377
4284
  return child.pid;
4378
4285
  }
4379
4286
  async function startMountControllerInForeground(rootPath) {
4380
- const child = startMountControllerProcess(rootPath, false, true);
4287
+ const child = startMountControllerProcess(rootPath, true, true);
4381
4288
  await waitForMountControllerRunning(rootPath, child.pid);
4382
4289
  return child;
4383
4290
  }
@@ -4394,7 +4301,7 @@ function startMountControllerProcess(rootPath, detached, openRootWhenReady) {
4394
4301
  if (openRootWhenReady) {
4395
4302
  args.push("--open-root-when-ready");
4396
4303
  }
4397
- const child = spawn4(process.execPath, [entrypoint, ...args], {
4304
+ const child = spawn3(process.execPath, [entrypoint, ...args], {
4398
4305
  cwd: process.cwd(),
4399
4306
  detached,
4400
4307
  env: process.env,
@@ -4419,45 +4326,30 @@ async function waitForMountControllerRunning(rootPath, pid) {
4419
4326
  }
4420
4327
  throw new Error("failed to start machine mount controller in background");
4421
4328
  }
4422
- async function waitForMountControllerReady(rootPath, pid, spinner) {
4423
- const deadline = Date.now() + 12e4;
4424
- while (Date.now() < deadline) {
4425
- const controller = getMountControllerState(rootPath);
4426
- const snapshot = readMountStatusSnapshot(rootPath);
4427
- if (snapshot?.controllerPid === pid && snapshot.startupMessage) {
4428
- spinner.text = snapshot.startupMessage;
4429
- }
4430
- if (snapshot?.controllerPid === pid && snapshot.startupPhase === "ready") {
4431
- return;
4432
- }
4433
- if (!controller.running && !processExists2(pid)) {
4434
- break;
4435
- }
4436
- await new Promise((resolve) => setTimeout(resolve, 100));
4437
- }
4438
- throw new Error("mount controller did not finish startup");
4439
- }
4440
- async function superviseForegroundMountController(child) {
4329
+ async function superviseForegroundMountController(rootPath, child, controls) {
4330
+ let lifecycleState = createMountLifecycleState();
4331
+ lifecycleState = updateMountLifecycleDisplay(
4332
+ lifecycleState,
4333
+ readMountStatusSnapshot(rootPath)
4334
+ );
4441
4335
  await new Promise((resolve, reject) => {
4442
4336
  const cleanup = () => {
4443
- process.off("SIGINT", onSigint);
4444
- process.off("SIGTERM", onSigterm);
4445
4337
  child.off("error", onError);
4446
4338
  child.off("exit", onExit);
4447
- };
4448
- const stopAndExit = (signal) => {
4449
- cleanup();
4450
- try {
4451
- child.kill(signal);
4452
- } catch {
4339
+ clearInterval(interval);
4340
+ if (lifecycleState.spinner) {
4341
+ lifecycleState.spinner.stop();
4342
+ lifecycleState.spinner = null;
4453
4343
  }
4454
- process.exit(0);
4455
- };
4456
- const onSigint = () => {
4457
- stopAndExit("SIGINT");
4458
4344
  };
4459
- const onSigterm = () => {
4460
- stopAndExit("SIGTERM");
4345
+ const backgroundAndExit = () => {
4346
+ cleanup();
4347
+ child.unref();
4348
+ console.log();
4349
+ console.log(chalk10.dim(` Backgrounded mount controller (pid ${child.pid ?? "unknown"}).`));
4350
+ console.log(chalk10.dim(" Use `computer mount status` to inspect sync state."));
4351
+ console.log();
4352
+ resolve();
4461
4353
  };
4462
4354
  const onError = (error) => {
4463
4355
  cleanup();
@@ -4475,26 +4367,367 @@ async function superviseForegroundMountController(child) {
4475
4367
  )
4476
4368
  );
4477
4369
  };
4478
- process.once("SIGINT", onSigint);
4479
- process.once("SIGTERM", onSigterm);
4370
+ const interval = setInterval(() => {
4371
+ const action = controls.consumeAction();
4372
+ if (action === "background") {
4373
+ backgroundAndExit();
4374
+ return;
4375
+ }
4376
+ if (action === "stop") {
4377
+ cleanup();
4378
+ resolve();
4379
+ return;
4380
+ }
4381
+ lifecycleState = updateMountLifecycleDisplay(
4382
+ lifecycleState,
4383
+ readMountStatusSnapshot(rootPath)
4384
+ );
4385
+ }, 1e3);
4480
4386
  child.once("error", onError);
4481
4387
  child.once("exit", onExit);
4482
4388
  });
4483
4389
  }
4484
4390
  function printMountStartSummary(config, pid, mode) {
4485
4391
  console.log();
4486
- console.log(chalk9.dim(` PID: ${pid}`));
4487
- console.log(chalk9.dim(` Root: ${config.rootPath}`));
4488
- console.log(chalk9.dim(` SSH alias: ${config.alias}`));
4489
- console.log(chalk9.dim(` Poll: ${config.pollIntervalMs}ms`));
4392
+ console.log(chalk10.dim(` PID: ${pid}`));
4393
+ console.log(chalk10.dim(` Root: ${config.rootPath}`));
4394
+ console.log(chalk10.dim(` SSH alias: ${config.alias}`));
4395
+ console.log(chalk10.dim(` Poll: ${config.pollIntervalMs}ms`));
4490
4396
  console.log();
4491
4397
  console.log(
4492
- chalk9.dim(
4493
- mode === "background" ? " Use `computer mount status` to inspect sync state." : " Press Ctrl-C to stop syncing."
4398
+ chalk10.dim(
4399
+ mode === "background" ? " Use `computer mount status` to inspect sync state." : " Press Ctrl-C to stop syncing or Ctrl-B to move it to the background."
4494
4400
  )
4495
4401
  );
4496
4402
  console.log();
4497
4403
  }
4404
+ function renderMountStatus(config) {
4405
+ const controller = getMountControllerState(config.rootPath);
4406
+ const snapshot = readMountStatusSnapshot(config.rootPath);
4407
+ console.log();
4408
+ console.log(` ${chalk10.bold("Machine Mounts")}`);
4409
+ console.log();
4410
+ console.log(
4411
+ ` ${chalk10.dim("Running")} ${controller.running ? chalk10.green("yes") : chalk10.dim("no")}`
4412
+ );
4413
+ if (controller.pid) {
4414
+ console.log(` ${chalk10.dim("PID")} ${controller.pid}`);
4415
+ }
4416
+ console.log(` ${chalk10.dim("Root")} ${config.rootPath}`);
4417
+ console.log(` ${chalk10.dim("Alias")} ${config.alias}`);
4418
+ console.log(
4419
+ ` ${chalk10.dim("Updated")} ${snapshot?.updatedAt ? timeAgo(snapshot.updatedAt) : chalk10.dim("never")}`
4420
+ );
4421
+ console.log(
4422
+ ` ${chalk10.dim("Healthy")} ${snapshot?.lastHealthySyncAt ? timeAgo(snapshot.lastHealthySyncAt) : chalk10.dim("never")}`
4423
+ );
4424
+ if (snapshot?.startupPhase && snapshot.startupPhase !== "ready") {
4425
+ console.log(` ${chalk10.dim("Startup")} ${chalk10.cyan(snapshot.startupPhase)}`);
4426
+ if (snapshot.startupMessage) {
4427
+ console.log(` ${chalk10.dim(snapshot.startupMessage)}`);
4428
+ }
4429
+ }
4430
+ if (controller.running && snapshot?.mounts.length) {
4431
+ console.log();
4432
+ for (const mount of snapshot.mounts) {
4433
+ renderMountSnapshot(mount);
4434
+ }
4435
+ }
4436
+ if (snapshot?.lastIssue) {
4437
+ console.log();
4438
+ console.log(` ${chalk10.dim("Last issue")} ${chalk10.yellow(snapshot.lastIssue)}`);
4439
+ }
4440
+ console.log();
4441
+ }
4442
+ function renderMountSnapshot(mount) {
4443
+ const state = formatMountState(mount.state);
4444
+ console.log(` ${chalk10.white(mount.handle)} ${state} ${chalk10.dim(mount.mountPath)}`);
4445
+ const details = formatMountDetails(mount);
4446
+ if (details) {
4447
+ console.log(` ${chalk10.dim(details)}`);
4448
+ }
4449
+ }
4450
+ function formatMountDetails(mount) {
4451
+ const details = [];
4452
+ if (mount.message) {
4453
+ details.push(mount.message);
4454
+ }
4455
+ if (mount.etaSeconds !== void 0 && !details.some((detail) => detail.includes("eta ~"))) {
4456
+ details.push(`eta ~${formatEtaSeconds(mount.etaSeconds)}`);
4457
+ }
4458
+ return details.length > 0 ? details.join("; ") : void 0;
4459
+ }
4460
+ function formatEtaSeconds(seconds) {
4461
+ if (seconds < 60) {
4462
+ return `${seconds}s`;
4463
+ }
4464
+ const minutes = Math.floor(seconds / 60);
4465
+ const remainder = seconds % 60;
4466
+ return remainder === 0 ? `${minutes}m` : `${minutes}m ${remainder}s`;
4467
+ }
4468
+ function attachForegroundControls(child) {
4469
+ let pendingAction = null;
4470
+ const canUseRawMode = process.stdin.isTTY && typeof process.stdin.setRawMode === "function";
4471
+ const requestStop = () => {
4472
+ if (!pendingAction) {
4473
+ pendingAction = "stop";
4474
+ }
4475
+ try {
4476
+ if (child.pid) {
4477
+ process.kill(child.pid, "SIGTERM");
4478
+ }
4479
+ } catch {
4480
+ }
4481
+ };
4482
+ const onData = (chunk) => {
4483
+ const text = chunk.toString();
4484
+ for (const character of text) {
4485
+ if (character === "") {
4486
+ if (!pendingAction) {
4487
+ pendingAction = "background";
4488
+ }
4489
+ } else if (character === "") {
4490
+ requestStop();
4491
+ }
4492
+ }
4493
+ };
4494
+ const onSigint = () => {
4495
+ requestStop();
4496
+ };
4497
+ const onSigterm = () => {
4498
+ requestStop();
4499
+ };
4500
+ if (canUseRawMode) {
4501
+ process.stdin.setRawMode(true);
4502
+ process.stdin.resume();
4503
+ process.stdin.on("data", onData);
4504
+ }
4505
+ process.on("SIGINT", onSigint);
4506
+ process.on("SIGTERM", onSigterm);
4507
+ return {
4508
+ consumeAction() {
4509
+ const action = pendingAction;
4510
+ pendingAction = null;
4511
+ return action;
4512
+ },
4513
+ close() {
4514
+ if (canUseRawMode) {
4515
+ process.stdin.off("data", onData);
4516
+ process.stdin.setRawMode(false);
4517
+ process.stdin.pause();
4518
+ }
4519
+ process.off("SIGINT", onSigint);
4520
+ process.off("SIGTERM", onSigterm);
4521
+ }
4522
+ };
4523
+ }
4524
+ function createMountLifecycleState() {
4525
+ return {
4526
+ view: void 0,
4527
+ spinner: null
4528
+ };
4529
+ }
4530
+ function updateMountLifecycleDisplay(state, snapshot) {
4531
+ const nextView = describeMountLifecycle(snapshot);
4532
+ const previousView = state.view;
4533
+ if (previousView?.key === nextView.key) {
4534
+ if (state.spinner) {
4535
+ state.spinner.text = nextView.text;
4536
+ }
4537
+ return {
4538
+ ...state,
4539
+ view: nextView
4540
+ };
4541
+ }
4542
+ if (state.spinner) {
4543
+ if (nextView.status === "success") {
4544
+ state.spinner.succeed(nextView.text);
4545
+ return {
4546
+ view: nextView,
4547
+ spinner: null
4548
+ };
4549
+ }
4550
+ if (nextView.status === "warning") {
4551
+ state.spinner.warn(nextView.text);
4552
+ return {
4553
+ view: nextView,
4554
+ spinner: null
4555
+ };
4556
+ }
4557
+ state.spinner.succeed(previousView?.completionText ?? previousView?.text ?? "");
4558
+ }
4559
+ if (nextView.status === "loading") {
4560
+ return {
4561
+ view: nextView,
4562
+ spinner: ora9({
4563
+ spinner: MOUNT_START_SPINNER,
4564
+ text: nextView.text
4565
+ }).start()
4566
+ };
4567
+ }
4568
+ console.log(chalk10.dim(` ${nextView.text}`));
4569
+ return {
4570
+ view: nextView,
4571
+ spinner: null
4572
+ };
4573
+ }
4574
+ function describeMountLifecycle(snapshot) {
4575
+ if (!snapshot) {
4576
+ return {
4577
+ key: "waiting",
4578
+ text: "Waiting for mount controller state",
4579
+ completionText: "Mount controller state received",
4580
+ status: "loading"
4581
+ };
4582
+ }
4583
+ const actionableMounts = (snapshot?.mounts ?? []).filter((mount) => mount.state !== "pending");
4584
+ const readyCount = actionableMounts.filter((mount) => mount.state === "mounted").length;
4585
+ const totalCount = actionableMounts.length;
4586
+ if (snapshot?.lastIssue && actionableMounts.some((mount) => mount.state === "error" || mount.state === "degraded")) {
4587
+ return {
4588
+ key: "issue",
4589
+ text: `Needs attention: ${snapshot.lastIssue}`,
4590
+ status: "warning"
4591
+ };
4592
+ }
4593
+ if (snapshot?.startupPhase === "cleaning") {
4594
+ return {
4595
+ key: "cleaning",
4596
+ text: "Cleaning stale mount state",
4597
+ completionText: "Cleaned stale mount state",
4598
+ status: "loading"
4599
+ };
4600
+ }
4601
+ if (actionableMounts.some((mount) => mount.state === "reconnecting")) {
4602
+ return {
4603
+ key: "waiting",
4604
+ text: formatLifecycleText(
4605
+ "Waiting for remote availability",
4606
+ readyCount,
4607
+ totalCount
4608
+ ),
4609
+ completionText: "Remote availability restored",
4610
+ status: "loading"
4611
+ };
4612
+ }
4613
+ const phaseMatch = findLifecyclePhaseMatch(actionableMounts);
4614
+ if (phaseMatch) {
4615
+ return phaseMatch;
4616
+ }
4617
+ if (snapshot?.startupPhase !== "ready") {
4618
+ return {
4619
+ key: "waiting",
4620
+ text: formatLifecycleText("Waiting for initial sync", readyCount, totalCount),
4621
+ completionText: "Initial sync started",
4622
+ status: "loading"
4623
+ };
4624
+ }
4625
+ if (totalCount === 0) {
4626
+ return {
4627
+ key: "connected",
4628
+ text: "Connected and waiting for SSH-ready machines",
4629
+ status: "success"
4630
+ };
4631
+ }
4632
+ return {
4633
+ key: "connected",
4634
+ text: "Connected and serving files bidirectionally",
4635
+ status: "success"
4636
+ };
4637
+ }
4638
+ function findLifecyclePhaseMatch(mounts) {
4639
+ const phaseDefinitions = [
4640
+ {
4641
+ key: "scanning",
4642
+ label: "Scanning files",
4643
+ completionText: "Finished scanning files",
4644
+ match: (mount) => (mount.status ?? "").toLowerCase().includes("scanning")
4645
+ },
4646
+ {
4647
+ key: "staging",
4648
+ label: "Staging files",
4649
+ completionText: "Finished staging files",
4650
+ match: (mount) => (mount.status ?? "").toLowerCase().includes("staging")
4651
+ },
4652
+ {
4653
+ key: "reconciling",
4654
+ label: "Reconciling changes",
4655
+ completionText: "Reconciled changes",
4656
+ match: (mount) => (mount.status ?? "").toLowerCase().includes("reconciling")
4657
+ },
4658
+ {
4659
+ key: "applying",
4660
+ label: "Applying changes",
4661
+ completionText: "Applied changes",
4662
+ match: (mount) => (mount.status ?? "").toLowerCase().includes("applying")
4663
+ },
4664
+ {
4665
+ key: "saving",
4666
+ label: "Finalizing sync",
4667
+ completionText: "Finalized sync state",
4668
+ match: (mount) => (mount.status ?? "").toLowerCase().includes("saving")
4669
+ }
4670
+ ];
4671
+ const readyCount = mounts.filter((mount) => mount.state === "mounted").length;
4672
+ const totalCount = mounts.length;
4673
+ for (const phase of phaseDefinitions) {
4674
+ const matches = mounts.filter(
4675
+ (mount) => mount.state === "syncing" && phase.match(mount)
4676
+ );
4677
+ if (matches.length === 0) {
4678
+ continue;
4679
+ }
4680
+ const representative = selectRepresentativeMount(matches);
4681
+ const progress = formatLifecycleProgress(representative);
4682
+ return {
4683
+ key: phase.key,
4684
+ text: formatLifecycleText(phase.label, readyCount, totalCount, progress),
4685
+ completionText: phase.completionText,
4686
+ status: "loading"
4687
+ };
4688
+ }
4689
+ return null;
4690
+ }
4691
+ function selectRepresentativeMount(mounts) {
4692
+ let best = mounts[0];
4693
+ for (const candidate of mounts.slice(1)) {
4694
+ const candidateProgress = getMountProgressPercent(candidate) ?? -1;
4695
+ const bestProgress = getMountProgressPercent(best) ?? -1;
4696
+ if (candidateProgress > bestProgress) {
4697
+ best = candidate;
4698
+ }
4699
+ }
4700
+ return best;
4701
+ }
4702
+ function formatLifecycleText(label, readyCount, totalCount, progress) {
4703
+ const details = [];
4704
+ if (totalCount > 0) {
4705
+ details.push(`${readyCount}/${totalCount} ready`);
4706
+ }
4707
+ if (progress) {
4708
+ details.push(progress);
4709
+ }
4710
+ return details.length > 0 ? `${label} (${details.join(", ")})` : label;
4711
+ }
4712
+ function formatLifecycleProgress(mount) {
4713
+ const parts = [];
4714
+ const percent = getMountProgressPercent(mount);
4715
+ if (percent !== void 0) {
4716
+ parts.push(`${percent}%`);
4717
+ }
4718
+ if (mount.etaSeconds !== void 0) {
4719
+ parts.push(`eta ~${formatEtaSeconds(mount.etaSeconds)}`);
4720
+ }
4721
+ return parts.length > 0 ? parts.join(", ") : void 0;
4722
+ }
4723
+ function getMountProgressPercent(mount) {
4724
+ const match = mount.progress?.match(/(\d+)%/);
4725
+ if (!match) {
4726
+ return void 0;
4727
+ }
4728
+ const percent = Number.parseInt(match[1] ?? "", 10);
4729
+ return Number.isFinite(percent) ? percent : void 0;
4730
+ }
4498
4731
  function processExists2(pid) {
4499
4732
  if (!Number.isInteger(pid) || pid <= 0) {
4500
4733
  return false;
@@ -4510,7 +4743,7 @@ function revealMountRootInFinder(rootPath) {
4510
4743
  if (process.platform !== "darwin") {
4511
4744
  return;
4512
4745
  }
4513
- const child = spawn4("open", [rootPath], {
4746
+ const child = spawn3("open", [rootPath], {
4514
4747
  stdio: "ignore",
4515
4748
  detached: true
4516
4749
  });
@@ -4521,35 +4754,36 @@ function revealMountRootInFinder(rootPath) {
4521
4754
  function formatMountState(state) {
4522
4755
  switch (state) {
4523
4756
  case "mounted":
4524
- return chalk9.green(state);
4757
+ return chalk10.green(state);
4758
+ case "syncing":
4525
4759
  case "reconnecting":
4526
- return chalk9.cyan(state);
4760
+ return chalk10.cyan(state);
4527
4761
  case "degraded":
4528
4762
  case "pending":
4529
- return chalk9.yellow(state);
4763
+ return chalk10.yellow(state);
4530
4764
  default:
4531
- return chalk9.red(state);
4765
+ return chalk10.red(state);
4532
4766
  }
4533
4767
  }
4534
4768
 
4535
4769
  // src/commands/logout.ts
4536
- import { Command as Command12 } from "commander";
4537
- import chalk10 from "chalk";
4538
- var logoutCommand = new Command12("logout").description("Remove stored API key").action(() => {
4770
+ import { Command as Command13 } from "commander";
4771
+ import chalk11 from "chalk";
4772
+ var logoutCommand = new Command13("logout").description("Remove stored API key").action(() => {
4539
4773
  if (!getStoredAPIKey()) {
4540
4774
  console.log();
4541
- console.log(chalk10.dim(" Not logged in."));
4775
+ console.log(chalk11.dim(" Not logged in."));
4542
4776
  if (hasEnvAPIKey()) {
4543
- console.log(chalk10.dim(" Environment API key is still active in this shell."));
4777
+ console.log(chalk11.dim(" Environment API key is still active in this shell."));
4544
4778
  }
4545
4779
  console.log();
4546
4780
  return;
4547
4781
  }
4548
4782
  clearAPIKey();
4549
4783
  console.log();
4550
- console.log(chalk10.green(" Logged out."));
4784
+ console.log(chalk11.green(" Logged out."));
4551
4785
  if (hasEnvAPIKey()) {
4552
- console.log(chalk10.dim(" Environment API key is still active in this shell."));
4786
+ console.log(chalk11.dim(" Environment API key is still active in this shell."));
4553
4787
  }
4554
4788
  console.log();
4555
4789
  });
@@ -4557,27 +4791,12 @@ var logoutCommand = new Command12("logout").description("Remove stored API key")
4557
4791
  // src/commands/upgrade.ts
4558
4792
  import { spawnSync } from "child_process";
4559
4793
  import { readFileSync as readFileSync2, realpathSync } from "fs";
4560
- import { Command as Command13 } from "commander";
4561
- import chalk11 from "chalk";
4794
+ import { Command as Command14 } from "commander";
4795
+ import chalk12 from "chalk";
4562
4796
  import ora10 from "ora";
4563
4797
  var pkg2 = JSON.parse(
4564
4798
  readFileSync2(new URL("../package.json", import.meta.url), "utf8")
4565
4799
  );
4566
- function normalizeVersion(version) {
4567
- return version.split("-")[0].split(".").map((part) => Number.parseInt(part, 10)).map((part) => Number.isNaN(part) ? 0 : part);
4568
- }
4569
- function compareVersions(a, b) {
4570
- const left = normalizeVersion(a);
4571
- const right = normalizeVersion(b);
4572
- const size = Math.max(left.length, right.length);
4573
- for (let index = 0; index < size; index += 1) {
4574
- const diff = (left[index] ?? 0) - (right[index] ?? 0);
4575
- if (diff !== 0) {
4576
- return diff;
4577
- }
4578
- }
4579
- return 0;
4580
- }
4581
4800
  function resolveExecutablePath() {
4582
4801
  const candidate = process.argv[1] || process.execPath;
4583
4802
  try {
@@ -4625,8 +4844,9 @@ function findNixProfileElement(executablePath) {
4625
4844
  }
4626
4845
  return null;
4627
4846
  }
4628
- function resolveUpgradeCommand(method, executablePath) {
4847
+ function resolveUpgradeCommand(method, executablePath, targetVersion) {
4629
4848
  const packageName = pkg2.name ?? "aicomputer";
4849
+ const packageSpec = `${packageName}@${targetVersion}`;
4630
4850
  switch (method) {
4631
4851
  case "nix": {
4632
4852
  const element = findNixProfileElement(executablePath);
@@ -4644,34 +4864,32 @@ function resolveUpgradeCommand(method, executablePath) {
4644
4864
  case "pnpm":
4645
4865
  return {
4646
4866
  command: "pnpm",
4647
- args: ["add", "-g", `${packageName}@latest`],
4648
- label: `pnpm add -g ${packageName}@latest`
4867
+ args: ["add", "-g", packageSpec],
4868
+ label: `pnpm add -g ${packageSpec}`
4649
4869
  };
4650
4870
  case "yarn":
4651
4871
  return {
4652
4872
  command: "yarn",
4653
- args: ["global", "add", `${packageName}@latest`],
4654
- label: `yarn global add ${packageName}@latest`
4873
+ args: ["global", "add", packageSpec],
4874
+ label: `yarn global add ${packageSpec}`
4655
4875
  };
4656
4876
  case "npm":
4657
4877
  case "unknown":
4658
4878
  return {
4659
4879
  command: "npm",
4660
- args: ["install", "-g", `${packageName}@latest`],
4661
- label: `npm install -g ${packageName}@latest`
4880
+ args: ["install", "-g", packageSpec],
4881
+ label: `npm install -g ${packageSpec}`
4662
4882
  };
4663
4883
  }
4664
4884
  }
4665
4885
  async function getLatestVersion(packageName) {
4666
- const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
4886
+ const response = await fetch(`https://registry.npmjs.org/${packageName}`);
4667
4887
  if (!response.ok) {
4668
4888
  throw new Error(`Failed to check npm registry (${response.status})`);
4669
4889
  }
4670
- const payload = await response.json();
4671
- if (!payload.version) {
4672
- throw new Error("Registry response missing version");
4673
- }
4674
- return payload.version;
4890
+ return resolveLatestPublishedVersion(
4891
+ await response.json()
4892
+ );
4675
4893
  }
4676
4894
  function attemptBundledMutagenRefresh(executablePath) {
4677
4895
  const install = spawnSync(
@@ -4686,12 +4904,12 @@ function attemptBundledMutagenRefresh(executablePath) {
4686
4904
  }
4687
4905
  console.log();
4688
4906
  console.log(
4689
- chalk11.yellow(
4907
+ chalk12.yellow(
4690
4908
  " CLI updated, but bundled Mutagen did not finish installing. `computer mount` will retry on first use."
4691
4909
  )
4692
4910
  );
4693
4911
  }
4694
- var upgradeCommand = new Command13("upgrade").description("Update the CLI to the latest version").action(async () => {
4912
+ var upgradeCommand = new Command14("upgrade").description("Update the CLI to the latest version").action(async () => {
4695
4913
  const currentVersion = pkg2.version ?? "0.0.0";
4696
4914
  const packageName = pkg2.name ?? "aicomputer";
4697
4915
  const spinner = ora10("Checking for updates...").start();
@@ -4713,7 +4931,7 @@ var upgradeCommand = new Command13("upgrade").description("Update the CLI to the
4713
4931
  const method = detectInstallMethod(executablePath);
4714
4932
  let upgrade;
4715
4933
  try {
4716
- upgrade = resolveUpgradeCommand(method, executablePath);
4934
+ upgrade = resolveUpgradeCommand(method, executablePath, latestVersion);
4717
4935
  } catch (error) {
4718
4936
  spinner.fail(
4719
4937
  error instanceof Error ? error.message : "Failed to prepare upgrade"
@@ -4724,9 +4942,9 @@ var upgradeCommand = new Command13("upgrade").description("Update the CLI to the
4724
4942
  spinner.stop();
4725
4943
  console.log();
4726
4944
  console.log(
4727
- chalk11.dim(` Updating ${chalk11.bold(`v${currentVersion}`)} -> ${chalk11.bold(`v${latestVersion}`)}`)
4945
+ chalk12.dim(` Updating ${chalk12.bold(`v${currentVersion}`)} -> ${chalk12.bold(`v${latestVersion}`)}`)
4728
4946
  );
4729
- console.log(chalk11.dim(` ${upgrade.label}`));
4947
+ console.log(chalk12.dim(` ${upgrade.label}`));
4730
4948
  console.log();
4731
4949
  const result = spawnSync(upgrade.command, upgrade.args, {
4732
4950
  stdio: "inherit"
@@ -4736,7 +4954,7 @@ var upgradeCommand = new Command13("upgrade").description("Update the CLI to the
4736
4954
  attemptBundledMutagenRefresh(executablePath);
4737
4955
  }
4738
4956
  console.log();
4739
- console.log(chalk11.green(` Updated to v${latestVersion}.`));
4957
+ console.log(chalk12.green(` Updated to v${latestVersion}.`));
4740
4958
  console.log();
4741
4959
  return;
4742
4960
  }
@@ -4744,10 +4962,10 @@ var upgradeCommand = new Command13("upgrade").description("Update the CLI to the
4744
4962
  });
4745
4963
 
4746
4964
  // src/commands/whoami.ts
4747
- import { Command as Command14 } from "commander";
4748
- import chalk12 from "chalk";
4965
+ import { Command as Command15 } from "commander";
4966
+ import chalk13 from "chalk";
4749
4967
  import ora11 from "ora";
4750
- var whoamiCommand = new Command14("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
4968
+ var whoamiCommand = new Command15("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
4751
4969
  const spinner = options.json ? null : ora11("Loading user...").start();
4752
4970
  try {
4753
4971
  const me = await api("/v1/me");
@@ -4757,39 +4975,66 @@ var whoamiCommand = new Command14("whoami").description("Show current user").opt
4757
4975
  return;
4758
4976
  }
4759
4977
  console.log();
4760
- console.log(` ${chalk12.bold.white(me.user.display_name || me.user.email)}`);
4978
+ console.log(
4979
+ ` ${chalk13.bold.white(me.user.display_name || me.user.email)}`
4980
+ );
4761
4981
  if (me.user.display_name) {
4762
- console.log(` ${chalk12.dim(me.user.email)}`);
4982
+ console.log(` ${chalk13.dim(me.user.email)}`);
4763
4983
  }
4764
4984
  if (me.api_key.name) {
4765
- console.log(` ${chalk12.dim("Key:")} ${me.api_key.name}`);
4985
+ console.log(` ${chalk13.dim("Key:")} ${me.api_key.name}`);
4986
+ }
4987
+ if (me.runtime_credit) {
4988
+ console.log(
4989
+ ` ${chalk13.dim("Credits:")} ${formatRuntimeHours(me.runtime_credit.available_credit_seconds)} remaining`
4990
+ );
4991
+ if (me.runtime_credit.in_grace && me.runtime_credit.grace_expires_at) {
4992
+ console.log(
4993
+ ` ${chalk13.dim("Grace:")} Stops at ${new Date(me.runtime_credit.grace_expires_at).toLocaleString()}`
4994
+ );
4995
+ } else {
4996
+ console.log(
4997
+ ` ${chalk13.dim("Reset:")} ${new Date(me.runtime_credit.period_ends_at).toLocaleDateString()}`
4998
+ );
4999
+ }
4766
5000
  }
4767
- console.log(` ${chalk12.dim("API:")} ${chalk12.dim(getBaseURL())}`);
5001
+ console.log(` ${chalk13.dim("API:")} ${chalk13.dim(getBaseURL())}`);
4768
5002
  console.log();
4769
5003
  } catch (error) {
4770
5004
  if (spinner) {
4771
- spinner.fail(error instanceof Error ? error.message : "Failed to load user");
5005
+ spinner.fail(
5006
+ error instanceof Error ? error.message : "Failed to load user"
5007
+ );
4772
5008
  } else {
4773
- console.error(error instanceof Error ? error.message : "Failed to load user");
5009
+ console.error(
5010
+ error instanceof Error ? error.message : "Failed to load user"
5011
+ );
4774
5012
  }
4775
5013
  process.exit(1);
4776
5014
  }
4777
5015
  });
5016
+ function formatRuntimeHours(seconds) {
5017
+ const hours = Math.max(seconds, 0) / 3600;
5018
+ if (hours >= 10) {
5019
+ return `${Math.round(hours)}h`;
5020
+ }
5021
+ return `${hours.toFixed(1)}h`;
5022
+ }
4778
5023
 
4779
5024
  // src/index.ts
4780
5025
  var pkg3 = JSON.parse(
4781
5026
  readFileSync3(new URL("../package.json", import.meta.url), "utf8")
4782
5027
  );
4783
- var cliName = process.argv[1] ? basename2(process.argv[1]) : "agentcomputer";
4784
- var program = new Command15();
5028
+ var cliName = process.argv[1] ? basename(process.argv[1]) : "agentcomputer";
5029
+ var program = new Command16();
4785
5030
  function appendTextSection(lines, title, values) {
4786
5031
  if (values.length === 0) {
4787
5032
  return;
4788
5033
  }
4789
- lines.push(` ${chalk13.dim(title)}`);
5034
+ lines.push(` ${chalk14.dim(title)}`);
4790
5035
  lines.push("");
4791
5036
  for (const value of values) {
4792
- lines.push(` ${chalk13.white(value)}`);
5037
+ lines.push(` ${chalk14.white(value)}`);
4793
5038
  }
4794
5039
  lines.push("");
4795
5040
  }
@@ -4798,10 +5043,10 @@ function appendTableSection(lines, title, entries) {
4798
5043
  return;
4799
5044
  }
4800
5045
  const width = Math.max(...entries.map((entry) => entry.term.length), 0) + 2;
4801
- lines.push(` ${chalk13.dim(title)}`);
5046
+ lines.push(` ${chalk14.dim(title)}`);
4802
5047
  lines.push("");
4803
5048
  for (const entry of entries) {
4804
- lines.push(` ${chalk13.white(padEnd(entry.term, width))}${chalk13.dim(entry.desc)}`);
5049
+ lines.push(` ${chalk14.white(padEnd(entry.term, width))}${chalk14.dim(entry.desc)}`);
4805
5050
  }
4806
5051
  lines.push("");
4807
5052
  }
@@ -4827,10 +5072,10 @@ function formatRootHelp(cmd) {
4827
5072
  ["Other", []]
4828
5073
  ];
4829
5074
  const otherGroup = groups.find(([name]) => name === "Other")[1];
4830
- lines.push(`${chalk13.bold(cliName)} ${chalk13.dim(`v${version}`)}`);
5075
+ lines.push(`${chalk14.bold(cliName)} ${chalk14.dim(`v${version}`)}`);
4831
5076
  lines.push("");
4832
5077
  if (cmd.description()) {
4833
- lines.push(` ${chalk13.dim(cmd.description())}`);
5078
+ lines.push(` ${chalk14.dim(cmd.description())}`);
4834
5079
  lines.push("");
4835
5080
  }
4836
5081
  appendTextSection(lines, "Usage", [`${cliName} <command> [options]`]);
@@ -4839,7 +5084,7 @@ function formatRootHelp(cmd) {
4839
5084
  const entry = { term: name, desc: sub.description() };
4840
5085
  if (["login", "logout", "whoami", "claude-login", "codex-login"].includes(name)) {
4841
5086
  groups[0][1].push(entry);
4842
- } else if (["create", "ls", "get", "rm"].includes(name)) {
5087
+ } else if (["create", "ls", "get", "power-on", "power-off", "rm"].includes(name)) {
4843
5088
  groups[1][1].push(entry);
4844
5089
  } else if (name === "image") {
4845
5090
  groups[2][1].push(entry);
@@ -4883,10 +5128,10 @@ function formatSubcommandHelp(cmd, helper) {
4883
5128
  term: helper.optionTerm(option),
4884
5129
  desc: helper.optionDescription(option)
4885
5130
  }));
4886
- lines.push(chalk13.bold(commandPath(cmd)));
5131
+ lines.push(chalk14.bold(commandPath(cmd)));
4887
5132
  lines.push("");
4888
5133
  if (description) {
4889
- lines.push(` ${chalk13.dim(description)}`);
5134
+ lines.push(` ${chalk14.dim(description)}`);
4890
5135
  lines.push("");
4891
5136
  }
4892
5137
  appendTextSection(lines, "Usage", [helper.commandUsage(cmd)]);
@@ -4912,6 +5157,7 @@ function applyHelpFormatting(cmd) {
4912
5157
  program.name(cliName).description("Agent Computer CLI").version(pkg3.version ?? "0.0.0").option("-y, --yes", "Skip confirmation prompts");
4913
5158
  program.addCommand(loginCommand);
4914
5159
  program.addCommand(upgradeCommand);
5160
+ program.addCommand(internalInstallAutosshCommand, { hidden: true });
4915
5161
  program.addCommand(internalInstallMutagenCommand, { hidden: true });
4916
5162
  program.addCommand(logoutCommand);
4917
5163
  program.addCommand(whoamiCommand);
@@ -4920,6 +5166,8 @@ program.addCommand(codexLoginCommand);
4920
5166
  program.addCommand(createCommand);
4921
5167
  program.addCommand(lsCommand);
4922
5168
  program.addCommand(getCommand);
5169
+ program.addCommand(powerOnCommand);
5170
+ program.addCommand(powerOffCommand);
4923
5171
  program.addCommand(imageCommand);
4924
5172
  program.addCommand(agentCommand);
4925
5173
  program.addCommand(acpCommand);