@magclaw/cli-core 0.1.31 → 0.1.32

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 +81 -32
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magclaw/cli-core",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
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
@@ -1118,6 +1118,10 @@ function defaultCliNpmPath(env = process.env) {
1118
1118
  return commandExists('npm', env) || (process.platform === 'win32' ? 'npm.cmd' : 'npm');
1119
1119
  }
1120
1120
 
1121
+ function contentHash(value = '') {
1122
+ return crypto.createHash('sha256').update(String(value || '')).digest('hex');
1123
+ }
1124
+
1121
1125
  function cliShimTargets({ packageSpec = '@magclaw/cli-core@latest', computerPackageSpec = '@magclaw/computer@latest' } = {}) {
1122
1126
  return [
1123
1127
  {
@@ -1146,9 +1150,11 @@ export function renderCliShimFiles({
1146
1150
  return targets.flatMap((target) => [
1147
1151
  {
1148
1152
  name: `${target.command}.cmd`,
1153
+ command: target.command,
1149
1154
  executable: true,
1150
1155
  content: [
1151
1156
  '@echo off',
1157
+ `rem MagClaw CLI shim generated by @magclaw/cli-core ${DAEMON_VERSION}.`,
1152
1158
  'setlocal',
1153
1159
  `set "NPM_BIN=${cmdEnvValue(targetNpm)}"`,
1154
1160
  `if not exist "%NPM_BIN%" set "NPM_BIN=${cmdEnvValue(fallback)}"`,
@@ -1161,8 +1167,10 @@ export function renderCliShimFiles({
1161
1167
  },
1162
1168
  {
1163
1169
  name: `${target.command}.ps1`,
1170
+ command: target.command,
1164
1171
  executable: true,
1165
1172
  content: [
1173
+ `# MagClaw CLI shim generated by @magclaw/cli-core ${DAEMON_VERSION}.`,
1166
1174
  `$npmBin = ${psSingleQuote(targetNpm)}`,
1167
1175
  `if (-not (Test-Path -LiteralPath $npmBin)) { $npmBin = ${psSingleQuote(fallback)} }`,
1168
1176
  `$packageSpec = ${psSingleQuote(target.packageSpec)}`,
@@ -1177,11 +1185,12 @@ export function renderCliShimFiles({
1177
1185
  return targets.map((target) => (
1178
1186
  {
1179
1187
  name: target.command,
1188
+ command: target.command,
1180
1189
  executable: true,
1181
1190
  content: [
1182
1191
  '#!/bin/sh',
1183
1192
  'set -eu',
1184
- '# MagClaw CLI shim generated by @magclaw/cli-core.',
1193
+ `# MagClaw CLI shim generated by @magclaw/cli-core ${DAEMON_VERSION}.`,
1185
1194
  `NPM_BIN=${shSingleQuote(targetNpm)}`,
1186
1195
  `PACKAGE_SPEC=${shSingleQuote(target.packageSpec)}`,
1187
1196
  'if [ ! -x "$NPM_BIN" ]; then',
@@ -1247,17 +1256,63 @@ async function existingDurableMagclawCommand(command = 'magclaw', env = process.
1247
1256
  return existing;
1248
1257
  }
1249
1258
 
1259
+ async function inspectCliShimFile(file, expectedContent) {
1260
+ const expectedHash = contentHash(expectedContent);
1261
+ let existing = null;
1262
+ try {
1263
+ existing = await readFile(file, 'utf8');
1264
+ } catch (error) {
1265
+ if (error?.code !== 'ENOENT') throw error;
1266
+ }
1267
+ if (existing === null) {
1268
+ return {
1269
+ file,
1270
+ exists: false,
1271
+ generated: true,
1272
+ upToDate: false,
1273
+ reason: 'missing',
1274
+ currentHash: '',
1275
+ expectedHash,
1276
+ };
1277
+ }
1278
+ const generated = isGeneratedMagClawShim(existing);
1279
+ const currentHash = contentHash(existing);
1280
+ return {
1281
+ file,
1282
+ exists: true,
1283
+ generated,
1284
+ upToDate: generated && currentHash === expectedHash,
1285
+ reason: generated ? (currentHash === expectedHash ? 'current' : 'outdated') : 'non_magclaw_command',
1286
+ currentHash,
1287
+ expectedHash,
1288
+ };
1289
+ }
1290
+
1250
1291
  async function writeCliShimFile(file, content, { force = false } = {}) {
1251
- if (existsSync(file) && !force) {
1252
- const existing = await readFile(file, 'utf8').catch(() => '');
1253
- if (!isGeneratedMagClawShim(existing)) {
1254
- const error = new Error(`Refusing to overwrite existing non-MagClaw command: ${file}`);
1255
- error.code = 'EEXIST';
1256
- throw error;
1257
- }
1292
+ const status = await inspectCliShimFile(file, content);
1293
+ if (status.exists && !status.generated && !force) {
1294
+ const error = new Error(`Refusing to overwrite existing non-MagClaw command: ${file}`);
1295
+ error.code = 'EEXIST';
1296
+ throw error;
1297
+ }
1298
+ if (status.upToDate && !force) {
1299
+ await chmod(file, 0o755).catch(() => {});
1300
+ return {
1301
+ ...status,
1302
+ changed: false,
1303
+ written: false,
1304
+ };
1258
1305
  }
1259
1306
  await writeFile(file, content);
1260
1307
  await chmod(file, 0o755).catch(() => {});
1308
+ return {
1309
+ ...status,
1310
+ changed: true,
1311
+ written: true,
1312
+ reason: force && status.exists ? 'forced' : status.reason,
1313
+ previousHash: status.currentHash,
1314
+ currentHash: status.expectedHash,
1315
+ };
1261
1316
  }
1262
1317
 
1263
1318
  async function installCliShim(options = {}, env = process.env) {
@@ -1266,24 +1321,6 @@ async function installCliShim(options = {}, env = process.env) {
1266
1321
  }
1267
1322
  const existing = await existingDurableMagclawCommand('magclaw', env);
1268
1323
  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
- };
1285
- }
1286
-
1287
1324
  const target = (existing || existingComputer) && !options.binDir && !options.cliBinDir && !options.force
1288
1325
  ? { dir: path.dirname(existing || existingComputer), explicit: false, pathReady: true }
1289
1326
  : await chooseCliShimBinDir(options, env);
@@ -1297,21 +1334,33 @@ async function installCliShim(options = {}, env = process.env) {
1297
1334
  packageSpec,
1298
1335
  computerPackageSpec,
1299
1336
  });
1300
- const written = [];
1337
+ const shimResults = [];
1301
1338
  for (const shim of shimFiles) {
1302
1339
  const file = path.join(target.dir, shim.name);
1303
- await writeCliShimFile(file, shim.content, { force: Boolean(options.force) });
1304
- written.push(file);
1340
+ const result = await writeCliShimFile(file, shim.content, { force: Boolean(options.force) });
1341
+ shimResults.push({
1342
+ command: shim.command || shim.name,
1343
+ name: shim.name,
1344
+ ...result,
1345
+ });
1305
1346
  }
1347
+ const changedShims = shimResults.filter((shim) => shim.changed);
1348
+ const reason = changedShims.length
1349
+ ? (changedShims.every((shim) => !shim.exists) ? 'installed' : 'updated')
1350
+ : 'already_current';
1306
1351
  return {
1307
1352
  ok: true,
1308
1353
  command: 'magclaw',
1309
1354
  commands: ['magclaw', 'magclaw-computer'],
1310
- installed: true,
1355
+ installed: changedShims.length > 0,
1356
+ updated: changedShims.length > 0,
1311
1357
  binDir: target.dir,
1312
- files: written,
1313
- path: written[0] || '',
1358
+ files: shimResults.map((shim) => shim.file),
1359
+ changedFiles: changedShims.map((shim) => shim.file),
1360
+ shims: shimResults,
1361
+ path: shimResults[0]?.file || '',
1314
1362
  pathReady: Boolean(target.pathReady),
1363
+ reason,
1315
1364
  packageSpec,
1316
1365
  computerPackageSpec,
1317
1366
  npmPath,