aicomputer 0.1.14 → 0.1.15

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