skild 0.4.5 → 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 +482 -435
- 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,21 +99,32 @@ 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
|
}
|
|
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) {
|
|
118
128
|
while (allNode.children.length === 1 && !allNode.children[0].isLeaf && allNode.children[0].children.length > 0) {
|
|
119
129
|
const singleChild = allNode.children[0];
|
|
120
130
|
for (const grandchild of singleChild.children) {
|
|
@@ -122,8 +132,6 @@ function buildSkillTree(skills) {
|
|
|
122
132
|
}
|
|
123
133
|
allNode.children = singleChild.children;
|
|
124
134
|
}
|
|
125
|
-
const root = { id: "", name: ".", depth: 0, children: [allNode], leafIndices: allNode.leafIndices.slice(), isLeaf: false };
|
|
126
|
-
return root;
|
|
127
135
|
}
|
|
128
136
|
function adjustDepth(node, delta) {
|
|
129
137
|
node.depth += delta;
|
|
@@ -134,28 +142,54 @@ function adjustDepth(node, delta) {
|
|
|
134
142
|
function flattenTree(root) {
|
|
135
143
|
const result = [];
|
|
136
144
|
function walk(node) {
|
|
137
|
-
if (node.id !== "")
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
for (const child of node.children) {
|
|
141
|
-
walk(child);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
for (const child of root.children) {
|
|
145
|
-
walk(child);
|
|
145
|
+
if (node.id !== "") result.push(node);
|
|
146
|
+
for (const child of node.children) walk(child);
|
|
146
147
|
}
|
|
148
|
+
for (const child of root.children) walk(child);
|
|
147
149
|
return result;
|
|
148
150
|
}
|
|
149
|
-
function
|
|
151
|
+
function getNodeSelection(node, selected) {
|
|
150
152
|
const total = node.leafIndices.length;
|
|
151
|
-
if (total === 0) return "none";
|
|
152
|
-
let
|
|
153
|
+
if (total === 0) return { state: "none", selectedCount: 0 };
|
|
154
|
+
let selectedCount = 0;
|
|
153
155
|
for (const idx of node.leafIndices) {
|
|
154
|
-
if (selected.has(idx))
|
|
156
|
+
if (selected.has(idx)) selectedCount++;
|
|
155
157
|
}
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
|
|
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;
|
|
180
|
+
}
|
|
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);
|
|
159
193
|
}
|
|
160
194
|
async function interactiveTreeSelect(items, options) {
|
|
161
195
|
const { title, subtitle, buildTree, formatNode, defaultAll, defaultSelected } = options;
|
|
@@ -178,103 +212,97 @@ async function interactiveTreeSelect(items, options) {
|
|
|
178
212
|
stdin.setRawMode(true);
|
|
179
213
|
stdin.resume();
|
|
180
214
|
readline.emitKeypressEvents(stdin);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const totalLines = flatNodes.length + 4;
|
|
184
|
-
stdout.write(`\x1B[${totalLines}A`);
|
|
185
|
-
stdout.write("\x1B[0J");
|
|
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
|
-
function initialRender() {
|
|
201
|
-
stdout.write(`
|
|
202
|
-
${chalk2.bold.cyan(title)}
|
|
203
|
-
`);
|
|
204
|
-
stdout.write(`${chalk2.dim(subtitle)}
|
|
205
|
-
|
|
206
|
-
`);
|
|
207
|
-
for (let i = 0; i < flatNodes.length; i++) {
|
|
208
|
-
const node = flatNodes[i];
|
|
209
|
-
const state = getNodeState(node, selected);
|
|
210
|
-
const isCursor = i === cursor;
|
|
211
|
-
stdout.write(formatNode(node, state, isCursor) + "\n");
|
|
212
|
-
}
|
|
213
|
-
stdout.write("\n");
|
|
214
|
-
}
|
|
215
|
-
initialRender();
|
|
215
|
+
const renderer = createRenderer(title, subtitle, flatNodes, selected, () => cursor, formatNode);
|
|
216
|
+
writeToTerminal(stdout, renderer.renderContent());
|
|
216
217
|
return new Promise((resolve) => {
|
|
217
|
-
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
|
+
}
|
|
218
224
|
stdin.setRawMode(wasRaw);
|
|
219
225
|
stdin.pause();
|
|
220
226
|
stdin.removeListener("keypress", onKeypress);
|
|
221
227
|
stdout.write("\x1B[?25h");
|
|
222
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
|
+
}
|
|
223
247
|
function onKeypress(_str, key) {
|
|
224
248
|
if (key.ctrl && key.name === "c") {
|
|
225
|
-
cleanup();
|
|
249
|
+
cleanup(true);
|
|
226
250
|
resolve(null);
|
|
227
251
|
return;
|
|
228
252
|
}
|
|
229
253
|
if (key.name === "return" || key.name === "enter") {
|
|
230
|
-
cleanup();
|
|
231
|
-
|
|
232
|
-
if (result.length === 0) {
|
|
233
|
-
resolve(null);
|
|
234
|
-
} else {
|
|
235
|
-
resolve(result);
|
|
236
|
-
}
|
|
254
|
+
cleanup(true);
|
|
255
|
+
resolve(selected.size > 0 ? Array.from(selected) : null);
|
|
237
256
|
return;
|
|
238
257
|
}
|
|
239
258
|
if (key.name === "up") {
|
|
240
259
|
cursor = (cursor - 1 + flatNodes.length) % flatNodes.length;
|
|
241
|
-
|
|
260
|
+
rerender();
|
|
242
261
|
return;
|
|
243
262
|
}
|
|
244
263
|
if (key.name === "down") {
|
|
245
264
|
cursor = (cursor + 1) % flatNodes.length;
|
|
246
|
-
|
|
265
|
+
rerender();
|
|
247
266
|
return;
|
|
248
267
|
}
|
|
249
268
|
if (key.name === "space") {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (state === "all") {
|
|
253
|
-
for (const idx of node.leafIndices) {
|
|
254
|
-
selected.delete(idx);
|
|
255
|
-
}
|
|
256
|
-
} else {
|
|
257
|
-
for (const idx of node.leafIndices) {
|
|
258
|
-
selected.add(idx);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
render();
|
|
269
|
+
toggleNode(flatNodes[cursor]);
|
|
270
|
+
rerender();
|
|
262
271
|
return;
|
|
263
272
|
}
|
|
264
273
|
if (key.name === "a") {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
selected.clear();
|
|
268
|
-
} else {
|
|
269
|
-
for (let i = 0; i < items.length; i++) selected.add(i);
|
|
270
|
-
}
|
|
271
|
-
render();
|
|
274
|
+
toggleAll();
|
|
275
|
+
rerender();
|
|
272
276
|
return;
|
|
273
277
|
}
|
|
274
278
|
}
|
|
275
279
|
stdin.on("keypress", onKeypress);
|
|
276
280
|
});
|
|
277
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
|
+
}
|
|
278
306
|
async function promptSkillsInteractive(skills, options = {}) {
|
|
279
307
|
if (skills.length === 0) return null;
|
|
280
308
|
const targetPlatforms = options.targetPlatforms || [];
|
|
@@ -292,100 +320,53 @@ async function promptSkillsInteractive(skills, options = {}) {
|
|
|
292
320
|
title: "Select skills to install",
|
|
293
321
|
subtitle: "\u2191\u2193 navigate \u2022 Space toggle \u2022 Enter confirm",
|
|
294
322
|
buildTree: buildSkillTree,
|
|
295
|
-
formatNode: (node,
|
|
296
|
-
|
|
297
|
-
const checkbox = state === "all" ? chalk2.green("\u25CF") : state === "partial" ? chalk2.yellow("\u25D0") : chalk2.dim("\u25CB");
|
|
298
|
-
let installedTag = "";
|
|
323
|
+
formatNode: (node, selection, isCursor) => {
|
|
324
|
+
let suffix = "";
|
|
299
325
|
if (node.isLeaf && node.leafIndices.length === 1) {
|
|
300
326
|
const skill = skills[node.leafIndices[0]];
|
|
301
327
|
if (skill?.installedPlatforms?.length) {
|
|
302
328
|
if (skill.installedPlatforms.length === targetPlatforms.length && targetPlatforms.length > 0) {
|
|
303
|
-
|
|
329
|
+
suffix = chalk2.dim(" [installed]");
|
|
304
330
|
} else if (skill.installedPlatforms.length > 0) {
|
|
305
|
-
|
|
331
|
+
suffix = chalk2.dim(` [installed on ${skill.installedPlatforms.length}]`);
|
|
306
332
|
}
|
|
307
333
|
}
|
|
308
334
|
}
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
let hint = "";
|
|
313
|
-
if (isCursor && node.leafIndices.length > 0) {
|
|
314
|
-
if (installedTag && state !== "all") {
|
|
315
|
-
hint = chalk2.dim(" \u2190 Space to reinstall");
|
|
316
|
-
} else {
|
|
317
|
-
hint = state === "all" ? chalk2.dim(" \u2190 Space to deselect") : chalk2.dim(" \u2190 Space to select");
|
|
318
|
-
}
|
|
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");
|
|
319
338
|
}
|
|
320
|
-
return
|
|
339
|
+
return formatted;
|
|
321
340
|
},
|
|
322
341
|
defaultAll: false,
|
|
323
|
-
// We handle default selection manually
|
|
324
342
|
defaultSelected
|
|
325
343
|
});
|
|
326
|
-
if (!selectedIndices
|
|
327
|
-
|
|
328
|
-
|
|
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);
|
|
329
347
|
console.log(chalk2.green(`
|
|
330
|
-
\u2713 ${
|
|
348
|
+
\u2713 Selected ${selectedSkills.length} skill${selectedSkills.length > 1 ? "s" : ""}: ${chalk2.cyan(names.join(", "))}
|
|
331
349
|
`));
|
|
332
|
-
return
|
|
350
|
+
return selectedSkills;
|
|
333
351
|
}
|
|
334
352
|
async function promptPlatformsInteractive(options = {}) {
|
|
335
353
|
const platformItems = PLATFORMS.map((p) => ({ platform: p }));
|
|
336
354
|
const selectedIndices = await interactiveTreeSelect(platformItems, {
|
|
337
355
|
title: "Select target platforms",
|
|
338
356
|
subtitle: "\u2191\u2193 navigate \u2022 Space toggle \u2022 Enter confirm",
|
|
339
|
-
buildTree:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
children: [],
|
|
345
|
-
leafIndices: [],
|
|
346
|
-
isLeaf: false
|
|
347
|
-
};
|
|
348
|
-
for (let i = 0; i < items.length; i++) {
|
|
349
|
-
const p = items[i].platform;
|
|
350
|
-
allNode.children.push({
|
|
351
|
-
id: p,
|
|
352
|
-
name: p,
|
|
353
|
-
depth: 2,
|
|
354
|
-
children: [],
|
|
355
|
-
leafIndices: [i],
|
|
356
|
-
isLeaf: true
|
|
357
|
-
});
|
|
358
|
-
allNode.leafIndices.push(i);
|
|
359
|
-
}
|
|
360
|
-
const root = { id: "", name: ".", depth: 0, children: [allNode], leafIndices: allNode.leafIndices.slice(), isLeaf: false };
|
|
361
|
-
return root;
|
|
362
|
-
},
|
|
363
|
-
formatNode: (node, state, isCursor) => {
|
|
364
|
-
const indent = " ".repeat(node.depth - 1);
|
|
365
|
-
const checkbox = state === "all" ? chalk2.green("\u25CF") : state === "partial" ? chalk2.yellow("\u25D0") : chalk2.dim("\u25CB");
|
|
366
|
-
let displayName = node.name;
|
|
367
|
-
if (node.name !== "All Platforms") {
|
|
368
|
-
const platform = node.name;
|
|
369
|
-
const display = PLATFORM_DISPLAY[platform];
|
|
370
|
-
if (display) displayName = display.name;
|
|
371
|
-
}
|
|
372
|
-
const name = isCursor ? chalk2.cyan.underline(displayName) : displayName;
|
|
373
|
-
const cursor = isCursor ? chalk2.cyan("\u203A ") : " ";
|
|
374
|
-
const count = node.leafIndices.length > 1 ? chalk2.dim(` (${node.leafIndices.length})`) : "";
|
|
375
|
-
let hint = "";
|
|
376
|
-
if (isCursor && node.leafIndices.length > 0) {
|
|
377
|
-
hint = state === "all" ? chalk2.dim(" \u2190 Space to deselect") : chalk2.dim(" \u2190 Space to select");
|
|
378
|
-
}
|
|
379
|
-
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);
|
|
380
362
|
},
|
|
381
363
|
defaultAll: options.defaultAll !== false
|
|
382
364
|
});
|
|
383
|
-
if (!selectedIndices
|
|
384
|
-
return null;
|
|
385
|
-
}
|
|
365
|
+
if (!selectedIndices) return null;
|
|
386
366
|
const selected = selectedIndices.map((i) => PLATFORMS[i]);
|
|
367
|
+
const names = selected.map((p) => PLATFORM_DISPLAY[p] || p);
|
|
387
368
|
console.log(chalk2.green(`
|
|
388
|
-
\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(", "))}
|
|
389
370
|
`));
|
|
390
371
|
return selected;
|
|
391
372
|
}
|
|
@@ -491,315 +472,381 @@ function asDiscoveredSkills(discovered, toSuggestedSource, toMaterializedDir) {
|
|
|
491
472
|
materializedDir: toMaterializedDir ? toMaterializedDir(d) : void 0
|
|
492
473
|
}));
|
|
493
474
|
}
|
|
494
|
-
|
|
475
|
+
function createContext(source, options) {
|
|
495
476
|
const scope = options.local ? "project" : "global";
|
|
496
477
|
const auth = loadRegistryAuth();
|
|
497
|
-
const
|
|
478
|
+
const registryUrl = options.registry || auth?.registryUrl;
|
|
498
479
|
const jsonOnly = Boolean(options.json);
|
|
499
|
-
const recursive = Boolean(options.recursive);
|
|
500
480
|
const yes = Boolean(options.yes);
|
|
501
|
-
const maxDepth = parsePositiveInt(options.depth, 6);
|
|
502
|
-
const maxSkills = parsePositiveInt(options.maxSkills, 200);
|
|
503
481
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !jsonOnly;
|
|
504
|
-
const requestedAll = Boolean(options.all);
|
|
505
|
-
const requestedTarget = options.target;
|
|
506
|
-
if (requestedAll && options.target) {
|
|
507
|
-
const message = "Invalid options: use either --all or --target, not both.";
|
|
508
|
-
if (jsonOnly) printJson({ ok: false, error: message });
|
|
509
|
-
else console.error(chalk3.red(message));
|
|
510
|
-
process.exitCode = 1;
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
482
|
let targets = [];
|
|
514
|
-
let
|
|
515
|
-
if (
|
|
483
|
+
let needsPlatformPrompt = false;
|
|
484
|
+
if (options.all) {
|
|
516
485
|
targets = [...PLATFORMS2];
|
|
517
|
-
} else if (
|
|
518
|
-
targets = [
|
|
486
|
+
} else if (options.target) {
|
|
487
|
+
targets = [options.target];
|
|
519
488
|
} else if (yes) {
|
|
520
489
|
targets = [...PLATFORMS2];
|
|
521
490
|
} else if (interactive) {
|
|
522
|
-
|
|
491
|
+
needsPlatformPrompt = true;
|
|
523
492
|
targets = [...PLATFORMS2];
|
|
524
493
|
} else {
|
|
525
494
|
targets = ["claude"];
|
|
526
495
|
}
|
|
527
|
-
|
|
528
|
-
|
|
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
|
+
}
|
|
529
|
+
try {
|
|
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;
|
|
538
|
+
}
|
|
539
|
+
} catch (error) {
|
|
540
|
+
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
541
|
+
if (ctx.jsonOnly) printJson({ ok: false, error: message });
|
|
542
|
+
else console.error(chalk3.red(message));
|
|
543
|
+
process.exitCode = 1;
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
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
|
+
}
|
|
529
553
|
try {
|
|
530
|
-
if (
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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;
|
|
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;
|
|
582
|
+
}
|
|
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;
|
|
591
|
+
}
|
|
592
|
+
const discovered = discoverSkillDirsWithHeuristics(ctx.materializedDir, { maxDepth, maxSkills });
|
|
593
|
+
if (discovered.length === 0) {
|
|
594
|
+
const message = `No SKILL.md found in source "${resolvedSource}".`;
|
|
595
|
+
if (jsonOnly) {
|
|
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;
|
|
535
603
|
}
|
|
604
|
+
ctx.discoveredSkills = asDiscoveredSkills(
|
|
605
|
+
discovered,
|
|
606
|
+
(d) => deriveChildSource(resolvedSource, d.relPath),
|
|
607
|
+
(d) => d.absDir
|
|
608
|
+
);
|
|
609
|
+
return true;
|
|
536
610
|
} catch (error) {
|
|
537
611
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
538
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."));
|
|
629
|
+
process.exitCode = 1;
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
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 });
|
|
539
643
|
else console.error(chalk3.red(message));
|
|
540
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.
|
|
677
|
+
`;
|
|
678
|
+
console.log(chalk3.yellow(headline));
|
|
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."));
|
|
691
|
+
process.exitCode = 1;
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
ctx.targets = selectedPlatforms;
|
|
695
|
+
ctx.needsPlatformPrompt = false;
|
|
696
|
+
}
|
|
697
|
+
if (ctx.spinner) ctx.spinner.start();
|
|
698
|
+
} else {
|
|
699
|
+
ctx.selectedSkills = discoveredSkills;
|
|
700
|
+
}
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
async function executeInstalls(ctx) {
|
|
704
|
+
const { selectedSkills, targets, scope, forceOverwrite, registryUrl, spinner } = ctx;
|
|
705
|
+
if (!selectedSkills || selectedSkills.length === 0) {
|
|
541
706
|
return;
|
|
542
707
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
for (const
|
|
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) {
|
|
551
716
|
try {
|
|
552
|
-
const record =
|
|
553
|
-
{ spec:
|
|
554
|
-
{ platform
|
|
717
|
+
const record = skill.suggestedSource.startsWith("@") && skill.suggestedSource.includes("/") ? await installRegistrySkill(
|
|
718
|
+
{ spec: skill.suggestedSource, registryUrl },
|
|
719
|
+
{ platform, scope, force: forceOverwrite }
|
|
555
720
|
) : await installSkill(
|
|
556
|
-
{ source:
|
|
557
|
-
{ platform
|
|
721
|
+
{ source: skill.suggestedSource, materializedDir: skill.materializedDir },
|
|
722
|
+
{ platform, scope, force: forceOverwrite, registryUrl }
|
|
558
723
|
);
|
|
559
|
-
results.push(record);
|
|
560
|
-
void reportDownload(record,
|
|
724
|
+
ctx.results.push(record);
|
|
725
|
+
void reportDownload(record, registryUrl);
|
|
561
726
|
} catch (error) {
|
|
562
727
|
if (error instanceof SkildError && error.code === "ALREADY_INSTALLED") {
|
|
563
728
|
const details = error.details;
|
|
564
|
-
skipped.push({
|
|
565
|
-
skillName: details?.skillName ||
|
|
566
|
-
platform
|
|
729
|
+
ctx.skipped.push({
|
|
730
|
+
skillName: details?.skillName || skill.suggestedSource,
|
|
731
|
+
platform,
|
|
567
732
|
installDir: details?.installDir || ""
|
|
568
733
|
});
|
|
569
734
|
continue;
|
|
570
735
|
}
|
|
571
736
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
572
|
-
errors.push({ platform
|
|
737
|
+
ctx.errors.push({ platform, error: message, inputSource: skill.suggestedSource });
|
|
573
738
|
}
|
|
574
739
|
}
|
|
575
740
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const headline = found.length === 1 ? `No SKILL.md found at root. Found 1 skill.
|
|
601
|
-
` : `No SKILL.md found at root. Found ${found.length} skills.
|
|
602
|
-
`;
|
|
603
|
-
console.log(chalk3.yellow(headline));
|
|
604
|
-
if (!interactive && !yes) {
|
|
605
|
-
console.log(chalk3.dim(`Tip: rerun with ${chalk3.cyan("skild install <source> --recursive")} to install all.`));
|
|
606
|
-
process.exitCode = 1;
|
|
607
|
-
return null;
|
|
608
|
-
}
|
|
609
|
-
if (!yes) {
|
|
610
|
-
const selected = await promptSkillsInteractive(found, { defaultAll: true });
|
|
611
|
-
if (!selected) {
|
|
612
|
-
console.log(chalk3.red("No skills selected."));
|
|
613
|
-
process.exitCode = 1;
|
|
614
|
-
return null;
|
|
615
|
-
}
|
|
616
|
-
if (deferPlatformSelection) {
|
|
617
|
-
const selectedPlatforms = await promptPlatformsInteractive({ defaultAll: true });
|
|
618
|
-
if (!selectedPlatforms) {
|
|
619
|
-
console.log(chalk3.red("No platforms selected."));
|
|
620
|
-
process.exitCode = 1;
|
|
621
|
-
return null;
|
|
622
|
-
}
|
|
623
|
-
targets = selectedPlatforms;
|
|
624
|
-
deferPlatformSelection = false;
|
|
625
|
-
}
|
|
626
|
-
effectiveRecursive = true;
|
|
627
|
-
if (spinner) spinner.start();
|
|
628
|
-
recursiveSkillCount = selected.length;
|
|
629
|
-
return selected;
|
|
630
|
-
}
|
|
631
|
-
effectiveRecursive = true;
|
|
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
|
+
});
|
|
632
765
|
}
|
|
633
|
-
|
|
634
|
-
return
|
|
766
|
+
process.exitCode = errors.length ? 1 : 0;
|
|
767
|
+
return;
|
|
635
768
|
}
|
|
636
|
-
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
let materializedRoot = null;
|
|
642
|
-
try {
|
|
643
|
-
async function ensurePlatformSelection() {
|
|
644
|
-
if (!deferPlatformSelection) return true;
|
|
645
|
-
const selectedPlatforms = await promptPlatformsInteractive({ defaultAll: true });
|
|
646
|
-
if (!selectedPlatforms) {
|
|
647
|
-
console.log(chalk3.red("No platforms selected."));
|
|
648
|
-
process.exitCode = 1;
|
|
649
|
-
return false;
|
|
650
|
-
}
|
|
651
|
-
targets = selectedPlatforms;
|
|
652
|
-
deferPlatformSelection = false;
|
|
653
|
-
return true;
|
|
654
|
-
}
|
|
655
|
-
if (resolvedSource.startsWith("@") && resolvedSource.includes("/")) {
|
|
656
|
-
if (!await ensurePlatformSelection()) return;
|
|
657
|
-
await installOne(resolvedSource);
|
|
658
|
-
} else {
|
|
659
|
-
const maybeLocalRoot = path2.resolve(resolvedSource);
|
|
660
|
-
const isLocal = fs2.existsSync(maybeLocalRoot);
|
|
661
|
-
if (isLocal) {
|
|
662
|
-
const hasSkillMd = fs2.existsSync(path2.join(maybeLocalRoot, "SKILL.md"));
|
|
663
|
-
if (hasSkillMd) {
|
|
664
|
-
if (!await ensurePlatformSelection()) return;
|
|
665
|
-
await installOne(resolvedSource);
|
|
666
|
-
} else {
|
|
667
|
-
const discovered = discoverSkillDirsWithHeuristics(maybeLocalRoot, { maxDepth, maxSkills });
|
|
668
|
-
if (discovered.length === 0) {
|
|
669
|
-
const message = `No SKILL.md found at ${maybeLocalRoot} (or within subdirectories).`;
|
|
670
|
-
if (jsonOnly) {
|
|
671
|
-
printJson({ ok: false, error: "SKILL_MD_NOT_FOUND", message, source, resolvedSource });
|
|
672
|
-
} else {
|
|
673
|
-
if (spinner) spinner.stop();
|
|
674
|
-
console.error(chalk3.red(message));
|
|
675
|
-
}
|
|
676
|
-
process.exitCode = 1;
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
const found = asDiscoveredSkills(discovered, (d) => path2.join(maybeLocalRoot, d.relPath));
|
|
680
|
-
const selected = await resolveDiscoveredSelection(found, spinner);
|
|
681
|
-
if (!selected) return;
|
|
682
|
-
if (spinner) spinner.text = `Installing ${chalk3.cyan(source)} \u2014 discovered ${selected.length} skills...`;
|
|
683
|
-
for (const skill of selected) {
|
|
684
|
-
if (spinner) spinner.text = `Installing ${chalk3.cyan(skill.relPath)} (${scope})...`;
|
|
685
|
-
await installOne(skill.suggestedSource, skill.materializedDir);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
} else {
|
|
689
|
-
const materialized = await materializeSourceToTemp(resolvedSource);
|
|
690
|
-
cleanupMaterialized = materialized.cleanup;
|
|
691
|
-
materializedRoot = materialized.dir;
|
|
692
|
-
const hasSkillMd = fs2.existsSync(path2.join(materializedRoot, "SKILL.md"));
|
|
693
|
-
if (hasSkillMd) {
|
|
694
|
-
await installOne(resolvedSource, materializedRoot);
|
|
695
|
-
} else {
|
|
696
|
-
const discovered = discoverSkillDirsWithHeuristics(materializedRoot, { maxDepth, maxSkills });
|
|
697
|
-
if (discovered.length === 0) {
|
|
698
|
-
const message = `No SKILL.md found in source "${resolvedSource}".`;
|
|
699
|
-
if (jsonOnly) {
|
|
700
|
-
printJson({ ok: false, error: "SKILL_MD_NOT_FOUND", message, source, resolvedSource });
|
|
701
|
-
} else {
|
|
702
|
-
if (spinner) spinner.stop();
|
|
703
|
-
console.error(chalk3.red(message));
|
|
704
|
-
}
|
|
705
|
-
process.exitCode = 1;
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
|
-
const found = asDiscoveredSkills(
|
|
709
|
-
discovered,
|
|
710
|
-
(d) => deriveChildSource(resolvedSource, d.relPath),
|
|
711
|
-
(d) => d.absDir
|
|
712
|
-
);
|
|
713
|
-
const selected = await resolveDiscoveredSelection(found, spinner);
|
|
714
|
-
if (!selected) return;
|
|
715
|
-
if (spinner) spinner.text = `Installing ${chalk3.cyan(source)} \u2014 discovered ${selected.length} skills...`;
|
|
716
|
-
for (const skill of selected) {
|
|
717
|
-
if (spinner) spinner.text = `Installing ${chalk3.cyan(skill.relPath)} (${scope})...`;
|
|
718
|
-
await installOne(skill.suggestedSource, skill.materializedDir);
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
} finally {
|
|
724
|
-
if (cleanupMaterialized) cleanupMaterialized();
|
|
725
|
-
}
|
|
726
|
-
if (jsonOnly) {
|
|
727
|
-
if (!effectiveRecursive && targets.length === 1) {
|
|
728
|
-
if (errors.length) printJson({ ok: false, error: errors[0]?.error || "Install failed." });
|
|
729
|
-
else printJson(results[0] ?? null);
|
|
730
|
-
} else {
|
|
731
|
-
printJson({
|
|
732
|
-
ok: errors.length === 0,
|
|
733
|
-
source,
|
|
734
|
-
resolvedSource,
|
|
735
|
-
scope,
|
|
736
|
-
recursive: effectiveRecursive,
|
|
737
|
-
all: allPlatformsSelected,
|
|
738
|
-
recursiveSkillCount,
|
|
739
|
-
results,
|
|
740
|
-
errors
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
process.exitCode = errors.length ? 1 : 0;
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
|
-
if (errors.length === 0) {
|
|
747
|
-
const displayName = results[0]?.canonicalName || results[0]?.name || source;
|
|
748
|
-
spinner.succeed(
|
|
749
|
-
effectiveRecursive ? `Installed ${chalk3.green(String(recursiveSkillCount ?? 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 || "")}`
|
|
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`)}`
|
|
750
774
|
);
|
|
751
775
|
} else {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
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 || "")}`
|
|
755
778
|
);
|
|
756
|
-
process.exitCode = 1;
|
|
757
|
-
if (!effectiveRecursive && targets.length === 1 && errors[0]) console.error(chalk3.red(errors[0].error));
|
|
758
779
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
}
|
|
768
|
-
} else if (effectiveRecursive || targets.length > 1) {
|
|
769
|
-
for (const r of results.slice(0, 60)) {
|
|
770
|
-
const displayName = r.canonicalName || r.name;
|
|
771
|
-
const suffix = r.hasSkillMd ? chalk3.green("\u2713") : chalk3.yellow("\u26A0");
|
|
772
|
-
console.log(` ${suffix} ${chalk3.cyan(displayName)} \u2192 ${chalk3.dim(r.platform)}`);
|
|
773
|
-
}
|
|
774
|
-
if (results.length > 60) console.log(chalk3.dim(` ... and ${results.length - 60} more`));
|
|
775
|
-
if (errors.length) {
|
|
776
|
-
console.log(chalk3.yellow("\nFailures:"));
|
|
777
|
-
for (const e of errors) console.log(chalk3.yellow(` - ${e.platform}: ${e.error}`));
|
|
778
|
-
}
|
|
779
|
-
process.exitCode = errors.length ? 1 : 0;
|
|
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));
|
|
780
788
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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")}`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (skipped.length > 0) {
|
|
813
|
+
const uniqueSkills = [...new Set(skipped.map((s) => s.skillName))];
|
|
814
|
+
console.log(chalk3.dim(`
|
|
784
815
|
Skipped ${skipped.length} already installed (${uniqueSkills.length} skill${uniqueSkills.length > 1 ? "s" : ""}):`));
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
}
|
|
795
|
-
console.log(chalk3.dim(`
|
|
796
|
-
To reinstall, use: ${chalk3.cyan("skild install <source> --force")}`));
|
|
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})`));
|
|
797
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);
|
|
798
841
|
} catch (error) {
|
|
799
842
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
800
|
-
if (jsonOnly) printJson({ ok: false, error: message });
|
|
801
|
-
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
|
+
}
|
|
802
848
|
process.exitCode = 1;
|
|
849
|
+
if (ctx.cleanupMaterialized) ctx.cleanupMaterialized();
|
|
803
850
|
}
|
|
804
851
|
}
|
|
805
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",
|