aicomputer 0.1.16 → 0.1.17

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.
Files changed (3) hide show
  1. package/README.md +10 -2
  2. package/dist/index.js +149 -37
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -95,6 +95,7 @@ computer ssh
95
95
  computer ssh my-box
96
96
  computer ssh --setup
97
97
  computer mount
98
+ computer mount --background
98
99
  computer mount status
99
100
  computer agent agents my-box
100
101
  computer agent sessions list my-box
@@ -117,8 +118,15 @@ ssh agentcomputer.ai
117
118
  ssh my-box@agentcomputer.ai
118
119
  ```
119
120
 
120
- Run `computer mount` to start a foreground controller that mirrors all SSH-ready
121
- machine homes under `~/agentcomputer/<handle>` while the command is running.
121
+ Run `computer mount` to start the mount controller in the foreground and mirror
122
+ all SSH-ready machine homes under `~/agentcomputer/<handle>`.
123
+ On macOS, the foreground command also opens Finder to `~/agentcomputer` after
124
+ the controller starts.
125
+
126
+ Run `computer mount --background` to start the same controller detached from
127
+ your terminal. It prints the controller PID immediately so you can inspect it
128
+ later with `computer mount status`.
129
+
122
130
  This uses the same SSH setup as `computer ssh --setup` and requires Mutagen plus
123
131
  OpenSSH client tools (`ssh` and `scp`) to already be installed locally. The
124
132
  temporary `~/agentcomputer` root is removed when the command exits.
package/dist/index.js CHANGED
@@ -2460,7 +2460,7 @@ _computer() {
2460
2460
  'open:Open in browser'
2461
2461
  'ssh:SSH into a computer'
2462
2462
  'ports:Manage published ports'
2463
- 'mount:Mirror SSH-ready machine homes into ~/agentcomputer while running'
2463
+ 'mount:Mirror SSH-ready machine homes into ~/agentcomputer'
2464
2464
  'agent:Manage cloud agent sessions'
2465
2465
  'acp:Run a local ACP bridge for remote agent sessions'
2466
2466
  'rm:Delete a computer'
@@ -2600,6 +2600,7 @@ _computer() {
2600
2600
  ;;
2601
2601
  mount)
2602
2602
  _arguments -C \\
2603
+ '--background[Run the mount controller in the background and print its PID]' \\
2603
2604
  '--alias[SSH host alias]:alias:' \\
2604
2605
  '--host[SSH gateway host]:host:' \\
2605
2606
  '--port[SSH gateway port]:port:' \\
@@ -2771,9 +2772,9 @@ var BASH_SCRIPT = `_computer() {
2771
2772
  ;;
2772
2773
  mount)
2773
2774
  if [[ $cword -eq 2 ]]; then
2774
- COMPREPLY=($(compgen -W "$mount_commands" -- "$cur"))
2775
+ COMPREPLY=($(compgen -W "$mount_commands --background --alias --host --port --poll-interval --connect-timeout" -- "$cur"))
2775
2776
  elif [[ $cword -ge 3 ]]; then
2776
- COMPREPLY=($(compgen -W "--alias --host --port --poll-interval --connect-timeout" -- "$cur"))
2777
+ COMPREPLY=($(compgen -W "--background --alias --host --port --poll-interval --connect-timeout" -- "$cur"))
2777
2778
  fi
2778
2779
  ;;
2779
2780
  ports)
@@ -3987,7 +3988,8 @@ function printNextStep(machineHandle) {
3987
3988
  }
3988
3989
 
3989
3990
  // src/commands/mount.ts
3990
- import { Command as Command10 } from "commander";
3991
+ import { spawn as spawn4 } from "child_process";
3992
+ import { Command as Command10, Option } from "commander";
3991
3993
  import chalk8 from "chalk";
3992
3994
  import ora9 from "ora";
3993
3995
 
@@ -4004,7 +4006,8 @@ function getMountControllerState(rootPath = defaultMountServiceConfig().rootPath
4004
4006
  }
4005
4007
  return { running: true, pid: lock.pid };
4006
4008
  }
4007
- async function runMountDaemon(config) {
4009
+ async function runMountDaemon(config, options = {}) {
4010
+ const { onStarted } = options;
4008
4011
  const paths = getMountPaths(config.rootPath);
4009
4012
  ensureMountDirectories(paths);
4010
4013
  await mkdir3(paths.rootPath, { recursive: true });
@@ -4018,6 +4021,7 @@ async function runMountDaemon(config) {
4018
4021
  },
4019
4022
  config.rootPath
4020
4023
  );
4024
+ onStarted?.();
4021
4025
  await teardownManagedSessions(config, paths);
4022
4026
  let running = false;
4023
4027
  let queued = false;
@@ -4145,40 +4149,39 @@ function processExists(pid) {
4145
4149
  }
4146
4150
 
4147
4151
  // 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();
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(
4153
+ "--background",
4154
+ "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();
4150
4159
  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
- );
4160
+ if (options.daemonized) {
4161
+ const config2 = readMountConfig() ?? defaultMountServiceConfig();
4162
+ await runMountDaemon(config2, {
4163
+ onStarted: () => {
4164
+ spinner.succeed("Machine mount controller running");
4165
+ printMountStartSummary(config2, process.pid, "foreground");
4166
+ }
4167
+ });
4168
+ return;
4159
4169
  }
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
- };
4170
+ const config = await resolveMountServiceConfig(options);
4172
4171
  writeMountConfig(config);
4173
- spinner.succeed("Machine mount controller running");
4174
- console.log();
4175
- console.log(chalk8.dim(` Root: ${config.rootPath}`));
4176
- console.log(chalk8.dim(` SSH alias: ${config.alias}`));
4177
- console.log(chalk8.dim(` Poll: ${config.pollIntervalMs}ms`));
4178
- console.log();
4179
- console.log(chalk8.dim(" Press Ctrl-C to stop syncing."));
4180
- console.log();
4181
- await runMountDaemon(config);
4172
+ if (options.background) {
4173
+ const pid = await startMountControllerInBackground(config.rootPath);
4174
+ spinner.succeed("Machine mount controller running in background");
4175
+ printMountStartSummary(config, pid, "background");
4176
+ return;
4177
+ }
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
+ });
4182
4185
  } catch (error) {
4183
4186
  spinner.fail(
4184
4187
  error instanceof Error ? error.message : "Failed to start machine mount controller"
@@ -4210,7 +4213,7 @@ var mountCommand = new Command10("mount").description("Mirror SSH-ready machines
4210
4213
  if (controller.running && snapshot?.mounts.length) {
4211
4214
  console.log();
4212
4215
  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);
4216
+ const state = formatMountState(mount.state);
4214
4217
  console.log(` ${chalk8.white(mount.handle)} ${state} ${chalk8.dim(mount.mountPath)}`);
4215
4218
  if (mount.message) {
4216
4219
  console.log(` ${chalk8.dim(mount.message)}`);
@@ -4231,6 +4234,115 @@ function parsePositiveInt(raw, label) {
4231
4234
  }
4232
4235
  return Math.round(value);
4233
4236
  }
4237
+ async function resolveMountServiceConfig(options) {
4238
+ const issues = getMountHostValidationIssues();
4239
+ if (issues.length > 0) {
4240
+ throw new Error(
4241
+ [
4242
+ ...issues.map((issue) => issue.message),
4243
+ ...formatMountHostInstallGuidance(issues)
4244
+ ].join("\n")
4245
+ );
4246
+ }
4247
+ const sshSetup = await ensureSSHAccessConfigured(options);
4248
+ 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
+ )
4258
+ };
4259
+ }
4260
+ async function startMountControllerInBackground(rootPath) {
4261
+ const controller = getMountControllerState(rootPath);
4262
+ if (controller.running) {
4263
+ throw new Error(`computer mount is already running (pid ${controller.pid})`);
4264
+ }
4265
+ const entrypoint = process.argv[1];
4266
+ if (!entrypoint) {
4267
+ throw new Error("unable to determine CLI entrypoint for background mount");
4268
+ }
4269
+ const child = spawn4(process.execPath, [entrypoint, "mount", "--daemonized"], {
4270
+ cwd: process.cwd(),
4271
+ detached: true,
4272
+ env: process.env,
4273
+ stdio: "ignore"
4274
+ });
4275
+ if (!child.pid) {
4276
+ throw new Error("failed to start machine mount controller in background");
4277
+ }
4278
+ child.unref();
4279
+ await waitForBackgroundMountController(rootPath, child.pid);
4280
+ return child.pid;
4281
+ }
4282
+ async function waitForBackgroundMountController(rootPath, pid) {
4283
+ const deadline = Date.now() + 3e3;
4284
+ while (Date.now() < deadline) {
4285
+ const controller = getMountControllerState(rootPath);
4286
+ if (controller.running && controller.pid === pid) {
4287
+ return;
4288
+ }
4289
+ if (!processExists2(pid)) {
4290
+ break;
4291
+ }
4292
+ await new Promise((resolve) => setTimeout(resolve, 50));
4293
+ }
4294
+ throw new Error("failed to start machine mount controller in background");
4295
+ }
4296
+ function printMountStartSummary(config, pid, mode) {
4297
+ 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`));
4302
+ console.log();
4303
+ console.log(
4304
+ chalk8.dim(
4305
+ mode === "background" ? " Use `computer mount status` to inspect sync state." : " Press Ctrl-C to stop syncing."
4306
+ )
4307
+ );
4308
+ console.log();
4309
+ }
4310
+ function processExists2(pid) {
4311
+ if (!Number.isInteger(pid) || pid <= 0) {
4312
+ return false;
4313
+ }
4314
+ try {
4315
+ process.kill(pid, 0);
4316
+ return true;
4317
+ } catch {
4318
+ return false;
4319
+ }
4320
+ }
4321
+ function revealMountRootInFinder(rootPath) {
4322
+ if (process.platform !== "darwin") {
4323
+ return;
4324
+ }
4325
+ const child = spawn4("open", [rootPath], {
4326
+ stdio: "ignore",
4327
+ detached: true
4328
+ });
4329
+ child.on("error", () => {
4330
+ });
4331
+ child.unref();
4332
+ }
4333
+ function formatMountState(state) {
4334
+ switch (state) {
4335
+ case "mounted":
4336
+ return chalk8.green(state);
4337
+ case "reconnecting":
4338
+ return chalk8.cyan(state);
4339
+ case "degraded":
4340
+ case "pending":
4341
+ return chalk8.yellow(state);
4342
+ default:
4343
+ return chalk8.red(state);
4344
+ }
4345
+ }
4234
4346
 
4235
4347
  // src/commands/logout.ts
4236
4348
  import { Command as Command11 } from "commander";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicomputer",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Computer CLI - manage your Agent Computer machines from the terminal",
5
5
  "homepage": "https://agentcomputer.ai",
6
6
  "repository": {