/** * M84 — the doctor primitive: mechanism only. * * ## The absence this closes * * There was no resolved-state report. `theokit info` answers "does my project parse?"; the question * an agent product actually has is different — **what will this installation do?** Which credential, * which config layers, which trust posture, which sandbox, which MCP servers, which skills, which * hooks. * * ## The hard rule: never print a secret * * A doctor that prints secrets is a doctor nobody can paste into an issue — so the one command built * for support becomes the one command you must not share. A credential is therefore reported as * `present`, `absent` or `unreadable`, and never as a value: not the value, not a prefix, not a * truncation, not its length. * * A truncated key keeps its most identifying bytes, and `sk-ant-…` in a public issue names the * account it belongs to. A length narrows a brute force and identifies the provider. Neither is ever * useful in a bug report, and both are free to leak. * * ## What stays the product's * * The LIST of checks. This module holds the quartet — `Check`, `Diagnosis`, `diagnose`, * `renderDiagnosis` — because that is the part every product re-derives identically. Which things to * check is exactly the part that differs per product, and absorbing it would make this a framework * for one app. */ /** What a single check found. */ interface Check { /** What was examined, as a human would name it: `credential`, `sandbox`, `mcp`. */ readonly name: string; /** * `warn` is deliberately NOT a failure. "no MCP servers configured" is worth saying and is not * broken — counting it as a failure makes a green install exit non-zero, and CI learns to ignore * the command. */ readonly status: 'ok' | 'warn' | 'fail'; /** One line a human can act on. Never a secret — see {@link secretPresence}. */ readonly detail: string; } interface Diagnosis { readonly checks: readonly Check[]; /** How many checks failed. Warnings are not counted. */ readonly failed: number; /** `0` when the installation is usable. See the note on the empty case. */ readonly exitCode: number; } /** * Aggregate checks into a verdict. * * An EMPTY list exits non-zero. That is not pedantry: a product whose check list failed to load * would otherwise report a clean bill of health for an installation nobody examined — and "no checks * ran" is a different fact from "everything passed", which is the distinction this whole module is * about. */ declare function diagnose(checks: readonly Check[]): Diagnosis; /** * Render a diagnosis as text a human reads and pastes. * * Plain text with no colour: the output's destination is an issue, a CI log or a terminal that may * not support colour, and escape codes in a pasted report are noise between the reader and the fact. */ declare function renderDiagnosis(diagnosis: Diagnosis): string; /** What a credential looks like from the outside, with the value never leaving. */ type SecretPresence = 'present' | 'absent' | 'unreadable'; /** * Report whether a secret is there — and nothing else about it. * * `unreadable` is its own state rather than folded into either neighbour: a file that exists but * cannot be read is neither present nor absent, and collapsing it sends the operator to the wrong * fix — provisioning a key they already have, or debugging a permission problem they do not have. * * An EMPTY value counts as absent. `OPENAI_API_KEY=` is how a key gets unset in practice, and * reporting it present sends an operator hunting for a network fault behind a 401. */ declare function secretPresence(value: string | undefined | Error): SecretPresence; /** * M84 — route the SDK's diagnostics somewhere, from an env var. * * `setDiagnosticsSink` already crossed as a pass-through. A seam whose ONLY use is "send this to * stderr, or to a file when debugging" should ship with that use — otherwise every product writes * the same thirty lines, and each one picks a different env var name, so the instruction in a bug * report ("set THEOKIT_DEBUG and re-run") is wrong for half the products built on the framework. */ /** Where diagnostics went, so a caller can say so. */ type DiagnosticDestination = { readonly kind: 'off'; } | { readonly kind: 'stderr'; } | { readonly kind: 'file'; readonly path: string; }; interface InstallDiagnosticSinkOptions { /** The environment to read. Injected so a test never depends on the ambient one. */ readonly env?: Readonly>; /** Where a write failure is reported. */ readonly onWarn?: (message: string) => void; /** * How the sink is installed. Defaults to the SDK's `setDiagnosticsSink`. * * Injected so a test can DRIVE the installed sink rather than assert that installation returned * a shape. The first version of the test asserted that no warning had fired — which was true only * because nothing had ever called the sink, and would have passed against a sink that swallowed * every failure. */ readonly install?: (sink: (message: string) => void) => void; } /** * Install the diagnostics sink from `THEOKIT_DIAGNOSTICS`. * * Returns where it went, rather than logging it: a function that announces itself on stdout would * corrupt the output of any command whose result is piped. */ declare function installDiagnosticSink(options?: InstallDiagnosticSinkOptions): DiagnosticDestination; export { type Check, type Diagnosis, type DiagnosticDestination, type InstallDiagnosticSinkOptions, type SecretPresence, diagnose, installDiagnosticSink, renderDiagnosis, secretPresence };