cadence-skill-installer 0.1.4 → 0.2.0
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/README.md +1 -0
- package/package.json +1 -1
- package/scripts/install-cadence-skill.mjs +145 -8
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ npx cadence-skill-installer
|
|
|
10
10
|
|
|
11
11
|
The installer shows a multi-select prompt (comma-separated choices) so you can install into multiple tools in one run.
|
|
12
12
|
If a selected tool already has Cadence installed, the installer prints an update notice and warns that files will be overwritten.
|
|
13
|
+
In TTY terminals, selection is a real interactive TUI: use arrow keys (or `j`/`k`) to move, `space` to toggle, `a` to toggle all, and `enter` to confirm.
|
|
13
14
|
|
|
14
15
|
## Non-interactive examples
|
|
15
16
|
|
package/package.json
CHANGED
|
@@ -22,7 +22,7 @@ const TOOL_TARGETS = [
|
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
key: "windsurf",
|
|
25
|
-
label: "
|
|
25
|
+
label: "Windsurf",
|
|
26
26
|
relPath: [".codeium", "windsurf", "skills", CADENCE_SKILL_NAME]
|
|
27
27
|
},
|
|
28
28
|
{
|
|
@@ -238,16 +238,136 @@ function parseInteractiveSelection(selection, targets) {
|
|
|
238
238
|
return uniqueIndexes.map((idx) => targets[idx]);
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
241
|
+
function renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice) {
|
|
242
|
+
output.write("\x1b[2J\x1b[H");
|
|
243
|
+
output.write("Select tools to install Cadence skill into (multi-select).\n");
|
|
244
|
+
output.write("Use arrow keys (or j/k) to move, space to toggle, a to toggle all, enter to confirm, q to cancel.\n\n");
|
|
245
245
|
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
246
|
+
targets.forEach((target, idx) => {
|
|
247
|
+
const pointer = idx === cursorIndex ? ">" : " ";
|
|
248
|
+
const checked = selectedIndexes.has(idx) ? "x" : " ";
|
|
249
|
+
output.write(`${pointer} [${checked}] ${target.label} (${target.targetDir})\n`);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
output.write(`\nSelected: ${selectedIndexes.size}\n`);
|
|
253
|
+
if (notice) {
|
|
254
|
+
output.write(`${notice}\n`);
|
|
249
255
|
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function chooseTargetsWithTui(targets) {
|
|
259
|
+
return new Promise((resolve, reject) => {
|
|
260
|
+
if (!input.isTTY || !output.isTTY) {
|
|
261
|
+
reject(new Error("Interactive TUI requires a TTY."));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let cursorIndex = 0;
|
|
266
|
+
let notice = "";
|
|
267
|
+
const selectedIndexes = new Set();
|
|
268
|
+
|
|
269
|
+
const cleanup = () => {
|
|
270
|
+
input.off("data", onData);
|
|
271
|
+
if (input.isTTY) {
|
|
272
|
+
input.setRawMode(false);
|
|
273
|
+
}
|
|
274
|
+
output.write("\x1b[?25h");
|
|
275
|
+
output.write("\n");
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const moveCursor = (delta) => {
|
|
279
|
+
const length = targets.length;
|
|
280
|
+
cursorIndex = (cursorIndex + delta + length) % length;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const toggleCurrent = () => {
|
|
284
|
+
if (selectedIndexes.has(cursorIndex)) {
|
|
285
|
+
selectedIndexes.delete(cursorIndex);
|
|
286
|
+
} else {
|
|
287
|
+
selectedIndexes.add(cursorIndex);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const toggleAll = () => {
|
|
292
|
+
if (selectedIndexes.size === targets.length) {
|
|
293
|
+
selectedIndexes.clear();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
for (let idx = 0; idx < targets.length; idx += 1) {
|
|
297
|
+
selectedIndexes.add(idx);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const finishSelection = () => {
|
|
302
|
+
if (selectedIndexes.size === 0) {
|
|
303
|
+
notice = "Select at least one tool before continuing.";
|
|
304
|
+
renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const orderedIndexes = [...selectedIndexes].sort((a, b) => a - b);
|
|
309
|
+
const selectedTargets = orderedIndexes.map((idx) => targets[idx]);
|
|
310
|
+
cleanup();
|
|
311
|
+
resolve(selectedTargets);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const onData = (chunk) => {
|
|
315
|
+
const key = chunk.toString("utf8");
|
|
316
|
+
|
|
317
|
+
if (key === "\u0003") {
|
|
318
|
+
cleanup();
|
|
319
|
+
reject(new Error("Cancelled by user."));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (key === "\r" || key === "\n") {
|
|
324
|
+
finishSelection();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (key === " " || key === "\u001b[3~") {
|
|
329
|
+
toggleCurrent();
|
|
330
|
+
notice = "";
|
|
331
|
+
renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (key === "a" || key === "A") {
|
|
336
|
+
toggleAll();
|
|
337
|
+
notice = "";
|
|
338
|
+
renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (key === "q" || key === "Q") {
|
|
343
|
+
cleanup();
|
|
344
|
+
reject(new Error("Cancelled by user."));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (key === "\u001b[A" || key === "k" || key === "K") {
|
|
349
|
+
moveCursor(-1);
|
|
350
|
+
notice = "";
|
|
351
|
+
renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (key === "\u001b[B" || key === "j" || key === "J") {
|
|
356
|
+
moveCursor(1);
|
|
357
|
+
notice = "";
|
|
358
|
+
renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
250
361
|
|
|
362
|
+
output.write("\x1b[?25l");
|
|
363
|
+
input.setRawMode(true);
|
|
364
|
+
input.resume();
|
|
365
|
+
input.on("data", onData);
|
|
366
|
+
renderMultiSelectTui(targets, cursorIndex, selectedIndexes, notice);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function chooseTargetsWithTextPrompt(targets) {
|
|
251
371
|
const rl = readline.createInterface({ input, output });
|
|
252
372
|
try {
|
|
253
373
|
output.write("Select tools to install Cadence skill into (multi-select).\n");
|
|
@@ -262,6 +382,23 @@ async function chooseTargets(parsed, targets) {
|
|
|
262
382
|
}
|
|
263
383
|
}
|
|
264
384
|
|
|
385
|
+
async function chooseTargets(parsed, targets) {
|
|
386
|
+
if (parsed.all) {
|
|
387
|
+
return targets;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (parsed.tools) {
|
|
391
|
+
const selectedKeys = parseToolKeyList(parsed.tools);
|
|
392
|
+
return targets.filter((target) => selectedKeys.includes(target.key));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (input.isTTY && output.isTTY) {
|
|
396
|
+
return chooseTargetsWithTui(targets);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return chooseTargetsWithTextPrompt(targets);
|
|
400
|
+
}
|
|
401
|
+
|
|
265
402
|
async function copyEntry(srcPath, destPath) {
|
|
266
403
|
const stat = await fs.lstat(srcPath);
|
|
267
404
|
|