import { type PortableSkillOptions } from "./portable-skills.js"; import { type SkillBundleEntry } from "./skill-bundle.js"; import type { SkillKind } from "./registry-types.js"; /** Header carrying the canonical content digest of the served bundle. */ export declare const BUNDLE_DIGEST_HEADER = "X-Skill-Bundle-Sha256"; /** Header carrying the HMAC signature of the served bundle, when the server can sign. */ export declare const BUNDLE_SIGNATURE_HEADER = "X-Skill-Bundle-Signature"; /** Header carrying the immutable revision identity of the served row (todos d061fcda). */ export declare const BUNDLE_REVISION_ID_HEADER = "X-Skill-Revision-Id"; /** Header carrying the per-slug write counter of the served row. */ export declare const BUNDLE_REVISION_NUMBER_HEADER = "X-Skill-Revision-Number"; /** Marker file written inside each corpus skill directory recording pull provenance. */ export declare const PULL_MARKER_FILE = ".hasna-skills.json"; /** * The slice of RemoteSkillsClient that `pullSkills` needs. Narrowed to an interface so a * test can inject a fake and the real HTTP client (which satisfies it structurally) is * only constructed on the production path. */ export interface SkillPullClient { listSkills(): Promise; getSkill(slug: string): Promise; getSkillMd(slug: string): Promise; /** * The skill's bundle, headers intact, or null when the instance serves none. * `Response` is the natural transport type here: verification reads the * X-Skill-Bundle-Sha256 / X-Skill-Bundle-Signature headers off it. */ getBundle(slug: string, version?: string): Promise; /** * The registry's record of one published version (hasna/apps#1630), or null. Optional: * an exact-version pull proves the received bytes against this digest; a client that * cannot answer it falls back to the bundle's own digest header. */ getSkillVersion?(slug: string, version: string): Promise<{ bundleSha256?: string; } | null>; } export interface PullSkillsOptions extends PortableSkillOptions { /** Explicit skill names to pull. Ignored when `all` is set. */ names?: string[]; /** Pull every skill the instance serves. */ all?: boolean; /** * Client override. `undefined` (the default) resolves one from configuration; * `null` models "no credential available" so the null-handling path stays testable. */ client?: SkillPullClient | null; /** HMAC signing key for bundle signature verification. Defaults to $SKILLS_SIGNING_KEY. */ signingKey?: string; } export interface PulledSkillResult { name: string; success: boolean; path?: string; kind?: SkillKind; version?: string; /** Canonical content hash of the installed bundle, when one was verified. */ contentHash?: string; /** Source commit recorded on the bundle, when the instance reported one. */ sourceCommit?: string; /** True when the pull created the corpus entry, false when it updated an existing one. */ created?: boolean; /** * The revision id (todos d061fcda) this pull installed, when the instance reported one. * Recorded in the marker so a later pull can detect that the remote moved on. */ revisionId?: string; /** True when the instance answered 410: the slug was deleted and pull reconciled. */ tombstoned?: boolean; /** With `tombstoned`: true when a local corpus entry existed and was removed. */ removed?: boolean; /** * With `tombstoned`: true when the local corpus entry was NOT pull-managed (no pull * marker), so it was left in place — a remote 410 never deletes a user-created skill. */ leftInPlace?: boolean; /** * True when the instance no longer serves a published revision under this slug (its * tombstone window expired) but the local copy is a revision-marked published install. * Reported instead of silently swapping in a bundled skill of the same name. */ purged?: boolean; error?: string; } export interface PullSkillsResult { results: PulledSkillResult[]; } export declare class PullSkillError extends Error { readonly detail?: string[] | undefined; constructor(message: string, detail?: string[] | undefined); } export interface VerifiedBundle { /** The received bundle bytes, owned by this process. */ bytes: Uint8Array; /** Canonical sha256 of `bytes`. */ contentHash: string; /** The server-declared digest, when the header was present. */ serverHash?: string; /** The server-declared signature, when the header was present. */ signature?: string; /** The server-declared revision id, when the header was present and well-formed. */ revisionId?: string; /** The server-declared per-slug write counter, when the header was present. */ revisionNumber?: number; } export declare function pullSkills(options?: PullSkillsOptions): Promise; /** * The verification core, synchronous over already-buffered bytes: the digest header * (when present) must equal the canonical sha256 of the received bytes, and the * signature header (when present) must verify against the signing key when one is * available — otherwise it is recorded with a warning, because a pull cannot check a * signature it has no key for. */ export declare function verifyBundleResponseBytes(buffer: ArrayBuffer, response: Response, verify?: { signingKey?: string; }): VerifiedBundle; /** * Atomically replace the corpus entry for a verified bundle: stage every entry 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. */ export declare function installBundleAtomically(name: string, entries: SkillBundleEntry[], options?: PortableSkillOptions, marker?: { version?: string; contentHash?: string; sourceCommit?: string; signature?: string; revisionId?: string; }): { path: string; created: boolean; }; /** * Record what a pull installed. Version/hash/source-commit are the provenance a later * `skills sync --check` or drift census needs; the signature is recorded when one was * present so an audit can tell "verified" from "declared". */ export declare function writePullMarker(dir: string, record: { skill: string; version?: string; contentHash?: string; sourceCommit?: string; signature?: string; revisionId?: string; source?: "pull" | "sync"; }): void; /** * Split `name@version` into its parts. A name without `@` pulls the current revision; an * empty version (`name@`) is an error the caller reports. Scoped-looking names are not a * concern here: skill slugs never start with `@`. */ export declare function splitNameVersion(raw: string): { name: string; version?: string; };