@magclaw/cli-core 0.1.31 → 0.1.33

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 +82 -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.33",
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,64 @@ 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
+ upToDate: true,
1313
+ reason: force && status.exists ? 'forced' : status.reason,
1314
+ previousHash: status.currentHash,
1315
+ currentHash: status.expectedHash,
1316
+ };
1261
1317
  }
1262
1318
 
1263
1319
  async function installCliShim(options = {}, env = process.env) {
@@ -1266,24 +1322,6 @@ async function installCliShim(options = {}, env = process.env) {
1266
1322
  }
1267
1323
  const existing = await existingDurableMagclawCommand('magclaw', env);
1268
1324
  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
1325
  const target = (existing || existingComputer) && !options.binDir && !options.cliBinDir && !options.force
1288
1326
  ? { dir: path.dirname(existing || existingComputer), explicit: false, pathReady: true }
1289
1327
  : await chooseCliShimBinDir(options, env);
@@ -1297,21 +1335,33 @@ async function installCliShim(options = {}, env = process.env) {
1297
1335
  packageSpec,
1298
1336
  computerPackageSpec,
1299
1337
  });
1300
- const written = [];
1338
+ const shimResults = [];
1301
1339
  for (const shim of shimFiles) {
1302
1340
  const file = path.join(target.dir, shim.name);
1303
- await writeCliShimFile(file, shim.content, { force: Boolean(options.force) });
1304
- written.push(file);
1341
+ const result = await writeCliShimFile(file, shim.content, { force: Boolean(options.force) });
1342
+ shimResults.push({
1343
+ command: shim.command || shim.name,
1344
+ name: shim.name,
1345
+ ...result,
1346
+ });
1305
1347
  }
1348
+ const changedShims = shimResults.filter((shim) => shim.changed);
1349
+ const reason = changedShims.length
1350
+ ? (changedShims.every((shim) => !shim.exists) ? 'installed' : 'updated')
1351
+ : 'already_current';
1306
1352
  return {
1307
1353
  ok: true,
1308
1354
  command: 'magclaw',
1309
1355
  commands: ['magclaw', 'magclaw-computer'],
1310
- installed: true,
1356
+ installed: changedShims.length > 0,
1357
+ updated: changedShims.length > 0,
1311
1358
  binDir: target.dir,
1312
- files: written,
1313
- path: written[0] || '',
1359
+ files: shimResults.map((shim) => shim.file),
1360
+ changedFiles: changedShims.map((shim) => shim.file),
1361
+ shims: shimResults,
1362
+ path: shimResults[0]?.file || '',
1314
1363
  pathReady: Boolean(target.pathReady),
1364
+ reason,
1315
1365
  packageSpec,
1316
1366
  computerPackageSpec,
1317
1367
  npmPath,