aicomputer 0.1.14 → 0.1.16

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,81 @@
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
+ padEnd,
27
+ promptForSSHComputer,
28
+ publishPort,
29
+ reconcileMounts,
30
+ resolveComputer,
31
+ setAPIKey,
32
+ teardownManagedSessions,
33
+ timeAgo,
34
+ vncURL,
35
+ webURL
36
+ } from "./chunk-5IEWKH52.js";
37
+ import {
38
+ defaultMountServiceConfig,
39
+ ensureMountDirectories,
40
+ getMountPaths,
41
+ readMountConfig,
42
+ readMountControllerLock,
43
+ readMountStatusSnapshot,
44
+ removeMountControllerLock,
45
+ writeMountConfig,
46
+ writeMountControllerLock,
47
+ writeMountStatusSnapshot
48
+ } from "./chunk-KXLTHWW3.js";
2
49
 
3
50
  // src/index.ts
4
- import { Command as Command13 } from "commander";
5
- import chalk13 from "chalk";
6
- import { readFileSync as readFileSync4 } from "fs";
51
+ import { Command as Command14 } from "commander";
52
+ import chalk12 from "chalk";
53
+ import { readFileSync as readFileSync3 } from "fs";
7
54
  import { basename as basename2 } from "path";
8
55
 
9
56
  // src/commands/access.ts
10
57
  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
58
  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
- }
59
+ import ora from "ora";
314
60
 
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
- }
61
+ // src/lib/ssh-access.ts
62
+ import { spawn } from "child_process";
382
63
 
383
64
  // src/lib/ssh-keys.ts
384
65
  import { basename } from "path";
385
- import { homedir as homedir3 } from "os";
386
- import { readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
66
+ import { homedir } from "os";
67
+ import { readFile, mkdir } from "fs/promises";
387
68
  import { execFileSync } from "child_process";
388
- import { existsSync as existsSync2 } from "fs";
69
+ import { existsSync } from "fs";
389
70
  var DEFAULT_PUBLIC_KEY_PATHS = [
390
- `${homedir3()}/.ssh/id_ed25519.pub`,
391
- `${homedir3()}/.ssh/id_ecdsa.pub`,
392
- `${homedir3()}/.ssh/id_rsa.pub`
71
+ `${homedir()}/.ssh/id_ed25519.pub`,
72
+ `${homedir()}/.ssh/id_ecdsa.pub`,
73
+ `${homedir()}/.ssh/id_rsa.pub`
393
74
  ];
394
75
  async function ensureDefaultSSHKeyRegistered() {
395
76
  for (const path of DEFAULT_PUBLIC_KEY_PATHS) {
396
77
  try {
397
- const publicKey2 = (await readFile2(path, "utf8")).trim();
78
+ const publicKey2 = (await readFile(path, "utf8")).trim();
398
79
  if (!publicKey2) {
399
80
  continue;
400
81
  }
@@ -418,7 +99,7 @@ async function ensureDefaultSSHKeyRegistered() {
418
99
  }
419
100
  }
420
101
  const generated = await generateSSHKey();
421
- const publicKey = (await readFile2(generated.publicKeyPath, "utf8")).trim();
102
+ const publicKey = (await readFile(generated.publicKeyPath, "utf8")).trim();
422
103
  const key = await api("/v1/ssh-keys", {
423
104
  method: "POST",
424
105
  body: JSON.stringify({
@@ -433,9 +114,9 @@ async function ensureDefaultSSHKeyRegistered() {
433
114
  };
434
115
  }
435
116
  async function generateSSHKey() {
436
- const sshDir = `${homedir3()}/.ssh`;
437
- if (!existsSync2(sshDir)) {
438
- await mkdir2(sshDir, { mode: 448 });
117
+ const sshDir = `${homedir()}/.ssh`;
118
+ if (!existsSync(sshDir)) {
119
+ await mkdir(sshDir, { mode: 448 });
439
120
  }
440
121
  const privateKeyPath = `${sshDir}/id_ed25519`;
441
122
  const publicKeyPath = `${privateKeyPath}.pub`;
@@ -447,7 +128,6 @@ async function generateSSHKey() {
447
128
  }
448
129
 
449
130
  // src/lib/ssh-access.ts
450
- import { spawn } from "child_process";
451
131
  async function prepareSSHConnection(computer) {
452
132
  const registered = await ensureDefaultSSHKeyRegistered();
453
133
  const info = await getConnectionInfo(computer.id);
@@ -504,6 +184,11 @@ import { constants } from "fs";
504
184
  import { access } from "fs/promises";
505
185
  import open from "open";
506
186
  var IMAGE_BROWSER_LAUNCHER = "/usr/local/bin/browser-launcher";
187
+ var IMAGE_BROWSER_CANDIDATES = [
188
+ "/usr/local/bin/google-chrome",
189
+ "/usr/local/bin/chromium",
190
+ IMAGE_BROWSER_LAUNCHER
191
+ ];
507
192
  async function isExecutable(path) {
508
193
  try {
509
194
  await access(path, constants.X_OK);
@@ -518,6 +203,8 @@ function hasBrokenChromeBrowserEnv() {
518
203
  case "chrome":
519
204
  case "google-chrome":
520
205
  case "google chrome":
206
+ case "browser":
207
+ case "browser-launcher":
521
208
  return true;
522
209
  default:
523
210
  return false;
@@ -531,9 +218,12 @@ async function openBrowserURL(url) {
531
218
  if (hasBrokenChromeBrowserEnv()) {
532
219
  delete process.env.BROWSER;
533
220
  }
534
- if (await isExecutable(IMAGE_BROWSER_LAUNCHER)) {
221
+ for (const browserPath of IMAGE_BROWSER_CANDIDATES) {
222
+ if (!await isExecutable(browserPath)) {
223
+ continue;
224
+ }
535
225
  try {
536
- await open(url, { app: { name: IMAGE_BROWSER_LAUNCHER } });
226
+ await open(url, { app: { name: browserPath } });
537
227
  return;
538
228
  } catch {
539
229
  }
@@ -546,39 +236,146 @@ async function openBrowserURL(url) {
546
236
  await open(url);
547
237
  }
548
238
 
239
+ // src/lib/ssh-config.ts
240
+ import { homedir as homedir2 } from "os";
241
+ import { join } from "path";
242
+ import { mkdir as mkdir2, readFile as readFile2, writeFile } from "fs/promises";
243
+ var MANAGED_BLOCK_START = "# >>> agentcomputer ssh setup >>>";
244
+ var MANAGED_BLOCK_END = "# <<< agentcomputer ssh setup <<<";
245
+ async function ensureSSHAliasConfig(options) {
246
+ const sshDir = join(homedir2(), ".ssh");
247
+ const configPath = join(sshDir, "config");
248
+ await mkdir2(sshDir, { recursive: true, mode: 448 });
249
+ let existing = "";
250
+ try {
251
+ existing = await readFile2(configPath, "utf8");
252
+ } catch (error) {
253
+ if (error.code !== "ENOENT") {
254
+ throw error;
255
+ }
256
+ }
257
+ const nextBlock = renderManagedBlock(options);
258
+ const managedBlockPattern = new RegExp(
259
+ `${escapeRegex(MANAGED_BLOCK_START)}[\\s\\S]*?${escapeRegex(MANAGED_BLOCK_END)}\\n?`,
260
+ "m"
261
+ );
262
+ let nextContents;
263
+ if (managedBlockPattern.test(existing)) {
264
+ nextContents = existing.replace(managedBlockPattern, nextBlock);
265
+ } else {
266
+ const normalized = existing.length === 0 ? "" : existing.endsWith("\n") ? existing : `${existing}
267
+ `;
268
+ nextContents = normalized.length === 0 ? nextBlock : `${normalized}
269
+ ${nextBlock}`;
270
+ }
271
+ const changed = nextContents !== existing;
272
+ if (changed) {
273
+ await writeFile(configPath, nextContents, { mode: 384 });
274
+ }
275
+ return {
276
+ configPath,
277
+ changed
278
+ };
279
+ }
280
+ function renderManagedBlock(options) {
281
+ const user = options.user?.trim() || "agentcomputer";
282
+ const identityFile = formatIdentityFilePath(options.identityFilePath);
283
+ return `${MANAGED_BLOCK_START}
284
+ Host ${options.alias}
285
+ HostName ${options.host}
286
+ Port ${options.port}
287
+ User ${user}
288
+ IdentityFile ${identityFile}
289
+ IdentitiesOnly yes
290
+ ServerAliveInterval 30
291
+ ServerAliveCountMax 4
292
+ ${MANAGED_BLOCK_END}
293
+ `;
294
+ }
295
+ function formatIdentityFilePath(path) {
296
+ const normalized = path.trim();
297
+ const homePath = `${homedir2()}/`;
298
+ if (normalized.startsWith(homePath)) {
299
+ return `~/${normalized.slice(homePath.length)}`;
300
+ }
301
+ return normalized.replaceAll(" ", "\\ ");
302
+ }
303
+ function escapeRegex(value) {
304
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
305
+ }
306
+
307
+ // src/lib/ssh-setup.ts
308
+ async function ensureSSHAccessConfigured(options = {}) {
309
+ const alias = normalizeSSHAlias(options.alias ?? "agentcomputer.ai");
310
+ const host = normalizeSSHHost(options.host ?? "ssh.agentcomputer.ai");
311
+ const port = typeof options.port === "number" ? parseSSHPort(String(options.port)) : parseSSHPort(options.port ?? "443");
312
+ const registered = await ensureDefaultSSHKeyRegistered();
313
+ const configResult = await ensureSSHAliasConfig({
314
+ alias,
315
+ host,
316
+ port,
317
+ user: "agentcomputer",
318
+ identityFilePath: registered.privateKeyPath
319
+ });
320
+ return {
321
+ alias,
322
+ host,
323
+ port,
324
+ configPath: configResult.configPath,
325
+ identityFilePath: registered.privateKeyPath,
326
+ changed: configResult.changed
327
+ };
328
+ }
329
+ function normalizeSSHAlias(value) {
330
+ const alias = value.trim();
331
+ if (!alias) {
332
+ throw new Error("ssh alias cannot be empty");
333
+ }
334
+ if (!/^[A-Za-z0-9._-]+$/.test(alias)) {
335
+ throw new Error(
336
+ "ssh alias may contain only letters, numbers, dot, dash, or underscore"
337
+ );
338
+ }
339
+ return alias;
340
+ }
341
+ function normalizeSSHHost(value) {
342
+ const host = value.trim();
343
+ if (!host) {
344
+ throw new Error("ssh host cannot be empty");
345
+ }
346
+ if (host.includes(" ")) {
347
+ throw new Error("ssh host cannot contain spaces");
348
+ }
349
+ return host;
350
+ }
351
+ function parseSSHPort(value) {
352
+ const parsed = Number.parseInt(value, 10);
353
+ if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {
354
+ throw new Error("ssh port must be between 1 and 65535");
355
+ }
356
+ return parsed;
357
+ }
358
+
549
359
  // 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) => {
360
+ 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
361
  const spinner = ora("Preparing access...").start();
552
362
  try {
553
363
  const computer = await resolveComputer(identifier);
554
364
  const info = await getConnectionInfo(computer.id);
555
- if (options.vnc && options.terminal) {
556
- throw new Error("choose either --vnc or --terminal");
557
- }
558
365
  if (options.vnc) {
559
366
  if (!info.connection.vnc_available) {
560
367
  throw new Error("VNC is not available for this computer");
561
368
  }
562
369
  const url = info.connection.vnc_url;
563
- spinner.succeed(`Opening VNC for ${chalk3.bold(computer.handle)}`);
370
+ spinner.succeed(`Opening VNC for ${chalk.bold(computer.handle)}`);
564
371
  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}`));
372
+ console.log(chalk.dim(` ${url}`));
576
373
  return;
577
374
  }
578
375
  const access2 = await createBrowserAccess(computer.id);
579
- spinner.succeed(`Opening ${chalk3.bold(computer.handle)}`);
376
+ spinner.succeed(`Opening ${chalk.bold(computer.handle)}`);
580
377
  await openBrowserURL(access2.access_url);
581
- console.log(chalk3.dim(` ${access2.access_url}`));
378
+ console.log(chalk.dim(` ${access2.access_url}`));
582
379
  } catch (error) {
583
380
  spinner.fail(
584
381
  error instanceof Error ? error.message : "Failed to open computer"
@@ -598,8 +395,8 @@ var sshCommand = new Command("ssh").description("Open an SSH session to a comput
598
395
  try {
599
396
  const computer = await resolveSSHComputer(identifier, spinner);
600
397
  const connection = await prepareSSHConnection(computer);
601
- spinner.succeed(`Connecting to ${chalk3.bold(computer.handle)}`);
602
- console.log(chalk3.dim(` ${connection.command}`));
398
+ spinner.succeed(`Connecting to ${chalk.bold(computer.handle)}`);
399
+ console.log(chalk.dim(` ${connection.command}`));
603
400
  console.log();
604
401
  await openSSHConnection(connection);
605
402
  } catch (error) {
@@ -621,24 +418,24 @@ portsCommand.command("ls").description("List published ports for a computer").ar
621
418
  spinner.stop();
622
419
  if (ports.length === 0) {
623
420
  console.log();
624
- console.log(chalk3.dim(" No published ports."));
421
+ console.log(chalk.dim(" No published ports."));
625
422
  console.log();
626
423
  return;
627
424
  }
628
425
  const subWidth = Math.max(10, ...ports.map((p) => p.subdomain.length));
629
426
  console.log();
630
427
  console.log(
631
- ` ${chalk3.dim(padEnd("Subdomain", subWidth + 2))}${chalk3.dim(padEnd("Port", 8))}${chalk3.dim("Protocol")}`
428
+ ` ${chalk.dim(padEnd("Subdomain", subWidth + 2))}${chalk.dim(padEnd("Port", 8))}${chalk.dim("Protocol")}`
632
429
  );
633
430
  console.log(
634
- ` ${chalk3.dim("-".repeat(subWidth + 2))}${chalk3.dim("-".repeat(8))}${chalk3.dim("-".repeat(8))}`
431
+ ` ${chalk.dim("-".repeat(subWidth + 2))}${chalk.dim("-".repeat(8))}${chalk.dim("-".repeat(8))}`
635
432
  );
636
433
  for (const port of ports) {
637
434
  const url = `https://${port.subdomain}--${computer.handle}.computer.agentcomputer.ai`;
638
435
  console.log(
639
436
  ` ${padEnd(port.subdomain, subWidth + 2)}${padEnd(String(port.target_port), 8)}${port.protocol}`
640
437
  );
641
- console.log(` ${chalk3.dim(url)}`);
438
+ console.log(` ${chalk.dim(url)}`);
642
439
  }
643
440
  console.log();
644
441
  } catch (error) {
@@ -666,9 +463,9 @@ portsCommand.command("publish").description("Publish an HTTP app port").argument
666
463
  });
667
464
  const url = `https://${published.subdomain}--${computer.handle}.computer.agentcomputer.ai`;
668
465
  spinner.succeed(
669
- `Published port ${chalk3.bold(String(published.target_port))}`
466
+ `Published port ${chalk.bold(String(published.target_port))}`
670
467
  );
671
- console.log(chalk3.dim(` ${url}`));
468
+ console.log(chalk.dim(` ${url}`));
672
469
  } catch (error) {
673
470
  spinner.fail(
674
471
  error instanceof Error ? error.message : "Failed to publish port"
@@ -685,7 +482,7 @@ portsCommand.command("rm").description("Unpublish an app port").argument("<id-or
685
482
  }
686
483
  const computer = await resolveComputer(identifier);
687
484
  await deletePublishedPort(computer.id, targetPort);
688
- spinner.succeed(`Removed port ${chalk3.bold(String(targetPort))}`);
485
+ spinner.succeed(`Removed port ${chalk.bold(String(targetPort))}`);
689
486
  } catch (error) {
690
487
  spinner.fail(
691
488
  error instanceof Error ? error.message : "Failed to remove port"
@@ -696,24 +493,14 @@ portsCommand.command("rm").description("Unpublish an app port").argument("<id-or
696
493
  async function setupSSHAlias(options) {
697
494
  const spinner = ora("Configuring global SSH access...").start();
698
495
  try {
699
- const alias = normalizeSSHAlias(options.alias ?? "agentcomputer.ai");
700
- const host = normalizeSSHHost(options.host ?? "ssh.agentcomputer.ai");
701
- const port = parseSSHPort(options.port ?? "443");
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`);
496
+ const setup = await ensureSSHAccessConfigured(options);
497
+ spinner.succeed(`SSH alias '${setup.alias}' is ready`);
711
498
  console.log();
712
- console.log(chalk3.dim(` SSH config: ${configResult.configPath}`));
713
- console.log(chalk3.dim(` Identity: ${registered.privateKeyPath}`));
499
+ console.log(chalk.dim(` SSH config: ${setup.configPath}`));
500
+ console.log(chalk.dim(` Identity: ${setup.identityFilePath}`));
714
501
  console.log();
715
- console.log(` ${chalk3.bold("Shell:")} ssh ${alias}`);
716
- console.log(` ${chalk3.bold("Direct:")} ssh <handle>@${alias}`);
502
+ console.log(` ${chalk.bold("Shell:")} ssh ${setup.alias}`);
503
+ console.log(` ${chalk.bold("Direct:")} ssh <handle>@${setup.alias}`);
717
504
  console.log();
718
505
  } catch (error) {
719
506
  spinner.fail(
@@ -722,35 +509,6 @@ async function setupSSHAlias(options) {
722
509
  process.exit(1);
723
510
  }
724
511
  }
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");
751
- }
752
- return parsed;
753
- }
754
512
  async function resolveSSHComputer(identifier, spinner) {
755
513
  const trimmed = identifier?.trim();
756
514
  if (trimmed) {
@@ -769,7 +527,7 @@ async function resolveSSHComputer(identifier, spinner) {
769
527
  }
770
528
 
771
529
  // src/commands/acp.ts
772
- import { readFileSync as readFileSync2 } from "fs";
530
+ import { readFileSync } from "fs";
773
531
  import readline from "readline";
774
532
  import { Command as Command2 } from "commander";
775
533
 
@@ -845,7 +603,7 @@ async function openAgentSessionEventsStream(computerID, sessionID, options = {})
845
603
  signal: options.signal
846
604
  });
847
605
  if (!response.ok) {
848
- const message = await readErrorMessage2(response);
606
+ const message = await readErrorMessage(response);
849
607
  throw new ApiError(response.status, message);
850
608
  }
851
609
  return response;
@@ -865,7 +623,7 @@ async function waitForSessionToSettle(computerID, sessionID, promptID, options =
865
623
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
866
624
  }
867
625
  }
868
- async function readErrorMessage2(response) {
626
+ async function readErrorMessage(response) {
869
627
  const contentType = response.headers.get("content-type") ?? "";
870
628
  if (contentType.includes("application/json")) {
871
629
  try {
@@ -881,7 +639,7 @@ async function readErrorMessage2(response) {
881
639
 
882
640
  // src/commands/acp.ts
883
641
  var pkg = JSON.parse(
884
- readFileSync2(new URL("../package.json", import.meta.url), "utf8")
642
+ readFileSync(new URL("../package.json", import.meta.url), "utf8")
885
643
  );
886
644
  function emit(message) {
887
645
  process.stdout.write(`${JSON.stringify(message)}
@@ -1112,22 +870,22 @@ acpCommand.command("serve").description("Serve a local stdio ACP bridge backed b
1112
870
 
1113
871
  // src/commands/agent.ts
1114
872
  import { Command as Command3 } from "commander";
1115
- import chalk4 from "chalk";
873
+ import chalk2 from "chalk";
1116
874
  import ora2 from "ora";
1117
875
  function formatAgentSessionStatus(status) {
1118
876
  switch (status) {
1119
877
  case "idle":
1120
- return chalk4.green(status);
878
+ return chalk2.green(status);
1121
879
  case "running":
1122
- return chalk4.blue(status);
880
+ return chalk2.blue(status);
1123
881
  case "cancelling":
1124
- return chalk4.yellow(status);
882
+ return chalk2.yellow(status);
1125
883
  case "interrupted":
1126
- return chalk4.yellow(status);
884
+ return chalk2.yellow(status);
1127
885
  case "failed":
1128
- return chalk4.red(status);
886
+ return chalk2.red(status);
1129
887
  case "closed":
1130
- return chalk4.gray(status);
888
+ return chalk2.gray(status);
1131
889
  default:
1132
890
  return status;
1133
891
  }
@@ -1136,14 +894,14 @@ function printAgents(agents) {
1136
894
  const idWidth = Math.max(5, ...agents.map((agent) => agent.id.length));
1137
895
  console.log();
1138
896
  console.log(
1139
- ` ${chalk4.dim(padEnd("Agent", idWidth + 2))}${chalk4.dim(padEnd("Installed", 12))}${chalk4.dim(padEnd("Creds", 8))}${chalk4.dim("Version")}`
897
+ ` ${chalk2.dim(padEnd("Agent", idWidth + 2))}${chalk2.dim(padEnd("Installed", 12))}${chalk2.dim(padEnd("Creds", 8))}${chalk2.dim("Version")}`
1140
898
  );
1141
899
  console.log(
1142
- ` ${chalk4.dim("-".repeat(idWidth + 2))}${chalk4.dim("-".repeat(12))}${chalk4.dim("-".repeat(8))}${chalk4.dim("-".repeat(12))}`
900
+ ` ${chalk2.dim("-".repeat(idWidth + 2))}${chalk2.dim("-".repeat(12))}${chalk2.dim("-".repeat(8))}${chalk2.dim("-".repeat(12))}`
1143
901
  );
1144
902
  for (const agent of agents) {
1145
903
  console.log(
1146
- ` ${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")}`
904
+ ` ${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")}`
1147
905
  );
1148
906
  }
1149
907
  console.log();
@@ -1151,7 +909,7 @@ function printAgents(agents) {
1151
909
  function printSessions(sessions, handleByComputerID = /* @__PURE__ */ new Map()) {
1152
910
  if (sessions.length === 0) {
1153
911
  console.log();
1154
- console.log(chalk4.dim(" No agent sessions found."));
912
+ console.log(chalk2.dim(" No agent sessions found."));
1155
913
  console.log();
1156
914
  return;
1157
915
  }
@@ -1160,19 +918,19 @@ function printSessions(sessions, handleByComputerID = /* @__PURE__ */ new Map())
1160
918
  const statusWidth = 13;
1161
919
  console.log();
1162
920
  console.log(
1163
- ` ${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")}`
921
+ ` ${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")}`
1164
922
  );
1165
923
  console.log(
1166
- ` ${chalk4.dim("-".repeat(14))}${chalk4.dim("-".repeat(nameWidth + 2))}${chalk4.dim("-".repeat(agentWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(20))}`
924
+ ` ${chalk2.dim("-".repeat(14))}${chalk2.dim("-".repeat(nameWidth + 2))}${chalk2.dim("-".repeat(agentWidth + 2))}${chalk2.dim("-".repeat(statusWidth + 2))}${chalk2.dim("-".repeat(20))}`
1167
925
  );
1168
926
  for (const session of sessions) {
1169
927
  const location = handleByComputerID.get(session.computer_id) ?? session.computer_id;
1170
928
  console.log(
1171
929
  ` ${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}`
1172
930
  );
1173
- console.log(` ${chalk4.dim(` cwd=${session.cwd} updated=${timeAgo(session.updated_at)}`)}`);
931
+ console.log(` ${chalk2.dim(` cwd=${session.cwd} updated=${timeAgo(session.updated_at)}`)}`);
1174
932
  if (session.last_stop_reason || session.last_error) {
1175
- console.log(` ${chalk4.dim(` ${session.last_error ? `error=${session.last_error}` : `stop=${session.last_stop_reason}`}`)}`);
933
+ console.log(` ${chalk2.dim(` ${session.last_error ? `error=${session.last_error}` : `stop=${session.last_stop_reason}`}`)}`);
1176
934
  }
1177
935
  }
1178
936
  console.log();
@@ -1186,7 +944,7 @@ var StreamPrinter = class {
1186
944
  }
1187
945
  writeDim(text) {
1188
946
  this.ensureBreak();
1189
- process.stdout.write(chalk4.dim(text));
947
+ process.stdout.write(chalk2.dim(text));
1190
948
  this.inlineOpen = !text.endsWith("\n");
1191
949
  }
1192
950
  writeLine(text) {
@@ -1254,7 +1012,7 @@ function renderSSEChunk(chunk, printer, asJson) {
1254
1012
  try {
1255
1013
  envelope = JSON.parse(payload);
1256
1014
  } catch {
1257
- printer.writeLine(chalk4.dim(payload));
1015
+ printer.writeLine(chalk2.dim(payload));
1258
1016
  return;
1259
1017
  }
1260
1018
  const method = typeof envelope.method === "string" ? envelope.method : "";
@@ -1281,14 +1039,14 @@ function renderSSEChunk(chunk, printer, asJson) {
1281
1039
  case "tool_call_update": {
1282
1040
  const title = typeof update?.title === "string" && update.title ? update.title : "tool";
1283
1041
  const status = typeof update?.status === "string" && update.status ? update.status : "in_progress";
1284
- printer.writeLine(chalk4.dim(`[tool:${status}] ${title}`));
1042
+ printer.writeLine(chalk2.dim(`[tool:${status}] ${title}`));
1285
1043
  return;
1286
1044
  }
1287
1045
  case "plan": {
1288
1046
  const entries = Array.isArray(update?.entries) ? update.entries : [];
1289
1047
  const detail = entries.filter((entry) => typeof entry === "object" && entry !== null).map((entry) => `- [${entry.status ?? "pending"}] ${entry.content ?? ""}`).join("\n");
1290
1048
  if (detail) {
1291
- printer.writeLine(chalk4.dim(`Plan
1049
+ printer.writeLine(chalk2.dim(`Plan
1292
1050
  ${detail}`));
1293
1051
  }
1294
1052
  return;
@@ -1298,7 +1056,7 @@ ${detail}`));
1298
1056
  }
1299
1057
  }
1300
1058
  if (method === "session/request_permission") {
1301
- printer.writeLine(chalk4.yellow("Permission requested by remote agent"));
1059
+ printer.writeLine(chalk2.yellow("Permission requested by remote agent"));
1302
1060
  return;
1303
1061
  }
1304
1062
  }
@@ -1426,12 +1184,12 @@ agentCommand.command("prompt").description("Send a prompt to a machine agent ses
1426
1184
  return;
1427
1185
  }
1428
1186
  console.log();
1429
- console.log(` ${chalk4.bold(session.name || "default")} ${formatAgentSessionStatus(settled.status)}`);
1187
+ console.log(` ${chalk2.bold(session.name || "default")} ${formatAgentSessionStatus(settled.status)}`);
1430
1188
  if (settled.last_stop_reason) {
1431
- console.log(chalk4.dim(` stop_reason=${settled.last_stop_reason}`));
1189
+ console.log(chalk2.dim(` stop_reason=${settled.last_stop_reason}`));
1432
1190
  }
1433
1191
  if (settled.last_error) {
1434
- console.log(chalk4.red(` error=${settled.last_error}`));
1192
+ console.log(chalk2.red(` error=${settled.last_error}`));
1435
1193
  }
1436
1194
  console.log();
1437
1195
  } catch (error) {
@@ -1542,7 +1300,7 @@ agentCommand.command("close").description("Close and delete a machine agent sess
1542
1300
  import { randomBytes as randomBytes2, createHash } from "crypto";
1543
1301
  import { input as textInput } from "@inquirer/prompts";
1544
1302
  import { Command as Command4 } from "commander";
1545
- import chalk5 from "chalk";
1303
+ import chalk3 from "chalk";
1546
1304
  import ora4 from "ora";
1547
1305
 
1548
1306
  // src/lib/remote-auth.ts
@@ -1755,7 +1513,7 @@ var claudeLoginCommand = new Command4("claude-login").alias("claude-auth").descr
1755
1513
  await runClaudeLogin(options);
1756
1514
  } catch (error) {
1757
1515
  const message = error instanceof Error ? error.message : "Failed to authenticate Claude";
1758
- console.error(chalk5.red(`
1516
+ console.error(chalk3.red(`
1759
1517
  ${message}`));
1760
1518
  process.exit(1);
1761
1519
  }
@@ -1768,7 +1526,7 @@ async function runClaudeLogin(options) {
1768
1526
  let activeTodoID = "target";
1769
1527
  let failureMessage = null;
1770
1528
  console.log();
1771
- console.log(chalk5.cyan("Authenticating with Claude Code...\n"));
1529
+ console.log(chalk3.cyan("Authenticating with Claude Code...\n"));
1772
1530
  try {
1773
1531
  const prepared = await prepareTargetMachine(options);
1774
1532
  target = prepared.computer;
@@ -1853,7 +1611,7 @@ async function runClaudeLogin(options) {
1853
1611
  }
1854
1612
  if (target) {
1855
1613
  console.log(
1856
- chalk5.green(`Claude login installed on ${chalk5.bold(target.handle)}.`)
1614
+ chalk3.green(`Claude login installed on ${chalk3.bold(target.handle)}.`)
1857
1615
  );
1858
1616
  console.log();
1859
1617
  }
@@ -1894,11 +1652,11 @@ function markVerificationTodo(items, id, result, successDetail) {
1894
1652
  }
1895
1653
  function printTodoList(items) {
1896
1654
  console.log();
1897
- console.log(chalk5.dim("TODO"));
1655
+ console.log(chalk3.dim("TODO"));
1898
1656
  console.log();
1899
1657
  for (const item of items) {
1900
- const marker = item.state === "done" ? chalk5.green("[x]") : item.state === "skipped" ? chalk5.yellow("[-]") : item.state === "failed" ? chalk5.red("[!]") : chalk5.dim("[ ]");
1901
- const detail = item.detail ? chalk5.dim(` ${item.detail}`) : "";
1658
+ const marker = item.state === "done" ? chalk3.green("[x]") : item.state === "skipped" ? chalk3.yellow("[-]") : item.state === "failed" ? chalk3.red("[!]") : chalk3.dim("[ ]");
1659
+ const detail = item.detail ? chalk3.dim(` ${item.detail}`) : "";
1902
1660
  console.log(` ${marker} ${item.label}${detail ? ` ${detail}` : ""}`);
1903
1661
  }
1904
1662
  console.log();
@@ -1926,7 +1684,7 @@ async function runManualOAuthFlow() {
1926
1684
  try {
1927
1685
  await openBrowserURL(url);
1928
1686
  } catch {
1929
- console.log(chalk5.yellow("Unable to open the browser automatically."));
1687
+ console.log(chalk3.yellow("Unable to open the browser automatically."));
1930
1688
  }
1931
1689
  console.log(
1932
1690
  "After completing authentication, copy the code shown on the success page."
@@ -2017,7 +1775,7 @@ function parseAuthorizationInput(value, expectedState) {
2017
1775
  }
2018
1776
  async function installClaudeAuth(target, oauth) {
2019
1777
  const spinner = ora4(
2020
- `Installing Claude auth on ${chalk5.bold(target.handle)}...`
1778
+ `Installing Claude auth on ${chalk3.bold(target.handle)}...`
2021
1779
  ).start();
2022
1780
  try {
2023
1781
  const installScript = buildInstallScript(oauth.refreshToken, oauth.scope);
@@ -2027,10 +1785,10 @@ async function installClaudeAuth(target, oauth) {
2027
1785
  installScript
2028
1786
  );
2029
1787
  if (result.stdout.trim()) {
2030
- spinner.succeed(`Installed Claude auth on ${chalk5.bold(target.handle)}`);
1788
+ spinner.succeed(`Installed Claude auth on ${chalk3.bold(target.handle)}`);
2031
1789
  return;
2032
1790
  }
2033
- spinner.succeed(`Installed Claude auth on ${chalk5.bold(target.handle)}`);
1791
+ spinner.succeed(`Installed Claude auth on ${chalk3.bold(target.handle)}`);
2034
1792
  } catch (error) {
2035
1793
  spinner.fail(
2036
1794
  error instanceof Error ? error.message : `Failed to install Claude auth on ${target.handle}`
@@ -2040,11 +1798,11 @@ async function installClaudeAuth(target, oauth) {
2040
1798
  }
2041
1799
  async function verifyTargetMachine(handle, target) {
2042
1800
  const spinner = ora4(
2043
- `Verifying Claude login on ${chalk5.bold(handle)}...`
1801
+ `Verifying Claude login on ${chalk3.bold(handle)}...`
2044
1802
  ).start();
2045
1803
  const result = await verifyStoredAuth(target);
2046
1804
  if (result.status === "verified") {
2047
- spinner.succeed(`Verified Claude login on ${chalk5.bold(handle)}`);
1805
+ spinner.succeed(`Verified Claude login on ${chalk3.bold(handle)}`);
2048
1806
  return result;
2049
1807
  }
2050
1808
  spinner.warn(result.detail);
@@ -2052,7 +1810,7 @@ async function verifyTargetMachine(handle, target) {
2052
1810
  }
2053
1811
  async function verifySharedInstall(primaryHandle, primaryComputerID, sharedInstall, skip, verify) {
2054
1812
  const spinner = ora4(
2055
- `Verifying shared-home Claude login from ${chalk5.bold(primaryHandle)}...`
1813
+ `Verifying shared-home Claude login from ${chalk3.bold(primaryHandle)}...`
2056
1814
  ).start();
2057
1815
  const result = await verifySecondaryMachine(
2058
1816
  primaryComputerID,
@@ -2062,7 +1820,7 @@ async function verifySharedInstall(primaryHandle, primaryComputerID, sharedInsta
2062
1820
  );
2063
1821
  if (result.status === "verified") {
2064
1822
  spinner.succeed(
2065
- `Verified shared-home Claude login on ${chalk5.bold(result.handle)}`
1823
+ `Verified shared-home Claude login on ${chalk3.bold(result.handle)}`
2066
1824
  );
2067
1825
  return result;
2068
1826
  }
@@ -2146,9 +1904,9 @@ function randomSuffix2(length) {
2146
1904
 
2147
1905
  // src/commands/computers.ts
2148
1906
  import { Command as Command5 } from "commander";
2149
- import chalk6 from "chalk";
1907
+ import chalk4 from "chalk";
2150
1908
  import ora5 from "ora";
2151
- import { select as select2, input as textInput2, confirm } from "@inquirer/prompts";
1909
+ import { select, input as textInput2, confirm } from "@inquirer/prompts";
2152
1910
 
2153
1911
  // src/lib/machine-sources.ts
2154
1912
  async function getMachineSourceSettings() {
@@ -2193,48 +1951,90 @@ function summarizeMachineSource(source) {
2193
1951
  return parts.join(" | ");
2194
1952
  }
2195
1953
 
1954
+ // src/lib/mount-control.ts
1955
+ import { unlinkSync } from "fs";
1956
+ import net from "net";
1957
+ async function notifyMountDaemon(paths) {
1958
+ return new Promise((resolve) => {
1959
+ const socket = net.createConnection(paths.socketPath);
1960
+ socket.on("connect", () => {
1961
+ socket.end("reconcile\n");
1962
+ resolve(true);
1963
+ });
1964
+ socket.on("error", () => {
1965
+ resolve(false);
1966
+ });
1967
+ });
1968
+ }
1969
+ function createMountControlServer(paths, onReconcile) {
1970
+ try {
1971
+ unlinkSync(paths.socketPath);
1972
+ } catch {
1973
+ }
1974
+ const server = net.createServer((socket) => {
1975
+ socket.setEncoding("utf8");
1976
+ let buffer = "";
1977
+ socket.on("data", (chunk) => {
1978
+ buffer += chunk;
1979
+ if (buffer.includes("\n")) {
1980
+ void Promise.resolve(onReconcile()).finally(() => {
1981
+ socket.end("ok\n");
1982
+ });
1983
+ }
1984
+ });
1985
+ socket.on("error", () => {
1986
+ socket.destroy();
1987
+ });
1988
+ });
1989
+ server.on("close", () => {
1990
+ try {
1991
+ unlinkSync(paths.socketPath);
1992
+ } catch {
1993
+ }
1994
+ });
1995
+ return server;
1996
+ }
1997
+
2196
1998
  // src/commands/computers.ts
2197
1999
  function isInternalCondition(value) {
2198
2000
  return /^[A-Z][a-zA-Z]+$/.test(value);
2199
2001
  }
2200
2002
  function printComputer(computer) {
2201
2003
  const vnc = vncURL(computer);
2202
- const terminal = terminalURL(computer);
2203
2004
  const ssh = computer.ssh_enabled ? formatSSHCommand2(computer.handle, computer.ssh_host, computer.ssh_port) : "disabled";
2204
2005
  const isCustom = computer.runtime_family === "custom-machine";
2205
2006
  console.log();
2206
- console.log(` ${chalk6.bold.white(computer.handle)} ${formatStatus(computer.status)}`);
2007
+ console.log(` ${chalk4.bold.white(computer.handle)} ${formatStatus(computer.status)}`);
2207
2008
  console.log();
2208
- console.log(` ${chalk6.dim("ID")} ${computer.id}`);
2209
- console.log(` ${chalk6.dim("Tier")} ${computer.tier}`);
2009
+ console.log(` ${chalk4.dim("ID")} ${computer.id}`);
2010
+ console.log(` ${chalk4.dim("Tier")} ${computer.tier}`);
2210
2011
  if (isCustom) {
2211
- console.log(` ${chalk6.dim("Runtime")} ${computer.runtime_family}`);
2212
- console.log(` ${chalk6.dim("Source")} ${computer.source_kind}`);
2213
- console.log(` ${chalk6.dim("Image")} ${computer.image_family}`);
2012
+ console.log(` ${chalk4.dim("Runtime")} ${computer.runtime_family}`);
2013
+ console.log(` ${chalk4.dim("Source")} ${computer.source_kind}`);
2014
+ console.log(` ${chalk4.dim("Image")} ${computer.image_family}`);
2214
2015
  } else {
2215
- console.log(` ${chalk6.dim("Runtime")} ${computer.runtime_family}`);
2216
- console.log(` ${chalk6.dim("Launch")} ${formatManagedWorkerLaunchSource(computer)}`);
2016
+ console.log(` ${chalk4.dim("Runtime")} ${computer.runtime_family}`);
2017
+ console.log(` ${chalk4.dim("Launch")} ${formatManagedWorkerLaunchSource(computer)}`);
2217
2018
  if (computer.user_source_id && computer.resolved_image_ref) {
2218
- console.log(` ${chalk6.dim("Image")} ${computer.resolved_image_ref}`);
2019
+ console.log(` ${chalk4.dim("Image")} ${computer.resolved_image_ref}`);
2219
2020
  }
2220
2021
  }
2221
- console.log(` ${chalk6.dim("Primary")} :${computer.primary_port}${computer.primary_path}`);
2222
- console.log(` ${chalk6.dim("Health")} ${computer.healthcheck_type}${computer.healthcheck_value ? ` (${computer.healthcheck_value})` : ""}`);
2022
+ console.log(` ${chalk4.dim("Primary")} :${computer.primary_port}${computer.primary_path}`);
2023
+ console.log(` ${chalk4.dim("Health")} ${computer.healthcheck_type}${computer.healthcheck_value ? ` (${computer.healthcheck_value})` : ""}`);
2223
2024
  console.log();
2224
- console.log(` ${chalk6.dim("Gateway")} ${chalk6.cyan(webURL(computer))}`);
2225
- console.log(` ${chalk6.dim("VNC")} ${vnc ? chalk6.cyan(vnc) : chalk6.dim("not available")}`);
2226
- console.log(` ${chalk6.dim("Terminal")} ${terminal ? chalk6.cyan(terminal) : chalk6.dim("not available")}`);
2227
- console.log(` ${chalk6.dim("SSH")} ${computer.ssh_enabled ? chalk6.white(ssh) : chalk6.dim(ssh)}`);
2025
+ console.log(` ${chalk4.dim("Gateway")} ${chalk4.cyan(webURL(computer))}`);
2026
+ console.log(` ${chalk4.dim("VNC")} ${vnc ? chalk4.cyan(vnc) : chalk4.dim("not available")}`);
2027
+ console.log(` ${chalk4.dim("SSH")} ${computer.ssh_enabled ? chalk4.white(ssh) : chalk4.dim(ssh)}`);
2228
2028
  if (computer.last_error) {
2229
2029
  console.log();
2230
2030
  if (isInternalCondition(computer.last_error)) {
2231
- console.log(` ${chalk6.dim("Condition")} ${chalk6.dim(computer.last_error)}`);
2031
+ console.log(` ${chalk4.dim("Condition")} ${chalk4.dim(computer.last_error)}`);
2232
2032
  } else {
2233
- console.log(` ${chalk6.dim("Error")} ${chalk6.red(computer.last_error)}`);
2033
+ console.log(` ${chalk4.dim("Error")} ${chalk4.red(computer.last_error)}`);
2234
2034
  }
2235
2035
  }
2236
2036
  console.log();
2237
- console.log(` ${chalk6.dim("Created")} ${timeAgo(computer.created_at)}`);
2037
+ console.log(` ${chalk4.dim("Created")} ${timeAgo(computer.created_at)}`);
2238
2038
  console.log();
2239
2039
  }
2240
2040
  function formatSSHCommand2(user, host, port) {
@@ -2271,16 +2071,16 @@ function printComputerTable(computers) {
2271
2071
  const createdWidth = 10;
2272
2072
  console.log();
2273
2073
  console.log(
2274
- ` ${chalk6.dim(padEnd("Handle", handleWidth + 2))}${chalk6.dim(padEnd("Status", statusWidth + 2))}${chalk6.dim(padEnd("Created", createdWidth + 2))}${chalk6.dim("URL")}`
2074
+ ` ${chalk4.dim(padEnd("Handle", handleWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim(padEnd("Created", createdWidth + 2))}${chalk4.dim("URL")}`
2275
2075
  );
2276
2076
  console.log(
2277
- ` ${chalk6.dim("-".repeat(handleWidth + 2))}${chalk6.dim("-".repeat(statusWidth + 2))}${chalk6.dim("-".repeat(createdWidth + 2))}${chalk6.dim("-".repeat(20))}`
2077
+ ` ${chalk4.dim("-".repeat(handleWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(createdWidth + 2))}${chalk4.dim("-".repeat(20))}`
2278
2078
  );
2279
2079
  for (const computer of computers) {
2280
2080
  const status = formatStatus(computer.status);
2281
- const created = chalk6.dim(timeAgo(computer.created_at));
2081
+ const created = chalk4.dim(timeAgo(computer.created_at));
2282
2082
  console.log(
2283
- ` ${chalk6.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${padEnd(created, createdWidth + 2)}${chalk6.cyan(webURL(computer))}`
2083
+ ` ${chalk4.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${padEnd(created, createdWidth + 2)}${chalk4.cyan(webURL(computer))}`
2284
2084
  );
2285
2085
  }
2286
2086
  console.log();
@@ -2290,23 +2090,19 @@ function printComputerTableVerbose(computers) {
2290
2090
  const statusWidth = 10;
2291
2091
  console.log();
2292
2092
  console.log(
2293
- ` ${chalk6.dim(padEnd("Handle", handleWidth + 2))}${chalk6.dim(padEnd("Status", statusWidth + 2))}${chalk6.dim("URLs")}`
2093
+ ` ${chalk4.dim(padEnd("Handle", handleWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim("URLs")}`
2294
2094
  );
2295
2095
  console.log(
2296
- ` ${chalk6.dim("-".repeat(handleWidth + 2))}${chalk6.dim("-".repeat(statusWidth + 2))}${chalk6.dim("-".repeat(20))}`
2096
+ ` ${chalk4.dim("-".repeat(handleWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(20))}`
2297
2097
  );
2298
2098
  for (const computer of computers) {
2299
2099
  const status = formatStatus(computer.status);
2300
2100
  const vnc = vncURL(computer);
2301
- const terminal = terminalURL(computer);
2302
- console.log(
2303
- ` ${chalk6.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${chalk6.cyan(webURL(computer))}`
2304
- );
2305
2101
  console.log(
2306
- ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk6.dim(vnc ?? "VNC not available")}`
2102
+ ` ${chalk4.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${chalk4.cyan(webURL(computer))}`
2307
2103
  );
2308
2104
  console.log(
2309
- ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk6.dim(terminal ?? "Terminal not available")}`
2105
+ ` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk4.dim(vnc ?? "VNC not available")}`
2310
2106
  );
2311
2107
  }
2312
2108
  console.log();
@@ -2322,7 +2118,7 @@ var lsCommand = new Command5("ls").description("List computers").option("--json"
2322
2118
  }
2323
2119
  if (computers.length === 0) {
2324
2120
  console.log();
2325
- console.log(chalk6.dim(" No computers found."));
2121
+ console.log(chalk4.dim(" No computers found."));
2326
2122
  console.log();
2327
2123
  return;
2328
2124
  }
@@ -2378,11 +2174,11 @@ var createCommand = new Command5("create").description("Create a computer").argu
2378
2174
  Boolean(selectedOptions.usePlatformDefault)
2379
2175
  );
2380
2176
  if (machineSourceNote) {
2381
- console.log(chalk6.dim(machineSourceNote));
2177
+ console.log(chalk4.dim(machineSourceNote));
2382
2178
  }
2383
2179
  const provisioningNote = createProvisioningNote(runtimeFamily, filesystemSettings);
2384
2180
  if (provisioningNote) {
2385
- console.log(chalk6.dim(provisioningNote));
2181
+ console.log(chalk4.dim(provisioningNote));
2386
2182
  }
2387
2183
  spinner = ora5(createSpinnerText(runtimeFamily, filesystemSettings, 0)).start();
2388
2184
  startTime = Date.now();
@@ -2420,7 +2216,12 @@ var createCommand = new Command5("create").description("Create a computer").argu
2420
2216
  }
2421
2217
  const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
2422
2218
  spinner.succeed(
2423
- chalk6.green(`Created ${chalk6.bold(computer.handle)} ${chalk6.dim(`[${elapsed}s]`)}`)
2219
+ chalk4.green(`Created ${chalk4.bold(computer.handle)} ${chalk4.dim(`[${elapsed}s]`)}`)
2220
+ );
2221
+ await notifyMountDaemon(
2222
+ getMountPaths((readMountConfig() ?? defaultMountServiceConfig()).rootPath)
2223
+ ).catch(
2224
+ () => false
2424
2225
  );
2425
2226
  printComputer(computer);
2426
2227
  } catch (error) {
@@ -2429,10 +2230,10 @@ var createCommand = new Command5("create").description("Create a computer").argu
2429
2230
  }
2430
2231
  const message = error instanceof Error ? error.message : "Failed to create computer";
2431
2232
  if (spinner) {
2432
- const suffix = startTime ? ` ${chalk6.dim(`[${((Date.now() - startTime) / 1e3).toFixed(1)}s]`)}` : "";
2233
+ const suffix = startTime ? ` ${chalk4.dim(`[${((Date.now() - startTime) / 1e3).toFixed(1)}s]`)}` : "";
2433
2234
  spinner.fail(`${message}${suffix}`);
2434
2235
  } else {
2435
- console.error(chalk6.red(message));
2236
+ console.error(chalk4.red(message));
2436
2237
  }
2437
2238
  process.exit(1);
2438
2239
  }
@@ -2444,11 +2245,11 @@ async function resolveCreateOptions(options) {
2444
2245
  validateCreateOptions(selectedOptions);
2445
2246
  return selectedOptions;
2446
2247
  }
2447
- const runtimeChoice = await select2({
2248
+ const runtimeChoice = await select({
2448
2249
  message: "Select runtime",
2449
2250
  choices: [
2450
2251
  {
2451
- name: "managed-worker - default Ubuntu desktop, SSH, VNC, terminal",
2252
+ name: "managed-worker - default Ubuntu desktop, SSH, and VNC",
2452
2253
  value: "managed-worker"
2453
2254
  },
2454
2255
  {
@@ -2464,7 +2265,7 @@ async function resolveCreateOptions(options) {
2464
2265
  selectedOptions.imageRef = (await textInput2({ message: "OCI image ref (required):" })).trim();
2465
2266
  selectedOptions.primaryPort = (await textInput2({ message: "Primary port:", default: "3000" })).trim();
2466
2267
  selectedOptions.primaryPath = (await textInput2({ message: "Primary path:", default: "/" })).trim();
2467
- selectedOptions.healthcheckType = await select2({
2268
+ selectedOptions.healthcheckType = await select({
2468
2269
  message: "Healthcheck type",
2469
2270
  choices: [
2470
2271
  { name: "tcp", value: "tcp" },
@@ -2495,11 +2296,11 @@ var removeCommand = new Command5("rm").description("Delete a computer").argument
2495
2296
  spinner.stop();
2496
2297
  if (!skipConfirm && process.stdin.isTTY) {
2497
2298
  const confirmed = await confirm({
2498
- message: `Delete computer ${chalk6.bold(computer.handle)}?`,
2299
+ message: `Delete computer ${chalk4.bold(computer.handle)}?`,
2499
2300
  default: false
2500
2301
  });
2501
2302
  if (!confirmed) {
2502
- console.log(chalk6.dim(" Cancelled."));
2303
+ console.log(chalk4.dim(" Cancelled."));
2503
2304
  return;
2504
2305
  }
2505
2306
  }
@@ -2507,7 +2308,12 @@ var removeCommand = new Command5("rm").description("Delete a computer").argument
2507
2308
  await api(`/v1/computers/${computer.id}`, {
2508
2309
  method: "DELETE"
2509
2310
  });
2510
- deleteSpinner.succeed(chalk6.green(`Deleted ${chalk6.bold(computer.handle)}`));
2311
+ deleteSpinner.succeed(chalk4.green(`Deleted ${chalk4.bold(computer.handle)}`));
2312
+ await notifyMountDaemon(
2313
+ getMountPaths((readMountConfig() ?? defaultMountServiceConfig()).rootPath)
2314
+ ).catch(
2315
+ () => false
2316
+ );
2511
2317
  } catch (error) {
2512
2318
  spinner.fail(
2513
2319
  error instanceof Error ? error.message : "Failed to delete computer"
@@ -2563,9 +2369,9 @@ function createMachineSourceNote(runtimeFamily, machineSourceSettings, usePlatfo
2563
2369
  return `Using managed-worker image source: ${summarizeMachineSourceSelection(machineSourceSettings)}.`;
2564
2370
  }
2565
2371
  function createSpinnerText(runtimeFamily, filesystemSettings, elapsedSeconds) {
2566
- const elapsedLabel = chalk6.dim(`${elapsedSeconds.toFixed(1)}s`);
2372
+ const elapsedLabel = chalk4.dim(`${elapsedSeconds.toFixed(1)}s`);
2567
2373
  if (runtimeFamily === "managed-worker" && filesystemSettings?.shared_enabled && elapsedSeconds >= 5) {
2568
- return `Creating computer... ${elapsedLabel} ${chalk6.dim("mounting shared home")}`;
2374
+ return `Creating computer... ${elapsedLabel} ${chalk4.dim("mounting shared home")}`;
2569
2375
  }
2570
2376
  return `Creating computer... ${elapsedLabel}`;
2571
2377
  }
@@ -2654,6 +2460,7 @@ _computer() {
2654
2460
  'open:Open in browser'
2655
2461
  'ssh:SSH into a computer'
2656
2462
  'ports:Manage published ports'
2463
+ 'mount:Mirror SSH-ready machine homes into ~/agentcomputer while running'
2657
2464
  'agent:Manage cloud agent sessions'
2658
2465
  'acp:Run a local ACP bridge for remote agent sessions'
2659
2466
  'rm:Delete a computer'
@@ -2677,6 +2484,11 @@ _computer() {
2677
2484
  'rm:Delete a machine image source'
2678
2485
  )
2679
2486
 
2487
+ local -a mount_commands
2488
+ mount_commands=(
2489
+ 'status:Show machine mount controller status'
2490
+ )
2491
+
2680
2492
  _arguments -C \\
2681
2493
  '(-h --help)'{-h,--help}'[Display help]' \\
2682
2494
  '(-V --version)'{-V,--version}'[Show version]' \\
@@ -2781,12 +2593,33 @@ _computer() {
2781
2593
  open)
2782
2594
  _arguments \\
2783
2595
  '--vnc[Open VNC desktop]' \\
2784
- '--terminal[Open terminal]' \\
2785
2596
  '1:computer:_computer_handles'
2786
2597
  ;;
2787
2598
  ssh)
2788
2599
  _arguments '1:computer:_computer_handles'
2789
2600
  ;;
2601
+ mount)
2602
+ _arguments -C \\
2603
+ '--alias[SSH host alias]:alias:' \\
2604
+ '--host[SSH gateway host]:host:' \\
2605
+ '--port[SSH gateway port]:port:' \\
2606
+ '--poll-interval[Reconcile interval in milliseconds]:ms:' \\
2607
+ '--connect-timeout[SSH connect timeout for Mutagen]:seconds:' \\
2608
+ '1:command:->mount_command' \\
2609
+ '*::arg:->mount_args'
2610
+ case "$state" in
2611
+ mount_command)
2612
+ _describe -t commands 'mount command' mount_commands
2613
+ ;;
2614
+ mount_args)
2615
+ case "$words[2]" in
2616
+ status)
2617
+ _arguments
2618
+ ;;
2619
+ esac
2620
+ ;;
2621
+ esac
2622
+ ;;
2790
2623
  rm)
2791
2624
  _arguments \\
2792
2625
  '(-y --yes)'{-y,--yes}'[Skip confirmation]' \\
@@ -2850,9 +2683,10 @@ var BASH_SCRIPT = `_computer() {
2850
2683
  local cur prev words cword
2851
2684
  _init_completion || return
2852
2685
 
2853
- local commands="login upgrade logout whoami claude-login claude-auth codex-login codex-auth create ls get image open ssh ports agent acp rm completion help"
2686
+ 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"
2854
2687
  local ports_commands="ls publish rm"
2855
2688
  local image_commands="ls save default rebuild rm"
2689
+ local mount_commands="status"
2856
2690
 
2857
2691
  if [[ $cword -eq 1 ]]; then
2858
2692
  COMPREPLY=($(compgen -W "$commands" -- "$cur"))
@@ -2930,11 +2764,18 @@ var BASH_SCRIPT = `_computer() {
2930
2764
  else
2931
2765
  case "$cmd" in
2932
2766
  get) COMPREPLY=($(compgen -W "--json" -- "$cur")) ;;
2933
- open) COMPREPLY=($(compgen -W "--vnc --terminal" -- "$cur")) ;;
2767
+ open) COMPREPLY=($(compgen -W "--vnc" -- "$cur")) ;;
2934
2768
  rm) COMPREPLY=($(compgen -W "--yes -y" -- "$cur")) ;;
2935
2769
  esac
2936
2770
  fi
2937
2771
  ;;
2772
+ mount)
2773
+ if [[ $cword -eq 2 ]]; then
2774
+ COMPREPLY=($(compgen -W "$mount_commands" -- "$cur"))
2775
+ elif [[ $cword -ge 3 ]]; then
2776
+ COMPREPLY=($(compgen -W "--alias --host --port --poll-interval --connect-timeout" -- "$cur"))
2777
+ fi
2778
+ ;;
2938
2779
  ports)
2939
2780
  if [[ $cword -eq 2 ]]; then
2940
2781
  COMPREPLY=($(compgen -W "$ports_commands" -- "$cur"))
@@ -2970,17 +2811,17 @@ var completionCommand = new Command6("completion").description("Generate shell c
2970
2811
  // src/commands/codex-login.ts
2971
2812
  import { spawn as spawn3 } from "child_process";
2972
2813
  import { readFile as readFile3 } from "fs/promises";
2973
- import { homedir as homedir4 } from "os";
2974
- import { join as join3 } from "path";
2814
+ import { homedir as homedir3 } from "os";
2815
+ import { join as join2 } from "path";
2975
2816
  import { Command as Command7 } from "commander";
2976
- import chalk7 from "chalk";
2817
+ import chalk5 from "chalk";
2977
2818
  import ora6 from "ora";
2978
2819
  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) => {
2979
2820
  try {
2980
2821
  await runCodexLogin(options);
2981
2822
  } catch (error) {
2982
2823
  const message = error instanceof Error ? error.message : "Failed to authenticate Codex";
2983
- console.error(chalk7.red(`
2824
+ console.error(chalk5.red(`
2984
2825
  ${message}`));
2985
2826
  process.exit(1);
2986
2827
  }
@@ -2993,7 +2834,7 @@ async function runCodexLogin(options) {
2993
2834
  let activeTodoID = "target";
2994
2835
  let failureMessage = null;
2995
2836
  console.log();
2996
- console.log(chalk7.cyan("Authenticating with Codex...\n"));
2837
+ console.log(chalk5.cyan("Authenticating with Codex...\n"));
2997
2838
  try {
2998
2839
  const prepared = await prepareTargetMachine2(options);
2999
2840
  target = prepared.computer;
@@ -3078,7 +2919,7 @@ async function runCodexLogin(options) {
3078
2919
  }
3079
2920
  if (target) {
3080
2921
  console.log(
3081
- chalk7.green(`Codex login installed on ${chalk7.bold(target.handle)}.`)
2922
+ chalk5.green(`Codex login installed on ${chalk5.bold(target.handle)}.`)
3082
2923
  );
3083
2924
  console.log();
3084
2925
  }
@@ -3119,11 +2960,11 @@ function markVerificationTodo2(items, id, result, successDetail) {
3119
2960
  }
3120
2961
  function printTodoList2(items) {
3121
2962
  console.log();
3122
- console.log(chalk7.dim("TODO"));
2963
+ console.log(chalk5.dim("TODO"));
3123
2964
  console.log();
3124
2965
  for (const item of items) {
3125
- const marker = item.state === "done" ? chalk7.green("[x]") : item.state === "skipped" ? chalk7.yellow("[-]") : item.state === "failed" ? chalk7.red("[!]") : chalk7.dim("[ ]");
3126
- const detail = item.detail ? chalk7.dim(` ${item.detail}`) : "";
2966
+ const marker = item.state === "done" ? chalk5.green("[x]") : item.state === "skipped" ? chalk5.yellow("[-]") : item.state === "failed" ? chalk5.red("[!]") : chalk5.dim("[ ]");
2967
+ const detail = item.detail ? chalk5.dim(` ${item.detail}`) : "";
3127
2968
  console.log(` ${marker} ${item.label}${detail ? ` ${detail}` : ""}`);
3128
2969
  }
3129
2970
  console.log();
@@ -3171,7 +3012,7 @@ async function getLocalCodexStatus() {
3171
3012
  return parseCodexStatusOutput(result.stdout, result.stderr);
3172
3013
  }
3173
3014
  async function readLocalCodexAuthFile() {
3174
- const authPath = join3(homedir4(), ".codex", "auth.json");
3015
+ const authPath = join2(homedir3(), ".codex", "auth.json");
3175
3016
  let raw;
3176
3017
  try {
3177
3018
  raw = await readFile3(authPath, "utf8");
@@ -3234,12 +3075,12 @@ async function captureLocalCommand(command, args) {
3234
3075
  }
3235
3076
  async function installCodexAuth(target, authJSON) {
3236
3077
  const spinner = ora6(
3237
- `Installing Codex login on ${chalk7.bold(target.handle)}...`
3078
+ `Installing Codex login on ${chalk5.bold(target.handle)}...`
3238
3079
  ).start();
3239
3080
  try {
3240
3081
  const installScript = buildInstallScript2(authJSON);
3241
3082
  await runRemoteCommand(target, ["bash", "-s"], installScript);
3242
- spinner.succeed(`Installed Codex login on ${chalk7.bold(target.handle)}`);
3083
+ spinner.succeed(`Installed Codex login on ${chalk5.bold(target.handle)}`);
3243
3084
  } catch (error) {
3244
3085
  spinner.fail(
3245
3086
  error instanceof Error ? error.message : `Failed to install Codex login on ${target.handle}`
@@ -3249,11 +3090,11 @@ async function installCodexAuth(target, authJSON) {
3249
3090
  }
3250
3091
  async function verifyTargetMachine2(handle, target) {
3251
3092
  const spinner = ora6(
3252
- `Verifying Codex login on ${chalk7.bold(handle)}...`
3093
+ `Verifying Codex login on ${chalk5.bold(handle)}...`
3253
3094
  ).start();
3254
3095
  const result = await verifyStoredCodexAuth(target);
3255
3096
  if (result.status === "verified") {
3256
- spinner.succeed(`Verified Codex login on ${chalk7.bold(handle)}`);
3097
+ spinner.succeed(`Verified Codex login on ${chalk5.bold(handle)}`);
3257
3098
  return result;
3258
3099
  }
3259
3100
  spinner.warn(result.detail);
@@ -3261,7 +3102,7 @@ async function verifyTargetMachine2(handle, target) {
3261
3102
  }
3262
3103
  async function verifySharedInstall2(primaryHandle, primaryComputerID, sharedInstall, skip, verify) {
3263
3104
  const spinner = ora6(
3264
- `Verifying shared-home Codex login from ${chalk7.bold(primaryHandle)}...`
3105
+ `Verifying shared-home Codex login from ${chalk5.bold(primaryHandle)}...`
3265
3106
  ).start();
3266
3107
  const result = await verifySecondaryMachine(
3267
3108
  primaryComputerID,
@@ -3271,7 +3112,7 @@ async function verifySharedInstall2(primaryHandle, primaryComputerID, sharedInst
3271
3112
  );
3272
3113
  if (result.status === "verified") {
3273
3114
  spinner.succeed(
3274
- `Verified shared-home Codex login on ${chalk7.bold(result.handle)}`
3115
+ `Verified shared-home Codex login on ${chalk5.bold(result.handle)}`
3275
3116
  );
3276
3117
  return result;
3277
3118
  }
@@ -3329,9 +3170,9 @@ function firstStatusLine2(value) {
3329
3170
  }
3330
3171
 
3331
3172
  // src/commands/images.ts
3332
- import { confirm as confirm2, input as textInput3, select as select3 } from "@inquirer/prompts";
3173
+ import { confirm as confirm2, input as textInput3, select as select2 } from "@inquirer/prompts";
3333
3174
  import { Command as Command8 } from "commander";
3334
- import chalk8 from "chalk";
3175
+ import chalk6 from "chalk";
3335
3176
  import ora7 from "ora";
3336
3177
  var imageCommand = new Command8("image").description("Manage machine image sources");
3337
3178
  imageCommand.command("ls").description("List machine image sources").option("--json", "Print raw JSON").action(async (options) => {
@@ -3364,7 +3205,7 @@ imageCommand.command("save").description("Create or update a machine image sourc
3364
3205
  return;
3365
3206
  }
3366
3207
  console.log();
3367
- console.log(chalk8.green("Saved machine image source."));
3208
+ console.log(chalk6.green("Saved machine image source."));
3368
3209
  printMachineSourceSettings(settings);
3369
3210
  } catch (error) {
3370
3211
  if (spinner) {
@@ -3403,9 +3244,9 @@ imageCommand.command("default").description("Set the default machine image sourc
3403
3244
  if (!usePlatformDefault) {
3404
3245
  const selected = settings.default_machine_source ?? void 0;
3405
3246
  const label = selected ? summarizeMachineSource(selected) : sourceID;
3406
- console.log(chalk8.green(`Selected ${chalk8.bold(label)} as the default machine image.`));
3247
+ console.log(chalk6.green(`Selected ${chalk6.bold(label)} as the default machine image.`));
3407
3248
  } else {
3408
- console.log(chalk8.green("Using the AgentComputer platform default image."));
3249
+ console.log(chalk6.green("Using the AgentComputer platform default image."));
3409
3250
  }
3410
3251
  printMachineSourceSettings(settings);
3411
3252
  } catch (error) {
@@ -3427,7 +3268,7 @@ imageCommand.command("rebuild").description("Rebuild a machine image source").ar
3427
3268
  return;
3428
3269
  }
3429
3270
  console.log();
3430
- console.log(chalk8.green(`Queued rebuild for ${chalk8.bold(sourceID)}.`));
3271
+ console.log(chalk6.green(`Queued rebuild for ${chalk6.bold(sourceID)}.`));
3431
3272
  printMachineSourceSettings(settings);
3432
3273
  } catch (error) {
3433
3274
  if (spinner) {
@@ -3446,7 +3287,7 @@ imageCommand.command("rm").description("Delete a machine image source").argument
3446
3287
  if (!skipConfirm && process.stdin.isTTY) {
3447
3288
  const confirmed = await confirmDeletion(sourceID);
3448
3289
  if (!confirmed) {
3449
- console.log(chalk8.dim(" Cancelled."));
3290
+ console.log(chalk6.dim(" Cancelled."));
3450
3291
  return;
3451
3292
  }
3452
3293
  }
@@ -3458,7 +3299,7 @@ imageCommand.command("rm").description("Delete a machine image source").argument
3458
3299
  return;
3459
3300
  }
3460
3301
  console.log();
3461
- console.log(chalk8.green(`Deleted machine image source ${chalk8.bold(sourceID)}.`));
3302
+ console.log(chalk6.green(`Deleted machine image source ${chalk6.bold(sourceID)}.`));
3462
3303
  printMachineSourceSettings(settings);
3463
3304
  } catch (error) {
3464
3305
  if (spinner) {
@@ -3470,14 +3311,14 @@ imageCommand.command("rm").description("Delete a machine image source").argument
3470
3311
  }
3471
3312
  });
3472
3313
  function printMachineSourceSettings(settings) {
3473
- console.log(` ${chalk8.dim("Default")} ${chalk8.white(summarizeDefaultMachineSource(settings))}`);
3314
+ console.log(` ${chalk6.dim("Default")} ${chalk6.white(summarizeDefaultMachineSource(settings))}`);
3474
3315
  console.log();
3475
3316
  if (settings.sources.length === 0) {
3476
- console.log(chalk8.dim(" No custom machine images configured yet."));
3317
+ console.log(chalk6.dim(" No custom machine images configured yet."));
3477
3318
  console.log();
3478
3319
  return;
3479
3320
  }
3480
- console.log(` ${chalk8.dim("Custom")} ${chalk8.white(formatMachineSourceCounts(settings.sources))}`);
3321
+ console.log(` ${chalk6.dim("Custom")} ${chalk6.white(formatMachineSourceCounts(settings.sources))}`);
3481
3322
  console.log();
3482
3323
  for (const source of sortMachineSources(settings.sources, settings.default_machine_source_id)) {
3483
3324
  printMachineSourceCard(source, settings.default_machine_source_id === source.id);
@@ -3492,17 +3333,17 @@ function printMachineSourceCard(source, isDefault) {
3492
3333
  ];
3493
3334
  const extra = machineSourceExtraParts(source);
3494
3335
  console.log(
3495
- ` ${statusLabel}${chalk8.bold(machineSourceTitle(source))}${isDefault ? chalk8.green(" default") : ""}`
3336
+ ` ${statusLabel}${chalk6.bold(machineSourceTitle(source))}${isDefault ? chalk6.green(" default") : ""}`
3496
3337
  );
3497
- console.log(` ${chalk8.dim(meta.concat(extra).join(" | "))}`);
3498
- console.log(` ${chalk8.dim(machineSourceStatusSummary(source))}`);
3338
+ console.log(` ${chalk6.dim(meta.concat(extra).join(" | "))}`);
3339
+ console.log(` ${chalk6.dim(machineSourceStatusSummary(source))}`);
3499
3340
  if (source.resolved_image_ref) {
3500
- console.log(` ${chalk8.dim("resolved")} ${source.resolved_image_ref}`);
3341
+ console.log(` ${chalk6.dim("resolved")} ${source.resolved_image_ref}`);
3501
3342
  } else if (source.last_good_resolved_image_ref) {
3502
- console.log(` ${chalk8.dim("last good")} ${source.last_good_resolved_image_ref}`);
3343
+ console.log(` ${chalk6.dim("last good")} ${source.last_good_resolved_image_ref}`);
3503
3344
  }
3504
3345
  if (source.error) {
3505
- console.log(` ${chalk8.red(source.error)}`);
3346
+ console.log(` ${chalk6.red(source.error)}`);
3506
3347
  }
3507
3348
  console.log();
3508
3349
  }
@@ -3572,13 +3413,13 @@ function formatMachineSourceStatus(status) {
3572
3413
  const text = status.toUpperCase();
3573
3414
  switch (status) {
3574
3415
  case "ready":
3575
- return chalk8.green(text);
3416
+ return chalk6.green(text);
3576
3417
  case "failed":
3577
- return chalk8.red(text);
3418
+ return chalk6.red(text);
3578
3419
  case "pending":
3579
3420
  case "resolving":
3580
3421
  case "building":
3581
- return chalk8.yellow(text);
3422
+ return chalk6.yellow(text);
3582
3423
  default:
3583
3424
  return text;
3584
3425
  }
@@ -3687,7 +3528,7 @@ async function resolveSaveInput(options) {
3687
3528
  return input;
3688
3529
  }
3689
3530
  async function selectMachineSourceKind() {
3690
- const kind = await select3({
3531
+ const kind = await select2({
3691
3532
  message: "Select machine image kind",
3692
3533
  choices: [
3693
3534
  { name: "oci-image - resolve an OCI image digest", value: "oci-image" },
@@ -3721,7 +3562,7 @@ async function confirmDeletion(sourceID) {
3721
3562
 
3722
3563
  // src/commands/login.ts
3723
3564
  import { Command as Command9 } from "commander";
3724
- import chalk9 from "chalk";
3565
+ import chalk7 from "chalk";
3725
3566
  import ora8 from "ora";
3726
3567
 
3727
3568
  // src/lib/browser-login.ts
@@ -3994,7 +3835,7 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
3994
3835
  if (existingKey && !options.force) {
3995
3836
  console.log();
3996
3837
  console.log(
3997
- chalk9.yellow(" Already logged in. Use --force to overwrite.")
3838
+ chalk7.yellow(" Already logged in. Use --force to overwrite.")
3998
3839
  );
3999
3840
  console.log();
4000
3841
  return;
@@ -4003,8 +3844,8 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
4003
3844
  const apiKey = await resolveAPIKeyInput(options.apiKey, options.stdin);
4004
3845
  if (!apiKey && wantsManualLogin) {
4005
3846
  console.log();
4006
- console.log(chalk9.dim(" Usage: computer login --api-key <ac_live_...>"));
4007
- console.log(chalk9.dim(` API: ${getBaseURL()}`));
3847
+ console.log(chalk7.dim(" Usage: computer login --api-key <ac_live_...>"));
3848
+ console.log(chalk7.dim(` API: ${getBaseURL()}`));
4008
3849
  console.log();
4009
3850
  process.exit(1);
4010
3851
  }
@@ -4014,7 +3855,7 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
4014
3855
  }
4015
3856
  if (!apiKey.startsWith("ac_live_")) {
4016
3857
  console.log();
4017
- console.log(chalk9.red(" API key must start with ac_live_"));
3858
+ console.log(chalk7.red(" API key must start with ac_live_"));
4018
3859
  console.log();
4019
3860
  process.exit(1);
4020
3861
  }
@@ -4022,7 +3863,7 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
4022
3863
  try {
4023
3864
  const me = await apiWithKey(apiKey, "/v1/me");
4024
3865
  setAPIKey(apiKey);
4025
- spinner.succeed(`Logged in as ${chalk9.bold(me.user.email)}`);
3866
+ spinner.succeed(`Logged in as ${chalk7.bold(me.user.email)}`);
4026
3867
  } catch (error) {
4027
3868
  spinner.fail(
4028
3869
  error instanceof Error ? error.message : "Failed to validate API key"
@@ -4042,15 +3883,15 @@ async function runBrowserLogin() {
4042
3883
  spinner.stop();
4043
3884
  console.log();
4044
3885
  console.log(
4045
- chalk9.yellow(" Browser auto-open failed. Open this URL to continue:")
3886
+ chalk7.yellow(" Browser auto-open failed. Open this URL to continue:")
4046
3887
  );
4047
- console.log(chalk9.dim(` ${attempt.loginURL}`));
3888
+ console.log(chalk7.dim(` ${attempt.loginURL}`));
4048
3889
  console.log();
4049
3890
  spinner.start("Waiting for browser login...");
4050
3891
  }
4051
3892
  spinner.text = "Waiting for browser login...";
4052
3893
  const result = await attempt.waitForResult();
4053
- spinner.succeed(`Logged in as ${chalk9.bold(result.me.user.email)}`);
3894
+ spinner.succeed(`Logged in as ${chalk7.bold(result.me.user.email)}`);
4054
3895
  await continueFirstLoginFlow(result);
4055
3896
  } catch (error) {
4056
3897
  spinner.fail(
@@ -4084,8 +3925,8 @@ async function continueFirstLoginFlow(result) {
4084
3925
  }
4085
3926
  console.log();
4086
3927
  console.log(
4087
- chalk9.cyan(
4088
- `Continuing first-time setup for ${chalk9.bold(machineHandle)}...
3928
+ chalk7.cyan(
3929
+ `Continuing first-time setup for ${chalk7.bold(machineHandle)}...
4089
3930
  `
4090
3931
  )
4091
3932
  );
@@ -4098,8 +3939,8 @@ async function continueFirstLoginFlow(result) {
4098
3939
  const spinner = ora8(`Preparing SSH access for ${machineHandle}...`).start();
4099
3940
  try {
4100
3941
  const connection = await prepareSSHConnectionByIdentifier(machineHandle);
4101
- spinner.succeed(`Connecting to ${chalk9.bold(machineHandle)}`);
4102
- console.log(chalk9.dim(` ${connection.command}`));
3942
+ spinner.succeed(`Connecting to ${chalk7.bold(machineHandle)}`);
3943
+ console.log(chalk7.dim(` ${connection.command}`));
4103
3944
  console.log();
4104
3945
  await openSSHConnection(connection);
4105
3946
  } catch (error) {
@@ -4110,19 +3951,19 @@ async function continueFirstLoginFlow(result) {
4110
3951
  }
4111
3952
  } catch (error) {
4112
3953
  const message = error instanceof Error ? error.message : "Failed to finish first-time setup";
4113
- console.error(chalk9.red(`
3954
+ console.error(chalk7.red(`
4114
3955
  ${message}`));
4115
3956
  console.log();
4116
3957
  if (result.provider === "claude") {
4117
3958
  console.log(
4118
- chalk9.dim(` computer claude-login --machine ${machineHandle}`)
3959
+ chalk7.dim(` computer claude-login --machine ${machineHandle}`)
4119
3960
  );
4120
3961
  } else if (result.provider === "codex") {
4121
3962
  console.log(
4122
- chalk9.dim(` computer codex-login --machine ${machineHandle}`)
3963
+ chalk7.dim(` computer codex-login --machine ${machineHandle}`)
4123
3964
  );
4124
3965
  }
4125
- console.log(chalk9.dim(` computer ssh ${machineHandle}`));
3966
+ console.log(chalk7.dim(` computer ssh ${machineHandle}`));
4126
3967
  console.log();
4127
3968
  process.exit(1);
4128
3969
  }
@@ -4136,45 +3977,291 @@ async function runSelectedProvider(provider, machineHandle) {
4136
3977
  await runCodexLogin({ machine: machineHandle });
4137
3978
  return;
4138
3979
  }
4139
- console.log(chalk9.green(`Sandbox ${chalk9.bold(machineHandle)} is ready.`));
3980
+ console.log(chalk7.green(`Sandbox ${chalk7.bold(machineHandle)} is ready.`));
4140
3981
  console.log();
4141
3982
  }
4142
3983
  function printNextStep(machineHandle) {
4143
- console.log(chalk9.green(`Sandbox ${chalk9.bold(machineHandle)} is ready.`));
4144
- console.log(chalk9.dim(` computer ssh ${machineHandle}`));
3984
+ console.log(chalk7.green(`Sandbox ${chalk7.bold(machineHandle)} is ready.`));
3985
+ console.log(chalk7.dim(` computer ssh ${machineHandle}`));
4145
3986
  console.log();
4146
3987
  }
4147
3988
 
4148
- // src/commands/logout.ts
3989
+ // src/commands/mount.ts
4149
3990
  import { Command as Command10 } from "commander";
4150
- import chalk10 from "chalk";
4151
- var logoutCommand = new Command10("logout").description("Remove stored API key").action(() => {
3991
+ import chalk8 from "chalk";
3992
+ import ora9 from "ora";
3993
+
3994
+ // src/lib/mount-daemon.ts
3995
+ import { mkdir as mkdir3 } from "fs/promises";
3996
+ function getMountControllerState(rootPath = defaultMountServiceConfig().rootPath) {
3997
+ const lock = readMountControllerLock(rootPath);
3998
+ if (!lock) {
3999
+ return { running: false };
4000
+ }
4001
+ if (!processExists(lock.pid)) {
4002
+ removeMountControllerLock(rootPath);
4003
+ return { running: false };
4004
+ }
4005
+ return { running: true, pid: lock.pid };
4006
+ }
4007
+ async function runMountDaemon(config) {
4008
+ const paths = getMountPaths(config.rootPath);
4009
+ ensureMountDirectories(paths);
4010
+ await mkdir3(paths.rootPath, { recursive: true });
4011
+ await acquireControllerLock(config.rootPath);
4012
+ writeMountStatusSnapshot(
4013
+ {
4014
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4015
+ controllerPid: process.pid,
4016
+ running: true,
4017
+ mounts: []
4018
+ },
4019
+ config.rootPath
4020
+ );
4021
+ await teardownManagedSessions(config, paths);
4022
+ let running = false;
4023
+ let queued = false;
4024
+ let shuttingDown = false;
4025
+ let activeRun = null;
4026
+ const runOnce = async () => {
4027
+ if (shuttingDown) {
4028
+ return;
4029
+ }
4030
+ if (running) {
4031
+ queued = true;
4032
+ return activeRun ?? void 0;
4033
+ }
4034
+ activeRun = (async () => {
4035
+ running = true;
4036
+ try {
4037
+ await reconcileMounts(config, paths, process.pid);
4038
+ } catch (error) {
4039
+ if (shuttingDown) {
4040
+ return;
4041
+ }
4042
+ const previous = readMountStatusSnapshot(config.rootPath);
4043
+ writeMountStatusSnapshot(
4044
+ {
4045
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4046
+ controllerPid: process.pid,
4047
+ running: true,
4048
+ lastHealthySyncAt: previous?.lastHealthySyncAt,
4049
+ lastSuccessfulSyncAt: previous?.lastSuccessfulSyncAt,
4050
+ lastIssueAt: (/* @__PURE__ */ new Date()).toISOString(),
4051
+ lastIssue: error instanceof Error ? error.message : "mount reconcile failed",
4052
+ lastError: error instanceof Error ? error.message : "mount reconcile failed",
4053
+ mounts: previous?.mounts ?? []
4054
+ },
4055
+ config.rootPath
4056
+ );
4057
+ console.error(
4058
+ error instanceof Error ? error.message : "mount reconcile failed"
4059
+ );
4060
+ } finally {
4061
+ running = false;
4062
+ activeRun = null;
4063
+ if (queued && !shuttingDown) {
4064
+ queued = false;
4065
+ void runOnce();
4066
+ }
4067
+ }
4068
+ })();
4069
+ return activeRun;
4070
+ };
4071
+ const server = createMountControlServer(paths, async () => {
4072
+ await runOnce();
4073
+ });
4074
+ await new Promise((resolve, reject) => {
4075
+ server.once("error", reject);
4076
+ server.listen(paths.socketPath, () => resolve());
4077
+ });
4078
+ const interval = setInterval(() => {
4079
+ void runOnce();
4080
+ }, config.pollIntervalMs);
4081
+ const shutdown = async () => {
4082
+ if (shuttingDown) {
4083
+ return;
4084
+ }
4085
+ shuttingDown = true;
4086
+ clearInterval(interval);
4087
+ server.close();
4088
+ if (activeRun) {
4089
+ await activeRun.catch(() => {
4090
+ });
4091
+ }
4092
+ await teardownManagedSessions(config, paths);
4093
+ const previous = readMountStatusSnapshot(config.rootPath);
4094
+ writeMountStatusSnapshot(
4095
+ {
4096
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4097
+ controllerPid: previous?.controllerPid,
4098
+ running: false,
4099
+ lastHealthySyncAt: previous?.lastHealthySyncAt,
4100
+ lastSuccessfulSyncAt: previous?.lastSuccessfulSyncAt,
4101
+ lastIssueAt: previous?.lastIssueAt,
4102
+ lastIssue: void 0,
4103
+ lastError: void 0,
4104
+ mounts: []
4105
+ },
4106
+ config.rootPath
4107
+ );
4108
+ removeMountControllerLock(config.rootPath);
4109
+ process.exit(0);
4110
+ };
4111
+ process.on("SIGINT", () => {
4112
+ void shutdown();
4113
+ });
4114
+ process.on("SIGTERM", () => {
4115
+ void shutdown();
4116
+ });
4117
+ await runOnce();
4118
+ await new Promise(() => {
4119
+ });
4120
+ }
4121
+ async function acquireControllerLock(rootPath) {
4122
+ const existing = readMountControllerLock(rootPath);
4123
+ if (existing && processExists(existing.pid)) {
4124
+ throw new Error(`computer mount is already running (pid ${existing.pid})`);
4125
+ }
4126
+ removeMountControllerLock(rootPath);
4127
+ writeMountControllerLock(
4128
+ {
4129
+ pid: process.pid,
4130
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
4131
+ },
4132
+ rootPath
4133
+ );
4134
+ }
4135
+ function processExists(pid) {
4136
+ if (!Number.isInteger(pid) || pid <= 0) {
4137
+ return false;
4138
+ }
4139
+ try {
4140
+ process.kill(pid, 0);
4141
+ return true;
4142
+ } catch {
4143
+ return false;
4144
+ }
4145
+ }
4146
+
4147
+ // src/commands/mount.ts
4148
+ 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) => {
4149
+ const spinner = ora9("Starting machine mount controller...").start();
4150
+ try {
4151
+ const issues = getMountHostValidationIssues();
4152
+ if (issues.length > 0) {
4153
+ throw new Error(
4154
+ [
4155
+ ...issues.map((issue) => issue.message),
4156
+ ...formatMountHostInstallGuidance(issues)
4157
+ ].join("\n")
4158
+ );
4159
+ }
4160
+ const sshSetup = await ensureSSHAccessConfigured(options);
4161
+ const config = {
4162
+ ...defaultMountServiceConfig(),
4163
+ alias: sshSetup.alias,
4164
+ host: sshSetup.host,
4165
+ port: sshSetup.port,
4166
+ pollIntervalMs: parsePositiveInt(options.pollInterval, "poll interval"),
4167
+ connectTimeoutSeconds: parsePositiveInt(
4168
+ options.connectTimeout,
4169
+ "connect timeout"
4170
+ )
4171
+ };
4172
+ writeMountConfig(config);
4173
+ spinner.succeed("Machine mount controller running");
4174
+ console.log();
4175
+ console.log(chalk8.dim(` Root: ${config.rootPath}`));
4176
+ console.log(chalk8.dim(` SSH alias: ${config.alias}`));
4177
+ console.log(chalk8.dim(` Poll: ${config.pollIntervalMs}ms`));
4178
+ console.log();
4179
+ console.log(chalk8.dim(" Press Ctrl-C to stop syncing."));
4180
+ console.log();
4181
+ await runMountDaemon(config);
4182
+ } catch (error) {
4183
+ spinner.fail(
4184
+ error instanceof Error ? error.message : "Failed to start machine mount controller"
4185
+ );
4186
+ process.exit(1);
4187
+ }
4188
+ }).addCommand(
4189
+ new Command10("status").description("Show machine mount controller status").action(() => {
4190
+ const config = readMountConfig() ?? defaultMountServiceConfig();
4191
+ const controller = getMountControllerState(config.rootPath);
4192
+ const snapshot = readMountStatusSnapshot(config.rootPath);
4193
+ console.log();
4194
+ console.log(` ${chalk8.bold("Machine Mounts")}`);
4195
+ console.log();
4196
+ console.log(
4197
+ ` ${chalk8.dim("Running")} ${controller.running ? chalk8.green("yes") : chalk8.dim("no")}`
4198
+ );
4199
+ if (controller.pid) {
4200
+ console.log(` ${chalk8.dim("PID")} ${controller.pid}`);
4201
+ }
4202
+ console.log(` ${chalk8.dim("Root")} ${config.rootPath}`);
4203
+ console.log(` ${chalk8.dim("Alias")} ${config.alias}`);
4204
+ console.log(
4205
+ ` ${chalk8.dim("Updated")} ${snapshot?.updatedAt ? timeAgo(snapshot.updatedAt) : chalk8.dim("never")}`
4206
+ );
4207
+ console.log(
4208
+ ` ${chalk8.dim("Healthy")} ${snapshot?.lastHealthySyncAt ? timeAgo(snapshot.lastHealthySyncAt) : chalk8.dim("never")}`
4209
+ );
4210
+ if (controller.running && snapshot?.mounts.length) {
4211
+ console.log();
4212
+ for (const mount of snapshot.mounts) {
4213
+ const state = mount.state === "mounted" ? chalk8.green(mount.state) : mount.state === "reconnecting" ? chalk8.cyan(mount.state) : mount.state === "degraded" ? chalk8.yellow(mount.state) : mount.state === "pending" ? chalk8.yellow(mount.state) : chalk8.red(mount.state);
4214
+ console.log(` ${chalk8.white(mount.handle)} ${state} ${chalk8.dim(mount.mountPath)}`);
4215
+ if (mount.message) {
4216
+ console.log(` ${chalk8.dim(mount.message)}`);
4217
+ }
4218
+ }
4219
+ }
4220
+ if (snapshot?.lastIssue) {
4221
+ console.log();
4222
+ console.log(` ${chalk8.dim("Last issue")} ${chalk8.yellow(snapshot.lastIssue)}`);
4223
+ }
4224
+ console.log();
4225
+ })
4226
+ );
4227
+ function parsePositiveInt(raw, label) {
4228
+ const value = Number(raw);
4229
+ if (!Number.isFinite(value) || value <= 0) {
4230
+ throw new Error(`${label} must be a positive number`);
4231
+ }
4232
+ return Math.round(value);
4233
+ }
4234
+
4235
+ // src/commands/logout.ts
4236
+ import { Command as Command11 } from "commander";
4237
+ import chalk9 from "chalk";
4238
+ var logoutCommand = new Command11("logout").description("Remove stored API key").action(() => {
4152
4239
  if (!getStoredAPIKey()) {
4153
4240
  console.log();
4154
- console.log(chalk10.dim(" Not logged in."));
4241
+ console.log(chalk9.dim(" Not logged in."));
4155
4242
  if (hasEnvAPIKey()) {
4156
- console.log(chalk10.dim(" Environment API key is still active in this shell."));
4243
+ console.log(chalk9.dim(" Environment API key is still active in this shell."));
4157
4244
  }
4158
4245
  console.log();
4159
4246
  return;
4160
4247
  }
4161
4248
  clearAPIKey();
4162
4249
  console.log();
4163
- console.log(chalk10.green(" Logged out."));
4250
+ console.log(chalk9.green(" Logged out."));
4164
4251
  if (hasEnvAPIKey()) {
4165
- console.log(chalk10.dim(" Environment API key is still active in this shell."));
4252
+ console.log(chalk9.dim(" Environment API key is still active in this shell."));
4166
4253
  }
4167
4254
  console.log();
4168
4255
  });
4169
4256
 
4170
4257
  // src/commands/upgrade.ts
4171
4258
  import { spawnSync } from "child_process";
4172
- import { readFileSync as readFileSync3, realpathSync } from "fs";
4173
- import { Command as Command11 } from "commander";
4174
- import chalk11 from "chalk";
4175
- import ora9 from "ora";
4259
+ import { readFileSync as readFileSync2, realpathSync } from "fs";
4260
+ import { Command as Command12 } from "commander";
4261
+ import chalk10 from "chalk";
4262
+ import ora10 from "ora";
4176
4263
  var pkg2 = JSON.parse(
4177
- readFileSync3(new URL("../package.json", import.meta.url), "utf8")
4264
+ readFileSync2(new URL("../package.json", import.meta.url), "utf8")
4178
4265
  );
4179
4266
  function normalizeVersion(version) {
4180
4267
  return version.split("-")[0].split(".").map((part) => Number.parseInt(part, 10)).map((part) => Number.isNaN(part) ? 0 : part);
@@ -4286,10 +4373,10 @@ async function getLatestVersion(packageName) {
4286
4373
  }
4287
4374
  return payload.version;
4288
4375
  }
4289
- var upgradeCommand = new Command11("upgrade").description("Update the CLI to the latest version").action(async () => {
4376
+ var upgradeCommand = new Command12("upgrade").description("Update the CLI to the latest version").action(async () => {
4290
4377
  const currentVersion = pkg2.version ?? "0.0.0";
4291
4378
  const packageName = pkg2.name ?? "aicomputer";
4292
- const spinner = ora9("Checking for updates...").start();
4379
+ const spinner = ora10("Checking for updates...").start();
4293
4380
  let latestVersion;
4294
4381
  try {
4295
4382
  latestVersion = await getLatestVersion(packageName);
@@ -4319,16 +4406,16 @@ var upgradeCommand = new Command11("upgrade").description("Update the CLI to the
4319
4406
  spinner.stop();
4320
4407
  console.log();
4321
4408
  console.log(
4322
- chalk11.dim(` Updating ${chalk11.bold(`v${currentVersion}`)} -> ${chalk11.bold(`v${latestVersion}`)}`)
4409
+ chalk10.dim(` Updating ${chalk10.bold(`v${currentVersion}`)} -> ${chalk10.bold(`v${latestVersion}`)}`)
4323
4410
  );
4324
- console.log(chalk11.dim(` ${upgrade.label}`));
4411
+ console.log(chalk10.dim(` ${upgrade.label}`));
4325
4412
  console.log();
4326
4413
  const result = spawnSync(upgrade.command, upgrade.args, {
4327
4414
  stdio: "inherit"
4328
4415
  });
4329
4416
  if (result.status === 0) {
4330
4417
  console.log();
4331
- console.log(chalk11.green(` Updated to v${latestVersion}.`));
4418
+ console.log(chalk10.green(` Updated to v${latestVersion}.`));
4332
4419
  console.log();
4333
4420
  return;
4334
4421
  }
@@ -4336,11 +4423,11 @@ var upgradeCommand = new Command11("upgrade").description("Update the CLI to the
4336
4423
  });
4337
4424
 
4338
4425
  // src/commands/whoami.ts
4339
- import { Command as Command12 } from "commander";
4340
- import chalk12 from "chalk";
4341
- import ora10 from "ora";
4342
- var whoamiCommand = new Command12("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
4343
- const spinner = options.json ? null : ora10("Loading user...").start();
4426
+ import { Command as Command13 } from "commander";
4427
+ import chalk11 from "chalk";
4428
+ import ora11 from "ora";
4429
+ var whoamiCommand = new Command13("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
4430
+ const spinner = options.json ? null : ora11("Loading user...").start();
4344
4431
  try {
4345
4432
  const me = await api("/v1/me");
4346
4433
  spinner?.stop();
@@ -4349,14 +4436,14 @@ var whoamiCommand = new Command12("whoami").description("Show current user").opt
4349
4436
  return;
4350
4437
  }
4351
4438
  console.log();
4352
- console.log(` ${chalk12.bold.white(me.user.display_name || me.user.email)}`);
4439
+ console.log(` ${chalk11.bold.white(me.user.display_name || me.user.email)}`);
4353
4440
  if (me.user.display_name) {
4354
- console.log(` ${chalk12.dim(me.user.email)}`);
4441
+ console.log(` ${chalk11.dim(me.user.email)}`);
4355
4442
  }
4356
4443
  if (me.api_key.name) {
4357
- console.log(` ${chalk12.dim("Key:")} ${me.api_key.name}`);
4444
+ console.log(` ${chalk11.dim("Key:")} ${me.api_key.name}`);
4358
4445
  }
4359
- console.log(` ${chalk12.dim("API:")} ${chalk12.dim(getBaseURL())}`);
4446
+ console.log(` ${chalk11.dim("API:")} ${chalk11.dim(getBaseURL())}`);
4360
4447
  console.log();
4361
4448
  } catch (error) {
4362
4449
  if (spinner) {
@@ -4370,18 +4457,18 @@ var whoamiCommand = new Command12("whoami").description("Show current user").opt
4370
4457
 
4371
4458
  // src/index.ts
4372
4459
  var pkg3 = JSON.parse(
4373
- readFileSync4(new URL("../package.json", import.meta.url), "utf8")
4460
+ readFileSync3(new URL("../package.json", import.meta.url), "utf8")
4374
4461
  );
4375
4462
  var cliName = process.argv[1] ? basename2(process.argv[1]) : "agentcomputer";
4376
- var program = new Command13();
4463
+ var program = new Command14();
4377
4464
  function appendTextSection(lines, title, values) {
4378
4465
  if (values.length === 0) {
4379
4466
  return;
4380
4467
  }
4381
- lines.push(` ${chalk13.dim(title)}`);
4468
+ lines.push(` ${chalk12.dim(title)}`);
4382
4469
  lines.push("");
4383
4470
  for (const value of values) {
4384
- lines.push(` ${chalk13.white(value)}`);
4471
+ lines.push(` ${chalk12.white(value)}`);
4385
4472
  }
4386
4473
  lines.push("");
4387
4474
  }
@@ -4390,10 +4477,10 @@ function appendTableSection(lines, title, entries) {
4390
4477
  return;
4391
4478
  }
4392
4479
  const width = Math.max(...entries.map((entry) => entry.term.length), 0) + 2;
4393
- lines.push(` ${chalk13.dim(title)}`);
4480
+ lines.push(` ${chalk12.dim(title)}`);
4394
4481
  lines.push("");
4395
4482
  for (const entry of entries) {
4396
- lines.push(` ${chalk13.white(padEnd(entry.term, width))}${chalk13.dim(entry.desc)}`);
4483
+ lines.push(` ${chalk12.white(padEnd(entry.term, width))}${chalk12.dim(entry.desc)}`);
4397
4484
  }
4398
4485
  lines.push("");
4399
4486
  }
@@ -4414,14 +4501,15 @@ function formatRootHelp(cmd) {
4414
4501
  ["Computers", []],
4415
4502
  ["Images", []],
4416
4503
  ["Access", []],
4504
+ ["Mounts", []],
4417
4505
  ["Agents", []],
4418
4506
  ["Other", []]
4419
4507
  ];
4420
4508
  const otherGroup = groups.find(([name]) => name === "Other")[1];
4421
- lines.push(`${chalk13.bold(cliName)} ${chalk13.dim(`v${version}`)}`);
4509
+ lines.push(`${chalk12.bold(cliName)} ${chalk12.dim(`v${version}`)}`);
4422
4510
  lines.push("");
4423
4511
  if (cmd.description()) {
4424
- lines.push(` ${chalk13.dim(cmd.description())}`);
4512
+ lines.push(` ${chalk12.dim(cmd.description())}`);
4425
4513
  lines.push("");
4426
4514
  }
4427
4515
  appendTextSection(lines, "Usage", [`${cliName} <command> [options]`]);
@@ -4436,8 +4524,10 @@ function formatRootHelp(cmd) {
4436
4524
  groups[2][1].push(entry);
4437
4525
  } else if (["open", "ssh", "ports"].includes(name)) {
4438
4526
  groups[3][1].push(entry);
4439
- } else if (["agent", "acp"].includes(name)) {
4527
+ } else if (name === "mount") {
4440
4528
  groups[4][1].push(entry);
4529
+ } else if (["agent", "acp"].includes(name)) {
4530
+ groups[5][1].push(entry);
4441
4531
  } else {
4442
4532
  otherGroup.push(entry);
4443
4533
  }
@@ -4472,10 +4562,10 @@ function formatSubcommandHelp(cmd, helper) {
4472
4562
  term: helper.optionTerm(option),
4473
4563
  desc: helper.optionDescription(option)
4474
4564
  }));
4475
- lines.push(chalk13.bold(commandPath(cmd)));
4565
+ lines.push(chalk12.bold(commandPath(cmd)));
4476
4566
  lines.push("");
4477
4567
  if (description) {
4478
- lines.push(` ${chalk13.dim(description)}`);
4568
+ lines.push(` ${chalk12.dim(description)}`);
4479
4569
  lines.push("");
4480
4570
  }
4481
4571
  appendTextSection(lines, "Usage", [helper.commandUsage(cmd)]);
@@ -4514,6 +4604,7 @@ program.addCommand(acpCommand);
4514
4604
  program.addCommand(openCommand);
4515
4605
  program.addCommand(sshCommand);
4516
4606
  program.addCommand(portsCommand);
4607
+ program.addCommand(mountCommand);
4517
4608
  program.addCommand(removeCommand);
4518
4609
  program.addCommand(completionCommand);
4519
4610
  applyHelpFormatting(program);