@magclaw/cli-core 0.1.23 → 0.1.24

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +66 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magclaw/cli-core",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "Shared local MagClaw CLI implementation used by daemon and computer packages.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -52,6 +52,7 @@ export const CAPABILITIES = [
52
52
  'agent:stop',
53
53
  'agent:skills:list',
54
54
  'daemon:upgrade',
55
+ 'daemon:close',
55
56
  'daemon:release_notice',
56
57
  'machine:runtime_models:detect',
57
58
  ];
@@ -585,6 +586,7 @@ function renderHelp() {
585
586
  ' --server-url <url> MagClaw Cloud URL',
586
587
  ' --api-key <key> Machine API key for connect',
587
588
  ' --background Install and run as a background service',
589
+ ' --disable With stop: suppress background relaunch until next start',
588
590
  ' --bin-dir <path> install-cli target directory',
589
591
  ' --to <version> Upgrade target version (default: latest)',
590
592
  ' --wait-cloud Wait for Cloud heartbeat during manual upgrade',
@@ -3452,6 +3454,41 @@ class MagClawDaemon {
3452
3454
  await this.startUpgradeWorker(message);
3453
3455
  }
3454
3456
 
3457
+ async handleDaemonClose(message) {
3458
+ const commandId = String(message.commandId || '').trim();
3459
+ const service = await readServiceState(this.paths.profile, this.env);
3460
+ const serviceStatus = backgroundServiceStatus(this.paths.profile, this.env);
3461
+ const packageInfo = runtimePackageInfo(this.env, service);
3462
+ const runMode = {
3463
+ mode: service.mode || serviceStatus.mode || 'foreground',
3464
+ background: Boolean(service.background),
3465
+ active: Boolean(serviceStatus.active),
3466
+ label: serviceStatus.label || '',
3467
+ serviceName: serviceStatus.serviceName || '',
3468
+ taskName: serviceStatus.taskName || '',
3469
+ packageName: packageInfo.name,
3470
+ packageVersion: packageInfo.version,
3471
+ packageKind: packageInfo.kind,
3472
+ packageSpec: packageInfo.spec,
3473
+ packageBin: packageInfo.bin,
3474
+ };
3475
+ logWarning('daemon', `Remote close requested (${message.reason || 'closed_from_cloud'}).`);
3476
+ for (const session of this.sessions.values()) session.stop();
3477
+ this.sessions.clear();
3478
+ this.send({ type: 'daemon:close:ack',
3479
+ commandId,
3480
+ status: 'stopping',
3481
+ reason: message.reason || 'closed_from_cloud',
3482
+ service: runMode,
3483
+ at: now(),
3484
+ });
3485
+ const background = stopBackground(this.paths.profile, this.env, { disable: message.disableBackground !== false });
3486
+ logInfo('daemon', `Close request stopped background service mode=${background.mode || 'foreground'} ok=${Boolean(background.ok)}.`);
3487
+ this.close();
3488
+ process.exitCode = 0;
3489
+ setTimeout(() => process.exit(0), 50).unref?.();
3490
+ }
3491
+
3455
3492
  async readyPayload() {
3456
3493
  const runtimes = await detectRuntimes(this.env);
3457
3494
  const owner = await ensureMachineFingerprint(this.paths.profile, this.env);
@@ -3657,6 +3694,9 @@ class MagClawDaemon {
3657
3694
  case 'daemon:upgrade':
3658
3695
  await this.handleDaemonUpgrade(message);
3659
3696
  break;
3697
+ case 'daemon:close':
3698
+ await this.handleDaemonClose(message);
3699
+ break;
3660
3700
  case 'agent:start':
3661
3701
  await this.handleAgentStart(message);
3662
3702
  break;
@@ -3996,7 +4036,9 @@ class MagClawDaemon {
3996
4036
  const requestModule = url.protocol === 'wss:' ? https : http;
3997
4037
  const requestUrl = new URL(url.href.replace(/^ws/, 'http'));
3998
4038
  const key = crypto.randomBytes(16).toString('base64');
3999
- logInfo('daemon', `Connecting MagClaw daemon profile "${this.paths.profile}" to ${this.config.serverUrl}...`);
4039
+ const packageInfo = runtimePackageInfo(this.env);
4040
+ logInfo('daemon', `Connecting MagClaw daemon v${packageInfo.version || DAEMON_VERSION} profile "${this.paths.profile}" to ${this.config.serverUrl}...`);
4041
+ if (this.closed) return;
4000
4042
  return new Promise((resolve, reject) => {
4001
4043
  let settled = false;
4002
4044
  const finish = (callback, value) => {
@@ -4285,6 +4327,7 @@ async function startMacBackground(profile, env = process.env) {
4285
4327
  ].join('\n');
4286
4328
  await writeFile(plist, xml);
4287
4329
  spawnSync('launchctl', ['bootout', `gui/${process.getuid()}`, plist], { stdio: 'ignore' });
4330
+ spawnSync('launchctl', ['enable', `gui/${process.getuid()}/${label}`], { stdio: 'ignore' });
4288
4331
  const boot = spawnSync('launchctl', ['bootstrap', `gui/${process.getuid()}`, plist], { encoding: 'utf8' });
4289
4332
  if (boot.status !== 0) {
4290
4333
  const load = spawnSync('launchctl', ['load', plist], { encoding: 'utf8' });
@@ -4451,32 +4494,49 @@ async function stopActiveDaemon(profile, env = process.env) {
4451
4494
  return { ok: stopped, running: !stopped, pid, signal };
4452
4495
  }
4453
4496
 
4454
- function stopBackground(profile, env = process.env) {
4497
+ function stopBackground(profile, env = process.env, options = {}) {
4455
4498
  if (process.platform === 'darwin') {
4456
4499
  const paths = profilePaths(profile, env);
4457
4500
  const label = launchAgentLabel(paths.profile);
4458
4501
  const plist = path.join(os.homedir(), 'Library', 'LaunchAgents', `${label}.plist`);
4502
+ if (options.disable) {
4503
+ const disabled = spawnSync('launchctl', ['disable', `gui/${process.getuid()}/${label}`], { encoding: 'utf8' });
4504
+ const stopped = spawnSync('launchctl', ['stop', label], { encoding: 'utf8' });
4505
+ if (stopped.status !== 0) spawnSync('launchctl', ['bootout', `gui/${process.getuid()}`, plist], { stdio: 'ignore' });
4506
+ return {
4507
+ ok: disabled.status === 0 || stopped.status === 0,
4508
+ mode: 'launchd',
4509
+ label,
4510
+ file: plist,
4511
+ disabled: disabled.status === 0,
4512
+ stopped: stopped.status === 0,
4513
+ error: disabled.status === 0 && stopped.status === 0 ? '' : String(stopped.stderr || disabled.stderr || stopped.stdout || disabled.stdout || '').trim(),
4514
+ };
4515
+ }
4459
4516
  spawnSync('launchctl', ['bootout', `gui/${process.getuid()}`, plist], { stdio: 'ignore' });
4460
4517
  return { ok: true, mode: 'launchd', label, file: plist };
4461
4518
  }
4462
4519
  if (process.platform === 'linux') {
4463
4520
  const paths = profilePaths(profile, env);
4464
4521
  const serviceName = systemdServiceName(paths.profile);
4465
- const result = spawnSync('systemctl', ['--user', 'disable', '--now', serviceName], { encoding: 'utf8' });
4522
+ const result = options.disable
4523
+ ? spawnSync('systemctl', ['--user', 'disable', '--now', serviceName], { encoding: 'utf8' })
4524
+ : spawnSync('systemctl', ['--user', 'stop', serviceName], { encoding: 'utf8' });
4466
4525
  return { ok: result.status === 0, mode: 'systemd', serviceName, error: result.stderr || '' };
4467
4526
  }
4468
4527
  if (process.platform === 'win32') {
4469
4528
  const paths = profilePaths(profile, env);
4470
4529
  const taskName = windowsTaskName(paths.profile);
4471
4530
  spawnSync('schtasks.exe', ['/End', '/TN', taskName], { stdio: 'ignore' });
4531
+ if (!options.disable) return { ok: true, mode: 'schtasks', taskName };
4472
4532
  const result = spawnSync('schtasks.exe', ['/Change', '/TN', taskName, '/DISABLE'], { encoding: 'utf8' });
4473
4533
  return { ok: result.status === 0, mode: 'schtasks', taskName, error: result.stderr || result.stdout || '' };
4474
4534
  }
4475
4535
  return { ok: false, mode: 'foreground' };
4476
4536
  }
4477
4537
 
4478
- async function stopDaemon(profile, env = process.env) {
4479
- const background = stopBackground(profile, env);
4538
+ async function stopDaemon(profile, env = process.env, options = {}) {
4539
+ const background = stopBackground(profile, env, options);
4480
4540
  const processResult = await stopActiveDaemon(profile, env);
4481
4541
  const backgroundRequired = background.mode !== 'foreground';
4482
4542
  return {
@@ -5211,7 +5271,7 @@ export async function main(argv = process.argv, env = process.env) {
5211
5271
  }
5212
5272
  case 'stop':
5213
5273
  requireExplicitProfile('stop', flags);
5214
- printJson(await stopDaemon(flags.profile, env));
5274
+ printJson(await stopDaemon(flags.profile, env, { disable: Boolean(flags.disable) }));
5215
5275
  break;
5216
5276
  case 'restart':
5217
5277
  printJson(await restartSavedBackground(flags, env));