# `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 (`/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` - `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.