/** * One-command installation (FR-26). * * The command `doctor` diagnoses. Everything here is written so that the two * agree by construction rather than by coincidence: the wiring `install` writes * is `REQUIRED_WIRING`, the same constant `doctor` checks against, and the * stamp it substitutes is the digest `doctor` recomputes. An installer and a * diagnostic that drift apart produce the worst outcome available — a correct * installation that reports itself broken, or a broken one that reports clean. * * The paranoid part is `~/.claude/settings.json`. It holds the user's other * hooks, other MCP servers, permissions and plugin configuration, and losing * any of it is worse than not shipping this story. Every write here merges, * never replaces; refuses a file that does not parse rather than clobbering it; * lands atomically; and backs up before the first modification. */ export type ActionOutcome = 'created' | 'updated' | 'unchanged' | 'refused'; export interface InstallAction { id: string; target: string; outcome: ActionOutcome; detail: string; /** Required on every `refused` action: the flag or edit that resolves it. */ fix?: string; } export interface InstallResult { actions: InstallAction[]; /** True when nothing on disk needed to change. */ unchanged: boolean; refusals: number; dry_run: boolean; hooks_dir: string; settings_path: string; } export interface InstallOptions { projectDir: string; homeDir?: string; hooksDir?: string; templateDir?: string; /** `user` writes ~/.claude/settings.json; `project` writes /.claude. */ scope?: 'user' | 'project'; /** Overwrite a hook script this command can prove the user edited. */ force?: boolean; /** Compute every outcome and write nothing. */ dryRun?: boolean; nodePath?: string; cliEntry?: string; hookEntry?: string; } /** Cortex runtime artifacts, which must never enter git or the app graph. */ export declare const IGNORE_ENTRIES: readonly [".cortex.db", ".cortex.db-wal", ".cortex.db-shm", ".cortex.spool.jsonl", ".cortex.spool.jsonl.processing", ".cortex.state", ".cortex.agent-used", ".cortex.index", ".cortex.index.tmp-*", ".cortex.substitution", ".cortex.turn-reads"]; export interface BakedPaths { nodePath: string; cliEntry: string; hookEntry: string; } /** * The single rendering. Used to write a script and, indirectly, to decide * whether one on disk is still what we would have written. */ export declare function renderHookScript(templateText: string, paths: BakedPaths): string; /** * True when the installed text is the template with *some* set of paths * substituted — whatever those paths are. * * Built as one regex over the whole template rather than a re-render, because * a re-render needs to know which recovered path belongs to which placeholder, * and getting that mapping wrong reads as "user modified" for a file nobody * touched. A placeholder appearing more than once becomes a backreference, so * a script whose two Node references disagree is correctly seen as edited. * * Captures are `[^\n]+?`: a substituted path never spans a line, and bounding * them to one line keeps the match from swallowing unrelated content and keeps * backtracking linear. */ export declare function installedMatchesTemplate(templateText: string, installedText: string): boolean; export declare function hooksDirIsShellSafe(hooksDir: string): boolean; export type ScriptState = 'absent' | 'unmodified' | 'modified' | 'unknown'; /** * Whether an installed script is still ours. * * `unknown` is not a failure state. Every hook installed before Story 2.3 is * unstamped, and `doctor` names this command as the fix for exactly those — so * refusing on `unknown` would break the documented repair path for the most * common installation there is. It is backed up and overwritten instead. */ export declare function classifyInstalledScript(templateText: string, installedText: string): Exclude; type Json = Record; export interface MergeResult { value: Json; changed: boolean; } /** * Add each required wiring that is not already present. * * Presence is decided by `commandSatisfiesWiring`, not string equality: a user * who re-quoted the path or moved the hooks directory already has a working * wiring, and appending a second entry would double every hook invocation. */ export declare function mergeHookWiring(settings: Json, hooksDir: string, paths: BakedPaths, /** * Wirings already present in the *other* settings files Claude Code merges, * keyed by {@link wiringKey}. * * Claude Code reads the union of `/.claude/settings.json`, * `settings.local.json` and `~/.claude/settings.json`, so an entry written * here while an equivalent one lives in another file does not replace it — * both fire. That doubled every spool line, every reflex and every flush, * and neither `install` nor `doctor` could see it. * * Keyed by event PLUS discriminator, not by event name. Story 5.2 put a second * required wiring on `PreToolUse`, and an event-keyed set would have skipped * it on any machine where `PreToolUse` was wired in another merged file — * after which `doctor` fails `hook-wiring` and names `cortex install` as the * fix that had just declined to help. */ wiredElsewhere?: ReadonlySet): MergeResult; export declare function mergeMcpServer(settings: Json, entry: Json): MergeResult; /** Append missing ignore entries, matched line-exact after trimming. */ export declare function mergeIgnoreEntries(current: string, entries: readonly string[]): { text: string; added: string[]; }; /** * Write via a sibling temp file and rename. A half-written `settings.json` is * a Claude Code that will not start, and the window for that is exactly as * long as a naive `writeFileSync` takes. */ export declare function writeFileAtomic(target: string, content: string): void; export declare function runInstall(options: InstallOptions): InstallResult; export {}; //# sourceMappingURL=install.d.ts.map