/**
 * Shared snapshot bookkeeping for the JSON-file configurators.
 *
 * Every writer that edits a user's config in place has to answer one question
 * on each apply(): is the value sitting in the file right now the one *we* put
 * there, or one the user put there?
 *
 * Snapshotting only on first touch is not enough. The sentinel is persisted in
 * the user's file so a restore survives a crash, which means it also survives
 * an unclean kill where no restore ever ran. If the user then edits the block
 * by hand — reasonably, since the proxy is gone — a presence-only guard keeps
 * the stale snapshot, apply() overwrites their edit, and the next restore
 * writes the stale value back over it. For Qwen that value is a live API key.
 *
 * So each writer also records what it wrote. A current value that still matches
 * the recorded write is ours and the snapshot stands; anything else is the
 * user's and must be re-snapshotted before we overwrite it.
 */
/**
 * Whether apply() should record `current` as the user's original value.
 *
 * - No snapshot yet: record one. This is the first touch.
 * - Snapshot present but no record of what we wrote: leave it alone. The file
 *   was written by a version that predates the write-record, so we cannot tell
 *   ours from the user's and the safe move is the old behaviour.
 * - Snapshot present and the current value is exactly what we wrote: leave it
 *   alone. This is the repeat-apply case the first-touch guard exists for.
 * - Snapshot present and the current value is *not* what we wrote: re-record.
 *   The user replaced it while the proxy was not running.
 */
export declare function shouldCaptureSnapshot(args: {
    hasSnapshot: boolean;
    written: unknown;
    current: unknown;
}): boolean;
/**
 * Whether a decoded snapshot file is structurally usable.
 *
 * A snapshot on disk is not necessarily one we wrote: it can be truncated by a
 * full disk, hand-edited, or left over from another version. `JSON.parse` is
 * happy with `{}`, `[]`, `null` and `"text"`, and every one of those then reads
 * as "a snapshot whose recorded original is absent" — which restore paths treat
 * as "the user had nothing here", and act on by deleting the user's real
 * config. Requiring the discriminating key present makes a malformed file fall
 * through to the caller's no-snapshot branch, which refuses to destroy
 * anything, instead of impersonating an empty one.
 */
export declare function isUsableSnapshot<K extends string>(value: unknown, requiredKey: K): value is Record<K, unknown>;
/** Deep copy through JSON, so a snapshot cannot alias the object it describes. */
export declare function cloneForSnapshot<T>(value: T): T;
/**
 * Whether the value currently in the file is the one this writer put there.
 *
 * Restore uses this as its licence to act. A base-URL check alone catches a
 * user who repointed the client elsewhere, but not one who kept the proxy URL
 * and changed something beside it — a rotated key, an added field. Reverting
 * those to the snapshot destroys a deliberate edit; for Qwen it destroys a
 * credential. No record of what we wrote (a config from before this existed)
 * means we cannot prove ownership either way, so the caller keeps its previous
 * behaviour rather than refusing every restore.
 */
export declare function isProxyOwnedValue(args: {
    written: unknown;
    current: unknown;
}): boolean;
/**
 * The path a chain of symlinks finally names, which need not exist. A path
 * that is not a link comes back unchanged.
 */
export declare function resolveSymlinkTarget(filePath: string): Promise<string>;
/**
 * Replace a file's contents without ever exposing a partial one.
 *
 * `writeFileSync` opens with `O_TRUNC`, so from the truncate until the last
 * byte lands the user's config is short — and a real config spans several
 * syscalls, not an instant. Anything reading concurrently, the CLI the config
 * belongs to included, can load a truncated file; a crash in that window leaves
 * it truncated permanently. Both Qwen and OpenCode keep live API keys there.
 *
 * Writing to a sibling temp file and renaming closes it: `rename(2)` within a
 * directory is atomic, so a reader sees either the whole old file or the whole
 * new one. The temp file must be a sibling — a rename across filesystems is a
 * copy, which reintroduces exactly the window this removes.
 *
 * PERMISSIONS ARE THE SUBTLE PART, and getting them wrong here leaks API keys.
 *
 * Writing through a temp file changes who decides the destination's mode. A
 * plain `writeFileSync` over an existing file leaves that file's mode alone, so
 * a config the user had locked to 0600 stayed 0600. A rename replaces the inode,
 * so the destination inherits the TEMP file's mode instead — and a temp file
 * created without an explicit mode lands at 0666 minus umask, i.e. 0644 on a
 * default system. Left unhandled, making the write atomic would have quietly
 * widened every credential file it touched from 0600 to 0644.
 *
 * So when the caller does not specify a mode, the destination's current mode is
 * carried over, which reproduces `writeFileSync`'s behaviour exactly; a file
 * that does not exist yet starts at 0600 rather than whatever umask allows.
 *
 * The mode is applied at CREATE time, not after. `writeFileSync` followed by
 * `chmodSync` would put the credential bytes on disk at 0644 first and tighten
 * them a moment later — a window a local reader can win. The trailing `chmod`
 * remains only to pin the exact mode, since umask masks the create mode; by
 * then the file has never been readable more widely than its final mode.
 *
 * On Windows the guarantee holds but the failure mode differs, which is worth
 * stating because the obvious worry there is the wrong one. Node's `renameSync`
 * is `MoveFileExW` with `MOVEFILE_REPLACE_EXISTING`; replacing a file on the
 * same volume is an atomic directory-entry update, so a concurrent reader still
 * sees the whole old file or the whole new one and never a torn one. What
 * Windows adds is that the rename can *fail* — `EPERM`/`EBUSY` when a reader
 * holds the destination open — where POSIX would succeed. That path is safe:
 * the catch below removes the temp file and rethrows, leaving the previous
 * config intact for the caller to report on.
 *
 * The sibling rule above is what makes that true. `MOVEFILE_COPY_ALLOWED` is
 * also set, so a cross-volume rename silently degrades to copy-then-delete and
 * is NOT atomic. Moving the temp file to `os.tmpdir()` would look like a
 * tidy-up and would quietly restore the exact window this function exists to
 * close, on Windows only, where nobody here would see it.
 *
 * A config kept in a dotfiles repository is often a symlink into it, a setup
 * Grok documents and its own writer honours. A rename over the link would
 * replace the link with a regular file and leave the repository's copy stale,
 * so the write lands on the end of the link chain instead, which need not
 * exist yet. The temp file is that target's sibling, so the rename stays
 * atomic.
 */
export declare function writeFileAtomic(filePath: string, contents: string, mode?: number): Promise<void>;
