/** * Passive update notifier (change: add-zero-interaction-onboarding). * * Best-practice, npm/gh/brew-style: a cached, non-blocking, fail-silent check * against the npm registry that tells a human "a newer openlore is available" * on stderr — never blocks a command, never prints in CI or non-interactive * contexts, and is silenceable. The actual upgrade is a separate explicit step * (`openlore update`); this module only *notifies*. * * Design rules (so it can never harm the hot path): * - Reads a cached result and prints synchronously (instant); a stale cache is * refreshed in the background and is NOT awaited, so no command ever waits on * the network for the notifier. * - Suppressed unless stderr is a TTY (keeps agent/MCP/hook output clean), and * in CI, and when OPENLORE_NO_UPDATE_NOTIFIER / NO_UPDATE_NOTIFIER is set. * - Every network and disk operation is wrapped to never throw. * - 100% deterministic given an injected lookup/clock — no LLM, no new runtime * dependency (uses npm itself so all `.npmrc` transport settings apply). */ import { type PlatformCommand, type PlatformCommandRuntime } from '../../utils/platform-command.js'; /** Published package name on npm. */ export declare const PACKAGE_NAME = "openlore"; /** Direct fallback used only when the npm executable is absent. */ export declare const REGISTRY_LATEST_URL = "https://registry.npmjs.org/openlore/latest"; /** How long a cached check stays fresh before a background refresh. */ export declare const CHECK_INTERVAL_MS: number; /** Network budget for the background refresh. */ export declare const FETCH_TIMEOUT_MS = 2000; /** Default cache file: ~/.openlore/update-check.json (per the repo cache convention). */ export declare function defaultCacheFile(): string; export interface UpdateCache { /** Latest version seen at the last successful check. */ latest: string; /** Epoch ms of the last check attempt. */ checkedAt: number; } export interface NotifyOptions { /** Override the cache file (tests). */ cacheFile?: string; /** Override the environment (tests). Defaults to process.env. */ env?: NodeJS.ProcessEnv; /** Where the banner is written. Defaults to process.stderr. */ stream?: { write(s: string): void; isTTY?: boolean; }; /** Clock injection (tests). Defaults to Date.now. */ now?: () => number; /** Network fetcher injection (tests). Defaults to global fetch. */ fetcher?: (url: string, init?: { signal?: AbortSignal; }) => Promise<{ ok: boolean; json(): Promise; }>; /** Package-manager lookup injection (tests). Defaults to `npm view`. */ lookup?: VersionLookup; /** * npm-resolution injection (tests). The "npm is absent" case cannot be produced by emptying * `PATH` on Windows, where npm is found beside the running Node executable, so a test states * absence here instead of arranging it through the environment. */ npmRuntime?: PlatformCommandRuntime; /** True when the lookup must not keep the process alive. */ background?: boolean; /** Force the TTY decision (tests). */ isTTY?: boolean; } export interface VersionLookupOptions { signal: AbortSignal; background: boolean; } export type VersionLookup = (options: VersionLookupOptions) => Promise; /** Keep an unref'ed lookup from surviving the process that requested it. */ export declare function terminateChildOnParentExit(child: { killed: boolean; kill(): boolean; }): () => void; /** * Where the npm lookup runs. NEVER the analyzed repository: npm reads the `.npmrc` of its working * directory, and that file is repo-controlled — it could point the lookup at an arbitrary registry * and answer with a version of its choosing (mcp-security: repo-controlled configuration is * untrusted). The notifier speaks about the GLOBALLY installed `openlore`, so the user scope is * also the correct one: `~/.npmrc` still supplies a private registry, its credentials, the CA and * the proxy. */ export declare function npmViewCwd(): string; /** Build the npm lookup as fixed argv so npm remains the owner of `.npmrc`. */ export declare function npmViewInvocation(platform?: NodeJS.Platform, runtime?: PlatformCommandRuntime): PlatformCommand; /** * True when `latest` is strictly newer than `current` by semver core * (major.minor.patch). Prerelease tags are ignored — a stable release is the * only thing we nudge a user toward. Returns false on any unparseable input. */ export declare function isNewer(current: string, latest: string): boolean; /** * Read the version out of an answer, whatever shape it came in. * * `npm view @latest version --json` answers with a bare string on some npm releases and with * an ARRAY of the matched versions on others (npm 10/11 here: `["3.2.0"]`) — an array the first * version of this lookup dropped, so the check silently reported "no update" forever. The registry * endpoint answers with the `{ version }` document. All three are accepted; an array keeps its * highest version even if npm does not return the entries in order. */ export declare function readAnswerVersion(answer: unknown): string | null; /** Fetch the latest published version, or null on any failure (never throws). */ export declare function fetchLatestVersion(opts?: NotifyOptions): Promise; /** Refresh the cache from the network. Fail-silent; safe to fire-and-forget. */ export declare function refreshCache(opts?: NotifyOptions): Promise; /** The boxed banner shown to a human on stderr. */ export declare function formatBanner(current: string, latest: string): string; /** * Print an update banner if a newer version is cached, then refresh a stale * cache in the background (NOT awaited). Returns true if a banner was printed. * * Suppressed entirely (returns false, no network) when: a silencing env var is * set, CI is detected, or stderr is not a TTY — so agents, MCP servers, hooks, * and pipelines never see it. */ export declare function notifyIfUpdateAvailable(current: string, opts?: NotifyOptions): boolean; //# sourceMappingURL=update-notifier.d.ts.map