@crustjs/skills 0.0.13 → 0.0.15
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 +28 -7
- package/dist/index.d.ts +52 -32
- package/dist/index.js +10 -9
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -60,13 +60,17 @@ runMain(app, {
|
|
|
60
60
|
skillPlugin({
|
|
61
61
|
version: "1.0.0",
|
|
62
62
|
// autoUpdate: true (default) — silently updates installed skills
|
|
63
|
-
// command:
|
|
63
|
+
// command: "skill" (default) — registers "my-cli skill" subcommand
|
|
64
|
+
// defaultScope: "global" | "project" — skip scope prompt when set
|
|
65
|
+
// installMode: "auto" | "symlink" | "copy" (default: "auto")
|
|
64
66
|
}),
|
|
65
67
|
],
|
|
66
68
|
});
|
|
67
69
|
```
|
|
68
70
|
|
|
69
|
-
The plugin automatically updates already-installed skills when the version changes. First-time installation is done via the interactive `skill` subcommand, or programmatically using the exported primitives.
|
|
71
|
+
The plugin automatically updates already-installed skills when the version changes, checking both project and global paths for the current working directory. First-time installation is done via the interactive `skill` subcommand (or `skill update` for update-only flows), or programmatically using the exported primitives.
|
|
72
|
+
|
|
73
|
+
Generated bundles are written once to a canonical store (`.crust/skills` for project scope, `~/.crust/skills` for global scope) and then installed into agent paths via symlink or copy depending on `installMode`.
|
|
70
74
|
|
|
71
75
|
### Programmatic Auto-Install
|
|
72
76
|
|
|
@@ -81,7 +85,7 @@ const app = defineCommand({
|
|
|
81
85
|
meta: { name: "my-cli", description: "My CLI" },
|
|
82
86
|
async run() {
|
|
83
87
|
// Detect agents and install skills if not yet present
|
|
84
|
-
const agents = await detectInstalledAgents(
|
|
88
|
+
const agents = await detectInstalledAgents();
|
|
85
89
|
const status = await skillStatus({ name: "my-cli", agents, scope: "global" });
|
|
86
90
|
|
|
87
91
|
const notInstalled = status.agents
|
|
@@ -107,9 +111,7 @@ runMain(app);
|
|
|
107
111
|
If auto-update does not appear to work:
|
|
108
112
|
|
|
109
113
|
- Ensure plugin is passed to `runMain(..., { plugins: [...] })`.
|
|
110
|
-
- Ensure at least one supported agent is detected
|
|
111
|
-
- `scope: "global"` -> checks supported global agent config roots (for example `~/.claude`, `~/.config/opencode`, `~/.codex`)
|
|
112
|
-
- `scope: "project"` -> checks project roots first (for example `<cwd>/.claude`, `<cwd>/.opencode`) then falls back to global roots
|
|
114
|
+
- Ensure at least one supported agent is detected. Auto-update checks both project and global install paths.
|
|
113
115
|
- Check for existing conflicting skill directories without `crust.json`.
|
|
114
116
|
|
|
115
117
|
## Recommended Export Pattern
|
|
@@ -192,6 +194,7 @@ const result = await generateSkill({
|
|
|
192
194
|
meta: { name: "my-cli", description: "My CLI tool", version: "1.0.0" },
|
|
193
195
|
agents: ["opencode"],
|
|
194
196
|
scope: "project", // default: "global"
|
|
197
|
+
installMode: "auto", // default: "auto" — symlink first, fallback to copy
|
|
195
198
|
clean: true, // default: true — removes existing skill dir first
|
|
196
199
|
force: false, // default: false — throws SkillConflictError if dir exists without crust.json
|
|
197
200
|
});
|
|
@@ -226,6 +229,20 @@ for (const file of files) {
|
|
|
226
229
|
}
|
|
227
230
|
```
|
|
228
231
|
|
|
232
|
+
### `resolveCanonicalSkillPath(scope, name)`
|
|
233
|
+
|
|
234
|
+
Resolves the canonical store path where Crust writes the single source-of-truth skill bundle. Agent install paths are symlinked (or copied) from this location.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { resolveCanonicalSkillPath } from "@crustjs/skills";
|
|
238
|
+
|
|
239
|
+
resolveCanonicalSkillPath("project", "use-my-cli");
|
|
240
|
+
// → "<cwd>/.crust/skills/use-my-cli"
|
|
241
|
+
|
|
242
|
+
resolveCanonicalSkillPath("global", "use-my-cli");
|
|
243
|
+
// → "~/.crust/skills/use-my-cli"
|
|
244
|
+
```
|
|
245
|
+
|
|
229
246
|
### `isValidSkillName(name)`
|
|
230
247
|
|
|
231
248
|
Validates a skill name against the [Agent Skills spec](https://agentskills.io/specification) pattern: 1–64 lowercase alphanumeric characters and hyphens, no leading/trailing/consecutive hyphens.
|
|
@@ -303,6 +320,10 @@ skills/use-my-cli/
|
|
|
303
320
|
|
|
304
321
|
Each skill directory contains a `crust.json` file that acts as an ownership marker. If `generateSkill()` encounters an existing directory without `crust.json`, it throws a `SkillConflictError` to prevent overwriting skills created manually or by other tools.
|
|
305
322
|
|
|
323
|
+
### Uninstall Cleanup
|
|
324
|
+
|
|
325
|
+
When `uninstallSkill()` removes agent install paths, it also checks whether any other agent paths still reference the skill. If no agent installs remain, the canonical store entry (`.crust/skills/<skill>` or `~/.crust/skills/<skill>`) is automatically removed.
|
|
326
|
+
|
|
306
327
|
Pass `force: true` to overwrite, or handle the error:
|
|
307
328
|
|
|
308
329
|
```ts
|
|
@@ -331,7 +352,7 @@ cp -r skills/use-my-cli/ .agents/skills/use-my-cli/
|
|
|
331
352
|
Global install for universal agents:
|
|
332
353
|
|
|
333
354
|
```sh
|
|
334
|
-
cp -r skills/use-my-cli/ ~/.
|
|
355
|
+
cp -r skills/use-my-cli/ ~/.agents/skills/use-my-cli/
|
|
335
356
|
```
|
|
336
357
|
|
|
337
358
|
### Claude Code
|
package/dist/index.d.ts
CHANGED
|
@@ -74,6 +74,8 @@ type AgentTarget = "amp" | "adal" | "antigravity" | "augment" | "claude-code" |
|
|
|
74
74
|
type AgentClass = "universal" | "additional";
|
|
75
75
|
/** Installation scope — global (home directory) or project (cwd). */
|
|
76
76
|
type Scope = "global" | "project";
|
|
77
|
+
/** Installation strategy for agent skill output paths. */
|
|
78
|
+
type SkillInstallMode = "auto" | "symlink" | "copy";
|
|
77
79
|
/**
|
|
78
80
|
* Top-level options for generating a skill bundle from a command tree.
|
|
79
81
|
*
|
|
@@ -105,6 +107,19 @@ interface GenerateOptions {
|
|
|
105
107
|
/** Agent targets to install skills for */
|
|
106
108
|
agents: AgentTarget[];
|
|
107
109
|
/**
|
|
110
|
+
* Installation strategy for agent output paths.
|
|
111
|
+
*
|
|
112
|
+
* - `"auto"` (default): create a symlink to the canonical `.crust/skills`
|
|
113
|
+
* bundle, falling back to a hard copy when symlinks are unavailable.
|
|
114
|
+
* - `"symlink"`: require symlinks; fail if a symlink cannot be created.
|
|
115
|
+
* - `"copy"`: write full copies directly into each agent path.
|
|
116
|
+
*
|
|
117
|
+
* Canonical bundles are always generated once under `.crust/skills` (project)
|
|
118
|
+
* or `~/.crust/skills` (global).
|
|
119
|
+
* @default "auto"
|
|
120
|
+
*/
|
|
121
|
+
installMode?: SkillInstallMode;
|
|
122
|
+
/**
|
|
108
123
|
* Installation scope — global (home directory) or project (cwd).
|
|
109
124
|
* @default "global"
|
|
110
125
|
*/
|
|
@@ -196,11 +211,7 @@ interface StatusResult {
|
|
|
196
211
|
* The plugin reads `name` and `description` from the root command's `meta`
|
|
197
212
|
* at setup time, so only `version` is required here.
|
|
198
213
|
*
|
|
199
|
-
* Installed agents are detected automatically
|
|
200
|
-
*
|
|
201
|
-
* - `scope: "global"` checks `~/.claude/` and `~/.config/opencode/`
|
|
202
|
-
* - `scope: "project"` checks `<cwd>/.claude/` / `<cwd>/.opencode/`, then
|
|
203
|
-
* falls back to global roots when local roots are missing
|
|
214
|
+
* Installed agents are detected automatically.
|
|
204
215
|
*
|
|
205
216
|
* Only detected agents are managed.
|
|
206
217
|
*
|
|
@@ -211,18 +222,32 @@ interface StatusResult {
|
|
|
211
222
|
* build custom auto-install logic with the exported primitives
|
|
212
223
|
* (`detectInstalledAgents`, `skillStatus`, `generateSkill`).
|
|
213
224
|
*
|
|
214
|
-
* **Interactive command** (default): registers a `skill` subcommand
|
|
225
|
+
* **Interactive command** (default): registers a `skill` subcommand (or the
|
|
226
|
+
* custom `command` name) that
|
|
215
227
|
* presents a single multiselect prompt for toggling agent installations.
|
|
216
|
-
*
|
|
228
|
+
*
|
|
229
|
+
* Scope resolution for interactive commands:
|
|
230
|
+
* - If `defaultScope` is set, that scope is used and no scope prompt is shown.
|
|
231
|
+
* - If `defaultScope` is not set and the terminal is interactive, users are
|
|
232
|
+
* prompted to choose `project` or `global`.
|
|
233
|
+
* - If `defaultScope` is not set and the terminal is non-interactive, scope
|
|
234
|
+
* falls back to `"global"`.
|
|
217
235
|
*/
|
|
218
236
|
interface SkillPluginOptions {
|
|
219
237
|
/** Skill version string — compared against the installed crust.json */
|
|
220
238
|
version: string;
|
|
221
239
|
/**
|
|
222
|
-
*
|
|
223
|
-
*
|
|
240
|
+
* Default installation scope for interactive commands.
|
|
241
|
+
*
|
|
242
|
+
* When omitted, interactive commands prompt for scope in TTY mode.
|
|
243
|
+
* Non-interactive mode falls back to "global".
|
|
224
244
|
*/
|
|
225
|
-
|
|
245
|
+
defaultScope?: Scope;
|
|
246
|
+
/**
|
|
247
|
+
* Installation strategy used when the plugin calls `generateSkill()`.
|
|
248
|
+
* @default "auto"
|
|
249
|
+
*/
|
|
250
|
+
installMode?: SkillInstallMode;
|
|
226
251
|
/**
|
|
227
252
|
* Automatically update skills when the installed version is outdated.
|
|
228
253
|
* @default true
|
|
@@ -237,11 +262,9 @@ interface SkillPluginOptions {
|
|
|
237
262
|
* newly selected agents are installed, deselected agents are uninstalled,
|
|
238
263
|
* and already-correct agents are skipped.
|
|
239
264
|
*
|
|
240
|
-
*
|
|
241
|
-
* - `string`: register with a custom command name
|
|
242
|
-
* @default true
|
|
265
|
+
* @default "skill"
|
|
243
266
|
*/
|
|
244
|
-
command?:
|
|
267
|
+
command?: string;
|
|
245
268
|
}
|
|
246
269
|
/** Returns agents that use the canonical `.agents/skills` layout. */
|
|
247
270
|
declare function getUniversalAgents(): AgentTarget[];
|
|
@@ -254,18 +277,22 @@ interface DetectInstalledAgentsOptions {
|
|
|
254
277
|
scope?: Scope;
|
|
255
278
|
/** Kept for backwards compatibility with previous API. */
|
|
256
279
|
home?: string;
|
|
257
|
-
/** Working directory for
|
|
280
|
+
/** Working directory for PATH lookups. */
|
|
258
281
|
cwd?: string;
|
|
259
282
|
/** Test-only hook to override command detection. */
|
|
260
283
|
commandChecker?: (command: string, cwd: string) => Promise<boolean>;
|
|
261
284
|
}
|
|
262
285
|
/**
|
|
263
|
-
* Detects installed additional agents by
|
|
286
|
+
* Detects installed additional agents by checking PATH for their CLI binaries.
|
|
264
287
|
*
|
|
265
288
|
* Universal agents are intentionally not detected here so callers can always
|
|
266
289
|
* present them as a single optional "Universal" install target.
|
|
267
290
|
*/
|
|
268
291
|
declare function detectInstalledAgents(options?: string | DetectInstalledAgentsOptions): Promise<AgentTarget[]>;
|
|
292
|
+
/**
|
|
293
|
+
* Resolves the canonical skill bundle path used by Crust.
|
|
294
|
+
*/
|
|
295
|
+
declare function resolveCanonicalSkillPath(scope: Scope, name: string): string;
|
|
269
296
|
/** Details about the conflict between an existing skill and an incoming one. */
|
|
270
297
|
interface SkillConflictDetails {
|
|
271
298
|
/** The agent where the conflict was detected */
|
|
@@ -326,14 +353,10 @@ declare function resolveSkillName(name: string): string;
|
|
|
326
353
|
/**
|
|
327
354
|
* Generates and installs agent skill bundles from a Crust command tree.
|
|
328
355
|
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
* 3. Checks the installed version — skips if up-to-date
|
|
334
|
-
* 4. Builds a canonical manifest from the command tree
|
|
335
|
-
* 5. Renders markdown files + `crust.json`
|
|
336
|
-
* 6. Writes files to the agent's skill directory
|
|
356
|
+
* The generator renders the bundle once into a canonical Crust store
|
|
357
|
+
* (`.crust/skills` project scope, `~/.crust/skills` global scope), then
|
|
358
|
+
* installs into agent-specific output paths using the configured install mode
|
|
359
|
+
* (`auto`, `symlink`, `copy`).
|
|
337
360
|
*
|
|
338
361
|
* @param options - Generation options including command, metadata, agents, and scope
|
|
339
362
|
* @returns Per-agent installation results
|
|
@@ -381,10 +404,7 @@ import { CrustPlugin } from "@crustjs/core";
|
|
|
381
404
|
* `name` and `description` are read from the root command's `meta` at setup
|
|
382
405
|
* time — only `version` needs to be supplied in the options.
|
|
383
406
|
*
|
|
384
|
-
* Installed agents are detected automatically
|
|
385
|
-
* - `scope: "global"` checks global config roots in the home directory
|
|
386
|
-
* - `scope: "project"` checks project-local config roots in the cwd, then
|
|
387
|
-
* falls back to global roots in the home directory
|
|
407
|
+
* Installed agents are detected automatically.
|
|
388
408
|
*
|
|
389
409
|
* Only detected agents are managed by automatic update and the interactive
|
|
390
410
|
* command.
|
|
@@ -397,13 +417,13 @@ import { CrustPlugin } from "@crustjs/core";
|
|
|
397
417
|
* Detected agents are shown with their current installation status pre-filled.
|
|
398
418
|
* The system reconciles the desired state: newly selected agents are installed,
|
|
399
419
|
* deselected agents are uninstalled, and already-correct agents are skipped.
|
|
400
|
-
*
|
|
420
|
+
* `command` configures the injected command name.
|
|
401
421
|
*
|
|
402
422
|
* For first-time installation, use the interactive command or build custom
|
|
403
423
|
* auto-install logic with the exported primitives (`detectInstalledAgents`,
|
|
404
424
|
* `skillStatus`, `generateSkill`).
|
|
405
425
|
*
|
|
406
|
-
* @param options - Plugin configuration with version and
|
|
426
|
+
* @param options - Plugin configuration with version and defaults
|
|
407
427
|
* @returns A `CrustPlugin` to register in a command's `plugins` array
|
|
408
428
|
*
|
|
409
429
|
* @example
|
|
@@ -414,7 +434,7 @@ import { CrustPlugin } from "@crustjs/core";
|
|
|
414
434
|
* const app = new Crust("my-cli").meta({ description: "My CLI" })
|
|
415
435
|
* .use(skillPlugin({
|
|
416
436
|
* version: "1.0.0",
|
|
417
|
-
* command:
|
|
437
|
+
* command: "skill", // registers "my-cli skill" subcommand
|
|
418
438
|
* }))
|
|
419
439
|
* .run(() => { /* ... */ });
|
|
420
440
|
*
|
|
@@ -422,4 +442,4 @@ import { CrustPlugin } from "@crustjs/core";
|
|
|
422
442
|
* ```
|
|
423
443
|
*/
|
|
424
444
|
declare function skillPlugin(options: SkillPluginOptions): CrustPlugin;
|
|
425
|
-
export { uninstallSkill, skillStatus, skillPlugin, resolveSkillName, isValidSkillName, isUniversalAgent, getUniversalAgents, getAdditionalAgents, generateSkill, detectInstalledAgents, UninstallStatus, UninstallResult, UninstallOptions, StatusResult, StatusOptions, SkillPluginOptions, SkillMeta, SkillConflictError, SkillConflictDetails, Scope, InstallStatus, GenerateResult, GenerateOptions, AgentTarget, AgentResult, AgentClass };
|
|
445
|
+
export { uninstallSkill, skillStatus, skillPlugin, resolveSkillName, resolveCanonicalSkillPath, isValidSkillName, isUniversalAgent, getUniversalAgents, getAdditionalAgents, generateSkill, detectInstalledAgents, UninstallStatus, UninstallResult, UninstallOptions, StatusResult, StatusOptions, SkillPluginOptions, SkillMeta, SkillInstallMode, SkillConflictError, SkillConflictDetails, Scope, InstallStatus, GenerateResult, GenerateOptions, AgentTarget, AgentResult, AgentClass };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{spawn as kQ}from"child_process";import{homedir as XQ}from"os";import{join as $}from"path";var G=$(".agents","skills");function QQ(Q){if(Q!==XQ())return $(Q,".config");let Z=process.env.XDG_CONFIG_HOME?.trim();return Z&&Z.length>0?Z:$(Q,".config")}function U(Q){return $(Q,".agents","skills")}var P={amp:{label:"Amp",class:"universal",projectSkillsDir:G,globalSkillsDir:U},adal:{label:"AdaL",class:"additional",projectSkillsDir:$(".adal","skills"),globalSkillsDir:(Q)=>$(Q,".adal","skills"),detectCommands:["adal"]},antigravity:{label:"Antigravity",class:"additional",projectSkillsDir:$(".agent","skills"),globalSkillsDir:(Q)=>$(Q,".gemini","antigravity","skills"),detectCommands:["antigravity"]},augment:{label:"Augment",class:"additional",projectSkillsDir:$(".augment","skills"),globalSkillsDir:(Q)=>$(Q,".augment","skills"),detectCommands:["augment"]},"claude-code":{label:"Claude Code",class:"additional",projectSkillsDir:$(".claude","skills"),globalSkillsDir:(Q)=>$(process.env.CLAUDE_CONFIG_DIR?.trim()||$(Q,".claude"),"skills"),detectCommands:["claude","claude-code"]},cline:{label:"Cline",class:"universal",projectSkillsDir:G,globalSkillsDir:U},codebuddy:{label:"CodeBuddy",class:"additional",projectSkillsDir:$(".codebuddy","skills"),globalSkillsDir:(Q)=>$(Q,".codebuddy","skills"),detectCommands:["codebuddy"]},codex:{label:"Codex",class:"universal",projectSkillsDir:G,globalSkillsDir:U},"command-code":{label:"Command Code",class:"additional",projectSkillsDir:$(".commandcode","skills"),globalSkillsDir:(Q)=>$(Q,".commandcode","skills"),detectCommands:["command-code","commandcode"]},continue:{label:"Continue",class:"additional",projectSkillsDir:$(".continue","skills"),globalSkillsDir:(Q)=>$(Q,".continue","skills"),detectCommands:["continue"]},cortex:{label:"Cortex Code",class:"additional",projectSkillsDir:$(".cortex","skills"),globalSkillsDir:(Q)=>$(Q,".snowflake","cortex","skills"),detectCommands:["cortex"]},crush:{label:"Crush",class:"additional",projectSkillsDir:$(".crush","skills"),globalSkillsDir:(Q)=>$(QQ(Q),"crush","skills"),detectCommands:["crush"]},cursor:{label:"Cursor",class:"universal",projectSkillsDir:G,globalSkillsDir:U},droid:{label:"Droid",class:"additional",projectSkillsDir:$(".factory","skills"),globalSkillsDir:(Q)=>$(Q,".factory","skills"),detectCommands:["droid"]},"gemini-cli":{label:"Gemini CLI",class:"universal",projectSkillsDir:G,globalSkillsDir:U},"github-copilot":{label:"GitHub Copilot",class:"universal",projectSkillsDir:G,globalSkillsDir:U},goose:{label:"Goose",class:"additional",projectSkillsDir:$(".goose","skills"),globalSkillsDir:(Q)=>$(QQ(Q),"goose","skills"),detectCommands:["goose"]},"iflow-cli":{label:"iFlow CLI",class:"additional",projectSkillsDir:$(".iflow","skills"),globalSkillsDir:(Q)=>$(Q,".iflow","skills"),detectCommands:["iflow","iflow-cli"]},junie:{label:"Junie",class:"additional",projectSkillsDir:$(".junie","skills"),globalSkillsDir:(Q)=>$(Q,".junie","skills"),detectCommands:["junie"]},kilo:{label:"Kilo Code",class:"additional",projectSkillsDir:$(".kilocode","skills"),globalSkillsDir:(Q)=>$(Q,".kilocode","skills"),detectCommands:["kilo","kilocode"]},"kimi-cli":{label:"Kimi Code CLI",class:"universal",projectSkillsDir:G,globalSkillsDir:U},"kiro-cli":{label:"Kiro CLI",class:"additional",projectSkillsDir:$(".kiro","skills"),globalSkillsDir:(Q)=>$(Q,".kiro","skills"),detectCommands:["kiro","kiro-cli"]},kode:{label:"Kode",class:"additional",projectSkillsDir:$(".kode","skills"),globalSkillsDir:(Q)=>$(Q,".kode","skills"),detectCommands:["kode"]},mcpjam:{label:"MCPJam",class:"additional",projectSkillsDir:$(".mcpjam","skills"),globalSkillsDir:(Q)=>$(Q,".mcpjam","skills"),detectCommands:["mcpjam"]},"mistral-vibe":{label:"Mistral Vibe",class:"additional",projectSkillsDir:$(".vibe","skills"),globalSkillsDir:(Q)=>$(Q,".vibe","skills"),detectCommands:["mistral-vibe","vibe"]},mux:{label:"Mux",class:"additional",projectSkillsDir:$(".mux","skills"),globalSkillsDir:(Q)=>$(Q,".mux","skills"),detectCommands:["mux"]},neovate:{label:"Neovate",class:"additional",projectSkillsDir:$(".neovate","skills"),globalSkillsDir:(Q)=>$(Q,".neovate","skills"),detectCommands:["neovate"]},opencode:{label:"OpenCode",class:"universal",projectSkillsDir:G,globalSkillsDir:U},openclaw:{label:"OpenClaw",class:"additional",projectSkillsDir:"skills",globalSkillsDir:(Q)=>$(Q,".openclaw","skills"),detectCommands:["openclaw"]},openhands:{label:"OpenHands",class:"additional",projectSkillsDir:$(".openhands","skills"),globalSkillsDir:(Q)=>$(Q,".openhands","skills"),detectCommands:["openhands"]},pi:{label:"Pi",class:"additional",projectSkillsDir:$(".pi","skills"),globalSkillsDir:(Q)=>$(Q,".pi","agent","skills"),detectCommands:["pi"]},pochi:{label:"Pochi",class:"additional",projectSkillsDir:$(".pochi","skills"),globalSkillsDir:(Q)=>$(Q,".pochi","skills"),detectCommands:["pochi"]},qoder:{label:"Qoder",class:"additional",projectSkillsDir:$(".qoder","skills"),globalSkillsDir:(Q)=>$(Q,".qoder","skills"),detectCommands:["qoder"]},"qwen-code":{label:"Qwen Code",class:"additional",projectSkillsDir:$(".qwen","skills"),globalSkillsDir:(Q)=>$(Q,".qwen","skills"),detectCommands:["qwen","qwen-code"]},replit:{label:"Replit",class:"universal",projectSkillsDir:G,globalSkillsDir:U},roo:{label:"Roo Code",class:"additional",projectSkillsDir:$(".roo","skills"),globalSkillsDir:(Q)=>$(Q,".roo","skills"),detectCommands:["roo","roo-code"]},trae:{label:"Trae",class:"additional",projectSkillsDir:$(".trae","skills"),globalSkillsDir:(Q)=>$(Q,".trae","skills"),detectCommands:["trae"]},"trae-cn":{label:"Trae CN",class:"additional",projectSkillsDir:$(".trae","skills"),globalSkillsDir:(Q)=>$(Q,".trae-cn","skills"),detectCommands:["trae-cn","trae"]},windsurf:{label:"Windsurf",class:"additional",projectSkillsDir:$(".windsurf","skills"),globalSkillsDir:(Q)=>$(Q,".codeium","windsurf","skills"),detectCommands:["windsurf"]},zencoder:{label:"Zencoder",class:"additional",projectSkillsDir:$(".zencoder","skills"),globalSkillsDir:(Q)=>$(Q,".zencoder","skills"),detectCommands:["zencoder"]}},s=Object.keys(P),k=Object.fromEntries(s.map((Q)=>[Q,P[Q].label]));function f(){return s.filter((Q)=>P[Q].class==="universal")}function h(){return s.filter((Q)=>P[Q].class==="additional")}function yQ(Q){return P[Q].class==="universal"}async function p(Q){let Z=typeof Q==="string"?{home:Q}:Q??{},X=Z.cwd??process.cwd(),W=Z.commandChecker??PQ,H=[];for(let q of h()){let B=P[q].detectCommands??[],x=!1;for(let z of B)if(await W(z,X)){x=!0;break}if(x)H.push(q)}return H}function u(Q,Z,X){let W=P[Q];if(Z==="project")return $(process.cwd(),W.projectSkillsDir,X);return $(W.globalSkillsDir(XQ()),X)}async function PQ(Q,Z){for(let X of[["--version"],["-v"],["version"]])if(await bQ(Q,X,Z))return!0;return!1}async function bQ(Q,Z,X){return new Promise((W)=>{let H=kQ(Q,Z,{cwd:X,stdio:"ignore",shell:!1}),q=!1,B=(z)=>{if(q)return;q=!0,W(z)},x=setTimeout(()=>{H.kill("SIGTERM"),B(!1)},1000);H.once("error",()=>{clearTimeout(x),B(!1)}),H.once("exit",(z)=>{clearTimeout(x),B(z===0)})})}class b extends Error{name="SkillConflictError";details;constructor(Q){let Z=`Skill conflict for agent "${Q.agent}": directory "${Q.outputDir}" already exists but was not created by Crust (no crust.json found). Delete or rename the conflicting skill to resolve.`;super(Z);this.details=Q}}import{access as MQ,mkdir as gQ,rm as wQ,writeFile as mQ}from"fs/promises";import{dirname as dQ,join as OQ}from"path";function ZQ(Q){return WQ(Q,[])}function WQ(Q,Z){let X=TQ(Q.meta.name),W=[...Z,X],H=CQ(Q.args),q=_Q(Q.effectiveFlags),B=DQ(Q.subCommands,W);return{name:X,path:W,description:Q.meta.description,usage:Q.meta.usage,runnable:typeof Q.run==="function",args:H,flags:q,children:B}}function TQ(Q){return Q.trim().toLowerCase()}function CQ(Q){if(!Q||Q.length===0)return[];return Q.map(IQ)}function IQ(Q){let Z={name:Q.name,type:Q.type,required:Q.required===!0,variadic:Q.variadic===!0};if(Q.description!==void 0)Z.description=Q.description;if(Q.default!==void 0)Z.default=$Q(Q.default);return Z}function _Q(Q){if(!Q)return[];return Object.keys(Q).sort().map((X)=>{return EQ(X,Q[X])})}function EQ(Q,Z){let X={name:Q,type:Z.type,required:Z.required===!0,multiple:Z.multiple===!0,short:Z.short,aliases:Z.aliases?[...Z.aliases].sort():[]};if(Z.description!==void 0)X.description=Z.description;if(Z.default!==void 0)X.default=$Q(Z.default);return X}function DQ(Q,Z){return Object.keys(Q).sort().map((W)=>{return WQ(Q[W],Z)})}function $Q(Q){if(Array.isArray(Q))return JSON.stringify(Q);return String(Q)}function N(Q){if(/[:#[\]{}&*!|>'"`,@?\\]|^\s|\s$|^---|[\n\r]/.test(Q))return`"${Q.replace(/\\/g,"\\\\").replace(/"/g,"\\\"").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}"`;return Q}function HQ(Q){return Q.replace(/(?<!\\)\|/g,"\\|")}function qQ(Q,Z){let X=[],W=YQ(Q);X.push({path:"SKILL.md",content:LQ(Q,Z,W)});for(let H of W){let q=T(H),B=H.children.length>0?vQ(H,Q):SQ(H,Q);X.push({path:q,content:B})}return X}function YQ(Q){let Z=[Q];for(let X of Q.children)Z.push(...YQ(X));return Z}function T(Q){if(Q.path.length<=1)return`commands/${Q.name}.md`;return`commands/${Q.path.slice(1).join("/")}.md`}function c(Q){return Q.path.join(" ")}function t(Q,Z){let X=Q.split("/").slice(0,-1),W=Z.split("/"),H=0;while(H<X.length&&H<W.length&&X[H]===W[H])H++;let q=X.length-H,B=W.slice(H);if(q===0)return B.join("/");return[...Array.from({length:q},()=>".."),...B].join("/")}function LQ(Q,Z,X){let W=[];if(W.push("---"),W.push(`name: ${N(Z.name)}`),W.push(`description: ${N(Z.description)}`),Z.license)W.push(`license: ${N(Z.license)}`);if(Z.compatibility)W.push(`compatibility: ${N(Z.compatibility)}`);if(Z.disableModelInvocation)W.push("disable-model-invocation: true");if(Z.allowedTools)W.push(`allowed-tools: ${N(Z.allowedTools)}`);if(W.push("metadata:"),W.push(` version: "${Z.version}"`),W.push("---"),W.push(""),W.push(`# ${Z.name}`),W.push(""),Q.description)W.push(Q.description),W.push("");let H=Z.name.startsWith("use-")?Z.name.slice(4):Z.name;if(W.push(`Use this skill when working with \`${H}\` commands, or when you need help with \`${H}\` syntax, flags, or subcommands.`),W.push(""),W.push("## Command Reference"),W.push(""),W.push("This table lists all commands and their documentation paths. **Do not read all command files at once.** Instead:"),W.push(""),W.push("1. Use the table below to find the relevant command"),W.push("2. Use the `Type` column to choose what to execute: commands labeled `runnable` (including `runnable, group`) are executable, while `group` commands are not"),W.push("3. Read only the specific file from the `commands/` directory that you need"),W.push(""),W.push(...jQ(X)),W.push(""),Q.runnable){W.push("## Usage"),W.push("");let q=T(Q);W.push(`The root command is directly executable. See [${Q.name}](${q}) for usage details.`),W.push("")}return W.join(`
|
|
3
|
-
`)}function
|
|
4
|
-
`)}function
|
|
5
|
-
`)}function
|
|
6
|
-
`}async function
|
|
7
|
-
${
|
|
8
|
-
${
|
|
9
|
-
Skipped ${
|
|
10
|
-
${
|
|
2
|
+
import{accessSync as fQ,constants as hQ}from"fs";import{homedir as n}from"os";import{delimiter as uQ,join as B}from"path";var y=B(".agents","skills"),cQ=B(".crust","skills");function YQ(Q){if(Q!==n())return B(Q,".config");let X=process.env.XDG_CONFIG_HOME?.trim();return X&&X.length>0?X:B(Q,".config")}function k(Q){return B(Q,".agents","skills")}function pQ(Q){return B(Q,".crust","skills")}var E={amp:{label:"Amp",class:"universal",projectSkillsDir:y,globalSkillsDir:k},adal:{label:"AdaL",class:"additional",projectSkillsDir:B(".adal","skills"),globalSkillsDir:(Q)=>B(Q,".adal","skills"),detectCommands:["adal"]},antigravity:{label:"Antigravity",class:"additional",projectSkillsDir:B(".agent","skills"),globalSkillsDir:(Q)=>B(Q,".gemini","antigravity","skills"),detectCommands:["antigravity"]},augment:{label:"Augment",class:"additional",projectSkillsDir:B(".augment","skills"),globalSkillsDir:(Q)=>B(Q,".augment","skills"),detectCommands:["augment"]},"claude-code":{label:"Claude Code",class:"additional",projectSkillsDir:B(".claude","skills"),globalSkillsDir:(Q)=>B(process.env.CLAUDE_CONFIG_DIR?.trim()||B(Q,".claude"),"skills"),detectCommands:["claude","claude-code"]},cline:{label:"Cline",class:"universal",projectSkillsDir:y,globalSkillsDir:k},codebuddy:{label:"CodeBuddy",class:"additional",projectSkillsDir:B(".codebuddy","skills"),globalSkillsDir:(Q)=>B(Q,".codebuddy","skills"),detectCommands:["codebuddy"]},codex:{label:"Codex",class:"universal",projectSkillsDir:y,globalSkillsDir:k},"command-code":{label:"Command Code",class:"additional",projectSkillsDir:B(".commandcode","skills"),globalSkillsDir:(Q)=>B(Q,".commandcode","skills"),detectCommands:["command-code","commandcode"]},continue:{label:"Continue",class:"additional",projectSkillsDir:B(".continue","skills"),globalSkillsDir:(Q)=>B(Q,".continue","skills"),detectCommands:["continue"]},cortex:{label:"Cortex Code",class:"additional",projectSkillsDir:B(".cortex","skills"),globalSkillsDir:(Q)=>B(Q,".snowflake","cortex","skills"),detectCommands:["cortex"]},crush:{label:"Crush",class:"additional",projectSkillsDir:B(".crush","skills"),globalSkillsDir:(Q)=>B(YQ(Q),"crush","skills"),detectCommands:["crush"]},cursor:{label:"Cursor",class:"universal",projectSkillsDir:y,globalSkillsDir:k},droid:{label:"Droid",class:"additional",projectSkillsDir:B(".factory","skills"),globalSkillsDir:(Q)=>B(Q,".factory","skills"),detectCommands:["droid"]},"gemini-cli":{label:"Gemini CLI",class:"universal",projectSkillsDir:y,globalSkillsDir:k},"github-copilot":{label:"GitHub Copilot",class:"universal",projectSkillsDir:y,globalSkillsDir:k},goose:{label:"Goose",class:"additional",projectSkillsDir:B(".goose","skills"),globalSkillsDir:(Q)=>B(YQ(Q),"goose","skills"),detectCommands:["goose"]},"iflow-cli":{label:"iFlow CLI",class:"additional",projectSkillsDir:B(".iflow","skills"),globalSkillsDir:(Q)=>B(Q,".iflow","skills"),detectCommands:["iflow","iflow-cli"]},junie:{label:"Junie",class:"additional",projectSkillsDir:B(".junie","skills"),globalSkillsDir:(Q)=>B(Q,".junie","skills"),detectCommands:["junie"]},kilo:{label:"Kilo Code",class:"additional",projectSkillsDir:B(".kilocode","skills"),globalSkillsDir:(Q)=>B(Q,".kilocode","skills"),detectCommands:["kilo","kilocode"]},"kimi-cli":{label:"Kimi Code CLI",class:"universal",projectSkillsDir:y,globalSkillsDir:k},"kiro-cli":{label:"Kiro CLI",class:"additional",projectSkillsDir:B(".kiro","skills"),globalSkillsDir:(Q)=>B(Q,".kiro","skills"),detectCommands:["kiro","kiro-cli"]},kode:{label:"Kode",class:"additional",projectSkillsDir:B(".kode","skills"),globalSkillsDir:(Q)=>B(Q,".kode","skills"),detectCommands:["kode"]},mcpjam:{label:"MCPJam",class:"additional",projectSkillsDir:B(".mcpjam","skills"),globalSkillsDir:(Q)=>B(Q,".mcpjam","skills"),detectCommands:["mcpjam"]},"mistral-vibe":{label:"Mistral Vibe",class:"additional",projectSkillsDir:B(".vibe","skills"),globalSkillsDir:(Q)=>B(Q,".vibe","skills"),detectCommands:["mistral-vibe","vibe"]},mux:{label:"Mux",class:"additional",projectSkillsDir:B(".mux","skills"),globalSkillsDir:(Q)=>B(Q,".mux","skills"),detectCommands:["mux"]},neovate:{label:"Neovate",class:"additional",projectSkillsDir:B(".neovate","skills"),globalSkillsDir:(Q)=>B(Q,".neovate","skills"),detectCommands:["neovate"]},opencode:{label:"OpenCode",class:"universal",projectSkillsDir:y,globalSkillsDir:k},openclaw:{label:"OpenClaw",class:"additional",projectSkillsDir:"skills",globalSkillsDir:(Q)=>B(Q,".openclaw","skills"),detectCommands:["openclaw"]},openhands:{label:"OpenHands",class:"additional",projectSkillsDir:B(".openhands","skills"),globalSkillsDir:(Q)=>B(Q,".openhands","skills"),detectCommands:["openhands"]},pi:{label:"Pi",class:"additional",projectSkillsDir:B(".pi","skills"),globalSkillsDir:(Q)=>B(Q,".pi","agent","skills"),detectCommands:["pi"]},pochi:{label:"Pochi",class:"additional",projectSkillsDir:B(".pochi","skills"),globalSkillsDir:(Q)=>B(Q,".pochi","skills"),detectCommands:["pochi"]},qoder:{label:"Qoder",class:"additional",projectSkillsDir:B(".qoder","skills"),globalSkillsDir:(Q)=>B(Q,".qoder","skills"),detectCommands:["qoder"]},"qwen-code":{label:"Qwen Code",class:"additional",projectSkillsDir:B(".qwen","skills"),globalSkillsDir:(Q)=>B(Q,".qwen","skills"),detectCommands:["qwen","qwen-code"]},replit:{label:"Replit",class:"universal",projectSkillsDir:y,globalSkillsDir:k},roo:{label:"Roo Code",class:"additional",projectSkillsDir:B(".roo","skills"),globalSkillsDir:(Q)=>B(Q,".roo","skills"),detectCommands:["roo","roo-code"]},trae:{label:"Trae",class:"additional",projectSkillsDir:B(".trae","skills"),globalSkillsDir:(Q)=>B(Q,".trae","skills"),detectCommands:["trae"]},"trae-cn":{label:"Trae CN",class:"additional",projectSkillsDir:B(".trae","skills"),globalSkillsDir:(Q)=>B(Q,".trae-cn","skills"),detectCommands:["trae-cn","trae"]},windsurf:{label:"Windsurf",class:"additional",projectSkillsDir:B(".windsurf","skills"),globalSkillsDir:(Q)=>B(Q,".codeium","windsurf","skills"),detectCommands:["windsurf"]},zencoder:{label:"Zencoder",class:"additional",projectSkillsDir:B(".zencoder","skills"),globalSkillsDir:(Q)=>B(Q,".zencoder","skills"),detectCommands:["zencoder"]}},p=Object.keys(E),j=Object.fromEntries(p.map((Q)=>[Q,E[Q].label]));function P(){return p.filter((Q)=>E[Q].class==="universal")}function D(){return p.filter((Q)=>E[Q].class==="additional")}function gQ(Q){return E[Q].class==="universal"}async function e(Q){let X=typeof Q==="string"?{home:Q}:Q??{},Z=X.cwd??process.cwd(),$=X.commandChecker??((W)=>Promise.resolve(mQ(W))),H=[];for(let W of D()){let z=E[W].detectCommands??[],J=!1;for(let q of z)if(await $(q,Z)){J=!0;break}if(J)H.push(W)}return H}function v(Q,X,Z){let $=E[Q];if(X==="project")return B(process.cwd(),$.projectSkillsDir,Z);return B($.globalSkillsDir(n()),Z)}function s(Q,X){if(Q==="project")return B(process.cwd(),cQ,X);return B(pQ(n()),X)}function mQ(Q){let Z=(process.env.PATH??"").split(uQ).filter((W)=>W.length>0),$=process.platform==="win32",H=$?(process.env.PATHEXT??".EXE;.CMD;.BAT;.COM").split(";").filter((W)=>W.length>0):[];for(let W of Z){if(!$&&qQ(B(W,Q)))return!0;if($){for(let z of H)if(qQ(B(W,Q+z)))return!0}}return!1}function qQ(Q){try{return fQ(Q,hQ.X_OK),!0}catch{return!1}}class b extends Error{name="SkillConflictError";details;constructor(Q){let X=`Skill conflict for agent "${Q.agent}": directory "${Q.outputDir}" already exists but was not created by Crust (no crust.json found). Delete or rename the conflicting skill to resolve.`;super(X);this.details=Q}}import{lstat as kQ,mkdir as bQ,readlink as YZ,realpath as qZ,rm as XQ,symlink as zZ,writeFile as JZ}from"fs/promises";import{dirname as IQ,join as RQ}from"path";function zQ(Q){return JQ(Q,[])}function JQ(Q,X){let Z=dQ(Q.meta.name),$=[...X,Z],H=rQ(Q.args),W=sQ(Q.effectiveFlags),z=oQ(Q.subCommands,$);return{name:Z,path:$,description:Q.meta.description,usage:Q.meta.usage,runnable:typeof Q.run==="function",args:H,flags:W,children:z}}function dQ(Q){return Q.trim().toLowerCase()}function rQ(Q){if(!Q||Q.length===0)return[];return Q.map(lQ)}function lQ(Q){let X={name:Q.name,type:Q.type,required:Q.required===!0,variadic:Q.variadic===!0};if(Q.description!==void 0)X.description=Q.description;if(Q.default!==void 0)X.default=KQ(Q.default);return X}function sQ(Q){if(!Q)return[];return Object.keys(Q).sort().map((Z)=>{return iQ(Z,Q[Z])})}function iQ(Q,X){let Z={name:Q,type:X.type,required:X.required===!0,multiple:X.multiple===!0,short:X.short,aliases:X.aliases?[...X.aliases].sort():[]};if(X.description!==void 0)Z.description=X.description;if(X.default!==void 0)Z.default=KQ(X.default);return Z}function oQ(Q,X){return Object.keys(Q).sort().map(($)=>{return JQ(Q[$],X)})}function KQ(Q){if(Array.isArray(Q))return JSON.stringify(Q);return String(Q)}function g(Q){if(/[:#[\]{}&*!|>'"`,@?\\]|^\s|\s$|^---|[\n\r]/.test(Q))return`"${Q.replace(/\\/g,"\\\\").replace(/"/g,"\\\"").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}"`;return Q}function xQ(Q){return Q.replace(/(?<!\\)\|/g,"\\|")}function MQ(Q,X){let Z=[],$=GQ(Q);Z.push({path:"SKILL.md",content:tQ(Q,X,$)});for(let H of $){let W=L(H),z=H.children.length>0?QZ(H,Q):eQ(H,Q);Z.push({path:W,content:z})}return Z}function GQ(Q){let X=[Q];for(let Z of Q.children)X.push(...GQ(Z));return X}function L(Q){if(Q.path.length<=1)return`commands/${Q.name}.md`;return`commands/${Q.path.slice(1).join("/")}.md`}function i(Q){return Q.path.join(" ")}function QQ(Q,X){let Z=Q.split("/").slice(0,-1),$=X.split("/"),H=0;while(H<Z.length&&H<$.length&&Z[H]===$[H])H++;let W=Z.length-H,z=$.slice(H);if(W===0)return z.join("/");return[...Array.from({length:W},()=>".."),...z].join("/")}function tQ(Q,X,Z){let $=[];if($.push("---"),$.push(`name: ${g(X.name)}`),$.push(`description: ${g(X.description)}`),X.license)$.push(`license: ${g(X.license)}`);if(X.compatibility)$.push(`compatibility: ${g(X.compatibility)}`);if(X.disableModelInvocation)$.push("disable-model-invocation: true");if(X.allowedTools)$.push(`allowed-tools: ${g(X.allowedTools)}`);if($.push("metadata:"),$.push(` version: "${X.version}"`),$.push("---"),$.push(""),$.push(`# ${X.name}`),$.push(""),Q.description)$.push(Q.description),$.push("");let H=X.name.startsWith("use-")?X.name.slice(4):X.name;if($.push(`Use this skill when working with \`${H}\` commands, or when you need help with \`${H}\` syntax, flags, or subcommands.`),$.push(""),$.push("## Command Reference"),$.push(""),$.push("This table lists all commands and their documentation paths. **Do not read all command files at once.** Instead:"),$.push(""),$.push("1. Use the table below to find the relevant command"),$.push("2. Use the `Type` column to choose what to execute: commands labeled `runnable` (including `runnable, group`) are executable, while `group` commands are not"),$.push("3. Read only the specific file from the `commands/` directory that you need"),$.push("4. For any command-specific answer, read that command's documentation file before responding"),$.push("5. Treat the command documentation file as the source of truth for usage, flags, options, aliases, and defaults"),$.push("6. Do not invent or assume undocumented flags/options; if something is missing from the file, say it is not documented"),$.push(""),$.push(...aQ(Z)),$.push(""),Q.runnable){$.push("## Usage"),$.push("");let W=L(Q);$.push(`The root command is directly executable. See [${Q.name}](${W}) for usage details.`),$.push("")}return $.join(`
|
|
3
|
+
`)}function aQ(Q){let X=[];X.push("| Command | Type | Documentation |"),X.push("| ------- | ---- | ------------- |");for(let Z of Q){let $=i(Z),H=L(Z),W=nQ(Z);X.push(`| \`${$}\` | ${W} | [${H}](${H}) |`)}return X}function nQ(Q){if(Q.runnable&&Q.children.length>0)return"runnable, group";if(Q.runnable)return"runnable";return"group"}function eQ(Q,X){let Z=[],$=i(Q);if(Z.push(`# \`${$}\``),Z.push(""),Q.description)Z.push(Q.description),Z.push("");if(Z.push("## Usage"),Z.push(""),Q.usage)Z.push("```"),Z.push(Q.usage),Z.push("```");else Z.push("```"),Z.push(wQ(Q)),Z.push("```");if(Z.push(""),Q.args.length>0)Z.push("## Arguments"),Z.push(""),Z.push(...FQ(Q.args)),Z.push("");if(Q.flags.length>0)Z.push("## Flags"),Z.push(""),Z.push(...OQ(Q.flags)),Z.push("");return Z.push("## Command Documentation Authority"),Z.push(""),Z.push("Only arguments, flags, options, aliases, and defaults documented in this file are supported for this command."),Z.push("Do not infer or invent additional command-line options."),Z.push(""),Z.push(...UQ(Q,X)),Z.join(`
|
|
4
|
+
`)}function QZ(Q,X){let Z=[],$=i(Q),H=L(Q);if(Z.push(`# \`${$}\``),Z.push(""),Q.description)Z.push(Q.description),Z.push("");if(Q.runnable){if(Z.push("## Usage"),Z.push(""),Q.usage)Z.push("```"),Z.push(Q.usage),Z.push("```");else Z.push("```"),Z.push(wQ(Q)),Z.push("```");if(Z.push(""),Q.args.length>0)Z.push("## Arguments"),Z.push(""),Z.push(...FQ(Q.args)),Z.push("");if(Q.flags.length>0)Z.push("## Flags"),Z.push(""),Z.push(...OQ(Q.flags)),Z.push("");Z.push("## Command Documentation Authority"),Z.push(""),Z.push("Only arguments, flags, options, aliases, and defaults documented in this file are supported for this command."),Z.push("Do not infer or invent additional command-line options."),Z.push("")}Z.push("## Subcommands"),Z.push("");for(let W of Q.children){let z=L(W),J=QQ(H,z),q=W.description?` - ${W.description}`:"";Z.push(`- [\`${W.name}\`](${J})${q}`)}return Z.push(""),Z.push(...UQ(Q,X)),Z.join(`
|
|
5
|
+
`)}function wQ(Q){let X=[...Q.path];for(let Z of Q.args)if(Z.variadic)X.push(Z.required?`<${Z.name}...>`:`[${Z.name}...]`);else X.push(Z.required?`<${Z.name}>`:`[${Z.name}]`);if(Q.flags.length>0)X.push("[options]");return X.join(" ")}function FQ(Q){let X=[];X.push("| Argument | Type | Required | Description |"),X.push("| -------- | ---- | -------- | ----------- |");for(let Z of Q){let $=Z.variadic?`${Z.name}...`:Z.name,H=Z.required?"Yes":"No",W=xQ(ZZ(Z));X.push(`| \`${$}\` | ${Z.type} | ${H} | ${W} |`)}return X}function ZZ(Q){let X=[];if(Q.description)X.push(Q.description);if(Q.default!==void 0)X.push(`Default: \`${Q.default}\``);return X.join(". ")||"-"}function OQ(Q){let X=[];X.push("| Flag | Type | Required | Description |"),X.push("| ---- | ---- | -------- | ----------- |");for(let Z of Q){let $=XZ(Z),H=Z.required?"Yes":"No",W=xQ($Z(Z));X.push(`| ${$} | ${Z.type} | ${H} | ${W} |`)}return X}function XZ(Q){let X=[`\`--${Q.name}\``];if(Q.short)X.push(`\`-${Q.short}\``);for(let Z of Q.aliases)X.push(`\`--${Z}\``);return X.join(", ")}function $Z(Q){let X=[];if(Q.description)X.push(Q.description);if(Q.multiple)X.push("Can be specified multiple times");if(Q.default!==void 0)X.push(`Default: \`${Q.default}\``);return X.join(". ")||"-"}function UQ(Q,X){let Z=[],$=L(Q);if(Z.push("---"),Z.push(""),Q.path.length>1){let W=Q.path.slice(0,-1),z=VQ(X,W);if(z){let J=L(z),q=QQ($,J),K=i(z);Z.push(`Parent: [\`${K}\`](${q})`),Z.push("")}}let H=QQ($,"SKILL.md");return Z.push(`[Skill Overview](${H})`),Z.push(""),Z}function VQ(Q,X){if(HZ(Q.path,X))return Q;for(let Z of Q.children){let $=VQ(Z,X);if($)return $}return}function HZ(Q,X){if(Q.length!==X.length)return!1;for(let Z=0;Z<Q.length;Z++)if(Q[Z]!==X[Z])return!1;return!0}import{readFile as WZ}from"fs/promises";import{join as BZ}from"path";var ZQ="crust.json";async function m(Q){try{let X=await WZ(BZ(Q,ZQ),"utf-8"),Z=JSON.parse(X);if(typeof Z==="object"&&Z!==null&&"version"in Z&&typeof Z.version==="string")return Z.version;return null}catch{return null}}var KZ="auto",EQ=/^[a-z0-9]+(-[a-z0-9]+)*$/;function PQ(Q){return Q.length>=1&&Q.length<=64&&EQ.test(Q)}function A(Q){return Q.startsWith("use-")?Q:`use-${Q}`}async function S(Q){let{command:X,meta:Z,agents:$,scope:H="global",clean:W=!0,force:z=!1,installMode:J=KZ}=Q,q=A(Z.name);if(!PQ(q))throw Error(`Invalid skill name "${q}": must be 1\u201364 lowercase `+`alphanumeric characters and hyphens, no leading/trailing/consecutive hyphens. Pattern: ${EQ.source}`);let K=$[0];if(!K)return{agents:[]};let x={...Z,name:q},G=zQ(X),w=MQ(G,x),h=OZ(x),C=[...w,...h].sort((M,U)=>M.path<U.path?-1:M.path>U.path?1:0),a=C.map((M)=>M.path),R=new Map;for(let M of $){let U=v(M,H,x.name),T=R.get(U);if(T)T.push(M);else R.set(U,[M])}let l=new Map;for(let M of R.keys())l.set(M,await m(M));let _=s(H,x.name),u=await m(_);if(u===null){if(await CQ(_)&&!z)throw new b({agent:K,outputDir:v(K,H,x.name)})}let c=u!==x.version;if(c){if(W)await HQ(_);await NQ(_,C)}let N=[];for(let[M,U]of R){let T=U[0];if(!T)continue;let I=l.get(M)??null,Y=await LQ(M,_),F=I!==null||Y.exists&&Y.isSymlink&&Y.pointsToCanonical;if(Y.exists&&!F&&!z)throw new b({agent:T,outputDir:M});let O=await MZ({outputDir:M,canonicalOutputDir:_,allFiles:C,clean:W,installMode:J,inspection:Y,installedVersion:I,currentVersion:x.version}),V=xZ({installedVersion:I,currentVersion:x.version,canonicalChanged:c,pathChanged:O});for(let SQ of U)N.push({agent:SQ,outputDir:M,files:V==="up-to-date"?[]:a,status:V,previousVersion:V==="updated"?I??void 0:void 0})}return{agents:N}}async function $Q(Q){let{name:X,agents:Z,scope:$="global"}=Q,H=A(X),W=s($,H),z=[],J=new Map;for(let K of Z){let x=v(K,$,H),G=J.get(x);if(G)G.push(K);else J.set(x,[K])}for(let[K,x]of J)if(await CQ(K)){await XQ(K,{recursive:!0,force:!0});for(let w of x)z.push({agent:w,outputDir:K,status:"removed"})}else for(let w of x)z.push({agent:w,outputDir:K,status:"not-found"});if(!await FZ(H,$))await XQ(W,{recursive:!0,force:!0});return{agents:z}}async function d(Q){let{name:X,agents:Z,scope:$="global"}=Q,H=A(X),W=[],z=new Map;for(let J of Z){let q=v(J,$,H),K=z.get(q);if(K)K.push(J);else z.set(q,[J])}for(let[J,q]of z){let K=await m(J);for(let x of q)W.push({agent:x,outputDir:J,installed:K!==null,version:K??void 0})}return{agents:W}}function xZ(Q){let{installedVersion:X,currentVersion:Z,canonicalChanged:$,pathChanged:H}=Q;if(X===null)return"installed";if(X===Z&&!$&&!H)return"up-to-date";return"updated"}async function MZ(Q){let{outputDir:X,canonicalOutputDir:Z,allFiles:$,clean:H,installMode:W,inspection:z,installedVersion:J,currentVersion:q}=Q;if(W==="copy")return _Q({outputDir:X,allFiles:$,clean:H,inspection:z,installedVersion:J,currentVersion:q});try{return await GZ({outputDir:X,canonicalOutputDir:Z,inspection:z})}catch(K){if(W==="symlink")throw Error(`Failed to create symlink at "${X}" (installMode: symlink).`,{cause:K});let x=await LQ(X,Z);return _Q({outputDir:X,allFiles:$,clean:H,inspection:x,installedVersion:J,currentVersion:q})}}async function _Q(Q){let{outputDir:X,allFiles:Z,clean:$,inspection:H,installedVersion:W,currentVersion:z}=Q;if(!(!H.exists||H.isSymlink||W!==z))return!1;if(H.isSymlink||$)await HQ(X);return await NQ(X,Z),!0}async function GZ(Q){let{outputDir:X,canonicalOutputDir:Z,inspection:$}=Q;if($.exists&&$.isSymlink&&$.pointsToCanonical)return!1;if($.exists)await HQ(X);return await wZ(Z,X),!0}async function LQ(Q,X){let Z;try{Z=await kQ(Q)}catch{return{exists:!1,isSymlink:!1,pointsToCanonical:!1}}let $=process.platform==="win32"&&Z.isDirectory()&&await yQ(Q)!==null;if(!(Z.isSymbolicLink()||$))return{exists:!0,isSymlink:!1,pointsToCanonical:!1};let[W,z,J]=await Promise.all([TQ(Q),TQ(X),yQ(Q)]);return{exists:!0,isSymlink:!0,pointsToCanonical:W!==null&&z!==null&&W===z||J===X}}async function TQ(Q){try{return await qZ(Q)}catch{return null}}async function yQ(Q){try{return await YZ(Q)}catch{return null}}async function wZ(Q,X){await bQ(IQ(X),{recursive:!0});let Z=process.platform==="win32"?"junction":"dir";await zZ(Q,X,Z)}async function CQ(Q){try{return await kQ(Q),!0}catch{return!1}}async function FZ(Q,X){let Z=new Set;for(let $ of p)Z.add(v($,X,Q));for(let $ of Z)if(await m($)!==null)return!0;return!1}function OZ(Q){let X={name:Q.name,description:Q.description,version:Q.version};return[{path:ZQ,content:`${JSON.stringify(X,null,"\t")}
|
|
6
|
+
`}]}async function HQ(Q){await XQ(Q,{recursive:!0,force:!0})}async function NQ(Q,X){let Z=new Set;for(let H of X){let W=RQ(Q,H.path),z=IQ(W);Z.add(z)}let $=[...Z].sort();for(let H of $)await bQ(H,{recursive:!0});for(let H of X){let W=RQ(Q,H.path);await JZ(W,H.content,"utf-8")}}import{Crust as DQ,VALIDATION_MODE_ENV as UZ}from"@crustjs/core";import{confirm as VZ,multiselect as RZ,select as _Z,spinner as r}from"@crustjs/prompts";import{bold as t,dim as f,yellow as vQ}from"@crustjs/style";var TZ="skill",yZ="global",o="__universal__";function kZ(Q){return Q==="global"||Q==="project"}async function AQ(Q,X){if(Q!==void 0){if(!kZ(Q))throw Error(`Invalid --scope value: ${String(Q)}. Expected "project" or "global".`);return Q}if(X.defaultScope)return X.defaultScope;return _Z({message:"Select scope",choices:[{label:"Project",value:"project"},{label:"Global",value:"global"}],default:yZ})}function WQ(Q){let X=new Set(P()),Z=[];if(Q.some(($)=>X.has($)))Z.push("Universal");for(let $ of Q){if(X.has($))continue;Z.push(j[$])}return Z}function jQ(Q){let X=new Set(P()),Z=[],$=Q.find((H)=>X.has(H.agent));if($)Z.push({label:"Universal",outputDir:$.outputDir});for(let H of Q){if(X.has(H.agent))continue;Z.push({label:j[H.agent],outputDir:H.outputDir})}return Z}function BQ(Q,X){return{name:Q.meta.name,description:Q.meta.description??"",version:X}}async function bZ(Q,X){let Z=[...P(),...D()];if(Z.length===0)return;let $=BQ(Q,X.version),H=["project","global"];for(let W of H){let J=(await d({name:$.name,agents:Z,scope:W})).agents.filter((q)=>q.installed&&q.version!==$.version);if(J.length===0)continue;try{await r({message:`Updating ${W} skills...`,task:async({updateMessage:q})=>{let K=await S({command:Q,meta:$,agents:J.map((w)=>w.agent),scope:W,installMode:X.installMode}),x=K.agents.filter((w)=>w.status==="updated").map((w)=>w.agent),G=WQ(x);if(G.length>0)q(`Updated skill "${A($.name)}" to v${$.version} for ${G.join(", ")} (${W})`);return K}})}catch(q){if(q instanceof b)console.warn(vQ(`Skill conflict: "${q.details.outputDir}" already exists but was not created by ${$.name}. Skipping auto-update for ${W}. Delete or rename the conflicting skill to resolve.`));else throw q}}}function IZ(Q){let X;return{name:"skills",async setup(Z,$){X=Z.rootCommand;let H=Q.command??TZ;if($.addSubCommand(X,H,EZ(X,Q,H)),process.env[UZ]==="1")return;if(Z.argv[0]===H)return;if(Q.autoUpdate!==!1)await bZ(X,Q)}}}function EZ(Q,X,Z){let $=PZ(Q,X);return new DQ(Z).meta({description:"Manage agent skill installations"}).flags({scope:{type:"string",description:"Install scope (project or global)"}}).run(async(H)=>{let W=BQ(Q,X.version),z=await AQ(H.flags.scope,X),J=await e(),q=P(),K=D(),x=await d({name:W.name,agents:[...q,...K],scope:z}),G=new Set(x.agents.filter((Y)=>Y.installed).map((Y)=>Y.agent)),w=new Set(J),h=new Map(x.agents.map((Y)=>[Y.agent,Y])),C=K.filter((Y)=>{if(w.has(Y))return!0;return h.get(Y)?.installed===!0}),a=C.filter((Y)=>G.has(Y)),R=[];if(q.length>0){let Y=q[0];if(!Y)throw Error("Expected at least one universal agent");let O=h.get(Y)?.outputDir??"path unavailable";R.push({label:"Universal",value:o,hint:O}),console.log(f("Universal installs to the shared .agents/skills directory."))}for(let Y of C){let O=h.get(Y)?.outputDir??"path unavailable";R.push({label:j[Y],value:Y,hint:O})}let l=q.length>0&&q.every((Y)=>G.has(Y)),_=[...a.filter((Y)=>!q.includes(Y))];if(l)_.unshift(o);let u=R.length===0?[]:await RZ({message:"Select agents to install skills for",choices:R,default:_,required:!1}),c=new Set(u.filter((Y)=>Y!==o));if(u.includes(o))for(let Y of q)c.add(Y);let N=[...c],M=N.filter((Y)=>!G.has(Y)),U=N.filter((Y)=>{let F=x.agents.find((O)=>O.agent===Y);return F?.installed===!0&&F.version!==W.version}),T=[...G].filter((Y)=>!N.includes(Y)),I=[...M,...U];if(I.length>0)try{let Y=await r({message:"Installing skills...",task:async()=>S({command:Q,meta:W,agents:I,scope:z,installMode:X.installMode})});console.log(`
|
|
7
|
+
${t(`Installed "${W.name}" v${W.version}`)}`);for(let F of jQ(Y.agents))console.log(f(` ${F.label} \u2192 ${F.outputDir}`))}catch(Y){if(Y instanceof b)if(await VZ({message:`"${Y.details.outputDir}" already exists but was not created by Crust. Overwrite?`,default:!1})){let O=await r({message:"Overwriting skill...",task:async()=>S({command:Q,meta:W,agents:[Y.details.agent],scope:z,force:!0,installMode:X.installMode})});console.log(`
|
|
8
|
+
${t(`Installed "${W.name}" v${W.version}`)}`);for(let V of jQ(O.agents))console.log(f(` ${V.label} \u2192 ${V.outputDir}`))}else console.log(f(`
|
|
9
|
+
Skipped ${j[Y.details.agent]}`));else throw Y}if(T.length>0){let F=(await r({message:"Removing skills...",task:async()=>$Q({name:W.name,agents:T,scope:z})})).agents.filter((V)=>V.status==="removed").map((V)=>V.agent),O=WQ(F);if(O.length>0)console.log(`
|
|
10
|
+
${t(`Removed from ${O.join(", ")}`)}`)}if(I.length===0&&T.length===0)console.log(f("No changes."))}).command($)._node}function PZ(Q,X){return new DQ("update").meta({description:"Update installed skills to latest version"}).flags({scope:{type:"string",description:"Update scope (project or global)"}}).run(async(Z)=>{let $=await AQ(Z.flags.scope,X),H=[...P(),...D()],W=BQ(Q,X.version),J=(await d({name:W.name,agents:H,scope:$})).agents.filter((q)=>q.installed&&q.version!==W.version);if(J.length===0){console.log(f(`No updates needed (${$}).`));return}try{let K=(await r({message:`Updating ${$} skills...`,task:async()=>S({command:Q,meta:W,agents:J.map((G)=>G.agent),scope:$,installMode:X.installMode})})).agents.filter((G)=>G.status==="updated").map((G)=>G.agent),x=WQ(K);if(x.length>0)console.log(`
|
|
11
|
+
${t(`Updated "${W.name}" to v${W.version} for ${x.join(", ")} (${$})`)}`)}catch(q){if(q instanceof b)console.warn(vQ(`Skipped ${j[q.details.agent]}: "${q.details.outputDir}" already exists but was not created by ${W.name}. Delete or rename the conflicting directory to resolve.`));else throw q}})}export{$Q as uninstallSkill,d as skillStatus,IZ as skillPlugin,A as resolveSkillName,s as resolveCanonicalSkillPath,PQ as isValidSkillName,gQ as isUniversalAgent,P as getUniversalAgents,D as getAdditionalAgents,S as generateSkill,e as detectInstalledAgents,b as SkillConflictError};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crustjs/skills",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "Agent skill generation from Crust command definitions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@crustjs/config": "0.0.0",
|
|
50
|
-
"@crustjs/core": "0.0.
|
|
50
|
+
"@crustjs/core": "0.0.12",
|
|
51
51
|
"bunup": "^0.16.29"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@crustjs/core": "0.0.
|
|
54
|
+
"@crustjs/core": "0.0.12",
|
|
55
55
|
"typescript": "^5"
|
|
56
56
|
}
|
|
57
57
|
}
|