skilld 0.0.1 → 0.1.2
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/LICENSE +21 -0
- package/README.md +119 -88
- package/dist/_chunks/config.mjs +20 -0
- package/dist/_chunks/config.mjs.map +1 -0
- package/dist/_chunks/llm.mjs +877 -0
- package/dist/_chunks/llm.mjs.map +1 -0
- package/dist/_chunks/releases.mjs +986 -0
- package/dist/_chunks/releases.mjs.map +1 -0
- package/dist/_chunks/storage.mjs +198 -0
- package/dist/_chunks/storage.mjs.map +1 -0
- package/dist/_chunks/sync-parallel.mjs +540 -0
- package/dist/_chunks/sync-parallel.mjs.map +1 -0
- package/dist/_chunks/types.d.mts +87 -0
- package/dist/_chunks/types.d.mts.map +1 -0
- package/dist/_chunks/utils.d.mts +352 -0
- package/dist/_chunks/utils.d.mts.map +1 -0
- package/dist/_chunks/version.d.mts +147 -0
- package/dist/_chunks/version.d.mts.map +1 -0
- package/dist/agent/index.d.mts +205 -0
- package/dist/agent/index.d.mts.map +1 -0
- package/dist/agent/index.mjs +2 -0
- package/dist/cache/index.d.mts +2 -0
- package/dist/cache/index.mjs +3 -0
- package/dist/cli.mjs +2650 -449
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +5 -14
- package/dist/index.mjs +7 -181
- package/dist/retriv/index.d.mts +12 -0
- package/dist/retriv/index.d.mts.map +1 -0
- package/dist/retriv/index.mjs +76 -0
- package/dist/retriv/index.mjs.map +1 -0
- package/dist/sources/index.d.mts +2 -0
- package/dist/sources/index.mjs +3 -0
- package/dist/types.d.mts +4 -37
- package/package.json +39 -13
- package/dist/agents.d.mts +0 -56
- package/dist/agents.d.mts.map +0 -1
- package/dist/agents.mjs +0 -148
- package/dist/agents.mjs.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/npm.d.mts +0 -48
- package/dist/npm.d.mts.map +0 -1
- package/dist/npm.mjs +0 -90
- package/dist/npm.mjs.map +0 -1
- package/dist/split-text.d.mts +0 -24
- package/dist/split-text.d.mts.map +0 -1
- package/dist/split-text.mjs +0 -87
- package/dist/split-text.mjs.map +0 -1
- package/dist/types.d.mts.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,503 +1,2704 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
2
|
+
import { a as getCacheDir, i as getPackageDbPath, n as REFERENCES_DIR, s as getVersionKey, t as CACHE_DIR } from "./_chunks/config.mjs";
|
|
3
|
+
import { _ as writeToCache, a as getShippedSkills, c as linkGithub, d as linkReleases, f as linkShippedSkill, g as resolvePkgDir, h as readCachedDocs, i as getPkgKeyFiles, l as linkPkg, m as listReferenceFiles, n as clearCache, o as hasShippedDocs, r as ensureCacheDir, s as isCached, u as linkReferences } from "./_chunks/storage.mjs";
|
|
4
|
+
import "./cache/index.mjs";
|
|
5
|
+
import { createIndex, searchSnippets } from "./retriv/index.mjs";
|
|
6
|
+
import { A as resolveEntryFiles, E as parseGitHubUrl, F as isGhAvailable, M as formatDiscussionsAsMarkdown, N as fetchGitHubIssues, P as formatIssuesAsMarkdown, S as fetchReadmeContent, _ as normalizeLlmsLinks, a as fetchPkgDist, c as readLocalDependencies, d as resolvePackageDocs, f as resolvePackageDocsWithAttempts, h as fetchLlmsTxt, i as fetchNpmRegistryMeta, j as fetchGitHubDiscussions, n as fetchLatestVersion, p as downloadLlmsDocs, r as fetchNpmPackage, t as fetchReleaseNotes, u as resolveLocalPackageDocs, y as fetchGitDocs } from "./_chunks/releases.mjs";
|
|
7
|
+
import "./sources/index.mjs";
|
|
8
|
+
import { c as sanitizeName, d as detectTargetAgent, f as getAgentVersion, i as generateSkillMd, l as detectImportedPackages, n as getModelName, p as agents, r as optimizeDocs, t as getAvailableModels, u as detectInstalledAgents } from "./_chunks/llm.mjs";
|
|
9
|
+
import "./agent/index.mjs";
|
|
10
|
+
import { createRequire } from "node:module";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { join, relative, resolve } from "node:path";
|
|
13
|
+
import { appendFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
14
|
import { execSync } from "node:child_process";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
import * as p from "@clack/prompts";
|
|
16
|
+
import { defineCommand, runMain } from "citty";
|
|
17
|
+
import pLimit from "p-limit";
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
const defaultFeatures = {
|
|
20
|
+
search: true,
|
|
21
|
+
issues: false,
|
|
22
|
+
discussions: false,
|
|
23
|
+
releases: true
|
|
24
|
+
};
|
|
25
|
+
const CONFIG_DIR = join(homedir(), ".skilld");
|
|
26
|
+
const CONFIG_PATH = join(CONFIG_DIR, "config.yaml");
|
|
27
|
+
function hasConfig() {
|
|
28
|
+
return existsSync(CONFIG_PATH);
|
|
29
|
+
}
|
|
30
|
+
function readConfig() {
|
|
31
|
+
if (!existsSync(CONFIG_PATH)) return {};
|
|
32
|
+
const content = readFileSync(CONFIG_PATH, "utf-8");
|
|
33
|
+
const config = {};
|
|
34
|
+
let inBlock = null;
|
|
35
|
+
const projects = [];
|
|
36
|
+
const features = {};
|
|
37
|
+
for (const line of content.split("\n")) {
|
|
38
|
+
if (line.startsWith("projects:")) {
|
|
39
|
+
inBlock = "projects";
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (line.startsWith("features:")) {
|
|
43
|
+
inBlock = "features";
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (inBlock === "projects") {
|
|
47
|
+
if (line.startsWith(" - ")) {
|
|
48
|
+
projects.push(line.slice(4).trim().replace(/^["']|["']$/g, ""));
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
inBlock = null;
|
|
52
|
+
}
|
|
53
|
+
if (inBlock === "features") {
|
|
54
|
+
const m = line.match(/^ {2}(\w+):\s*(.+)/);
|
|
55
|
+
if (m) {
|
|
56
|
+
const key = m[1];
|
|
57
|
+
if (key in defaultFeatures) features[key] = m[2] === "true";
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
inBlock = null;
|
|
61
|
+
}
|
|
62
|
+
const [key, ...rest] = line.split(":");
|
|
63
|
+
const value = rest.join(":").trim().replace(/^["']|["']$/g, "");
|
|
64
|
+
if (key === "model" && value) config.model = value;
|
|
65
|
+
if (key === "agent" && value) config.agent = value;
|
|
66
|
+
if (key === "skipLlm") config.skipLlm = value === "true";
|
|
67
|
+
}
|
|
68
|
+
if (projects.length > 0) config.projects = projects;
|
|
69
|
+
if (Object.keys(features).length > 0) config.features = {
|
|
70
|
+
...defaultFeatures,
|
|
71
|
+
...features
|
|
72
|
+
};
|
|
73
|
+
return config;
|
|
74
|
+
}
|
|
75
|
+
function writeConfig(config) {
|
|
76
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
77
|
+
let yaml = "";
|
|
78
|
+
if (config.model) yaml += `model: ${config.model}\n`;
|
|
79
|
+
if (config.agent) yaml += `agent: ${config.agent}\n`;
|
|
80
|
+
if (config.skipLlm) yaml += `skipLlm: true\n`;
|
|
81
|
+
if (config.features) {
|
|
82
|
+
yaml += "features:\n";
|
|
83
|
+
for (const [k, v] of Object.entries(config.features)) yaml += ` ${k}: ${v}\n`;
|
|
84
|
+
}
|
|
85
|
+
if (config.projects?.length) {
|
|
86
|
+
yaml += "projects:\n";
|
|
87
|
+
for (const p of config.projects) yaml += ` - ${p}\n`;
|
|
88
|
+
}
|
|
89
|
+
writeFileSync(CONFIG_PATH, yaml);
|
|
90
|
+
}
|
|
91
|
+
function updateConfig(updates) {
|
|
92
|
+
writeConfig({
|
|
93
|
+
...readConfig(),
|
|
94
|
+
...updates
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function registerProject(projectPath) {
|
|
98
|
+
const config = readConfig();
|
|
99
|
+
const projects = new Set(config.projects || []);
|
|
100
|
+
projects.add(projectPath);
|
|
101
|
+
writeConfig({
|
|
102
|
+
...config,
|
|
103
|
+
projects: [...projects]
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function unregisterProject(projectPath) {
|
|
107
|
+
const config = readConfig();
|
|
108
|
+
const projects = (config.projects || []).filter((p) => p !== projectPath);
|
|
109
|
+
writeConfig({
|
|
110
|
+
...config,
|
|
111
|
+
projects
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function getRegisteredProjects() {
|
|
115
|
+
return readConfig().projects || [];
|
|
116
|
+
}
|
|
117
|
+
async function configCommand() {
|
|
118
|
+
const config = readConfig();
|
|
119
|
+
const features = config.features ?? defaultFeatures;
|
|
120
|
+
const enabledCount = Object.values(features).filter(Boolean).length;
|
|
121
|
+
const action = await p.select({
|
|
122
|
+
message: "Settings",
|
|
123
|
+
options: [
|
|
124
|
+
{
|
|
125
|
+
label: "Change features",
|
|
126
|
+
value: "features",
|
|
127
|
+
hint: `${enabledCount}/4 enabled`
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
label: "Change model",
|
|
131
|
+
value: "model",
|
|
132
|
+
hint: config.model || "auto"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
label: "Change agent",
|
|
136
|
+
value: "agent",
|
|
137
|
+
hint: config.agent || "auto-detect"
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
});
|
|
141
|
+
if (p.isCancel(action)) {
|
|
142
|
+
p.cancel("Cancelled");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
switch (action) {
|
|
146
|
+
case "features": {
|
|
147
|
+
const selected = await p.multiselect({
|
|
148
|
+
message: "Enable features",
|
|
149
|
+
options: [
|
|
150
|
+
{
|
|
151
|
+
label: "Semantic + token search",
|
|
152
|
+
value: "search",
|
|
153
|
+
hint: "local query engine to cut token costs and speed up grep"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
label: "Release notes",
|
|
157
|
+
value: "releases",
|
|
158
|
+
hint: "track changelogs for installed packages"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
label: "GitHub issues",
|
|
162
|
+
value: "issues",
|
|
163
|
+
hint: "surface common problems and solutions"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
label: "GitHub discussions",
|
|
167
|
+
value: "discussions",
|
|
168
|
+
hint: "include Q&A and community knowledge"
|
|
169
|
+
}
|
|
170
|
+
].map((f) => ({
|
|
171
|
+
label: f.label,
|
|
172
|
+
value: f.value,
|
|
173
|
+
hint: f.hint
|
|
174
|
+
})),
|
|
175
|
+
initialValues: Object.entries(features).filter(([, v]) => v).map(([k]) => k),
|
|
176
|
+
required: false
|
|
177
|
+
});
|
|
178
|
+
if (p.isCancel(selected)) return;
|
|
179
|
+
updateConfig({ features: {
|
|
180
|
+
search: selected.includes("search"),
|
|
181
|
+
issues: selected.includes("issues"),
|
|
182
|
+
discussions: selected.includes("discussions"),
|
|
183
|
+
releases: selected.includes("releases")
|
|
184
|
+
} });
|
|
185
|
+
p.log.success(`Features updated: ${selected.length} enabled`);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case "model": {
|
|
189
|
+
const available = await getAvailableModels();
|
|
190
|
+
if (available.length === 0) {
|
|
191
|
+
p.log.warn("No LLM CLIs found");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const model = await p.select({
|
|
195
|
+
message: "Select default model",
|
|
196
|
+
options: [{
|
|
197
|
+
label: "Auto (prompt each time)",
|
|
198
|
+
value: ""
|
|
199
|
+
}, ...available.map((m) => ({
|
|
200
|
+
label: m.recommended ? `${m.name} (Recommended)` : m.name,
|
|
201
|
+
value: m.id,
|
|
202
|
+
hint: m.hint
|
|
203
|
+
}))],
|
|
204
|
+
initialValue: config.model || ""
|
|
205
|
+
});
|
|
206
|
+
if (p.isCancel(model)) return;
|
|
207
|
+
updateConfig({ model: model || void 0 });
|
|
208
|
+
p.log.success(model ? `Default model set to ${model}` : "Model will be prompted each time");
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case "agent": {
|
|
212
|
+
const agentChoice = await p.select({
|
|
213
|
+
message: "Select default agent",
|
|
214
|
+
options: [{
|
|
215
|
+
label: "Auto-detect",
|
|
216
|
+
value: ""
|
|
217
|
+
}, ...Object.entries(agents).map(([id, a]) => ({
|
|
218
|
+
label: a.displayName,
|
|
219
|
+
value: id,
|
|
220
|
+
hint: a.skillsDir
|
|
221
|
+
}))],
|
|
222
|
+
initialValue: config.agent || ""
|
|
223
|
+
});
|
|
224
|
+
if (p.isCancel(agentChoice)) return;
|
|
225
|
+
updateConfig({ agent: agentChoice || void 0 });
|
|
226
|
+
p.log.success(agentChoice ? `Default agent set to ${agentChoice}` : "Agent will be auto-detected");
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function parseSkillFrontmatter(skillPath) {
|
|
232
|
+
if (!existsSync(skillPath)) return null;
|
|
233
|
+
const match = readFileSync(skillPath, "utf-8").match(/^---\n([\s\S]*?)\n---/);
|
|
234
|
+
if (!match) return null;
|
|
235
|
+
const info = {};
|
|
236
|
+
const lines = match[1].split("\n");
|
|
237
|
+
for (const line of lines) {
|
|
238
|
+
const [key, ...rest] = line.split(":");
|
|
239
|
+
const value = rest.join(":").trim().replace(/^["']|["']$/g, "");
|
|
240
|
+
if (key === "packageName") info.packageName = value;
|
|
241
|
+
if (key === "version") info.version = value;
|
|
242
|
+
if (key === "source") info.source = value;
|
|
243
|
+
if (key === "syncedAt") info.syncedAt = value;
|
|
244
|
+
if (key === "generator") info.generator = value;
|
|
245
|
+
}
|
|
246
|
+
return info;
|
|
247
|
+
}
|
|
248
|
+
function readLock(skillsDir) {
|
|
249
|
+
const lockPath = join(skillsDir, "skilld-lock.yaml");
|
|
250
|
+
if (!existsSync(lockPath)) return null;
|
|
251
|
+
const content = readFileSync(lockPath, "utf-8");
|
|
252
|
+
const skills = {};
|
|
253
|
+
let currentSkill = null;
|
|
254
|
+
for (const line of content.split("\n")) {
|
|
255
|
+
const skillMatch = line.match(/^ {2}(\S+):$/);
|
|
256
|
+
if (skillMatch) {
|
|
257
|
+
currentSkill = skillMatch[1];
|
|
258
|
+
skills[currentSkill] = {};
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (currentSkill && line.startsWith(" ")) {
|
|
262
|
+
const [key, ...rest] = line.trim().split(":");
|
|
263
|
+
const value = rest.join(":").trim().replace(/^["']|["']$/g, "");
|
|
264
|
+
if (key && value) skills[currentSkill][key] = value;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return { skills };
|
|
268
|
+
}
|
|
269
|
+
function writeLock(skillsDir, skillName, info) {
|
|
270
|
+
const lockPath = join(skillsDir, "skilld-lock.yaml");
|
|
271
|
+
let lock = { skills: {} };
|
|
272
|
+
if (existsSync(lockPath)) lock = readLock(skillsDir) || { skills: {} };
|
|
273
|
+
lock.skills[skillName] = info;
|
|
274
|
+
let yaml = "skills:\n";
|
|
275
|
+
for (const [name, skill] of Object.entries(lock.skills)) {
|
|
276
|
+
yaml += ` ${name}:\n`;
|
|
277
|
+
if (skill.packageName) yaml += ` packageName: "${skill.packageName}"\n`;
|
|
278
|
+
if (skill.version) yaml += ` version: "${skill.version}"\n`;
|
|
279
|
+
if (skill.source) yaml += ` source: "${skill.source}"\n`;
|
|
280
|
+
if (skill.syncedAt) yaml += ` syncedAt: "${skill.syncedAt}"\n`;
|
|
281
|
+
if (skill.generator) yaml += ` generator: "${skill.generator}"\n`;
|
|
282
|
+
}
|
|
283
|
+
writeFileSync(lockPath, yaml);
|
|
284
|
+
}
|
|
285
|
+
function removeLockEntry(skillsDir, skillName) {
|
|
286
|
+
const lockPath = join(skillsDir, "skilld-lock.yaml");
|
|
287
|
+
const lock = readLock(skillsDir);
|
|
288
|
+
if (!lock) return;
|
|
289
|
+
delete lock.skills[skillName];
|
|
290
|
+
if (Object.keys(lock.skills).length === 0) {
|
|
291
|
+
unlinkSync(lockPath);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
let yaml = "skills:\n";
|
|
295
|
+
for (const [name, skill] of Object.entries(lock.skills)) {
|
|
296
|
+
yaml += ` ${name}:\n`;
|
|
297
|
+
if (skill.packageName) yaml += ` packageName: "${skill.packageName}"\n`;
|
|
298
|
+
if (skill.version) yaml += ` version: "${skill.version}"\n`;
|
|
299
|
+
if (skill.source) yaml += ` source: "${skill.source}"\n`;
|
|
300
|
+
if (skill.syncedAt) yaml += ` syncedAt: "${skill.syncedAt}"\n`;
|
|
301
|
+
if (skill.generator) yaml += ` generator: "${skill.generator}"\n`;
|
|
302
|
+
}
|
|
303
|
+
writeFileSync(lockPath, yaml);
|
|
304
|
+
}
|
|
305
|
+
async function installCommand(opts) {
|
|
306
|
+
const cwd = process.cwd();
|
|
307
|
+
const agent = agents[opts.agent];
|
|
308
|
+
const skillsDir = opts.global ? join(__require("node:os").homedir(), ".skilld", "skills") : join(cwd, agent.skillsDir);
|
|
309
|
+
const lock = readLock(skillsDir);
|
|
310
|
+
if (!lock || Object.keys(lock.skills).length === 0) {
|
|
311
|
+
p.log.warn("No skilld-lock.yaml found. Run `skilld` to sync skills first.");
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const skills = Object.entries(lock.skills);
|
|
315
|
+
const toRestore = [];
|
|
316
|
+
for (const [name, info] of skills) {
|
|
317
|
+
if (!info.version) continue;
|
|
318
|
+
if (info.source === "shipped") {
|
|
319
|
+
if (!existsSync(join(skillsDir, name))) toRestore.push({
|
|
320
|
+
name,
|
|
321
|
+
info
|
|
322
|
+
});
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const skillDir = join(skillsDir, name);
|
|
326
|
+
const referencesPath = join(skillDir, ".skilld");
|
|
327
|
+
const skillMdPath = join(skillDir, "SKILL.md");
|
|
328
|
+
if (!existsSync(skillDir) || !existsSync(skillMdPath) || !existsSync(referencesPath) || lstatSync(referencesPath).isSymbolicLink() && !existsSync(referencesPath) || existsSync(skillMdPath) && lstatSync(skillMdPath).isSymbolicLink() && !existsSync(skillMdPath)) toRestore.push({
|
|
329
|
+
name,
|
|
330
|
+
info
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
if (toRestore.length === 0) {
|
|
334
|
+
p.log.success("All references already linked");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
p.log.info(`Restoring ${toRestore.length} references`);
|
|
338
|
+
ensureCacheDir();
|
|
339
|
+
for (const { name, info } of toRestore) {
|
|
340
|
+
const version = info.version;
|
|
341
|
+
const pkgName = info.packageName || unsanitizeName(name, info.source);
|
|
342
|
+
if (info.source === "shipped") {
|
|
343
|
+
const match = getShippedSkills(pkgName, cwd, version).find((s) => s.skillName === name);
|
|
344
|
+
if (match) {
|
|
345
|
+
linkShippedSkill(skillsDir, name, match.skillDir);
|
|
346
|
+
p.log.success(`Linked ${name}`);
|
|
347
|
+
} else p.log.warn(`${name}: package ${pkgName} no longer ships this skill`);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const skillDir = join(skillsDir, name);
|
|
351
|
+
const referencesPath = join(skillDir, ".skilld");
|
|
352
|
+
const globalCachePath = getCacheDir(pkgName, version);
|
|
353
|
+
const spin = p.spinner();
|
|
354
|
+
if (isCached(pkgName, version)) {
|
|
355
|
+
spin.start(`Linking ${name}`);
|
|
356
|
+
mkdirSync(skillDir, { recursive: true });
|
|
357
|
+
mkdirSync(referencesPath, { recursive: true });
|
|
358
|
+
linkPkgSymlink(referencesPath, pkgName, cwd, version);
|
|
359
|
+
if (!pkgHasShippedDocs(pkgName, cwd, version) && !isReadmeOnly(globalCachePath)) {
|
|
360
|
+
const docsLink = join(referencesPath, "docs");
|
|
361
|
+
const cachedDocs = join(globalCachePath, "docs");
|
|
362
|
+
if (existsSync(docsLink)) unlinkSync(docsLink);
|
|
363
|
+
if (existsSync(cachedDocs)) symlinkSync(cachedDocs, docsLink, "junction");
|
|
364
|
+
}
|
|
365
|
+
const githubLink = join(referencesPath, "github");
|
|
366
|
+
const cachedGithub = join(globalCachePath, "github");
|
|
367
|
+
if (existsSync(githubLink)) unlinkSync(githubLink);
|
|
368
|
+
if (existsSync(cachedGithub)) symlinkSync(cachedGithub, githubLink, "junction");
|
|
369
|
+
const releasesLink = join(referencesPath, "releases");
|
|
370
|
+
const cachedReleases = join(globalCachePath, "releases");
|
|
371
|
+
if (existsSync(releasesLink)) unlinkSync(releasesLink);
|
|
372
|
+
if (existsSync(cachedReleases)) symlinkSync(cachedReleases, releasesLink, "junction");
|
|
373
|
+
spin.stop(`Linked ${name}`);
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
spin.start(`Downloading ${name}@${version}`);
|
|
377
|
+
const resolved = await resolvePackageDocs(pkgName, { version });
|
|
378
|
+
if (!resolved) {
|
|
379
|
+
spin.stop(`Could not resolve: ${name}`);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
const cachedDocs = [];
|
|
383
|
+
const docsToIndex = [];
|
|
384
|
+
if (resolved.gitDocsUrl && resolved.repoUrl) {
|
|
385
|
+
const gh = parseGitHubUrl(resolved.repoUrl);
|
|
386
|
+
if (gh) {
|
|
387
|
+
const gitDocs = await fetchGitDocs(gh.owner, gh.repo, version, pkgName);
|
|
388
|
+
if (gitDocs?.files.length) {
|
|
389
|
+
const BATCH_SIZE = 20;
|
|
390
|
+
for (let i = 0; i < gitDocs.files.length; i += BATCH_SIZE) {
|
|
391
|
+
const batch = gitDocs.files.slice(i, i + BATCH_SIZE);
|
|
392
|
+
const results = await Promise.all(batch.map(async (file) => {
|
|
393
|
+
const url = `${gitDocs.baseUrl}/${file}`;
|
|
394
|
+
const res = await fetch(url, { headers: { "User-Agent": "skilld/1.0" } }).catch(() => null);
|
|
395
|
+
if (!res?.ok) return null;
|
|
396
|
+
return {
|
|
397
|
+
file,
|
|
398
|
+
content: await res.text()
|
|
399
|
+
};
|
|
400
|
+
}));
|
|
401
|
+
for (const r of results) if (r) {
|
|
402
|
+
const cachePath = gitDocs.docsPrefix ? r.file.replace(gitDocs.docsPrefix, "") : r.file;
|
|
403
|
+
cachedDocs.push({
|
|
404
|
+
path: cachePath,
|
|
405
|
+
content: r.content
|
|
406
|
+
});
|
|
407
|
+
docsToIndex.push({
|
|
408
|
+
id: cachePath,
|
|
409
|
+
content: r.content,
|
|
410
|
+
metadata: {
|
|
411
|
+
package: pkgName,
|
|
412
|
+
source: cachePath,
|
|
413
|
+
type: "doc"
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (resolved.llmsUrl && cachedDocs.length === 0) {
|
|
422
|
+
const llmsContent = await fetchLlmsTxt(resolved.llmsUrl);
|
|
423
|
+
if (llmsContent) {
|
|
424
|
+
cachedDocs.push({
|
|
425
|
+
path: "llms.txt",
|
|
426
|
+
content: normalizeLlmsLinks(llmsContent.raw)
|
|
427
|
+
});
|
|
428
|
+
if (llmsContent.links.length > 0) {
|
|
429
|
+
const docs = await downloadLlmsDocs(llmsContent, resolved.docsUrl || new URL(resolved.llmsUrl).origin);
|
|
430
|
+
for (const doc of docs) {
|
|
431
|
+
const cachePath = join("docs", ...(doc.url.startsWith("/") ? doc.url.slice(1) : doc.url).split("/"));
|
|
432
|
+
cachedDocs.push({
|
|
433
|
+
path: cachePath,
|
|
434
|
+
content: doc.content
|
|
435
|
+
});
|
|
436
|
+
docsToIndex.push({
|
|
437
|
+
id: doc.url,
|
|
438
|
+
content: doc.content,
|
|
439
|
+
metadata: {
|
|
440
|
+
package: pkgName,
|
|
441
|
+
source: cachePath,
|
|
442
|
+
type: "doc"
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (resolved.readmeUrl && cachedDocs.length === 0) {
|
|
450
|
+
const content = await fetchReadmeContent(resolved.readmeUrl);
|
|
451
|
+
if (content) {
|
|
452
|
+
cachedDocs.push({
|
|
453
|
+
path: "docs/README.md",
|
|
454
|
+
content
|
|
455
|
+
});
|
|
456
|
+
docsToIndex.push({
|
|
457
|
+
id: "README.md",
|
|
458
|
+
content,
|
|
459
|
+
metadata: {
|
|
460
|
+
package: pkgName,
|
|
461
|
+
source: "docs/README.md",
|
|
462
|
+
type: "doc"
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (cachedDocs.length > 0) {
|
|
468
|
+
writeToCache(pkgName, version, cachedDocs);
|
|
469
|
+
mkdirSync(referencesPath, { recursive: true });
|
|
470
|
+
linkPkgSymlink(referencesPath, pkgName, cwd, version);
|
|
471
|
+
if (!isReadmeOnly(globalCachePath)) {
|
|
472
|
+
const docsLink = join(referencesPath, "docs");
|
|
473
|
+
const cachedDocsDir = join(globalCachePath, "docs");
|
|
474
|
+
if (existsSync(docsLink)) unlinkSync(docsLink);
|
|
475
|
+
if (existsSync(cachedDocsDir)) symlinkSync(cachedDocsDir, docsLink, "junction");
|
|
476
|
+
}
|
|
477
|
+
if (docsToIndex.length > 0) await createIndex(docsToIndex, { dbPath: getPackageDbPath(pkgName, version) });
|
|
478
|
+
const pkgDir = resolvePkgDir(pkgName, cwd, version);
|
|
479
|
+
const entryFiles = pkgDir ? await resolveEntryFiles(pkgDir) : [];
|
|
480
|
+
if (entryFiles.length > 0) await createIndex(entryFiles.map((e) => ({
|
|
481
|
+
id: e.path,
|
|
482
|
+
content: e.content,
|
|
483
|
+
metadata: {
|
|
484
|
+
package: pkgName,
|
|
485
|
+
source: `pkg/${e.path}`,
|
|
486
|
+
type: e.type
|
|
487
|
+
}
|
|
488
|
+
})), { dbPath: getPackageDbPath(pkgName, version) });
|
|
489
|
+
spin.stop(`Downloaded and linked ${name}`);
|
|
490
|
+
} else spin.stop(`No docs found for ${name}`);
|
|
491
|
+
}
|
|
492
|
+
p.outro("Install complete");
|
|
493
|
+
}
|
|
494
|
+
function unsanitizeName(sanitized, source) {
|
|
495
|
+
if (source?.includes("ungh://")) {
|
|
496
|
+
const match = source.match(/ungh:\/\/([^/]+)\/(.+)/);
|
|
497
|
+
if (match) return `@${match[1]}/${match[2]}`;
|
|
498
|
+
}
|
|
499
|
+
if (sanitized.startsWith("antfu-")) return `@antfu/${sanitized.slice(6)}`;
|
|
500
|
+
if (sanitized.startsWith("clack-")) return `@clack/${sanitized.slice(6)}`;
|
|
501
|
+
if (sanitized.startsWith("nuxt-")) return `@nuxt/${sanitized.slice(5)}`;
|
|
502
|
+
if (sanitized.startsWith("vue-")) return `@vue/${sanitized.slice(4)}`;
|
|
503
|
+
if (sanitized.startsWith("vueuse-")) return `@vueuse/${sanitized.slice(7)}`;
|
|
504
|
+
return sanitized;
|
|
505
|
+
}
|
|
506
|
+
function linkPkgSymlink(referencesDir, name, cwd, version) {
|
|
507
|
+
const pkgPath = resolvePkgDir(name, cwd, version);
|
|
508
|
+
if (!pkgPath) return;
|
|
509
|
+
const pkgLink = join(referencesDir, "pkg");
|
|
510
|
+
if (existsSync(pkgLink)) unlinkSync(pkgLink);
|
|
511
|
+
symlinkSync(pkgPath, pkgLink, "junction");
|
|
512
|
+
}
|
|
513
|
+
function isReadmeOnly(cacheDir) {
|
|
514
|
+
const docsDir = join(cacheDir, "docs");
|
|
515
|
+
if (!existsSync(docsDir)) return false;
|
|
516
|
+
const files = readdirSync(docsDir);
|
|
517
|
+
return files.length === 1 && files[0] === "README.md";
|
|
518
|
+
}
|
|
519
|
+
function pkgHasShippedDocs(name, cwd, version) {
|
|
520
|
+
const pkgPath = resolvePkgDir(name, cwd, version);
|
|
521
|
+
if (!pkgPath) return false;
|
|
522
|
+
for (const candidate of [
|
|
523
|
+
"docs",
|
|
524
|
+
"documentation",
|
|
525
|
+
"doc"
|
|
526
|
+
]) if (existsSync(join(pkgPath, candidate))) return true;
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
function* iterateSkills(opts = {}) {
|
|
530
|
+
const { scope = "all", cwd = process.cwd() } = opts;
|
|
531
|
+
const agentTypes = opts.agents ?? Object.keys(agents);
|
|
532
|
+
for (const agentType of agentTypes) {
|
|
533
|
+
const agent = agents[agentType];
|
|
534
|
+
if (scope === "local" || scope === "all") {
|
|
535
|
+
const localDir = join(cwd, agent.skillsDir);
|
|
536
|
+
if (existsSync(localDir)) {
|
|
537
|
+
const lock = readLock(localDir);
|
|
538
|
+
const entries = readdirSync(localDir).filter((f) => !f.startsWith(".") && f !== "skilld-lock.yaml");
|
|
539
|
+
for (const name of entries) {
|
|
540
|
+
const dir = join(localDir, name);
|
|
541
|
+
if (lock?.skills[name]) yield {
|
|
542
|
+
name,
|
|
543
|
+
dir,
|
|
544
|
+
agent: agentType,
|
|
545
|
+
info: lock.skills[name],
|
|
546
|
+
scope: "local"
|
|
547
|
+
};
|
|
548
|
+
else {
|
|
549
|
+
const info = parseSkillFrontmatter(join(dir, "_SKILL.md"));
|
|
550
|
+
if (info?.generator === "skilld") yield {
|
|
551
|
+
name,
|
|
552
|
+
dir,
|
|
553
|
+
agent: agentType,
|
|
554
|
+
info,
|
|
555
|
+
scope: "local"
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if ((scope === "global" || scope === "all") && agent.globalSkillsDir) {
|
|
562
|
+
const globalDir = agent.globalSkillsDir;
|
|
563
|
+
if (existsSync(globalDir)) {
|
|
564
|
+
const lock = readLock(globalDir);
|
|
565
|
+
const entries = readdirSync(globalDir).filter((f) => !f.startsWith(".") && f !== "skilld-lock.yaml");
|
|
566
|
+
for (const name of entries) {
|
|
567
|
+
const dir = join(globalDir, name);
|
|
568
|
+
if (lock?.skills[name]) yield {
|
|
569
|
+
name,
|
|
570
|
+
dir,
|
|
571
|
+
agent: agentType,
|
|
572
|
+
info: lock.skills[name],
|
|
573
|
+
scope: "global"
|
|
574
|
+
};
|
|
575
|
+
else {
|
|
576
|
+
const info = parseSkillFrontmatter(join(dir, "_SKILL.md"));
|
|
577
|
+
if (info?.generator === "skilld") yield {
|
|
578
|
+
name,
|
|
579
|
+
dir,
|
|
580
|
+
agent: agentType,
|
|
581
|
+
info,
|
|
582
|
+
scope: "global"
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
function isOutdated(skill, depVersion) {
|
|
591
|
+
if (!skill.info?.version) return true;
|
|
592
|
+
return skill.info.version.split(".").slice(0, 2).join(".") !== depVersion.replace(/^[\^~]/, "").split(".").slice(0, 2).join(".");
|
|
593
|
+
}
|
|
594
|
+
async function getProjectState(cwd = process.cwd()) {
|
|
595
|
+
const skills = [...iterateSkills({
|
|
596
|
+
scope: "local",
|
|
597
|
+
cwd
|
|
598
|
+
})];
|
|
599
|
+
const localDeps = await readLocalDependencies(cwd).catch(() => []);
|
|
600
|
+
const deps = new Map(localDeps.map((d) => [d.name, d.version]));
|
|
601
|
+
const skillByName = new Map(skills.map((s) => [s.name, s]));
|
|
602
|
+
const skillByPkgName = /* @__PURE__ */ new Map();
|
|
603
|
+
for (const s of skills) if (s.info?.packageName) skillByPkgName.set(s.info.packageName, s);
|
|
604
|
+
const missing = [];
|
|
605
|
+
const outdated = [];
|
|
606
|
+
const synced = [];
|
|
607
|
+
const matchedSkillNames = /* @__PURE__ */ new Set();
|
|
608
|
+
for (const [pkgName, version] of deps) {
|
|
609
|
+
const normalizedName = pkgName.replace(/^@/, "").replace(/\//g, "-");
|
|
610
|
+
const skill = skillByName.get(normalizedName) || skillByName.get(pkgName) || skillByPkgName.get(pkgName);
|
|
611
|
+
if (!skill) missing.push(pkgName);
|
|
612
|
+
else {
|
|
613
|
+
matchedSkillNames.add(skill.name);
|
|
614
|
+
if (isOutdated(skill, version)) outdated.push({
|
|
615
|
+
...skill,
|
|
616
|
+
packageName: pkgName,
|
|
617
|
+
latestVersion: version
|
|
618
|
+
});
|
|
619
|
+
else synced.push({
|
|
620
|
+
...skill,
|
|
621
|
+
packageName: pkgName,
|
|
622
|
+
latestVersion: version
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return {
|
|
627
|
+
skills,
|
|
628
|
+
deps,
|
|
629
|
+
missing,
|
|
630
|
+
outdated,
|
|
631
|
+
synced,
|
|
632
|
+
unmatched: skills.filter((s) => !matchedSkillNames.has(s.name))
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function getSkillsDir(agent, scope, cwd = process.cwd()) {
|
|
636
|
+
const agentConfig = agents[agent];
|
|
637
|
+
if (scope === "global") {
|
|
638
|
+
if (!agentConfig.globalSkillsDir) throw new Error(`Agent ${agent} does not support global skills`);
|
|
639
|
+
return agentConfig.globalSkillsDir;
|
|
640
|
+
}
|
|
641
|
+
return join(cwd, agentConfig.skillsDir);
|
|
642
|
+
}
|
|
643
|
+
async function removeCommand(state, opts) {
|
|
644
|
+
const scope = opts.global ? "global" : "local";
|
|
645
|
+
const allSkills = [...iterateSkills({ scope })];
|
|
646
|
+
const skills = opts.packages ? allSkills.filter((s) => opts.packages.includes(s.name)) : await pickSkillsToRemove(allSkills, scope);
|
|
647
|
+
if (!skills || skills.length === 0) {
|
|
648
|
+
p.log.info("No skills selected");
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (!opts.yes) {
|
|
652
|
+
const confirmed = await p.confirm({ message: `Remove ${skills.length} skill(s)? ${skills.map((s) => s.name).join(", ")}` });
|
|
653
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
654
|
+
p.cancel("Cancelled");
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
for (const skill of skills) {
|
|
659
|
+
const skillsDir = getSkillsDir(skill.agent, skill.scope);
|
|
660
|
+
if (existsSync(skill.dir)) {
|
|
661
|
+
rmSync(skill.dir, {
|
|
662
|
+
recursive: true,
|
|
663
|
+
force: true
|
|
664
|
+
});
|
|
665
|
+
removeLockEntry(skillsDir, skill.name);
|
|
666
|
+
p.log.success(`Removed ${skill.name}`);
|
|
667
|
+
} else p.log.warn(`${skill.name} not found`);
|
|
668
|
+
}
|
|
669
|
+
p.outro(`Removed ${skills.length} skill(s)`);
|
|
670
|
+
}
|
|
671
|
+
async function pickSkillsToRemove(skills, scope) {
|
|
672
|
+
if (skills.length === 0) {
|
|
673
|
+
p.log.warn(`No ${scope} skills installed`);
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
const options = skills.map((skill) => ({
|
|
677
|
+
label: skill.name,
|
|
678
|
+
value: skill.name,
|
|
679
|
+
hint: skill.info?.version ? `@${skill.info.version}` : void 0
|
|
680
|
+
}));
|
|
681
|
+
const selected = await p.multiselect({
|
|
682
|
+
message: "Select skills to remove",
|
|
683
|
+
options,
|
|
684
|
+
required: false
|
|
685
|
+
});
|
|
686
|
+
if (p.isCancel(selected)) {
|
|
687
|
+
p.cancel("Cancelled");
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
const selectedSet = new Set(selected);
|
|
691
|
+
return skills.filter((s) => selectedSet.has(s.name));
|
|
692
|
+
}
|
|
693
|
+
function highlightTerms(content, terms) {
|
|
694
|
+
if (terms.length === 0) return content;
|
|
695
|
+
const sorted = [...terms].sort((a, b) => b.length - a.length);
|
|
696
|
+
const pattern = new RegExp(`(${sorted.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})`, "gi");
|
|
697
|
+
return content.replace(pattern, "\x1B[33m$1\x1B[0m");
|
|
698
|
+
}
|
|
699
|
+
function formatSnippet(r) {
|
|
700
|
+
const refPath = `.claude/skills/${r.package}/.skilld/${r.source}`;
|
|
701
|
+
const lineRange = r.lineStart === r.lineEnd ? `L${r.lineStart}` : `L${r.lineStart}-${r.lineEnd}`;
|
|
702
|
+
const score = `\x1B[90m${r.score.toFixed(2)}\x1B[0m`;
|
|
703
|
+
const scopeStr = r.scope?.length ? `${r.scope.map((e) => e.name).join(".")} → ` : "";
|
|
704
|
+
const entityStr = r.entities?.map((e) => e.signature || `${e.type} ${e.name}`).join(", ");
|
|
705
|
+
const highlighted = highlightTerms(r.content, r.highlights);
|
|
706
|
+
return [
|
|
707
|
+
`${r.package} ${score}${entityStr ? ` \x1B[36m${scopeStr}${entityStr}\x1B[0m` : ""}`,
|
|
708
|
+
`\x1B[90m${refPath}:${lineRange}\x1B[0m`,
|
|
709
|
+
` ${highlighted.replace(/\n/g, "\n ")}`
|
|
710
|
+
].join("\n");
|
|
711
|
+
}
|
|
712
|
+
function findPackageDbs(packageFilter) {
|
|
713
|
+
if (!existsSync(REFERENCES_DIR)) return [];
|
|
714
|
+
const normalize = (s) => s.toLowerCase().replace(/[-_]/g, "");
|
|
715
|
+
return readdirSync(REFERENCES_DIR).filter((name) => name.includes("@")).filter((name) => {
|
|
716
|
+
if (!packageFilter) return true;
|
|
717
|
+
const pkg = name.split("@")[0];
|
|
718
|
+
const filter = normalize(packageFilter);
|
|
719
|
+
return normalize(pkg).includes(filter) || pkg.startsWith(packageFilter);
|
|
720
|
+
}).map((dir) => join(REFERENCES_DIR, dir, "search.db")).filter((db) => existsSync(db));
|
|
721
|
+
}
|
|
722
|
+
async function searchCommand(rawQuery, packageFilter) {
|
|
723
|
+
const dbs = findPackageDbs(packageFilter);
|
|
724
|
+
if (dbs.length === 0) {
|
|
725
|
+
if (packageFilter) p.log.warn(`No docs indexed for "${packageFilter}". Run \`skilld add ${packageFilter}\` first.`);
|
|
726
|
+
else p.log.warn("No docs indexed yet. Run `skilld add <package>` first.");
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
let query = rawQuery;
|
|
730
|
+
let filter;
|
|
731
|
+
const prefixMatch = rawQuery.match(/^(issues?|docs?|releases?):(.+)$/i);
|
|
732
|
+
if (prefixMatch) {
|
|
733
|
+
const prefix = prefixMatch[1].toLowerCase();
|
|
734
|
+
query = prefixMatch[2];
|
|
735
|
+
if (prefix.startsWith("issue")) filter = { type: "issue" };
|
|
736
|
+
else if (prefix.startsWith("release")) filter = { type: "release" };
|
|
737
|
+
else filter = { type: { $in: ["doc", "docs"] } };
|
|
738
|
+
}
|
|
739
|
+
const start = performance.now();
|
|
740
|
+
const merged = (await Promise.all(dbs.map((dbPath) => searchSnippets(query, { dbPath }, {
|
|
741
|
+
limit: filter ? 10 : 5,
|
|
742
|
+
filter
|
|
743
|
+
})))).flat().sort((a, b) => b.score - a.score).slice(0, 5);
|
|
744
|
+
const elapsed = ((performance.now() - start) / 1e3).toFixed(2);
|
|
745
|
+
if (merged.length === 0) {
|
|
746
|
+
p.log.warn(`No results for "${query}"`);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const output = merged.map((r) => formatSnippet(r)).join("\n\n");
|
|
750
|
+
p.log.message(`${output}\n\n${merged.length} results (${elapsed}s)`);
|
|
751
|
+
}
|
|
752
|
+
const require$1 = createRequire(import.meta.url);
|
|
753
|
+
const { version: skilldVersion } = require$1("../package.json");
|
|
754
|
+
function countDocs(packageName, version) {
|
|
755
|
+
if (!version) return 0;
|
|
756
|
+
const cacheDir = getCacheDir(packageName, version);
|
|
757
|
+
if (!existsSync(cacheDir)) return 0;
|
|
758
|
+
let count = 0;
|
|
759
|
+
const walk = (dir, depth = 0) => {
|
|
760
|
+
if (depth > 3) return;
|
|
12
761
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
762
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
763
|
+
if (entry.name === "search.db") continue;
|
|
764
|
+
if (entry.isDirectory()) walk(join(dir, entry.name), depth + 1);
|
|
765
|
+
else if (entry.name.endsWith(".md") || entry.name.endsWith(".mdx")) count++;
|
|
766
|
+
}
|
|
767
|
+
} catch {}
|
|
768
|
+
};
|
|
769
|
+
walk(cacheDir);
|
|
770
|
+
return count;
|
|
771
|
+
}
|
|
772
|
+
function countEmbeddings(packageName, version) {
|
|
773
|
+
if (!version) return null;
|
|
774
|
+
const dbPath = getPackageDbPath(packageName, version);
|
|
775
|
+
if (!existsSync(dbPath)) return null;
|
|
776
|
+
try {
|
|
777
|
+
const { DatabaseSync } = require$1("node:sqlite");
|
|
778
|
+
const db = new DatabaseSync(dbPath, {
|
|
779
|
+
open: true,
|
|
780
|
+
readOnly: true
|
|
781
|
+
});
|
|
782
|
+
const row = db.prepare("SELECT count(*) as cnt FROM vector_metadata").get();
|
|
783
|
+
db.close();
|
|
784
|
+
return row?.cnt ?? null;
|
|
785
|
+
} catch {
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
function countRefDocs(skillDir) {
|
|
790
|
+
const refsDir = join(skillDir, ".skilld");
|
|
791
|
+
if (!existsSync(refsDir)) return 0;
|
|
792
|
+
let count = 0;
|
|
793
|
+
const walk = (dir, depth = 0) => {
|
|
794
|
+
if (depth > 3) return;
|
|
795
|
+
try {
|
|
796
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) if (entry.isDirectory() || entry.isSymbolicLink()) try {
|
|
797
|
+
if (statSync(join(dir, entry.name)).isDirectory()) walk(join(dir, entry.name), depth + 1);
|
|
798
|
+
} catch {
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
else if (entry.name.endsWith(".md") || entry.name.endsWith(".mdx")) count++;
|
|
802
|
+
} catch {}
|
|
803
|
+
};
|
|
804
|
+
walk(refsDir);
|
|
805
|
+
return count;
|
|
806
|
+
}
|
|
807
|
+
function timeAgo(iso) {
|
|
808
|
+
if (!iso) return "";
|
|
809
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
810
|
+
const days = Math.floor(diff / 864e5);
|
|
811
|
+
if (days <= 0) return "today";
|
|
812
|
+
if (days === 1) return "1d ago";
|
|
813
|
+
if (days < 7) return `${days}d ago`;
|
|
814
|
+
if (days < 30) return `${Math.floor(days / 7)}w ago`;
|
|
815
|
+
return `${Math.floor(days / 30)}mo ago`;
|
|
816
|
+
}
|
|
817
|
+
function formatSource(source) {
|
|
818
|
+
if (!source) return "";
|
|
819
|
+
if (source === "shipped") return "shipped";
|
|
820
|
+
if (source.includes("llms.txt")) return "llms.txt";
|
|
821
|
+
if (source.includes("github.com")) return source.replace(/https?:\/\/github\.com\//, "");
|
|
822
|
+
return source;
|
|
823
|
+
}
|
|
824
|
+
const dim = (s) => `\x1B[90m${s}\x1B[0m`;
|
|
825
|
+
const bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
826
|
+
const green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
827
|
+
function getLastSynced$1() {
|
|
828
|
+
let latest = null;
|
|
829
|
+
for (const skill of iterateSkills()) if (skill.info?.syncedAt) {
|
|
830
|
+
const d = new Date(skill.info.syncedAt);
|
|
831
|
+
if (!latest || d > latest) latest = d;
|
|
832
|
+
}
|
|
833
|
+
if (!latest) return null;
|
|
834
|
+
return timeAgo(latest.toISOString());
|
|
835
|
+
}
|
|
836
|
+
function buildConfigLines() {
|
|
837
|
+
const config = readConfig();
|
|
838
|
+
const lines = [];
|
|
839
|
+
lines.push(`Version v${skilldVersion}`);
|
|
840
|
+
const lastSynced = getLastSynced$1();
|
|
841
|
+
if (lastSynced) lines.push(`Synced ${dim(lastSynced)}`);
|
|
842
|
+
lines.push(`Config ${dim(join(CACHE_DIR, "config.yaml"))}${hasConfig() ? "" : dim(" (not created)")}`);
|
|
843
|
+
lines.push(`Cache ${dim(CACHE_DIR)}`);
|
|
844
|
+
const withCli = Object.entries(agents).filter(([_, a]) => a.cli);
|
|
845
|
+
const installed = [];
|
|
846
|
+
for (const [id, agent] of withCli) {
|
|
847
|
+
const ver = getAgentVersion(id);
|
|
848
|
+
if (ver) installed.push(`${agent.displayName} v${ver}`);
|
|
849
|
+
}
|
|
850
|
+
if (installed.length > 0) lines.push(`Agents ${installed.join(", ")}`);
|
|
851
|
+
if (config.model) lines.push(`Model ${config.model}`);
|
|
852
|
+
const features = {
|
|
853
|
+
...defaultFeatures,
|
|
854
|
+
...config.features
|
|
855
|
+
};
|
|
856
|
+
const parts = Object.entries(features).map(([k, v]) => `${k}: ${v ? green("on") : dim("off")}`);
|
|
857
|
+
lines.push(`Features ${parts.join(", ")}`);
|
|
858
|
+
if (config.projects?.length) lines.push(`Projects ${config.projects.length} registered`);
|
|
859
|
+
return lines;
|
|
860
|
+
}
|
|
861
|
+
function statusCommand(opts = {}) {
|
|
862
|
+
const allSkills = [...iterateSkills({ scope: opts.global ? "global" : "all" })];
|
|
863
|
+
p.log.step(bold("Skilld Config"));
|
|
864
|
+
p.log.message(buildConfigLines().join("\n"));
|
|
865
|
+
if (allSkills.length === 0) {
|
|
866
|
+
p.log.step(bold("Skills"));
|
|
867
|
+
p.log.message(`${dim("(none)")}\n\nRun ${bold("skilld add <package>")} to install skills`);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
const localPkgs = /* @__PURE__ */ new Map();
|
|
871
|
+
const globalPkgs = /* @__PURE__ */ new Map();
|
|
872
|
+
for (const skill of allSkills) {
|
|
873
|
+
const key = skill.info?.packageName || skill.name;
|
|
874
|
+
const map = skill.scope === "local" ? localPkgs : globalPkgs;
|
|
875
|
+
if (!map.has(key)) map.set(key, {
|
|
876
|
+
name: skill.name,
|
|
877
|
+
info: skill.info || {},
|
|
878
|
+
agents: new Set([skill.agent]),
|
|
879
|
+
scope: skill.scope
|
|
880
|
+
});
|
|
881
|
+
else map.get(key).agents.add(skill.agent);
|
|
882
|
+
}
|
|
883
|
+
const buildPackageLines = (pkgs) => {
|
|
884
|
+
const lines = [];
|
|
885
|
+
for (const [, pkg] of pkgs) {
|
|
886
|
+
const { info } = pkg;
|
|
887
|
+
const parts = [`${info.source === "shipped" ? "▶" : "◆"} ${bold(pkg.name)}`];
|
|
888
|
+
if (info.version) parts.push(dim(info.version));
|
|
889
|
+
const source = formatSource(info.source);
|
|
890
|
+
if (source && source !== "shipped") parts.push(dim(source));
|
|
891
|
+
lines.push(parts.join(" "));
|
|
892
|
+
const meta = [];
|
|
893
|
+
const pkgName = info.packageName || pkg.name;
|
|
894
|
+
const docs = countDocs(pkgName, info.version) || countRefDocs(join(pkg.scope === "global" ? agents[pkg.agents.values().next().value].globalSkillsDir : join(process.cwd(), agents[pkg.agents.values().next().value].skillsDir), pkg.name));
|
|
895
|
+
if (docs > 0) meta.push(`${docs} docs`);
|
|
896
|
+
const embeddings = countEmbeddings(pkgName, info.version);
|
|
897
|
+
if (embeddings !== null) meta.push(`${embeddings} chunks`);
|
|
898
|
+
const ago = timeAgo(info.syncedAt);
|
|
899
|
+
if (ago) meta.push(`synced ${ago}`);
|
|
900
|
+
if (pkg.agents.size > 0) {
|
|
901
|
+
const agentNames = [...pkg.agents].map((a) => agents[a].displayName);
|
|
902
|
+
meta.push(agentNames.join(", "));
|
|
903
|
+
}
|
|
904
|
+
if (meta.length > 0) lines.push(` ${dim(meta.join(" · "))}`);
|
|
905
|
+
}
|
|
906
|
+
return lines;
|
|
907
|
+
};
|
|
908
|
+
if (!opts.global && localPkgs.size > 0) {
|
|
909
|
+
p.log.step(`${bold("Local")} (project)`);
|
|
910
|
+
p.log.message(buildPackageLines(localPkgs).join("\n"));
|
|
911
|
+
}
|
|
912
|
+
if (globalPkgs.size > 0) {
|
|
913
|
+
p.log.step(bold("Global"));
|
|
914
|
+
p.log.message(buildPackageLines(globalPkgs).join("\n"));
|
|
915
|
+
}
|
|
916
|
+
if (!opts.global && localPkgs.size === 0) {
|
|
917
|
+
p.log.step(`${bold("Local")} (project)`);
|
|
918
|
+
p.log.message(dim("(none)"));
|
|
919
|
+
}
|
|
920
|
+
const total = localPkgs.size + globalPkgs.size;
|
|
921
|
+
p.log.info(`${total} package${total !== 1 ? "s" : ""}`);
|
|
922
|
+
}
|
|
923
|
+
const RESOLVE_STEP_LABELS = {
|
|
924
|
+
"npm": "npm registry",
|
|
925
|
+
"github-docs": "GitHub docs",
|
|
926
|
+
"github-meta": "GitHub meta",
|
|
927
|
+
"github-search": "GitHub search",
|
|
928
|
+
"readme": "README",
|
|
929
|
+
"llms.txt": "llms.txt",
|
|
930
|
+
"local": "node_modules"
|
|
931
|
+
};
|
|
932
|
+
function showResolveAttempts(attempts) {
|
|
933
|
+
if (attempts.length === 0) return;
|
|
934
|
+
p.log.message("\x1B[90mResolution attempts:\x1B[0m");
|
|
935
|
+
for (const attempt of attempts) {
|
|
936
|
+
const icon = attempt.status === "success" ? "\x1B[32m✓\x1B[0m" : "\x1B[90m✗\x1B[0m";
|
|
937
|
+
const source = `\x1B[90m${attempt.source}\x1B[0m`;
|
|
938
|
+
const msg = attempt.message ? ` - ${attempt.message}` : "";
|
|
939
|
+
p.log.message(` ${icon} ${source}${msg}`);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function formatTaskResults(results) {
|
|
943
|
+
return results.map((r) => {
|
|
944
|
+
if (r.status === "error") return `\x1B[31m✖\x1B[0m ${r.msg}`;
|
|
945
|
+
if (r.status === "warn") return `\x1B[33m▲\x1B[0m ${r.msg}`;
|
|
946
|
+
return `\x1B[32m✓\x1B[0m ${r.msg}`;
|
|
947
|
+
}).join("\n");
|
|
948
|
+
}
|
|
949
|
+
async function ensureGitignore(skillsDir, cwd, isGlobal) {
|
|
950
|
+
if (isGlobal) return;
|
|
951
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
952
|
+
const pattern = ".skilld";
|
|
953
|
+
if (existsSync(gitignorePath)) {
|
|
954
|
+
if (readFileSync(gitignorePath, "utf-8").split("\n").some((line) => line.trim() === pattern)) return;
|
|
955
|
+
}
|
|
956
|
+
p.log.info(`\x1B[1mGit guidance:\x1B[0m\n \x1B[32m✓\x1B[0m Commit: \x1B[36m${skillsDir}/*/SKILL.md\x1B[0m\n \x1B[32m✓\x1B[0m Commit: \x1B[36m${skillsDir}/skilld-lock.yaml\x1B[0m\n \x1B[31m✗\x1B[0m Ignore: \x1B[36m${pattern}\x1B[0m \x1B[90m(recreated by \`skilld install\`)\x1B[0m`);
|
|
957
|
+
const add = await p.confirm({
|
|
958
|
+
message: `Add \`${pattern}\` to .gitignore?`,
|
|
959
|
+
initialValue: true
|
|
960
|
+
});
|
|
961
|
+
if (p.isCancel(add) || !add) return;
|
|
962
|
+
const entry = `\n# Skilld references (recreated by \`skilld install\`)\n${pattern}\n`;
|
|
963
|
+
if (existsSync(gitignorePath)) appendFileSync(gitignorePath, `${readFileSync(gitignorePath, "utf-8").endsWith("\n") ? "" : "\n"}${entry}`);
|
|
964
|
+
else writeFileSync(gitignorePath, entry);
|
|
965
|
+
p.log.success("Updated .gitignore");
|
|
966
|
+
}
|
|
967
|
+
async function syncCommand(state, opts) {
|
|
968
|
+
if (opts.packages && opts.packages.length > 0) {
|
|
969
|
+
if (opts.packages.length > 1) {
|
|
970
|
+
const { syncPackagesParallel } = await import("./_chunks/sync-parallel.mjs");
|
|
971
|
+
return syncPackagesParallel({
|
|
972
|
+
packages: opts.packages,
|
|
973
|
+
global: opts.global,
|
|
974
|
+
agent: opts.agent,
|
|
975
|
+
model: opts.model,
|
|
976
|
+
yes: opts.yes,
|
|
977
|
+
force: opts.force
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
await syncSinglePackage(opts.packages[0], opts);
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
const packages = await interactivePicker(state);
|
|
984
|
+
if (!packages || packages.length === 0) {
|
|
985
|
+
p.outro("No packages selected");
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
if (packages.length > 1) {
|
|
989
|
+
const { syncPackagesParallel } = await import("./_chunks/sync-parallel.mjs");
|
|
990
|
+
return syncPackagesParallel({
|
|
991
|
+
packages,
|
|
992
|
+
global: opts.global,
|
|
993
|
+
agent: opts.agent,
|
|
994
|
+
model: opts.model,
|
|
995
|
+
yes: opts.yes,
|
|
996
|
+
force: opts.force
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
await syncSinglePackage(packages[0], opts);
|
|
1000
|
+
}
|
|
1001
|
+
async function interactivePicker(state) {
|
|
1002
|
+
const spin = p.spinner();
|
|
1003
|
+
spin.start("Detecting imports...");
|
|
1004
|
+
const { packages: detected, error } = await detectImportedPackages(process.cwd());
|
|
1005
|
+
const declaredMap = state.deps;
|
|
1006
|
+
if (error || detected.length === 0) {
|
|
1007
|
+
spin.stop(error ? `Detection failed: ${error}` : "No imports detected");
|
|
1008
|
+
if (declaredMap.size === 0) {
|
|
1009
|
+
p.log.warn("No dependencies found");
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
return pickFromList([...declaredMap.entries()].map(([name, version]) => ({
|
|
1013
|
+
name,
|
|
1014
|
+
version: maskPatch(version),
|
|
1015
|
+
count: 0,
|
|
1016
|
+
inPkgJson: true
|
|
1017
|
+
})), state);
|
|
1018
|
+
}
|
|
1019
|
+
spin.stop(`Loaded ${detected.length} project skills`);
|
|
1020
|
+
return pickFromList(detected.map((pkg) => ({
|
|
1021
|
+
name: pkg.name,
|
|
1022
|
+
version: declaredMap.get(pkg.name),
|
|
1023
|
+
count: pkg.count,
|
|
1024
|
+
inPkgJson: declaredMap.has(pkg.name)
|
|
1025
|
+
})), state);
|
|
1026
|
+
}
|
|
1027
|
+
function maskPatch(version) {
|
|
1028
|
+
if (!version) return void 0;
|
|
1029
|
+
const parts = version.split(".");
|
|
1030
|
+
if (parts.length >= 3) {
|
|
1031
|
+
parts[2] = "x";
|
|
1032
|
+
return parts.slice(0, 3).join(".");
|
|
1033
|
+
}
|
|
1034
|
+
return version;
|
|
1035
|
+
}
|
|
1036
|
+
async function pickFromList(packages, state) {
|
|
1037
|
+
const missingSet = new Set(state.missing);
|
|
1038
|
+
const outdatedSet = new Set(state.outdated.map((s) => s.name));
|
|
1039
|
+
const options = packages.map((pkg) => ({
|
|
1040
|
+
label: pkg.inPkgJson ? `${pkg.name} ★` : pkg.name,
|
|
1041
|
+
value: pkg.name,
|
|
1042
|
+
hint: [maskPatch(pkg.version), pkg.count > 0 ? `${pkg.count} imports` : null].filter(Boolean).join(" · ") || void 0
|
|
1043
|
+
}));
|
|
1044
|
+
const initialValues = packages.filter((pkg) => missingSet.has(pkg.name) || outdatedSet.has(pkg.name)).map((pkg) => pkg.name);
|
|
1045
|
+
const selected = await p.multiselect({
|
|
1046
|
+
message: "Select packages to sync",
|
|
1047
|
+
options,
|
|
1048
|
+
required: false,
|
|
1049
|
+
initialValues
|
|
1050
|
+
});
|
|
1051
|
+
if (p.isCancel(selected)) {
|
|
1052
|
+
p.cancel("Cancelled");
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
return selected;
|
|
1056
|
+
}
|
|
1057
|
+
async function selectModel(skipPrompt) {
|
|
1058
|
+
const config = readConfig();
|
|
1059
|
+
const available = await getAvailableModels();
|
|
1060
|
+
if (available.length === 0) {
|
|
1061
|
+
p.log.warn("No LLM CLIs found (claude, gemini, codex)");
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
if (config.model && available.some((m) => m.id === config.model)) return config.model;
|
|
1065
|
+
if (skipPrompt) return available.find((m) => m.recommended)?.id ?? available[0].id;
|
|
1066
|
+
const modelChoice = await p.select({
|
|
1067
|
+
message: "Model for SKILL.md generation",
|
|
1068
|
+
options: available.map((m) => ({
|
|
1069
|
+
label: m.recommended ? `${m.name} (Recommended)` : m.name,
|
|
1070
|
+
value: m.id,
|
|
1071
|
+
hint: `${m.agentName} · ${m.hint}`
|
|
1072
|
+
})),
|
|
1073
|
+
initialValue: available.find((m) => m.recommended)?.id ?? available[0].id
|
|
1074
|
+
});
|
|
1075
|
+
if (p.isCancel(modelChoice)) {
|
|
1076
|
+
p.cancel("Cancelled");
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
updateConfig({ model: modelChoice });
|
|
1080
|
+
return modelChoice;
|
|
1081
|
+
}
|
|
1082
|
+
async function selectSkillSections() {
|
|
1083
|
+
const selected = await p.multiselect({
|
|
1084
|
+
message: "Generate SKILL.md with LLM",
|
|
1085
|
+
options: [
|
|
1086
|
+
{
|
|
1087
|
+
label: "Best practices",
|
|
1088
|
+
value: "best-practices",
|
|
1089
|
+
hint: "gotchas, pitfalls, patterns"
|
|
1090
|
+
},
|
|
1091
|
+
{
|
|
1092
|
+
label: "API reference",
|
|
1093
|
+
value: "api",
|
|
1094
|
+
hint: "exported functions & composables"
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
label: "Custom prompt",
|
|
1098
|
+
value: "custom",
|
|
1099
|
+
hint: "add your own instructions"
|
|
1100
|
+
}
|
|
1101
|
+
],
|
|
1102
|
+
initialValues: ["best-practices", "api"],
|
|
1103
|
+
required: false
|
|
1104
|
+
});
|
|
1105
|
+
if (p.isCancel(selected)) return {
|
|
1106
|
+
sections: [],
|
|
1107
|
+
cancelled: true
|
|
1108
|
+
};
|
|
1109
|
+
const sections = selected;
|
|
1110
|
+
if (sections.length === 0) return {
|
|
1111
|
+
sections: [],
|
|
1112
|
+
cancelled: false
|
|
1113
|
+
};
|
|
1114
|
+
let customPrompt;
|
|
1115
|
+
if (sections.includes("custom")) {
|
|
1116
|
+
const text = await p.text({
|
|
1117
|
+
message: "Custom instructions",
|
|
1118
|
+
placeholder: "e.g. \"Focus on SSR patterns\" or \"Include migration notes from v2 to v3\""
|
|
1119
|
+
});
|
|
1120
|
+
if (p.isCancel(text)) return {
|
|
1121
|
+
sections: [],
|
|
1122
|
+
cancelled: true
|
|
1123
|
+
};
|
|
1124
|
+
customPrompt = text;
|
|
1125
|
+
}
|
|
1126
|
+
return {
|
|
1127
|
+
sections,
|
|
1128
|
+
customPrompt,
|
|
1129
|
+
cancelled: false
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
async function syncSinglePackage(packageName, config) {
|
|
1133
|
+
const spin = p.spinner();
|
|
1134
|
+
spin.start(`Resolving ${packageName}`);
|
|
1135
|
+
const cwd = process.cwd();
|
|
1136
|
+
const localVersion = (await readLocalDependencies(cwd).catch(() => [])).find((d) => d.name === packageName)?.version;
|
|
1137
|
+
const resolveResult = await resolvePackageDocsWithAttempts(packageName, {
|
|
1138
|
+
version: localVersion,
|
|
1139
|
+
cwd,
|
|
1140
|
+
onProgress: (step) => spin.message(`${packageName}: ${RESOLVE_STEP_LABELS[step]}`)
|
|
1141
|
+
});
|
|
1142
|
+
let resolved = resolveResult.package;
|
|
1143
|
+
if (!resolved) {
|
|
1144
|
+
const { readFileSync, existsSync } = await import("node:fs");
|
|
1145
|
+
const { join, resolve } = await import("node:path");
|
|
1146
|
+
const pkgPath = join(cwd, "package.json");
|
|
1147
|
+
if (existsSync(pkgPath)) {
|
|
1148
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1149
|
+
const depVersion = {
|
|
1150
|
+
...pkg.dependencies,
|
|
1151
|
+
...pkg.devDependencies
|
|
1152
|
+
}[packageName];
|
|
1153
|
+
if (depVersion?.startsWith("link:")) {
|
|
1154
|
+
spin.message(`Resolving local package: ${packageName}`);
|
|
1155
|
+
resolved = await resolveLocalPackageDocs(resolve(cwd, depVersion.slice(5)));
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (!resolved) {
|
|
1160
|
+
spin.stop(`Could not find docs for: ${packageName}`);
|
|
1161
|
+
showResolveAttempts(resolveResult.attempts);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
const version = localVersion || resolved.version || "latest";
|
|
1165
|
+
const versionKey = getVersionKey(version);
|
|
1166
|
+
if (!existsSync(join(cwd, "node_modules", packageName))) {
|
|
1167
|
+
spin.message(`Downloading ${packageName}@${version} dist`);
|
|
1168
|
+
await fetchPkgDist(packageName, version);
|
|
1169
|
+
}
|
|
1170
|
+
const shippedSkills = getShippedSkills(packageName, cwd, version);
|
|
1171
|
+
if (shippedSkills.length > 0) {
|
|
1172
|
+
const agent = agents[config.agent];
|
|
1173
|
+
const baseDir = config.global ? join(CACHE_DIR, "skills") : join(cwd, agent.skillsDir);
|
|
1174
|
+
mkdirSync(baseDir, { recursive: true });
|
|
1175
|
+
for (const shipped of shippedSkills) {
|
|
1176
|
+
linkShippedSkill(baseDir, shipped.skillName, shipped.skillDir);
|
|
1177
|
+
writeLock(baseDir, shipped.skillName, {
|
|
1178
|
+
packageName,
|
|
1179
|
+
version,
|
|
1180
|
+
source: "shipped",
|
|
1181
|
+
syncedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
1182
|
+
generator: "skilld"
|
|
1183
|
+
});
|
|
1184
|
+
p.log.success(`Linked shipped skill: ${shipped.skillName} → ${relative(cwd, shipped.skillDir)}`);
|
|
1185
|
+
}
|
|
1186
|
+
if (!config.global) registerProject(cwd);
|
|
1187
|
+
spin.stop(`Shipped ${shippedSkills.length} skill(s) from ${packageName}`);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
if (config.force) {
|
|
1191
|
+
clearCache(packageName, version);
|
|
1192
|
+
const forcedDbPath = getPackageDbPath(packageName, version);
|
|
1193
|
+
if (existsSync(forcedDbPath)) rmSync(forcedDbPath, {
|
|
1194
|
+
recursive: true,
|
|
1195
|
+
force: true
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
const useCache = isCached(packageName, version);
|
|
1199
|
+
spin.stop(`Resolved ${packageName}@${useCache ? versionKey : version}${config.force ? " (force)" : useCache ? " (cached)" : ""}`);
|
|
1200
|
+
ensureCacheDir();
|
|
1201
|
+
const agent = agents[config.agent];
|
|
1202
|
+
const baseDir = config.global ? join(CACHE_DIR, "skills") : join(cwd, agent.skillsDir);
|
|
1203
|
+
const skillDir = join(baseDir, sanitizeName(packageName));
|
|
1204
|
+
mkdirSync(skillDir, { recursive: true });
|
|
1205
|
+
let docSource = resolved.readmeUrl || "readme";
|
|
1206
|
+
let docsType = "readme";
|
|
1207
|
+
const fetchedDocs = [];
|
|
1208
|
+
const fetchedIssues = [];
|
|
1209
|
+
const fetchedDiscussions = [];
|
|
1210
|
+
const fetchedReleases = [];
|
|
1211
|
+
const resourceTasks = [];
|
|
1212
|
+
if (!useCache) resourceTasks.push({
|
|
1213
|
+
title: "Fetching documentation",
|
|
1214
|
+
task: async (message) => {
|
|
1215
|
+
const cachedDocs = [];
|
|
1216
|
+
if (resolved.gitDocsUrl && resolved.repoUrl) {
|
|
1217
|
+
const gh = parseGitHubUrl(resolved.repoUrl);
|
|
1218
|
+
if (gh) {
|
|
1219
|
+
const gitDocs = await fetchGitDocs(gh.owner, gh.repo, version, packageName);
|
|
1220
|
+
if (gitDocs && gitDocs.files.length > 0) {
|
|
1221
|
+
message(`Downloading ${gitDocs.files.length} docs from ${gitDocs.ref}`);
|
|
1222
|
+
const BATCH_SIZE = 20;
|
|
1223
|
+
const results = [];
|
|
1224
|
+
for (let i = 0; i < gitDocs.files.length; i += BATCH_SIZE) {
|
|
1225
|
+
const batch = gitDocs.files.slice(i, i + BATCH_SIZE);
|
|
1226
|
+
const batchResults = await Promise.all(batch.map(async (file) => {
|
|
1227
|
+
const url = `${gitDocs.baseUrl}/${file}`;
|
|
1228
|
+
const res = await fetch(url, { headers: { "User-Agent": "skilld/1.0" } }).catch(() => null);
|
|
1229
|
+
if (!res?.ok) return null;
|
|
1230
|
+
return {
|
|
1231
|
+
file,
|
|
1232
|
+
content: await res.text()
|
|
1233
|
+
};
|
|
1234
|
+
}));
|
|
1235
|
+
results.push(...batchResults);
|
|
1236
|
+
}
|
|
1237
|
+
for (const r of results) if (r) {
|
|
1238
|
+
const cachePath = gitDocs.docsPrefix ? r.file.replace(gitDocs.docsPrefix, "") : r.file;
|
|
1239
|
+
cachedDocs.push({
|
|
1240
|
+
path: cachePath,
|
|
1241
|
+
content: r.content
|
|
1242
|
+
});
|
|
1243
|
+
fetchedDocs.push({
|
|
1244
|
+
id: cachePath,
|
|
1245
|
+
content: r.content,
|
|
1246
|
+
metadata: {
|
|
1247
|
+
package: packageName,
|
|
1248
|
+
source: cachePath,
|
|
1249
|
+
type: "doc"
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
const downloaded = results.filter(Boolean).length;
|
|
1254
|
+
if (downloaded > 0) {
|
|
1255
|
+
docSource = `${resolved.repoUrl}/tree/${gitDocs.ref}/docs`;
|
|
1256
|
+
docsType = "docs";
|
|
1257
|
+
writeToCache(packageName, version, cachedDocs);
|
|
1258
|
+
return `Downloaded ${downloaded} git docs`;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
if (resolved.llmsUrl && cachedDocs.length === 0) {
|
|
1264
|
+
message("Fetching llms.txt");
|
|
1265
|
+
const llmsContent = await fetchLlmsTxt(resolved.llmsUrl);
|
|
1266
|
+
if (llmsContent) {
|
|
1267
|
+
docSource = resolved.llmsUrl;
|
|
1268
|
+
docsType = "llms.txt";
|
|
1269
|
+
const baseUrl = resolved.docsUrl || new URL(resolved.llmsUrl).origin;
|
|
1270
|
+
cachedDocs.push({
|
|
1271
|
+
path: "llms.txt",
|
|
1272
|
+
content: normalizeLlmsLinks(llmsContent.raw, baseUrl)
|
|
1273
|
+
});
|
|
1274
|
+
if (llmsContent.links.length > 0) {
|
|
1275
|
+
message(`Downloading ${llmsContent.links.length} linked docs`);
|
|
1276
|
+
const docs = await downloadLlmsDocs(llmsContent, baseUrl);
|
|
1277
|
+
for (const doc of docs) {
|
|
1278
|
+
const cachePath = join("docs", ...(doc.url.startsWith("/") ? doc.url.slice(1) : doc.url).split("/"));
|
|
1279
|
+
cachedDocs.push({
|
|
1280
|
+
path: cachePath,
|
|
1281
|
+
content: doc.content
|
|
1282
|
+
});
|
|
1283
|
+
fetchedDocs.push({
|
|
1284
|
+
id: doc.url,
|
|
1285
|
+
content: doc.content,
|
|
1286
|
+
metadata: {
|
|
1287
|
+
package: packageName,
|
|
1288
|
+
source: cachePath,
|
|
1289
|
+
type: "doc"
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
writeToCache(packageName, version, cachedDocs);
|
|
1294
|
+
return `Saved ${docs.length + 1} docs from llms.txt`;
|
|
1295
|
+
}
|
|
1296
|
+
writeToCache(packageName, version, cachedDocs);
|
|
1297
|
+
return "Saved llms.txt";
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (resolved.readmeUrl && cachedDocs.length === 0) {
|
|
1301
|
+
message("Fetching README");
|
|
1302
|
+
const content = await fetchReadmeContent(resolved.readmeUrl);
|
|
1303
|
+
if (content) {
|
|
1304
|
+
cachedDocs.push({
|
|
1305
|
+
path: "docs/README.md",
|
|
1306
|
+
content
|
|
1307
|
+
});
|
|
1308
|
+
fetchedDocs.push({
|
|
1309
|
+
id: "README.md",
|
|
1310
|
+
content,
|
|
1311
|
+
metadata: {
|
|
1312
|
+
package: packageName,
|
|
1313
|
+
source: "docs/README.md",
|
|
1314
|
+
type: "doc"
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
writeToCache(packageName, version, cachedDocs);
|
|
1318
|
+
return "Saved README.md";
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return "No docs found";
|
|
1322
|
+
}
|
|
33
1323
|
});
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
1324
|
+
const features = readConfig().features ?? defaultFeatures;
|
|
1325
|
+
const issuesPath = join(getCacheDir(packageName, version), "github", "RECENT-ISSUES.md");
|
|
1326
|
+
if (features.issues && resolved.repoUrl && isGhAvailable() && !existsSync(issuesPath)) {
|
|
1327
|
+
const gh = parseGitHubUrl(resolved.repoUrl);
|
|
1328
|
+
if (gh) resourceTasks.push({
|
|
1329
|
+
title: "Fetching GitHub issues",
|
|
1330
|
+
task: async () => {
|
|
1331
|
+
const issues = await fetchGitHubIssues(gh.owner, gh.repo, 20);
|
|
1332
|
+
if (issues.length > 0) {
|
|
1333
|
+
writeToCache(packageName, version, [{
|
|
1334
|
+
path: "github/RECENT-ISSUES.md",
|
|
1335
|
+
content: formatIssuesAsMarkdown(issues)
|
|
1336
|
+
}]);
|
|
1337
|
+
for (const issue of issues) fetchedIssues.push({
|
|
1338
|
+
id: `issue-${issue.number}`,
|
|
1339
|
+
content: `#${issue.number}: ${issue.title}\n\n${issue.body || ""}`,
|
|
1340
|
+
metadata: {
|
|
1341
|
+
package: packageName,
|
|
1342
|
+
source: "github/RECENT-ISSUES.md",
|
|
1343
|
+
type: "issue",
|
|
1344
|
+
number: issue.number
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
return `Cached ${issues.length} issues`;
|
|
1348
|
+
}
|
|
1349
|
+
return "No issues found";
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
const discussionsPath = join(getCacheDir(packageName, version), "github", "RECENT-DISCUSSIONS.md");
|
|
1354
|
+
if (features.discussions && resolved.repoUrl && isGhAvailable() && !existsSync(discussionsPath)) {
|
|
1355
|
+
const gh = parseGitHubUrl(resolved.repoUrl);
|
|
1356
|
+
if (gh) resourceTasks.push({
|
|
1357
|
+
title: "Fetching GitHub discussions",
|
|
1358
|
+
task: async () => {
|
|
1359
|
+
const discussions = await fetchGitHubDiscussions(gh.owner, gh.repo, 20);
|
|
1360
|
+
if (discussions.length > 0) {
|
|
1361
|
+
writeToCache(packageName, version, [{
|
|
1362
|
+
path: "github/RECENT-DISCUSSIONS.md",
|
|
1363
|
+
content: formatDiscussionsAsMarkdown(discussions)
|
|
1364
|
+
}]);
|
|
1365
|
+
for (const d of discussions) fetchedDiscussions.push({
|
|
1366
|
+
id: `discussion-${d.number}`,
|
|
1367
|
+
content: `#${d.number}: ${d.title}\n\n${d.body || ""}`,
|
|
1368
|
+
metadata: {
|
|
1369
|
+
package: packageName,
|
|
1370
|
+
source: "github/RECENT-DISCUSSIONS.md",
|
|
1371
|
+
type: "discussion",
|
|
1372
|
+
number: d.number
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
return `Cached ${discussions.length} discussions`;
|
|
1376
|
+
}
|
|
1377
|
+
return "No discussions found";
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
const releasesPath = join(getCacheDir(packageName, version), "releases");
|
|
1382
|
+
if (features.releases && resolved.repoUrl && !existsSync(releasesPath)) {
|
|
1383
|
+
const gh = parseGitHubUrl(resolved.repoUrl);
|
|
1384
|
+
if (gh) resourceTasks.push({
|
|
1385
|
+
title: "Fetching release notes",
|
|
1386
|
+
task: async () => {
|
|
1387
|
+
const releaseDocs = await fetchReleaseNotes(gh.owner, gh.repo, version, resolved.gitRef, packageName);
|
|
1388
|
+
if (releaseDocs.length > 0) {
|
|
1389
|
+
writeToCache(packageName, version, releaseDocs);
|
|
1390
|
+
for (const doc of releaseDocs) fetchedReleases.push({
|
|
1391
|
+
id: doc.path,
|
|
1392
|
+
content: doc.content,
|
|
1393
|
+
metadata: {
|
|
1394
|
+
package: packageName,
|
|
1395
|
+
source: doc.path,
|
|
1396
|
+
type: "release"
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
return `Cached ${releaseDocs.length} release note(s)`;
|
|
1400
|
+
}
|
|
1401
|
+
return "No releases found";
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
if (resourceTasks.length > 0) {
|
|
1406
|
+
const resSpin = p.spinner();
|
|
1407
|
+
resSpin.start("Finding resources");
|
|
1408
|
+
const resResults = [];
|
|
1409
|
+
for (const task of resourceTasks) {
|
|
1410
|
+
resSpin.message(task.title);
|
|
1411
|
+
try {
|
|
1412
|
+
const result = await task.task((msg) => resSpin.message(msg));
|
|
1413
|
+
if (result === "No discussions found") continue;
|
|
1414
|
+
resResults.push({
|
|
1415
|
+
msg: result,
|
|
1416
|
+
status: result.startsWith("No ") ? "warn" : "ok"
|
|
1417
|
+
});
|
|
1418
|
+
} catch {
|
|
1419
|
+
resResults.push({
|
|
1420
|
+
msg: `${task.title} failed`,
|
|
1421
|
+
status: "error"
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
resSpin.stop("Fetched resources");
|
|
1426
|
+
p.log.message(formatTaskResults(resResults));
|
|
1427
|
+
}
|
|
1428
|
+
try {
|
|
1429
|
+
linkPkg(skillDir, packageName, cwd, version);
|
|
1430
|
+
if (!hasShippedDocs(packageName, cwd, version) && docsType !== "readme") linkReferences(skillDir, packageName, version);
|
|
1431
|
+
linkGithub(skillDir, packageName, version);
|
|
1432
|
+
linkReleases(skillDir, packageName, version);
|
|
1433
|
+
} catch {}
|
|
1434
|
+
const dbPath = getPackageDbPath(packageName, version);
|
|
1435
|
+
const indexTasks = [];
|
|
1436
|
+
if (!existsSync(dbPath)) if (fetchedDocs.length > 0 || fetchedIssues.length > 0 || fetchedDiscussions.length > 0 || fetchedReleases.length > 0) {
|
|
1437
|
+
if (fetchedDocs.length > 0) indexTasks.push({
|
|
1438
|
+
title: `Indexing ${fetchedDocs.length} docs`,
|
|
1439
|
+
task: async (message) => {
|
|
1440
|
+
await createIndex(fetchedDocs, {
|
|
1441
|
+
dbPath,
|
|
1442
|
+
onProgress: (current, total, doc) => {
|
|
1443
|
+
message(`Indexing doc ${doc?.id ? doc.id.split("/").pop() : ""} - ${current}/${total}`);
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
return `Indexed ${fetchedDocs.length} docs`;
|
|
1447
|
+
}
|
|
1448
|
+
});
|
|
1449
|
+
if (fetchedIssues.length > 0) indexTasks.push({
|
|
1450
|
+
title: `Indexing ${fetchedIssues.length} issues`,
|
|
1451
|
+
task: async (message) => {
|
|
1452
|
+
await createIndex(fetchedIssues, {
|
|
1453
|
+
dbPath,
|
|
1454
|
+
onProgress: (current, total, doc) => {
|
|
1455
|
+
message(`Indexing doc ${doc?.id ? doc.id.split("/").pop() : ""} - ${current}/${total}`);
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
return `Indexed ${fetchedIssues.length} issues`;
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
if (fetchedDiscussions.length > 0) indexTasks.push({
|
|
1462
|
+
title: `Indexing ${fetchedDiscussions.length} discussions`,
|
|
1463
|
+
task: async (message) => {
|
|
1464
|
+
await createIndex(fetchedDiscussions, {
|
|
1465
|
+
dbPath,
|
|
1466
|
+
onProgress: (current, total, doc) => {
|
|
1467
|
+
message(`Indexing doc ${doc?.id ? doc.id.split("/").pop() : ""} - ${current}/${total}`);
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
return `Indexed ${fetchedDiscussions.length} discussions`;
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1473
|
+
if (fetchedReleases.length > 0) indexTasks.push({
|
|
1474
|
+
title: `Indexing ${fetchedReleases.length} releases`,
|
|
1475
|
+
task: async (message) => {
|
|
1476
|
+
await createIndex(fetchedReleases, {
|
|
1477
|
+
dbPath,
|
|
1478
|
+
onProgress: (current, total, doc) => {
|
|
1479
|
+
message(`Indexing doc ${doc?.id ? doc.id.split("/").pop() : ""} - ${current}/${total}`);
|
|
1480
|
+
}
|
|
1481
|
+
});
|
|
1482
|
+
return `Indexed ${fetchedReleases.length} releases`;
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
} else indexTasks.push({
|
|
1486
|
+
title: "Indexing cached docs",
|
|
1487
|
+
task: async (message) => {
|
|
1488
|
+
const cachedDocs = readCachedDocs(packageName, version);
|
|
1489
|
+
if (cachedDocs.length === 0) return "No docs to index";
|
|
1490
|
+
const docsToIndex = cachedDocs.filter((doc) => !doc.path.startsWith("github/")).map((doc) => ({
|
|
1491
|
+
id: doc.path,
|
|
1492
|
+
content: doc.content,
|
|
1493
|
+
metadata: {
|
|
1494
|
+
package: packageName,
|
|
1495
|
+
source: doc.path,
|
|
1496
|
+
type: "doc"
|
|
1497
|
+
}
|
|
1498
|
+
}));
|
|
1499
|
+
const issuesDoc = cachedDocs.find((doc) => doc.path === "github/RECENT-ISSUES.md");
|
|
1500
|
+
if (issuesDoc) {
|
|
1501
|
+
const issueBlocks = issuesDoc.content.split(/\n---\n/).filter(Boolean);
|
|
1502
|
+
for (const block of issueBlocks) {
|
|
1503
|
+
const match = block.match(/## #(\d+): (.+)/);
|
|
1504
|
+
if (match) docsToIndex.push({
|
|
1505
|
+
id: `issue-${match[1]}`,
|
|
1506
|
+
content: block,
|
|
1507
|
+
metadata: {
|
|
1508
|
+
package: packageName,
|
|
1509
|
+
source: "github/RECENT-ISSUES.md",
|
|
1510
|
+
type: "issue",
|
|
1511
|
+
number: Number(match[1])
|
|
1512
|
+
}
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
const discussionsDoc = cachedDocs.find((doc) => doc.path === "github/RECENT-DISCUSSIONS.md");
|
|
1517
|
+
if (discussionsDoc) {
|
|
1518
|
+
const discussionBlocks = discussionsDoc.content.split(/\n---\n/).filter(Boolean);
|
|
1519
|
+
for (const block of discussionBlocks) {
|
|
1520
|
+
const match = block.match(/## #(\d+): (.+)/);
|
|
1521
|
+
if (match) docsToIndex.push({
|
|
1522
|
+
id: `discussion-${match[1]}`,
|
|
1523
|
+
content: block,
|
|
1524
|
+
metadata: {
|
|
1525
|
+
package: packageName,
|
|
1526
|
+
source: "github/RECENT-DISCUSSIONS.md",
|
|
1527
|
+
type: "discussion",
|
|
1528
|
+
number: Number(match[1])
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
await createIndex(docsToIndex, {
|
|
1534
|
+
dbPath,
|
|
1535
|
+
onProgress: (current, total, doc) => {
|
|
1536
|
+
message(`Indexing ${doc?.type === "source" || doc?.type === "types" ? "code" : "doc"} ${doc?.id ? doc.id.split("/").pop() : ""} - ${current}/${total}`);
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
return `Indexed ${docsToIndex.length} docs`;
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
const pkgDir = resolvePkgDir(packageName, cwd, version);
|
|
1543
|
+
const entryFiles = features.search && pkgDir ? await resolveEntryFiles(pkgDir) : [];
|
|
1544
|
+
if (entryFiles.length > 0) {
|
|
1545
|
+
const entryLabel = entryFiles.length === 1 ? entryFiles[0].path : `${entryFiles.length} entry files`;
|
|
1546
|
+
indexTasks.push({
|
|
1547
|
+
title: `Indexing ${entryLabel}`,
|
|
1548
|
+
task: async (message) => {
|
|
1549
|
+
await createIndex(entryFiles.map((e) => ({
|
|
1550
|
+
id: e.path,
|
|
1551
|
+
content: e.content,
|
|
1552
|
+
metadata: {
|
|
1553
|
+
package: packageName,
|
|
1554
|
+
source: `pkg/${e.path}`,
|
|
1555
|
+
type: e.type
|
|
1556
|
+
}
|
|
1557
|
+
})), {
|
|
1558
|
+
dbPath,
|
|
1559
|
+
onProgress: (current, total, doc) => {
|
|
1560
|
+
message(`Indexing code ${doc?.id ? doc.id.split("/").pop() : ""} - ${current}/${total}`);
|
|
1561
|
+
}
|
|
1562
|
+
});
|
|
1563
|
+
return `Indexed ${entryLabel}`;
|
|
1564
|
+
}
|
|
45
1565
|
});
|
|
46
1566
|
}
|
|
47
|
-
|
|
1567
|
+
if (indexTasks.length > 0) {
|
|
1568
|
+
const idxSpin = p.spinner();
|
|
1569
|
+
idxSpin.start("Creating search index");
|
|
1570
|
+
const idxResults = [];
|
|
1571
|
+
for (const task of indexTasks) {
|
|
1572
|
+
idxSpin.message(task.title);
|
|
1573
|
+
try {
|
|
1574
|
+
const result = await task.task((msg) => idxSpin.message(msg));
|
|
1575
|
+
idxResults.push({
|
|
1576
|
+
msg: result,
|
|
1577
|
+
status: result.startsWith("No ") ? "warn" : "ok"
|
|
1578
|
+
});
|
|
1579
|
+
} catch {
|
|
1580
|
+
idxResults.push({
|
|
1581
|
+
msg: `${task.title} failed`,
|
|
1582
|
+
status: "error"
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
idxSpin.stop("Search index ready");
|
|
1587
|
+
p.log.message(formatTaskResults(idxResults));
|
|
1588
|
+
}
|
|
1589
|
+
const cacheDir = getCacheDir(packageName, version);
|
|
1590
|
+
if (useCache) {
|
|
1591
|
+
if (existsSync(join(cacheDir, "docs", "index.md")) || existsSync(join(cacheDir, "docs", "guide"))) {
|
|
1592
|
+
docSource = resolved.repoUrl ? `${resolved.repoUrl}/tree/v${version}/docs` : "git";
|
|
1593
|
+
docsType = "docs";
|
|
1594
|
+
} else if (existsSync(join(cacheDir, "llms.txt"))) {
|
|
1595
|
+
docSource = resolved.llmsUrl || "llms.txt";
|
|
1596
|
+
docsType = "llms.txt";
|
|
1597
|
+
} else if (existsSync(join(cacheDir, "docs", "README.md"))) docsType = "readme";
|
|
1598
|
+
}
|
|
1599
|
+
const hasGithub = existsSync(join(getCacheDir(packageName, version), "github"));
|
|
1600
|
+
const hasReleases = existsSync(releasesPath);
|
|
1601
|
+
const hasChangelog = pkgDir ? ["CHANGELOG.md", "changelog.md"].find((f) => existsSync(join(pkgDir, f))) || false : false;
|
|
1602
|
+
const relatedSkills = await findRelatedSkills(packageName, baseDir);
|
|
1603
|
+
const shippedDocs = hasShippedDocs(packageName, cwd, version);
|
|
1604
|
+
const pkgFiles = getPkgKeyFiles(packageName, cwd, version);
|
|
1605
|
+
const baseSkillMd = generateSkillMd({
|
|
1606
|
+
name: packageName,
|
|
1607
|
+
version,
|
|
1608
|
+
releasedAt: resolved.releasedAt,
|
|
1609
|
+
description: resolved.description,
|
|
1610
|
+
dependencies: resolved.dependencies,
|
|
1611
|
+
distTags: resolved.distTags,
|
|
1612
|
+
relatedSkills,
|
|
1613
|
+
hasGithub,
|
|
1614
|
+
hasReleases,
|
|
1615
|
+
hasChangelog,
|
|
1616
|
+
docsType,
|
|
1617
|
+
hasShippedDocs: shippedDocs,
|
|
1618
|
+
pkgFiles
|
|
1619
|
+
});
|
|
1620
|
+
writeFileSync(join(skillDir, "SKILL.md"), baseSkillMd);
|
|
1621
|
+
writeLock(baseDir, sanitizeName(packageName), {
|
|
1622
|
+
packageName,
|
|
1623
|
+
version,
|
|
1624
|
+
source: docSource,
|
|
1625
|
+
syncedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
1626
|
+
generator: "skilld"
|
|
1627
|
+
});
|
|
1628
|
+
p.log.success(`Created base skill: ${relative(cwd, skillDir)}`);
|
|
1629
|
+
if (!readConfig().skipLlm && (!config.yes || config.model)) {
|
|
1630
|
+
const { sections, customPrompt, cancelled } = config.model ? {
|
|
1631
|
+
sections: ["best-practices", "api"],
|
|
1632
|
+
customPrompt: void 0,
|
|
1633
|
+
cancelled: false
|
|
1634
|
+
} : await selectSkillSections();
|
|
1635
|
+
if (!cancelled && sections.length > 0) {
|
|
1636
|
+
const model = config.model ?? await selectModel(false);
|
|
1637
|
+
if (model) await enhanceSkillWithLLM({
|
|
1638
|
+
packageName,
|
|
1639
|
+
version,
|
|
1640
|
+
skillDir,
|
|
1641
|
+
model,
|
|
1642
|
+
resolved,
|
|
1643
|
+
relatedSkills,
|
|
1644
|
+
hasGithub,
|
|
1645
|
+
hasReleases,
|
|
1646
|
+
hasChangelog,
|
|
1647
|
+
docsType,
|
|
1648
|
+
hasShippedDocs: shippedDocs,
|
|
1649
|
+
pkgFiles,
|
|
1650
|
+
force: config.force,
|
|
1651
|
+
sections,
|
|
1652
|
+
customPrompt
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
if (!config.global) registerProject(cwd);
|
|
1657
|
+
await ensureGitignore(agent.skillsDir, cwd, config.global);
|
|
1658
|
+
p.outro(`Synced ${packageName} to ${relative(cwd, skillDir)}`);
|
|
48
1659
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
1660
|
+
async function enhanceSkillWithLLM(opts) {
|
|
1661
|
+
const { packageName, version, skillDir, model, resolved, relatedSkills, hasGithub, hasReleases, hasChangelog, docsType, hasShippedDocs: shippedDocs, pkgFiles, force, sections, customPrompt } = opts;
|
|
1662
|
+
const llmSpin = p.spinner();
|
|
1663
|
+
llmSpin.start(`Agent exploring ${packageName}`);
|
|
1664
|
+
const { optimized, wasOptimized } = await optimizeDocs({
|
|
1665
|
+
packageName,
|
|
1666
|
+
skillDir,
|
|
1667
|
+
model,
|
|
1668
|
+
version,
|
|
1669
|
+
hasGithub,
|
|
1670
|
+
hasReleases,
|
|
1671
|
+
hasChangelog,
|
|
1672
|
+
docFiles: listReferenceFiles(skillDir),
|
|
1673
|
+
noCache: force,
|
|
1674
|
+
sections,
|
|
1675
|
+
customPrompt,
|
|
1676
|
+
onProgress: ({ type, chunk }) => {
|
|
1677
|
+
if (type === "reasoning" && chunk.startsWith("[")) llmSpin.message(chunk);
|
|
1678
|
+
else if (type === "text") llmSpin.message(`Writing...`);
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
if (wasOptimized) {
|
|
1682
|
+
llmSpin.stop("Generated best practices");
|
|
1683
|
+
const body = cleanSkillMd(optimized);
|
|
1684
|
+
const skillMd = generateSkillMd({
|
|
1685
|
+
name: packageName,
|
|
1686
|
+
version,
|
|
1687
|
+
releasedAt: resolved.releasedAt,
|
|
1688
|
+
dependencies: resolved.dependencies,
|
|
1689
|
+
distTags: resolved.distTags,
|
|
1690
|
+
body,
|
|
1691
|
+
relatedSkills,
|
|
1692
|
+
hasGithub,
|
|
1693
|
+
hasReleases,
|
|
1694
|
+
hasChangelog,
|
|
1695
|
+
docsType,
|
|
1696
|
+
hasShippedDocs: shippedDocs,
|
|
1697
|
+
pkgFiles
|
|
1698
|
+
});
|
|
1699
|
+
writeFileSync(join(skillDir, "SKILL.md"), skillMd);
|
|
1700
|
+
} else llmSpin.stop("LLM optimization failed");
|
|
1701
|
+
}
|
|
1702
|
+
async function findRelatedSkills(packageName, skillsDir) {
|
|
1703
|
+
const related = [];
|
|
1704
|
+
const npmInfo = await fetchNpmPackage(packageName);
|
|
1705
|
+
if (!npmInfo?.dependencies) return related;
|
|
1706
|
+
const deps = Object.keys(npmInfo.dependencies);
|
|
1707
|
+
if (!existsSync(skillsDir)) return related;
|
|
1708
|
+
const installedSkills = readdirSync(skillsDir);
|
|
1709
|
+
for (const skill of installedSkills) if (deps.some((d) => sanitizeName(d) === skill)) related.push(skill);
|
|
1710
|
+
return related.slice(0, 5);
|
|
1711
|
+
}
|
|
1712
|
+
function cleanSkillMd(content) {
|
|
1713
|
+
let cleaned = content.replace(/^```markdown\n?/m, "").replace(/\n?```$/m, "").trim();
|
|
1714
|
+
const fmMatch = cleaned.match(/^-{3,}\n/);
|
|
1715
|
+
if (fmMatch) {
|
|
1716
|
+
const afterOpen = fmMatch[0].length;
|
|
1717
|
+
const closeMatch = cleaned.slice(afterOpen).match(/\n-{3,}/);
|
|
1718
|
+
if (closeMatch) cleaned = cleaned.slice(afterOpen + closeMatch.index + closeMatch[0].length).trim();
|
|
1719
|
+
else cleaned = cleaned.slice(afterOpen).trim();
|
|
96
1720
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
1721
|
+
return cleaned;
|
|
1722
|
+
}
|
|
1723
|
+
async function uninstallCommand(opts) {
|
|
1724
|
+
let scope = opts.scope;
|
|
1725
|
+
const registeredProjects = getRegisteredProjects();
|
|
1726
|
+
if (!scope) {
|
|
1727
|
+
const allHint = registeredProjects.length > 0 ? `${registeredProjects.length} projects + global + cache` : "global skills + cache";
|
|
1728
|
+
const selected = await p.select({
|
|
1729
|
+
message: "What do you want to uninstall?",
|
|
1730
|
+
options: [{
|
|
1731
|
+
label: "This project",
|
|
1732
|
+
value: "project",
|
|
1733
|
+
hint: "current project only"
|
|
1734
|
+
}, {
|
|
1735
|
+
label: "Everything",
|
|
1736
|
+
value: "all",
|
|
1737
|
+
hint: allHint
|
|
1738
|
+
}]
|
|
1739
|
+
});
|
|
1740
|
+
if (p.isCancel(selected)) {
|
|
1741
|
+
p.cancel("Cancelled");
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
scope = selected;
|
|
103
1745
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
1746
|
+
const toRemove = [];
|
|
1747
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
1748
|
+
const projectsToUnregister = [];
|
|
1749
|
+
const agentFilter = opts.agent ? [opts.agent] : void 0;
|
|
1750
|
+
const addToRemove = (label, path, version) => {
|
|
1751
|
+
if (seenPaths.has(path)) return;
|
|
1752
|
+
seenPaths.add(path);
|
|
1753
|
+
toRemove.push({
|
|
1754
|
+
label,
|
|
1755
|
+
path,
|
|
1756
|
+
version
|
|
1757
|
+
});
|
|
107
1758
|
};
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
1759
|
+
const addSkillsFromLock = (skillsDir, label) => {
|
|
1760
|
+
const trackedNames = [];
|
|
1761
|
+
const lock = readLock(skillsDir);
|
|
1762
|
+
if (lock?.skills) {
|
|
1763
|
+
for (const [skillName, info] of Object.entries(lock.skills)) {
|
|
1764
|
+
trackedNames.push(skillName);
|
|
1765
|
+
const skillDir = join(skillsDir, skillName);
|
|
1766
|
+
if (existsSync(skillDir)) {
|
|
1767
|
+
const version = info.version ? `${info.version.split(".").slice(0, 2).join(".")}.x` : void 0;
|
|
1768
|
+
addToRemove(`${label}: ${skillName}`, skillDir, version);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
const lockPath = join(skillsDir, "skilld-lock.yaml");
|
|
1772
|
+
if (existsSync(lockPath)) addToRemove(`${label}: skilld-lock.yaml`, lockPath);
|
|
1773
|
+
}
|
|
1774
|
+
return trackedNames;
|
|
1775
|
+
};
|
|
1776
|
+
const findUntrackedSkills = (skillsDir, trackedNames) => {
|
|
1777
|
+
if (!existsSync(skillsDir)) return [];
|
|
1778
|
+
const tracked = new Set(trackedNames);
|
|
1779
|
+
return readdirSync(skillsDir).filter((f) => !f.startsWith(".") && f !== "skilld-lock.yaml" && !tracked.has(f));
|
|
1780
|
+
};
|
|
1781
|
+
const untrackedByDir = /* @__PURE__ */ new Map();
|
|
1782
|
+
const processedDirs = /* @__PURE__ */ new Set();
|
|
1783
|
+
const processSkillsDir = (skillsDir, label) => {
|
|
1784
|
+
if (processedDirs.has(skillsDir)) return;
|
|
1785
|
+
processedDirs.add(skillsDir);
|
|
1786
|
+
const untracked = findUntrackedSkills(skillsDir, addSkillsFromLock(skillsDir, label));
|
|
1787
|
+
if (untracked.length > 0) untrackedByDir.set(skillsDir, {
|
|
1788
|
+
label,
|
|
1789
|
+
skills: untracked
|
|
1790
|
+
});
|
|
1791
|
+
};
|
|
1792
|
+
if (scope === "project") {
|
|
1793
|
+
for (const [name, agent] of Object.entries(agents)) {
|
|
1794
|
+
if (agentFilter && !agentFilter.includes(name)) continue;
|
|
1795
|
+
processSkillsDir(join(process.cwd(), agent.skillsDir), "project");
|
|
1796
|
+
}
|
|
1797
|
+
projectsToUnregister.push(process.cwd());
|
|
1798
|
+
}
|
|
1799
|
+
if (scope === "all") {
|
|
1800
|
+
const projectPaths = registeredProjects.length > 0 ? registeredProjects : [process.cwd()];
|
|
1801
|
+
if (registeredProjects.length > 0) {
|
|
1802
|
+
p.log.info("Projects to uninstall from:");
|
|
1803
|
+
for (const proj of projectPaths) p.log.message(` ${proj}`);
|
|
1804
|
+
}
|
|
1805
|
+
for (const projectPath of projectPaths) {
|
|
1806
|
+
if (!existsSync(projectPath)) continue;
|
|
1807
|
+
const shortPath = projectPath.replace(process.env.HOME || "", "~");
|
|
1808
|
+
for (const [name, agent] of Object.entries(agents)) {
|
|
1809
|
+
if (agentFilter && !agentFilter.includes(name)) continue;
|
|
1810
|
+
processSkillsDir(join(projectPath, agent.skillsDir), shortPath);
|
|
1811
|
+
}
|
|
1812
|
+
projectsToUnregister.push(projectPath);
|
|
1813
|
+
}
|
|
1814
|
+
for (const [name, agent] of Object.entries(agents)) {
|
|
1815
|
+
if (agentFilter && !agentFilter.includes(name)) continue;
|
|
1816
|
+
if (!agent.globalSkillsDir) continue;
|
|
1817
|
+
processSkillsDir(agent.globalSkillsDir, "user");
|
|
1818
|
+
}
|
|
1819
|
+
if (existsSync(CACHE_DIR)) addToRemove("~/.skilld cache", CACHE_DIR);
|
|
1820
|
+
}
|
|
1821
|
+
if (untrackedByDir.size > 0) {
|
|
1822
|
+
const groupedUntracked = /* @__PURE__ */ new Map();
|
|
1823
|
+
for (const [_dir, { label, skills }] of untrackedByDir) {
|
|
1824
|
+
if (!groupedUntracked.has(label)) groupedUntracked.set(label, /* @__PURE__ */ new Set());
|
|
1825
|
+
for (const s of skills) groupedUntracked.get(label).add(s);
|
|
1826
|
+
}
|
|
1827
|
+
const totalUntracked = [...groupedUntracked.values()].reduce((sum, s) => sum + s.size, 0);
|
|
1828
|
+
p.log.warn(`${totalUntracked} untracked skill(s) will remain (not managed by skilld):`);
|
|
1829
|
+
for (const [label, skills] of groupedUntracked) p.log.message(` ${label}: ${[...skills].join(", ")}`);
|
|
1830
|
+
}
|
|
1831
|
+
if (toRemove.length === 0) {
|
|
1832
|
+
p.log.info("Nothing to uninstall");
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1836
|
+
for (const item of toRemove) {
|
|
1837
|
+
const [prefix, name] = item.label.includes(": ") ? item.label.split(": ", 2) : ["other", item.label];
|
|
1838
|
+
if (!groups.has(prefix)) groups.set(prefix, []);
|
|
1839
|
+
groups.get(prefix).push({
|
|
1840
|
+
name,
|
|
1841
|
+
version: item.version
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
const formatGroup = (items) => items.map((i) => i.version ? `${i.name}@${i.version}` : i.name).join(", ");
|
|
1845
|
+
p.log.info(`Will remove ${toRemove.length} items:`);
|
|
1846
|
+
for (const [prefix, items] of groups) p.log.message(` ${prefix}: ${formatGroup(items)}`);
|
|
1847
|
+
if (!opts.yes) {
|
|
1848
|
+
const confirmed = await p.confirm({ message: "Proceed with uninstall?" });
|
|
1849
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
1850
|
+
p.cancel("Cancelled");
|
|
1851
|
+
return;
|
|
124
1852
|
}
|
|
125
|
-
} catch {
|
|
126
|
-
return null;
|
|
127
1853
|
}
|
|
1854
|
+
for (const item of toRemove) rmSync(item.path, {
|
|
1855
|
+
recursive: true,
|
|
1856
|
+
force: true
|
|
1857
|
+
});
|
|
1858
|
+
for (const [prefix, items] of groups) p.log.success(`Removed ${prefix}: ${formatGroup(items)}`);
|
|
1859
|
+
if (scope !== "all") for (const proj of projectsToUnregister) unregisterProject(proj);
|
|
1860
|
+
p.outro("skilld uninstalled");
|
|
128
1861
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
sonnet: "claude-sonnet-4-20250514",
|
|
132
|
-
opus: "claude-opus-4-20250514"
|
|
133
|
-
};
|
|
134
|
-
async function tryAnthropicSDK(content, packageName, model) {
|
|
1862
|
+
function hasGhCli() {
|
|
1863
|
+
if (process.env.SKILLD_NO_GH) return false;
|
|
135
1864
|
try {
|
|
136
|
-
|
|
137
|
-
return
|
|
138
|
-
model: MODEL_MAP[model],
|
|
139
|
-
max_tokens: 8192,
|
|
140
|
-
messages: [{
|
|
141
|
-
role: "user",
|
|
142
|
-
content: `${OPTIMIZE_PROMPT}\n\nPackage: ${packageName}\n\n${content}`
|
|
143
|
-
}]
|
|
144
|
-
})).content.find((b) => b.type === "text")?.text || null;
|
|
1865
|
+
execSync("gh --version", { stdio: "ignore" });
|
|
1866
|
+
return true;
|
|
145
1867
|
} catch {
|
|
146
|
-
return
|
|
1868
|
+
return false;
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
async function runWizard() {
|
|
1872
|
+
p.note("Skilld gives your AI agent skill knowledge on your NPM\ndependencies gathered from versioned docs, source code\nand GitHub issues.", "Welcome to skilld");
|
|
1873
|
+
const ghInstalled = hasGhCli();
|
|
1874
|
+
if (ghInstalled) p.log.success("GitHub CLI detected — will use it to pull issues and discussions.");
|
|
1875
|
+
else p.log.warn("GitHub CLI not found. Install it to enable issues/discussions:\n \x1B[36mhttps://cli.github.com\x1B[0m");
|
|
1876
|
+
const selected = await p.multiselect({
|
|
1877
|
+
message: "Which features would you like to enable?",
|
|
1878
|
+
options: [
|
|
1879
|
+
{
|
|
1880
|
+
label: "Semantic + token search",
|
|
1881
|
+
value: "search",
|
|
1882
|
+
hint: "local query engine to cut token costs and speed up grep"
|
|
1883
|
+
},
|
|
1884
|
+
{
|
|
1885
|
+
label: "Release notes",
|
|
1886
|
+
value: "releases",
|
|
1887
|
+
hint: "track changelogs for installed packages"
|
|
1888
|
+
},
|
|
1889
|
+
{
|
|
1890
|
+
label: "GitHub issues",
|
|
1891
|
+
value: "issues",
|
|
1892
|
+
hint: "surface common problems and solutions",
|
|
1893
|
+
disabled: !ghInstalled
|
|
1894
|
+
},
|
|
1895
|
+
{
|
|
1896
|
+
label: "GitHub discussions",
|
|
1897
|
+
value: "discussions",
|
|
1898
|
+
hint: "include Q&A and community knowledge",
|
|
1899
|
+
disabled: !ghInstalled
|
|
1900
|
+
}
|
|
1901
|
+
],
|
|
1902
|
+
initialValues: [...Object.entries(defaultFeatures).filter(([, v]) => v).map(([k]) => k), ...ghInstalled ? ["issues", "discussions"] : []],
|
|
1903
|
+
required: false
|
|
1904
|
+
});
|
|
1905
|
+
if (p.isCancel(selected)) {
|
|
1906
|
+
p.cancel("Setup cancelled");
|
|
1907
|
+
process.exit(0);
|
|
1908
|
+
}
|
|
1909
|
+
const features = {
|
|
1910
|
+
search: selected.includes("search"),
|
|
1911
|
+
issues: selected.includes("issues"),
|
|
1912
|
+
discussions: selected.includes("discussions"),
|
|
1913
|
+
releases: selected.includes("releases")
|
|
1914
|
+
};
|
|
1915
|
+
const allModels = process.env.SKILLD_NO_AGENTS ? [] : await getAvailableModels();
|
|
1916
|
+
let modelId;
|
|
1917
|
+
if (allModels.length > 0) {
|
|
1918
|
+
p.note("Skills work without an LLM, but one can rewrite your\nSKILL.md files with best practices and better structure.\n\x1B[90mThis is separate from the agent where skills are installed —\nthe target agent is auto-detected from your project files.\x1B[0m", "Optional: LLM optimization");
|
|
1919
|
+
const modelChoice = await p.select({
|
|
1920
|
+
message: "Model for generating SKILL.md",
|
|
1921
|
+
options: [{
|
|
1922
|
+
label: "Skip",
|
|
1923
|
+
value: "",
|
|
1924
|
+
hint: "use raw docs, no LLM needed"
|
|
1925
|
+
}, ...allModels.map((m) => ({
|
|
1926
|
+
label: m.recommended ? `${m.name} (Recommended)` : m.name,
|
|
1927
|
+
value: m.id,
|
|
1928
|
+
hint: `${m.agentName} · ${m.hint}`
|
|
1929
|
+
}))]
|
|
1930
|
+
});
|
|
1931
|
+
if (p.isCancel(modelChoice)) {
|
|
1932
|
+
p.cancel("Setup cancelled");
|
|
1933
|
+
process.exit(0);
|
|
1934
|
+
}
|
|
1935
|
+
modelId = modelChoice || void 0;
|
|
1936
|
+
} else {
|
|
1937
|
+
p.log.warn("No supported LLM CLIs detected (claude, gemini, codex).\n Skills will still work, but won't be LLM-optimized.");
|
|
1938
|
+
const proceed = await p.confirm({
|
|
1939
|
+
message: "Continue without LLM optimization?",
|
|
1940
|
+
initialValue: true
|
|
1941
|
+
});
|
|
1942
|
+
if (p.isCancel(proceed) || !proceed) {
|
|
1943
|
+
p.cancel("Setup cancelled");
|
|
1944
|
+
process.exit(0);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
updateConfig({
|
|
1948
|
+
features,
|
|
1949
|
+
...modelId ? { model: modelId } : { skipLlm: true }
|
|
1950
|
+
});
|
|
1951
|
+
p.outro("Thanks, you're all set! Change config anytime with `skilld config`.");
|
|
1952
|
+
}
|
|
1953
|
+
const _emit = process.emit;
|
|
1954
|
+
process.emit = (event, ...args) => event === "warning" && args[0]?.name === "ExperimentalWarning" && args[0]?.message?.includes("SQLite") ? false : _emit.apply(process, [event, ...args]);
|
|
1955
|
+
const { version } = createRequire(import.meta.url)("../package.json");
|
|
1956
|
+
function getRepoHint(name, cwd) {
|
|
1957
|
+
const pkgJsonPath = join(cwd, "node_modules", name, "package.json");
|
|
1958
|
+
if (!existsSync(pkgJsonPath)) return void 0;
|
|
1959
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
1960
|
+
const url = typeof pkg.repository === "string" ? pkg.repository : pkg.repository?.url;
|
|
1961
|
+
if (!url) return void 0;
|
|
1962
|
+
return url.replace(/^git\+/, "").replace(/\.git$/, "").replace(/^git:\/\//, "https://").replace(/^ssh:\/\/git@github\.com/, "https://github.com").replace(/^https?:\/\/(www\.)?github\.com\//, "");
|
|
1963
|
+
}
|
|
1964
|
+
function formatStatus(synced, outdated) {
|
|
1965
|
+
const parts = [];
|
|
1966
|
+
if (synced > 0) parts.push(`\x1B[32m${synced} synced\x1B[0m`);
|
|
1967
|
+
if (outdated > 0) parts.push(`\x1B[33m${outdated} outdated\x1B[0m`);
|
|
1968
|
+
return `Skills: ${parts.join(" · ")}`;
|
|
1969
|
+
}
|
|
1970
|
+
function relativeTime(date) {
|
|
1971
|
+
const diff = Date.now() - date.getTime();
|
|
1972
|
+
const mins = Math.floor(diff / 6e4);
|
|
1973
|
+
const hours = Math.floor(diff / 36e5);
|
|
1974
|
+
const days = Math.floor(diff / 864e5);
|
|
1975
|
+
if (mins < 1) return "just now";
|
|
1976
|
+
if (mins < 60) return `${mins}m ago`;
|
|
1977
|
+
if (hours < 24) return `${hours}h ago`;
|
|
1978
|
+
return `${days}d ago`;
|
|
1979
|
+
}
|
|
1980
|
+
function getLastSynced(state) {
|
|
1981
|
+
let latest = null;
|
|
1982
|
+
for (const skill of state.skills) if (skill.info?.syncedAt) {
|
|
1983
|
+
const d = new Date(skill.info.syncedAt);
|
|
1984
|
+
if (!latest || d > latest) latest = d;
|
|
1985
|
+
}
|
|
1986
|
+
return latest ? relativeTime(latest) : null;
|
|
1987
|
+
}
|
|
1988
|
+
const NOISE_CHARS = "⣿⡿⣷⣾⣽⣻⢿⡷⣯⣟⡾⣵⣳⢾⡽⣞⡷⣝⢯";
|
|
1989
|
+
function djb2(s) {
|
|
1990
|
+
let h = 5381;
|
|
1991
|
+
for (let i = 0; i < s.length; i++) h = (h << 5) + h + s.charCodeAt(i) >>> 0;
|
|
1992
|
+
return h;
|
|
1993
|
+
}
|
|
1994
|
+
function hueToChannel(p, q, t) {
|
|
1995
|
+
const t1 = t < 0 ? t + 1 : t > 1 ? t - 1 : t;
|
|
1996
|
+
if (t1 < 1 / 6) return p + (q - p) * 6 * t1;
|
|
1997
|
+
if (t1 < 1 / 2) return q;
|
|
1998
|
+
if (t1 < 2 / 3) return p + (q - p) * (2 / 3 - t1) * 6;
|
|
1999
|
+
return p;
|
|
2000
|
+
}
|
|
2001
|
+
function hsl(h, s, l) {
|
|
2002
|
+
const q = l < .5 ? l * (1 + s) : l + s - l * s;
|
|
2003
|
+
const p = 2 * l - q;
|
|
2004
|
+
return [
|
|
2005
|
+
Math.round(hueToChannel(p, q, h + 1 / 3) * 255),
|
|
2006
|
+
Math.round(hueToChannel(p, q, h) * 255),
|
|
2007
|
+
Math.round(hueToChannel(p, q, h - 1 / 3) * 255)
|
|
2008
|
+
];
|
|
2009
|
+
}
|
|
2010
|
+
const BRAND_HUE = djb2(process.cwd()) % 360 / 360;
|
|
2011
|
+
function noiseChar(brightness, density = 0) {
|
|
2012
|
+
if (brightness < .08) return " ";
|
|
2013
|
+
const b = Math.min(brightness, 1);
|
|
2014
|
+
const ch = Math.random() < density ? "⣿" : NOISE_CHARS[Math.floor(Math.random() * 19)];
|
|
2015
|
+
const [r, g, bl] = hsl(BRAND_HUE, .4 + b * .15, .35 + b * .25);
|
|
2016
|
+
return `\x1B[38;2;${r};${g};${bl}m${ch}`;
|
|
2017
|
+
}
|
|
2018
|
+
function noiseLine(len, brightnessFn, density = 0) {
|
|
2019
|
+
let s = "";
|
|
2020
|
+
for (let i = 0; i < len; i++) s += noiseChar(brightnessFn(i), density);
|
|
2021
|
+
return `${s}\x1B[0m`;
|
|
2022
|
+
}
|
|
2023
|
+
function brandFrame(t, floor = 0, density = 0) {
|
|
2024
|
+
const cx = 5;
|
|
2025
|
+
const cy = 1;
|
|
2026
|
+
const brightness = (x, y) => {
|
|
2027
|
+
const d = Math.sqrt((x - cx) ** 2 + ((y - cy) * 3) ** 2);
|
|
2028
|
+
let val = 0;
|
|
2029
|
+
for (let ring = 0; ring < 3; ring++) {
|
|
2030
|
+
const rt = t - ring * .5;
|
|
2031
|
+
if (rt <= 0) continue;
|
|
2032
|
+
const front = rt * 4;
|
|
2033
|
+
const proximity = Math.abs(d - front);
|
|
2034
|
+
val += Math.exp(-proximity * proximity * .8) * Math.exp(-rt * .4);
|
|
2035
|
+
}
|
|
2036
|
+
const base = Math.max(0, (t - 1.5) * .3) * (Math.random() * .3 + .1);
|
|
2037
|
+
return Math.min(1, Math.max(floor, val + base));
|
|
2038
|
+
};
|
|
2039
|
+
return [
|
|
2040
|
+
noiseLine(10, (x) => brightness(x, 0), density),
|
|
2041
|
+
`${noiseLine(2, (x) => brightness(x, 1), density)} %NAME% ${noiseLine(2, (x) => brightness(x + 8, 1), density)} %VER%`,
|
|
2042
|
+
noiseLine(10, (x) => brightness(x, 2), density)
|
|
2043
|
+
].join("\n");
|
|
2044
|
+
}
|
|
2045
|
+
async function brandLoader(work, minMs = 1500) {
|
|
2046
|
+
if (process.env.SKILLD_EFFECT === "none") return work();
|
|
2047
|
+
const logUpdate = (await import("log-update")).default;
|
|
2048
|
+
const name = "\x1B[1m\x1B[38;2;255;255;255mskilld\x1B[0m";
|
|
2049
|
+
const ver = `\x1B[2mv${version}\x1B[0m`;
|
|
2050
|
+
const status = "\x1B[2mSetting up your environment\x1B[0m";
|
|
2051
|
+
const start = Date.now();
|
|
2052
|
+
const sub = (raw) => raw.replace("%NAME%", name).replace("%VER%", ver);
|
|
2053
|
+
let done = false;
|
|
2054
|
+
const result = Promise.all([work(), new Promise((r) => setTimeout(r, minMs))]).then(([v]) => {
|
|
2055
|
+
done = true;
|
|
2056
|
+
return v;
|
|
2057
|
+
});
|
|
2058
|
+
while (!done) {
|
|
2059
|
+
logUpdate(`\n ${sub(brandFrame((Date.now() - start) / 1e3))}\n\n ${status}`);
|
|
2060
|
+
await new Promise((r) => setTimeout(r, 60));
|
|
2061
|
+
}
|
|
2062
|
+
const outroMs = 500;
|
|
2063
|
+
const outroStart = Date.now();
|
|
2064
|
+
const tFinal = (outroStart - start) / 1e3;
|
|
2065
|
+
while (Date.now() - outroStart < outroMs) {
|
|
2066
|
+
const p = (Date.now() - outroStart) / outroMs;
|
|
2067
|
+
const eased = p * p;
|
|
2068
|
+
logUpdate(`\n ${sub(brandFrame(tFinal + p * .5, eased * .9, eased))}\n`);
|
|
2069
|
+
await new Promise((r) => setTimeout(r, 40));
|
|
147
2070
|
}
|
|
2071
|
+
logUpdate(`\n ${sub(brandFrame(tFinal + 1, .9, 1))}\n`);
|
|
2072
|
+
logUpdate.done();
|
|
2073
|
+
return result;
|
|
148
2074
|
}
|
|
149
|
-
|
|
2075
|
+
function introLine({ state, generators, modelId }) {
|
|
2076
|
+
const name = "\x1B[1m\x1B[35mskilld\x1B[0m";
|
|
2077
|
+
const ver = `\x1B[90mv${version}\x1B[0m`;
|
|
2078
|
+
const lastSynced = getLastSynced(state);
|
|
2079
|
+
const synced = lastSynced ? ` · \x1B[90msynced ${lastSynced}\x1B[0m` : "";
|
|
2080
|
+
const modelStr = modelId ? ` · ${getModelName(modelId)}` : "";
|
|
2081
|
+
const genStr = generators?.length ? generators.map((g) => `${g.name} v${g.version}`).join(", ") : "";
|
|
2082
|
+
return `${name} ${ver}${synced}${genStr ? `\n\x1B[90m↳ ${genStr}${modelStr}\x1B[0m` : ""}`;
|
|
2083
|
+
}
|
|
2084
|
+
function getInstalledGenerators() {
|
|
2085
|
+
return detectInstalledAgents().filter((id) => agents[id].cli).map((id) => {
|
|
2086
|
+
const version = getAgentVersion(id);
|
|
2087
|
+
return version ? {
|
|
2088
|
+
name: agents[id].displayName,
|
|
2089
|
+
version
|
|
2090
|
+
} : null;
|
|
2091
|
+
}).filter((a) => a !== null);
|
|
2092
|
+
}
|
|
2093
|
+
async function prepareSync(cwd, agentFlag) {
|
|
2094
|
+
const agent = resolveAgent(agentFlag);
|
|
2095
|
+
if (!agent) return;
|
|
2096
|
+
const state = await getProjectState(cwd);
|
|
2097
|
+
if (state.outdated.length === 0) {
|
|
2098
|
+
p.log.success("Skills up to date");
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
await syncCommand(state, {
|
|
2102
|
+
packages: state.outdated.map((s) => s.packageName || s.name),
|
|
2103
|
+
global: false,
|
|
2104
|
+
agent,
|
|
2105
|
+
yes: true
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
function resolveAgent(agentFlag) {
|
|
2109
|
+
return agentFlag ?? detectTargetAgent() ?? readConfig().agent ?? null;
|
|
2110
|
+
}
|
|
2111
|
+
const sharedArgs = {
|
|
2112
|
+
global: {
|
|
2113
|
+
type: "boolean",
|
|
2114
|
+
alias: "g",
|
|
2115
|
+
description: "Install globally to ~/.claude/skills",
|
|
2116
|
+
default: false
|
|
2117
|
+
},
|
|
2118
|
+
agent: {
|
|
2119
|
+
type: "string",
|
|
2120
|
+
alias: "a",
|
|
2121
|
+
description: "Agent where skills are installed (claude-code, cursor, windsurf, etc.)"
|
|
2122
|
+
},
|
|
2123
|
+
yes: {
|
|
2124
|
+
type: "boolean",
|
|
2125
|
+
alias: "y",
|
|
2126
|
+
description: "Skip prompts, use defaults",
|
|
2127
|
+
default: false
|
|
2128
|
+
},
|
|
2129
|
+
force: {
|
|
2130
|
+
type: "boolean",
|
|
2131
|
+
alias: "f",
|
|
2132
|
+
description: "Ignore all caches, re-fetch docs and regenerate",
|
|
2133
|
+
default: false
|
|
2134
|
+
}
|
|
2135
|
+
};
|
|
2136
|
+
const SUBCOMMAND_NAMES = [
|
|
2137
|
+
"add",
|
|
2138
|
+
"update",
|
|
2139
|
+
"status",
|
|
2140
|
+
"config",
|
|
2141
|
+
"remove",
|
|
2142
|
+
"install",
|
|
2143
|
+
"uninstall",
|
|
2144
|
+
"search"
|
|
2145
|
+
];
|
|
2146
|
+
const addCommand = defineCommand({
|
|
150
2147
|
meta: {
|
|
151
|
-
name: "
|
|
152
|
-
description: "
|
|
2148
|
+
name: "add",
|
|
2149
|
+
description: "Add skills for package(s)"
|
|
153
2150
|
},
|
|
154
2151
|
args: {
|
|
155
|
-
|
|
2152
|
+
package: {
|
|
156
2153
|
type: "positional",
|
|
157
|
-
description: "
|
|
158
|
-
required:
|
|
159
|
-
},
|
|
160
|
-
output: {
|
|
161
|
-
type: "string",
|
|
162
|
-
alias: "o",
|
|
163
|
-
description: "Output directory (legacy mode)"
|
|
2154
|
+
description: "Package(s) to sync, comma-separated (e.g., vue,nuxt,pinia)",
|
|
2155
|
+
required: true
|
|
164
2156
|
},
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
2157
|
+
...sharedArgs
|
|
2158
|
+
},
|
|
2159
|
+
async run({ args }) {
|
|
2160
|
+
const cwd = process.cwd();
|
|
2161
|
+
const agent = resolveAgent(args.agent);
|
|
2162
|
+
if (!agent) {
|
|
2163
|
+
p.log.warn("Could not detect agent. Use --agent <name>");
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
const state = await getProjectState(cwd);
|
|
2167
|
+
p.intro(introLine({ state }));
|
|
2168
|
+
return syncCommand(state, {
|
|
2169
|
+
packages: args.package.split(",").map((s) => s.trim()).filter(Boolean),
|
|
2170
|
+
global: args.global,
|
|
2171
|
+
agent,
|
|
2172
|
+
yes: args.yes,
|
|
2173
|
+
force: args.force
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
const updateSubCommand = defineCommand({
|
|
2178
|
+
meta: {
|
|
2179
|
+
name: "update",
|
|
2180
|
+
description: "Update outdated skills"
|
|
2181
|
+
},
|
|
2182
|
+
args: {
|
|
2183
|
+
package: {
|
|
2184
|
+
type: "positional",
|
|
2185
|
+
description: "Package(s) to update, comma-separated. Without args, syncs all outdated.",
|
|
2186
|
+
required: false
|
|
180
2187
|
},
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
2188
|
+
...sharedArgs
|
|
2189
|
+
},
|
|
2190
|
+
async run({ args }) {
|
|
2191
|
+
const cwd = process.cwd();
|
|
2192
|
+
const agent = resolveAgent(args.agent);
|
|
2193
|
+
if (!agent) {
|
|
2194
|
+
p.log.warn("Could not detect agent. Use --agent <name>");
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
const state = await getProjectState(cwd);
|
|
2198
|
+
const generators = getInstalledGenerators();
|
|
2199
|
+
const config = readConfig();
|
|
2200
|
+
p.intro(introLine({
|
|
2201
|
+
state,
|
|
2202
|
+
generators,
|
|
2203
|
+
modelId: config.model
|
|
2204
|
+
}));
|
|
2205
|
+
if (args.package) return syncCommand(state, {
|
|
2206
|
+
packages: args.package.split(",").map((s) => s.trim()).filter(Boolean),
|
|
2207
|
+
global: args.global,
|
|
2208
|
+
agent,
|
|
2209
|
+
yes: args.yes,
|
|
2210
|
+
force: args.force
|
|
2211
|
+
});
|
|
2212
|
+
if (state.outdated.length === 0) {
|
|
2213
|
+
p.log.success("All skills up to date");
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
return syncCommand(state, {
|
|
2217
|
+
packages: state.outdated.map((s) => s.packageName || s.name),
|
|
2218
|
+
global: args.global,
|
|
2219
|
+
agent,
|
|
2220
|
+
yes: args.yes,
|
|
2221
|
+
force: args.force
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2224
|
+
});
|
|
2225
|
+
const statusSubCommand = defineCommand({
|
|
2226
|
+
meta: {
|
|
2227
|
+
name: "status",
|
|
2228
|
+
description: "Show skill status"
|
|
2229
|
+
},
|
|
2230
|
+
args: { global: sharedArgs.global },
|
|
2231
|
+
run({ args }) {
|
|
2232
|
+
return statusCommand({ global: args.global });
|
|
2233
|
+
}
|
|
2234
|
+
});
|
|
2235
|
+
const configSubCommand = defineCommand({
|
|
2236
|
+
meta: {
|
|
2237
|
+
name: "config",
|
|
2238
|
+
description: "Edit settings"
|
|
2239
|
+
},
|
|
2240
|
+
args: {},
|
|
2241
|
+
async run() {
|
|
2242
|
+
const state = await getProjectState(process.cwd());
|
|
2243
|
+
const generators = getInstalledGenerators();
|
|
2244
|
+
const config = readConfig();
|
|
2245
|
+
p.intro(introLine({
|
|
2246
|
+
state,
|
|
2247
|
+
generators,
|
|
2248
|
+
modelId: config.model
|
|
2249
|
+
}));
|
|
2250
|
+
return configCommand();
|
|
2251
|
+
}
|
|
2252
|
+
});
|
|
2253
|
+
const removeSubCommand = defineCommand({
|
|
2254
|
+
meta: {
|
|
2255
|
+
name: "remove",
|
|
2256
|
+
description: "Remove installed skills"
|
|
2257
|
+
},
|
|
2258
|
+
args: { ...sharedArgs },
|
|
2259
|
+
async run({ args }) {
|
|
2260
|
+
const cwd = process.cwd();
|
|
2261
|
+
const agent = resolveAgent(args.agent);
|
|
2262
|
+
if (!agent) {
|
|
2263
|
+
p.log.warn("Could not detect agent. Use --agent <name>");
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
const state = await getProjectState(cwd);
|
|
2267
|
+
const generators = getInstalledGenerators();
|
|
2268
|
+
const config = readConfig();
|
|
2269
|
+
const scope = args.global ? "global" : "project";
|
|
2270
|
+
const intro = {
|
|
2271
|
+
state,
|
|
2272
|
+
generators,
|
|
2273
|
+
modelId: config.model
|
|
2274
|
+
};
|
|
2275
|
+
p.intro(`${introLine(intro)} · remove (${scope})`);
|
|
2276
|
+
return removeCommand(state, {
|
|
2277
|
+
global: args.global,
|
|
2278
|
+
agent,
|
|
2279
|
+
yes: args.yes
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2282
|
+
});
|
|
2283
|
+
const installSubCommand = defineCommand({
|
|
2284
|
+
meta: {
|
|
2285
|
+
name: "install",
|
|
2286
|
+
description: "Restore references from lockfile"
|
|
2287
|
+
},
|
|
2288
|
+
args: {
|
|
2289
|
+
global: sharedArgs.global,
|
|
2290
|
+
agent: sharedArgs.agent
|
|
2291
|
+
},
|
|
2292
|
+
async run({ args }) {
|
|
2293
|
+
const agent = resolveAgent(args.agent);
|
|
2294
|
+
if (!agent) {
|
|
2295
|
+
p.log.warn("Could not detect agent. Use --agent <name>");
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
p.intro(`\x1B[1m\x1B[35mskilld\x1B[0m install`);
|
|
2299
|
+
return installCommand({
|
|
2300
|
+
global: args.global,
|
|
2301
|
+
agent
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
});
|
|
2305
|
+
const uninstallSubCommand = defineCommand({
|
|
2306
|
+
meta: {
|
|
2307
|
+
name: "uninstall",
|
|
2308
|
+
description: "Remove skilld data"
|
|
2309
|
+
},
|
|
2310
|
+
args: { ...sharedArgs },
|
|
2311
|
+
async run({ args }) {
|
|
2312
|
+
p.intro(`\x1B[1m\x1B[35mskilld\x1B[0m uninstall`);
|
|
2313
|
+
return uninstallCommand({
|
|
2314
|
+
scope: args.global ? "all" : void 0,
|
|
2315
|
+
agent: args.agent,
|
|
2316
|
+
yes: args.yes
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
});
|
|
2320
|
+
const searchSubCommand = defineCommand({
|
|
2321
|
+
meta: {
|
|
2322
|
+
name: "search",
|
|
2323
|
+
description: "Search indexed docs"
|
|
2324
|
+
},
|
|
2325
|
+
args: {
|
|
2326
|
+
query: {
|
|
2327
|
+
type: "positional",
|
|
2328
|
+
description: "Search query (e.g., \"useFetch options\")",
|
|
2329
|
+
required: true
|
|
185
2330
|
},
|
|
186
|
-
|
|
2331
|
+
package: {
|
|
187
2332
|
type: "string",
|
|
188
|
-
alias: "
|
|
189
|
-
description: "
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
2333
|
+
alias: "p",
|
|
2334
|
+
description: "Filter by package name"
|
|
2335
|
+
}
|
|
2336
|
+
},
|
|
2337
|
+
async run({ args }) {
|
|
2338
|
+
return searchCommand(args.query, args.package || void 0);
|
|
2339
|
+
}
|
|
2340
|
+
});
|
|
2341
|
+
runMain(defineCommand({
|
|
2342
|
+
meta: {
|
|
2343
|
+
name: "skilld",
|
|
2344
|
+
description: "Sync package documentation for agentic use"
|
|
2345
|
+
},
|
|
2346
|
+
args: {
|
|
2347
|
+
prepare: {
|
|
193
2348
|
type: "boolean",
|
|
194
|
-
|
|
195
|
-
description: "Install skills globally",
|
|
2349
|
+
description: "Non-interactive sync for pnpm prepare hook (outdated only, no LLM, always exits 0)",
|
|
196
2350
|
default: false
|
|
197
2351
|
},
|
|
198
|
-
|
|
199
|
-
type: "string",
|
|
200
|
-
alias: "a",
|
|
201
|
-
description: "Target specific agent (claude-code, cursor, windsurf, etc.)"
|
|
202
|
-
},
|
|
203
|
-
force: {
|
|
2352
|
+
background: {
|
|
204
2353
|
type: "boolean",
|
|
205
|
-
alias: "
|
|
206
|
-
description: "
|
|
2354
|
+
alias: "b",
|
|
2355
|
+
description: "Run --prepare in background (detached process)",
|
|
207
2356
|
default: false
|
|
208
2357
|
},
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
2358
|
+
agent: sharedArgs.agent
|
|
2359
|
+
},
|
|
2360
|
+
subCommands: {
|
|
2361
|
+
add: addCommand,
|
|
2362
|
+
update: updateSubCommand,
|
|
2363
|
+
status: statusSubCommand,
|
|
2364
|
+
config: configSubCommand,
|
|
2365
|
+
remove: removeSubCommand,
|
|
2366
|
+
install: installSubCommand,
|
|
2367
|
+
uninstall: uninstallSubCommand,
|
|
2368
|
+
search: searchSubCommand
|
|
220
2369
|
},
|
|
221
2370
|
async run({ args }) {
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
global: args.global,
|
|
241
|
-
agents: targetAgents,
|
|
242
|
-
optimize: args.optimize,
|
|
243
|
-
currentAgent
|
|
244
|
-
});
|
|
2371
|
+
const firstArg = process.argv[2];
|
|
2372
|
+
if (firstArg && !firstArg.startsWith("-") && SUBCOMMAND_NAMES.includes(firstArg)) return;
|
|
2373
|
+
const cwd = process.cwd();
|
|
2374
|
+
if (args.prepare) {
|
|
2375
|
+
if (args.background) {
|
|
2376
|
+
const { spawn } = await import("node:child_process");
|
|
2377
|
+
spawn(process.execPath, [
|
|
2378
|
+
process.argv[1],
|
|
2379
|
+
"--prepare",
|
|
2380
|
+
...args.agent ? ["--agent", args.agent] : []
|
|
2381
|
+
], {
|
|
2382
|
+
cwd,
|
|
2383
|
+
detached: true,
|
|
2384
|
+
stdio: "ignore"
|
|
2385
|
+
}).unref();
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
await prepareSync(cwd, args.agent).catch(() => {});
|
|
245
2389
|
return;
|
|
246
2390
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
2391
|
+
const currentAgent = resolveAgent(args.agent);
|
|
2392
|
+
if (!currentAgent) {
|
|
2393
|
+
p.log.warn("Could not detect agent. Use --agent <name> or `skilld config`");
|
|
2394
|
+
p.log.info(`Supported: ${Object.keys(agents).join(", ")}`);
|
|
251
2395
|
return;
|
|
252
2396
|
}
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
2397
|
+
const { state, selfUpdate } = await brandLoader(async () => {
|
|
2398
|
+
const config = readConfig();
|
|
2399
|
+
const state = await getProjectState(cwd);
|
|
2400
|
+
let selfUpdate = null;
|
|
2401
|
+
const tasks = [];
|
|
2402
|
+
if (!(process.env.npm_command === "exec")) tasks.push(fetchNpmRegistryMeta("skilld", version).then((meta) => {
|
|
2403
|
+
const latestTag = meta.distTags?.latest;
|
|
2404
|
+
if (latestTag && latestTag.version !== version) selfUpdate = {
|
|
2405
|
+
latest: latestTag.version,
|
|
2406
|
+
releasedAt: latestTag.releasedAt
|
|
2407
|
+
};
|
|
2408
|
+
}).catch(() => {}));
|
|
2409
|
+
if (state.unmatched.length > 0) {
|
|
2410
|
+
const limit = pLimit(5);
|
|
2411
|
+
tasks.push(Promise.all(state.unmatched.map((skill) => limit(async () => {
|
|
2412
|
+
const pkgName = skill.info?.packageName || skill.name;
|
|
2413
|
+
const latest = await fetchLatestVersion(pkgName);
|
|
2414
|
+
if (latest && isOutdated(skill, latest)) state.outdated.push({
|
|
2415
|
+
...skill,
|
|
2416
|
+
packageName: pkgName,
|
|
2417
|
+
latestVersion: latest
|
|
2418
|
+
});
|
|
2419
|
+
else if (latest) state.synced.push({
|
|
2420
|
+
...skill,
|
|
2421
|
+
packageName: pkgName,
|
|
2422
|
+
latestVersion: latest
|
|
2423
|
+
});
|
|
2424
|
+
}))).then(() => {}));
|
|
259
2425
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
|
|
2426
|
+
await Promise.all(tasks);
|
|
2427
|
+
return {
|
|
2428
|
+
config,
|
|
2429
|
+
state,
|
|
2430
|
+
selfUpdate
|
|
2431
|
+
};
|
|
2432
|
+
});
|
|
2433
|
+
if (selfUpdate) {
|
|
2434
|
+
const released = selfUpdate.releasedAt ? `\x1B[90m · ${relativeTime(new Date(selfUpdate.releasedAt))}\x1B[0m` : "";
|
|
2435
|
+
const cmd = `npx nypm add${realpathSync(process.argv[1]).startsWith(resolve(cwd, "node_modules")) ? "" : " -g"} skilld@${selfUpdate.latest}`;
|
|
2436
|
+
p.note(`\x1B[90m${version}\x1B[0m → \x1B[1m\x1B[32m${selfUpdate.latest}\x1B[0m${released}\n\x1B[36m${cmd}\x1B[0m`, "\x1B[33mUpdate available\x1B[0m");
|
|
270
2437
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
options:
|
|
281
|
-
|
|
2438
|
+
if (state.skills.length === 0) {
|
|
2439
|
+
if (!hasConfig()) await runWizard();
|
|
2440
|
+
const pkgJsonPath = join(cwd, "package.json");
|
|
2441
|
+
const projectName = existsSync(pkgJsonPath) ? JSON.parse(readFileSync(pkgJsonPath, "utf-8")).name : void 0;
|
|
2442
|
+
const projectLabel = projectName ? `Generating skills for \x1B[36m${projectName}\x1B[0m` : "Generating skills for current directory";
|
|
2443
|
+
p.log.step(projectLabel);
|
|
2444
|
+
p.log.info("Tip: Only generate skills for packages your agent struggles with.\n The fewer skills, the more context you have for everything else :)");
|
|
2445
|
+
const source = await p.select({
|
|
2446
|
+
message: "How should I find packages?",
|
|
2447
|
+
options: [
|
|
2448
|
+
{
|
|
2449
|
+
label: "Scan source files",
|
|
2450
|
+
value: "imports",
|
|
2451
|
+
hint: "Find actually used imports"
|
|
2452
|
+
},
|
|
2453
|
+
{
|
|
2454
|
+
label: "Use package.json",
|
|
2455
|
+
value: "deps",
|
|
2456
|
+
hint: `All ${state.deps.size} dependencies`
|
|
2457
|
+
},
|
|
2458
|
+
{
|
|
2459
|
+
label: "Enter manually",
|
|
2460
|
+
value: "manual"
|
|
2461
|
+
}
|
|
2462
|
+
]
|
|
282
2463
|
});
|
|
283
|
-
if (
|
|
284
|
-
|
|
2464
|
+
if (p.isCancel(source)) {
|
|
2465
|
+
p.cancel("Setup cancelled");
|
|
285
2466
|
return;
|
|
286
2467
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (availableModels.length > 0) {
|
|
293
|
-
const modelChoice = await consola.prompt("Optimize with LLM?", {
|
|
294
|
-
type: "select",
|
|
295
|
-
options: [...availableModels.map((m) => ({
|
|
296
|
-
label: `${m.name} ${m.available ? "(available)" : ""}`,
|
|
297
|
-
value: m.id,
|
|
298
|
-
hint: m.description
|
|
299
|
-
})), {
|
|
300
|
-
label: "Skip optimization",
|
|
301
|
-
value: "skip"
|
|
302
|
-
}],
|
|
303
|
-
initial: availableModels[0]?.id || "skip"
|
|
2468
|
+
let selected;
|
|
2469
|
+
if (source === "manual") {
|
|
2470
|
+
const input = await p.text({
|
|
2471
|
+
message: "Enter package names (comma-separated)",
|
|
2472
|
+
placeholder: "vue, nuxt, pinia"
|
|
304
2473
|
});
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
2474
|
+
if (p.isCancel(input) || !input) {
|
|
2475
|
+
p.cancel("No packages entered");
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
selected = input.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2479
|
+
} else {
|
|
2480
|
+
let usages;
|
|
2481
|
+
if (source === "imports") {
|
|
2482
|
+
const spinner = p.spinner();
|
|
2483
|
+
spinner.start("Scanning imports...");
|
|
2484
|
+
const result = await detectImportedPackages(cwd);
|
|
2485
|
+
spinner.stop(`Found ${result.packages.length} imported packages`);
|
|
2486
|
+
if (result.packages.length === 0) {
|
|
2487
|
+
p.log.warn("No imports found, falling back to package.json");
|
|
2488
|
+
usages = [...state.deps.keys()].map((name) => ({
|
|
2489
|
+
name,
|
|
2490
|
+
count: 0
|
|
2491
|
+
}));
|
|
2492
|
+
} else {
|
|
2493
|
+
const depSet = new Set(state.deps.keys());
|
|
2494
|
+
usages = result.packages.filter((pkg) => depSet.has(pkg.name) || pkg.source === "preset");
|
|
2495
|
+
if (usages.length === 0) {
|
|
2496
|
+
p.log.warn("No matching dependencies, using all imports");
|
|
2497
|
+
usages = result.packages;
|
|
2498
|
+
}
|
|
329
2499
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
2500
|
+
} else usages = [...state.deps.keys()].map((name) => ({
|
|
2501
|
+
name,
|
|
2502
|
+
count: 0
|
|
2503
|
+
}));
|
|
2504
|
+
const packages = usages.map((u) => u.name);
|
|
2505
|
+
const sourceMap = new Map(usages.map((u) => [u.name, u.source]));
|
|
2506
|
+
const maxLen = Math.max(...packages.map((n) => n.length));
|
|
2507
|
+
const choice = await p.multiselect({
|
|
2508
|
+
message: `Select packages (${packages.length} found)`,
|
|
2509
|
+
options: packages.map((name) => {
|
|
2510
|
+
const ver = state.deps.get(name)?.replace(/^[\^~>=<]/, "") || "";
|
|
2511
|
+
const repo = getRepoHint(name, cwd);
|
|
2512
|
+
const hint = sourceMap.get(name) === "preset" ? "nuxt module" : void 0;
|
|
2513
|
+
const pad = " ".repeat(maxLen - name.length + 2);
|
|
2514
|
+
const meta = [
|
|
2515
|
+
ver,
|
|
2516
|
+
hint,
|
|
2517
|
+
repo
|
|
2518
|
+
].filter(Boolean).join(" ");
|
|
2519
|
+
return {
|
|
2520
|
+
label: meta ? `${name}${pad}\x1B[90m${meta}\x1B[39m` : name,
|
|
2521
|
+
value: name
|
|
2522
|
+
};
|
|
2523
|
+
}),
|
|
2524
|
+
initialValues: packages
|
|
2525
|
+
});
|
|
2526
|
+
if (p.isCancel(choice) || choice.length === 0) {
|
|
2527
|
+
p.cancel("No packages selected");
|
|
2528
|
+
return;
|
|
350
2529
|
}
|
|
2530
|
+
selected = choice;
|
|
351
2531
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
let resolvedUrl = url;
|
|
359
|
-
let skillName = config.packageName;
|
|
360
|
-
let skillVersion = config.packageVersion;
|
|
361
|
-
let isRawReadme = false;
|
|
362
|
-
let description = "";
|
|
363
|
-
if (!url.includes("://") && !url.startsWith("http")) {
|
|
364
|
-
if (!config.quiet) consola.start(`Resolving docs for package: ${url}`);
|
|
365
|
-
skillName = url;
|
|
366
|
-
const resolved = await resolvePackageDocs(url);
|
|
367
|
-
if (!resolved) {
|
|
368
|
-
consola.error(`Could not find docs for package: ${url}`);
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
description = resolved.description || "";
|
|
372
|
-
skillVersion = skillVersion || resolved.version;
|
|
373
|
-
if (resolved.llmsUrl) resolvedUrl = resolved.llmsUrl;
|
|
374
|
-
else if (resolved.docsUrl) resolvedUrl = resolved.docsUrl;
|
|
375
|
-
else if (resolved.readmeUrl) {
|
|
376
|
-
resolvedUrl = resolved.readmeUrl;
|
|
377
|
-
isRawReadme = true;
|
|
378
|
-
} else {
|
|
379
|
-
consola.error(`No documentation found for ${url}`);
|
|
380
|
-
return;
|
|
2532
|
+
return syncCommand(state, {
|
|
2533
|
+
packages: selected,
|
|
2534
|
+
global: false,
|
|
2535
|
+
agent: currentAgent,
|
|
2536
|
+
yes: false
|
|
2537
|
+
});
|
|
381
2538
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
2539
|
+
const status = formatStatus(state.synced.length, state.outdated.length);
|
|
2540
|
+
p.log.info(status);
|
|
2541
|
+
while (true) {
|
|
2542
|
+
const options = [];
|
|
2543
|
+
options.push({
|
|
2544
|
+
label: "Add new skills",
|
|
2545
|
+
value: "install"
|
|
2546
|
+
});
|
|
2547
|
+
if (state.outdated.length > 0) options.push({
|
|
2548
|
+
label: "Update skills",
|
|
2549
|
+
value: "update",
|
|
2550
|
+
hint: `\x1B[33m${state.outdated.length} outdated\x1B[0m`
|
|
2551
|
+
});
|
|
2552
|
+
options.push({
|
|
2553
|
+
label: "Remove skills",
|
|
2554
|
+
value: "remove"
|
|
2555
|
+
}, {
|
|
2556
|
+
label: "Status",
|
|
2557
|
+
value: "status"
|
|
2558
|
+
}, {
|
|
2559
|
+
label: "Configure",
|
|
2560
|
+
value: "config"
|
|
2561
|
+
});
|
|
2562
|
+
const action = await p.select({
|
|
2563
|
+
message: "What would you like to do?",
|
|
2564
|
+
options
|
|
2565
|
+
});
|
|
2566
|
+
if (p.isCancel(action)) {
|
|
2567
|
+
p.cancel("Cancelled");
|
|
396
2568
|
return;
|
|
397
2569
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
2570
|
+
switch (action) {
|
|
2571
|
+
case "install": {
|
|
2572
|
+
const installedNames = new Set(state.skills.map((s) => s.packageName || s.name));
|
|
2573
|
+
const uninstalledDeps = [...state.deps.keys()].filter((d) => !installedNames.has(d));
|
|
2574
|
+
const allDepsInstalled = uninstalledDeps.length === 0;
|
|
2575
|
+
const source = await p.select({
|
|
2576
|
+
message: "How should I find packages?",
|
|
2577
|
+
options: [
|
|
2578
|
+
{
|
|
2579
|
+
label: "Scan source files",
|
|
2580
|
+
value: "imports",
|
|
2581
|
+
hint: allDepsInstalled ? "all installed" : "find actually used imports",
|
|
2582
|
+
disabled: allDepsInstalled
|
|
2583
|
+
},
|
|
2584
|
+
{
|
|
2585
|
+
label: "Use package.json",
|
|
2586
|
+
value: "deps",
|
|
2587
|
+
hint: allDepsInstalled ? "all installed" : `${uninstalledDeps.length} uninstalled`,
|
|
2588
|
+
disabled: allDepsInstalled
|
|
2589
|
+
},
|
|
2590
|
+
{
|
|
2591
|
+
label: "Enter manually",
|
|
2592
|
+
value: "manual"
|
|
2593
|
+
}
|
|
2594
|
+
]
|
|
2595
|
+
});
|
|
2596
|
+
if (p.isCancel(source)) continue;
|
|
2597
|
+
let selected;
|
|
2598
|
+
if (source === "manual") {
|
|
2599
|
+
const input = await p.text({
|
|
2600
|
+
message: "Enter package names (comma-separated)",
|
|
2601
|
+
placeholder: "vue, nuxt, pinia"
|
|
2602
|
+
});
|
|
2603
|
+
if (p.isCancel(input) || !input) continue;
|
|
2604
|
+
selected = input.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2605
|
+
if (selected.length === 0) continue;
|
|
2606
|
+
} else {
|
|
2607
|
+
let usages;
|
|
2608
|
+
if (source === "imports") {
|
|
2609
|
+
const spinner = p.spinner();
|
|
2610
|
+
spinner.start("Scanning imports...");
|
|
2611
|
+
const result = await detectImportedPackages(cwd);
|
|
2612
|
+
spinner.stop(`Found ${result.packages.length} imported packages`);
|
|
2613
|
+
if (result.packages.length === 0) {
|
|
2614
|
+
p.log.warn("No imports found, falling back to package.json");
|
|
2615
|
+
usages = uninstalledDeps.map((name) => ({
|
|
2616
|
+
name,
|
|
2617
|
+
count: 0
|
|
2618
|
+
}));
|
|
2619
|
+
} else {
|
|
2620
|
+
const depSet = new Set(state.deps.keys());
|
|
2621
|
+
usages = result.packages.filter((pkg) => depSet.has(pkg.name) || pkg.source === "preset").filter((pkg) => !installedNames.has(pkg.name));
|
|
2622
|
+
if (usages.length === 0) {
|
|
2623
|
+
p.log.warn("All detected imports already have skills");
|
|
2624
|
+
continue;
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
} else usages = uninstalledDeps.map((name) => ({
|
|
2628
|
+
name,
|
|
2629
|
+
count: 0
|
|
2630
|
+
}));
|
|
2631
|
+
const packages = usages.map((u) => u.name);
|
|
2632
|
+
const sourceMap = new Map(usages.map((u) => [u.name, u.source]));
|
|
2633
|
+
const maxLen = Math.max(...packages.map((n) => n.length));
|
|
2634
|
+
const choice = await p.multiselect({
|
|
2635
|
+
message: `Select packages (${packages.length} found)`,
|
|
2636
|
+
options: packages.map((name) => {
|
|
2637
|
+
const ver = state.deps.get(name)?.replace(/^[\^~>=<]/, "") || "";
|
|
2638
|
+
const repo = getRepoHint(name, cwd);
|
|
2639
|
+
const hint = sourceMap.get(name) === "preset" ? "nuxt module" : void 0;
|
|
2640
|
+
const pad = " ".repeat(maxLen - name.length + 2);
|
|
2641
|
+
const meta = [
|
|
2642
|
+
ver,
|
|
2643
|
+
hint,
|
|
2644
|
+
repo
|
|
2645
|
+
].filter(Boolean).join(" ");
|
|
2646
|
+
return {
|
|
2647
|
+
label: meta ? `${name}${pad}\x1B[90m${meta}\x1B[39m` : name,
|
|
2648
|
+
value: name
|
|
2649
|
+
};
|
|
2650
|
+
}),
|
|
2651
|
+
initialValues: packages
|
|
2652
|
+
});
|
|
2653
|
+
if (p.isCancel(choice) || choice.length === 0) continue;
|
|
2654
|
+
selected = choice;
|
|
2655
|
+
}
|
|
2656
|
+
return syncCommand(state, {
|
|
2657
|
+
packages: selected,
|
|
2658
|
+
global: false,
|
|
2659
|
+
agent: currentAgent,
|
|
2660
|
+
yes: false
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
case "update": {
|
|
2664
|
+
if (state.outdated.length === 0) {
|
|
2665
|
+
p.log.success("All skills up to date");
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
const selected = await p.multiselect({
|
|
2669
|
+
message: "Select packages to update",
|
|
2670
|
+
options: state.outdated.map((s) => ({
|
|
2671
|
+
label: s.name,
|
|
2672
|
+
value: s.packageName || s.name,
|
|
2673
|
+
hint: `${s.info?.version ?? "unknown"} → ${s.latestVersion}`
|
|
2674
|
+
})),
|
|
2675
|
+
initialValues: state.outdated.map((s) => s.packageName || s.name)
|
|
2676
|
+
});
|
|
2677
|
+
if (p.isCancel(selected) || selected.length === 0) continue;
|
|
2678
|
+
return syncCommand(state, {
|
|
2679
|
+
packages: selected,
|
|
2680
|
+
global: false,
|
|
2681
|
+
agent: currentAgent,
|
|
2682
|
+
yes: false
|
|
2683
|
+
});
|
|
2684
|
+
}
|
|
2685
|
+
case "remove":
|
|
2686
|
+
await removeCommand(state, {
|
|
2687
|
+
global: false,
|
|
2688
|
+
agent: currentAgent,
|
|
2689
|
+
yes: false
|
|
2690
|
+
});
|
|
2691
|
+
continue;
|
|
2692
|
+
case "status":
|
|
2693
|
+
await statusCommand({ global: false });
|
|
2694
|
+
continue;
|
|
2695
|
+
case "config":
|
|
2696
|
+
await configCommand();
|
|
2697
|
+
continue;
|
|
410
2698
|
}
|
|
411
|
-
readmeContent = await res.text();
|
|
412
|
-
}
|
|
413
|
-
if (!readmeContent) {
|
|
414
|
-
consola.error("README content is empty");
|
|
415
|
-
return;
|
|
416
2699
|
}
|
|
417
|
-
const name = skillName || "package";
|
|
418
|
-
let finalContent = readmeContent;
|
|
419
|
-
if (config.optimize !== false && config.optimizeModel) {
|
|
420
|
-
!config.quiet && consola.start(`Optimizing ${name} with ${config.optimizeModel}...`);
|
|
421
|
-
const { optimized, wasOptimized } = await optimizeDocs(readmeContent, name, config.currentAgent || null, config.optimizeModel);
|
|
422
|
-
finalContent = optimized;
|
|
423
|
-
if (!config.quiet && wasOptimized) consola.success(`Optimized: ${readmeContent.length} → ${optimized.length} chars`);
|
|
424
|
-
else if (!config.quiet && !wasOptimized) consola.warn(`Optimization skipped (no LLM available)`);
|
|
425
|
-
}
|
|
426
|
-
const { installed, paths } = installSkillForAgents(name, generateSkillMd({
|
|
427
|
-
name,
|
|
428
|
-
version: skillVersion,
|
|
429
|
-
description
|
|
430
|
-
}, finalContent), {
|
|
431
|
-
global: config.global,
|
|
432
|
-
agents: config.agents
|
|
433
|
-
});
|
|
434
|
-
if (!config.quiet) {
|
|
435
|
-
consola.success(`Installed skill: ${name}`);
|
|
436
|
-
consola.info(` Agents: ${installed.map((a) => agents[a].displayName).join(", ")}`);
|
|
437
|
-
consola.info(` Paths: ${paths.join(", ")}`);
|
|
438
|
-
}
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
if (!config.quiet) consola.start(`Generating skill from ${resolvedUrl}`);
|
|
442
|
-
if (config.outputDir) {
|
|
443
|
-
const result = await generateSkill({
|
|
444
|
-
url: resolvedUrl,
|
|
445
|
-
outputDir: config.outputDir,
|
|
446
|
-
maxPages: config.maxPages,
|
|
447
|
-
chunkSize: config.chunkSize,
|
|
448
|
-
skipLlmsTxt: config.skipLlmsTxt,
|
|
449
|
-
model: config.model
|
|
450
|
-
}, config.quiet ? void 0 : ({ url: pageUrl, count, phase }) => {
|
|
451
|
-
const icon = phase === "fetch" ? "📄" : "🔍";
|
|
452
|
-
consola.info(`${icon} [${count}] ${pageUrl}`);
|
|
453
|
-
});
|
|
454
|
-
if (!config.quiet) {
|
|
455
|
-
consola.success(`Generated skill: ${result.siteName}`);
|
|
456
|
-
consola.info(` SKILL.md: ${result.skillPath}`);
|
|
457
|
-
consola.info(` References: ${result.referencesDir}`);
|
|
458
|
-
consola.info(` Database: ${result.dbPath}`);
|
|
459
|
-
consola.info(` Chunks: ${result.chunkCount}`);
|
|
460
|
-
}
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
const result = await generateSkill({
|
|
464
|
-
url: resolvedUrl,
|
|
465
|
-
outputDir: ".skilld-temp",
|
|
466
|
-
maxPages: config.maxPages,
|
|
467
|
-
chunkSize: config.chunkSize,
|
|
468
|
-
skipLlmsTxt: config.skipLlmsTxt,
|
|
469
|
-
model: config.model
|
|
470
|
-
}, config.quiet ? void 0 : ({ url: pageUrl, count, phase }) => {
|
|
471
|
-
if (phase === "fetch" && count % 5 === 0) consola.info(` [${count}] ${pageUrl}`);
|
|
472
|
-
});
|
|
473
|
-
const name = skillName || result.siteName;
|
|
474
|
-
const body = `# ${name}
|
|
475
|
-
|
|
476
|
-
This skill provides searchable documentation for ${name}.
|
|
477
|
-
|
|
478
|
-
## Search Database
|
|
479
|
-
- Path: ${result.dbPath}
|
|
480
|
-
- Chunks indexed: ${result.chunkCount}
|
|
481
|
-
|
|
482
|
-
## References
|
|
483
|
-
Individual documentation chunks are available in the references directory.
|
|
484
|
-
`;
|
|
485
|
-
const { installed, paths } = installSkillForAgents(name, generateSkillMd({
|
|
486
|
-
name,
|
|
487
|
-
version: skillVersion,
|
|
488
|
-
description
|
|
489
|
-
}, body), {
|
|
490
|
-
global: config.global,
|
|
491
|
-
agents: config.agents
|
|
492
|
-
});
|
|
493
|
-
if (!config.quiet) {
|
|
494
|
-
consola.success(`Installed skill: ${name}`);
|
|
495
|
-
consola.info(` Agents: ${installed.map((a) => agents[a].displayName).join(", ")}`);
|
|
496
|
-
consola.info(` Paths: ${paths.join(", ")}`);
|
|
497
|
-
consola.info(` Chunks: ${result.chunkCount}`);
|
|
498
2700
|
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
export {};
|
|
2701
|
+
}));
|
|
2702
|
+
export { defaultFeatures as a, writeLock as i, selectModel as n, readConfig as o, selectSkillSections as r, registerProject as s, ensureGitignore as t };
|
|
502
2703
|
|
|
503
2704
|
//# sourceMappingURL=cli.mjs.map
|