skild 0.4.5 → 0.4.7
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 +484 -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,98 @@ 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
|
+
opencode: "OpenCode"
|
|
288
|
+
};
|
|
289
|
+
function formatTreeNode(node, selection, isCursor, options = {}) {
|
|
290
|
+
const { state, selectedCount } = selection;
|
|
291
|
+
const totalCount = node.leafIndices.length;
|
|
292
|
+
const indent = " ".repeat(node.depth - 1);
|
|
293
|
+
const checkbox = state === "all" ? chalk2.green("\u25CF") : state === "partial" ? chalk2.yellow("\u25D0") : chalk2.dim("\u25CB");
|
|
294
|
+
const name = isCursor ? chalk2.cyan.underline(node.name) : node.name;
|
|
295
|
+
const cursorMark = isCursor ? chalk2.cyan("\u203A ") : " ";
|
|
296
|
+
let count = "";
|
|
297
|
+
if (totalCount > 1) {
|
|
298
|
+
count = chalk2.dim(` (${selectedCount}/${totalCount})`);
|
|
299
|
+
}
|
|
300
|
+
const suffix = options.suffix || "";
|
|
301
|
+
let hint = "";
|
|
302
|
+
if (isCursor && totalCount > 0) {
|
|
303
|
+
hint = state === "all" ? chalk2.dim(" \u2190 Space to deselect") : chalk2.dim(" \u2190 Space to select");
|
|
304
|
+
}
|
|
305
|
+
return `${cursorMark}${indent}${checkbox} ${name}${count}${suffix}${hint}`;
|
|
306
|
+
}
|
|
278
307
|
async function promptSkillsInteractive(skills, options = {}) {
|
|
279
308
|
if (skills.length === 0) return null;
|
|
280
309
|
const targetPlatforms = options.targetPlatforms || [];
|
|
@@ -292,100 +321,53 @@ async function promptSkillsInteractive(skills, options = {}) {
|
|
|
292
321
|
title: "Select skills to install",
|
|
293
322
|
subtitle: "\u2191\u2193 navigate \u2022 Space toggle \u2022 Enter confirm",
|
|
294
323
|
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 = "";
|
|
324
|
+
formatNode: (node, selection, isCursor) => {
|
|
325
|
+
let suffix = "";
|
|
299
326
|
if (node.isLeaf && node.leafIndices.length === 1) {
|
|
300
327
|
const skill = skills[node.leafIndices[0]];
|
|
301
328
|
if (skill?.installedPlatforms?.length) {
|
|
302
329
|
if (skill.installedPlatforms.length === targetPlatforms.length && targetPlatforms.length > 0) {
|
|
303
|
-
|
|
330
|
+
suffix = chalk2.dim(" [installed]");
|
|
304
331
|
} else if (skill.installedPlatforms.length > 0) {
|
|
305
|
-
|
|
332
|
+
suffix = chalk2.dim(` [installed on ${skill.installedPlatforms.length}]`);
|
|
306
333
|
}
|
|
307
334
|
}
|
|
308
335
|
}
|
|
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
|
-
}
|
|
336
|
+
const formatted = formatTreeNode(node, selection, isCursor, { suffix });
|
|
337
|
+
if (isCursor && suffix && selection.state !== "all") {
|
|
338
|
+
return formatted.replace("\u2190 Space to select", "\u2190 Space to reinstall");
|
|
319
339
|
}
|
|
320
|
-
return
|
|
340
|
+
return formatted;
|
|
321
341
|
},
|
|
322
342
|
defaultAll: false,
|
|
323
|
-
// We handle default selection manually
|
|
324
343
|
defaultSelected
|
|
325
344
|
});
|
|
326
|
-
if (!selectedIndices
|
|
327
|
-
|
|
328
|
-
|
|
345
|
+
if (!selectedIndices) return null;
|
|
346
|
+
const selectedSkills = selectedIndices.map((i) => skills[i]);
|
|
347
|
+
const names = selectedSkills.map((s) => s.relPath === "." ? s.suggestedSource : s.relPath);
|
|
329
348
|
console.log(chalk2.green(`
|
|
330
|
-
\u2713 ${
|
|
349
|
+
\u2713 Selected ${selectedSkills.length} skill${selectedSkills.length > 1 ? "s" : ""}: ${chalk2.cyan(names.join(", "))}
|
|
331
350
|
`));
|
|
332
|
-
return
|
|
351
|
+
return selectedSkills;
|
|
333
352
|
}
|
|
334
353
|
async function promptPlatformsInteractive(options = {}) {
|
|
335
354
|
const platformItems = PLATFORMS.map((p) => ({ platform: p }));
|
|
336
355
|
const selectedIndices = await interactiveTreeSelect(platformItems, {
|
|
337
356
|
title: "Select target platforms",
|
|
338
357
|
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}`;
|
|
358
|
+
buildTree: buildPlatformTree,
|
|
359
|
+
formatNode: (node, selection, isCursor) => {
|
|
360
|
+
const displayName = node.name === "All Platforms" ? node.name : PLATFORM_DISPLAY[node.name] || node.name;
|
|
361
|
+
const modifiedNode = { ...node, name: displayName };
|
|
362
|
+
return formatTreeNode(modifiedNode, selection, isCursor);
|
|
380
363
|
},
|
|
381
364
|
defaultAll: options.defaultAll !== false
|
|
382
365
|
});
|
|
383
|
-
if (!selectedIndices
|
|
384
|
-
return null;
|
|
385
|
-
}
|
|
366
|
+
if (!selectedIndices) return null;
|
|
386
367
|
const selected = selectedIndices.map((i) => PLATFORMS[i]);
|
|
368
|
+
const names = selected.map((p) => PLATFORM_DISPLAY[p] || p);
|
|
387
369
|
console.log(chalk2.green(`
|
|
388
|
-
\u2713 Installing to ${selected.length} platform${selected.length > 1 ? "s" : ""}
|
|
370
|
+
\u2713 Installing to ${selected.length} platform${selected.length > 1 ? "s" : ""}: ${chalk2.cyan(names.join(", "))}
|
|
389
371
|
`));
|
|
390
372
|
return selected;
|
|
391
373
|
}
|
|
@@ -450,6 +432,7 @@ function discoverSkillDirsWithHeuristics(rootDir, options) {
|
|
|
450
432
|
path.join(".agent", "skills"),
|
|
451
433
|
path.join(".claude", "skills"),
|
|
452
434
|
path.join(".codex", "skills"),
|
|
435
|
+
path.join(".opencode", "skill"),
|
|
453
436
|
path.join(".github", "skills")
|
|
454
437
|
];
|
|
455
438
|
for (const rel of candidates) {
|
|
@@ -491,315 +474,381 @@ function asDiscoveredSkills(discovered, toSuggestedSource, toMaterializedDir) {
|
|
|
491
474
|
materializedDir: toMaterializedDir ? toMaterializedDir(d) : void 0
|
|
492
475
|
}));
|
|
493
476
|
}
|
|
494
|
-
|
|
477
|
+
function createContext(source, options) {
|
|
495
478
|
const scope = options.local ? "project" : "global";
|
|
496
479
|
const auth = loadRegistryAuth();
|
|
497
|
-
const
|
|
480
|
+
const registryUrl = options.registry || auth?.registryUrl;
|
|
498
481
|
const jsonOnly = Boolean(options.json);
|
|
499
|
-
const recursive = Boolean(options.recursive);
|
|
500
482
|
const yes = Boolean(options.yes);
|
|
501
|
-
const maxDepth = parsePositiveInt(options.depth, 6);
|
|
502
|
-
const maxSkills = parsePositiveInt(options.maxSkills, 200);
|
|
503
483
|
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
484
|
let targets = [];
|
|
514
|
-
let
|
|
515
|
-
if (
|
|
485
|
+
let needsPlatformPrompt = false;
|
|
486
|
+
if (options.all) {
|
|
516
487
|
targets = [...PLATFORMS2];
|
|
517
|
-
} else if (
|
|
518
|
-
targets = [
|
|
488
|
+
} else if (options.target) {
|
|
489
|
+
targets = [options.target];
|
|
519
490
|
} else if (yes) {
|
|
520
491
|
targets = [...PLATFORMS2];
|
|
521
492
|
} else if (interactive) {
|
|
522
|
-
|
|
493
|
+
needsPlatformPrompt = true;
|
|
523
494
|
targets = [...PLATFORMS2];
|
|
524
495
|
} else {
|
|
525
496
|
targets = ["claude"];
|
|
526
497
|
}
|
|
527
|
-
|
|
528
|
-
|
|
498
|
+
return {
|
|
499
|
+
source,
|
|
500
|
+
options,
|
|
501
|
+
scope,
|
|
502
|
+
registryUrl,
|
|
503
|
+
jsonOnly,
|
|
504
|
+
interactive,
|
|
505
|
+
yes,
|
|
506
|
+
maxDepth: parsePositiveInt(options.depth, 6),
|
|
507
|
+
maxSkills: parsePositiveInt(options.maxSkills, 200),
|
|
508
|
+
resolvedSource: source.trim(),
|
|
509
|
+
targets,
|
|
510
|
+
forceOverwrite: Boolean(options.force),
|
|
511
|
+
needsPlatformPrompt,
|
|
512
|
+
discoveredSkills: null,
|
|
513
|
+
selectedSkills: null,
|
|
514
|
+
isSingleSkill: false,
|
|
515
|
+
materializedDir: null,
|
|
516
|
+
cleanupMaterialized: null,
|
|
517
|
+
results: [],
|
|
518
|
+
errors: [],
|
|
519
|
+
skipped: [],
|
|
520
|
+
spinner: null
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
async function resolveSource(ctx) {
|
|
524
|
+
if (ctx.options.all && ctx.options.target) {
|
|
525
|
+
const message = "Invalid options: use either --all or --target, not both.";
|
|
526
|
+
if (ctx.jsonOnly) printJson({ ok: false, error: message });
|
|
527
|
+
else console.error(chalk3.red(message));
|
|
528
|
+
process.exitCode = 1;
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
try {
|
|
532
|
+
if (looksLikeAlias(ctx.resolvedSource)) {
|
|
533
|
+
if (ctx.spinner) ctx.spinner.text = `Resolving ${chalk3.cyan(ctx.resolvedSource)}...`;
|
|
534
|
+
const registryUrl = resolveRegistryUrl(ctx.registryUrl);
|
|
535
|
+
const resolved = await resolveRegistryAlias(registryUrl, ctx.resolvedSource);
|
|
536
|
+
if (!ctx.jsonOnly) {
|
|
537
|
+
logger.info(`Resolved ${chalk3.cyan(ctx.resolvedSource)} \u2192 ${chalk3.cyan(resolved.spec)} (${resolved.type})`);
|
|
538
|
+
}
|
|
539
|
+
ctx.resolvedSource = resolved.spec;
|
|
540
|
+
}
|
|
541
|
+
} catch (error) {
|
|
542
|
+
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
543
|
+
if (ctx.jsonOnly) printJson({ ok: false, error: message });
|
|
544
|
+
else console.error(chalk3.red(message));
|
|
545
|
+
process.exitCode = 1;
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
async function discoverSkills(ctx) {
|
|
551
|
+
const { resolvedSource, maxDepth, maxSkills, jsonOnly } = ctx;
|
|
552
|
+
if (ctx.spinner) {
|
|
553
|
+
ctx.spinner.text = `Discovery at ${chalk3.cyan(ctx.source)}...`;
|
|
554
|
+
}
|
|
529
555
|
try {
|
|
530
|
-
if (
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
556
|
+
if (resolvedSource.startsWith("@") && resolvedSource.includes("/")) {
|
|
557
|
+
ctx.isSingleSkill = true;
|
|
558
|
+
ctx.selectedSkills = [{ relPath: resolvedSource, suggestedSource: resolvedSource }];
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
const maybeLocalRoot = path2.resolve(resolvedSource);
|
|
562
|
+
const isLocal = fs2.existsSync(maybeLocalRoot);
|
|
563
|
+
if (isLocal) {
|
|
564
|
+
const hasSkillMd2 = fs2.existsSync(path2.join(maybeLocalRoot, "SKILL.md"));
|
|
565
|
+
if (hasSkillMd2) {
|
|
566
|
+
ctx.isSingleSkill = true;
|
|
567
|
+
ctx.selectedSkills = [{ relPath: maybeLocalRoot, suggestedSource: resolvedSource }];
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
const discovered2 = discoverSkillDirsWithHeuristics(maybeLocalRoot, { maxDepth, maxSkills });
|
|
571
|
+
if (discovered2.length === 0) {
|
|
572
|
+
const message = `No SKILL.md found at ${maybeLocalRoot} (or within subdirectories).`;
|
|
573
|
+
if (jsonOnly) {
|
|
574
|
+
printJson({ ok: false, error: "SKILL_MD_NOT_FOUND", message, source: ctx.source, resolvedSource });
|
|
575
|
+
} else {
|
|
576
|
+
if (ctx.spinner) ctx.spinner.stop();
|
|
577
|
+
console.error(chalk3.red(message));
|
|
578
|
+
}
|
|
579
|
+
process.exitCode = 1;
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
ctx.discoveredSkills = asDiscoveredSkills(discovered2, (d) => path2.join(maybeLocalRoot, d.relPath));
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
const materialized = await materializeSourceToTemp(resolvedSource);
|
|
586
|
+
ctx.materializedDir = materialized.dir;
|
|
587
|
+
ctx.cleanupMaterialized = materialized.cleanup;
|
|
588
|
+
const hasSkillMd = fs2.existsSync(path2.join(ctx.materializedDir, "SKILL.md"));
|
|
589
|
+
if (hasSkillMd) {
|
|
590
|
+
ctx.isSingleSkill = true;
|
|
591
|
+
ctx.selectedSkills = [{ relPath: ".", suggestedSource: resolvedSource, materializedDir: ctx.materializedDir }];
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
const discovered = discoverSkillDirsWithHeuristics(ctx.materializedDir, { maxDepth, maxSkills });
|
|
595
|
+
if (discovered.length === 0) {
|
|
596
|
+
const message = `No SKILL.md found in source "${resolvedSource}".`;
|
|
597
|
+
if (jsonOnly) {
|
|
598
|
+
printJson({ ok: false, error: "SKILL_MD_NOT_FOUND", message, source: ctx.source, resolvedSource });
|
|
599
|
+
} else {
|
|
600
|
+
if (ctx.spinner) ctx.spinner.stop();
|
|
601
|
+
console.error(chalk3.red(message));
|
|
602
|
+
}
|
|
603
|
+
process.exitCode = 1;
|
|
604
|
+
return false;
|
|
535
605
|
}
|
|
606
|
+
ctx.discoveredSkills = asDiscoveredSkills(
|
|
607
|
+
discovered,
|
|
608
|
+
(d) => deriveChildSource(resolvedSource, d.relPath),
|
|
609
|
+
(d) => d.absDir
|
|
610
|
+
);
|
|
611
|
+
return true;
|
|
536
612
|
} catch (error) {
|
|
537
613
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
538
614
|
if (jsonOnly) printJson({ ok: false, error: message });
|
|
615
|
+
else {
|
|
616
|
+
if (ctx.spinner) ctx.spinner.stop();
|
|
617
|
+
console.error(chalk3.red(message));
|
|
618
|
+
}
|
|
619
|
+
process.exitCode = 1;
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
async function promptSelections(ctx) {
|
|
624
|
+
const { discoveredSkills, isSingleSkill, jsonOnly, interactive, yes, options } = ctx;
|
|
625
|
+
if (isSingleSkill) {
|
|
626
|
+
if (ctx.needsPlatformPrompt) {
|
|
627
|
+
if (ctx.spinner) ctx.spinner.stop();
|
|
628
|
+
const selectedPlatforms = await promptPlatformsInteractive({ defaultAll: true });
|
|
629
|
+
if (!selectedPlatforms) {
|
|
630
|
+
console.log(chalk3.red("No platforms selected."));
|
|
631
|
+
process.exitCode = 1;
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
ctx.targets = selectedPlatforms;
|
|
635
|
+
ctx.needsPlatformPrompt = false;
|
|
636
|
+
}
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
if (!discoveredSkills || discoveredSkills.length === 0) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
if (discoveredSkills.length > ctx.maxSkills) {
|
|
643
|
+
const message = `Found more than ${ctx.maxSkills} skills. Increase --max-skills to proceed.`;
|
|
644
|
+
if (jsonOnly) printJson({ ok: false, error: "TOO_MANY_SKILLS", message, source: ctx.source, resolvedSource: ctx.resolvedSource, maxSkills: ctx.maxSkills });
|
|
539
645
|
else console.error(chalk3.red(message));
|
|
540
646
|
process.exitCode = 1;
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
if (!options.recursive && !yes) {
|
|
650
|
+
if (jsonOnly) {
|
|
651
|
+
const foundOutput = discoveredSkills.map(({ relPath, suggestedSource }) => ({ relPath, suggestedSource }));
|
|
652
|
+
printJson({
|
|
653
|
+
ok: false,
|
|
654
|
+
error: "MULTI_SKILL_SOURCE",
|
|
655
|
+
message: "Source is not a skill root (missing SKILL.md). Multiple skills were found.",
|
|
656
|
+
source: ctx.source,
|
|
657
|
+
resolvedSource: ctx.resolvedSource,
|
|
658
|
+
found: foundOutput
|
|
659
|
+
});
|
|
660
|
+
process.exitCode = 1;
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
if (ctx.spinner) ctx.spinner.stop();
|
|
664
|
+
const headline = discoveredSkills.length === 1 ? `No SKILL.md found at root. Found 1 skill.
|
|
665
|
+
` : `No SKILL.md found at root. Found ${discoveredSkills.length} skills.
|
|
666
|
+
`;
|
|
667
|
+
console.log(chalk3.yellow(headline));
|
|
668
|
+
if (!interactive) {
|
|
669
|
+
console.log(chalk3.dim(`Tip: rerun with ${chalk3.cyan("skild install <source> --recursive")} to install all.`));
|
|
670
|
+
process.exitCode = 1;
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (!yes && interactive) {
|
|
675
|
+
if (ctx.spinner) ctx.spinner.stop();
|
|
676
|
+
if (options.recursive) {
|
|
677
|
+
const headline = discoveredSkills.length === 1 ? `No SKILL.md found at root. Found 1 skill.
|
|
678
|
+
` : `No SKILL.md found at root. Found ${discoveredSkills.length} skills.
|
|
679
|
+
`;
|
|
680
|
+
console.log(chalk3.yellow(headline));
|
|
681
|
+
}
|
|
682
|
+
const selected = await promptSkillsInteractive(discoveredSkills, { defaultAll: true });
|
|
683
|
+
if (!selected) {
|
|
684
|
+
console.log(chalk3.red("No skills selected."));
|
|
685
|
+
process.exitCode = 1;
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
ctx.selectedSkills = selected;
|
|
689
|
+
if (ctx.needsPlatformPrompt) {
|
|
690
|
+
const selectedPlatforms = await promptPlatformsInteractive({ defaultAll: true });
|
|
691
|
+
if (!selectedPlatforms) {
|
|
692
|
+
console.log(chalk3.red("No platforms selected."));
|
|
693
|
+
process.exitCode = 1;
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
ctx.targets = selectedPlatforms;
|
|
697
|
+
ctx.needsPlatformPrompt = false;
|
|
698
|
+
}
|
|
699
|
+
if (ctx.spinner) ctx.spinner.start();
|
|
700
|
+
} else {
|
|
701
|
+
ctx.selectedSkills = discoveredSkills;
|
|
702
|
+
}
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
705
|
+
async function executeInstalls(ctx) {
|
|
706
|
+
const { selectedSkills, targets, scope, forceOverwrite, registryUrl, spinner } = ctx;
|
|
707
|
+
if (!selectedSkills || selectedSkills.length === 0) {
|
|
541
708
|
return;
|
|
542
709
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
for (const
|
|
710
|
+
if (spinner) {
|
|
711
|
+
spinner.text = selectedSkills.length > 1 ? `Installing ${chalk3.cyan(ctx.source)} \u2014 ${selectedSkills.length} skills...` : `Installing ${chalk3.cyan(ctx.source)}...`;
|
|
712
|
+
}
|
|
713
|
+
for (const skill of selectedSkills) {
|
|
714
|
+
if (spinner) {
|
|
715
|
+
spinner.text = `Installing ${chalk3.cyan(skill.relPath === "." ? ctx.source : skill.relPath)}...`;
|
|
716
|
+
}
|
|
717
|
+
for (const platform of targets) {
|
|
551
718
|
try {
|
|
552
|
-
const record =
|
|
553
|
-
{ spec:
|
|
554
|
-
{ platform
|
|
719
|
+
const record = skill.suggestedSource.startsWith("@") && skill.suggestedSource.includes("/") ? await installRegistrySkill(
|
|
720
|
+
{ spec: skill.suggestedSource, registryUrl },
|
|
721
|
+
{ platform, scope, force: forceOverwrite }
|
|
555
722
|
) : await installSkill(
|
|
556
|
-
{ source:
|
|
557
|
-
{ platform
|
|
723
|
+
{ source: skill.suggestedSource, materializedDir: skill.materializedDir },
|
|
724
|
+
{ platform, scope, force: forceOverwrite, registryUrl }
|
|
558
725
|
);
|
|
559
|
-
results.push(record);
|
|
560
|
-
void reportDownload(record,
|
|
726
|
+
ctx.results.push(record);
|
|
727
|
+
void reportDownload(record, registryUrl);
|
|
561
728
|
} catch (error) {
|
|
562
729
|
if (error instanceof SkildError && error.code === "ALREADY_INSTALLED") {
|
|
563
730
|
const details = error.details;
|
|
564
|
-
skipped.push({
|
|
565
|
-
skillName: details?.skillName ||
|
|
566
|
-
platform
|
|
731
|
+
ctx.skipped.push({
|
|
732
|
+
skillName: details?.skillName || skill.suggestedSource,
|
|
733
|
+
platform,
|
|
567
734
|
installDir: details?.installDir || ""
|
|
568
735
|
});
|
|
569
736
|
continue;
|
|
570
737
|
}
|
|
571
738
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
572
|
-
errors.push({ platform
|
|
739
|
+
ctx.errors.push({ platform, error: message, inputSource: skill.suggestedSource });
|
|
573
740
|
}
|
|
574
741
|
}
|
|
575
742
|
}
|
|
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;
|
|
743
|
+
}
|
|
744
|
+
function reportResults(ctx) {
|
|
745
|
+
const { results, errors, skipped, spinner, jsonOnly, selectedSkills, targets } = ctx;
|
|
746
|
+
if (ctx.cleanupMaterialized) {
|
|
747
|
+
ctx.cleanupMaterialized();
|
|
748
|
+
}
|
|
749
|
+
const isMultiSkill = (selectedSkills?.length ?? 0) > 1;
|
|
750
|
+
if (jsonOnly) {
|
|
751
|
+
if (!isMultiSkill && targets.length === 1) {
|
|
752
|
+
if (errors.length) printJson({ ok: false, error: errors[0]?.error || "Install failed." });
|
|
753
|
+
else printJson(results[0] ?? null);
|
|
754
|
+
} else {
|
|
755
|
+
printJson({
|
|
756
|
+
ok: errors.length === 0,
|
|
757
|
+
source: ctx.source,
|
|
758
|
+
resolvedSource: ctx.resolvedSource,
|
|
759
|
+
scope: ctx.scope,
|
|
760
|
+
recursive: isMultiSkill,
|
|
761
|
+
all: targets.length === PLATFORMS2.length,
|
|
762
|
+
skillCount: selectedSkills?.length ?? 0,
|
|
763
|
+
results,
|
|
764
|
+
errors,
|
|
765
|
+
skipped
|
|
766
|
+
});
|
|
632
767
|
}
|
|
633
|
-
|
|
634
|
-
return
|
|
768
|
+
process.exitCode = errors.length ? 1 : 0;
|
|
769
|
+
return;
|
|
635
770
|
}
|
|
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 || "")}`
|
|
771
|
+
if (errors.length === 0 && (results.length > 0 || skipped.length > 0)) {
|
|
772
|
+
const displayName = results[0]?.canonicalName || results[0]?.name || ctx.source;
|
|
773
|
+
if (skipped.length > 0) {
|
|
774
|
+
spinner?.succeed(
|
|
775
|
+
`Installed ${chalk3.green(results.length)} and skipped ${chalk3.dim(skipped.length)} (already installed) to ${chalk3.dim(`${targets.length} platforms`)}`
|
|
750
776
|
);
|
|
751
777
|
} 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`
|
|
778
|
+
spinner?.succeed(
|
|
779
|
+
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
780
|
);
|
|
756
|
-
process.exitCode = 1;
|
|
757
|
-
if (!effectiveRecursive && targets.length === 1 && errors[0]) console.error(chalk3.red(errors[0].error));
|
|
758
781
|
}
|
|
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;
|
|
782
|
+
} else if (errors.length > 0) {
|
|
783
|
+
const attempted = results.length + errors.length;
|
|
784
|
+
spinner?.fail(
|
|
785
|
+
isMultiSkill ? `Install had failures (${errors.length}/${attempted} installs failed)` : `Failed to install ${chalk3.red(ctx.source)} to ${errors.length}/${targets.length} platforms`
|
|
786
|
+
);
|
|
787
|
+
process.exitCode = 1;
|
|
788
|
+
if (!isMultiSkill && targets.length === 1 && errors[0]) {
|
|
789
|
+
console.error(chalk3.red(errors[0].error));
|
|
780
790
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
791
|
+
}
|
|
792
|
+
if (isMultiSkill || targets.length > 1) {
|
|
793
|
+
for (const r of results.slice(0, 60)) {
|
|
794
|
+
const displayName = r.canonicalName || r.name;
|
|
795
|
+
const suffix = r.hasSkillMd ? chalk3.green("\u2713") : chalk3.yellow("\u26A0");
|
|
796
|
+
console.log(` ${suffix} ${chalk3.cyan(displayName)} \u2192 ${chalk3.dim(r.platform)}`);
|
|
797
|
+
}
|
|
798
|
+
if (results.length > 60) console.log(chalk3.dim(` ... and ${results.length - 60} more`));
|
|
799
|
+
if (errors.length) {
|
|
800
|
+
console.log(chalk3.yellow("\nFailures:"));
|
|
801
|
+
for (const e of errors) console.log(chalk3.yellow(` - ${e.platform}: ${e.error}`));
|
|
802
|
+
}
|
|
803
|
+
process.exitCode = errors.length ? 1 : 0;
|
|
804
|
+
} else if (!isMultiSkill && targets.length === 1 && results[0]) {
|
|
805
|
+
const record = results[0];
|
|
806
|
+
if (record.hasSkillMd) logger.installDetail("SKILL.md found \u2713");
|
|
807
|
+
else logger.installDetail("Warning: No SKILL.md found", true);
|
|
808
|
+
if (record.skill?.validation && !record.skill.validation.ok) {
|
|
809
|
+
logger.installDetail(`Validation: ${chalk3.yellow("failed")} (${record.skill.validation.issues.length} issues)`, true);
|
|
810
|
+
} else if (record.skill?.validation?.ok) {
|
|
811
|
+
logger.installDetail(`Validation: ${chalk3.green("ok")}`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (skipped.length > 0) {
|
|
815
|
+
const uniqueSkills = [...new Set(skipped.map((s) => s.skillName))];
|
|
816
|
+
console.log(chalk3.dim(`
|
|
784
817
|
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")}`));
|
|
818
|
+
const bySkill = /* @__PURE__ */ new Map();
|
|
819
|
+
for (const s of skipped) {
|
|
820
|
+
const platforms = bySkill.get(s.skillName) || [];
|
|
821
|
+
platforms.push(s.platform);
|
|
822
|
+
bySkill.set(s.skillName, platforms);
|
|
823
|
+
}
|
|
824
|
+
for (const [skillName, platforms] of bySkill.entries()) {
|
|
825
|
+
const platformsStr = platforms.length === PLATFORMS2.length ? "all platforms" : platforms.join(", ");
|
|
826
|
+
console.log(chalk3.dim(` - ${skillName} (${platformsStr})`));
|
|
797
827
|
}
|
|
828
|
+
console.log(chalk3.dim(`
|
|
829
|
+
To reinstall, use: ${chalk3.cyan("skild install <source> --force")}`));
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
async function install(source, options = {}) {
|
|
833
|
+
const ctx = createContext(source, options);
|
|
834
|
+
if (!ctx.jsonOnly) {
|
|
835
|
+
ctx.spinner = createSpinner(`Interpreting ${chalk3.cyan(ctx.source)}...`);
|
|
836
|
+
}
|
|
837
|
+
try {
|
|
838
|
+
if (!await resolveSource(ctx)) return;
|
|
839
|
+
if (!await discoverSkills(ctx)) return;
|
|
840
|
+
if (!await promptSelections(ctx)) return;
|
|
841
|
+
await executeInstalls(ctx);
|
|
842
|
+
reportResults(ctx);
|
|
798
843
|
} catch (error) {
|
|
799
844
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
800
|
-
if (jsonOnly) printJson({ ok: false, error: message });
|
|
801
|
-
else
|
|
845
|
+
if (ctx.jsonOnly) printJson({ ok: false, error: message });
|
|
846
|
+
else {
|
|
847
|
+
if (ctx.spinner) ctx.spinner.fail(chalk3.red(message));
|
|
848
|
+
else console.error(chalk3.red(message));
|
|
849
|
+
}
|
|
802
850
|
process.exitCode = 1;
|
|
851
|
+
if (ctx.cleanupMaterialized) ctx.cleanupMaterialized();
|
|
803
852
|
}
|
|
804
853
|
}
|
|
805
854
|
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.7",
|
|
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.7"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "^20.10.0",
|