skilld 0.0.1 → 0.1.1
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
|
@@ -0,0 +1,877 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { exec, execSync, spawn } from "node:child_process";
|
|
5
|
+
import { globby } from "globby";
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { findDynamicImports, findStaticImports } from "mlly";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
const home = homedir();
|
|
10
|
+
const configHome = process.env.XDG_CONFIG_HOME || join(home, ".config");
|
|
11
|
+
const claudeHome = process.env.CLAUDE_CONFIG_DIR || join(home, ".claude");
|
|
12
|
+
const codexHome = process.env.CODEX_HOME || join(home, ".codex");
|
|
13
|
+
const agents = {
|
|
14
|
+
"claude-code": {
|
|
15
|
+
name: "claude-code",
|
|
16
|
+
displayName: "Claude Code",
|
|
17
|
+
skillsDir: ".claude/skills",
|
|
18
|
+
globalSkillsDir: join(claudeHome, "skills"),
|
|
19
|
+
detectInstalled: () => existsSync(claudeHome),
|
|
20
|
+
cli: "claude"
|
|
21
|
+
},
|
|
22
|
+
"cursor": {
|
|
23
|
+
name: "cursor",
|
|
24
|
+
displayName: "Cursor",
|
|
25
|
+
skillsDir: ".cursor/skills",
|
|
26
|
+
globalSkillsDir: join(home, ".cursor/skills"),
|
|
27
|
+
detectInstalled: () => existsSync(join(home, ".cursor"))
|
|
28
|
+
},
|
|
29
|
+
"windsurf": {
|
|
30
|
+
name: "windsurf",
|
|
31
|
+
displayName: "Windsurf",
|
|
32
|
+
skillsDir: ".windsurf/skills",
|
|
33
|
+
globalSkillsDir: join(home, ".codeium/windsurf/skills"),
|
|
34
|
+
detectInstalled: () => existsSync(join(home, ".codeium/windsurf"))
|
|
35
|
+
},
|
|
36
|
+
"cline": {
|
|
37
|
+
name: "cline",
|
|
38
|
+
displayName: "Cline",
|
|
39
|
+
skillsDir: ".cline/skills",
|
|
40
|
+
globalSkillsDir: join(home, ".cline/skills"),
|
|
41
|
+
detectInstalled: () => existsSync(join(home, ".cline"))
|
|
42
|
+
},
|
|
43
|
+
"codex": {
|
|
44
|
+
name: "codex",
|
|
45
|
+
displayName: "Codex",
|
|
46
|
+
skillsDir: ".codex/skills",
|
|
47
|
+
globalSkillsDir: join(codexHome, "skills"),
|
|
48
|
+
detectInstalled: () => existsSync(codexHome),
|
|
49
|
+
cli: "codex"
|
|
50
|
+
},
|
|
51
|
+
"github-copilot": {
|
|
52
|
+
name: "github-copilot",
|
|
53
|
+
displayName: "GitHub Copilot",
|
|
54
|
+
skillsDir: ".github/skills",
|
|
55
|
+
globalSkillsDir: join(home, ".copilot/skills"),
|
|
56
|
+
detectInstalled: () => existsSync(join(home, ".copilot"))
|
|
57
|
+
},
|
|
58
|
+
"gemini-cli": {
|
|
59
|
+
name: "gemini-cli",
|
|
60
|
+
displayName: "Gemini CLI",
|
|
61
|
+
skillsDir: ".gemini/skills",
|
|
62
|
+
globalSkillsDir: join(home, ".gemini/skills"),
|
|
63
|
+
detectInstalled: () => existsSync(join(home, ".gemini")),
|
|
64
|
+
cli: "gemini"
|
|
65
|
+
},
|
|
66
|
+
"goose": {
|
|
67
|
+
name: "goose",
|
|
68
|
+
displayName: "Goose",
|
|
69
|
+
skillsDir: ".goose/skills",
|
|
70
|
+
globalSkillsDir: join(configHome, "goose/skills"),
|
|
71
|
+
detectInstalled: () => existsSync(join(configHome, "goose")),
|
|
72
|
+
cli: "goose"
|
|
73
|
+
},
|
|
74
|
+
"amp": {
|
|
75
|
+
name: "amp",
|
|
76
|
+
displayName: "Amp",
|
|
77
|
+
skillsDir: ".agents/skills",
|
|
78
|
+
globalSkillsDir: join(configHome, "agents/skills"),
|
|
79
|
+
detectInstalled: () => existsSync(join(configHome, "amp"))
|
|
80
|
+
},
|
|
81
|
+
"opencode": {
|
|
82
|
+
name: "opencode",
|
|
83
|
+
displayName: "OpenCode",
|
|
84
|
+
skillsDir: ".opencode/skills",
|
|
85
|
+
globalSkillsDir: join(configHome, "opencode/skills"),
|
|
86
|
+
detectInstalled: () => existsSync(join(configHome, "opencode"))
|
|
87
|
+
},
|
|
88
|
+
"roo": {
|
|
89
|
+
name: "roo",
|
|
90
|
+
displayName: "Roo Code",
|
|
91
|
+
skillsDir: ".roo/skills",
|
|
92
|
+
globalSkillsDir: join(home, ".roo/skills"),
|
|
93
|
+
detectInstalled: () => existsSync(join(home, ".roo"))
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
function detectInstalledAgents() {
|
|
97
|
+
return Object.entries(agents).filter(([_, config]) => config.detectInstalled()).map(([type]) => type);
|
|
98
|
+
}
|
|
99
|
+
function detectTargetAgent() {
|
|
100
|
+
if (process.env.CLAUDE_CODE || process.env.CLAUDE_CONFIG_DIR) return "claude-code";
|
|
101
|
+
if (process.env.CURSOR_SESSION || process.env.CURSOR_TRACE_ID) return "cursor";
|
|
102
|
+
if (process.env.WINDSURF_SESSION) return "windsurf";
|
|
103
|
+
if (process.env.CLINE_TASK_ID) return "cline";
|
|
104
|
+
if (process.env.CODEX_HOME || process.env.CODEX_SESSION) return "codex";
|
|
105
|
+
if (process.env.GITHUB_COPILOT_SESSION) return "github-copilot";
|
|
106
|
+
if (process.env.GEMINI_API_KEY && process.env.GEMINI_SESSION) return "gemini-cli";
|
|
107
|
+
if (process.env.GOOSE_SESSION) return "goose";
|
|
108
|
+
if (process.env.AMP_SESSION) return "amp";
|
|
109
|
+
if (process.env.OPENCODE_SESSION) return "opencode";
|
|
110
|
+
if (process.env.ROO_SESSION) return "roo";
|
|
111
|
+
const cwd = process.cwd();
|
|
112
|
+
if (existsSync(join(cwd, ".claude")) || existsSync(join(cwd, "CLAUDE.md"))) return "claude-code";
|
|
113
|
+
if (existsSync(join(cwd, ".cursor")) || existsSync(join(cwd, ".cursorrules"))) return "cursor";
|
|
114
|
+
if (existsSync(join(cwd, ".windsurf")) || existsSync(join(cwd, ".windsurfrules"))) return "windsurf";
|
|
115
|
+
if (existsSync(join(cwd, ".cline"))) return "cline";
|
|
116
|
+
if (existsSync(join(cwd, ".codex"))) return "codex";
|
|
117
|
+
if (existsSync(join(cwd, ".github", "copilot-instructions.md"))) return "github-copilot";
|
|
118
|
+
if (existsSync(join(cwd, ".gemini")) || existsSync(join(cwd, "AGENTS.md"))) return "gemini-cli";
|
|
119
|
+
if (existsSync(join(cwd, ".goose"))) return "goose";
|
|
120
|
+
if (existsSync(join(cwd, ".roo"))) return "roo";
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
function getAgentVersion(agentType) {
|
|
124
|
+
const agent = agents[agentType];
|
|
125
|
+
if (!agent.cli) return null;
|
|
126
|
+
try {
|
|
127
|
+
const output = execSync(`${agent.cli} --version`, {
|
|
128
|
+
encoding: "utf-8",
|
|
129
|
+
timeout: 3e3,
|
|
130
|
+
stdio: [
|
|
131
|
+
"pipe",
|
|
132
|
+
"pipe",
|
|
133
|
+
"pipe"
|
|
134
|
+
]
|
|
135
|
+
}).trim();
|
|
136
|
+
const match = output.match(/v?(\d+\.\d+\.\d+(?:-[a-z0-9.]+)?)/);
|
|
137
|
+
return match ? match[1] : output.split("\n")[0];
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const NUXT_CONFIG_FILES = [
|
|
143
|
+
"nuxt.config.ts",
|
|
144
|
+
"nuxt.config.js",
|
|
145
|
+
"nuxt.config.mjs"
|
|
146
|
+
];
|
|
147
|
+
const NUXT_ECOSYSTEM = [
|
|
148
|
+
"vue",
|
|
149
|
+
"nitro",
|
|
150
|
+
"h3"
|
|
151
|
+
];
|
|
152
|
+
async function findNuxtConfig(cwd) {
|
|
153
|
+
for (const name of NUXT_CONFIG_FILES) {
|
|
154
|
+
const path = join(cwd, name);
|
|
155
|
+
const content = await readFile(path, "utf8").catch(() => null);
|
|
156
|
+
if (content) return {
|
|
157
|
+
path,
|
|
158
|
+
content
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
function extractModuleStrings(node) {
|
|
164
|
+
if (!node || typeof node !== "object") return [];
|
|
165
|
+
if (node.type === "Property" && !node.computed && node.key?.type === "Identifier" && node.key.name === "modules" && node.value?.type === "ArrayExpression") return node.value.elements.filter((el) => el?.type === "Literal" && typeof el.value === "string").map((el) => el.value);
|
|
166
|
+
const results = [];
|
|
167
|
+
if (Array.isArray(node)) for (const child of node) results.push(...extractModuleStrings(child));
|
|
168
|
+
else for (const key of Object.keys(node)) {
|
|
169
|
+
if (key === "start" || key === "end" || key === "type") continue;
|
|
170
|
+
const val = node[key];
|
|
171
|
+
if (val && typeof val === "object") results.push(...extractModuleStrings(val));
|
|
172
|
+
}
|
|
173
|
+
return results;
|
|
174
|
+
}
|
|
175
|
+
async function detectNuxtModules(cwd) {
|
|
176
|
+
const config = await findNuxtConfig(cwd);
|
|
177
|
+
if (!config) return [];
|
|
178
|
+
const { parseSync } = await import("oxc-parser");
|
|
179
|
+
const modules = extractModuleStrings(parseSync(config.path, config.content).program);
|
|
180
|
+
const seen = /* @__PURE__ */ new Set();
|
|
181
|
+
const packages = [];
|
|
182
|
+
for (const mod of modules) if (!seen.has(mod)) {
|
|
183
|
+
seen.add(mod);
|
|
184
|
+
packages.push({
|
|
185
|
+
name: mod,
|
|
186
|
+
count: 0,
|
|
187
|
+
source: "preset"
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
for (const pkg of NUXT_ECOSYSTEM) if (!seen.has(pkg)) {
|
|
191
|
+
seen.add(pkg);
|
|
192
|
+
packages.push({
|
|
193
|
+
name: pkg,
|
|
194
|
+
count: 0,
|
|
195
|
+
source: "preset"
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return packages;
|
|
199
|
+
}
|
|
200
|
+
async function detectPresetPackages(cwd) {
|
|
201
|
+
return detectNuxtModules(cwd);
|
|
202
|
+
}
|
|
203
|
+
const PATTERNS = ["**/*.{ts,js,vue,mjs,cjs,tsx,jsx,mts,cts}"];
|
|
204
|
+
const IGNORE = [
|
|
205
|
+
"**/node_modules/**",
|
|
206
|
+
"**/dist/**",
|
|
207
|
+
"**/.nuxt/**",
|
|
208
|
+
"**/.output/**",
|
|
209
|
+
"**/coverage/**"
|
|
210
|
+
];
|
|
211
|
+
function addPackage(counts, specifier) {
|
|
212
|
+
if (!specifier || specifier.startsWith(".") || specifier.startsWith("/")) return;
|
|
213
|
+
const name = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
|
|
214
|
+
if (!isNodeBuiltin(name)) counts.set(name, (counts.get(name) || 0) + 1);
|
|
215
|
+
}
|
|
216
|
+
async function detectImportedPackages(cwd = process.cwd()) {
|
|
217
|
+
try {
|
|
218
|
+
const counts = /* @__PURE__ */ new Map();
|
|
219
|
+
const files = await globby(PATTERNS, {
|
|
220
|
+
cwd,
|
|
221
|
+
ignore: IGNORE,
|
|
222
|
+
gitignore: true,
|
|
223
|
+
absolute: true
|
|
224
|
+
});
|
|
225
|
+
await Promise.all(files.map(async (file) => {
|
|
226
|
+
const content = await readFile(file, "utf8");
|
|
227
|
+
for (const imp of findStaticImports(content)) addPackage(counts, imp.specifier);
|
|
228
|
+
for (const imp of findDynamicImports(content)) {
|
|
229
|
+
const match = imp.expression.match(/^['"]([^'"]+)['"]$/);
|
|
230
|
+
if (match) addPackage(counts, match[1]);
|
|
231
|
+
}
|
|
232
|
+
}));
|
|
233
|
+
const packages = [...counts.entries()].map(([name, count]) => ({
|
|
234
|
+
name,
|
|
235
|
+
count,
|
|
236
|
+
source: "import"
|
|
237
|
+
})).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
238
|
+
const presets = await detectPresetPackages(cwd);
|
|
239
|
+
const importNames = new Set(packages.map((p) => p.name));
|
|
240
|
+
for (const preset of presets) if (!importNames.has(preset.name)) packages.push(preset);
|
|
241
|
+
return { packages };
|
|
242
|
+
} catch (err) {
|
|
243
|
+
return {
|
|
244
|
+
packages: [],
|
|
245
|
+
error: String(err)
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const NODE_BUILTINS = new Set([
|
|
250
|
+
"assert",
|
|
251
|
+
"buffer",
|
|
252
|
+
"child_process",
|
|
253
|
+
"cluster",
|
|
254
|
+
"console",
|
|
255
|
+
"constants",
|
|
256
|
+
"crypto",
|
|
257
|
+
"dgram",
|
|
258
|
+
"dns",
|
|
259
|
+
"domain",
|
|
260
|
+
"events",
|
|
261
|
+
"fs",
|
|
262
|
+
"http",
|
|
263
|
+
"https",
|
|
264
|
+
"module",
|
|
265
|
+
"net",
|
|
266
|
+
"os",
|
|
267
|
+
"path",
|
|
268
|
+
"perf_hooks",
|
|
269
|
+
"process",
|
|
270
|
+
"punycode",
|
|
271
|
+
"querystring",
|
|
272
|
+
"readline",
|
|
273
|
+
"repl",
|
|
274
|
+
"stream",
|
|
275
|
+
"string_decoder",
|
|
276
|
+
"sys",
|
|
277
|
+
"timers",
|
|
278
|
+
"tls",
|
|
279
|
+
"tty",
|
|
280
|
+
"url",
|
|
281
|
+
"util",
|
|
282
|
+
"v8",
|
|
283
|
+
"vm",
|
|
284
|
+
"wasi",
|
|
285
|
+
"worker_threads",
|
|
286
|
+
"zlib"
|
|
287
|
+
]);
|
|
288
|
+
function isNodeBuiltin(pkg) {
|
|
289
|
+
const base = pkg.startsWith("node:") ? pkg.slice(5) : pkg;
|
|
290
|
+
return NODE_BUILTINS.has(base.split("/")[0]);
|
|
291
|
+
}
|
|
292
|
+
function sanitizeName(name) {
|
|
293
|
+
return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").slice(0, 255) || "unnamed-skill";
|
|
294
|
+
}
|
|
295
|
+
function installSkillForAgents(skillName, skillContent, options = {}) {
|
|
296
|
+
const isGlobal = options.global ?? false;
|
|
297
|
+
const cwd = options.cwd || process.cwd();
|
|
298
|
+
const sanitized = sanitizeName(skillName);
|
|
299
|
+
const targetAgents = options.agents || detectInstalledAgents();
|
|
300
|
+
const installed = [];
|
|
301
|
+
const paths = [];
|
|
302
|
+
for (const agentType of targetAgents) {
|
|
303
|
+
const agent = agents[agentType];
|
|
304
|
+
if (isGlobal && !agent.globalSkillsDir) continue;
|
|
305
|
+
const skillDir = join(isGlobal ? agent.globalSkillsDir : join(cwd, agent.skillsDir), sanitized);
|
|
306
|
+
mkdirSync(skillDir, { recursive: true });
|
|
307
|
+
writeFileSync(join(skillDir, "_SKILL.md"), skillContent);
|
|
308
|
+
if (options.files) for (const [filename, content] of Object.entries(options.files)) writeFileSync(join(skillDir, filename), content);
|
|
309
|
+
installed.push(agentType);
|
|
310
|
+
paths.push(skillDir);
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
installed,
|
|
314
|
+
paths
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function formatDocTree(files) {
|
|
318
|
+
const dirs = /* @__PURE__ */ new Map();
|
|
319
|
+
for (const f of files) {
|
|
320
|
+
const dir = dirname(f);
|
|
321
|
+
dirs.set(dir, (dirs.get(dir) || 0) + 1);
|
|
322
|
+
}
|
|
323
|
+
return [...dirs.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([dir, count]) => `- \`${dir}/\` (${count} .md files)`).join("\n");
|
|
324
|
+
}
|
|
325
|
+
function generateImportantBlock({ packageName, hasGithub, hasReleases, hasChangelog, docsType, hasShippedDocs, skillDir }) {
|
|
326
|
+
const searchDesc = hasGithub ? "Docs + GitHub" : "Docs";
|
|
327
|
+
const searchCmd = `\`Bash 'npx skilld ${packageName} -q "<query>"'\``;
|
|
328
|
+
const docsPath = hasShippedDocs ? `\`${skillDir}/.skilld/pkg/docs/\` or \`${skillDir}/.skilld/pkg/README.md\`` : docsType === "llms.txt" ? `\`${skillDir}/.skilld/docs/llms.txt\`` : docsType === "readme" ? `\`${skillDir}/.skilld/pkg/README.md\`` : `\`${skillDir}/.skilld/docs/\``;
|
|
329
|
+
const rows = [
|
|
330
|
+
[searchDesc, searchCmd],
|
|
331
|
+
["Docs", docsPath],
|
|
332
|
+
["Package", `\`${skillDir}/.skilld/pkg/\``]
|
|
333
|
+
];
|
|
334
|
+
if (hasGithub) rows.push(["GitHub", `\`${skillDir}/.skilld/github/\``]);
|
|
335
|
+
if (hasChangelog) rows.push(["Changelog", `\`${skillDir}/.skilld/pkg/${hasChangelog}\``]);
|
|
336
|
+
if (hasReleases) rows.push(["Releases", `\`${skillDir}/.skilld/releases/\``]);
|
|
337
|
+
return `**IMPORTANT:** Use these references\n\n${[
|
|
338
|
+
"| Resource | Command |",
|
|
339
|
+
"|----------|---------|",
|
|
340
|
+
...rows.map(([desc, cmd]) => `| ${desc} | ${cmd} |`)
|
|
341
|
+
].join("\n")}`;
|
|
342
|
+
}
|
|
343
|
+
function buildSkillPrompt(opts) {
|
|
344
|
+
const { packageName, skillDir, version, hasGithub, hasReleases, hasChangelog, docFiles, docsType = "docs", hasShippedDocs = false, sections, customPrompt } = opts;
|
|
345
|
+
const hasBestPractices = !sections || sections.includes("best-practices");
|
|
346
|
+
const hasApi = !sections || sections.includes("api");
|
|
347
|
+
const hasCustom = sections?.includes("custom") && customPrompt;
|
|
348
|
+
const versionContext = version ? ` v${version}` : "";
|
|
349
|
+
const docsSection = docFiles?.length ? `**Documentation** (use Read tool to explore):\n${formatDocTree(docFiles)}` : "";
|
|
350
|
+
const importantBlock = generateImportantBlock({
|
|
351
|
+
packageName,
|
|
352
|
+
hasGithub,
|
|
353
|
+
hasReleases,
|
|
354
|
+
hasChangelog,
|
|
355
|
+
docsType,
|
|
356
|
+
hasShippedDocs,
|
|
357
|
+
skillDir
|
|
358
|
+
});
|
|
359
|
+
const taskParts = [];
|
|
360
|
+
if (hasBestPractices) taskParts.push(`Find novel best practices from the references. Every item must link to its source.
|
|
361
|
+
|
|
362
|
+
Look for: tip, warning, best practice, avoid, pitfall, note, important.`);
|
|
363
|
+
if (hasApi) taskParts.push(`**Generate an API reference section.** List the package's exported functions/composables grouped by documentation page or category. Each function gets a one-liner description. Link group headings to the source doc URL when available.`);
|
|
364
|
+
if (hasCustom) taskParts.push(`**Custom instructions from the user:**\n${customPrompt}`);
|
|
365
|
+
const formatParts = [];
|
|
366
|
+
if (hasBestPractices) formatParts.push(`\`\`\`
|
|
367
|
+
[✅ descriptive title](./.skilld/path/to/source.md)
|
|
368
|
+
\`\`\`ts
|
|
369
|
+
code example (1-3 lines)
|
|
370
|
+
\`\`\`
|
|
371
|
+
|
|
372
|
+
[❌ pitfall title](./.skilld/path/to/source.md#section)
|
|
373
|
+
\`\`\`ts
|
|
374
|
+
wrong // correct way
|
|
375
|
+
\`\`\`
|
|
376
|
+
\`\`\``);
|
|
377
|
+
if (hasApi) formatParts.push(`API reference format${hasBestPractices ? " (place at end, after best practices)" : ""}:
|
|
378
|
+
\`\`\`
|
|
379
|
+
## API
|
|
380
|
+
|
|
381
|
+
### [Category Name](./.skilld/docs/category.md)
|
|
382
|
+
- functionName — one-line description
|
|
383
|
+
- anotherFn — one-line description
|
|
384
|
+
\`\`\`
|
|
385
|
+
|
|
386
|
+
Link group headings to the local \`./.skilld/\` source file.
|
|
387
|
+
|
|
388
|
+
For single-page-docs packages, use a flat list without grouping. Skip the API section entirely for packages with fewer than 3 exports.`);
|
|
389
|
+
const rules = [];
|
|
390
|
+
if (hasBestPractices) rules.push("- **5-10 best practice items**, MAX 150 lines for best practices");
|
|
391
|
+
if (hasApi) rules.push("- **API section:** list all public exports, grouped by doc page, MAX 80 lines");
|
|
392
|
+
rules.push("- Link to exact source file where you found info", "- TypeScript only, Vue uses `<script setup lang=\"ts\">`", "- Imperative voice (\"Use X\" not \"You should use X\")", "- **NEVER fetch external URLs.** All information is in the local `./.skilld/` directory. Use Read/Glob only.");
|
|
393
|
+
return `Generate SKILL.md body for "${packageName}"${versionContext}.
|
|
394
|
+
|
|
395
|
+
${importantBlock}
|
|
396
|
+
${docsSection ? `${docsSection}\n` : ""}
|
|
397
|
+
|
|
398
|
+
## Skill Quality Principles
|
|
399
|
+
|
|
400
|
+
The context window is a shared resource. Skills share it with system prompt, conversation history, other skills, and the user request.
|
|
401
|
+
|
|
402
|
+
- **Only add what Claude doesn't know.** Claude already knows general programming, popular APIs, common patterns. Challenge every line: "Does this justify its token cost?"
|
|
403
|
+
- **Prefer concise examples over verbose explanations.** A 2-line code example beats a paragraph.
|
|
404
|
+
- **Skip:** API signatures, installation steps, tutorials, marketing, general programming knowledge, anything in the package README that's obvious
|
|
405
|
+
- **Include:** Non-obvious gotchas, surprising defaults, version-specific breaking changes, pitfalls from issues, patterns that differ from what Claude would assume
|
|
406
|
+
|
|
407
|
+
## Task
|
|
408
|
+
|
|
409
|
+
${taskParts.join("\n\n")}
|
|
410
|
+
|
|
411
|
+
## Format
|
|
412
|
+
|
|
413
|
+
${formatParts.join("\n\n")}
|
|
414
|
+
|
|
415
|
+
## Rules
|
|
416
|
+
|
|
417
|
+
${rules.join("\n")}
|
|
418
|
+
|
|
419
|
+
## Output
|
|
420
|
+
|
|
421
|
+
Write the body content to \`${skillDir}/_SKILL.md\` using the Write tool.
|
|
422
|
+
Do NOT output the content to stdout. Write it to the file only.
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
const FILE_PATTERN_MAP = {
|
|
426
|
+
"vue": ["*.vue"],
|
|
427
|
+
"svelte": ["*.svelte"],
|
|
428
|
+
"astro": ["*.astro"],
|
|
429
|
+
"solid-js": ["*.jsx", "*.tsx"],
|
|
430
|
+
"qwik": ["*.tsx"],
|
|
431
|
+
"marko": ["*.marko"],
|
|
432
|
+
"riot": ["*.riot"],
|
|
433
|
+
"typescript": [
|
|
434
|
+
"*.ts",
|
|
435
|
+
"*.tsx",
|
|
436
|
+
"*.mts",
|
|
437
|
+
"*.cts"
|
|
438
|
+
],
|
|
439
|
+
"coffeescript": ["*.coffee"],
|
|
440
|
+
"livescript": ["*.ls"],
|
|
441
|
+
"elm": ["*.elm"],
|
|
442
|
+
"sass": ["*.scss", "*.sass"],
|
|
443
|
+
"less": ["*.less"],
|
|
444
|
+
"stylus": ["*.styl"],
|
|
445
|
+
"postcss": ["*.css", "*.pcss"],
|
|
446
|
+
"pug": ["*.pug"],
|
|
447
|
+
"ejs": ["*.ejs"],
|
|
448
|
+
"handlebars": ["*.hbs", "*.handlebars"],
|
|
449
|
+
"mustache": ["*.mustache"],
|
|
450
|
+
"nunjucks": ["*.njk"],
|
|
451
|
+
"liquid": ["*.liquid"],
|
|
452
|
+
"yaml": ["*.yaml", "*.yml"],
|
|
453
|
+
"js-yaml": ["*.yaml", "*.yml"],
|
|
454
|
+
"toml": ["*.toml"],
|
|
455
|
+
"@iarna/toml": ["*.toml"],
|
|
456
|
+
"json5": ["*.json5"],
|
|
457
|
+
"jsonc-parser": ["*.jsonc"],
|
|
458
|
+
"markdown-it": ["*.md"],
|
|
459
|
+
"marked": ["*.md"],
|
|
460
|
+
"remark": ["*.md", "*.mdx"],
|
|
461
|
+
"@mdx-js/mdx": ["*.mdx"],
|
|
462
|
+
"graphql": ["*.graphql", "*.gql"],
|
|
463
|
+
"graphql-tag": ["*.graphql", "*.gql"],
|
|
464
|
+
"@graphql-codegen/cli": ["*.graphql", "*.gql"],
|
|
465
|
+
"prisma": ["*.prisma"],
|
|
466
|
+
"@prisma/client": ["*.prisma"],
|
|
467
|
+
"wasm-pack": ["*.wasm"]
|
|
468
|
+
};
|
|
469
|
+
function generateSkillMd(opts) {
|
|
470
|
+
const header = generatePackageHeader(opts);
|
|
471
|
+
const refs = generateReferencesBlock(opts);
|
|
472
|
+
const content = opts.body ? `${header}\n\n${refs}${opts.body}` : `${header}\n\n${refs}`;
|
|
473
|
+
return `${generateFrontmatter(opts)}${content}
|
|
474
|
+
${generateFooter(opts.relatedSkills)}`;
|
|
475
|
+
}
|
|
476
|
+
function formatRelativeDate(isoDate) {
|
|
477
|
+
const date = new Date(isoDate);
|
|
478
|
+
const diffMs = (/* @__PURE__ */ new Date()).getTime() - date.getTime();
|
|
479
|
+
const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
|
|
480
|
+
if (diffDays === 0) return "today";
|
|
481
|
+
if (diffDays === 1) return "yesterday";
|
|
482
|
+
if (diffDays < 7) return `${diffDays} days ago`;
|
|
483
|
+
if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;
|
|
484
|
+
if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago`;
|
|
485
|
+
return `${Math.floor(diffDays / 365)} years ago`;
|
|
486
|
+
}
|
|
487
|
+
function generatePackageHeader({ name, description, version, releasedAt, dependencies, distTags, hasGithub }) {
|
|
488
|
+
const lines = [`# ${name}`];
|
|
489
|
+
if (description) lines.push("", `> ${description}`);
|
|
490
|
+
if (version) {
|
|
491
|
+
const relativeDate = releasedAt ? formatRelativeDate(releasedAt) : "";
|
|
492
|
+
const versionStr = relativeDate ? `${version} (${relativeDate})` : version;
|
|
493
|
+
lines.push("", `**Version:** ${versionStr}`);
|
|
494
|
+
}
|
|
495
|
+
if (dependencies && Object.keys(dependencies).length > 0) {
|
|
496
|
+
const deps = Object.entries(dependencies).map(([n, v]) => `${n}@${v}`).join(", ");
|
|
497
|
+
lines.push(`**Deps:** ${deps}`);
|
|
498
|
+
}
|
|
499
|
+
if (distTags && Object.keys(distTags).length > 0) {
|
|
500
|
+
const tags = Object.entries(distTags).map(([tag, info]) => {
|
|
501
|
+
const relDate = info.releasedAt ? ` (${formatRelativeDate(info.releasedAt)})` : "";
|
|
502
|
+
return `${tag}: ${info.version}${relDate}`;
|
|
503
|
+
}).join(", ");
|
|
504
|
+
lines.push(`**Tags:** ${tags}`);
|
|
505
|
+
}
|
|
506
|
+
if (hasGithub) lines.push(`**GitHub:** \`./.skilld/github/\``);
|
|
507
|
+
return lines.join("\n");
|
|
508
|
+
}
|
|
509
|
+
function generateFrontmatter({ name, version, globs }) {
|
|
510
|
+
const patterns = globs ?? FILE_PATTERN_MAP[name];
|
|
511
|
+
const description = patterns?.length ? `Load skill when working with ${patterns.join(", ")} files or importing from "${name}".` : `Load skill when using anything from the package "${name}".`;
|
|
512
|
+
const lines = [
|
|
513
|
+
"---",
|
|
514
|
+
`name: ${sanitizeName(name)}-skilld`,
|
|
515
|
+
`description: ${description}`
|
|
516
|
+
];
|
|
517
|
+
if (patterns?.length) lines.push(`globs: ${JSON.stringify(patterns)}`);
|
|
518
|
+
if (version) lines.push(`version: "${version}"`);
|
|
519
|
+
lines.push("---", "", "");
|
|
520
|
+
return lines.join("\n");
|
|
521
|
+
}
|
|
522
|
+
function generateReferencesBlock({ name, hasGithub, hasReleases, docsType = "docs", hasShippedDocs = false, pkgFiles = [] }) {
|
|
523
|
+
const lines = [
|
|
524
|
+
"## References",
|
|
525
|
+
"",
|
|
526
|
+
`IMPORTANT: Search all references (semantic and keyword) using \`skilld ${name} -q "<query>"\`.`,
|
|
527
|
+
""
|
|
528
|
+
];
|
|
529
|
+
const fileList = pkgFiles.length ? ` — ${pkgFiles.map((f) => `\`${f}\``).join(", ")}` : "";
|
|
530
|
+
lines.push(`**Package:** \`./.skilld/pkg/\`${fileList}`);
|
|
531
|
+
if (hasShippedDocs) lines.push(`**Docs:** \`./.skilld/pkg/docs/\``);
|
|
532
|
+
else if (docsType === "llms.txt") lines.push(`**Docs:** \`./.skilld/docs/llms.txt\``);
|
|
533
|
+
else if (docsType === "docs") lines.push(`**Docs:** \`./.skilld/docs/\``);
|
|
534
|
+
if (hasGithub) lines.push(`**GitHub:** \`./.skilld/github/\``);
|
|
535
|
+
if (hasReleases) lines.push(`**Releases:** \`./.skilld/releases/\``);
|
|
536
|
+
lines.push("");
|
|
537
|
+
return lines.join("\n");
|
|
538
|
+
}
|
|
539
|
+
function generateFooter(relatedSkills) {
|
|
540
|
+
if (relatedSkills.length === 0) return "";
|
|
541
|
+
return `\nRelated: ${relatedSkills.join(", ")}\n`;
|
|
542
|
+
}
|
|
543
|
+
const CACHE_DIR = join(homedir(), ".skilld", "llm-cache");
|
|
544
|
+
const CLI_MODELS = {
|
|
545
|
+
"opus": {
|
|
546
|
+
cli: "claude",
|
|
547
|
+
model: "opus",
|
|
548
|
+
name: "Opus 4.5",
|
|
549
|
+
hint: "Most capable",
|
|
550
|
+
agentId: "claude-code"
|
|
551
|
+
},
|
|
552
|
+
"sonnet": {
|
|
553
|
+
cli: "claude",
|
|
554
|
+
model: "sonnet",
|
|
555
|
+
name: "Sonnet 4.5",
|
|
556
|
+
hint: "Balanced",
|
|
557
|
+
recommended: true,
|
|
558
|
+
agentId: "claude-code"
|
|
559
|
+
},
|
|
560
|
+
"haiku": {
|
|
561
|
+
cli: "claude",
|
|
562
|
+
model: "haiku",
|
|
563
|
+
name: "Haiku 4.5",
|
|
564
|
+
hint: "Fastest",
|
|
565
|
+
agentId: "claude-code"
|
|
566
|
+
},
|
|
567
|
+
"gemini-2.5-pro": {
|
|
568
|
+
cli: "gemini",
|
|
569
|
+
model: "gemini-2.5-pro",
|
|
570
|
+
name: "Gemini 2.5 Pro",
|
|
571
|
+
hint: "Most capable",
|
|
572
|
+
agentId: "gemini-cli"
|
|
573
|
+
},
|
|
574
|
+
"gemini-2.5-flash": {
|
|
575
|
+
cli: "gemini",
|
|
576
|
+
model: "gemini-2.5-flash",
|
|
577
|
+
name: "Gemini 2.5 Flash",
|
|
578
|
+
hint: "Balanced",
|
|
579
|
+
agentId: "gemini-cli"
|
|
580
|
+
},
|
|
581
|
+
"gemini-2.5-flash-lite": {
|
|
582
|
+
cli: "gemini",
|
|
583
|
+
model: "gemini-2.5-flash-lite",
|
|
584
|
+
name: "Gemini 2.5 Flash Lite",
|
|
585
|
+
hint: "Fastest",
|
|
586
|
+
agentId: "gemini-cli"
|
|
587
|
+
},
|
|
588
|
+
"gemini-3-pro": {
|
|
589
|
+
cli: "gemini",
|
|
590
|
+
model: "gemini-3-pro-preview",
|
|
591
|
+
name: "Gemini 3 Pro",
|
|
592
|
+
hint: "Most capable",
|
|
593
|
+
agentId: "gemini-cli"
|
|
594
|
+
},
|
|
595
|
+
"gemini-3-flash": {
|
|
596
|
+
cli: "gemini",
|
|
597
|
+
model: "gemini-3-flash-preview",
|
|
598
|
+
name: "Gemini 3 Flash",
|
|
599
|
+
hint: "Balanced",
|
|
600
|
+
agentId: "gemini-cli"
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
function getModelName(id) {
|
|
604
|
+
return CLI_MODELS[id]?.name ?? id;
|
|
605
|
+
}
|
|
606
|
+
async function getAvailableModels() {
|
|
607
|
+
const { promisify } = await import("node:util");
|
|
608
|
+
const execAsync = promisify(exec);
|
|
609
|
+
const agentsWithCli = detectInstalledAgents().filter((id) => agents[id].cli);
|
|
610
|
+
const cliChecks = await Promise.all(agentsWithCli.map(async (agentId) => {
|
|
611
|
+
const cli = agents[agentId].cli;
|
|
612
|
+
try {
|
|
613
|
+
await execAsync(`which ${cli}`);
|
|
614
|
+
return agentId;
|
|
615
|
+
} catch {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
}));
|
|
619
|
+
const availableAgentIds = new Set(cliChecks.filter((id) => id != null));
|
|
620
|
+
return Object.entries(CLI_MODELS).filter(([_, config]) => availableAgentIds.has(config.agentId)).map(([id, config]) => ({
|
|
621
|
+
id,
|
|
622
|
+
name: config.name,
|
|
623
|
+
hint: config.hint,
|
|
624
|
+
recommended: config.recommended,
|
|
625
|
+
agentId: config.agentId,
|
|
626
|
+
agentName: agents[config.agentId]?.displayName ?? config.agentId
|
|
627
|
+
}));
|
|
628
|
+
}
|
|
629
|
+
function resolveReferenceDirs(skillDir) {
|
|
630
|
+
const refsDir = join(skillDir, ".skilld");
|
|
631
|
+
if (!existsSync(refsDir)) return [];
|
|
632
|
+
return readdirSync(refsDir).map((entry) => join(refsDir, entry)).filter((p) => lstatSync(p).isSymbolicLink()).map((p) => realpathSync(p));
|
|
633
|
+
}
|
|
634
|
+
function buildCliArgs(cli, model, skillDir) {
|
|
635
|
+
const symlinkDirs = resolveReferenceDirs(skillDir);
|
|
636
|
+
if (cli === "claude") return [
|
|
637
|
+
"-p",
|
|
638
|
+
"--model",
|
|
639
|
+
model,
|
|
640
|
+
"--output-format",
|
|
641
|
+
"stream-json",
|
|
642
|
+
"--verbose",
|
|
643
|
+
"--include-partial-messages",
|
|
644
|
+
"--allowedTools",
|
|
645
|
+
"Read Glob Grep Write",
|
|
646
|
+
"--add-dir",
|
|
647
|
+
skillDir,
|
|
648
|
+
...symlinkDirs.flatMap((d) => ["--add-dir", d]),
|
|
649
|
+
"--dangerously-skip-permissions",
|
|
650
|
+
"--no-session-persistence"
|
|
651
|
+
];
|
|
652
|
+
return [
|
|
653
|
+
"-o",
|
|
654
|
+
"stream-json",
|
|
655
|
+
"-m",
|
|
656
|
+
model,
|
|
657
|
+
"-y",
|
|
658
|
+
"--include-directories",
|
|
659
|
+
skillDir,
|
|
660
|
+
...symlinkDirs.flatMap((d) => ["--include-directories", d])
|
|
661
|
+
];
|
|
662
|
+
}
|
|
663
|
+
function hashPrompt(prompt, model) {
|
|
664
|
+
return createHash("sha256").update(`exec:${model}:${prompt}`).digest("hex").slice(0, 16);
|
|
665
|
+
}
|
|
666
|
+
function getCached(prompt, model, maxAge = 10080 * 60 * 1e3) {
|
|
667
|
+
const path = join(CACHE_DIR, `${hashPrompt(prompt, model)}.json`);
|
|
668
|
+
if (!existsSync(path)) return null;
|
|
669
|
+
try {
|
|
670
|
+
const { text, timestamp } = JSON.parse(readFileSync(path, "utf-8"));
|
|
671
|
+
return Date.now() - timestamp > maxAge ? null : text;
|
|
672
|
+
} catch {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function setCache(prompt, model, text) {
|
|
677
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
678
|
+
writeFileSync(join(CACHE_DIR, `${hashPrompt(prompt, model)}.json`), JSON.stringify({
|
|
679
|
+
text,
|
|
680
|
+
model,
|
|
681
|
+
timestamp: Date.now()
|
|
682
|
+
}));
|
|
683
|
+
}
|
|
684
|
+
function parseClaudeLine(line) {
|
|
685
|
+
try {
|
|
686
|
+
const obj = JSON.parse(line);
|
|
687
|
+
if (obj.type === "stream_event") {
|
|
688
|
+
const evt = obj.event;
|
|
689
|
+
if (!evt) return {};
|
|
690
|
+
if (evt.type === "content_block_delta" && evt.delta?.type === "text_delta") return { textDelta: evt.delta.text };
|
|
691
|
+
if (evt.type === "content_block_start" && evt.content_block?.type === "tool_use") return { toolName: evt.content_block.name };
|
|
692
|
+
return {};
|
|
693
|
+
}
|
|
694
|
+
if (obj.type === "assistant" && obj.message?.content) {
|
|
695
|
+
const content = obj.message.content;
|
|
696
|
+
const tools = content.filter((c) => c.type === "tool_use");
|
|
697
|
+
if (tools.length) {
|
|
698
|
+
const names = tools.map((t) => t.name);
|
|
699
|
+
const hint = tools.map((t) => {
|
|
700
|
+
const input = t.input || {};
|
|
701
|
+
return input.file_path || input.path || input.pattern || input.query || input.command || "";
|
|
702
|
+
}).filter(Boolean).join(", ");
|
|
703
|
+
return {
|
|
704
|
+
toolName: names.join(", "),
|
|
705
|
+
toolHint: hint || void 0
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
const text = content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
709
|
+
if (text) return { fullText: text };
|
|
710
|
+
}
|
|
711
|
+
if (obj.type === "result") {
|
|
712
|
+
const u = obj.usage;
|
|
713
|
+
return {
|
|
714
|
+
done: true,
|
|
715
|
+
usage: u ? {
|
|
716
|
+
input: u.input_tokens ?? u.inputTokens ?? 0,
|
|
717
|
+
output: u.output_tokens ?? u.outputTokens ?? 0
|
|
718
|
+
} : void 0,
|
|
719
|
+
cost: obj.total_cost_usd,
|
|
720
|
+
turns: obj.num_turns
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
} catch {}
|
|
724
|
+
return {};
|
|
725
|
+
}
|
|
726
|
+
function parseGeminiLine(line) {
|
|
727
|
+
try {
|
|
728
|
+
const obj = JSON.parse(line);
|
|
729
|
+
if (obj.type === "message" && obj.role === "assistant" && obj.content) return obj.delta ? { textDelta: obj.content } : { fullText: obj.content };
|
|
730
|
+
if (obj.type === "tool_use" || obj.type === "tool_call") return { toolName: obj.name || obj.tool || "tool" };
|
|
731
|
+
if (obj.type === "result") {
|
|
732
|
+
const s = obj.stats;
|
|
733
|
+
return {
|
|
734
|
+
done: true,
|
|
735
|
+
usage: s ? {
|
|
736
|
+
input: s.input_tokens ?? s.input ?? 0,
|
|
737
|
+
output: s.output_tokens ?? s.output ?? 0
|
|
738
|
+
} : void 0,
|
|
739
|
+
turns: s?.tool_calls
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
} catch {}
|
|
743
|
+
return {};
|
|
744
|
+
}
|
|
745
|
+
async function optimizeDocs(opts) {
|
|
746
|
+
const { packageName, skillDir, model = "sonnet", version, hasGithub, docFiles, onProgress, timeout = 18e4, noCache, sections, customPrompt } = opts;
|
|
747
|
+
const prompt = buildSkillPrompt({
|
|
748
|
+
packageName,
|
|
749
|
+
skillDir,
|
|
750
|
+
version,
|
|
751
|
+
hasGithub,
|
|
752
|
+
docFiles,
|
|
753
|
+
sections,
|
|
754
|
+
customPrompt
|
|
755
|
+
});
|
|
756
|
+
if (!noCache) {
|
|
757
|
+
const cached = getCached(prompt, model);
|
|
758
|
+
if (cached) {
|
|
759
|
+
onProgress?.({
|
|
760
|
+
chunk: "[cached]",
|
|
761
|
+
type: "text",
|
|
762
|
+
text: cached,
|
|
763
|
+
reasoning: ""
|
|
764
|
+
});
|
|
765
|
+
return {
|
|
766
|
+
optimized: cached,
|
|
767
|
+
wasOptimized: true,
|
|
768
|
+
finishReason: "cached"
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
const cliConfig = CLI_MODELS[model];
|
|
773
|
+
if (!cliConfig) return {
|
|
774
|
+
optimized: "",
|
|
775
|
+
wasOptimized: false,
|
|
776
|
+
error: `No CLI mapping for model: ${model}`
|
|
777
|
+
};
|
|
778
|
+
const { cli, model: cliModel } = cliConfig;
|
|
779
|
+
const args = buildCliArgs(cli, cliModel, skillDir);
|
|
780
|
+
const parseLine = cli === "claude" ? parseClaudeLine : parseGeminiLine;
|
|
781
|
+
writeFileSync(join(skillDir, "PROMPT.md"), prompt);
|
|
782
|
+
const outputPath = join(skillDir, "__SKILL.md");
|
|
783
|
+
return new Promise((resolve) => {
|
|
784
|
+
const proc = spawn(cli, args, {
|
|
785
|
+
stdio: [
|
|
786
|
+
"pipe",
|
|
787
|
+
"pipe",
|
|
788
|
+
"pipe"
|
|
789
|
+
],
|
|
790
|
+
timeout,
|
|
791
|
+
env: {
|
|
792
|
+
...process.env,
|
|
793
|
+
NO_COLOR: "1"
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
let buffer = "";
|
|
797
|
+
let usage;
|
|
798
|
+
let cost;
|
|
799
|
+
onProgress?.({
|
|
800
|
+
chunk: "[starting...]",
|
|
801
|
+
type: "reasoning",
|
|
802
|
+
text: "",
|
|
803
|
+
reasoning: ""
|
|
804
|
+
});
|
|
805
|
+
proc.stdin.write(prompt);
|
|
806
|
+
proc.stdin.end();
|
|
807
|
+
proc.stdout.on("data", (chunk) => {
|
|
808
|
+
buffer += chunk.toString();
|
|
809
|
+
const lines = buffer.split("\n");
|
|
810
|
+
buffer = lines.pop() || "";
|
|
811
|
+
for (const line of lines) {
|
|
812
|
+
if (!line.trim()) continue;
|
|
813
|
+
const evt = parseLine(line);
|
|
814
|
+
if (evt.toolName) {
|
|
815
|
+
const hint = evt.toolHint ? `[${evt.toolName}: ${shortenPath(evt.toolHint)}]` : `[${evt.toolName}]`;
|
|
816
|
+
onProgress?.({
|
|
817
|
+
chunk: hint,
|
|
818
|
+
type: "reasoning",
|
|
819
|
+
text: "",
|
|
820
|
+
reasoning: hint
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
if (evt.usage) usage = evt.usage;
|
|
824
|
+
if (evt.cost != null) cost = evt.cost;
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
let stderr = "";
|
|
828
|
+
proc.stderr.on("data", (chunk) => {
|
|
829
|
+
stderr += chunk.toString();
|
|
830
|
+
});
|
|
831
|
+
proc.on("close", (code) => {
|
|
832
|
+
if (buffer.trim()) {
|
|
833
|
+
const evt = parseLine(buffer);
|
|
834
|
+
if (evt.usage) usage = evt.usage;
|
|
835
|
+
if (evt.cost != null) cost = evt.cost;
|
|
836
|
+
}
|
|
837
|
+
const optimized = existsSync(outputPath) ? readFileSync(outputPath, "utf-8").trim() : "";
|
|
838
|
+
if (!optimized && code !== 0) {
|
|
839
|
+
resolve({
|
|
840
|
+
optimized: "",
|
|
841
|
+
wasOptimized: false,
|
|
842
|
+
error: stderr.trim() || `CLI exited with code ${code}`
|
|
843
|
+
});
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (!noCache && optimized) setCache(prompt, model, optimized);
|
|
847
|
+
const usageResult = usage ? {
|
|
848
|
+
inputTokens: usage.input,
|
|
849
|
+
outputTokens: usage.output,
|
|
850
|
+
totalTokens: usage.input + usage.output
|
|
851
|
+
} : void 0;
|
|
852
|
+
resolve({
|
|
853
|
+
optimized,
|
|
854
|
+
wasOptimized: !!optimized,
|
|
855
|
+
finishReason: code === 0 ? "stop" : "error",
|
|
856
|
+
usage: usageResult,
|
|
857
|
+
cost
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
proc.on("error", (err) => {
|
|
861
|
+
resolve({
|
|
862
|
+
optimized: "",
|
|
863
|
+
wasOptimized: false,
|
|
864
|
+
error: err.message
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
function shortenPath(p) {
|
|
870
|
+
const refIdx = p.indexOf(".skilld/");
|
|
871
|
+
if (refIdx !== -1) return p.slice(refIdx + 8);
|
|
872
|
+
const parts = p.split("/");
|
|
873
|
+
return parts.length > 2 ? `.../${parts.slice(-2).join("/")}` : p;
|
|
874
|
+
}
|
|
875
|
+
export { FILE_PATTERN_MAP as a, sanitizeName as c, detectTargetAgent as d, getAgentVersion as f, generateSkillMd as i, detectImportedPackages as l, getModelName as n, buildSkillPrompt as o, agents as p, optimizeDocs as r, installSkillForAgents as s, getAvailableModels as t, detectInstalledAgents as u };
|
|
876
|
+
|
|
877
|
+
//# sourceMappingURL=llm.mjs.map
|