@crustjs/skills 0.1.1 → 0.1.2

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
@@ -25,17 +25,17 @@ import { generateSkill } from "@crustjs/skills";
25
25
  import { rootCommand } from "./commands.ts";
26
26
 
27
27
  const result = await generateSkill({
28
- command: rootCommand,
29
- meta: {
30
- name: "my-cli",
31
- description: "CLI tool for managing widgets",
32
- version: "1.0.0",
33
- },
34
- agents: ["opencode", "claude-code"],
28
+ command: rootCommand,
29
+ meta: {
30
+ name: "my-cli",
31
+ description: "CLI tool for managing widgets",
32
+ version: "1.0.0",
33
+ },
34
+ agents: ["opencode", "claude-code"],
35
35
  });
36
36
 
37
37
  for (const agent of result.agents) {
38
- console.log(`${agent.agent}: ${agent.status} -> ${agent.outputDir}`);
38
+ console.log(`${agent.agent}: ${agent.status} -> ${agent.outputDir}`);
39
39
  }
40
40
  ```
41
41
 
@@ -48,26 +48,26 @@ import { Crust } from "@crustjs/core";
48
48
  import { skillPlugin } from "@crustjs/skills";
49
49
 
50
50
  const app = new Crust("my-cli")
51
- .meta({ description: "My CLI" })
52
- .use(
53
- skillPlugin({
54
- version: "1.0.0",
55
- instructions: `
51
+ .meta({ description: "My CLI" })
52
+ .use(
53
+ skillPlugin({
54
+ version: "1.0.0",
55
+ instructions: `
56
56
  Prefer readonly commands before mutating project state.
57
57
 
58
58
  ## Response Policy
59
59
 
60
60
  - Read the relevant command doc before suggesting flags.
61
61
  `,
62
- // autoUpdate: true (default) — silently updates installed skills
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")
66
- }),
67
- )
68
- .run(() => {
69
- console.log("hello");
70
- });
62
+ // autoUpdate: true (default) — silently updates installed skills
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")
66
+ }),
67
+ )
68
+ .run(() => {
69
+ console.log("hello");
70
+ });
71
71
 
72
72
  await app.execute();
73
73
  ```
@@ -89,29 +89,29 @@ import { skillPlugin } from "@crustjs/skills";
89
89
  import pkg from "./package.json" with { type: "json" };
90
90
 
91
91
  const app = new Crust("my-cli")
92
- .meta({ description: "My CLI" })
93
- .use(
94
- skillPlugin({
95
- version: pkg.version,
96
- customSkills: [
97
- // Inherits `version: pkg.version` from the plugin — the typical
98
- // case when the bundle ships in the same package as the CLI.
99
- {
100
- name: "funnel-builder",
101
- // Resolved against the nearest package.json walking up from
102
- // process.argv[1] — same rules as installSkillBundle().
103
- sourceDir: "skills/funnel-builder",
104
- },
105
- // Explicit override for an independently-versioned bundle.
106
- {
107
- name: "vendored-toolkit",
108
- sourceDir: "skills/vendored-toolkit",
109
- version: "0.3.0",
110
- },
111
- ],
112
- }),
113
- )
114
- .run(() => {});
92
+ .meta({ description: "My CLI" })
93
+ .use(
94
+ skillPlugin({
95
+ version: pkg.version,
96
+ customSkills: [
97
+ // Inherits `version: pkg.version` from the plugin — the typical
98
+ // case when the bundle ships in the same package as the CLI.
99
+ {
100
+ name: "funnel-builder",
101
+ // Resolved against the nearest package.json walking up from
102
+ // process.argv[1] — same rules as installSkillBundle().
103
+ sourceDir: "skills/funnel-builder",
104
+ },
105
+ // Explicit override for an independently-versioned bundle.
106
+ {
107
+ name: "vendored-toolkit",
108
+ sourceDir: "skills/vendored-toolkit",
109
+ version: "0.3.0",
110
+ },
111
+ ],
112
+ }),
113
+ )
114
+ .run(() => {});
115
115
 
116
116
  await app.execute();
117
117
  ```
@@ -161,29 +161,27 @@ opt out, or an explicit array to scope the install.
161
161
  import { Crust } from "@crustjs/core";
162
162
  import { generateSkill } from "@crustjs/skills";
163
163
 
164
- export const app = new Crust("my-cli")
165
- .meta({ description: "My CLI" })
166
- .run(async (ctx) => {
167
- // Defaults to universal + agents detected on PATH. Idempotent: targets
168
- // that already match the current version are returned as `up-to-date`.
169
- const result = await generateSkill({
170
- command: ctx.command,
171
- meta: {
172
- name: ctx.command.meta.name,
173
- description: ctx.command.meta.description ?? "",
174
- version: "1.0.0",
175
- },
176
- scope: "global",
177
- });
178
-
179
- const changed = result.agents.filter((a) => a.status !== "up-to-date");
180
- if (changed.length > 0) {
181
- console.log(`Installed or updated skills for ${changed.length} target(s).`);
182
- }
183
- });
164
+ export const app = new Crust("my-cli").meta({ description: "My CLI" }).run(async (ctx) => {
165
+ // Defaults to universal + agents detected on PATH. Idempotent: targets
166
+ // that already match the current version are returned as `up-to-date`.
167
+ const result = await generateSkill({
168
+ command: ctx.command,
169
+ meta: {
170
+ name: ctx.command.meta.name,
171
+ description: ctx.command.meta.description ?? "",
172
+ version: "1.0.0",
173
+ },
174
+ scope: "global",
175
+ });
176
+
177
+ const changed = result.agents.filter((a) => a.status !== "up-to-date");
178
+ if (changed.length > 0) {
179
+ console.log(`Installed or updated skills for ${changed.length} target(s).`);
180
+ }
181
+ });
184
182
 
185
183
  if (import.meta.main) {
186
- await app.execute();
184
+ await app.execute();
187
185
  }
188
186
  ```
189
187
 
@@ -208,14 +206,14 @@ import { Crust } from "@crustjs/core";
208
206
 
209
207
  // Export the command — used by skill generation.
210
208
  export const rootCommand = new Crust("my-cli")
211
- .meta({ description: "My CLI tool" })
212
- .run(({ args }) => {
213
- console.log("Hello from my-cli!");
214
- });
209
+ .meta({ description: "My CLI tool" })
210
+ .run(({ args }) => {
211
+ console.log("Hello from my-cli!");
212
+ });
215
213
 
216
214
  // Only execute when run directly — not when imported for generation.
217
215
  if (import.meta.main) {
218
- await rootCommand.execute();
216
+ await rootCommand.execute();
219
217
  }
220
218
  ```
221
219
 
@@ -235,35 +233,35 @@ import { Crust } from "@crustjs/core";
235
233
  import { annotate, skillPlugin } from "@crustjs/skills";
236
234
 
237
235
  const deploy = annotate(
238
- new Crust("deploy")
239
- .meta({ description: "Deploy the application" })
240
- .flags({
241
- "dry-run": { type: "boolean", description: "Preview changes only" },
242
- })
243
- .run(() => {
244
- // ...
245
- }),
246
- [
247
- "Prefer `--dry-run` before executing deployment changes.",
248
- "Ask for confirmation before production deployments.",
249
- ],
236
+ new Crust("deploy")
237
+ .meta({ description: "Deploy the application" })
238
+ .flags({
239
+ "dry-run": { type: "boolean", description: "Preview changes only" },
240
+ })
241
+ .run(() => {
242
+ // ...
243
+ }),
244
+ [
245
+ "Prefer `--dry-run` before executing deployment changes.",
246
+ "Ask for confirmation before production deployments.",
247
+ ],
250
248
  );
251
249
 
252
250
  const app = new Crust("my-cli")
253
- .meta({ description: "My CLI" })
254
- .use(
255
- skillPlugin({
256
- version: "1.0.0",
257
- instructions: `
251
+ .meta({ description: "My CLI" })
252
+ .use(
253
+ skillPlugin({
254
+ version: "1.0.0",
255
+ instructions: `
258
256
  Read command docs before suggesting exact flags.
259
257
 
260
258
  ## Answer Style
261
259
 
262
260
  - Prefer exact syntax copied from the relevant command file.
263
261
  `,
264
- }),
265
- )
266
- .command(deploy);
262
+ }),
263
+ )
264
+ .command(deploy);
267
265
  ```
268
266
 
269
267
  This pattern lets `crust skills generate` import the command definition without triggering `app.execute()`.
@@ -321,18 +319,18 @@ The `meta.name` must be a valid skill name — lowercase alphanumeric with hyphe
321
319
  import { generateSkill } from "@crustjs/skills";
322
320
 
323
321
  const result = await generateSkill({
324
- command: rootCommand,
325
- meta: {
326
- name: "my-cli",
327
- description: "My CLI tool",
328
- version: "1.0.0",
329
- instructions: ["Prefer readonly commands before making changes."],
330
- },
331
- agents: ["opencode"],
332
- scope: "project", // default: "global"
333
- installMode: "auto", // default: "auto" — symlink first, fallback to copy
334
- clean: true, // default: true — removes existing skill dir first
335
- force: false, // default: false — throws SkillConflictError if dir exists without crust.json
322
+ command: rootCommand,
323
+ meta: {
324
+ name: "my-cli",
325
+ description: "My CLI tool",
326
+ version: "1.0.0",
327
+ instructions: ["Prefer readonly commands before making changes."],
328
+ },
329
+ agents: ["opencode"],
330
+ scope: "project", // default: "global"
331
+ installMode: "auto", // default: "auto" — symlink first, fallback to copy
332
+ clean: true, // default: true — removes existing skill dir first
333
+ force: false, // default: false — set true to rewrite same-version output or overwrite conflicts
336
334
  });
337
335
 
338
336
  // result.agents — per-agent install results
@@ -360,8 +358,8 @@ const manifest = buildManifest(rootCommand);
360
358
  const files = renderSkill(manifest, { name: "my-cli", description: "My CLI" });
361
359
 
362
360
  for (const file of files) {
363
- console.log(file.path); // e.g. "SKILL.md", "commands/serve.md"
364
- console.log(file.content); // markdown content
361
+ console.log(file.path); // e.g. "SKILL.md", "commands/serve.md"
362
+ console.log(file.content); // markdown content
365
363
  }
366
364
  ```
367
365
 
@@ -402,15 +400,15 @@ The `SkillMeta` object controls the generated `SKILL.md` frontmatter. Beyond the
402
400
 
403
401
  ```ts
404
402
  const meta: SkillMeta = {
405
- name: "my-cli",
406
- description: "CLI tool for managing widgets",
407
- version: "1.0.0",
408
-
409
- // Optional fields — emitted in SKILL.md YAML frontmatter when set
410
- allowedTools: "Bash(my-cli *) Read Grep", // Pre-approved tools (avoids per-use prompts)
411
- license: "MIT", // License name or reference
412
- compatibility: "Requires my-cli on PATH", // Environment requirements (max 500 chars)
413
- disableModelInvocation: false, // true = agent won't auto-load; user must invoke manually
403
+ name: "my-cli",
404
+ description: "CLI tool for managing widgets",
405
+ version: "1.0.0",
406
+
407
+ // Optional fields — emitted in SKILL.md YAML frontmatter when set
408
+ allowedTools: "Bash(my-cli *) Read Grep", // Pre-approved tools (avoids per-use prompts)
409
+ license: "MIT", // License name or reference
410
+ compatibility: "Requires my-cli on PATH", // Environment requirements (max 500 chars)
411
+ disableModelInvocation: false, // true = agent won't auto-load; user must invoke manually
414
412
  };
415
413
  ```
416
414
 
@@ -448,11 +446,11 @@ skills/my-cli/
448
446
 
449
447
  ### File Details
450
448
 
451
- | File | Purpose |
452
- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
453
- | `SKILL.md` | Agent entrypoint with YAML frontmatter and an embedded command reference table listing every command path, type (runnable/group), and documentation link. |
454
- | `commands/*.md` | Per-command reference files. Leaf commands include usage, arguments, flags, defaults, and aliases. Group commands list subcommands with links. |
455
- | `crust.json` | Crust-specific JSON metadata: name, description, and version. Also serves as an ownership marker — its presence indicates the skill was generated by Crust. |
449
+ | File | Purpose |
450
+ | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
451
+ | `SKILL.md` | Agent entrypoint with YAML frontmatter and an embedded command reference table listing every command path, type (runnable/group), and documentation link. |
452
+ | `commands/*.md` | Per-command reference files. Leaf commands include usage, arguments, flags, defaults, and aliases. Group commands list subcommands with links. |
453
+ | `crust.json` | Crust-specific JSON metadata: name, description, and version. Also serves as an ownership marker — its presence indicates the skill was generated by Crust. |
456
454
 
457
455
  ## Conflict Detection
458
456
 
@@ -485,26 +483,24 @@ uninstall the existing skill first.
485
483
  import { generateSkill, SkillConflictError } from "@crustjs/skills";
486
484
 
487
485
  try {
488
- await generateSkill({ command, meta, agents });
486
+ await generateSkill({ command, meta, agents });
489
487
  } catch (err) {
490
- if (!(err instanceof SkillConflictError)) throw err;
491
-
492
- if (err.details.kindMismatch) {
493
- const { existing, attempted } = err.details.kindMismatch;
494
- console.error(
495
- `Cannot install ${attempted} skill at ${err.details.outputDir} — ` +
496
- `existing skill was installed as ${existing}.`,
497
- );
498
- } else if (err.details.manifestMalformed) {
499
- console.error(
500
- `crust.json at ${err.details.outputDir} is malformed: ` +
501
- `${err.details.manifestMalformed.reason}.`,
502
- );
503
- } else {
504
- console.error(
505
- `${err.details.outputDir} exists but was not created by Crust.`,
506
- );
507
- }
488
+ if (!(err instanceof SkillConflictError)) throw err;
489
+
490
+ if (err.details.kindMismatch) {
491
+ const { existing, attempted } = err.details.kindMismatch;
492
+ console.error(
493
+ `Cannot install ${attempted} skill at ${err.details.outputDir} — ` +
494
+ `existing skill was installed as ${existing}.`,
495
+ );
496
+ } else if (err.details.manifestMalformed) {
497
+ console.error(
498
+ `crust.json at ${err.details.outputDir} is malformed: ` +
499
+ `${err.details.manifestMalformed.reason}.`,
500
+ );
501
+ } else {
502
+ console.error(`${err.details.outputDir} exists but was not created by Crust.`);
503
+ }
508
504
  }
509
505
  ```
510
506
 
@@ -557,11 +553,11 @@ import { installSkillBundle } from "@crustjs/skills";
557
553
  import pkg from "./package.json" with { type: "json" };
558
554
 
559
555
  await installSkillBundle({
560
- // Resolved relative to the nearest package.json walking up from
561
- // process.argv[1]. You can also pass an absolute string or a file: URL.
562
- sourceDir: "skills/funnel-builder",
563
- agents: ["claude-code", "opencode"],
564
- version: pkg.version,
556
+ // Resolved relative to the nearest package.json walking up from
557
+ // process.argv[1]. You can also pass an absolute string or a file: URL.
558
+ sourceDir: "skills/funnel-builder",
559
+ agents: ["claude-code", "opencode"],
560
+ version: pkg.version,
565
561
  });
566
562
  ```
567
563
 
@@ -585,9 +581,9 @@ publishes multiple bundles with independent versions:
585
581
 
586
582
  ```ts
587
583
  await installSkillBundle({
588
- sourceDir: "skills/funnel-builder",
589
- agents: ["claude-code"],
590
- version: "2.0.0",
584
+ sourceDir: "skills/funnel-builder",
585
+ agents: ["claude-code"],
586
+ version: "2.0.0",
591
587
  });
592
588
  ```
593
589
 
@@ -599,15 +595,15 @@ await installSkillBundle({
599
595
 
600
596
  ### Options
601
597
 
602
- | Option | Type | Default | Description |
603
- | ------------- | ----------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------- |
604
- | `sourceDir` | `string \| URL` | — required | Bundle directory. Absolute path, `file:` URL, or relative path resolved from the nearest `package.json`. |
605
- | `agents` | `AgentTarget[]` | — required | Agents to install for. `[]` validates the bundle without installing (no auto-detection — unlike `generateSkill()`). |
606
- | `version` | `string` | — required | Recorded in `crust.json` and compared on subsequent installs. |
607
- | `scope` | `"global" \| "project"` | `"global"` | Install scope. When `process.cwd()` is the home directory, `"project"` normalizes to `"global"`. |
608
- | `installMode` | `"auto" \| "symlink" \| "copy"` | `"auto"` | Same semantics as `generateSkill()`. `"auto"` symlinks from the canonical store, falling back to copy. |
609
- | `clean` | `boolean` | `true` | Remove the existing skill directory before writing. |
610
- | `force` | `boolean` | `false` | Overwrite a conflicting directory (no `crust.json`, kind mismatch, or malformed manifest) instead of throwing. |
598
+ | Option | Type | Default | Description |
599
+ | ------------- | ------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------- |
600
+ | `sourceDir` | `string \| URL` | — required | Bundle directory. Absolute path, `file:` URL, or relative path resolved from the nearest `package.json`. |
601
+ | `agents` | `AgentTarget[]` | — required | Agents to install for. `[]` validates the bundle without installing (no auto-detection — unlike `generateSkill()`). |
602
+ | `version` | `string` | — required | Recorded in `crust.json` and compared on subsequent installs. |
603
+ | `scope` | `"global" \| "project"` | `"global"` | Install scope. When `process.cwd()` is the home directory, `"project"` normalizes to `"global"`. |
604
+ | `installMode` | `"auto" \| "symlink" \| "copy"` | `"auto"` | Same semantics as `generateSkill()`. `"auto"` symlinks from the canonical store, falling back to copy. |
605
+ | `clean` | `boolean` | `true` | Remove the existing skill directory before writing. |
606
+ | `force` | `boolean` | `false` | Rewrite even when the recorded version is unchanged, and overwrite a conflicting directory instead of throwing. |
611
607
 
612
608
  ### What gets copied
613
609
 
@@ -618,8 +614,9 @@ UTF-8 to read its required frontmatter.
618
614
 
619
615
  Bundle content changes do not propagate without a `version` bump:
620
616
  identical-version reinstalls report `up-to-date` and leave the canonical
621
- store untouched. Pass a fresh `version` (typically wired to the consuming
622
- package's `package.json` `version`) whenever the bundle contents change.
617
+ store untouched, unless `force: true` is passed. Pass a fresh `version`
618
+ (typically wired to the consuming package's `package.json` `version`)
619
+ whenever the bundle contents change.
623
620
 
624
621
  Bundle contents are copied as authored — no implicit name-based filtering.
625
622
  Dotfiles, `node_modules/`, `.DS_Store`, and editor cruft are all copied if
@@ -639,9 +636,9 @@ Two gotchas trip up bundle authors who publish to npm:
639
636
 
640
637
  ```json
641
638
  {
642
- "name": "acme-skills",
643
- "version": "1.0.0",
644
- "files": ["dist", "skills"]
639
+ "name": "acme-skills",
640
+ "version": "1.0.0",
641
+ "files": ["dist", "skills"]
645
642
  }
646
643
  ```
647
644
 
@@ -654,9 +651,9 @@ Two gotchas trip up bundle authors who publish to npm:
654
651
  import skillsPkg from "acme-skills/package.json" with { type: "json" };
655
652
 
656
653
  await installSkillBundle({
657
- sourceDir: new URL(import.meta.resolve("acme-skills/skills/funnel-builder")),
658
- agents: ["claude-code"],
659
- version: skillsPkg.version,
654
+ sourceDir: new URL(import.meta.resolve("acme-skills/skills/funnel-builder")),
655
+ agents: ["claude-code"],
656
+ version: skillsPkg.version,
660
657
  });
661
658
  ```
662
659
 
package/dist/index.d.ts CHANGED
@@ -170,9 +170,10 @@ interface GenerateOptions {
170
170
  */
171
171
  clean?: boolean;
172
172
  /**
173
- * When `true`, overwrite an existing skill directory even if it was not
174
- * created by Crust (i.e. has no `crust.json`). Without this flag, a
175
- * conflict throws a {@link SkillConflictError}.
173
+ * When `true`, rewrite the generated skill even when the recorded version is
174
+ * unchanged, and overwrite an existing conflicting directory (for example,
175
+ * no `crust.json`, malformed `crust.json`, or a different skill kind)
176
+ * instead of throwing {@link SkillConflictError}.
176
177
  * @default false
177
178
  */
178
179
  force?: boolean;
@@ -240,8 +241,8 @@ interface InstallSkillBundleOptions {
240
241
  * Required. Typically wired to the consuming package's `package.json`
241
242
  * `version` (e.g. via `import pkg from "./package.json" with { type:
242
243
  * "json" }`). Identical-version reinstalls report `up-to-date` and skip
243
- * the canonical-store rewrite, so bump this whenever bundle contents
244
- * change.
244
+ * the canonical-store rewrite (unless `force: true` is passed), so bump
245
+ * this whenever bundle contents change.
245
246
  */
246
247
  version: string;
247
248
  /**
@@ -260,8 +261,10 @@ interface InstallSkillBundleOptions {
260
261
  */
261
262
  clean?: boolean;
262
263
  /**
263
- * When `true`, overwrite an existing skill directory even if it conflicts
264
- * (no `crust.json`, or a `crust.json` whose `kind` differs from `"bundle"`).
264
+ * When `true`, rewrite the bundle even when the recorded version is
265
+ * unchanged, and overwrite an existing conflicting directory (for example,
266
+ * no `crust.json`, malformed `crust.json`, or a different skill kind)
267
+ * instead of throwing {@link SkillConflictError}.
265
268
  * @default false
266
269
  */
267
270
  force?: boolean;
@@ -646,13 +649,15 @@ declare function annotate<T extends SkillCommandTarget>(target: T, annotations:
646
649
  * consuming package's `package.json` `version`.
647
650
  *
648
651
  * Bundles and generated skills cannot share a name unless the existing
649
- * install is removed first. To overwrite a kind-mismatched install, pass
650
- * `force: true`.
652
+ * install is removed first. `force: true` overwrites a conflicting install
653
+ * (no `crust.json`, malformed `crust.json`, or kind mismatch) and also
654
+ * rewrites a same-version bundle.
651
655
  *
652
656
  * @param options - Bundle install options (see {@link InstallSkillBundleOptions})
653
657
  * @returns Per-agent install results
654
- * @throws {SkillConflictError} If the canonical store exists with a different
655
- * kind or with no `crust.json` (and `force` is not set).
658
+ * @throws {SkillConflictError} If the canonical store exists with no
659
+ * `crust.json`, a malformed `crust.json`, or a different kind (and `force`
660
+ * is not set).
656
661
  * @throws {Error} If `SKILL.md` is missing, its frontmatter lacks `name:` or
657
662
  * `description:`, the declared `name` is not a valid skill name, the
658
663
  * declared `name` does not match `expectedName` when set, the source
@@ -739,13 +744,15 @@ interface SkillConflictDetails {
739
744
  * Thrown when an install entrypoint detects that the target skill directory
740
745
  * already exists but cannot be overwritten safely.
741
746
  *
742
- * Two flavours:
747
+ * Three flavours:
743
748
  * - **No `crust.json`** — directory exists but was not created by Crust.
744
749
  * This prevents Crust from silently overwriting a skill that was manually
745
750
  * created or installed by another tool.
751
+ * - **Malformed `crust.json`** — directory is Crust-owned but its manifest
752
+ * cannot be interpreted (see {@link SkillConflictDetails.manifestMalformed}).
746
753
  * - **Kind mismatch** — directory was created by Crust but with a different
747
754
  * {@link SkillKind} (e.g. an existing `generated` skill collides with an
748
- * incoming `bundle` install). `force: true` bypasses both cases.
755
+ * incoming `bundle` install). `force: true` bypasses all three cases.
749
756
  *
750
757
  * @example
751
758
  * ```ts
@@ -807,7 +814,7 @@ declare function resolveSkillName(name: string): string;
807
814
  *
808
815
  * @param options - Generation options including command, metadata, agents, and scope
809
816
  * @returns Per-agent installation results
810
- * @throws {SkillConflictError} If the output directory exists but was not created by Crust
817
+ * @throws {SkillConflictError} If the output directory conflicts (no `crust.json`, malformed `crust.json`, or kind mismatch) and `force` is not set
811
818
  *
812
819
  * @example
813
820
  * ```ts
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  // @bun
2
- import{accessSync as wX,constants as MX,statSync as TX}from"fs";import{homedir as JQ}from"os";import{delimiter as _X,join as J}from"path";var y=J(".agents","skills"),OX=J(".crust","skills");function NQ(Q){if(Q!==JQ())return J(Q,".config");let X=process.env.XDG_CONFIG_HOME?.trim();return X&&X.length>0?X:J(Q,".config")}function f(Q){return J(Q,".agents","skills")}function jX(Q){return J(Q,".crust","skills")}function u(Q){return Q==="project"&&process.cwd()===JQ()?"global":Q}var s={amp:{label:"Amp",class:"universal",projectSkillsDir:y,globalSkillsDir:f},adal:{label:"AdaL",class:"additional",projectSkillsDir:J(".adal","skills"),globalSkillsDir:(Q)=>J(Q,".adal","skills"),detectCommands:["adal"]},antigravity:{label:"Antigravity",class:"additional",projectSkillsDir:J(".agent","skills"),globalSkillsDir:(Q)=>J(Q,".gemini","antigravity","skills"),detectCommands:["antigravity"]},augment:{label:"Augment",class:"additional",projectSkillsDir:J(".augment","skills"),globalSkillsDir:(Q)=>J(Q,".augment","skills"),detectCommands:["augment"]},"claude-code":{label:"Claude Code",class:"additional",projectSkillsDir:J(".claude","skills"),globalSkillsDir:(Q)=>J(process.env.CLAUDE_CONFIG_DIR?.trim()||J(Q,".claude"),"skills"),detectCommands:["claude","claude-code"]},cline:{label:"Cline",class:"universal",projectSkillsDir:y,globalSkillsDir:f},codebuddy:{label:"CodeBuddy",class:"additional",projectSkillsDir:J(".codebuddy","skills"),globalSkillsDir:(Q)=>J(Q,".codebuddy","skills"),detectCommands:["codebuddy"]},codex:{label:"Codex",class:"universal",projectSkillsDir:y,globalSkillsDir:f},"command-code":{label:"Command Code",class:"additional",projectSkillsDir:J(".commandcode","skills"),globalSkillsDir:(Q)=>J(Q,".commandcode","skills"),detectCommands:["command-code","commandcode"]},continue:{label:"Continue",class:"additional",projectSkillsDir:J(".continue","skills"),globalSkillsDir:(Q)=>J(Q,".continue","skills"),detectCommands:["continue"]},cortex:{label:"Cortex Code",class:"additional",projectSkillsDir:J(".cortex","skills"),globalSkillsDir:(Q)=>J(Q,".snowflake","cortex","skills"),detectCommands:["cortex"]},crush:{label:"Crush",class:"additional",projectSkillsDir:J(".crush","skills"),globalSkillsDir:(Q)=>J(NQ(Q),"crush","skills"),detectCommands:["crush"]},cursor:{label:"Cursor",class:"universal",projectSkillsDir:y,globalSkillsDir:f},droid:{label:"Droid",class:"additional",projectSkillsDir:J(".factory","skills"),globalSkillsDir:(Q)=>J(Q,".factory","skills"),detectCommands:["droid"]},"gemini-cli":{label:"Gemini CLI",class:"universal",projectSkillsDir:y,globalSkillsDir:f},"github-copilot":{label:"GitHub Copilot",class:"universal",projectSkillsDir:y,globalSkillsDir:f},goose:{label:"Goose",class:"additional",projectSkillsDir:J(".goose","skills"),globalSkillsDir:(Q)=>J(NQ(Q),"goose","skills"),detectCommands:["goose"]},"iflow-cli":{label:"iFlow CLI",class:"additional",projectSkillsDir:J(".iflow","skills"),globalSkillsDir:(Q)=>J(Q,".iflow","skills"),detectCommands:["iflow","iflow-cli"]},junie:{label:"Junie",class:"additional",projectSkillsDir:J(".junie","skills"),globalSkillsDir:(Q)=>J(Q,".junie","skills"),detectCommands:["junie"]},kilo:{label:"Kilo Code",class:"additional",projectSkillsDir:J(".kilocode","skills"),globalSkillsDir:(Q)=>J(Q,".kilocode","skills"),detectCommands:["kilo","kilocode"]},"kimi-cli":{label:"Kimi Code CLI",class:"universal",projectSkillsDir:y,globalSkillsDir:f},"kiro-cli":{label:"Kiro CLI",class:"additional",projectSkillsDir:J(".kiro","skills"),globalSkillsDir:(Q)=>J(Q,".kiro","skills"),detectCommands:["kiro","kiro-cli"]},kode:{label:"Kode",class:"additional",projectSkillsDir:J(".kode","skills"),globalSkillsDir:(Q)=>J(Q,".kode","skills"),detectCommands:["kode"]},mcpjam:{label:"MCPJam",class:"additional",projectSkillsDir:J(".mcpjam","skills"),globalSkillsDir:(Q)=>J(Q,".mcpjam","skills"),detectCommands:["mcpjam"]},"mistral-vibe":{label:"Mistral Vibe",class:"additional",projectSkillsDir:J(".vibe","skills"),globalSkillsDir:(Q)=>J(Q,".vibe","skills"),detectCommands:["mistral-vibe","vibe"]},mux:{label:"Mux",class:"additional",projectSkillsDir:J(".mux","skills"),globalSkillsDir:(Q)=>J(Q,".mux","skills"),detectCommands:["mux"]},neovate:{label:"Neovate",class:"additional",projectSkillsDir:J(".neovate","skills"),globalSkillsDir:(Q)=>J(Q,".neovate","skills"),detectCommands:["neovate"]},opencode:{label:"OpenCode",class:"universal",projectSkillsDir:y,globalSkillsDir:f},openclaw:{label:"OpenClaw",class:"additional",projectSkillsDir:"skills",globalSkillsDir:(Q)=>J(Q,".openclaw","skills"),detectCommands:["openclaw"]},openhands:{label:"OpenHands",class:"additional",projectSkillsDir:J(".openhands","skills"),globalSkillsDir:(Q)=>J(Q,".openhands","skills"),detectCommands:["openhands"]},pi:{label:"Pi",class:"additional",projectSkillsDir:J(".pi","skills"),globalSkillsDir:(Q)=>J(Q,".pi","agent","skills"),detectCommands:["pi"]},pochi:{label:"Pochi",class:"additional",projectSkillsDir:J(".pochi","skills"),globalSkillsDir:(Q)=>J(Q,".pochi","skills"),detectCommands:["pochi"]},qoder:{label:"Qoder",class:"additional",projectSkillsDir:J(".qoder","skills"),globalSkillsDir:(Q)=>J(Q,".qoder","skills"),detectCommands:["qoder"]},"qwen-code":{label:"Qwen Code",class:"additional",projectSkillsDir:J(".qwen","skills"),globalSkillsDir:(Q)=>J(Q,".qwen","skills"),detectCommands:["qwen","qwen-code"]},replit:{label:"Replit",class:"universal",projectSkillsDir:y,globalSkillsDir:f},roo:{label:"Roo Code",class:"additional",projectSkillsDir:J(".roo","skills"),globalSkillsDir:(Q)=>J(Q,".roo","skills"),detectCommands:["roo","roo-code"]},trae:{label:"Trae",class:"additional",projectSkillsDir:J(".trae","skills"),globalSkillsDir:(Q)=>J(Q,".trae","skills"),detectCommands:["trae"]},"trae-cn":{label:"Trae CN",class:"additional",projectSkillsDir:J(".trae","skills"),globalSkillsDir:(Q)=>J(Q,".trae-cn","skills"),detectCommands:["trae-cn","trae"]},windsurf:{label:"Windsurf",class:"additional",projectSkillsDir:J(".windsurf","skills"),globalSkillsDir:(Q)=>J(Q,".codeium","windsurf","skills"),detectCommands:["windsurf"]},zencoder:{label:"Zencoder",class:"additional",projectSkillsDir:J(".zencoder","skills"),globalSkillsDir:(Q)=>J(Q,".zencoder","skills"),detectCommands:["zencoder"]}},a=Object.keys(s),A=Object.fromEntries(a.map((Q)=>[Q,s[Q].label]));function S(){return a.filter((Q)=>s[Q].class==="universal")}function m(){return a.filter((Q)=>s[Q].class==="additional")}function bX(Q){return s[Q].class==="universal"}async function n(Q){let X=typeof Q==="string"?{home:Q}:Q??{},Z=X.cwd??process.cwd(),H=X.commandChecker??((W)=>Promise.resolve(RX(W))),$=[];for(let W of m()){let Y=s[W].detectCommands??[],G=!1;for(let x of Y)if(await H(x,Z)){G=!0;break}if(G)$.push(W)}return $}function N(Q,X,Z){let H=u(X),$=s[Q];if(H==="project")return J(process.cwd(),$.projectSkillsDir,Z);return J($.globalSkillsDir(JQ()),Z)}function c(Q,X){if(u(Q)==="project")return J(process.cwd(),OX,X);return J(jX(JQ()),X)}function RX(Q){let Z=(process.env.PATH??"").split(_X).filter((W)=>W.length>0),H=process.platform==="win32",$=H?(process.env.PATHEXT??".EXE;.CMD;.BAT;.COM").split(";").filter((W)=>W.length>0):[];for(let W of Z){if(!H&&PQ(J(W,Q)))return!0;if(H){for(let Y of $)if(PQ(J(W,Q+Y)))return!0}}return!1}function PQ(Q){try{if(!TX(Q).isFile())return!1;return wX(Q,MX.X_OK),!0}catch{return!1}}import{Crust as EX}from"@crustjs/core";function BQ(Q){if(Q===void 0)return[];return(Array.isArray(Q)?Q:[Q]).flatMap((Z)=>Z.split(/\r?\n/)).map((Z)=>Z.trim()).filter((Z)=>Z.length>0)}function kQ(Q){let X=Q?.trim();if(!X)return[];return X.split(/\r?\n/)}function FQ(Q){return Q.length>0}var DQ=Symbol("crust.skill.commandAnnotations");function IX(Q){return Q instanceof EX?Q._node:Q}function CX(Q,X){let Z=IX(Q),H=BQ(typeof X==="string"||Array.isArray(X)?X:X.instructions??[]);if(H.length===0)return Q;let $=xQ(Z)?.instructions??[],W=[...new Set([...$,...H])];return Object.defineProperty(Z,DQ,{value:{instructions:W},enumerable:!0,configurable:!0}),Q}function xQ(Q){let X=Q[DQ];if(!X?.instructions||X.instructions.length===0)return;return{instructions:[...X.instructions]}}import{readdir as BZ,readFile as $X,realpath as YX,stat as qX}from"fs/promises";import{join as IQ,sep as WX}from"path";import{resolveSourceDir as zZ}from"@crustjs/utils";import{lstat as nX,mkdir as eQ,readlink as tX,realpath as eX,rm as qQ,symlink as QZ,writeFile as lQ}from"fs/promises";import{dirname as QX,join as sQ}from"path";class L extends Error{name="SkillConflictError";details;constructor(Q){super(LX(Q));this.details=Q}}function LX(Q){let X=`Skill conflict for agent "${Q.agent}": directory "${Q.outputDir}"`;if(Q.kindMismatch)return`${X} was installed as a "${Q.kindMismatch.existing}" skill but "${Q.kindMismatch.attempted}" was attempted. Use force: true to overwrite, or uninstall the existing skill first.`;if(Q.manifestMalformed){let{reason:Z,rawKind:H}=Q.manifestMalformed;switch(Z){case"unknown-kind":return`${X} was created by Crust but its crust.json declares an unrecognized kind "${H??"<unknown>"}" \u2014 likely a hand-edit typo or a `+"crust.json written by a newer Crust release. Fix the kind field, upgrade Crust, or pass force: true to overwrite.";case"parse-error":return`${X} contains a crust.json that is not valid JSON. Repair the file, or pass force: true to overwrite the directory.`;case"not-an-object":return`${X} contains a crust.json whose top-level value is not a JSON object. Repair the file, or pass force: true to overwrite the directory.`;case"missing-version":return`${X} contains a crust.json with no "version" string. Repair the file, or pass force: true to overwrite the directory.`}}return`${X} already exists but was not created by Crust (no crust.json found). Delete or rename the conflicting skill to resolve.`}function AQ(Q){return SQ(Q,[])}function SQ(Q,X){let Z=NX(Q.meta.name),H=[...X,Z],$=PX(Q.args),W=DX(Q.effectiveFlags),Y=SX(Q.subCommands,H),G=xQ(Q);return{name:Z,path:H,description:Q.meta.description,usage:Q.meta.usage,instructions:G?.instructions,runnable:typeof Q.run==="function",args:$,flags:W,children:Y}}function NX(Q){return Q.trim().toLowerCase()}function PX(Q){if(!Q||Q.length===0)return[];return Q.map(kX)}function kX(Q){let X={name:Q.name,type:vQ(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=hQ(Q.default);return X}function DX(Q){if(!Q)return[];return Object.keys(Q).sort().map((Z)=>{return AX(Z,Q[Z])})}function AX(Q,X){let Z={name:Q,type:vQ(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=hQ(X.default);return Z}function SX(Q,X){return Object.keys(Q).sort().map((H)=>{return SQ(Q[H],X)})}function vQ(Q){if(Q==="number"||Q==="boolean")return Q;return"string"}function hQ(Q){if(Array.isArray(Q))return JSON.stringify(Q);return String(Q)}function WQ(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 yQ(Q){return Q.replace(/(?<!\\)\|/g,"\\|")}function fQ(Q,X){let Z=[],H=gQ(Q);Z.push({path:"SKILL.md",content:vX(Q,X,H)});for(let $ of H){let W=i($),Y=$.children.length>0?gX($,Q):fX($,Q);Z.push({path:W,content:Y})}return Z}function gQ(Q){let X=[Q];for(let Z of Q.children)X.push(...gQ(Z));return X}function i(Q){if(Q.path.length<=1)return`commands/${Q.name}.md`;return`commands/${Q.path.slice(1).join("/")}.md`}function UQ(Q){return Q.path.join(" ")}function VQ(Q,X){let Z=Q.split("/").slice(0,-1),H=X.split("/"),$=0;while($<Z.length&&$<H.length&&Z[$]===H[$])$++;let W=Z.length-$,Y=H.slice($);if(W===0)return Y.join("/");return[...Array.from({length:W},()=>".."),...Y].join("/")}function vX(Q,X,Z){let H=[];if(H.push("---"),H.push(`name: ${WQ(X.name)}`),H.push(`description: ${WQ(X.description)}`),X.license)H.push(`license: ${WQ(X.license)}`);if(X.compatibility)H.push(`compatibility: ${WQ(X.compatibility)}`);if(X.disableModelInvocation)H.push("disable-model-invocation: true");if(X.allowedTools)H.push(`allowed-tools: ${WQ(X.allowedTools)}`);if(H.push("metadata:"),H.push(` version: "${X.version}"`),H.push("---"),H.push(""),H.push(`# ${X.name}`),H.push(""),Q.description)H.push(Q.description),H.push("");H.push(`You should use this skill when you need accurate help with \`${X.name}\` commands, including command selection, syntax, arguments, flags, defaults, and subcommands.`),H.push("");let $=sX(X.instructions);if(H.push("## How to Use This Skill"),H.push(""),H.push("1. You must find the command that best matches the user's task from the Command Reference below."),H.push("2. You must check the `Type` column before suggesting execution: `runnable` and `runnable, group` commands can be executed, while `group` commands are organizational only."),H.push("3. You should read only the linked file or files you need from `commands/`."),H.push("4. You must read a command's file before answering a command-specific question or suggesting that command."),H.push("5. You must treat the command file as the source of truth for usage, arguments, flags, aliases, and defaults."),H.push("6. If a flag, argument, alias, or default is not documented there, you must say it is not documented instead of guessing."),H.push(""),FQ($))H.push("## General Guidance"),H.push(""),H.push(...$),H.push("");if(H.push("## Command Reference"),H.push(""),H.push("You should use this table to locate the command file you need."),H.push(""),H.push(...hX(Z)),H.push(""),Q.runnable){H.push("## Usage"),H.push("");let W=i(Q);H.push(`The root command is directly executable. You should see [${Q.name}](${W}) for usage details.`),H.push("")}return H.join(`
2
+ import{accessSync as wX,constants as MX,statSync as TX}from"fs";import{homedir as JQ}from"os";import{delimiter as _X,join as J}from"path";var y=J(".agents","skills"),OX=J(".crust","skills");function NQ(Q){if(Q!==JQ())return J(Q,".config");let X=process.env.XDG_CONFIG_HOME?.trim();return X&&X.length>0?X:J(Q,".config")}function f(Q){return J(Q,".agents","skills")}function jX(Q){return J(Q,".crust","skills")}function u(Q){return Q==="project"&&process.cwd()===JQ()?"global":Q}var s={amp:{label:"Amp",class:"universal",projectSkillsDir:y,globalSkillsDir:f},adal:{label:"AdaL",class:"additional",projectSkillsDir:J(".adal","skills"),globalSkillsDir:(Q)=>J(Q,".adal","skills"),detectCommands:["adal"]},antigravity:{label:"Antigravity",class:"additional",projectSkillsDir:J(".agent","skills"),globalSkillsDir:(Q)=>J(Q,".gemini","antigravity","skills"),detectCommands:["antigravity"]},augment:{label:"Augment",class:"additional",projectSkillsDir:J(".augment","skills"),globalSkillsDir:(Q)=>J(Q,".augment","skills"),detectCommands:["augment"]},"claude-code":{label:"Claude Code",class:"additional",projectSkillsDir:J(".claude","skills"),globalSkillsDir:(Q)=>J(process.env.CLAUDE_CONFIG_DIR?.trim()||J(Q,".claude"),"skills"),detectCommands:["claude","claude-code"]},cline:{label:"Cline",class:"universal",projectSkillsDir:y,globalSkillsDir:f},codebuddy:{label:"CodeBuddy",class:"additional",projectSkillsDir:J(".codebuddy","skills"),globalSkillsDir:(Q)=>J(Q,".codebuddy","skills"),detectCommands:["codebuddy"]},codex:{label:"Codex",class:"universal",projectSkillsDir:y,globalSkillsDir:f},"command-code":{label:"Command Code",class:"additional",projectSkillsDir:J(".commandcode","skills"),globalSkillsDir:(Q)=>J(Q,".commandcode","skills"),detectCommands:["command-code","commandcode"]},continue:{label:"Continue",class:"additional",projectSkillsDir:J(".continue","skills"),globalSkillsDir:(Q)=>J(Q,".continue","skills"),detectCommands:["continue"]},cortex:{label:"Cortex Code",class:"additional",projectSkillsDir:J(".cortex","skills"),globalSkillsDir:(Q)=>J(Q,".snowflake","cortex","skills"),detectCommands:["cortex"]},crush:{label:"Crush",class:"additional",projectSkillsDir:J(".crush","skills"),globalSkillsDir:(Q)=>J(NQ(Q),"crush","skills"),detectCommands:["crush"]},cursor:{label:"Cursor",class:"universal",projectSkillsDir:y,globalSkillsDir:f},droid:{label:"Droid",class:"additional",projectSkillsDir:J(".factory","skills"),globalSkillsDir:(Q)=>J(Q,".factory","skills"),detectCommands:["droid"]},"gemini-cli":{label:"Gemini CLI",class:"universal",projectSkillsDir:y,globalSkillsDir:f},"github-copilot":{label:"GitHub Copilot",class:"universal",projectSkillsDir:y,globalSkillsDir:f},goose:{label:"Goose",class:"additional",projectSkillsDir:J(".goose","skills"),globalSkillsDir:(Q)=>J(NQ(Q),"goose","skills"),detectCommands:["goose"]},"iflow-cli":{label:"iFlow CLI",class:"additional",projectSkillsDir:J(".iflow","skills"),globalSkillsDir:(Q)=>J(Q,".iflow","skills"),detectCommands:["iflow","iflow-cli"]},junie:{label:"Junie",class:"additional",projectSkillsDir:J(".junie","skills"),globalSkillsDir:(Q)=>J(Q,".junie","skills"),detectCommands:["junie"]},kilo:{label:"Kilo Code",class:"additional",projectSkillsDir:J(".kilocode","skills"),globalSkillsDir:(Q)=>J(Q,".kilocode","skills"),detectCommands:["kilo","kilocode"]},"kimi-cli":{label:"Kimi Code CLI",class:"universal",projectSkillsDir:y,globalSkillsDir:f},"kiro-cli":{label:"Kiro CLI",class:"additional",projectSkillsDir:J(".kiro","skills"),globalSkillsDir:(Q)=>J(Q,".kiro","skills"),detectCommands:["kiro","kiro-cli"]},kode:{label:"Kode",class:"additional",projectSkillsDir:J(".kode","skills"),globalSkillsDir:(Q)=>J(Q,".kode","skills"),detectCommands:["kode"]},mcpjam:{label:"MCPJam",class:"additional",projectSkillsDir:J(".mcpjam","skills"),globalSkillsDir:(Q)=>J(Q,".mcpjam","skills"),detectCommands:["mcpjam"]},"mistral-vibe":{label:"Mistral Vibe",class:"additional",projectSkillsDir:J(".vibe","skills"),globalSkillsDir:(Q)=>J(Q,".vibe","skills"),detectCommands:["mistral-vibe","vibe"]},mux:{label:"Mux",class:"additional",projectSkillsDir:J(".mux","skills"),globalSkillsDir:(Q)=>J(Q,".mux","skills"),detectCommands:["mux"]},neovate:{label:"Neovate",class:"additional",projectSkillsDir:J(".neovate","skills"),globalSkillsDir:(Q)=>J(Q,".neovate","skills"),detectCommands:["neovate"]},opencode:{label:"OpenCode",class:"universal",projectSkillsDir:y,globalSkillsDir:f},openclaw:{label:"OpenClaw",class:"additional",projectSkillsDir:"skills",globalSkillsDir:(Q)=>J(Q,".openclaw","skills"),detectCommands:["openclaw"]},openhands:{label:"OpenHands",class:"additional",projectSkillsDir:J(".openhands","skills"),globalSkillsDir:(Q)=>J(Q,".openhands","skills"),detectCommands:["openhands"]},pi:{label:"Pi",class:"additional",projectSkillsDir:J(".pi","skills"),globalSkillsDir:(Q)=>J(Q,".pi","agent","skills"),detectCommands:["pi"]},pochi:{label:"Pochi",class:"additional",projectSkillsDir:J(".pochi","skills"),globalSkillsDir:(Q)=>J(Q,".pochi","skills"),detectCommands:["pochi"]},qoder:{label:"Qoder",class:"additional",projectSkillsDir:J(".qoder","skills"),globalSkillsDir:(Q)=>J(Q,".qoder","skills"),detectCommands:["qoder"]},"qwen-code":{label:"Qwen Code",class:"additional",projectSkillsDir:J(".qwen","skills"),globalSkillsDir:(Q)=>J(Q,".qwen","skills"),detectCommands:["qwen","qwen-code"]},replit:{label:"Replit",class:"universal",projectSkillsDir:y,globalSkillsDir:f},roo:{label:"Roo Code",class:"additional",projectSkillsDir:J(".roo","skills"),globalSkillsDir:(Q)=>J(Q,".roo","skills"),detectCommands:["roo","roo-code"]},trae:{label:"Trae",class:"additional",projectSkillsDir:J(".trae","skills"),globalSkillsDir:(Q)=>J(Q,".trae","skills"),detectCommands:["trae"]},"trae-cn":{label:"Trae CN",class:"additional",projectSkillsDir:J(".trae","skills"),globalSkillsDir:(Q)=>J(Q,".trae-cn","skills"),detectCommands:["trae-cn","trae"]},windsurf:{label:"Windsurf",class:"additional",projectSkillsDir:J(".windsurf","skills"),globalSkillsDir:(Q)=>J(Q,".codeium","windsurf","skills"),detectCommands:["windsurf"]},zencoder:{label:"Zencoder",class:"additional",projectSkillsDir:J(".zencoder","skills"),globalSkillsDir:(Q)=>J(Q,".zencoder","skills"),detectCommands:["zencoder"]}},a=Object.keys(s),A=Object.fromEntries(a.map((Q)=>[Q,s[Q].label]));function S(){return a.filter((Q)=>s[Q].class==="universal")}function m(){return a.filter((Q)=>s[Q].class==="additional")}function bX(Q){return s[Q].class==="universal"}async function n(Q){let X=typeof Q==="string"?{home:Q}:Q??{},Z=X.cwd??process.cwd(),H=X.commandChecker??((W)=>Promise.resolve(RX(W))),$=[];for(let W of m()){let Y=s[W].detectCommands??[],F=!1;for(let x of Y)if(await H(x,Z)){F=!0;break}if(F)$.push(W)}return $}function N(Q,X,Z){let H=u(X),$=s[Q];if(H==="project")return J(process.cwd(),$.projectSkillsDir,Z);return J($.globalSkillsDir(JQ()),Z)}function c(Q,X){if(u(Q)==="project")return J(process.cwd(),OX,X);return J(jX(JQ()),X)}function RX(Q){let Z=(process.env.PATH??"").split(_X).filter((W)=>W.length>0),H=process.platform==="win32",$=H?(process.env.PATHEXT??".EXE;.CMD;.BAT;.COM").split(";").filter((W)=>W.length>0):[];for(let W of Z){if(!H&&PQ(J(W,Q)))return!0;if(H){for(let Y of $)if(PQ(J(W,Q+Y)))return!0}}return!1}function PQ(Q){try{if(!TX(Q).isFile())return!1;return wX(Q,MX.X_OK),!0}catch{return!1}}import{Crust as EX}from"@crustjs/core";function BQ(Q){if(Q===void 0)return[];return(Array.isArray(Q)?Q:[Q]).flatMap((Z)=>Z.split(/\r?\n/)).map((Z)=>Z.trim()).filter((Z)=>Z.length>0)}function kQ(Q){let X=Q?.trim();if(!X)return[];return X.split(/\r?\n/)}function FQ(Q){return Q.length>0}var DQ=Symbol("crust.skill.commandAnnotations");function IX(Q){return Q instanceof EX?Q._node:Q}function CX(Q,X){let Z=IX(Q),H=BQ(typeof X==="string"||Array.isArray(X)?X:X.instructions??[]);if(H.length===0)return Q;let $=xQ(Z)?.instructions??[],W=[...new Set([...$,...H])];return Object.defineProperty(Z,DQ,{value:{instructions:W},enumerable:!0,configurable:!0}),Q}function xQ(Q){let X=Q[DQ];if(!X?.instructions||X.instructions.length===0)return;return{instructions:[...X.instructions]}}import{readdir as BZ,readFile as $X,realpath as YX,stat as qX}from"fs/promises";import{join as IQ,sep as WX}from"path";import{resolveSourceDir as zZ}from"@crustjs/utils";import{lstat as nX,mkdir as eQ,readlink as tX,realpath as eX,rm as qQ,symlink as QZ,writeFile as lQ}from"fs/promises";import{dirname as QX,join as sQ}from"path";class L extends Error{name="SkillConflictError";details;constructor(Q){super(LX(Q));this.details=Q}}function LX(Q){let X=`Skill conflict for agent "${Q.agent}": directory "${Q.outputDir}"`;if(Q.kindMismatch)return`${X} was installed as a "${Q.kindMismatch.existing}" skill but "${Q.kindMismatch.attempted}" was attempted. Use force: true to overwrite, or uninstall the existing skill first.`;if(Q.manifestMalformed){let{reason:Z,rawKind:H}=Q.manifestMalformed;switch(Z){case"unknown-kind":return`${X} was created by Crust but its crust.json declares an unrecognized kind "${H??"<unknown>"}" \u2014 likely a hand-edit typo or a `+"crust.json written by a newer Crust release. Fix the kind field, upgrade Crust, or pass force: true to overwrite.";case"parse-error":return`${X} contains a crust.json that is not valid JSON. Repair the file, or pass force: true to overwrite the directory.`;case"not-an-object":return`${X} contains a crust.json whose top-level value is not a JSON object. Repair the file, or pass force: true to overwrite the directory.`;case"missing-version":return`${X} contains a crust.json with no "version" string. Repair the file, or pass force: true to overwrite the directory.`}}return`${X} already exists but was not created by Crust (no crust.json found). Delete or rename the conflicting skill to resolve.`}function AQ(Q){return SQ(Q,[])}function SQ(Q,X){let Z=NX(Q.meta.name),H=[...X,Z],$=PX(Q.args),W=DX(Q.effectiveFlags),Y=SX(Q.subCommands,H),F=xQ(Q);return{name:Z,path:H,description:Q.meta.description,usage:Q.meta.usage,instructions:F?.instructions,runnable:typeof Q.run==="function",args:$,flags:W,children:Y}}function NX(Q){return Q.trim().toLowerCase()}function PX(Q){if(!Q||Q.length===0)return[];return Q.map(kX)}function kX(Q){let X={name:Q.name,type:vQ(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=hQ(Q.default);return X}function DX(Q){if(!Q)return[];return Object.keys(Q).sort().map((Z)=>{return AX(Z,Q[Z])})}function AX(Q,X){let Z={name:Q,type:vQ(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=hQ(X.default);return Z}function SX(Q,X){return Object.keys(Q).sort().map((H)=>{return SQ(Q[H],X)})}function vQ(Q){if(Q==="number"||Q==="boolean")return Q;return"string"}function hQ(Q){if(Array.isArray(Q))return JSON.stringify(Q);return String(Q)}function WQ(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 yQ(Q){return Q.replace(/(?<!\\)\|/g,"\\|")}function fQ(Q,X){let Z=[],H=gQ(Q);Z.push({path:"SKILL.md",content:vX(Q,X,H)});for(let $ of H){let W=i($),Y=$.children.length>0?gX($,Q):fX($,Q);Z.push({path:W,content:Y})}return Z}function gQ(Q){let X=[Q];for(let Z of Q.children)X.push(...gQ(Z));return X}function i(Q){if(Q.path.length<=1)return`commands/${Q.name}.md`;return`commands/${Q.path.slice(1).join("/")}.md`}function UQ(Q){return Q.path.join(" ")}function VQ(Q,X){let Z=Q.split("/").slice(0,-1),H=X.split("/"),$=0;while($<Z.length&&$<H.length&&Z[$]===H[$])$++;let W=Z.length-$,Y=H.slice($);if(W===0)return Y.join("/");return[...Array.from({length:W},()=>".."),...Y].join("/")}function vX(Q,X,Z){let H=[];if(H.push("---"),H.push(`name: ${WQ(X.name)}`),H.push(`description: ${WQ(X.description)}`),X.license)H.push(`license: ${WQ(X.license)}`);if(X.compatibility)H.push(`compatibility: ${WQ(X.compatibility)}`);if(X.disableModelInvocation)H.push("disable-model-invocation: true");if(X.allowedTools)H.push(`allowed-tools: ${WQ(X.allowedTools)}`);if(H.push("metadata:"),H.push(` version: "${X.version}"`),H.push("---"),H.push(""),H.push(`# ${X.name}`),H.push(""),Q.description)H.push(Q.description),H.push("");H.push(`You should use this skill when you need accurate help with \`${X.name}\` commands, including command selection, syntax, arguments, flags, defaults, and subcommands.`),H.push("");let $=sX(X.instructions);if(H.push("## How to Use This Skill"),H.push(""),H.push("1. You must find the command that best matches the user's task from the Command Reference below."),H.push("2. You must check the `Type` column before suggesting execution: `runnable` and `runnable, group` commands can be executed, while `group` commands are organizational only."),H.push("3. You should read only the linked file or files you need from `commands/`."),H.push("4. You must read a command's file before answering a command-specific question or suggesting that command."),H.push("5. You must treat the command file as the source of truth for usage, arguments, flags, aliases, and defaults."),H.push("6. If a flag, argument, alias, or default is not documented there, you must say it is not documented instead of guessing."),H.push(""),FQ($))H.push("## General Guidance"),H.push(""),H.push(...$),H.push("");if(H.push("## Command Reference"),H.push(""),H.push("You should use this table to locate the command file you need."),H.push(""),H.push(...hX(Z)),H.push(""),Q.runnable){H.push("## Usage"),H.push("");let W=i(Q);H.push(`The root command is directly executable. You should see [${Q.name}](${W}) for usage details.`),H.push("")}return H.join(`
3
3
  `)}function hX(Q){let X=[];X.push("| Command | Type | Documentation |"),X.push("| ------- | ---- | ------------- |");for(let Z of Q){let H=UQ(Z),$=i(Z),W=yX(Z);X.push(`| \`${H}\` | ${W} | [${$}](${$}) |`)}return X}function yX(Q){if(Q.runnable&&Q.children.length>0)return"runnable, group";if(Q.runnable)return"runnable";return"group"}function fX(Q,X){let Z=[];return Z.push(...pQ(Q)),Z.push(...uQ(Q)),Z.push(...mQ(Q)),Z.push(...dQ(Q,X)),Z.join(`
4
4
  `)}function gX(Q,X){let Z=[],H=i(Q);if(Z.push(...pQ(Q)),Z.push(...uQ(Q)),Q.runnable)Z.push(...mQ(Q));return Z.push(...uX(Q,H)),Z.push(...dQ(Q,X)),Z.join(`
5
- `)}function pX(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 pQ(Q){let X=[`# \`${UQ(Q)}\``,""];if(Q.description)X.push(Q.description,"");return X}function uQ(Q){let X=Q.instructions??[];if(!FQ(X))return[];return["## Command Instructions","",...cQ(X),""]}function mQ(Q){let X=["## Usage","","```",Q.usage??pX(Q),"```",""];if(Q.args.length>0)X.push("## Arguments","",...mX(Q.args),"");if(Q.flags.length>0)X.push("## Flags","",...dX(Q.flags),"");return X.push("## Command Documentation Authority","","You must treat only the arguments, flags, options, aliases, and defaults documented in this file as supported for this command.","You must not infer or invent additional command-line options.",""),X}function uX(Q,X){let Z=["## Subcommands",""];for(let H of Q.children){let $=i(H),W=VQ(X,$),Y=H.description?` - ${H.description}`:"";Z.push(`- [\`${H.name}\`](${W})${Y}`)}return Z.push(""),Z}function mX(Q){let X=[];X.push("| Argument | Type | Required | Description |"),X.push("| -------- | ---- | -------- | ----------- |");for(let Z of Q){let H=Z.variadic?`${Z.name}...`:Z.name,$=Z.required?"Yes":"No",W=yQ(cX(Z));X.push(`| \`${H}\` | ${Z.type} | ${$} | ${W} |`)}return X}function cX(Q){let X=[];if(Q.description)X.push(Q.description);if(Q.default!==void 0)X.push(`Default: \`${Q.default}\``);return X.join(". ")||"-"}function dX(Q){let X=[];X.push("| Flag | Type | Required | Description |"),X.push("| ---- | ---- | -------- | ----------- |");for(let Z of Q){let H=rX(Z),$=Z.required?"Yes":"No",W=yQ(lX(Z));X.push(`| ${H} | ${Z.type} | ${$} | ${W} |`)}return X}function rX(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 lX(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 cQ(Q){return Q.map((X)=>`- ${X}`)}function sX(Q){if(typeof Q==="string")return kQ(Q);return cQ(BQ(Q))}function dQ(Q,X){let Z=[],H=i(Q);if(Z.push("---"),Z.push(""),Q.path.length>1){let W=Q.path.slice(0,-1),Y=rQ(X,W);if(Y){let G=i(Y),x=VQ(H,G),q=UQ(Y);Z.push(`Parent: [\`${q}\`](${x})`),Z.push("")}}let $=VQ(H,"SKILL.md");return Z.push(`[Skill Overview](${$})`),Z.push(""),Z}function rQ(Q,X){if(iX(Q.path,X))return Q;for(let Z of Q.children){let H=rQ(Z,X);if(H)return H}return}function iX(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 oX}from"fs/promises";import{join as aX}from"path";var t="crust.json";async function zQ(Q){let X;try{X=await oX(aX(Q,t),"utf-8")}catch{return{status:"absent"}}let Z;try{Z=JSON.parse(X)}catch{return{status:"malformed",reason:"parse-error"}}if(typeof Z!=="object"||Z===null)return{status:"malformed",reason:"not-an-object"};let H=Z;if(typeof H.version!=="string")return{status:"malformed",reason:"missing-version"};let{version:$,kind:W}=H;if(W===void 0)return{status:"ok",manifest:{version:$,kind:"generated"}};if(W==="bundle"||W==="generated")return{status:"ok",manifest:{version:$,kind:W}};return{status:"malformed",reason:"unknown-kind",rawKind:typeof W==="string"?W:JSON.stringify(W)}}async function wQ(Q){let X=await zQ(Q);return X.status==="ok"?X.manifest:null}async function YQ(Q){return(await wQ(Q))?.version??null}var XZ="auto";async function ZZ(Q){if(Q!==void 0)return Q;return[...S(),...await n()]}function XX(Q){if(Q!==void 0)return Q;return[...a]}var ZX=/^[a-z0-9]+(-[a-z0-9]+)*$/;function e(Q){return Q.length>=1&&Q.length<=64&&ZX.test(Q)}function QQ(Q){return Q}function _Q(Q){return Q.startsWith("use-")?Q:`use-${Q}`}async function XQ(Q){let{command:X,meta:Z,scope:H="global",clean:$=!0,force:W=!1,installMode:Y=XZ}=Q,G=await ZZ(Q.agents),x=QQ(Z.name),q=_Q(Z.name);if(!e(x))throw Error(`Invalid skill name "${x}": must be 1\u201364 lowercase `+`alphanumeric characters and hyphens, no leading/trailing/consecutive hyphens. Pattern: ${ZX.source}`);if(G.length===0)return{agents:[]};let F={...Z,name:x},K=AQ(X),V=fQ(K,F);return OQ({files:V,meta:F,agents:G,scope:H,clean:$,force:W,installMode:Y,kind:"generated",legacyResolvedName:q})}async function OQ(Q){let{files:X,meta:Z,agents:H,scope:$,clean:W,force:Y,installMode:G,kind:x,legacyResolvedName:q}=Q,F=H[0];if(!F)return{agents:[]};let K=JZ(Z,x),V=[...X,...K].sort((B,U)=>B.path<U.path?-1:B.path>U.path?1:0),w=V.map((B)=>B.path),M=new Map;for(let B of H){let U=N(B,$,Z.name),_=M.get(U);if(_)_.push(B);else M.set(U,[B])}let O=c($,Z.name),T=c($,q),b=new Map;for(let[B,U]of M){let _=U[0];if(!_)continue;b.set(B,await bQ({outputDir:B,legacyOutputDir:N(_,$,q),canonicalOutputDir:O,legacyCanonicalOutputDir:T}))}let v=await zQ(O),R=v.status==="ok"?v.manifest:null,P=R?.version??null;if((await jQ(O,O)).exists&&R===null&&!Y)throw new L({agent:F,outputDir:O,manifestMalformed:iQ(v)});if(R!==null&&R.kind!==x&&!Y)throw new L({agent:F,outputDir:O,kindMismatch:{existing:R.kind,attempted:x}});let D=R!==null&&R.kind!==x,g=P!==Z.version||D;if(g){if(W)await RQ(O);await HX(O,V)}let p=[];for(let[B,U]of M){let _=U[0];if(!_)continue;let z=b.get(B);if(!z)continue;if(z.current.inspection.exists&&!z.current.isCrustManaged&&!Y){let $Q=await zQ(B);throw new L({agent:_,outputDir:B,manifestMalformed:iQ($Q)})}if(z.current.manifest!==null&&z.current.manifest.kind!==x&&!Y)throw new L({agent:_,outputDir:B,kindMismatch:{existing:z.current.manifest.kind,attempted:x}});let j=await $Z({outputDir:B,canonicalOutputDir:O,allFiles:V,clean:W,installMode:G,inspection:z.current.inspection,installedVersion:z.preferredVersion,currentVersion:Z.version,installedKind:z.current.manifest?.kind??null,currentKind:x}),E=await YZ(z),C=HZ({installedVersion:z.preferredVersion,currentVersion:Z.version,canonicalChanged:g,pathChanged:j||E||z.preferredOutputDir!==B});for(let $Q of U)p.push({agent:$Q,outputDir:B,files:C==="up-to-date"?[]:w,status:C,previousVersion:C==="updated"?z.preferredVersion??void 0:void 0})}if(q!==Z.name){let B=await YQ(T);if(T!==O&&B!==null&&!await TQ(q,$))await qQ(T,{recursive:!0,force:!0})}return{agents:p}}async function KQ(Q){let{name:X,scope:Z="global"}=Q,H=XX(Q.agents),$=QQ(X),W=_Q(X),Y=c(Z,$),G=c(Z,W),x=[],q=new Map;for(let F of H){let K=N(F,Z,$),V=q.get(K);if(V)V.push(F);else q.set(K,[F])}for(let[F,K]of q){let V=K[0];if(!V)continue;let w=N(V,Z,W),M=await bQ({outputDir:F,legacyOutputDir:w,canonicalOutputDir:Y,legacyCanonicalOutputDir:G}),O=await MQ(M.current),T=M.legacy.outputDir!==M.current.outputDir?await MQ(M.legacy):!1,b=O||T,v=O?F:T?w:F;for(let R of K)x.push({agent:R,outputDir:v,status:b?"removed":"not-found"})}if(await YQ(Y)!==null&&!await TQ($,Z))await qQ(Y,{recursive:!0,force:!0});{let F=await YQ(G);if(G!==Y&&F!==null&&!await TQ(W,Z))await qQ(G,{recursive:!0,force:!0})}return{agents:x}}async function d(Q){let{name:X,scope:Z="global"}=Q,H=XX(Q.agents),$=QQ(X),W=_Q(X),Y=[],G=new Map;for(let x of H){let q=N(x,Z,$),F=G.get(q);if(F)F.push(x);else G.set(q,[x])}for(let[x,q]of G){let F=q[0];if(!F)continue;let K=N(F,Z,W),V=c(Z,$),w=c(Z,W),M=await bQ({outputDir:x,legacyOutputDir:K,canonicalOutputDir:V,legacyCanonicalOutputDir:w}),O=M.preferredOutputDir??x,T=M.preferredVersion;for(let b of q)Y.push({agent:b,outputDir:O,installed:T!==null,version:T??void 0})}return{agents:Y}}function iQ(Q){if(Q.status!=="malformed")return;return Q.rawKind!==void 0?{reason:Q.reason,rawKind:Q.rawKind}:{reason:Q.reason}}function HZ(Q){let{installedVersion:X,currentVersion:Z,canonicalChanged:H,pathChanged:$}=Q;if(X===null)return"installed";if(X===Z&&!H&&!$)return"up-to-date";return"updated"}async function $Z(Q){let{outputDir:X,canonicalOutputDir:Z,allFiles:H,clean:$,installMode:W,inspection:Y,installedVersion:G,currentVersion:x,installedKind:q,currentKind:F}=Q;if(W==="copy")return oQ({outputDir:X,allFiles:H,clean:$,inspection:Y,installedVersion:G,currentVersion:x,installedKind:q,currentKind:F});try{return await WZ({outputDir:X,canonicalOutputDir:Z,inspection:Y})}catch(K){if(W==="symlink")throw Error(`Failed to create symlink at "${X}" (installMode: symlink).`,{cause:K});let V=await jQ(X,Z);return oQ({outputDir:X,allFiles:H,clean:$,inspection:V,installedVersion:G,currentVersion:x,installedKind:q,currentKind:F})}}async function oQ(Q){let{outputDir:X,allFiles:Z,clean:H,inspection:$,installedVersion:W,currentVersion:Y,installedKind:G,currentKind:x}=Q,q=G!==null&&G!==x;if(!(!$.exists||$.isSymlink||W!==Y||q))return!1;if($.isSymlink||H)await RQ(X);return await HX(X,Z),!0}async function WZ(Q){let{outputDir:X,canonicalOutputDir:Z,inspection:H}=Q;if(H.exists&&H.isSymlink&&H.pointsToCanonical)return!1;if(H.exists)await RQ(X);return await qZ(Z,X),!0}async function jQ(Q,X){let Z;try{Z=await nX(Q)}catch{return{exists:!1,isSymlink:!1,pointsToCanonical:!1}}let H=process.platform==="win32"&&Z.isDirectory()&&await tQ(Q)!==null;if(!(Z.isSymbolicLink()||H))return{exists:!0,isSymlink:!1,pointsToCanonical:!1};let[W,Y,G]=await Promise.all([nQ(Q),nQ(X),tQ(Q)]);return{exists:!0,isSymlink:!0,pointsToCanonical:W!==null&&Y!==null&&W===Y||G===X}}async function aQ(Q,X){let[Z,H]=await Promise.all([wQ(Q),jQ(Q,X)]),$=Z?.version??null,W=$!==null||H.exists&&H.isSymlink&&H.pointsToCanonical;return{outputDir:Q,version:$,manifest:Z,inspection:H,isCrustManaged:W}}async function bQ(Q){let{outputDir:X,legacyOutputDir:Z,canonicalOutputDir:H,legacyCanonicalOutputDir:$}=Q,W=await aQ(X,H),Y=Z===X?W:await aQ(Z,$);if(W.isCrustManaged)return{current:W,legacy:Y,preferredVersion:W.version,preferredOutputDir:W.outputDir};if(Y.isCrustManaged)return{current:W,legacy:Y,preferredVersion:Y.version,preferredOutputDir:Y.outputDir};return{current:W,legacy:Y,preferredVersion:null,preferredOutputDir:null}}async function MQ(Q){if(!Q.isCrustManaged||!Q.inspection.exists)return!1;return await qQ(Q.outputDir,{recursive:!0,force:!0}),!0}async function YZ(Q){if(Q.legacy.outputDir===Q.current.outputDir)return!1;return MQ(Q.legacy)}async function nQ(Q){try{return await eX(Q)}catch{return null}}async function tQ(Q){try{return await tX(Q)}catch{return null}}async function qZ(Q,X){await eQ(QX(X),{recursive:!0});let Z=process.platform==="win32"?"junction":"dir";await QZ(Q,X,Z)}async function TQ(Q,X){let Z=new Set;for(let H of a)Z.add(N(H,X,Q));for(let H of Z)if(await YQ(H)!==null)return!0;return!1}function JZ(Q,X){let Z={name:Q.name,description:Q.description,version:Q.version,kind:X};return[{path:t,content:`${JSON.stringify(Z,null,"\t")}
6
- `}]}async function RQ(Q){await qQ(Q,{recursive:!0,force:!0})}async function HX(Q,X){let Z=new Set;for(let $ of X){let W=sQ(Q,$.path),Y=QX(W);Z.add(Y)}let H=[...Z].sort();for(let $ of H)await eQ($,{recursive:!0});for(let $ of X){let W=sQ(Q,$.path);if(typeof $.content==="string")await lQ(W,$.content,"utf-8");else await lQ(W,$.content)}}var EQ="SKILL.md";function KZ(Q){let X={name:null,description:null},H=(Q.startsWith("\uFEFF")?Q.slice(1):Q).split(/\r?\n/,51),$=0;while($<H.length&&H[$]?.trim()==="")$++;if($>=H.length||H[$]!=="---")return X;$++;let W=Math.min(50,H.length);for(let Y=$;Y<W;Y++){let G=H[Y];if(G===void 0)break;if(/^---\s*$/.test(G))break;let x=G.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*?)\s*$/);if(!x)continue;let q=x[1];if(q!=="name"&&q!=="description")continue;if(X[q]!==null)continue;X[q]=GZ(x[2]??"")}return X}function GZ(Q){let X=Q[0];if(X==='"'||X==="'"){let H=Q.indexOf(X,1);if(H!==-1)return Q.slice(1,H)}let Z=Q.search(/(^|\s)#/);return Z===-1?Q:Q.slice(0,Z).trimEnd()}function FZ(Q,X,Z){let H=X.endsWith(WX)?X:X+WX;if(Q!==X&&!Q.startsWith(H))throw Error(`Bundle path traversal rejected: "${Z}" resolves to "${Q}", which is outside the bundle root "${X}".`)}async function JX(Q,X,Z,H){let $=await BZ(Q,{withFileTypes:!0}),W=[];for(let Y of $){let G=Y.name,x=IQ(Q,G),q=Z===""?G:`${Z}/${G}`,F=await YX(x);FZ(F,X,x);let K=await qX(F);if(K.isDirectory()){if(H.has(F))continue;H.add(F),W.push(...await JX(x,X,q,H))}else if(K.isFile())W.push({relPath:q,absPath:x})}return W}async function xZ(Q){let X=zZ(Q),Z;try{Z=await YX(X)}catch(F){throw Error(`Bundle source directory "${X}" does not exist or is not accessible.`,{cause:F})}if(!(await qX(Z)).isDirectory())throw Error(`Bundle source path "${Z}" is not a directory.`);let W=await JX(Z,Z,"",new Set([Z])),Y=W.find((F)=>F.relPath===EQ);if(!Y)throw Error(`Bundle is missing SKILL.md at the bundle root "${Z}". Every skill bundle must contain a top-level SKILL.md file.`);if(W.some((F)=>F.relPath===t))throw Error(`Bundle source at "${Z}" contains a reserved file "${t}" at the root. Crust regenerates this file during installation; remove it from your bundle source.`);let G=await Promise.all(W.map(async(F)=>({path:F.relPath,content:await $X(F.absPath)}))),x=await $X(Y.absPath,"utf-8"),q=KZ(x);if(q.name===null||q.name==="")throw Error(`Bundle SKILL.md is missing a top-level \`name:\` field in its YAML frontmatter (at "${IQ(Z,EQ)}"). Add \`name: <skill-name>\` to the frontmatter block.`);if(q.description===null||q.description==="")throw Error(`Bundle SKILL.md is missing a top-level \`description:\` field in its YAML frontmatter (at "${IQ(Z,EQ)}"). Add \`description: <one-line summary>\` to the frontmatter block.`);return{files:G,frontmatter:{name:q.name,description:q.description}}}async function ZQ(Q){let{sourceDir:X,agents:Z,version:H,scope:$="global",clean:W=!0,force:Y=!1,installMode:G="auto",expectedName:x}=Q,{files:q,frontmatter:F}=await xZ(X),K=QQ(F.name);if(!e(K))throw Error(`Invalid skill name "${K}" in SKILL.md frontmatter: must be 1\u201364 lowercase `+"alphanumeric characters and hyphens, no leading/trailing/consecutive hyphens.");if(x!==void 0&&K!==x)throw Error(`Bundle SKILL.md frontmatter name "${K}" does not match the expected name "${x}". Update the bundle's SKILL.md frontmatter \`name:\` field, or change the configured \`name\` to match.`);if(Z.length===0)return{agents:[]};let V={name:K,description:F.description,version:H};return OQ({files:[...q],meta:V,agents:Z,scope:$,clean:W,force:Y,installMode:G,kind:"bundle",legacyResolvedName:K})}import{Crust as zX,VALIDATION_MODE_ENV as VZ}from"@crustjs/core";import{spinner as h}from"@crustjs/progress";import{confirm as KX,multiselect as GX,select as UZ}from"@crustjs/prompts";import{bold as l,dim as k,yellow as o}from"@crustjs/style";var wZ="skill",FX="global",r="__universal__";function xX(Q){return Q==="global"||Q==="project"}function MZ(Q){return Q==="auto"||Q==="symlink"||Q==="copy"}function VX(Q){if(Q===void 0)return;if(!xX(Q))throw Error(`Invalid --scope value: ${String(Q)}. Expected "project" or "global".`);return Q}async function UX(Q,X){let Z=VX(Q);if(Z!==void 0)return Z;if(X.defaultScope)return X.defaultScope;return UZ({message:"Select scope",choices:[{label:"Project",value:"project"},{label:"Global",value:"global"}],default:FX})}function HQ(Q){let X=new Set(S()),Z=[];if(Q.some((H)=>X.has(H)))Z.push("Universal");for(let H of Q){if(X.has(H))continue;Z.push(A[H])}return Z}function GQ(Q){let X=new Set(S()),Z=[],H=Q.find(($)=>X.has($.agent));if(H)Z.push({label:"Universal",outputDir:H.outputDir});for(let $ of Q){if(X.has($.agent))continue;Z.push({label:A[$.agent],outputDir:$.outputDir})}return Z}function CQ(Q,X){return{name:Q.meta.name,description:Q.meta.description??"",version:X.version,instructions:X.instructions,license:X.license,allowedTools:X.allowedTools,compatibility:X.compatibility,disableModelInvocation:X.disableModelInvocation}}function TZ(Q,X){if(X===void 0)return[];if(!Array.isArray(X))throw Error(`skillPlugin: customSkills must be an array, got ${X===null?"null":typeof X}.`);if(X.length===0)return[];let Z=new Set;for(let H=0;H<X.length;H++){let $=X[H];if(!$||typeof $!=="object")throw Error(`skillPlugin: customSkills[${H}] must be an object, got ${$===null?"null":typeof $}.`);if(typeof $.name!=="string"||$.name.length===0)throw Error(`skillPlugin: customSkills[${H}].name must be a non-empty string.`);if(!e($.name))throw Error(`skillPlugin: customSkills[${H}].name "${$.name}" is not a valid skill name. `+"Must be 1\u201364 lowercase alphanumeric characters and hyphens, "+"no leading/trailing/consecutive hyphens.");if($.name===Q)throw Error(`skillPlugin: customSkills[${H}].name "${$.name}" collides with the main skill name. Custom skill bundle names must differ from the root command name.`);if(Z.has($.name))throw Error(`skillPlugin: customSkills contains duplicate name "${$.name}". Each entry must declare a unique name.`);if(Z.add($.name),$.version!==void 0&&(typeof $.version!=="string"||$.version.length===0))throw Error(`skillPlugin: customSkills[${H}].version (for "${$.name}") must be a non-empty string when set, or omitted to inherit the plugin's \`version\`.`);if(typeof $.sourceDir!=="string"&&!($.sourceDir instanceof URL))throw Error(`skillPlugin: customSkills[${H}].sourceDir (for "${$.name}") must be a string or URL, got ${typeof $.sourceDir}.`);if($.scope!==void 0&&!xX($.scope))throw Error(`skillPlugin: customSkills[${H}].scope (for "${$.name}") must be "project" or "global", got ${JSON.stringify($.scope)}.`);if($.installMode!==void 0&&!MZ($.installMode))throw Error(`skillPlugin: customSkills[${H}].installMode (for "${$.name}") must be "auto", "symlink", or "copy", got ${JSON.stringify($.installMode)}.`)}return X}function _Z(Q,X){let Z=Q.scope??X.defaultScope;if(Z!==void 0)return[u(Z)];return[...new Set(["project","global"].map((H)=>u(H)))]}async function OZ(Q,X){let Z=[...S(),...m()];if(Z.length===0)return;let H=_Z(Q,X),$=Q.installMode??X.installMode,W=Q.version??X.version;for(let Y of H){let x=(await d({name:Q.name,agents:Z,scope:Y})).agents.filter((q)=>{if(!q.installed)return!1;let F=N(q.agent,Y,Q.name);return q.version!==W||q.outputDir!==F});if(x.length===0)continue;try{await h({message:`Updating ${Y} skills [${Q.name}]...`,task:async({updateMessage:q})=>{let F=await ZQ({sourceDir:Q.sourceDir,agents:x.map((w)=>w.agent),version:W,scope:Y,installMode:$,expectedName:Q.name}),K=F.agents.filter((w)=>w.status==="updated").map((w)=>w.agent),V=HQ(K);if(V.length>0)q(`Updated bundle "${Q.name}" to v${W} for ${V.join(", ")} (${Y})`);return F}})}catch(q){if(q instanceof L){let F=q.details.kindMismatch?` (existing skill is "${q.details.kindMismatch.existing}", attempted "${q.details.kindMismatch.attempted}")`:"";console.warn(o(`Skill conflict [${Q.name}]: "${q.details.outputDir}" already exists but conflicts with the requested install${F}. Skipping auto-update for ${Y}. Delete or rename the conflicting skill to resolve.`))}else throw q}}}function LQ(Q,X,Z,H){if(!H.installed)return!1;let $=N(Q,X,Z.name);return H.version!==Z.version||H.outputDir!==$}async function jZ(Q,X,Z){let H=[...S(),...m()];if(H.length===0){await BX(Z,X);return}let $=CQ(Q,X),W=[...new Set(["project","global"].map((Y)=>u(Y)))];for(let Y of W){let x=(await d({name:$.name,agents:H,scope:Y})).agents.filter((q)=>LQ(q.agent,Y,$,q));if(x.length===0)continue;try{await h({message:`Updating ${Y} skills...`,task:async({updateMessage:q})=>{let F=await XQ({command:Q,meta:$,agents:x.map((w)=>w.agent),scope:Y,installMode:X.installMode}),K=F.agents.filter((w)=>w.status==="updated").map((w)=>w.agent),V=HQ(K);if(V.length>0)q(`Updated skill "${$.name}" to v${$.version} for ${V.join(", ")} (${Y})`);return F}})}catch(q){if(q instanceof L)console.warn(o(`Skill conflict: "${q.details.outputDir}" already exists but was not created by ${$.name}. Skipping auto-update for ${Y}. Delete or rename the conflicting skill to resolve.`));else throw q}}await BX(Z,X)}async function BX(Q,X){for(let Z of Q)try{await OZ(Z,X)}catch(H){let $=H instanceof Error?H.message:String(H);console.warn(o(`Skill auto-update failed [${Z.name}]: ${$}. Continuing with remaining skills.`))}}function bZ(Q){let X;return{name:"skills",async setup(Z,H){X=Z.rootCommand;let $=Q.command??wZ,W=TZ(X.meta.name,Q.customSkills);if(H.addSubCommand(X,$,EZ(X,Q,W,$)),process.env[VZ]==="1")return;if(Z.argv[0]===$)return;if(Q.autoUpdate!==!1)await jZ(X,Q,W)}}}async function RZ(Q){let{entry:X,options:Z,scope:H,installAll:$,isInteractive:W}=Q,Y=X.installMode??Z.installMode,G=X.version??Z.version,x=await n(),q=S(),F=m(),K=await d({name:X.name,agents:[...q,...F],scope:H}),V=new Set(K.agents.filter((B)=>B.installed).map((B)=>B.agent)),w=new Set(x),M=new Map(K.agents.map((B)=>[B.agent,B])),O=F.filter((B)=>{if(w.has(B))return!0;return M.get(B)?.installed===!0}),T=O.filter((B)=>V.has(B)),b=[];if(q.length>0){let B=q[0];if(!B)throw Error("Expected at least one universal agent");let _=M.get(B)?.outputDir??"path unavailable";b.push({label:"Universal",value:r,hint:_});let z=q.map((j)=>A[j]).join(", ");if(W&&!$)console.log(k(`Agents supporting universal skills: ${z}`))}for(let B of O){let _=M.get(B)?.outputDir??"path unavailable";b.push({label:A[B],value:B,hint:_})}let v=q.length>0&&q.every((B)=>V.has(B)),R=[...T.filter((B)=>!q.includes(B))];if(v)R.unshift(r);let P;if($)P=[...q,...O];else{let B=b.length===0?[]:await GX({message:`Select agents to install skills for [${X.name}]`,choices:b,default:R,required:!1}),U=new Set(B.filter((_)=>_!==r));if(B.includes(r))for(let _ of q)U.add(_);P=[...U]}let I=P.filter((B)=>!V.has(B)),D=P.filter((B)=>{let U=M.get(B);if(!U?.installed)return!1;let _=N(B,H,X.name);return U.version!==G||U.outputDir!==_}),g=[...V].filter((B)=>!P.includes(B)),p=[...I,...D];if(p.length>0)try{let B=await h({message:`Installing skills [${X.name}]...`,task:async()=>ZQ({sourceDir:X.sourceDir,agents:p,version:G,scope:H,installMode:Y,expectedName:X.name})});console.log(`
7
- ${l(`Installed bundle "${X.name}" v${G}`)}`);for(let U of GQ(B.agents))console.log(k(` ${U.label} \u2192 ${U.outputDir}`))}catch(B){if(B instanceof L){let U=B.details.kindMismatch?` (existing is a "${B.details.kindMismatch.existing}" skill, attempted "${B.details.kindMismatch.attempted}")`:" but was not created by Crust";if($?!0:await KX({message:`"${B.details.outputDir}" already exists${U}. Overwrite?`,default:!1})){let z=await h({message:`Overwriting bundle [${X.name}]...`,task:async()=>ZQ({sourceDir:X.sourceDir,agents:[B.details.agent],version:G,scope:H,force:!0,installMode:Y,expectedName:X.name})});console.log(`
8
- ${l(`Installed bundle "${X.name}" v${G}`)}`);for(let j of GQ(z.agents))console.log(k(` ${j.label} \u2192 ${j.outputDir}`))}else console.log(k(`
9
- Skipped ${A[B.details.agent]} [${X.name}]`))}else throw B}if(g.length>0){let U=(await h({message:`Removing skills [${X.name}]...`,task:async()=>KQ({name:X.name,agents:g,scope:H})})).agents.filter((z)=>z.status==="removed").map((z)=>z.agent),_=HQ(U);if(_.length>0)console.log(`
10
- ${l(`Removed bundle "${X.name}" from ${_.join(", ")}`)}`)}if(p.length===0&&g.length===0)console.log(k(`No changes [${X.name}].`))}function EZ(Q,X,Z,H){let $=IZ(Q,X,Z);return new zX(H).meta({description:"Manage agent skill installations"}).flags({scope:{type:"string",description:"Install scope (project or global)"},all:{type:"boolean",description:"Install for all detected agents non-interactively (universal + detected)"}}).run(async(W)=>{let Y=CQ(Q,X),G=W.flags.all===!0,x=!!process.stdin.isTTY,q=G?VX(W.flags.scope)??X.defaultScope??FX:await UX(W.flags.scope,X),F=await n(),K=S(),V=m(),w=await d({name:Y.name,agents:[...K,...V],scope:q}),M=new Set(w.agents.filter((z)=>z.installed).map((z)=>z.agent)),O=new Set(F),T=new Map(w.agents.map((z)=>[z.agent,z])),b=V.filter((z)=>{if(O.has(z))return!0;return T.get(z)?.installed===!0}),v=b.filter((z)=>M.has(z)),R=[];if(K.length>0){let z=K[0];if(!z)throw Error("Expected at least one universal agent");let E=T.get(z)?.outputDir??"path unavailable";R.push({label:"Universal",value:r,hint:E});let C=K.map(($Q)=>A[$Q]).join(", ");if(x&&!G)console.log(k(`Agents supporting universal skills: ${C}`))}for(let z of b){let E=T.get(z)?.outputDir??"path unavailable";R.push({label:A[z],value:z,hint:E})}let P=K.length>0&&K.every((z)=>M.has(z)),I=[...v.filter((z)=>!K.includes(z))];if(P)I.unshift(r);let D;if(G)D=[...K,...b];else{let z=R.length===0?[]:await GX({message:"Select agents to install skills for",choices:R,default:I,required:!1}),j=new Set(z.filter((E)=>E!==r));if(z.includes(r))for(let E of K)j.add(E);D=[...j]}let g=D.filter((z)=>!M.has(z)),p=D.filter((z)=>{let j=T.get(z);return j!==void 0&&LQ(z,q,Y,j)}),B=[...M].filter((z)=>!D.includes(z)),U=[...g,...p];if(U.length>0)try{let z=await h({message:"Installing skills...",task:async()=>XQ({command:Q,meta:Y,agents:U,scope:q,installMode:X.installMode})});console.log(`
11
- ${l(`Installed "${Y.name}" v${Y.version}`)}`);for(let j of GQ(z.agents))console.log(k(` ${j.label} \u2192 ${j.outputDir}`))}catch(z){if(z instanceof L)if(G?!0:await KX({message:`"${z.details.outputDir}" already exists but was not created by Crust. Overwrite?`,default:!1})){let E=await h({message:"Overwriting skill...",task:async()=>XQ({command:Q,meta:Y,agents:[z.details.agent],scope:q,force:!0,installMode:X.installMode})});console.log(`
5
+ `)}function pX(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 pQ(Q){let X=[`# \`${UQ(Q)}\``,""];if(Q.description)X.push(Q.description,"");return X}function uQ(Q){let X=Q.instructions??[];if(!FQ(X))return[];return["## Command Instructions","",...cQ(X),""]}function mQ(Q){let X=["## Usage","","```",Q.usage??pX(Q),"```",""];if(Q.args.length>0)X.push("## Arguments","",...mX(Q.args),"");if(Q.flags.length>0)X.push("## Flags","",...dX(Q.flags),"");return X.push("## Command Documentation Authority","","You must treat only the arguments, flags, options, aliases, and defaults documented in this file as supported for this command.","You must not infer or invent additional command-line options.",""),X}function uX(Q,X){let Z=["## Subcommands",""];for(let H of Q.children){let $=i(H),W=VQ(X,$),Y=H.description?` - ${H.description}`:"";Z.push(`- [\`${H.name}\`](${W})${Y}`)}return Z.push(""),Z}function mX(Q){let X=[];X.push("| Argument | Type | Required | Description |"),X.push("| -------- | ---- | -------- | ----------- |");for(let Z of Q){let H=Z.variadic?`${Z.name}...`:Z.name,$=Z.required?"Yes":"No",W=yQ(cX(Z));X.push(`| \`${H}\` | ${Z.type} | ${$} | ${W} |`)}return X}function cX(Q){let X=[];if(Q.description)X.push(Q.description);if(Q.default!==void 0)X.push(`Default: \`${Q.default}\``);return X.join(". ")||"-"}function dX(Q){let X=[];X.push("| Flag | Type | Required | Description |"),X.push("| ---- | ---- | -------- | ----------- |");for(let Z of Q){let H=rX(Z),$=Z.required?"Yes":"No",W=yQ(lX(Z));X.push(`| ${H} | ${Z.type} | ${$} | ${W} |`)}return X}function rX(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 lX(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 cQ(Q){return Q.map((X)=>`- ${X}`)}function sX(Q){if(typeof Q==="string")return kQ(Q);return cQ(BQ(Q))}function dQ(Q,X){let Z=[],H=i(Q);if(Z.push("---"),Z.push(""),Q.path.length>1){let W=Q.path.slice(0,-1),Y=rQ(X,W);if(Y){let F=i(Y),x=VQ(H,F),q=UQ(Y);Z.push(`Parent: [\`${q}\`](${x})`),Z.push("")}}let $=VQ(H,"SKILL.md");return Z.push(`[Skill Overview](${$})`),Z.push(""),Z}function rQ(Q,X){if(iX(Q.path,X))return Q;for(let Z of Q.children){let H=rQ(Z,X);if(H)return H}return}function iX(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 oX}from"fs/promises";import{join as aX}from"path";var t="crust.json";async function zQ(Q){let X;try{X=await oX(aX(Q,t),"utf-8")}catch{return{status:"absent"}}let Z;try{Z=JSON.parse(X)}catch{return{status:"malformed",reason:"parse-error"}}if(typeof Z!=="object"||Z===null)return{status:"malformed",reason:"not-an-object"};let H=Z;if(typeof H.version!=="string")return{status:"malformed",reason:"missing-version"};let{version:$,kind:W}=H;if(W===void 0)return{status:"ok",manifest:{version:$,kind:"generated"}};if(W==="bundle"||W==="generated")return{status:"ok",manifest:{version:$,kind:W}};return{status:"malformed",reason:"unknown-kind",rawKind:typeof W==="string"?W:JSON.stringify(W)}}async function wQ(Q){let X=await zQ(Q);return X.status==="ok"?X.manifest:null}async function YQ(Q){return(await wQ(Q))?.version??null}var XZ="auto";async function ZZ(Q){if(Q!==void 0)return Q;return[...S(),...await n()]}function XX(Q){if(Q!==void 0)return Q;return[...a]}var ZX=/^[a-z0-9]+(-[a-z0-9]+)*$/;function e(Q){return Q.length>=1&&Q.length<=64&&ZX.test(Q)}function QQ(Q){return Q}function _Q(Q){return Q.startsWith("use-")?Q:`use-${Q}`}async function XQ(Q){let{command:X,meta:Z,scope:H="global",clean:$=!0,force:W=!1,installMode:Y=XZ}=Q,F=await ZZ(Q.agents),x=QQ(Z.name),q=_Q(Z.name);if(!e(x))throw Error(`Invalid skill name "${x}": must be 1\u201364 lowercase `+`alphanumeric characters and hyphens, no leading/trailing/consecutive hyphens. Pattern: ${ZX.source}`);if(F.length===0)return{agents:[]};let G={...Z,name:x},K=AQ(X),V=fQ(K,G);return OQ({files:V,meta:G,agents:F,scope:H,clean:$,force:W,installMode:Y,kind:"generated",legacyResolvedName:q})}async function OQ(Q){let{files:X,meta:Z,agents:H,scope:$,clean:W,force:Y,installMode:F,kind:x,legacyResolvedName:q}=Q,G=H[0];if(!G)return{agents:[]};let K=JZ(Z,x),V=[...X,...K].sort((B,w)=>B.path<w.path?-1:B.path>w.path?1:0),U=V.map((B)=>B.path),M=new Map;for(let B of H){let w=N(B,$,Z.name),_=M.get(w);if(_)_.push(B);else M.set(w,[B])}let O=c($,Z.name),T=c($,q),b=new Map;for(let[B,w]of M){let _=w[0];if(!_)continue;b.set(B,await bQ({outputDir:B,legacyOutputDir:N(_,$,q),canonicalOutputDir:O,legacyCanonicalOutputDir:T}))}let v=await zQ(O),R=v.status==="ok"?v.manifest:null,P=R?.version??null;if((await jQ(O,O)).exists&&R===null&&!Y)throw new L({agent:G,outputDir:O,manifestMalformed:iQ(v)});if(R!==null&&R.kind!==x&&!Y)throw new L({agent:G,outputDir:O,kindMismatch:{existing:R.kind,attempted:x}});let D=R!==null&&R.kind!==x,g=Y||P!==Z.version||D;if(g){if(W)await RQ(O);await HX(O,V)}let p=[];for(let[B,w]of M){let _=w[0];if(!_)continue;let z=b.get(B);if(!z)continue;if(z.current.inspection.exists&&!z.current.isCrustManaged&&!Y){let $Q=await zQ(B);throw new L({agent:_,outputDir:B,manifestMalformed:iQ($Q)})}if(z.current.manifest!==null&&z.current.manifest.kind!==x&&!Y)throw new L({agent:_,outputDir:B,kindMismatch:{existing:z.current.manifest.kind,attempted:x}});let j=await $Z({outputDir:B,canonicalOutputDir:O,allFiles:V,clean:W,installMode:F,inspection:z.current.inspection,installedVersion:z.preferredVersion,currentVersion:Z.version,force:Y,installedKind:z.current.manifest?.kind??null,currentKind:x}),E=await YZ(z),C=HZ({installedVersion:z.preferredVersion,currentVersion:Z.version,canonicalChanged:g,pathChanged:j||E||z.preferredOutputDir!==B});for(let $Q of w)p.push({agent:$Q,outputDir:B,files:C==="up-to-date"?[]:U,status:C,previousVersion:C==="updated"?z.preferredVersion??void 0:void 0})}if(q!==Z.name){let B=await YQ(T);if(T!==O&&B!==null&&!await TQ(q,$))await qQ(T,{recursive:!0,force:!0})}return{agents:p}}async function KQ(Q){let{name:X,scope:Z="global"}=Q,H=XX(Q.agents),$=QQ(X),W=_Q(X),Y=c(Z,$),F=c(Z,W),x=[],q=new Map;for(let G of H){let K=N(G,Z,$),V=q.get(K);if(V)V.push(G);else q.set(K,[G])}for(let[G,K]of q){let V=K[0];if(!V)continue;let U=N(V,Z,W),M=await bQ({outputDir:G,legacyOutputDir:U,canonicalOutputDir:Y,legacyCanonicalOutputDir:F}),O=await MQ(M.current),T=M.legacy.outputDir!==M.current.outputDir?await MQ(M.legacy):!1,b=O||T,v=O?G:T?U:G;for(let R of K)x.push({agent:R,outputDir:v,status:b?"removed":"not-found"})}if(await YQ(Y)!==null&&!await TQ($,Z))await qQ(Y,{recursive:!0,force:!0});{let G=await YQ(F);if(F!==Y&&G!==null&&!await TQ(W,Z))await qQ(F,{recursive:!0,force:!0})}return{agents:x}}async function d(Q){let{name:X,scope:Z="global"}=Q,H=XX(Q.agents),$=QQ(X),W=_Q(X),Y=[],F=new Map;for(let x of H){let q=N(x,Z,$),G=F.get(q);if(G)G.push(x);else F.set(q,[x])}for(let[x,q]of F){let G=q[0];if(!G)continue;let K=N(G,Z,W),V=c(Z,$),U=c(Z,W),M=await bQ({outputDir:x,legacyOutputDir:K,canonicalOutputDir:V,legacyCanonicalOutputDir:U}),O=M.preferredOutputDir??x,T=M.preferredVersion;for(let b of q)Y.push({agent:b,outputDir:O,installed:T!==null,version:T??void 0})}return{agents:Y}}function iQ(Q){if(Q.status!=="malformed")return;return Q.rawKind!==void 0?{reason:Q.reason,rawKind:Q.rawKind}:{reason:Q.reason}}function HZ(Q){let{installedVersion:X,currentVersion:Z,canonicalChanged:H,pathChanged:$}=Q;if(X===null)return"installed";if(X===Z&&!H&&!$)return"up-to-date";return"updated"}async function $Z(Q){let{outputDir:X,canonicalOutputDir:Z,allFiles:H,clean:$,installMode:W,inspection:Y,installedVersion:F,currentVersion:x,force:q,installedKind:G,currentKind:K}=Q;if(W==="copy")return oQ({outputDir:X,allFiles:H,clean:$,inspection:Y,installedVersion:F,currentVersion:x,force:q,installedKind:G,currentKind:K});try{return await WZ({outputDir:X,canonicalOutputDir:Z,inspection:Y})}catch(V){if(W==="symlink")throw Error(`Failed to create symlink at "${X}" (installMode: symlink).`,{cause:V});let U=await jQ(X,Z);return oQ({outputDir:X,allFiles:H,clean:$,inspection:U,installedVersion:F,currentVersion:x,force:q,installedKind:G,currentKind:K})}}async function oQ(Q){let{outputDir:X,allFiles:Z,clean:H,inspection:$,installedVersion:W,currentVersion:Y,force:F,installedKind:x,currentKind:q}=Q,G=x!==null&&x!==q;if(!(F||!$.exists||$.isSymlink||W!==Y||G))return!1;if($.isSymlink||H)await RQ(X);return await HX(X,Z),!0}async function WZ(Q){let{outputDir:X,canonicalOutputDir:Z,inspection:H}=Q;if(H.exists&&H.isSymlink&&H.pointsToCanonical)return!1;if(H.exists)await RQ(X);return await qZ(Z,X),!0}async function jQ(Q,X){let Z;try{Z=await nX(Q)}catch{return{exists:!1,isSymlink:!1,pointsToCanonical:!1}}let H=process.platform==="win32"&&Z.isDirectory()&&await tQ(Q)!==null;if(!(Z.isSymbolicLink()||H))return{exists:!0,isSymlink:!1,pointsToCanonical:!1};let[W,Y,F]=await Promise.all([nQ(Q),nQ(X),tQ(Q)]);return{exists:!0,isSymlink:!0,pointsToCanonical:W!==null&&Y!==null&&W===Y||F===X}}async function aQ(Q,X){let[Z,H]=await Promise.all([wQ(Q),jQ(Q,X)]),$=Z?.version??null,W=$!==null||H.exists&&H.isSymlink&&H.pointsToCanonical;return{outputDir:Q,version:$,manifest:Z,inspection:H,isCrustManaged:W}}async function bQ(Q){let{outputDir:X,legacyOutputDir:Z,canonicalOutputDir:H,legacyCanonicalOutputDir:$}=Q,W=await aQ(X,H),Y=Z===X?W:await aQ(Z,$);if(W.isCrustManaged)return{current:W,legacy:Y,preferredVersion:W.version,preferredOutputDir:W.outputDir};if(Y.isCrustManaged)return{current:W,legacy:Y,preferredVersion:Y.version,preferredOutputDir:Y.outputDir};return{current:W,legacy:Y,preferredVersion:null,preferredOutputDir:null}}async function MQ(Q){if(!Q.isCrustManaged||!Q.inspection.exists)return!1;return await qQ(Q.outputDir,{recursive:!0,force:!0}),!0}async function YZ(Q){if(Q.legacy.outputDir===Q.current.outputDir)return!1;return MQ(Q.legacy)}async function nQ(Q){try{return await eX(Q)}catch{return null}}async function tQ(Q){try{return await tX(Q)}catch{return null}}async function qZ(Q,X){await eQ(QX(X),{recursive:!0});let Z=process.platform==="win32"?"junction":"dir";await QZ(Q,X,Z)}async function TQ(Q,X){let Z=new Set;for(let H of a)Z.add(N(H,X,Q));for(let H of Z)if(await YQ(H)!==null)return!0;return!1}function JZ(Q,X){let Z={name:Q.name,description:Q.description,version:Q.version,kind:X};return[{path:t,content:`${JSON.stringify(Z,null,"\t")}
6
+ `}]}async function RQ(Q){await qQ(Q,{recursive:!0,force:!0})}async function HX(Q,X){let Z=new Set;for(let $ of X){let W=sQ(Q,$.path),Y=QX(W);Z.add(Y)}let H=[...Z].sort();for(let $ of H)await eQ($,{recursive:!0});for(let $ of X){let W=sQ(Q,$.path);if(typeof $.content==="string")await lQ(W,$.content,"utf-8");else await lQ(W,$.content)}}var EQ="SKILL.md";function KZ(Q){let X={name:null,description:null},H=(Q.startsWith("\uFEFF")?Q.slice(1):Q).split(/\r?\n/,51),$=0;while($<H.length&&H[$]?.trim()==="")$++;if($>=H.length||H[$]!=="---")return X;$++;let W=Math.min(50,H.length);for(let Y=$;Y<W;Y++){let F=H[Y];if(F===void 0)break;if(/^---\s*$/.test(F))break;let x=F.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*?)\s*$/);if(!x)continue;let q=x[1];if(q!=="name"&&q!=="description")continue;if(X[q]!==null)continue;X[q]=GZ(x[2]??"")}return X}function GZ(Q){let X=Q[0];if(X==='"'||X==="'"){let H=Q.indexOf(X,1);if(H!==-1)return Q.slice(1,H)}let Z=Q.search(/(^|\s)#/);return Z===-1?Q:Q.slice(0,Z).trimEnd()}function FZ(Q,X,Z){let H=X.endsWith(WX)?X:X+WX;if(Q!==X&&!Q.startsWith(H))throw Error(`Bundle path traversal rejected: "${Z}" resolves to "${Q}", which is outside the bundle root "${X}".`)}async function JX(Q,X,Z,H){let $=await BZ(Q,{withFileTypes:!0}),W=[];for(let Y of $){let F=Y.name,x=IQ(Q,F),q=Z===""?F:`${Z}/${F}`,G=await YX(x);FZ(G,X,x);let K=await qX(G);if(K.isDirectory()){if(H.has(G))continue;H.add(G),W.push(...await JX(x,X,q,H))}else if(K.isFile())W.push({relPath:q,absPath:x})}return W}async function xZ(Q){let X=zZ(Q),Z;try{Z=await YX(X)}catch(G){throw Error(`Bundle source directory "${X}" does not exist or is not accessible.`,{cause:G})}if(!(await qX(Z)).isDirectory())throw Error(`Bundle source path "${Z}" is not a directory.`);let W=await JX(Z,Z,"",new Set([Z])),Y=W.find((G)=>G.relPath===EQ);if(!Y)throw Error(`Bundle is missing SKILL.md at the bundle root "${Z}". Every skill bundle must contain a top-level SKILL.md file.`);if(W.some((G)=>G.relPath===t))throw Error(`Bundle source at "${Z}" contains a reserved file "${t}" at the root. Crust regenerates this file during installation; remove it from your bundle source.`);let F=await Promise.all(W.map(async(G)=>({path:G.relPath,content:await $X(G.absPath)}))),x=await $X(Y.absPath,"utf-8"),q=KZ(x);if(q.name===null||q.name==="")throw Error(`Bundle SKILL.md is missing a top-level \`name:\` field in its YAML frontmatter (at "${IQ(Z,EQ)}"). Add \`name: <skill-name>\` to the frontmatter block.`);if(q.description===null||q.description==="")throw Error(`Bundle SKILL.md is missing a top-level \`description:\` field in its YAML frontmatter (at "${IQ(Z,EQ)}"). Add \`description: <one-line summary>\` to the frontmatter block.`);return{files:F,frontmatter:{name:q.name,description:q.description}}}async function ZQ(Q){let{sourceDir:X,agents:Z,version:H,scope:$="global",clean:W=!0,force:Y=!1,installMode:F="auto",expectedName:x}=Q,{files:q,frontmatter:G}=await xZ(X),K=QQ(G.name);if(!e(K))throw Error(`Invalid skill name "${K}" in SKILL.md frontmatter: must be 1\u201364 lowercase `+"alphanumeric characters and hyphens, no leading/trailing/consecutive hyphens.");if(x!==void 0&&K!==x)throw Error(`Bundle SKILL.md frontmatter name "${K}" does not match the expected name "${x}". Update the bundle's SKILL.md frontmatter \`name:\` field, or change the configured \`name\` to match.`);if(Z.length===0)return{agents:[]};let V={name:K,description:G.description,version:H};return OQ({files:[...q],meta:V,agents:Z,scope:$,clean:W,force:Y,installMode:F,kind:"bundle",legacyResolvedName:K})}import{Crust as zX,VALIDATION_MODE_ENV as VZ}from"@crustjs/core";import{spinner as h}from"@crustjs/progress";import{confirm as KX,multiselect as GX,select as UZ}from"@crustjs/prompts";import{bold as l,dim as k,yellow as o}from"@crustjs/style";var wZ="skill",FX="global",r="__universal__";function xX(Q){return Q==="global"||Q==="project"}function MZ(Q){return Q==="auto"||Q==="symlink"||Q==="copy"}function VX(Q){if(Q===void 0)return;if(!xX(Q))throw Error(`Invalid --scope value: ${String(Q)}. Expected "project" or "global".`);return Q}async function UX(Q,X){let Z=VX(Q);if(Z!==void 0)return Z;if(X.defaultScope)return X.defaultScope;return UZ({message:"Select scope",choices:[{label:"Project",value:"project"},{label:"Global",value:"global"}],default:FX})}function HQ(Q){let X=new Set(S()),Z=[];if(Q.some((H)=>X.has(H)))Z.push("Universal");for(let H of Q){if(X.has(H))continue;Z.push(A[H])}return Z}function GQ(Q){let X=new Set(S()),Z=[],H=Q.find(($)=>X.has($.agent));if(H)Z.push({label:"Universal",outputDir:H.outputDir});for(let $ of Q){if(X.has($.agent))continue;Z.push({label:A[$.agent],outputDir:$.outputDir})}return Z}function CQ(Q,X){return{name:Q.meta.name,description:Q.meta.description??"",version:X.version,instructions:X.instructions,license:X.license,allowedTools:X.allowedTools,compatibility:X.compatibility,disableModelInvocation:X.disableModelInvocation}}function TZ(Q,X){if(X===void 0)return[];if(!Array.isArray(X))throw Error(`skillPlugin: customSkills must be an array, got ${X===null?"null":typeof X}.`);if(X.length===0)return[];let Z=new Set;for(let H=0;H<X.length;H++){let $=X[H];if(!$||typeof $!=="object")throw Error(`skillPlugin: customSkills[${H}] must be an object, got ${$===null?"null":typeof $}.`);if(typeof $.name!=="string"||$.name.length===0)throw Error(`skillPlugin: customSkills[${H}].name must be a non-empty string.`);if(!e($.name))throw Error(`skillPlugin: customSkills[${H}].name "${$.name}" is not a valid skill name. `+"Must be 1\u201364 lowercase alphanumeric characters and hyphens, "+"no leading/trailing/consecutive hyphens.");if($.name===Q)throw Error(`skillPlugin: customSkills[${H}].name "${$.name}" collides with the main skill name. Custom skill bundle names must differ from the root command name.`);if(Z.has($.name))throw Error(`skillPlugin: customSkills contains duplicate name "${$.name}". Each entry must declare a unique name.`);if(Z.add($.name),$.version!==void 0&&(typeof $.version!=="string"||$.version.length===0))throw Error(`skillPlugin: customSkills[${H}].version (for "${$.name}") must be a non-empty string when set, or omitted to inherit the plugin's \`version\`.`);if(typeof $.sourceDir!=="string"&&!($.sourceDir instanceof URL))throw Error(`skillPlugin: customSkills[${H}].sourceDir (for "${$.name}") must be a string or URL, got ${typeof $.sourceDir}.`);if($.scope!==void 0&&!xX($.scope))throw Error(`skillPlugin: customSkills[${H}].scope (for "${$.name}") must be "project" or "global", got ${JSON.stringify($.scope)}.`);if($.installMode!==void 0&&!MZ($.installMode))throw Error(`skillPlugin: customSkills[${H}].installMode (for "${$.name}") must be "auto", "symlink", or "copy", got ${JSON.stringify($.installMode)}.`)}return X}function _Z(Q,X){let Z=Q.scope??X.defaultScope;if(Z!==void 0)return[u(Z)];return[...new Set(["project","global"].map((H)=>u(H)))]}async function OZ(Q,X){let Z=[...S(),...m()];if(Z.length===0)return;let H=_Z(Q,X),$=Q.installMode??X.installMode,W=Q.version??X.version;for(let Y of H){let x=(await d({name:Q.name,agents:Z,scope:Y})).agents.filter((q)=>{if(!q.installed)return!1;let G=N(q.agent,Y,Q.name);return q.version!==W||q.outputDir!==G});if(x.length===0)continue;try{await h({message:`Updating ${Y} skills [${Q.name}]...`,task:async({updateMessage:q})=>{let G=await ZQ({sourceDir:Q.sourceDir,agents:x.map((U)=>U.agent),version:W,scope:Y,installMode:$,expectedName:Q.name}),K=G.agents.filter((U)=>U.status==="updated").map((U)=>U.agent),V=HQ(K);if(V.length>0)q(`Updated bundle "${Q.name}" to v${W} for ${V.join(", ")} (${Y})`);return G}})}catch(q){if(q instanceof L){let G=q.details.kindMismatch?` (existing skill is "${q.details.kindMismatch.existing}", attempted "${q.details.kindMismatch.attempted}")`:"";console.warn(o(`Skill conflict [${Q.name}]: "${q.details.outputDir}" already exists but conflicts with the requested install${G}. Skipping auto-update for ${Y}. Delete or rename the conflicting skill to resolve.`))}else throw q}}}function LQ(Q,X,Z,H){if(!H.installed)return!1;let $=N(Q,X,Z.name);return H.version!==Z.version||H.outputDir!==$}async function jZ(Q,X,Z){let H=[...S(),...m()];if(H.length===0){await BX(Z,X);return}let $=CQ(Q,X),W=[...new Set(["project","global"].map((Y)=>u(Y)))];for(let Y of W){let x=(await d({name:$.name,agents:H,scope:Y})).agents.filter((q)=>LQ(q.agent,Y,$,q));if(x.length===0)continue;try{await h({message:`Updating ${Y} skills...`,task:async({updateMessage:q})=>{let G=await XQ({command:Q,meta:$,agents:x.map((U)=>U.agent),scope:Y,installMode:X.installMode}),K=G.agents.filter((U)=>U.status==="updated").map((U)=>U.agent),V=HQ(K);if(V.length>0)q(`Updated skill "${$.name}" to v${$.version} for ${V.join(", ")} (${Y})`);return G}})}catch(q){if(q instanceof L)console.warn(o(`Skill conflict: "${q.details.outputDir}" already exists but was not created by ${$.name}. Skipping auto-update for ${Y}. Delete or rename the conflicting skill to resolve.`));else throw q}}await BX(Z,X)}async function BX(Q,X){for(let Z of Q)try{await OZ(Z,X)}catch(H){let $=H instanceof Error?H.message:String(H);console.warn(o(`Skill auto-update failed [${Z.name}]: ${$}. Continuing with remaining skills.`))}}function bZ(Q){let X;return{name:"skills",async setup(Z,H){X=Z.rootCommand;let $=Q.command??wZ,W=TZ(X.meta.name,Q.customSkills);if(H.addSubCommand(X,$,EZ(X,Q,W,$)),process.env[VZ]==="1")return;if(Z.argv[0]===$)return;if(Q.autoUpdate!==!1)await jZ(X,Q,W)}}}async function RZ(Q){let{entry:X,options:Z,scope:H,installAll:$,isInteractive:W}=Q,Y=X.installMode??Z.installMode,F=X.version??Z.version,x=await n(),q=S(),G=m(),K=await d({name:X.name,agents:[...q,...G],scope:H}),V=new Set(K.agents.filter((B)=>B.installed).map((B)=>B.agent)),U=new Set(x),M=new Map(K.agents.map((B)=>[B.agent,B])),O=G.filter((B)=>{if(U.has(B))return!0;return M.get(B)?.installed===!0}),T=O.filter((B)=>V.has(B)),b=[];if(q.length>0){let B=q[0];if(!B)throw Error("Expected at least one universal agent");let _=M.get(B)?.outputDir??"path unavailable";b.push({label:"Universal",value:r,hint:_});let z=q.map((j)=>A[j]).join(", ");if(W&&!$)console.log(k(`Agents supporting universal skills: ${z}`))}for(let B of O){let _=M.get(B)?.outputDir??"path unavailable";b.push({label:A[B],value:B,hint:_})}let v=q.length>0&&q.every((B)=>V.has(B)),R=[...T.filter((B)=>!q.includes(B))];if(v)R.unshift(r);let P;if($)P=[...q,...O];else{let B=b.length===0?[]:await GX({message:`Select agents to install skills for [${X.name}]`,choices:b,default:R,required:!1}),w=new Set(B.filter((_)=>_!==r));if(B.includes(r))for(let _ of q)w.add(_);P=[...w]}let I=P.filter((B)=>!V.has(B)),D=P.filter((B)=>{let w=M.get(B);if(!w?.installed)return!1;let _=N(B,H,X.name);return w.version!==F||w.outputDir!==_}),g=[...V].filter((B)=>!P.includes(B)),p=[...I,...D];if(p.length>0)try{let B=await h({message:`Installing skills [${X.name}]...`,task:async()=>ZQ({sourceDir:X.sourceDir,agents:p,version:F,scope:H,installMode:Y,expectedName:X.name})});console.log(`
7
+ ${l(`Installed bundle "${X.name}" v${F}`)}`);for(let w of GQ(B.agents))console.log(k(` ${w.label} \u2192 ${w.outputDir}`))}catch(B){if(B instanceof L){let w=B.details.kindMismatch?` (existing is a "${B.details.kindMismatch.existing}" skill, attempted "${B.details.kindMismatch.attempted}")`:" but was not created by Crust";if($?!0:await KX({message:`"${B.details.outputDir}" already exists${w}. Overwrite?`,default:!1})){let z=await h({message:`Overwriting bundle [${X.name}]...`,task:async()=>ZQ({sourceDir:X.sourceDir,agents:[B.details.agent],version:F,scope:H,force:!0,installMode:Y,expectedName:X.name})});console.log(`
8
+ ${l(`Installed bundle "${X.name}" v${F}`)}`);for(let j of GQ(z.agents))console.log(k(` ${j.label} \u2192 ${j.outputDir}`))}else console.log(k(`
9
+ Skipped ${A[B.details.agent]} [${X.name}]`))}else throw B}if(g.length>0){let w=(await h({message:`Removing skills [${X.name}]...`,task:async()=>KQ({name:X.name,agents:g,scope:H})})).agents.filter((z)=>z.status==="removed").map((z)=>z.agent),_=HQ(w);if(_.length>0)console.log(`
10
+ ${l(`Removed bundle "${X.name}" from ${_.join(", ")}`)}`)}if(p.length===0&&g.length===0)console.log(k(`No changes [${X.name}].`))}function EZ(Q,X,Z,H){let $=IZ(Q,X,Z);return new zX(H).meta({description:"Manage agent skill installations"}).flags({scope:{type:"string",description:"Install scope (project or global)"},all:{type:"boolean",description:"Install for all detected agents non-interactively (universal + detected)"}}).run(async(W)=>{let Y=CQ(Q,X),F=W.flags.all===!0,x=!!process.stdin.isTTY,q=F?VX(W.flags.scope)??X.defaultScope??FX:await UX(W.flags.scope,X),G=await n(),K=S(),V=m(),U=await d({name:Y.name,agents:[...K,...V],scope:q}),M=new Set(U.agents.filter((z)=>z.installed).map((z)=>z.agent)),O=new Set(G),T=new Map(U.agents.map((z)=>[z.agent,z])),b=V.filter((z)=>{if(O.has(z))return!0;return T.get(z)?.installed===!0}),v=b.filter((z)=>M.has(z)),R=[];if(K.length>0){let z=K[0];if(!z)throw Error("Expected at least one universal agent");let E=T.get(z)?.outputDir??"path unavailable";R.push({label:"Universal",value:r,hint:E});let C=K.map(($Q)=>A[$Q]).join(", ");if(x&&!F)console.log(k(`Agents supporting universal skills: ${C}`))}for(let z of b){let E=T.get(z)?.outputDir??"path unavailable";R.push({label:A[z],value:z,hint:E})}let P=K.length>0&&K.every((z)=>M.has(z)),I=[...v.filter((z)=>!K.includes(z))];if(P)I.unshift(r);let D;if(F)D=[...K,...b];else{let z=R.length===0?[]:await GX({message:"Select agents to install skills for",choices:R,default:I,required:!1}),j=new Set(z.filter((E)=>E!==r));if(z.includes(r))for(let E of K)j.add(E);D=[...j]}let g=D.filter((z)=>!M.has(z)),p=D.filter((z)=>{let j=T.get(z);return j!==void 0&&LQ(z,q,Y,j)}),B=[...M].filter((z)=>!D.includes(z)),w=[...g,...p];if(w.length>0)try{let z=await h({message:"Installing skills...",task:async()=>XQ({command:Q,meta:Y,agents:w,scope:q,installMode:X.installMode})});console.log(`
11
+ ${l(`Installed "${Y.name}" v${Y.version}`)}`);for(let j of GQ(z.agents))console.log(k(` ${j.label} \u2192 ${j.outputDir}`))}catch(z){if(z instanceof L)if(F?!0:await KX({message:`"${z.details.outputDir}" already exists but was not created by Crust. Overwrite?`,default:!1})){let E=await h({message:"Overwriting skill...",task:async()=>XQ({command:Q,meta:Y,agents:[z.details.agent],scope:q,force:!0,installMode:X.installMode})});console.log(`
12
12
  ${l(`Installed "${Y.name}" v${Y.version}`)}`);for(let C of GQ(E.agents))console.log(k(` ${C.label} \u2192 ${C.outputDir}`))}else console.log(k(`
13
13
  Skipped ${A[z.details.agent]}`));else throw z}if(B.length>0){let j=(await h({message:"Removing skills...",task:async()=>KQ({name:Y.name,agents:B,scope:q})})).agents.filter((C)=>C.status==="removed").map((C)=>C.agent),E=HQ(j);if(E.length>0)console.log(`
14
- ${l(`Removed from ${E.join(", ")}`)}`)}if(U.length===0&&B.length===0)console.log(k("No changes."));let _=[];for(let z of Z){let j=z.scope??q;try{await RZ({entry:z,options:X,scope:j,installAll:G,isInteractive:x})}catch(E){let C=E instanceof Error?E.message:String(E);console.warn(o(`Skill reconciliation failed [${z.name}]: ${C}. Continuing with remaining skills.`)),_.push(z.name)}}if(_.length>0)process.exitCode=1}).command($)._node}function IZ(Q,X,Z){return new zX("update").meta({description:"Update installed skills to latest version"}).flags({scope:{type:"string",description:"Update scope (project or global)"}}).run(async(H)=>{let $=await UX(H.flags.scope,X),W=u($),Y=[...S(),...m()],G=CQ(Q,X),q=(await d({name:G.name,agents:Y,scope:$})).agents.filter((K)=>LQ(K.agent,$,G,K));if(q.length===0)console.log(k(`No updates needed (${W}).`));else try{let V=(await h({message:`Updating ${W} skills...`,task:async()=>XQ({command:Q,meta:G,agents:q.map((M)=>M.agent),scope:$,installMode:X.installMode})})).agents.filter((M)=>M.status==="updated").map((M)=>M.agent),w=HQ(V);if(w.length>0)console.log(`
15
- ${l(`Updated "${G.name}" to v${G.version} for ${w.join(", ")} (${W})`)}`)}catch(K){if(K instanceof L)console.warn(o(`Skipped ${A[K.details.agent]}: "${K.details.outputDir}" already exists but was not created by ${G.name}. Delete or rename the conflicting directory to resolve.`));else throw K}let F=[];for(let K of Z){let V=K.scope??$,w=u(V),M=K.installMode??X.installMode,O=K.version??X.version;try{let b=(await d({name:K.name,agents:Y,scope:V})).agents.filter((I)=>{if(!I.installed)return!1;let D=N(I.agent,V,K.name);return I.version!==O||I.outputDir!==D});if(b.length===0){console.log(k(`No updates needed [${K.name}] (${w}).`));continue}let R=(await h({message:`Updating ${w} skills [${K.name}]...`,task:async()=>ZQ({sourceDir:K.sourceDir,agents:b.map((I)=>I.agent),version:O,scope:V,installMode:M,expectedName:K.name})})).agents.filter((I)=>I.status==="updated").map((I)=>I.agent),P=HQ(R);if(P.length>0)console.log(`
16
- ${l(`Updated bundle "${K.name}" to v${O} for ${P.join(", ")} (${w})`)}`)}catch(T){if(T instanceof L){let b=T.details.kindMismatch?` (existing is a "${T.details.kindMismatch.existing}" skill, attempted "${T.details.kindMismatch.attempted}")`:"";console.warn(o(`Skipped ${A[T.details.agent]} [${K.name}]: "${T.details.outputDir}" already exists${b}. Delete or rename the conflicting directory to resolve.`))}else{let b=T instanceof Error?T.message:String(T);console.warn(o(`Skill update failed [${K.name}]: ${b}. Continuing with remaining skills.`)),F.push(K.name)}}}if(F.length>0)process.exitCode=1})}export{KQ as uninstallSkill,d as skillStatus,bZ as skillPlugin,QQ as resolveSkillName,c as resolveCanonicalSkillPath,e as isValidSkillName,bX as isUniversalAgent,ZQ as installSkillBundle,S as getUniversalAgents,m as getAdditionalAgents,XQ as generateSkill,n as detectInstalledAgents,CX as annotate,L as SkillConflictError};
14
+ ${l(`Removed from ${E.join(", ")}`)}`)}if(w.length===0&&B.length===0)console.log(k("No changes."));let _=[];for(let z of Z){let j=z.scope??q;try{await RZ({entry:z,options:X,scope:j,installAll:F,isInteractive:x})}catch(E){let C=E instanceof Error?E.message:String(E);console.warn(o(`Skill reconciliation failed [${z.name}]: ${C}. Continuing with remaining skills.`)),_.push(z.name)}}if(_.length>0)process.exitCode=1}).command($)._node}function IZ(Q,X,Z){return new zX("update").meta({description:"Update installed skills to latest version"}).flags({scope:{type:"string",description:"Update scope (project or global)"}}).run(async(H)=>{let $=await UX(H.flags.scope,X),W=u($),Y=[...S(),...m()],F=CQ(Q,X),q=(await d({name:F.name,agents:Y,scope:$})).agents.filter((K)=>LQ(K.agent,$,F,K));if(q.length===0)console.log(k(`No updates needed (${W}).`));else try{let V=(await h({message:`Updating ${W} skills...`,task:async()=>XQ({command:Q,meta:F,agents:q.map((M)=>M.agent),scope:$,installMode:X.installMode})})).agents.filter((M)=>M.status==="updated").map((M)=>M.agent),U=HQ(V);if(U.length>0)console.log(`
15
+ ${l(`Updated "${F.name}" to v${F.version} for ${U.join(", ")} (${W})`)}`)}catch(K){if(K instanceof L)console.warn(o(`Skipped ${A[K.details.agent]}: "${K.details.outputDir}" already exists but was not created by ${F.name}. Delete or rename the conflicting directory to resolve.`));else throw K}let G=[];for(let K of Z){let V=K.scope??$,U=u(V),M=K.installMode??X.installMode,O=K.version??X.version;try{let b=(await d({name:K.name,agents:Y,scope:V})).agents.filter((I)=>{if(!I.installed)return!1;let D=N(I.agent,V,K.name);return I.version!==O||I.outputDir!==D});if(b.length===0){console.log(k(`No updates needed [${K.name}] (${U}).`));continue}let R=(await h({message:`Updating ${U} skills [${K.name}]...`,task:async()=>ZQ({sourceDir:K.sourceDir,agents:b.map((I)=>I.agent),version:O,scope:V,installMode:M,expectedName:K.name})})).agents.filter((I)=>I.status==="updated").map((I)=>I.agent),P=HQ(R);if(P.length>0)console.log(`
16
+ ${l(`Updated bundle "${K.name}" to v${O} for ${P.join(", ")} (${U})`)}`)}catch(T){if(T instanceof L){let b=T.details.kindMismatch?` (existing is a "${T.details.kindMismatch.existing}" skill, attempted "${T.details.kindMismatch.attempted}")`:"";console.warn(o(`Skipped ${A[T.details.agent]} [${K.name}]: "${T.details.outputDir}" already exists${b}. Delete or rename the conflicting directory to resolve.`))}else{let b=T instanceof Error?T.message:String(T);console.warn(o(`Skill update failed [${K.name}]: ${b}. Continuing with remaining skills.`)),G.push(K.name)}}}if(G.length>0)process.exitCode=1})}export{KQ as uninstallSkill,d as skillStatus,bZ as skillPlugin,QQ as resolveSkillName,c as resolveCanonicalSkillPath,e as isValidSkillName,bX as isUniversalAgent,ZQ as installSkillBundle,S as getUniversalAgents,m as getAdditionalAgents,XQ as generateSkill,n as detectInstalledAgents,CX as annotate,L as SkillConflictError};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crustjs/skills",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Agent skill generation from Crust command definitions",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -46,15 +46,15 @@
46
46
  "@crustjs/progress": "0.0.4",
47
47
  "@crustjs/prompts": "0.1.0",
48
48
  "@crustjs/style": "0.2.0",
49
- "@crustjs/utils": "0.0.2"
49
+ "@crustjs/utils": "0.0.3"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@crustjs/config": "0.0.0",
53
- "@crustjs/core": "0.0.18",
53
+ "@crustjs/core": "0.0.19",
54
54
  "bunup": "^0.16.31"
55
55
  },
56
56
  "peerDependencies": {
57
- "@crustjs/core": "0.0.18",
57
+ "@crustjs/core": "0.0.19",
58
58
  "typescript": "^6.0.3"
59
59
  }
60
60
  }