aicomputer 0.1.16 → 0.1.18

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
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  formatMountHostInstallGuidance,
4
4
  getMountHostValidationIssues
5
- } from "./chunk-OWK5N76S.js";
5
+ } from "./chunk-JMRAYXUO.js";
6
6
  import {
7
7
  ApiError,
8
8
  api,
@@ -33,7 +33,13 @@ import {
33
33
  timeAgo,
34
34
  vncURL,
35
35
  webURL
36
- } from "./chunk-5IEWKH52.js";
36
+ } from "./chunk-3ZUTAUUD.js";
37
+ import {
38
+ AGENTCOMPUTER_MUTAGEN_PATH_ENV,
39
+ ensureBundledMutagenInstalled,
40
+ ensureMutagenCommandPath,
41
+ isAbortError
42
+ } from "./chunk-F2U4SFJ4.js";
37
43
  import {
38
44
  defaultMountServiceConfig,
39
45
  ensureMountDirectories,
@@ -48,8 +54,8 @@ import {
48
54
  } from "./chunk-KXLTHWW3.js";
49
55
 
50
56
  // src/index.ts
51
- import { Command as Command14 } from "commander";
52
- import chalk12 from "chalk";
57
+ import { Command as Command15 } from "commander";
58
+ import chalk13 from "chalk";
53
59
  import { readFileSync as readFileSync3 } from "fs";
54
60
  import { basename as basename2 } from "path";
55
61
 
@@ -2460,7 +2466,7 @@ _computer() {
2460
2466
  'open:Open in browser'
2461
2467
  'ssh:SSH into a computer'
2462
2468
  'ports:Manage published ports'
2463
- 'mount:Mirror SSH-ready machine homes into ~/agentcomputer while running'
2469
+ 'mount:Mirror SSH-ready machine homes into ~/agentcomputer'
2464
2470
  'agent:Manage cloud agent sessions'
2465
2471
  'acp:Run a local ACP bridge for remote agent sessions'
2466
2472
  'rm:Delete a computer'
@@ -2600,6 +2606,7 @@ _computer() {
2600
2606
  ;;
2601
2607
  mount)
2602
2608
  _arguments -C \\
2609
+ '--background[Run the mount controller in the background and print its PID]' \\
2603
2610
  '--alias[SSH host alias]:alias:' \\
2604
2611
  '--host[SSH gateway host]:host:' \\
2605
2612
  '--port[SSH gateway port]:port:' \\
@@ -2771,9 +2778,9 @@ var BASH_SCRIPT = `_computer() {
2771
2778
  ;;
2772
2779
  mount)
2773
2780
  if [[ $cword -eq 2 ]]; then
2774
- COMPREPLY=($(compgen -W "$mount_commands" -- "$cur"))
2781
+ COMPREPLY=($(compgen -W "$mount_commands --background --alias --host --port --poll-interval --connect-timeout" -- "$cur"))
2775
2782
  elif [[ $cword -ge 3 ]]; then
2776
- COMPREPLY=($(compgen -W "--alias --host --port --poll-interval --connect-timeout" -- "$cur"))
2783
+ COMPREPLY=($(compgen -W "--background --alias --host --port --poll-interval --connect-timeout" -- "$cur"))
2777
2784
  fi
2778
2785
  ;;
2779
2786
  ports)
@@ -3560,9 +3567,35 @@ async function confirmDeletion(sourceID) {
3560
3567
  });
3561
3568
  }
3562
3569
 
3563
- // src/commands/login.ts
3570
+ // src/commands/internal-install-mutagen.ts
3564
3571
  import { Command as Command9 } from "commander";
3565
3572
  import chalk7 from "chalk";
3573
+ var internalInstallMutagenCommand = new Command9("internal-install-mutagen").option("--quiet", "Suppress output unless installation fails").action(async (options) => {
3574
+ try {
3575
+ const executablePath = await ensureBundledMutagenInstalled();
3576
+ if (!options.quiet) {
3577
+ console.log();
3578
+ console.log(
3579
+ chalk7.green(
3580
+ ` Bundled Mutagen ready at ${executablePath}.`
3581
+ )
3582
+ );
3583
+ console.log();
3584
+ }
3585
+ } catch (error) {
3586
+ if (!options.quiet) {
3587
+ const message = error instanceof Error ? error.message : "failed to install bundled Mutagen";
3588
+ console.error();
3589
+ console.error(chalk7.red(` ${message}`));
3590
+ console.error();
3591
+ }
3592
+ process.exit(1);
3593
+ }
3594
+ });
3595
+
3596
+ // src/commands/login.ts
3597
+ import { Command as Command10 } from "commander";
3598
+ import chalk8 from "chalk";
3566
3599
  import ora8 from "ora";
3567
3600
 
3568
3601
  // src/lib/browser-login.ts
@@ -3830,12 +3863,12 @@ function escapeHTML(value) {
3830
3863
  }
3831
3864
 
3832
3865
  // src/commands/login.ts
3833
- var loginCommand = new Command9("login").description("Authenticate the CLI").option("--api-key <key>", "API key starting with ac_live_").option("--stdin", "Read the API key from stdin").option("-f, --force", "Overwrite an existing stored API key").action(async (options) => {
3866
+ var loginCommand = new Command10("login").description("Authenticate the CLI").option("--api-key <key>", "API key starting with ac_live_").option("--stdin", "Read the API key from stdin").option("-f, --force", "Overwrite an existing stored API key").action(async (options) => {
3834
3867
  const existingKey = getStoredAPIKey();
3835
3868
  if (existingKey && !options.force) {
3836
3869
  console.log();
3837
3870
  console.log(
3838
- chalk7.yellow(" Already logged in. Use --force to overwrite.")
3871
+ chalk8.yellow(" Already logged in. Use --force to overwrite.")
3839
3872
  );
3840
3873
  console.log();
3841
3874
  return;
@@ -3844,8 +3877,8 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
3844
3877
  const apiKey = await resolveAPIKeyInput(options.apiKey, options.stdin);
3845
3878
  if (!apiKey && wantsManualLogin) {
3846
3879
  console.log();
3847
- console.log(chalk7.dim(" Usage: computer login --api-key <ac_live_...>"));
3848
- console.log(chalk7.dim(` API: ${getBaseURL()}`));
3880
+ console.log(chalk8.dim(" Usage: computer login --api-key <ac_live_...>"));
3881
+ console.log(chalk8.dim(` API: ${getBaseURL()}`));
3849
3882
  console.log();
3850
3883
  process.exit(1);
3851
3884
  }
@@ -3855,7 +3888,7 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
3855
3888
  }
3856
3889
  if (!apiKey.startsWith("ac_live_")) {
3857
3890
  console.log();
3858
- console.log(chalk7.red(" API key must start with ac_live_"));
3891
+ console.log(chalk8.red(" API key must start with ac_live_"));
3859
3892
  console.log();
3860
3893
  process.exit(1);
3861
3894
  }
@@ -3863,7 +3896,7 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
3863
3896
  try {
3864
3897
  const me = await apiWithKey(apiKey, "/v1/me");
3865
3898
  setAPIKey(apiKey);
3866
- spinner.succeed(`Logged in as ${chalk7.bold(me.user.email)}`);
3899
+ spinner.succeed(`Logged in as ${chalk8.bold(me.user.email)}`);
3867
3900
  } catch (error) {
3868
3901
  spinner.fail(
3869
3902
  error instanceof Error ? error.message : "Failed to validate API key"
@@ -3883,15 +3916,15 @@ async function runBrowserLogin() {
3883
3916
  spinner.stop();
3884
3917
  console.log();
3885
3918
  console.log(
3886
- chalk7.yellow(" Browser auto-open failed. Open this URL to continue:")
3919
+ chalk8.yellow(" Browser auto-open failed. Open this URL to continue:")
3887
3920
  );
3888
- console.log(chalk7.dim(` ${attempt.loginURL}`));
3921
+ console.log(chalk8.dim(` ${attempt.loginURL}`));
3889
3922
  console.log();
3890
3923
  spinner.start("Waiting for browser login...");
3891
3924
  }
3892
3925
  spinner.text = "Waiting for browser login...";
3893
3926
  const result = await attempt.waitForResult();
3894
- spinner.succeed(`Logged in as ${chalk7.bold(result.me.user.email)}`);
3927
+ spinner.succeed(`Logged in as ${chalk8.bold(result.me.user.email)}`);
3895
3928
  await continueFirstLoginFlow(result);
3896
3929
  } catch (error) {
3897
3930
  spinner.fail(
@@ -3925,8 +3958,8 @@ async function continueFirstLoginFlow(result) {
3925
3958
  }
3926
3959
  console.log();
3927
3960
  console.log(
3928
- chalk7.cyan(
3929
- `Continuing first-time setup for ${chalk7.bold(machineHandle)}...
3961
+ chalk8.cyan(
3962
+ `Continuing first-time setup for ${chalk8.bold(machineHandle)}...
3930
3963
  `
3931
3964
  )
3932
3965
  );
@@ -3939,8 +3972,8 @@ async function continueFirstLoginFlow(result) {
3939
3972
  const spinner = ora8(`Preparing SSH access for ${machineHandle}...`).start();
3940
3973
  try {
3941
3974
  const connection = await prepareSSHConnectionByIdentifier(machineHandle);
3942
- spinner.succeed(`Connecting to ${chalk7.bold(machineHandle)}`);
3943
- console.log(chalk7.dim(` ${connection.command}`));
3975
+ spinner.succeed(`Connecting to ${chalk8.bold(machineHandle)}`);
3976
+ console.log(chalk8.dim(` ${connection.command}`));
3944
3977
  console.log();
3945
3978
  await openSSHConnection(connection);
3946
3979
  } catch (error) {
@@ -3951,19 +3984,19 @@ async function continueFirstLoginFlow(result) {
3951
3984
  }
3952
3985
  } catch (error) {
3953
3986
  const message = error instanceof Error ? error.message : "Failed to finish first-time setup";
3954
- console.error(chalk7.red(`
3987
+ console.error(chalk8.red(`
3955
3988
  ${message}`));
3956
3989
  console.log();
3957
3990
  if (result.provider === "claude") {
3958
3991
  console.log(
3959
- chalk7.dim(` computer claude-login --machine ${machineHandle}`)
3992
+ chalk8.dim(` computer claude-login --machine ${machineHandle}`)
3960
3993
  );
3961
3994
  } else if (result.provider === "codex") {
3962
3995
  console.log(
3963
- chalk7.dim(` computer codex-login --machine ${machineHandle}`)
3996
+ chalk8.dim(` computer codex-login --machine ${machineHandle}`)
3964
3997
  );
3965
3998
  }
3966
- console.log(chalk7.dim(` computer ssh ${machineHandle}`));
3999
+ console.log(chalk8.dim(` computer ssh ${machineHandle}`));
3967
4000
  console.log();
3968
4001
  process.exit(1);
3969
4002
  }
@@ -3977,18 +4010,19 @@ async function runSelectedProvider(provider, machineHandle) {
3977
4010
  await runCodexLogin({ machine: machineHandle });
3978
4011
  return;
3979
4012
  }
3980
- console.log(chalk7.green(`Sandbox ${chalk7.bold(machineHandle)} is ready.`));
4013
+ console.log(chalk8.green(`Sandbox ${chalk8.bold(machineHandle)} is ready.`));
3981
4014
  console.log();
3982
4015
  }
3983
4016
  function printNextStep(machineHandle) {
3984
- console.log(chalk7.green(`Sandbox ${chalk7.bold(machineHandle)} is ready.`));
3985
- console.log(chalk7.dim(` computer ssh ${machineHandle}`));
4017
+ console.log(chalk8.green(`Sandbox ${chalk8.bold(machineHandle)} is ready.`));
4018
+ console.log(chalk8.dim(` computer ssh ${machineHandle}`));
3986
4019
  console.log();
3987
4020
  }
3988
4021
 
3989
4022
  // src/commands/mount.ts
3990
- import { Command as Command10 } from "commander";
3991
- import chalk8 from "chalk";
4023
+ import { spawn as spawn4 } from "child_process";
4024
+ import { Command as Command11, Option } from "commander";
4025
+ import chalk9 from "chalk";
3992
4026
  import ora9 from "ora";
3993
4027
 
3994
4028
  // src/lib/mount-daemon.ts
@@ -4004,7 +4038,8 @@ function getMountControllerState(rootPath = defaultMountServiceConfig().rootPath
4004
4038
  }
4005
4039
  return { running: true, pid: lock.pid };
4006
4040
  }
4007
- async function runMountDaemon(config) {
4041
+ async function runMountDaemon(config, options = {}) {
4042
+ const { onReady, onStarted } = options;
4008
4043
  const paths = getMountPaths(config.rootPath);
4009
4044
  ensureMountDirectories(paths);
4010
4045
  await mkdir3(paths.rootPath, { recursive: true });
@@ -4014,15 +4049,35 @@ async function runMountDaemon(config) {
4014
4049
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4015
4050
  controllerPid: process.pid,
4016
4051
  running: true,
4052
+ startupPhase: "starting",
4053
+ startupMessage: "Starting mount controller...",
4017
4054
  mounts: []
4018
4055
  },
4019
4056
  config.rootPath
4020
4057
  );
4058
+ onStarted?.();
4059
+ writeMountStatusSnapshot(
4060
+ {
4061
+ ...readMountStatusSnapshot(config.rootPath) ?? {
4062
+ controllerPid: process.pid,
4063
+ running: true,
4064
+ mounts: []
4065
+ },
4066
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4067
+ controllerPid: process.pid,
4068
+ running: true,
4069
+ startupPhase: "cleaning",
4070
+ startupMessage: "Cleaning stale mount state..."
4071
+ },
4072
+ config.rootPath
4073
+ );
4021
4074
  await teardownManagedSessions(config, paths);
4075
+ await mkdir3(paths.rootPath, { recursive: true });
4022
4076
  let running = false;
4023
4077
  let queued = false;
4024
4078
  let shuttingDown = false;
4025
4079
  let activeRun = null;
4080
+ let activeRunController = null;
4026
4081
  const runOnce = async () => {
4027
4082
  if (shuttingDown) {
4028
4083
  return;
@@ -4031,12 +4086,19 @@ async function runMountDaemon(config) {
4031
4086
  queued = true;
4032
4087
  return activeRun ?? void 0;
4033
4088
  }
4089
+ const controller = new AbortController();
4090
+ activeRunController = controller;
4034
4091
  activeRun = (async () => {
4035
4092
  running = true;
4036
4093
  try {
4037
- await reconcileMounts(config, paths, process.pid);
4094
+ await reconcileMounts(
4095
+ config,
4096
+ paths,
4097
+ process.pid,
4098
+ controller.signal
4099
+ );
4038
4100
  } catch (error) {
4039
- if (shuttingDown) {
4101
+ if (shuttingDown && isAbortError(error)) {
4040
4102
  return;
4041
4103
  }
4042
4104
  const previous = readMountStatusSnapshot(config.rootPath);
@@ -4045,6 +4107,8 @@ async function runMountDaemon(config) {
4045
4107
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4046
4108
  controllerPid: process.pid,
4047
4109
  running: true,
4110
+ startupPhase: previous?.startupPhase === "ready" ? "ready" : "syncing",
4111
+ startupMessage: previous?.startupPhase === "ready" ? void 0 : "Waiting for initial sync...",
4048
4112
  lastHealthySyncAt: previous?.lastHealthySyncAt,
4049
4113
  lastSuccessfulSyncAt: previous?.lastSuccessfulSyncAt,
4050
4114
  lastIssueAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4060,6 +4124,7 @@ async function runMountDaemon(config) {
4060
4124
  } finally {
4061
4125
  running = false;
4062
4126
  activeRun = null;
4127
+ activeRunController = null;
4063
4128
  if (queued && !shuttingDown) {
4064
4129
  queued = false;
4065
4130
  void runOnce();
@@ -4075,6 +4140,21 @@ async function runMountDaemon(config) {
4075
4140
  server.once("error", reject);
4076
4141
  server.listen(paths.socketPath, () => resolve());
4077
4142
  });
4143
+ writeMountStatusSnapshot(
4144
+ {
4145
+ ...readMountStatusSnapshot(config.rootPath) ?? {
4146
+ controllerPid: process.pid,
4147
+ running: true,
4148
+ mounts: []
4149
+ },
4150
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4151
+ controllerPid: process.pid,
4152
+ running: true,
4153
+ startupPhase: "syncing",
4154
+ startupMessage: "Waiting for initial sync..."
4155
+ },
4156
+ config.rootPath
4157
+ );
4078
4158
  const interval = setInterval(() => {
4079
4159
  void runOnce();
4080
4160
  }, config.pollIntervalMs);
@@ -4085,6 +4165,7 @@ async function runMountDaemon(config) {
4085
4165
  shuttingDown = true;
4086
4166
  clearInterval(interval);
4087
4167
  server.close();
4168
+ activeRunController?.abort();
4088
4169
  if (activeRun) {
4089
4170
  await activeRun.catch(() => {
4090
4171
  });
@@ -4096,6 +4177,8 @@ async function runMountDaemon(config) {
4096
4177
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4097
4178
  controllerPid: previous?.controllerPid,
4098
4179
  running: false,
4180
+ startupPhase: void 0,
4181
+ startupMessage: void 0,
4099
4182
  lastHealthySyncAt: previous?.lastHealthySyncAt,
4100
4183
  lastSuccessfulSyncAt: previous?.lastSuccessfulSyncAt,
4101
4184
  lastIssueAt: previous?.lastIssueAt,
@@ -4115,6 +4198,8 @@ async function runMountDaemon(config) {
4115
4198
  void shutdown();
4116
4199
  });
4117
4200
  await runOnce();
4201
+ writeMountStatusSnapshot(markMountStartupReady(readMountStatusSnapshot(config.rootPath), process.pid), config.rootPath);
4202
+ onReady?.();
4118
4203
  await new Promise(() => {
4119
4204
  });
4120
4205
  }
@@ -4143,42 +4228,69 @@ function processExists(pid) {
4143
4228
  return false;
4144
4229
  }
4145
4230
  }
4231
+ function markMountStartupReady(snapshot, controllerPid) {
4232
+ return {
4233
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4234
+ controllerPid,
4235
+ running: true,
4236
+ startupPhase: "ready",
4237
+ startupMessage: void 0,
4238
+ lastHealthySyncAt: snapshot?.lastHealthySyncAt,
4239
+ lastSuccessfulSyncAt: snapshot?.lastSuccessfulSyncAt,
4240
+ lastIssueAt: snapshot?.lastIssueAt,
4241
+ lastIssue: snapshot?.lastIssue,
4242
+ lastError: snapshot?.lastError,
4243
+ mounts: snapshot?.mounts ?? []
4244
+ };
4245
+ }
4146
4246
 
4147
4247
  // src/commands/mount.ts
4148
- var mountCommand = new Command10("mount").description("Mirror SSH-ready machines under ~/agentcomputer while this command is running").option("--alias <alias>", "SSH host alias", "agentcomputer.ai").option("--host <host>", "SSH gateway host", "ssh.agentcomputer.ai").option("--port <port>", "SSH gateway port", "443").option("--poll-interval <ms>", "Reconcile interval in milliseconds", "5000").option("--connect-timeout <seconds>", "SSH connect timeout for Mutagen", "5").action(async (options) => {
4149
- const spinner = ora9("Starting machine mount controller...").start();
4248
+ var MOUNT_START_SPINNER = {
4249
+ interval: 90,
4250
+ frames: ["\u25F0", "\u25F3", "\u25F2", "\u25F1"]
4251
+ };
4252
+ var mountCommand = new Command11("mount").description("Mirror SSH-ready machines under ~/agentcomputer with a local mount controller").option("--alias <alias>", "SSH host alias", "agentcomputer.ai").option("--host <host>", "SSH gateway host", "ssh.agentcomputer.ai").option("--port <port>", "SSH gateway port", "443").option("--poll-interval <ms>", "Reconcile interval in milliseconds", "5000").option("--connect-timeout <seconds>", "SSH connect timeout for Mutagen", "5").option(
4253
+ "--background",
4254
+ "Run the mount controller in the background and print its PID"
4255
+ ).addOption(new Option("--daemonized").hideHelp()).addOption(new Option("--open-root-when-ready").hideHelp()).action(async (options) => {
4256
+ const spinner = ora9({
4257
+ spinner: MOUNT_START_SPINNER,
4258
+ text: options.background ? "Starting machine mount controller in background..." : "Starting machine mount controller..."
4259
+ }).start();
4150
4260
  try {
4151
- const issues = getMountHostValidationIssues();
4152
- if (issues.length > 0) {
4153
- throw new Error(
4154
- [
4155
- ...issues.map((issue) => issue.message),
4156
- ...formatMountHostInstallGuidance(issues)
4157
- ].join("\n")
4158
- );
4261
+ if (options.daemonized) {
4262
+ const config = readMountConfig() ?? defaultMountServiceConfig();
4263
+ await runMountDaemon(config, {
4264
+ onStarted: () => {
4265
+ spinner.succeed("Machine mount controller running");
4266
+ printMountStartSummary(config, process.pid, "foreground");
4267
+ },
4268
+ onReady: () => {
4269
+ if (options.openRootWhenReady) {
4270
+ revealMountRootInFinder(config.rootPath);
4271
+ }
4272
+ }
4273
+ });
4274
+ return;
4159
4275
  }
4160
- const sshSetup = await ensureSSHAccessConfigured(options);
4161
- const config = {
4162
- ...defaultMountServiceConfig(),
4163
- alias: sshSetup.alias,
4164
- host: sshSetup.host,
4165
- port: sshSetup.port,
4166
- pollIntervalMs: parsePositiveInt(options.pollInterval, "poll interval"),
4167
- connectTimeoutSeconds: parsePositiveInt(
4168
- options.connectTimeout,
4169
- "connect timeout"
4170
- )
4171
- };
4172
- writeMountConfig(config);
4276
+ const resolved = await resolveMountServiceConfig(options);
4277
+ writeMountConfig(resolved.config);
4278
+ process.env[AGENTCOMPUTER_MUTAGEN_PATH_ENV] = resolved.mutagenPath;
4279
+ if (options.background) {
4280
+ const pid = await startMountControllerInBackground(resolved.config.rootPath);
4281
+ spinner.succeed("Machine mount controller running in background");
4282
+ printMountStartSummary(resolved.config, pid, "background");
4283
+ return;
4284
+ }
4285
+ const child = await startMountControllerInForeground(resolved.config.rootPath);
4286
+ await waitForMountControllerReady(resolved.config.rootPath, child.pid, spinner);
4173
4287
  spinner.succeed("Machine mount controller running");
4174
- console.log();
4175
- console.log(chalk8.dim(` Root: ${config.rootPath}`));
4176
- console.log(chalk8.dim(` SSH alias: ${config.alias}`));
4177
- console.log(chalk8.dim(` Poll: ${config.pollIntervalMs}ms`));
4178
- console.log();
4179
- console.log(chalk8.dim(" Press Ctrl-C to stop syncing."));
4180
- console.log();
4181
- await runMountDaemon(config);
4288
+ printMountStartSummary(
4289
+ resolved.config,
4290
+ child.pid ?? process.pid,
4291
+ "foreground"
4292
+ );
4293
+ await superviseForegroundMountController(child);
4182
4294
  } catch (error) {
4183
4295
  spinner.fail(
4184
4296
  error instanceof Error ? error.message : "Failed to start machine mount controller"
@@ -4186,40 +4298,40 @@ var mountCommand = new Command10("mount").description("Mirror SSH-ready machines
4186
4298
  process.exit(1);
4187
4299
  }
4188
4300
  }).addCommand(
4189
- new Command10("status").description("Show machine mount controller status").action(() => {
4301
+ new Command11("status").description("Show machine mount controller status").action(() => {
4190
4302
  const config = readMountConfig() ?? defaultMountServiceConfig();
4191
4303
  const controller = getMountControllerState(config.rootPath);
4192
4304
  const snapshot = readMountStatusSnapshot(config.rootPath);
4193
4305
  console.log();
4194
- console.log(` ${chalk8.bold("Machine Mounts")}`);
4306
+ console.log(` ${chalk9.bold("Machine Mounts")}`);
4195
4307
  console.log();
4196
4308
  console.log(
4197
- ` ${chalk8.dim("Running")} ${controller.running ? chalk8.green("yes") : chalk8.dim("no")}`
4309
+ ` ${chalk9.dim("Running")} ${controller.running ? chalk9.green("yes") : chalk9.dim("no")}`
4198
4310
  );
4199
4311
  if (controller.pid) {
4200
- console.log(` ${chalk8.dim("PID")} ${controller.pid}`);
4312
+ console.log(` ${chalk9.dim("PID")} ${controller.pid}`);
4201
4313
  }
4202
- console.log(` ${chalk8.dim("Root")} ${config.rootPath}`);
4203
- console.log(` ${chalk8.dim("Alias")} ${config.alias}`);
4314
+ console.log(` ${chalk9.dim("Root")} ${config.rootPath}`);
4315
+ console.log(` ${chalk9.dim("Alias")} ${config.alias}`);
4204
4316
  console.log(
4205
- ` ${chalk8.dim("Updated")} ${snapshot?.updatedAt ? timeAgo(snapshot.updatedAt) : chalk8.dim("never")}`
4317
+ ` ${chalk9.dim("Updated")} ${snapshot?.updatedAt ? timeAgo(snapshot.updatedAt) : chalk9.dim("never")}`
4206
4318
  );
4207
4319
  console.log(
4208
- ` ${chalk8.dim("Healthy")} ${snapshot?.lastHealthySyncAt ? timeAgo(snapshot.lastHealthySyncAt) : chalk8.dim("never")}`
4320
+ ` ${chalk9.dim("Healthy")} ${snapshot?.lastHealthySyncAt ? timeAgo(snapshot.lastHealthySyncAt) : chalk9.dim("never")}`
4209
4321
  );
4210
4322
  if (controller.running && snapshot?.mounts.length) {
4211
4323
  console.log();
4212
4324
  for (const mount of snapshot.mounts) {
4213
- const state = mount.state === "mounted" ? chalk8.green(mount.state) : mount.state === "reconnecting" ? chalk8.cyan(mount.state) : mount.state === "degraded" ? chalk8.yellow(mount.state) : mount.state === "pending" ? chalk8.yellow(mount.state) : chalk8.red(mount.state);
4214
- console.log(` ${chalk8.white(mount.handle)} ${state} ${chalk8.dim(mount.mountPath)}`);
4325
+ const state = formatMountState(mount.state);
4326
+ console.log(` ${chalk9.white(mount.handle)} ${state} ${chalk9.dim(mount.mountPath)}`);
4215
4327
  if (mount.message) {
4216
- console.log(` ${chalk8.dim(mount.message)}`);
4328
+ console.log(` ${chalk9.dim(mount.message)}`);
4217
4329
  }
4218
4330
  }
4219
4331
  }
4220
4332
  if (snapshot?.lastIssue) {
4221
4333
  console.log();
4222
- console.log(` ${chalk8.dim("Last issue")} ${chalk8.yellow(snapshot.lastIssue)}`);
4334
+ console.log(` ${chalk9.dim("Last issue")} ${chalk9.yellow(snapshot.lastIssue)}`);
4223
4335
  }
4224
4336
  console.log();
4225
4337
  })
@@ -4231,25 +4343,213 @@ function parsePositiveInt(raw, label) {
4231
4343
  }
4232
4344
  return Math.round(value);
4233
4345
  }
4346
+ async function resolveMountServiceConfig(options) {
4347
+ const issues = getMountHostValidationIssues();
4348
+ if (issues.length > 0) {
4349
+ throw new Error(
4350
+ [
4351
+ ...issues.map((issue) => issue.message),
4352
+ ...formatMountHostInstallGuidance(issues)
4353
+ ].join("\n")
4354
+ );
4355
+ }
4356
+ const mutagenPath = await ensureMutagenCommandPath();
4357
+ const sshSetup = await ensureSSHAccessConfigured(options);
4358
+ return {
4359
+ config: {
4360
+ ...defaultMountServiceConfig(),
4361
+ alias: sshSetup.alias,
4362
+ host: sshSetup.host,
4363
+ port: sshSetup.port,
4364
+ pollIntervalMs: parsePositiveInt(options.pollInterval, "poll interval"),
4365
+ connectTimeoutSeconds: parsePositiveInt(
4366
+ options.connectTimeout,
4367
+ "connect timeout"
4368
+ )
4369
+ },
4370
+ mutagenPath
4371
+ };
4372
+ }
4373
+ async function startMountControllerInBackground(rootPath) {
4374
+ const child = startMountControllerProcess(rootPath, true, false);
4375
+ child.unref();
4376
+ await waitForMountControllerRunning(rootPath, child.pid);
4377
+ return child.pid;
4378
+ }
4379
+ async function startMountControllerInForeground(rootPath) {
4380
+ const child = startMountControllerProcess(rootPath, false, true);
4381
+ await waitForMountControllerRunning(rootPath, child.pid);
4382
+ return child;
4383
+ }
4384
+ function startMountControllerProcess(rootPath, detached, openRootWhenReady) {
4385
+ const controller = getMountControllerState(rootPath);
4386
+ if (controller.running) {
4387
+ throw new Error(`computer mount is already running (pid ${controller.pid})`);
4388
+ }
4389
+ const entrypoint = process.argv[1];
4390
+ if (!entrypoint) {
4391
+ throw new Error("unable to determine CLI entrypoint for background mount");
4392
+ }
4393
+ const args = ["mount", "--daemonized"];
4394
+ if (openRootWhenReady) {
4395
+ args.push("--open-root-when-ready");
4396
+ }
4397
+ const child = spawn4(process.execPath, [entrypoint, ...args], {
4398
+ cwd: process.cwd(),
4399
+ detached,
4400
+ env: process.env,
4401
+ stdio: "ignore"
4402
+ });
4403
+ if (!child.pid) {
4404
+ throw new Error("failed to start machine mount controller");
4405
+ }
4406
+ return child;
4407
+ }
4408
+ async function waitForMountControllerRunning(rootPath, pid) {
4409
+ const deadline = Date.now() + 3e3;
4410
+ while (Date.now() < deadline) {
4411
+ const controller = getMountControllerState(rootPath);
4412
+ if (controller.running && controller.pid === pid) {
4413
+ return;
4414
+ }
4415
+ if (!processExists2(pid)) {
4416
+ break;
4417
+ }
4418
+ await new Promise((resolve) => setTimeout(resolve, 50));
4419
+ }
4420
+ throw new Error("failed to start machine mount controller in background");
4421
+ }
4422
+ async function waitForMountControllerReady(rootPath, pid, spinner) {
4423
+ const deadline = Date.now() + 12e4;
4424
+ while (Date.now() < deadline) {
4425
+ const controller = getMountControllerState(rootPath);
4426
+ const snapshot = readMountStatusSnapshot(rootPath);
4427
+ if (snapshot?.controllerPid === pid && snapshot.startupMessage) {
4428
+ spinner.text = snapshot.startupMessage;
4429
+ }
4430
+ if (snapshot?.controllerPid === pid && snapshot.startupPhase === "ready") {
4431
+ return;
4432
+ }
4433
+ if (!controller.running && !processExists2(pid)) {
4434
+ break;
4435
+ }
4436
+ await new Promise((resolve) => setTimeout(resolve, 100));
4437
+ }
4438
+ throw new Error("mount controller did not finish startup");
4439
+ }
4440
+ async function superviseForegroundMountController(child) {
4441
+ await new Promise((resolve, reject) => {
4442
+ const cleanup = () => {
4443
+ process.off("SIGINT", onSigint);
4444
+ process.off("SIGTERM", onSigterm);
4445
+ child.off("error", onError);
4446
+ child.off("exit", onExit);
4447
+ };
4448
+ const stopAndExit = (signal) => {
4449
+ cleanup();
4450
+ try {
4451
+ child.kill(signal);
4452
+ } catch {
4453
+ }
4454
+ process.exit(0);
4455
+ };
4456
+ const onSigint = () => {
4457
+ stopAndExit("SIGINT");
4458
+ };
4459
+ const onSigterm = () => {
4460
+ stopAndExit("SIGTERM");
4461
+ };
4462
+ const onError = (error) => {
4463
+ cleanup();
4464
+ reject(error);
4465
+ };
4466
+ const onExit = (code, signal) => {
4467
+ cleanup();
4468
+ if (code === 0) {
4469
+ resolve();
4470
+ return;
4471
+ }
4472
+ reject(
4473
+ new Error(
4474
+ signal ? `machine mount controller exited from ${signal}` : `machine mount controller exited with code ${code ?? "unknown"}`
4475
+ )
4476
+ );
4477
+ };
4478
+ process.once("SIGINT", onSigint);
4479
+ process.once("SIGTERM", onSigterm);
4480
+ child.once("error", onError);
4481
+ child.once("exit", onExit);
4482
+ });
4483
+ }
4484
+ function printMountStartSummary(config, pid, mode) {
4485
+ console.log();
4486
+ console.log(chalk9.dim(` PID: ${pid}`));
4487
+ console.log(chalk9.dim(` Root: ${config.rootPath}`));
4488
+ console.log(chalk9.dim(` SSH alias: ${config.alias}`));
4489
+ console.log(chalk9.dim(` Poll: ${config.pollIntervalMs}ms`));
4490
+ console.log();
4491
+ console.log(
4492
+ chalk9.dim(
4493
+ mode === "background" ? " Use `computer mount status` to inspect sync state." : " Press Ctrl-C to stop syncing."
4494
+ )
4495
+ );
4496
+ console.log();
4497
+ }
4498
+ function processExists2(pid) {
4499
+ if (!Number.isInteger(pid) || pid <= 0) {
4500
+ return false;
4501
+ }
4502
+ try {
4503
+ process.kill(pid, 0);
4504
+ return true;
4505
+ } catch {
4506
+ return false;
4507
+ }
4508
+ }
4509
+ function revealMountRootInFinder(rootPath) {
4510
+ if (process.platform !== "darwin") {
4511
+ return;
4512
+ }
4513
+ const child = spawn4("open", [rootPath], {
4514
+ stdio: "ignore",
4515
+ detached: true
4516
+ });
4517
+ child.on("error", () => {
4518
+ });
4519
+ child.unref();
4520
+ }
4521
+ function formatMountState(state) {
4522
+ switch (state) {
4523
+ case "mounted":
4524
+ return chalk9.green(state);
4525
+ case "reconnecting":
4526
+ return chalk9.cyan(state);
4527
+ case "degraded":
4528
+ case "pending":
4529
+ return chalk9.yellow(state);
4530
+ default:
4531
+ return chalk9.red(state);
4532
+ }
4533
+ }
4234
4534
 
4235
4535
  // src/commands/logout.ts
4236
- import { Command as Command11 } from "commander";
4237
- import chalk9 from "chalk";
4238
- var logoutCommand = new Command11("logout").description("Remove stored API key").action(() => {
4536
+ import { Command as Command12 } from "commander";
4537
+ import chalk10 from "chalk";
4538
+ var logoutCommand = new Command12("logout").description("Remove stored API key").action(() => {
4239
4539
  if (!getStoredAPIKey()) {
4240
4540
  console.log();
4241
- console.log(chalk9.dim(" Not logged in."));
4541
+ console.log(chalk10.dim(" Not logged in."));
4242
4542
  if (hasEnvAPIKey()) {
4243
- console.log(chalk9.dim(" Environment API key is still active in this shell."));
4543
+ console.log(chalk10.dim(" Environment API key is still active in this shell."));
4244
4544
  }
4245
4545
  console.log();
4246
4546
  return;
4247
4547
  }
4248
4548
  clearAPIKey();
4249
4549
  console.log();
4250
- console.log(chalk9.green(" Logged out."));
4550
+ console.log(chalk10.green(" Logged out."));
4251
4551
  if (hasEnvAPIKey()) {
4252
- console.log(chalk9.dim(" Environment API key is still active in this shell."));
4552
+ console.log(chalk10.dim(" Environment API key is still active in this shell."));
4253
4553
  }
4254
4554
  console.log();
4255
4555
  });
@@ -4257,8 +4557,8 @@ var logoutCommand = new Command11("logout").description("Remove stored API key")
4257
4557
  // src/commands/upgrade.ts
4258
4558
  import { spawnSync } from "child_process";
4259
4559
  import { readFileSync as readFileSync2, realpathSync } from "fs";
4260
- import { Command as Command12 } from "commander";
4261
- import chalk10 from "chalk";
4560
+ import { Command as Command13 } from "commander";
4561
+ import chalk11 from "chalk";
4262
4562
  import ora10 from "ora";
4263
4563
  var pkg2 = JSON.parse(
4264
4564
  readFileSync2(new URL("../package.json", import.meta.url), "utf8")
@@ -4373,7 +4673,25 @@ async function getLatestVersion(packageName) {
4373
4673
  }
4374
4674
  return payload.version;
4375
4675
  }
4376
- var upgradeCommand = new Command12("upgrade").description("Update the CLI to the latest version").action(async () => {
4676
+ function attemptBundledMutagenRefresh(executablePath) {
4677
+ const install = spawnSync(
4678
+ process.execPath,
4679
+ [executablePath, "internal-install-mutagen", "--quiet"],
4680
+ {
4681
+ stdio: "ignore"
4682
+ }
4683
+ );
4684
+ if (install.status === 0) {
4685
+ return;
4686
+ }
4687
+ console.log();
4688
+ console.log(
4689
+ chalk11.yellow(
4690
+ " CLI updated, but bundled Mutagen did not finish installing. `computer mount` will retry on first use."
4691
+ )
4692
+ );
4693
+ }
4694
+ var upgradeCommand = new Command13("upgrade").description("Update the CLI to the latest version").action(async () => {
4377
4695
  const currentVersion = pkg2.version ?? "0.0.0";
4378
4696
  const packageName = pkg2.name ?? "aicomputer";
4379
4697
  const spinner = ora10("Checking for updates...").start();
@@ -4406,16 +4724,19 @@ var upgradeCommand = new Command12("upgrade").description("Update the CLI to the
4406
4724
  spinner.stop();
4407
4725
  console.log();
4408
4726
  console.log(
4409
- chalk10.dim(` Updating ${chalk10.bold(`v${currentVersion}`)} -> ${chalk10.bold(`v${latestVersion}`)}`)
4727
+ chalk11.dim(` Updating ${chalk11.bold(`v${currentVersion}`)} -> ${chalk11.bold(`v${latestVersion}`)}`)
4410
4728
  );
4411
- console.log(chalk10.dim(` ${upgrade.label}`));
4729
+ console.log(chalk11.dim(` ${upgrade.label}`));
4412
4730
  console.log();
4413
4731
  const result = spawnSync(upgrade.command, upgrade.args, {
4414
4732
  stdio: "inherit"
4415
4733
  });
4416
4734
  if (result.status === 0) {
4735
+ if (method !== "nix") {
4736
+ attemptBundledMutagenRefresh(executablePath);
4737
+ }
4417
4738
  console.log();
4418
- console.log(chalk10.green(` Updated to v${latestVersion}.`));
4739
+ console.log(chalk11.green(` Updated to v${latestVersion}.`));
4419
4740
  console.log();
4420
4741
  return;
4421
4742
  }
@@ -4423,10 +4744,10 @@ var upgradeCommand = new Command12("upgrade").description("Update the CLI to the
4423
4744
  });
4424
4745
 
4425
4746
  // src/commands/whoami.ts
4426
- import { Command as Command13 } from "commander";
4427
- import chalk11 from "chalk";
4747
+ import { Command as Command14 } from "commander";
4748
+ import chalk12 from "chalk";
4428
4749
  import ora11 from "ora";
4429
- var whoamiCommand = new Command13("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
4750
+ var whoamiCommand = new Command14("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
4430
4751
  const spinner = options.json ? null : ora11("Loading user...").start();
4431
4752
  try {
4432
4753
  const me = await api("/v1/me");
@@ -4436,14 +4757,14 @@ var whoamiCommand = new Command13("whoami").description("Show current user").opt
4436
4757
  return;
4437
4758
  }
4438
4759
  console.log();
4439
- console.log(` ${chalk11.bold.white(me.user.display_name || me.user.email)}`);
4760
+ console.log(` ${chalk12.bold.white(me.user.display_name || me.user.email)}`);
4440
4761
  if (me.user.display_name) {
4441
- console.log(` ${chalk11.dim(me.user.email)}`);
4762
+ console.log(` ${chalk12.dim(me.user.email)}`);
4442
4763
  }
4443
4764
  if (me.api_key.name) {
4444
- console.log(` ${chalk11.dim("Key:")} ${me.api_key.name}`);
4765
+ console.log(` ${chalk12.dim("Key:")} ${me.api_key.name}`);
4445
4766
  }
4446
- console.log(` ${chalk11.dim("API:")} ${chalk11.dim(getBaseURL())}`);
4767
+ console.log(` ${chalk12.dim("API:")} ${chalk12.dim(getBaseURL())}`);
4447
4768
  console.log();
4448
4769
  } catch (error) {
4449
4770
  if (spinner) {
@@ -4460,15 +4781,15 @@ var pkg3 = JSON.parse(
4460
4781
  readFileSync3(new URL("../package.json", import.meta.url), "utf8")
4461
4782
  );
4462
4783
  var cliName = process.argv[1] ? basename2(process.argv[1]) : "agentcomputer";
4463
- var program = new Command14();
4784
+ var program = new Command15();
4464
4785
  function appendTextSection(lines, title, values) {
4465
4786
  if (values.length === 0) {
4466
4787
  return;
4467
4788
  }
4468
- lines.push(` ${chalk12.dim(title)}`);
4789
+ lines.push(` ${chalk13.dim(title)}`);
4469
4790
  lines.push("");
4470
4791
  for (const value of values) {
4471
- lines.push(` ${chalk12.white(value)}`);
4792
+ lines.push(` ${chalk13.white(value)}`);
4472
4793
  }
4473
4794
  lines.push("");
4474
4795
  }
@@ -4477,10 +4798,10 @@ function appendTableSection(lines, title, entries) {
4477
4798
  return;
4478
4799
  }
4479
4800
  const width = Math.max(...entries.map((entry) => entry.term.length), 0) + 2;
4480
- lines.push(` ${chalk12.dim(title)}`);
4801
+ lines.push(` ${chalk13.dim(title)}`);
4481
4802
  lines.push("");
4482
4803
  for (const entry of entries) {
4483
- lines.push(` ${chalk12.white(padEnd(entry.term, width))}${chalk12.dim(entry.desc)}`);
4804
+ lines.push(` ${chalk13.white(padEnd(entry.term, width))}${chalk13.dim(entry.desc)}`);
4484
4805
  }
4485
4806
  lines.push("");
4486
4807
  }
@@ -4506,10 +4827,10 @@ function formatRootHelp(cmd) {
4506
4827
  ["Other", []]
4507
4828
  ];
4508
4829
  const otherGroup = groups.find(([name]) => name === "Other")[1];
4509
- lines.push(`${chalk12.bold(cliName)} ${chalk12.dim(`v${version}`)}`);
4830
+ lines.push(`${chalk13.bold(cliName)} ${chalk13.dim(`v${version}`)}`);
4510
4831
  lines.push("");
4511
4832
  if (cmd.description()) {
4512
- lines.push(` ${chalk12.dim(cmd.description())}`);
4833
+ lines.push(` ${chalk13.dim(cmd.description())}`);
4513
4834
  lines.push("");
4514
4835
  }
4515
4836
  appendTextSection(lines, "Usage", [`${cliName} <command> [options]`]);
@@ -4562,10 +4883,10 @@ function formatSubcommandHelp(cmd, helper) {
4562
4883
  term: helper.optionTerm(option),
4563
4884
  desc: helper.optionDescription(option)
4564
4885
  }));
4565
- lines.push(chalk12.bold(commandPath(cmd)));
4886
+ lines.push(chalk13.bold(commandPath(cmd)));
4566
4887
  lines.push("");
4567
4888
  if (description) {
4568
- lines.push(` ${chalk12.dim(description)}`);
4889
+ lines.push(` ${chalk13.dim(description)}`);
4569
4890
  lines.push("");
4570
4891
  }
4571
4892
  appendTextSection(lines, "Usage", [helper.commandUsage(cmd)]);
@@ -4591,6 +4912,7 @@ function applyHelpFormatting(cmd) {
4591
4912
  program.name(cliName).description("Agent Computer CLI").version(pkg3.version ?? "0.0.0").option("-y, --yes", "Skip confirmation prompts");
4592
4913
  program.addCommand(loginCommand);
4593
4914
  program.addCommand(upgradeCommand);
4915
+ program.addCommand(internalInstallMutagenCommand, { hidden: true });
4594
4916
  program.addCommand(logoutCommand);
4595
4917
  program.addCommand(whoamiCommand);
4596
4918
  program.addCommand(claudeLoginCommand);