@crustjs/skills 0.1.2 → 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,899 +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`, 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}.
177
- * @default false
178
- */
179
- force?: boolean;
180
- }
181
- /**
182
- * Top-level options for installing a hand-authored skill bundle.
183
- *
184
- * Unlike {@link GenerateOptions}, the bundle entrypoint does not render
185
- * `SKILL.md` from a command tree — it copies a directory the caller has
186
- * already authored. The bundle's `SKILL.md` frontmatter is the source of
187
- * truth for `name` and `description`; Crust reads them but does not rewrite
188
- * the file. A fresh `crust.json` is written alongside the bundle for
189
- * ownership and version tracking.
190
- *
191
- * Bundle files are copied as raw bytes. `SKILL.md` is also parsed as UTF-8
192
- * to read its required frontmatter.
193
- *
194
- * Bundle content changes do not propagate without a `version` bump:
195
- * identical-version reinstalls report `up-to-date` and leave the canonical
196
- * store untouched. Pass a fresh `version` whenever the bundle contents
197
- * change (e.g. wire it to the consuming package's `package.json` `version`).
198
- *
199
- * @example
200
- * ```ts
201
- * import { installSkillBundle } from "@crustjs/skills";
202
- * import pkg from "./package.json" with { type: "json" };
203
- *
204
- * // SKILL.md frontmatter supplies name + description; the caller passes
205
- * // the version explicitly (typically wired to package.json).
206
- * await installSkillBundle({
207
- * sourceDir: "skills/funnel-builder",
208
- * agents: ["claude-code"],
209
- * version: pkg.version,
210
- * });
211
- * ```
212
- */
213
- interface InstallSkillBundleOptions {
214
- /**
215
- * Source directory containing the bundle to install.
216
- *
217
- * Resolution rules (mirror `@crustjs/create`'s `scaffold({ template })`):
218
- * - `URL` — must use `file:` protocol; resolved via `fileURLToPath()`.
219
- * - Absolute string path — used as-is via `path.resolve()`.
220
- * - Relative string path — resolved from the nearest `package.json`
221
- * directory walking up from `process.argv[1]`. Throws if `process.argv[1]`
222
- * is unset or no `package.json` is found.
223
- *
224
- * The directory must contain a `SKILL.md` whose YAML frontmatter declares
225
- * top-level `name:` and `description:` fields.
226
- */
227
- sourceDir: string | URL;
228
- /**
229
- * Agent targets to install the bundle for.
230
- *
231
- * Required — unlike {@link GenerateOptions.agents}, the bundle entrypoint
232
- * does not auto-detect agents. Pass `[]` for a validated no-op: no install
233
- * is performed, but `sourceDir`, `SKILL.md`, bundle paths, frontmatter, and
234
- * skill name are still validated.
235
- */
236
- agents: AgentTarget[];
237
- /**
238
- * Version string recorded for this install and compared on subsequent
239
- * installs to decide between `installed` / `updated` / `up-to-date`.
240
- *
241
- * Required. Typically wired to the consuming package's `package.json`
242
- * `version` (e.g. via `import pkg from "./package.json" with { type:
243
- * "json" }`). Identical-version reinstalls report `up-to-date` and skip
244
- * the canonical-store rewrite (unless `force: true` is passed), so bump
245
- * this whenever bundle contents change.
246
- */
247
- version: string;
248
- /**
249
- * Installation strategy for agent output paths.
250
- * @default "auto"
251
- */
252
- installMode?: SkillInstallMode;
253
- /**
254
- * Installation scope — global (home directory) or project (cwd).
255
- * @default "global"
256
- */
257
- scope?: Scope;
258
- /**
259
- * When `true`, removes the existing skill directory before writing.
260
- * @default true
261
- */
262
- clean?: boolean;
263
- /**
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}.
268
- * @default false
269
- */
270
- force?: boolean;
271
- /**
272
- * When set, the bundle's `SKILL.md` frontmatter `name:` must equal this
273
- * string. A mismatch throws before any filesystem write.
274
- *
275
- * Used by `skillPlugin`'s `customSkills` reconciliation to keep the
276
- * config-level `name` (used for status / uninstall lookups) in lockstep
277
- * with the frontmatter `name` (the canonical install path), preventing
278
- * orphan installs.
279
- */
280
- expectedName?: string;
281
- }
282
- /**
283
- * Result returned by `installSkillBundle` after writing files to disk.
284
- *
285
- * Type alias of {@link GenerateResult} — the per-agent shape is identical.
286
- */
287
- type InstallSkillBundleResult = GenerateResult;
288
- /** Status of an individual agent installation. */
289
- type InstallStatus = "installed" | "updated" | "up-to-date";
290
- /** 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";
291
58
  type UninstallStatus = "removed" | "not-found";
292
- /** Per-agent result from a generateSkill call. */
293
59
  interface AgentResult {
294
- /** Which agent this result is for */
295
- agent: AgentTarget;
296
- /** Absolute path to the skill output directory for this agent */
297
- outputDir: string;
298
- /** List of files that were written (relative paths) */
299
- files: string[];
300
- /** What happened during this installation */
301
- status: InstallStatus;
302
- /** Previous version string when status is "updated" */
303
- previousVersion?: string;
304
- }
305
- /**
306
- * Result returned by `generateSkill` after writing files to disk.
307
- */
308
- interface GenerateResult {
309
- /** Per-agent installation results */
310
- agents: AgentResult[];
311
- }
312
- /** Options for removing installed skills. */
313
- interface UninstallOptions {
314
- /** Skill name to uninstall */
315
- name: string;
316
- /**
317
- * Agent targets to uninstall from.
318
- *
319
- * When omitted (or explicitly `undefined`), defaults to every supported
320
- * agent so the uninstall sweep covers any path that may hold an install,
321
- * regardless of what is on the current machine's `PATH`. Pass an explicit
322
- * array to scope the uninstall; `agents: []` is treated as a no-op (no
323
- * paths are touched).
324
- *
325
- * Default resolution does not perform `PATH` I/O — the entrypoint already
326
- * stats each per-agent path during the sweep.
327
- */
328
- agents?: AgentTarget[];
329
- /**
330
- * Installation scope to uninstall from.
331
- * When `process.cwd()` is the home directory, `"project"` is treated as `"global"`.
332
- * @default "global"
333
- */
334
- scope?: Scope;
335
- }
336
- /** Result returned by `uninstallSkill`. */
337
- interface UninstallResult {
338
- /** Per-agent uninstall results */
339
- agents: Array<{
340
- agent: AgentTarget;
341
- outputDir: string;
342
- status: UninstallStatus;
343
- }>;
344
- }
345
- /** Options for checking installed skill status. */
346
- interface StatusOptions {
347
- /** Skill name to check */
348
- name: string;
349
- /**
350
- * Agent targets to check.
351
- *
352
- * When omitted (or explicitly `undefined`), defaults to every supported
353
- * agent so the status sweep reports an entry for any path that may hold
354
- * an install, regardless of what is on the current machine's `PATH`. Pass
355
- * an explicit array to scope the check; `agents: []` is treated as a no-op
356
- * (returns an empty result).
357
- *
358
- * Default resolution does not perform `PATH` I/O — the entrypoint already
359
- * stats each per-agent path during the sweep.
360
- */
361
- agents?: AgentTarget[];
362
- /**
363
- * Installation scope to check.
364
- * When `process.cwd()` is the home directory, `"project"` is treated as `"global"`.
365
- * @default "global"
366
- */
367
- scope?: Scope;
368
- }
369
- /** Result returned by `skillStatus`. */
370
- interface StatusResult {
371
- /** Per-agent status results */
372
- agents: Array<{
373
- agent: AgentTarget;
374
- outputDir: string;
375
- installed: boolean;
376
- version?: string;
377
- }>;
378
- }
379
- /**
380
- * Configuration for a single hand-authored skill bundle managed by
381
- * {@link skillPlugin} alongside the auto-generated command-reference skill.
382
- *
383
- * Each entry is reconciled through the same plugin lifecycle as the main
384
- * skill — auto-update on version change, surfaced in the interactive `skill`
385
- * subcommand multiselect, supports uninstall via the same toggle UX, and
386
- * respects `autoUpdate: false` and `--all` non-interactive mode. Bundles
387
- * inherit `version`, `defaultScope`, and `installMode` from the plugin
388
- * unless overridden per-entry.
389
- *
390
- * The bundle's `SKILL.md` frontmatter remains the source of truth for the
391
- * display `name` and `description` (validated by {@link installSkillBundle}
392
- * at install time). The duplicated `name` field on this config is what the
393
- * plugin uses for cheap collision-detection, status lookups, and uninstall
394
- * paths without having to read the bundle's frontmatter at plugin setup.
395
- *
396
- * @example
397
- * ```ts
398
- * import { skillPlugin } from "@crustjs/skills";
399
- * import pkg from "./package.json" with { type: "json" };
400
- *
401
- * skillPlugin({
402
- * version: pkg.version,
403
- * customSkills: [
404
- * // Inherits `version: pkg.version` from the plugin.
405
- * { name: "funnel-builder", sourceDir: "skills/funnel-builder" },
406
- * // Explicit override for an independently-versioned bundle.
407
- * {
408
- * name: "vendored-toolkit",
409
- * sourceDir: "skills/vendored-toolkit",
410
- * version: "0.3.0",
411
- * },
412
- * ],
413
- * });
414
- * ```
415
- */
416
- interface CustomSkillConfig extends Pick<InstallSkillBundleOptions, "sourceDir" | "scope" | "installMode"> {
417
- /**
418
- * Skill name used by the plugin for collision detection, status lookups,
419
- * and uninstall paths.
420
- *
421
- * Must satisfy `isValidSkillName` (1–64 lowercase alphanumeric characters
422
- * and hyphens, no leading/trailing/consecutive hyphens), must be unique
423
- * within the `customSkills` array, and must not collide with the main
424
- * skill's name (derived from the root command's `meta`).
425
- *
426
- * The bundle's `SKILL.md` frontmatter `name:` must match this value —
427
- * mismatches are rejected at install time so plugin status / uninstall
428
- * paths can never drift from the canonical install location.
429
- */
430
- name: string;
431
- /**
432
- * Version override. When omitted, the bundle inherits the plugin's
433
- * top-level {@link SkillPluginOptions.version}. Drives auto-update
434
- * detection: a bundle is reinstalled when its recorded `crust.json`
435
- * version differs from the effective (entry-or-plugin) version.
436
- *
437
- * Inheriting from the plugin matches the typical case where the bundle
438
- * ships in the same package as the consuming CLI — one `pkg.version`
439
- * drives the main skill and every bundle. Pass an explicit value when a
440
- * bundle's release cadence is independent of the consuming CLI (for
441
- * example, vendored from another package).
442
- *
443
- * The bundle's `SKILL.md` frontmatter `version:` / `metadata.version`,
444
- * if any, is intentionally not read — this option (or its plugin-level
445
- * fallback) is the sole source of truth.
446
- */
447
- version?: InstallSkillBundleOptions["version"];
448
- /**
449
- * Installation scope override. When omitted, the bundle inherits
450
- * {@link SkillPluginOptions.defaultScope} resolution: explicit `--scope`
451
- * flag wins, else `defaultScope`, else the interactive scope prompt
452
- * (or `"global"` in non-interactive mode).
453
- */
454
- scope?: InstallSkillBundleOptions["scope"];
455
- /**
456
- * Installation strategy override. When omitted, inherits
457
- * {@link SkillPluginOptions.installMode} (default `"auto"`).
458
- */
459
- installMode?: InstallSkillBundleOptions["installMode"];
460
- }
461
- /**
462
- * Options for the skill plugin.
463
- *
464
- * The plugin reads `name` and `description` from the root command's `meta`
465
- * at setup time, so only `version` is required here.
466
- *
467
- * Installed agents are detected automatically.
468
- *
469
- * Only detected agents are managed.
470
- *
471
- * **Auto-update** (default): silently updates already-installed skills when a
472
- * new version is detected. Disable with `autoUpdate: false`.
473
- *
474
- * For first-time installation, use the interactive `skill` subcommand or
475
- * build custom auto-install logic with the exported primitives
476
- * (`detectInstalledAgents`, `skillStatus`, `generateSkill`).
477
- *
478
- * **Interactive command** (default): registers a `skill` subcommand (or the
479
- * custom `command` name) that
480
- * presents a single multiselect prompt for toggling agent installations.
481
- *
482
- * Scope resolution for interactive commands:
483
- * - If `defaultScope` is set, that scope is used and no scope prompt is shown.
484
- * - If `defaultScope` is not set and the terminal is interactive, users are
485
- * prompted to choose `project` or `global`.
486
- * - If `defaultScope` is not set and the terminal is non-interactive, scope
487
- * falls back to `"global"`.
488
- * - When `process.cwd()` is the home directory, `"project"` is normalized to
489
- * `"global"` for path resolution and update/status messaging.
490
- */
491
- interface SkillPluginOptions {
492
- /** Skill version string — compared against the installed crust.json */
493
- version: string;
494
- /**
495
- * Default installation scope for interactive commands.
496
- *
497
- * When omitted, interactive commands prompt for scope in TTY mode.
498
- * Non-interactive mode falls back to "global".
499
- * When `process.cwd()` is the home directory, `"project"` behaves as `"global"`.
500
- */
501
- defaultScope?: Scope;
502
- /**
503
- * Installation strategy used when the plugin calls `generateSkill()`.
504
- * @default "auto"
505
- */
506
- installMode?: SkillInstallMode;
507
- /**
508
- * Automatically update skills when the installed version is outdated.
509
- * @default true
510
- */
511
- autoUpdate?: boolean;
512
- /**
513
- * Additional top-level instructions rendered into the generated `SKILL.md`.
514
- *
515
- * **Note:** When a `string` value contains markdown headings (e.g. `## Foo`),
516
- * they are rendered at the same level as `## General Guidance`, not nested
517
- * under it. Use a `string[]` of plain instructions to avoid unintended
518
- * heading hierarchy.
519
- */
520
- instructions?: string | string[];
521
- /** License name or reference emitted in SKILL.md frontmatter. */
522
- license?: string;
523
- /**
524
- * Space-delimited list of pre-approved tools the skill may use.
525
- *
526
- * @example "Bash(my-cli *) Read Grep"
527
- */
528
- allowedTools?: string;
529
- /** Environment requirements or compatibility notes (max 500 chars). */
530
- compatibility?: string;
531
- /**
532
- * When `true`, prevents agents from automatically loading this skill.
533
- * @default false
534
- */
535
- disableModelInvocation?: boolean;
536
- /**
537
- * Hand-authored skill bundles to manage alongside the auto-generated
538
- * command-reference skill.
539
- *
540
- * Each entry is reconciled through the same plugin lifecycle as the main
541
- * skill — auto-update on version change, surfaced in the interactive
542
- * `skill` subcommand multiselect (one prompt per bundle, in array order,
543
- * after the main-skill prompt), supports uninstall via the same toggle
544
- * UX, and respects `autoUpdate: false` and `--all` non-interactive mode.
545
- *
546
- * Bundles share the canonical `.crust/skills` store with the main skill
547
- * via {@link installSkillBundle} and inherit `defaultScope` /
548
- * `installMode` resolution unless overridden per-entry.
549
- *
550
- * Each entry's effective `version` drives auto-update detection (compared
551
- * against the recorded `crust.json` version). When the entry omits
552
- * `version`, the plugin's top-level {@link SkillPluginOptions.version} is
553
- * used — the typical case when the bundle ships in the same package as
554
- * the consuming CLI.
555
- *
556
- * Setup-time validation enforces:
557
- * - Each `name` satisfies `isValidSkillName`.
558
- * - No `name` collides with the main skill's name.
559
- * - All `name` values are unique within the array.
560
- * - When set, `version` is a non-empty string.
561
- * - Each `sourceDir` is a `string` or `URL`.
562
- *
563
- * `sourceDir` resolution-time errors (non-`file:` URL, missing source
564
- * directory, missing `SKILL.md`, etc.) defer to the underlying
565
- * `installSkillBundle` invocation and surface there with descriptive
566
- * messages.
567
- *
568
- * When omitted or empty, plugin behavior is byte-identical to running
569
- * without the option — only the auto-generated main skill is managed.
570
- *
571
- * @default []
572
- */
573
- customSkills?: CustomSkillConfig[];
574
- /**
575
- * Register an interactive skill management subcommand on the root command.
576
- *
577
- * The command presents a single multiselect prompt listing all detected
578
- * agents with their current installation status pre-filled. The user
579
- * toggles agents on/off and the system reconciles the desired state:
580
- * newly selected agents are installed, deselected agents are uninstalled,
581
- * and already-correct agents are skipped.
582
- *
583
- * @default "skill"
584
- */
585
- command?: string;
586
- }
587
- /** Returns agents that use the canonical `.agents/skills` layout. */
588
- declare function getUniversalAgents(): AgentTarget[];
589
- /** Returns agents that use agent-specific skill roots. */
590
- declare function getAdditionalAgents(): AgentTarget[];
591
- /** Returns true if the agent uses the canonical `.agents/skills` layout. */
592
- declare function isUniversalAgent(agent: AgentTarget): boolean;
593
- interface DetectInstalledAgentsOptions {
594
- /** Kept for backwards compatibility with previous API. */
595
- scope?: Scope;
596
- /** Kept for backwards compatibility with previous API. */
597
- home?: string;
598
- /** Working directory for PATH lookups. */
599
- cwd?: string;
600
- /** Test-only hook to override command detection. */
601
- 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);
602
128
  }
603
- /**
604
- * Detects installed additional agents by checking PATH for their CLI binaries.
605
- *
606
- * Universal agents are intentionally not detected here so callers can always
607
- * present them as a single optional "Universal" install target.
608
- */
609
- declare function detectInstalledAgents(options?: string | DetectInstalledAgentsOptions): Promise<AgentTarget[]>;
610
- /**
611
- * Resolves the canonical skill bundle path used by Crust.
612
- */
613
- declare function resolveCanonicalSkillPath(scope: Scope, name: string): string;
614
- import { CommandNode as CommandNode2 } from "@crustjs/core";
615
- import { Crust } from "@crustjs/core";
616
- /**
617
- * Agent-oriented instructions attached to a command for skills rendering.
618
- */
619
- interface SkillCommandAnnotations {
620
- /** Additional prompt guidance rendered into the command's markdown file */
621
- instructions?: string[];
622
- }
623
- type SkillCommandTarget = CommandNode2 | Crust<any, any, any>;
624
- /**
625
- * Attaches agent-facing instructions to a command definition without changing
626
- * the public `@crustjs/core` API surface.
627
- *
628
- * The instructions are stored on the internal command node using an enumerable
629
- * symbol so they survive Crust's immutable clone/spread builder operations.
630
- *
631
- * Duplicate instructions are silently deduplicated — calling `annotate()` again
632
- * with the same text is a safe no-op.
633
- */
634
- declare function annotate<T extends SkillCommandTarget>(target: T, annotations: string | string[] | SkillCommandAnnotations): T;
635
- /**
636
- * Installs a hand-authored skill bundle through the same canonical-store and
637
- * agent-fan-out pipeline used by {@link generateSkill}.
638
- *
639
- * Unlike `generateSkill`, this entrypoint does not render any markdown — it
640
- * copies the directory at `sourceDir` as authored (subject to a
641
- * path-traversal guard against symlink escapes and a cycle guard) and
642
- * writes a fresh `crust.json` recording `kind: "bundle"`. Bundle authors
643
- * are responsible for keeping `sourceDir` clean — `crust.json` at the
644
- * bundle root is reserved and will throw if present in the source.
645
- *
646
- * The bundle's `SKILL.md` frontmatter is the source of truth for `name` and
647
- * `description`; both are required and read by Crust without rewriting the
648
- * file. The caller supplies `version` explicitly — typically wired to the
649
- * consuming package's `package.json` `version`.
650
- *
651
- * Bundles and generated skills cannot share a name unless the existing
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.
655
- *
656
- * @param options - Bundle install options (see {@link InstallSkillBundleOptions})
657
- * @returns Per-agent install results
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).
661
- * @throws {Error} If `SKILL.md` is missing, its frontmatter lacks `name:` or
662
- * `description:`, the declared `name` is not a valid skill name, the
663
- * declared `name` does not match `expectedName` when set, the source
664
- * directory escapes itself via symlink, or `sourceDir` cannot be resolved.
665
- *
666
- * @example
667
- * ```ts
668
- * import { installSkillBundle } from "@crustjs/skills";
669
- * import pkg from "./package.json" with { type: "json" };
670
- *
671
- * await installSkillBundle({
672
- * sourceDir: "skills/funnel-builder",
673
- * agents: ["claude-code"],
674
- * version: pkg.version,
675
- * });
676
- * ```
677
- */
678
- declare function installSkillBundle(options: InstallSkillBundleOptions): Promise<InstallSkillBundleResult>;
679
- /**
680
- * Why an installed manifest could not be interpreted.
681
- *
682
- * - `parse-error`: `crust.json` is present but is not valid JSON.
683
- * - `not-an-object`: top-level JSON value is not an object.
684
- * - `missing-version`: `version` field is absent or not a string.
685
- * - `unknown-kind`: `kind` is present but is neither `"bundle"` nor `"generated"` —
686
- * typically a hand-edit typo or a forward-compatible value emitted by a
687
- * newer Crust release.
688
- */
689
- type InstalledManifestMalformedReason = "parse-error" | "not-an-object" | "missing-version" | "unknown-kind";
690
- /**
691
- * Describes a kind mismatch between an existing installed bundle and an
692
- * incoming install attempt.
693
- *
694
- * Set on {@link SkillConflictDetails.kindMismatch} when {@link generateSkill}
695
- * or {@link installSkillBundle} discovers an existing `crust.json` whose
696
- * `kind` differs from the kind being installed (e.g. a generated skill
697
- * already lives at the target path and a bundle install was attempted).
698
- */
699
- interface SkillKindMismatch {
700
- /** Kind recorded in the existing `crust.json` */
701
- existing: SkillKind;
702
- /** Kind requested by the current install attempt */
703
- attempted: SkillKind;
704
- }
705
- /**
706
- * Describes a malformed `crust.json` discovered at the conflicting skill
707
- * directory.
708
- *
709
- * Set on {@link SkillConflictDetails.manifestMalformed} when the directory
710
- * contains a `crust.json` that exists but cannot be interpreted — e.g. it is
711
- * not valid JSON, lacks a `version`, or has an unrecognized `kind` value
712
- * (a hand-edit typo like `"bundel"`, or a forward-compatible value emitted by
713
- * a newer Crust release). Distinct from a missing `crust.json`, which keeps
714
- * the original "not created by Crust" semantics.
715
- */
716
- interface SkillManifestMalformed {
717
- /** Why the manifest could not be interpreted. */
718
- reason: InstalledManifestMalformedReason;
719
- /** Raw `kind` value when `reason === "unknown-kind"`. */
720
- rawKind?: string;
721
- }
722
- /** Details about the conflict between an existing skill and an incoming one. */
723
129
  interface SkillConflictDetails {
724
- /** The agent where the conflict was detected */
725
- agent: AgentTarget;
726
- /** Absolute path to the conflicting skill directory */
727
- outputDir: string;
728
- /**
729
- * Set when the conflict is a `kind` mismatch (existing `crust.json`
730
- * reports a different `kind` than the one being installed).
731
- *
732
- * Absent for "no-crust.json" conflicts (the original case).
733
- */
734
- kindMismatch?: SkillKindMismatch;
735
- /**
736
- * Set when `crust.json` is present at the conflicting directory but cannot
737
- * be interpreted (invalid JSON, missing version, unrecognized `kind`,
738
- * etc.). Lets the error message distinguish a Crust-owned-but-broken
739
- * manifest from a directory that simply was never managed by Crust.
740
- */
741
- manifestMalformed?: SkillManifestMalformed;
742
- }
743
- /**
744
- * Thrown when an install entrypoint detects that the target skill directory
745
- * already exists but cannot be overwritten safely.
746
- *
747
- * Three flavours:
748
- * - **No `crust.json`** — directory exists but was not created by Crust.
749
- * This prevents Crust from silently overwriting a skill that was manually
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}).
753
- * - **Kind mismatch** — directory was created by Crust but with a different
754
- * {@link SkillKind} (e.g. an existing `generated` skill collides with an
755
- * incoming `bundle` install). `force: true` bypasses all three cases.
756
- *
757
- * @example
758
- * ```ts
759
- * import { generateSkill, SkillConflictError } from "@crustjs/skills";
760
- *
761
- * try {
762
- * await generateSkill({ command, meta, agents });
763
- * } catch (err) {
764
- * if (err instanceof SkillConflictError) {
765
- * if (err.details.kindMismatch) {
766
- * console.error(
767
- * `Cannot install ${err.details.kindMismatch.attempted} skill — ` +
768
- * `${err.details.kindMismatch.existing} skill already at "${err.details.outputDir}".`,
769
- * );
770
- * } else {
771
- * console.error(
772
- * `Conflict: "${err.details.outputDir}" already exists and was not created by Crust.`,
773
- * );
774
- * }
775
- * }
776
- * }
777
- * ```
778
- */
779
- declare class SkillConflictError extends Error {
780
- readonly name = "SkillConflictError";
781
- readonly details: SkillConflictDetails;
782
- constructor(details: SkillConflictDetails);
783
- }
784
- /**
785
- * Validates a resolved skill name against the Agent Skills specification.
786
- *
787
- * @param name - The resolved skill name to validate
788
- * @returns `true` if valid, `false` otherwise
789
- */
790
- declare function isValidSkillName(name: string): boolean;
791
- /**
792
- * Resolves the canonical current skill name.
793
- *
794
- * All generated output (directory names, crust.json metadata, SKILL.md content)
795
- * uses the resolved name directly. Consumers pass the raw CLI name
796
- * (e.g. `"my-cli"`), and this function returns that same canonical name.
797
- *
798
- * @param name - The raw CLI tool name
799
- * @returns The canonical skill name
800
- *
801
- * @example
802
- * ```ts
803
- * resolveSkillName("my-cli"); // "my-cli"
804
- * ```
805
- */
806
- declare function resolveSkillName(name: string): string;
807
- /**
808
- * Generates and installs agent skill bundles from a Crust command tree.
809
- *
810
- * The generator renders the bundle once into a canonical Crust store
811
- * (`.crust/skills` project scope, `~/.crust/skills` global scope), then
812
- * installs into agent-specific output paths using the configured install mode
813
- * (`auto`, `symlink`, `copy`).
814
- *
815
- * @param options - Generation options including command, metadata, agents, and scope
816
- * @returns Per-agent installation results
817
- * @throws {SkillConflictError} If the output directory conflicts (no `crust.json`, malformed `crust.json`, or kind mismatch) and `force` is not set
818
- *
819
- * @example
820
- * ```ts
821
- * import { generateSkill } from "@crustjs/skills";
822
- * import { rootCommand } from "./commands.ts";
823
- *
824
- * const result = await generateSkill({
825
- * command: rootCommand,
826
- * meta: {
827
- * name: "my-cli",
828
- * description: "CLI tool for managing widgets",
829
- * version: "1.0.0",
830
- * },
831
- * agents: ["claude-code", "opencode"],
832
- * });
833
- *
834
- * for (const r of result.agents) {
835
- * console.log(`${r.agent}: ${r.status} → ${r.outputDir}`);
836
- * }
837
- * ```
838
- */
839
- declare function generateSkill(options: GenerateOptions): Promise<GenerateResult>;
840
- /**
841
- * Removes installed skills from agent directories.
842
- *
843
- * @param options - Uninstall options specifying name, agents, and scope
844
- * @returns Per-agent uninstall results
845
- */
846
- declare function uninstallSkill(options: UninstallOptions): Promise<UninstallResult>;
847
- /**
848
- * Checks the installation status of skills across agent directories.
849
- *
850
- * @param options - Status options specifying name, agents, and scope
851
- * @returns Per-agent status results
852
- */
853
- declare function skillStatus(options: StatusOptions): Promise<StatusResult>;
854
- import { CrustPlugin } from "@crustjs/core";
855
- /**
856
- * Plugin that manages agent skills for a Crust CLI application.
857
- *
858
- * `name` and `description` are read from the root command's `meta` at setup
859
- * time — only `version` needs to be supplied in the options.
860
- *
861
- * Installed agents are detected automatically.
862
- *
863
- * Only detected agents are managed by automatic update and the interactive
864
- * command.
865
- *
866
- * **Auto-update** (default): silently updates already-installed skills when a
867
- * new version is detected. Disable with `autoUpdate: false`.
868
- *
869
- * **Interactive command** (default): registers a `skill` subcommand that
870
- * presents a single multiselect prompt for toggling agent installations.
871
- * Detected agents are shown with their current installation status pre-filled.
872
- * The system reconciles the desired state: newly selected agents are installed,
873
- * deselected agents are uninstalled, and already-correct agents are skipped.
874
- * `command` configures the injected command name.
875
- *
876
- * For first-time installation, use the interactive command or build custom
877
- * auto-install logic with the exported primitives (`detectInstalledAgents`,
878
- * `skillStatus`, `generateSkill`).
879
- *
880
- * @param options - Plugin configuration with version and defaults
881
- * @returns A `CrustPlugin` to register in a command's `plugins` array
882
- *
883
- * @example
884
- * ```ts
885
- * import { Crust } from "@crustjs/core";
886
- * import { skillPlugin } from "@crustjs/skills";
887
- *
888
- * const app = new Crust("my-cli").meta({ description: "My CLI" })
889
- * .use(skillPlugin({
890
- * version: "1.0.0",
891
- * command: "skill", // registers "my-cli skill" subcommand
892
- * }))
893
- * .run(() => { /* ... *�/ });
894
- *
895
- * await app.execute();
896
- * ```
897
- */
898
- declare function skillPlugin(options: SkillPluginOptions): CrustPlugin;
899
- 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 };