skild 0.10.8 → 0.10.10
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/dist/index.js +135 -46
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -86,10 +86,31 @@ var logger = {
|
|
|
86
86
|
};
|
|
87
87
|
|
|
88
88
|
// src/utils/interactive-select.ts
|
|
89
|
-
import { checkbox } from "@inquirer/prompts";
|
|
90
89
|
import readline from "readline";
|
|
91
90
|
import chalk2 from "chalk";
|
|
92
91
|
import { PLATFORMS } from "@skild/core";
|
|
92
|
+
function buildSkillTree(skills) {
|
|
93
|
+
const allNode = createTreeNode("all", "All Skills", 1, false);
|
|
94
|
+
for (let i = 0; i < skills.length; i++) {
|
|
95
|
+
const relPath = skills[i].relPath;
|
|
96
|
+
const parts = relPath === "." ? ["."] : relPath.split("/").filter(Boolean);
|
|
97
|
+
allNode.leafIndices.push(i);
|
|
98
|
+
let current = allNode;
|
|
99
|
+
for (let d = 0; d < parts.length; d++) {
|
|
100
|
+
const part = parts[d];
|
|
101
|
+
const nodeId = parts.slice(0, d + 1).join("/");
|
|
102
|
+
let child = current.children.find((c2) => c2.name === part);
|
|
103
|
+
if (!child) {
|
|
104
|
+
child = createTreeNode(nodeId, part, d + 2, d === parts.length - 1);
|
|
105
|
+
current.children.push(child);
|
|
106
|
+
}
|
|
107
|
+
child.leafIndices.push(i);
|
|
108
|
+
current = child;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
collapseIntermediateNodes(allNode);
|
|
112
|
+
return wrapWithRoot(allNode);
|
|
113
|
+
}
|
|
93
114
|
function buildSyncTree(choices) {
|
|
94
115
|
const root = createTreeNode("root", "All targets", 1, false);
|
|
95
116
|
const platforms = /* @__PURE__ */ new Map();
|
|
@@ -123,12 +144,56 @@ function buildPlatformTree(items) {
|
|
|
123
144
|
}
|
|
124
145
|
return wrapWithRoot(allNode);
|
|
125
146
|
}
|
|
147
|
+
function buildTreeFromSkillNodes(nodes, totalSkills) {
|
|
148
|
+
const allNode = createTreeNode("all", "All Skills", 1, false);
|
|
149
|
+
const attach = (node, depth) => {
|
|
150
|
+
const treeNode = createTreeNode(
|
|
151
|
+
node.id,
|
|
152
|
+
node.label,
|
|
153
|
+
depth,
|
|
154
|
+
Boolean(node.skillIndex != null && (!node.children || node.children.length === 0)),
|
|
155
|
+
node.skillIndex != null ? [node.skillIndex] : []
|
|
156
|
+
);
|
|
157
|
+
if (node.children?.length) {
|
|
158
|
+
for (const child of node.children) {
|
|
159
|
+
const childNode = attach(child, depth + 1);
|
|
160
|
+
treeNode.children.push(childNode);
|
|
161
|
+
treeNode.leafIndices.push(...childNode.leafIndices);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return treeNode;
|
|
165
|
+
};
|
|
166
|
+
for (const node of nodes) {
|
|
167
|
+
const childNode = attach(node, 2);
|
|
168
|
+
allNode.children.push(childNode);
|
|
169
|
+
allNode.leafIndices.push(...childNode.leafIndices);
|
|
170
|
+
}
|
|
171
|
+
if (allNode.leafIndices.length === 0 && totalSkills > 0) {
|
|
172
|
+
for (let i = 0; i < totalSkills; i++) allNode.leafIndices.push(i);
|
|
173
|
+
}
|
|
174
|
+
return wrapWithRoot(allNode);
|
|
175
|
+
}
|
|
126
176
|
function createTreeNode(id, name, depth, isLeaf, leafIndices = []) {
|
|
127
177
|
return { id, name, depth, children: [], leafIndices, isLeaf };
|
|
128
178
|
}
|
|
129
179
|
function wrapWithRoot(allNode) {
|
|
130
180
|
return { id: "", name: ".", depth: 0, children: [allNode], leafIndices: [...allNode.leafIndices], isLeaf: false };
|
|
131
181
|
}
|
|
182
|
+
function collapseIntermediateNodes(allNode) {
|
|
183
|
+
while (allNode.children.length === 1 && !allNode.children[0].isLeaf && allNode.children[0].children.length > 0) {
|
|
184
|
+
const singleChild = allNode.children[0];
|
|
185
|
+
for (const grandchild of singleChild.children) {
|
|
186
|
+
adjustDepth(grandchild, -1);
|
|
187
|
+
}
|
|
188
|
+
allNode.children = singleChild.children;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function adjustDepth(node, delta) {
|
|
192
|
+
node.depth += delta;
|
|
193
|
+
for (const child of node.children) {
|
|
194
|
+
adjustDepth(child, delta);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
132
197
|
function flattenTree(root) {
|
|
133
198
|
const result = [];
|
|
134
199
|
function walk(node) {
|
|
@@ -150,23 +215,26 @@ function getNodeSelection(node, selected) {
|
|
|
150
215
|
else if (selectedCount > 0) state = "partial";
|
|
151
216
|
return { state, selectedCount };
|
|
152
217
|
}
|
|
153
|
-
function createRenderer(title, subtitle, flatNodes, selected, getCursor, formatNode) {
|
|
218
|
+
function createRenderer(title, subtitle, flatNodes, selected, getCursor, getViewOffset, viewHeight, formatNode) {
|
|
154
219
|
function renderContent() {
|
|
155
220
|
const lines = [];
|
|
156
221
|
lines.push(chalk2.bold.cyan(title));
|
|
157
222
|
lines.push(chalk2.dim(subtitle));
|
|
158
223
|
lines.push("");
|
|
159
224
|
const cursor = getCursor();
|
|
160
|
-
|
|
225
|
+
const offset = getViewOffset();
|
|
226
|
+
const end = Math.min(flatNodes.length, offset + viewHeight);
|
|
227
|
+
for (let i = offset; i < end; i++) {
|
|
161
228
|
const node = flatNodes[i];
|
|
162
229
|
const selection = getNodeSelection(node, selected);
|
|
163
230
|
lines.push(formatNode(node, selection, i === cursor));
|
|
164
231
|
}
|
|
165
|
-
lines.push(
|
|
232
|
+
lines.push(chalk2.dim(`
|
|
233
|
+
Showing ${end - offset}/${flatNodes.length} (offset ${offset + 1})`));
|
|
166
234
|
return lines;
|
|
167
235
|
}
|
|
168
236
|
function getLineCount() {
|
|
169
|
-
return 4 + flatNodes.length;
|
|
237
|
+
return 4 + Math.min(viewHeight, flatNodes.length);
|
|
170
238
|
}
|
|
171
239
|
return { renderContent, getLineCount };
|
|
172
240
|
}
|
|
@@ -186,6 +254,9 @@ async function interactiveTreeSelect(items, options) {
|
|
|
186
254
|
const root = buildTree(items);
|
|
187
255
|
const flatNodes = flattenTree(root);
|
|
188
256
|
if (flatNodes.length === 0) return null;
|
|
257
|
+
const stdout = process.stdout;
|
|
258
|
+
const rows = typeof stdout.rows === "number" ? stdout.rows : 24;
|
|
259
|
+
const viewHeight = Math.max(5, rows - 5);
|
|
189
260
|
const selected = /* @__PURE__ */ new Set();
|
|
190
261
|
if (defaultSelected) {
|
|
191
262
|
for (const idx of defaultSelected) selected.add(idx);
|
|
@@ -193,8 +264,8 @@ async function interactiveTreeSelect(items, options) {
|
|
|
193
264
|
for (let i = 0; i < items.length; i++) selected.add(i);
|
|
194
265
|
}
|
|
195
266
|
let cursor = 0;
|
|
267
|
+
let viewOffset = 0;
|
|
196
268
|
const stdin = process.stdin;
|
|
197
|
-
const stdout = process.stdout;
|
|
198
269
|
if (!stdin.isTTY || !stdout.isTTY) {
|
|
199
270
|
return defaultAll ? Array.from(selected) : null;
|
|
200
271
|
}
|
|
@@ -206,7 +277,16 @@ async function interactiveTreeSelect(items, options) {
|
|
|
206
277
|
if (useAltScreen) {
|
|
207
278
|
stdout.write("\x1B[?1049h");
|
|
208
279
|
}
|
|
209
|
-
const renderer = createRenderer(
|
|
280
|
+
const renderer = createRenderer(
|
|
281
|
+
title,
|
|
282
|
+
subtitle,
|
|
283
|
+
flatNodes,
|
|
284
|
+
selected,
|
|
285
|
+
() => cursor,
|
|
286
|
+
() => viewOffset,
|
|
287
|
+
viewHeight,
|
|
288
|
+
formatNode
|
|
289
|
+
);
|
|
210
290
|
writeToTerminal(stdout, renderer.renderContent());
|
|
211
291
|
return new Promise((resolve) => {
|
|
212
292
|
function cleanup(clear = false) {
|
|
@@ -254,11 +334,13 @@ async function interactiveTreeSelect(items, options) {
|
|
|
254
334
|
}
|
|
255
335
|
if (key.name === "up") {
|
|
256
336
|
cursor = (cursor - 1 + flatNodes.length) % flatNodes.length;
|
|
337
|
+
if (cursor < viewOffset) viewOffset = cursor;
|
|
257
338
|
rerender();
|
|
258
339
|
return;
|
|
259
340
|
}
|
|
260
341
|
if (key.name === "down") {
|
|
261
342
|
cursor = (cursor + 1) % flatNodes.length;
|
|
343
|
+
if (cursor >= viewOffset + viewHeight) viewOffset = cursor - viewHeight + 1;
|
|
262
344
|
rerender();
|
|
263
345
|
return;
|
|
264
346
|
}
|
|
@@ -289,7 +371,7 @@ function formatTreeNode(node, selection, isCursor, options = {}) {
|
|
|
289
371
|
const { state, selectedCount } = selection;
|
|
290
372
|
const totalCount = node.leafIndices.length;
|
|
291
373
|
const indent = " ".repeat(node.depth - 1);
|
|
292
|
-
const
|
|
374
|
+
const checkbox = state === "all" ? chalk2.green("\u25CF") : state === "partial" ? chalk2.yellow("\u25D0") : chalk2.dim("\u25CB");
|
|
293
375
|
const name = isCursor ? chalk2.cyan.underline(node.name) : node.name;
|
|
294
376
|
const cursorMark = isCursor ? chalk2.cyan("\u203A ") : " ";
|
|
295
377
|
let count = "";
|
|
@@ -301,13 +383,21 @@ function formatTreeNode(node, selection, isCursor, options = {}) {
|
|
|
301
383
|
if (isCursor && totalCount > 0) {
|
|
302
384
|
hint = state === "all" ? chalk2.dim(" \u2190 Space to deselect") : chalk2.dim(" \u2190 Space to select");
|
|
303
385
|
}
|
|
304
|
-
return `${cursorMark}${indent}${
|
|
386
|
+
return `${cursorMark}${indent}${checkbox} ${name}${count}${suffix}${hint}`;
|
|
305
387
|
}
|
|
306
388
|
function truncateDescription(value, maxLen) {
|
|
307
389
|
const trimmed = value.trim();
|
|
308
390
|
if (trimmed.length <= maxLen) return trimmed;
|
|
309
391
|
return `${trimmed.slice(0, maxLen - 3).trimEnd()}...`;
|
|
310
392
|
}
|
|
393
|
+
function getSkillDescriptionSuffix(skills, node, isCursor) {
|
|
394
|
+
if (!isCursor || !node.isLeaf || node.leafIndices.length !== 1) return "";
|
|
395
|
+
const skill = skills[node.leafIndices[0]];
|
|
396
|
+
if (!skill?.description) return "";
|
|
397
|
+
const description = truncateDescription(skill.description, 72);
|
|
398
|
+
if (!description) return "";
|
|
399
|
+
return chalk2.dim(` - ${description}`);
|
|
400
|
+
}
|
|
311
401
|
function getPlatformDisplay(platform) {
|
|
312
402
|
return PLATFORM_DISPLAY[platform] || platform;
|
|
313
403
|
}
|
|
@@ -322,25 +412,33 @@ async function promptSkillsInteractive(skills, options = {}) {
|
|
|
322
412
|
const isFullyInstalled = hasInstalledCheck && installedOnTargets.length === targetPlatforms.length;
|
|
323
413
|
if (options.defaultAll !== false && !isFullyInstalled) defaultSelected.add(i);
|
|
324
414
|
}
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
installedSuffix =
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
415
|
+
const selectedIndices = await interactiveTreeSelect(skills, {
|
|
416
|
+
title: "Select skills to install",
|
|
417
|
+
subtitle: "\u2191\u2193 navigate \u2022 Space toggle \u2022 Enter confirm",
|
|
418
|
+
buildTree: buildSkillTree,
|
|
419
|
+
formatNode: (node, selection, isCursor) => {
|
|
420
|
+
let installedSuffix = "";
|
|
421
|
+
if (node.isLeaf && node.leafIndices.length === 1) {
|
|
422
|
+
const skill = skills[node.leafIndices[0]];
|
|
423
|
+
if (skill?.installedPlatforms?.length) {
|
|
424
|
+
if (skill.installedPlatforms.length === targetPlatforms.length && targetPlatforms.length > 0) {
|
|
425
|
+
installedSuffix = chalk2.dim(" [installed]");
|
|
426
|
+
} else if (skill.installedPlatforms.length > 0) {
|
|
427
|
+
installedSuffix = chalk2.dim(` [installed on ${skill.installedPlatforms.length}]`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const descriptionSuffix = getSkillDescriptionSuffix(skills, node, isCursor);
|
|
432
|
+
const formatted = formatTreeNode(node, selection, isCursor, { suffix: `${installedSuffix}${descriptionSuffix}` });
|
|
433
|
+
if (isCursor && installedSuffix && selection.state !== "all") {
|
|
434
|
+
return formatted.replace("\u2190 Space to select", "\u2190 Space to reinstall");
|
|
435
|
+
}
|
|
436
|
+
return formatted;
|
|
437
|
+
},
|
|
438
|
+
defaultAll: false,
|
|
439
|
+
defaultSelected
|
|
343
440
|
});
|
|
441
|
+
if (!selectedIndices) return null;
|
|
344
442
|
const selectedSkills = selectedIndices.map((i) => skills[i]);
|
|
345
443
|
const names = selectedSkills.map((s) => s.relPath === "." ? s.suggestedSource : s.relPath);
|
|
346
444
|
console.log(chalk2.green(`
|
|
@@ -378,26 +476,17 @@ async function promptSyncTargetsInteractive(choices) {
|
|
|
378
476
|
return selected;
|
|
379
477
|
}
|
|
380
478
|
async function promptSkillsTreeInteractive(skills, tree, options = {}) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
name: `${label}${desc ? chalk2.dim(desc) : ""}`,
|
|
391
|
-
value: i,
|
|
392
|
-
checked: defaultSelected.has(i)
|
|
393
|
-
};
|
|
394
|
-
});
|
|
395
|
-
const selectedIndices = await checkbox({
|
|
396
|
-
message: "Select skills",
|
|
397
|
-
choices,
|
|
398
|
-
loop: false,
|
|
399
|
-
pageSize: Math.min(20, choices.length)
|
|
479
|
+
const selectedIndices = await interactiveTreeSelect(skills, {
|
|
480
|
+
title: "Select skills from markdown",
|
|
481
|
+
subtitle: "\u2191\u2193 navigate \u2022 Space toggle \u2022 Enter confirm",
|
|
482
|
+
buildTree: () => buildTreeFromSkillNodes(tree, skills.length),
|
|
483
|
+
formatNode: (node, selection, isCursor) => {
|
|
484
|
+
const descriptionSuffix = getSkillDescriptionSuffix(skills, node, isCursor);
|
|
485
|
+
return formatTreeNode(node, selection, isCursor, { suffix: descriptionSuffix });
|
|
486
|
+
},
|
|
487
|
+
defaultAll: options.defaultAll !== false
|
|
400
488
|
});
|
|
489
|
+
if (!selectedIndices) return null;
|
|
401
490
|
const selectedSkills = selectedIndices.map((i) => skills[i]);
|
|
402
491
|
const names = selectedSkills.map((s) => s.displayName || s.relPath || s.suggestedSource);
|
|
403
492
|
console.log(chalk2.green(`
|