export declare const PACKAGE_NAME = "llm-relay"; /** Re-exec marker: set on the child so an update can never recurse. */ export declare const SUPPRESS_ENV = "LLM_RELAY_NO_SELF_UPDATE"; /** How long a registry answer is reused before we ask again. */ export declare const CHECK_INTERVAL_MS: number; export type InstallKind = "global" | "dev" | "managed"; /** * How the CALLER classified this invocation. A `read-only` command (a status * query, a help screen) must never be the moment a global package is replaced * and the process re-execed; only a `mutating` one may be. * * This module deliberately cannot derive the classification itself: the * subcommand table lives in `cli.ts`, which already imports this module, so * importing that table back would be a cycle. It arrives as a RUNTIME * PARAMETER instead, and its absence means `read-only` — this module never * guesses that an invocation is a safe moment to update. */ export type CommandEffect = "read-only" | "mutating"; export interface UpdateCache { checkedAt: number; latest: string; } /** True only for a full-string semver — the gate every registry- or cache-supplied version must clear. */ export declare function isValidVersion(v: unknown): v is string; /** -1 / 0 / 1, semver-ordered for the subset we publish. Unparseable sorts equal. */ export declare function compareVersions(a: string, b: string): number; export declare function isOutdated(current: string, latest: string): boolean; /** * A global install is one whose package root sits under the npm global root. * A checkout with a `.git` dir is a dev tree (never npm-managed). Anything else * — npx cache, a project-local dependency — is `managed`: notify, don't touch. */ export declare function classifyInstall(packageRoot: string, globalRoot: string | null): InstallKind; /** Bin *names* declared by a package.json `bin` field (string or map form). */ export declare function binNames(pkg: { name?: string; bin?: string | Record; }): string[]; /** * Shim files to delete after an update: every bin the OLD version installed but * the NEW one no longer declares, in each of npm's shim spellings. */ export declare function staleShimFiles(binDir: string, previous: string[], current: string[], present: string[]): string[]; export declare function readPackageJson(root: string): { name?: string; version?: string; bin?: string | Record; }; export declare function currentVersion(): string; /** * Where the 6h npm-version check caches its answer. * * ⚠ Under vitest, never touch the developer's real update-check cache — a test run would * otherwise suppress or force a real upgrade prompt. Exported so * `test/persistent-paths-vitest.test.ts` can assert the redirect DIRECTLY rather than inferring * it from a read that returns null either way. */ export declare function defaultUpdateCachePath(): string; export declare function readCache(now: number): string | null; /** Latest published version, or null on any network/parse failure (fail-open). */ export declare function fetchLatestVersion(): Promise; /** Delete shims left over from bins the new version no longer declares. */ export declare function pruneStaleShims(binDir: string | null, previous: string[], current: string[]): string[]; /** * True when this invocation should consult the registry at all. * * The decision is the CALLER's: `classification` says whether this invocation * is a moment at which replacing the global install and re-execing is * acceptable. It defaults to `read-only`, so a caller that has not classified * its command yet gets the safe answer — a status query like `llm-relay keys` * must not rewrite the user's global install underneath them. * * `argv` may only ever SUPPRESS the check (help/version stay instant and * offline even if the caller classified the invocation as mutating). It can * never promote one, so this module cannot decide on its own that an update is * safe here. */ export declare function shouldCheckUpdates(argv: string[], env: NodeJS.ProcessEnv, classification?: CommandEffect): boolean; /** The seams `installGlobalUpdate` needs, so it can be exercised without touching a real install. */ export interface GlobalInstallDeps { /** Run npm. Takes an ARGV ARRAY — never a command string (see `npm()`). */ run: (args: string[]) => { ok: boolean; stdout: string; stderr: string; }; /** Create a scratch dir for the proof tarball, or "" when one cannot be made. */ stage: () => string; /** Absolute paths of the tarballs sitting in a staging dir. */ staged: (dir: string) => string[]; /** Remove a staging dir. */ discard: (dir: string) => void; /** Delete the shims of bins the new version will not re-declare. */ pruneShims: (previous: string[]) => void; notify: (msg: string) => void; } export interface GlobalInstallOutcome { ok: boolean; /** npm's stderr from the attempt that decided the outcome. */ stderr: string; /** * True only in the one state that must never be reported as "continuing on * the old version": the working global package was removed and neither its * replacement nor the old version could be put back. */ missingInstall: boolean; } /** * Install `latest` over the current global package. * * The EEXIST branch used to `npm uninstall -g` the WORKING package and then * retry the install; a transient registry or network failure on that retry left * the user with no binary at all, while stderr claimed we were "continuing on" * the version that had just been deleted. This binary sits in the path of every * agent session, so that blast radius is total. * * The removal is therefore REORDERED behind a proof: `npm pack` puts the exact * replacement tarball on local disk first. If that fails, nothing is removed * and the working install is untouched. If it succeeds the reinstall reads that * local file, so it no longer depends on the registry at all — and a rollback to * `current` is still attempted if even the local install fails, so the only way * to end with no install is for three npm invocations in a row to fail. */ export declare function installGlobalUpdate(latest: string, current: string, previousBins: string[], deps: GlobalInstallDeps): GlobalInstallOutcome; /** * Compare against the registry and, for a global install, replace it in place * and re-exec. Returns only when the process should continue on this version. */ export declare function ensureUpToDate(now?: number): Promise;