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
|
@@ -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
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|