cadence-skill-installer 0.2.36 → 0.2.37

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cadence-skill-installer",
3
- "version": "0.2.36",
3
+ "version": "0.2.37",
4
4
  "description": "Install the Cadence skill into supported AI tool skill directories.",
5
5
  "repository": "https://github.com/snowdamiz/cadence",
6
6
  "private": false,
@@ -11,6 +11,7 @@ const CADENCE_SKILL_NAME = "cadence";
11
11
  const SCRIPT_PATH = fileURLToPath(import.meta.url);
12
12
  const SCRIPT_DIR = path.dirname(SCRIPT_PATH);
13
13
  const PACKAGE_JSON_PATH = path.resolve(SCRIPT_DIR, "..", "package.json");
14
+ const INSTALL_VERSION_MARKER = ".cadence-version";
14
15
 
15
16
  const TOOL_TARGETS = [
16
17
  { key: "codex", label: "Codex", relPath: [".codex", "skills", CADENCE_SKILL_NAME] },
@@ -211,14 +212,17 @@ async function detectInstallState(targetDir) {
211
212
  const markerPath = path.join(targetDir, "SKILL.md");
212
213
  try {
213
214
  const markerStat = await fs.stat(markerPath);
215
+ const installedVersion = markerStat.isFile() ? await readInstalledVersion(targetDir) : null;
214
216
  return {
215
217
  exists: true,
216
- cadenceInstalled: markerStat.isFile()
218
+ cadenceInstalled: markerStat.isFile(),
219
+ installedVersion
217
220
  };
218
221
  } catch {
219
222
  return {
220
223
  exists: true,
221
- cadenceInstalled: false
224
+ cadenceInstalled: false,
225
+ installedVersion: null
222
226
  };
223
227
  }
224
228
  }
@@ -232,7 +236,32 @@ async function addInstallState(targets) {
232
236
  );
233
237
  }
234
238
 
235
- function printUpdateNotice(selectedTargets) {
239
+ function formatVersionTransition(installState, installerVersion) {
240
+ if (!installState?.cadenceInstalled) {
241
+ return `not-installed > ${installerVersion}`;
242
+ }
243
+
244
+ const currentVersion = installState.installedVersion || "unknown";
245
+ return `${currentVersion} > ${installerVersion}`;
246
+ }
247
+
248
+ async function readInstalledVersion(targetDir) {
249
+ const markerPath = path.join(targetDir, INSTALL_VERSION_MARKER);
250
+ try {
251
+ const raw = await fs.readFile(markerPath, "utf8");
252
+ const trimmed = raw.trim();
253
+ return trimmed || null;
254
+ } catch {
255
+ return null;
256
+ }
257
+ }
258
+
259
+ async function writeInstalledVersion(targetDir, installerVersion) {
260
+ const markerPath = path.join(targetDir, INSTALL_VERSION_MARKER);
261
+ await fs.writeFile(markerPath, `${installerVersion}\n`, "utf8");
262
+ }
263
+
264
+ function printUpdateNotice(selectedTargets, installerVersion) {
236
265
  const cadenceInstalled = selectedTargets.filter((target) => target.installState?.cadenceInstalled);
237
266
  const existingPaths = selectedTargets.filter(
238
267
  (target) => target.installState?.exists && !target.installState?.cadenceInstalled
@@ -246,7 +275,7 @@ function printUpdateNotice(selectedTargets) {
246
275
 
247
276
  cadenceInstalled.forEach((target) => {
248
277
  output.write(
249
- `- ${target.label}: existing Cadence skill detected at ${target.targetDir}. Files will be overwritten.\n`
278
+ `- ${target.label}: existing Cadence skill detected at ${target.targetDir} (${formatVersionTransition(target.installState, installerVersion)}). Files will be overwritten.\n`
250
279
  );
251
280
  });
252
281
 
@@ -307,10 +336,13 @@ function parseInteractiveSelection(selection, targets) {
307
336
  return uniqueIndexes.map((idx) => targets[idx]);
308
337
  }
309
338
 
310
- function renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice) {
339
+ function renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice, installerVersion) {
311
340
  output.write("\x1b[2J\x1b[H");
312
341
  colorizeAsciiBanner().forEach((line) => output.write(`${line}\n`));
313
342
  output.write("\n");
343
+ output.write(
344
+ `${style("Version:", ANSI.bold, ANSI.brightBlue)} ${style(installerVersion, ANSI.bold, ANSI.white)}\n`
345
+ );
314
346
  output.write(style("Select tools to install Cadence skill into (multi-select).\n", ANSI.bold, ANSI.white));
315
347
  output.write(
316
348
  style(
@@ -330,8 +362,12 @@ function renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice) {
330
362
  : isSelected
331
363
  ? style(target.label, ANSI.brightGreen)
332
364
  : target.label;
365
+ const versionText = style(
366
+ `[${formatVersionTransition(target.installState, installerVersion)}]`,
367
+ isCurrent ? ANSI.brightCyan : ANSI.dim
368
+ );
333
369
  const targetPathText = isCurrent ? style(target.targetDir, ANSI.brightCyan) : style(target.targetDir, ANSI.dim);
334
- output.write(`${pointer} [${checked}] ${labelText} (${targetPathText})\n`);
370
+ output.write(`${pointer} [${checked}] ${labelText} ${versionText} (${targetPathText})\n`);
335
371
  });
336
372
 
337
373
  output.write(`\n${style("Selected:", ANSI.bold, ANSI.brightBlue)} ${style(String(selectedIndexes.size), ANSI.bold, ANSI.white)}\n`);
@@ -340,7 +376,7 @@ function renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice) {
340
376
  }
341
377
  }
342
378
 
343
- function chooseTargetsWithTui(targets) {
379
+ function chooseTargetsWithTui(targets, installerVersion) {
344
380
  return new Promise((resolve, reject) => {
345
381
  if (!input.isTTY || !output.isTTY) {
346
382
  reject(new Error("Interactive TUI requires a TTY."));
@@ -386,7 +422,7 @@ function chooseTargetsWithTui(targets) {
386
422
  const finishSelection = () => {
387
423
  if (selectedIndexes.size === 0) {
388
424
  notice = "Select at least one tool before continuing.";
389
- renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice);
425
+ renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice, installerVersion);
390
426
  return;
391
427
  }
392
428
 
@@ -413,14 +449,14 @@ function chooseTargetsWithTui(targets) {
413
449
  if (key === " " || key === "\u001b[3~") {
414
450
  toggleCurrent();
415
451
  notice = "";
416
- renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice);
452
+ renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice, installerVersion);
417
453
  return;
418
454
  }
419
455
 
420
456
  if (key === "a" || key === "A") {
421
457
  toggleAll();
422
458
  notice = "";
423
- renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice);
459
+ renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice, installerVersion);
424
460
  return;
425
461
  }
426
462
 
@@ -433,14 +469,14 @@ function chooseTargetsWithTui(targets) {
433
469
  if (key === "\u001b[A" || key === "k" || key === "K") {
434
470
  moveCursor(-1);
435
471
  notice = "";
436
- renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice);
472
+ renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice, installerVersion);
437
473
  return;
438
474
  }
439
475
 
440
476
  if (key === "\u001b[B" || key === "j" || key === "J") {
441
477
  moveCursor(1);
442
478
  notice = "";
443
- renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice);
479
+ renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice, installerVersion);
444
480
  }
445
481
  };
446
482
 
@@ -448,17 +484,20 @@ function chooseTargetsWithTui(targets) {
448
484
  input.setRawMode(true);
449
485
  input.resume();
450
486
  input.on("data", onData);
451
- renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice);
487
+ renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice, installerVersion);
452
488
  });
453
489
  }
454
490
 
455
- async function chooseTargetsWithTextPrompt(targets) {
491
+ async function chooseTargetsWithTextPrompt(targets, installerVersion) {
456
492
  const rl = readline.createInterface({ input, output });
457
493
  try {
494
+ output.write(`Version: ${installerVersion}\n`);
458
495
  output.write("Select tools to install Cadence skill into (multi-select).\n");
459
496
  output.write("Enter numbers separated by commas, or type 'all'.\n\n");
460
497
  targets.forEach((target, idx) => {
461
- output.write(`${idx + 1}. ${target.label} (${target.targetDir})\n`);
498
+ output.write(
499
+ `${idx + 1}. ${target.label} [${formatVersionTransition(target.installState, installerVersion)}] (${target.targetDir})\n`
500
+ );
462
501
  });
463
502
  const answer = await rl.question("\nSelection: ");
464
503
  return parseInteractiveSelection(answer, targets);
@@ -467,7 +506,7 @@ async function chooseTargetsWithTextPrompt(targets) {
467
506
  }
468
507
  }
469
508
 
470
- async function chooseTargets(parsed, targets) {
509
+ async function chooseTargets(parsed, targets, installerVersion) {
471
510
  if (parsed.all) {
472
511
  return targets;
473
512
  }
@@ -478,10 +517,10 @@ async function chooseTargets(parsed, targets) {
478
517
  }
479
518
 
480
519
  if (input.isTTY && output.isTTY) {
481
- return chooseTargetsWithTui(targets);
520
+ return chooseTargetsWithTui(targets, installerVersion);
482
521
  }
483
522
 
484
- return chooseTargetsWithTextPrompt(targets);
523
+ return chooseTargetsWithTextPrompt(targets, installerVersion);
485
524
  }
486
525
 
487
526
  async function copySkillContents(sourceDir, targetDir) {
@@ -501,7 +540,7 @@ async function copySkillContents(sourceDir, targetDir) {
501
540
  );
502
541
  }
503
542
 
504
- async function confirmInstall(parsed, selectedTargets) {
543
+ async function confirmInstall(parsed, selectedTargets, installerVersion) {
505
544
  if (parsed.yes) {
506
545
  return true;
507
546
  }
@@ -515,7 +554,9 @@ async function confirmInstall(parsed, selectedTargets) {
515
554
  : target.installState?.exists
516
555
  ? style(" [existing path detected: conflicting files may be overwritten]", ANSI.salmon)
517
556
  : "";
518
- output.write(`${style("-", ANSI.dim)} ${style(target.targetDir, ANSI.periwinkle)}${suffix}\n`);
557
+ output.write(
558
+ `${style("-", ANSI.dim)} ${style(target.targetDir, ANSI.periwinkle)} ${style(`[${formatVersionTransition(target.installState, installerVersion)}]`, ANSI.dim)}${suffix}\n`
559
+ );
519
560
  });
520
561
  const answer = await rl.question(style("Continue? [y/N]: ", ANSI.bold, ANSI.white));
521
562
  return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
@@ -545,11 +586,11 @@ async function main() {
545
586
  const homeDir = parsed.home || os.homedir();
546
587
  const sourceDir = resolveSourceDir();
547
588
  await ensureSourceDir(sourceDir);
548
- const targets = buildTargets(homeDir);
589
+ const targets = await addInstallState(buildTargets(homeDir));
549
590
 
550
591
  let selectedTargets;
551
592
  try {
552
- selectedTargets = await chooseTargets(parsed, targets);
593
+ selectedTargets = await chooseTargets(parsed, targets, installerVersion);
553
594
  } catch (error) {
554
595
  output.write(`Error: ${error.message}\n`);
555
596
  process.exitCode = 1;
@@ -561,10 +602,10 @@ async function main() {
561
602
  return;
562
603
  }
563
604
 
564
- const selectedTargetsWithState = await addInstallState(selectedTargets);
565
- printUpdateNotice(selectedTargetsWithState);
605
+ const selectedTargetsWithState = selectedTargets;
606
+ printUpdateNotice(selectedTargetsWithState, installerVersion);
566
607
 
567
- const confirmed = await confirmInstall(parsed, selectedTargetsWithState);
608
+ const confirmed = await confirmInstall(parsed, selectedTargetsWithState, installerVersion);
568
609
  if (!confirmed) {
569
610
  output.write("Installation cancelled.\n");
570
611
  return;
@@ -576,8 +617,11 @@ async function main() {
576
617
 
577
618
  for (const target of selectedTargetsWithState) {
578
619
  await copySkillContents(sourceDir, target.targetDir);
620
+ await writeInstalledVersion(target.targetDir, installerVersion);
579
621
  const action = target.installState?.exists ? "Updated" : "Installed";
580
- output.write(`${style(action, ANSI.bold, ANSI.brightGreen)} ${style(target.label, ANSI.bold, ANSI.white)}: ${style(target.targetDir, ANSI.periwinkle)}\n`);
622
+ output.write(
623
+ `${style(action, ANSI.bold, ANSI.brightGreen)} ${style(target.label, ANSI.bold, ANSI.white)}: ${style(target.targetDir, ANSI.periwinkle)} ${style(`[${formatVersionTransition(target.installState, installerVersion)}]`, ANSI.dim)}\n`
624
+ );
581
625
  }
582
626
 
583
627
  output.write(