Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
# `apply`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
The plugin-side apply hook. The installer host owns the actual
|
||||
transactional materialisation (staged-tmp + atomic-rename + journal-based
|
||||
rollback); this hook's only job is to inspect the planned ops and return
|
||||
an `AgentSkillsHandle` summarising what was installed so `postInstall`
|
||||
can render a meaningful summary line.
|
||||
|
||||
## Features
|
||||
|
||||
- Counts `write` ops to derive the `installed` count.
|
||||
- Echoes back the resolved `targetDir` so `postInstall` doesn't need to
|
||||
re-derive it from ops or answers.
|
||||
- Pure pass-through — no fs I/O, no side effects.
|
||||
|
||||
## Basic Example
|
||||
|
||||
```ts
|
||||
import { apply } from "@suaveplan/install-agent-skills";
|
||||
|
||||
const handle = await apply(ops, ctx, ".suaveplan/skills");
|
||||
console.log(`installed: ${handle.installed}`);
|
||||
```
|
||||
|
||||
## Advanced Example
|
||||
|
||||
```ts
|
||||
import { apply } from "@suaveplan/install-agent-skills";
|
||||
import type { FileOp, InstallerContext } from "@suaveplan/installer";
|
||||
|
||||
async function dryRunSummary(
|
||||
ops: ReadonlyArray<FileOp>,
|
||||
ctx: InstallerContext,
|
||||
targetDir: string
|
||||
): Promise<string> {
|
||||
const handle = await apply(ops, ctx, targetDir);
|
||||
return `dry-run plan: ${handle.installed} files would land in ${handle.targetDir}`;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `apply(ops, ctx, targetDir): Promise<AgentSkillsHandle>` — pure
|
||||
function returning `{ installed, targetDir }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Apply is intentionally trivial because the installer host enforces
|
||||
transactional semantics via its own `applyOps` — every `FileOp` returned
|
||||
from `plan` is materialised through staged-tmp + atomic-rename with a
|
||||
journal-based rollback, so plugin-side apply does not need to perform
|
||||
any I/O. Plugin authors who need to perform side effects beyond what
|
||||
`FileOp` can express should do so in `postInstall` (where the host has
|
||||
already committed the apply transaction) rather than reaching into
|
||||
apply directly — that keeps
|
||||
rollback-on-failure semantics intact. The `targetDir` argument is
|
||||
threaded through from `plugin.ts` which extracts it from the index
|
||||
patch op so we don't lose it across the plan/apply boundary.
|
||||
@@ -0,0 +1,69 @@
|
||||
# `detect`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Reports what is already present at the target skills directory. The host
|
||||
calls `detect(ctx)` before prompts run; the result feeds into the user's
|
||||
decision about whether to proceed, `--force`, or supply `--only` /
|
||||
`--exclude` to limit the install. Detect is read-only: it only inspects
|
||||
`ctx.fs` and never mutates state.
|
||||
|
||||
## Features
|
||||
|
||||
- Reports `alreadyInstalled: boolean` based on the target directory's
|
||||
presence — `false` means a fresh install path.
|
||||
- Lists existing skill directories as `conflicts` (excluding
|
||||
`index.json`) so the host UI can show what would be touched.
|
||||
- Defaults to scanning `.suaveplan/skills` relative to `ctx.cwd`; the
|
||||
optional second argument lets callers pre-supply a target dir from
|
||||
partial answers.
|
||||
- Tolerant of degenerate states — when `listDir` throws (e.g. the target
|
||||
is a file masquerading as a dir), returns `conflicts: []` rather than
|
||||
propagating the error.
|
||||
|
||||
## Basic Example
|
||||
|
||||
```ts
|
||||
import { detect } from "@suaveplan/install-agent-skills";
|
||||
|
||||
const result = await detect(ctx);
|
||||
if (result.alreadyInstalled) {
|
||||
console.warn("Skills already installed; use --force to overwrite");
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Example
|
||||
|
||||
```ts
|
||||
import { detect } from "@suaveplan/install-agent-skills";
|
||||
|
||||
const result = await detect(ctx, { targetDir: "/srv/agent/skills" });
|
||||
if (result.alreadyInstalled && (result.conflicts?.length ?? 0) > 0) {
|
||||
console.log("Existing skills:");
|
||||
for (const id of result.conflicts ?? []) {
|
||||
console.log(` - ${id}`);
|
||||
}
|
||||
console.log(
|
||||
`Run again with --only ${result.conflicts?.join(",")} to refresh just those`
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `detect(ctx, answers?): Promise<DetectResult>` — `answers` may be
|
||||
partial; only `targetDir` is consulted at this stage.
|
||||
- Return shape — `{ alreadyInstalled, conflicts?, notes? }` from the
|
||||
host's `DetectResult` type.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Detect runs _before_ prompts so it can't access fully-validated answers;
|
||||
the partial `answers` parameter lets the bin pass through whatever the
|
||||
user already supplied (e.g. via `--answers <file>`). Reading the
|
||||
target's directory listing instead of stating each known skill's
|
||||
manifest is cheaper and gives the user a holistic view — including
|
||||
unrelated skills that were dropped into the directory by hand.
|
||||
@@ -0,0 +1,78 @@
|
||||
# `error-codes`
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the canonical error-code registry for `@suaveplan/install-agent-skills`. Every plugin-side failure path
|
||||
(invalid bundled manifest, unknown skill id under `--only`/`--exclude`,
|
||||
empty/invalid target directory) surfaces as an `InstallSkillsError`
|
||||
carrying a literal-typed `code` from this registry. The host's plan-stage
|
||||
catch (`runPlan`) re-throws any `SuaveplanError` unchanged, so these codes
|
||||
reach the caller without being wrapped in `INSTALLER.PLAN_FAILED`.
|
||||
|
||||
## Features
|
||||
|
||||
- Frozen registry built via `defineCodes("INSTALL_SKILLS", [...])`.
|
||||
- `InstallSkillsError` extends `SuaveplanError<InstallSkillsErrorCode>`
|
||||
with a public widened constructor.
|
||||
- Three codes in the minimum surface: `MANIFEST_INVALID`, `UNKNOWN_SKILL`,
|
||||
`TARGET_DIR_INVALID`.
|
||||
- Stable `error.name === "InstallSkillsError"` so serialised payloads
|
||||
carry a recognisable name across bundlers.
|
||||
|
||||
## Basic Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
INSTALL_SKILLS_CODES,
|
||||
InstallSkillsError,
|
||||
} from "@suaveplan/install-agent-skills";
|
||||
|
||||
throw new InstallSkillsError(
|
||||
INSTALL_SKILLS_CODES.UNKNOWN_SKILL,
|
||||
"install_skills.unknown_skill",
|
||||
{ context: { id: "nonexistent" } }
|
||||
);
|
||||
```
|
||||
|
||||
## Advanced Example
|
||||
|
||||
```ts
|
||||
import { InstallSkillsError } from "@suaveplan/install-agent-skills";
|
||||
|
||||
function classify(err: unknown): string {
|
||||
if (!(err instanceof InstallSkillsError)) return "other";
|
||||
switch (err.code) {
|
||||
case "INSTALL_SKILLS.MANIFEST_INVALID":
|
||||
return "rebuild bundle";
|
||||
case "INSTALL_SKILLS.UNKNOWN_SKILL":
|
||||
return "fix --only/--exclude";
|
||||
case "INSTALL_SKILLS.TARGET_DIR_INVALID":
|
||||
return "supply targetDir";
|
||||
default:
|
||||
return "unhandled";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `INSTALL_SKILLS_CODES` — frozen `{ [K]: "INSTALL_SKILLS.K" }` map.
|
||||
- `type InstallSkillsErrorCode` — union of every literal in the registry.
|
||||
- `class InstallSkillsError extends SuaveplanError<InstallSkillsErrorCode>`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The host's plan stage uses `instanceof SuaveplanError` (the shared base
|
||||
class) to decide whether to re-throw or wrap, so codes from this
|
||||
registry flow through the host's plan-stage catch unchanged and reach
|
||||
the caller with their original `INSTALL_SKILLS.*` code intact.
|
||||
`MANIFEST_INVALID` covers parse, read, and schema errors — callers
|
||||
don't have to handle three variants and the underlying cause is always
|
||||
available via `err.cause` for log-and-continue paths. `UNKNOWN_SKILL`
|
||||
is emitted by the plan stage when an `--only` or `--exclude` entry
|
||||
doesn't match any id in the bundled manifest, with the offending id
|
||||
captured in `err.context.id` so error reporters can show useful
|
||||
suggestions. `TARGET_DIR_INVALID` guards against empty or non-string
|
||||
answers; under normal use the prompt schema's default value
|
||||
(`.suaveplan/skills`) prevents this path entirely, but defensive
|
||||
validation ensures programmatic callers can't slip past it.
|
||||
@@ -0,0 +1,78 @@
|
||||
# `manifest-loader`
|
||||
|
||||
## Purpose
|
||||
|
||||
Loads and validates the bundled `assets/skills/manifest.json` produced by
|
||||
`scripts/build-skills-bundle.ts`. The loader is the runtime equivalent of
|
||||
the plugin's manifest schema — every entry must have a string `id`,
|
||||
semver `version`, non-empty `dir`, and array of relative `files`.
|
||||
Validation failures surface as
|
||||
`InstallSkillsError(INSTALL_SKILLS.MANIFEST_INVALID)` with the underlying
|
||||
Zod error attached as `cause`.
|
||||
|
||||
## Features
|
||||
|
||||
- Zod-validated `BundledManifestSchema` — `{ schemaVersion: 1, skills: [...] }`.
|
||||
- Per-skill validation: `id`, semver `version`, `dir`, `files`.
|
||||
- **Not memoised** — every call re-reads the file so the testing harness
|
||||
can swap in alternate manifests by passing `manifestPath`.
|
||||
- Defaults to the bundled file shipped inside the package
|
||||
(`<pkg-root>/assets/skills/manifest.json`).
|
||||
- Distinguishes parse, read, and schema failures only by `messageKey`;
|
||||
the surfaced code is always `INSTALL_SKILLS.MANIFEST_INVALID`.
|
||||
|
||||
## Basic Example
|
||||
|
||||
```ts
|
||||
import { loadBundledManifest } from "@suaveplan/install-agent-skills";
|
||||
|
||||
const manifest = await loadBundledManifest();
|
||||
console.log(`bundled ${manifest.skills.length} skills`);
|
||||
```
|
||||
|
||||
## Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
InstallSkillsError,
|
||||
loadBundledManifest,
|
||||
} from "@suaveplan/install-agent-skills";
|
||||
|
||||
try {
|
||||
const manifest = await loadBundledManifest({
|
||||
manifestPath: "/tmp/test-fixture-manifest.json",
|
||||
});
|
||||
for (const skill of manifest.skills) {
|
||||
console.log(`${skill.id}@${skill.version} (${skill.files.length} files)`);
|
||||
}
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof InstallSkillsError &&
|
||||
err.code === "INSTALL_SKILLS.MANIFEST_INVALID"
|
||||
) {
|
||||
console.error("Bundle is broken — rebuild via build-skills-bundle.ts");
|
||||
process.exit(2);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `loadBundledManifest(options?: LoadOptions): Promise<BundledManifest>`
|
||||
- `interface LoadOptions` — `{ manifestPath?: string }`.
|
||||
- `BundledManifest` — `{ schemaVersion: 1, skills: SkillEntry[] }`.
|
||||
- `SkillEntry` — `{ id, version, dir, files }`.
|
||||
- `BundledManifestSchema` — Zod schema, exported for advanced reuse.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The default manifest path is resolved via `import.meta.url` so the loader
|
||||
works correctly under ESM with no fallback to `__dirname` — the package
|
||||
ships ESM-only and `import.meta.url` is always available. Schema
|
||||
violations carry `context.issues` (the Zod issue list) plus
|
||||
`context.path` so error reporters can show field-level breakdowns.
|
||||
Read failures (file missing, permission denied) and parse failures
|
||||
(truncated/garbage JSON) get distinct `messageKey` values
|
||||
(`unreadable`, `unparseable`, `invalid`) but share the same code so
|
||||
consumers don't need three handlers.
|
||||
@@ -0,0 +1,85 @@
|
||||
# `plan`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Builds the `FileOp[]` plan for installing the selected skills. Reads the
|
||||
bundled manifest, applies `--only` / `--exclude` filters, and emits a
|
||||
`mkdir` per skill, a `write` per file, and a single `patch-json` for the
|
||||
project's skill index. This is the most complex stage of the plugin —
|
||||
the host's `apply` stage materialises whatever `plan` produces, so all
|
||||
filtering, conflict-policy assignment, and index-shape decisions live
|
||||
here.
|
||||
|
||||
## Features
|
||||
|
||||
- Reads bundled `assets/skills/manifest.json` via `loadBundledManifest`.
|
||||
- Applies `only` filter first, then `exclude` filter — matches the spec
|
||||
semantics and prevents `only` from being silently overridden by
|
||||
`exclude`.
|
||||
- Raises `INSTALL_SKILLS.UNKNOWN_SKILL` when an `only` or `exclude` id
|
||||
doesn't match any bundled skill — prevents silent typos.
|
||||
- Emits per-file conflict policy: `overwrite` when `answers.overwrite`,
|
||||
otherwise `skip-if-exists`. The skill-index `patch-json` always uses
|
||||
`merge` for idempotent re-installs.
|
||||
- Uses an injectable `nowIso` for deterministic test output and a
|
||||
fixture-friendly `bundleRoot` / `manifestPath` so tests don't depend
|
||||
on the real bundled file.
|
||||
|
||||
## Basic Example
|
||||
|
||||
```ts
|
||||
import { plan } from "@suaveplan/install-agent-skills";
|
||||
|
||||
const ops = await plan({
|
||||
answers: { targetDir: ".suaveplan/skills" },
|
||||
ctx,
|
||||
});
|
||||
console.log(`will perform ${ops.length} ops`);
|
||||
```
|
||||
|
||||
## Advanced Example
|
||||
|
||||
```ts
|
||||
import { plan } from "@suaveplan/install-agent-skills";
|
||||
|
||||
const ops = await plan({
|
||||
answers: {
|
||||
targetDir: ".suaveplan/skills",
|
||||
only: ["crypto", "events", "logger"],
|
||||
overwrite: true,
|
||||
},
|
||||
ctx,
|
||||
bundleRoot: "/tmp/test-fixture-skills",
|
||||
manifestPath: "/tmp/test-fixture-skills/manifest.json",
|
||||
nowIso: "2026-04-28T00:00:00.000Z",
|
||||
});
|
||||
|
||||
const writes = ops.filter((o) => o.kind === "write");
|
||||
const indexPatch = ops.find((o) => o.kind === "patch-json");
|
||||
console.log(
|
||||
`${writes.length} files, index at ${
|
||||
indexPatch && indexPatch.kind === "patch-json" ? indexPatch.path : "?"
|
||||
}`
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `plan(options: PlanOptions): Promise<ReadonlyArray<FileOp>>`
|
||||
- `interface PlanOptions` —
|
||||
`{ answers, ctx, bundleRoot?, manifestPath?, nowIso? }`.
|
||||
- Errors: `INSTALL_SKILLS.UNKNOWN_SKILL`, `INSTALL_SKILLS.TARGET_DIR_INVALID`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Skill index entries are stored as a Record keyed by `id` (not an array)
|
||||
so `JsonMerge`'s deep-merge semantics naturally upsert on re-install
|
||||
without producing duplicate entries — re-running with the same skill set
|
||||
overwrites entries by id rather than appending. The default `nowIso` is
|
||||
`new Date().toISOString()`; tests inject a fixed value for determinism.
|
||||
File contents are read eagerly during plan (not lazily during apply) so
|
||||
the apply stage's transactional semantics see byte-final ops — this
|
||||
trades memory for simpler rollback semantics.
|
||||
@@ -0,0 +1,88 @@
|
||||
# `plugin`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Composes the agent-skills installer plugin from its constituent stages
|
||||
(`detect`, `prompts`, `plan`, `apply`, `postInstall`). Exposes both the
|
||||
default `agentSkillsPlugin` (no-options instance using the bundled
|
||||
manifest) and a `buildAgentSkillsPlugin(options)` factory for tests and
|
||||
embedders that want to override the bundle root or capture deterministic
|
||||
timestamps.
|
||||
|
||||
## Features
|
||||
|
||||
- Default export is `agentSkillsPlugin` so `import "@suaveplan/install-agent-skills"` returns a ready-to-mount
|
||||
plugin via `mod.default`.
|
||||
- Factory `buildAgentSkillsPlugin(options)` accepts `bundleRoot`,
|
||||
`manifestPath`, and `nowIso` for full test control.
|
||||
- The `apply` lambda derives `targetDir` from the planned `index.json`
|
||||
op so `postInstall` always sees the resolved path, even when answers
|
||||
used a relative path.
|
||||
- Stable plugin metadata: `id: "agent-skills"`, `version: "0.1.0"`.
|
||||
|
||||
## Basic Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
agentSkillsPlugin,
|
||||
Host,
|
||||
DiskFs,
|
||||
} from "@suaveplan/installer";
|
||||
import "@suaveplan/install-agent-skills"; // ensures default export is loaded
|
||||
|
||||
const fs = new DiskFs({ cwd: process.cwd() });
|
||||
const host = new Host(ctx, fs);
|
||||
const result = await host.run(agentSkillsPlugin, {
|
||||
answers: { targetDir: ".suaveplan/skills" },
|
||||
});
|
||||
```
|
||||
|
||||
## Advanced Example
|
||||
|
||||
```ts
|
||||
import { buildAgentSkillsPlugin } from "@suaveplan/install-agent-skills";
|
||||
import { runPlugin } from "@suaveplan/installer/testing";
|
||||
|
||||
const plugin = buildAgentSkillsPlugin({
|
||||
bundleRoot: "/tmp/test-bundle",
|
||||
manifestPath: "/tmp/test-bundle/manifest.json",
|
||||
nowIso: "2026-04-28T00:00:00.000Z",
|
||||
});
|
||||
|
||||
const result = await runPlugin(plugin, {
|
||||
answers: {
|
||||
targetDir: "/x",
|
||||
only: ["crypto"],
|
||||
},
|
||||
});
|
||||
expect(result.handle?.installed).toBe(2);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `agentSkillsPlugin: InstallerPlugin<AgentSkillsAnswers, AgentSkillsHandle>`
|
||||
— default-options instance.
|
||||
- `buildAgentSkillsPlugin(options?): InstallerPlugin<AgentSkillsAnswers, AgentSkillsHandle>`
|
||||
— factory.
|
||||
- `interface AgentSkillsPluginOptions` —
|
||||
`{ bundleRoot?, manifestPath?, nowIso? }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The default `agentSkillsPlugin` is created via
|
||||
`buildAgentSkillsPlugin()` so the no-options path stays
|
||||
test-coverage-friendly (one factory, two callers). The `apply`
|
||||
lambda's `readTargetFromOps` helper reads the trailing
|
||||
`/index.json` op to derive the resolved target — this is more robust
|
||||
than threading answers through closures because it survives the host's
|
||||
`only`/`exclude` filtering, which can drop earlier ops but never the
|
||||
index patch. The factory's options are forwarded to `plan` via spread
|
||||
conditionals so omitting any option produces a runtime that uses the
|
||||
default (the bundled manifest path, the bundled assets root, and a
|
||||
fresh `Date.now()` timestamp respectively). Tests use `nowIso` to fix
|
||||
the timestamp for snapshot assertions; production runs let it default
|
||||
so each install carries its own ISO 8601 marker in the index.
|
||||
@@ -0,0 +1,70 @@
|
||||
# `post-install`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Prints a short summary line and a next-step hint after the host's apply
|
||||
stage commits. Uses `ctx.cli.write` so the testing harness's `ScriptCli`
|
||||
captures the output for assertions, and so production runs can be piped
|
||||
through any compatible `InstallerCli` writer (e.g. one that wraps a
|
||||
structured logger).
|
||||
|
||||
## Features
|
||||
|
||||
- One summary line — `installed N skills to <dir>`.
|
||||
- One follow-up hint pointing at `--only` / `--exclude` for refinement.
|
||||
- No fs I/O, no side effects beyond `cli.write` calls.
|
||||
- Honours `ctx.signal` via the host's pre-invocation abort guard
|
||||
(`runPostInstall`).
|
||||
|
||||
## Basic Example
|
||||
|
||||
```ts
|
||||
import { postInstall } from "@suaveplan/install-agent-skills";
|
||||
|
||||
await postInstall(
|
||||
{ installed: 3, targetDir: ".suaveplan/skills" },
|
||||
ctx
|
||||
);
|
||||
// stdout: installed 3 skills to .suaveplan/skills
|
||||
// Tip: commit the new directory and re-run with --only / --exclude to refine.
|
||||
```
|
||||
|
||||
## Advanced Example
|
||||
|
||||
```ts
|
||||
import { postInstall } from "@suaveplan/install-agent-skills";
|
||||
|
||||
const lines: string[] = [];
|
||||
const captureCtx = {
|
||||
...ctx,
|
||||
cli: { ...ctx.cli, write: (line: string) => lines.push(line) },
|
||||
};
|
||||
await postInstall(
|
||||
{ installed: 5, targetDir: "/srv/agent/skills" },
|
||||
captureCtx
|
||||
);
|
||||
console.log(`captured ${lines.length} lines`);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `postInstall(handle: AgentSkillsHandle, ctx: InstallerContext): Promise<void>`
|
||||
- Side effects: two `ctx.cli.write` calls.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
PostInstall is intentionally minimal — anything elaborate (e.g.
|
||||
generating a summary file, registering completions, sending telemetry)
|
||||
should live in a dedicated post-install helper composed at the bin
|
||||
layer rather than baked into the plugin. Keeping the message in stdout
|
||||
(via `cli.write`) instead of `logger.info` is deliberate: the bin's
|
||||
default cli writes to `process.stdout` so the message is visible by
|
||||
default, even when the logger is configured for higher verbosity
|
||||
thresholds. The host's `runPostInstall` already wraps this call in an
|
||||
OpenTelemetry span, so spinning up a per-call span here would
|
||||
double-instrument the same boundary. Plugins that need richer post-run
|
||||
behaviour should compose this function rather than replace it; the
|
||||
small surface keeps that composable in tests and embedders.
|
||||
@@ -0,0 +1,75 @@
|
||||
# `prompts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Builds the Zod-compatible `PromptSchema<AgentSkillsAnswers>` consumed by
|
||||
the host's prompt-validation stage. The schema validates `targetDir`,
|
||||
`only`, `exclude`, and `overwrite` answer shapes, defaulting `targetDir`
|
||||
to `.suaveplan/skills` when absent. Exposed as a builder function so
|
||||
plugin tests can re-derive the schema with different defaults if needed.
|
||||
|
||||
## Features
|
||||
|
||||
- `targetDir`: required string, default `.suaveplan/skills`.
|
||||
- `only`/`exclude`: optional `string[]` filters applied by the plan
|
||||
stage.
|
||||
- `overwrite`: optional boolean — `true` flips per-write conflict policy
|
||||
from `skip-if-exists` to `overwrite`.
|
||||
- Zod schema is exported separately so tests can validate answers
|
||||
directly without going through the host pipeline.
|
||||
|
||||
## Basic Example
|
||||
|
||||
```ts
|
||||
import { buildPromptSchema } from "@suaveplan/install-agent-skills";
|
||||
|
||||
const schema = buildPromptSchema();
|
||||
const result = schema.safeParse({ targetDir: ".suaveplan/skills" });
|
||||
if (result.success) {
|
||||
console.log(result.data.targetDir);
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Example
|
||||
|
||||
```ts
|
||||
import { AnswersSchema, buildPromptSchema } from "@suaveplan/install-agent-skills";
|
||||
|
||||
const explicit = AnswersSchema.parse({
|
||||
targetDir: "/srv/agent/skills",
|
||||
only: ["crypto", "events"],
|
||||
overwrite: true,
|
||||
});
|
||||
|
||||
// Or use the builder when threading through the installer host:
|
||||
import { Host } from "@suaveplan/installer";
|
||||
const host = new Host(ctx, fs);
|
||||
await host.run(myPlugin, {
|
||||
answers: AnswersSchema.parse({ targetDir: "/.suaveplan/skills" }),
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `buildPromptSchema(): PromptSchema<AgentSkillsAnswers>` — function
|
||||
that returns the Zod schema cast to the host's structural prompt
|
||||
contract.
|
||||
- `AnswersSchema` — the underlying Zod schema for direct use.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The cast to `PromptSchema<AgentSkillsAnswers>` is a structural narrowing
|
||||
— Zod's `safeParse` and `parse` already match the contract, but Zod
|
||||
returns a richer type that the host doesn't need to know about.
|
||||
Defaulting `targetDir` inside the schema means callers who supply an
|
||||
empty answer object still get a usable plan; the plan stage's defensive
|
||||
`TARGET_DIR_INVALID` guard covers the unlikely path where someone
|
||||
supplies an empty string explicitly. Both `only` and `exclude` are
|
||||
optional arrays — when omitted they're treated as "no filter" by the
|
||||
plan stage. The `overwrite` boolean flips per-write conflict policy
|
||||
without affecting the index patch (which always merges); this matches
|
||||
the spec's idempotent re-install requirement and lets users force a
|
||||
content refresh without churning the index entry shape.
|
||||
@@ -0,0 +1,74 @@
|
||||
# `answers.types`
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the typed answer and handle shapes for the agent-skills installer
|
||||
plugin. `AgentSkillsAnswers` is what `prompts` validates and `plan` reads;
|
||||
`AgentSkillsHandle` is what `apply` returns and `postInstall` consumes.
|
||||
Keeping these in a dedicated types file lets the plugin module structure
|
||||
stay flat without circular imports between prompts/plan/apply.
|
||||
|
||||
## Features
|
||||
|
||||
- `AgentSkillsAnswers` carries `targetDir` (default
|
||||
`.suaveplan/skills`), optional `only`/`exclude` filters, and optional
|
||||
`overwrite` flag.
|
||||
- `AgentSkillsHandle` carries the `installed` count and the resolved
|
||||
`targetDir` so postInstall can render an informative summary.
|
||||
- Pure types — zero runtime cost. Schemas live next to `prompts/prompts.ts`.
|
||||
|
||||
## Basic Example
|
||||
|
||||
```ts
|
||||
import type {
|
||||
AgentSkillsAnswers,
|
||||
} from "@suaveplan/install-agent-skills";
|
||||
|
||||
const answers: AgentSkillsAnswers = {
|
||||
targetDir: ".suaveplan/skills",
|
||||
only: ["crypto", "events"],
|
||||
overwrite: false,
|
||||
};
|
||||
```
|
||||
|
||||
## Advanced Example
|
||||
|
||||
```ts
|
||||
import type {
|
||||
AgentSkillsAnswers,
|
||||
AgentSkillsHandle,
|
||||
} from "@suaveplan/install-agent-skills";
|
||||
|
||||
interface RunRecord {
|
||||
readonly answers: AgentSkillsAnswers;
|
||||
readonly handle: AgentSkillsHandle;
|
||||
readonly startedAt: string;
|
||||
readonly endedAt: string;
|
||||
}
|
||||
|
||||
function summarise(record: RunRecord): string {
|
||||
return `[${record.endedAt}] installed ${record.handle.installed} skills to ${record.handle.targetDir}`;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `interface AgentSkillsAnswers`
|
||||
- `targetDir: string` — relative or absolute path under `ctx.cwd`.
|
||||
- `only?: ReadonlyArray<string>` — skill ids to include exclusively.
|
||||
- `exclude?: ReadonlyArray<string>` — skill ids to drop.
|
||||
- `overwrite?: boolean` — when true, plan emits `conflict: "overwrite"`
|
||||
on every `write` op instead of `skip-if-exists`.
|
||||
- `interface AgentSkillsHandle`
|
||||
- `installed: number` — count of `write` ops planned.
|
||||
- `targetDir: string` — resolved path used by postInstall.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The Zod runtime schema for `AgentSkillsAnswers` lives in
|
||||
`prompts/prompts.ts` and is type-asserted to `PromptSchema<AgentSkillsAnswers>`
|
||||
because Zod's structural inference is broader than the strict
|
||||
`InstallerPlugin<TAnswers>` generic. The handle is intentionally minimal
|
||||
— callers that need a richer summary (e.g. per-skill outcomes) should
|
||||
record their own state in `apply` and surface it via a custom
|
||||
`THandle` shape on a derived plugin.
|
||||
Reference in New Issue
Block a user