@crustjs/skills 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,892 +1,178 @@
1
- import { CommandNode } from "@crustjs/core";
2
- /**
3
- * Metadata for the generated skill bundle.
4
- *
5
- * This information populates the `SKILL.md` frontmatter and distribution
6
- * metadata files (`crust.json`).
7
- *
8
- * @example
9
- * ```ts
10
- * const meta: SkillMeta = {
11
- * name: "my-cli",
12
- * description: "CLI tool for managing widgets",
13
- * version: "1.0.0",
14
- * };
15
- * // generateSkill() will output to `my-cli/` with name "my-cli"
16
- * ```
17
- */
18
- interface SkillMeta {
19
- /**
20
- * Skill name — the user-facing CLI name (e.g. `"my-cli"`).
21
- *
22
- * `generateSkill()`, `uninstallSkill()`, and `skillStatus()` treat this as
23
- * the canonical raw skill name for output directory paths, SKILL.md
24
- * frontmatter, and crust.json metadata. For example, `name: "my-cli"`
25
- * produces output under `my-cli/`.
26
- *
27
- * The resolved name must conform to the Agent Skills spec: 1–64 lowercase
28
- * alphanumeric characters and hyphens, no leading/trailing/consecutive
29
- * hyphens.
30
- */
31
- name: string;
32
- /** Human-readable description of what the CLI does */
33
- description: string;
34
- /** Version string for the generated skill bundle */
35
- version: string;
36
- /**
37
- * License name or reference to a bundled license file.
38
- *
39
- * Emitted in SKILL.md YAML frontmatter as `license:`.
40
- */
41
- license?: string;
42
- /**
43
- * Environment requirements or compatibility notes (max 500 chars per spec).
44
- *
45
- * Indicates intended product, required system packages, network access, etc.
46
- * Emitted in SKILL.md YAML frontmatter as `compatibility:`.
47
- *
48
- * @example "Requires deploy-cli installed on PATH"
49
- */
50
- compatibility?: string;
51
- /**
52
- * When `true`, prevents agents from automatically loading this skill.
53
- * Users must invoke it manually with `/skill-name`.
54
- *
55
- * Emitted in SKILL.md YAML frontmatter as `disable-model-invocation: true`.
56
- * @default false
57
- */
58
- disableModelInvocation?: boolean;
59
- /**
60
- * Space-delimited list of pre-approved tools the skill may use.
61
- *
62
- * For CLI skills, setting this to `Bash(<cli-name> *)` allows agents to
63
- * execute the CLI without per-use permission prompts.
64
- *
65
- * Emitted in SKILL.md YAML frontmatter as `allowed-tools:`.
66
- *
67
- * @example "Bash(my-cli *) Read Grep"
68
- */
69
- allowedTools?: string;
70
- /**
71
- * Additional top-level instructions rendered into `SKILL.md`.
72
- *
73
- * Use this for plugin- or product-specific guidance that should be visible
74
- * before agents inspect individual command documentation files.
75
- *
76
- * **Note:** When a `string` value contains markdown headings (e.g. `## Foo`),
77
- * they are rendered at the same level as `## General Guidance`, not nested
78
- * under it. Use a `string[]` of plain instructions to avoid unintended
79
- * heading hierarchy.
80
- */
81
- instructions?: string | string[];
82
- }
83
- /** Supported agent targets for skill installation. */
84
- type AgentTarget = "amp" | "adal" | "antigravity" | "augment" | "claude-code" | "cline" | "codebuddy" | "codex" | "command-code" | "continue" | "cortex" | "crush" | "cursor" | "droid" | "gemini-cli" | "github-copilot" | "goose" | "iflow-cli" | "junie" | "kilo" | "kimi-cli" | "kiro-cli" | "kode" | "mcpjam" | "mistral-vibe" | "mux" | "neovate" | "opencode" | "openclaw" | "openhands" | "pi" | "pochi" | "qoder" | "qwen-code" | "replit" | "roo" | "trae" | "trae-cn" | "windsurf" | "zencoder";
85
- /** Agent install class used by interactive skill management UX. */
1
+ import { CommandDefinition, CommandSnapshot, ExtensionFactory } from "@crustjs/core";
2
+ //#region src/build.d.ts
3
+ /** Options for rendering a skill source. */
4
+ interface WriteSkillsOptions {
5
+ /** Application whose command tree is rendered into a generated skill. Omit to write only `extras`. */
6
+ readonly app?: {
7
+ snapshot(): Promise<CommandSnapshot>;
8
+ };
9
+ /** `skills` directory that receives one subdirectory per skill. */
10
+ readonly outDir: string;
11
+ /** Version recorded in the generated skill's SKILL.md metadata. Omitted when absent. */
12
+ readonly version?: string;
13
+ /** Generated skill name. Defaults to the root command name. */
14
+ readonly name?: string;
15
+ /** Generated skill description. Defaults to the root command description. */
16
+ readonly description?: string;
17
+ /** Hand-authored skill directories included alongside the generated skill. */
18
+ readonly extras?: readonly (string | URL)[];
19
+ }
20
+ /**
21
+ * Renders generated and authored skills into a package-ready skill source.
22
+ */
23
+ export declare function writeSkills({ app, ...options }: WriteSkillsOptions): Promise<readonly string[]>;
24
+ /** Renders skills from a Command Snapshot prepared in this or another process. */
25
+ export declare function writeSkillsFromSnapshot(snapshot: CommandSnapshot, options: Omit<WriteSkillsOptions, "app">): Promise<readonly string[]>;
26
+ //#endregion
27
+ //#region src/agents.d.ts
86
28
  type AgentClass = "universal" | "additional";
87
- /** Installation scope — global (home directory) or project (cwd, except home dir which normalizes to global). */
88
29
  type Scope = "global" | "project";
89
- /** Installation strategy for agent skill output paths. */
90
- type SkillInstallMode = "auto" | "symlink" | "copy";
91
- /**
92
- * Origin of an installed skill bundle.
93
- *
94
- * Recorded in `crust.json` as the top-level `kind` field so Crust can detect
95
- * when a generated and a hand-authored bundle would collide on the same name.
96
- *
97
- * - `"generated"` produced by {@link generateSkill} from a Crust command tree.
98
- * - `"bundle"` — installed by {@link installSkillBundle} from a hand-authored
99
- * directory containing a `SKILL.md` and supporting files.
100
- *
101
- * Legacy `crust.json` files (written before this field existed) are read as
102
- * `"generated"` for backward compatibility.
103
- */
104
- type SkillKind = "generated" | "bundle";
105
- /**
106
- * Top-level options for generating a skill bundle from a command tree.
107
- *
108
- * The `meta.name` value is used directly for all output paths and metadata.
109
- * For example, `name: "my-cli"` produces skill directories named `my-cli/`
110
- * and sets the manifest/frontmatter name to `"my-cli"`.
111
- *
112
- * @example
113
- * ```ts
114
- * import { generateSkill } from "@crustjs/skills";
115
- * import { rootCommand } from "./commands.ts";
116
- *
117
- * await generateSkill({
118
- * command: rootCommand,
119
- * meta: {
120
- * name: "my-cli", // output: my-cli/
121
- * description: "CLI tool for managing widgets",
122
- * version: "1.0.0",
123
- * },
124
- * agents: ["claude-code", "opencode"],
125
- * });
126
- * ```
127
- */
128
- interface GenerateOptions {
129
- /** Root command to generate the skill from */
130
- command: CommandNode;
131
- /** Skill metadata for the generated bundle */
132
- meta: SkillMeta;
133
- /**
134
- * Agent targets to install skills for.
135
- *
136
- * When omitted (or explicitly `undefined`), defaults to
137
- * `[...getUniversalAgents(), ...await detectInstalledAgents()]` — the union
138
- * of always-included universal agents and additional agents whose CLI is
139
- * detected on `PATH`. Pass an explicit array to override; `agents: []`
140
- * is treated as a no-op (no install performed).
141
- *
142
- * **Note:** Omitting this field performs filesystem I/O via
143
- * `detectInstalledAgents()` to probe `PATH` for installed agent CLIs.
144
- */
145
- agents?: AgentTarget[];
146
- /**
147
- * Installation strategy for agent output paths.
148
- *
149
- * - `"auto"` (default): create a symlink to the canonical `.crust/skills`
150
- * bundle, falling back to a hard copy when symlinks are unavailable.
151
- * - `"symlink"`: require symlinks; fail if a symlink cannot be created.
152
- * - `"copy"`: write full copies directly into each agent path.
153
- *
154
- * Canonical bundles are always generated once under `.crust/skills` (project)
155
- * or `~/.crust/skills` (global). When `process.cwd()` is the home directory,
156
- * project scope is normalized to the global location.
157
- * @default "auto"
158
- */
159
- installMode?: SkillInstallMode;
160
- /**
161
- * Installation scope — global (home directory) or project (cwd).
162
- * When `process.cwd()` is the home directory, `"project"` is treated as `"global"`.
163
- * @default "global"
164
- */
165
- scope?: Scope;
166
- /**
167
- * When `true`, removes the existing skill directory before writing.
168
- * Prevents stale files from previous generations.
169
- * @default true
170
- */
171
- clean?: boolean;
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}.
176
- * @default false
177
- */
178
- force?: boolean;
179
- }
180
- /**
181
- * Top-level options for installing a hand-authored skill bundle.
182
- *
183
- * Unlike {@link GenerateOptions}, the bundle entrypoint does not render
184
- * `SKILL.md` from a command tree — it copies a directory the caller has
185
- * already authored. The bundle's `SKILL.md` frontmatter is the source of
186
- * truth for `name` and `description`; Crust reads them but does not rewrite
187
- * the file. A fresh `crust.json` is written alongside the bundle for
188
- * ownership and version tracking.
189
- *
190
- * Bundle files are copied as raw bytes. `SKILL.md` is also parsed as UTF-8
191
- * to read its required frontmatter.
192
- *
193
- * Bundle content changes do not propagate without a `version` bump:
194
- * identical-version reinstalls report `up-to-date` and leave the canonical
195
- * store untouched. Pass a fresh `version` whenever the bundle contents
196
- * change (e.g. wire it to the consuming package's `package.json` `version`).
197
- *
198
- * @example
199
- * ```ts
200
- * import { installSkillBundle } from "@crustjs/skills";
201
- * import pkg from "./package.json" with { type: "json" };
202
- *
203
- * // SKILL.md frontmatter supplies name + description; the caller passes
204
- * // the version explicitly (typically wired to package.json).
205
- * await installSkillBundle({
206
- * sourceDir: "skills/funnel-builder",
207
- * agents: ["claude-code"],
208
- * version: pkg.version,
209
- * });
210
- * ```
211
- */
212
- interface InstallSkillBundleOptions {
213
- /**
214
- * Source directory containing the bundle to install.
215
- *
216
- * Resolution rules (mirror `@crustjs/create`'s `scaffold({ template })`):
217
- * - `URL` — must use `file:` protocol; resolved via `fileURLToPath()`.
218
- * - Absolute string path — used as-is via `path.resolve()`.
219
- * - Relative string path — resolved from the nearest `package.json`
220
- * directory walking up from `process.argv[1]`. Throws if `process.argv[1]`
221
- * is unset or no `package.json` is found.
222
- *
223
- * The directory must contain a `SKILL.md` whose YAML frontmatter declares
224
- * top-level `name:` and `description:` fields.
225
- */
226
- sourceDir: string | URL;
227
- /**
228
- * Agent targets to install the bundle for.
229
- *
230
- * Required — unlike {@link GenerateOptions.agents}, the bundle entrypoint
231
- * does not auto-detect agents. Pass `[]` for a validated no-op: no install
232
- * is performed, but `sourceDir`, `SKILL.md`, bundle paths, frontmatter, and
233
- * skill name are still validated.
234
- */
235
- agents: AgentTarget[];
236
- /**
237
- * Version string recorded for this install and compared on subsequent
238
- * installs to decide between `installed` / `updated` / `up-to-date`.
239
- *
240
- * Required. Typically wired to the consuming package's `package.json`
241
- * `version` (e.g. via `import pkg from "./package.json" with { type:
242
- * "json" }`). Identical-version reinstalls report `up-to-date` and skip
243
- * the canonical-store rewrite, so bump this whenever bundle contents
244
- * change.
245
- */
246
- version: string;
247
- /**
248
- * Installation strategy for agent output paths.
249
- * @default "auto"
250
- */
251
- installMode?: SkillInstallMode;
252
- /**
253
- * Installation scope — global (home directory) or project (cwd).
254
- * @default "global"
255
- */
256
- scope?: Scope;
257
- /**
258
- * When `true`, removes the existing skill directory before writing.
259
- * @default true
260
- */
261
- clean?: boolean;
262
- /**
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"`).
265
- * @default false
266
- */
267
- force?: boolean;
268
- /**
269
- * When set, the bundle's `SKILL.md` frontmatter `name:` must equal this
270
- * string. A mismatch throws before any filesystem write.
271
- *
272
- * Used by `skillPlugin`'s `customSkills` reconciliation to keep the
273
- * config-level `name` (used for status / uninstall lookups) in lockstep
274
- * with the frontmatter `name` (the canonical install path), preventing
275
- * orphan installs.
276
- */
277
- expectedName?: string;
278
- }
279
- /**
280
- * Result returned by `installSkillBundle` after writing files to disk.
281
- *
282
- * Type alias of {@link GenerateResult} — the per-agent shape is identical.
283
- */
284
- type InstallSkillBundleResult = GenerateResult;
285
- /** Status of an individual agent installation. */
286
- type InstallStatus = "installed" | "updated" | "up-to-date";
287
- /** Status of an individual agent uninstallation. */
30
+ type AgentTarget = "amp" | "adal" | "antigravity" | "augment" | "claude-code" | "cline" | "codebuddy" | "codex" | "command-code" | "continue" | "cortex" | "crush" | "cursor" | "droid" | "gemini-cli" | "github-copilot" | "goose" | "iflow-cli" | "junie" | "kilo" | "kimi-cli" | "kiro-cli" | "kode" | "mcpjam" | "mistral-vibe" | "mux" | "neovate" | "opencode" | "openclaw" | "openhands" | "pi" | "pochi" | "qoder" | "qwen-code" | "replit" | "roo" | "trae" | "trae-cn" | "warp" | "windsurf" | "zed" | "zencoder";
31
+ /** Returns agents that use the canonical `.agents/skills` layout. */
32
+ export declare function getUniversalAgents(): AgentTarget[];
33
+ /** Returns agents that do not use the canonical layout at both scopes. */
34
+ export declare function getAdditionalAgents(): AgentTarget[];
35
+ /** Returns true if the agent uses the canonical layout at both scopes. */
36
+ export declare function isUniversalAgent(agent: AgentTarget): boolean;
37
+ /**
38
+ * Detects installed non-universal agents by checking PATH for their CLI binaries.
39
+ *
40
+ * Universal agents are intentionally not detected here so callers can always
41
+ * present them as a single optional "Universal" install target.
42
+ */
43
+ export declare function detectInstalledAgents(): Promise<AgentTarget[]>;
44
+ //#endregion
45
+ //#region src/types.d.ts
46
+ /** Options for linking one packaged skill source into agent directories. */
47
+ interface InstallSkillOptions {
48
+ /** Package directory `skills/<name>` containing the skill's SKILL.md. */
49
+ sourceDir: string | URL;
50
+ /** Agent targets. Omit to use universal plus PATH-detected agents. */
51
+ agents?: AgentTarget[];
52
+ /** Agent-directory scope. @default "global" */
53
+ scope?: Scope;
54
+ /** Allow replacing a directory that is not owned by this skill. @default false */
55
+ force?: boolean;
56
+ }
57
+ type InstallStatus = "installed" | "repaired" | "up-to-date";
288
58
  type UninstallStatus = "removed" | "not-found";
289
- /** Per-agent result from a generateSkill call. */
290
59
  interface AgentResult {
291
- /** Which agent this result is for */
292
- agent: AgentTarget;
293
- /** Absolute path to the skill output directory for this agent */
294
- outputDir: string;
295
- /** List of files that were written (relative paths) */
296
- files: string[];
297
- /** What happened during this installation */
298
- status: InstallStatus;
299
- /** Previous version string when status is "updated" */
300
- previousVersion?: string;
301
- }
302
- /**
303
- * Result returned by `generateSkill` after writing files to disk.
304
- */
305
- interface GenerateResult {
306
- /** Per-agent installation results */
307
- agents: AgentResult[];
308
- }
309
- /** Options for removing installed skills. */
310
- interface UninstallOptions {
311
- /** Skill name to uninstall */
312
- name: string;
313
- /**
314
- * Agent targets to uninstall from.
315
- *
316
- * When omitted (or explicitly `undefined`), defaults to every supported
317
- * agent so the uninstall sweep covers any path that may hold an install,
318
- * regardless of what is on the current machine's `PATH`. Pass an explicit
319
- * array to scope the uninstall; `agents: []` is treated as a no-op (no
320
- * paths are touched).
321
- *
322
- * Default resolution does not perform `PATH` I/O — the entrypoint already
323
- * stats each per-agent path during the sweep.
324
- */
325
- agents?: AgentTarget[];
326
- /**
327
- * Installation scope to uninstall from.
328
- * When `process.cwd()` is the home directory, `"project"` is treated as `"global"`.
329
- * @default "global"
330
- */
331
- scope?: Scope;
332
- }
333
- /** Result returned by `uninstallSkill`. */
334
- interface UninstallResult {
335
- /** Per-agent uninstall results */
336
- agents: Array<{
337
- agent: AgentTarget;
338
- outputDir: string;
339
- status: UninstallStatus;
340
- }>;
341
- }
342
- /** Options for checking installed skill status. */
343
- interface StatusOptions {
344
- /** Skill name to check */
345
- name: string;
346
- /**
347
- * Agent targets to check.
348
- *
349
- * When omitted (or explicitly `undefined`), defaults to every supported
350
- * agent so the status sweep reports an entry for any path that may hold
351
- * an install, regardless of what is on the current machine's `PATH`. Pass
352
- * an explicit array to scope the check; `agents: []` is treated as a no-op
353
- * (returns an empty result).
354
- *
355
- * Default resolution does not perform `PATH` I/O — the entrypoint already
356
- * stats each per-agent path during the sweep.
357
- */
358
- agents?: AgentTarget[];
359
- /**
360
- * Installation scope to check.
361
- * When `process.cwd()` is the home directory, `"project"` is treated as `"global"`.
362
- * @default "global"
363
- */
364
- scope?: Scope;
365
- }
366
- /** Result returned by `skillStatus`. */
367
- interface StatusResult {
368
- /** Per-agent status results */
369
- agents: Array<{
370
- agent: AgentTarget;
371
- outputDir: string;
372
- installed: boolean;
373
- version?: string;
374
- }>;
375
- }
376
- /**
377
- * Configuration for a single hand-authored skill bundle managed by
378
- * {@link skillPlugin} alongside the auto-generated command-reference skill.
379
- *
380
- * Each entry is reconciled through the same plugin lifecycle as the main
381
- * skill — auto-update on version change, surfaced in the interactive `skill`
382
- * subcommand multiselect, supports uninstall via the same toggle UX, and
383
- * respects `autoUpdate: false` and `--all` non-interactive mode. Bundles
384
- * inherit `version`, `defaultScope`, and `installMode` from the plugin
385
- * unless overridden per-entry.
386
- *
387
- * The bundle's `SKILL.md` frontmatter remains the source of truth for the
388
- * display `name` and `description` (validated by {@link installSkillBundle}
389
- * at install time). The duplicated `name` field on this config is what the
390
- * plugin uses for cheap collision-detection, status lookups, and uninstall
391
- * paths without having to read the bundle's frontmatter at plugin setup.
392
- *
393
- * @example
394
- * ```ts
395
- * import { skillPlugin } from "@crustjs/skills";
396
- * import pkg from "./package.json" with { type: "json" };
397
- *
398
- * skillPlugin({
399
- * version: pkg.version,
400
- * customSkills: [
401
- * // Inherits `version: pkg.version` from the plugin.
402
- * { name: "funnel-builder", sourceDir: "skills/funnel-builder" },
403
- * // Explicit override for an independently-versioned bundle.
404
- * {
405
- * name: "vendored-toolkit",
406
- * sourceDir: "skills/vendored-toolkit",
407
- * version: "0.3.0",
408
- * },
409
- * ],
410
- * });
411
- * ```
412
- */
413
- interface CustomSkillConfig extends Pick<InstallSkillBundleOptions, "sourceDir" | "scope" | "installMode"> {
414
- /**
415
- * Skill name used by the plugin for collision detection, status lookups,
416
- * and uninstall paths.
417
- *
418
- * Must satisfy `isValidSkillName` (1–64 lowercase alphanumeric characters
419
- * and hyphens, no leading/trailing/consecutive hyphens), must be unique
420
- * within the `customSkills` array, and must not collide with the main
421
- * skill's name (derived from the root command's `meta`).
422
- *
423
- * The bundle's `SKILL.md` frontmatter `name:` must match this value —
424
- * mismatches are rejected at install time so plugin status / uninstall
425
- * paths can never drift from the canonical install location.
426
- */
427
- name: string;
428
- /**
429
- * Version override. When omitted, the bundle inherits the plugin's
430
- * top-level {@link SkillPluginOptions.version}. Drives auto-update
431
- * detection: a bundle is reinstalled when its recorded `crust.json`
432
- * version differs from the effective (entry-or-plugin) version.
433
- *
434
- * Inheriting from the plugin matches the typical case where the bundle
435
- * ships in the same package as the consuming CLI — one `pkg.version`
436
- * drives the main skill and every bundle. Pass an explicit value when a
437
- * bundle's release cadence is independent of the consuming CLI (for
438
- * example, vendored from another package).
439
- *
440
- * The bundle's `SKILL.md` frontmatter `version:` / `metadata.version`,
441
- * if any, is intentionally not read — this option (or its plugin-level
442
- * fallback) is the sole source of truth.
443
- */
444
- version?: InstallSkillBundleOptions["version"];
445
- /**
446
- * Installation scope override. When omitted, the bundle inherits
447
- * {@link SkillPluginOptions.defaultScope} resolution: explicit `--scope`
448
- * flag wins, else `defaultScope`, else the interactive scope prompt
449
- * (or `"global"` in non-interactive mode).
450
- */
451
- scope?: InstallSkillBundleOptions["scope"];
452
- /**
453
- * Installation strategy override. When omitted, inherits
454
- * {@link SkillPluginOptions.installMode} (default `"auto"`).
455
- */
456
- installMode?: InstallSkillBundleOptions["installMode"];
457
- }
458
- /**
459
- * Options for the skill plugin.
460
- *
461
- * The plugin reads `name` and `description` from the root command's `meta`
462
- * at setup time, so only `version` is required here.
463
- *
464
- * Installed agents are detected automatically.
465
- *
466
- * Only detected agents are managed.
467
- *
468
- * **Auto-update** (default): silently updates already-installed skills when a
469
- * new version is detected. Disable with `autoUpdate: false`.
470
- *
471
- * For first-time installation, use the interactive `skill` subcommand or
472
- * build custom auto-install logic with the exported primitives
473
- * (`detectInstalledAgents`, `skillStatus`, `generateSkill`).
474
- *
475
- * **Interactive command** (default): registers a `skill` subcommand (or the
476
- * custom `command` name) that
477
- * presents a single multiselect prompt for toggling agent installations.
478
- *
479
- * Scope resolution for interactive commands:
480
- * - If `defaultScope` is set, that scope is used and no scope prompt is shown.
481
- * - If `defaultScope` is not set and the terminal is interactive, users are
482
- * prompted to choose `project` or `global`.
483
- * - If `defaultScope` is not set and the terminal is non-interactive, scope
484
- * falls back to `"global"`.
485
- * - When `process.cwd()` is the home directory, `"project"` is normalized to
486
- * `"global"` for path resolution and update/status messaging.
487
- */
488
- interface SkillPluginOptions {
489
- /** Skill version string — compared against the installed crust.json */
490
- version: string;
491
- /**
492
- * Default installation scope for interactive commands.
493
- *
494
- * When omitted, interactive commands prompt for scope in TTY mode.
495
- * Non-interactive mode falls back to "global".
496
- * When `process.cwd()` is the home directory, `"project"` behaves as `"global"`.
497
- */
498
- defaultScope?: Scope;
499
- /**
500
- * Installation strategy used when the plugin calls `generateSkill()`.
501
- * @default "auto"
502
- */
503
- installMode?: SkillInstallMode;
504
- /**
505
- * Automatically update skills when the installed version is outdated.
506
- * @default true
507
- */
508
- autoUpdate?: boolean;
509
- /**
510
- * Additional top-level instructions rendered into the generated `SKILL.md`.
511
- *
512
- * **Note:** When a `string` value contains markdown headings (e.g. `## Foo`),
513
- * they are rendered at the same level as `## General Guidance`, not nested
514
- * under it. Use a `string[]` of plain instructions to avoid unintended
515
- * heading hierarchy.
516
- */
517
- instructions?: string | string[];
518
- /** License name or reference emitted in SKILL.md frontmatter. */
519
- license?: string;
520
- /**
521
- * Space-delimited list of pre-approved tools the skill may use.
522
- *
523
- * @example "Bash(my-cli *) Read Grep"
524
- */
525
- allowedTools?: string;
526
- /** Environment requirements or compatibility notes (max 500 chars). */
527
- compatibility?: string;
528
- /**
529
- * When `true`, prevents agents from automatically loading this skill.
530
- * @default false
531
- */
532
- disableModelInvocation?: boolean;
533
- /**
534
- * Hand-authored skill bundles to manage alongside the auto-generated
535
- * command-reference skill.
536
- *
537
- * Each entry is reconciled through the same plugin lifecycle as the main
538
- * skill — auto-update on version change, surfaced in the interactive
539
- * `skill` subcommand multiselect (one prompt per bundle, in array order,
540
- * after the main-skill prompt), supports uninstall via the same toggle
541
- * UX, and respects `autoUpdate: false` and `--all` non-interactive mode.
542
- *
543
- * Bundles share the canonical `.crust/skills` store with the main skill
544
- * via {@link installSkillBundle} and inherit `defaultScope` /
545
- * `installMode` resolution unless overridden per-entry.
546
- *
547
- * Each entry's effective `version` drives auto-update detection (compared
548
- * against the recorded `crust.json` version). When the entry omits
549
- * `version`, the plugin's top-level {@link SkillPluginOptions.version} is
550
- * used — the typical case when the bundle ships in the same package as
551
- * the consuming CLI.
552
- *
553
- * Setup-time validation enforces:
554
- * - Each `name` satisfies `isValidSkillName`.
555
- * - No `name` collides with the main skill's name.
556
- * - All `name` values are unique within the array.
557
- * - When set, `version` is a non-empty string.
558
- * - Each `sourceDir` is a `string` or `URL`.
559
- *
560
- * `sourceDir` resolution-time errors (non-`file:` URL, missing source
561
- * directory, missing `SKILL.md`, etc.) defer to the underlying
562
- * `installSkillBundle` invocation and surface there with descriptive
563
- * messages.
564
- *
565
- * When omitted or empty, plugin behavior is byte-identical to running
566
- * without the option — only the auto-generated main skill is managed.
567
- *
568
- * @default []
569
- */
570
- customSkills?: CustomSkillConfig[];
571
- /**
572
- * Register an interactive skill management subcommand on the root command.
573
- *
574
- * The command presents a single multiselect prompt listing all detected
575
- * agents with their current installation status pre-filled. The user
576
- * toggles agents on/off and the system reconciles the desired state:
577
- * newly selected agents are installed, deselected agents are uninstalled,
578
- * and already-correct agents are skipped.
579
- *
580
- * @default "skill"
581
- */
582
- command?: string;
583
- }
584
- /** Returns agents that use the canonical `.agents/skills` layout. */
585
- declare function getUniversalAgents(): AgentTarget[];
586
- /** Returns agents that use agent-specific skill roots. */
587
- declare function getAdditionalAgents(): AgentTarget[];
588
- /** Returns true if the agent uses the canonical `.agents/skills` layout. */
589
- declare function isUniversalAgent(agent: AgentTarget): boolean;
590
- interface DetectInstalledAgentsOptions {
591
- /** Kept for backwards compatibility with previous API. */
592
- scope?: Scope;
593
- /** Kept for backwards compatibility with previous API. */
594
- home?: string;
595
- /** Working directory for PATH lookups. */
596
- cwd?: string;
597
- /** Test-only hook to override command detection. */
598
- commandChecker?: (command: string, cwd: string) => Promise<boolean>;
60
+ agent: AgentTarget;
61
+ outputDir: string;
62
+ /** Effective scope after remapping project scope at the home directory. */
63
+ scope: Scope;
64
+ status: InstallStatus;
65
+ }
66
+ interface InstallSkillResult {
67
+ agents: AgentResult[];
68
+ }
69
+ interface UninstallSkillOptions {
70
+ name: string;
71
+ agents?: AgentTarget[];
72
+ scope?: Scope;
73
+ }
74
+ interface UninstallSkillResult {
75
+ agents: Array<{
76
+ agent: AgentTarget;
77
+ outputDir: string;
78
+ scope: Scope;
79
+ status: UninstallStatus;
80
+ }>;
81
+ }
82
+ interface SkillStatusOptions {
83
+ name: string;
84
+ /** Expected source used to identify stale-target links. */
85
+ sourceDir: string | URL;
86
+ agents?: AgentTarget[];
87
+ scope?: Scope;
88
+ }
89
+ type SkillLinkStatus = "linked" | "dangling" | "conflict" | "absent";
90
+ interface SkillStatusResult {
91
+ agents: Array<{
92
+ agent: AgentTarget;
93
+ outputDir: string;
94
+ scope: Scope;
95
+ status: SkillLinkStatus;
96
+ }>;
97
+ }
98
+ /** Options for the skills extension. */
99
+ interface SkillOptions {
100
+ /** Packaged skills directory read at runtime for discovery and installation. */
101
+ distDir: string | URL;
102
+ /** Hand-authored skill directories (URL, absolute, or package-root-relative path) built alongside the generated skill. */
103
+ extras?: readonly (string | URL)[];
104
+ /** Generated command skill name. Defaults to the root command name. */
105
+ name?: string;
106
+ /** Generated command skill description. Defaults to the root command description. */
107
+ description?: string;
108
+ /** Whether to build the generated command skill. Set `false` to ship only `extras`. @default true */
109
+ generated?: boolean;
110
+ /**
111
+ * Agent-directory scope used when no `--scope` flag is passed.
112
+ * When set, skips the scope prompt and limits automatic link repairs to this scope.
113
+ * When omitted, interactive management prompts for scope.
114
+ * @default "global" for `--all` and the scope prompt.
115
+ */
116
+ defaultScope?: Scope;
117
+ /** Repair stale or dangling owned links before commands run. @default true */
118
+ autoUpdate?: boolean;
119
+ /** Name of the interactive management command. @default "skill" */
120
+ command?: string;
121
+ }
122
+ //#endregion
123
+ //#region src/errors.d.ts
124
+ export declare class SkillSourceConflictError extends Error {
125
+ override readonly name = "SkillSourceConflictError";
126
+ readonly skillName: string;
127
+ constructor(skillName: string);
599
128
  }
600
- /**
601
- * Detects installed additional agents by checking PATH for their CLI binaries.
602
- *
603
- * Universal agents are intentionally not detected here so callers can always
604
- * present them as a single optional "Universal" install target.
605
- */
606
- declare function detectInstalledAgents(options?: string | DetectInstalledAgentsOptions): Promise<AgentTarget[]>;
607
- /**
608
- * Resolves the canonical skill bundle path used by Crust.
609
- */
610
- declare function resolveCanonicalSkillPath(scope: Scope, name: string): string;
611
- import { CommandNode as CommandNode2 } from "@crustjs/core";
612
- import { Crust } from "@crustjs/core";
613
- /**
614
- * Agent-oriented instructions attached to a command for skills rendering.
615
- */
616
- interface SkillCommandAnnotations {
617
- /** Additional prompt guidance rendered into the command's markdown file */
618
- instructions?: string[];
619
- }
620
- type SkillCommandTarget = CommandNode2 | Crust<any, any, any>;
621
- /**
622
- * Attaches agent-facing instructions to a command definition without changing
623
- * the public `@crustjs/core` API surface.
624
- *
625
- * The instructions are stored on the internal command node using an enumerable
626
- * symbol so they survive Crust's immutable clone/spread builder operations.
627
- *
628
- * Duplicate instructions are silently deduplicated — calling `annotate()` again
629
- * with the same text is a safe no-op.
630
- */
631
- declare function annotate<T extends SkillCommandTarget>(target: T, annotations: string | string[] | SkillCommandAnnotations): T;
632
- /**
633
- * Installs a hand-authored skill bundle through the same canonical-store and
634
- * agent-fan-out pipeline used by {@link generateSkill}.
635
- *
636
- * Unlike `generateSkill`, this entrypoint does not render any markdown — it
637
- * copies the directory at `sourceDir` as authored (subject to a
638
- * path-traversal guard against symlink escapes and a cycle guard) and
639
- * writes a fresh `crust.json` recording `kind: "bundle"`. Bundle authors
640
- * are responsible for keeping `sourceDir` clean — `crust.json` at the
641
- * bundle root is reserved and will throw if present in the source.
642
- *
643
- * The bundle's `SKILL.md` frontmatter is the source of truth for `name` and
644
- * `description`; both are required and read by Crust without rewriting the
645
- * file. The caller supplies `version` explicitly — typically wired to the
646
- * consuming package's `package.json` `version`.
647
- *
648
- * 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`.
651
- *
652
- * @param options - Bundle install options (see {@link InstallSkillBundleOptions})
653
- * @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).
656
- * @throws {Error} If `SKILL.md` is missing, its frontmatter lacks `name:` or
657
- * `description:`, the declared `name` is not a valid skill name, the
658
- * declared `name` does not match `expectedName` when set, the source
659
- * directory escapes itself via symlink, or `sourceDir` cannot be resolved.
660
- *
661
- * @example
662
- * ```ts
663
- * import { installSkillBundle } from "@crustjs/skills";
664
- * import pkg from "./package.json" with { type: "json" };
665
- *
666
- * await installSkillBundle({
667
- * sourceDir: "skills/funnel-builder",
668
- * agents: ["claude-code"],
669
- * version: pkg.version,
670
- * });
671
- * ```
672
- */
673
- declare function installSkillBundle(options: InstallSkillBundleOptions): Promise<InstallSkillBundleResult>;
674
- /**
675
- * Why an installed manifest could not be interpreted.
676
- *
677
- * - `parse-error`: `crust.json` is present but is not valid JSON.
678
- * - `not-an-object`: top-level JSON value is not an object.
679
- * - `missing-version`: `version` field is absent or not a string.
680
- * - `unknown-kind`: `kind` is present but is neither `"bundle"` nor `"generated"` —
681
- * typically a hand-edit typo or a forward-compatible value emitted by a
682
- * newer Crust release.
683
- */
684
- type InstalledManifestMalformedReason = "parse-error" | "not-an-object" | "missing-version" | "unknown-kind";
685
- /**
686
- * Describes a kind mismatch between an existing installed bundle and an
687
- * incoming install attempt.
688
- *
689
- * Set on {@link SkillConflictDetails.kindMismatch} when {@link generateSkill}
690
- * or {@link installSkillBundle} discovers an existing `crust.json` whose
691
- * `kind` differs from the kind being installed (e.g. a generated skill
692
- * already lives at the target path and a bundle install was attempted).
693
- */
694
- interface SkillKindMismatch {
695
- /** Kind recorded in the existing `crust.json` */
696
- existing: SkillKind;
697
- /** Kind requested by the current install attempt */
698
- attempted: SkillKind;
699
- }
700
- /**
701
- * Describes a malformed `crust.json` discovered at the conflicting skill
702
- * directory.
703
- *
704
- * Set on {@link SkillConflictDetails.manifestMalformed} when the directory
705
- * contains a `crust.json` that exists but cannot be interpreted — e.g. it is
706
- * not valid JSON, lacks a `version`, or has an unrecognized `kind` value
707
- * (a hand-edit typo like `"bundel"`, or a forward-compatible value emitted by
708
- * a newer Crust release). Distinct from a missing `crust.json`, which keeps
709
- * the original "not created by Crust" semantics.
710
- */
711
- interface SkillManifestMalformed {
712
- /** Why the manifest could not be interpreted. */
713
- reason: InstalledManifestMalformedReason;
714
- /** Raw `kind` value when `reason === "unknown-kind"`. */
715
- rawKind?: string;
716
- }
717
- /** Details about the conflict between an existing skill and an incoming one. */
718
129
  interface SkillConflictDetails {
719
- /** The agent where the conflict was detected */
720
- agent: AgentTarget;
721
- /** Absolute path to the conflicting skill directory */
722
- outputDir: string;
723
- /**
724
- * Set when the conflict is a `kind` mismatch (existing `crust.json`
725
- * reports a different `kind` than the one being installed).
726
- *
727
- * Absent for "no-crust.json" conflicts (the original case).
728
- */
729
- kindMismatch?: SkillKindMismatch;
730
- /**
731
- * Set when `crust.json` is present at the conflicting directory but cannot
732
- * be interpreted (invalid JSON, missing version, unrecognized `kind`,
733
- * etc.). Lets the error message distinguish a Crust-owned-but-broken
734
- * manifest from a directory that simply was never managed by Crust.
735
- */
736
- manifestMalformed?: SkillManifestMalformed;
737
- }
738
- /**
739
- * Thrown when an install entrypoint detects that the target skill directory
740
- * already exists but cannot be overwritten safely.
741
- *
742
- * Two flavours:
743
- * - **No `crust.json`** — directory exists but was not created by Crust.
744
- * This prevents Crust from silently overwriting a skill that was manually
745
- * created or installed by another tool.
746
- * - **Kind mismatch** — directory was created by Crust but with a different
747
- * {@link SkillKind} (e.g. an existing `generated` skill collides with an
748
- * incoming `bundle` install). `force: true` bypasses both cases.
749
- *
750
- * @example
751
- * ```ts
752
- * import { generateSkill, SkillConflictError } from "@crustjs/skills";
753
- *
754
- * try {
755
- * await generateSkill({ command, meta, agents });
756
- * } catch (err) {
757
- * if (err instanceof SkillConflictError) {
758
- * if (err.details.kindMismatch) {
759
- * console.error(
760
- * `Cannot install ${err.details.kindMismatch.attempted} skill — ` +
761
- * `${err.details.kindMismatch.existing} skill already at "${err.details.outputDir}".`,
762
- * );
763
- * } else {
764
- * console.error(
765
- * `Conflict: "${err.details.outputDir}" already exists and was not created by Crust.`,
766
- * );
767
- * }
768
- * }
769
- * }
770
- * ```
771
- */
772
- declare class SkillConflictError extends Error {
773
- readonly name = "SkillConflictError";
774
- readonly details: SkillConflictDetails;
775
- constructor(details: SkillConflictDetails);
776
- }
777
- /**
778
- * Validates a resolved skill name against the Agent Skills specification.
779
- *
780
- * @param name - The resolved skill name to validate
781
- * @returns `true` if valid, `false` otherwise
782
- */
783
- declare function isValidSkillName(name: string): boolean;
784
- /**
785
- * Resolves the canonical current skill name.
786
- *
787
- * All generated output (directory names, crust.json metadata, SKILL.md content)
788
- * uses the resolved name directly. Consumers pass the raw CLI name
789
- * (e.g. `"my-cli"`), and this function returns that same canonical name.
790
- *
791
- * @param name - The raw CLI tool name
792
- * @returns The canonical skill name
793
- *
794
- * @example
795
- * ```ts
796
- * resolveSkillName("my-cli"); // "my-cli"
797
- * ```
798
- */
799
- declare function resolveSkillName(name: string): string;
800
- /**
801
- * Generates and installs agent skill bundles from a Crust command tree.
802
- *
803
- * The generator renders the bundle once into a canonical Crust store
804
- * (`.crust/skills` project scope, `~/.crust/skills` global scope), then
805
- * installs into agent-specific output paths using the configured install mode
806
- * (`auto`, `symlink`, `copy`).
807
- *
808
- * @param options - Generation options including command, metadata, agents, and scope
809
- * @returns Per-agent installation results
810
- * @throws {SkillConflictError} If the output directory exists but was not created by Crust
811
- *
812
- * @example
813
- * ```ts
814
- * import { generateSkill } from "@crustjs/skills";
815
- * import { rootCommand } from "./commands.ts";
816
- *
817
- * const result = await generateSkill({
818
- * command: rootCommand,
819
- * meta: {
820
- * name: "my-cli",
821
- * description: "CLI tool for managing widgets",
822
- * version: "1.0.0",
823
- * },
824
- * agents: ["claude-code", "opencode"],
825
- * });
826
- *
827
- * for (const r of result.agents) {
828
- * console.log(`${r.agent}: ${r.status} → ${r.outputDir}`);
829
- * }
830
- * ```
831
- */
832
- declare function generateSkill(options: GenerateOptions): Promise<GenerateResult>;
833
- /**
834
- * Removes installed skills from agent directories.
835
- *
836
- * @param options - Uninstall options specifying name, agents, and scope
837
- * @returns Per-agent uninstall results
838
- */
839
- declare function uninstallSkill(options: UninstallOptions): Promise<UninstallResult>;
840
- /**
841
- * Checks the installation status of skills across agent directories.
842
- *
843
- * @param options - Status options specifying name, agents, and scope
844
- * @returns Per-agent status results
845
- */
846
- declare function skillStatus(options: StatusOptions): Promise<StatusResult>;
847
- import { CrustPlugin } from "@crustjs/core";
848
- /**
849
- * Plugin that manages agent skills for a Crust CLI application.
850
- *
851
- * `name` and `description` are read from the root command's `meta` at setup
852
- * time — only `version` needs to be supplied in the options.
853
- *
854
- * Installed agents are detected automatically.
855
- *
856
- * Only detected agents are managed by automatic update and the interactive
857
- * command.
858
- *
859
- * **Auto-update** (default): silently updates already-installed skills when a
860
- * new version is detected. Disable with `autoUpdate: false`.
861
- *
862
- * **Interactive command** (default): registers a `skill` subcommand that
863
- * presents a single multiselect prompt for toggling agent installations.
864
- * Detected agents are shown with their current installation status pre-filled.
865
- * The system reconciles the desired state: newly selected agents are installed,
866
- * deselected agents are uninstalled, and already-correct agents are skipped.
867
- * `command` configures the injected command name.
868
- *
869
- * For first-time installation, use the interactive command or build custom
870
- * auto-install logic with the exported primitives (`detectInstalledAgents`,
871
- * `skillStatus`, `generateSkill`).
872
- *
873
- * @param options - Plugin configuration with version and defaults
874
- * @returns A `CrustPlugin` to register in a command's `plugins` array
875
- *
876
- * @example
877
- * ```ts
878
- * import { Crust } from "@crustjs/core";
879
- * import { skillPlugin } from "@crustjs/skills";
880
- *
881
- * const app = new Crust("my-cli").meta({ description: "My CLI" })
882
- * .use(skillPlugin({
883
- * version: "1.0.0",
884
- * command: "skill", // registers "my-cli skill" subcommand
885
- * }))
886
- * .run(() => { /* ... *�/ });
887
- *
888
- * await app.execute();
889
- * ```
890
- */
891
- declare function skillPlugin(options: SkillPluginOptions): CrustPlugin;
892
- export { uninstallSkill, skillStatus, skillPlugin, resolveSkillName, resolveCanonicalSkillPath, isValidSkillName, isUniversalAgent, installSkillBundle, getUniversalAgents, getAdditionalAgents, generateSkill, detectInstalledAgents, annotate, UninstallStatus, UninstallResult, UninstallOptions, StatusResult, StatusOptions, SkillPluginOptions, SkillMeta, SkillManifestMalformed, SkillKindMismatch, SkillKind, SkillInstallMode, SkillConflictError, SkillConflictDetails, SkillCommandAnnotations, Scope, InstallStatus, InstallSkillBundleResult, InstallSkillBundleOptions, GenerateResult, GenerateOptions, CustomSkillConfig, AgentTarget, AgentResult, AgentClass };
130
+ agent: AgentTarget;
131
+ outputDir: string;
132
+ }
133
+ /** Refuses to overwrite an agent entry that is not owned by the requested skill. */
134
+ export declare class SkillConflictError extends Error {
135
+ override readonly name = "SkillConflictError";
136
+ readonly details: SkillConflictDetails;
137
+ constructor(details: SkillConflictDetails);
138
+ }
139
+ //#endregion
140
+ //#region src/extension.d.ts
141
+ export declare const skill: ExtensionFactory<[options: SkillOptions], {}, [], [], readonly CommandDefinition<any, any, any, any>[]>;
142
+ //#endregion
143
+ //#region src/generate.d.ts
144
+ /** Links one packaged skill source into the requested agent directories. */
145
+ export declare function installSkill(options: InstallSkillOptions): Promise<InstallSkillResult>;
146
+ /** Unlinks only agent-directory entries carrying the requested skill's ownership signature. */
147
+ export declare function uninstallSkill(options: UninstallSkillOptions): Promise<UninstallSkillResult>;
148
+ /** Reports the ownership and health of each requested agent-directory entry. */
149
+ export declare function getSkillStatus(options: SkillStatusOptions): Promise<SkillStatusResult>;
150
+ //#endregion
151
+ //#region src/skill-name.d.ts
152
+ /**
153
+ * Validates a resolved skill name against the Agent Skills specification.
154
+ *
155
+ * @param name - The resolved skill name to validate
156
+ * @returns `true` if valid, `false` otherwise
157
+ */
158
+ export declare function isValidSkillName(name: string): boolean;
159
+ //#endregion
160
+ //#region src/source.d.ts
161
+ export declare class SkillSourceUnavailableError extends Error {
162
+ override readonly name = "SkillSourceUnavailableError";
163
+ }
164
+ /**
165
+ * Resolves a logical packaged skill-source root. When the package path is
166
+ * unavailable, falls back to the executable directory: absolute and URL
167
+ * sources by their basename, relative sources by the same relative path.
168
+ */
169
+ export declare function resolveSkillSource(source: string | URL): string;
170
+ interface PackagedSkill {
171
+ readonly sourceDir: string;
172
+ readonly name: string;
173
+ readonly description: string;
174
+ }
175
+ /** Reads every self-describing skill directory in a packaged skill source. */
176
+ export declare function loadPackagedSkills(source: string | URL): readonly PackagedSkill[];
177
+ //#endregion
178
+ export type { AgentClass, AgentResult, AgentTarget, InstallSkillOptions, InstallSkillResult, InstallStatus, PackagedSkill, Scope, SkillLinkStatus, SkillOptions, SkillStatusOptions, SkillStatusResult, UninstallSkillOptions, UninstallSkillResult, UninstallStatus, WriteSkillsOptions };