@magclaw/cli-core 0.1.25 → 0.1.27
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 +113 -34
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -785,17 +785,66 @@ export function selectRuntimeCommandPath(output, fallback = '', platform = proce
|
|
|
785
785
|
.sort((left, right) => left.score - right.score || left.index - right.index)[0]?.file || paths[0];
|
|
786
786
|
}
|
|
787
787
|
|
|
788
|
+
function homeDirForEnv(env = process.env) {
|
|
789
|
+
return String(env.HOME || env.USERPROFILE || os.homedir() || '').trim();
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function uniquePathEntries(entries = []) {
|
|
793
|
+
const seen = new Set();
|
|
794
|
+
const result = [];
|
|
795
|
+
for (const entry of entries) {
|
|
796
|
+
const value = String(entry || '').trim();
|
|
797
|
+
if (!value) continue;
|
|
798
|
+
const key = durablePathKey(value);
|
|
799
|
+
if (seen.has(key)) continue;
|
|
800
|
+
seen.add(key);
|
|
801
|
+
result.push(value);
|
|
802
|
+
}
|
|
803
|
+
return result;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
export function runtimeSearchPathEntries(env = process.env) {
|
|
807
|
+
const home = homeDirForEnv(env);
|
|
808
|
+
const userEntries = home ? [
|
|
809
|
+
path.join(home, '.local', 'bin'),
|
|
810
|
+
path.join(home, 'bin'),
|
|
811
|
+
path.join(home, '.npm-global', 'bin'),
|
|
812
|
+
path.join(home, '.volta', 'bin'),
|
|
813
|
+
path.join(home, '.bun', 'bin'),
|
|
814
|
+
process.platform === 'win32' ? path.join(home, 'AppData', 'Roaming', 'npm') : '',
|
|
815
|
+
] : [];
|
|
816
|
+
const platformEntries = process.platform === 'darwin'
|
|
817
|
+
? ['/opt/homebrew/bin', '/opt/homebrew/sbin', '/usr/local/bin', '/usr/local/sbin']
|
|
818
|
+
: process.platform === 'win32'
|
|
819
|
+
? []
|
|
820
|
+
: ['/usr/local/bin', '/usr/local/sbin'];
|
|
821
|
+
return uniquePathEntries([
|
|
822
|
+
...pathDirs(env),
|
|
823
|
+
env.NVM_BIN,
|
|
824
|
+
env.VOLTA_HOME ? path.join(env.VOLTA_HOME, 'bin') : '',
|
|
825
|
+
env.BUN_INSTALL ? path.join(env.BUN_INSTALL, 'bin') : '',
|
|
826
|
+
path.dirname(process.execPath),
|
|
827
|
+
...userEntries,
|
|
828
|
+
...platformEntries,
|
|
829
|
+
]);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function runtimeDetectionEnv(env = process.env) {
|
|
833
|
+
return {
|
|
834
|
+
...env,
|
|
835
|
+
PATH: runtimeSearchPathEntries(env).join(path.delimiter),
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
788
839
|
function commandExists(command, env = process.env) {
|
|
840
|
+
const runtimeEnv = runtimeDetectionEnv(env);
|
|
789
841
|
const checker = process.platform === 'win32' ? 'where' : 'command';
|
|
790
842
|
const args = process.platform === 'win32' ? [command] : ['-v', command];
|
|
791
843
|
const result = process.platform === 'win32'
|
|
792
|
-
? commandOutput(checker, args, { env, timeoutMs: 1500 })
|
|
793
|
-
: commandOutput('/bin/sh', ['-lc', `command -v ${JSON.stringify(command)}`], { env, timeoutMs: 1500 });
|
|
844
|
+
? commandOutput(checker, args, { env: runtimeEnv, timeoutMs: 1500 })
|
|
845
|
+
: commandOutput('/bin/sh', ['-lc', `command -v ${JSON.stringify(command)}`], { env: runtimeEnv, timeoutMs: 1500 });
|
|
794
846
|
if (result.ok) return selectRuntimeCommandPath(result.stdout, command);
|
|
795
|
-
for (const candidate of
|
|
796
|
-
path.join(path.dirname(process.execPath), command),
|
|
797
|
-
path.join(os.homedir(), '.local', 'bin', command),
|
|
798
|
-
]) {
|
|
847
|
+
for (const candidate of runtimeSearchPathEntries(runtimeEnv).map((dir) => path.join(dir, command))) {
|
|
799
848
|
if (existsSync(candidate)) return candidate;
|
|
800
849
|
}
|
|
801
850
|
return '';
|
|
@@ -1046,13 +1095,15 @@ async function tryInstallCliShim(options = {}, env = process.env) {
|
|
|
1046
1095
|
}
|
|
1047
1096
|
|
|
1048
1097
|
function runtimeVersion(command, env = process.env) {
|
|
1049
|
-
const
|
|
1098
|
+
const runtimeEnv = runtimeDetectionEnv(env);
|
|
1099
|
+
const result = commandOutput(command, ['--version'], { env: runtimeEnv, timeoutMs: 3000 });
|
|
1050
1100
|
if (!result.ok && result.error?.code === 'ENOENT') return '';
|
|
1051
1101
|
return result.stdout || result.stderr || '';
|
|
1052
1102
|
}
|
|
1053
1103
|
|
|
1054
1104
|
function codexAppServerCapable(command, env = process.env) {
|
|
1055
|
-
const
|
|
1105
|
+
const runtimeEnv = runtimeDetectionEnv(env);
|
|
1106
|
+
const result = commandOutput(command, ['app-server', '--help'], { env: runtimeEnv, timeoutMs: 3000 });
|
|
1056
1107
|
if (result.error?.code === 'ENOENT') return false;
|
|
1057
1108
|
if (result.ok) return true;
|
|
1058
1109
|
const output = `${result.stdout}\n${result.stderr}`.toLowerCase();
|
|
@@ -1060,6 +1111,7 @@ function codexAppServerCapable(command, env = process.env) {
|
|
|
1060
1111
|
}
|
|
1061
1112
|
|
|
1062
1113
|
function defaultCodexCommand(env = process.env) {
|
|
1114
|
+
const runtimeEnv = runtimeDetectionEnv(env);
|
|
1063
1115
|
const macAppBinary = '/Applications/Codex.app/Contents/Resources/codex';
|
|
1064
1116
|
const candidates = [env.CODEX_PATH, env.MAGCLAW_CODEX_PATH, macAppBinary, 'codex']
|
|
1065
1117
|
.map((item) => String(item || '').trim())
|
|
@@ -1067,9 +1119,9 @@ function defaultCodexCommand(env = process.env) {
|
|
|
1067
1119
|
for (const candidate of [...new Set(candidates)]) {
|
|
1068
1120
|
const command = runtimeCommandHasPathSeparator(candidate)
|
|
1069
1121
|
? candidate
|
|
1070
|
-
: commandExists(candidate,
|
|
1122
|
+
: commandExists(candidate, runtimeEnv) || candidate;
|
|
1071
1123
|
if (runtimeCommandHasPathSeparator(command) && !existsSync(command)) continue;
|
|
1072
|
-
const result = commandOutput(command, ['--version'], { env, timeoutMs: 3000 });
|
|
1124
|
+
const result = commandOutput(command, ['--version'], { env: runtimeEnv, timeoutMs: 3000 });
|
|
1073
1125
|
if (result.ok) return command;
|
|
1074
1126
|
}
|
|
1075
1127
|
return candidates[0] || 'codex';
|
|
@@ -1088,7 +1140,7 @@ function codexRuntimeModels(command, env = process.env) {
|
|
|
1088
1140
|
reasoningEffort: ['low', 'medium', 'high', 'xhigh'],
|
|
1089
1141
|
defaultReasoningEffort: 'medium',
|
|
1090
1142
|
};
|
|
1091
|
-
const result = commandOutput(command, ['debug', 'models'], { env, timeoutMs: 5000 });
|
|
1143
|
+
const result = commandOutput(command, ['debug', 'models'], { env: runtimeDetectionEnv(env), timeoutMs: 5000 });
|
|
1092
1144
|
if (!result.ok) return fallback;
|
|
1093
1145
|
try {
|
|
1094
1146
|
const data = JSON.parse(result.stdout || '{}');
|
|
@@ -1121,14 +1173,15 @@ function codexRuntimeModels(command, env = process.env) {
|
|
|
1121
1173
|
}
|
|
1122
1174
|
|
|
1123
1175
|
export async function detectRuntimes(env = process.env) {
|
|
1124
|
-
const
|
|
1176
|
+
const runtimeEnv = runtimeDetectionEnv(env);
|
|
1177
|
+
const codexCommand = defaultCodexCommand(runtimeEnv);
|
|
1125
1178
|
const candidates = [
|
|
1126
1179
|
{
|
|
1127
1180
|
id: 'codex',
|
|
1128
1181
|
name: 'Codex CLI',
|
|
1129
1182
|
command: codexCommand,
|
|
1130
1183
|
createSupported: true,
|
|
1131
|
-
modelsFor: (command) => codexRuntimeModels(command,
|
|
1184
|
+
modelsFor: (command) => codexRuntimeModels(command, runtimeEnv),
|
|
1132
1185
|
},
|
|
1133
1186
|
{
|
|
1134
1187
|
id: 'claude-code',
|
|
@@ -1180,10 +1233,10 @@ export async function detectRuntimes(env = process.env) {
|
|
|
1180
1233
|
},
|
|
1181
1234
|
];
|
|
1182
1235
|
return candidates.map((item) => {
|
|
1183
|
-
const pathValue = runtimeCommandHasPathSeparator(item.command) ? (existsSync(item.command) ? item.command : '') : commandExists(item.command,
|
|
1236
|
+
const pathValue = runtimeCommandHasPathSeparator(item.command) ? (existsSync(item.command) ? item.command : '') : commandExists(item.command, runtimeEnv);
|
|
1184
1237
|
const runtimeCommand = pathValue || item.command;
|
|
1185
1238
|
const installed = Boolean(pathValue);
|
|
1186
|
-
const version = installed ? runtimeVersion(runtimeCommand,
|
|
1239
|
+
const version = installed ? runtimeVersion(runtimeCommand, runtimeEnv) : '';
|
|
1187
1240
|
const modelInfo = installed && item.modelsFor ? item.modelsFor(runtimeCommand) : {
|
|
1188
1241
|
models: item.models || [],
|
|
1189
1242
|
modelNames: (item.models || []).map((model) => ({ slug: model, name: model })),
|
|
@@ -1199,7 +1252,7 @@ export async function detectRuntimes(env = process.env) {
|
|
|
1199
1252
|
installed,
|
|
1200
1253
|
version,
|
|
1201
1254
|
createSupported: item.createSupported !== false,
|
|
1202
|
-
appServer: item.id === 'codex' && installed ? codexAppServerCapable(runtimeCommand,
|
|
1255
|
+
appServer: item.id === 'codex' && installed ? codexAppServerCapable(runtimeCommand, runtimeEnv) : false,
|
|
1203
1256
|
...modelInfo,
|
|
1204
1257
|
};
|
|
1205
1258
|
});
|
|
@@ -3520,14 +3573,15 @@ class MagClawDaemon {
|
|
|
3520
3573
|
const commandId = String(message.commandId || '').trim();
|
|
3521
3574
|
const service = await readServiceState(this.paths.profile, this.env);
|
|
3522
3575
|
const serviceStatus = backgroundServiceStatus(this.paths.profile, this.env);
|
|
3576
|
+
const serviceRunMode = daemonServiceRunMode(service, serviceStatus);
|
|
3523
3577
|
const packageInfo = runtimePackageInfo(this.env, service);
|
|
3524
3578
|
const runMode = {
|
|
3525
|
-
mode:
|
|
3526
|
-
background:
|
|
3527
|
-
active:
|
|
3528
|
-
label: serviceStatus.label || '',
|
|
3529
|
-
serviceName: serviceStatus.serviceName || '',
|
|
3530
|
-
taskName: serviceStatus.taskName || '',
|
|
3579
|
+
mode: serviceRunMode.mode,
|
|
3580
|
+
background: serviceRunMode.background,
|
|
3581
|
+
active: serviceRunMode.active,
|
|
3582
|
+
label: serviceRunMode.background ? serviceStatus.label || '' : '',
|
|
3583
|
+
serviceName: serviceRunMode.background ? serviceStatus.serviceName || '' : '',
|
|
3584
|
+
taskName: serviceRunMode.background ? serviceStatus.taskName || '' : '',
|
|
3531
3585
|
packageName: packageInfo.name,
|
|
3532
3586
|
packageVersion: packageInfo.version,
|
|
3533
3587
|
packageKind: packageInfo.kind,
|
|
@@ -3550,8 +3604,15 @@ class MagClawDaemon {
|
|
|
3550
3604
|
service: runMode,
|
|
3551
3605
|
at: now(),
|
|
3552
3606
|
});
|
|
3553
|
-
const
|
|
3554
|
-
|
|
3607
|
+
const shouldStopBackground = Boolean(runMode.background);
|
|
3608
|
+
const background = shouldStopBackground
|
|
3609
|
+
? stopBackground(this.paths.profile, this.env, { disable: message.disableBackground !== false })
|
|
3610
|
+
: { ok: true, mode: runMode.mode || 'foreground', skipped: true };
|
|
3611
|
+
if (shouldStopBackground) {
|
|
3612
|
+
logInfo('daemon', `Close request stopped background service mode=${background.mode || 'foreground'} ok=${Boolean(background.ok)}.`);
|
|
3613
|
+
} else {
|
|
3614
|
+
logInfo('daemon', 'Foreground close request did not stop background service.');
|
|
3615
|
+
}
|
|
3555
3616
|
this.close();
|
|
3556
3617
|
process.exitCode = 0;
|
|
3557
3618
|
setTimeout(() => process.exit(0), 50).unref?.();
|
|
@@ -3562,6 +3623,7 @@ class MagClawDaemon {
|
|
|
3562
3623
|
const owner = await ensureMachineFingerprint(this.paths.profile, this.env);
|
|
3563
3624
|
const service = await readServiceState(this.paths.profile, this.env);
|
|
3564
3625
|
const serviceStatus = backgroundServiceStatus(this.paths.profile, this.env);
|
|
3626
|
+
const serviceRunMode = daemonServiceRunMode(service, serviceStatus);
|
|
3565
3627
|
const upgrade = await readUpgradeHandoff(this.paths.profile, this.env);
|
|
3566
3628
|
const packageInfo = runtimePackageInfo(this.env, service);
|
|
3567
3629
|
return {
|
|
@@ -3581,12 +3643,12 @@ class MagClawDaemon {
|
|
|
3581
3643
|
packageBin: packageInfo.bin,
|
|
3582
3644
|
cliCoreVersion: CLI_CORE_VERSION,
|
|
3583
3645
|
service: {
|
|
3584
|
-
mode:
|
|
3585
|
-
background:
|
|
3586
|
-
active:
|
|
3587
|
-
label: serviceStatus.label || '',
|
|
3588
|
-
serviceName: serviceStatus.serviceName || '',
|
|
3589
|
-
taskName: serviceStatus.taskName || '',
|
|
3646
|
+
mode: serviceRunMode.mode,
|
|
3647
|
+
background: serviceRunMode.background,
|
|
3648
|
+
active: serviceRunMode.active,
|
|
3649
|
+
label: serviceRunMode.background ? serviceStatus.label || '' : '',
|
|
3650
|
+
serviceName: serviceRunMode.background ? serviceStatus.serviceName || '' : '',
|
|
3651
|
+
taskName: serviceRunMode.background ? serviceStatus.taskName || '' : '',
|
|
3590
3652
|
launcher: service.launcher || '',
|
|
3591
3653
|
packageSpec: service.packageSpec || packageInfo.spec || '',
|
|
3592
3654
|
packageName: service.packageName || packageInfo.name,
|
|
@@ -4102,6 +4164,7 @@ class MagClawDaemon {
|
|
|
4102
4164
|
await this.refreshConfigFromDisk();
|
|
4103
4165
|
const service = await readServiceState(this.paths.profile, this.env);
|
|
4104
4166
|
const serviceStatus = backgroundServiceStatus(this.paths.profile, this.env);
|
|
4167
|
+
const serviceRunMode = daemonServiceRunMode(service, serviceStatus);
|
|
4105
4168
|
const packageInfo = runtimePackageInfo(this.env, service);
|
|
4106
4169
|
const url = toWebSocketUrl(this.config.serverUrl, {
|
|
4107
4170
|
...this.config,
|
|
@@ -4111,9 +4174,9 @@ class MagClawDaemon {
|
|
|
4111
4174
|
packageSpec: packageInfo.spec,
|
|
4112
4175
|
packageBin: packageInfo.bin,
|
|
4113
4176
|
cliCoreVersion: CLI_CORE_VERSION,
|
|
4114
|
-
serviceMode:
|
|
4115
|
-
serviceBackground:
|
|
4116
|
-
serviceActive:
|
|
4177
|
+
serviceMode: serviceRunMode.mode,
|
|
4178
|
+
serviceBackground: serviceRunMode.background,
|
|
4179
|
+
serviceActive: serviceRunMode.active,
|
|
4117
4180
|
});
|
|
4118
4181
|
const requestModule = url.protocol === 'wss:' ? https : http;
|
|
4119
4182
|
const requestUrl = new URL(url.href.replace(/^ws/, 'http'));
|
|
@@ -4261,6 +4324,10 @@ async function writeLauncher(profile, env = process.env) {
|
|
|
4261
4324
|
const npmPath = commandExists('npm', env);
|
|
4262
4325
|
const nodeDir = path.dirname(process.execPath);
|
|
4263
4326
|
const npmDir = npmPath ? path.dirname(npmPath) : '';
|
|
4327
|
+
const launchPathEntries = runtimeSearchPathEntries({
|
|
4328
|
+
...env,
|
|
4329
|
+
PATH: [nodeDir, npmDir, env.PATH || '/usr/bin:/bin:/usr/sbin:/sbin'].filter(Boolean).join(path.delimiter),
|
|
4330
|
+
});
|
|
4264
4331
|
const commandMode = String(env.MAGCLAW_DAEMON_COMMAND_MODE || '').trim().toLowerCase();
|
|
4265
4332
|
const useNpmLauncher = Boolean(npmPath) && !['local', 'local-repo', 'repo', 'source'].includes(commandMode);
|
|
4266
4333
|
const launcher = path.join(paths.runDir, 'launcher.js');
|
|
@@ -4312,6 +4379,8 @@ async function writeLauncher(profile, env = process.env) {
|
|
|
4312
4379
|
`const useNpmLauncher = ${JSON.stringify(useNpmLauncher)};`,
|
|
4313
4380
|
`const nodeDir = ${JSON.stringify(nodeDir)};`,
|
|
4314
4381
|
`const npmDir = ${JSON.stringify(npmDir)};`,
|
|
4382
|
+
`const pathDelimiter = ${JSON.stringify(path.delimiter)};`,
|
|
4383
|
+
`const launchPathEntries = ${JSON.stringify(launchPathEntries)};`,
|
|
4315
4384
|
`const fallbackBin = ${JSON.stringify(fallbackBin)};`,
|
|
4316
4385
|
`const profile = ${JSON.stringify(paths.profile)};`,
|
|
4317
4386
|
`const daemonHome = ${JSON.stringify(daemonRoot(env))};`,
|
|
@@ -4334,7 +4403,7 @@ async function writeLauncher(profile, env = process.env) {
|
|
|
4334
4403
|
"const args = useNpmLauncher",
|
|
4335
4404
|
" ? ['exec', '--yes', '--package', packageSpec, '--', packageBin, 'connect', '--profile', profile]",
|
|
4336
4405
|
" : [fallbackBin, 'connect', '--profile', profile];",
|
|
4337
|
-
"const launchPath = [nodeDir, npmDir, process.env.PATH || '/usr/bin:/bin:/usr/sbin:/sbin'].filter(Boolean).join(
|
|
4406
|
+
"const launchPath = [...launchPathEntries, nodeDir, npmDir, process.env.PATH || '/usr/bin:/bin:/usr/sbin:/sbin'].filter(Boolean).join(pathDelimiter);",
|
|
4338
4407
|
"const childEnv = {",
|
|
4339
4408
|
" ...process.env,",
|
|
4340
4409
|
" MAGCLAW_DAEMON_HOME: daemonHome,",
|
|
@@ -4536,6 +4605,15 @@ function backgroundServiceStatus(profile, env = process.env) {
|
|
|
4536
4605
|
return { mode: 'foreground', active: false };
|
|
4537
4606
|
}
|
|
4538
4607
|
|
|
4608
|
+
function daemonServiceRunMode(service = {}, serviceStatus = {}) {
|
|
4609
|
+
const background = service.background === true;
|
|
4610
|
+
return {
|
|
4611
|
+
mode: background ? (service.mode || serviceStatus.mode || 'foreground') : 'foreground',
|
|
4612
|
+
background,
|
|
4613
|
+
active: background ? Boolean(serviceStatus.active) : false,
|
|
4614
|
+
};
|
|
4615
|
+
}
|
|
4616
|
+
|
|
4539
4617
|
function launchctlResultIsNotLoaded(result) {
|
|
4540
4618
|
const detail = String(result?.stderr || result?.stdout || '').toLowerCase();
|
|
4541
4619
|
return detail.includes('no such process') || detail.includes('could not find service') || detail.includes('not found');
|
|
@@ -5270,6 +5348,7 @@ async function runComputerSetup(flags, env = process.env) {
|
|
|
5270
5348
|
...result,
|
|
5271
5349
|
cli,
|
|
5272
5350
|
computerId: config.computerId,
|
|
5351
|
+
computerName: config.computerName || config.name || displayName,
|
|
5273
5352
|
profile: config.profile,
|
|
5274
5353
|
serverName: config.serverName,
|
|
5275
5354
|
serverSlug: approved.serverSlug || serverSlug,
|