import { CommandNode } from "@crustjs/core"; /** * Metadata for the generated skill bundle. * * This information populates the `SKILL.md` frontmatter and distribution * metadata files (`crust.json`). * * @example * ```ts * const meta: SkillMeta = { * name: "my-cli", * description: "CLI tool for managing widgets", * version: "1.0.0", * }; * // generateSkill() will output to `my-cli/` with name "my-cli" * ``` */ interface SkillMeta { /** * Skill name — the user-facing CLI name (e.g. `"my-cli"`). * * `generateSkill()`, `uninstallSkill()`, and `skillStatus()` treat this as * the canonical raw skill name for output directory paths, SKILL.md * frontmatter, and crust.json metadata. For example, `name: "my-cli"` * produces output under `my-cli/`. * * The resolved name must conform to the Agent Skills spec: 1–64 lowercase * alphanumeric characters and hyphens, no leading/trailing/consecutive * hyphens. */ name: string; /** Human-readable description of what the CLI does */ description: string; /** Version string for the generated skill bundle */ version: string; /** * License name or reference to a bundled license file. * * Emitted in SKILL.md YAML frontmatter as `license:`. */ license?: string; /** * Environment requirements or compatibility notes (max 500 chars per spec). * * Indicates intended product, required system packages, network access, etc. * Emitted in SKILL.md YAML frontmatter as `compatibility:`. * * @example "Requires deploy-cli installed on PATH" */ compatibility?: string; /** * When `true`, prevents agents from automatically loading this skill. * Users must invoke it manually with `/skill-name`. * * Emitted in SKILL.md YAML frontmatter as `disable-model-invocation: true`. * @default false */ disableModelInvocation?: boolean; /** * Space-delimited list of pre-approved tools the skill may use. * * For CLI skills, setting this to `Bash( *)` allows agents to * execute the CLI without per-use permission prompts. * * Emitted in SKILL.md YAML frontmatter as `allowed-tools:`. * * @example "Bash(my-cli *) Read Grep" */ allowedTools?: string; /** * Additional top-level instructions rendered into `SKILL.md`. * * Use this for plugin- or product-specific guidance that should be visible * before agents inspect individual command documentation files. * * **Note:** When a `string` value contains markdown headings (e.g. `## Foo`), * they are rendered at the same level as `## General Guidance`, not nested * under it. Use a `string[]` of plain instructions to avoid unintended * heading hierarchy. */ instructions?: string | string[]; } /** Supported agent targets for skill installation. */ 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"; /** Agent install class used by interactive skill management UX. */ type AgentClass = "universal" | "additional"; /** Installation scope — global (home directory) or project (cwd, except home dir which normalizes to global). */ type Scope = "global" | "project"; /** Installation strategy for agent skill output paths. */ type SkillInstallMode = "auto" | "symlink" | "copy"; /** * Origin of an installed skill bundle. * * Recorded in `crust.json` as the top-level `kind` field so Crust can detect * when a generated and a hand-authored bundle would collide on the same name. * * - `"generated"` — produced by {@link generateSkill} from a Crust command tree. * - `"bundle"` — installed by {@link installSkillBundle} from a hand-authored * directory containing a `SKILL.md` and supporting files. * * Legacy `crust.json` files (written before this field existed) are read as * `"generated"` for backward compatibility. */ type SkillKind = "generated" | "bundle"; /** * Top-level options for generating a skill bundle from a command tree. * * The `meta.name` value is used directly for all output paths and metadata. * For example, `name: "my-cli"` produces skill directories named `my-cli/` * and sets the manifest/frontmatter name to `"my-cli"`. * * @example * ```ts * import { generateSkill } from "@crustjs/skills"; * import { rootCommand } from "./commands.ts"; * * await generateSkill({ * command: rootCommand, * meta: { * name: "my-cli", // output: my-cli/ * description: "CLI tool for managing widgets", * version: "1.0.0", * }, * agents: ["claude-code", "opencode"], * }); * ``` */ interface GenerateOptions { /** Root command to generate the skill from */ command: CommandNode; /** Skill metadata for the generated bundle */ meta: SkillMeta; /** * Agent targets to install skills for. * * When omitted (or explicitly `undefined`), defaults to * `[...getUniversalAgents(), ...await detectInstalledAgents()]` — the union * of always-included universal agents and additional agents whose CLI is * detected on `PATH`. Pass an explicit array to override; `agents: []` * is treated as a no-op (no install performed). * * **Note:** Omitting this field performs filesystem I/O via * `detectInstalledAgents()` to probe `PATH` for installed agent CLIs. */ agents?: AgentTarget[]; /** * Installation strategy for agent output paths. * * - `"auto"` (default): create a symlink to the canonical `.crust/skills` * bundle, falling back to a hard copy when symlinks are unavailable. * - `"symlink"`: require symlinks; fail if a symlink cannot be created. * - `"copy"`: write full copies directly into each agent path. * * Canonical bundles are always generated once under `.crust/skills` (project) * or `~/.crust/skills` (global). When `process.cwd()` is the home directory, * project scope is normalized to the global location. * @default "auto" */ installMode?: SkillInstallMode; /** * Installation scope — global (home directory) or project (cwd). * When `process.cwd()` is the home directory, `"project"` is treated as `"global"`. * @default "global" */ scope?: Scope; /** * When `true`, removes the existing skill directory before writing. * Prevents stale files from previous generations. * @default true */ clean?: boolean; /** * When `true`, rewrite the generated skill even when the recorded version is * unchanged, and overwrite an existing conflicting directory (for example, * no `crust.json`, malformed `crust.json`, or a different skill kind) * instead of throwing {@link SkillConflictError}. * @default false */ force?: boolean; } /** * Top-level options for installing a hand-authored skill bundle. * * Unlike {@link GenerateOptions}, the bundle entrypoint does not render * `SKILL.md` from a command tree — it copies a directory the caller has * already authored. The bundle's `SKILL.md` frontmatter is the source of * truth for `name` and `description`; Crust reads them but does not rewrite * the file. A fresh `crust.json` is written alongside the bundle for * ownership and version tracking. * * Bundle files are copied as raw bytes. `SKILL.md` is also parsed as UTF-8 * to read its required frontmatter. * * Bundle content changes do not propagate without a `version` bump: * identical-version reinstalls report `up-to-date` and leave the canonical * store untouched. Pass a fresh `version` whenever the bundle contents * change (e.g. wire it to the consuming package's `package.json` `version`). * * @example * ```ts * import { installSkillBundle } from "@crustjs/skills"; * import pkg from "./package.json" with { type: "json" }; * * // SKILL.md frontmatter supplies name + description; the caller passes * // the version explicitly (typically wired to package.json). * await installSkillBundle({ * sourceDir: "skills/funnel-builder", * agents: ["claude-code"], * version: pkg.version, * }); * ``` */ interface InstallSkillBundleOptions { /** * Source directory containing the bundle to install. * * Resolution rules (mirror `@crustjs/create`'s `scaffold({ template })`): * - `URL` — must use `file:` protocol; resolved via `fileURLToPath()`. * - Absolute string path — used as-is via `path.resolve()`. * - Relative string path — resolved from the nearest `package.json` * directory walking up from `process.argv[1]`. Throws if `process.argv[1]` * is unset or no `package.json` is found. * * The directory must contain a `SKILL.md` whose YAML frontmatter declares * top-level `name:` and `description:` fields. */ sourceDir: string | URL; /** * Agent targets to install the bundle for. * * Required — unlike {@link GenerateOptions.agents}, the bundle entrypoint * does not auto-detect agents. Pass `[]` for a validated no-op: no install * is performed, but `sourceDir`, `SKILL.md`, bundle paths, frontmatter, and * skill name are still validated. */ agents: AgentTarget[]; /** * Version string recorded for this install and compared on subsequent * installs to decide between `installed` / `updated` / `up-to-date`. * * Required. Typically wired to the consuming package's `package.json` * `version` (e.g. via `import pkg from "./package.json" with { type: * "json" }`). Identical-version reinstalls report `up-to-date` and skip * the canonical-store rewrite (unless `force: true` is passed), so bump * this whenever bundle contents change. */ version: string; /** * Installation strategy for agent output paths. * @default "auto" */ installMode?: SkillInstallMode; /** * Installation scope — global (home directory) or project (cwd). * @default "global" */ scope?: Scope; /** * When `true`, removes the existing skill directory before writing. * @default true */ clean?: boolean; /** * When `true`, rewrite the bundle even when the recorded version is * unchanged, and overwrite an existing conflicting directory (for example, * no `crust.json`, malformed `crust.json`, or a different skill kind) * instead of throwing {@link SkillConflictError}. * @default false */ force?: boolean; /** * When set, the bundle's `SKILL.md` frontmatter `name:` must equal this * string. A mismatch throws before any filesystem write. * * Used by `skillPlugin`'s `customSkills` reconciliation to keep the * config-level `name` (used for status / uninstall lookups) in lockstep * with the frontmatter `name` (the canonical install path), preventing * orphan installs. */ expectedName?: string; } /** * Result returned by `installSkillBundle` after writing files to disk. * * Type alias of {@link GenerateResult} — the per-agent shape is identical. */ type InstallSkillBundleResult = GenerateResult; /** Status of an individual agent installation. */ type InstallStatus = "installed" | "updated" | "up-to-date"; /** Status of an individual agent uninstallation. */ type UninstallStatus = "removed" | "not-found"; /** Per-agent result from a generateSkill call. */ interface AgentResult { /** Which agent this result is for */ agent: AgentTarget; /** Absolute path to the skill output directory for this agent */ outputDir: string; /** List of files that were written (relative paths) */ files: string[]; /** What happened during this installation */ status: InstallStatus; /** Previous version string when status is "updated" */ previousVersion?: string; } /** * Result returned by `generateSkill` after writing files to disk. */ interface GenerateResult { /** Per-agent installation results */ agents: AgentResult[]; } /** Options for removing installed skills. */ interface UninstallOptions { /** Skill name to uninstall */ name: string; /** * Agent targets to uninstall from. * * When omitted (or explicitly `undefined`), defaults to every supported * agent so the uninstall sweep covers any path that may hold an install, * regardless of what is on the current machine's `PATH`. Pass an explicit * array to scope the uninstall; `agents: []` is treated as a no-op (no * paths are touched). * * Default resolution does not perform `PATH` I/O — the entrypoint already * stats each per-agent path during the sweep. */ agents?: AgentTarget[]; /** * Installation scope to uninstall from. * When `process.cwd()` is the home directory, `"project"` is treated as `"global"`. * @default "global" */ scope?: Scope; } /** Result returned by `uninstallSkill`. */ interface UninstallResult { /** Per-agent uninstall results */ agents: Array<{ agent: AgentTarget; outputDir: string; status: UninstallStatus; }>; } /** Options for checking installed skill status. */ interface StatusOptions { /** Skill name to check */ name: string; /** * Agent targets to check. * * When omitted (or explicitly `undefined`), defaults to every supported * agent so the status sweep reports an entry for any path that may hold * an install, regardless of what is on the current machine's `PATH`. Pass * an explicit array to scope the check; `agents: []` is treated as a no-op * (returns an empty result). * * Default resolution does not perform `PATH` I/O — the entrypoint already * stats each per-agent path during the sweep. */ agents?: AgentTarget[]; /** * Installation scope to check. * When `process.cwd()` is the home directory, `"project"` is treated as `"global"`. * @default "global" */ scope?: Scope; } /** Result returned by `skillStatus`. */ interface StatusResult { /** Per-agent status results */ agents: Array<{ agent: AgentTarget; outputDir: string; installed: boolean; version?: string; }>; } /** * Configuration for a single hand-authored skill bundle managed by * {@link skillPlugin} alongside the auto-generated command-reference skill. * * Each entry is reconciled through the same plugin lifecycle as the main * skill — auto-update on version change, surfaced in the interactive `skill` * subcommand multiselect, supports uninstall via the same toggle UX, and * respects `autoUpdate: false` and `--all` non-interactive mode. Bundles * inherit `version`, `defaultScope`, and `installMode` from the plugin * unless overridden per-entry. * * The bundle's `SKILL.md` frontmatter remains the source of truth for the * display `name` and `description` (validated by {@link installSkillBundle} * at install time). The duplicated `name` field on this config is what the * plugin uses for cheap collision-detection, status lookups, and uninstall * paths without having to read the bundle's frontmatter at plugin setup. * * @example * ```ts * import { skillPlugin } from "@crustjs/skills"; * import pkg from "./package.json" with { type: "json" }; * * skillPlugin({ * version: pkg.version, * customSkills: [ * // Inherits `version: pkg.version` from the plugin. * { name: "funnel-builder", sourceDir: "skills/funnel-builder" }, * // Explicit override for an independently-versioned bundle. * { * name: "vendored-toolkit", * sourceDir: "skills/vendored-toolkit", * version: "0.3.0", * }, * ], * }); * ``` */ interface CustomSkillConfig extends Pick { /** * Skill name used by the plugin for collision detection, status lookups, * and uninstall paths. * * Must satisfy `isValidSkillName` (1–64 lowercase alphanumeric characters * and hyphens, no leading/trailing/consecutive hyphens), must be unique * within the `customSkills` array, and must not collide with the main * skill's name (derived from the root command's `meta`). * * The bundle's `SKILL.md` frontmatter `name:` must match this value — * mismatches are rejected at install time so plugin status / uninstall * paths can never drift from the canonical install location. */ name: string; /** * Version override. When omitted, the bundle inherits the plugin's * top-level {@link SkillPluginOptions.version}. Drives auto-update * detection: a bundle is reinstalled when its recorded `crust.json` * version differs from the effective (entry-or-plugin) version. * * Inheriting from the plugin matches the typical case where the bundle * ships in the same package as the consuming CLI — one `pkg.version` * drives the main skill and every bundle. Pass an explicit value when a * bundle's release cadence is independent of the consuming CLI (for * example, vendored from another package). * * The bundle's `SKILL.md` frontmatter `version:` / `metadata.version`, * if any, is intentionally not read — this option (or its plugin-level * fallback) is the sole source of truth. */ version?: InstallSkillBundleOptions["version"]; /** * Installation scope override. When omitted, the bundle inherits * {@link SkillPluginOptions.defaultScope} resolution: explicit `--scope` * flag wins, else `defaultScope`, else the interactive scope prompt * (or `"global"` in non-interactive mode). */ scope?: InstallSkillBundleOptions["scope"]; /** * Installation strategy override. When omitted, inherits * {@link SkillPluginOptions.installMode} (default `"auto"`). */ installMode?: InstallSkillBundleOptions["installMode"]; } /** * Options for the skill plugin. * * The plugin reads `name` and `description` from the root command's `meta` * at setup time, so only `version` is required here. * * Installed agents are detected automatically. * * Only detected agents are managed. * * **Auto-update** (default): silently updates already-installed skills when a * new version is detected. Disable with `autoUpdate: false`. * * For first-time installation, use the interactive `skill` subcommand or * build custom auto-install logic with the exported primitives * (`detectInstalledAgents`, `skillStatus`, `generateSkill`). * * **Interactive command** (default): registers a `skill` subcommand (or the * custom `command` name) that * presents a single multiselect prompt for toggling agent installations. * * Scope resolution for interactive commands: * - If `defaultScope` is set, that scope is used and no scope prompt is shown. * - If `defaultScope` is not set and the terminal is interactive, users are * prompted to choose `project` or `global`. * - If `defaultScope` is not set and the terminal is non-interactive, scope * falls back to `"global"`. * - When `process.cwd()` is the home directory, `"project"` is normalized to * `"global"` for path resolution and update/status messaging. */ interface SkillPluginOptions { /** Skill version string — compared against the installed crust.json */ version: string; /** * Default installation scope for interactive commands. * * When omitted, interactive commands prompt for scope in TTY mode. * Non-interactive mode falls back to "global". * When `process.cwd()` is the home directory, `"project"` behaves as `"global"`. */ defaultScope?: Scope; /** * Installation strategy used when the plugin calls `generateSkill()`. * @default "auto" */ installMode?: SkillInstallMode; /** * Automatically update skills when the installed version is outdated. * @default true */ autoUpdate?: boolean; /** * Additional top-level instructions rendered into the generated `SKILL.md`. * * **Note:** When a `string` value contains markdown headings (e.g. `## Foo`), * they are rendered at the same level as `## General Guidance`, not nested * under it. Use a `string[]` of plain instructions to avoid unintended * heading hierarchy. */ instructions?: string | string[]; /** License name or reference emitted in SKILL.md frontmatter. */ license?: string; /** * Space-delimited list of pre-approved tools the skill may use. * * @example "Bash(my-cli *) Read Grep" */ allowedTools?: string; /** Environment requirements or compatibility notes (max 500 chars). */ compatibility?: string; /** * When `true`, prevents agents from automatically loading this skill. * @default false */ disableModelInvocation?: boolean; /** * Hand-authored skill bundles to manage alongside the auto-generated * command-reference skill. * * Each entry is reconciled through the same plugin lifecycle as the main * skill — auto-update on version change, surfaced in the interactive * `skill` subcommand multiselect (one prompt per bundle, in array order, * after the main-skill prompt), supports uninstall via the same toggle * UX, and respects `autoUpdate: false` and `--all` non-interactive mode. * * Bundles share the canonical `.crust/skills` store with the main skill * via {@link installSkillBundle} and inherit `defaultScope` / * `installMode` resolution unless overridden per-entry. * * Each entry's effective `version` drives auto-update detection (compared * against the recorded `crust.json` version). When the entry omits * `version`, the plugin's top-level {@link SkillPluginOptions.version} is * used — the typical case when the bundle ships in the same package as * the consuming CLI. * * Setup-time validation enforces: * - Each `name` satisfies `isValidSkillName`. * - No `name` collides with the main skill's name. * - All `name` values are unique within the array. * - When set, `version` is a non-empty string. * - Each `sourceDir` is a `string` or `URL`. * * `sourceDir` resolution-time errors (non-`file:` URL, missing source * directory, missing `SKILL.md`, etc.) defer to the underlying * `installSkillBundle` invocation and surface there with descriptive * messages. * * When omitted or empty, plugin behavior is byte-identical to running * without the option — only the auto-generated main skill is managed. * * @default [] */ customSkills?: CustomSkillConfig[]; /** * Register an interactive skill management subcommand on the root command. * * The command presents a single multiselect prompt listing all detected * agents with their current installation status pre-filled. The user * toggles agents on/off and the system reconciles the desired state: * newly selected agents are installed, deselected agents are uninstalled, * and already-correct agents are skipped. * * @default "skill" */ command?: string; } /** Returns agents that use the canonical `.agents/skills` layout. */ declare function getUniversalAgents(): AgentTarget[]; /** Returns agents that use agent-specific skill roots. */ declare function getAdditionalAgents(): AgentTarget[]; /** Returns true if the agent uses the canonical `.agents/skills` layout. */ declare function isUniversalAgent(agent: AgentTarget): boolean; interface DetectInstalledAgentsOptions { /** Kept for backwards compatibility with previous API. */ scope?: Scope; /** Kept for backwards compatibility with previous API. */ home?: string; /** Working directory for PATH lookups. */ cwd?: string; /** Test-only hook to override command detection. */ commandChecker?: (command: string, cwd: string) => Promise; } /** * Detects installed additional agents by checking PATH for their CLI binaries. * * Universal agents are intentionally not detected here so callers can always * present them as a single optional "Universal" install target. */ declare function detectInstalledAgents(options?: string | DetectInstalledAgentsOptions): Promise; /** * Resolves the canonical skill bundle path used by Crust. */ declare function resolveCanonicalSkillPath(scope: Scope, name: string): string; import { CommandNode as CommandNode2 } from "@crustjs/core"; import { Crust } from "@crustjs/core"; /** * Agent-oriented instructions attached to a command for skills rendering. */ interface SkillCommandAnnotations { /** Additional prompt guidance rendered into the command's markdown file */ instructions?: string[]; } type SkillCommandTarget = CommandNode2 | Crust; /** * Attaches agent-facing instructions to a command definition without changing * the public `@crustjs/core` API surface. * * The instructions are stored on the internal command node using an enumerable * symbol so they survive Crust's immutable clone/spread builder operations. * * Duplicate instructions are silently deduplicated — calling `annotate()` again * with the same text is a safe no-op. */ declare function annotate(target: T, annotations: string | string[] | SkillCommandAnnotations): T; /** * Installs a hand-authored skill bundle through the same canonical-store and * agent-fan-out pipeline used by {@link generateSkill}. * * Unlike `generateSkill`, this entrypoint does not render any markdown — it * copies the directory at `sourceDir` as authored (subject to a * path-traversal guard against symlink escapes and a cycle guard) and * writes a fresh `crust.json` recording `kind: "bundle"`. Bundle authors * are responsible for keeping `sourceDir` clean — `crust.json` at the * bundle root is reserved and will throw if present in the source. * * The bundle's `SKILL.md` frontmatter is the source of truth for `name` and * `description`; both are required and read by Crust without rewriting the * file. The caller supplies `version` explicitly — typically wired to the * consuming package's `package.json` `version`. * * Bundles and generated skills cannot share a name unless the existing * install is removed first. `force: true` overwrites a conflicting install * (no `crust.json`, malformed `crust.json`, or kind mismatch) and also * rewrites a same-version bundle. * * @param options - Bundle install options (see {@link InstallSkillBundleOptions}) * @returns Per-agent install results * @throws {SkillConflictError} If the canonical store exists with no * `crust.json`, a malformed `crust.json`, or a different kind (and `force` * is not set). * @throws {Error} If `SKILL.md` is missing, its frontmatter lacks `name:` or * `description:`, the declared `name` is not a valid skill name, the * declared `name` does not match `expectedName` when set, the source * directory escapes itself via symlink, or `sourceDir` cannot be resolved. * * @example * ```ts * import { installSkillBundle } from "@crustjs/skills"; * import pkg from "./package.json" with { type: "json" }; * * await installSkillBundle({ * sourceDir: "skills/funnel-builder", * agents: ["claude-code"], * version: pkg.version, * }); * ``` */ declare function installSkillBundle(options: InstallSkillBundleOptions): Promise; /** * Why an installed manifest could not be interpreted. * * - `parse-error`: `crust.json` is present but is not valid JSON. * - `not-an-object`: top-level JSON value is not an object. * - `missing-version`: `version` field is absent or not a string. * - `unknown-kind`: `kind` is present but is neither `"bundle"` nor `"generated"` — * typically a hand-edit typo or a forward-compatible value emitted by a * newer Crust release. */ type InstalledManifestMalformedReason = "parse-error" | "not-an-object" | "missing-version" | "unknown-kind"; /** * Describes a kind mismatch between an existing installed bundle and an * incoming install attempt. * * Set on {@link SkillConflictDetails.kindMismatch} when {@link generateSkill} * or {@link installSkillBundle} discovers an existing `crust.json` whose * `kind` differs from the kind being installed (e.g. a generated skill * already lives at the target path and a bundle install was attempted). */ interface SkillKindMismatch { /** Kind recorded in the existing `crust.json` */ existing: SkillKind; /** Kind requested by the current install attempt */ attempted: SkillKind; } /** * Describes a malformed `crust.json` discovered at the conflicting skill * directory. * * Set on {@link SkillConflictDetails.manifestMalformed} when the directory * contains a `crust.json` that exists but cannot be interpreted — e.g. it is * not valid JSON, lacks a `version`, or has an unrecognized `kind` value * (a hand-edit typo like `"bundel"`, or a forward-compatible value emitted by * a newer Crust release). Distinct from a missing `crust.json`, which keeps * the original "not created by Crust" semantics. */ interface SkillManifestMalformed { /** Why the manifest could not be interpreted. */ reason: InstalledManifestMalformedReason; /** Raw `kind` value when `reason === "unknown-kind"`. */ rawKind?: string; } /** Details about the conflict between an existing skill and an incoming one. */ interface SkillConflictDetails { /** The agent where the conflict was detected */ agent: AgentTarget; /** Absolute path to the conflicting skill directory */ outputDir: string; /** * Set when the conflict is a `kind` mismatch (existing `crust.json` * reports a different `kind` than the one being installed). * * Absent for "no-crust.json" conflicts (the original case). */ kindMismatch?: SkillKindMismatch; /** * Set when `crust.json` is present at the conflicting directory but cannot * be interpreted (invalid JSON, missing version, unrecognized `kind`, * etc.). Lets the error message distinguish a Crust-owned-but-broken * manifest from a directory that simply was never managed by Crust. */ manifestMalformed?: SkillManifestMalformed; } /** * Thrown when an install entrypoint detects that the target skill directory * already exists but cannot be overwritten safely. * * Three flavours: * - **No `crust.json`** — directory exists but was not created by Crust. * This prevents Crust from silently overwriting a skill that was manually * created or installed by another tool. * - **Malformed `crust.json`** — directory is Crust-owned but its manifest * cannot be interpreted (see {@link SkillConflictDetails.manifestMalformed}). * - **Kind mismatch** — directory was created by Crust but with a different * {@link SkillKind} (e.g. an existing `generated` skill collides with an * incoming `bundle` install). `force: true` bypasses all three cases. * * @example * ```ts * import { generateSkill, SkillConflictError } from "@crustjs/skills"; * * try { * await generateSkill({ command, meta, agents }); * } catch (err) { * if (err instanceof SkillConflictError) { * if (err.details.kindMismatch) { * console.error( * `Cannot install ${err.details.kindMismatch.attempted} skill — ` + * `${err.details.kindMismatch.existing} skill already at "${err.details.outputDir}".`, * ); * } else { * console.error( * `Conflict: "${err.details.outputDir}" already exists and was not created by Crust.`, * ); * } * } * } * ``` */ declare class SkillConflictError extends Error { readonly name = "SkillConflictError"; readonly details: SkillConflictDetails; constructor(details: SkillConflictDetails); } /** * Validates a resolved skill name against the Agent Skills specification. * * @param name - The resolved skill name to validate * @returns `true` if valid, `false` otherwise */ declare function isValidSkillName(name: string): boolean; /** * Resolves the canonical current skill name. * * All generated output (directory names, crust.json metadata, SKILL.md content) * uses the resolved name directly. Consumers pass the raw CLI name * (e.g. `"my-cli"`), and this function returns that same canonical name. * * @param name - The raw CLI tool name * @returns The canonical skill name * * @example * ```ts * resolveSkillName("my-cli"); // "my-cli" * ``` */ declare function resolveSkillName(name: string): string; /** * Generates and installs agent skill bundles from a Crust command tree. * * The generator renders the bundle once into a canonical Crust store * (`.crust/skills` project scope, `~/.crust/skills` global scope), then * installs into agent-specific output paths using the configured install mode * (`auto`, `symlink`, `copy`). * * @param options - Generation options including command, metadata, agents, and scope * @returns Per-agent installation results * @throws {SkillConflictError} If the output directory conflicts (no `crust.json`, malformed `crust.json`, or kind mismatch) and `force` is not set * * @example * ```ts * import { generateSkill } from "@crustjs/skills"; * import { rootCommand } from "./commands.ts"; * * const result = await generateSkill({ * command: rootCommand, * meta: { * name: "my-cli", * description: "CLI tool for managing widgets", * version: "1.0.0", * }, * agents: ["claude-code", "opencode"], * }); * * for (const r of result.agents) { * console.log(`${r.agent}: ${r.status} → ${r.outputDir}`); * } * ``` */ declare function generateSkill(options: GenerateOptions): Promise; /** * Removes installed skills from agent directories. * * @param options - Uninstall options specifying name, agents, and scope * @returns Per-agent uninstall results */ declare function uninstallSkill(options: UninstallOptions): Promise; /** * Checks the installation status of skills across agent directories. * * @param options - Status options specifying name, agents, and scope * @returns Per-agent status results */ declare function skillStatus(options: StatusOptions): Promise; import { CrustPlugin } from "@crustjs/core"; /** * Plugin that manages agent skills for a Crust CLI application. * * `name` and `description` are read from the root command's `meta` at setup * time — only `version` needs to be supplied in the options. * * Installed agents are detected automatically. * * Only detected agents are managed by automatic update and the interactive * command. * * **Auto-update** (default): silently updates already-installed skills when a * new version is detected. Disable with `autoUpdate: false`. * * **Interactive command** (default): registers a `skill` subcommand that * presents a single multiselect prompt for toggling agent installations. * Detected agents are shown with their current installation status pre-filled. * The system reconciles the desired state: newly selected agents are installed, * deselected agents are uninstalled, and already-correct agents are skipped. * `command` configures the injected command name. * * For first-time installation, use the interactive command or build custom * auto-install logic with the exported primitives (`detectInstalledAgents`, * `skillStatus`, `generateSkill`). * * @param options - Plugin configuration with version and defaults * @returns A `CrustPlugin` to register in a command's `plugins` array * * @example * ```ts * import { Crust } from "@crustjs/core"; * import { skillPlugin } from "@crustjs/skills"; * * const app = new Crust("my-cli").meta({ description: "My CLI" }) * .use(skillPlugin({ * version: "1.0.0", * command: "skill", // registers "my-cli skill" subcommand * })) * .run(() => { /* ... *�/ }); * * await app.execute(); * ``` */ declare function skillPlugin(options: SkillPluginOptions): CrustPlugin; 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 };