import type { SkillKind, SkillMeta } from "./registry-types.js"; import { type SkillValidationResult } from "./skill-validation.js"; import { normalizePortableSkillName, readDeclaredSkillVersion, readPortableSkillManifest } from "./portable-skills-files.js"; import type { BulkPortPortableSkillOptions, BulkPortResult, PortableSkillOptions, PortableSkillRunOptions, PortableSkillRunResult, PortableSkillSummary, PortableSkillWriteResult, PortPortableSkillOptions, ScaffoldPortableSkillOptions } from "./portable-skills-types.js"; export { normalizePortableSkillName, readDeclaredSkillVersion, readPortableSkillManifest, }; export * from "./portable-skills-types.js"; /** * Resolve the corpus: the directory holding one folder per installed skill. * * THIS IS THE ONE CANONICAL CORPUS RESOLUTION. Every local discovery and * publish path — list, search, info, push, pull, sync, registry — reads the * corpus through this function (directly or via resolveCorpusRoot(), which * delegates here), so a migrated owner layout is never bypassed by a path that * still resolves installed/ (bug 170b0e9b: 'skills list --all' returned 87 * entries while the migrated corpus held 688). * * Precedence is explicit-over-ambient, most specific first: * 1. `options.rootDir` - the corpus, named outright (no suffix) * 2. the migrated owner layout - /skills/ when a migration record * exists there (the record is the authority; a skills/ directory someone * created by hand is not the corpus) * 3. `getDataDir()` - /installed (pre-migration corpus, * without importing any legacy directories), where the app folder is * $HASNA_SKILLS_DIR, else ~/.hasna/skills * * The app folder holds app data (config.json, skills.db, auth.json); the corpus * is a named subfolder of it, matching every sibling Hasna app. One variable * relocates the app folder and the corpus moves with it. * * `options.homeDir` used to sit *below* the $HASNA_SKILLS_DIR lookup, so an * ambient environment variable silently overrode an argument the caller had * passed deliberately - the caller could not target a directory at all once the * variable was set anywhere in the process. Reading the environment is now left * entirely to getDataDir(), so there is one place that knows the variable's name * and one rule for which source wins. */ export declare function getPortableSkillsRoot(options?: PortableSkillOptions): string; /** Resolve an existing authoring corpus without creating or migrating directories. */ export declare function getPortableSkillsRootReadOnly(options?: PortableSkillOptions): string; export declare function getPortableSkillPath(name: string, options?: PortableSkillOptions): string; export declare function findPortableSkill(name: string, options?: PortableSkillOptions): PortableSkillSummary | null; export declare function listPortableSkills(options?: PortableSkillOptions): PortableSkillSummary[]; export declare function listPortableSkillMetas(options?: PortableSkillOptions): SkillMeta[]; export declare function isOfficialSkillName(name: string): boolean; export declare function scaffoldPortableSkill(name: string, options?: ScaffoldPortableSkillOptions): PortableSkillWriteResult; /** * Import every immediate subfolder of a directory as a portable skill. * Skip-on-error by default: non-skill folders and per-skill failures are recorded * in the summary instead of aborting the whole run. */ export declare function portPortableSkillDirectory(sourceDir: string, options?: BulkPortPortableSkillOptions): BulkPortResult; export declare function portPortableSkill(sourcePath: string, options?: PortPortableSkillOptions): PortableSkillWriteResult; /** Metadata a Skills instance reports for a skill, used to fill the corpus manifest. */ export interface CorpusSkillMeta { displayName?: string; description?: string; category?: string; tags?: string[]; version?: string; kind?: SkillKind; /** * The hosted registry's revision id for this skill (todos d061fcda), when the * instance reported one. Carried onto the metadata-only pull path so the pull marker * records which revision was installed there too. */ revisionId?: string; /** * The row's SKILL.md, served in the published metadata so a client can recompute the * content-addressed revision id and PROVE the declared revision identifies the content * it received (todos d061fcda). */ skillMd?: string; /** * The row's stored source (the payload's own `source` is always the client view * "remote"). The canonical revision hash is computed over the stored value, so the * client needs it to recompute the id. */ publishedSource?: string; } export interface WriteCorpusSkillInput { name: string; /** The SKILL.md document as served by the instance. Written verbatim. */ skillMd: string; meta?: CorpusSkillMeta | null; } /** * Write a skill fetched from a Skills instance into the local corpus (the * canonical root — installed/ before the layout migration, /skills/ * after it), so loadRegistry() surfaces it to both the CLI * (`skills list --all`) and the MCP (`list_skills`) with no further step — the whole * point of the pull: the corpus is already a first-class registry source. * * SKILL.md is written verbatim: it is the agent-facing artifact and the registry's * frontmatter source. A canonical skill.json is written beside it so * listPortableSkillMetas() reports the right kind/category/tags/version even when the * fetched SKILL.md carries thin frontmatter. * * Idempotent: re-writing the same fetched bytes yields byte-identical files. It * overwrites SKILL.md and skill.json — the instance is the source of truth for a pulled * skill — but removes nothing else, so a re-pull never destroys sibling files. */ export declare function writeCorpusSkill(input: WriteCorpusSkillInput, options?: PortableSkillOptions): PortableSkillWriteResult; /** * Atomically replace the corpus entry for a metadata-only pull (todos b4d956a3): * stage SKILL.md and skill.json in a sibling directory, then rename into place — the * existing entry is moved aside first and removed only after the staged tree is in * position. A failure at any point leaves either the old entry or nothing — never a * partial skill, mirroring installBundleAtomically on the bundle path. * * The manifest construction is identical to writeCorpusSkill; what differs is that no * write ever lands in the live target before the swap. The direct-overwrite order of * writeCorpusSkill (SKILL.md, then skill.json) can destroy the prior good copy when a * mid-write failure (ENOSPC, EISDIR, crash) hits between the two writes, leaving a * truncated or mismatched SKILL.md/skill.json pair that loadRegistry then serves. */ export declare function installCorpusSkillAtomically(input: WriteCorpusSkillInput, options?: PortableSkillOptions): PortableSkillWriteResult; export declare function validatePortableSkillDirectory(name: string, skillPath: string): SkillValidationResult; export declare function runPortableSkill(name: string, args: string[], options?: PortableSkillRunOptions): Promise;