@magclaw/cli-core 0.1.29 → 0.1.31
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 +207 -38
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -588,7 +588,7 @@ function renderHelp() {
|
|
|
588
588
|
' status Show daemon status for one profile',
|
|
589
589
|
' list List local daemon profiles and connected Computers',
|
|
590
590
|
' logs Print recent daemon logs for one profile',
|
|
591
|
-
' install-cli Install or repair
|
|
591
|
+
' install-cli Install or repair durable magclaw command shims',
|
|
592
592
|
' upgrade Upgrade the background daemon package',
|
|
593
593
|
' doctor Show runtime and environment diagnostics',
|
|
594
594
|
' uninstall Stop and remove the background daemon service',
|
|
@@ -1110,64 +1110,88 @@ function defaultCliPackageSpec(env = process.env) {
|
|
|
1110
1110
|
return String(env.MAGCLAW_CLI_PACKAGE_SPEC || '@magclaw/cli-core@latest').trim() || '@magclaw/cli-core@latest';
|
|
1111
1111
|
}
|
|
1112
1112
|
|
|
1113
|
+
function defaultComputerCliPackageSpec(env = process.env) {
|
|
1114
|
+
return String(env.MAGCLAW_COMPUTER_CLI_PACKAGE_SPEC || '@magclaw/computer@latest').trim() || '@magclaw/computer@latest';
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1113
1117
|
function defaultCliNpmPath(env = process.env) {
|
|
1114
1118
|
return commandExists('npm', env) || (process.platform === 'win32' ? 'npm.cmd' : 'npm');
|
|
1115
1119
|
}
|
|
1116
1120
|
|
|
1117
|
-
|
|
1121
|
+
function cliShimTargets({ packageSpec = '@magclaw/cli-core@latest', computerPackageSpec = '@magclaw/computer@latest' } = {}) {
|
|
1122
|
+
return [
|
|
1123
|
+
{
|
|
1124
|
+
command: 'magclaw',
|
|
1125
|
+
packageSpec: String(packageSpec || '@magclaw/cli-core@latest').trim() || '@magclaw/cli-core@latest',
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
command: 'magclaw-computer',
|
|
1129
|
+
packageSpec: String(computerPackageSpec || '@magclaw/computer@latest').trim() || '@magclaw/computer@latest',
|
|
1130
|
+
},
|
|
1131
|
+
];
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
export function renderCliShimFiles({
|
|
1135
|
+
platform = process.platform,
|
|
1136
|
+
npmPath = '',
|
|
1137
|
+
packageSpec = '@magclaw/cli-core@latest',
|
|
1138
|
+
computerPackageSpec = '@magclaw/computer@latest',
|
|
1139
|
+
} = {}) {
|
|
1118
1140
|
const targetPackage = String(packageSpec || '@magclaw/cli-core@latest').trim() || '@magclaw/cli-core@latest';
|
|
1141
|
+
const targetComputerPackage = String(computerPackageSpec || '@magclaw/computer@latest').trim() || '@magclaw/computer@latest';
|
|
1119
1142
|
const targetNpm = String(npmPath || (platform === 'win32' ? 'npm.cmd' : 'npm')).trim() || (platform === 'win32' ? 'npm.cmd' : 'npm');
|
|
1143
|
+
const targets = cliShimTargets({ packageSpec: targetPackage, computerPackageSpec: targetComputerPackage });
|
|
1120
1144
|
if (platform === 'win32') {
|
|
1121
1145
|
const fallback = path.win32.basename(targetNpm) || 'npm.cmd';
|
|
1122
|
-
return [
|
|
1146
|
+
return targets.flatMap((target) => [
|
|
1123
1147
|
{
|
|
1124
|
-
name:
|
|
1148
|
+
name: `${target.command}.cmd`,
|
|
1125
1149
|
executable: true,
|
|
1126
1150
|
content: [
|
|
1127
1151
|
'@echo off',
|
|
1128
1152
|
'setlocal',
|
|
1129
1153
|
`set "NPM_BIN=${cmdEnvValue(targetNpm)}"`,
|
|
1130
1154
|
`if not exist "%NPM_BIN%" set "NPM_BIN=${cmdEnvValue(fallback)}"`,
|
|
1131
|
-
`set "PACKAGE_SPEC=${cmdEnvValue(
|
|
1155
|
+
`set "PACKAGE_SPEC=${cmdEnvValue(target.packageSpec)}"`,
|
|
1132
1156
|
'set "ARGS=%*"',
|
|
1133
|
-
|
|
1157
|
+
`"%NPM_BIN%" exec --yes --package "%PACKAGE_SPEC%" -- ${target.command} %ARGS%`,
|
|
1134
1158
|
'exit /b %ERRORLEVEL%',
|
|
1135
1159
|
'',
|
|
1136
1160
|
].join('\r\n'),
|
|
1137
1161
|
},
|
|
1138
1162
|
{
|
|
1139
|
-
name:
|
|
1163
|
+
name: `${target.command}.ps1`,
|
|
1140
1164
|
executable: true,
|
|
1141
1165
|
content: [
|
|
1142
1166
|
`$npmBin = ${psSingleQuote(targetNpm)}`,
|
|
1143
1167
|
`if (-not (Test-Path -LiteralPath $npmBin)) { $npmBin = ${psSingleQuote(fallback)} }`,
|
|
1144
|
-
`$packageSpec = ${psSingleQuote(
|
|
1145
|
-
|
|
1168
|
+
`$packageSpec = ${psSingleQuote(target.packageSpec)}`,
|
|
1169
|
+
`& $npmBin exec --yes --package $packageSpec -- ${target.command} @args`,
|
|
1146
1170
|
'exit $LASTEXITCODE',
|
|
1147
1171
|
'',
|
|
1148
1172
|
].join('\n'),
|
|
1149
1173
|
},
|
|
1150
|
-
];
|
|
1174
|
+
]);
|
|
1151
1175
|
}
|
|
1152
1176
|
const fallback = path.basename(targetNpm) || 'npm';
|
|
1153
|
-
return
|
|
1177
|
+
return targets.map((target) => (
|
|
1154
1178
|
{
|
|
1155
|
-
name:
|
|
1179
|
+
name: target.command,
|
|
1156
1180
|
executable: true,
|
|
1157
1181
|
content: [
|
|
1158
1182
|
'#!/bin/sh',
|
|
1159
1183
|
'set -eu',
|
|
1160
1184
|
'# MagClaw CLI shim generated by @magclaw/cli-core.',
|
|
1161
1185
|
`NPM_BIN=${shSingleQuote(targetNpm)}`,
|
|
1162
|
-
`PACKAGE_SPEC=${shSingleQuote(
|
|
1186
|
+
`PACKAGE_SPEC=${shSingleQuote(target.packageSpec)}`,
|
|
1163
1187
|
'if [ ! -x "$NPM_BIN" ]; then',
|
|
1164
1188
|
` NPM_BIN=${shSingleQuote(fallback)}`,
|
|
1165
1189
|
'fi',
|
|
1166
|
-
|
|
1190
|
+
`exec "$NPM_BIN" exec --yes --package "$PACKAGE_SPEC" -- ${target.command} "$@"`,
|
|
1167
1191
|
'',
|
|
1168
1192
|
].join('\n'),
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1193
|
+
}
|
|
1194
|
+
));
|
|
1171
1195
|
}
|
|
1172
1196
|
|
|
1173
1197
|
async function chooseCliShimBinDir(options = {}, env = process.env) {
|
|
@@ -1203,17 +1227,21 @@ async function chooseCliShimBinDir(options = {}, env = process.env) {
|
|
|
1203
1227
|
return { dir: fallback, explicit: false, pathReady: directoryIsInPath(fallback, env) };
|
|
1204
1228
|
}
|
|
1205
1229
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1230
|
+
function isGeneratedMagClawShim(content = '') {
|
|
1231
|
+
return Boolean(
|
|
1232
|
+
content.includes('MagClaw CLI shim generated by @magclaw/cli-core')
|
|
1233
|
+
|| content.includes('MagClaw CLI shim generated by @magclaw/daemon')
|
|
1234
|
+
|| content.includes('@magclaw/cli-core@')
|
|
1235
|
+
|| content.includes('@magclaw/daemon@')
|
|
1236
|
+
|| content.includes('@magclaw/computer@')
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
async function existingDurableMagclawCommand(command = 'magclaw', env = process.env) {
|
|
1241
|
+
const existing = commandExists(command, env);
|
|
1208
1242
|
if (!existing || pathLooksEphemeralCli(existing)) return '';
|
|
1209
1243
|
const content = await readFile(existing, 'utf8').catch(() => '');
|
|
1210
|
-
if (
|
|
1211
|
-
content
|
|
1212
|
-
&& !content.includes('MagClaw CLI shim generated by @magclaw/cli-core')
|
|
1213
|
-
&& !content.includes('MagClaw CLI shim generated by @magclaw/daemon')
|
|
1214
|
-
&& !content.includes('@magclaw/cli-core@')
|
|
1215
|
-
&& !content.includes('@magclaw/daemon@')
|
|
1216
|
-
) {
|
|
1244
|
+
if (content && !isGeneratedMagClawShim(content)) {
|
|
1217
1245
|
return '';
|
|
1218
1246
|
}
|
|
1219
1247
|
return existing;
|
|
@@ -1222,12 +1250,7 @@ async function existingDurableMagclawCommand(env = process.env) {
|
|
|
1222
1250
|
async function writeCliShimFile(file, content, { force = false } = {}) {
|
|
1223
1251
|
if (existsSync(file) && !force) {
|
|
1224
1252
|
const existing = await readFile(file, 'utf8').catch(() => '');
|
|
1225
|
-
if (
|
|
1226
|
-
!existing.includes('MagClaw CLI shim generated by @magclaw/cli-core')
|
|
1227
|
-
&& !existing.includes('MagClaw CLI shim generated by @magclaw/daemon')
|
|
1228
|
-
&& !existing.includes('@magclaw/cli-core@')
|
|
1229
|
-
&& !existing.includes('@magclaw/daemon@')
|
|
1230
|
-
) {
|
|
1253
|
+
if (!isGeneratedMagClawShim(existing)) {
|
|
1231
1254
|
const error = new Error(`Refusing to overwrite existing non-MagClaw command: ${file}`);
|
|
1232
1255
|
error.code = 'EEXIST';
|
|
1233
1256
|
throw error;
|
|
@@ -1241,16 +1264,39 @@ async function installCliShim(options = {}, env = process.env) {
|
|
|
1241
1264
|
if (env.MAGCLAW_INSTALL_CLI === '0' || options.installCli === false || options.noInstallCli) {
|
|
1242
1265
|
return { ok: true, command: 'magclaw', installed: false, skipped: true, reason: 'disabled' };
|
|
1243
1266
|
}
|
|
1244
|
-
const existing = await existingDurableMagclawCommand(env);
|
|
1245
|
-
|
|
1246
|
-
|
|
1267
|
+
const existing = await existingDurableMagclawCommand('magclaw', env);
|
|
1268
|
+
const existingComputer = await existingDurableMagclawCommand('magclaw-computer', env);
|
|
1269
|
+
const existingCommandsShareDir = Boolean(
|
|
1270
|
+
existing
|
|
1271
|
+
&& existingComputer
|
|
1272
|
+
&& path.dirname(existing) === path.dirname(existingComputer)
|
|
1273
|
+
);
|
|
1274
|
+
if (existingCommandsShareDir && !options.binDir && !options.cliBinDir && !options.force) {
|
|
1275
|
+
return {
|
|
1276
|
+
ok: true,
|
|
1277
|
+
command: 'magclaw',
|
|
1278
|
+
commands: ['magclaw', 'magclaw-computer'],
|
|
1279
|
+
installed: false,
|
|
1280
|
+
path: existing,
|
|
1281
|
+
files: [existing, existingComputer],
|
|
1282
|
+
pathReady: true,
|
|
1283
|
+
reason: 'already_available',
|
|
1284
|
+
};
|
|
1247
1285
|
}
|
|
1248
1286
|
|
|
1249
|
-
const target =
|
|
1287
|
+
const target = (existing || existingComputer) && !options.binDir && !options.cliBinDir && !options.force
|
|
1288
|
+
? { dir: path.dirname(existing || existingComputer), explicit: false, pathReady: true }
|
|
1289
|
+
: await chooseCliShimBinDir(options, env);
|
|
1250
1290
|
await mkdir(target.dir, { recursive: true });
|
|
1251
1291
|
const npmPath = String(options.npmPath || defaultCliNpmPath(env)).trim() || (process.platform === 'win32' ? 'npm.cmd' : 'npm');
|
|
1252
1292
|
const packageSpec = String(options.packageSpec || options.cliPackageSpec || defaultCliPackageSpec(env)).trim() || '@magclaw/cli-core@latest';
|
|
1253
|
-
const
|
|
1293
|
+
const computerPackageSpec = String(options.computerPackageSpec || options.computerCliPackageSpec || defaultComputerCliPackageSpec(env)).trim() || '@magclaw/computer@latest';
|
|
1294
|
+
const shimFiles = renderCliShimFiles({
|
|
1295
|
+
platform: process.platform,
|
|
1296
|
+
npmPath,
|
|
1297
|
+
packageSpec,
|
|
1298
|
+
computerPackageSpec,
|
|
1299
|
+
});
|
|
1254
1300
|
const written = [];
|
|
1255
1301
|
for (const shim of shimFiles) {
|
|
1256
1302
|
const file = path.join(target.dir, shim.name);
|
|
@@ -1260,12 +1306,14 @@ async function installCliShim(options = {}, env = process.env) {
|
|
|
1260
1306
|
return {
|
|
1261
1307
|
ok: true,
|
|
1262
1308
|
command: 'magclaw',
|
|
1309
|
+
commands: ['magclaw', 'magclaw-computer'],
|
|
1263
1310
|
installed: true,
|
|
1264
1311
|
binDir: target.dir,
|
|
1265
1312
|
files: written,
|
|
1266
1313
|
path: written[0] || '',
|
|
1267
1314
|
pathReady: Boolean(target.pathReady),
|
|
1268
1315
|
packageSpec,
|
|
1316
|
+
computerPackageSpec,
|
|
1269
1317
|
npmPath,
|
|
1270
1318
|
};
|
|
1271
1319
|
}
|
|
@@ -5567,8 +5615,12 @@ function normalizeSetupServerSlug(value = '') {
|
|
|
5567
5615
|
return String(value || '').trim().replace(/^\/+/, '').replace(/\/+$/, '');
|
|
5568
5616
|
}
|
|
5569
5617
|
|
|
5618
|
+
function normalizeSetupServerUrl(value = '') {
|
|
5619
|
+
return String(value || DEFAULT_SERVER_URL).replace(/\/+$/, '');
|
|
5620
|
+
}
|
|
5621
|
+
|
|
5570
5622
|
async function postSetupJson(serverUrl, pathname, body = {}) {
|
|
5571
|
-
const url = `${
|
|
5623
|
+
const url = `${normalizeSetupServerUrl(serverUrl)}${pathname}`;
|
|
5572
5624
|
const response = await fetch(url, {
|
|
5573
5625
|
method: 'POST',
|
|
5574
5626
|
headers: { 'content-type': 'application/json' },
|
|
@@ -5593,6 +5645,113 @@ function computerTargetProfile(flags = {}, fallback = DEFAULT_PROFILE) {
|
|
|
5593
5645
|
return profileFromComputerTarget(flags.server || flags.serverSlug || flags.slug || flags._?.[1] || flags.profile || fallback);
|
|
5594
5646
|
}
|
|
5595
5647
|
|
|
5648
|
+
function savedComputerSetupMatches(config = {}, target = {}) {
|
|
5649
|
+
const token = String(config.token || config.machineToken || config.apiKey || '').trim();
|
|
5650
|
+
const computerId = String(config.computerId || '').trim();
|
|
5651
|
+
if (!token || !computerId) return false;
|
|
5652
|
+
if (normalizeSetupServerUrl(config.serverUrl) !== normalizeSetupServerUrl(target.serverUrl)) return false;
|
|
5653
|
+
|
|
5654
|
+
const targetProfile = safeProfileName(target.profile || target.serverSlug || DEFAULT_PROFILE);
|
|
5655
|
+
const configProfile = safeProfileName(config.profile || targetProfile);
|
|
5656
|
+
if (configProfile !== targetProfile) return false;
|
|
5657
|
+
|
|
5658
|
+
const targetSlug = normalizeSetupServerSlug(target.serverSlug);
|
|
5659
|
+
const configSlug = normalizeSetupServerSlug(config.serverSlug || config.slug || '');
|
|
5660
|
+
if (configSlug && targetSlug && configSlug !== targetSlug) return false;
|
|
5661
|
+
return true;
|
|
5662
|
+
}
|
|
5663
|
+
|
|
5664
|
+
async function reusableComputerSetupProfile(target = {}, env = process.env) {
|
|
5665
|
+
if (target.force || target.relogin || target.reauthorize) return null;
|
|
5666
|
+
const profile = safeProfileName(target.profile || target.serverSlug || DEFAULT_PROFILE);
|
|
5667
|
+
const config = await readProfile(profile, env);
|
|
5668
|
+
if (!savedComputerSetupMatches(config, { ...target, profile })) return null;
|
|
5669
|
+
const service = await readServiceState(profile, env);
|
|
5670
|
+
if (service.remoteClosed) return null;
|
|
5671
|
+
return {
|
|
5672
|
+
config: {
|
|
5673
|
+
...config,
|
|
5674
|
+
profile,
|
|
5675
|
+
serverUrl: normalizeSetupServerUrl(config.serverUrl || target.serverUrl),
|
|
5676
|
+
token: String(config.token || config.machineToken || config.apiKey || '').trim(),
|
|
5677
|
+
pairToken: '',
|
|
5678
|
+
},
|
|
5679
|
+
service,
|
|
5680
|
+
serviceStatus: backgroundServiceStatus(profile, env),
|
|
5681
|
+
};
|
|
5682
|
+
}
|
|
5683
|
+
|
|
5684
|
+
async function finishReusableComputerSetup(existing, flags = {}, env = process.env) {
|
|
5685
|
+
const requestedSlug = normalizeSetupServerSlug(flags._?.[1] || flags.server || flags.serverSlug || flags.slug);
|
|
5686
|
+
const serverSlug = existing.config.serverSlug || requestedSlug;
|
|
5687
|
+
const config = await buildConfig({
|
|
5688
|
+
...flags,
|
|
5689
|
+
profile: existing.config.profile,
|
|
5690
|
+
serverUrl: existing.config.serverUrl,
|
|
5691
|
+
apiKey: existing.config.token,
|
|
5692
|
+
computerId: existing.config.computerId,
|
|
5693
|
+
workspaceId: existing.config.workspaceId || existing.config.workspace,
|
|
5694
|
+
name: existing.config.name,
|
|
5695
|
+
serverName: existing.config.serverName || serverSlug,
|
|
5696
|
+
serverSlug,
|
|
5697
|
+
fingerprint: existing.config.fingerprint,
|
|
5698
|
+
}, env);
|
|
5699
|
+
await saveProfile(config.profile, config, env);
|
|
5700
|
+
const cli = await tryInstallCliShim(flags, env);
|
|
5701
|
+
const computerName = config.computerName || config.name || os.hostname();
|
|
5702
|
+
const basePayload = {
|
|
5703
|
+
cli,
|
|
5704
|
+
computerId: config.computerId,
|
|
5705
|
+
computerName,
|
|
5706
|
+
profile: config.profile,
|
|
5707
|
+
serverName: config.serverName,
|
|
5708
|
+
serverSlug: config.serverSlug,
|
|
5709
|
+
reused: true,
|
|
5710
|
+
reason: 'already_configured',
|
|
5711
|
+
};
|
|
5712
|
+
if (flags.noStart || flags.noRun) {
|
|
5713
|
+
printJson({
|
|
5714
|
+
ok: true,
|
|
5715
|
+
started: false,
|
|
5716
|
+
...basePayload,
|
|
5717
|
+
next: `Run magclaw-computer start ${config.profile} when ready.`,
|
|
5718
|
+
});
|
|
5719
|
+
return;
|
|
5720
|
+
}
|
|
5721
|
+
|
|
5722
|
+
const serviceStatus = existing.serviceStatus || backgroundServiceStatus(config.profile, env);
|
|
5723
|
+
let result;
|
|
5724
|
+
if (serviceStatus.active) {
|
|
5725
|
+
result = {
|
|
5726
|
+
ok: true,
|
|
5727
|
+
mode: serviceStatus.mode,
|
|
5728
|
+
active: true,
|
|
5729
|
+
alreadyRunning: true,
|
|
5730
|
+
started: false,
|
|
5731
|
+
label: serviceStatus.label,
|
|
5732
|
+
serviceName: serviceStatus.serviceName,
|
|
5733
|
+
taskName: serviceStatus.taskName,
|
|
5734
|
+
file: serviceStatus.file,
|
|
5735
|
+
status: serviceStatus.status,
|
|
5736
|
+
state: serviceStatus.state,
|
|
5737
|
+
};
|
|
5738
|
+
} else {
|
|
5739
|
+
const started = await startBackground(config.profile, env);
|
|
5740
|
+
result = {
|
|
5741
|
+
...started,
|
|
5742
|
+
started: Boolean(started.ok),
|
|
5743
|
+
};
|
|
5744
|
+
}
|
|
5745
|
+
printJson({
|
|
5746
|
+
...result,
|
|
5747
|
+
...basePayload,
|
|
5748
|
+
});
|
|
5749
|
+
if (!result.ok) {
|
|
5750
|
+
logWarning('daemon', 'Falling back to foreground mode.');
|
|
5751
|
+
await runForegroundDaemon(config, env);
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
5754
|
+
|
|
5596
5755
|
async function runComputerSetup(flags, env = process.env) {
|
|
5597
5756
|
const subcommand = String(flags._?.[0] || '').trim();
|
|
5598
5757
|
if (!['setup', 'attach', 'login'].includes(subcommand)) {
|
|
@@ -5600,8 +5759,18 @@ async function runComputerSetup(flags, env = process.env) {
|
|
|
5600
5759
|
}
|
|
5601
5760
|
const serverSlug = normalizeSetupServerSlug(flags._?.[1] || flags.server || flags.serverSlug || flags.slug);
|
|
5602
5761
|
if (!serverSlug) throw new Error('Run computer setup with a server slug, for example: magclaw computer setup /my-server');
|
|
5603
|
-
const serverUrl =
|
|
5762
|
+
const serverUrl = normalizeSetupServerUrl(flags.serverUrl || env.MAGCLAW_PUBLIC_URL || DEFAULT_SERVER_URL);
|
|
5604
5763
|
const profile = safeProfileName(flags.profile && flags.profile !== DEFAULT_PROFILE ? flags.profile : serverSlug);
|
|
5764
|
+
const existing = await reusableComputerSetupProfile({
|
|
5765
|
+
...flags,
|
|
5766
|
+
profile,
|
|
5767
|
+
serverSlug,
|
|
5768
|
+
serverUrl,
|
|
5769
|
+
}, env);
|
|
5770
|
+
if (existing) {
|
|
5771
|
+
await finishReusableComputerSetup(existing, flags, env);
|
|
5772
|
+
return;
|
|
5773
|
+
}
|
|
5605
5774
|
const owner = await ensureMachineFingerprint(profile, env);
|
|
5606
5775
|
const displayName = String(flags.displayName || flags.name || os.hostname()).trim();
|
|
5607
5776
|
const packageInfo = runtimePackageInfo(env);
|