/** * `jdcodec doctor` — first-run diagnostic for the local connector. * * Answers one question for a customer: "is JD Codec installed, * authenticated, and able to reach the cloud?" Each probe runs * independently and contributes a single line to the report; failures * never abort the run, so the customer sees the full picture in one * pass instead of having to re-run after each fix. * * Probes are pure-ish: each takes the IO and config it needs as * parameters, which makes them easy to unit-test with synthetic * inputs. The orchestrator at the bottom wires defaults for * production use. * * Exit code rules: * 0 — all probes ok (warnings allowed) * 1 — at least one probe failed * 2 — reserved for catastrophic doctor failure (uncaught throw) */ import { DisplayIO } from "./display.js"; import { type UpdateVerdict } from "./update-check.js"; export type CheckStatus = "ok" | "warn" | "fail"; export interface CheckResult { name: string; status: CheckStatus; /** One-line summary printed next to the status badge. */ detail: string; /** Optional multi-line fix instructions printed indented under the line. */ hint?: string; /** Optional docs URL appended after the hint. */ docsLink?: string; } export interface SpawnResult { exitCode: number | null; stderr: string; stdout: string; /** True if the process timed out before exiting. */ timedOut?: boolean; } export interface DoctorIO { /** Defaults to `process.versions`. */ processVersions?: { node: string; }; /** Defaults to `process.env.PATH`. */ pathEnv?: string; /** Defaults to `which ` lookup via spawnSync. */ which?: (cmd: string) => string | null; /** Defaults to `which -a ` lookup via spawnSync. Returns every * matching path on PATH (deduped, in PATH order). */ whichAll?: (cmd: string) => string[]; /** Defaults to spawning a real subprocess. */ spawnAsync?: (cmd: string, args: string[], timeoutMs: number) => Promise; /** Defaults to fs.existsSync + readFileSync. */ readFile?: (path: string) => string | null; /** Defaults to `~/.jdcodec/config.json`. */ configPath?: string; /** Defaults to `process.env.JDC_API_KEY`. */ apiKeyEnv?: string | undefined; /** Defaults to `https://api.jdcodec.com` (or JDC_CLOUD_URL). */ cloudUrl?: string; /** Defaults to globalThis.fetch. */ fetchImpl?: typeof fetch; /** Defaults to crypto.randomUUID. */ generateRequestId?: () => string; /** Defaults to defaultDisplay (writes to stdout). */ display?: DisplayIO; } export declare function probeNodeVersion(version: string): CheckResult; export declare function probeNpx(whichResult: string | null): CheckResult; export declare function probePlaywrightMcp(spawnAsync: (cmd: string, args: string[], timeoutMs: number) => Promise): Promise; export declare function probeConnectorVersion(): CheckResult; /** * Surfaces an advisory warning when the npm registry advertises a newer * `jdcodec` than this build. Never returns `fail` — being out of date * is not broken. Test hook lets the suite inject a verdict directly. */ export declare function probeUpdateAvailable(verdictOverride?: UpdateVerdict): Promise; export interface ConfigProbeOutput { result: CheckResult; /** Resolved api key (env wins over file), or null. Returned so the * key-shape probe doesn't have to re-do this work. */ apiKey: string | null; /** Where the api key was found, for downstream messaging. */ source: "env" | "file" | null; } export declare function probeConfigFile(configPath: string, apiKeyEnv: string | undefined, readFile: (path: string) => string | null): ConfigProbeOutput; export declare function probeKeyShape(apiKey: string | null): CheckResult; export interface CloudProbeInput { cloudUrl: string; apiKey: string | null; fetchImpl: typeof fetch; generateRequestId: () => string; timeoutMs?: number; } /** * Sends a deliberately malformed `POST /v1/snapshot` with the bearer * attached. Auth runs before body validation in the worker, so: * - 400 malformed_request → AUTH OK (the only failure was our body) * - 401 auth_invalid / auth_revoked → AUTH FAIL (specific reason) * - 5xx → upstream issue, can't tell — report as warn * - network error / timeout → report as warn * * Cheap by design: no session is created, no usage event emitted, * because the request never reaches the snapshot handler past auth. */ export declare function probeCloudAuth(input: CloudProbeInput): Promise; export declare function probeNpmGlobalPath(pathEnv: string): CheckResult; export declare function probeMultipleBinaries(paths: string[]): CheckResult; /** * Detects a globally-installed `jdcodec` (via `npm install -g`) at a * version that disagrees with the running connector. This is the * silent-shadowing failure mode where `npx jdcodec` (with no version * spec) finds the global install first and uses it, instead of * fetching the latest from the registry. The shadowed-old binary * may pre-date features the customer expected to have, producing * confusing errors that look like configuration problems but are * really version-shadow problems. * * Warns when: * - npm is on PATH (else we can't probe) * - `npm ls -g --depth=0 --json` lists `jdcodec` * - the listed version is not the same as the running connector * * Best-effort: any spawn / parse failure is silently downgraded to * an "ok" with `npm not probed` — we don't want this probe to be * the source of false alarms when it's the diagnostic that's broken. */ export declare function probeGlobalNpmConflict(whichNpm: string | null, spawnAsync: (cmd: string, args: string[], timeoutMs: number) => Promise, runningVersion: string): Promise; export declare function renderCheck(result: CheckResult, display: DisplayIO): void; export declare function summariseExitCode(results: CheckResult[]): number; export declare function runDoctor(opts?: DoctorIO): Promise;