aicomputer 0.1.19 → 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,42 +1,26 @@
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";
6
13
  import {
7
- ApiError,
8
- api,
9
- apiWithKey,
10
- clearAPIKey,
11
- createBrowserAccess,
12
- createComputer,
13
- deleteComputer,
14
- deletePublishedPort,
15
14
  formatStatus,
16
- getAPIKey,
17
- getBaseURL,
18
- getComputerByID,
19
- getConnectionInfo,
20
- getFilesystemSettings,
21
- getStoredAPIKey,
22
- getWebURL,
23
- hasEnvAPIKey,
24
- listComputers,
25
- listPublishedPorts,
26
15
  padEnd,
27
16
  promptForSSHComputer,
28
- publishPort,
29
17
  reconcileMounts,
30
- resolveComputer,
31
- setAPIKey,
32
18
  teardownManagedSessions,
33
- timeAgo,
34
- vncURL,
35
- webURL
36
- } from "./chunk-5JYMQXKN.js";
19
+ timeAgo
20
+ } from "./chunk-36BZXAAX.js";
37
21
  import {
38
22
  isAbortError
39
- } from "./chunk-NN4GECN6.js";
23
+ } from "./chunk-TPFE3CC6.js";
40
24
  import {
41
25
  defaultMountServiceConfig,
42
26
  ensureMountDirectories,
@@ -53,144 +37,56 @@ import {
53
37
  AGENTCOMPUTER_MUTAGEN_PATH_ENV,
54
38
  ensureBundledMutagenInstalled,
55
39
  ensureMutagenCommandPath
56
- } from "./chunk-MDSPJ57B.js";
40
+ } from "./chunk-GD42GHW3.js";
57
41
  import {
58
- compareVersions,
59
- resolveLatestPublishedVersion
60
- } from "./chunk-GGBVVRLL.js";
42
+ ensureDefaultSSHKeyRegistered,
43
+ openSSHConnection,
44
+ prepareSSHConnection,
45
+ prepareSSHConnectionByIdentifier
46
+ } from "./chunk-4TE5XTYE.js";
47
+ import {
48
+ ensureBundledAutosshInstalled
49
+ } from "./chunk-3ZF7JRBW.js";
50
+ import {
51
+ ApiError,
52
+ api,
53
+ apiWithKey,
54
+ clearAPIKey,
55
+ createBrowserAccess,
56
+ createComputer,
57
+ deleteComputer,
58
+ deletePublishedPort,
59
+ getAPIKey,
60
+ getBaseURL,
61
+ getComputerByID,
62
+ getConnectionInfo,
63
+ getFilesystemSettings,
64
+ getStoredAPIKey,
65
+ getWebURL,
66
+ hasEnvAPIKey,
67
+ listComputers,
68
+ listPublishedPorts,
69
+ powerOffComputer,
70
+ powerOnComputer,
71
+ publishPort,
72
+ resolveComputer,
73
+ setAPIKey,
74
+ vncURL,
75
+ webURL
76
+ } from "./chunk-LOGK7YYJ.js";
77
+ import "./chunk-5Y2NWK5I.js";
61
78
 
62
79
  // src/index.ts
63
- import { Command as Command15 } from "commander";
64
- import chalk13 from "chalk";
80
+ import { Command as Command16 } from "commander";
81
+ import chalk14 from "chalk";
65
82
  import { readFileSync as readFileSync3 } from "fs";
66
- import { basename as basename2 } from "path";
83
+ import { basename } from "path";
67
84
 
68
85
  // src/commands/access.ts
69
86
  import { Command } from "commander";
70
87
  import chalk from "chalk";
71
88
  import ora from "ora";
72
89
 
73
- // src/lib/ssh-access.ts
74
- import { spawn } from "child_process";
75
-
76
- // src/lib/ssh-keys.ts
77
- import { basename } from "path";
78
- import { homedir } from "os";
79
- import { readFile, mkdir } from "fs/promises";
80
- import { execFileSync } from "child_process";
81
- import { existsSync } from "fs";
82
- var DEFAULT_PUBLIC_KEY_PATHS = [
83
- `${homedir()}/.ssh/id_ed25519.pub`,
84
- `${homedir()}/.ssh/id_ecdsa.pub`,
85
- `${homedir()}/.ssh/id_rsa.pub`
86
- ];
87
- async function ensureDefaultSSHKeyRegistered() {
88
- for (const path of DEFAULT_PUBLIC_KEY_PATHS) {
89
- try {
90
- const publicKey2 = (await readFile(path, "utf8")).trim();
91
- if (!publicKey2) {
92
- continue;
93
- }
94
- const key2 = await api("/v1/ssh-keys", {
95
- method: "POST",
96
- body: JSON.stringify({
97
- name: basename(path),
98
- public_key: publicKey2
99
- })
100
- });
101
- return {
102
- key: key2,
103
- publicKeyPath: path,
104
- privateKeyPath: path.replace(/\.pub$/, "")
105
- };
106
- } catch (error) {
107
- if (error?.code === "ENOENT") {
108
- continue;
109
- }
110
- throw error;
111
- }
112
- }
113
- const generated = await generateSSHKey();
114
- const publicKey = (await readFile(generated.publicKeyPath, "utf8")).trim();
115
- const key = await api("/v1/ssh-keys", {
116
- method: "POST",
117
- body: JSON.stringify({
118
- name: basename(generated.publicKeyPath),
119
- public_key: publicKey
120
- })
121
- });
122
- return {
123
- key,
124
- publicKeyPath: generated.publicKeyPath,
125
- privateKeyPath: generated.privateKeyPath
126
- };
127
- }
128
- async function generateSSHKey() {
129
- const sshDir = `${homedir()}/.ssh`;
130
- if (!existsSync(sshDir)) {
131
- await mkdir(sshDir, { mode: 448 });
132
- }
133
- const privateKeyPath = `${sshDir}/id_ed25519`;
134
- const publicKeyPath = `${privateKeyPath}.pub`;
135
- console.log("No SSH key found \u2014 generating one at", publicKeyPath);
136
- execFileSync("ssh-keygen", ["-t", "ed25519", "-f", privateKeyPath, "-N", ""], {
137
- stdio: "inherit"
138
- });
139
- return { publicKeyPath, privateKeyPath };
140
- }
141
-
142
- // src/lib/ssh-access.ts
143
- async function prepareSSHConnection(computer) {
144
- const registered = await ensureDefaultSSHKeyRegistered();
145
- const info = await getConnectionInfo(computer.id);
146
- if (!info.connection.ssh_available) {
147
- throw new Error("SSH is not available for this computer");
148
- }
149
- return {
150
- computer,
151
- command: formatSSHCommand(
152
- info.connection.ssh_user,
153
- info.connection.ssh_host,
154
- info.connection.ssh_port
155
- ),
156
- args: [
157
- "-i",
158
- registered.privateKeyPath,
159
- "-p",
160
- String(info.connection.ssh_port),
161
- `${info.connection.ssh_user}@${info.connection.ssh_host}`
162
- ]
163
- };
164
- }
165
- async function prepareSSHConnectionByIdentifier(identifier) {
166
- const computer = await resolveComputer(identifier);
167
- return prepareSSHConnection(computer);
168
- }
169
- async function openSSHConnection(connection) {
170
- await new Promise((resolve, reject) => {
171
- const child = spawn("ssh", connection.args, {
172
- stdio: "inherit"
173
- });
174
- child.on("error", reject);
175
- child.on("exit", (code) => {
176
- if (code === 0) {
177
- resolve();
178
- return;
179
- }
180
- reject(new Error(`ssh exited with code ${code ?? 1}`));
181
- });
182
- });
183
- }
184
- function formatSSHCommand(user, host, port) {
185
- if (!user.trim() || !host.trim()) {
186
- return "ssh unavailable";
187
- }
188
- if (port <= 0 || port === 22) {
189
- return `ssh ${user}@${host}`;
190
- }
191
- return `ssh -p ${port} ${user}@${host}`;
192
- }
193
-
194
90
  // src/lib/open-browser.ts
195
91
  import { constants } from "fs";
196
92
  import { access } from "fs/promises";
@@ -248,74 +144,6 @@ async function openBrowserURL(url) {
248
144
  await open(url);
249
145
  }
250
146
 
251
- // src/lib/ssh-config.ts
252
- import { homedir as homedir2 } from "os";
253
- import { join } from "path";
254
- import { mkdir as mkdir2, readFile as readFile2, writeFile } from "fs/promises";
255
- var MANAGED_BLOCK_START = "# >>> agentcomputer ssh setup >>>";
256
- var MANAGED_BLOCK_END = "# <<< agentcomputer ssh setup <<<";
257
- async function ensureSSHAliasConfig(options) {
258
- const sshDir = join(homedir2(), ".ssh");
259
- const configPath = join(sshDir, "config");
260
- await mkdir2(sshDir, { recursive: true, mode: 448 });
261
- let existing = "";
262
- try {
263
- existing = await readFile2(configPath, "utf8");
264
- } catch (error) {
265
- if (error.code !== "ENOENT") {
266
- throw error;
267
- }
268
- }
269
- const nextBlock = renderManagedBlock(options);
270
- const managedBlockPattern = new RegExp(
271
- `${escapeRegex(MANAGED_BLOCK_START)}[\\s\\S]*?${escapeRegex(MANAGED_BLOCK_END)}\\n?`,
272
- "m"
273
- );
274
- let nextContents;
275
- if (managedBlockPattern.test(existing)) {
276
- nextContents = existing.replace(managedBlockPattern, nextBlock);
277
- } else {
278
- const normalized = existing.length === 0 ? "" : existing.endsWith("\n") ? existing : `${existing}
279
- `;
280
- nextContents = normalized.length === 0 ? nextBlock : `${normalized}
281
- ${nextBlock}`;
282
- }
283
- const changed = nextContents !== existing;
284
- if (changed) {
285
- await writeFile(configPath, nextContents, { mode: 384 });
286
- }
287
- return {
288
- configPath,
289
- changed
290
- };
291
- }
292
- function renderManagedBlock(options) {
293
- const user = options.user?.trim() || "agentcomputer";
294
- const identityFile = formatIdentityFilePath(options.identityFilePath);
295
- return `${MANAGED_BLOCK_START}
296
- Host ${options.alias}
297
- HostName ${options.host}
298
- Port ${options.port}
299
- User ${user}
300
- IdentityFile ${identityFile}
301
- IdentitiesOnly yes
302
- ServerAliveInterval 30
303
- ServerAliveCountMax 4
304
- ${MANAGED_BLOCK_END}
305
- `;
306
- }
307
- function formatIdentityFilePath(path) {
308
- const normalized = path.trim();
309
- const homePath = `${homedir2()}/`;
310
- if (normalized.startsWith(homePath)) {
311
- return `~/${normalized.slice(homePath.length)}`;
312
- }
313
- return normalized.replaceAll(" ", "\\ ");
314
- }
315
- function escapeRegex(value) {
316
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
317
- }
318
-
319
147
  // src/lib/ssh-setup.ts
320
148
  async function ensureSSHAccessConfigured(options = {}) {
321
149
  const alias = normalizeSSHAlias(options.alias ?? "agentcomputer.ai");
@@ -395,8 +223,8 @@ var openCommand = new Command("open").description("Open a computer in your brows
395
223
  process.exit(1);
396
224
  }
397
225
  });
398
- 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(
399
- 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) => {
400
228
  if (options.setup) {
401
229
  await setupSSHAlias(options);
402
230
  return;
@@ -406,7 +234,10 @@ var sshCommand = new Command("ssh").description("Open an SSH session to a comput
406
234
  ).start();
407
235
  try {
408
236
  const computer = await resolveSSHComputer(identifier, spinner);
409
- const connection = await prepareSSHConnection(computer);
237
+ const connection = await prepareSSHConnection(computer, {
238
+ extraArgs: sshArgs,
239
+ tmux: options.tmux
240
+ });
410
241
  spinner.succeed(`Connecting to ${chalk.bold(computer.handle)}`);
411
242
  console.log(chalk.dim(` ${connection.command}`));
412
243
  console.log();
@@ -1317,7 +1148,7 @@ import ora4 from "ora";
1317
1148
 
1318
1149
  // src/lib/remote-auth.ts
1319
1150
  import { randomBytes } from "crypto";
1320
- import { spawn as spawn2 } from "child_process";
1151
+ import { spawn } from "child_process";
1321
1152
  import ora3 from "ora";
1322
1153
  var readyPollIntervalMs = 2e3;
1323
1154
  var readyPollTimeoutMs = 18e4;
@@ -1453,7 +1284,7 @@ async function runRemoteCommand(target, remoteArgs, script) {
1453
1284
  ...remoteArgs
1454
1285
  ];
1455
1286
  return new Promise((resolve, reject) => {
1456
- const child = spawn2("ssh", args, {
1287
+ const child = spawn("ssh", args, {
1457
1288
  stdio: ["pipe", "pipe", "pipe"]
1458
1289
  });
1459
1290
  let stdout = "";
@@ -2013,13 +1844,14 @@ function isInternalCondition(value) {
2013
1844
  }
2014
1845
  function printComputer(computer) {
2015
1846
  const vnc = vncURL(computer);
2016
- 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";
2017
1848
  const isCustom = computer.runtime_family === "custom-machine";
2018
1849
  console.log();
2019
1850
  console.log(` ${chalk4.bold.white(computer.handle)} ${formatStatus(computer.status)}`);
2020
1851
  console.log();
2021
1852
  console.log(` ${chalk4.dim("ID")} ${computer.id}`);
2022
1853
  console.log(` ${chalk4.dim("Tier")} ${computer.tier}`);
1854
+ console.log(` ${chalk4.dim("Power")} ${formatDesiredPowerState(computer.desired_power_state)}`);
2023
1855
  if (isCustom) {
2024
1856
  console.log(` ${chalk4.dim("Runtime")} ${computer.runtime_family}`);
2025
1857
  console.log(` ${chalk4.dim("Source")} ${computer.source_kind}`);
@@ -2049,7 +1881,7 @@ function printComputer(computer) {
2049
1881
  console.log(` ${chalk4.dim("Created")} ${timeAgo(computer.created_at)}`);
2050
1882
  console.log();
2051
1883
  }
2052
- function formatSSHCommand2(user, host, port) {
1884
+ function formatSSHCommand(user, host, port) {
2053
1885
  if (!user.trim() || !host.trim()) {
2054
1886
  return "ssh unavailable";
2055
1887
  }
@@ -2077,6 +1909,12 @@ function formatManagedWorkerLaunchSource(computer) {
2077
1909
  }
2078
1910
  return `saved custom source ${computer.user_source_id}`;
2079
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
+ }
2080
1918
  function printComputerTable(computers) {
2081
1919
  const handleWidth = Math.max(6, ...computers.map((c) => c.handle.length));
2082
1920
  const statusWidth = 12;
@@ -2333,6 +2171,72 @@ var removeCommand = new Command5("rm").description("Delete a computer").argument
2333
2171
  process.exit(1);
2334
2172
  }
2335
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
+ });
2336
2240
  function parseRuntimeFamilyOption(value) {
2337
2241
  switch (value) {
2338
2242
  case void 0:
@@ -2468,6 +2372,8 @@ _computer() {
2468
2372
  'create:Create a computer'
2469
2373
  'ls:List computers'
2470
2374
  'get:Show computer details'
2375
+ 'power-on:Power on a managed worker'
2376
+ 'power-off:Power off a managed worker'
2471
2377
  'image:Manage machine image sources'
2472
2378
  'open:Open in browser'
2473
2379
  'ssh:SSH into a computer'
@@ -2558,6 +2464,11 @@ _computer() {
2558
2464
  '--json[Print raw JSON]' \\
2559
2465
  '1:computer:_computer_handles'
2560
2466
  ;;
2467
+ power-on|power-off)
2468
+ _arguments \\
2469
+ '--json[Print raw JSON]' \\
2470
+ '1:computer:_computer_handles'
2471
+ ;;
2561
2472
  image)
2562
2473
  _arguments -C \\
2563
2474
  '1:command:->image_command' \\
@@ -2822,10 +2733,10 @@ var completionCommand = new Command6("completion").description("Generate shell c
2822
2733
  });
2823
2734
 
2824
2735
  // src/commands/codex-login.ts
2825
- import { spawn as spawn3 } from "child_process";
2826
- import { readFile as readFile3 } from "fs/promises";
2827
- import { homedir as homedir3 } from "os";
2828
- 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";
2829
2740
  import { Command as Command7 } from "commander";
2830
2741
  import chalk5 from "chalk";
2831
2742
  import ora6 from "ora";
@@ -3025,10 +2936,10 @@ async function getLocalCodexStatus() {
3025
2936
  return parseCodexStatusOutput(result.stdout, result.stderr);
3026
2937
  }
3027
2938
  async function readLocalCodexAuthFile() {
3028
- const authPath = join2(homedir3(), ".codex", "auth.json");
2939
+ const authPath = join(homedir(), ".codex", "auth.json");
3029
2940
  let raw;
3030
2941
  try {
3031
- raw = await readFile3(authPath, "utf8");
2942
+ raw = await readFile(authPath, "utf8");
3032
2943
  } catch (error) {
3033
2944
  throw new Error(
3034
2945
  error instanceof Error ? `failed to read ${authPath}: ${error.message}` : `failed to read ${authPath}`
@@ -3046,7 +2957,7 @@ async function readLocalCodexAuthFile() {
3046
2957
  }
3047
2958
  async function runInteractiveCodexLogin() {
3048
2959
  await new Promise((resolve, reject) => {
3049
- const child = spawn3("codex", ["login"], {
2960
+ const child = spawn2("codex", ["login"], {
3050
2961
  stdio: "inherit"
3051
2962
  });
3052
2963
  child.on("error", (error) => {
@@ -3069,7 +2980,7 @@ async function runInteractiveCodexLogin() {
3069
2980
  }
3070
2981
  async function captureLocalCommand(command, args) {
3071
2982
  return new Promise((resolve, reject) => {
3072
- const child = spawn3(command, args, {
2983
+ const child = spawn2(command, args, {
3073
2984
  stdio: ["ignore", "pipe", "pipe"]
3074
2985
  });
3075
2986
  let stdout = "";
@@ -3573,16 +3484,42 @@ async function confirmDeletion(sourceID) {
3573
3484
  });
3574
3485
  }
3575
3486
 
3576
- // src/commands/internal-install-mutagen.ts
3487
+ // src/commands/internal-install-autossh.ts
3577
3488
  import { Command as Command9 } from "commander";
3578
3489
  import chalk7 from "chalk";
3579
- 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) => {
3580
3517
  try {
3581
3518
  const executablePath = await ensureBundledMutagenInstalled();
3582
3519
  if (!options.quiet) {
3583
3520
  console.log();
3584
3521
  console.log(
3585
- chalk7.green(
3522
+ chalk8.green(
3586
3523
  ` Bundled Mutagen ready at ${executablePath}.`
3587
3524
  )
3588
3525
  );
@@ -3592,7 +3529,7 @@ var internalInstallMutagenCommand = new Command9("internal-install-mutagen").opt
3592
3529
  if (!options.quiet) {
3593
3530
  const message = error instanceof Error ? error.message : "failed to install bundled Mutagen";
3594
3531
  console.error();
3595
- console.error(chalk7.red(` ${message}`));
3532
+ console.error(chalk8.red(` ${message}`));
3596
3533
  console.error();
3597
3534
  }
3598
3535
  process.exit(1);
@@ -3600,8 +3537,8 @@ var internalInstallMutagenCommand = new Command9("internal-install-mutagen").opt
3600
3537
  });
3601
3538
 
3602
3539
  // src/commands/login.ts
3603
- import { Command as Command10 } from "commander";
3604
- import chalk8 from "chalk";
3540
+ import { Command as Command11 } from "commander";
3541
+ import chalk9 from "chalk";
3605
3542
  import ora8 from "ora";
3606
3543
 
3607
3544
  // src/lib/browser-login.ts
@@ -3869,12 +3806,12 @@ function escapeHTML(value) {
3869
3806
  }
3870
3807
 
3871
3808
  // src/commands/login.ts
3872
- 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) => {
3873
3810
  const existingKey = getStoredAPIKey();
3874
3811
  if (existingKey && !options.force) {
3875
3812
  console.log();
3876
3813
  console.log(
3877
- chalk8.yellow(" Already logged in. Use --force to overwrite.")
3814
+ chalk9.yellow(" Already logged in. Use --force to overwrite.")
3878
3815
  );
3879
3816
  console.log();
3880
3817
  return;
@@ -3883,8 +3820,8 @@ var loginCommand = new Command10("login").description("Authenticate the CLI").op
3883
3820
  const apiKey = await resolveAPIKeyInput(options.apiKey, options.stdin);
3884
3821
  if (!apiKey && wantsManualLogin) {
3885
3822
  console.log();
3886
- console.log(chalk8.dim(" Usage: computer login --api-key <ac_live_...>"));
3887
- 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()}`));
3888
3825
  console.log();
3889
3826
  process.exit(1);
3890
3827
  }
@@ -3894,7 +3831,7 @@ var loginCommand = new Command10("login").description("Authenticate the CLI").op
3894
3831
  }
3895
3832
  if (!apiKey.startsWith("ac_live_")) {
3896
3833
  console.log();
3897
- console.log(chalk8.red(" API key must start with ac_live_"));
3834
+ console.log(chalk9.red(" API key must start with ac_live_"));
3898
3835
  console.log();
3899
3836
  process.exit(1);
3900
3837
  }
@@ -3902,7 +3839,7 @@ var loginCommand = new Command10("login").description("Authenticate the CLI").op
3902
3839
  try {
3903
3840
  const me = await apiWithKey(apiKey, "/v1/me");
3904
3841
  setAPIKey(apiKey);
3905
- spinner.succeed(`Logged in as ${chalk8.bold(me.user.email)}`);
3842
+ spinner.succeed(`Logged in as ${chalk9.bold(me.user.email)}`);
3906
3843
  } catch (error) {
3907
3844
  spinner.fail(
3908
3845
  error instanceof Error ? error.message : "Failed to validate API key"
@@ -3922,15 +3859,15 @@ async function runBrowserLogin() {
3922
3859
  spinner.stop();
3923
3860
  console.log();
3924
3861
  console.log(
3925
- chalk8.yellow(" Browser auto-open failed. Open this URL to continue:")
3862
+ chalk9.yellow(" Browser auto-open failed. Open this URL to continue:")
3926
3863
  );
3927
- console.log(chalk8.dim(` ${attempt.loginURL}`));
3864
+ console.log(chalk9.dim(` ${attempt.loginURL}`));
3928
3865
  console.log();
3929
3866
  spinner.start("Waiting for browser login...");
3930
3867
  }
3931
3868
  spinner.text = "Waiting for browser login...";
3932
3869
  const result = await attempt.waitForResult();
3933
- spinner.succeed(`Logged in as ${chalk8.bold(result.me.user.email)}`);
3870
+ spinner.succeed(`Logged in as ${chalk9.bold(result.me.user.email)}`);
3934
3871
  await continueFirstLoginFlow(result);
3935
3872
  } catch (error) {
3936
3873
  spinner.fail(
@@ -3964,8 +3901,8 @@ async function continueFirstLoginFlow(result) {
3964
3901
  }
3965
3902
  console.log();
3966
3903
  console.log(
3967
- chalk8.cyan(
3968
- `Continuing first-time setup for ${chalk8.bold(machineHandle)}...
3904
+ chalk9.cyan(
3905
+ `Continuing first-time setup for ${chalk9.bold(machineHandle)}...
3969
3906
  `
3970
3907
  )
3971
3908
  );
@@ -3978,8 +3915,8 @@ async function continueFirstLoginFlow(result) {
3978
3915
  const spinner = ora8(`Preparing SSH access for ${machineHandle}...`).start();
3979
3916
  try {
3980
3917
  const connection = await prepareSSHConnectionByIdentifier(machineHandle);
3981
- spinner.succeed(`Connecting to ${chalk8.bold(machineHandle)}`);
3982
- console.log(chalk8.dim(` ${connection.command}`));
3918
+ spinner.succeed(`Connecting to ${chalk9.bold(machineHandle)}`);
3919
+ console.log(chalk9.dim(` ${connection.command}`));
3983
3920
  console.log();
3984
3921
  await openSSHConnection(connection);
3985
3922
  } catch (error) {
@@ -3990,19 +3927,19 @@ async function continueFirstLoginFlow(result) {
3990
3927
  }
3991
3928
  } catch (error) {
3992
3929
  const message = error instanceof Error ? error.message : "Failed to finish first-time setup";
3993
- console.error(chalk8.red(`
3930
+ console.error(chalk9.red(`
3994
3931
  ${message}`));
3995
3932
  console.log();
3996
3933
  if (result.provider === "claude") {
3997
3934
  console.log(
3998
- chalk8.dim(` computer claude-login --machine ${machineHandle}`)
3935
+ chalk9.dim(` computer claude-login --machine ${machineHandle}`)
3999
3936
  );
4000
3937
  } else if (result.provider === "codex") {
4001
3938
  console.log(
4002
- chalk8.dim(` computer codex-login --machine ${machineHandle}`)
3939
+ chalk9.dim(` computer codex-login --machine ${machineHandle}`)
4003
3940
  );
4004
3941
  }
4005
- console.log(chalk8.dim(` computer ssh ${machineHandle}`));
3942
+ console.log(chalk9.dim(` computer ssh ${machineHandle}`));
4006
3943
  console.log();
4007
3944
  process.exit(1);
4008
3945
  }
@@ -4016,23 +3953,23 @@ async function runSelectedProvider(provider, machineHandle) {
4016
3953
  await runCodexLogin({ machine: machineHandle });
4017
3954
  return;
4018
3955
  }
4019
- console.log(chalk8.green(`Sandbox ${chalk8.bold(machineHandle)} is ready.`));
3956
+ console.log(chalk9.green(`Sandbox ${chalk9.bold(machineHandle)} is ready.`));
4020
3957
  console.log();
4021
3958
  }
4022
3959
  function printNextStep(machineHandle) {
4023
- console.log(chalk8.green(`Sandbox ${chalk8.bold(machineHandle)} is ready.`));
4024
- 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}`));
4025
3962
  console.log();
4026
3963
  }
4027
3964
 
4028
3965
  // src/commands/mount.ts
4029
- import { spawn as spawn4 } from "child_process";
4030
- import { Command as Command11, Option } from "commander";
4031
- 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";
4032
3969
  import ora9 from "ora";
4033
3970
 
4034
3971
  // src/lib/mount-daemon.ts
4035
- import { mkdir as mkdir3 } from "fs/promises";
3972
+ import { mkdir } from "fs/promises";
4036
3973
  function getMountControllerState(rootPath = defaultMountServiceConfig().rootPath) {
4037
3974
  const lock = readMountControllerLock(rootPath);
4038
3975
  if (!lock) {
@@ -4048,7 +3985,7 @@ async function runMountDaemon(config, options = {}) {
4048
3985
  const { onReady, onStarted } = options;
4049
3986
  const paths = getMountPaths(config.rootPath);
4050
3987
  ensureMountDirectories(paths);
4051
- await mkdir3(paths.rootPath, { recursive: true });
3988
+ await mkdir(paths.rootPath, { recursive: true });
4052
3989
  await acquireControllerLock(config.rootPath);
4053
3990
  writeMountStatusSnapshot(
4054
3991
  {
@@ -4078,10 +4015,11 @@ async function runMountDaemon(config, options = {}) {
4078
4015
  config.rootPath
4079
4016
  );
4080
4017
  await teardownManagedSessions(config, paths);
4081
- await mkdir3(paths.rootPath, { recursive: true });
4018
+ await mkdir(paths.rootPath, { recursive: true });
4082
4019
  let running = false;
4083
4020
  let queued = false;
4084
4021
  let shuttingDown = false;
4022
+ let readyNotified = false;
4085
4023
  let activeRun = null;
4086
4024
  let activeRunController = null;
4087
4025
  const runOnce = async () => {
@@ -4103,6 +4041,11 @@ async function runMountDaemon(config, options = {}) {
4103
4041
  process.pid,
4104
4042
  controller.signal
4105
4043
  );
4044
+ const snapshot = readMountStatusSnapshot(config.rootPath);
4045
+ if (!readyNotified && snapshot?.startupPhase === "ready") {
4046
+ readyNotified = true;
4047
+ onReady?.();
4048
+ }
4106
4049
  } catch (error) {
4107
4050
  if (shuttingDown && isAbortError(error)) {
4108
4051
  return;
@@ -4204,8 +4147,6 @@ async function runMountDaemon(config, options = {}) {
4204
4147
  void shutdown();
4205
4148
  });
4206
4149
  await runOnce();
4207
- writeMountStatusSnapshot(markMountStartupReady(readMountStatusSnapshot(config.rootPath), process.pid), config.rootPath);
4208
- onReady?.();
4209
4150
  await new Promise(() => {
4210
4151
  });
4211
4152
  }
@@ -4234,28 +4175,13 @@ function processExists(pid) {
4234
4175
  return false;
4235
4176
  }
4236
4177
  }
4237
- function markMountStartupReady(snapshot, controllerPid) {
4238
- return {
4239
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4240
- controllerPid,
4241
- running: true,
4242
- startupPhase: "ready",
4243
- startupMessage: void 0,
4244
- lastHealthySyncAt: snapshot?.lastHealthySyncAt,
4245
- lastSuccessfulSyncAt: snapshot?.lastSuccessfulSyncAt,
4246
- lastIssueAt: snapshot?.lastIssueAt,
4247
- lastIssue: snapshot?.lastIssue,
4248
- lastError: snapshot?.lastError,
4249
- mounts: snapshot?.mounts ?? []
4250
- };
4251
- }
4252
4178
 
4253
4179
  // src/commands/mount.ts
4254
4180
  var MOUNT_START_SPINNER = {
4255
4181
  interval: 90,
4256
4182
  frames: ["\u25F0", "\u25F3", "\u25F2", "\u25F1"]
4257
4183
  };
4258
- 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(
4259
4185
  "--background",
4260
4186
  "Run the mount controller in the background and print its PID"
4261
4187
  ).addOption(new Option("--daemonized").hideHelp()).addOption(new Option("--open-root-when-ready").hideHelp()).action(async (options) => {
@@ -4289,14 +4215,22 @@ var mountCommand = new Command11("mount").description("Mirror SSH-ready machines
4289
4215
  return;
4290
4216
  }
4291
4217
  const child = await startMountControllerInForeground(resolved.config.rootPath);
4292
- await waitForMountControllerReady(resolved.config.rootPath, child.pid, spinner);
4218
+ const controls = attachForegroundControls(child);
4293
4219
  spinner.succeed("Machine mount controller running");
4294
4220
  printMountStartSummary(
4295
4221
  resolved.config,
4296
4222
  child.pid ?? process.pid,
4297
4223
  "foreground"
4298
4224
  );
4299
- await superviseForegroundMountController(child);
4225
+ try {
4226
+ await superviseForegroundMountController(
4227
+ resolved.config.rootPath,
4228
+ child,
4229
+ controls
4230
+ );
4231
+ } finally {
4232
+ controls.close();
4233
+ }
4300
4234
  } catch (error) {
4301
4235
  spinner.fail(
4302
4236
  error instanceof Error ? error.message : "Failed to start machine mount controller"
@@ -4304,42 +4238,9 @@ var mountCommand = new Command11("mount").description("Mirror SSH-ready machines
4304
4238
  process.exit(1);
4305
4239
  }
4306
4240
  }).addCommand(
4307
- new Command11("status").description("Show machine mount controller status").action(() => {
4241
+ new Command12("status").description("Show machine mount controller status").action(() => {
4308
4242
  const config = readMountConfig() ?? defaultMountServiceConfig();
4309
- const controller = getMountControllerState(config.rootPath);
4310
- const snapshot = readMountStatusSnapshot(config.rootPath);
4311
- console.log();
4312
- console.log(` ${chalk9.bold("Machine Mounts")}`);
4313
- console.log();
4314
- console.log(
4315
- ` ${chalk9.dim("Running")} ${controller.running ? chalk9.green("yes") : chalk9.dim("no")}`
4316
- );
4317
- if (controller.pid) {
4318
- console.log(` ${chalk9.dim("PID")} ${controller.pid}`);
4319
- }
4320
- console.log(` ${chalk9.dim("Root")} ${config.rootPath}`);
4321
- console.log(` ${chalk9.dim("Alias")} ${config.alias}`);
4322
- console.log(
4323
- ` ${chalk9.dim("Updated")} ${snapshot?.updatedAt ? timeAgo(snapshot.updatedAt) : chalk9.dim("never")}`
4324
- );
4325
- console.log(
4326
- ` ${chalk9.dim("Healthy")} ${snapshot?.lastHealthySyncAt ? timeAgo(snapshot.lastHealthySyncAt) : chalk9.dim("never")}`
4327
- );
4328
- if (controller.running && snapshot?.mounts.length) {
4329
- console.log();
4330
- for (const mount of snapshot.mounts) {
4331
- const state = formatMountState(mount.state);
4332
- console.log(` ${chalk9.white(mount.handle)} ${state} ${chalk9.dim(mount.mountPath)}`);
4333
- if (mount.message) {
4334
- console.log(` ${chalk9.dim(mount.message)}`);
4335
- }
4336
- }
4337
- }
4338
- if (snapshot?.lastIssue) {
4339
- console.log();
4340
- console.log(` ${chalk9.dim("Last issue")} ${chalk9.yellow(snapshot.lastIssue)}`);
4341
- }
4342
- console.log();
4243
+ renderMountStatus(config);
4343
4244
  })
4344
4245
  );
4345
4246
  function parsePositiveInt(raw, label) {
@@ -4383,7 +4284,7 @@ async function startMountControllerInBackground(rootPath) {
4383
4284
  return child.pid;
4384
4285
  }
4385
4286
  async function startMountControllerInForeground(rootPath) {
4386
- const child = startMountControllerProcess(rootPath, false, true);
4287
+ const child = startMountControllerProcess(rootPath, true, true);
4387
4288
  await waitForMountControllerRunning(rootPath, child.pid);
4388
4289
  return child;
4389
4290
  }
@@ -4400,7 +4301,7 @@ function startMountControllerProcess(rootPath, detached, openRootWhenReady) {
4400
4301
  if (openRootWhenReady) {
4401
4302
  args.push("--open-root-when-ready");
4402
4303
  }
4403
- const child = spawn4(process.execPath, [entrypoint, ...args], {
4304
+ const child = spawn3(process.execPath, [entrypoint, ...args], {
4404
4305
  cwd: process.cwd(),
4405
4306
  detached,
4406
4307
  env: process.env,
@@ -4425,45 +4326,30 @@ async function waitForMountControllerRunning(rootPath, pid) {
4425
4326
  }
4426
4327
  throw new Error("failed to start machine mount controller in background");
4427
4328
  }
4428
- async function waitForMountControllerReady(rootPath, pid, spinner) {
4429
- const deadline = Date.now() + 12e4;
4430
- while (Date.now() < deadline) {
4431
- const controller = getMountControllerState(rootPath);
4432
- const snapshot = readMountStatusSnapshot(rootPath);
4433
- if (snapshot?.controllerPid === pid && snapshot.startupMessage) {
4434
- spinner.text = snapshot.startupMessage;
4435
- }
4436
- if (snapshot?.controllerPid === pid && snapshot.startupPhase === "ready") {
4437
- return;
4438
- }
4439
- if (!controller.running && !processExists2(pid)) {
4440
- break;
4441
- }
4442
- await new Promise((resolve) => setTimeout(resolve, 100));
4443
- }
4444
- throw new Error("mount controller did not finish startup");
4445
- }
4446
- async function superviseForegroundMountController(child) {
4329
+ async function superviseForegroundMountController(rootPath, child, controls) {
4330
+ let lifecycleState = createMountLifecycleState();
4331
+ lifecycleState = updateMountLifecycleDisplay(
4332
+ lifecycleState,
4333
+ readMountStatusSnapshot(rootPath)
4334
+ );
4447
4335
  await new Promise((resolve, reject) => {
4448
4336
  const cleanup = () => {
4449
- process.off("SIGINT", onSigint);
4450
- process.off("SIGTERM", onSigterm);
4451
4337
  child.off("error", onError);
4452
4338
  child.off("exit", onExit);
4453
- };
4454
- const stopAndExit = (signal) => {
4455
- cleanup();
4456
- try {
4457
- child.kill(signal);
4458
- } catch {
4339
+ clearInterval(interval);
4340
+ if (lifecycleState.spinner) {
4341
+ lifecycleState.spinner.stop();
4342
+ lifecycleState.spinner = null;
4459
4343
  }
4460
- process.exit(0);
4461
4344
  };
4462
- const onSigint = () => {
4463
- stopAndExit("SIGINT");
4464
- };
4465
- const onSigterm = () => {
4466
- 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();
4467
4353
  };
4468
4354
  const onError = (error) => {
4469
4355
  cleanup();
@@ -4481,26 +4367,367 @@ async function superviseForegroundMountController(child) {
4481
4367
  )
4482
4368
  );
4483
4369
  };
4484
- process.once("SIGINT", onSigint);
4485
- 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);
4486
4386
  child.once("error", onError);
4487
4387
  child.once("exit", onExit);
4488
4388
  });
4489
4389
  }
4490
4390
  function printMountStartSummary(config, pid, mode) {
4491
4391
  console.log();
4492
- console.log(chalk9.dim(` PID: ${pid}`));
4493
- console.log(chalk9.dim(` Root: ${config.rootPath}`));
4494
- console.log(chalk9.dim(` SSH alias: ${config.alias}`));
4495
- 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`));
4496
4396
  console.log();
4497
4397
  console.log(
4498
- chalk9.dim(
4499
- 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."
4500
4400
  )
4501
4401
  );
4502
4402
  console.log();
4503
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
+ }
4504
4731
  function processExists2(pid) {
4505
4732
  if (!Number.isInteger(pid) || pid <= 0) {
4506
4733
  return false;
@@ -4516,7 +4743,7 @@ function revealMountRootInFinder(rootPath) {
4516
4743
  if (process.platform !== "darwin") {
4517
4744
  return;
4518
4745
  }
4519
- const child = spawn4("open", [rootPath], {
4746
+ const child = spawn3("open", [rootPath], {
4520
4747
  stdio: "ignore",
4521
4748
  detached: true
4522
4749
  });
@@ -4527,35 +4754,36 @@ function revealMountRootInFinder(rootPath) {
4527
4754
  function formatMountState(state) {
4528
4755
  switch (state) {
4529
4756
  case "mounted":
4530
- return chalk9.green(state);
4757
+ return chalk10.green(state);
4758
+ case "syncing":
4531
4759
  case "reconnecting":
4532
- return chalk9.cyan(state);
4760
+ return chalk10.cyan(state);
4533
4761
  case "degraded":
4534
4762
  case "pending":
4535
- return chalk9.yellow(state);
4763
+ return chalk10.yellow(state);
4536
4764
  default:
4537
- return chalk9.red(state);
4765
+ return chalk10.red(state);
4538
4766
  }
4539
4767
  }
4540
4768
 
4541
4769
  // src/commands/logout.ts
4542
- import { Command as Command12 } from "commander";
4543
- import chalk10 from "chalk";
4544
- 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(() => {
4545
4773
  if (!getStoredAPIKey()) {
4546
4774
  console.log();
4547
- console.log(chalk10.dim(" Not logged in."));
4775
+ console.log(chalk11.dim(" Not logged in."));
4548
4776
  if (hasEnvAPIKey()) {
4549
- 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."));
4550
4778
  }
4551
4779
  console.log();
4552
4780
  return;
4553
4781
  }
4554
4782
  clearAPIKey();
4555
4783
  console.log();
4556
- console.log(chalk10.green(" Logged out."));
4784
+ console.log(chalk11.green(" Logged out."));
4557
4785
  if (hasEnvAPIKey()) {
4558
- 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."));
4559
4787
  }
4560
4788
  console.log();
4561
4789
  });
@@ -4563,8 +4791,8 @@ var logoutCommand = new Command12("logout").description("Remove stored API key")
4563
4791
  // src/commands/upgrade.ts
4564
4792
  import { spawnSync } from "child_process";
4565
4793
  import { readFileSync as readFileSync2, realpathSync } from "fs";
4566
- import { Command as Command13 } from "commander";
4567
- import chalk11 from "chalk";
4794
+ import { Command as Command14 } from "commander";
4795
+ import chalk12 from "chalk";
4568
4796
  import ora10 from "ora";
4569
4797
  var pkg2 = JSON.parse(
4570
4798
  readFileSync2(new URL("../package.json", import.meta.url), "utf8")
@@ -4676,12 +4904,12 @@ function attemptBundledMutagenRefresh(executablePath) {
4676
4904
  }
4677
4905
  console.log();
4678
4906
  console.log(
4679
- chalk11.yellow(
4907
+ chalk12.yellow(
4680
4908
  " CLI updated, but bundled Mutagen did not finish installing. `computer mount` will retry on first use."
4681
4909
  )
4682
4910
  );
4683
4911
  }
4684
- 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 () => {
4685
4913
  const currentVersion = pkg2.version ?? "0.0.0";
4686
4914
  const packageName = pkg2.name ?? "aicomputer";
4687
4915
  const spinner = ora10("Checking for updates...").start();
@@ -4714,9 +4942,9 @@ var upgradeCommand = new Command13("upgrade").description("Update the CLI to the
4714
4942
  spinner.stop();
4715
4943
  console.log();
4716
4944
  console.log(
4717
- chalk11.dim(` Updating ${chalk11.bold(`v${currentVersion}`)} -> ${chalk11.bold(`v${latestVersion}`)}`)
4945
+ chalk12.dim(` Updating ${chalk12.bold(`v${currentVersion}`)} -> ${chalk12.bold(`v${latestVersion}`)}`)
4718
4946
  );
4719
- console.log(chalk11.dim(` ${upgrade.label}`));
4947
+ console.log(chalk12.dim(` ${upgrade.label}`));
4720
4948
  console.log();
4721
4949
  const result = spawnSync(upgrade.command, upgrade.args, {
4722
4950
  stdio: "inherit"
@@ -4726,7 +4954,7 @@ var upgradeCommand = new Command13("upgrade").description("Update the CLI to the
4726
4954
  attemptBundledMutagenRefresh(executablePath);
4727
4955
  }
4728
4956
  console.log();
4729
- console.log(chalk11.green(` Updated to v${latestVersion}.`));
4957
+ console.log(chalk12.green(` Updated to v${latestVersion}.`));
4730
4958
  console.log();
4731
4959
  return;
4732
4960
  }
@@ -4734,10 +4962,10 @@ var upgradeCommand = new Command13("upgrade").description("Update the CLI to the
4734
4962
  });
4735
4963
 
4736
4964
  // src/commands/whoami.ts
4737
- import { Command as Command14 } from "commander";
4738
- import chalk12 from "chalk";
4965
+ import { Command as Command15 } from "commander";
4966
+ import chalk13 from "chalk";
4739
4967
  import ora11 from "ora";
4740
- 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) => {
4741
4969
  const spinner = options.json ? null : ora11("Loading user...").start();
4742
4970
  try {
4743
4971
  const me = await api("/v1/me");
@@ -4747,39 +4975,66 @@ var whoamiCommand = new Command14("whoami").description("Show current user").opt
4747
4975
  return;
4748
4976
  }
4749
4977
  console.log();
4750
- 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
+ );
4751
4981
  if (me.user.display_name) {
4752
- console.log(` ${chalk12.dim(me.user.email)}`);
4982
+ console.log(` ${chalk13.dim(me.user.email)}`);
4753
4983
  }
4754
4984
  if (me.api_key.name) {
4755
- console.log(` ${chalk12.dim("Key:")} ${me.api_key.name}`);
4985
+ console.log(` ${chalk13.dim("Key:")} ${me.api_key.name}`);
4756
4986
  }
4757
- console.log(` ${chalk12.dim("API:")} ${chalk12.dim(getBaseURL())}`);
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
+ }
5000
+ }
5001
+ console.log(` ${chalk13.dim("API:")} ${chalk13.dim(getBaseURL())}`);
4758
5002
  console.log();
4759
5003
  } catch (error) {
4760
5004
  if (spinner) {
4761
- 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
+ );
4762
5008
  } else {
4763
- 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
+ );
4764
5012
  }
4765
5013
  process.exit(1);
4766
5014
  }
4767
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
+ }
4768
5023
 
4769
5024
  // src/index.ts
4770
5025
  var pkg3 = JSON.parse(
4771
5026
  readFileSync3(new URL("../package.json", import.meta.url), "utf8")
4772
5027
  );
4773
- var cliName = process.argv[1] ? basename2(process.argv[1]) : "agentcomputer";
4774
- var program = new Command15();
5028
+ var cliName = process.argv[1] ? basename(process.argv[1]) : "agentcomputer";
5029
+ var program = new Command16();
4775
5030
  function appendTextSection(lines, title, values) {
4776
5031
  if (values.length === 0) {
4777
5032
  return;
4778
5033
  }
4779
- lines.push(` ${chalk13.dim(title)}`);
5034
+ lines.push(` ${chalk14.dim(title)}`);
4780
5035
  lines.push("");
4781
5036
  for (const value of values) {
4782
- lines.push(` ${chalk13.white(value)}`);
5037
+ lines.push(` ${chalk14.white(value)}`);
4783
5038
  }
4784
5039
  lines.push("");
4785
5040
  }
@@ -4788,10 +5043,10 @@ function appendTableSection(lines, title, entries) {
4788
5043
  return;
4789
5044
  }
4790
5045
  const width = Math.max(...entries.map((entry) => entry.term.length), 0) + 2;
4791
- lines.push(` ${chalk13.dim(title)}`);
5046
+ lines.push(` ${chalk14.dim(title)}`);
4792
5047
  lines.push("");
4793
5048
  for (const entry of entries) {
4794
- 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)}`);
4795
5050
  }
4796
5051
  lines.push("");
4797
5052
  }
@@ -4817,10 +5072,10 @@ function formatRootHelp(cmd) {
4817
5072
  ["Other", []]
4818
5073
  ];
4819
5074
  const otherGroup = groups.find(([name]) => name === "Other")[1];
4820
- lines.push(`${chalk13.bold(cliName)} ${chalk13.dim(`v${version}`)}`);
5075
+ lines.push(`${chalk14.bold(cliName)} ${chalk14.dim(`v${version}`)}`);
4821
5076
  lines.push("");
4822
5077
  if (cmd.description()) {
4823
- lines.push(` ${chalk13.dim(cmd.description())}`);
5078
+ lines.push(` ${chalk14.dim(cmd.description())}`);
4824
5079
  lines.push("");
4825
5080
  }
4826
5081
  appendTextSection(lines, "Usage", [`${cliName} <command> [options]`]);
@@ -4829,7 +5084,7 @@ function formatRootHelp(cmd) {
4829
5084
  const entry = { term: name, desc: sub.description() };
4830
5085
  if (["login", "logout", "whoami", "claude-login", "codex-login"].includes(name)) {
4831
5086
  groups[0][1].push(entry);
4832
- } else if (["create", "ls", "get", "rm"].includes(name)) {
5087
+ } else if (["create", "ls", "get", "power-on", "power-off", "rm"].includes(name)) {
4833
5088
  groups[1][1].push(entry);
4834
5089
  } else if (name === "image") {
4835
5090
  groups[2][1].push(entry);
@@ -4873,10 +5128,10 @@ function formatSubcommandHelp(cmd, helper) {
4873
5128
  term: helper.optionTerm(option),
4874
5129
  desc: helper.optionDescription(option)
4875
5130
  }));
4876
- lines.push(chalk13.bold(commandPath(cmd)));
5131
+ lines.push(chalk14.bold(commandPath(cmd)));
4877
5132
  lines.push("");
4878
5133
  if (description) {
4879
- lines.push(` ${chalk13.dim(description)}`);
5134
+ lines.push(` ${chalk14.dim(description)}`);
4880
5135
  lines.push("");
4881
5136
  }
4882
5137
  appendTextSection(lines, "Usage", [helper.commandUsage(cmd)]);
@@ -4902,6 +5157,7 @@ function applyHelpFormatting(cmd) {
4902
5157
  program.name(cliName).description("Agent Computer CLI").version(pkg3.version ?? "0.0.0").option("-y, --yes", "Skip confirmation prompts");
4903
5158
  program.addCommand(loginCommand);
4904
5159
  program.addCommand(upgradeCommand);
5160
+ program.addCommand(internalInstallAutosshCommand, { hidden: true });
4905
5161
  program.addCommand(internalInstallMutagenCommand, { hidden: true });
4906
5162
  program.addCommand(logoutCommand);
4907
5163
  program.addCommand(whoamiCommand);
@@ -4910,6 +5166,8 @@ program.addCommand(codexLoginCommand);
4910
5166
  program.addCommand(createCommand);
4911
5167
  program.addCommand(lsCommand);
4912
5168
  program.addCommand(getCommand);
5169
+ program.addCommand(powerOnCommand);
5170
+ program.addCommand(powerOffCommand);
4913
5171
  program.addCommand(imageCommand);
4914
5172
  program.addCommand(agentCommand);
4915
5173
  program.addCommand(acpCommand);