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.
- package/README.md +65 -111
- package/dist/_chunks/config.mjs +1 -2
- package/dist/_chunks/config.mjs.map +1 -1
- package/dist/_chunks/{llm.mjs → detect-imports.mjs} +935 -480
- package/dist/_chunks/detect-imports.mjs.map +1 -0
- package/dist/_chunks/releases.mjs +102 -126
- package/dist/_chunks/releases.mjs.map +1 -1
- package/dist/_chunks/storage.mjs +5 -38
- package/dist/_chunks/storage.mjs.map +1 -1
- package/dist/_chunks/sync-parallel.mjs +4 -4
- package/dist/_chunks/sync-parallel.mjs.map +1 -1
- package/dist/_chunks/utils.d.mts.map +1 -1
- package/dist/_chunks/version.d.mts +5 -36
- package/dist/_chunks/version.d.mts.map +1 -1
- package/dist/agent/index.d.mts +143 -100
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +2 -2
- package/dist/cache/index.d.mts +2 -2
- package/dist/cache/index.mjs +3 -3
- package/dist/cli.mjs +80 -72
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +3 -3
- package/dist/types.d.mts +1 -1
- package/package.json +1 -1
- package/dist/_chunks/llm.mjs.map +0 -1
|
@@ -1,128 +1,527 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
name: "
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
"
|
|
60
|
-
name: "
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
"
|
|
68
|
-
name: "
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
"
|
|
76
|
-
name: "
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
"
|
|
83
|
-
name: "
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
"
|
|
90
|
-
name: "
|
|
91
|
-
|
|
92
|
-
|
|
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(
|
|
515
|
+
return Object.entries(targets).filter(([_, config]) => config.detectInstalled()).map(([type]) => type);
|
|
99
516
|
}
|
|
100
517
|
function detectTargetAgent() {
|
|
101
|
-
|
|
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
|
-
|
|
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 =
|
|
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)}
|
|
1008
|
+
`name: ${dirName ?? sanitizeName(name)}`,
|
|
760
1009
|
`description: ${yamlEscape(desc)}`
|
|
761
1010
|
];
|
|
762
|
-
|
|
763
|
-
if (version)
|
|
764
|
-
if (body && 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
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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
|
-
|
|
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
|
-
|
|
804
|
-
cli: "claude",
|
|
1060
|
+
haiku: {
|
|
805
1061
|
model: "haiku",
|
|
806
1062
|
name: "Haiku 4.5",
|
|
807
|
-
hint: "Fastest for quick answers"
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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 `${
|
|
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) =>
|
|
1306
|
+
const agentsWithCli = detectInstalledAgents().filter((id) => targets[id].cli);
|
|
867
1307
|
const cliChecks = await Promise.all(agentsWithCli.map(async (agentId) => {
|
|
868
|
-
const 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:
|
|
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
|
-
|
|
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
|
|
1068
|
-
const
|
|
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
|
-
|
|
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=
|
|
1838
|
+
//# sourceMappingURL=detect-imports.mjs.map
|