@crustjs/skills 0.0.6 → 0.0.8

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 CHANGED
@@ -31,11 +31,87 @@ const result = await generateSkill({
31
31
  description: "CLI tool for managing widgets",
32
32
  version: "1.0.0",
33
33
  },
34
+ agents: ["opencode", "claude-code"],
34
35
  });
35
36
 
36
- console.log(`Generated ${result.files.length} files to ${result.outputDir}`);
37
+ for (const agent of result.agents) {
38
+ console.log(`${agent.agent}: ${agent.status} -> ${agent.outputDir}`);
39
+ }
37
40
  ```
38
41
 
42
+ ### Runtime Plugin (`autoUpdate`)
43
+
44
+ `skillPlugin()` is a runtime plugin. Register it in `runMain(..., { plugins })`.
45
+ Do not put a `plugins` field inside `defineCommand(...)`.
46
+
47
+ ```ts
48
+ import { defineCommand, runMain } from "@crustjs/core";
49
+ import { skillPlugin } from "@crustjs/skills";
50
+
51
+ const app = defineCommand({
52
+ meta: { name: "my-cli", description: "My CLI" },
53
+ run() {
54
+ console.log("hello");
55
+ },
56
+ });
57
+
58
+ runMain(app, {
59
+ plugins: [
60
+ skillPlugin({
61
+ version: "1.0.0",
62
+ // autoUpdate: true (default) — silently updates installed skills
63
+ // command: true (default) — registers "my-cli skill" subcommand
64
+ }),
65
+ ],
66
+ });
67
+ ```
68
+
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.
70
+
71
+ ### Programmatic Auto-Install
72
+
73
+ For full control over first-time installation, use the exported primitives
74
+ directly in your own setup logic:
75
+
76
+ ```ts
77
+ import { defineCommand, runMain } from "@crustjs/core";
78
+ import { detectInstalledAgents, generateSkill, skillStatus } from "@crustjs/skills";
79
+
80
+ const app = defineCommand({
81
+ meta: { name: "my-cli", description: "My CLI" },
82
+ async run() {
83
+ // Detect agents and install skills if not yet present
84
+ const agents = await detectInstalledAgents({ scope: "global" });
85
+ const status = await skillStatus({ name: "my-cli", agents, scope: "global" });
86
+
87
+ const notInstalled = status.agents
88
+ .filter((a) => !a.installed)
89
+ .map((a) => a.agent);
90
+
91
+ if (notInstalled.length > 0) {
92
+ await generateSkill({
93
+ command: app,
94
+ meta: { name: "my-cli", description: "My CLI", version: "1.0.0" },
95
+ agents: notInstalled,
96
+ scope: "global",
97
+ });
98
+ }
99
+ },
100
+ });
101
+
102
+ runMain(app);
103
+ ```
104
+
105
+ #### Troubleshooting
106
+
107
+ If auto-update does not appear to work:
108
+
109
+ - Ensure plugin is passed to `runMain(..., { plugins: [...] })`.
110
+ - Ensure at least one supported agent is detected for your scope:
111
+ - `scope: "global"` -> `~/.claude` or `~/.config/opencode`
112
+ - `scope: "project"` -> `<cwd>/.claude` or `<cwd>/.opencode` (falls back to global roots)
113
+ - Check for existing conflicting skill directories without `crust.json`.
114
+
39
115
  ## Recommended Export Pattern
40
116
 
41
117
  To avoid side effects when your command module is imported for generation, guard runtime code with `import.meta.main`:
@@ -69,20 +145,20 @@ crust skills generate <module> [options]
69
145
 
70
146
  ### Arguments
71
147
 
72
- | Argument | Description |
73
- | -------- | ----------- |
148
+ | Argument | Description |
149
+ | -------- | ------------------------------------------------ |
74
150
  | `module` | Path to the command module (e.g. `./src/cli.ts`) |
75
151
 
76
152
  ### Flags
77
153
 
78
- | Flag | Alias | Required | Default | Description |
79
- | ---- | ----- | -------- | ------- | ----------- |
80
- | `--name` | `-n` | Yes | - | Skill name (used as directory name) |
81
- | `--description` | `-d` | Yes | - | Human-readable description |
82
- | `--version` | `-V` | No | - | Version string |
83
- | `--out-dir` | `-o` | No | `.` | Output directory |
84
- | `--clean` | - | No | `true` | Remove existing skill directory before writing |
85
- | `--export` | `-e` | No | `default` | Named export to use from the module |
154
+ | Flag | Alias | Required | Default | Description |
155
+ | --------------- | ----- | -------- | --------- | ---------------------------------------------- |
156
+ | `--name` | `-n` | Yes | - | Skill name (used as directory name) |
157
+ | `--description` | `-d` | Yes | - | Human-readable description |
158
+ | `--version` | `-V` | No | - | Version string |
159
+ | `--out-dir` | `-o` | No | `.` | Output directory |
160
+ | `--clean` | - | No | `true` | Remove existing skill directory before writing |
161
+ | `--export` | `-e` | No | `default` | Named export to use from the module |
86
162
 
87
163
  ### Examples
88
164
 
@@ -114,13 +190,13 @@ import { generateSkill } from "@crustjs/skills";
114
190
  const result = await generateSkill({
115
191
  command: rootCommand,
116
192
  meta: { name: "my-cli", description: "My CLI tool", version: "1.0.0" },
117
- outDir: "./dist", // default: "."
118
- clean: true, // default: true — removes existing skill dir first
119
- force: false, // default: falsethrows SkillConflictError if dir exists without crust.json
193
+ agents: ["opencode"],
194
+ scope: "project", // default: "global"
195
+ clean: true, // default: trueremoves existing skill dir first
196
+ force: false, // default: false — throws SkillConflictError if dir exists without crust.json
120
197
  });
121
198
 
122
- // result.outputDirabsolute path to the generated skill directory
123
- // result.files — sorted list of written file paths (relative to outputDir)
199
+ // result.agentsper-agent install results
124
200
  ```
125
201
 
126
202
  ### `buildManifest(command)`
@@ -145,7 +221,7 @@ const manifest = buildManifest(rootCommand);
145
221
  const files = renderSkill(manifest, { name: "my-cli", description: "My CLI" });
146
222
 
147
223
  for (const file of files) {
148
- console.log(file.path); // e.g. "SKILL.md", "commands/serve.md"
224
+ console.log(file.path); // e.g. "SKILL.md", "commands/serve.md"
149
225
  console.log(file.content); // markdown content
150
226
  }
151
227
  ```
@@ -157,9 +233,9 @@ Validates a skill name against the [Agent Skills spec](https://agentskills.io/sp
157
233
  ```ts
158
234
  import { isValidSkillName } from "@crustjs/skills";
159
235
 
160
- isValidSkillName("my-cli"); // true
161
- isValidSkillName("My_CLI"); // false — uppercase and underscores not allowed
162
- isValidSkillName("-leading"); // false — leading hyphen
236
+ isValidSkillName("my-cli"); // true
237
+ isValidSkillName("My_CLI"); // false — uppercase and underscores not allowed
238
+ isValidSkillName("-leading"); // false — leading hyphen
163
239
  isValidSkillName("a".repeat(65)); // false — exceeds 64 characters
164
240
  ```
165
241
 
@@ -176,19 +252,19 @@ const meta: SkillMeta = {
176
252
  version: "1.0.0",
177
253
 
178
254
  // Optional fields — emitted in SKILL.md YAML frontmatter when set
179
- allowedTools: "Bash(my-cli *) Read Grep", // Pre-approved tools (avoids per-use prompts)
180
- license: "MIT", // License name or reference
181
- compatibility: "Requires my-cli on PATH", // Environment requirements (max 500 chars)
182
- disableModelInvocation: false, // true = agent won't auto-load; user must invoke manually
255
+ allowedTools: "Bash(my-cli *) Read Grep", // Pre-approved tools (avoids per-use prompts)
256
+ license: "MIT", // License name or reference
257
+ compatibility: "Requires my-cli on PATH", // Environment requirements (max 500 chars)
258
+ disableModelInvocation: false, // true = agent won't auto-load; user must invoke manually
183
259
  };
184
260
  ```
185
261
 
186
- | Field | Frontmatter Key | Description |
187
- | ----- | --------------- | ----------- |
188
- | `allowedTools` | `allowed-tools` | Space-delimited list of pre-approved tools (e.g. `Bash(my-cli *) Read Grep`) |
189
- | `license` | `license` | License name or file reference |
190
- | `compatibility` | `compatibility` | Environment requirements or compatibility notes |
191
- | `disableModelInvocation` | `disable-model-invocation` | When `true`, prevents agents from auto-loading the skill |
262
+ | Field | Frontmatter Key | Description |
263
+ | ------------------------ | -------------------------- | ---------------------------------------------------------------------------- |
264
+ | `allowedTools` | `allowed-tools` | Space-delimited list of pre-approved tools (e.g. `Bash(my-cli *) Read Grep`) |
265
+ | `license` | `license` | License name or file reference |
266
+ | `compatibility` | `compatibility` | Environment requirements or compatibility notes |
267
+ | `disableModelInvocation` | `disable-model-invocation` | When `true`, prevents agents from auto-loading the skill |
192
268
 
193
269
  ## Escaping
194
270
 
@@ -218,12 +294,12 @@ skills/use-my-cli/
218
294
 
219
295
  ### File Details
220
296
 
221
- | File | Purpose |
222
- | ---- | ------- |
223
- | `SKILL.md` | Agent entrypoint with YAML frontmatter. Directs agents to load specific command files on demand (lazy loading). |
224
- | `command-index.md` | Markdown table listing every command, its type (runnable/group), and documentation path. |
225
- | `commands/*.md` | Per-command reference files. Leaf commands include usage, arguments, flags, defaults, and aliases. Group commands list subcommands with links. |
226
- | `crust.json` | Crust-specific JSON metadata: name, description, version, entrypoint, and list of all command paths. Also serves as an ownership marker — its presence indicates the skill was generated by Crust. |
297
+ | File | Purpose |
298
+ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
299
+ | `SKILL.md` | Agent entrypoint with YAML frontmatter. Directs agents to load specific command files on demand (lazy loading). |
300
+ | `command-index.md` | Markdown table listing every command, its type (runnable/group), and documentation path. |
301
+ | `commands/*.md` | Per-command reference files. Leaf commands include usage, arguments, flags, defaults, and aliases. Group commands list subcommands with links. |
302
+ | `crust.json` | Crust-specific JSON metadata: name, description, version, entrypoint, and list of all command paths. Also serves as an ownership marker — its presence indicates the skill was generated by Crust. |
227
303
 
228
304
  ## Conflict Detection
229
305
 
package/dist/index.d.ts CHANGED
@@ -194,13 +194,20 @@ interface StatusResult {
194
194
  * The plugin reads `name` and `description` from the root command's `meta`
195
195
  * at setup time, so only `version` is required here.
196
196
  *
197
- * Installed agents are detected automatically by checking for global
198
- * configuration directories (`~/.claude/` for Claude Code,
199
- * `~/.config/opencode/` for OpenCode). Only detected agents are managed.
197
+ * Installed agents are detected automatically based on the configured scope.
198
+ *
199
+ * - `scope: "global"` checks `~/.claude/` and `~/.config/opencode/`
200
+ * - `scope: "project"` checks `<cwd>/.claude/` / `<cwd>/.opencode/`, then
201
+ * falls back to global roots when local roots are missing
202
+ *
203
+ * Only detected agents are managed.
200
204
  *
201
205
  * **Auto-update** (default): silently updates already-installed skills when a
202
- * new version is detected. Set `autoInstall: true` to also install skills that
203
- * are not yet present.
206
+ * new version is detected. Disable with `autoUpdate: false`.
207
+ *
208
+ * For first-time installation, use the interactive `skill` subcommand or
209
+ * build custom auto-install logic with the exported primitives
210
+ * (`detectInstalledAgents`, `skillStatus`, `generateSkill`).
204
211
  *
205
212
  * **Interactive command** (default): registers a `skill` subcommand that
206
213
  * presents a single multiselect prompt for toggling agent installations.
@@ -215,13 +222,6 @@ interface SkillPluginOptions {
215
222
  */
216
223
  scope?: Scope;
217
224
  /**
218
- * Automatically install skills when not yet present.
219
- * Set to `true` to install on first CLI invocation without requiring the
220
- * interactive skill command.
221
- * @default false
222
- */
223
- autoInstall?: boolean;
224
- /**
225
225
  * Automatically update skills when the installed version is outdated.
226
226
  * @default true
227
227
  */
@@ -242,21 +242,36 @@ interface SkillPluginOptions {
242
242
  command?: boolean | string;
243
243
  }
244
244
  /**
245
- * Detects which supported agents are installed by checking for the
246
- * existence of their global configuration directories.
247
- *
248
- * Detection always checks global paths regardless of the intended
249
- * installation scope — if the agent's global config directory exists,
250
- * the agent is considered installed.
245
+ * Options for detecting installed agents.
246
+ */
247
+ interface DetectInstalledAgentsOptions {
248
+ /**
249
+ * Detection scope.
250
+ * - `global`: checks global config roots under home directory.
251
+ * - `project`: checks project-local config roots under cwd, then falls back
252
+ * to global roots under home directory when local roots are missing.
253
+ * @default "global"
254
+ */
255
+ scope?: Scope;
256
+ /** Home directory override used for global detection (tests). */
257
+ home?: string;
258
+ /** Working directory override used for project detection (tests). */
259
+ cwd?: string;
260
+ }
261
+ /**
262
+ * Detects which supported agents are installed by checking for agent
263
+ * configuration roots for the requested scope.
251
264
  *
252
265
  * Detection table:
253
- * | Agent | Config directory |
254
- * | ------------ | ----------------------------- |
255
- * | `claude-code`| `<homedir>/.claude/` |
256
- * | `opencode` | `<homedir>/.config/opencode/` |
257
- *
258
- * @param home - Override the home directory for detection (defaults to `os.homedir()`).
259
- * Primarily useful for testing.
266
+ * | Scope | Agent | Config directories checked |
267
+ * | --------- | ------------ | ------------------------------------------ |
268
+ * | `global` | `claude-code`| `<homedir>/.claude/` |
269
+ * | `global` | `opencode` | `<homedir>/.config/opencode/` |
270
+ * | `project` | `claude-code`| `<cwd>/.claude/`, fallback `<homedir>/.claude/` |
271
+ * | `project` | `opencode` | `<cwd>/.opencode/`, fallback `<homedir>/.config/opencode/` |
272
+ *
273
+ * @param options - Optional scope/home/cwd overrides. For backwards
274
+ * compatibility, passing a string is treated as `home`.
260
275
  * @returns Array of detected agent targets (may be empty)
261
276
  *
262
277
  * @example
@@ -265,7 +280,7 @@ interface SkillPluginOptions {
265
280
  * // ["claude-code"] — only Claude Code config found
266
281
  * ```
267
282
  */
268
- declare function detectInstalledAgents(home?: string): Promise<AgentTarget[]>;
283
+ declare function detectInstalledAgents(options?: string | DetectInstalledAgentsOptions): Promise<AgentTarget[]>;
269
284
  /** Details about the conflict between an existing skill and an incoming one. */
270
285
  interface SkillConflictDetails {
271
286
  /** The agent where the conflict was detected */
@@ -381,13 +396,16 @@ import { CrustPlugin } from "@crustjs/core";
381
396
  * `name` and `description` are read from the root command's `meta` at setup
382
397
  * time — only `version` needs to be supplied in the options.
383
398
  *
384
- * Installed agents are detected automatically by checking for global
385
- * configuration directories. Only detected agents are managed by the
386
- * middleware and the interactive command.
399
+ * Installed agents are detected automatically based on the configured scope.
400
+ * - `scope: "global"` checks global config roots in the home directory
401
+ * - `scope: "project"` checks project-local config roots in the cwd, then
402
+ * falls back to global roots in the home directory
403
+ *
404
+ * Only detected agents are managed by automatic update and the interactive
405
+ * command.
387
406
  *
388
407
  * **Auto-update** (default): silently updates already-installed skills when a
389
- * new version is detected. Set `autoInstall: true` to also install skills that
390
- * are not yet present.
408
+ * new version is detected. Disable with `autoUpdate: false`.
391
409
  *
392
410
  * **Interactive command** (default): registers a `skill` subcommand that
393
411
  * presents a single multiselect prompt for toggling agent installations.
@@ -396,6 +414,10 @@ import { CrustPlugin } from "@crustjs/core";
396
414
  * deselected agents are uninstalled, and already-correct agents are skipped.
397
415
  * Set `command: false` to disable command injection.
398
416
  *
417
+ * For first-time installation, use the interactive command or build custom
418
+ * auto-install logic with the exported primitives (`detectInstalledAgents`,
419
+ * `skillStatus`, `generateSkill`).
420
+ *
399
421
  * @param options - Plugin configuration with version and scope
400
422
  * @returns A `CrustPlugin` to register in a command's `plugins` array
401
423
  *
@@ -406,6 +428,9 @@ import { CrustPlugin } from "@crustjs/core";
406
428
  *
407
429
  * const app = defineCommand({
408
430
  * meta: { name: "my-cli", description: "My CLI" },
431
+ * });
432
+ *
433
+ * runMain(app, {
409
434
  * plugins: [
410
435
  * skillPlugin({
411
436
  * version: "1.0.0",
@@ -413,8 +438,6 @@ import { CrustPlugin } from "@crustjs/core";
413
438
  * }),
414
439
  * ],
415
440
  * });
416
- *
417
- * runMain(app);
418
441
  * ```
419
442
  */
420
443
  declare function skillPlugin(options: SkillPluginOptions): CrustPlugin;
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  // @bun
2
- import{access as QO}from"fs/promises";import{homedir as y}from"os";import{join as I}from"path";var XO=["claude-code","opencode"],x={"claude-code":"Claude Code",opencode:"OpenCode"};function L(O,X,Q){let Z=X==="global"?y():process.cwd();switch(O){case"claude-code":return I(Z,".claude","skills",Q);case"opencode":if(X==="global")return I(Z,".config","opencode","skills",Q);return I(Z,".opencode","skills",Q)}}async function F(O){let X=O??y(),Q=[];for(let Z of XO){let H=ZO(X,Z);if(await QO(H).then(()=>!0).catch(()=>!1))Q.push(Z)}return Q}function ZO(O,X){switch(X){case"claude-code":return I(O,".claude");case"opencode":return I(O,".config","opencode")}}class j extends Error{name="SkillConflictError";details;constructor(O){let X=`Skill conflict for agent "${O.agent}": directory "${O.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=O}}import{access as o,mkdir as wO,rm as s,writeFile as LO}from"fs/promises";import{dirname as FO,join as i}from"path";function v(O){return h(O,[])}function h(O,X){let Q=HO(O.meta.name),Z=[...X,Q],H=WO(O.args),W=JO(O.flags),$=qO(O.subCommands,Z);return{name:Q,path:Z,description:O.meta.description,usage:O.meta.usage,runnable:typeof O.run==="function",args:H,flags:W,children:$}}function HO(O){return O.trim().toLowerCase()}function WO(O){if(!O||O.length===0)return[];return O.map($O)}function $O(O){let X={name:O.name,type:O.type,required:O.required===!0,variadic:O.variadic===!0};if(O.description!==void 0)X.description=O.description;if(O.default!==void 0)X.default=u(O.default);return X}function JO(O){if(!O)return[];return Object.keys(O).sort().map((Q)=>{return KO(Q,O[Q])})}function KO(O,X){let Q={name:O,type:X.type,required:X.required===!0,multiple:X.multiple===!0,aliases:RO(X.alias)};if(X.description!==void 0)Q.description=X.description;if(X.default!==void 0)Q.default=u(X.default);return Q}function RO(O){if(O===void 0)return[];if(typeof O==="string")return[O];return[...O].sort()}function qO(O,X){return Object.keys(O).sort().map((Z)=>{return h(O[Z],X)})}function u(O){if(Array.isArray(O))return JSON.stringify(O);return String(O)}function _(O){if(/[:#[\]{}&*!|>'"`,@?\\]|^\s|\s$|^---|[\n\r]/.test(O))return`"${O.replace(/\\/g,"\\\\").replace(/"/g,"\\\"").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}"`;return O}function p(O){return O.replace(/(?<!\\)\|/g,"\\|")}function f(O,X){let Q=[],Z=c(O);Q.push({path:"SKILL.md",content:YO(O,X)}),Q.push({path:"command-index.md",content:BO(O,Z)});for(let H of Z){let W=G(H),$=H.children.length>0?GO(H,O):xO(H,O);Q.push({path:W,content:$})}return Q}function c(O){let X=[O];for(let Q of O.children)X.push(...c(Q));return X}function G(O){if(O.path.length<=1)return`commands/${O.name}.md`;return`commands/${O.path.slice(1).join("/")}.md`}function E(O){return O.path.join(" ")}function D(O,X){let Q=O.split("/").slice(0,-1),Z=X.split("/"),H=0;while(H<Q.length&&H<Z.length&&Q[H]===Z[H])H++;let W=Q.length-H,$=Z.slice(H);if(W===0)return $.join("/");return[...Array.from({length:W},()=>".."),...$].join("/")}function YO(O,X){let Q=[];if(Q.push("---"),Q.push(`name: ${_(X.name)}`),Q.push(`description: ${_(X.description)}`),X.license)Q.push(`license: ${_(X.license)}`);if(X.compatibility)Q.push(`compatibility: ${_(X.compatibility)}`);if(X.disableModelInvocation)Q.push("disable-model-invocation: true");if(X.allowedTools)Q.push(`allowed-tools: ${_(X.allowedTools)}`);if(Q.push("metadata:"),Q.push(` version: "${X.version}"`),Q.push("---"),Q.push(""),Q.push(`# ${X.name}`),Q.push(""),O.description)Q.push(O.description),Q.push("");let Z=X.name.startsWith("use-")?X.name.slice(4):X.name;if(Q.push(`Use this skill when working with \`${Z}\` commands, or when you need help with \`${Z}\` syntax, flags, or subcommands.`),Q.push(""),Q.push("## Command Reference"),Q.push(""),Q.push("For the full list of commands and their documentation paths, see [command-index.md](command-index.md). **Do not read all command files at once.** Instead:"),Q.push(""),Q.push("1. Check [command-index.md](command-index.md) to find the relevant command"),Q.push("2. Read only the specific file from the `commands/` directory that you need"),Q.push(""),O.children.length>0){Q.push("## Available Commands"),Q.push("");for(let H of O.children){let W=G(H),$=H.description?` - ${H.description}`:"";Q.push(`- [\`${H.name}\`](${W})${$}`)}Q.push("")}if(O.runnable){Q.push("## Usage"),Q.push("");let H=G(O);Q.push(`The root command is directly executable. See [${O.name}](${H}) for usage details.`),Q.push("")}return Q.join(`
3
- `)}function BO(O,X){let Q=[];Q.push("# Command Index"),Q.push(""),Q.push("| Command | Type | Documentation |"),Q.push("| ------- | ---- | ------------- |");for(let Z of X){let H=E(Z),W=G(Z),$=UO(Z);Q.push(`| \`${H}\` | ${$} | [${W}](${W}) |`)}return Q.push(""),Q.join(`
4
- `)}function UO(O){if(O.runnable&&O.children.length>0)return"runnable, group";if(O.runnable)return"runnable";return"group"}function xO(O,X){let Q=[],Z=E(O);if(Q.push(`# \`${Z}\``),Q.push(""),O.description)Q.push(O.description),Q.push("");if(Q.push("## Usage"),Q.push(""),O.usage)Q.push("```"),Q.push(O.usage),Q.push("```");else Q.push("```"),Q.push(g(O)),Q.push("```");if(Q.push(""),O.args.length>0)Q.push("## Arguments"),Q.push(""),Q.push(...m(O.args)),Q.push("");if(O.flags.length>0)Q.push("## Flags"),Q.push(""),Q.push(...d(O.flags)),Q.push("");return Q.push(...r(O,X)),Q.join(`
5
- `)}function GO(O,X){let Q=[],Z=E(O),H=G(O);if(Q.push(`# \`${Z}\``),Q.push(""),O.description)Q.push(O.description),Q.push("");if(O.runnable){if(Q.push("## Usage"),Q.push(""),O.usage)Q.push("```"),Q.push(O.usage),Q.push("```");else Q.push("```"),Q.push(g(O)),Q.push("```");if(Q.push(""),O.args.length>0)Q.push("## Arguments"),Q.push(""),Q.push(...m(O.args)),Q.push("");if(O.flags.length>0)Q.push("## Flags"),Q.push(""),Q.push(...d(O.flags)),Q.push("")}Q.push("## Subcommands"),Q.push("");for(let W of O.children){let $=G(W),q=D(H,$),R=W.description?` - ${W.description}`:"";Q.push(`- [\`${W.name}\`](${q})${R}`)}return Q.push(""),Q.push(...r(O,X)),Q.join(`
6
- `)}function g(O){let X=[...O.path];for(let Q of O.args)if(Q.variadic)X.push(Q.required?`<${Q.name}...>`:`[${Q.name}...]`);else X.push(Q.required?`<${Q.name}>`:`[${Q.name}]`);if(O.flags.length>0)X.push("[options]");return X.join(" ")}function m(O){let X=[];X.push("| Argument | Type | Required | Description |"),X.push("| -------- | ---- | -------- | ----------- |");for(let Q of O){let Z=Q.variadic?`${Q.name}...`:Q.name,H=Q.required?"Yes":"No",W=p(zO(Q));X.push(`| \`${Z}\` | ${Q.type} | ${H} | ${W} |`)}return X}function zO(O){let X=[];if(O.description)X.push(O.description);if(O.default!==void 0)X.push(`Default: \`${O.default}\``);return X.join(". ")||"-"}function d(O){let X=[];X.push("| Flag | Type | Required | Description |"),X.push("| ---- | ---- | -------- | ----------- |");for(let Q of O){let Z=VO(Q),H=Q.required?"Yes":"No",W=p(jO(Q));X.push(`| ${Z} | ${Q.type} | ${H} | ${W} |`)}return X}function VO(O){let X=[`\`--${O.name}\``];for(let Q of O.aliases)X.push(`\`-${Q}\``);return X.join(", ")}function jO(O){let X=[];if(O.description)X.push(O.description);if(O.multiple)X.push("Can be specified multiple times");if(O.default!==void 0)X.push(`Default: \`${O.default}\``);return X.join(". ")||"-"}function r(O,X){let Q=[],Z=G(O);if(Q.push("---"),Q.push(""),O.path.length>1){let W=O.path.slice(0,-1),$=l(X,W);if($){let q=G($),R=D(Z,q),z=E($);Q.push(`Parent: [\`${z}\`](${R})`),Q.push("")}}let H=D(Z,"command-index.md");return Q.push(`[Command Index](${H})`),Q.push(""),Q}function l(O,X){if(MO(O.path,X))return O;for(let Q of O.children){let Z=l(Q,X);if(Z)return Z}return}function MO(O,X){if(O.length!==X.length)return!1;for(let Q=0;Q<O.length;Q++)if(O[Q]!==X[Q])return!1;return!0}import{readFile as IO}from"fs/promises";import{join as _O}from"path";var P="crust.json";async function b(O){try{let X=await IO(_O(O,P),"utf-8"),Q=JSON.parse(X);if(typeof Q==="object"&&Q!==null&&"version"in Q&&typeof Q.version==="string")return Q.version;return null}catch{return null}}var t=/^[a-z0-9]+(-[a-z0-9]+)*$/;function n(O){return O.length>=1&&O.length<=64&&t.test(O)}function S(O){return O.startsWith("use-")?O:`use-${O}`}async function w(O){let{command:X,meta:Q,agents:Z,scope:H="global",clean:W=!0,force:$=!1}=O,q=S(Q.name);if(!n(q))throw Error(`Invalid skill name "${q}": must be 1\u201364 lowercase `+`alphanumeric characters and hyphens, no leading/trailing/consecutive hyphens. Pattern: ${t.source}`);let R={...Q,name:q},z=v(X),M=f(z,R),B=EO(z,R),U=[...M,...B].sort((J,Y)=>J.path<Y.path?-1:J.path>Y.path?1:0),K=[];for(let J of Z){let Y=L(J,H,R.name),V=await b(Y);if(V===null){if(await o(Y).then(()=>!0).catch(()=>!1)&&!$)throw new j({agent:J,outputDir:Y})}let T=V===null?"installed":V===R.version?"up-to-date":"updated";if(T==="up-to-date"){K.push({agent:J,outputDir:Y,files:[],status:"up-to-date"});continue}let OO=T==="updated"?V??void 0:void 0;if(W)await kO(Y);await TO(Y,U),K.push({agent:J,outputDir:Y,files:U.map((C)=>C.path),status:T,previousVersion:OO})}return{agents:K}}async function A(O){let{name:X,agents:Q,scope:Z="global"}=O,H=S(X),W=[];for(let $ of Q){let q=L($,Z,H);if(await o(q).then(()=>!0).catch(()=>!1))await s(q,{recursive:!0,force:!0}),W.push({agent:$,outputDir:q,status:"removed"});else W.push({agent:$,outputDir:q,status:"not-found"})}return{agents:W}}async function k(O){let{name:X,agents:Q,scope:Z="global"}=O,H=S(X),W=[];for(let $ of Q){let q=L($,Z,H),R=await b(q);W.push({agent:$,outputDir:q,installed:R!==null,version:R??void 0})}return{agents:W}}function EO(O,X){return[{path:P,content:SO(O,X)}]}function SO(O,X){let Q=a(O),Z={name:X.name,description:X.description,version:X.version,entrypoint:"SKILL.md",commands:Q};return`${JSON.stringify(Z,null,"\t")}
7
- `}function a(O){let X=[O.path.join(" ")];for(let Q of O.children)X.push(...a(Q));return X}async function kO(O){await s(O,{recursive:!0,force:!0})}async function TO(O,X){let Q=new Set;for(let H of X){let W=i(O,H.path),$=FO(W);Q.add($)}let Z=[...Q].sort();for(let H of Z)await wO(H,{recursive:!0});for(let H of X){let W=i(O,H.path);await LO(W,H.content,"utf-8")}}import{defineCommand as DO}from"@crustjs/core";import{confirm as PO,multiselect as bO,spinner as N}from"@crustjs/prompts";function e(O,X){return{name:O.meta.name,description:O.meta.description??"",version:X}}function AO(O){let X,Q=null;return{name:"skills",setup(Z,H){if(X=Z.rootCommand,O.command!==!1){let W=typeof O.command==="string"?O.command:"skill";Q=NO(X,O),H.addSubCommand(X,W,Q)}},async middleware(Z,H){if(Q&&Z.route?.command===Q){await H();return}let W=await F();if(W.length===0){await H();return}let $=O.autoInstall??!1,q=O.autoUpdate??!0,R=e(X,O.version),M=(await k({name:R.name,agents:W,scope:O.scope??"global"})).agents.filter((B)=>{if(!B.installed)return $;if(B.version!==R.version)return q;return!1});if(M.length>0)try{let B=await w({command:X,meta:R,agents:M.map((J)=>J.agent),scope:O.scope}),U=B.agents.filter((J)=>J.status==="installed").map((J)=>x[J.agent]),K=B.agents.filter((J)=>J.status==="updated").map((J)=>x[J.agent]);if(U.length>0)if(O.command!==!1){let J=`${X.meta.name} skill`;console.log(`Auto-installed skill "${R.name}" v${R.version} for ${U.join(", ")}. Manage with \`${J}\`.`)}else console.log(`Auto-installed skill "${R.name}" v${R.version} for ${U.join(", ")}.`);if(K.length>0)console.log(`Updated skill "${R.name}" to v${R.version} for ${K.join(", ")}.`)}catch(B){if(B instanceof j)console.warn(`Skill conflict: "${B.details.outputDir}" already exists but was not created by ${R.name}. Skipping auto-update. Delete or rename the conflicting skill to resolve.`);else throw B}await H()}}}function NO(O,X){return DO({meta:{name:"skill",description:"Manage agent skill installations"},async run(){let Q=e(O,X.version),Z=X.scope??"global",H=await F();if(H.length===0){console.log("No supported agents detected. Install Claude Code or OpenCode first.");return}let W=await k({name:Q.name,agents:H,scope:Z}),$=[],q=W.agents.map((K)=>{let J=K.installed?`v${K.version} installed`:"not installed";if(K.installed)$.push(K.agent);return{label:x[K.agent],value:K.agent,hint:J}}),R=await bO({message:"Select agents to install skills for",choices:q,default:$,required:!1}),z=R.filter((K)=>!$.includes(K)),M=R.filter((K)=>{let J=W.agents.find((Y)=>Y.agent===K);return J?.installed===!0&&J.version!==Q.version}),B=$.filter((K)=>!R.includes(K)),U=[...z,...M];if(U.length>0)try{let K=await N({message:"Installing skills...",task:async()=>w({command:O,meta:Q,agents:U,scope:Z})});console.log(`
8
- Installed "${Q.name}" v${Q.version}`);for(let J of K.agents)console.log(` ${x[J.agent]} \u2192 ${J.outputDir}`)}catch(K){if(K instanceof j)if(await PO({message:`"${K.details.outputDir}" already exists but was not created by Crust. Overwrite?`,default:!1})){let Y=await N({message:"Overwriting skill...",task:async()=>w({command:O,meta:Q,agents:[K.details.agent],scope:Z,force:!0})});console.log(`
9
- Installed "${Q.name}" v${Q.version}`);for(let V of Y.agents)console.log(` ${x[V.agent]} \u2192 ${V.outputDir}`)}else console.log(`
10
- Skipped ${x[K.details.agent]}`);else throw K}if(B.length>0){let J=(await N({message:"Removing skills...",task:async()=>A({name:Q.name,agents:B,scope:Z})})).agents.filter((Y)=>Y.status==="removed").map((Y)=>x[Y.agent]);if(J.length>0)console.log(`
11
- Removed from ${J.join(", ")}`)}if(U.length===0&&B.length===0)console.log("No changes.")}})}export{A as uninstallSkill,k as skillStatus,AO as skillPlugin,S as resolveSkillName,n as isValidSkillName,w as generateSkill,F as detectInstalledAgents,j as SkillConflictError};
2
+ import{access as JQ}from"fs/promises";import{homedir as h}from"os";import{join as G}from"path";var RQ=["claude-code","opencode"],V={"claude-code":"Claude Code",opencode:"OpenCode"};function F(X,Z,Q){let H=Z==="global"?h():process.cwd();switch(X){case"claude-code":return G(H,".claude","skills",Q);case"opencode":if(Z==="global")return G(H,".config","opencode","skills",Q);return G(H,".opencode","skills",Q)}}async function S(X){let Z=typeof X==="string"?{home:X}:X??{},Q=Z.scope??"global",H=Z.home??h(),W=Z.cwd??process.cwd(),$=[];for(let J of RQ){let R=qQ(J,Q,H,W),Y=!1;for(let K of R)if(Y=await JQ(K).then(()=>!0).catch(()=>!1),Y)break;if(Y)$.push(J)}return $}function qQ(X,Z,Q,H){if(Z==="project")switch(X){case"claude-code":return[G(H,".claude"),G(Q,".claude")];case"opencode":return[G(H,".opencode"),G(Q,".config","opencode")]}switch(X){case"claude-code":return[G(Q,".claude")];case"opencode":return[G(Q,".config","opencode")]}}class j extends Error{name="SkillConflictError";details;constructor(X){let Z=`Skill conflict for agent "${X.agent}": directory "${X.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=X}}import{access as s,mkdir as LQ,rm as n,writeFile as TQ}from"fs/promises";import{dirname as PQ,join as t}from"path";function u(X){return p(X,[])}function p(X,Z){let Q=YQ(X.meta.name),H=[...Z,Q],W=BQ(X.args),$=OQ(X.flags),J=zQ(X.subCommands,H);return{name:Q,path:H,description:X.meta.description,usage:X.meta.usage,runnable:typeof X.run==="function",args:W,flags:$,children:J}}function YQ(X){return X.trim().toLowerCase()}function BQ(X){if(!X||X.length===0)return[];return X.map(KQ)}function KQ(X){let Z={name:X.name,type:X.type,required:X.required===!0,variadic:X.variadic===!0};if(X.description!==void 0)Z.description=X.description;if(X.default!==void 0)Z.default=f(X.default);return Z}function OQ(X){if(!X)return[];return Object.keys(X).sort().map((Q)=>{return xQ(Q,X[Q])})}function xQ(X,Z){let Q={name:X,type:Z.type,required:Z.required===!0,multiple:Z.multiple===!0,aliases:GQ(Z.alias)};if(Z.description!==void 0)Q.description=Z.description;if(Z.default!==void 0)Q.default=f(Z.default);return Q}function GQ(X){if(X===void 0)return[];if(typeof X==="string")return[X];return[...X].sort()}function zQ(X,Z){return Object.keys(X).sort().map((H)=>{return p(X[H],Z)})}function f(X){if(Array.isArray(X))return JSON.stringify(X);return String(X)}function w(X){if(/[:#[\]{}&*!|>'"`,@?\\]|^\s|\s$|^---|[\n\r]/.test(X))return`"${X.replace(/\\/g,"\\\\").replace(/"/g,"\\\"").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}"`;return X}function g(X){return X.replace(/(?<!\\)\|/g,"\\|")}function c(X,Z){let Q=[],H=m(X);Q.push({path:"SKILL.md",content:UQ(X,Z)}),Q.push({path:"command-index.md",content:VQ(X,H)});for(let W of H){let $=z(W),J=W.children.length>0?IQ(W,X):MQ(W,X);Q.push({path:$,content:J})}return Q}function m(X){let Z=[X];for(let Q of X.children)Z.push(...m(Q));return Z}function z(X){if(X.path.length<=1)return`commands/${X.name}.md`;return`commands/${X.path.slice(1).join("/")}.md`}function E(X){return X.path.join(" ")}function y(X,Z){let Q=X.split("/").slice(0,-1),H=Z.split("/"),W=0;while(W<Q.length&&W<H.length&&Q[W]===H[W])W++;let $=Q.length-W,J=H.slice(W);if($===0)return J.join("/");return[...Array.from({length:$},()=>".."),...J].join("/")}function UQ(X,Z){let Q=[];if(Q.push("---"),Q.push(`name: ${w(Z.name)}`),Q.push(`description: ${w(Z.description)}`),Z.license)Q.push(`license: ${w(Z.license)}`);if(Z.compatibility)Q.push(`compatibility: ${w(Z.compatibility)}`);if(Z.disableModelInvocation)Q.push("disable-model-invocation: true");if(Z.allowedTools)Q.push(`allowed-tools: ${w(Z.allowedTools)}`);if(Q.push("metadata:"),Q.push(` version: "${Z.version}"`),Q.push("---"),Q.push(""),Q.push(`# ${Z.name}`),Q.push(""),X.description)Q.push(X.description),Q.push("");let H=Z.name.startsWith("use-")?Z.name.slice(4):Z.name;if(Q.push(`Use this skill when working with \`${H}\` commands, or when you need help with \`${H}\` syntax, flags, or subcommands.`),Q.push(""),Q.push("## Command Reference"),Q.push(""),Q.push("For the full list of commands and their documentation paths, see [command-index.md](command-index.md). **Do not read all command files at once.** Instead:"),Q.push(""),Q.push("1. Check [command-index.md](command-index.md) to find the relevant command"),Q.push("2. Read only the specific file from the `commands/` directory that you need"),Q.push(""),X.children.length>0){Q.push("## Available Commands"),Q.push("");for(let W of X.children){let $=z(W),J=W.description?` - ${W.description}`:"";Q.push(`- [\`${W.name}\`](${$})${J}`)}Q.push("")}if(X.runnable){Q.push("## Usage"),Q.push("");let W=z(X);Q.push(`The root command is directly executable. See [${X.name}](${W}) for usage details.`),Q.push("")}return Q.join(`
3
+ `)}function VQ(X,Z){let Q=[];Q.push("# Command Index"),Q.push(""),Q.push("| Command | Type | Documentation |"),Q.push("| ------- | ---- | ------------- |");for(let H of Z){let W=E(H),$=z(H),J=jQ(H);Q.push(`| \`${W}\` | ${J} | [${$}](${$}) |`)}return Q.push(""),Q.join(`
4
+ `)}function jQ(X){if(X.runnable&&X.children.length>0)return"runnable, group";if(X.runnable)return"runnable";return"group"}function MQ(X,Z){let Q=[],H=E(X);if(Q.push(`# \`${H}\``),Q.push(""),X.description)Q.push(X.description),Q.push("");if(Q.push("## Usage"),Q.push(""),X.usage)Q.push("```"),Q.push(X.usage),Q.push("```");else Q.push("```"),Q.push(d(X)),Q.push("```");if(Q.push(""),X.args.length>0)Q.push("## Arguments"),Q.push(""),Q.push(...r(X.args)),Q.push("");if(X.flags.length>0)Q.push("## Flags"),Q.push(""),Q.push(...l(X.flags)),Q.push("");return Q.push(...i(X,Z)),Q.join(`
5
+ `)}function IQ(X,Z){let Q=[],H=E(X),W=z(X);if(Q.push(`# \`${H}\``),Q.push(""),X.description)Q.push(X.description),Q.push("");if(X.runnable){if(Q.push("## Usage"),Q.push(""),X.usage)Q.push("```"),Q.push(X.usage),Q.push("```");else Q.push("```"),Q.push(d(X)),Q.push("```");if(Q.push(""),X.args.length>0)Q.push("## Arguments"),Q.push(""),Q.push(...r(X.args)),Q.push("");if(X.flags.length>0)Q.push("## Flags"),Q.push(""),Q.push(...l(X.flags)),Q.push("")}Q.push("## Subcommands"),Q.push("");for(let $ of X.children){let J=z($),R=y(W,J),Y=$.description?` - ${$.description}`:"";Q.push(`- [\`${$.name}\`](${R})${Y}`)}return Q.push(""),Q.push(...i(X,Z)),Q.join(`
6
+ `)}function d(X){let Z=[...X.path];for(let Q of X.args)if(Q.variadic)Z.push(Q.required?`<${Q.name}...>`:`[${Q.name}...]`);else Z.push(Q.required?`<${Q.name}>`:`[${Q.name}]`);if(X.flags.length>0)Z.push("[options]");return Z.join(" ")}function r(X){let Z=[];Z.push("| Argument | Type | Required | Description |"),Z.push("| -------- | ---- | -------- | ----------- |");for(let Q of X){let H=Q.variadic?`${Q.name}...`:Q.name,W=Q.required?"Yes":"No",$=g(_Q(Q));Z.push(`| \`${H}\` | ${Q.type} | ${W} | ${$} |`)}return Z}function _Q(X){let Z=[];if(X.description)Z.push(X.description);if(X.default!==void 0)Z.push(`Default: \`${X.default}\``);return Z.join(". ")||"-"}function l(X){let Z=[];Z.push("| Flag | Type | Required | Description |"),Z.push("| ---- | ---- | -------- | ----------- |");for(let Q of X){let H=wQ(Q),W=Q.required?"Yes":"No",$=g(kQ(Q));Z.push(`| ${H} | ${Q.type} | ${W} | ${$} |`)}return Z}function wQ(X){let Z=[`\`--${X.name}\``];for(let Q of X.aliases)Z.push(`\`-${Q}\``);return Z.join(", ")}function kQ(X){let Z=[];if(X.description)Z.push(X.description);if(X.multiple)Z.push("Can be specified multiple times");if(X.default!==void 0)Z.push(`Default: \`${X.default}\``);return Z.join(". ")||"-"}function i(X,Z){let Q=[],H=z(X);if(Q.push("---"),Q.push(""),X.path.length>1){let $=X.path.slice(0,-1),J=o(Z,$);if(J){let R=z(J),Y=y(H,R),K=E(J);Q.push(`Parent: [\`${K}\`](${Y})`),Q.push("")}}let W=y(H,"command-index.md");return Q.push(`[Command Index](${W})`),Q.push(""),Q}function o(X,Z){if(FQ(X.path,Z))return X;for(let Q of X.children){let H=o(Q,Z);if(H)return H}return}function FQ(X,Z){if(X.length!==Z.length)return!1;for(let Q=0;Q<X.length;Q++)if(X[Q]!==Z[Q])return!1;return!0}import{readFile as SQ}from"fs/promises";import{join as EQ}from"path";var C="crust.json";async function D(X){try{let Z=await SQ(EQ(X,C),"utf-8"),Q=JSON.parse(Z);if(typeof Q==="object"&&Q!==null&&"version"in Q&&typeof Q.version==="string")return Q.version;return null}catch{return null}}var a=/^[a-z0-9]+(-[a-z0-9]+)*$/;function e(X){return X.length>=1&&X.length<=64&&a.test(X)}function _(X){return X.startsWith("use-")?X:`use-${X}`}async function k(X){let{command:Z,meta:Q,agents:H,scope:W="global",clean:$=!0,force:J=!1}=X,R=_(Q.name);if(!e(R))throw Error(`Invalid skill name "${R}": must be 1\u201364 lowercase `+`alphanumeric characters and hyphens, no leading/trailing/consecutive hyphens. Pattern: ${a.source}`);let Y={...Q,name:R},K=u(Z),x=c(K,Y),b=bQ(K,Y),M=[...x,...b].sort((q,B)=>q.path<B.path?-1:q.path>B.path?1:0),U=[];for(let q of H){let B=F(q,W,Y.name),O=await D(B);if(O===null){if(await s(B).then(()=>!0).catch(()=>!1)&&!J)throw new j({agent:q,outputDir:B})}let I=O===null?"installed":O===Y.version?"up-to-date":"updated";if(I==="up-to-date"){U.push({agent:q,outputDir:B,files:[],status:"up-to-date"});continue}let $Q=I==="updated"?O??void 0:void 0;if($)await CQ(B);await DQ(B,M),U.push({agent:q,outputDir:B,files:M.map((v)=>v.path),status:I,previousVersion:$Q})}return{agents:U}}async function A(X){let{name:Z,agents:Q,scope:H="global"}=X,W=_(Z),$=[];for(let J of Q){let R=F(J,H,W);if(await s(R).then(()=>!0).catch(()=>!1))await n(R,{recursive:!0,force:!0}),$.push({agent:J,outputDir:R,status:"removed"});else $.push({agent:J,outputDir:R,status:"not-found"})}return{agents:$}}async function L(X){let{name:Z,agents:Q,scope:H="global"}=X,W=_(Z),$=[];for(let J of Q){let R=F(J,H,W),Y=await D(R);$.push({agent:J,outputDir:R,installed:Y!==null,version:Y??void 0})}return{agents:$}}function bQ(X,Z){return[{path:C,content:yQ(X,Z)}]}function yQ(X,Z){let Q=QQ(X),H={name:Z.name,description:Z.description,version:Z.version,entrypoint:"SKILL.md",commands:Q};return`${JSON.stringify(H,null,"\t")}
7
+ `}function QQ(X){let Z=[X.path.join(" ")];for(let Q of X.children)Z.push(...QQ(Q));return Z}async function CQ(X){await n(X,{recursive:!0,force:!0})}async function DQ(X,Z){let Q=new Set;for(let W of Z){let $=t(X,W.path),J=PQ($);Q.add(J)}let H=[...Q].sort();for(let W of H)await LQ(W,{recursive:!0});for(let W of Z){let $=t(X,W.path);await TQ($,W.content,"utf-8")}}import{defineCommand as AQ,VALIDATION_MODE_ENV as NQ}from"@crustjs/core";import{confirm as vQ,multiselect as hQ,spinner as P}from"@crustjs/prompts";import{bold as N,dim as T,yellow as XQ}from"@crustjs/style";var ZQ="skill",HQ="global";function WQ(X,Z){return{name:X.meta.name,description:X.meta.description??"",version:Z}}async function uQ(X,Z){let Q=Z.scope??HQ,H=await S({scope:Q});if(H.length===0)return;let W=WQ(X,Z.version),J=(await L({name:W.name,agents:H,scope:Q})).agents.filter((R)=>R.installed&&R.version!==W.version);if(J.length===0)return;try{await P({message:"Updating skills...",task:async({updateMessage:R})=>{let Y=await k({command:X,meta:W,agents:J.map((x)=>x.agent),scope:Z.scope}),K=Y.agents.filter((x)=>x.status==="updated").map((x)=>V[x.agent]);if(K.length>0)R(`Updated skill "${_(W.name)}" to v${W.version} for ${K.join(", ")}`);return Y}})}catch(R){if(R instanceof j)console.warn(XQ(`Skill conflict: "${R.details.outputDir}" already exists but was not created by ${W.name}. Skipping auto-update. Delete or rename the conflicting skill to resolve.`));else throw R}}function pQ(X){let Z;return{name:"skills",async setup(Q,H){Z=Q.rootCommand;let W=typeof X.command==="string"?X.command:ZQ;if(X.command!==!1)H.addSubCommand(Z,W,fQ(Z,X));if(process.env[NQ]==="1")return;if(X.command!==!1&&Q.argv[0]===W)return;if(X.autoUpdate!==!1)await uQ(Z,X)}}}function fQ(X,Z){return AQ({meta:{name:ZQ,description:"Manage agent skill installations"},async run(){let Q=WQ(X,Z.version),H=Z.scope??HQ,W=await S({scope:H});if(W.length===0){console.log(XQ("No supported agents detected. Install Claude Code or OpenCode first."));return}let $=await L({name:Q.name,agents:W,scope:H}),J=[],R=$.agents.map((q)=>{let B=q.installed?`v${q.version} installed`:"not installed";if(q.installed)J.push(q.agent);return{label:V[q.agent],value:q.agent,hint:B}}),Y=process.stdin.isTTY,K=await hQ({message:"Select agents to install skills for",choices:R,default:J,initial:!Y?W:void 0,required:!1}),x=K.filter((q)=>!J.includes(q)),b=K.filter((q)=>{let B=$.agents.find((O)=>O.agent===q);return B?.installed===!0&&B.version!==Q.version}),M=J.filter((q)=>!K.includes(q)),U=[...x,...b];if(U.length>0)try{let q=await P({message:"Installing skills...",task:async()=>k({command:X,meta:Q,agents:U,scope:H})});console.log(`
8
+ ${N(`Installed "${Q.name}" v${Q.version}`)}`);for(let B of q.agents)console.log(T(` ${V[B.agent]} \u2192 ${B.outputDir}`))}catch(q){if(q instanceof j)if(await vQ({message:`"${q.details.outputDir}" already exists but was not created by Crust. Overwrite?`,default:!1,initial:!Y?!1:void 0})){let O=await P({message:"Overwriting skill...",task:async()=>k({command:X,meta:Q,agents:[q.details.agent],scope:H,force:!0})});console.log(`
9
+ ${N(`Installed "${Q.name}" v${Q.version}`)}`);for(let I of O.agents)console.log(T(` ${V[I.agent]} \u2192 ${I.outputDir}`))}else console.log(T(`
10
+ Skipped ${V[q.details.agent]}`));else throw q}if(M.length>0){let B=(await P({message:"Removing skills...",task:async()=>A({name:Q.name,agents:M,scope:H})})).agents.filter((O)=>O.status==="removed").map((O)=>V[O.agent]);if(B.length>0)console.log(`
11
+ ${N(`Removed from ${B.join(", ")}`)}`)}if(U.length===0&&M.length===0)console.log(T("No changes."))}})}export{A as uninstallSkill,L as skillStatus,pQ as skillPlugin,_ as resolveSkillName,e as isValidSkillName,k as generateSkill,S as detectInstalledAgents,j as SkillConflictError};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crustjs/skills",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Agent skill generation from Crust command definitions",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -41,15 +41,17 @@
41
41
  "check:types": "tsc --noEmit",
42
42
  "test": "bun test"
43
43
  },
44
+ "dependencies": {
45
+ "@crustjs/prompts": "0.0.7",
46
+ "@crustjs/style": "0.0.4"
47
+ },
44
48
  "devDependencies": {
45
49
  "@crustjs/config": "0.0.0",
46
50
  "@crustjs/core": "0.0.9",
47
- "@crustjs/prompts": "0.0.6",
48
51
  "bunup": "^0.16.29"
49
52
  },
50
53
  "peerDependencies": {
51
54
  "@crustjs/core": "0.0.9",
52
- "@crustjs/prompts": "0.0.6",
53
55
  "typescript": "^5"
54
56
  }
55
57
  }