skild 0.10.14 → 0.10.16
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/chunk-U4SVSURE.js +299 -0
- package/dist/index.js +159 -354
- package/dist/ui/interactive-tree-prompt.d.ts +38 -0
- package/dist/ui/interactive-tree-prompt.js +12 -0
- package/package.json +4 -3
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// src/ui/interactive-tree-prompt.ts
|
|
2
|
+
import readline from "readline";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import stringWidth from "string-width";
|
|
5
|
+
var altScreenRefCount = 0;
|
|
6
|
+
var altScreenActive = false;
|
|
7
|
+
var altScreenExitTimer = null;
|
|
8
|
+
var altScreenStdout = null;
|
|
9
|
+
var postPromptLogs = [];
|
|
10
|
+
function enqueuePostPromptLog(message) {
|
|
11
|
+
postPromptLogs.push(message);
|
|
12
|
+
}
|
|
13
|
+
function flushPostPromptLogs(stdout) {
|
|
14
|
+
if (altScreenActive) return;
|
|
15
|
+
if (postPromptLogs.length === 0) return;
|
|
16
|
+
for (const msg of postPromptLogs.splice(0)) {
|
|
17
|
+
stdout.write(msg.endsWith("\n") ? msg : msg + "\n");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function enterAltScreen(stdout) {
|
|
21
|
+
if (altScreenExitTimer) {
|
|
22
|
+
clearTimeout(altScreenExitTimer);
|
|
23
|
+
altScreenExitTimer = null;
|
|
24
|
+
}
|
|
25
|
+
altScreenStdout = stdout;
|
|
26
|
+
if (!altScreenActive) {
|
|
27
|
+
stdout.write("\x1B[?1049h");
|
|
28
|
+
stdout.write("\x1B[H");
|
|
29
|
+
stdout.write("\x1B[2J");
|
|
30
|
+
altScreenActive = true;
|
|
31
|
+
}
|
|
32
|
+
altScreenRefCount += 1;
|
|
33
|
+
}
|
|
34
|
+
function exitAltScreenDeferred() {
|
|
35
|
+
if (altScreenExitTimer) return;
|
|
36
|
+
const stdout = altScreenStdout;
|
|
37
|
+
if (!stdout) return;
|
|
38
|
+
altScreenExitTimer = setTimeout(() => {
|
|
39
|
+
altScreenExitTimer = null;
|
|
40
|
+
if (altScreenRefCount !== 0) return;
|
|
41
|
+
if (!altScreenActive) return;
|
|
42
|
+
stdout.write("\x1B[?1049l");
|
|
43
|
+
altScreenActive = false;
|
|
44
|
+
flushPostPromptLogs(stdout);
|
|
45
|
+
}, 200);
|
|
46
|
+
}
|
|
47
|
+
function leaveAltScreen(stdout) {
|
|
48
|
+
if (altScreenStdout && altScreenStdout !== stdout) {
|
|
49
|
+
altScreenStdout.write("\x1B[?1049l");
|
|
50
|
+
altScreenActive = false;
|
|
51
|
+
altScreenStdout = stdout;
|
|
52
|
+
}
|
|
53
|
+
altScreenRefCount = Math.max(0, altScreenRefCount - 1);
|
|
54
|
+
if (altScreenRefCount === 0) exitAltScreenDeferred();
|
|
55
|
+
}
|
|
56
|
+
function flushInteractiveUiNow() {
|
|
57
|
+
const stdout = altScreenStdout || process.stdout;
|
|
58
|
+
if (altScreenExitTimer) {
|
|
59
|
+
clearTimeout(altScreenExitTimer);
|
|
60
|
+
altScreenExitTimer = null;
|
|
61
|
+
}
|
|
62
|
+
if (altScreenRefCount !== 0) return;
|
|
63
|
+
if (altScreenActive) {
|
|
64
|
+
stdout.write("\x1B[?1049l");
|
|
65
|
+
altScreenActive = false;
|
|
66
|
+
}
|
|
67
|
+
flushPostPromptLogs(stdout);
|
|
68
|
+
}
|
|
69
|
+
function getNodeSelection(node, selected) {
|
|
70
|
+
const total = node.leafIndices.length;
|
|
71
|
+
if (total === 0) return { state: "none", selectedCount: 0 };
|
|
72
|
+
let selectedCount = 0;
|
|
73
|
+
for (const idx of node.leafIndices) {
|
|
74
|
+
if (selected.has(idx)) selectedCount += 1;
|
|
75
|
+
}
|
|
76
|
+
let state = "none";
|
|
77
|
+
if (selectedCount === total) state = "all";
|
|
78
|
+
else if (selectedCount > 0) state = "partial";
|
|
79
|
+
return { state, selectedCount };
|
|
80
|
+
}
|
|
81
|
+
function flattenTree(root) {
|
|
82
|
+
const result = [];
|
|
83
|
+
function walk(node) {
|
|
84
|
+
if (node.id !== "") result.push(node);
|
|
85
|
+
for (const child of node.children) walk(child);
|
|
86
|
+
}
|
|
87
|
+
for (const child of root.children) walk(child);
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
function createRenderer(title, subtitle, flatNodes, selected, getCursor, getViewOffset, viewHeight, maxWidth, formatNode) {
|
|
91
|
+
function renderContent() {
|
|
92
|
+
const lines = [];
|
|
93
|
+
lines.push(chalk.bold.cyan(title));
|
|
94
|
+
lines.push(chalk.dim(subtitle));
|
|
95
|
+
lines.push("");
|
|
96
|
+
const cursor = getCursor();
|
|
97
|
+
const offset = getViewOffset();
|
|
98
|
+
const end = Math.min(flatNodes.length, offset + viewHeight);
|
|
99
|
+
for (let i = offset; i < end; i++) {
|
|
100
|
+
const node = flatNodes[i];
|
|
101
|
+
const selection = getNodeSelection(node, selected);
|
|
102
|
+
lines.push(formatNode(node, selection, i === cursor, maxWidth));
|
|
103
|
+
}
|
|
104
|
+
lines.push("");
|
|
105
|
+
lines.push(chalk.dim(`Space toggle \u2022 Enter confirm \u2022 A select all \u2022 Ctrl+C cancel`));
|
|
106
|
+
lines.push(chalk.dim(`Showing ${end - offset}/${flatNodes.length} (offset ${offset + 1})`));
|
|
107
|
+
return lines;
|
|
108
|
+
}
|
|
109
|
+
function getLineCount() {
|
|
110
|
+
return 6 + Math.min(viewHeight, flatNodes.length);
|
|
111
|
+
}
|
|
112
|
+
return { renderContent, getLineCount };
|
|
113
|
+
}
|
|
114
|
+
function writeToTerminal(stdout, lines) {
|
|
115
|
+
for (const line of lines) {
|
|
116
|
+
stdout.write(line + "\n");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function clearAndRerender(stdout, lines) {
|
|
120
|
+
stdout.write("\x1B[?25l");
|
|
121
|
+
stdout.write("\x1B[H");
|
|
122
|
+
stdout.write("\x1B[2J");
|
|
123
|
+
writeToTerminal(stdout, lines);
|
|
124
|
+
}
|
|
125
|
+
async function interactiveTreeSelect(items, options) {
|
|
126
|
+
const { title, subtitle, buildTree, formatNode, defaultAll, defaultSelected } = options;
|
|
127
|
+
const root = buildTree(items);
|
|
128
|
+
const flatNodes = flattenTree(root);
|
|
129
|
+
if (flatNodes.length === 0) return null;
|
|
130
|
+
const stdin = process.stdin;
|
|
131
|
+
const stdout = process.stdout;
|
|
132
|
+
if (!stdin.isTTY || !stdout.isTTY) {
|
|
133
|
+
if (defaultSelected) return Array.from(defaultSelected);
|
|
134
|
+
return defaultAll ? items.map((_, i) => i) : null;
|
|
135
|
+
}
|
|
136
|
+
const cols = typeof stdout.columns === "number" ? stdout.columns : 120;
|
|
137
|
+
const rows = typeof stdout.rows === "number" ? stdout.rows : 24;
|
|
138
|
+
const viewHeight = Math.max(5, rows - 8);
|
|
139
|
+
const maxWidth = Math.max(40, cols - 2);
|
|
140
|
+
const selected = /* @__PURE__ */ new Set();
|
|
141
|
+
if (defaultSelected) {
|
|
142
|
+
for (const idx of defaultSelected) selected.add(idx);
|
|
143
|
+
} else if (defaultAll) {
|
|
144
|
+
for (let i = 0; i < items.length; i++) selected.add(i);
|
|
145
|
+
}
|
|
146
|
+
let cursor = 0;
|
|
147
|
+
let viewOffset = 0;
|
|
148
|
+
const wasRaw = Boolean(stdin.isRaw);
|
|
149
|
+
stdin.setRawMode(true);
|
|
150
|
+
stdin.resume();
|
|
151
|
+
readline.emitKeypressEvents(stdin);
|
|
152
|
+
enterAltScreen(stdout);
|
|
153
|
+
const renderer = createRenderer(
|
|
154
|
+
title,
|
|
155
|
+
subtitle,
|
|
156
|
+
flatNodes,
|
|
157
|
+
selected,
|
|
158
|
+
() => cursor,
|
|
159
|
+
() => viewOffset,
|
|
160
|
+
viewHeight,
|
|
161
|
+
maxWidth,
|
|
162
|
+
formatNode
|
|
163
|
+
);
|
|
164
|
+
clearAndRerender(stdout, renderer.renderContent());
|
|
165
|
+
return new Promise((resolve) => {
|
|
166
|
+
function cleanup() {
|
|
167
|
+
stdin.setRawMode(wasRaw);
|
|
168
|
+
stdin.pause();
|
|
169
|
+
stdin.removeListener("keypress", onKeypress);
|
|
170
|
+
stdout.write("\x1B[?25h");
|
|
171
|
+
leaveAltScreen(stdout);
|
|
172
|
+
}
|
|
173
|
+
function rerender() {
|
|
174
|
+
clearAndRerender(stdout, renderer.renderContent());
|
|
175
|
+
}
|
|
176
|
+
function toggleNode(node) {
|
|
177
|
+
const { state } = getNodeSelection(node, selected);
|
|
178
|
+
const shouldSelectAll = state !== "all";
|
|
179
|
+
if (shouldSelectAll) {
|
|
180
|
+
for (const idx of node.leafIndices) selected.add(idx);
|
|
181
|
+
} else {
|
|
182
|
+
for (const idx of node.leafIndices) selected.delete(idx);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function toggleAll() {
|
|
186
|
+
if (selected.size === items.length) {
|
|
187
|
+
selected.clear();
|
|
188
|
+
} else {
|
|
189
|
+
for (let i = 0; i < items.length; i++) selected.add(i);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function onKeypress(_str, key) {
|
|
193
|
+
if (key.ctrl && key.name === "c") {
|
|
194
|
+
cleanup();
|
|
195
|
+
resolve(null);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (key.name === "return" || key.name === "enter") {
|
|
199
|
+
cleanup();
|
|
200
|
+
resolve(selected.size > 0 ? Array.from(selected) : null);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (key.name === "up") {
|
|
204
|
+
if (cursor === 0) return;
|
|
205
|
+
cursor -= 1;
|
|
206
|
+
if (cursor < viewOffset) viewOffset = cursor;
|
|
207
|
+
rerender();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (key.name === "down") {
|
|
211
|
+
if (cursor === flatNodes.length - 1) return;
|
|
212
|
+
cursor += 1;
|
|
213
|
+
if (cursor >= viewOffset + viewHeight) viewOffset = cursor - viewHeight + 1;
|
|
214
|
+
rerender();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (key.name === "space") {
|
|
218
|
+
toggleNode(flatNodes[cursor]);
|
|
219
|
+
rerender();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (key.name === "a") {
|
|
223
|
+
toggleAll();
|
|
224
|
+
rerender();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
stdin.on("keypress", onKeypress);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
function truncateVisible(value, maxLen) {
|
|
232
|
+
let width = 0;
|
|
233
|
+
let result = "";
|
|
234
|
+
for (const ch of value) {
|
|
235
|
+
const w = stringWidth(ch);
|
|
236
|
+
if (width + w > maxLen - 1) {
|
|
237
|
+
result += "\u2026";
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
width += w;
|
|
241
|
+
result += ch;
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
function truncateMiddleVisible(value, maxLen) {
|
|
246
|
+
if (stringWidth(value) <= maxLen) return value;
|
|
247
|
+
if (maxLen <= 1) return "\u2026";
|
|
248
|
+
const leftMax = Math.max(1, Math.floor((maxLen - 1) / 2));
|
|
249
|
+
const rightMax = Math.max(1, maxLen - 1 - leftMax);
|
|
250
|
+
const left = truncateVisible(value, leftMax).replace(/…$/, "");
|
|
251
|
+
let width = 0;
|
|
252
|
+
let right = "";
|
|
253
|
+
for (let i = value.length - 1; i >= 0; i--) {
|
|
254
|
+
const ch = value[i];
|
|
255
|
+
const w = stringWidth(ch);
|
|
256
|
+
if (width + w > rightMax) break;
|
|
257
|
+
width += w;
|
|
258
|
+
right = ch + right;
|
|
259
|
+
}
|
|
260
|
+
if (!right) right = value[value.length - 1] || "";
|
|
261
|
+
return `${left}\u2026${right}`;
|
|
262
|
+
}
|
|
263
|
+
function formatInteractiveRow(input) {
|
|
264
|
+
const styleName = input.styleName || ((s) => s);
|
|
265
|
+
const prefix = `${input.cursorMark}${input.indent}${input.glyph} `;
|
|
266
|
+
const fixedWidth = stringWidth(prefix) + stringWidth(input.count);
|
|
267
|
+
let rawName = input.name;
|
|
268
|
+
let rawSuffix = input.suffixText || "";
|
|
269
|
+
let rawHint = input.hintText || "";
|
|
270
|
+
const fits = (n, s, h) => {
|
|
271
|
+
const nStyled = input.isCursor ? styleName(n) : n;
|
|
272
|
+
const sStyled = s ? chalk.dim(s) : "";
|
|
273
|
+
const hStyled = h ? chalk.dim(h) : "";
|
|
274
|
+
return stringWidth(`${prefix}${nStyled}${input.count}${sStyled}${hStyled}`) <= input.maxWidth;
|
|
275
|
+
};
|
|
276
|
+
if (rawSuffix && !fits(rawName, rawSuffix, rawHint)) {
|
|
277
|
+
const available = Math.max(1, input.maxWidth - fixedWidth - stringWidth(rawName) - stringWidth(rawHint));
|
|
278
|
+
rawSuffix = truncateVisible(rawSuffix, available);
|
|
279
|
+
}
|
|
280
|
+
if (rawHint && !fits(rawName, rawSuffix, rawHint)) {
|
|
281
|
+
const available = Math.max(1, input.maxWidth - fixedWidth - stringWidth(rawName) - stringWidth(rawSuffix));
|
|
282
|
+
rawHint = truncateVisible(rawHint, available);
|
|
283
|
+
}
|
|
284
|
+
if (!fits(rawName, rawSuffix, rawHint)) {
|
|
285
|
+
const available = Math.max(1, input.maxWidth - fixedWidth - stringWidth(rawSuffix) - stringWidth(rawHint));
|
|
286
|
+
rawName = truncateMiddleVisible(rawName, available);
|
|
287
|
+
}
|
|
288
|
+
const finalName = input.isCursor ? styleName(rawName) : rawName;
|
|
289
|
+
const suffix = rawSuffix ? chalk.dim(rawSuffix) : "";
|
|
290
|
+
const hint = rawHint ? chalk.dim(rawHint) : "";
|
|
291
|
+
return `${prefix}${finalName}${input.count}${suffix}${hint}`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export {
|
|
295
|
+
enqueuePostPromptLog,
|
|
296
|
+
flushInteractiveUiNow,
|
|
297
|
+
interactiveTreeSelect,
|
|
298
|
+
formatInteractiveRow
|
|
299
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
enqueuePostPromptLog,
|
|
4
|
+
flushInteractiveUiNow,
|
|
5
|
+
formatInteractiveRow,
|
|
6
|
+
interactiveTreeSelect
|
|
7
|
+
} from "./chunk-U4SVSURE.js";
|
|
2
8
|
|
|
3
9
|
// src/index.ts
|
|
4
10
|
import { Command } from "commander";
|
|
@@ -86,10 +92,41 @@ var logger = {
|
|
|
86
92
|
};
|
|
87
93
|
|
|
88
94
|
// src/utils/interactive-select.ts
|
|
89
|
-
import readline from "readline";
|
|
90
|
-
import stringWidth from "string-width";
|
|
91
95
|
import chalk2 from "chalk";
|
|
92
96
|
import { PLATFORMS } from "@skild/core";
|
|
97
|
+
var PLATFORM_DISPLAY = {
|
|
98
|
+
claude: "Claude",
|
|
99
|
+
codex: "Codex",
|
|
100
|
+
copilot: "Copilot",
|
|
101
|
+
antigravity: "Antigravity",
|
|
102
|
+
opencode: "OpenCode",
|
|
103
|
+
cursor: "Cursor",
|
|
104
|
+
windsurf: "Windsurf"
|
|
105
|
+
};
|
|
106
|
+
function createTreeNode(id, name, depth, isLeaf, leafIndices = []) {
|
|
107
|
+
return { id, name, depth, children: [], leafIndices, isLeaf };
|
|
108
|
+
}
|
|
109
|
+
function wrapWithRoot(allNode) {
|
|
110
|
+
return {
|
|
111
|
+
id: "",
|
|
112
|
+
name: ".",
|
|
113
|
+
depth: 0,
|
|
114
|
+
children: [allNode],
|
|
115
|
+
leafIndices: [...allNode.leafIndices],
|
|
116
|
+
isLeaf: false
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function adjustDepth(node, delta) {
|
|
120
|
+
node.depth += delta;
|
|
121
|
+
for (const child of node.children) adjustDepth(child, delta);
|
|
122
|
+
}
|
|
123
|
+
function collapseIntermediateNodes(allNode) {
|
|
124
|
+
while (allNode.children.length === 1 && !allNode.children[0].isLeaf && allNode.children[0].children.length > 0) {
|
|
125
|
+
const singleChild = allNode.children[0];
|
|
126
|
+
for (const grandchild of singleChild.children) adjustDepth(grandchild, -1);
|
|
127
|
+
allNode.children = singleChild.children;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
93
130
|
function buildSkillTree(skills) {
|
|
94
131
|
const allNode = createTreeNode("all", "All Skills", 1, false);
|
|
95
132
|
for (let i = 0; i < skills.length; i++) {
|
|
@@ -112,39 +149,6 @@ function buildSkillTree(skills) {
|
|
|
112
149
|
collapseIntermediateNodes(allNode);
|
|
113
150
|
return wrapWithRoot(allNode);
|
|
114
151
|
}
|
|
115
|
-
function buildSyncTree(choices) {
|
|
116
|
-
const root = createTreeNode("root", "All targets", 1, false);
|
|
117
|
-
const platforms = /* @__PURE__ */ new Map();
|
|
118
|
-
for (let i = 0; i < choices.length; i++) {
|
|
119
|
-
const choice = choices[i];
|
|
120
|
-
let platformNode = platforms.get(choice.targetPlatform);
|
|
121
|
-
if (!platformNode) {
|
|
122
|
-
platformNode = createTreeNode(choice.targetPlatform, choice.targetPlatform, 2, false);
|
|
123
|
-
platforms.set(choice.targetPlatform, platformNode);
|
|
124
|
-
root.children.push(platformNode);
|
|
125
|
-
}
|
|
126
|
-
const skillNode = createTreeNode(
|
|
127
|
-
`${choice.targetPlatform}:${choice.skill}`,
|
|
128
|
-
choice.displayName,
|
|
129
|
-
3,
|
|
130
|
-
true,
|
|
131
|
-
[i]
|
|
132
|
-
);
|
|
133
|
-
platformNode.children.push(skillNode);
|
|
134
|
-
platformNode.leafIndices.push(i);
|
|
135
|
-
root.leafIndices.push(i);
|
|
136
|
-
}
|
|
137
|
-
return wrapWithRoot(root);
|
|
138
|
-
}
|
|
139
|
-
function buildPlatformTree(items) {
|
|
140
|
-
const allNode = createTreeNode("all", "All Platforms", 1, false);
|
|
141
|
-
for (let i = 0; i < items.length; i++) {
|
|
142
|
-
const platform = items[i].platform;
|
|
143
|
-
allNode.children.push(createTreeNode(platform, platform, 2, true, [i]));
|
|
144
|
-
allNode.leafIndices.push(i);
|
|
145
|
-
}
|
|
146
|
-
return wrapWithRoot(allNode);
|
|
147
|
-
}
|
|
148
152
|
function buildTreeFromSkillNodes(nodes, totalSkills) {
|
|
149
153
|
const allNode = createTreeNode("all", "All Skills", 1, false);
|
|
150
154
|
const attach = (node, depth) => {
|
|
@@ -174,280 +178,38 @@ function buildTreeFromSkillNodes(nodes, totalSkills) {
|
|
|
174
178
|
}
|
|
175
179
|
return wrapWithRoot(allNode);
|
|
176
180
|
}
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
function collapseIntermediateNodes(allNode) {
|
|
184
|
-
while (allNode.children.length === 1 && !allNode.children[0].isLeaf && allNode.children[0].children.length > 0) {
|
|
185
|
-
const singleChild = allNode.children[0];
|
|
186
|
-
for (const grandchild of singleChild.children) {
|
|
187
|
-
adjustDepth(grandchild, -1);
|
|
188
|
-
}
|
|
189
|
-
allNode.children = singleChild.children;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
function adjustDepth(node, delta) {
|
|
193
|
-
node.depth += delta;
|
|
194
|
-
for (const child of node.children) {
|
|
195
|
-
adjustDepth(child, delta);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
function flattenTree(root) {
|
|
199
|
-
const result = [];
|
|
200
|
-
function walk(node) {
|
|
201
|
-
if (node.id !== "") result.push(node);
|
|
202
|
-
for (const child of node.children) walk(child);
|
|
203
|
-
}
|
|
204
|
-
for (const child of root.children) walk(child);
|
|
205
|
-
return result;
|
|
206
|
-
}
|
|
207
|
-
function getNodeSelection(node, selected) {
|
|
208
|
-
const total = node.leafIndices.length;
|
|
209
|
-
if (total === 0) return { state: "none", selectedCount: 0 };
|
|
210
|
-
let selectedCount = 0;
|
|
211
|
-
for (const idx of node.leafIndices) {
|
|
212
|
-
if (selected.has(idx)) selectedCount++;
|
|
213
|
-
}
|
|
214
|
-
let state = "none";
|
|
215
|
-
if (selectedCount === total) state = "all";
|
|
216
|
-
else if (selectedCount > 0) state = "partial";
|
|
217
|
-
return { state, selectedCount };
|
|
218
|
-
}
|
|
219
|
-
function createRenderer(title, subtitle, flatNodes, selected, getCursor, getViewOffset, viewHeight, maxWidth, formatNode) {
|
|
220
|
-
function renderContent() {
|
|
221
|
-
const lines = [];
|
|
222
|
-
lines.push(chalk2.bold.cyan(title));
|
|
223
|
-
lines.push(chalk2.dim(subtitle));
|
|
224
|
-
lines.push("");
|
|
225
|
-
const cursor = getCursor();
|
|
226
|
-
const offset = getViewOffset();
|
|
227
|
-
const end = Math.min(flatNodes.length, offset + viewHeight);
|
|
228
|
-
for (let i = offset; i < end; i++) {
|
|
229
|
-
const node = flatNodes[i];
|
|
230
|
-
const selection = getNodeSelection(node, selected);
|
|
231
|
-
lines.push(formatNode(node, selection, i === cursor, maxWidth));
|
|
232
|
-
}
|
|
233
|
-
lines.push(chalk2.dim(`
|
|
234
|
-
Space toggle \u2022 Enter confirm \u2022 A select all \u2022 Ctrl+C cancel`));
|
|
235
|
-
lines.push(chalk2.dim(`Showing ${end - offset}/${flatNodes.length} (offset ${offset + 1})`));
|
|
236
|
-
return lines;
|
|
237
|
-
}
|
|
238
|
-
function getLineCount() {
|
|
239
|
-
return 5 + Math.min(viewHeight, flatNodes.length);
|
|
240
|
-
}
|
|
241
|
-
return { renderContent, getLineCount };
|
|
242
|
-
}
|
|
243
|
-
function writeToTerminal(stdout, lines) {
|
|
244
|
-
for (const line of lines) {
|
|
245
|
-
stdout.write(line + "\n");
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
function clearAndRerender(stdout, lineCount, lines) {
|
|
249
|
-
stdout.write("\x1B[?25l");
|
|
250
|
-
stdout.write("\x1B[H");
|
|
251
|
-
stdout.write("\x1B[2J");
|
|
252
|
-
writeToTerminal(stdout, lines);
|
|
253
|
-
}
|
|
254
|
-
async function interactiveTreeSelect(items, options) {
|
|
255
|
-
const { title, subtitle, buildTree, formatNode, defaultAll, defaultSelected } = options;
|
|
256
|
-
const root = buildTree(items);
|
|
257
|
-
const flatNodes = flattenTree(root);
|
|
258
|
-
if (flatNodes.length === 0) return null;
|
|
259
|
-
const stdout = process.stdout;
|
|
260
|
-
const cols = typeof stdout.columns === "number" ? stdout.columns : 120;
|
|
261
|
-
const rows = typeof stdout.rows === "number" ? stdout.rows : 24;
|
|
262
|
-
const viewHeight = Math.max(5, rows - 5);
|
|
263
|
-
const maxWidth = Math.max(40, cols - 2);
|
|
264
|
-
const selected = /* @__PURE__ */ new Set();
|
|
265
|
-
if (defaultSelected) {
|
|
266
|
-
for (const idx of defaultSelected) selected.add(idx);
|
|
267
|
-
} else if (defaultAll) {
|
|
268
|
-
for (let i = 0; i < items.length; i++) selected.add(i);
|
|
269
|
-
}
|
|
270
|
-
let cursor = 0;
|
|
271
|
-
let viewOffset = 0;
|
|
272
|
-
const stdin = process.stdin;
|
|
273
|
-
if (!stdin.isTTY || !stdout.isTTY) {
|
|
274
|
-
return defaultAll ? Array.from(selected) : null;
|
|
275
|
-
}
|
|
276
|
-
const useAltScreen = true;
|
|
277
|
-
const wasRaw = Boolean(stdin.isRaw);
|
|
278
|
-
stdin.setRawMode(true);
|
|
279
|
-
stdin.resume();
|
|
280
|
-
readline.emitKeypressEvents(stdin);
|
|
281
|
-
if (useAltScreen) {
|
|
282
|
-
stdout.write("\x1B[?1049h");
|
|
283
|
-
}
|
|
284
|
-
const renderer = createRenderer(
|
|
285
|
-
title,
|
|
286
|
-
subtitle,
|
|
287
|
-
flatNodes,
|
|
288
|
-
selected,
|
|
289
|
-
() => cursor,
|
|
290
|
-
() => viewOffset,
|
|
291
|
-
viewHeight,
|
|
292
|
-
maxWidth,
|
|
293
|
-
(node, selection, isCursor) => formatNode(node, selection, isCursor, maxWidth)
|
|
294
|
-
);
|
|
295
|
-
writeToTerminal(stdout, renderer.renderContent());
|
|
296
|
-
return new Promise((resolve) => {
|
|
297
|
-
function cleanup(clear = false) {
|
|
298
|
-
if (clear && !useAltScreen) {
|
|
299
|
-
const lineCount = renderer.getLineCount();
|
|
300
|
-
stdout.write(`\x1B[${lineCount}A`);
|
|
301
|
-
stdout.write("\x1B[0J");
|
|
302
|
-
}
|
|
303
|
-
stdin.setRawMode(wasRaw);
|
|
304
|
-
stdin.pause();
|
|
305
|
-
stdin.removeListener("keypress", onKeypress);
|
|
306
|
-
stdout.write("\x1B[?25h");
|
|
307
|
-
if (useAltScreen) {
|
|
308
|
-
stdout.write("\x1B[?1049l");
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
function rerender() {
|
|
312
|
-
clearAndRerender(stdout, renderer.getLineCount(), renderer.renderContent());
|
|
313
|
-
}
|
|
314
|
-
function toggleNode(node) {
|
|
315
|
-
const { state } = getNodeSelection(node, selected);
|
|
316
|
-
const shouldSelectAll = state !== "all";
|
|
317
|
-
if (shouldSelectAll) {
|
|
318
|
-
for (const idx of node.leafIndices) selected.add(idx);
|
|
319
|
-
} else {
|
|
320
|
-
for (const idx of node.leafIndices) selected.delete(idx);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
function toggleAll() {
|
|
324
|
-
if (selected.size === items.length) {
|
|
325
|
-
selected.clear();
|
|
326
|
-
} else {
|
|
327
|
-
for (let i = 0; i < items.length; i++) selected.add(i);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
function onKeypress(_str, key) {
|
|
331
|
-
if (key.ctrl && key.name === "c") {
|
|
332
|
-
cleanup(true);
|
|
333
|
-
resolve(null);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
if (key.name === "return" || key.name === "enter") {
|
|
337
|
-
cleanup(true);
|
|
338
|
-
resolve(selected.size > 0 ? Array.from(selected) : null);
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
if (key.name === "up") {
|
|
342
|
-
cursor = (cursor - 1 + flatNodes.length) % flatNodes.length;
|
|
343
|
-
if (cursor < viewOffset) viewOffset = cursor;
|
|
344
|
-
rerender();
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
if (key.name === "down") {
|
|
348
|
-
cursor = (cursor + 1) % flatNodes.length;
|
|
349
|
-
if (cursor >= viewOffset + viewHeight) viewOffset = cursor - viewHeight + 1;
|
|
350
|
-
rerender();
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
if (key.name === "space") {
|
|
354
|
-
toggleNode(flatNodes[cursor]);
|
|
355
|
-
rerender();
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
if (key.name === "a") {
|
|
359
|
-
toggleAll();
|
|
360
|
-
rerender();
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
stdin.on("keypress", onKeypress);
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
var PLATFORM_DISPLAY = {
|
|
368
|
-
claude: "Claude",
|
|
369
|
-
codex: "Codex",
|
|
370
|
-
copilot: "Copilot",
|
|
371
|
-
antigravity: "Antigravity",
|
|
372
|
-
opencode: "OpenCode",
|
|
373
|
-
cursor: "Cursor",
|
|
374
|
-
windsurf: "Windsurf"
|
|
375
|
-
};
|
|
376
|
-
function truncateVisible(value, maxLen) {
|
|
377
|
-
let width = 0;
|
|
378
|
-
let result = "";
|
|
379
|
-
for (const ch of value) {
|
|
380
|
-
const w = stringWidth(ch);
|
|
381
|
-
if (width + w > maxLen - 1) {
|
|
382
|
-
result += "\u2026";
|
|
383
|
-
return result;
|
|
384
|
-
}
|
|
385
|
-
width += w;
|
|
386
|
-
result += ch;
|
|
181
|
+
function buildPlatformTree(items) {
|
|
182
|
+
const allNode = createTreeNode("all", "All Platforms", 1, false);
|
|
183
|
+
for (let i = 0; i < items.length; i++) {
|
|
184
|
+
const platform = items[i].platform;
|
|
185
|
+
allNode.children.push(createTreeNode(platform, platform, 2, true, [i]));
|
|
186
|
+
allNode.leafIndices.push(i);
|
|
387
187
|
}
|
|
388
|
-
return
|
|
188
|
+
return wrapWithRoot(allNode);
|
|
389
189
|
}
|
|
390
|
-
function
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const w = stringWidth(ch);
|
|
401
|
-
if (width + w > rightMax) break;
|
|
402
|
-
width += w;
|
|
403
|
-
right = ch + right;
|
|
404
|
-
}
|
|
405
|
-
if (!right) right = value[value.length - 1] || "";
|
|
406
|
-
return `${left}\u2026${right}`;
|
|
407
|
-
}
|
|
408
|
-
function formatTreeNode(node, selection, isCursor, options = {}) {
|
|
409
|
-
const { state, selectedCount } = selection;
|
|
410
|
-
const totalCount = node.leafIndices.length;
|
|
411
|
-
const indent = " ".repeat(node.depth - 1);
|
|
412
|
-
const checkbox = state === "all" ? chalk2.green("\u25CF") : state === "partial" ? chalk2.yellow("\u25D0") : chalk2.dim("\u25CB");
|
|
413
|
-
const baseName = node.name || "";
|
|
414
|
-
let name = isCursor ? chalk2.cyan.underline(baseName) : baseName;
|
|
415
|
-
const cursorMark = isCursor ? chalk2.cyan("\u203A ") : " ";
|
|
416
|
-
let count = "";
|
|
417
|
-
if (totalCount > 1) {
|
|
418
|
-
count = chalk2.dim(` (${selectedCount}/${totalCount})`);
|
|
419
|
-
}
|
|
420
|
-
let rawSuffix = options.suffixText || "";
|
|
421
|
-
let rawHint = isCursor ? options.hintText || "" : "";
|
|
422
|
-
let hint = rawHint ? chalk2.dim(rawHint) : "";
|
|
423
|
-
let suffix = rawSuffix ? chalk2.dim(rawSuffix) : "";
|
|
424
|
-
const prefix = `${cursorMark}${indent}${checkbox} `;
|
|
425
|
-
const maxWidth = options.maxWidth;
|
|
426
|
-
if (maxWidth) {
|
|
427
|
-
const fixedWidth = stringWidth(prefix) + stringWidth(count);
|
|
428
|
-
const fits = (n, s, h) => {
|
|
429
|
-
const nStyled = isCursor ? chalk2.cyan.underline(n) : n;
|
|
430
|
-
const sStyled = s ? chalk2.dim(s) : "";
|
|
431
|
-
const hStyled = h ? chalk2.dim(h) : "";
|
|
432
|
-
return stringWidth(`${prefix}${nStyled}${count}${sStyled}${hStyled}`) <= maxWidth;
|
|
433
|
-
};
|
|
434
|
-
if (rawSuffix && !fits(baseName, rawSuffix, rawHint)) {
|
|
435
|
-
const available = Math.max(1, maxWidth - fixedWidth - stringWidth(baseName) - stringWidth(rawHint));
|
|
436
|
-
rawSuffix = truncateVisible(rawSuffix, available);
|
|
437
|
-
suffix = chalk2.dim(rawSuffix);
|
|
438
|
-
}
|
|
439
|
-
if (rawHint && !fits(baseName, rawSuffix, rawHint)) {
|
|
440
|
-
const available = Math.max(1, maxWidth - fixedWidth - stringWidth(baseName) - stringWidth(rawSuffix));
|
|
441
|
-
rawHint = truncateVisible(rawHint, available);
|
|
442
|
-
hint = chalk2.dim(rawHint);
|
|
443
|
-
}
|
|
444
|
-
if (!fits(baseName, rawSuffix, rawHint)) {
|
|
445
|
-
const available = Math.max(1, maxWidth - fixedWidth - stringWidth(rawSuffix) - stringWidth(rawHint));
|
|
446
|
-
const truncated = truncateMiddleVisible(baseName, available);
|
|
447
|
-
name = isCursor ? chalk2.cyan.underline(truncated) : truncated;
|
|
190
|
+
function buildSyncTree(choices) {
|
|
191
|
+
const root = createTreeNode("root", "All targets", 1, false);
|
|
192
|
+
const platforms = /* @__PURE__ */ new Map();
|
|
193
|
+
for (let i = 0; i < choices.length; i++) {
|
|
194
|
+
const choice = choices[i];
|
|
195
|
+
let platformNode = platforms.get(choice.targetPlatform);
|
|
196
|
+
if (!platformNode) {
|
|
197
|
+
platformNode = createTreeNode(choice.targetPlatform, choice.targetPlatform, 2, false);
|
|
198
|
+
platforms.set(choice.targetPlatform, platformNode);
|
|
199
|
+
root.children.push(platformNode);
|
|
448
200
|
}
|
|
201
|
+
const skillNode = createTreeNode(
|
|
202
|
+
`${choice.targetPlatform}:${choice.skill}`,
|
|
203
|
+
choice.displayName,
|
|
204
|
+
3,
|
|
205
|
+
true,
|
|
206
|
+
[i]
|
|
207
|
+
);
|
|
208
|
+
platformNode.children.push(skillNode);
|
|
209
|
+
platformNode.leafIndices.push(i);
|
|
210
|
+
root.leafIndices.push(i);
|
|
449
211
|
}
|
|
450
|
-
return
|
|
212
|
+
return wrapWithRoot(root);
|
|
451
213
|
}
|
|
452
214
|
function truncateDescription(value, maxLen) {
|
|
453
215
|
const trimmed = value.trim();
|
|
@@ -459,8 +221,7 @@ function getSkillDescriptionSuffix(skills, node, isCursor) {
|
|
|
459
221
|
const skill = skills[node.leafIndices[0]];
|
|
460
222
|
if (!skill?.description) return "";
|
|
461
223
|
const description = truncateDescription(skill.description, 72);
|
|
462
|
-
|
|
463
|
-
return ` - ${description}`;
|
|
224
|
+
return description ? ` - ${description}` : "";
|
|
464
225
|
}
|
|
465
226
|
function getPlatformDisplay(platform) {
|
|
466
227
|
return PLATFORM_DISPLAY[platform] || platform;
|
|
@@ -474,6 +235,26 @@ function buildSpaceHint(node, selection) {
|
|
|
474
235
|
}
|
|
475
236
|
return isLeaf ? " (Space: select)" : " (Space: select all)";
|
|
476
237
|
}
|
|
238
|
+
function formatTreeNode(node, selection, isCursor, maxWidth, options = {}) {
|
|
239
|
+
const { state, selectedCount } = selection;
|
|
240
|
+
const totalCount = node.leafIndices.length;
|
|
241
|
+
const indent = " ".repeat(Math.max(0, node.depth - 1));
|
|
242
|
+
const glyph = state === "all" ? chalk2.green("\u25CF") : state === "partial" ? chalk2.yellow("\u25D0") : chalk2.dim("\u25CB");
|
|
243
|
+
const cursorMark = isCursor ? chalk2.cyan("\u203A ") : " ";
|
|
244
|
+
const count = totalCount > 1 ? chalk2.dim(` (${selectedCount}/${totalCount})`) : "";
|
|
245
|
+
return formatInteractiveRow({
|
|
246
|
+
cursorMark,
|
|
247
|
+
indent,
|
|
248
|
+
glyph,
|
|
249
|
+
name: node.name || "",
|
|
250
|
+
count,
|
|
251
|
+
suffixText: options.suffixText || "",
|
|
252
|
+
hintText: isCursor ? options.hintText || "" : "",
|
|
253
|
+
isCursor,
|
|
254
|
+
maxWidth,
|
|
255
|
+
styleName: (s) => chalk2.cyan.underline(s)
|
|
256
|
+
});
|
|
257
|
+
}
|
|
477
258
|
async function promptSkillsInteractive(skills, options = {}) {
|
|
478
259
|
if (skills.length === 0) return null;
|
|
479
260
|
const targetPlatforms = options.targetPlatforms || [];
|
|
@@ -502,12 +283,10 @@ async function promptSkillsInteractive(skills, options = {}) {
|
|
|
502
283
|
}
|
|
503
284
|
}
|
|
504
285
|
const descriptionSuffix = getSkillDescriptionSuffix(skills, node, isCursor);
|
|
505
|
-
|
|
286
|
+
return formatTreeNode(node, selection, isCursor, maxWidth, {
|
|
506
287
|
suffixText: `${installedSuffixText}${descriptionSuffix}`,
|
|
507
|
-
hintText: buildSpaceHint(node, selection)
|
|
508
|
-
maxWidth
|
|
288
|
+
hintText: buildSpaceHint(node, selection)
|
|
509
289
|
});
|
|
510
|
-
return formatted;
|
|
511
290
|
},
|
|
512
291
|
defaultAll: false,
|
|
513
292
|
defaultSelected
|
|
@@ -515,40 +294,15 @@ async function promptSkillsInteractive(skills, options = {}) {
|
|
|
515
294
|
if (!selectedIndices) return null;
|
|
516
295
|
const selectedSkills = selectedIndices.map((i) => skills[i]);
|
|
517
296
|
const names = selectedSkills.map((s) => s.relPath === "." ? s.suggestedSource : s.relPath);
|
|
518
|
-
|
|
297
|
+
enqueuePostPromptLog(
|
|
298
|
+
chalk2.green(
|
|
299
|
+
`
|
|
519
300
|
\u2713 Selected ${selectedSkills.length} skill${selectedSkills.length > 1 ? "s" : ""}: ${chalk2.cyan(names.join(", "))}
|
|
520
|
-
`
|
|
301
|
+
`
|
|
302
|
+
)
|
|
303
|
+
);
|
|
521
304
|
return selectedSkills;
|
|
522
305
|
}
|
|
523
|
-
async function promptSyncTargetsInteractive(choices) {
|
|
524
|
-
if (choices.length === 0) return null;
|
|
525
|
-
const selectedIndices = await interactiveTreeSelect(choices, {
|
|
526
|
-
title: "Select sync targets",
|
|
527
|
-
subtitle: "\u2191\u2193 navigate \u2022 Space toggle \u2022 Enter confirm",
|
|
528
|
-
buildTree: buildSyncTree,
|
|
529
|
-
formatNode: (node, selection, isCursor, maxWidth) => {
|
|
530
|
-
if (!node.isLeaf && node.depth === 2) {
|
|
531
|
-
const display = getPlatformDisplay(node.name);
|
|
532
|
-
return formatTreeNode({ ...node, name: display }, selection, isCursor, { hintText: buildSpaceHint(node, selection), maxWidth });
|
|
533
|
-
}
|
|
534
|
-
if (node.isLeaf && node.leafIndices.length === 1) {
|
|
535
|
-
const choice = choices[node.leafIndices[0]];
|
|
536
|
-
const platformLabel = getPlatformDisplay(choice.sourcePlatform);
|
|
537
|
-
const suffixText = ` [from ${platformLabel} \xB7 ${choice.sourceTypeLabel}]`;
|
|
538
|
-
return formatTreeNode(node, selection, isCursor, { suffixText, hintText: buildSpaceHint(node, selection), maxWidth });
|
|
539
|
-
}
|
|
540
|
-
return formatTreeNode(node, selection, isCursor, { hintText: buildSpaceHint(node, selection), maxWidth });
|
|
541
|
-
},
|
|
542
|
-
defaultAll: true
|
|
543
|
-
});
|
|
544
|
-
if (!selectedIndices) return null;
|
|
545
|
-
const selected = selectedIndices.map((i) => choices[i]);
|
|
546
|
-
const summary = selected.map((c2) => `${c2.displayName}\u2192${getPlatformDisplay(c2.targetPlatform)}`).join(", ");
|
|
547
|
-
console.log(chalk2.green(`
|
|
548
|
-
\u2713 Syncing ${selected.length} target(s): ${chalk2.cyan(summary)}
|
|
549
|
-
`));
|
|
550
|
-
return selected;
|
|
551
|
-
}
|
|
552
306
|
async function promptSkillsTreeInteractive(skills, tree, options = {}) {
|
|
553
307
|
const selectedIndices = await interactiveTreeSelect(skills, {
|
|
554
308
|
title: "Select skills from markdown",
|
|
@@ -556,16 +310,23 @@ async function promptSkillsTreeInteractive(skills, tree, options = {}) {
|
|
|
556
310
|
buildTree: () => buildTreeFromSkillNodes(tree, skills.length),
|
|
557
311
|
formatNode: (node, selection, isCursor, maxWidth) => {
|
|
558
312
|
const descriptionSuffix = getSkillDescriptionSuffix(skills, node, isCursor);
|
|
559
|
-
return formatTreeNode(node, selection, isCursor,
|
|
313
|
+
return formatTreeNode(node, selection, isCursor, maxWidth, {
|
|
314
|
+
suffixText: descriptionSuffix,
|
|
315
|
+
hintText: buildSpaceHint(node, selection)
|
|
316
|
+
});
|
|
560
317
|
},
|
|
561
318
|
defaultAll: options.defaultAll !== false
|
|
562
319
|
});
|
|
563
320
|
if (!selectedIndices) return null;
|
|
564
321
|
const selectedSkills = selectedIndices.map((i) => skills[i]);
|
|
565
322
|
const names = selectedSkills.map((s) => s.displayName || s.relPath || s.suggestedSource);
|
|
566
|
-
|
|
323
|
+
enqueuePostPromptLog(
|
|
324
|
+
chalk2.green(
|
|
325
|
+
`
|
|
567
326
|
\u2713 Selected ${selectedSkills.length} skill${selectedSkills.length > 1 ? "s" : ""}: ${chalk2.cyan(names.join(", "))}
|
|
568
|
-
`
|
|
327
|
+
`
|
|
328
|
+
)
|
|
329
|
+
);
|
|
569
330
|
return selectedSkills;
|
|
570
331
|
}
|
|
571
332
|
async function promptPlatformsInteractive(options = {}) {
|
|
@@ -577,16 +338,57 @@ async function promptPlatformsInteractive(options = {}) {
|
|
|
577
338
|
buildTree: buildPlatformTree,
|
|
578
339
|
formatNode: (node, selection, isCursor, maxWidth) => {
|
|
579
340
|
const displayName = node.name === "All Platforms" ? node.name : PLATFORM_DISPLAY[node.name] || node.name;
|
|
580
|
-
|
|
581
|
-
|
|
341
|
+
return formatTreeNode({ ...node, name: displayName }, selection, isCursor, maxWidth, {
|
|
342
|
+
hintText: buildSpaceHint(node, selection)
|
|
343
|
+
});
|
|
582
344
|
},
|
|
583
345
|
defaultAll: options.defaultAll !== false
|
|
584
346
|
});
|
|
585
347
|
if (!selectedIndices) return null;
|
|
586
348
|
const selected = selectedIndices.map((i) => platforms[i]);
|
|
587
349
|
const names = selected.map((p) => PLATFORM_DISPLAY[p] || p);
|
|
588
|
-
|
|
350
|
+
enqueuePostPromptLog(
|
|
351
|
+
chalk2.green(
|
|
352
|
+
`
|
|
589
353
|
\u2713 Installing to ${selected.length} platform${selected.length > 1 ? "s" : ""}: ${chalk2.cyan(names.join(", "))}
|
|
354
|
+
`
|
|
355
|
+
)
|
|
356
|
+
);
|
|
357
|
+
return selected;
|
|
358
|
+
}
|
|
359
|
+
async function promptSyncTargetsInteractive(choices) {
|
|
360
|
+
if (choices.length === 0) return null;
|
|
361
|
+
const selectedIndices = await interactiveTreeSelect(choices, {
|
|
362
|
+
title: "Select sync targets",
|
|
363
|
+
subtitle: "\u2191\u2193 navigate \u2022 Space toggle \u2022 Enter confirm",
|
|
364
|
+
buildTree: buildSyncTree,
|
|
365
|
+
formatNode: (node, selection, isCursor, maxWidth) => {
|
|
366
|
+
if (!node.isLeaf && node.depth === 2) {
|
|
367
|
+
const display = getPlatformDisplay(node.name);
|
|
368
|
+
return formatTreeNode({ ...node, name: display }, selection, isCursor, maxWidth, {
|
|
369
|
+
hintText: buildSpaceHint(node, selection)
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
if (node.isLeaf && node.leafIndices.length === 1) {
|
|
373
|
+
const choice = choices[node.leafIndices[0]];
|
|
374
|
+
const platformLabel = getPlatformDisplay(choice.sourcePlatform);
|
|
375
|
+
const suffixText = ` [from ${platformLabel} \xB7 ${choice.sourceTypeLabel}]`;
|
|
376
|
+
return formatTreeNode(node, selection, isCursor, maxWidth, {
|
|
377
|
+
suffixText,
|
|
378
|
+
hintText: buildSpaceHint(node, selection)
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
return formatTreeNode(node, selection, isCursor, maxWidth, {
|
|
382
|
+
hintText: buildSpaceHint(node, selection)
|
|
383
|
+
});
|
|
384
|
+
},
|
|
385
|
+
defaultAll: true
|
|
386
|
+
});
|
|
387
|
+
if (!selectedIndices) return null;
|
|
388
|
+
const selected = selectedIndices.map((i) => choices[i]);
|
|
389
|
+
const summary = selected.map((c2) => `${c2.displayName}\u2192${getPlatformDisplay(c2.targetPlatform)}`).join(", ");
|
|
390
|
+
enqueuePostPromptLog(chalk2.green(`
|
|
391
|
+
\u2713 Syncing ${selected.length} target(s): ${chalk2.cyan(summary)}
|
|
590
392
|
`));
|
|
591
393
|
return selected;
|
|
592
394
|
}
|
|
@@ -1522,6 +1324,8 @@ async function promptSelections(ctx) {
|
|
|
1522
1324
|
}
|
|
1523
1325
|
ctx.targets = selectedPlatforms;
|
|
1524
1326
|
ctx.needsPlatformPrompt = false;
|
|
1327
|
+
flushInteractiveUiNow();
|
|
1328
|
+
if (ctx.spinner) ctx.spinner.start();
|
|
1525
1329
|
}
|
|
1526
1330
|
return true;
|
|
1527
1331
|
}
|
|
@@ -1588,6 +1392,7 @@ async function promptSelections(ctx) {
|
|
|
1588
1392
|
ctx.targets = selectedPlatforms;
|
|
1589
1393
|
ctx.needsPlatformPrompt = false;
|
|
1590
1394
|
}
|
|
1395
|
+
flushInteractiveUiNow();
|
|
1591
1396
|
if (ctx.spinner) ctx.spinner.start();
|
|
1592
1397
|
} else {
|
|
1593
1398
|
ctx.selectedSkills = discoveredSkills;
|
|
@@ -1811,7 +1616,7 @@ import chalk5 from "chalk";
|
|
|
1811
1616
|
import { PLATFORMS as PLATFORMS3, listAllSkills, listSkills as listSkills2 } from "@skild/core";
|
|
1812
1617
|
|
|
1813
1618
|
// src/utils/table-utils.ts
|
|
1814
|
-
import
|
|
1619
|
+
import stringWidth from "string-width";
|
|
1815
1620
|
import chalk4 from "chalk";
|
|
1816
1621
|
var BORDERS = {
|
|
1817
1622
|
rounded: {
|
|
@@ -1855,7 +1660,7 @@ var BORDERS = {
|
|
|
1855
1660
|
}
|
|
1856
1661
|
};
|
|
1857
1662
|
function getWidth(str) {
|
|
1858
|
-
return
|
|
1663
|
+
return stringWidth(str);
|
|
1859
1664
|
}
|
|
1860
1665
|
function pad(str, targetWidth, align = "left") {
|
|
1861
1666
|
const currentWidth = getWidth(str);
|
|
@@ -1877,7 +1682,7 @@ function truncate(str, maxWidth) {
|
|
|
1877
1682
|
let result = "";
|
|
1878
1683
|
let width = 0;
|
|
1879
1684
|
for (const char of plain) {
|
|
1880
|
-
const charWidth =
|
|
1685
|
+
const charWidth = stringWidth(char);
|
|
1881
1686
|
if (width + charWidth + 1 > maxWidth) break;
|
|
1882
1687
|
result += char;
|
|
1883
1688
|
width += charWidth;
|
|
@@ -2299,9 +2104,9 @@ import chalk11 from "chalk";
|
|
|
2299
2104
|
import { fetchWithTimeout as fetchWithTimeout3, resolveRegistryUrl as resolveRegistryUrl2, SkildError as SkildError6 } from "@skild/core";
|
|
2300
2105
|
|
|
2301
2106
|
// src/utils/prompt.ts
|
|
2302
|
-
import
|
|
2107
|
+
import readline from "readline";
|
|
2303
2108
|
async function promptLine(question, defaultValue) {
|
|
2304
|
-
const rl =
|
|
2109
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
2305
2110
|
try {
|
|
2306
2111
|
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
2307
2112
|
const answer = await new Promise((resolve) => rl.question(`${question}${suffix}: `, resolve));
|
|
@@ -2321,7 +2126,7 @@ async function promptPassword(question) {
|
|
|
2321
2126
|
const wasRaw = Boolean(stdin.isRaw);
|
|
2322
2127
|
stdin.setRawMode(true);
|
|
2323
2128
|
stdin.resume();
|
|
2324
|
-
|
|
2129
|
+
readline.emitKeypressEvents(stdin);
|
|
2325
2130
|
const buf = [];
|
|
2326
2131
|
return await new Promise((resolve, reject) => {
|
|
2327
2132
|
function cleanup() {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type SelectionState = 'all' | 'none' | 'partial';
|
|
2
|
+
interface SelectionInfo {
|
|
3
|
+
state: SelectionState;
|
|
4
|
+
selectedCount: number;
|
|
5
|
+
}
|
|
6
|
+
type TreeNode = {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
depth: number;
|
|
10
|
+
children: TreeNode[];
|
|
11
|
+
leafIndices: number[];
|
|
12
|
+
isLeaf: boolean;
|
|
13
|
+
};
|
|
14
|
+
interface TreeSelectOptions<T> {
|
|
15
|
+
title: string;
|
|
16
|
+
subtitle: string;
|
|
17
|
+
buildTree: (items: T[]) => TreeNode;
|
|
18
|
+
formatNode: (node: TreeNode, selection: SelectionInfo, isCursor: boolean, maxWidth: number) => string;
|
|
19
|
+
defaultAll: boolean;
|
|
20
|
+
defaultSelected?: Set<number>;
|
|
21
|
+
}
|
|
22
|
+
declare function enqueuePostPromptLog(message: string): void;
|
|
23
|
+
declare function flushInteractiveUiNow(): void;
|
|
24
|
+
declare function interactiveTreeSelect<T>(items: T[], options: TreeSelectOptions<T>): Promise<number[] | null>;
|
|
25
|
+
declare function formatInteractiveRow(input: {
|
|
26
|
+
cursorMark: string;
|
|
27
|
+
indent: string;
|
|
28
|
+
glyph: string;
|
|
29
|
+
name: string;
|
|
30
|
+
count: string;
|
|
31
|
+
suffixText?: string;
|
|
32
|
+
hintText?: string;
|
|
33
|
+
isCursor: boolean;
|
|
34
|
+
maxWidth: number;
|
|
35
|
+
styleName?: (s: string) => string;
|
|
36
|
+
}): string;
|
|
37
|
+
|
|
38
|
+
export { type SelectionInfo, type SelectionState, type TreeNode, type TreeSelectOptions, enqueuePostPromptLog, flushInteractiveUiNow, formatInteractiveRow, interactiveTreeSelect };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skild",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.16",
|
|
4
4
|
"description": "The npm for Agent Skills — Discover, install, manage, and publish AI Agent Skills with ease.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"string-width": "^8.1.0",
|
|
44
44
|
"tar": "^7.4.3",
|
|
45
45
|
"unified": "^11.0.4",
|
|
46
|
-
"@skild/core": "^0.10.
|
|
46
|
+
"@skild/core": "^0.10.16"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/mdast": "^4.0.4",
|
|
@@ -55,9 +55,10 @@
|
|
|
55
55
|
"node": ">=18"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
|
-
"build": "tsup src/index.ts --format esm --dts --clean --silent",
|
|
58
|
+
"build": "tsup src/index.ts src/ui/interactive-tree-prompt.ts --format esm --dts --clean --silent",
|
|
59
59
|
"dev": "tsup src/index.ts --format esm --watch",
|
|
60
60
|
"start": "pnpm exec node dist/index.js",
|
|
61
|
+
"selfcheck:ui": "pnpm build && node scripts/interactive-tree-prompt-selfcheck.mjs",
|
|
61
62
|
"typecheck": "pnpm -C ../core build && tsc -p tsconfig.json --noEmit"
|
|
62
63
|
}
|
|
63
64
|
}
|