/**
 * Self-update install plumbing.
 *
 * The hard requirement: an upgrade must replace the copy that is currently
 * running. A bare `npm install -g` writes into the global prefix of whatever
 * `npm` PATH happens to resolve — on machines with more than one node
 * installation (nvm + Homebrew + vendored runtimes) that prefix can belong to
 * a different node than the one this copy lives under. The install then
 * "succeeds" while the running copy stays stale and re-prompts forever.
 *
 * So every step here is anchored to the running package root on disk, never
 * to PATH resolution.
 */
export declare const NPM_PACKAGE_NAME = "@phnx-labs/agents-cli";
/**
 * First published release that stopped the usage/auth-health probe from reading
 * Claude Code's interactive login (commit 3f3554c51). Pre-this versions on a
 * macOS box re-introduce the Touch ID storm + fleet-wide revocation class
 * (RUSH-2415 / RUSH-1822). Anything older is a latent regression while a fixed
 * copy sits next to it.
 */
export declare const TOUCH_ID_STORM_FIXED_SINCE = "1.22.30";
export type PackageManager = 'npm' | 'bun';
/**
 * The directory bun installs global packages into:
 *   <BUN_INSTALL>/install/global   (BUN_INSTALL defaults to ~/.bun)
 *
 * A globally-installed scoped package then lives at
 * `<bunGlobalDir>/node_modules/@phnx-labs/agents-cli` — note there is NO `lib`
 * segment, unlike npm's POSIX layout. That single difference is why an
 * npm-based upgrade silently misses a bun install (see deriveGlobalPrefix).
 */
export declare function bunGlobalDir(): string;
/**
 * Identify which package manager owns the install at `packageRoot`, so the
 * upgrade can shell out to the one that actually replaces this copy.
 *
 * bun lays a global package out as `<bunGlobalDir>/node_modules/<scoped pkg>`,
 * so the prefix (the parent of `node_modules`) is the bun global dir itself.
 * Everything else — npm's `<prefix>/lib/node_modules` and the Windows
 * `<prefix>/node_modules` — is treated as npm.
 *
 * Detection is path-based (no subprocess): it matches the resolved bun global
 * dir from BUN_INSTALL/$HOME, and falls back to the structural `.bun/install/
 * global` tail for a relocated BUN_INSTALL not exported into this process.
 */
export declare function detectPackageManager(packageRoot: string): PackageManager;
export interface UpdateCheckCache {
    lastCheck: number;
    latestVersion: string;
    dismissed?: string;
}
/** Read the cached update-check state from disk. Returns null if the file is missing or corrupt. */
export declare function readUpdateCache(file: string): UpdateCheckCache | null;
/**
 * Persist the latest known version and current timestamp. Preserves an
 * existing `dismissed` marker — the background refresh must not erase a
 * user's "Skip this version" choice, or they get re-prompted for the exact
 * version they dismissed.
 */
export declare function saveUpdateCheck(file: string, latestVersion: string): void;
/** Record that the user chose to skip `version`; suppresses prompts until a newer version appears. */
export declare function dismissUpdateVersion(file: string, version: string): void;
/** Whether the cached state warrants an upgrade prompt for a copy running `currentVersion`. */
export declare function shouldPromptUpgrade(cache: UpdateCheckCache | null, currentVersion: string): boolean;
/**
 * Short TTL for the multi-install PATH scan cache (RUSH-2324). The full
 * `findAgentsCliInstalls` walk over PATH + known roots costs ~1ms on a warm
 * box and runs on every ordinary CLI invocation via `maybeWarnMultiInstall`.
 * Same 5-minute window as the detached-sync spawn gate so both bootstrap
 * savings share one recency policy.
 */
export declare const MULTI_INSTALL_SCAN_TTL_MS: number;
/** On-disk shape for the multi-install scan cache (beside `.update-check`). */
export interface MultiInstallScanCache {
    scannedAt: number;
    /** Full PATH string at scan time — any change invalidates the cache. */
    pathEnv: string;
    runningRoot: string;
    runningVersion: string;
    inventory: MultiInstallInventoryEntry[];
}
/** Read a multi-install scan cache. Returns null if missing or corrupt. */
export declare function readMultiInstallScanCache(file: string): MultiInstallScanCache | null;
/** Persist a multi-install scan result. Best-effort. */
export declare function writeMultiInstallScanCache(file: string, cache: MultiInstallScanCache): void;
/**
 * Whether a multi-install scan cache is still usable for this invocation.
 * Invalid when missing, past TTL, PATH changed, or the running copy identity
 * (root / version) changed — those are exactly the cases where a re-scan can
 * surface a different inventory and re-fire the warning.
 */
export declare function isMultiInstallScanFresh(cache: MultiInstallScanCache | null, pathEnv: string, runningRoot: string, runningVersion: string, now?: number, ttlMs?: number): boolean;
/**
 * Resolve the multi-install inventory, hitting the on-disk scan cache when
 * fresh. Callers that only need the inventory (the bootstrap multi-install
 * warning) avoid the ~1ms PATH walk on the warm path (RUSH-2324).
 */
export declare function resolveMultiInstallInventory(runningRoot: string, runningVersion: string, pathEnv: string, cacheFile: string, opts?: {
    now?: number;
    ttlMs?: number;
    findOpts?: FindAgentsCliInstallsOptions;
}): MultiInstallInventoryEntry[];
/**
 * The on-disk package root of the copy that is currently running.
 *
 * For a plain JS install this is just `<__dirname>/..`. Under the compiled
 * standalone binary (shipped since 1.20.53) it is not: Bun sets `__dirname` to
 * its embedded virtual FS, so `<__dirname>/..` yields `/$bunfs` — a path that
 * exists nowhere. That phantom value was reported as a second install by the
 * multi-install check, and rejected by deriveGlobalPrefix as "not an
 * npm-managed install", so every self-upgrade from a compiled copy failed.
 *
 * The physical executable is `process.execPath`, which ships inside the
 * package (`<packageRoot>/dist/bin/agents`). Walk up from it to the directory
 * whose package.json actually names this package rather than assuming a fixed
 * depth, so a change to the dist layout surfaces as a clear throw here instead
 * of a wrong prefix that npm would happily install into.
 */
export declare function resolveRunningPackageRoot(dirname: string, execPath?: string): string;
/**
 * Derive the npm global prefix that owns the install at `packageRoot`.
 *
 * npm's global layout for a scoped package:
 *   POSIX:   <prefix>/lib/node_modules/@phnx-labs/agents-cli
 *   Windows: <prefix>/node_modules/@phnx-labs/agents-cli
 *
 * Throws when `packageRoot` is not inside a node_modules tree (e.g. running
 * from a source checkout) — there is no prefix to install into, and guessing
 * one is exactly the bug this module exists to prevent.
 */
export declare function deriveGlobalPrefix(packageRoot: string): string;
/**
 * Sweep npm arborist's "retired" staging dir for `packageRoot` before a
 * reify (PHNX-3393).
 *
 * npm (@npmcli/arborist) reifies an install by first renaming the tree it is
 * about to replace out of the way into a sibling directory —
 * `retirePath(from)` in arborist's own source names it
 * `.<basename>-<8-char sha1 hash of the full path>`, sibling to `from` — then
 * stages the new tree and renames it into place. Because the hash is a pure
 * function of `packageRoot`'s path, that staging path is IDENTICAL on every
 * reify of this install. A crash between the retire-rename and the final
 * rename (SIGKILL, a killed terminal, a box that lost power mid-upgrade)
 * leaves that exact directory behind, non-empty. `rename(2)` cannot replace a
 * non-empty directory, so every subsequent upgrade's reify fails ENOTEMPTY at
 * the same path forever — nothing about a plain retry ever clears it.
 *
 * Removing any stale `.<basename>-*` sibling before install self-heals this:
 * npm re-stages cleanly once the collision is gone. Matches only the
 * retire-path shape (a dot-prefixed sibling starting with the package's own
 * basename), so an unrelated dotfile in the same directory is left alone.
 * Best-effort per entry: one unremovable sibling must not block the rest.
 */
export declare function sweepStaleInstallStaging(packageRoot: string): Promise<string[]>;
/**
 * Install `spec` into an explicit global prefix. `--prefix` pins the
 * destination no matter which npm binary PATH resolves. `--ignore-scripts`
 * skips lifecycle scripts; the caller refreshes alias shims afterwards via
 * refreshAliasShims().
 *
 * `signal`, when passed, is wired into `execFile`'s own `signal` option —
 * Node kills the child process on abort and the returned promise rejects,
 * rather than the caller merely giving up on awaiting an orphaned process
 * (self-update-service.ts's daemon tick needs a real kill here: on a
 * deadline abort an un-killed `npm install -g` keeps writing into the same
 * global prefix a subsequent retry then installs into concurrently).
 */
export declare function installPackageIntoPrefix(spec: string, prefix: string, signal?: AbortSignal): Promise<void>;
/**
 * Install `spec` into bun's global store with `bun add -g`. bun writes to
 * `<bunGlobalDir>/node_modules/<pkg>`, which is exactly the running package
 * root for a bun install — so verifyInstalledVersion() sees the new version
 * in place. bun skips untrusted lifecycle scripts, so the caller refreshes
 * alias shims afterwards via refreshAliasShims() rather than relying on the
 * package's postinstall hook.
 *
 * `signal` behaves exactly as documented on {@link installPackageIntoPrefix}.
 */
export declare function installPackageWithBun(spec: string, signal?: AbortSignal): Promise<void>;
/**
 * Verify a downloaded tarball's bytes against a Subresource Integrity (SRI)
 * string of the form `sha512-<base64>` — npm's `dist.integrity`. Recomputes the
 * digest over the actual bytes with the named algorithm and compares it, in
 * constant time, to the decoded expected digest.
 *
 * Fails closed: a malformed SRI, an algorithm weaker than sha512, or any digest
 * mismatch throws. This is the gate that makes self-update refuse a tampered or
 * corrupted tarball *before* it is ever handed to a package manager to install.
 */
export declare function verifyTarballIntegrity(tarball: Buffer, integrity: string): void;
/**
 * Download the published tarball at `tarballUrl` and prove its bytes match
 * `integrity` before returning a path to it on disk. The returned .tgz is safe
 * to hand to `npm install`/`bun add` — it has been verified byte-for-byte
 * against the registry attestation. Fails closed: a non-200, a download error,
 * or a hash mismatch throws and no file path is returned, so the caller never
 * installs an unverified artifact.
 *
 * `signal`, when passed, aborts the fetch alongside the own `timeoutMs` timer
 * (whichever fires first) — a real cancellation of the in-flight request, not
 * just an abandoned await.
 */
export declare function downloadVerifiedTarball(tarballUrl: string, integrity: string, timeoutMs?: number, signal?: AbortSignal): Promise<string>;
/** Read the version field of the package.json at `packageRoot`, fresh from disk. */
export declare function readInstalledVersion(packageRoot: string): Promise<string>;
/**
 * Assert that the install at `packageRoot` now carries `expectedVersion`.
 * npm exiting 0 only proves it wrote *somewhere*; this proves it wrote *here*.
 */
export declare function verifyInstalledVersion(packageRoot: string, expectedVersion: string): Promise<void>;
/**
 * Re-run the freshly installed copy's postinstall in shims-only mode so the
 * bare-command aliases (secrets, sessions, ...) pick up the new entrypoint
 * and any aliases added in the new version. Best-effort: a failure here
 * leaves the previous shims in place, which still point at the (now
 * upgraded) package root.
 */
export declare function refreshAliasShims(packageRoot: string, signal?: AbortSignal): Promise<void>;
/** One global bin link the upgrade reconciled: what it is and what happened. */
export interface BinLinkRepair {
    /** The `package.json#bin` key (`agents`, `ag`, `browser`, `computer`). */
    name: string;
    /** `<prefix>/bin/<name>` — the PATH entry the box's shell resolves. */
    linkPath: string;
    /** Absolute path the link must resolve to (`<packageRoot>/<bin target>`). */
    target: string;
    /**
     * `ok` — already resolved to the freshly-installed target.
     * `repaired` — was missing / dangling / pointing elsewhere, now relinked.
     * `failed` — could not be made to resolve (see `error`).
     */
    action: 'ok' | 'repaired' | 'failed';
    /** Set only for `failed`: why the relink did not take. */
    error?: string;
}
/**
 * The global bin links the upgrade OWNS: `<prefix>/bin/<name>` for every
 * `package.json#bin` entry (`agents`, `ag`, `browser`, `computer`).
 *
 * npm creates these on a normal `install -g`, but a box with several installs
 * (or a reify interrupted after the old links were retired) can end an upgrade
 * with the package at the new version and these links **missing** — the state
 * that stranded zion (PHNX-2768): `/opt/homebrew/lib/node_modules/...` at
 * 1.22.40 but `/opt/homebrew/bin/{agents,ag,browser,computer}` gone, so every
 * `agents` invocation was "command not found" until the links were relinked by
 * hand. The rollout probe reported it `unverified`; the box was left broken.
 *
 * So the upgrade verifies these links right after installing and **restores any
 * that are wrong** — covering the sibling entrypoints, not just `agents`, since
 * `ag`/`browser`/`computer` share the same failure. Each link is reconciled
 * independently; a link that cannot be made to resolve is reported `failed` so
 * the caller can fail loud (the rollout then marks the box failed, not merely
 * unverified) instead of returning a box the package upgraded but cannot run.
 *
 * POSIX only — Windows npm bins are `.cmd`/`.ps1` shims, not symlinks, so this
 * relink shape does not apply and the caller skips it there. `prefix` is the
 * npm global prefix from {@link deriveGlobalPrefix}; the bun path uses its own
 * bin layout and is out of scope.
 */
export declare function ensureGlobalBinLinks(packageRoot: string, prefix: string): Promise<BinLinkRepair[]>;
export interface AgentsCliInstall {
    /** The PATH entry (`<dir>/agents`) that resolves to this install, when found through PATH. */
    binPath?: string;
    /** Package root containing package.json and dist/. */
    packageRoot: string;
    version: string;
    /** Whether this copy uses the serialized, atomic helper-bundle installer. */
    atomicHelperInstall: boolean;
}
export interface FindAgentsCliInstallsOptions {
    homeDir?: string;
    fnmDir?: string;
    npmCacheDir?: string;
    globalNodeModulesDirs?: string[];
}
export interface MultiInstallInventoryEntry {
    packageRoot: string;
    version: string;
    note: string;
    /** True for the copy that is currently executing. */
    running: boolean;
    /**
     * True when a bare `agents doctor --fix` deletes this copy (RUSH-2415:
     * npx-cache / unsafe-legacy / pre-1.22.30 with a fixed peer). False for the
     * running copy and for any duplicate --fix will not touch — a healthy
     * >=1.22.30 peer, or a pre-1.22.30 copy with no fixed peer to fall back to;
     * both need the manual command from manualUninstallCommand() (RUSH-2705/2713).
     */
    autoPurgeable: boolean;
}
/**
 * The exact shell command that removes the install at `packageRoot` by hand —
 * the remedy for duplicates `doctor --fix` deliberately will not purge
 * (RUSH-2705). `--prefix` pins the target tree no matter which npm binary
 * PATH resolves, mirroring installPackageIntoPrefix.
 */
export declare function manualUninstallCommand(packageRoot: string): string;
export declare function buildMultiInstallInventory(runningRoot: string, runningVersion: string, installs: AgentsCliInstall[]): MultiInstallInventoryEntry[];
/** Why a discovered install is safe to delete without an interactive confirm. */
export type RemovableInstallReason = 'npx-cache' | 'unsafe-legacy-helper' | 'pre-fixed-version';
export interface RemovableAgentsCliInstall {
    packageRoot: string;
    version: string;
    reasons: RemovableInstallReason[];
}
export interface PurgeRemovableInstallsResult {
    removed: RemovableAgentsCliInstall[];
    failed: Array<RemovableAgentsCliInstall & {
        error: string;
    }>;
    skippedRunning: number;
}
/** True when the package root lives under npm's `_npx` cache (ephemeral runs). */
export declare function isNpxCacheInstall(packageRoot: string): boolean;
/**
 * A release string that is at least TOUCH_ID_STORM_FIXED_SINCE, or a side-by-side
 * dev build (`0.0.0-dev.*`) which tracks main and therefore carries the fix.
 * Non-semver junk never qualifies as "fixed" — better to leave a weird copy
 * alone than delete the only working install.
 */
export declare function isTouchIdStormFixedVersion(version: string): boolean;
/**
 * Classify discovered installs that doctor --fix / upgrade may delete.
 *
 * Never marks the running package root. Auto-purge is limited to copies that
 * cannot be the intended primary install:
 *   - npx-cache trees (ephemeral)
 *   - pre-atomic-helper-installer trees ("unsafe legacy helper installer")
 *   - pre-TOUCH_ID_STORM_FIXED_SINCE trees, but only when at least one fixed
 *     copy already exists on the box (so we never strand the machine)
 *
 * A pre-fixed copy that is also the only install stays — the user must upgrade
 * it in place rather than delete it.
 */
export declare function classifyRemovableAgentsCliInstalls(runningRoot: string, installs: AgentsCliInstall[], opts?: {
    fixedSince?: string;
}): RemovableAgentsCliInstall[];
/**
 * Delete classified removable package roots from disk. Re-reads package.json
 * immediately before the unlink so a path that is no longer @phnx-labs/agents-cli
 * is never removed. Best-effort: one failure does not stop the rest.
 */
export declare function purgeRemovableAgentsCliInstalls(candidates: RemovableAgentsCliInstall[], opts?: {
    dryRun?: boolean;
    runningRoot?: string;
}): PurgeRemovableInstallsResult;
/** A detected duplicate that `doctor --fix` will not purge (RUSH-2705). */
export interface UnresolvedDuplicateInstall {
    packageRoot: string;
    version: string;
    /** Exact shell command that removes this copy by hand. */
    manualRemoveCommand: string;
}
export interface RemediateStaleInstallsResult extends PurgeRemovableInstallsResult {
    inventory: MultiInstallInventoryEntry[];
    candidates: RemovableAgentsCliInstall[];
    /**
     * Duplicates detected but NOT auto-purged, for either reason:
     *   1. a healthy >=1.22.30 global (safe, just redundant), or
     *   2. a pre-1.22.30 copy left alone only because no fixed peer exists to fall
     *      back to (the anti-stranding guard) — this one is genuinely vulnerable,
     *      not healthy.
     * Either way the caller must surface manualRemoveCommand — otherwise the
     * multi-install warning keeps firing with no working remedy (RUSH-2705/2713).
     */
    unresolved: UnresolvedDuplicateInstall[];
}
/**
 * Scan + classify + purge in one call. Used by `agents doctor --fix` and
 * `agents upgrade` so both paths remediate the same set of latent copies.
 */
export declare function remediateStaleAgentsCliInstalls(opts: {
    runningRoot: string;
    runningVersion?: string;
    pathEnv?: string;
    findOpts?: FindAgentsCliInstallsOptions;
    dryRun?: boolean;
}): RemediateStaleInstallsResult;
/**
 * Resolve every `agents` entrypoint on PATH, then inspect the bounded global
 * install layouts used by NVM, fnm, Volta, Bun, npm, and npx. More than one
 * distinct package root means upgrades,
 * shims, and the command the user types can act on different copies — the
 * divergence behind silently-failing self-updates.
 *
 * npm bin entries are symlinks that resolve to `<packageRoot>/dist/index.js`
 * (the dev install's `~/.local/bin/agents` chains through the dev prefix to
 * the same shape). A shim pointed at the compiled standalone binary resolves
 * to `<packageRoot>/dist/bin/agents` instead; that shape counts too, otherwise
 * the copy that actually runs — the compiled one is typically first on PATH —
 * is invisible to this scan and its root looks like a separate install.
 * Anything that resolves to neither shape inside a package named
 * @phnx-labs/agents-cli is some other tool and is skipped.
 * POSIX-only: Windows npm bins are .cmd wrappers, not symlinks.
 */
export declare function findAgentsCliInstalls(pathEnv: string, opts?: FindAgentsCliInstallsOptions): AgentsCliInstall[];
