aicomputer 0.1.17 → 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
 
@@ -3561,9 +3567,35 @@ async function confirmDeletion(sourceID) {
3561
3567
  });
3562
3568
  }
3563
3569
 
3564
- // src/commands/login.ts
3570
+ // src/commands/internal-install-mutagen.ts
3565
3571
  import { Command as Command9 } from "commander";
3566
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";
3567
3599
  import ora8 from "ora";
3568
3600
 
3569
3601
  // src/lib/browser-login.ts
@@ -3831,12 +3863,12 @@ function escapeHTML(value) {
3831
3863
  }
3832
3864
 
3833
3865
  // src/commands/login.ts
3834
- 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) => {
3835
3867
  const existingKey = getStoredAPIKey();
3836
3868
  if (existingKey && !options.force) {
3837
3869
  console.log();
3838
3870
  console.log(
3839
- chalk7.yellow(" Already logged in. Use --force to overwrite.")
3871
+ chalk8.yellow(" Already logged in. Use --force to overwrite.")
3840
3872
  );
3841
3873
  console.log();
3842
3874
  return;
@@ -3845,8 +3877,8 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
3845
3877
  const apiKey = await resolveAPIKeyInput(options.apiKey, options.stdin);
3846
3878
  if (!apiKey && wantsManualLogin) {
3847
3879
  console.log();
3848
- console.log(chalk7.dim(" Usage: computer login --api-key <ac_live_...>"));
3849
- 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()}`));
3850
3882
  console.log();
3851
3883
  process.exit(1);
3852
3884
  }
@@ -3856,7 +3888,7 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
3856
3888
  }
3857
3889
  if (!apiKey.startsWith("ac_live_")) {
3858
3890
  console.log();
3859
- console.log(chalk7.red(" API key must start with ac_live_"));
3891
+ console.log(chalk8.red(" API key must start with ac_live_"));
3860
3892
  console.log();
3861
3893
  process.exit(1);
3862
3894
  }
@@ -3864,7 +3896,7 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
3864
3896
  try {
3865
3897
  const me = await apiWithKey(apiKey, "/v1/me");
3866
3898
  setAPIKey(apiKey);
3867
- spinner.succeed(`Logged in as ${chalk7.bold(me.user.email)}`);
3899
+ spinner.succeed(`Logged in as ${chalk8.bold(me.user.email)}`);
3868
3900
  } catch (error) {
3869
3901
  spinner.fail(
3870
3902
  error instanceof Error ? error.message : "Failed to validate API key"
@@ -3884,15 +3916,15 @@ async function runBrowserLogin() {
3884
3916
  spinner.stop();
3885
3917
  console.log();
3886
3918
  console.log(
3887
- chalk7.yellow(" Browser auto-open failed. Open this URL to continue:")
3919
+ chalk8.yellow(" Browser auto-open failed. Open this URL to continue:")
3888
3920
  );
3889
- console.log(chalk7.dim(` ${attempt.loginURL}`));
3921
+ console.log(chalk8.dim(` ${attempt.loginURL}`));
3890
3922
  console.log();
3891
3923
  spinner.start("Waiting for browser login...");
3892
3924
  }
3893
3925
  spinner.text = "Waiting for browser login...";
3894
3926
  const result = await attempt.waitForResult();
3895
- spinner.succeed(`Logged in as ${chalk7.bold(result.me.user.email)}`);
3927
+ spinner.succeed(`Logged in as ${chalk8.bold(result.me.user.email)}`);
3896
3928
  await continueFirstLoginFlow(result);
3897
3929
  } catch (error) {
3898
3930
  spinner.fail(
@@ -3926,8 +3958,8 @@ async function continueFirstLoginFlow(result) {
3926
3958
  }
3927
3959
  console.log();
3928
3960
  console.log(
3929
- chalk7.cyan(
3930
- `Continuing first-time setup for ${chalk7.bold(machineHandle)}...
3961
+ chalk8.cyan(
3962
+ `Continuing first-time setup for ${chalk8.bold(machineHandle)}...
3931
3963
  `
3932
3964
  )
3933
3965
  );
@@ -3940,8 +3972,8 @@ async function continueFirstLoginFlow(result) {
3940
3972
  const spinner = ora8(`Preparing SSH access for ${machineHandle}...`).start();
3941
3973
  try {
3942
3974
  const connection = await prepareSSHConnectionByIdentifier(machineHandle);
3943
- spinner.succeed(`Connecting to ${chalk7.bold(machineHandle)}`);
3944
- console.log(chalk7.dim(` ${connection.command}`));
3975
+ spinner.succeed(`Connecting to ${chalk8.bold(machineHandle)}`);
3976
+ console.log(chalk8.dim(` ${connection.command}`));
3945
3977
  console.log();
3946
3978
  await openSSHConnection(connection);
3947
3979
  } catch (error) {
@@ -3952,19 +3984,19 @@ async function continueFirstLoginFlow(result) {
3952
3984
  }
3953
3985
  } catch (error) {
3954
3986
  const message = error instanceof Error ? error.message : "Failed to finish first-time setup";
3955
- console.error(chalk7.red(`
3987
+ console.error(chalk8.red(`
3956
3988
  ${message}`));
3957
3989
  console.log();
3958
3990
  if (result.provider === "claude") {
3959
3991
  console.log(
3960
- chalk7.dim(` computer claude-login --machine ${machineHandle}`)
3992
+ chalk8.dim(` computer claude-login --machine ${machineHandle}`)
3961
3993
  );
3962
3994
  } else if (result.provider === "codex") {
3963
3995
  console.log(
3964
- chalk7.dim(` computer codex-login --machine ${machineHandle}`)
3996
+ chalk8.dim(` computer codex-login --machine ${machineHandle}`)
3965
3997
  );
3966
3998
  }
3967
- console.log(chalk7.dim(` computer ssh ${machineHandle}`));
3999
+ console.log(chalk8.dim(` computer ssh ${machineHandle}`));
3968
4000
  console.log();
3969
4001
  process.exit(1);
3970
4002
  }
@@ -3978,19 +4010,19 @@ async function runSelectedProvider(provider, machineHandle) {
3978
4010
  await runCodexLogin({ machine: machineHandle });
3979
4011
  return;
3980
4012
  }
3981
- console.log(chalk7.green(`Sandbox ${chalk7.bold(machineHandle)} is ready.`));
4013
+ console.log(chalk8.green(`Sandbox ${chalk8.bold(machineHandle)} is ready.`));
3982
4014
  console.log();
3983
4015
  }
3984
4016
  function printNextStep(machineHandle) {
3985
- console.log(chalk7.green(`Sandbox ${chalk7.bold(machineHandle)} is ready.`));
3986
- 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}`));
3987
4019
  console.log();
3988
4020
  }
3989
4021
 
3990
4022
  // src/commands/mount.ts
3991
4023
  import { spawn as spawn4 } from "child_process";
3992
- import { Command as Command10, Option } from "commander";
3993
- import chalk8 from "chalk";
4024
+ import { Command as Command11, Option } from "commander";
4025
+ import chalk9 from "chalk";
3994
4026
  import ora9 from "ora";
3995
4027
 
3996
4028
  // src/lib/mount-daemon.ts
@@ -4007,7 +4039,7 @@ function getMountControllerState(rootPath = defaultMountServiceConfig().rootPath
4007
4039
  return { running: true, pid: lock.pid };
4008
4040
  }
4009
4041
  async function runMountDaemon(config, options = {}) {
4010
- const { onStarted } = options;
4042
+ const { onReady, onStarted } = options;
4011
4043
  const paths = getMountPaths(config.rootPath);
4012
4044
  ensureMountDirectories(paths);
4013
4045
  await mkdir3(paths.rootPath, { recursive: true });
@@ -4017,16 +4049,35 @@ async function runMountDaemon(config, options = {}) {
4017
4049
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4018
4050
  controllerPid: process.pid,
4019
4051
  running: true,
4052
+ startupPhase: "starting",
4053
+ startupMessage: "Starting mount controller...",
4020
4054
  mounts: []
4021
4055
  },
4022
4056
  config.rootPath
4023
4057
  );
4024
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
+ );
4025
4074
  await teardownManagedSessions(config, paths);
4075
+ await mkdir3(paths.rootPath, { recursive: true });
4026
4076
  let running = false;
4027
4077
  let queued = false;
4028
4078
  let shuttingDown = false;
4029
4079
  let activeRun = null;
4080
+ let activeRunController = null;
4030
4081
  const runOnce = async () => {
4031
4082
  if (shuttingDown) {
4032
4083
  return;
@@ -4035,12 +4086,19 @@ async function runMountDaemon(config, options = {}) {
4035
4086
  queued = true;
4036
4087
  return activeRun ?? void 0;
4037
4088
  }
4089
+ const controller = new AbortController();
4090
+ activeRunController = controller;
4038
4091
  activeRun = (async () => {
4039
4092
  running = true;
4040
4093
  try {
4041
- await reconcileMounts(config, paths, process.pid);
4094
+ await reconcileMounts(
4095
+ config,
4096
+ paths,
4097
+ process.pid,
4098
+ controller.signal
4099
+ );
4042
4100
  } catch (error) {
4043
- if (shuttingDown) {
4101
+ if (shuttingDown && isAbortError(error)) {
4044
4102
  return;
4045
4103
  }
4046
4104
  const previous = readMountStatusSnapshot(config.rootPath);
@@ -4049,6 +4107,8 @@ async function runMountDaemon(config, options = {}) {
4049
4107
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4050
4108
  controllerPid: process.pid,
4051
4109
  running: true,
4110
+ startupPhase: previous?.startupPhase === "ready" ? "ready" : "syncing",
4111
+ startupMessage: previous?.startupPhase === "ready" ? void 0 : "Waiting for initial sync...",
4052
4112
  lastHealthySyncAt: previous?.lastHealthySyncAt,
4053
4113
  lastSuccessfulSyncAt: previous?.lastSuccessfulSyncAt,
4054
4114
  lastIssueAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4064,6 +4124,7 @@ async function runMountDaemon(config, options = {}) {
4064
4124
  } finally {
4065
4125
  running = false;
4066
4126
  activeRun = null;
4127
+ activeRunController = null;
4067
4128
  if (queued && !shuttingDown) {
4068
4129
  queued = false;
4069
4130
  void runOnce();
@@ -4079,6 +4140,21 @@ async function runMountDaemon(config, options = {}) {
4079
4140
  server.once("error", reject);
4080
4141
  server.listen(paths.socketPath, () => resolve());
4081
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
+ );
4082
4158
  const interval = setInterval(() => {
4083
4159
  void runOnce();
4084
4160
  }, config.pollIntervalMs);
@@ -4089,6 +4165,7 @@ async function runMountDaemon(config, options = {}) {
4089
4165
  shuttingDown = true;
4090
4166
  clearInterval(interval);
4091
4167
  server.close();
4168
+ activeRunController?.abort();
4092
4169
  if (activeRun) {
4093
4170
  await activeRun.catch(() => {
4094
4171
  });
@@ -4100,6 +4177,8 @@ async function runMountDaemon(config, options = {}) {
4100
4177
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4101
4178
  controllerPid: previous?.controllerPid,
4102
4179
  running: false,
4180
+ startupPhase: void 0,
4181
+ startupMessage: void 0,
4103
4182
  lastHealthySyncAt: previous?.lastHealthySyncAt,
4104
4183
  lastSuccessfulSyncAt: previous?.lastSuccessfulSyncAt,
4105
4184
  lastIssueAt: previous?.lastIssueAt,
@@ -4119,6 +4198,8 @@ async function runMountDaemon(config, options = {}) {
4119
4198
  void shutdown();
4120
4199
  });
4121
4200
  await runOnce();
4201
+ writeMountStatusSnapshot(markMountStartupReady(readMountStatusSnapshot(config.rootPath), process.pid), config.rootPath);
4202
+ onReady?.();
4122
4203
  await new Promise(() => {
4123
4204
  });
4124
4205
  }
@@ -4147,41 +4228,69 @@ function processExists(pid) {
4147
4228
  return false;
4148
4229
  }
4149
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
+ }
4150
4246
 
4151
4247
  // src/commands/mount.ts
4152
- var mountCommand = new Command10("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(
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(
4153
4253
  "--background",
4154
4254
  "Run the mount controller in the background and print its PID"
4155
- ).addOption(new Option("--daemonized").hideHelp()).action(async (options) => {
4156
- const spinner = ora9(
4157
- options.background ? "Starting machine mount controller in background..." : "Starting machine mount controller..."
4158
- ).start();
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();
4159
4260
  try {
4160
4261
  if (options.daemonized) {
4161
- const config2 = readMountConfig() ?? defaultMountServiceConfig();
4162
- await runMountDaemon(config2, {
4262
+ const config = readMountConfig() ?? defaultMountServiceConfig();
4263
+ await runMountDaemon(config, {
4163
4264
  onStarted: () => {
4164
4265
  spinner.succeed("Machine mount controller running");
4165
- printMountStartSummary(config2, process.pid, "foreground");
4266
+ printMountStartSummary(config, process.pid, "foreground");
4267
+ },
4268
+ onReady: () => {
4269
+ if (options.openRootWhenReady) {
4270
+ revealMountRootInFinder(config.rootPath);
4271
+ }
4166
4272
  }
4167
4273
  });
4168
4274
  return;
4169
4275
  }
4170
- const config = await resolveMountServiceConfig(options);
4171
- writeMountConfig(config);
4276
+ const resolved = await resolveMountServiceConfig(options);
4277
+ writeMountConfig(resolved.config);
4278
+ process.env[AGENTCOMPUTER_MUTAGEN_PATH_ENV] = resolved.mutagenPath;
4172
4279
  if (options.background) {
4173
- const pid = await startMountControllerInBackground(config.rootPath);
4280
+ const pid = await startMountControllerInBackground(resolved.config.rootPath);
4174
4281
  spinner.succeed("Machine mount controller running in background");
4175
- printMountStartSummary(config, pid, "background");
4282
+ printMountStartSummary(resolved.config, pid, "background");
4176
4283
  return;
4177
4284
  }
4178
- await runMountDaemon(config, {
4179
- onStarted: () => {
4180
- spinner.succeed("Machine mount controller running");
4181
- printMountStartSummary(config, process.pid, "foreground");
4182
- revealMountRootInFinder(config.rootPath);
4183
- }
4184
- });
4285
+ const child = await startMountControllerInForeground(resolved.config.rootPath);
4286
+ await waitForMountControllerReady(resolved.config.rootPath, child.pid, spinner);
4287
+ spinner.succeed("Machine mount controller running");
4288
+ printMountStartSummary(
4289
+ resolved.config,
4290
+ child.pid ?? process.pid,
4291
+ "foreground"
4292
+ );
4293
+ await superviseForegroundMountController(child);
4185
4294
  } catch (error) {
4186
4295
  spinner.fail(
4187
4296
  error instanceof Error ? error.message : "Failed to start machine mount controller"
@@ -4189,40 +4298,40 @@ var mountCommand = new Command10("mount").description("Mirror SSH-ready machines
4189
4298
  process.exit(1);
4190
4299
  }
4191
4300
  }).addCommand(
4192
- new Command10("status").description("Show machine mount controller status").action(() => {
4301
+ new Command11("status").description("Show machine mount controller status").action(() => {
4193
4302
  const config = readMountConfig() ?? defaultMountServiceConfig();
4194
4303
  const controller = getMountControllerState(config.rootPath);
4195
4304
  const snapshot = readMountStatusSnapshot(config.rootPath);
4196
4305
  console.log();
4197
- console.log(` ${chalk8.bold("Machine Mounts")}`);
4306
+ console.log(` ${chalk9.bold("Machine Mounts")}`);
4198
4307
  console.log();
4199
4308
  console.log(
4200
- ` ${chalk8.dim("Running")} ${controller.running ? chalk8.green("yes") : chalk8.dim("no")}`
4309
+ ` ${chalk9.dim("Running")} ${controller.running ? chalk9.green("yes") : chalk9.dim("no")}`
4201
4310
  );
4202
4311
  if (controller.pid) {
4203
- console.log(` ${chalk8.dim("PID")} ${controller.pid}`);
4312
+ console.log(` ${chalk9.dim("PID")} ${controller.pid}`);
4204
4313
  }
4205
- console.log(` ${chalk8.dim("Root")} ${config.rootPath}`);
4206
- console.log(` ${chalk8.dim("Alias")} ${config.alias}`);
4314
+ console.log(` ${chalk9.dim("Root")} ${config.rootPath}`);
4315
+ console.log(` ${chalk9.dim("Alias")} ${config.alias}`);
4207
4316
  console.log(
4208
- ` ${chalk8.dim("Updated")} ${snapshot?.updatedAt ? timeAgo(snapshot.updatedAt) : chalk8.dim("never")}`
4317
+ ` ${chalk9.dim("Updated")} ${snapshot?.updatedAt ? timeAgo(snapshot.updatedAt) : chalk9.dim("never")}`
4209
4318
  );
4210
4319
  console.log(
4211
- ` ${chalk8.dim("Healthy")} ${snapshot?.lastHealthySyncAt ? timeAgo(snapshot.lastHealthySyncAt) : chalk8.dim("never")}`
4320
+ ` ${chalk9.dim("Healthy")} ${snapshot?.lastHealthySyncAt ? timeAgo(snapshot.lastHealthySyncAt) : chalk9.dim("never")}`
4212
4321
  );
4213
4322
  if (controller.running && snapshot?.mounts.length) {
4214
4323
  console.log();
4215
4324
  for (const mount of snapshot.mounts) {
4216
4325
  const state = formatMountState(mount.state);
4217
- console.log(` ${chalk8.white(mount.handle)} ${state} ${chalk8.dim(mount.mountPath)}`);
4326
+ console.log(` ${chalk9.white(mount.handle)} ${state} ${chalk9.dim(mount.mountPath)}`);
4218
4327
  if (mount.message) {
4219
- console.log(` ${chalk8.dim(mount.message)}`);
4328
+ console.log(` ${chalk9.dim(mount.message)}`);
4220
4329
  }
4221
4330
  }
4222
4331
  }
4223
4332
  if (snapshot?.lastIssue) {
4224
4333
  console.log();
4225
- console.log(` ${chalk8.dim("Last issue")} ${chalk8.yellow(snapshot.lastIssue)}`);
4334
+ console.log(` ${chalk9.dim("Last issue")} ${chalk9.yellow(snapshot.lastIssue)}`);
4226
4335
  }
4227
4336
  console.log();
4228
4337
  })
@@ -4244,20 +4353,35 @@ async function resolveMountServiceConfig(options) {
4244
4353
  ].join("\n")
4245
4354
  );
4246
4355
  }
4356
+ const mutagenPath = await ensureMutagenCommandPath();
4247
4357
  const sshSetup = await ensureSSHAccessConfigured(options);
4248
4358
  return {
4249
- ...defaultMountServiceConfig(),
4250
- alias: sshSetup.alias,
4251
- host: sshSetup.host,
4252
- port: sshSetup.port,
4253
- pollIntervalMs: parsePositiveInt(options.pollInterval, "poll interval"),
4254
- connectTimeoutSeconds: parsePositiveInt(
4255
- options.connectTimeout,
4256
- "connect timeout"
4257
- )
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
4258
4371
  };
4259
4372
  }
4260
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) {
4261
4385
  const controller = getMountControllerState(rootPath);
4262
4386
  if (controller.running) {
4263
4387
  throw new Error(`computer mount is already running (pid ${controller.pid})`);
@@ -4266,20 +4390,22 @@ async function startMountControllerInBackground(rootPath) {
4266
4390
  if (!entrypoint) {
4267
4391
  throw new Error("unable to determine CLI entrypoint for background mount");
4268
4392
  }
4269
- const child = spawn4(process.execPath, [entrypoint, "mount", "--daemonized"], {
4393
+ const args = ["mount", "--daemonized"];
4394
+ if (openRootWhenReady) {
4395
+ args.push("--open-root-when-ready");
4396
+ }
4397
+ const child = spawn4(process.execPath, [entrypoint, ...args], {
4270
4398
  cwd: process.cwd(),
4271
- detached: true,
4399
+ detached,
4272
4400
  env: process.env,
4273
4401
  stdio: "ignore"
4274
4402
  });
4275
4403
  if (!child.pid) {
4276
- throw new Error("failed to start machine mount controller in background");
4404
+ throw new Error("failed to start machine mount controller");
4277
4405
  }
4278
- child.unref();
4279
- await waitForBackgroundMountController(rootPath, child.pid);
4280
- return child.pid;
4406
+ return child;
4281
4407
  }
4282
- async function waitForBackgroundMountController(rootPath, pid) {
4408
+ async function waitForMountControllerRunning(rootPath, pid) {
4283
4409
  const deadline = Date.now() + 3e3;
4284
4410
  while (Date.now() < deadline) {
4285
4411
  const controller = getMountControllerState(rootPath);
@@ -4293,15 +4419,77 @@ async function waitForBackgroundMountController(rootPath, pid) {
4293
4419
  }
4294
4420
  throw new Error("failed to start machine mount controller in background");
4295
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
+ }
4296
4484
  function printMountStartSummary(config, pid, mode) {
4297
4485
  console.log();
4298
- console.log(chalk8.dim(` PID: ${pid}`));
4299
- console.log(chalk8.dim(` Root: ${config.rootPath}`));
4300
- console.log(chalk8.dim(` SSH alias: ${config.alias}`));
4301
- console.log(chalk8.dim(` Poll: ${config.pollIntervalMs}ms`));
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`));
4302
4490
  console.log();
4303
4491
  console.log(
4304
- chalk8.dim(
4492
+ chalk9.dim(
4305
4493
  mode === "background" ? " Use `computer mount status` to inspect sync state." : " Press Ctrl-C to stop syncing."
4306
4494
  )
4307
4495
  );
@@ -4333,35 +4521,35 @@ function revealMountRootInFinder(rootPath) {
4333
4521
  function formatMountState(state) {
4334
4522
  switch (state) {
4335
4523
  case "mounted":
4336
- return chalk8.green(state);
4524
+ return chalk9.green(state);
4337
4525
  case "reconnecting":
4338
- return chalk8.cyan(state);
4526
+ return chalk9.cyan(state);
4339
4527
  case "degraded":
4340
4528
  case "pending":
4341
- return chalk8.yellow(state);
4529
+ return chalk9.yellow(state);
4342
4530
  default:
4343
- return chalk8.red(state);
4531
+ return chalk9.red(state);
4344
4532
  }
4345
4533
  }
4346
4534
 
4347
4535
  // src/commands/logout.ts
4348
- import { Command as Command11 } from "commander";
4349
- import chalk9 from "chalk";
4350
- 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(() => {
4351
4539
  if (!getStoredAPIKey()) {
4352
4540
  console.log();
4353
- console.log(chalk9.dim(" Not logged in."));
4541
+ console.log(chalk10.dim(" Not logged in."));
4354
4542
  if (hasEnvAPIKey()) {
4355
- 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."));
4356
4544
  }
4357
4545
  console.log();
4358
4546
  return;
4359
4547
  }
4360
4548
  clearAPIKey();
4361
4549
  console.log();
4362
- console.log(chalk9.green(" Logged out."));
4550
+ console.log(chalk10.green(" Logged out."));
4363
4551
  if (hasEnvAPIKey()) {
4364
- 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."));
4365
4553
  }
4366
4554
  console.log();
4367
4555
  });
@@ -4369,8 +4557,8 @@ var logoutCommand = new Command11("logout").description("Remove stored API key")
4369
4557
  // src/commands/upgrade.ts
4370
4558
  import { spawnSync } from "child_process";
4371
4559
  import { readFileSync as readFileSync2, realpathSync } from "fs";
4372
- import { Command as Command12 } from "commander";
4373
- import chalk10 from "chalk";
4560
+ import { Command as Command13 } from "commander";
4561
+ import chalk11 from "chalk";
4374
4562
  import ora10 from "ora";
4375
4563
  var pkg2 = JSON.parse(
4376
4564
  readFileSync2(new URL("../package.json", import.meta.url), "utf8")
@@ -4485,7 +4673,25 @@ async function getLatestVersion(packageName) {
4485
4673
  }
4486
4674
  return payload.version;
4487
4675
  }
4488
- 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 () => {
4489
4695
  const currentVersion = pkg2.version ?? "0.0.0";
4490
4696
  const packageName = pkg2.name ?? "aicomputer";
4491
4697
  const spinner = ora10("Checking for updates...").start();
@@ -4518,16 +4724,19 @@ var upgradeCommand = new Command12("upgrade").description("Update the CLI to the
4518
4724
  spinner.stop();
4519
4725
  console.log();
4520
4726
  console.log(
4521
- chalk10.dim(` Updating ${chalk10.bold(`v${currentVersion}`)} -> ${chalk10.bold(`v${latestVersion}`)}`)
4727
+ chalk11.dim(` Updating ${chalk11.bold(`v${currentVersion}`)} -> ${chalk11.bold(`v${latestVersion}`)}`)
4522
4728
  );
4523
- console.log(chalk10.dim(` ${upgrade.label}`));
4729
+ console.log(chalk11.dim(` ${upgrade.label}`));
4524
4730
  console.log();
4525
4731
  const result = spawnSync(upgrade.command, upgrade.args, {
4526
4732
  stdio: "inherit"
4527
4733
  });
4528
4734
  if (result.status === 0) {
4735
+ if (method !== "nix") {
4736
+ attemptBundledMutagenRefresh(executablePath);
4737
+ }
4529
4738
  console.log();
4530
- console.log(chalk10.green(` Updated to v${latestVersion}.`));
4739
+ console.log(chalk11.green(` Updated to v${latestVersion}.`));
4531
4740
  console.log();
4532
4741
  return;
4533
4742
  }
@@ -4535,10 +4744,10 @@ var upgradeCommand = new Command12("upgrade").description("Update the CLI to the
4535
4744
  });
4536
4745
 
4537
4746
  // src/commands/whoami.ts
4538
- import { Command as Command13 } from "commander";
4539
- import chalk11 from "chalk";
4747
+ import { Command as Command14 } from "commander";
4748
+ import chalk12 from "chalk";
4540
4749
  import ora11 from "ora";
4541
- 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) => {
4542
4751
  const spinner = options.json ? null : ora11("Loading user...").start();
4543
4752
  try {
4544
4753
  const me = await api("/v1/me");
@@ -4548,14 +4757,14 @@ var whoamiCommand = new Command13("whoami").description("Show current user").opt
4548
4757
  return;
4549
4758
  }
4550
4759
  console.log();
4551
- 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)}`);
4552
4761
  if (me.user.display_name) {
4553
- console.log(` ${chalk11.dim(me.user.email)}`);
4762
+ console.log(` ${chalk12.dim(me.user.email)}`);
4554
4763
  }
4555
4764
  if (me.api_key.name) {
4556
- console.log(` ${chalk11.dim("Key:")} ${me.api_key.name}`);
4765
+ console.log(` ${chalk12.dim("Key:")} ${me.api_key.name}`);
4557
4766
  }
4558
- console.log(` ${chalk11.dim("API:")} ${chalk11.dim(getBaseURL())}`);
4767
+ console.log(` ${chalk12.dim("API:")} ${chalk12.dim(getBaseURL())}`);
4559
4768
  console.log();
4560
4769
  } catch (error) {
4561
4770
  if (spinner) {
@@ -4572,15 +4781,15 @@ var pkg3 = JSON.parse(
4572
4781
  readFileSync3(new URL("../package.json", import.meta.url), "utf8")
4573
4782
  );
4574
4783
  var cliName = process.argv[1] ? basename2(process.argv[1]) : "agentcomputer";
4575
- var program = new Command14();
4784
+ var program = new Command15();
4576
4785
  function appendTextSection(lines, title, values) {
4577
4786
  if (values.length === 0) {
4578
4787
  return;
4579
4788
  }
4580
- lines.push(` ${chalk12.dim(title)}`);
4789
+ lines.push(` ${chalk13.dim(title)}`);
4581
4790
  lines.push("");
4582
4791
  for (const value of values) {
4583
- lines.push(` ${chalk12.white(value)}`);
4792
+ lines.push(` ${chalk13.white(value)}`);
4584
4793
  }
4585
4794
  lines.push("");
4586
4795
  }
@@ -4589,10 +4798,10 @@ function appendTableSection(lines, title, entries) {
4589
4798
  return;
4590
4799
  }
4591
4800
  const width = Math.max(...entries.map((entry) => entry.term.length), 0) + 2;
4592
- lines.push(` ${chalk12.dim(title)}`);
4801
+ lines.push(` ${chalk13.dim(title)}`);
4593
4802
  lines.push("");
4594
4803
  for (const entry of entries) {
4595
- 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)}`);
4596
4805
  }
4597
4806
  lines.push("");
4598
4807
  }
@@ -4618,10 +4827,10 @@ function formatRootHelp(cmd) {
4618
4827
  ["Other", []]
4619
4828
  ];
4620
4829
  const otherGroup = groups.find(([name]) => name === "Other")[1];
4621
- lines.push(`${chalk12.bold(cliName)} ${chalk12.dim(`v${version}`)}`);
4830
+ lines.push(`${chalk13.bold(cliName)} ${chalk13.dim(`v${version}`)}`);
4622
4831
  lines.push("");
4623
4832
  if (cmd.description()) {
4624
- lines.push(` ${chalk12.dim(cmd.description())}`);
4833
+ lines.push(` ${chalk13.dim(cmd.description())}`);
4625
4834
  lines.push("");
4626
4835
  }
4627
4836
  appendTextSection(lines, "Usage", [`${cliName} <command> [options]`]);
@@ -4674,10 +4883,10 @@ function formatSubcommandHelp(cmd, helper) {
4674
4883
  term: helper.optionTerm(option),
4675
4884
  desc: helper.optionDescription(option)
4676
4885
  }));
4677
- lines.push(chalk12.bold(commandPath(cmd)));
4886
+ lines.push(chalk13.bold(commandPath(cmd)));
4678
4887
  lines.push("");
4679
4888
  if (description) {
4680
- lines.push(` ${chalk12.dim(description)}`);
4889
+ lines.push(` ${chalk13.dim(description)}`);
4681
4890
  lines.push("");
4682
4891
  }
4683
4892
  appendTextSection(lines, "Usage", [helper.commandUsage(cmd)]);
@@ -4703,6 +4912,7 @@ function applyHelpFormatting(cmd) {
4703
4912
  program.name(cliName).description("Agent Computer CLI").version(pkg3.version ?? "0.0.0").option("-y, --yes", "Skip confirmation prompts");
4704
4913
  program.addCommand(loginCommand);
4705
4914
  program.addCommand(upgradeCommand);
4915
+ program.addCommand(internalInstallMutagenCommand, { hidden: true });
4706
4916
  program.addCommand(logoutCommand);
4707
4917
  program.addCommand(whoamiCommand);
4708
4918
  program.addCommand(claudeLoginCommand);