skild 0.4.4 → 0.4.6
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 +524 -406
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9,7 +9,19 @@ import { createRequire } from "module";
|
|
|
9
9
|
import fs2 from "fs";
|
|
10
10
|
import path2 from "path";
|
|
11
11
|
import chalk3 from "chalk";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
deriveChildSource,
|
|
14
|
+
fetchWithTimeout,
|
|
15
|
+
installRegistrySkill,
|
|
16
|
+
installSkill,
|
|
17
|
+
isValidAlias,
|
|
18
|
+
loadRegistryAuth,
|
|
19
|
+
materializeSourceToTemp,
|
|
20
|
+
resolveRegistryAlias,
|
|
21
|
+
resolveRegistryUrl,
|
|
22
|
+
SkildError,
|
|
23
|
+
PLATFORMS as PLATFORMS2
|
|
24
|
+
} from "@skild/core";
|
|
13
25
|
|
|
14
26
|
// src/utils/logger.ts
|
|
15
27
|
import chalk from "chalk";
|
|
@@ -75,21 +87,8 @@ var logger = {
|
|
|
75
87
|
import readline from "readline";
|
|
76
88
|
import chalk2 from "chalk";
|
|
77
89
|
import { PLATFORMS } from "@skild/core";
|
|
78
|
-
var PLATFORM_DISPLAY = {
|
|
79
|
-
claude: { name: "Claude" },
|
|
80
|
-
codex: { name: "Codex" },
|
|
81
|
-
copilot: { name: "Copilot" },
|
|
82
|
-
antigravity: { name: "Antigravity" }
|
|
83
|
-
};
|
|
84
90
|
function buildSkillTree(skills) {
|
|
85
|
-
const allNode =
|
|
86
|
-
id: "all",
|
|
87
|
-
name: "All Skills",
|
|
88
|
-
depth: 1,
|
|
89
|
-
children: [],
|
|
90
|
-
leafIndices: [],
|
|
91
|
-
isLeaf: false
|
|
92
|
-
};
|
|
91
|
+
const allNode = createTreeNode("all", "All Skills", 1, false);
|
|
93
92
|
for (let i = 0; i < skills.length; i++) {
|
|
94
93
|
const relPath = skills[i].relPath;
|
|
95
94
|
const parts = relPath === "." ? ["."] : relPath.split("/").filter(Boolean);
|
|
@@ -100,57 +99,107 @@ function buildSkillTree(skills) {
|
|
|
100
99
|
const nodeId = parts.slice(0, d + 1).join("/");
|
|
101
100
|
let child = current.children.find((c2) => c2.name === part);
|
|
102
101
|
if (!child) {
|
|
103
|
-
child =
|
|
104
|
-
id: nodeId,
|
|
105
|
-
name: part,
|
|
106
|
-
depth: d + 2,
|
|
107
|
-
// +2 because allNode is depth 1
|
|
108
|
-
children: [],
|
|
109
|
-
leafIndices: [],
|
|
110
|
-
isLeaf: d === parts.length - 1
|
|
111
|
-
};
|
|
102
|
+
child = createTreeNode(nodeId, part, d + 2, d === parts.length - 1);
|
|
112
103
|
current.children.push(child);
|
|
113
104
|
}
|
|
114
105
|
child.leafIndices.push(i);
|
|
115
106
|
current = child;
|
|
116
107
|
}
|
|
117
108
|
}
|
|
118
|
-
|
|
119
|
-
return
|
|
109
|
+
collapseIntermediateNodes(allNode);
|
|
110
|
+
return wrapWithRoot(allNode);
|
|
111
|
+
}
|
|
112
|
+
function buildPlatformTree(items) {
|
|
113
|
+
const allNode = createTreeNode("all", "All Platforms", 1, false);
|
|
114
|
+
for (let i = 0; i < items.length; i++) {
|
|
115
|
+
const platform = items[i].platform;
|
|
116
|
+
allNode.children.push(createTreeNode(platform, platform, 2, true, [i]));
|
|
117
|
+
allNode.leafIndices.push(i);
|
|
118
|
+
}
|
|
119
|
+
return wrapWithRoot(allNode);
|
|
120
|
+
}
|
|
121
|
+
function createTreeNode(id, name, depth, isLeaf, leafIndices = []) {
|
|
122
|
+
return { id, name, depth, children: [], leafIndices, isLeaf };
|
|
123
|
+
}
|
|
124
|
+
function wrapWithRoot(allNode) {
|
|
125
|
+
return { id: "", name: ".", depth: 0, children: [allNode], leafIndices: [...allNode.leafIndices], isLeaf: false };
|
|
126
|
+
}
|
|
127
|
+
function collapseIntermediateNodes(allNode) {
|
|
128
|
+
while (allNode.children.length === 1 && !allNode.children[0].isLeaf && allNode.children[0].children.length > 0) {
|
|
129
|
+
const singleChild = allNode.children[0];
|
|
130
|
+
for (const grandchild of singleChild.children) {
|
|
131
|
+
adjustDepth(grandchild, -1);
|
|
132
|
+
}
|
|
133
|
+
allNode.children = singleChild.children;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function adjustDepth(node, delta) {
|
|
137
|
+
node.depth += delta;
|
|
138
|
+
for (const child of node.children) {
|
|
139
|
+
adjustDepth(child, delta);
|
|
140
|
+
}
|
|
120
141
|
}
|
|
121
142
|
function flattenTree(root) {
|
|
122
143
|
const result = [];
|
|
123
144
|
function walk(node) {
|
|
124
|
-
if (node.id !== "")
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
for (const child of node.children) {
|
|
128
|
-
walk(child);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
for (const child of root.children) {
|
|
132
|
-
walk(child);
|
|
145
|
+
if (node.id !== "") result.push(node);
|
|
146
|
+
for (const child of node.children) walk(child);
|
|
133
147
|
}
|
|
148
|
+
for (const child of root.children) walk(child);
|
|
134
149
|
return result;
|
|
135
150
|
}
|
|
136
|
-
function
|
|
151
|
+
function getNodeSelection(node, selected) {
|
|
137
152
|
const total = node.leafIndices.length;
|
|
138
|
-
if (total === 0) return "none";
|
|
139
|
-
let
|
|
153
|
+
if (total === 0) return { state: "none", selectedCount: 0 };
|
|
154
|
+
let selectedCount = 0;
|
|
140
155
|
for (const idx of node.leafIndices) {
|
|
141
|
-
if (selected.has(idx))
|
|
156
|
+
if (selected.has(idx)) selectedCount++;
|
|
157
|
+
}
|
|
158
|
+
let state = "none";
|
|
159
|
+
if (selectedCount === total) state = "all";
|
|
160
|
+
else if (selectedCount > 0) state = "partial";
|
|
161
|
+
return { state, selectedCount };
|
|
162
|
+
}
|
|
163
|
+
function createRenderer(title, subtitle, flatNodes, selected, getCursor, formatNode) {
|
|
164
|
+
function renderContent() {
|
|
165
|
+
const lines = [];
|
|
166
|
+
lines.push(chalk2.bold.cyan(title));
|
|
167
|
+
lines.push(chalk2.dim(subtitle));
|
|
168
|
+
lines.push("");
|
|
169
|
+
const cursor = getCursor();
|
|
170
|
+
for (let i = 0; i < flatNodes.length; i++) {
|
|
171
|
+
const node = flatNodes[i];
|
|
172
|
+
const selection = getNodeSelection(node, selected);
|
|
173
|
+
lines.push(formatNode(node, selection, i === cursor));
|
|
174
|
+
}
|
|
175
|
+
lines.push("");
|
|
176
|
+
return lines;
|
|
177
|
+
}
|
|
178
|
+
function getLineCount() {
|
|
179
|
+
return 4 + flatNodes.length;
|
|
142
180
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
181
|
+
return { renderContent, getLineCount };
|
|
182
|
+
}
|
|
183
|
+
function writeToTerminal(stdout, lines) {
|
|
184
|
+
for (const line of lines) {
|
|
185
|
+
stdout.write(line + "\n");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function clearAndRerender(stdout, lineCount, lines) {
|
|
189
|
+
stdout.write("\x1B[?25l");
|
|
190
|
+
stdout.write(`\x1B[${lineCount}A`);
|
|
191
|
+
stdout.write("\x1B[0J");
|
|
192
|
+
writeToTerminal(stdout, lines);
|
|
146
193
|
}
|
|
147
194
|
async function interactiveTreeSelect(items, options) {
|
|
148
|
-
const { title, subtitle, buildTree, formatNode, defaultAll } = options;
|
|
195
|
+
const { title, subtitle, buildTree, formatNode, defaultAll, defaultSelected } = options;
|
|
149
196
|
const root = buildTree(items);
|
|
150
197
|
const flatNodes = flattenTree(root);
|
|
151
198
|
if (flatNodes.length === 0) return null;
|
|
152
199
|
const selected = /* @__PURE__ */ new Set();
|
|
153
|
-
if (
|
|
200
|
+
if (defaultSelected) {
|
|
201
|
+
for (const idx of defaultSelected) selected.add(idx);
|
|
202
|
+
} else if (defaultAll) {
|
|
154
203
|
for (let i = 0; i < items.length; i++) selected.add(i);
|
|
155
204
|
}
|
|
156
205
|
let cursor = 0;
|
|
@@ -163,186 +212,161 @@ async function interactiveTreeSelect(items, options) {
|
|
|
163
212
|
stdin.setRawMode(true);
|
|
164
213
|
stdin.resume();
|
|
165
214
|
readline.emitKeypressEvents(stdin);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const totalLines = flatNodes.length + 4;
|
|
169
|
-
stdout.write(`\x1B[${totalLines}A`);
|
|
170
|
-
stdout.write("\x1B[0J");
|
|
171
|
-
stdout.write(`
|
|
172
|
-
${chalk2.bold.cyan(title)}
|
|
173
|
-
`);
|
|
174
|
-
stdout.write(`${chalk2.dim(subtitle)}
|
|
175
|
-
|
|
176
|
-
`);
|
|
177
|
-
for (let i = 0; i < flatNodes.length; i++) {
|
|
178
|
-
const node = flatNodes[i];
|
|
179
|
-
const state = getNodeState(node, selected);
|
|
180
|
-
const isCursor = i === cursor;
|
|
181
|
-
stdout.write(formatNode(node, state, isCursor) + "\n");
|
|
182
|
-
}
|
|
183
|
-
stdout.write("\n");
|
|
184
|
-
}
|
|
185
|
-
function initialRender() {
|
|
186
|
-
stdout.write(`
|
|
187
|
-
${chalk2.bold.cyan(title)}
|
|
188
|
-
`);
|
|
189
|
-
stdout.write(`${chalk2.dim(subtitle)}
|
|
190
|
-
|
|
191
|
-
`);
|
|
192
|
-
for (let i = 0; i < flatNodes.length; i++) {
|
|
193
|
-
const node = flatNodes[i];
|
|
194
|
-
const state = getNodeState(node, selected);
|
|
195
|
-
const isCursor = i === cursor;
|
|
196
|
-
stdout.write(formatNode(node, state, isCursor) + "\n");
|
|
197
|
-
}
|
|
198
|
-
stdout.write("\n");
|
|
199
|
-
}
|
|
200
|
-
initialRender();
|
|
215
|
+
const renderer = createRenderer(title, subtitle, flatNodes, selected, () => cursor, formatNode);
|
|
216
|
+
writeToTerminal(stdout, renderer.renderContent());
|
|
201
217
|
return new Promise((resolve) => {
|
|
202
|
-
function cleanup() {
|
|
218
|
+
function cleanup(clear = false) {
|
|
219
|
+
if (clear) {
|
|
220
|
+
const lineCount = renderer.getLineCount();
|
|
221
|
+
stdout.write(`\x1B[${lineCount}A`);
|
|
222
|
+
stdout.write("\x1B[0J");
|
|
223
|
+
}
|
|
203
224
|
stdin.setRawMode(wasRaw);
|
|
204
225
|
stdin.pause();
|
|
205
226
|
stdin.removeListener("keypress", onKeypress);
|
|
206
227
|
stdout.write("\x1B[?25h");
|
|
207
228
|
}
|
|
229
|
+
function rerender() {
|
|
230
|
+
clearAndRerender(stdout, renderer.getLineCount(), renderer.renderContent());
|
|
231
|
+
}
|
|
232
|
+
function toggleNode(node) {
|
|
233
|
+
const { state } = getNodeSelection(node, selected);
|
|
234
|
+
if (state === "all") {
|
|
235
|
+
for (const idx of node.leafIndices) selected.delete(idx);
|
|
236
|
+
} else {
|
|
237
|
+
for (const idx of node.leafIndices) selected.add(idx);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function toggleAll() {
|
|
241
|
+
if (selected.size === items.length) {
|
|
242
|
+
selected.clear();
|
|
243
|
+
} else {
|
|
244
|
+
for (let i = 0; i < items.length; i++) selected.add(i);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
208
247
|
function onKeypress(_str, key) {
|
|
209
248
|
if (key.ctrl && key.name === "c") {
|
|
210
|
-
cleanup();
|
|
249
|
+
cleanup(true);
|
|
211
250
|
resolve(null);
|
|
212
251
|
return;
|
|
213
252
|
}
|
|
214
253
|
if (key.name === "return" || key.name === "enter") {
|
|
215
|
-
cleanup();
|
|
216
|
-
|
|
217
|
-
if (result.length === 0) {
|
|
218
|
-
resolve(null);
|
|
219
|
-
} else {
|
|
220
|
-
resolve(result);
|
|
221
|
-
}
|
|
254
|
+
cleanup(true);
|
|
255
|
+
resolve(selected.size > 0 ? Array.from(selected) : null);
|
|
222
256
|
return;
|
|
223
257
|
}
|
|
224
258
|
if (key.name === "up") {
|
|
225
259
|
cursor = (cursor - 1 + flatNodes.length) % flatNodes.length;
|
|
226
|
-
|
|
260
|
+
rerender();
|
|
227
261
|
return;
|
|
228
262
|
}
|
|
229
263
|
if (key.name === "down") {
|
|
230
264
|
cursor = (cursor + 1) % flatNodes.length;
|
|
231
|
-
|
|
265
|
+
rerender();
|
|
232
266
|
return;
|
|
233
267
|
}
|
|
234
268
|
if (key.name === "space") {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (state === "all") {
|
|
238
|
-
for (const idx of node.leafIndices) {
|
|
239
|
-
selected.delete(idx);
|
|
240
|
-
}
|
|
241
|
-
} else {
|
|
242
|
-
for (const idx of node.leafIndices) {
|
|
243
|
-
selected.add(idx);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
render();
|
|
269
|
+
toggleNode(flatNodes[cursor]);
|
|
270
|
+
rerender();
|
|
247
271
|
return;
|
|
248
272
|
}
|
|
249
273
|
if (key.name === "a") {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
selected.clear();
|
|
253
|
-
} else {
|
|
254
|
-
for (let i = 0; i < items.length; i++) selected.add(i);
|
|
255
|
-
}
|
|
256
|
-
render();
|
|
274
|
+
toggleAll();
|
|
275
|
+
rerender();
|
|
257
276
|
return;
|
|
258
277
|
}
|
|
259
278
|
}
|
|
260
279
|
stdin.on("keypress", onKeypress);
|
|
261
280
|
});
|
|
262
281
|
}
|
|
282
|
+
var PLATFORM_DISPLAY = {
|
|
283
|
+
claude: "Claude",
|
|
284
|
+
codex: "Codex",
|
|
285
|
+
copilot: "Copilot",
|
|
286
|
+
antigravity: "Antigravity"
|
|
287
|
+
};
|
|
288
|
+
function formatTreeNode(node, selection, isCursor, options = {}) {
|
|
289
|
+
const { state, selectedCount } = selection;
|
|
290
|
+
const totalCount = node.leafIndices.length;
|
|
291
|
+
const indent = " ".repeat(node.depth - 1);
|
|
292
|
+
const checkbox = state === "all" ? chalk2.green("\u25CF") : state === "partial" ? chalk2.yellow("\u25D0") : chalk2.dim("\u25CB");
|
|
293
|
+
const name = isCursor ? chalk2.cyan.underline(node.name) : node.name;
|
|
294
|
+
const cursorMark = isCursor ? chalk2.cyan("\u203A ") : " ";
|
|
295
|
+
let count = "";
|
|
296
|
+
if (totalCount > 1) {
|
|
297
|
+
count = chalk2.dim(` (${selectedCount}/${totalCount})`);
|
|
298
|
+
}
|
|
299
|
+
const suffix = options.suffix || "";
|
|
300
|
+
let hint = "";
|
|
301
|
+
if (isCursor && totalCount > 0) {
|
|
302
|
+
hint = state === "all" ? chalk2.dim(" \u2190 Space to deselect") : chalk2.dim(" \u2190 Space to select");
|
|
303
|
+
}
|
|
304
|
+
return `${cursorMark}${indent}${checkbox} ${name}${count}${suffix}${hint}`;
|
|
305
|
+
}
|
|
263
306
|
async function promptSkillsInteractive(skills, options = {}) {
|
|
264
307
|
if (skills.length === 0) return null;
|
|
308
|
+
const targetPlatforms = options.targetPlatforms || [];
|
|
309
|
+
const hasInstalledCheck = targetPlatforms.length > 0;
|
|
310
|
+
const defaultSelected = /* @__PURE__ */ new Set();
|
|
311
|
+
for (let i = 0; i < skills.length; i++) {
|
|
312
|
+
const skill = skills[i];
|
|
313
|
+
const installedOnTargets = skill.installedPlatforms?.filter((p) => targetPlatforms.includes(p)) || [];
|
|
314
|
+
const isFullyInstalled = hasInstalledCheck && installedOnTargets.length === targetPlatforms.length;
|
|
315
|
+
if (options.defaultAll !== false && !isFullyInstalled) {
|
|
316
|
+
defaultSelected.add(i);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
265
319
|
const selectedIndices = await interactiveTreeSelect(skills, {
|
|
266
320
|
title: "Select skills to install",
|
|
267
321
|
subtitle: "\u2191\u2193 navigate \u2022 Space toggle \u2022 Enter confirm",
|
|
268
322
|
buildTree: buildSkillTree,
|
|
269
|
-
formatNode: (node,
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
323
|
+
formatNode: (node, selection, isCursor) => {
|
|
324
|
+
let suffix = "";
|
|
325
|
+
if (node.isLeaf && node.leafIndices.length === 1) {
|
|
326
|
+
const skill = skills[node.leafIndices[0]];
|
|
327
|
+
if (skill?.installedPlatforms?.length) {
|
|
328
|
+
if (skill.installedPlatforms.length === targetPlatforms.length && targetPlatforms.length > 0) {
|
|
329
|
+
suffix = chalk2.dim(" [installed]");
|
|
330
|
+
} else if (skill.installedPlatforms.length > 0) {
|
|
331
|
+
suffix = chalk2.dim(` [installed on ${skill.installedPlatforms.length}]`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
278
334
|
}
|
|
279
|
-
|
|
335
|
+
const formatted = formatTreeNode(node, selection, isCursor, { suffix });
|
|
336
|
+
if (isCursor && suffix && selection.state !== "all") {
|
|
337
|
+
return formatted.replace("\u2190 Space to select", "\u2190 Space to reinstall");
|
|
338
|
+
}
|
|
339
|
+
return formatted;
|
|
280
340
|
},
|
|
281
|
-
defaultAll:
|
|
341
|
+
defaultAll: false,
|
|
342
|
+
defaultSelected
|
|
282
343
|
});
|
|
283
|
-
if (!selectedIndices
|
|
284
|
-
|
|
285
|
-
|
|
344
|
+
if (!selectedIndices) return null;
|
|
345
|
+
const selectedSkills = selectedIndices.map((i) => skills[i]);
|
|
346
|
+
const names = selectedSkills.map((s) => s.relPath === "." ? s.suggestedSource : s.relPath);
|
|
286
347
|
console.log(chalk2.green(`
|
|
287
|
-
\u2713 ${
|
|
348
|
+
\u2713 Selected ${selectedSkills.length} skill${selectedSkills.length > 1 ? "s" : ""}: ${chalk2.cyan(names.join(", "))}
|
|
288
349
|
`));
|
|
289
|
-
return
|
|
350
|
+
return selectedSkills;
|
|
290
351
|
}
|
|
291
352
|
async function promptPlatformsInteractive(options = {}) {
|
|
292
353
|
const platformItems = PLATFORMS.map((p) => ({ platform: p }));
|
|
293
354
|
const selectedIndices = await interactiveTreeSelect(platformItems, {
|
|
294
355
|
title: "Select target platforms",
|
|
295
356
|
subtitle: "\u2191\u2193 navigate \u2022 Space toggle \u2022 Enter confirm",
|
|
296
|
-
buildTree:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
children: [],
|
|
302
|
-
leafIndices: [],
|
|
303
|
-
isLeaf: false
|
|
304
|
-
};
|
|
305
|
-
for (let i = 0; i < items.length; i++) {
|
|
306
|
-
const p = items[i].platform;
|
|
307
|
-
allNode.children.push({
|
|
308
|
-
id: p,
|
|
309
|
-
name: p,
|
|
310
|
-
depth: 2,
|
|
311
|
-
children: [],
|
|
312
|
-
leafIndices: [i],
|
|
313
|
-
isLeaf: true
|
|
314
|
-
});
|
|
315
|
-
allNode.leafIndices.push(i);
|
|
316
|
-
}
|
|
317
|
-
const root = { id: "", name: ".", depth: 0, children: [allNode], leafIndices: allNode.leafIndices.slice(), isLeaf: false };
|
|
318
|
-
return root;
|
|
319
|
-
},
|
|
320
|
-
formatNode: (node, state, isCursor) => {
|
|
321
|
-
const indent = " ".repeat(node.depth - 1);
|
|
322
|
-
const checkbox = state === "all" ? chalk2.green("\u25CF") : state === "partial" ? chalk2.yellow("\u25D0") : chalk2.dim("\u25CB");
|
|
323
|
-
let displayName = node.name;
|
|
324
|
-
if (node.name !== "All Platforms") {
|
|
325
|
-
const platform = node.name;
|
|
326
|
-
const display = PLATFORM_DISPLAY[platform];
|
|
327
|
-
if (display) displayName = display.name;
|
|
328
|
-
}
|
|
329
|
-
const name = isCursor ? chalk2.cyan.underline(displayName) : displayName;
|
|
330
|
-
const cursor = isCursor ? chalk2.cyan("\u203A ") : " ";
|
|
331
|
-
const count = node.leafIndices.length > 1 ? chalk2.dim(` (${node.leafIndices.length})`) : "";
|
|
332
|
-
let hint = "";
|
|
333
|
-
if (isCursor && node.leafIndices.length > 0) {
|
|
334
|
-
hint = state === "all" ? chalk2.dim(" \u2190 Space to deselect") : chalk2.dim(" \u2190 Space to select");
|
|
335
|
-
}
|
|
336
|
-
return `${cursor}${indent}${checkbox} ${name}${count}${hint}`;
|
|
357
|
+
buildTree: buildPlatformTree,
|
|
358
|
+
formatNode: (node, selection, isCursor) => {
|
|
359
|
+
const displayName = node.name === "All Platforms" ? node.name : PLATFORM_DISPLAY[node.name] || node.name;
|
|
360
|
+
const modifiedNode = { ...node, name: displayName };
|
|
361
|
+
return formatTreeNode(modifiedNode, selection, isCursor);
|
|
337
362
|
},
|
|
338
363
|
defaultAll: options.defaultAll !== false
|
|
339
364
|
});
|
|
340
|
-
if (!selectedIndices
|
|
341
|
-
return null;
|
|
342
|
-
}
|
|
365
|
+
if (!selectedIndices) return null;
|
|
343
366
|
const selected = selectedIndices.map((i) => PLATFORMS[i]);
|
|
367
|
+
const names = selected.map((p) => PLATFORM_DISPLAY[p] || p);
|
|
344
368
|
console.log(chalk2.green(`
|
|
345
|
-
\u2713 Installing to ${selected.length} platform${selected.length > 1 ? "s" : ""}
|
|
369
|
+
\u2713 Installing to ${selected.length} platform${selected.length > 1 ? "s" : ""}: ${chalk2.cyan(names.join(", "))}
|
|
346
370
|
`));
|
|
347
371
|
return selected;
|
|
348
372
|
}
|
|
@@ -448,287 +472,381 @@ function asDiscoveredSkills(discovered, toSuggestedSource, toMaterializedDir) {
|
|
|
448
472
|
materializedDir: toMaterializedDir ? toMaterializedDir(d) : void 0
|
|
449
473
|
}));
|
|
450
474
|
}
|
|
451
|
-
|
|
475
|
+
function createContext(source, options) {
|
|
452
476
|
const scope = options.local ? "project" : "global";
|
|
453
477
|
const auth = loadRegistryAuth();
|
|
454
|
-
const
|
|
478
|
+
const registryUrl = options.registry || auth?.registryUrl;
|
|
455
479
|
const jsonOnly = Boolean(options.json);
|
|
456
|
-
const recursive = Boolean(options.recursive);
|
|
457
480
|
const yes = Boolean(options.yes);
|
|
458
|
-
const maxDepth = parsePositiveInt(options.depth, 6);
|
|
459
|
-
const maxSkills = parsePositiveInt(options.maxSkills, 200);
|
|
460
481
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !jsonOnly;
|
|
461
|
-
const requestedAll = Boolean(options.all);
|
|
462
|
-
const requestedTarget = options.target;
|
|
463
|
-
if (requestedAll && options.target) {
|
|
464
|
-
const message = "Invalid options: use either --all or --target, not both.";
|
|
465
|
-
if (jsonOnly) printJson({ ok: false, error: message });
|
|
466
|
-
else console.error(chalk3.red(message));
|
|
467
|
-
process.exitCode = 1;
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
482
|
let targets = [];
|
|
471
|
-
let
|
|
472
|
-
if (
|
|
483
|
+
let needsPlatformPrompt = false;
|
|
484
|
+
if (options.all) {
|
|
473
485
|
targets = [...PLATFORMS2];
|
|
474
|
-
} else if (
|
|
475
|
-
targets = [
|
|
486
|
+
} else if (options.target) {
|
|
487
|
+
targets = [options.target];
|
|
476
488
|
} else if (yes) {
|
|
477
489
|
targets = [...PLATFORMS2];
|
|
478
490
|
} else if (interactive) {
|
|
479
|
-
|
|
491
|
+
needsPlatformPrompt = true;
|
|
480
492
|
targets = [...PLATFORMS2];
|
|
481
493
|
} else {
|
|
482
494
|
targets = ["claude"];
|
|
483
495
|
}
|
|
484
|
-
|
|
485
|
-
|
|
496
|
+
return {
|
|
497
|
+
source,
|
|
498
|
+
options,
|
|
499
|
+
scope,
|
|
500
|
+
registryUrl,
|
|
501
|
+
jsonOnly,
|
|
502
|
+
interactive,
|
|
503
|
+
yes,
|
|
504
|
+
maxDepth: parsePositiveInt(options.depth, 6),
|
|
505
|
+
maxSkills: parsePositiveInt(options.maxSkills, 200),
|
|
506
|
+
resolvedSource: source.trim(),
|
|
507
|
+
targets,
|
|
508
|
+
forceOverwrite: Boolean(options.force),
|
|
509
|
+
needsPlatformPrompt,
|
|
510
|
+
discoveredSkills: null,
|
|
511
|
+
selectedSkills: null,
|
|
512
|
+
isSingleSkill: false,
|
|
513
|
+
materializedDir: null,
|
|
514
|
+
cleanupMaterialized: null,
|
|
515
|
+
results: [],
|
|
516
|
+
errors: [],
|
|
517
|
+
skipped: [],
|
|
518
|
+
spinner: null
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
async function resolveSource(ctx) {
|
|
522
|
+
if (ctx.options.all && ctx.options.target) {
|
|
523
|
+
const message = "Invalid options: use either --all or --target, not both.";
|
|
524
|
+
if (ctx.jsonOnly) printJson({ ok: false, error: message });
|
|
525
|
+
else console.error(chalk3.red(message));
|
|
526
|
+
process.exitCode = 1;
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
486
529
|
try {
|
|
487
|
-
if (looksLikeAlias(resolvedSource)) {
|
|
488
|
-
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
530
|
+
if (looksLikeAlias(ctx.resolvedSource)) {
|
|
531
|
+
if (ctx.spinner) ctx.spinner.text = `Resolving ${chalk3.cyan(ctx.resolvedSource)}...`;
|
|
532
|
+
const registryUrl = resolveRegistryUrl(ctx.registryUrl);
|
|
533
|
+
const resolved = await resolveRegistryAlias(registryUrl, ctx.resolvedSource);
|
|
534
|
+
if (!ctx.jsonOnly) {
|
|
535
|
+
logger.info(`Resolved ${chalk3.cyan(ctx.resolvedSource)} \u2192 ${chalk3.cyan(resolved.spec)} (${resolved.type})`);
|
|
536
|
+
}
|
|
537
|
+
ctx.resolvedSource = resolved.spec;
|
|
492
538
|
}
|
|
493
539
|
} catch (error) {
|
|
494
540
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
495
|
-
if (jsonOnly) printJson({ ok: false, error: message });
|
|
541
|
+
if (ctx.jsonOnly) printJson({ ok: false, error: message });
|
|
496
542
|
else console.error(chalk3.red(message));
|
|
497
543
|
process.exitCode = 1;
|
|
498
|
-
return;
|
|
544
|
+
return false;
|
|
499
545
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
async function discoverSkills(ctx) {
|
|
549
|
+
const { resolvedSource, maxDepth, maxSkills, jsonOnly } = ctx;
|
|
550
|
+
if (ctx.spinner) {
|
|
551
|
+
ctx.spinner.text = `Discovery at ${chalk3.cyan(ctx.source)}...`;
|
|
552
|
+
}
|
|
553
|
+
try {
|
|
554
|
+
if (resolvedSource.startsWith("@") && resolvedSource.includes("/")) {
|
|
555
|
+
ctx.isSingleSkill = true;
|
|
556
|
+
ctx.selectedSkills = [{ relPath: resolvedSource, suggestedSource: resolvedSource }];
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
const maybeLocalRoot = path2.resolve(resolvedSource);
|
|
560
|
+
const isLocal = fs2.existsSync(maybeLocalRoot);
|
|
561
|
+
if (isLocal) {
|
|
562
|
+
const hasSkillMd2 = fs2.existsSync(path2.join(maybeLocalRoot, "SKILL.md"));
|
|
563
|
+
if (hasSkillMd2) {
|
|
564
|
+
ctx.isSingleSkill = true;
|
|
565
|
+
ctx.selectedSkills = [{ relPath: maybeLocalRoot, suggestedSource: resolvedSource }];
|
|
566
|
+
return true;
|
|
519
567
|
}
|
|
568
|
+
const discovered2 = discoverSkillDirsWithHeuristics(maybeLocalRoot, { maxDepth, maxSkills });
|
|
569
|
+
if (discovered2.length === 0) {
|
|
570
|
+
const message = `No SKILL.md found at ${maybeLocalRoot} (or within subdirectories).`;
|
|
571
|
+
if (jsonOnly) {
|
|
572
|
+
printJson({ ok: false, error: "SKILL_MD_NOT_FOUND", message, source: ctx.source, resolvedSource });
|
|
573
|
+
} else {
|
|
574
|
+
if (ctx.spinner) ctx.spinner.stop();
|
|
575
|
+
console.error(chalk3.red(message));
|
|
576
|
+
}
|
|
577
|
+
process.exitCode = 1;
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
ctx.discoveredSkills = asDiscoveredSkills(discovered2, (d) => path2.join(maybeLocalRoot, d.relPath));
|
|
581
|
+
return true;
|
|
520
582
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
return null;
|
|
583
|
+
const materialized = await materializeSourceToTemp(resolvedSource);
|
|
584
|
+
ctx.materializedDir = materialized.dir;
|
|
585
|
+
ctx.cleanupMaterialized = materialized.cleanup;
|
|
586
|
+
const hasSkillMd = fs2.existsSync(path2.join(ctx.materializedDir, "SKILL.md"));
|
|
587
|
+
if (hasSkillMd) {
|
|
588
|
+
ctx.isSingleSkill = true;
|
|
589
|
+
ctx.selectedSkills = [{ relPath: ".", suggestedSource: resolvedSource, materializedDir: ctx.materializedDir }];
|
|
590
|
+
return true;
|
|
530
591
|
}
|
|
531
|
-
|
|
592
|
+
const discovered = discoverSkillDirsWithHeuristics(ctx.materializedDir, { maxDepth, maxSkills });
|
|
593
|
+
if (discovered.length === 0) {
|
|
594
|
+
const message = `No SKILL.md found in source "${resolvedSource}".`;
|
|
532
595
|
if (jsonOnly) {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
596
|
+
printJson({ ok: false, error: "SKILL_MD_NOT_FOUND", message, source: ctx.source, resolvedSource });
|
|
597
|
+
} else {
|
|
598
|
+
if (ctx.spinner) ctx.spinner.stop();
|
|
599
|
+
console.error(chalk3.red(message));
|
|
600
|
+
}
|
|
601
|
+
process.exitCode = 1;
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
ctx.discoveredSkills = asDiscoveredSkills(
|
|
605
|
+
discovered,
|
|
606
|
+
(d) => deriveChildSource(resolvedSource, d.relPath),
|
|
607
|
+
(d) => d.absDir
|
|
608
|
+
);
|
|
609
|
+
return true;
|
|
610
|
+
} catch (error) {
|
|
611
|
+
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
612
|
+
if (jsonOnly) printJson({ ok: false, error: message });
|
|
613
|
+
else {
|
|
614
|
+
if (ctx.spinner) ctx.spinner.stop();
|
|
615
|
+
console.error(chalk3.red(message));
|
|
616
|
+
}
|
|
617
|
+
process.exitCode = 1;
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
async function promptSelections(ctx) {
|
|
622
|
+
const { discoveredSkills, isSingleSkill, jsonOnly, interactive, yes, options } = ctx;
|
|
623
|
+
if (isSingleSkill) {
|
|
624
|
+
if (ctx.needsPlatformPrompt) {
|
|
625
|
+
if (ctx.spinner) ctx.spinner.stop();
|
|
626
|
+
const selectedPlatforms = await promptPlatformsInteractive({ defaultAll: true });
|
|
627
|
+
if (!selectedPlatforms) {
|
|
628
|
+
console.log(chalk3.red("No platforms selected."));
|
|
542
629
|
process.exitCode = 1;
|
|
543
|
-
return
|
|
630
|
+
return false;
|
|
544
631
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
632
|
+
ctx.targets = selectedPlatforms;
|
|
633
|
+
ctx.needsPlatformPrompt = false;
|
|
634
|
+
}
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
if (!discoveredSkills || discoveredSkills.length === 0) {
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
if (discoveredSkills.length > ctx.maxSkills) {
|
|
641
|
+
const message = `Found more than ${ctx.maxSkills} skills. Increase --max-skills to proceed.`;
|
|
642
|
+
if (jsonOnly) printJson({ ok: false, error: "TOO_MANY_SKILLS", message, source: ctx.source, resolvedSource: ctx.resolvedSource, maxSkills: ctx.maxSkills });
|
|
643
|
+
else console.error(chalk3.red(message));
|
|
644
|
+
process.exitCode = 1;
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
if (!options.recursive && !yes) {
|
|
648
|
+
if (jsonOnly) {
|
|
649
|
+
const foundOutput = discoveredSkills.map(({ relPath, suggestedSource }) => ({ relPath, suggestedSource }));
|
|
650
|
+
printJson({
|
|
651
|
+
ok: false,
|
|
652
|
+
error: "MULTI_SKILL_SOURCE",
|
|
653
|
+
message: "Source is not a skill root (missing SKILL.md). Multiple skills were found.",
|
|
654
|
+
source: ctx.source,
|
|
655
|
+
resolvedSource: ctx.resolvedSource,
|
|
656
|
+
found: foundOutput
|
|
657
|
+
});
|
|
658
|
+
process.exitCode = 1;
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
if (ctx.spinner) ctx.spinner.stop();
|
|
662
|
+
const headline = discoveredSkills.length === 1 ? `No SKILL.md found at root. Found 1 skill.
|
|
663
|
+
` : `No SKILL.md found at root. Found ${discoveredSkills.length} skills.
|
|
664
|
+
`;
|
|
665
|
+
console.log(chalk3.yellow(headline));
|
|
666
|
+
if (!interactive) {
|
|
667
|
+
console.log(chalk3.dim(`Tip: rerun with ${chalk3.cyan("skild install <source> --recursive")} to install all.`));
|
|
668
|
+
process.exitCode = 1;
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (!yes && interactive) {
|
|
673
|
+
if (ctx.spinner) ctx.spinner.stop();
|
|
674
|
+
if (options.recursive) {
|
|
675
|
+
const headline = discoveredSkills.length === 1 ? `No SKILL.md found at root. Found 1 skill.
|
|
676
|
+
` : `No SKILL.md found at root. Found ${discoveredSkills.length} skills.
|
|
548
677
|
`;
|
|
549
678
|
console.log(chalk3.yellow(headline));
|
|
550
|
-
|
|
551
|
-
|
|
679
|
+
}
|
|
680
|
+
const selected = await promptSkillsInteractive(discoveredSkills, { defaultAll: true });
|
|
681
|
+
if (!selected) {
|
|
682
|
+
console.log(chalk3.red("No skills selected."));
|
|
683
|
+
process.exitCode = 1;
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
ctx.selectedSkills = selected;
|
|
687
|
+
if (ctx.needsPlatformPrompt) {
|
|
688
|
+
const selectedPlatforms = await promptPlatformsInteractive({ defaultAll: true });
|
|
689
|
+
if (!selectedPlatforms) {
|
|
690
|
+
console.log(chalk3.red("No platforms selected."));
|
|
552
691
|
process.exitCode = 1;
|
|
553
|
-
return
|
|
554
|
-
}
|
|
555
|
-
if (!yes) {
|
|
556
|
-
const selected = await promptSkillsInteractive(found, { defaultAll: true });
|
|
557
|
-
if (!selected) {
|
|
558
|
-
console.log(chalk3.red("No skills selected."));
|
|
559
|
-
process.exitCode = 1;
|
|
560
|
-
return null;
|
|
561
|
-
}
|
|
562
|
-
if (deferPlatformSelection) {
|
|
563
|
-
const selectedPlatforms = await promptPlatformsInteractive({ defaultAll: true });
|
|
564
|
-
if (!selectedPlatforms) {
|
|
565
|
-
console.log(chalk3.red("No platforms selected."));
|
|
566
|
-
process.exitCode = 1;
|
|
567
|
-
return null;
|
|
568
|
-
}
|
|
569
|
-
targets = selectedPlatforms;
|
|
570
|
-
deferPlatformSelection = false;
|
|
571
|
-
}
|
|
572
|
-
effectiveRecursive = true;
|
|
573
|
-
if (spinner) spinner.start();
|
|
574
|
-
recursiveSkillCount = selected.length;
|
|
575
|
-
return selected;
|
|
692
|
+
return false;
|
|
576
693
|
}
|
|
577
|
-
|
|
694
|
+
ctx.targets = selectedPlatforms;
|
|
695
|
+
ctx.needsPlatformPrompt = false;
|
|
578
696
|
}
|
|
579
|
-
|
|
580
|
-
|
|
697
|
+
if (ctx.spinner) ctx.spinner.start();
|
|
698
|
+
} else {
|
|
699
|
+
ctx.selectedSkills = discoveredSkills;
|
|
581
700
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
if (jsonOnly) {
|
|
617
|
-
printJson({ ok: false, error: "SKILL_MD_NOT_FOUND", message, source, resolvedSource });
|
|
618
|
-
} else {
|
|
619
|
-
if (spinner) spinner.stop();
|
|
620
|
-
console.error(chalk3.red(message));
|
|
621
|
-
}
|
|
622
|
-
process.exitCode = 1;
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
const found = asDiscoveredSkills(discovered, (d) => path2.join(maybeLocalRoot, d.relPath));
|
|
626
|
-
const selected = await resolveDiscoveredSelection(found, spinner);
|
|
627
|
-
if (!selected) return;
|
|
628
|
-
if (spinner) spinner.text = `Installing ${chalk3.cyan(source)} \u2014 discovered ${selected.length} skills...`;
|
|
629
|
-
for (const skill of selected) {
|
|
630
|
-
if (spinner) spinner.text = `Installing ${chalk3.cyan(skill.relPath)} (${scope})...`;
|
|
631
|
-
await installOne(skill.suggestedSource, skill.materializedDir);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
} else {
|
|
635
|
-
const materialized = await materializeSourceToTemp(resolvedSource);
|
|
636
|
-
cleanupMaterialized = materialized.cleanup;
|
|
637
|
-
materializedRoot = materialized.dir;
|
|
638
|
-
const hasSkillMd = fs2.existsSync(path2.join(materializedRoot, "SKILL.md"));
|
|
639
|
-
if (hasSkillMd) {
|
|
640
|
-
await installOne(resolvedSource, materializedRoot);
|
|
641
|
-
} else {
|
|
642
|
-
const discovered = discoverSkillDirsWithHeuristics(materializedRoot, { maxDepth, maxSkills });
|
|
643
|
-
if (discovered.length === 0) {
|
|
644
|
-
const message = `No SKILL.md found in source "${resolvedSource}".`;
|
|
645
|
-
if (jsonOnly) {
|
|
646
|
-
printJson({ ok: false, error: "SKILL_MD_NOT_FOUND", message, source, resolvedSource });
|
|
647
|
-
} else {
|
|
648
|
-
if (spinner) spinner.stop();
|
|
649
|
-
console.error(chalk3.red(message));
|
|
650
|
-
}
|
|
651
|
-
process.exitCode = 1;
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
const found = asDiscoveredSkills(
|
|
655
|
-
discovered,
|
|
656
|
-
(d) => deriveChildSource(resolvedSource, d.relPath),
|
|
657
|
-
(d) => d.absDir
|
|
658
|
-
);
|
|
659
|
-
const selected = await resolveDiscoveredSelection(found, spinner);
|
|
660
|
-
if (!selected) return;
|
|
661
|
-
if (spinner) spinner.text = `Installing ${chalk3.cyan(source)} \u2014 discovered ${selected.length} skills...`;
|
|
662
|
-
for (const skill of selected) {
|
|
663
|
-
if (spinner) spinner.text = `Installing ${chalk3.cyan(skill.relPath)} (${scope})...`;
|
|
664
|
-
await installOne(skill.suggestedSource, skill.materializedDir);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
async function executeInstalls(ctx) {
|
|
704
|
+
const { selectedSkills, targets, scope, forceOverwrite, registryUrl, spinner } = ctx;
|
|
705
|
+
if (!selectedSkills || selectedSkills.length === 0) {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (spinner) {
|
|
709
|
+
spinner.text = selectedSkills.length > 1 ? `Installing ${chalk3.cyan(ctx.source)} \u2014 ${selectedSkills.length} skills...` : `Installing ${chalk3.cyan(ctx.source)}...`;
|
|
710
|
+
}
|
|
711
|
+
for (const skill of selectedSkills) {
|
|
712
|
+
if (spinner) {
|
|
713
|
+
spinner.text = `Installing ${chalk3.cyan(skill.relPath === "." ? ctx.source : skill.relPath)}...`;
|
|
714
|
+
}
|
|
715
|
+
for (const platform of targets) {
|
|
716
|
+
try {
|
|
717
|
+
const record = skill.suggestedSource.startsWith("@") && skill.suggestedSource.includes("/") ? await installRegistrySkill(
|
|
718
|
+
{ spec: skill.suggestedSource, registryUrl },
|
|
719
|
+
{ platform, scope, force: forceOverwrite }
|
|
720
|
+
) : await installSkill(
|
|
721
|
+
{ source: skill.suggestedSource, materializedDir: skill.materializedDir },
|
|
722
|
+
{ platform, scope, force: forceOverwrite, registryUrl }
|
|
723
|
+
);
|
|
724
|
+
ctx.results.push(record);
|
|
725
|
+
void reportDownload(record, registryUrl);
|
|
726
|
+
} catch (error) {
|
|
727
|
+
if (error instanceof SkildError && error.code === "ALREADY_INSTALLED") {
|
|
728
|
+
const details = error.details;
|
|
729
|
+
ctx.skipped.push({
|
|
730
|
+
skillName: details?.skillName || skill.suggestedSource,
|
|
731
|
+
platform,
|
|
732
|
+
installDir: details?.installDir || ""
|
|
733
|
+
});
|
|
734
|
+
continue;
|
|
667
735
|
}
|
|
736
|
+
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
737
|
+
ctx.errors.push({ platform, error: message, inputSource: skill.suggestedSource });
|
|
668
738
|
}
|
|
669
|
-
} finally {
|
|
670
|
-
if (cleanupMaterialized) cleanupMaterialized();
|
|
671
739
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
function reportResults(ctx) {
|
|
743
|
+
const { results, errors, skipped, spinner, jsonOnly, selectedSkills, targets } = ctx;
|
|
744
|
+
if (ctx.cleanupMaterialized) {
|
|
745
|
+
ctx.cleanupMaterialized();
|
|
746
|
+
}
|
|
747
|
+
const isMultiSkill = (selectedSkills?.length ?? 0) > 1;
|
|
748
|
+
if (jsonOnly) {
|
|
749
|
+
if (!isMultiSkill && targets.length === 1) {
|
|
750
|
+
if (errors.length) printJson({ ok: false, error: errors[0]?.error || "Install failed." });
|
|
751
|
+
else printJson(results[0] ?? null);
|
|
752
|
+
} else {
|
|
753
|
+
printJson({
|
|
754
|
+
ok: errors.length === 0,
|
|
755
|
+
source: ctx.source,
|
|
756
|
+
resolvedSource: ctx.resolvedSource,
|
|
757
|
+
scope: ctx.scope,
|
|
758
|
+
recursive: isMultiSkill,
|
|
759
|
+
all: targets.length === PLATFORMS2.length,
|
|
760
|
+
skillCount: selectedSkills?.length ?? 0,
|
|
761
|
+
results,
|
|
762
|
+
errors,
|
|
763
|
+
skipped
|
|
764
|
+
});
|
|
691
765
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
766
|
+
process.exitCode = errors.length ? 1 : 0;
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
if (errors.length === 0 && (results.length > 0 || skipped.length > 0)) {
|
|
770
|
+
const displayName = results[0]?.canonicalName || results[0]?.name || ctx.source;
|
|
771
|
+
if (skipped.length > 0) {
|
|
772
|
+
spinner?.succeed(
|
|
773
|
+
`Installed ${chalk3.green(results.length)} and skipped ${chalk3.dim(skipped.length)} (already installed) to ${chalk3.dim(`${targets.length} platforms`)}`
|
|
696
774
|
);
|
|
697
775
|
} else {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
effectiveRecursive ? `Install had failures (${errors.length}/${attempted} installs failed)` : `Failed to install ${chalk3.red(source)} to ${errors.length}/${targets.length} platforms`
|
|
776
|
+
spinner?.succeed(
|
|
777
|
+
isMultiSkill ? `Installed ${chalk3.green(String(selectedSkills?.length ?? results.length))}${chalk3.dim(" skills")} to ${chalk3.dim(`${targets.length} platforms`)}` : targets.length > 1 ? `Installed ${chalk3.green(displayName)} to ${chalk3.dim(`${results.length} platforms`)}` : `Installed ${chalk3.green(displayName)} to ${chalk3.dim(results[0]?.installDir || "")}`
|
|
701
778
|
);
|
|
702
|
-
process.exitCode = 1;
|
|
703
|
-
if (!effectiveRecursive && targets.length === 1 && errors[0]) console.error(chalk3.red(errors[0].error));
|
|
704
779
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
}
|
|
725
|
-
|
|
780
|
+
} else if (errors.length > 0) {
|
|
781
|
+
const attempted = results.length + errors.length;
|
|
782
|
+
spinner?.fail(
|
|
783
|
+
isMultiSkill ? `Install had failures (${errors.length}/${attempted} installs failed)` : `Failed to install ${chalk3.red(ctx.source)} to ${errors.length}/${targets.length} platforms`
|
|
784
|
+
);
|
|
785
|
+
process.exitCode = 1;
|
|
786
|
+
if (!isMultiSkill && targets.length === 1 && errors[0]) {
|
|
787
|
+
console.error(chalk3.red(errors[0].error));
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
if (isMultiSkill || targets.length > 1) {
|
|
791
|
+
for (const r of results.slice(0, 60)) {
|
|
792
|
+
const displayName = r.canonicalName || r.name;
|
|
793
|
+
const suffix = r.hasSkillMd ? chalk3.green("\u2713") : chalk3.yellow("\u26A0");
|
|
794
|
+
console.log(` ${suffix} ${chalk3.cyan(displayName)} \u2192 ${chalk3.dim(r.platform)}`);
|
|
795
|
+
}
|
|
796
|
+
if (results.length > 60) console.log(chalk3.dim(` ... and ${results.length - 60} more`));
|
|
797
|
+
if (errors.length) {
|
|
798
|
+
console.log(chalk3.yellow("\nFailures:"));
|
|
799
|
+
for (const e of errors) console.log(chalk3.yellow(` - ${e.platform}: ${e.error}`));
|
|
800
|
+
}
|
|
801
|
+
process.exitCode = errors.length ? 1 : 0;
|
|
802
|
+
} else if (!isMultiSkill && targets.length === 1 && results[0]) {
|
|
803
|
+
const record = results[0];
|
|
804
|
+
if (record.hasSkillMd) logger.installDetail("SKILL.md found \u2713");
|
|
805
|
+
else logger.installDetail("Warning: No SKILL.md found", true);
|
|
806
|
+
if (record.skill?.validation && !record.skill.validation.ok) {
|
|
807
|
+
logger.installDetail(`Validation: ${chalk3.yellow("failed")} (${record.skill.validation.issues.length} issues)`, true);
|
|
808
|
+
} else if (record.skill?.validation?.ok) {
|
|
809
|
+
logger.installDetail(`Validation: ${chalk3.green("ok")}`);
|
|
726
810
|
}
|
|
811
|
+
}
|
|
812
|
+
if (skipped.length > 0) {
|
|
813
|
+
const uniqueSkills = [...new Set(skipped.map((s) => s.skillName))];
|
|
814
|
+
console.log(chalk3.dim(`
|
|
815
|
+
Skipped ${skipped.length} already installed (${uniqueSkills.length} skill${uniqueSkills.length > 1 ? "s" : ""}):`));
|
|
816
|
+
const bySkill = /* @__PURE__ */ new Map();
|
|
817
|
+
for (const s of skipped) {
|
|
818
|
+
const platforms = bySkill.get(s.skillName) || [];
|
|
819
|
+
platforms.push(s.platform);
|
|
820
|
+
bySkill.set(s.skillName, platforms);
|
|
821
|
+
}
|
|
822
|
+
for (const [skillName, platforms] of bySkill.entries()) {
|
|
823
|
+
const platformsStr = platforms.length === PLATFORMS2.length ? "all platforms" : platforms.join(", ");
|
|
824
|
+
console.log(chalk3.dim(` - ${skillName} (${platformsStr})`));
|
|
825
|
+
}
|
|
826
|
+
console.log(chalk3.dim(`
|
|
827
|
+
To reinstall, use: ${chalk3.cyan("skild install <source> --force")}`));
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
async function install(source, options = {}) {
|
|
831
|
+
const ctx = createContext(source, options);
|
|
832
|
+
if (!ctx.jsonOnly) {
|
|
833
|
+
ctx.spinner = createSpinner(`Interpreting ${chalk3.cyan(ctx.source)}...`);
|
|
834
|
+
}
|
|
835
|
+
try {
|
|
836
|
+
if (!await resolveSource(ctx)) return;
|
|
837
|
+
if (!await discoverSkills(ctx)) return;
|
|
838
|
+
if (!await promptSelections(ctx)) return;
|
|
839
|
+
await executeInstalls(ctx);
|
|
840
|
+
reportResults(ctx);
|
|
727
841
|
} catch (error) {
|
|
728
842
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
729
|
-
if (jsonOnly) printJson({ ok: false, error: message });
|
|
730
|
-
else
|
|
843
|
+
if (ctx.jsonOnly) printJson({ ok: false, error: message });
|
|
844
|
+
else {
|
|
845
|
+
if (ctx.spinner) ctx.spinner.fail(chalk3.red(message));
|
|
846
|
+
else console.error(chalk3.red(message));
|
|
847
|
+
}
|
|
731
848
|
process.exitCode = 1;
|
|
849
|
+
if (ctx.cleanupMaterialized) ctx.cleanupMaterialized();
|
|
732
850
|
}
|
|
733
851
|
}
|
|
734
852
|
async function reportDownload(record, registryOverride) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skild",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
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",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"commander": "^12.1.0",
|
|
39
39
|
"ora": "^8.0.1",
|
|
40
40
|
"tar": "^7.4.3",
|
|
41
|
-
"@skild/core": "^0.4.
|
|
41
|
+
"@skild/core": "^0.4.6"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "^20.10.0",
|