/** * Engram MCP provisioning (feature 66, P4 — AC3, decision D3). * * The memory substrate's registration is **ask-first and reversible**, which is * why it goes through Pi's own consent surface instead of writing a * machine-global `mcp.json` behind the operator's back: * * - `pi.registerMcpServer(name, config)` is session-scoped and NOT persisted * (Pi's contract), so the package registers on every session start and nothing * outlives the session unless the operator asks for it. * - Registration happens only when the `engram` binary actually resolves — a * registered-but-broken server would surface as an extension error on every * session, which is a worse default than not registering. * - A server the operator already declared in an `mcp.json` Pi reads — the * user file or, for a trusted project, `/.pi/mcp.json` — wins (Pi gives * it precedence), so the package reports that and does not shadow it. * - The operator's `memory.engram` config key is the consent switch: absent or * `false` registers nothing. This module never provisions without it. * * Every outcome is reported verbatim — "exactly what was written" is the rule * the sibling `init-workspace` consent flow already follows, and a * provisioning step the operator cannot read back is one they cannot trust. * * The install route is `pi install npm:gentle-engram@latest`. The CLI's own * setup subcommand is deliberately NOT the documented route: it pins whatever * version it ran with and keeps serving it until it is run again, so an updated * binary can sit unused behind a stale registration. * * Pi-free by construction — the binary lookup, the environment, the "already * declared" names and the register callback are all injected, so every branch * is tested with no Pi session, no PATH and no real binary. */ /** The name the server is registered under, and what `mcp.json` would declare. */ export declare const ENGRAM_SERVER_NAME = "engram"; /** The argv the workflow's runners use (pi's own registration, measured). */ export declare const ENGRAM_ARGS: readonly ["mcp", "--tools=agent"]; /** The one documented install route (D3). */ export declare const ENGRAM_INSTALL_ROUTE = "pi install npm:gentle-engram@latest"; export type EngramStatus = "registered" | "not-consented" | "config-invalid" | "binary-missing" | "already-declared" | "unsupported-host"; export interface EngramServerConfig { command: string; args: string[]; } export interface EngramProvisionInput { /** The operator's consent: `config.memory.engram`. Absent means no. */ consented: boolean; /** True when the package's config file exists but does not parse or validate. * Consent is then UNKNOWN, not refused: `loadConfig` fails closed to the * shipped default, so reading `consented` off that default would tell an * opted-in operator to opt in — `config/load.ts` calls a present-but-invalid * file a problem, never a silent default, and `dispatch.ts` refuses on the * same flag. Checked before consent. */ configInvalid?: boolean; /** Absolute path of the resolved `engram` binary, or null when not found. */ binaryPath: string | null; /** Server names the operator already declared (a `mcp.json` name wins), read * from every scope Pi itself reads — see `declaredMcpServerPaths`. */ declaredNames: readonly string[]; /** Pi's registrar, injected so this module never imports Pi. `null` when the * host's ExtensionAPI predates `registerMcpServer` — the package's peer floor * is a range, so an older host degrades instead of throwing. */ register: ((name: string, config: EngramServerConfig) => void) | null; } export interface EngramReport { status: EngramStatus; /** Present only when `status === "registered"`: exactly what was written. */ server?: { name: string; } & EngramServerConfig; /** One line an operator can read back; states what was written or why not. */ message: string; } /** * The server names an operator already declared in an `mcp.json` file. Pi gives * a declared server precedence over an extension registration, so the caller * passes this in and the module reports rather than shadows. Unreadable or * malformed JSON yields `[]`: a file this module cannot parse is not evidence * of a declaration, and refusing to register on it would be a silent gap. */ export declare function readDeclaredServerNames(path: string): string[]; /** * The union of the names declared across the `mcp.json` files Pi reads. One * file's declaration is not evidence about the other's. */ export declare function readDeclaredServerNamesFrom(paths: readonly string[]): string[]; /** * The `mcp.json` files Pi reads for one session: the user-level file, and — for * a trusted project only — `/.pi/mcp.json`, whose entry replaces a * same-named user entry (Pi's `mcp.md`: project configuration is read only after * trust is granted, and "a project entry replaces a user-level entry with the * same name"). The anti-shadow guard has to consult the same surface the host * applies: reading only the user file let a project declaration slip past it, so * the package registered anyway and then reported precedence over a file it had * never opened. */ export declare function declaredMcpServerPaths({ agentDir, cwd, projectTrusted, }: { agentDir: string; cwd: string; projectTrusted: boolean; }): string[]; /** * Resolve `engram` on `PATH` without a shell, honouring `PATHEXT` on Windows. * Injected `exists`/`env` keep the lookup testable; the production caller takes * the defaults. */ export declare function resolveEngramBinary({ env, exists, platform, }?: { env?: Record; exists?: (path: string) => boolean; platform?: NodeJS.Platform; }): string | null; /** * Decide and perform the registration, in the order that makes the outcome * truthful: a config that cannot be read first (its consent is unknown, never * refused), then consent (never provision unasked), then the binary (never * register a server that cannot start), then the operator's own declaration * (never shadow it). Returns the report; it never throws and never mutates * anything beyond the injected `register` call. */ export declare function provisionEngram(input: EngramProvisionInput): EngramReport;