skilld 0.2.0 → 0.3.0

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.
@@ -1,128 +1,527 @@
1
- import { C as repairMarkdown, w as sanitizeMarkdown, x as writeSections, y as readCachedSection } from "./storage.mjs";
1
+ import { _ as writeSections, b as sanitizeMarkdown, h as readCachedSection, y as repairMarkdown } from "./storage.mjs";
2
+ import { createRequire } from "node:module";
2
3
  import { homedir } from "node:os";
3
4
  import { dirname, join } from "pathe";
4
5
  import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, unlinkSync, writeFileSync } from "node:fs";
5
6
  import { exec, spawn, spawnSync } from "node:child_process";
6
7
  import { globby } from "globby";
7
8
  import { findDynamicImports, findStaticImports } from "mlly";
8
- import { readFile } from "node:fs/promises";
9
9
  import { createHash } from "node:crypto";
10
- const home = homedir();
11
- const configHome = process.env.XDG_CONFIG_HOME || join(home, ".config");
12
- const claudeHome = process.env.CLAUDE_CONFIG_DIR || join(home, ".claude");
13
- const codexHome = process.env.CODEX_HOME || join(home, ".codex");
14
- const agents = {
15
- "claude-code": {
16
- name: "claude-code",
17
- displayName: "Claude Code",
18
- skillsDir: ".claude/skills",
19
- globalSkillsDir: join(claudeHome, "skills"),
20
- detectInstalled: () => existsSync(claudeHome),
21
- cli: "claude"
22
- },
23
- "cursor": {
24
- name: "cursor",
25
- displayName: "Cursor",
26
- skillsDir: ".cursor/skills",
27
- globalSkillsDir: join(home, ".cursor/skills"),
28
- detectInstalled: () => existsSync(join(home, ".cursor"))
29
- },
30
- "windsurf": {
31
- name: "windsurf",
32
- displayName: "Windsurf",
33
- skillsDir: ".windsurf/skills",
34
- globalSkillsDir: join(home, ".codeium/windsurf/skills"),
35
- detectInstalled: () => existsSync(join(home, ".codeium/windsurf"))
36
- },
37
- "cline": {
38
- name: "cline",
39
- displayName: "Cline",
40
- skillsDir: ".cline/skills",
41
- globalSkillsDir: join(home, ".cline/skills"),
42
- detectInstalled: () => existsSync(join(home, ".cline"))
43
- },
44
- "codex": {
45
- name: "codex",
46
- displayName: "Codex",
47
- skillsDir: ".codex/skills",
48
- globalSkillsDir: join(codexHome, "skills"),
49
- detectInstalled: () => existsSync(codexHome),
50
- cli: "codex"
51
- },
52
- "github-copilot": {
53
- name: "github-copilot",
54
- displayName: "GitHub Copilot",
55
- skillsDir: ".github/skills",
56
- globalSkillsDir: join(home, ".copilot/skills"),
57
- detectInstalled: () => existsSync(join(home, ".copilot"))
10
+ import { readFile } from "node:fs/promises";
11
+ var __defProp = Object.defineProperty;
12
+ var __exportAll = (all, no_symbols) => {
13
+ let target = {};
14
+ for (var name in all) __defProp(target, name, {
15
+ get: all[name],
16
+ enumerable: true
17
+ });
18
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
19
+ return target;
20
+ };
21
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
22
+ const SPEC_FRONTMATTER = {
23
+ "name": {
24
+ name: "name",
25
+ required: true,
26
+ description: "Skill identifier. Must match parent directory name.",
27
+ constraints: "1-64 chars, lowercase alphanumeric + hyphens"
58
28
  },
59
- "gemini-cli": {
60
- name: "gemini-cli",
61
- displayName: "Gemini CLI",
62
- skillsDir: ".gemini/skills",
63
- globalSkillsDir: join(home, ".gemini/skills"),
64
- detectInstalled: () => existsSync(join(home, ".gemini")),
65
- cli: "gemini"
29
+ "description": {
30
+ name: "description",
31
+ required: true,
32
+ description: "What the skill does and when to use it.",
33
+ constraints: "1-1024 chars"
66
34
  },
67
- "goose": {
68
- name: "goose",
69
- displayName: "Goose",
70
- skillsDir: ".goose/skills",
71
- globalSkillsDir: join(configHome, "goose/skills"),
72
- detectInstalled: () => existsSync(join(configHome, "goose")),
73
- cli: "goose"
35
+ "license": {
36
+ name: "license",
37
+ required: false,
38
+ description: "License reference"
74
39
  },
75
- "amp": {
76
- name: "amp",
77
- displayName: "Amp",
78
- skillsDir: ".agents/skills",
79
- globalSkillsDir: join(configHome, "agents/skills"),
80
- detectInstalled: () => existsSync(join(configHome, "amp"))
40
+ "compatibility": {
41
+ name: "compatibility",
42
+ required: false,
43
+ description: "Environment requirements",
44
+ constraints: "max 500 chars"
81
45
  },
82
- "opencode": {
83
- name: "opencode",
84
- displayName: "OpenCode",
85
- skillsDir: ".opencode/skills",
86
- globalSkillsDir: join(configHome, "opencode/skills"),
87
- detectInstalled: () => existsSync(join(configHome, "opencode"))
46
+ "metadata": {
47
+ name: "metadata",
48
+ required: false,
49
+ description: "Arbitrary key-value pairs"
88
50
  },
89
- "roo": {
90
- name: "roo",
91
- displayName: "Roo Code",
92
- skillsDir: ".roo/skills",
93
- globalSkillsDir: join(home, ".roo/skills"),
94
- detectInstalled: () => existsSync(join(home, ".roo"))
51
+ "allowed-tools": {
52
+ name: "allowed-tools",
53
+ required: false,
54
+ description: "Space-delimited pre-approved tools (experimental)"
95
55
  }
96
56
  };
57
+ const BASE_DEFAULTS = {
58
+ skillFilename: "SKILL.md",
59
+ nameMatchesDir: true,
60
+ namePattern: "^[a-z0-9]+(-[a-z0-9]+)*$",
61
+ additionalSkillsDirs: [],
62
+ extensions: [],
63
+ notes: []
64
+ };
65
+ function defineTarget(target) {
66
+ return {
67
+ ...BASE_DEFAULTS,
68
+ ...target
69
+ };
70
+ }
71
+ const configHome$2 = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
72
+ const amp = defineTarget({
73
+ agent: "amp",
74
+ displayName: "Amp",
75
+ detectInstalled: () => existsSync(join(configHome$2, "amp")),
76
+ detectEnv: () => !!process.env.AMP_SESSION,
77
+ detectProject: (cwd) => existsSync(join(cwd, ".agents", "AGENTS.md")),
78
+ skillsDir: ".agents/skills",
79
+ globalSkillsDir: join(configHome$2, "agents/skills"),
80
+ additionalSkillsDirs: [
81
+ ".claude/skills",
82
+ "~/.config/amp/skills",
83
+ "~/.claude/skills"
84
+ ],
85
+ frontmatter: [{
86
+ ...SPEC_FRONTMATTER.name,
87
+ description: "Unique identifier. Project skills override user-wide ones with same name."
88
+ }, {
89
+ ...SPEC_FRONTMATTER.description,
90
+ description: "Always visible to the model; determines when skill is invoked."
91
+ }],
92
+ discoveryStrategy: "lazy",
93
+ discoveryNotes: "Names + descriptions visible at startup. Full SKILL.md body loads only when agent decides to invoke based on description match.",
94
+ agentSkillsSpec: false,
95
+ extensions: ["mcp.json for bundling MCP server configurations"],
96
+ docs: "https://ampcode.com/news/agent-skills",
97
+ notes: [
98
+ "Reads .claude/skills/ natively — emitting there covers Claude Code, Cursor, Cline, Copilot, AND Amp.",
99
+ "Skills can bundle MCP servers via mcp.json in the skill directory.",
100
+ "AGENTS.md supports @-mentions to reference files (e.g. @doc/style.md, @doc/*.md globs).",
101
+ "AGENTS.md files with globs frontmatter are conditionally included only when Amp reads matching files."
102
+ ]
103
+ });
104
+ const claudeHome = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
105
+ const claudeCode = defineTarget({
106
+ agent: "claude-code",
107
+ displayName: "Claude Code",
108
+ detectInstalled: () => existsSync(claudeHome),
109
+ detectEnv: () => !!(process.env.CLAUDE_CODE || process.env.CLAUDE_CONFIG_DIR),
110
+ detectProject: (cwd) => existsSync(join(cwd, ".claude")) || existsSync(join(cwd, "CLAUDE.md")),
111
+ cli: "claude",
112
+ skillsDir: ".claude/skills",
113
+ globalSkillsDir: join(claudeHome, "skills"),
114
+ frontmatter: [
115
+ {
116
+ ...SPEC_FRONTMATTER.name,
117
+ required: false,
118
+ description: "Skill identifier, becomes /slash-command. Defaults to directory name if omitted.",
119
+ constraints: "1-64 chars, ^[a-z0-9]+(-[a-z0-9]+)*$"
120
+ },
121
+ {
122
+ ...SPEC_FRONTMATTER.description,
123
+ description: "What the skill does and when to use it. Used for auto-discovery matching."
124
+ },
125
+ SPEC_FRONTMATTER.license,
126
+ SPEC_FRONTMATTER.compatibility,
127
+ SPEC_FRONTMATTER.metadata,
128
+ SPEC_FRONTMATTER["allowed-tools"],
129
+ {
130
+ name: "disable-model-invocation",
131
+ required: false,
132
+ description: "When true, skill only loads via explicit /name invocation"
133
+ },
134
+ {
135
+ name: "user-invocable",
136
+ required: false,
137
+ description: "When false, hides from / menu but still auto-loads"
138
+ },
139
+ {
140
+ name: "argument-hint",
141
+ required: false,
142
+ description: "Hint shown during autocomplete, e.g. [issue-number]"
143
+ },
144
+ {
145
+ name: "model",
146
+ required: false,
147
+ description: "Model to use when skill is active"
148
+ },
149
+ {
150
+ name: "context",
151
+ required: false,
152
+ description: "Set to \"fork\" to run in a forked subagent context"
153
+ },
154
+ {
155
+ name: "agent",
156
+ required: false,
157
+ description: "Subagent type when context: fork (e.g. Explore, Plan)"
158
+ }
159
+ ],
160
+ discoveryStrategy: "eager",
161
+ discoveryNotes: "Scans skill dirs at startup, reads name + description only. Full body loads on invocation. Budget: 2% of context window for all skill descriptions.",
162
+ agentSkillsSpec: true,
163
+ extensions: [
164
+ "disable-model-invocation",
165
+ "user-invocable",
166
+ "argument-hint",
167
+ "model",
168
+ "context",
169
+ "agent",
170
+ "hooks",
171
+ "$ARGUMENTS substitution",
172
+ "!`command` dynamic context"
173
+ ],
174
+ docs: "https://code.claude.com/docs/en/skills",
175
+ notes: [
176
+ "`globs` is NOT a valid frontmatter field for skills (only for rules). Unknown fields are silently ignored.",
177
+ "`version` and `generated_by` should go under `metadata` map, not as top-level fields.",
178
+ "Skill descriptions have a char budget of 2% of context window (~16k chars fallback). Override with SLASH_COMMAND_TOOL_CHAR_BUDGET env var.",
179
+ "Keep SKILL.md under 500 lines. Move detailed reference to separate files.",
180
+ "Supports monorepo auto-discovery: nested .claude/skills/ dirs in subdirectories.",
181
+ "Supporting dirs: scripts/, references/, assets/ alongside SKILL.md."
182
+ ]
183
+ });
184
+ const home$5 = homedir();
185
+ const cline = defineTarget({
186
+ agent: "cline",
187
+ displayName: "Cline",
188
+ detectInstalled: () => existsSync(join(home$5, ".cline")),
189
+ detectEnv: () => !!process.env.CLINE_TASK_ID,
190
+ detectProject: (cwd) => existsSync(join(cwd, ".cline")),
191
+ skillsDir: ".cline/skills",
192
+ globalSkillsDir: join(home$5, ".cline/skills"),
193
+ additionalSkillsDirs: [".clinerules/skills", ".claude/skills"],
194
+ frontmatter: [{
195
+ ...SPEC_FRONTMATTER.name,
196
+ description: "Must exactly match the directory name."
197
+ }, {
198
+ ...SPEC_FRONTMATTER.description,
199
+ description: "When to activate. Used for matching."
200
+ }],
201
+ discoveryStrategy: "eager",
202
+ discoveryNotes: "At startup reads name + description from each skill. Full content loads on-demand via use_skill tool. Dozens of skills have near-zero context cost.",
203
+ agentSkillsSpec: false,
204
+ docs: "https://docs.cline.bot/features/skills",
205
+ notes: [
206
+ "Only `name` and `description` are parsed. `version`, `globs`, etc. are silently ignored.",
207
+ "Cline reads .claude/skills/ as a fallback — emitting there covers both Claude Code and Cline.",
208
+ "Rules system (.clinerules/) is separate: always-on behavioral constraints with globs/tags frontmatter.",
209
+ "Global skills override project skills when names conflict.",
210
+ "Supporting dirs: docs/, scripts/, templates/ alongside SKILL.md."
211
+ ]
212
+ });
213
+ const codexHome = process.env.CODEX_HOME || join(homedir(), ".codex");
214
+ const codex = defineTarget({
215
+ agent: "codex",
216
+ displayName: "Codex",
217
+ detectInstalled: () => existsSync(codexHome),
218
+ detectEnv: () => !!(process.env.CODEX_HOME || process.env.CODEX_SESSION),
219
+ detectProject: (cwd) => existsSync(join(cwd, ".codex")),
220
+ cli: "codex",
221
+ skillsDir: ".agents/skills",
222
+ globalSkillsDir: join(homedir(), ".agents/skills"),
223
+ additionalSkillsDirs: ["~/.codex/skills", "/etc/codex/skills"],
224
+ frontmatter: [
225
+ {
226
+ ...SPEC_FRONTMATTER.name,
227
+ description: "Skill identifier.",
228
+ constraints: "1-64 chars, ^[a-z0-9-]+$, no leading/trailing/consecutive hyphens"
229
+ },
230
+ {
231
+ ...SPEC_FRONTMATTER.description,
232
+ description: "Must include when-to-use criteria. Primary triggering mechanism.",
233
+ constraints: "1-1024 chars, no angle brackets (< or >)"
234
+ },
235
+ SPEC_FRONTMATTER.license,
236
+ SPEC_FRONTMATTER["allowed-tools"],
237
+ SPEC_FRONTMATTER.metadata
238
+ ],
239
+ discoveryStrategy: "lazy",
240
+ discoveryNotes: "Startup scan reads name + description + optional agents/openai.yaml. Full body loads only on invocation. Supports $1-$9 and $ARGUMENTS placeholders.",
241
+ agentSkillsSpec: true,
242
+ extensions: [
243
+ "agents/openai.yaml (UI metadata + MCP dependencies)",
244
+ "$1-$9 positional argument placeholders",
245
+ "AGENTS.override.md for temporary overrides"
246
+ ],
247
+ docs: "https://developers.openai.com/codex/skills",
248
+ notes: [
249
+ "BUG IN CURRENT CODE: skillsDir is .codex/skills/ but should be .agents/skills/. The .codex/ directory is for config, not skills.",
250
+ "Description field cannot contain angle brackets (< or >).",
251
+ "Optional agents/openai.yaml provides UI metadata: display_name, icon, brand_color, default_prompt.",
252
+ "AGENTS.md walks from git root to CWD, concatenating all found files.",
253
+ "Live reload: detects skill file changes without restart (v0.95.0+).",
254
+ "Size limit: 32 KiB default (project_doc_max_bytes), configurable in ~/.codex/config.toml."
255
+ ]
256
+ });
257
+ const home$4 = homedir();
258
+ const cursor = defineTarget({
259
+ agent: "cursor",
260
+ displayName: "Cursor",
261
+ detectInstalled: () => existsSync(join(home$4, ".cursor")),
262
+ detectEnv: () => !!(process.env.CURSOR_SESSION || process.env.CURSOR_TRACE_ID),
263
+ detectProject: (cwd) => existsSync(join(cwd, ".cursor")) || existsSync(join(cwd, ".cursorrules")),
264
+ skillsDir: ".cursor/skills",
265
+ globalSkillsDir: join(home$4, ".cursor/skills"),
266
+ additionalSkillsDirs: [
267
+ ".claude/skills",
268
+ ".codex/skills",
269
+ "~/.claude/skills",
270
+ "~/.codex/skills"
271
+ ],
272
+ frontmatter: [
273
+ SPEC_FRONTMATTER.name,
274
+ {
275
+ ...SPEC_FRONTMATTER.description,
276
+ description: "Agent uses this to decide relevance for auto-invocation."
277
+ },
278
+ SPEC_FRONTMATTER.license,
279
+ SPEC_FRONTMATTER.compatibility,
280
+ SPEC_FRONTMATTER.metadata,
281
+ {
282
+ name: "disable-model-invocation",
283
+ required: false,
284
+ description: "When true, only loads via explicit /skill-name"
285
+ }
286
+ ],
287
+ discoveryStrategy: "lazy",
288
+ discoveryNotes: "Reads name + description at conversation start. Full SKILL.md body loads only when agent determines relevance. Users can also invoke via /skill-name.",
289
+ agentSkillsSpec: true,
290
+ extensions: ["disable-model-invocation"],
291
+ docs: "https://cursor.com/docs/context/skills",
292
+ notes: [
293
+ "Cursor scans .claude/skills/ and .codex/skills/ natively — emitting to .claude/skills/ covers both Claude Code and Cursor.",
294
+ "The Rules system (.cursor/rules/*.mdc) is separate and uses different frontmatter (trigger, globs, alwaysApply).",
295
+ "Skills appear in Settings > Rules > Agent Decides section.",
296
+ "Supporting dirs: scripts/, references/, assets/ alongside SKILL.md."
297
+ ]
298
+ });
299
+ const home$3 = homedir();
300
+ const geminiCli = defineTarget({
301
+ agent: "gemini-cli",
302
+ displayName: "Gemini CLI",
303
+ detectInstalled: () => existsSync(join(home$3, ".gemini")),
304
+ detectEnv: () => !!(process.env.GEMINI_API_KEY && process.env.GEMINI_SESSION),
305
+ detectProject: (cwd) => existsSync(join(cwd, ".gemini")) || existsSync(join(cwd, "AGENTS.md")),
306
+ cli: "gemini",
307
+ skillsDir: ".gemini/skills",
308
+ globalSkillsDir: join(home$3, ".gemini/skills"),
309
+ frontmatter: [
310
+ SPEC_FRONTMATTER.name,
311
+ {
312
+ ...SPEC_FRONTMATTER.description,
313
+ description: "Primary trigger — agent uses this to match tasks."
314
+ },
315
+ SPEC_FRONTMATTER.license,
316
+ SPEC_FRONTMATTER.compatibility,
317
+ SPEC_FRONTMATTER.metadata,
318
+ SPEC_FRONTMATTER["allowed-tools"]
319
+ ],
320
+ discoveryStrategy: "eager",
321
+ discoveryNotes: "Scans at session start, injects ~100 tokens per skill (name+description). Activation via activate_skill tool requires user confirmation. Skill stays active for session duration.",
322
+ agentSkillsSpec: true,
323
+ docs: "https://geminicli.com/docs/cli/skills/",
324
+ notes: [
325
+ "Management commands: /skills list, /skills enable <name>, /skills disable <name>, /skills reload.",
326
+ "GEMINI.md context files are separate from skills — support @file.md import syntax.",
327
+ "settings.json can configure additional context filenames: [\"AGENTS.md\", \"CONTEXT.md\", \"GEMINI.md\"].",
328
+ "scripts/, references/, assets/ directories are defined by spec but implementation is still incomplete (issue #15895)."
329
+ ]
330
+ });
331
+ const home$2 = homedir();
332
+ const githubCopilot = defineTarget({
333
+ agent: "github-copilot",
334
+ displayName: "GitHub Copilot",
335
+ detectInstalled: () => existsSync(join(home$2, ".copilot")),
336
+ detectEnv: () => !!process.env.GITHUB_COPILOT_SESSION,
337
+ detectProject: (cwd) => existsSync(join(cwd, ".github", "copilot-instructions.md")),
338
+ skillsDir: ".github/skills",
339
+ globalSkillsDir: join(home$2, ".copilot/skills"),
340
+ additionalSkillsDirs: [".claude/skills", "~/.claude/skills"],
341
+ frontmatter: [
342
+ SPEC_FRONTMATTER.name,
343
+ {
344
+ ...SPEC_FRONTMATTER.description,
345
+ description: "What the skill does AND when to use it."
346
+ },
347
+ SPEC_FRONTMATTER.license,
348
+ SPEC_FRONTMATTER.compatibility,
349
+ {
350
+ ...SPEC_FRONTMATTER.metadata,
351
+ description: "Arbitrary key-value pairs (e.g. version, author)"
352
+ },
353
+ SPEC_FRONTMATTER["allowed-tools"]
354
+ ],
355
+ discoveryStrategy: "lazy",
356
+ discoveryNotes: "3-level progressive disclosure: (1) ~100 tokens for name+description, (2) full SKILL.md body <5000 tokens on activation, (3) resources from scripts/references/assets/ on demand.",
357
+ agentSkillsSpec: true,
358
+ docs: "https://docs.github.com/en/copilot/concepts/agents/about-agent-skills",
359
+ notes: [
360
+ "Copilot auto-detects .claude/skills/ as a legacy path — emitting there covers multiple agents.",
361
+ "Instructions system (.github/instructions/*.instructions.md) is separate, uses applyTo globs.",
362
+ "copilot-instructions.md at .github/ root is always applied (repo-wide).",
363
+ "AGENTS.md also recognized as of Aug 2025.",
364
+ "excludeAgent property in instructions can hide from code-review or coding-agent.",
365
+ "Keep SKILL.md under 500 lines / 5000 tokens for optimal loading."
366
+ ]
367
+ });
368
+ const configHome$1 = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
369
+ const goose = defineTarget({
370
+ agent: "goose",
371
+ displayName: "Goose",
372
+ detectInstalled: () => existsSync(join(configHome$1, "goose")),
373
+ detectEnv: () => !!process.env.GOOSE_SESSION,
374
+ detectProject: (cwd) => existsSync(join(cwd, ".goose")),
375
+ cli: "goose",
376
+ skillsDir: ".goose/skills",
377
+ globalSkillsDir: join(configHome$1, "goose/skills"),
378
+ additionalSkillsDirs: [
379
+ ".claude/skills",
380
+ ".agents/skills",
381
+ "~/.claude/skills",
382
+ "~/.config/agents/skills"
383
+ ],
384
+ frontmatter: [{
385
+ ...SPEC_FRONTMATTER.name,
386
+ description: "Skill identifier."
387
+ }, {
388
+ ...SPEC_FRONTMATTER.description,
389
+ description: "Brief purpose statement; used for matching."
390
+ }],
391
+ discoveryStrategy: "eager",
392
+ discoveryNotes: "Scans all 6 directories at startup, merges discovered skills. Later directories override earlier ones on name conflict.",
393
+ agentSkillsSpec: false,
394
+ docs: "https://block.github.io/goose/docs/guides/context-engineering/using-skills/",
395
+ notes: [
396
+ "Reads .claude/skills/ natively — emitting there covers both Claude Code and Goose.",
397
+ "Also supports .goosehints / .goosehints.local for general project instructions (separate from skills).",
398
+ "Supporting files alongside SKILL.md (scripts, templates, configs) are accessible."
399
+ ]
400
+ });
401
+ const configHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
402
+ const opencode = defineTarget({
403
+ agent: "opencode",
404
+ displayName: "OpenCode",
405
+ detectInstalled: () => existsSync(join(configHome, "opencode")),
406
+ detectEnv: () => !!process.env.OPENCODE_SESSION,
407
+ detectProject: (cwd) => existsSync(join(cwd, ".opencode")),
408
+ skillsDir: ".opencode/skills",
409
+ globalSkillsDir: join(configHome, "opencode/skills"),
410
+ additionalSkillsDirs: [
411
+ ".claude/skills",
412
+ ".agents/skills",
413
+ "~/.claude/skills",
414
+ "~/.agents/skills"
415
+ ],
416
+ frontmatter: [
417
+ {
418
+ ...SPEC_FRONTMATTER.name,
419
+ description: "Must match directory name."
420
+ },
421
+ {
422
+ ...SPEC_FRONTMATTER.description,
423
+ description: "Used for matching."
424
+ },
425
+ SPEC_FRONTMATTER.license,
426
+ SPEC_FRONTMATTER.compatibility,
427
+ SPEC_FRONTMATTER.metadata
428
+ ],
429
+ discoveryStrategy: "eager",
430
+ discoveryNotes: "Walks from CWD to git worktree root, then loads global definitions. Agents access skills via native skill tool. Skills can be permission-controlled per-agent.",
431
+ agentSkillsSpec: true,
432
+ extensions: ["Per-agent skill permissions (allow/deny/ask with glob patterns)"],
433
+ docs: "https://opencode.ai/docs/skills/",
434
+ notes: [
435
+ "Reads .claude/skills/ and .agents/skills/ natively — emitting to .claude/skills/ covers multiple agents.",
436
+ "Custom agents in .opencode/agents/ have rich config: model, temperature, tools, permission, color.",
437
+ "opencode.json supports an instructions field with glob patterns pointing to instruction files.",
438
+ "AGENTS.md (or CLAUDE.md fallback) for general instructions."
439
+ ]
440
+ });
441
+ const home$1 = homedir();
442
+ const roo = defineTarget({
443
+ agent: "roo",
444
+ displayName: "Roo Code",
445
+ detectInstalled: () => existsSync(join(home$1, ".roo")),
446
+ detectEnv: () => !!process.env.ROO_SESSION,
447
+ detectProject: (cwd) => existsSync(join(cwd, ".roo")),
448
+ skillsDir: ".roo/skills",
449
+ globalSkillsDir: join(home$1, ".roo/skills"),
450
+ frontmatter: [{
451
+ ...SPEC_FRONTMATTER.name,
452
+ description: "Must exactly match the directory name."
453
+ }, {
454
+ ...SPEC_FRONTMATTER.description,
455
+ description: "When to activate."
456
+ }],
457
+ discoveryStrategy: "eager",
458
+ discoveryNotes: "Reads all SKILL.md files at startup. File watchers detect changes during session. Uses read_file to load full content on activation.",
459
+ agentSkillsSpec: false,
460
+ extensions: ["Mode-specific skill directories: .roo/skills-{modeSlug}/"],
461
+ docs: "https://docs.roocode.com/features/skills",
462
+ notes: [
463
+ "Does NOT read .claude/skills/ or .agents/skills/ — requires its own .roo/skills/ directory.",
464
+ "Mode-specific dirs: .roo/skills-code/, .roo/skills-architect/ etc. target specific modes.",
465
+ "Override priority: project mode-specific > project generic > global mode-specific > global generic.",
466
+ "Supports symlinks for shared skill libraries across projects.",
467
+ "Rules system (.roo/rules/) is separate — .md/.txt files loaded alphabetically into system prompt.",
468
+ "Legacy fallback: .roorules file if .roo/rules/ is empty.",
469
+ "Skills manageable from Settings panel (v3.46.0+)."
470
+ ]
471
+ });
472
+ const home = homedir();
473
+ const windsurf = defineTarget({
474
+ agent: "windsurf",
475
+ displayName: "Windsurf",
476
+ detectInstalled: () => existsSync(join(home, ".codeium/windsurf")),
477
+ detectEnv: () => !!process.env.WINDSURF_SESSION,
478
+ detectProject: (cwd) => existsSync(join(cwd, ".windsurf")) || existsSync(join(cwd, ".windsurfrules")),
479
+ skillsDir: ".windsurf/skills",
480
+ globalSkillsDir: join(home, ".codeium/windsurf/skills"),
481
+ frontmatter: [{
482
+ ...SPEC_FRONTMATTER.name,
483
+ description: "Skill identifier.",
484
+ constraints: "Lowercase, numbers, hyphens only"
485
+ }, {
486
+ ...SPEC_FRONTMATTER.description,
487
+ description: "Used by Cascade for automatic invocation matching."
488
+ }],
489
+ discoveryStrategy: "eager",
490
+ discoveryNotes: "Cascade matches description against user requests for auto-invocation. Manual invocation via @skill-name.",
491
+ agentSkillsSpec: false,
492
+ docs: "https://docs.windsurf.com/windsurf/cascade/skills",
493
+ notes: [
494
+ "Only `name` and `description` are documented as frontmatter fields. Other fields may be silently ignored.",
495
+ "Rules system is separate: .windsurf/rules/*.md with trigger/globs/alwaysApply frontmatter.",
496
+ "Rules have a 6,000 char per-file limit and 12,000 char total limit. Skills have no documented limit.",
497
+ "Legacy .windsurfrules at project root still supported but deprecated.",
498
+ "Supporting files alongside SKILL.md are loaded via progressive disclosure."
499
+ ]
500
+ });
501
+ const targets = {
502
+ "claude-code": claudeCode,
503
+ "cursor": cursor,
504
+ "windsurf": windsurf,
505
+ "cline": cline,
506
+ "codex": codex,
507
+ "github-copilot": githubCopilot,
508
+ "gemini-cli": geminiCli,
509
+ "goose": goose,
510
+ "amp": amp,
511
+ "opencode": opencode,
512
+ "roo": roo
513
+ };
97
514
  function detectInstalledAgents() {
98
- return Object.entries(agents).filter(([_, config]) => config.detectInstalled()).map(([type]) => type);
515
+ return Object.entries(targets).filter(([_, config]) => config.detectInstalled()).map(([type]) => type);
99
516
  }
100
517
  function detectTargetAgent() {
101
- if (process.env.CLAUDE_CODE || process.env.CLAUDE_CONFIG_DIR) return "claude-code";
102
- if (process.env.CURSOR_SESSION || process.env.CURSOR_TRACE_ID) return "cursor";
103
- if (process.env.WINDSURF_SESSION) return "windsurf";
104
- if (process.env.CLINE_TASK_ID) return "cline";
105
- if (process.env.CODEX_HOME || process.env.CODEX_SESSION) return "codex";
106
- if (process.env.GITHUB_COPILOT_SESSION) return "github-copilot";
107
- if (process.env.GEMINI_API_KEY && process.env.GEMINI_SESSION) return "gemini-cli";
108
- if (process.env.GOOSE_SESSION) return "goose";
109
- if (process.env.AMP_SESSION) return "amp";
110
- if (process.env.OPENCODE_SESSION) return "opencode";
111
- if (process.env.ROO_SESSION) return "roo";
518
+ for (const [type, target] of Object.entries(targets)) if (target.detectEnv()) return type;
112
519
  const cwd = process.cwd();
113
- if (existsSync(join(cwd, ".claude")) || existsSync(join(cwd, "CLAUDE.md"))) return "claude-code";
114
- if (existsSync(join(cwd, ".cursor")) || existsSync(join(cwd, ".cursorrules"))) return "cursor";
115
- if (existsSync(join(cwd, ".windsurf")) || existsSync(join(cwd, ".windsurfrules"))) return "windsurf";
116
- if (existsSync(join(cwd, ".cline"))) return "cline";
117
- if (existsSync(join(cwd, ".codex"))) return "codex";
118
- if (existsSync(join(cwd, ".github", "copilot-instructions.md"))) return "github-copilot";
119
- if (existsSync(join(cwd, ".gemini")) || existsSync(join(cwd, "AGENTS.md"))) return "gemini-cli";
120
- if (existsSync(join(cwd, ".goose"))) return "goose";
121
- if (existsSync(join(cwd, ".roo"))) return "roo";
520
+ for (const [type, target] of Object.entries(targets)) if (target.detectProject(cwd)) return type;
122
521
  return null;
123
522
  }
124
523
  function getAgentVersion(agentType) {
125
- const agent = agents[agentType];
524
+ const agent = targets[agentType];
126
525
  if (!agent.cli) return null;
127
526
  try {
128
527
  const result = spawnSync(agent.cli, ["--version"], {
@@ -142,189 +541,6 @@ function getAgentVersion(agentType) {
142
541
  return null;
143
542
  }
144
543
  }
145
- const NUXT_CONFIG_FILES = [
146
- "nuxt.config.ts",
147
- "nuxt.config.js",
148
- "nuxt.config.mjs"
149
- ];
150
- const NUXT_ECOSYSTEM = [
151
- "vue",
152
- "nitro",
153
- "h3"
154
- ];
155
- async function findNuxtConfig(cwd) {
156
- for (const name of NUXT_CONFIG_FILES) {
157
- const path = join(cwd, name);
158
- const content = await readFile(path, "utf8").catch(() => null);
159
- if (content) return {
160
- path,
161
- content
162
- };
163
- }
164
- return null;
165
- }
166
- function extractModuleStrings(node) {
167
- if (!node || typeof node !== "object") return [];
168
- 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);
169
- const results = [];
170
- if (Array.isArray(node)) for (const child of node) results.push(...extractModuleStrings(child));
171
- else for (const key of Object.keys(node)) {
172
- if (key === "start" || key === "end" || key === "type") continue;
173
- const val = node[key];
174
- if (val && typeof val === "object") results.push(...extractModuleStrings(val));
175
- }
176
- return results;
177
- }
178
- async function detectNuxtModules(cwd) {
179
- const config = await findNuxtConfig(cwd);
180
- if (!config) return [];
181
- const { parseSync } = await import("oxc-parser");
182
- const modules = extractModuleStrings(parseSync(config.path, config.content).program);
183
- const seen = /* @__PURE__ */ new Set();
184
- const packages = [];
185
- for (const mod of modules) if (!seen.has(mod)) {
186
- seen.add(mod);
187
- packages.push({
188
- name: mod,
189
- count: 0,
190
- source: "preset"
191
- });
192
- }
193
- for (const pkg of NUXT_ECOSYSTEM) if (!seen.has(pkg)) {
194
- seen.add(pkg);
195
- packages.push({
196
- name: pkg,
197
- count: 0,
198
- source: "preset"
199
- });
200
- }
201
- return packages;
202
- }
203
- async function detectPresetPackages(cwd) {
204
- return detectNuxtModules(cwd);
205
- }
206
- const PATTERNS = ["**/*.{ts,js,vue,mjs,cjs,tsx,jsx,mts,cts}"];
207
- const IGNORE = [
208
- "**/node_modules/**",
209
- "**/dist/**",
210
- "**/.nuxt/**",
211
- "**/.output/**",
212
- "**/coverage/**"
213
- ];
214
- function addPackage(counts, specifier) {
215
- if (!specifier || specifier.startsWith(".") || specifier.startsWith("/")) return;
216
- const name = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
217
- if (!isNodeBuiltin(name)) counts.set(name, (counts.get(name) || 0) + 1);
218
- }
219
- async function detectImportedPackages(cwd = process.cwd()) {
220
- try {
221
- const counts = /* @__PURE__ */ new Map();
222
- const files = await globby(PATTERNS, {
223
- cwd,
224
- ignore: IGNORE,
225
- gitignore: true,
226
- absolute: true
227
- });
228
- await Promise.all(files.map(async (file) => {
229
- const content = await readFile(file, "utf8");
230
- for (const imp of findStaticImports(content)) addPackage(counts, imp.specifier);
231
- for (const imp of findDynamicImports(content)) {
232
- const match = imp.expression.match(/^['"]([^'"]+)['"]$/);
233
- if (match) addPackage(counts, match[1]);
234
- }
235
- }));
236
- const packages = [...counts.entries()].map(([name, count]) => ({
237
- name,
238
- count,
239
- source: "import"
240
- })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
241
- const presets = await detectPresetPackages(cwd);
242
- const importNames = new Set(packages.map((p) => p.name));
243
- for (const preset of presets) if (!importNames.has(preset.name)) packages.push(preset);
244
- return { packages };
245
- } catch (err) {
246
- return {
247
- packages: [],
248
- error: String(err)
249
- };
250
- }
251
- }
252
- const NODE_BUILTINS = new Set([
253
- "assert",
254
- "buffer",
255
- "child_process",
256
- "cluster",
257
- "console",
258
- "constants",
259
- "crypto",
260
- "dgram",
261
- "dns",
262
- "domain",
263
- "events",
264
- "fs",
265
- "http",
266
- "https",
267
- "module",
268
- "net",
269
- "os",
270
- "path",
271
- "perf_hooks",
272
- "process",
273
- "punycode",
274
- "querystring",
275
- "readline",
276
- "repl",
277
- "stream",
278
- "string_decoder",
279
- "sys",
280
- "timers",
281
- "tls",
282
- "tty",
283
- "url",
284
- "util",
285
- "v8",
286
- "vm",
287
- "wasi",
288
- "worker_threads",
289
- "zlib"
290
- ]);
291
- function isNodeBuiltin(pkg) {
292
- const base = pkg.startsWith("node:") ? pkg.slice(5) : pkg;
293
- return NODE_BUILTINS.has(base.split("/")[0]);
294
- }
295
- function sanitizeName(name) {
296
- return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").slice(0, 255) || "unnamed-skill";
297
- }
298
- function computeSkillDirName(packageName, repoUrl) {
299
- if (repoUrl) {
300
- const match = repoUrl.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:[/#]|$)/);
301
- if (match) return sanitizeName(`${match[1]}-${match[2]}`);
302
- }
303
- return sanitizeName(packageName);
304
- }
305
- function installSkillForAgents(skillName, skillContent, options = {}) {
306
- const isGlobal = options.global ?? false;
307
- const cwd = options.cwd || process.cwd();
308
- const sanitized = sanitizeName(skillName);
309
- const targetAgents = options.agents || detectInstalledAgents();
310
- const installed = [];
311
- const paths = [];
312
- for (const agentType of targetAgents) {
313
- const agent = agents[agentType];
314
- if (isGlobal && !agent.globalSkillsDir) continue;
315
- const skillDir = join(isGlobal ? agent.globalSkillsDir : join(cwd, agent.skillsDir), sanitized);
316
- const skilldDir = join(skillDir, ".skilld");
317
- mkdirSync(skilldDir, { recursive: true });
318
- writeFileSync(join(skilldDir, "_SKILL.md"), sanitizeMarkdown(repairMarkdown(skillContent)));
319
- if (options.files) for (const [filename, content] of Object.entries(options.files)) writeFileSync(join(skillDir, filename), filename.endsWith(".md") ? sanitizeMarkdown(repairMarkdown(content)) : content);
320
- installed.push(agentType);
321
- paths.push(skillDir);
322
- }
323
- return {
324
- installed,
325
- paths
326
- };
327
- }
328
544
  function apiSection({ packageName, hasReleases, hasChangelog }) {
329
545
  const searchHints = [`\`skilld search "added" -p ${packageName}\``, `\`skilld search "new" -p ${packageName}\``];
330
546
  return {
@@ -609,6 +825,39 @@ function yamlParseKV(line) {
609
825
  if (!key) return null;
610
826
  return [key, yamlUnescape(rawValue)];
611
827
  }
828
+ function sanitizeName(name) {
829
+ return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").slice(0, 255) || "unnamed-skill";
830
+ }
831
+ function computeSkillDirName(packageName, repoUrl) {
832
+ if (repoUrl) {
833
+ const match = repoUrl.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:[/#]|$)/);
834
+ if (match) return sanitizeName(`${match[1]}-${match[2]}`);
835
+ }
836
+ return sanitizeName(packageName);
837
+ }
838
+ function installSkillForAgents(skillName, skillContent, options = {}) {
839
+ const isGlobal = options.global ?? false;
840
+ const cwd = options.cwd || process.cwd();
841
+ const sanitized = sanitizeName(skillName);
842
+ const targetAgents = options.agents || detectInstalledAgents();
843
+ const installed = [];
844
+ const paths = [];
845
+ for (const agentType of targetAgents) {
846
+ const agent = targets[agentType];
847
+ if (isGlobal && !agent.globalSkillsDir) continue;
848
+ const skillDir = join(isGlobal ? agent.globalSkillsDir : join(cwd, agent.skillsDir), sanitized);
849
+ const skilldDir = join(skillDir, ".skilld");
850
+ mkdirSync(skilldDir, { recursive: true });
851
+ writeFileSync(join(skilldDir, "_SKILL.md"), sanitizeMarkdown(repairMarkdown(skillContent)));
852
+ if (options.files) for (const [filename, content] of Object.entries(options.files)) writeFileSync(join(skillDir, filename), filename.endsWith(".md") ? sanitizeMarkdown(repairMarkdown(content)) : content);
853
+ installed.push(agentType);
854
+ paths.push(skillDir);
855
+ }
856
+ return {
857
+ installed,
858
+ paths
859
+ };
860
+ }
612
861
  const FILE_PATTERN_MAP = {
613
862
  "vue": ["*.vue"],
614
863
  "svelte": ["*.svelte"],
@@ -756,12 +1005,16 @@ function generateFrontmatter({ name, version, description: pkgDescription, globs
756
1005
  }
757
1006
  const lines = [
758
1007
  "---",
759
- `name: ${dirName ?? sanitizeName(name)}-skilld`,
1008
+ `name: ${dirName ?? sanitizeName(name)}`,
760
1009
  `description: ${yamlEscape(desc)}`
761
1010
  ];
762
- if (patterns?.length) lines.push(`globs: ${JSON.stringify(patterns)}`);
763
- if (version) lines.push(`version: ${yamlEscape(version)}`);
764
- if (body && generatedBy) lines.push(`generated_by: ${yamlEscape(generatedBy)}`);
1011
+ const metaEntries = [];
1012
+ if (version) metaEntries.push(` version: ${yamlEscape(version)}`);
1013
+ if (body && generatedBy) metaEntries.push(` generated_by: ${yamlEscape(generatedBy)}`);
1014
+ if (metaEntries.length) {
1015
+ lines.push("metadata:");
1016
+ lines.push(...metaEntries);
1017
+ }
765
1018
  lines.push("---", "", "");
766
1019
  return lines.join("\n");
767
1020
  }
@@ -783,89 +1036,276 @@ function generateFooter(relatedSkills) {
783
1036
  if (relatedSkills.length === 0) return "";
784
1037
  return `\nRelated: ${relatedSkills.join(", ")}\n`;
785
1038
  }
786
- const CACHE_DIR = join(homedir(), ".skilld", "llm-cache");
787
- const CLI_MODELS = {
788
- "opus": {
789
- cli: "claude",
1039
+ var claude_exports = /* @__PURE__ */ __exportAll({
1040
+ agentId: () => agentId$2,
1041
+ buildArgs: () => buildArgs$2,
1042
+ cli: () => cli$2,
1043
+ models: () => models$2,
1044
+ parseLine: () => parseLine$2
1045
+ });
1046
+ const cli$2 = "claude";
1047
+ const agentId$2 = "claude-code";
1048
+ const models$2 = {
1049
+ opus: {
790
1050
  model: "opus",
791
1051
  name: "Opus 4.6",
792
- hint: "Most capable for complex work",
793
- agentId: "claude-code"
1052
+ hint: "Most capable for complex work"
794
1053
  },
795
- "sonnet": {
796
- cli: "claude",
1054
+ sonnet: {
797
1055
  model: "sonnet",
798
1056
  name: "Sonnet 4.5",
799
1057
  hint: "Best for everyday tasks",
800
- recommended: true,
801
- agentId: "claude-code"
1058
+ recommended: true
802
1059
  },
803
- "haiku": {
804
- cli: "claude",
1060
+ haiku: {
805
1061
  model: "haiku",
806
1062
  name: "Haiku 4.5",
807
- hint: "Fastest for quick answers",
808
- agentId: "claude-code"
809
- },
810
- "gemini-3-pro": {
811
- cli: "gemini",
812
- model: "gemini-3-pro-preview",
813
- name: "Gemini 3 Pro",
814
- hint: "Most capable",
815
- agentId: "gemini-cli"
816
- },
817
- "gemini-3-flash": {
818
- cli: "gemini",
819
- model: "gemini-3-flash-preview",
820
- name: "Gemini 3 Flash",
821
- hint: "Balanced",
822
- recommended: true,
823
- agentId: "gemini-cli"
824
- },
1063
+ hint: "Fastest for quick answers"
1064
+ }
1065
+ };
1066
+ function buildArgs$2(model, skillDir, symlinkDirs) {
1067
+ const skilldDir = join(skillDir, ".skilld");
1068
+ return [
1069
+ "-p",
1070
+ "--model",
1071
+ model,
1072
+ "--output-format",
1073
+ "stream-json",
1074
+ "--verbose",
1075
+ "--include-partial-messages",
1076
+ "--allowedTools",
1077
+ [
1078
+ ...[skillDir, ...symlinkDirs].flatMap((d) => [
1079
+ `Read(${d}/**)`,
1080
+ `Glob(${d}/**)`,
1081
+ `Grep(${d}/**)`
1082
+ ]),
1083
+ `Write(${skilldDir}/**)`,
1084
+ `Bash(*skilld search*)`
1085
+ ].join(" "),
1086
+ "--add-dir",
1087
+ skillDir,
1088
+ ...symlinkDirs.flatMap((d) => ["--add-dir", d]),
1089
+ "--no-session-persistence"
1090
+ ];
1091
+ }
1092
+ function parseLine$2(line) {
1093
+ try {
1094
+ const obj = JSON.parse(line);
1095
+ if (obj.type === "stream_event") {
1096
+ const evt = obj.event;
1097
+ if (!evt) return {};
1098
+ if (evt.type === "content_block_delta" && evt.delta?.type === "text_delta") return { textDelta: evt.delta.text };
1099
+ if (evt.type === "content_block_start" && evt.content_block?.type === "tool_use") return { toolName: evt.content_block.name };
1100
+ return {};
1101
+ }
1102
+ if (obj.type === "assistant" && obj.message?.content) {
1103
+ const content = obj.message.content;
1104
+ const tools = content.filter((c) => c.type === "tool_use");
1105
+ if (tools.length) {
1106
+ const names = tools.map((t) => t.name);
1107
+ const hint = tools.map((t) => {
1108
+ const input = t.input || {};
1109
+ return input.file_path || input.path || input.pattern || input.query || input.command || "";
1110
+ }).filter(Boolean).join(", ");
1111
+ const writeTool = tools.find((t) => t.name === "Write" && t.input?.content);
1112
+ return {
1113
+ toolName: names.join(", "),
1114
+ toolHint: hint || void 0,
1115
+ writeContent: writeTool?.input?.content
1116
+ };
1117
+ }
1118
+ const text = content.filter((c) => c.type === "text").map((c) => c.text).join("");
1119
+ if (text) return { fullText: text };
1120
+ }
1121
+ if (obj.type === "result") {
1122
+ const u = obj.usage;
1123
+ return {
1124
+ done: true,
1125
+ usage: u ? {
1126
+ input: u.input_tokens ?? u.inputTokens ?? 0,
1127
+ output: u.output_tokens ?? u.outputTokens ?? 0
1128
+ } : void 0,
1129
+ cost: obj.total_cost_usd,
1130
+ turns: obj.num_turns
1131
+ };
1132
+ }
1133
+ } catch {}
1134
+ return {};
1135
+ }
1136
+ var codex_exports = /* @__PURE__ */ __exportAll({
1137
+ agentId: () => agentId$1,
1138
+ buildArgs: () => buildArgs$1,
1139
+ cli: () => cli$1,
1140
+ models: () => models$1,
1141
+ parseLine: () => parseLine$1
1142
+ });
1143
+ const cli$1 = "codex";
1144
+ const agentId$1 = "codex";
1145
+ const models$1 = {
825
1146
  "gpt-5.2-codex": {
826
- cli: "codex",
827
1147
  model: "gpt-5.2-codex",
828
1148
  name: "GPT-5.2 Codex",
829
- hint: "Frontier agentic coding model",
830
- agentId: "codex"
1149
+ hint: "Frontier agentic coding model"
831
1150
  },
832
1151
  "gpt-5.1-codex-max": {
833
- cli: "codex",
834
1152
  model: "gpt-5.1-codex-max",
835
1153
  name: "GPT-5.1 Codex Max",
836
- hint: "Codex-optimized flagship",
837
- agentId: "codex"
1154
+ hint: "Codex-optimized flagship"
838
1155
  },
839
1156
  "gpt-5.2": {
840
- cli: "codex",
841
1157
  model: "gpt-5.2",
842
1158
  name: "GPT-5.2",
843
- hint: "Latest frontier model",
844
- agentId: "codex"
1159
+ hint: "Latest frontier model"
845
1160
  },
846
1161
  "gpt-5.1-codex-mini": {
847
- cli: "codex",
848
1162
  model: "gpt-5.1-codex-mini",
849
1163
  name: "GPT-5.1 Codex Mini",
850
1164
  hint: "Optimized for codex, cheaper & faster",
851
- recommended: true,
852
- agentId: "codex"
1165
+ recommended: true
853
1166
  }
854
1167
  };
1168
+ function buildArgs$1(model, skillDir, symlinkDirs) {
1169
+ return [
1170
+ "exec",
1171
+ "--json",
1172
+ "--model",
1173
+ model,
1174
+ "--full-auto",
1175
+ "--writeable-dirs",
1176
+ join(skillDir, ".skilld"),
1177
+ "--add-dir",
1178
+ skillDir,
1179
+ ...symlinkDirs.flatMap((d) => ["--add-dir", d]),
1180
+ "-"
1181
+ ];
1182
+ }
1183
+ function parseLine$1(line) {
1184
+ try {
1185
+ const obj = JSON.parse(line);
1186
+ if (obj.type === "item.completed" && obj.item) {
1187
+ const item = obj.item;
1188
+ if (item.type === "agent_message" && item.text) return { fullText: item.text };
1189
+ if (item.type === "command_execution" && item.aggregated_output) {
1190
+ const cmd = item.command || "";
1191
+ const writeContent = /^cat\s*>|>/.test(cmd) ? item.aggregated_output : void 0;
1192
+ return {
1193
+ toolName: "Bash",
1194
+ toolHint: `(${item.aggregated_output.length} chars output)`,
1195
+ writeContent
1196
+ };
1197
+ }
1198
+ }
1199
+ if (obj.type === "item.started" && obj.item?.type === "command_execution") return {
1200
+ toolName: "Bash",
1201
+ toolHint: obj.item.command
1202
+ };
1203
+ if (obj.type === "turn.completed" && obj.usage) return {
1204
+ done: true,
1205
+ usage: {
1206
+ input: obj.usage.input_tokens ?? 0,
1207
+ output: obj.usage.output_tokens ?? 0
1208
+ }
1209
+ };
1210
+ if (obj.type === "turn.failed" || obj.type === "error") return { done: true };
1211
+ } catch {}
1212
+ return {};
1213
+ }
1214
+ var gemini_exports = /* @__PURE__ */ __exportAll({
1215
+ agentId: () => agentId,
1216
+ buildArgs: () => buildArgs,
1217
+ cli: () => cli,
1218
+ models: () => models,
1219
+ parseLine: () => parseLine
1220
+ });
1221
+ const cli = "gemini";
1222
+ const agentId = "gemini-cli";
1223
+ const models = {
1224
+ "gemini-3-pro": {
1225
+ model: "gemini-3-pro-preview",
1226
+ name: "Gemini 3 Pro",
1227
+ hint: "Most capable"
1228
+ },
1229
+ "gemini-3-flash": {
1230
+ model: "gemini-3-flash-preview",
1231
+ name: "Gemini 3 Flash",
1232
+ hint: "Balanced",
1233
+ recommended: true
1234
+ }
1235
+ };
1236
+ function buildArgs(model, skillDir, symlinkDirs) {
1237
+ return [
1238
+ "-o",
1239
+ "stream-json",
1240
+ "-m",
1241
+ model,
1242
+ "--allowed-tools",
1243
+ "read_file,write_file,glob_tool",
1244
+ "--include-directories",
1245
+ skillDir,
1246
+ ...symlinkDirs.flatMap((d) => ["--include-directories", d])
1247
+ ];
1248
+ }
1249
+ function parseLine(line) {
1250
+ try {
1251
+ const obj = JSON.parse(line);
1252
+ if (obj.type === "message" && obj.role === "assistant" && obj.content) return obj.delta ? { textDelta: obj.content } : { fullText: obj.content };
1253
+ if (obj.type === "tool_use" || obj.type === "tool_call") {
1254
+ const name = obj.tool_name || obj.name || obj.tool || "tool";
1255
+ if (name === "write_file" && obj.args?.content) return {
1256
+ toolName: name,
1257
+ writeContent: obj.args.content
1258
+ };
1259
+ return { toolName: name };
1260
+ }
1261
+ if (obj.type === "result") {
1262
+ const s = obj.stats;
1263
+ return {
1264
+ done: true,
1265
+ usage: s ? {
1266
+ input: s.input_tokens ?? s.input ?? 0,
1267
+ output: s.output_tokens ?? s.output ?? 0
1268
+ } : void 0,
1269
+ turns: s?.tool_calls
1270
+ };
1271
+ }
1272
+ } catch {}
1273
+ return {};
1274
+ }
1275
+ const CLI_DEFS = [
1276
+ claude_exports,
1277
+ gemini_exports,
1278
+ codex_exports
1279
+ ];
1280
+ const CLI_BUILD_ARGS = {
1281
+ claude: buildArgs$2,
1282
+ gemini: buildArgs,
1283
+ codex: buildArgs$1
1284
+ };
1285
+ const CLI_PARSE_LINE = {
1286
+ claude: parseLine$2,
1287
+ gemini: parseLine,
1288
+ codex: parseLine$1
1289
+ };
1290
+ const CLI_MODELS = Object.fromEntries(CLI_DEFS.flatMap((def) => Object.entries(def.models).map(([id, entry]) => [id, {
1291
+ ...entry,
1292
+ cli: def.cli,
1293
+ agentId: def.agentId
1294
+ }])));
855
1295
  function getModelName(id) {
856
1296
  return CLI_MODELS[id]?.name ?? id;
857
1297
  }
858
1298
  function getModelLabel(id) {
859
1299
  const config = CLI_MODELS[id];
860
1300
  if (!config) return id;
861
- return `${agents[config.agentId]?.displayName ?? config.cli} · ${config.name}`;
1301
+ return `${targets[config.agentId]?.displayName ?? config.cli} · ${config.name}`;
862
1302
  }
863
1303
  async function getAvailableModels() {
864
1304
  const { promisify } = await import("node:util");
865
1305
  const execAsync = promisify(exec);
866
- const agentsWithCli = detectInstalledAgents().filter((id) => agents[id].cli);
1306
+ const agentsWithCli = detectInstalledAgents().filter((id) => targets[id].cli);
867
1307
  const cliChecks = await Promise.all(agentsWithCli.map(async (agentId) => {
868
- const cli = agents[agentId].cli;
1308
+ const cli = targets[agentId].cli;
869
1309
  try {
870
1310
  await execAsync(`which ${cli}`);
871
1311
  return agentId;
@@ -880,7 +1320,7 @@ async function getAvailableModels() {
880
1320
  hint: config.hint,
881
1321
  recommended: config.recommended,
882
1322
  agentId: config.agentId,
883
- agentName: agents[config.agentId]?.displayName ?? config.agentId
1323
+ agentName: targets[config.agentId]?.displayName ?? config.agentId
884
1324
  }));
885
1325
  }
886
1326
  function resolveReferenceDirs(skillDir) {
@@ -888,57 +1328,9 @@ function resolveReferenceDirs(skillDir) {
888
1328
  if (!existsSync(refsDir)) return [];
889
1329
  return readdirSync(refsDir).map((entry) => join(refsDir, entry)).filter((p) => lstatSync(p).isSymbolicLink()).map((p) => realpathSync(p));
890
1330
  }
891
- function buildCliArgs(cli, model, skillDir, _outputFile) {
892
- const symlinkDirs = resolveReferenceDirs(skillDir);
893
- if (cli === "claude") {
894
- const skilldDir = join(skillDir, ".skilld");
895
- return [
896
- "-p",
897
- "--model",
898
- model,
899
- "--output-format",
900
- "stream-json",
901
- "--verbose",
902
- "--include-partial-messages",
903
- "--allowedTools",
904
- [
905
- ...[skillDir, ...symlinkDirs].flatMap((d) => [
906
- `Read(${d}/**)`,
907
- `Glob(${d}/**)`,
908
- `Grep(${d}/**)`
909
- ]),
910
- `Write(${skilldDir}/**)`,
911
- `Bash(*skilld search*)`
912
- ].join(" "),
913
- "--add-dir",
914
- skillDir,
915
- ...symlinkDirs.flatMap((d) => ["--add-dir", d]),
916
- "--no-session-persistence"
917
- ];
918
- }
919
- if (cli === "codex") return [
920
- "exec",
921
- "--json",
922
- "--model",
923
- model,
924
- "--full-auto",
925
- ...symlinkDirs.flatMap((d) => ["--add-dir", d]),
926
- "-"
927
- ];
928
- return [
929
- "-o",
930
- "stream-json",
931
- "-m",
932
- model,
933
- "--allowed-tools",
934
- "read_file,write_file,list_directory,glob_tool",
935
- "--include-directories",
936
- skillDir,
937
- ...symlinkDirs.flatMap((d) => ["--include-directories", d])
938
- ];
939
- }
1331
+ const CACHE_DIR = join(homedir(), ".skilld", "llm-cache");
940
1332
  function normalizePromptForHash(prompt) {
941
- return prompt.replace(/\/[^\s`]*\.claude\/skills\/[^\s/`]+/g, "<SKILL_DIR>");
1333
+ return prompt.replace(/\/[^\s`]*\.(?:claude|codex|gemini)\/skills\/[^\s/`]+/g, "<SKILL_DIR>");
942
1334
  }
943
1335
  function hashPrompt(prompt, model, section) {
944
1336
  return createHash("sha256").update(`exec:${model}:${section}:${normalizePromptForHash(prompt)}`).digest("hex").slice(0, 16);
@@ -965,95 +1357,6 @@ function setCache(prompt, model, section, text) {
965
1357
  timestamp: Date.now()
966
1358
  }), { mode: 384 });
967
1359
  }
968
- function parseClaudeLine(line) {
969
- try {
970
- const obj = JSON.parse(line);
971
- if (obj.type === "stream_event") {
972
- const evt = obj.event;
973
- if (!evt) return {};
974
- if (evt.type === "content_block_delta" && evt.delta?.type === "text_delta") return { textDelta: evt.delta.text };
975
- if (evt.type === "content_block_start" && evt.content_block?.type === "tool_use") return { toolName: evt.content_block.name };
976
- return {};
977
- }
978
- if (obj.type === "assistant" && obj.message?.content) {
979
- const content = obj.message.content;
980
- const tools = content.filter((c) => c.type === "tool_use");
981
- if (tools.length) {
982
- const names = tools.map((t) => t.name);
983
- const hint = tools.map((t) => {
984
- const input = t.input || {};
985
- return input.file_path || input.path || input.pattern || input.query || input.command || "";
986
- }).filter(Boolean).join(", ");
987
- const writeTool = tools.find((t) => t.name === "Write" && t.input?.content);
988
- return {
989
- toolName: names.join(", "),
990
- toolHint: hint || void 0,
991
- writeContent: writeTool?.input?.content
992
- };
993
- }
994
- const text = content.filter((c) => c.type === "text").map((c) => c.text).join("");
995
- if (text) return { fullText: text };
996
- }
997
- if (obj.type === "result") {
998
- const u = obj.usage;
999
- return {
1000
- done: true,
1001
- usage: u ? {
1002
- input: u.input_tokens ?? u.inputTokens ?? 0,
1003
- output: u.output_tokens ?? u.outputTokens ?? 0
1004
- } : void 0,
1005
- cost: obj.total_cost_usd,
1006
- turns: obj.num_turns
1007
- };
1008
- }
1009
- } catch {}
1010
- return {};
1011
- }
1012
- function parseGeminiLine(line) {
1013
- try {
1014
- const obj = JSON.parse(line);
1015
- if (obj.type === "message" && obj.role === "assistant" && obj.content) return obj.delta ? { textDelta: obj.content } : { fullText: obj.content };
1016
- if (obj.type === "tool_use" || obj.type === "tool_call") return { toolName: obj.tool_name || obj.name || obj.tool || "tool" };
1017
- if (obj.type === "result") {
1018
- const s = obj.stats;
1019
- return {
1020
- done: true,
1021
- usage: s ? {
1022
- input: s.input_tokens ?? s.input ?? 0,
1023
- output: s.output_tokens ?? s.output ?? 0
1024
- } : void 0,
1025
- turns: s?.tool_calls
1026
- };
1027
- }
1028
- } catch {}
1029
- return {};
1030
- }
1031
- function parseCodexLine(line) {
1032
- try {
1033
- const obj = JSON.parse(line);
1034
- if (obj.type === "item.completed" && obj.item) {
1035
- const item = obj.item;
1036
- if (item.type === "agent_message" && item.text) return { fullText: item.text };
1037
- if (item.type === "command_execution" && item.aggregated_output) return {
1038
- toolName: "Bash",
1039
- toolHint: `(${item.aggregated_output.length} chars output)`
1040
- };
1041
- }
1042
- if (obj.type === "item.started" && obj.item?.type === "command_execution") return {
1043
- toolName: "Bash",
1044
- toolHint: obj.item.command
1045
- };
1046
- if (obj.type === "turn.completed" && obj.usage) return {
1047
- done: true,
1048
- usage: {
1049
- input: obj.usage.input_tokens ?? 0,
1050
- output: obj.usage.output_tokens ?? 0
1051
- }
1052
- };
1053
- if (obj.type === "turn.failed" || obj.type === "error") return { done: true };
1054
- } catch {}
1055
- return {};
1056
- }
1057
1360
  function optimizeSection(opts) {
1058
1361
  const { section, prompt, outputFile, skillDir, model, onProgress, timeout, debug, preExistingFiles } = opts;
1059
1362
  const cliConfig = CLI_MODELS[model];
@@ -1064,14 +1367,16 @@ function optimizeSection(opts) {
1064
1367
  error: `No CLI mapping for model: ${model}`
1065
1368
  });
1066
1369
  const { cli, model: cliModel } = cliConfig;
1067
- const args = buildCliArgs(cli, cliModel, skillDir, outputFile);
1068
- const parseLine = cli === "claude" ? parseClaudeLine : cli === "codex" ? parseCodexLine : parseGeminiLine;
1370
+ const symlinkDirs = resolveReferenceDirs(skillDir);
1371
+ const args = CLI_BUILD_ARGS[cli](cliModel, skillDir, symlinkDirs);
1372
+ const parseLine = CLI_PARSE_LINE[cli];
1069
1373
  const skilldDir = join(skillDir, ".skilld");
1070
1374
  const outputPath = join(skilldDir, outputFile);
1071
1375
  if (existsSync(outputPath)) unlinkSync(outputPath);
1072
1376
  writeFileSync(join(skilldDir, `PROMPT_${section}.md`), prompt);
1073
1377
  return new Promise((resolve) => {
1074
1378
  const proc = spawn(cli, args, {
1379
+ cwd: skilldDir,
1075
1380
  stdio: [
1076
1381
  "pipe",
1077
1382
  "pipe",
@@ -1378,6 +1683,156 @@ function cleanSectionOutput(content) {
1378
1683
  cleaned = sanitizeMarkdown(cleaned);
1379
1684
  return cleaned;
1380
1685
  }
1381
- export { detectImportedPackages as _, generateSkillMd as a, getAgentVersion as b, yamlParseKV as c, SECTION_OUTPUT_FILES as d, buildAllSectionPrompts as f, sanitizeName as g, installSkillForAgents as h, optimizeDocs as i, yamlUnescape as l, computeSkillDirName as m, getModelLabel as n, FILE_PATTERN_MAP as o, buildSectionPrompt as p, getModelName as r, yamlEscape as s, getAvailableModels as t, SECTION_MERGE_ORDER as u, detectInstalledAgents as v, agents as x, detectTargetAgent as y };
1686
+ const NUXT_CONFIG_FILES = [
1687
+ "nuxt.config.ts",
1688
+ "nuxt.config.js",
1689
+ "nuxt.config.mjs"
1690
+ ];
1691
+ const NUXT_ECOSYSTEM = [
1692
+ "vue",
1693
+ "nitro",
1694
+ "h3"
1695
+ ];
1696
+ async function findNuxtConfig(cwd) {
1697
+ for (const name of NUXT_CONFIG_FILES) {
1698
+ const path = join(cwd, name);
1699
+ const content = await readFile(path, "utf8").catch(() => null);
1700
+ if (content) return {
1701
+ path,
1702
+ content
1703
+ };
1704
+ }
1705
+ return null;
1706
+ }
1707
+ function extractModuleStrings(node) {
1708
+ if (!node || typeof node !== "object") return [];
1709
+ 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);
1710
+ const results = [];
1711
+ if (Array.isArray(node)) for (const child of node) results.push(...extractModuleStrings(child));
1712
+ else for (const key of Object.keys(node)) {
1713
+ if (key === "start" || key === "end" || key === "type") continue;
1714
+ const val = node[key];
1715
+ if (val && typeof val === "object") results.push(...extractModuleStrings(val));
1716
+ }
1717
+ return results;
1718
+ }
1719
+ async function detectNuxtModules(cwd) {
1720
+ const config = await findNuxtConfig(cwd);
1721
+ if (!config) return [];
1722
+ const { parseSync } = await import("oxc-parser");
1723
+ const modules = extractModuleStrings(parseSync(config.path, config.content).program);
1724
+ const seen = /* @__PURE__ */ new Set();
1725
+ const packages = [];
1726
+ for (const mod of modules) if (!seen.has(mod)) {
1727
+ seen.add(mod);
1728
+ packages.push({
1729
+ name: mod,
1730
+ count: 0,
1731
+ source: "preset"
1732
+ });
1733
+ }
1734
+ for (const pkg of NUXT_ECOSYSTEM) if (!seen.has(pkg)) {
1735
+ seen.add(pkg);
1736
+ packages.push({
1737
+ name: pkg,
1738
+ count: 0,
1739
+ source: "preset"
1740
+ });
1741
+ }
1742
+ return packages;
1743
+ }
1744
+ async function detectPresetPackages(cwd) {
1745
+ return detectNuxtModules(cwd);
1746
+ }
1747
+ const PATTERNS = ["**/*.{ts,js,vue,mjs,cjs,tsx,jsx,mts,cts}"];
1748
+ const IGNORE = [
1749
+ "**/node_modules/**",
1750
+ "**/dist/**",
1751
+ "**/.nuxt/**",
1752
+ "**/.output/**",
1753
+ "**/coverage/**"
1754
+ ];
1755
+ function addPackage(counts, specifier) {
1756
+ if (!specifier || specifier.startsWith(".") || specifier.startsWith("/")) return;
1757
+ const name = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
1758
+ if (!isNodeBuiltin(name)) counts.set(name, (counts.get(name) || 0) + 1);
1759
+ }
1760
+ async function detectImportedPackages(cwd = process.cwd()) {
1761
+ try {
1762
+ const counts = /* @__PURE__ */ new Map();
1763
+ const files = await globby(PATTERNS, {
1764
+ cwd,
1765
+ ignore: IGNORE,
1766
+ gitignore: true,
1767
+ absolute: true
1768
+ });
1769
+ await Promise.all(files.map(async (file) => {
1770
+ const content = await readFile(file, "utf8");
1771
+ for (const imp of findStaticImports(content)) addPackage(counts, imp.specifier);
1772
+ for (const imp of findDynamicImports(content)) {
1773
+ const match = imp.expression.match(/^['"]([^'"]+)['"]$/);
1774
+ if (match) addPackage(counts, match[1]);
1775
+ }
1776
+ }));
1777
+ const packages = [...counts.entries()].map(([name, count]) => ({
1778
+ name,
1779
+ count,
1780
+ source: "import"
1781
+ })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
1782
+ const presets = await detectPresetPackages(cwd);
1783
+ const importNames = new Set(packages.map((p) => p.name));
1784
+ for (const preset of presets) if (!importNames.has(preset.name)) packages.push(preset);
1785
+ return { packages };
1786
+ } catch (err) {
1787
+ return {
1788
+ packages: [],
1789
+ error: String(err)
1790
+ };
1791
+ }
1792
+ }
1793
+ const NODE_BUILTINS = new Set([
1794
+ "assert",
1795
+ "buffer",
1796
+ "child_process",
1797
+ "cluster",
1798
+ "console",
1799
+ "constants",
1800
+ "crypto",
1801
+ "dgram",
1802
+ "dns",
1803
+ "domain",
1804
+ "events",
1805
+ "fs",
1806
+ "http",
1807
+ "https",
1808
+ "module",
1809
+ "net",
1810
+ "os",
1811
+ "path",
1812
+ "perf_hooks",
1813
+ "process",
1814
+ "punycode",
1815
+ "querystring",
1816
+ "readline",
1817
+ "repl",
1818
+ "stream",
1819
+ "string_decoder",
1820
+ "sys",
1821
+ "timers",
1822
+ "tls",
1823
+ "tty",
1824
+ "url",
1825
+ "util",
1826
+ "v8",
1827
+ "vm",
1828
+ "wasi",
1829
+ "worker_threads",
1830
+ "zlib"
1831
+ ]);
1832
+ function isNodeBuiltin(pkg) {
1833
+ const base = pkg.startsWith("node:") ? pkg.slice(5) : pkg;
1834
+ return NODE_BUILTINS.has(base.split("/")[0]);
1835
+ }
1836
+ export { __require as S, buildSectionPrompt as _, optimizeDocs as a, getAgentVersion as b, computeSkillDirName as c, yamlEscape as d, yamlParseKV as f, buildAllSectionPrompts as g, SECTION_OUTPUT_FILES as h, getModelName as i, installSkillForAgents as l, SECTION_MERGE_ORDER as m, getAvailableModels as n, generateSkillMd as o, yamlUnescape as p, getModelLabel as r, FILE_PATTERN_MAP as s, detectImportedPackages as t, sanitizeName as u, detectInstalledAgents as v, targets as x, detectTargetAgent as y };
1382
1837
 
1383
- //# sourceMappingURL=llm.mjs.map
1838
+ //# sourceMappingURL=detect-imports.mjs.map