@magclaw/cli-core 0.1.23 → 0.1.25
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 +197 -12
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
|
];
|
|
@@ -403,6 +404,12 @@ export function profilePaths(profile = DEFAULT_PROFILE, env = process.env) {
|
|
|
403
404
|
};
|
|
404
405
|
}
|
|
405
406
|
|
|
407
|
+
function sleepSync(ms) {
|
|
408
|
+
const timeout = Math.max(0, Number(ms) || 0);
|
|
409
|
+
if (!timeout) return;
|
|
410
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, timeout);
|
|
411
|
+
}
|
|
412
|
+
|
|
406
413
|
function machineFingerprintValue() {
|
|
407
414
|
return `mfp_${crypto.createHash('sha256')
|
|
408
415
|
.update([
|
|
@@ -585,6 +592,7 @@ function renderHelp() {
|
|
|
585
592
|
' --server-url <url> MagClaw Cloud URL',
|
|
586
593
|
' --api-key <key> Machine API key for connect',
|
|
587
594
|
' --background Install and run as a background service',
|
|
595
|
+
' --disable With stop: suppress background relaunch until next start',
|
|
588
596
|
' --bin-dir <path> install-cli target directory',
|
|
589
597
|
' --to <version> Upgrade target version (default: latest)',
|
|
590
598
|
' --wait-cloud Wait for Cloud heartbeat during manual upgrade',
|
|
@@ -671,6 +679,10 @@ async function readServiceState(profile = DEFAULT_PROFILE, env = process.env) {
|
|
|
671
679
|
previousPackageSpec: state.previousPackageSpec || '',
|
|
672
680
|
installedDaemonVersion: state.installedDaemonVersion || DAEMON_VERSION,
|
|
673
681
|
installedPackageVersion: state.installedPackageVersion || packageVersion || state.installedDaemonVersion || DAEMON_VERSION,
|
|
682
|
+
remoteClosed: Boolean(state.remoteClosed),
|
|
683
|
+
remoteClosedAt: state.remoteClosedAt || '',
|
|
684
|
+
remoteCloseReason: state.remoteCloseReason || '',
|
|
685
|
+
remoteCloseCommandId: state.remoteCloseCommandId || '',
|
|
674
686
|
updatedAt: state.updatedAt || '',
|
|
675
687
|
};
|
|
676
688
|
}
|
|
@@ -689,6 +701,31 @@ async function writeServiceState(profile = DEFAULT_PROFILE, patch = {}, env = pr
|
|
|
689
701
|
return next;
|
|
690
702
|
}
|
|
691
703
|
|
|
704
|
+
async function clearRemoteClosedServiceState(profile = DEFAULT_PROFILE, env = process.env) {
|
|
705
|
+
return writeServiceState(profile, {
|
|
706
|
+
remoteClosed: false,
|
|
707
|
+
remoteClosedAt: '',
|
|
708
|
+
remoteCloseReason: '',
|
|
709
|
+
remoteCloseCommandId: '',
|
|
710
|
+
}, env);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
async function markForegroundServiceState(profile = DEFAULT_PROFILE, env = process.env) {
|
|
714
|
+
const packageInfo = runtimePackageInfo(env);
|
|
715
|
+
return writeServiceState(profile, {
|
|
716
|
+
mode: 'foreground',
|
|
717
|
+
background: false,
|
|
718
|
+
launcher: 'foreground',
|
|
719
|
+
packageSpec: packageInfo.spec,
|
|
720
|
+
packageName: packageInfo.name,
|
|
721
|
+
packageVersion: packageInfo.version,
|
|
722
|
+
packageKind: packageInfo.kind,
|
|
723
|
+
packageBin: packageInfo.bin,
|
|
724
|
+
installedDaemonVersion: packageInfo.version || DAEMON_VERSION,
|
|
725
|
+
installedPackageVersion: packageInfo.version || DAEMON_VERSION,
|
|
726
|
+
}, env);
|
|
727
|
+
}
|
|
728
|
+
|
|
692
729
|
async function readUpgradeHandoff(profile = DEFAULT_PROFILE, env = process.env) {
|
|
693
730
|
const paths = profilePaths(profile, env);
|
|
694
731
|
const handoff = await readJsonFile(paths.upgradeHandoff, null);
|
|
@@ -1185,6 +1222,27 @@ export function toWebSocketUrl(serverUrl, config = {}) {
|
|
|
1185
1222
|
if (/^mfp_[a-f0-9]{64}$/i.test(machineFingerprint)) {
|
|
1186
1223
|
url.searchParams.set('machine_fingerprint', machineFingerprint.toLowerCase());
|
|
1187
1224
|
}
|
|
1225
|
+
const packageName = String(config.packageName || '').trim();
|
|
1226
|
+
const packageVersion = String(config.packageVersion || config.daemonVersion || '').trim();
|
|
1227
|
+
const packageKind = String(config.packageKind || '').trim();
|
|
1228
|
+
const packageSpec = String(config.packageSpec || '').trim();
|
|
1229
|
+
const packageBin = String(config.packageBin || '').trim();
|
|
1230
|
+
const cliCoreVersion = String(config.cliCoreVersion || '').trim();
|
|
1231
|
+
const serviceMode = String(config.serviceMode || '').trim();
|
|
1232
|
+
const serviceBackground = Boolean(config.serviceBackground);
|
|
1233
|
+
const serviceActive = Boolean(config.serviceActive);
|
|
1234
|
+
if (packageName) url.searchParams.set('package_name', packageName);
|
|
1235
|
+
if (packageVersion) {
|
|
1236
|
+
url.searchParams.set('package_version', packageVersion);
|
|
1237
|
+
url.searchParams.set('daemon_version', packageVersion);
|
|
1238
|
+
}
|
|
1239
|
+
if (packageKind) url.searchParams.set('package_kind', packageKind);
|
|
1240
|
+
if (packageSpec) url.searchParams.set('package_spec', packageSpec);
|
|
1241
|
+
if (packageBin) url.searchParams.set('package_bin', packageBin);
|
|
1242
|
+
if (cliCoreVersion) url.searchParams.set('cli_core_version', cliCoreVersion);
|
|
1243
|
+
if (serviceMode) url.searchParams.set('service_mode', serviceMode);
|
|
1244
|
+
url.searchParams.set('service_background', String(serviceBackground));
|
|
1245
|
+
url.searchParams.set('service_active', String(serviceActive));
|
|
1188
1246
|
return url;
|
|
1189
1247
|
}
|
|
1190
1248
|
|
|
@@ -3096,6 +3154,12 @@ class ClaudeAgentSession {
|
|
|
3096
3154
|
}
|
|
3097
3155
|
}
|
|
3098
3156
|
|
|
3157
|
+
function readOnlySessionAckStatus(session) {
|
|
3158
|
+
const status = String(session?.status || '').toLowerCase();
|
|
3159
|
+
if ((!status || status === 'offline') && !session?.started) return 'idle';
|
|
3160
|
+
return session?.status || 'idle';
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3099
3163
|
class MagClawDaemon {
|
|
3100
3164
|
constructor(config, env = process.env) {
|
|
3101
3165
|
this.env = env;
|
|
@@ -3452,6 +3516,47 @@ class MagClawDaemon {
|
|
|
3452
3516
|
await this.startUpgradeWorker(message);
|
|
3453
3517
|
}
|
|
3454
3518
|
|
|
3519
|
+
async handleDaemonClose(message) {
|
|
3520
|
+
const commandId = String(message.commandId || '').trim();
|
|
3521
|
+
const service = await readServiceState(this.paths.profile, this.env);
|
|
3522
|
+
const serviceStatus = backgroundServiceStatus(this.paths.profile, this.env);
|
|
3523
|
+
const packageInfo = runtimePackageInfo(this.env, service);
|
|
3524
|
+
const runMode = {
|
|
3525
|
+
mode: service.mode || serviceStatus.mode || 'foreground',
|
|
3526
|
+
background: Boolean(service.background),
|
|
3527
|
+
active: Boolean(serviceStatus.active),
|
|
3528
|
+
label: serviceStatus.label || '',
|
|
3529
|
+
serviceName: serviceStatus.serviceName || '',
|
|
3530
|
+
taskName: serviceStatus.taskName || '',
|
|
3531
|
+
packageName: packageInfo.name,
|
|
3532
|
+
packageVersion: packageInfo.version,
|
|
3533
|
+
packageKind: packageInfo.kind,
|
|
3534
|
+
packageSpec: packageInfo.spec,
|
|
3535
|
+
packageBin: packageInfo.bin,
|
|
3536
|
+
};
|
|
3537
|
+
logWarning('daemon', `Remote close requested (${message.reason || 'closed_from_cloud'}).`);
|
|
3538
|
+
await writeServiceState(this.paths.profile, {
|
|
3539
|
+
remoteClosed: true,
|
|
3540
|
+
remoteClosedAt: now(),
|
|
3541
|
+
remoteCloseReason: message.reason || 'closed_from_cloud',
|
|
3542
|
+
remoteCloseCommandId: commandId,
|
|
3543
|
+
}, this.env);
|
|
3544
|
+
for (const session of this.sessions.values()) session.stop();
|
|
3545
|
+
this.sessions.clear();
|
|
3546
|
+
this.send({ type: 'daemon:close:ack',
|
|
3547
|
+
commandId,
|
|
3548
|
+
status: 'stopping',
|
|
3549
|
+
reason: message.reason || 'closed_from_cloud',
|
|
3550
|
+
service: runMode,
|
|
3551
|
+
at: now(),
|
|
3552
|
+
});
|
|
3553
|
+
const background = stopBackground(this.paths.profile, this.env, { disable: message.disableBackground !== false });
|
|
3554
|
+
logInfo('daemon', `Close request stopped background service mode=${background.mode || 'foreground'} ok=${Boolean(background.ok)}.`);
|
|
3555
|
+
this.close();
|
|
3556
|
+
process.exitCode = 0;
|
|
3557
|
+
setTimeout(() => process.exit(0), 50).unref?.();
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3455
3560
|
async readyPayload() {
|
|
3456
3561
|
const runtimes = await detectRuntimes(this.env);
|
|
3457
3562
|
const owner = await ensureMachineFingerprint(this.paths.profile, this.env);
|
|
@@ -3657,6 +3762,9 @@ class MagClawDaemon {
|
|
|
3657
3762
|
case 'daemon:upgrade':
|
|
3658
3763
|
await this.handleDaemonUpgrade(message);
|
|
3659
3764
|
break;
|
|
3765
|
+
case 'daemon:close':
|
|
3766
|
+
await this.handleDaemonClose(message);
|
|
3767
|
+
break;
|
|
3660
3768
|
case 'agent:start':
|
|
3661
3769
|
await this.handleAgentStart(message);
|
|
3662
3770
|
break;
|
|
@@ -3706,7 +3814,7 @@ class MagClawDaemon {
|
|
|
3706
3814
|
? await session.listSkills()
|
|
3707
3815
|
: { agent: { id: agent.id, name: agent.name || agent.id }, global: [], workspace: [], plugin: [], tools: [] };
|
|
3708
3816
|
this.send({ type: 'agent:skills:list_result', commandId: message.commandId, agentId: agent.id, skills });
|
|
3709
|
-
this.send({ type: 'agent:ack', commandId: message.commandId, agentId: agent.id, status: session
|
|
3817
|
+
this.send({ type: 'agent:ack', commandId: message.commandId, agentId: agent.id, status: readOnlySessionAckStatus(session) });
|
|
3710
3818
|
} catch (error) {
|
|
3711
3819
|
this.send({ type: 'agent:skills:list_result', commandId: message.commandId, agentId: agent.id, skills: { agent: { id: agent.id, name: agent.name || agent.id }, global: [], workspace: [], plugin: [], tools: [], error: error.message } });
|
|
3712
3820
|
this.send({ type: 'agent:error', commandId: message.commandId, agentId: agent.id, error: error.message });
|
|
@@ -3731,7 +3839,7 @@ class MagClawDaemon {
|
|
|
3731
3839
|
const session = await this.sessionForWorkspaceRequest(message);
|
|
3732
3840
|
const tree = await session.listWorkspace(message.path || message.payload?.path || '');
|
|
3733
3841
|
this.send({ type: 'agent:workspace:list_result', commandId: message.commandId, agentId: session.agent.id, tree });
|
|
3734
|
-
this.send({ type: 'agent:ack', commandId: message.commandId, agentId: session.agent.id, status: session
|
|
3842
|
+
this.send({ type: 'agent:ack', commandId: message.commandId, agentId: session.agent.id, status: readOnlySessionAckStatus(session) });
|
|
3735
3843
|
} catch (error) {
|
|
3736
3844
|
this.send({ type: 'agent:error', commandId: message.commandId, agentId: message.agentId || null, error: error.message });
|
|
3737
3845
|
}
|
|
@@ -3742,7 +3850,7 @@ class MagClawDaemon {
|
|
|
3742
3850
|
const session = await this.sessionForWorkspaceRequest(message);
|
|
3743
3851
|
const file = await session.readWorkspaceFile(message.path || message.payload?.path || 'MEMORY.md');
|
|
3744
3852
|
this.send({ type: 'agent:workspace:file_result', commandId: message.commandId, agentId: session.agent.id, file: file.file });
|
|
3745
|
-
this.send({ type: 'agent:ack', commandId: message.commandId, agentId: session.agent.id, status: session
|
|
3853
|
+
this.send({ type: 'agent:ack', commandId: message.commandId, agentId: session.agent.id, status: readOnlySessionAckStatus(session) });
|
|
3746
3854
|
} catch (error) {
|
|
3747
3855
|
this.send({ type: 'agent:error', commandId: message.commandId, agentId: message.agentId || null, error: error.message });
|
|
3748
3856
|
}
|
|
@@ -3992,11 +4100,26 @@ class MagClawDaemon {
|
|
|
3992
4100
|
async connectOnce() {
|
|
3993
4101
|
if (this.closed) return;
|
|
3994
4102
|
await this.refreshConfigFromDisk();
|
|
3995
|
-
const
|
|
4103
|
+
const service = await readServiceState(this.paths.profile, this.env);
|
|
4104
|
+
const serviceStatus = backgroundServiceStatus(this.paths.profile, this.env);
|
|
4105
|
+
const packageInfo = runtimePackageInfo(this.env, service);
|
|
4106
|
+
const url = toWebSocketUrl(this.config.serverUrl, {
|
|
4107
|
+
...this.config,
|
|
4108
|
+
packageName: packageInfo.name,
|
|
4109
|
+
packageVersion: packageInfo.version,
|
|
4110
|
+
packageKind: packageInfo.kind,
|
|
4111
|
+
packageSpec: packageInfo.spec,
|
|
4112
|
+
packageBin: packageInfo.bin,
|
|
4113
|
+
cliCoreVersion: CLI_CORE_VERSION,
|
|
4114
|
+
serviceMode: service.mode || serviceStatus.mode || 'foreground',
|
|
4115
|
+
serviceBackground: Boolean(service.background),
|
|
4116
|
+
serviceActive: Boolean(serviceStatus.active),
|
|
4117
|
+
});
|
|
3996
4118
|
const requestModule = url.protocol === 'wss:' ? https : http;
|
|
3997
4119
|
const requestUrl = new URL(url.href.replace(/^ws/, 'http'));
|
|
3998
4120
|
const key = crypto.randomBytes(16).toString('base64');
|
|
3999
|
-
logInfo('daemon', `Connecting MagClaw daemon profile "${this.paths.profile}" to ${this.config.serverUrl}...`);
|
|
4121
|
+
logInfo('daemon', `Connecting MagClaw daemon v${packageInfo.version || DAEMON_VERSION} profile "${this.paths.profile}" to ${this.config.serverUrl}...`);
|
|
4122
|
+
if (this.closed) return;
|
|
4000
4123
|
return new Promise((resolve, reject) => {
|
|
4001
4124
|
let settled = false;
|
|
4002
4125
|
const finish = (callback, value) => {
|
|
@@ -4176,6 +4299,10 @@ async function writeLauncher(profile, env = process.env) {
|
|
|
4176
4299
|
installedDaemonVersion: packageVersion || DAEMON_VERSION,
|
|
4177
4300
|
installedPackageVersion: packageVersion || DAEMON_VERSION,
|
|
4178
4301
|
commandMode: useNpmLauncher ? 'npm' : 'local',
|
|
4302
|
+
remoteClosed: false,
|
|
4303
|
+
remoteClosedAt: '',
|
|
4304
|
+
remoteCloseReason: '',
|
|
4305
|
+
remoteCloseCommandId: '',
|
|
4179
4306
|
}, env);
|
|
4180
4307
|
const code = [
|
|
4181
4308
|
'#!/usr/bin/env node',
|
|
@@ -4192,6 +4319,11 @@ async function writeLauncher(profile, env = process.env) {
|
|
|
4192
4319
|
`const defaultPackageSpec = ${JSON.stringify(service.packageSpec)};`,
|
|
4193
4320
|
"let service = {};",
|
|
4194
4321
|
"try { service = JSON.parse(fs.readFileSync(serviceFile, 'utf8')); } catch {}",
|
|
4322
|
+
"if (service.remoteClosed) {",
|
|
4323
|
+
" const stamp = new Date().toISOString().replace('T', ' ').slice(0, 19);",
|
|
4324
|
+
" console.error(`${stamp} INFO DAEMON profile ${profile} is closed from MagClaw Cloud; run magclaw start/restart/connect to reconnect.`);",
|
|
4325
|
+
" process.exit(0);",
|
|
4326
|
+
"}",
|
|
4195
4327
|
"const packageSpec = String(service.packageSpec || defaultPackageSpec || '@magclaw/daemon@latest');",
|
|
4196
4328
|
"const packageName = String(service.packageName || (packageSpec.startsWith('@magclaw/computer@') || packageSpec === '@magclaw/computer' ? '@magclaw/computer' : '@magclaw/daemon'));",
|
|
4197
4329
|
"const packageKind = String(service.packageKind || (packageName === '@magclaw/computer' ? 'computer' : 'daemon'));",
|
|
@@ -4285,6 +4417,7 @@ async function startMacBackground(profile, env = process.env) {
|
|
|
4285
4417
|
].join('\n');
|
|
4286
4418
|
await writeFile(plist, xml);
|
|
4287
4419
|
spawnSync('launchctl', ['bootout', `gui/${process.getuid()}`, plist], { stdio: 'ignore' });
|
|
4420
|
+
spawnSync('launchctl', ['enable', `gui/${process.getuid()}/${label}`], { stdio: 'ignore' });
|
|
4288
4421
|
const boot = spawnSync('launchctl', ['bootstrap', `gui/${process.getuid()}`, plist], { encoding: 'utf8' });
|
|
4289
4422
|
if (boot.status !== 0) {
|
|
4290
4423
|
const load = spawnSync('launchctl', ['load', plist], { encoding: 'utf8' });
|
|
@@ -4403,6 +4536,29 @@ function backgroundServiceStatus(profile, env = process.env) {
|
|
|
4403
4536
|
return { mode: 'foreground', active: false };
|
|
4404
4537
|
}
|
|
4405
4538
|
|
|
4539
|
+
function launchctlResultIsNotLoaded(result) {
|
|
4540
|
+
const detail = String(result?.stderr || result?.stdout || '').toLowerCase();
|
|
4541
|
+
return detail.includes('no such process') || detail.includes('could not find service') || detail.includes('not found');
|
|
4542
|
+
}
|
|
4543
|
+
|
|
4544
|
+
function launchctlServiceIsStopped(serviceTarget) {
|
|
4545
|
+
const result = spawnSync('launchctl', ['print', serviceTarget], { encoding: 'utf8' });
|
|
4546
|
+
if (result.status !== 0) return launchctlResultIsNotLoaded(result);
|
|
4547
|
+
const output = String(result.stdout || result.stderr || '');
|
|
4548
|
+
if (/\bstate\s*=\s*running\b/i.test(output)) return false;
|
|
4549
|
+
if (/\bpid\s*=\s*[1-9][0-9]*\b/i.test(output)) return false;
|
|
4550
|
+
return true;
|
|
4551
|
+
}
|
|
4552
|
+
|
|
4553
|
+
function waitForBackgroundServiceStopped(serviceTarget, timeoutMs = 2000) {
|
|
4554
|
+
const deadline = Date.now() + Math.max(250, Number(timeoutMs) || 2000);
|
|
4555
|
+
while (Date.now() < deadline) {
|
|
4556
|
+
if (launchctlServiceIsStopped(serviceTarget)) return true;
|
|
4557
|
+
sleepSync(100);
|
|
4558
|
+
}
|
|
4559
|
+
return launchctlServiceIsStopped(serviceTarget);
|
|
4560
|
+
}
|
|
4561
|
+
|
|
4406
4562
|
async function waitForPidExit(pid, timeoutMs = 2000) {
|
|
4407
4563
|
const deadline = Date.now() + timeoutMs;
|
|
4408
4564
|
while (Date.now() < deadline) {
|
|
@@ -4451,32 +4607,58 @@ async function stopActiveDaemon(profile, env = process.env) {
|
|
|
4451
4607
|
return { ok: stopped, running: !stopped, pid, signal };
|
|
4452
4608
|
}
|
|
4453
4609
|
|
|
4454
|
-
function stopBackground(profile, env = process.env) {
|
|
4610
|
+
function stopBackground(profile, env = process.env, options = {}) {
|
|
4455
4611
|
if (process.platform === 'darwin') {
|
|
4456
4612
|
const paths = profilePaths(profile, env);
|
|
4457
4613
|
const label = launchAgentLabel(paths.profile);
|
|
4614
|
+
const serviceTarget = `gui/${process.getuid()}/${label}`;
|
|
4458
4615
|
const plist = path.join(os.homedir(), 'Library', 'LaunchAgents', `${label}.plist`);
|
|
4459
|
-
|
|
4460
|
-
|
|
4616
|
+
if (options.disable) {
|
|
4617
|
+
const disabled = spawnSync('launchctl', ['disable', serviceTarget], { encoding: 'utf8' });
|
|
4618
|
+
const stopped = spawnSync('launchctl', ['stop', label], { encoding: 'utf8' });
|
|
4619
|
+
const stoppedConfirmed = stopped.status === 0 && waitForBackgroundServiceStopped(serviceTarget);
|
|
4620
|
+
const needsBootout = disabled.status !== 0 || !stoppedConfirmed;
|
|
4621
|
+
const bootout = needsBootout
|
|
4622
|
+
? spawnSync('launchctl', ['bootout', serviceTarget], { encoding: 'utf8' })
|
|
4623
|
+
: null;
|
|
4624
|
+
const bootoutOk = bootout ? (bootout.status === 0 || launchctlResultIsNotLoaded(bootout)) : false;
|
|
4625
|
+
if (stopped.status !== 0) spawnSync('launchctl', ['bootout', `gui/${process.getuid()}`, plist], { stdio: 'ignore' });
|
|
4626
|
+
return {
|
|
4627
|
+
ok: disabled.status === 0 && (stoppedConfirmed || bootoutOk),
|
|
4628
|
+
mode: 'launchd',
|
|
4629
|
+
label,
|
|
4630
|
+
serviceTarget,
|
|
4631
|
+
file: plist,
|
|
4632
|
+
disabled: disabled.status === 0,
|
|
4633
|
+
stopped: stoppedConfirmed || bootoutOk,
|
|
4634
|
+
bootout: bootout ? bootout.status === 0 : false,
|
|
4635
|
+
error: disabled.status === 0 && (stoppedConfirmed || bootoutOk) ? '' : String(bootout?.stderr || stopped.stderr || disabled.stderr || bootout?.stdout || stopped.stdout || disabled.stdout || '').trim(),
|
|
4636
|
+
};
|
|
4637
|
+
}
|
|
4638
|
+
const bootout = spawnSync('launchctl', ['bootout', serviceTarget], { encoding: 'utf8' });
|
|
4639
|
+
return { ok: bootout.status === 0 || launchctlResultIsNotLoaded(bootout), mode: 'launchd', label, serviceTarget, file: plist, error: bootout.stderr || bootout.stdout || '' };
|
|
4461
4640
|
}
|
|
4462
4641
|
if (process.platform === 'linux') {
|
|
4463
4642
|
const paths = profilePaths(profile, env);
|
|
4464
4643
|
const serviceName = systemdServiceName(paths.profile);
|
|
4465
|
-
const result =
|
|
4644
|
+
const result = options.disable
|
|
4645
|
+
? spawnSync('systemctl', ['--user', 'disable', '--now', serviceName], { encoding: 'utf8' })
|
|
4646
|
+
: spawnSync('systemctl', ['--user', 'stop', serviceName], { encoding: 'utf8' });
|
|
4466
4647
|
return { ok: result.status === 0, mode: 'systemd', serviceName, error: result.stderr || '' };
|
|
4467
4648
|
}
|
|
4468
4649
|
if (process.platform === 'win32') {
|
|
4469
4650
|
const paths = profilePaths(profile, env);
|
|
4470
4651
|
const taskName = windowsTaskName(paths.profile);
|
|
4471
4652
|
spawnSync('schtasks.exe', ['/End', '/TN', taskName], { stdio: 'ignore' });
|
|
4653
|
+
if (!options.disable) return { ok: true, mode: 'schtasks', taskName };
|
|
4472
4654
|
const result = spawnSync('schtasks.exe', ['/Change', '/TN', taskName, '/DISABLE'], { encoding: 'utf8' });
|
|
4473
4655
|
return { ok: result.status === 0, mode: 'schtasks', taskName, error: result.stderr || result.stdout || '' };
|
|
4474
4656
|
}
|
|
4475
4657
|
return { ok: false, mode: 'foreground' };
|
|
4476
4658
|
}
|
|
4477
4659
|
|
|
4478
|
-
async function stopDaemon(profile, env = process.env) {
|
|
4479
|
-
const background = stopBackground(profile, env);
|
|
4660
|
+
async function stopDaemon(profile, env = process.env, options = {}) {
|
|
4661
|
+
const background = stopBackground(profile, env, options);
|
|
4480
4662
|
const processResult = await stopActiveDaemon(profile, env);
|
|
4481
4663
|
const backgroundRequired = background.mode !== 'foreground';
|
|
4482
4664
|
return {
|
|
@@ -5081,6 +5263,7 @@ async function runComputerSetup(flags, env = process.env) {
|
|
|
5081
5263
|
fingerprint: owner.fingerprint,
|
|
5082
5264
|
}, env);
|
|
5083
5265
|
await saveProfile(config.profile, config, env);
|
|
5266
|
+
await clearRemoteClosedServiceState(config.profile, env);
|
|
5084
5267
|
const cli = await tryInstallCliShim(flags, env);
|
|
5085
5268
|
const result = await startBackground(config.profile, env);
|
|
5086
5269
|
printJson({
|
|
@@ -5119,6 +5302,7 @@ async function buildConfig(flags, env = process.env) {
|
|
|
5119
5302
|
|
|
5120
5303
|
async function runForegroundDaemon(config, env = process.env) {
|
|
5121
5304
|
const releaseLock = await acquireDaemonLock(config.profile, config, env);
|
|
5305
|
+
await markForegroundServiceState(config.profile, env);
|
|
5122
5306
|
const daemon = new MagClawDaemon(config, env);
|
|
5123
5307
|
let forceExitTimer = null;
|
|
5124
5308
|
const shutdown = (signal) => {
|
|
@@ -5145,6 +5329,7 @@ async function runConnect(flags, env = process.env) {
|
|
|
5145
5329
|
throw new Error('Run connect with --api-key, --pair-token for legacy pairing, or use a saved profile with a machine token.');
|
|
5146
5330
|
}
|
|
5147
5331
|
await saveProfile(config.profile, config, env);
|
|
5332
|
+
await clearRemoteClosedServiceState(config.profile, env);
|
|
5148
5333
|
const cli = await tryInstallCliShim(flags, env);
|
|
5149
5334
|
if (flags.background) {
|
|
5150
5335
|
const result = await startBackground(config.profile, env);
|
|
@@ -5211,7 +5396,7 @@ export async function main(argv = process.argv, env = process.env) {
|
|
|
5211
5396
|
}
|
|
5212
5397
|
case 'stop':
|
|
5213
5398
|
requireExplicitProfile('stop', flags);
|
|
5214
|
-
printJson(await stopDaemon(flags.profile, env));
|
|
5399
|
+
printJson(await stopDaemon(flags.profile, env, { disable: Boolean(flags.disable) }));
|
|
5215
5400
|
break;
|
|
5216
5401
|
case 'restart':
|
|
5217
5402
|
printJson(await restartSavedBackground(flags, env));
|