@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.
- package/package.json +1 -1
- package/src/cli.js +66 -6
package/package.json
CHANGED
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
|
-
|
|
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 =
|
|
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));
|