aicomputer 0.1.13 → 0.1.15

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,400 +1,82 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ formatMountHostInstallGuidance,
4
+ getMountHostValidationIssues
5
+ } from "./chunk-OWK5N76S.js";
6
+ import {
7
+ ApiError,
8
+ api,
9
+ apiWithKey,
10
+ clearAPIKey,
11
+ createBrowserAccess,
12
+ createComputer,
13
+ deleteComputer,
14
+ deletePublishedPort,
15
+ formatStatus,
16
+ getAPIKey,
17
+ getBaseURL,
18
+ getComputerByID,
19
+ getConnectionInfo,
20
+ getFilesystemSettings,
21
+ getStoredAPIKey,
22
+ getWebURL,
23
+ hasEnvAPIKey,
24
+ listComputers,
25
+ listPublishedPorts,
26
+ minuteSecondAgo,
27
+ padEnd,
28
+ promptForSSHComputer,
29
+ publishPort,
30
+ reconcileMounts,
31
+ resolveComputer,
32
+ setAPIKey,
33
+ teardownManagedSessions,
34
+ timeAgo,
35
+ vncURL,
36
+ webURL
37
+ } from "./chunk-KQQUR2YX.js";
38
+ import {
39
+ defaultMountServiceConfig,
40
+ ensureMountDirectories,
41
+ getMountPaths,
42
+ readMountConfig,
43
+ readMountControllerLock,
44
+ readMountStatusSnapshot,
45
+ removeMountControllerLock,
46
+ writeMountConfig,
47
+ writeMountControllerLock,
48
+ writeMountStatusSnapshot
49
+ } from "./chunk-5JVJROSI.js";
2
50
 
3
51
  // src/index.ts
4
- import { Command as Command12 } from "commander";
52
+ import { Command as Command14 } from "commander";
5
53
  import chalk12 from "chalk";
6
54
  import { readFileSync as readFileSync3 } from "fs";
7
55
  import { basename as basename2 } from "path";
8
56
 
9
57
  // src/commands/access.ts
10
58
  import { Command } from "commander";
11
- import chalk3 from "chalk";
12
- import ora from "ora";
13
-
14
- // src/lib/config.ts
15
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
16
- import { homedir } from "os";
17
- import { join } from "path";
18
- var CONFIG_DIR = join(homedir(), ".computer");
19
- var CONFIG_FILE = join(CONFIG_DIR, "config.json");
20
- function ensureConfigDir() {
21
- if (!existsSync(CONFIG_DIR)) {
22
- mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
23
- }
24
- }
25
- function readConfig() {
26
- ensureConfigDir();
27
- if (!existsSync(CONFIG_FILE)) {
28
- return {};
29
- }
30
- try {
31
- return JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
32
- } catch {
33
- return {};
34
- }
35
- }
36
- function writeConfig(config) {
37
- ensureConfigDir();
38
- const tempFile = `${CONFIG_FILE}.${process.pid}.tmp`;
39
- writeFileSync(tempFile, JSON.stringify(config, null, 2), { mode: 384 });
40
- renameSync(tempFile, CONFIG_FILE);
41
- }
42
- function getAPIKey() {
43
- const envValue = process.env.COMPUTER_API_KEY ?? process.env.AGENTCOMPUTER_API_KEY;
44
- if (envValue) {
45
- return envValue.trim();
46
- }
47
- return getStoredAPIKey();
48
- }
49
- function getStoredAPIKey() {
50
- return readConfig().auth?.apiKey?.trim() || null;
51
- }
52
- function hasEnvAPIKey() {
53
- return Boolean(process.env.COMPUTER_API_KEY ?? process.env.AGENTCOMPUTER_API_KEY);
54
- }
55
- function setAPIKey(apiKey) {
56
- const config = readConfig();
57
- config.auth = { apiKey: apiKey.trim() };
58
- writeConfig(config);
59
- }
60
- function clearAPIKey() {
61
- const config = readConfig();
62
- delete config.auth;
63
- writeConfig(config);
64
- }
65
-
66
- // src/lib/api.ts
67
- var BASE_URL = process.env.COMPUTER_API_URL ?? process.env.AGENTCOMPUTER_API_URL ?? "https://api.computer.agentcomputer.ai";
68
- var WEB_URL = process.env.COMPUTER_WEB_URL ?? process.env.AGENTCOMPUTER_WEB_URL ?? resolveDefaultWebURL(BASE_URL);
69
- var ApiError = class extends Error {
70
- constructor(status, message) {
71
- super(message);
72
- this.status = status;
73
- this.name = "ApiError";
74
- }
75
- };
76
- function getBaseURL() {
77
- return BASE_URL;
78
- }
79
- function getWebURL() {
80
- return WEB_URL;
81
- }
82
- async function api(path, options = {}) {
83
- const apiKey = getAPIKey();
84
- if (!apiKey) {
85
- throw new ApiError(401, "not logged in; run 'computer login' first");
86
- }
87
- return requestWithKey(apiKey, path, options);
88
- }
89
- function resolveDefaultWebURL(apiURL) {
90
- try {
91
- const parsed = new URL(apiURL);
92
- if (parsed.hostname === "api.computer.agentcomputer.ai") {
93
- return "https://agentcomputer.ai";
94
- }
95
- if (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1") {
96
- return `${parsed.protocol}//${parsed.hostname}:3000`;
97
- }
98
- } catch {
99
- return "https://agentcomputer.ai";
100
- }
101
- return "https://agentcomputer.ai";
102
- }
103
- async function apiWithKey(apiKey, path, options = {}) {
104
- return requestWithKey(apiKey, path, options);
105
- }
106
- async function requestWithKey(apiKey, path, options) {
107
- const headers = {
108
- Accept: "application/json",
109
- ...options.headers ?? {}
110
- };
111
- if (options.body !== void 0) {
112
- headers["Content-Type"] = "application/json";
113
- }
114
- if (apiKey) {
115
- headers.Authorization = `Bearer ${apiKey}`;
116
- }
117
- const response = await fetch(`${BASE_URL}${path}`, {
118
- ...options,
119
- headers
120
- });
121
- if (!response.ok) {
122
- throw new ApiError(response.status, await readErrorMessage(response));
123
- }
124
- if (response.status === 204) {
125
- return void 0;
126
- }
127
- return await response.json();
128
- }
129
- async function readErrorMessage(response) {
130
- const contentType = response.headers.get("content-type") ?? "";
131
- if (contentType.includes("application/json")) {
132
- try {
133
- const payload = await response.json();
134
- if (payload.error) {
135
- return payload.error;
136
- }
137
- return JSON.stringify(payload);
138
- } catch {
139
- return response.statusText || "request failed";
140
- }
141
- }
142
- const body = await response.text();
143
- return body || response.statusText || "request failed";
144
- }
145
-
146
- // src/lib/computers.ts
147
- async function listComputers() {
148
- const response = await api("/v1/computers");
149
- return response.computers;
150
- }
151
- async function getComputerByID(id) {
152
- return api(`/v1/computers/${id}`);
153
- }
154
- async function createComputer(input) {
155
- return api("/v1/computers", {
156
- method: "POST",
157
- body: JSON.stringify(input)
158
- });
159
- }
160
- async function deleteComputer(computerID) {
161
- return api(`/v1/computers/${computerID}`, {
162
- method: "DELETE"
163
- });
164
- }
165
- async function getFilesystemSettings() {
166
- return api("/v1/me/filesystem");
167
- }
168
- async function getConnectionInfo(computerID) {
169
- return api(`/v1/computers/${computerID}/connection`);
170
- }
171
- async function createBrowserAccess(computerID) {
172
- return api(`/v1/computers/${computerID}/access/browser`, {
173
- method: "POST"
174
- });
175
- }
176
- async function createTerminalAccess(computerID) {
177
- return api(`/v1/computers/${computerID}/access/terminal`, {
178
- method: "POST"
179
- });
180
- }
181
- async function listPublishedPorts(computerID) {
182
- const response = await api(`/v1/computers/${computerID}/ports`);
183
- return response.ports;
184
- }
185
- async function publishPort(computerID, input) {
186
- return api(`/v1/computers/${computerID}/ports`, {
187
- method: "POST",
188
- body: JSON.stringify(input)
189
- });
190
- }
191
- async function deletePublishedPort(computerID, targetPort) {
192
- return api(`/v1/computers/${computerID}/ports/${targetPort}`, {
193
- method: "DELETE"
194
- });
195
- }
196
- async function resolveComputer(identifier) {
197
- try {
198
- return await getComputerByID(identifier);
199
- } catch (error) {
200
- if (!(error instanceof Error) || !("status" in error)) {
201
- throw error;
202
- }
203
- const status = Reflect.get(error, "status");
204
- if (status !== 404) {
205
- throw error;
206
- }
207
- }
208
- const computers = await listComputers();
209
- const exact = computers.find(
210
- (computer) => computer.handle === identifier || computer.id === identifier
211
- );
212
- if (exact) {
213
- return exact;
214
- }
215
- throw new Error(`computer '${identifier}' not found`);
216
- }
217
- function webURL(computer) {
218
- return `https://${computer.primary_web_host}${normalizePrimaryPath(computer.primary_path)}`;
219
- }
220
- function vncURL(computer) {
221
- if (!computer.vnc_enabled) {
222
- return null;
223
- }
224
- const domain = computer.primary_web_host.replace(/^[^.]+\./, "");
225
- return `https://6080--${computer.handle}.${domain}`;
226
- }
227
- function terminalURL(computer) {
228
- if (computer.runtime_family !== "managed-worker") {
229
- return null;
230
- }
231
- const domain = computer.primary_web_host.replace(/^[^.]+\./, "");
232
- return `https://8788--${computer.handle}.${domain}`;
233
- }
234
- function normalizePrimaryPath(primaryPath) {
235
- const trimmed = primaryPath?.trim();
236
- if (!trimmed) {
237
- return "/";
238
- }
239
- return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
240
- }
241
-
242
- // src/lib/computer-picker.ts
243
- import { select } from "@inquirer/prompts";
244
- import chalk2 from "chalk";
245
-
246
- // src/lib/format.ts
247
59
  import chalk from "chalk";
248
- function padEnd(str, len) {
249
- const visible = str.replace(/\u001b\[[0-9;]*m/g, "");
250
- return str + " ".repeat(Math.max(0, len - visible.length));
251
- }
252
- function timeAgo(dateStr) {
253
- const seconds = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1e3);
254
- if (seconds < 60) return `${seconds}s ago`;
255
- const minutes = Math.floor(seconds / 60);
256
- if (minutes < 60) return `${minutes}m ago`;
257
- const hours = Math.floor(minutes / 60);
258
- if (hours < 24) return `${hours}h ago`;
259
- const days = Math.floor(hours / 24);
260
- return `${days}d ago`;
261
- }
262
- function formatStatus(status) {
263
- switch (status) {
264
- case "running":
265
- return chalk.green(status);
266
- case "pending":
267
- case "provisioning":
268
- case "starting":
269
- return chalk.yellow(status);
270
- case "stopping":
271
- case "stopped":
272
- case "deleted":
273
- return chalk.gray(status);
274
- case "error":
275
- return chalk.red(status);
276
- default:
277
- return status;
278
- }
279
- }
280
-
281
- // src/lib/computer-picker.ts
282
- async function promptForSSHComputer(computers, message) {
283
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
284
- throw new Error("computer id or handle is required when not running interactively");
285
- }
286
- const available = computers.filter(isSSHSelectable);
287
- if (available.length === 0) {
288
- if (computers.length === 0) {
289
- throw new Error("no computers found");
290
- }
291
- throw new Error("no running computers with SSH enabled");
292
- }
293
- const handleWidth = Math.max(6, ...available.map((computer) => computer.handle.length));
294
- const selectedID = await select({
295
- message,
296
- pageSize: Math.min(available.length, 10),
297
- choices: available.map((computer) => ({
298
- name: `${padEnd(chalk2.white(computer.handle), handleWidth + 2)}${padEnd(formatStatus(computer.status), 12)}${chalk2.dim(describeSSHChoice(computer))}`,
299
- value: computer.id
300
- }))
301
- });
302
- return available.find((computer) => computer.id === selectedID) ?? available[0];
303
- }
304
- function isSSHSelectable(computer) {
305
- return computer.ssh_enabled && computer.status === "running";
306
- }
307
- function describeSSHChoice(computer) {
308
- const displayName = computer.display_name.trim();
309
- if (displayName && displayName !== computer.handle) {
310
- return `${displayName} ${timeAgo(computer.updated_at)}`;
311
- }
312
- return `${computer.runtime_family} ${timeAgo(computer.updated_at)}`;
313
- }
60
+ import ora from "ora";
314
61
 
315
- // src/lib/ssh-config.ts
316
- import { homedir as homedir2 } from "os";
317
- import { join as join2 } from "path";
318
- import { mkdir, readFile, writeFile } from "fs/promises";
319
- var MANAGED_BLOCK_START = "# >>> agentcomputer ssh setup >>>";
320
- var MANAGED_BLOCK_END = "# <<< agentcomputer ssh setup <<<";
321
- async function ensureSSHAliasConfig(options) {
322
- const sshDir = join2(homedir2(), ".ssh");
323
- const configPath = join2(sshDir, "config");
324
- await mkdir(sshDir, { recursive: true, mode: 448 });
325
- let existing = "";
326
- try {
327
- existing = await readFile(configPath, "utf8");
328
- } catch (error) {
329
- if (error.code !== "ENOENT") {
330
- throw error;
331
- }
332
- }
333
- const nextBlock = renderManagedBlock(options);
334
- const managedBlockPattern = new RegExp(
335
- `${escapeRegex(MANAGED_BLOCK_START)}[\\s\\S]*?${escapeRegex(MANAGED_BLOCK_END)}\\n?`,
336
- "m"
337
- );
338
- let nextContents;
339
- if (managedBlockPattern.test(existing)) {
340
- nextContents = existing.replace(managedBlockPattern, nextBlock);
341
- } else {
342
- const normalized = existing.length === 0 ? "" : existing.endsWith("\n") ? existing : `${existing}
343
- `;
344
- nextContents = normalized.length === 0 ? nextBlock : `${normalized}
345
- ${nextBlock}`;
346
- }
347
- const changed = nextContents !== existing;
348
- if (changed) {
349
- await writeFile(configPath, nextContents, { mode: 384 });
350
- }
351
- return {
352
- configPath,
353
- changed
354
- };
355
- }
356
- function renderManagedBlock(options) {
357
- const user = options.user?.trim() || "agentcomputer";
358
- const identityFile = formatIdentityFilePath(options.identityFilePath);
359
- return `${MANAGED_BLOCK_START}
360
- Host ${options.alias}
361
- HostName ${options.host}
362
- Port ${options.port}
363
- User ${user}
364
- IdentityFile ${identityFile}
365
- IdentitiesOnly yes
366
- ServerAliveInterval 30
367
- ServerAliveCountMax 4
368
- ${MANAGED_BLOCK_END}
369
- `;
370
- }
371
- function formatIdentityFilePath(path) {
372
- const normalized = path.trim();
373
- const homePath = `${homedir2()}/`;
374
- if (normalized.startsWith(homePath)) {
375
- return `~/${normalized.slice(homePath.length)}`;
376
- }
377
- return normalized.replaceAll(" ", "\\ ");
378
- }
379
- function escapeRegex(value) {
380
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
381
- }
62
+ // src/lib/ssh-access.ts
63
+ import { spawn } from "child_process";
382
64
 
383
65
  // src/lib/ssh-keys.ts
384
66
  import { basename } from "path";
385
- import { homedir as homedir3 } from "os";
386
- import { readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
67
+ import { homedir } from "os";
68
+ import { readFile, mkdir } from "fs/promises";
387
69
  import { execFileSync } from "child_process";
388
- import { existsSync as existsSync2 } from "fs";
70
+ import { existsSync } from "fs";
389
71
  var DEFAULT_PUBLIC_KEY_PATHS = [
390
- `${homedir3()}/.ssh/id_ed25519.pub`,
391
- `${homedir3()}/.ssh/id_ecdsa.pub`,
392
- `${homedir3()}/.ssh/id_rsa.pub`
72
+ `${homedir()}/.ssh/id_ed25519.pub`,
73
+ `${homedir()}/.ssh/id_ecdsa.pub`,
74
+ `${homedir()}/.ssh/id_rsa.pub`
393
75
  ];
394
76
  async function ensureDefaultSSHKeyRegistered() {
395
77
  for (const path of DEFAULT_PUBLIC_KEY_PATHS) {
396
78
  try {
397
- const publicKey2 = (await readFile2(path, "utf8")).trim();
79
+ const publicKey2 = (await readFile(path, "utf8")).trim();
398
80
  if (!publicKey2) {
399
81
  continue;
400
82
  }
@@ -418,7 +100,7 @@ async function ensureDefaultSSHKeyRegistered() {
418
100
  }
419
101
  }
420
102
  const generated = await generateSSHKey();
421
- const publicKey = (await readFile2(generated.publicKeyPath, "utf8")).trim();
103
+ const publicKey = (await readFile(generated.publicKeyPath, "utf8")).trim();
422
104
  const key = await api("/v1/ssh-keys", {
423
105
  method: "POST",
424
106
  body: JSON.stringify({
@@ -433,9 +115,9 @@ async function ensureDefaultSSHKeyRegistered() {
433
115
  };
434
116
  }
435
117
  async function generateSSHKey() {
436
- const sshDir = `${homedir3()}/.ssh`;
437
- if (!existsSync2(sshDir)) {
438
- await mkdir2(sshDir, { mode: 448 });
118
+ const sshDir = `${homedir()}/.ssh`;
119
+ if (!existsSync(sshDir)) {
120
+ await mkdir(sshDir, { mode: 448 });
439
121
  }
440
122
  const privateKeyPath = `${sshDir}/id_ed25519`;
441
123
  const publicKeyPath = `${privateKeyPath}.pub`;
@@ -447,7 +129,6 @@ async function generateSSHKey() {
447
129
  }
448
130
 
449
131
  // src/lib/ssh-access.ts
450
- import { spawn } from "child_process";
451
132
  async function prepareSSHConnection(computer) {
452
133
  const registered = await ensureDefaultSSHKeyRegistered();
453
134
  const info = await getConnectionInfo(computer.id);
@@ -504,6 +185,11 @@ import { constants } from "fs";
504
185
  import { access } from "fs/promises";
505
186
  import open from "open";
506
187
  var IMAGE_BROWSER_LAUNCHER = "/usr/local/bin/browser-launcher";
188
+ var IMAGE_BROWSER_CANDIDATES = [
189
+ "/usr/local/bin/google-chrome",
190
+ "/usr/local/bin/chromium",
191
+ IMAGE_BROWSER_LAUNCHER
192
+ ];
507
193
  async function isExecutable(path) {
508
194
  try {
509
195
  await access(path, constants.X_OK);
@@ -518,6 +204,8 @@ function hasBrokenChromeBrowserEnv() {
518
204
  case "chrome":
519
205
  case "google-chrome":
520
206
  case "google chrome":
207
+ case "browser":
208
+ case "browser-launcher":
521
209
  return true;
522
210
  default:
523
211
  return false;
@@ -531,9 +219,12 @@ async function openBrowserURL(url) {
531
219
  if (hasBrokenChromeBrowserEnv()) {
532
220
  delete process.env.BROWSER;
533
221
  }
534
- if (await isExecutable(IMAGE_BROWSER_LAUNCHER)) {
222
+ for (const browserPath of IMAGE_BROWSER_CANDIDATES) {
223
+ if (!await isExecutable(browserPath)) {
224
+ continue;
225
+ }
535
226
  try {
536
- await open(url, { app: { name: IMAGE_BROWSER_LAUNCHER } });
227
+ await open(url, { app: { name: browserPath } });
537
228
  return;
538
229
  } catch {
539
230
  }
@@ -546,39 +237,146 @@ async function openBrowserURL(url) {
546
237
  await open(url);
547
238
  }
548
239
 
240
+ // src/lib/ssh-config.ts
241
+ import { homedir as homedir2 } from "os";
242
+ import { join } from "path";
243
+ import { mkdir as mkdir2, readFile as readFile2, writeFile } from "fs/promises";
244
+ var MANAGED_BLOCK_START = "# >>> agentcomputer ssh setup >>>";
245
+ var MANAGED_BLOCK_END = "# <<< agentcomputer ssh setup <<<";
246
+ async function ensureSSHAliasConfig(options) {
247
+ const sshDir = join(homedir2(), ".ssh");
248
+ const configPath = join(sshDir, "config");
249
+ await mkdir2(sshDir, { recursive: true, mode: 448 });
250
+ let existing = "";
251
+ try {
252
+ existing = await readFile2(configPath, "utf8");
253
+ } catch (error) {
254
+ if (error.code !== "ENOENT") {
255
+ throw error;
256
+ }
257
+ }
258
+ const nextBlock = renderManagedBlock(options);
259
+ const managedBlockPattern = new RegExp(
260
+ `${escapeRegex(MANAGED_BLOCK_START)}[\\s\\S]*?${escapeRegex(MANAGED_BLOCK_END)}\\n?`,
261
+ "m"
262
+ );
263
+ let nextContents;
264
+ if (managedBlockPattern.test(existing)) {
265
+ nextContents = existing.replace(managedBlockPattern, nextBlock);
266
+ } else {
267
+ const normalized = existing.length === 0 ? "" : existing.endsWith("\n") ? existing : `${existing}
268
+ `;
269
+ nextContents = normalized.length === 0 ? nextBlock : `${normalized}
270
+ ${nextBlock}`;
271
+ }
272
+ const changed = nextContents !== existing;
273
+ if (changed) {
274
+ await writeFile(configPath, nextContents, { mode: 384 });
275
+ }
276
+ return {
277
+ configPath,
278
+ changed
279
+ };
280
+ }
281
+ function renderManagedBlock(options) {
282
+ const user = options.user?.trim() || "agentcomputer";
283
+ const identityFile = formatIdentityFilePath(options.identityFilePath);
284
+ return `${MANAGED_BLOCK_START}
285
+ Host ${options.alias}
286
+ HostName ${options.host}
287
+ Port ${options.port}
288
+ User ${user}
289
+ IdentityFile ${identityFile}
290
+ IdentitiesOnly yes
291
+ ServerAliveInterval 30
292
+ ServerAliveCountMax 4
293
+ ${MANAGED_BLOCK_END}
294
+ `;
295
+ }
296
+ function formatIdentityFilePath(path) {
297
+ const normalized = path.trim();
298
+ const homePath = `${homedir2()}/`;
299
+ if (normalized.startsWith(homePath)) {
300
+ return `~/${normalized.slice(homePath.length)}`;
301
+ }
302
+ return normalized.replaceAll(" ", "\\ ");
303
+ }
304
+ function escapeRegex(value) {
305
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
306
+ }
307
+
308
+ // src/lib/ssh-setup.ts
309
+ async function ensureSSHAccessConfigured(options = {}) {
310
+ const alias = normalizeSSHAlias(options.alias ?? "agentcomputer.ai");
311
+ const host = normalizeSSHHost(options.host ?? "ssh.agentcomputer.ai");
312
+ const port = typeof options.port === "number" ? parseSSHPort(String(options.port)) : parseSSHPort(options.port ?? "443");
313
+ const registered = await ensureDefaultSSHKeyRegistered();
314
+ const configResult = await ensureSSHAliasConfig({
315
+ alias,
316
+ host,
317
+ port,
318
+ user: "agentcomputer",
319
+ identityFilePath: registered.privateKeyPath
320
+ });
321
+ return {
322
+ alias,
323
+ host,
324
+ port,
325
+ configPath: configResult.configPath,
326
+ identityFilePath: registered.privateKeyPath,
327
+ changed: configResult.changed
328
+ };
329
+ }
330
+ function normalizeSSHAlias(value) {
331
+ const alias = value.trim();
332
+ if (!alias) {
333
+ throw new Error("ssh alias cannot be empty");
334
+ }
335
+ if (!/^[A-Za-z0-9._-]+$/.test(alias)) {
336
+ throw new Error(
337
+ "ssh alias may contain only letters, numbers, dot, dash, or underscore"
338
+ );
339
+ }
340
+ return alias;
341
+ }
342
+ function normalizeSSHHost(value) {
343
+ const host = value.trim();
344
+ if (!host) {
345
+ throw new Error("ssh host cannot be empty");
346
+ }
347
+ if (host.includes(" ")) {
348
+ throw new Error("ssh host cannot contain spaces");
349
+ }
350
+ return host;
351
+ }
352
+ function parseSSHPort(value) {
353
+ const parsed = Number.parseInt(value, 10);
354
+ if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {
355
+ throw new Error("ssh port must be between 1 and 65535");
356
+ }
357
+ return parsed;
358
+ }
359
+
549
360
  // src/commands/access.ts
550
- var openCommand = new Command("open").description("Open a computer in your browser").argument("<id-or-handle>", "Computer id or handle").option("--vnc", "Open VNC desktop instead of gateway").option("--terminal", "Open the terminal surface instead of gateway").action(async (identifier, options) => {
361
+ var openCommand = new Command("open").description("Open a computer in your browser").argument("<id-or-handle>", "Computer id or handle").option("--vnc", "Open VNC desktop instead of gateway").action(async (identifier, options) => {
551
362
  const spinner = ora("Preparing access...").start();
552
363
  try {
553
364
  const computer = await resolveComputer(identifier);
554
365
  const info = await getConnectionInfo(computer.id);
555
- if (options.vnc && options.terminal) {
556
- throw new Error("choose either --vnc or --terminal");
557
- }
558
366
  if (options.vnc) {
559
367
  if (!info.connection.vnc_available) {
560
368
  throw new Error("VNC is not available for this computer");
561
369
  }
562
370
  const url = info.connection.vnc_url;
563
- spinner.succeed(`Opening VNC for ${chalk3.bold(computer.handle)}`);
371
+ spinner.succeed(`Opening VNC for ${chalk.bold(computer.handle)}`);
564
372
  await openBrowserURL(url);
565
- console.log(chalk3.dim(` ${url}`));
566
- return;
567
- }
568
- if (options.terminal) {
569
- if (!info.connection.terminal_available) {
570
- throw new Error("Terminal access is not available for this computer");
571
- }
572
- const access3 = await createTerminalAccess(computer.id);
573
- spinner.succeed(`Opening terminal for ${chalk3.bold(computer.handle)}`);
574
- await openBrowserURL(access3.access_url);
575
- console.log(chalk3.dim(` ${access3.access_url}`));
373
+ console.log(chalk.dim(` ${url}`));
576
374
  return;
577
375
  }
578
376
  const access2 = await createBrowserAccess(computer.id);
579
- spinner.succeed(`Opening ${chalk3.bold(computer.handle)}`);
377
+ spinner.succeed(`Opening ${chalk.bold(computer.handle)}`);
580
378
  await openBrowserURL(access2.access_url);
581
- console.log(chalk3.dim(` ${access2.access_url}`));
379
+ console.log(chalk.dim(` ${access2.access_url}`));
582
380
  } catch (error) {
583
381
  spinner.fail(
584
382
  error instanceof Error ? error.message : "Failed to open computer"
@@ -586,7 +384,7 @@ var openCommand = new Command("open").description("Open a computer in your brows
586
384
  process.exit(1);
587
385
  }
588
386
  });
589
- var sshCommand = new Command("ssh").description("Open an SSH session to a computer").argument("[id-or-handle]", "Computer id or handle").option("--setup", "Register key and configure a global SSH alias").option("--alias <alias>", "SSH host alias", "agentcomputer.ai").option("--host <host>", "SSH gateway host", "ssh.agentcomputer.ai").option("--port <port>", "SSH gateway port", "22").action(
387
+ 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(
590
388
  async (identifier, options) => {
591
389
  if (options.setup) {
592
390
  await setupSSHAlias(options);
@@ -598,8 +396,8 @@ var sshCommand = new Command("ssh").description("Open an SSH session to a comput
598
396
  try {
599
397
  const computer = await resolveSSHComputer(identifier, spinner);
600
398
  const connection = await prepareSSHConnection(computer);
601
- spinner.succeed(`Connecting to ${chalk3.bold(computer.handle)}`);
602
- console.log(chalk3.dim(` ${connection.command}`));
399
+ spinner.succeed(`Connecting to ${chalk.bold(computer.handle)}`);
400
+ console.log(chalk.dim(` ${connection.command}`));
603
401
  console.log();
604
402
  await openSSHConnection(connection);
605
403
  } catch (error) {
@@ -621,24 +419,24 @@ portsCommand.command("ls").description("List published ports for a computer").ar
621
419
  spinner.stop();
622
420
  if (ports.length === 0) {
623
421
  console.log();
624
- console.log(chalk3.dim(" No published ports."));
422
+ console.log(chalk.dim(" No published ports."));
625
423
  console.log();
626
424
  return;
627
425
  }
628
426
  const subWidth = Math.max(10, ...ports.map((p) => p.subdomain.length));
629
427
  console.log();
630
428
  console.log(
631
- ` ${chalk3.dim(padEnd("Subdomain", subWidth + 2))}${chalk3.dim(padEnd("Port", 8))}${chalk3.dim("Protocol")}`
429
+ ` ${chalk.dim(padEnd("Subdomain", subWidth + 2))}${chalk.dim(padEnd("Port", 8))}${chalk.dim("Protocol")}`
632
430
  );
633
431
  console.log(
634
- ` ${chalk3.dim("-".repeat(subWidth + 2))}${chalk3.dim("-".repeat(8))}${chalk3.dim("-".repeat(8))}`
432
+ ` ${chalk.dim("-".repeat(subWidth + 2))}${chalk.dim("-".repeat(8))}${chalk.dim("-".repeat(8))}`
635
433
  );
636
434
  for (const port of ports) {
637
435
  const url = `https://${port.subdomain}--${computer.handle}.computer.agentcomputer.ai`;
638
436
  console.log(
639
437
  ` ${padEnd(port.subdomain, subWidth + 2)}${padEnd(String(port.target_port), 8)}${port.protocol}`
640
438
  );
641
- console.log(` ${chalk3.dim(url)}`);
439
+ console.log(` ${chalk.dim(url)}`);
642
440
  }
643
441
  console.log();
644
442
  } catch (error) {
@@ -666,9 +464,9 @@ portsCommand.command("publish").description("Publish an HTTP app port").argument
666
464
  });
667
465
  const url = `https://${published.subdomain}--${computer.handle}.computer.agentcomputer.ai`;
668
466
  spinner.succeed(
669
- `Published port ${chalk3.bold(String(published.target_port))}`
467
+ `Published port ${chalk.bold(String(published.target_port))}`
670
468
  );
671
- console.log(chalk3.dim(` ${url}`));
469
+ console.log(chalk.dim(` ${url}`));
672
470
  } catch (error) {
673
471
  spinner.fail(
674
472
  error instanceof Error ? error.message : "Failed to publish port"
@@ -685,71 +483,32 @@ portsCommand.command("rm").description("Unpublish an app port").argument("<id-or
685
483
  }
686
484
  const computer = await resolveComputer(identifier);
687
485
  await deletePublishedPort(computer.id, targetPort);
688
- spinner.succeed(`Removed port ${chalk3.bold(String(targetPort))}`);
486
+ spinner.succeed(`Removed port ${chalk.bold(String(targetPort))}`);
689
487
  } catch (error) {
690
488
  spinner.fail(
691
489
  error instanceof Error ? error.message : "Failed to remove port"
692
490
  );
693
- process.exit(1);
694
- }
695
- });
696
- async function setupSSHAlias(options) {
697
- const spinner = ora("Configuring global SSH access...").start();
698
- try {
699
- const alias = normalizeSSHAlias(options.alias ?? "agentcomputer.ai");
700
- const host = normalizeSSHHost(options.host ?? "ssh.agentcomputer.ai");
701
- const port = parseSSHPort(options.port ?? "22");
702
- const registered = await ensureDefaultSSHKeyRegistered();
703
- const configResult = await ensureSSHAliasConfig({
704
- alias,
705
- host,
706
- port,
707
- user: "agentcomputer",
708
- identityFilePath: registered.privateKeyPath
709
- });
710
- spinner.succeed(`SSH alias '${alias}' is ready`);
711
- console.log();
712
- console.log(chalk3.dim(` SSH config: ${configResult.configPath}`));
713
- console.log(chalk3.dim(` Identity: ${registered.privateKeyPath}`));
714
- console.log();
715
- console.log(` ${chalk3.bold("Shell:")} ssh ${alias}`);
716
- console.log(` ${chalk3.bold("Direct:")} ssh <handle>@${alias}`);
717
- console.log();
718
- } catch (error) {
719
- spinner.fail(
720
- error instanceof Error ? error.message : "Failed to configure SSH alias"
721
- );
722
- process.exit(1);
723
- }
724
- }
725
- function normalizeSSHAlias(value) {
726
- const alias = value.trim();
727
- if (!alias) {
728
- throw new Error("ssh alias cannot be empty");
729
- }
730
- if (!/^[A-Za-z0-9._-]+$/.test(alias)) {
731
- throw new Error(
732
- "ssh alias may contain only letters, numbers, dot, dash, or underscore"
733
- );
734
- }
735
- return alias;
736
- }
737
- function normalizeSSHHost(value) {
738
- const host = value.trim();
739
- if (!host) {
740
- throw new Error("ssh host cannot be empty");
741
- }
742
- if (host.includes(" ")) {
743
- throw new Error("ssh host cannot contain spaces");
744
- }
745
- return host;
746
- }
747
- function parseSSHPort(value) {
748
- const parsed = Number.parseInt(value, 10);
749
- if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {
750
- throw new Error("ssh port must be between 1 and 65535");
491
+ process.exit(1);
492
+ }
493
+ });
494
+ async function setupSSHAlias(options) {
495
+ const spinner = ora("Configuring global SSH access...").start();
496
+ try {
497
+ const setup = await ensureSSHAccessConfigured(options);
498
+ spinner.succeed(`SSH alias '${setup.alias}' is ready`);
499
+ console.log();
500
+ console.log(chalk.dim(` SSH config: ${setup.configPath}`));
501
+ console.log(chalk.dim(` Identity: ${setup.identityFilePath}`));
502
+ console.log();
503
+ console.log(` ${chalk.bold("Shell:")} ssh ${setup.alias}`);
504
+ console.log(` ${chalk.bold("Direct:")} ssh <handle>@${setup.alias}`);
505
+ console.log();
506
+ } catch (error) {
507
+ spinner.fail(
508
+ error instanceof Error ? error.message : "Failed to configure SSH alias"
509
+ );
510
+ process.exit(1);
751
511
  }
752
- return parsed;
753
512
  }
754
513
  async function resolveSSHComputer(identifier, spinner) {
755
514
  const trimmed = identifier?.trim();
@@ -769,7 +528,7 @@ async function resolveSSHComputer(identifier, spinner) {
769
528
  }
770
529
 
771
530
  // src/commands/acp.ts
772
- import { readFileSync as readFileSync2 } from "fs";
531
+ import { readFileSync } from "fs";
773
532
  import readline from "readline";
774
533
  import { Command as Command2 } from "commander";
775
534
 
@@ -782,10 +541,6 @@ async function listAgentSessions(computerID) {
782
541
  const response = await api(`/v1/computers/${computerID}/agent-sessions`);
783
542
  return response.sessions;
784
543
  }
785
- async function listFleetAgentSessions() {
786
- const response = await api("/v1/fleet/agent-sessions");
787
- return response.sessions;
788
- }
789
544
  async function createAgentSession(computerID, input) {
790
545
  const response = await api(`/v1/computers/${computerID}/agent-sessions`, {
791
546
  method: "POST",
@@ -849,7 +604,7 @@ async function openAgentSessionEventsStream(computerID, sessionID, options = {})
849
604
  signal: options.signal
850
605
  });
851
606
  if (!response.ok) {
852
- const message = await readErrorMessage2(response);
607
+ const message = await readErrorMessage(response);
853
608
  throw new ApiError(response.status, message);
854
609
  }
855
610
  return response;
@@ -869,7 +624,7 @@ async function waitForSessionToSettle(computerID, sessionID, promptID, options =
869
624
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
870
625
  }
871
626
  }
872
- async function readErrorMessage2(response) {
627
+ async function readErrorMessage(response) {
873
628
  const contentType = response.headers.get("content-type") ?? "";
874
629
  if (contentType.includes("application/json")) {
875
630
  try {
@@ -885,7 +640,7 @@ async function readErrorMessage2(response) {
885
640
 
886
641
  // src/commands/acp.ts
887
642
  var pkg = JSON.parse(
888
- readFileSync2(new URL("../package.json", import.meta.url), "utf8")
643
+ readFileSync(new URL("../package.json", import.meta.url), "utf8")
889
644
  );
890
645
  function emit(message) {
891
646
  process.stdout.write(`${JSON.stringify(message)}
@@ -1116,22 +871,22 @@ acpCommand.command("serve").description("Serve a local stdio ACP bridge backed b
1116
871
 
1117
872
  // src/commands/agent.ts
1118
873
  import { Command as Command3 } from "commander";
1119
- import chalk4 from "chalk";
874
+ import chalk2 from "chalk";
1120
875
  import ora2 from "ora";
1121
876
  function formatAgentSessionStatus(status) {
1122
877
  switch (status) {
1123
878
  case "idle":
1124
- return chalk4.green(status);
879
+ return chalk2.green(status);
1125
880
  case "running":
1126
- return chalk4.blue(status);
881
+ return chalk2.blue(status);
1127
882
  case "cancelling":
1128
- return chalk4.yellow(status);
883
+ return chalk2.yellow(status);
1129
884
  case "interrupted":
1130
- return chalk4.yellow(status);
885
+ return chalk2.yellow(status);
1131
886
  case "failed":
1132
- return chalk4.red(status);
887
+ return chalk2.red(status);
1133
888
  case "closed":
1134
- return chalk4.gray(status);
889
+ return chalk2.gray(status);
1135
890
  default:
1136
891
  return status;
1137
892
  }
@@ -1140,14 +895,14 @@ function printAgents(agents) {
1140
895
  const idWidth = Math.max(5, ...agents.map((agent) => agent.id.length));
1141
896
  console.log();
1142
897
  console.log(
1143
- ` ${chalk4.dim(padEnd("Agent", idWidth + 2))}${chalk4.dim(padEnd("Installed", 12))}${chalk4.dim(padEnd("Creds", 8))}${chalk4.dim("Version")}`
898
+ ` ${chalk2.dim(padEnd("Agent", idWidth + 2))}${chalk2.dim(padEnd("Installed", 12))}${chalk2.dim(padEnd("Creds", 8))}${chalk2.dim("Version")}`
1144
899
  );
1145
900
  console.log(
1146
- ` ${chalk4.dim("-".repeat(idWidth + 2))}${chalk4.dim("-".repeat(12))}${chalk4.dim("-".repeat(8))}${chalk4.dim("-".repeat(12))}`
901
+ ` ${chalk2.dim("-".repeat(idWidth + 2))}${chalk2.dim("-".repeat(12))}${chalk2.dim("-".repeat(8))}${chalk2.dim("-".repeat(12))}`
1147
902
  );
1148
903
  for (const agent of agents) {
1149
904
  console.log(
1150
- ` ${padEnd(agent.id, idWidth + 2)}${padEnd(agent.installed ? chalk4.green("yes") : chalk4.gray("no"), 12)}${padEnd(agent.credentialsAvailable ? chalk4.green("yes") : chalk4.yellow("no"), 8)}${agent.version ?? chalk4.dim("unknown")}`
905
+ ` ${padEnd(agent.id, idWidth + 2)}${padEnd(agent.installed ? chalk2.green("yes") : chalk2.gray("no"), 12)}${padEnd(agent.credentialsAvailable ? chalk2.green("yes") : chalk2.yellow("no"), 8)}${agent.version ?? chalk2.dim("unknown")}`
1151
906
  );
1152
907
  }
1153
908
  console.log();
@@ -1155,7 +910,7 @@ function printAgents(agents) {
1155
910
  function printSessions(sessions, handleByComputerID = /* @__PURE__ */ new Map()) {
1156
911
  if (sessions.length === 0) {
1157
912
  console.log();
1158
- console.log(chalk4.dim(" No agent sessions found."));
913
+ console.log(chalk2.dim(" No agent sessions found."));
1159
914
  console.log();
1160
915
  return;
1161
916
  }
@@ -1164,19 +919,19 @@ function printSessions(sessions, handleByComputerID = /* @__PURE__ */ new Map())
1164
919
  const statusWidth = 13;
1165
920
  console.log();
1166
921
  console.log(
1167
- ` ${chalk4.dim(padEnd("Session", 14))}${chalk4.dim(padEnd("Name", nameWidth + 2))}${chalk4.dim(padEnd("Agent", agentWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim("Location")}`
922
+ ` ${chalk2.dim(padEnd("Session", 14))}${chalk2.dim(padEnd("Name", nameWidth + 2))}${chalk2.dim(padEnd("Agent", agentWidth + 2))}${chalk2.dim(padEnd("Status", statusWidth + 2))}${chalk2.dim("Location")}`
1168
923
  );
1169
924
  console.log(
1170
- ` ${chalk4.dim("-".repeat(14))}${chalk4.dim("-".repeat(nameWidth + 2))}${chalk4.dim("-".repeat(agentWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(20))}`
925
+ ` ${chalk2.dim("-".repeat(14))}${chalk2.dim("-".repeat(nameWidth + 2))}${chalk2.dim("-".repeat(agentWidth + 2))}${chalk2.dim("-".repeat(statusWidth + 2))}${chalk2.dim("-".repeat(20))}`
1171
926
  );
1172
927
  for (const session of sessions) {
1173
928
  const location = handleByComputerID.get(session.computer_id) ?? session.computer_id;
1174
929
  console.log(
1175
930
  ` ${padEnd(session.id.slice(0, 12), 14)}${padEnd(session.name || "default", nameWidth + 2)}${padEnd(session.agent, agentWidth + 2)}${padEnd(formatAgentSessionStatus(session.status), statusWidth + 2)}${location}`
1176
931
  );
1177
- console.log(` ${chalk4.dim(` cwd=${session.cwd} updated=${timeAgo(session.updated_at)}`)}`);
932
+ console.log(` ${chalk2.dim(` cwd=${session.cwd} updated=${timeAgo(session.updated_at)}`)}`);
1178
933
  if (session.last_stop_reason || session.last_error) {
1179
- console.log(` ${chalk4.dim(` ${session.last_error ? `error=${session.last_error}` : `stop=${session.last_stop_reason}`}`)}`);
934
+ console.log(` ${chalk2.dim(` ${session.last_error ? `error=${session.last_error}` : `stop=${session.last_stop_reason}`}`)}`);
1180
935
  }
1181
936
  }
1182
937
  console.log();
@@ -1190,7 +945,7 @@ var StreamPrinter = class {
1190
945
  }
1191
946
  writeDim(text) {
1192
947
  this.ensureBreak();
1193
- process.stdout.write(chalk4.dim(text));
948
+ process.stdout.write(chalk2.dim(text));
1194
949
  this.inlineOpen = !text.endsWith("\n");
1195
950
  }
1196
951
  writeLine(text) {
@@ -1258,7 +1013,7 @@ function renderSSEChunk(chunk, printer, asJson) {
1258
1013
  try {
1259
1014
  envelope = JSON.parse(payload);
1260
1015
  } catch {
1261
- printer.writeLine(chalk4.dim(payload));
1016
+ printer.writeLine(chalk2.dim(payload));
1262
1017
  return;
1263
1018
  }
1264
1019
  const method = typeof envelope.method === "string" ? envelope.method : "";
@@ -1285,14 +1040,14 @@ function renderSSEChunk(chunk, printer, asJson) {
1285
1040
  case "tool_call_update": {
1286
1041
  const title = typeof update?.title === "string" && update.title ? update.title : "tool";
1287
1042
  const status = typeof update?.status === "string" && update.status ? update.status : "in_progress";
1288
- printer.writeLine(chalk4.dim(`[tool:${status}] ${title}`));
1043
+ printer.writeLine(chalk2.dim(`[tool:${status}] ${title}`));
1289
1044
  return;
1290
1045
  }
1291
1046
  case "plan": {
1292
1047
  const entries = Array.isArray(update?.entries) ? update.entries : [];
1293
1048
  const detail = entries.filter((entry) => typeof entry === "object" && entry !== null).map((entry) => `- [${entry.status ?? "pending"}] ${entry.content ?? ""}`).join("\n");
1294
1049
  if (detail) {
1295
- printer.writeLine(chalk4.dim(`Plan
1050
+ printer.writeLine(chalk2.dim(`Plan
1296
1051
  ${detail}`));
1297
1052
  }
1298
1053
  return;
@@ -1302,7 +1057,7 @@ ${detail}`));
1302
1057
  }
1303
1058
  }
1304
1059
  if (method === "session/request_permission") {
1305
- printer.writeLine(chalk4.yellow("Permission requested by remote agent"));
1060
+ printer.writeLine(chalk2.yellow("Permission requested by remote agent"));
1306
1061
  return;
1307
1062
  }
1308
1063
  }
@@ -1430,12 +1185,12 @@ agentCommand.command("prompt").description("Send a prompt to a machine agent ses
1430
1185
  return;
1431
1186
  }
1432
1187
  console.log();
1433
- console.log(` ${chalk4.bold(session.name || "default")} ${formatAgentSessionStatus(settled.status)}`);
1188
+ console.log(` ${chalk2.bold(session.name || "default")} ${formatAgentSessionStatus(settled.status)}`);
1434
1189
  if (settled.last_stop_reason) {
1435
- console.log(chalk4.dim(` stop_reason=${settled.last_stop_reason}`));
1190
+ console.log(chalk2.dim(` stop_reason=${settled.last_stop_reason}`));
1436
1191
  }
1437
1192
  if (settled.last_error) {
1438
- console.log(chalk4.red(` error=${settled.last_error}`));
1193
+ console.log(chalk2.red(` error=${settled.last_error}`));
1439
1194
  }
1440
1195
  console.log();
1441
1196
  } catch (error) {
@@ -1541,35 +1296,12 @@ agentCommand.command("close").description("Close and delete a machine agent sess
1541
1296
  process.exit(1);
1542
1297
  }
1543
1298
  });
1544
- var fleetCommand = new Command3("fleet").description("View agent activity across your fleet");
1545
- fleetCommand.command("status").description("List open agent sessions across all machines").option("--json", "Print raw JSON").action(async (options) => {
1546
- const spinner = options.json ? null : ora2("Fetching fleet status...").start();
1547
- try {
1548
- const [sessions, computers] = await Promise.all([
1549
- listFleetAgentSessions(),
1550
- listComputers()
1551
- ]);
1552
- spinner?.stop();
1553
- if (options.json) {
1554
- console.log(JSON.stringify({ sessions }, null, 2));
1555
- return;
1556
- }
1557
- const handleByComputerID = new Map(computers.map((computer) => [computer.id, computer.handle]));
1558
- printSessions(sessions, handleByComputerID);
1559
- } catch (error) {
1560
- spinner?.fail(error instanceof Error ? error.message : "Failed to fetch fleet status");
1561
- if (!spinner) {
1562
- console.error(error instanceof Error ? error.message : "Failed to fetch fleet status");
1563
- }
1564
- process.exit(1);
1565
- }
1566
- });
1567
1299
 
1568
1300
  // src/commands/claude-auth.ts
1569
1301
  import { randomBytes as randomBytes2, createHash } from "crypto";
1570
1302
  import { input as textInput } from "@inquirer/prompts";
1571
1303
  import { Command as Command4 } from "commander";
1572
- import chalk5 from "chalk";
1304
+ import chalk3 from "chalk";
1573
1305
  import ora4 from "ora";
1574
1306
 
1575
1307
  // src/lib/remote-auth.ts
@@ -1782,7 +1514,7 @@ var claudeLoginCommand = new Command4("claude-login").alias("claude-auth").descr
1782
1514
  await runClaudeLogin(options);
1783
1515
  } catch (error) {
1784
1516
  const message = error instanceof Error ? error.message : "Failed to authenticate Claude";
1785
- console.error(chalk5.red(`
1517
+ console.error(chalk3.red(`
1786
1518
  ${message}`));
1787
1519
  process.exit(1);
1788
1520
  }
@@ -1795,7 +1527,7 @@ async function runClaudeLogin(options) {
1795
1527
  let activeTodoID = "target";
1796
1528
  let failureMessage = null;
1797
1529
  console.log();
1798
- console.log(chalk5.cyan("Authenticating with Claude Code...\n"));
1530
+ console.log(chalk3.cyan("Authenticating with Claude Code...\n"));
1799
1531
  try {
1800
1532
  const prepared = await prepareTargetMachine(options);
1801
1533
  target = prepared.computer;
@@ -1880,7 +1612,7 @@ async function runClaudeLogin(options) {
1880
1612
  }
1881
1613
  if (target) {
1882
1614
  console.log(
1883
- chalk5.green(`Claude login installed on ${chalk5.bold(target.handle)}.`)
1615
+ chalk3.green(`Claude login installed on ${chalk3.bold(target.handle)}.`)
1884
1616
  );
1885
1617
  console.log();
1886
1618
  }
@@ -1921,11 +1653,11 @@ function markVerificationTodo(items, id, result, successDetail) {
1921
1653
  }
1922
1654
  function printTodoList(items) {
1923
1655
  console.log();
1924
- console.log(chalk5.dim("TODO"));
1656
+ console.log(chalk3.dim("TODO"));
1925
1657
  console.log();
1926
1658
  for (const item of items) {
1927
- const marker = item.state === "done" ? chalk5.green("[x]") : item.state === "skipped" ? chalk5.yellow("[-]") : item.state === "failed" ? chalk5.red("[!]") : chalk5.dim("[ ]");
1928
- const detail = item.detail ? chalk5.dim(` ${item.detail}`) : "";
1659
+ const marker = item.state === "done" ? chalk3.green("[x]") : item.state === "skipped" ? chalk3.yellow("[-]") : item.state === "failed" ? chalk3.red("[!]") : chalk3.dim("[ ]");
1660
+ const detail = item.detail ? chalk3.dim(` ${item.detail}`) : "";
1929
1661
  console.log(` ${marker} ${item.label}${detail ? ` ${detail}` : ""}`);
1930
1662
  }
1931
1663
  console.log();
@@ -1953,7 +1685,7 @@ async function runManualOAuthFlow() {
1953
1685
  try {
1954
1686
  await openBrowserURL(url);
1955
1687
  } catch {
1956
- console.log(chalk5.yellow("Unable to open the browser automatically."));
1688
+ console.log(chalk3.yellow("Unable to open the browser automatically."));
1957
1689
  }
1958
1690
  console.log(
1959
1691
  "After completing authentication, copy the code shown on the success page."
@@ -2044,7 +1776,7 @@ function parseAuthorizationInput(value, expectedState) {
2044
1776
  }
2045
1777
  async function installClaudeAuth(target, oauth) {
2046
1778
  const spinner = ora4(
2047
- `Installing Claude auth on ${chalk5.bold(target.handle)}...`
1779
+ `Installing Claude auth on ${chalk3.bold(target.handle)}...`
2048
1780
  ).start();
2049
1781
  try {
2050
1782
  const installScript = buildInstallScript(oauth.refreshToken, oauth.scope);
@@ -2054,10 +1786,10 @@ async function installClaudeAuth(target, oauth) {
2054
1786
  installScript
2055
1787
  );
2056
1788
  if (result.stdout.trim()) {
2057
- spinner.succeed(`Installed Claude auth on ${chalk5.bold(target.handle)}`);
1789
+ spinner.succeed(`Installed Claude auth on ${chalk3.bold(target.handle)}`);
2058
1790
  return;
2059
1791
  }
2060
- spinner.succeed(`Installed Claude auth on ${chalk5.bold(target.handle)}`);
1792
+ spinner.succeed(`Installed Claude auth on ${chalk3.bold(target.handle)}`);
2061
1793
  } catch (error) {
2062
1794
  spinner.fail(
2063
1795
  error instanceof Error ? error.message : `Failed to install Claude auth on ${target.handle}`
@@ -2067,11 +1799,11 @@ async function installClaudeAuth(target, oauth) {
2067
1799
  }
2068
1800
  async function verifyTargetMachine(handle, target) {
2069
1801
  const spinner = ora4(
2070
- `Verifying Claude login on ${chalk5.bold(handle)}...`
1802
+ `Verifying Claude login on ${chalk3.bold(handle)}...`
2071
1803
  ).start();
2072
1804
  const result = await verifyStoredAuth(target);
2073
1805
  if (result.status === "verified") {
2074
- spinner.succeed(`Verified Claude login on ${chalk5.bold(handle)}`);
1806
+ spinner.succeed(`Verified Claude login on ${chalk3.bold(handle)}`);
2075
1807
  return result;
2076
1808
  }
2077
1809
  spinner.warn(result.detail);
@@ -2079,7 +1811,7 @@ async function verifyTargetMachine(handle, target) {
2079
1811
  }
2080
1812
  async function verifySharedInstall(primaryHandle, primaryComputerID, sharedInstall, skip, verify) {
2081
1813
  const spinner = ora4(
2082
- `Verifying shared-home Claude login from ${chalk5.bold(primaryHandle)}...`
1814
+ `Verifying shared-home Claude login from ${chalk3.bold(primaryHandle)}...`
2083
1815
  ).start();
2084
1816
  const result = await verifySecondaryMachine(
2085
1817
  primaryComputerID,
@@ -2089,7 +1821,7 @@ async function verifySharedInstall(primaryHandle, primaryComputerID, sharedInsta
2089
1821
  );
2090
1822
  if (result.status === "verified") {
2091
1823
  spinner.succeed(
2092
- `Verified shared-home Claude login on ${chalk5.bold(result.handle)}`
1824
+ `Verified shared-home Claude login on ${chalk3.bold(result.handle)}`
2093
1825
  );
2094
1826
  return result;
2095
1827
  }
@@ -2173,9 +1905,9 @@ function randomSuffix2(length) {
2173
1905
 
2174
1906
  // src/commands/computers.ts
2175
1907
  import { Command as Command5 } from "commander";
2176
- import chalk6 from "chalk";
1908
+ import chalk4 from "chalk";
2177
1909
  import ora5 from "ora";
2178
- import { select as select2, input as textInput2, confirm } from "@inquirer/prompts";
1910
+ import { select, input as textInput2, confirm } from "@inquirer/prompts";
2179
1911
 
2180
1912
  // src/lib/machine-sources.ts
2181
1913
  async function getMachineSourceSettings() {
@@ -2220,48 +1952,90 @@ function summarizeMachineSource(source) {
2220
1952
  return parts.join(" | ");
2221
1953
  }
2222
1954
 
1955
+ // src/lib/mount-control.ts
1956
+ import { unlinkSync } from "fs";
1957
+ import net from "net";
1958
+ async function notifyMountDaemon(paths) {
1959
+ return new Promise((resolve) => {
1960
+ const socket = net.createConnection(paths.socketPath);
1961
+ socket.on("connect", () => {
1962
+ socket.end("reconcile\n");
1963
+ resolve(true);
1964
+ });
1965
+ socket.on("error", () => {
1966
+ resolve(false);
1967
+ });
1968
+ });
1969
+ }
1970
+ function createMountControlServer(paths, onReconcile) {
1971
+ try {
1972
+ unlinkSync(paths.socketPath);
1973
+ } catch {
1974
+ }
1975
+ const server = net.createServer((socket) => {
1976
+ socket.setEncoding("utf8");
1977
+ let buffer = "";
1978
+ socket.on("data", (chunk) => {
1979
+ buffer += chunk;
1980
+ if (buffer.includes("\n")) {
1981
+ void Promise.resolve(onReconcile()).finally(() => {
1982
+ socket.end("ok\n");
1983
+ });
1984
+ }
1985
+ });
1986
+ socket.on("error", () => {
1987
+ socket.destroy();
1988
+ });
1989
+ });
1990
+ server.on("close", () => {
1991
+ try {
1992
+ unlinkSync(paths.socketPath);
1993
+ } catch {
1994
+ }
1995
+ });
1996
+ return server;
1997
+ }
1998
+
2223
1999
  // src/commands/computers.ts
2224
2000
  function isInternalCondition(value) {
2225
2001
  return /^[A-Z][a-zA-Z]+$/.test(value);
2226
2002
  }
2227
2003
  function printComputer(computer) {
2228
2004
  const vnc = vncURL(computer);
2229
- const terminal = terminalURL(computer);
2230
2005
  const ssh = computer.ssh_enabled ? formatSSHCommand2(computer.handle, computer.ssh_host, computer.ssh_port) : "disabled";
2231
2006
  const isCustom = computer.runtime_family === "custom-machine";
2232
2007
  console.log();
2233
- console.log(` ${chalk6.bold.white(computer.handle)} ${formatStatus(computer.status)}`);
2008
+ console.log(` ${chalk4.bold.white(computer.handle)} ${formatStatus(computer.status)}`);
2234
2009
  console.log();
2235
- console.log(` ${chalk6.dim("ID")} ${computer.id}`);
2236
- console.log(` ${chalk6.dim("Tier")} ${computer.tier}`);
2010
+ console.log(` ${chalk4.dim("ID")} ${computer.id}`);
2011
+ console.log(` ${chalk4.dim("Tier")} ${computer.tier}`);
2237
2012
  if (isCustom) {
2238
- console.log(` ${chalk6.dim("Runtime")} ${computer.runtime_family}`);
2239
- console.log(` ${chalk6.dim("Source")} ${computer.source_kind}`);
2240
- console.log(` ${chalk6.dim("Image")} ${computer.image_family}`);
2013
+ console.log(` ${chalk4.dim("Runtime")} ${computer.runtime_family}`);
2014
+ console.log(` ${chalk4.dim("Source")} ${computer.source_kind}`);
2015
+ console.log(` ${chalk4.dim("Image")} ${computer.image_family}`);
2241
2016
  } else {
2242
- console.log(` ${chalk6.dim("Runtime")} ${computer.runtime_family}`);
2243
- console.log(` ${chalk6.dim("Launch")} ${formatManagedWorkerLaunchSource(computer)}`);
2017
+ console.log(` ${chalk4.dim("Runtime")} ${computer.runtime_family}`);
2018
+ console.log(` ${chalk4.dim("Launch")} ${formatManagedWorkerLaunchSource(computer)}`);
2244
2019
  if (computer.user_source_id && computer.resolved_image_ref) {
2245
- console.log(` ${chalk6.dim("Image")} ${computer.resolved_image_ref}`);
2020
+ console.log(` ${chalk4.dim("Image")} ${computer.resolved_image_ref}`);
2246
2021
  }
2247
2022
  }
2248
- console.log(` ${chalk6.dim("Primary")} :${computer.primary_port}${computer.primary_path}`);
2249
- console.log(` ${chalk6.dim("Health")} ${computer.healthcheck_type}${computer.healthcheck_value ? ` (${computer.healthcheck_value})` : ""}`);
2023
+ console.log(` ${chalk4.dim("Primary")} :${computer.primary_port}${computer.primary_path}`);
2024
+ console.log(` ${chalk4.dim("Health")} ${computer.healthcheck_type}${computer.healthcheck_value ? ` (${computer.healthcheck_value})` : ""}`);
2250
2025
  console.log();
2251
- console.log(` ${chalk6.dim("Gateway")} ${chalk6.cyan(webURL(computer))}`);
2252
- console.log(` ${chalk6.dim("VNC")} ${vnc ? chalk6.cyan(vnc) : chalk6.dim("not available")}`);
2253
- console.log(` ${chalk6.dim("Terminal")} ${terminal ? chalk6.cyan(terminal) : chalk6.dim("not available")}`);
2254
- console.log(` ${chalk6.dim("SSH")} ${computer.ssh_enabled ? chalk6.white(ssh) : chalk6.dim(ssh)}`);
2026
+ console.log(` ${chalk4.dim("Gateway")} ${chalk4.cyan(webURL(computer))}`);
2027
+ console.log(` ${chalk4.dim("VNC")} ${vnc ? chalk4.cyan(vnc) : chalk4.dim("not available")}`);
2028
+ console.log(` ${chalk4.dim("SSH")} ${computer.ssh_enabled ? chalk4.white(ssh) : chalk4.dim(ssh)}`);
2255
2029
  if (computer.last_error) {
2256
2030
  console.log();
2257
2031
  if (isInternalCondition(computer.last_error)) {
2258
- console.log(` ${chalk6.dim("Condition")} ${chalk6.dim(computer.last_error)}`);
2032
+ console.log(` ${chalk4.dim("Condition")} ${chalk4.dim(computer.last_error)}`);
2259
2033
  } else {
2260
- console.log(` ${chalk6.dim("Error")} ${chalk6.red(computer.last_error)}`);
2034
+ console.log(` ${chalk4.dim("Error")} ${chalk4.red(computer.last_error)}`);
2261
2035
  }
2262
2036
  }
2263
2037
  console.log();
2264
- console.log(` ${chalk6.dim("Created")} ${timeAgo(computer.created_at)}`);
2038
+ console.log(` ${chalk4.dim("Created")} ${timeAgo(computer.created_at)}`);
2265
2039
  console.log();
2266
2040
  }
2267
2041
  function formatSSHCommand2(user, host, port) {
@@ -2298,16 +2072,16 @@ function printComputerTable(computers) {
2298
2072
  const createdWidth = 10;
2299
2073
  console.log();
2300
2074
  console.log(
2301
- ` ${chalk6.dim(padEnd("Handle", handleWidth + 2))}${chalk6.dim(padEnd("Status", statusWidth + 2))}${chalk6.dim(padEnd("Created", createdWidth + 2))}${chalk6.dim("URL")}`
2075
+ ` ${chalk4.dim(padEnd("Handle", handleWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim(padEnd("Created", createdWidth + 2))}${chalk4.dim("URL")}`
2302
2076
  );
2303
2077
  console.log(
2304
- ` ${chalk6.dim("-".repeat(handleWidth + 2))}${chalk6.dim("-".repeat(statusWidth + 2))}${chalk6.dim("-".repeat(createdWidth + 2))}${chalk6.dim("-".repeat(20))}`
2078
+ ` ${chalk4.dim("-".repeat(handleWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(createdWidth + 2))}${chalk4.dim("-".repeat(20))}`
2305
2079
  );
2306
2080
  for (const computer of computers) {
2307
2081
  const status = formatStatus(computer.status);
2308
- const created = chalk6.dim(timeAgo(computer.created_at));
2082
+ const created = chalk4.dim(timeAgo(computer.created_at));
2309
2083
  console.log(
2310
- ` ${chalk6.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${padEnd(created, createdWidth + 2)}${chalk6.cyan(webURL(computer))}`
2084
+ ` ${chalk4.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${padEnd(created, createdWidth + 2)}${chalk4.cyan(webURL(computer))}`
2311
2085
  );
2312
2086
  }
2313
2087
  console.log();
@@ -2317,23 +2091,19 @@ function printComputerTableVerbose(computers) {
2317
2091
  const statusWidth = 10;
2318
2092
  console.log();
2319
2093
  console.log(
2320
- ` ${chalk6.dim(padEnd("Handle", handleWidth + 2))}${chalk6.dim(padEnd("Status", statusWidth + 2))}${chalk6.dim("URLs")}`
2094
+ ` ${chalk4.dim(padEnd("Handle", handleWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim("URLs")}`
2321
2095
  );
2322
2096
  console.log(
2323
- ` ${chalk6.dim("-".repeat(handleWidth + 2))}${chalk6.dim("-".repeat(statusWidth + 2))}${chalk6.dim("-".repeat(20))}`
2097
+ ` ${chalk4.dim("-".repeat(handleWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(20))}`
2324
2098
  );
2325
2099
  for (const computer of computers) {
2326
2100
  const status = formatStatus(computer.status);
2327
2101
  const vnc = vncURL(computer);
2328
- const terminal = terminalURL(computer);
2329
2102
  console.log(
2330
- ` ${chalk6.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${chalk6.cyan(webURL(computer))}`
2103
+ ` ${chalk4.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${chalk4.cyan(webURL(computer))}`
2331
2104
  );
2332
2105
  console.log(
2333
- ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk6.dim(vnc ?? "VNC not available")}`
2334
- );
2335
- console.log(
2336
- ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk6.dim(terminal ?? "Terminal not available")}`
2106
+ ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk4.dim(vnc ?? "VNC not available")}`
2337
2107
  );
2338
2108
  }
2339
2109
  console.log();
@@ -2349,7 +2119,7 @@ var lsCommand = new Command5("ls").description("List computers").option("--json"
2349
2119
  }
2350
2120
  if (computers.length === 0) {
2351
2121
  console.log();
2352
- console.log(chalk6.dim(" No computers found."));
2122
+ console.log(chalk4.dim(" No computers found."));
2353
2123
  console.log();
2354
2124
  return;
2355
2125
  }
@@ -2405,11 +2175,11 @@ var createCommand = new Command5("create").description("Create a computer").argu
2405
2175
  Boolean(selectedOptions.usePlatformDefault)
2406
2176
  );
2407
2177
  if (machineSourceNote) {
2408
- console.log(chalk6.dim(machineSourceNote));
2178
+ console.log(chalk4.dim(machineSourceNote));
2409
2179
  }
2410
2180
  const provisioningNote = createProvisioningNote(runtimeFamily, filesystemSettings);
2411
2181
  if (provisioningNote) {
2412
- console.log(chalk6.dim(provisioningNote));
2182
+ console.log(chalk4.dim(provisioningNote));
2413
2183
  }
2414
2184
  spinner = ora5(createSpinnerText(runtimeFamily, filesystemSettings, 0)).start();
2415
2185
  startTime = Date.now();
@@ -2447,7 +2217,12 @@ var createCommand = new Command5("create").description("Create a computer").argu
2447
2217
  }
2448
2218
  const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
2449
2219
  spinner.succeed(
2450
- chalk6.green(`Created ${chalk6.bold(computer.handle)} ${chalk6.dim(`[${elapsed}s]`)}`)
2220
+ chalk4.green(`Created ${chalk4.bold(computer.handle)} ${chalk4.dim(`[${elapsed}s]`)}`)
2221
+ );
2222
+ await notifyMountDaemon(
2223
+ getMountPaths((readMountConfig() ?? defaultMountServiceConfig()).rootPath)
2224
+ ).catch(
2225
+ () => false
2451
2226
  );
2452
2227
  printComputer(computer);
2453
2228
  } catch (error) {
@@ -2456,10 +2231,10 @@ var createCommand = new Command5("create").description("Create a computer").argu
2456
2231
  }
2457
2232
  const message = error instanceof Error ? error.message : "Failed to create computer";
2458
2233
  if (spinner) {
2459
- const suffix = startTime ? ` ${chalk6.dim(`[${((Date.now() - startTime) / 1e3).toFixed(1)}s]`)}` : "";
2234
+ const suffix = startTime ? ` ${chalk4.dim(`[${((Date.now() - startTime) / 1e3).toFixed(1)}s]`)}` : "";
2460
2235
  spinner.fail(`${message}${suffix}`);
2461
2236
  } else {
2462
- console.error(chalk6.red(message));
2237
+ console.error(chalk4.red(message));
2463
2238
  }
2464
2239
  process.exit(1);
2465
2240
  }
@@ -2471,11 +2246,11 @@ async function resolveCreateOptions(options) {
2471
2246
  validateCreateOptions(selectedOptions);
2472
2247
  return selectedOptions;
2473
2248
  }
2474
- const runtimeChoice = await select2({
2249
+ const runtimeChoice = await select({
2475
2250
  message: "Select runtime",
2476
2251
  choices: [
2477
2252
  {
2478
- name: "managed-worker - default Ubuntu desktop, SSH, VNC, terminal",
2253
+ name: "managed-worker - default Ubuntu desktop, SSH, and VNC",
2479
2254
  value: "managed-worker"
2480
2255
  },
2481
2256
  {
@@ -2491,7 +2266,7 @@ async function resolveCreateOptions(options) {
2491
2266
  selectedOptions.imageRef = (await textInput2({ message: "OCI image ref (required):" })).trim();
2492
2267
  selectedOptions.primaryPort = (await textInput2({ message: "Primary port:", default: "3000" })).trim();
2493
2268
  selectedOptions.primaryPath = (await textInput2({ message: "Primary path:", default: "/" })).trim();
2494
- selectedOptions.healthcheckType = await select2({
2269
+ selectedOptions.healthcheckType = await select({
2495
2270
  message: "Healthcheck type",
2496
2271
  choices: [
2497
2272
  { name: "tcp", value: "tcp" },
@@ -2522,11 +2297,11 @@ var removeCommand = new Command5("rm").description("Delete a computer").argument
2522
2297
  spinner.stop();
2523
2298
  if (!skipConfirm && process.stdin.isTTY) {
2524
2299
  const confirmed = await confirm({
2525
- message: `Delete computer ${chalk6.bold(computer.handle)}?`,
2300
+ message: `Delete computer ${chalk4.bold(computer.handle)}?`,
2526
2301
  default: false
2527
2302
  });
2528
2303
  if (!confirmed) {
2529
- console.log(chalk6.dim(" Cancelled."));
2304
+ console.log(chalk4.dim(" Cancelled."));
2530
2305
  return;
2531
2306
  }
2532
2307
  }
@@ -2534,7 +2309,12 @@ var removeCommand = new Command5("rm").description("Delete a computer").argument
2534
2309
  await api(`/v1/computers/${computer.id}`, {
2535
2310
  method: "DELETE"
2536
2311
  });
2537
- deleteSpinner.succeed(chalk6.green(`Deleted ${chalk6.bold(computer.handle)}`));
2312
+ deleteSpinner.succeed(chalk4.green(`Deleted ${chalk4.bold(computer.handle)}`));
2313
+ await notifyMountDaemon(
2314
+ getMountPaths((readMountConfig() ?? defaultMountServiceConfig()).rootPath)
2315
+ ).catch(
2316
+ () => false
2317
+ );
2538
2318
  } catch (error) {
2539
2319
  spinner.fail(
2540
2320
  error instanceof Error ? error.message : "Failed to delete computer"
@@ -2590,9 +2370,9 @@ function createMachineSourceNote(runtimeFamily, machineSourceSettings, usePlatfo
2590
2370
  return `Using managed-worker image source: ${summarizeMachineSourceSelection(machineSourceSettings)}.`;
2591
2371
  }
2592
2372
  function createSpinnerText(runtimeFamily, filesystemSettings, elapsedSeconds) {
2593
- const elapsedLabel = chalk6.dim(`${elapsedSeconds.toFixed(1)}s`);
2373
+ const elapsedLabel = chalk4.dim(`${elapsedSeconds.toFixed(1)}s`);
2594
2374
  if (runtimeFamily === "managed-worker" && filesystemSettings?.shared_enabled && elapsedSeconds >= 5) {
2595
- return `Creating computer... ${elapsedLabel} ${chalk6.dim("mounting shared home")}`;
2375
+ return `Creating computer... ${elapsedLabel} ${chalk4.dim("mounting shared home")}`;
2596
2376
  }
2597
2377
  return `Creating computer... ${elapsedLabel}`;
2598
2378
  }
@@ -2667,6 +2447,7 @@ _computer() {
2667
2447
  local -a commands
2668
2448
  commands=(
2669
2449
  'login:Authenticate the CLI'
2450
+ 'upgrade:Update the CLI to the latest version'
2670
2451
  'logout:Remove stored API key'
2671
2452
  'whoami:Show current user'
2672
2453
  'claude-login:Authenticate Claude Code on a computer'
@@ -2680,8 +2461,8 @@ _computer() {
2680
2461
  'open:Open in browser'
2681
2462
  'ssh:SSH into a computer'
2682
2463
  'ports:Manage published ports'
2464
+ 'mount:Mirror SSH-ready machine homes into ~/agentcomputer while running'
2683
2465
  'agent:Manage cloud agent sessions'
2684
- 'fleet:View agent activity across your fleet'
2685
2466
  'acp:Run a local ACP bridge for remote agent sessions'
2686
2467
  'rm:Delete a computer'
2687
2468
  'completion:Generate shell completions'
@@ -2704,6 +2485,11 @@ _computer() {
2704
2485
  'rm:Delete a machine image source'
2705
2486
  )
2706
2487
 
2488
+ local -a mount_commands
2489
+ mount_commands=(
2490
+ 'status:Show machine mount controller status'
2491
+ )
2492
+
2707
2493
  _arguments -C \\
2708
2494
  '(-h --help)'{-h,--help}'[Display help]' \\
2709
2495
  '(-V --version)'{-V,--version}'[Show version]' \\
@@ -2808,12 +2594,33 @@ _computer() {
2808
2594
  open)
2809
2595
  _arguments \\
2810
2596
  '--vnc[Open VNC desktop]' \\
2811
- '--terminal[Open terminal]' \\
2812
2597
  '1:computer:_computer_handles'
2813
2598
  ;;
2814
2599
  ssh)
2815
2600
  _arguments '1:computer:_computer_handles'
2816
2601
  ;;
2602
+ mount)
2603
+ _arguments -C \\
2604
+ '--alias[SSH host alias]:alias:' \\
2605
+ '--host[SSH gateway host]:host:' \\
2606
+ '--port[SSH gateway port]:port:' \\
2607
+ '--poll-interval[Reconcile interval in milliseconds]:ms:' \\
2608
+ '--connect-timeout[SSH connect timeout for Mutagen]:seconds:' \\
2609
+ '1:command:->mount_command' \\
2610
+ '*::arg:->mount_args'
2611
+ case "$state" in
2612
+ mount_command)
2613
+ _describe -t commands 'mount command' mount_commands
2614
+ ;;
2615
+ mount_args)
2616
+ case "$words[2]" in
2617
+ status)
2618
+ _arguments
2619
+ ;;
2620
+ esac
2621
+ ;;
2622
+ esac
2623
+ ;;
2817
2624
  rm)
2818
2625
  _arguments \\
2819
2626
  '(-y --yes)'{-y,--yes}'[Skip confirmation]' \\
@@ -2877,9 +2684,10 @@ var BASH_SCRIPT = `_computer() {
2877
2684
  local cur prev words cword
2878
2685
  _init_completion || return
2879
2686
 
2880
- local commands="login logout whoami claude-login claude-auth codex-login codex-auth create ls get image open ssh ports agent fleet acp rm completion help"
2687
+ local commands="login upgrade logout whoami claude-login claude-auth codex-login codex-auth create ls get image open ssh ports mount agent acp rm completion help"
2881
2688
  local ports_commands="ls publish rm"
2882
2689
  local image_commands="ls save default rebuild rm"
2690
+ local mount_commands="status"
2883
2691
 
2884
2692
  if [[ $cword -eq 1 ]]; then
2885
2693
  COMPREPLY=($(compgen -W "$commands" -- "$cur"))
@@ -2957,11 +2765,18 @@ var BASH_SCRIPT = `_computer() {
2957
2765
  else
2958
2766
  case "$cmd" in
2959
2767
  get) COMPREPLY=($(compgen -W "--json" -- "$cur")) ;;
2960
- open) COMPREPLY=($(compgen -W "--vnc --terminal" -- "$cur")) ;;
2768
+ open) COMPREPLY=($(compgen -W "--vnc" -- "$cur")) ;;
2961
2769
  rm) COMPREPLY=($(compgen -W "--yes -y" -- "$cur")) ;;
2962
2770
  esac
2963
2771
  fi
2964
2772
  ;;
2773
+ mount)
2774
+ if [[ $cword -eq 2 ]]; then
2775
+ COMPREPLY=($(compgen -W "$mount_commands" -- "$cur"))
2776
+ elif [[ $cword -ge 3 ]]; then
2777
+ COMPREPLY=($(compgen -W "--alias --host --port --poll-interval --connect-timeout" -- "$cur"))
2778
+ fi
2779
+ ;;
2965
2780
  ports)
2966
2781
  if [[ $cword -eq 2 ]]; then
2967
2782
  COMPREPLY=($(compgen -W "$ports_commands" -- "$cur"))
@@ -2997,17 +2812,17 @@ var completionCommand = new Command6("completion").description("Generate shell c
2997
2812
  // src/commands/codex-login.ts
2998
2813
  import { spawn as spawn3 } from "child_process";
2999
2814
  import { readFile as readFile3 } from "fs/promises";
3000
- import { homedir as homedir4 } from "os";
3001
- import { join as join3 } from "path";
2815
+ import { homedir as homedir3 } from "os";
2816
+ import { join as join2 } from "path";
3002
2817
  import { Command as Command7 } from "commander";
3003
- import chalk7 from "chalk";
2818
+ import chalk5 from "chalk";
3004
2819
  import ora6 from "ora";
3005
2820
  var codexLoginCommand = new Command7("codex-login").alias("codex-auth").description("Authenticate Codex on a computer").option("--machine <id-or-handle>", "Use a specific computer").option("--keep-helper", "Keep a temporary helper machine if one is created").option("--skip-cross-check", "Skip verification on a second shared machine").option("--verbose", "Show step-by-step auth diagnostics").action(async (options) => {
3006
2821
  try {
3007
2822
  await runCodexLogin(options);
3008
2823
  } catch (error) {
3009
2824
  const message = error instanceof Error ? error.message : "Failed to authenticate Codex";
3010
- console.error(chalk7.red(`
2825
+ console.error(chalk5.red(`
3011
2826
  ${message}`));
3012
2827
  process.exit(1);
3013
2828
  }
@@ -3020,7 +2835,7 @@ async function runCodexLogin(options) {
3020
2835
  let activeTodoID = "target";
3021
2836
  let failureMessage = null;
3022
2837
  console.log();
3023
- console.log(chalk7.cyan("Authenticating with Codex...\n"));
2838
+ console.log(chalk5.cyan("Authenticating with Codex...\n"));
3024
2839
  try {
3025
2840
  const prepared = await prepareTargetMachine2(options);
3026
2841
  target = prepared.computer;
@@ -3105,7 +2920,7 @@ async function runCodexLogin(options) {
3105
2920
  }
3106
2921
  if (target) {
3107
2922
  console.log(
3108
- chalk7.green(`Codex login installed on ${chalk7.bold(target.handle)}.`)
2923
+ chalk5.green(`Codex login installed on ${chalk5.bold(target.handle)}.`)
3109
2924
  );
3110
2925
  console.log();
3111
2926
  }
@@ -3146,11 +2961,11 @@ function markVerificationTodo2(items, id, result, successDetail) {
3146
2961
  }
3147
2962
  function printTodoList2(items) {
3148
2963
  console.log();
3149
- console.log(chalk7.dim("TODO"));
2964
+ console.log(chalk5.dim("TODO"));
3150
2965
  console.log();
3151
2966
  for (const item of items) {
3152
- const marker = item.state === "done" ? chalk7.green("[x]") : item.state === "skipped" ? chalk7.yellow("[-]") : item.state === "failed" ? chalk7.red("[!]") : chalk7.dim("[ ]");
3153
- const detail = item.detail ? chalk7.dim(` ${item.detail}`) : "";
2967
+ const marker = item.state === "done" ? chalk5.green("[x]") : item.state === "skipped" ? chalk5.yellow("[-]") : item.state === "failed" ? chalk5.red("[!]") : chalk5.dim("[ ]");
2968
+ const detail = item.detail ? chalk5.dim(` ${item.detail}`) : "";
3154
2969
  console.log(` ${marker} ${item.label}${detail ? ` ${detail}` : ""}`);
3155
2970
  }
3156
2971
  console.log();
@@ -3198,7 +3013,7 @@ async function getLocalCodexStatus() {
3198
3013
  return parseCodexStatusOutput(result.stdout, result.stderr);
3199
3014
  }
3200
3015
  async function readLocalCodexAuthFile() {
3201
- const authPath = join3(homedir4(), ".codex", "auth.json");
3016
+ const authPath = join2(homedir3(), ".codex", "auth.json");
3202
3017
  let raw;
3203
3018
  try {
3204
3019
  raw = await readFile3(authPath, "utf8");
@@ -3261,12 +3076,12 @@ async function captureLocalCommand(command, args) {
3261
3076
  }
3262
3077
  async function installCodexAuth(target, authJSON) {
3263
3078
  const spinner = ora6(
3264
- `Installing Codex login on ${chalk7.bold(target.handle)}...`
3079
+ `Installing Codex login on ${chalk5.bold(target.handle)}...`
3265
3080
  ).start();
3266
3081
  try {
3267
3082
  const installScript = buildInstallScript2(authJSON);
3268
3083
  await runRemoteCommand(target, ["bash", "-s"], installScript);
3269
- spinner.succeed(`Installed Codex login on ${chalk7.bold(target.handle)}`);
3084
+ spinner.succeed(`Installed Codex login on ${chalk5.bold(target.handle)}`);
3270
3085
  } catch (error) {
3271
3086
  spinner.fail(
3272
3087
  error instanceof Error ? error.message : `Failed to install Codex login on ${target.handle}`
@@ -3276,11 +3091,11 @@ async function installCodexAuth(target, authJSON) {
3276
3091
  }
3277
3092
  async function verifyTargetMachine2(handle, target) {
3278
3093
  const spinner = ora6(
3279
- `Verifying Codex login on ${chalk7.bold(handle)}...`
3094
+ `Verifying Codex login on ${chalk5.bold(handle)}...`
3280
3095
  ).start();
3281
3096
  const result = await verifyStoredCodexAuth(target);
3282
3097
  if (result.status === "verified") {
3283
- spinner.succeed(`Verified Codex login on ${chalk7.bold(handle)}`);
3098
+ spinner.succeed(`Verified Codex login on ${chalk5.bold(handle)}`);
3284
3099
  return result;
3285
3100
  }
3286
3101
  spinner.warn(result.detail);
@@ -3288,7 +3103,7 @@ async function verifyTargetMachine2(handle, target) {
3288
3103
  }
3289
3104
  async function verifySharedInstall2(primaryHandle, primaryComputerID, sharedInstall, skip, verify) {
3290
3105
  const spinner = ora6(
3291
- `Verifying shared-home Codex login from ${chalk7.bold(primaryHandle)}...`
3106
+ `Verifying shared-home Codex login from ${chalk5.bold(primaryHandle)}...`
3292
3107
  ).start();
3293
3108
  const result = await verifySecondaryMachine(
3294
3109
  primaryComputerID,
@@ -3298,7 +3113,7 @@ async function verifySharedInstall2(primaryHandle, primaryComputerID, sharedInst
3298
3113
  );
3299
3114
  if (result.status === "verified") {
3300
3115
  spinner.succeed(
3301
- `Verified shared-home Codex login on ${chalk7.bold(result.handle)}`
3116
+ `Verified shared-home Codex login on ${chalk5.bold(result.handle)}`
3302
3117
  );
3303
3118
  return result;
3304
3119
  }
@@ -3356,9 +3171,9 @@ function firstStatusLine2(value) {
3356
3171
  }
3357
3172
 
3358
3173
  // src/commands/images.ts
3359
- import { confirm as confirm2, input as textInput3, select as select3 } from "@inquirer/prompts";
3174
+ import { confirm as confirm2, input as textInput3, select as select2 } from "@inquirer/prompts";
3360
3175
  import { Command as Command8 } from "commander";
3361
- import chalk8 from "chalk";
3176
+ import chalk6 from "chalk";
3362
3177
  import ora7 from "ora";
3363
3178
  var imageCommand = new Command8("image").description("Manage machine image sources");
3364
3179
  imageCommand.command("ls").description("List machine image sources").option("--json", "Print raw JSON").action(async (options) => {
@@ -3391,7 +3206,7 @@ imageCommand.command("save").description("Create or update a machine image sourc
3391
3206
  return;
3392
3207
  }
3393
3208
  console.log();
3394
- console.log(chalk8.green("Saved machine image source."));
3209
+ console.log(chalk6.green("Saved machine image source."));
3395
3210
  printMachineSourceSettings(settings);
3396
3211
  } catch (error) {
3397
3212
  if (spinner) {
@@ -3430,9 +3245,9 @@ imageCommand.command("default").description("Set the default machine image sourc
3430
3245
  if (!usePlatformDefault) {
3431
3246
  const selected = settings.default_machine_source ?? void 0;
3432
3247
  const label = selected ? summarizeMachineSource(selected) : sourceID;
3433
- console.log(chalk8.green(`Selected ${chalk8.bold(label)} as the default machine image.`));
3248
+ console.log(chalk6.green(`Selected ${chalk6.bold(label)} as the default machine image.`));
3434
3249
  } else {
3435
- console.log(chalk8.green("Using the AgentComputer platform default image."));
3250
+ console.log(chalk6.green("Using the AgentComputer platform default image."));
3436
3251
  }
3437
3252
  printMachineSourceSettings(settings);
3438
3253
  } catch (error) {
@@ -3454,7 +3269,7 @@ imageCommand.command("rebuild").description("Rebuild a machine image source").ar
3454
3269
  return;
3455
3270
  }
3456
3271
  console.log();
3457
- console.log(chalk8.green(`Queued rebuild for ${chalk8.bold(sourceID)}.`));
3272
+ console.log(chalk6.green(`Queued rebuild for ${chalk6.bold(sourceID)}.`));
3458
3273
  printMachineSourceSettings(settings);
3459
3274
  } catch (error) {
3460
3275
  if (spinner) {
@@ -3473,7 +3288,7 @@ imageCommand.command("rm").description("Delete a machine image source").argument
3473
3288
  if (!skipConfirm && process.stdin.isTTY) {
3474
3289
  const confirmed = await confirmDeletion(sourceID);
3475
3290
  if (!confirmed) {
3476
- console.log(chalk8.dim(" Cancelled."));
3291
+ console.log(chalk6.dim(" Cancelled."));
3477
3292
  return;
3478
3293
  }
3479
3294
  }
@@ -3485,7 +3300,7 @@ imageCommand.command("rm").description("Delete a machine image source").argument
3485
3300
  return;
3486
3301
  }
3487
3302
  console.log();
3488
- console.log(chalk8.green(`Deleted machine image source ${chalk8.bold(sourceID)}.`));
3303
+ console.log(chalk6.green(`Deleted machine image source ${chalk6.bold(sourceID)}.`));
3489
3304
  printMachineSourceSettings(settings);
3490
3305
  } catch (error) {
3491
3306
  if (spinner) {
@@ -3497,14 +3312,14 @@ imageCommand.command("rm").description("Delete a machine image source").argument
3497
3312
  }
3498
3313
  });
3499
3314
  function printMachineSourceSettings(settings) {
3500
- console.log(` ${chalk8.dim("Default")} ${chalk8.white(summarizeDefaultMachineSource(settings))}`);
3315
+ console.log(` ${chalk6.dim("Default")} ${chalk6.white(summarizeDefaultMachineSource(settings))}`);
3501
3316
  console.log();
3502
3317
  if (settings.sources.length === 0) {
3503
- console.log(chalk8.dim(" No custom machine images configured yet."));
3318
+ console.log(chalk6.dim(" No custom machine images configured yet."));
3504
3319
  console.log();
3505
3320
  return;
3506
3321
  }
3507
- console.log(` ${chalk8.dim("Custom")} ${chalk8.white(formatMachineSourceCounts(settings.sources))}`);
3322
+ console.log(` ${chalk6.dim("Custom")} ${chalk6.white(formatMachineSourceCounts(settings.sources))}`);
3508
3323
  console.log();
3509
3324
  for (const source of sortMachineSources(settings.sources, settings.default_machine_source_id)) {
3510
3325
  printMachineSourceCard(source, settings.default_machine_source_id === source.id);
@@ -3519,17 +3334,17 @@ function printMachineSourceCard(source, isDefault) {
3519
3334
  ];
3520
3335
  const extra = machineSourceExtraParts(source);
3521
3336
  console.log(
3522
- ` ${statusLabel}${chalk8.bold(machineSourceTitle(source))}${isDefault ? chalk8.green(" default") : ""}`
3337
+ ` ${statusLabel}${chalk6.bold(machineSourceTitle(source))}${isDefault ? chalk6.green(" default") : ""}`
3523
3338
  );
3524
- console.log(` ${chalk8.dim(meta.concat(extra).join(" | "))}`);
3525
- console.log(` ${chalk8.dim(machineSourceStatusSummary(source))}`);
3339
+ console.log(` ${chalk6.dim(meta.concat(extra).join(" | "))}`);
3340
+ console.log(` ${chalk6.dim(machineSourceStatusSummary(source))}`);
3526
3341
  if (source.resolved_image_ref) {
3527
- console.log(` ${chalk8.dim("resolved")} ${source.resolved_image_ref}`);
3342
+ console.log(` ${chalk6.dim("resolved")} ${source.resolved_image_ref}`);
3528
3343
  } else if (source.last_good_resolved_image_ref) {
3529
- console.log(` ${chalk8.dim("last good")} ${source.last_good_resolved_image_ref}`);
3344
+ console.log(` ${chalk6.dim("last good")} ${source.last_good_resolved_image_ref}`);
3530
3345
  }
3531
3346
  if (source.error) {
3532
- console.log(` ${chalk8.red(source.error)}`);
3347
+ console.log(` ${chalk6.red(source.error)}`);
3533
3348
  }
3534
3349
  console.log();
3535
3350
  }
@@ -3599,13 +3414,13 @@ function formatMachineSourceStatus(status) {
3599
3414
  const text = status.toUpperCase();
3600
3415
  switch (status) {
3601
3416
  case "ready":
3602
- return chalk8.green(text);
3417
+ return chalk6.green(text);
3603
3418
  case "failed":
3604
- return chalk8.red(text);
3419
+ return chalk6.red(text);
3605
3420
  case "pending":
3606
3421
  case "resolving":
3607
3422
  case "building":
3608
- return chalk8.yellow(text);
3423
+ return chalk6.yellow(text);
3609
3424
  default:
3610
3425
  return text;
3611
3426
  }
@@ -3714,7 +3529,7 @@ async function resolveSaveInput(options) {
3714
3529
  return input;
3715
3530
  }
3716
3531
  async function selectMachineSourceKind() {
3717
- const kind = await select3({
3532
+ const kind = await select2({
3718
3533
  message: "Select machine image kind",
3719
3534
  choices: [
3720
3535
  { name: "oci-image - resolve an OCI image digest", value: "oci-image" },
@@ -3748,7 +3563,7 @@ async function confirmDeletion(sourceID) {
3748
3563
 
3749
3564
  // src/commands/login.ts
3750
3565
  import { Command as Command9 } from "commander";
3751
- import chalk9 from "chalk";
3566
+ import chalk7 from "chalk";
3752
3567
  import ora8 from "ora";
3753
3568
 
3754
3569
  // src/lib/browser-login.ts
@@ -4021,7 +3836,7 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
4021
3836
  if (existingKey && !options.force) {
4022
3837
  console.log();
4023
3838
  console.log(
4024
- chalk9.yellow(" Already logged in. Use --force to overwrite.")
3839
+ chalk7.yellow(" Already logged in. Use --force to overwrite.")
4025
3840
  );
4026
3841
  console.log();
4027
3842
  return;
@@ -4030,8 +3845,8 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
4030
3845
  const apiKey = await resolveAPIKeyInput(options.apiKey, options.stdin);
4031
3846
  if (!apiKey && wantsManualLogin) {
4032
3847
  console.log();
4033
- console.log(chalk9.dim(" Usage: computer login --api-key <ac_live_...>"));
4034
- console.log(chalk9.dim(` API: ${getBaseURL()}`));
3848
+ console.log(chalk7.dim(" Usage: computer login --api-key <ac_live_...>"));
3849
+ console.log(chalk7.dim(` API: ${getBaseURL()}`));
4035
3850
  console.log();
4036
3851
  process.exit(1);
4037
3852
  }
@@ -4041,7 +3856,7 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
4041
3856
  }
4042
3857
  if (!apiKey.startsWith("ac_live_")) {
4043
3858
  console.log();
4044
- console.log(chalk9.red(" API key must start with ac_live_"));
3859
+ console.log(chalk7.red(" API key must start with ac_live_"));
4045
3860
  console.log();
4046
3861
  process.exit(1);
4047
3862
  }
@@ -4049,7 +3864,7 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
4049
3864
  try {
4050
3865
  const me = await apiWithKey(apiKey, "/v1/me");
4051
3866
  setAPIKey(apiKey);
4052
- spinner.succeed(`Logged in as ${chalk9.bold(me.user.email)}`);
3867
+ spinner.succeed(`Logged in as ${chalk7.bold(me.user.email)}`);
4053
3868
  } catch (error) {
4054
3869
  spinner.fail(
4055
3870
  error instanceof Error ? error.message : "Failed to validate API key"
@@ -4069,15 +3884,15 @@ async function runBrowserLogin() {
4069
3884
  spinner.stop();
4070
3885
  console.log();
4071
3886
  console.log(
4072
- chalk9.yellow(" Browser auto-open failed. Open this URL to continue:")
3887
+ chalk7.yellow(" Browser auto-open failed. Open this URL to continue:")
4073
3888
  );
4074
- console.log(chalk9.dim(` ${attempt.loginURL}`));
3889
+ console.log(chalk7.dim(` ${attempt.loginURL}`));
4075
3890
  console.log();
4076
3891
  spinner.start("Waiting for browser login...");
4077
3892
  }
4078
3893
  spinner.text = "Waiting for browser login...";
4079
3894
  const result = await attempt.waitForResult();
4080
- spinner.succeed(`Logged in as ${chalk9.bold(result.me.user.email)}`);
3895
+ spinner.succeed(`Logged in as ${chalk7.bold(result.me.user.email)}`);
4081
3896
  await continueFirstLoginFlow(result);
4082
3897
  } catch (error) {
4083
3898
  spinner.fail(
@@ -4111,8 +3926,8 @@ async function continueFirstLoginFlow(result) {
4111
3926
  }
4112
3927
  console.log();
4113
3928
  console.log(
4114
- chalk9.cyan(
4115
- `Continuing first-time setup for ${chalk9.bold(machineHandle)}...
3929
+ chalk7.cyan(
3930
+ `Continuing first-time setup for ${chalk7.bold(machineHandle)}...
4116
3931
  `
4117
3932
  )
4118
3933
  );
@@ -4125,8 +3940,8 @@ async function continueFirstLoginFlow(result) {
4125
3940
  const spinner = ora8(`Preparing SSH access for ${machineHandle}...`).start();
4126
3941
  try {
4127
3942
  const connection = await prepareSSHConnectionByIdentifier(machineHandle);
4128
- spinner.succeed(`Connecting to ${chalk9.bold(machineHandle)}`);
4129
- console.log(chalk9.dim(` ${connection.command}`));
3943
+ spinner.succeed(`Connecting to ${chalk7.bold(machineHandle)}`);
3944
+ console.log(chalk7.dim(` ${connection.command}`));
4130
3945
  console.log();
4131
3946
  await openSSHConnection(connection);
4132
3947
  } catch (error) {
@@ -4137,19 +3952,19 @@ async function continueFirstLoginFlow(result) {
4137
3952
  }
4138
3953
  } catch (error) {
4139
3954
  const message = error instanceof Error ? error.message : "Failed to finish first-time setup";
4140
- console.error(chalk9.red(`
3955
+ console.error(chalk7.red(`
4141
3956
  ${message}`));
4142
3957
  console.log();
4143
3958
  if (result.provider === "claude") {
4144
3959
  console.log(
4145
- chalk9.dim(` computer claude-login --machine ${machineHandle}`)
3960
+ chalk7.dim(` computer claude-login --machine ${machineHandle}`)
4146
3961
  );
4147
3962
  } else if (result.provider === "codex") {
4148
3963
  console.log(
4149
- chalk9.dim(` computer codex-login --machine ${machineHandle}`)
3964
+ chalk7.dim(` computer codex-login --machine ${machineHandle}`)
4150
3965
  );
4151
3966
  }
4152
- console.log(chalk9.dim(` computer ssh ${machineHandle}`));
3967
+ console.log(chalk7.dim(` computer ssh ${machineHandle}`));
4153
3968
  console.log();
4154
3969
  process.exit(1);
4155
3970
  }
@@ -4163,43 +3978,451 @@ async function runSelectedProvider(provider, machineHandle) {
4163
3978
  await runCodexLogin({ machine: machineHandle });
4164
3979
  return;
4165
3980
  }
4166
- console.log(chalk9.green(`Sandbox ${chalk9.bold(machineHandle)} is ready.`));
3981
+ console.log(chalk7.green(`Sandbox ${chalk7.bold(machineHandle)} is ready.`));
4167
3982
  console.log();
4168
3983
  }
4169
3984
  function printNextStep(machineHandle) {
4170
- console.log(chalk9.green(`Sandbox ${chalk9.bold(machineHandle)} is ready.`));
4171
- console.log(chalk9.dim(` computer ssh ${machineHandle}`));
3985
+ console.log(chalk7.green(`Sandbox ${chalk7.bold(machineHandle)} is ready.`));
3986
+ console.log(chalk7.dim(` computer ssh ${machineHandle}`));
4172
3987
  console.log();
4173
3988
  }
4174
3989
 
4175
- // src/commands/logout.ts
3990
+ // src/commands/mount.ts
4176
3991
  import { Command as Command10 } from "commander";
4177
- import chalk10 from "chalk";
4178
- var logoutCommand = new Command10("logout").description("Remove stored API key").action(() => {
3992
+ import chalk8 from "chalk";
3993
+ import ora9 from "ora";
3994
+
3995
+ // src/lib/mount-daemon.ts
3996
+ import { mkdir as mkdir3 } from "fs/promises";
3997
+ function getMountControllerState(rootPath = defaultMountServiceConfig().rootPath) {
3998
+ const lock = readMountControllerLock(rootPath);
3999
+ if (!lock) {
4000
+ return { running: false };
4001
+ }
4002
+ if (!processExists(lock.pid)) {
4003
+ removeMountControllerLock(rootPath);
4004
+ return { running: false };
4005
+ }
4006
+ return { running: true, pid: lock.pid };
4007
+ }
4008
+ async function runMountDaemon(config) {
4009
+ const paths = getMountPaths(config.rootPath);
4010
+ ensureMountDirectories(paths);
4011
+ await mkdir3(paths.rootPath, { recursive: true });
4012
+ await acquireControllerLock(config.rootPath);
4013
+ writeMountStatusSnapshot(
4014
+ {
4015
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4016
+ controllerPid: process.pid,
4017
+ running: true,
4018
+ mounts: []
4019
+ },
4020
+ config.rootPath
4021
+ );
4022
+ await teardownManagedSessions(config, paths);
4023
+ let running = false;
4024
+ let queued = false;
4025
+ let shuttingDown = false;
4026
+ let activeRun = null;
4027
+ const runOnce = async () => {
4028
+ if (shuttingDown) {
4029
+ return;
4030
+ }
4031
+ if (running) {
4032
+ queued = true;
4033
+ return activeRun ?? void 0;
4034
+ }
4035
+ activeRun = (async () => {
4036
+ running = true;
4037
+ try {
4038
+ await reconcileMounts(config, paths, process.pid);
4039
+ } catch (error) {
4040
+ if (shuttingDown) {
4041
+ return;
4042
+ }
4043
+ const previous = readMountStatusSnapshot(config.rootPath);
4044
+ writeMountStatusSnapshot(
4045
+ {
4046
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4047
+ controllerPid: process.pid,
4048
+ running: true,
4049
+ lastSuccessfulSyncAt: previous?.lastSuccessfulSyncAt,
4050
+ lastError: error instanceof Error ? error.message : "mount reconcile failed",
4051
+ mounts: previous?.mounts ?? []
4052
+ },
4053
+ config.rootPath
4054
+ );
4055
+ console.error(
4056
+ error instanceof Error ? error.message : "mount reconcile failed"
4057
+ );
4058
+ } finally {
4059
+ running = false;
4060
+ activeRun = null;
4061
+ if (queued && !shuttingDown) {
4062
+ queued = false;
4063
+ void runOnce();
4064
+ }
4065
+ }
4066
+ })();
4067
+ return activeRun;
4068
+ };
4069
+ const server = createMountControlServer(paths, async () => {
4070
+ await runOnce();
4071
+ });
4072
+ await new Promise((resolve, reject) => {
4073
+ server.once("error", reject);
4074
+ server.listen(paths.socketPath, () => resolve());
4075
+ });
4076
+ const interval = setInterval(() => {
4077
+ void runOnce();
4078
+ }, config.pollIntervalMs);
4079
+ const shutdown = async () => {
4080
+ if (shuttingDown) {
4081
+ return;
4082
+ }
4083
+ shuttingDown = true;
4084
+ clearInterval(interval);
4085
+ server.close();
4086
+ if (activeRun) {
4087
+ await activeRun.catch(() => {
4088
+ });
4089
+ }
4090
+ await teardownManagedSessions(config, paths);
4091
+ const previous = readMountStatusSnapshot(config.rootPath);
4092
+ writeMountStatusSnapshot(
4093
+ {
4094
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4095
+ controllerPid: previous?.controllerPid,
4096
+ running: false,
4097
+ lastSuccessfulSyncAt: previous?.lastSuccessfulSyncAt,
4098
+ lastError: void 0,
4099
+ mounts: []
4100
+ },
4101
+ config.rootPath
4102
+ );
4103
+ removeMountControllerLock(config.rootPath);
4104
+ process.exit(0);
4105
+ };
4106
+ process.on("SIGINT", () => {
4107
+ void shutdown();
4108
+ });
4109
+ process.on("SIGTERM", () => {
4110
+ void shutdown();
4111
+ });
4112
+ await runOnce();
4113
+ await new Promise(() => {
4114
+ });
4115
+ }
4116
+ async function acquireControllerLock(rootPath) {
4117
+ const existing = readMountControllerLock(rootPath);
4118
+ if (existing && processExists(existing.pid)) {
4119
+ throw new Error(`computer mount is already running (pid ${existing.pid})`);
4120
+ }
4121
+ removeMountControllerLock(rootPath);
4122
+ writeMountControllerLock(
4123
+ {
4124
+ pid: process.pid,
4125
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
4126
+ },
4127
+ rootPath
4128
+ );
4129
+ }
4130
+ function processExists(pid) {
4131
+ if (!Number.isInteger(pid) || pid <= 0) {
4132
+ return false;
4133
+ }
4134
+ try {
4135
+ process.kill(pid, 0);
4136
+ return true;
4137
+ } catch {
4138
+ return false;
4139
+ }
4140
+ }
4141
+
4142
+ // src/commands/mount.ts
4143
+ var mountCommand = new Command10("mount").description("Mirror SSH-ready machines under ~/agentcomputer while this command is running").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").action(async (options) => {
4144
+ const spinner = ora9("Starting machine mount controller...").start();
4145
+ try {
4146
+ const issues = getMountHostValidationIssues();
4147
+ if (issues.length > 0) {
4148
+ throw new Error(
4149
+ [
4150
+ ...issues.map((issue) => issue.message),
4151
+ ...formatMountHostInstallGuidance(issues)
4152
+ ].join("\n")
4153
+ );
4154
+ }
4155
+ const sshSetup = await ensureSSHAccessConfigured(options);
4156
+ const config = {
4157
+ ...defaultMountServiceConfig(),
4158
+ alias: sshSetup.alias,
4159
+ host: sshSetup.host,
4160
+ port: sshSetup.port,
4161
+ pollIntervalMs: parsePositiveInt(options.pollInterval, "poll interval"),
4162
+ connectTimeoutSeconds: parsePositiveInt(
4163
+ options.connectTimeout,
4164
+ "connect timeout"
4165
+ )
4166
+ };
4167
+ writeMountConfig(config);
4168
+ spinner.succeed("Machine mount controller running");
4169
+ console.log();
4170
+ console.log(chalk8.dim(` Root: ${config.rootPath}`));
4171
+ console.log(chalk8.dim(` SSH alias: ${config.alias}`));
4172
+ console.log(chalk8.dim(` Poll: ${config.pollIntervalMs}ms`));
4173
+ console.log();
4174
+ console.log(chalk8.dim(" Press Ctrl-C to stop syncing."));
4175
+ console.log();
4176
+ await runMountDaemon(config);
4177
+ } catch (error) {
4178
+ spinner.fail(
4179
+ error instanceof Error ? error.message : "Failed to start machine mount controller"
4180
+ );
4181
+ process.exit(1);
4182
+ }
4183
+ }).addCommand(
4184
+ new Command10("status").description("Show machine mount controller status").action(() => {
4185
+ const config = readMountConfig() ?? defaultMountServiceConfig();
4186
+ const controller = getMountControllerState(config.rootPath);
4187
+ const snapshot = readMountStatusSnapshot(config.rootPath);
4188
+ console.log();
4189
+ console.log(` ${chalk8.bold("Machine Mounts")}`);
4190
+ console.log();
4191
+ console.log(
4192
+ ` ${chalk8.dim("Running")} ${controller.running ? chalk8.green("yes") : chalk8.dim("no")}`
4193
+ );
4194
+ if (controller.pid) {
4195
+ console.log(` ${chalk8.dim("PID")} ${controller.pid}`);
4196
+ }
4197
+ console.log(` ${chalk8.dim("Root")} ${config.rootPath}`);
4198
+ console.log(` ${chalk8.dim("Alias")} ${config.alias}`);
4199
+ console.log(
4200
+ ` ${chalk8.dim("Updated")} ${snapshot?.updatedAt ? minuteSecondAgo(snapshot.updatedAt) : chalk8.dim("never")}`
4201
+ );
4202
+ console.log(
4203
+ ` ${chalk8.dim("Last sync")} ${snapshot?.lastSuccessfulSyncAt ? minuteSecondAgo(snapshot.lastSuccessfulSyncAt) : chalk8.dim("never")}`
4204
+ );
4205
+ if (controller.running && snapshot?.mounts.length) {
4206
+ console.log();
4207
+ for (const mount of snapshot.mounts) {
4208
+ const state = mount.state === "mounted" ? chalk8.green(mount.state) : mount.state === "pending" ? chalk8.yellow(mount.state) : chalk8.red(mount.state);
4209
+ console.log(` ${chalk8.white(mount.handle)} ${state} ${chalk8.dim(mount.mountPath)}`);
4210
+ if (mount.message) {
4211
+ console.log(` ${chalk8.dim(mount.message)}`);
4212
+ }
4213
+ }
4214
+ }
4215
+ if (snapshot?.lastError) {
4216
+ console.log();
4217
+ console.log(` ${chalk8.dim("Last error")} ${chalk8.red(snapshot.lastError)}`);
4218
+ }
4219
+ console.log();
4220
+ })
4221
+ );
4222
+ function parsePositiveInt(raw, label) {
4223
+ const value = Number(raw);
4224
+ if (!Number.isFinite(value) || value <= 0) {
4225
+ throw new Error(`${label} must be a positive number`);
4226
+ }
4227
+ return Math.round(value);
4228
+ }
4229
+
4230
+ // src/commands/logout.ts
4231
+ import { Command as Command11 } from "commander";
4232
+ import chalk9 from "chalk";
4233
+ var logoutCommand = new Command11("logout").description("Remove stored API key").action(() => {
4179
4234
  if (!getStoredAPIKey()) {
4180
4235
  console.log();
4181
- console.log(chalk10.dim(" Not logged in."));
4236
+ console.log(chalk9.dim(" Not logged in."));
4182
4237
  if (hasEnvAPIKey()) {
4183
- console.log(chalk10.dim(" Environment API key is still active in this shell."));
4238
+ console.log(chalk9.dim(" Environment API key is still active in this shell."));
4184
4239
  }
4185
4240
  console.log();
4186
4241
  return;
4187
4242
  }
4188
4243
  clearAPIKey();
4189
4244
  console.log();
4190
- console.log(chalk10.green(" Logged out."));
4245
+ console.log(chalk9.green(" Logged out."));
4191
4246
  if (hasEnvAPIKey()) {
4192
- console.log(chalk10.dim(" Environment API key is still active in this shell."));
4247
+ console.log(chalk9.dim(" Environment API key is still active in this shell."));
4248
+ }
4249
+ console.log();
4250
+ });
4251
+
4252
+ // src/commands/upgrade.ts
4253
+ import { spawnSync } from "child_process";
4254
+ import { readFileSync as readFileSync2, realpathSync } from "fs";
4255
+ import { Command as Command12 } from "commander";
4256
+ import chalk10 from "chalk";
4257
+ import ora10 from "ora";
4258
+ var pkg2 = JSON.parse(
4259
+ readFileSync2(new URL("../package.json", import.meta.url), "utf8")
4260
+ );
4261
+ function normalizeVersion(version) {
4262
+ return version.split("-")[0].split(".").map((part) => Number.parseInt(part, 10)).map((part) => Number.isNaN(part) ? 0 : part);
4263
+ }
4264
+ function compareVersions(a, b) {
4265
+ const left = normalizeVersion(a);
4266
+ const right = normalizeVersion(b);
4267
+ const size = Math.max(left.length, right.length);
4268
+ for (let index = 0; index < size; index += 1) {
4269
+ const diff = (left[index] ?? 0) - (right[index] ?? 0);
4270
+ if (diff !== 0) {
4271
+ return diff;
4272
+ }
4193
4273
  }
4274
+ return 0;
4275
+ }
4276
+ function resolveExecutablePath() {
4277
+ const candidate = process.argv[1] || process.execPath;
4278
+ try {
4279
+ return realpathSync(candidate);
4280
+ } catch {
4281
+ return candidate;
4282
+ }
4283
+ }
4284
+ function detectInstallMethod(executablePath) {
4285
+ const resolved = `${executablePath}\0${process.execPath}`.toLowerCase();
4286
+ if (resolved.includes("/nix/store/") || resolved.includes("\\nix\\store\\")) {
4287
+ return "nix";
4288
+ }
4289
+ if (resolved.includes("/.pnpm/") || resolved.includes("/pnpm/") || resolved.includes("\\pnpm\\")) {
4290
+ return "pnpm";
4291
+ }
4292
+ if (resolved.includes("/.yarn/") || resolved.includes("/yarn/") || resolved.includes("\\yarn\\")) {
4293
+ return "yarn";
4294
+ }
4295
+ if (resolved.includes("/node_modules/") || resolved.includes("\\node_modules\\")) {
4296
+ return "npm";
4297
+ }
4298
+ return "unknown";
4299
+ }
4300
+ function findNixProfileElement(executablePath) {
4301
+ const result = spawnSync("nix", ["profile", "list", "--json"], {
4302
+ encoding: "utf8"
4303
+ });
4304
+ if (result.status !== 0 || !result.stdout.trim()) {
4305
+ return null;
4306
+ }
4307
+ const profile = JSON.parse(result.stdout);
4308
+ const resolvedExecutable = executablePath.toLowerCase();
4309
+ for (const [name, element] of Object.entries(profile.elements ?? {})) {
4310
+ const storePaths = Array.isArray(element.storePaths) ? element.storePaths.map((storePath) => storePath.toLowerCase()) : [];
4311
+ if (storePaths.some((storePath) => resolvedExecutable.startsWith(storePath))) {
4312
+ return name;
4313
+ }
4314
+ }
4315
+ for (const [name, element] of Object.entries(profile.elements ?? {})) {
4316
+ const originalUrl = String(element.originalUrl ?? "").toLowerCase();
4317
+ if (originalUrl.includes("agentcomputer") || originalUrl.includes("apps/cli")) {
4318
+ return name;
4319
+ }
4320
+ }
4321
+ return null;
4322
+ }
4323
+ function resolveUpgradeCommand(method, executablePath) {
4324
+ const packageName = pkg2.name ?? "aicomputer";
4325
+ switch (method) {
4326
+ case "nix": {
4327
+ const element = findNixProfileElement(executablePath);
4328
+ if (!element) {
4329
+ throw new Error(
4330
+ "Nix install detected, but no matching Nix profile entry was found. Rerun your original Nix install command or update that profile manually."
4331
+ );
4332
+ }
4333
+ return {
4334
+ command: "nix",
4335
+ args: ["profile", "upgrade", element],
4336
+ label: `nix profile upgrade ${element}`
4337
+ };
4338
+ }
4339
+ case "pnpm":
4340
+ return {
4341
+ command: "pnpm",
4342
+ args: ["add", "-g", `${packageName}@latest`],
4343
+ label: `pnpm add -g ${packageName}@latest`
4344
+ };
4345
+ case "yarn":
4346
+ return {
4347
+ command: "yarn",
4348
+ args: ["global", "add", `${packageName}@latest`],
4349
+ label: `yarn global add ${packageName}@latest`
4350
+ };
4351
+ case "npm":
4352
+ case "unknown":
4353
+ return {
4354
+ command: "npm",
4355
+ args: ["install", "-g", `${packageName}@latest`],
4356
+ label: `npm install -g ${packageName}@latest`
4357
+ };
4358
+ }
4359
+ }
4360
+ async function getLatestVersion(packageName) {
4361
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
4362
+ if (!response.ok) {
4363
+ throw new Error(`Failed to check npm registry (${response.status})`);
4364
+ }
4365
+ const payload = await response.json();
4366
+ if (!payload.version) {
4367
+ throw new Error("Registry response missing version");
4368
+ }
4369
+ return payload.version;
4370
+ }
4371
+ var upgradeCommand = new Command12("upgrade").description("Update the CLI to the latest version").action(async () => {
4372
+ const currentVersion = pkg2.version ?? "0.0.0";
4373
+ const packageName = pkg2.name ?? "aicomputer";
4374
+ const spinner = ora10("Checking for updates...").start();
4375
+ let latestVersion;
4376
+ try {
4377
+ latestVersion = await getLatestVersion(packageName);
4378
+ } catch (error) {
4379
+ spinner.fail(
4380
+ error instanceof Error ? error.message : "Failed to check for updates"
4381
+ );
4382
+ process.exit(1);
4383
+ return;
4384
+ }
4385
+ if (compareVersions(latestVersion, currentVersion) <= 0) {
4386
+ spinner.succeed(`You're up to date (v${currentVersion}).`);
4387
+ return;
4388
+ }
4389
+ const executablePath = resolveExecutablePath();
4390
+ const method = detectInstallMethod(executablePath);
4391
+ let upgrade;
4392
+ try {
4393
+ upgrade = resolveUpgradeCommand(method, executablePath);
4394
+ } catch (error) {
4395
+ spinner.fail(
4396
+ error instanceof Error ? error.message : "Failed to prepare upgrade"
4397
+ );
4398
+ process.exit(1);
4399
+ return;
4400
+ }
4401
+ spinner.stop();
4194
4402
  console.log();
4403
+ console.log(
4404
+ chalk10.dim(` Updating ${chalk10.bold(`v${currentVersion}`)} -> ${chalk10.bold(`v${latestVersion}`)}`)
4405
+ );
4406
+ console.log(chalk10.dim(` ${upgrade.label}`));
4407
+ console.log();
4408
+ const result = spawnSync(upgrade.command, upgrade.args, {
4409
+ stdio: "inherit"
4410
+ });
4411
+ if (result.status === 0) {
4412
+ console.log();
4413
+ console.log(chalk10.green(` Updated to v${latestVersion}.`));
4414
+ console.log();
4415
+ return;
4416
+ }
4417
+ process.exit(result.status ?? 1);
4195
4418
  });
4196
4419
 
4197
4420
  // src/commands/whoami.ts
4198
- import { Command as Command11 } from "commander";
4421
+ import { Command as Command13 } from "commander";
4199
4422
  import chalk11 from "chalk";
4200
- import ora9 from "ora";
4201
- var whoamiCommand = new Command11("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
4202
- const spinner = options.json ? null : ora9("Loading user...").start();
4423
+ import ora11 from "ora";
4424
+ var whoamiCommand = new Command13("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
4425
+ const spinner = options.json ? null : ora11("Loading user...").start();
4203
4426
  try {
4204
4427
  const me = await api("/v1/me");
4205
4428
  spinner?.stop();
@@ -4228,11 +4451,11 @@ var whoamiCommand = new Command11("whoami").description("Show current user").opt
4228
4451
  });
4229
4452
 
4230
4453
  // src/index.ts
4231
- var pkg2 = JSON.parse(
4454
+ var pkg3 = JSON.parse(
4232
4455
  readFileSync3(new URL("../package.json", import.meta.url), "utf8")
4233
4456
  );
4234
4457
  var cliName = process.argv[1] ? basename2(process.argv[1]) : "agentcomputer";
4235
- var program = new Command12();
4458
+ var program = new Command14();
4236
4459
  function appendTextSection(lines, title, values) {
4237
4460
  if (values.length === 0) {
4238
4461
  return;
@@ -4266,13 +4489,14 @@ function commandPath(cmd) {
4266
4489
  return parts.join(" ");
4267
4490
  }
4268
4491
  function formatRootHelp(cmd) {
4269
- const version = pkg2.version ?? "0.0.0";
4492
+ const version = pkg3.version ?? "0.0.0";
4270
4493
  const lines = [];
4271
4494
  const groups = [
4272
4495
  ["Auth", []],
4273
4496
  ["Computers", []],
4274
4497
  ["Images", []],
4275
4498
  ["Access", []],
4499
+ ["Mounts", []],
4276
4500
  ["Agents", []],
4277
4501
  ["Other", []]
4278
4502
  ];
@@ -4295,8 +4519,10 @@ function formatRootHelp(cmd) {
4295
4519
  groups[2][1].push(entry);
4296
4520
  } else if (["open", "ssh", "ports"].includes(name)) {
4297
4521
  groups[3][1].push(entry);
4298
- } else if (["agent", "fleet", "acp"].includes(name)) {
4522
+ } else if (name === "mount") {
4299
4523
  groups[4][1].push(entry);
4524
+ } else if (["agent", "acp"].includes(name)) {
4525
+ groups[5][1].push(entry);
4300
4526
  } else {
4301
4527
  otherGroup.push(entry);
4302
4528
  }
@@ -4357,8 +4583,9 @@ function applyHelpFormatting(cmd) {
4357
4583
  applyHelpFormatting(subcommand);
4358
4584
  }
4359
4585
  }
4360
- program.name(cliName).description("Agent Computer CLI").version(pkg2.version ?? "0.0.0").option("-y, --yes", "Skip confirmation prompts");
4586
+ program.name(cliName).description("Agent Computer CLI").version(pkg3.version ?? "0.0.0").option("-y, --yes", "Skip confirmation prompts");
4361
4587
  program.addCommand(loginCommand);
4588
+ program.addCommand(upgradeCommand);
4362
4589
  program.addCommand(logoutCommand);
4363
4590
  program.addCommand(whoamiCommand);
4364
4591
  program.addCommand(claudeLoginCommand);
@@ -4368,11 +4595,11 @@ program.addCommand(lsCommand);
4368
4595
  program.addCommand(getCommand);
4369
4596
  program.addCommand(imageCommand);
4370
4597
  program.addCommand(agentCommand);
4371
- program.addCommand(fleetCommand);
4372
4598
  program.addCommand(acpCommand);
4373
4599
  program.addCommand(openCommand);
4374
4600
  program.addCommand(sshCommand);
4375
4601
  program.addCommand(portsCommand);
4602
+ program.addCommand(mountCommand);
4376
4603
  program.addCommand(removeCommand);
4377
4604
  program.addCommand(completionCommand);
4378
4605
  applyHelpFormatting(program);