/**
 * Cross-platform secure credential storage.
 *
 * macOS: every keychain operation goes through the signed `Agents CLI.app`
 * helper. The helper attaches a biometry-or-passcode access control to every
 * item it writes, so the OS itself gates decryption with Touch ID. A single
 * LAContext lives for the helper's process lifetime, so a batch read pops
 * Touch ID once and reuses the assertion for every item in the same batch.
 * No /usr/bin/security fast path: that path bypasses the helper's ACL,
 * exposes items to the legacy password sheet, and would defeat the model.
 *
 * Linux: libsecret (GNOME Keyring) via the `secret-tool` CLI. No biometry —
 * items are unlocked when the keyring is open.
 *
 * Windows: Windows Credential Manager (CRED_TYPE_GENERIC,
 * CRED_PERSIST_LOCAL_MACHINE) via a PowerShell P/Invoke shim, with the same
 * AES-256-GCM encrypted-file fallback used on Linux when the credential store
 * is unreachable (no logon session / no powershell.exe). No biometry.
 *
 * Items are device-local: the biometry access control requires the OS to
 * treat them as bound to this device, so cross-machine propagation goes
 * through the explicit export/import flow in src/lib/secrets/sync.ts
 * rather than the system's cloud-keychain path.
 */
import { type SpawnSyncOptions, type SpawnSyncReturns } from 'child_process';
import type { NativeImportReport } from './fallback.js';
export type { NativeImportReport, NativeImportResult, NativeImportStatus } from './fallback.js';
export declare const SECRETS_ITEM_PREFIX = "agents-cli.secrets.";
/**
 * Thrown when a keychain helper / security spawnSync is killed because it
 * exceeded its timeout. A wedged coreauthd / LocalAuthentication dialog can hang
 * the parent forever; this makes the failure explicit and arms the read back-off.
 */
export declare class KeychainHelperTimeoutError extends Error {
    readonly bin: string;
    readonly args: string[];
    constructor(bin: string, args: string[]);
}
/** Test seam: suppress the side-effect daemon boot in unit tests. */
export declare function setKeychainDaemonBootForTest(enabled: boolean): void;
/** Test seam: exercise the timeout wrapper with an arbitrary binary. */
export declare function spawnKeychainHelperForTest(bin: string, args: string[], opts: SpawnSyncOptions, timeoutMs: number): SpawnSyncReturns<Buffer>;
/** Supported secret resolution backends. */
export type SecretProvider = 'keychain' | 'env' | 'file' | 'exec';
/** A typed reference to a secret, consisting of a provider and a provider-specific value. */
export interface SecretRef {
    provider: SecretProvider;
    value: string;
}
/**
 * A bundle value: either a string (literal or provider-prefixed ref) or
 * an object `{value: string}` used to escape a literal that would otherwise
 * be parsed as a ref (e.g. a URL that happens to start with 'env:').
 */
export type BundleValue = string | {
    value: string;
};
/** Parse a bundle value into either a literal string or a typed secret ref. */
export declare function parseBundleValue(raw: BundleValue): {
    literal: string;
} | {
    ref: SecretRef;
};
/** Serialize a secret ref back to its `provider:value` string form. */
export declare function serializeRef(ref: SecretRef): string;
/**
 * Guard a secret value before it is written to the current platform's primary
 * backend.
 *
 * A value is empty on every platform → always rejected. Embedded newlines are
 * rejected ONLY on darwin: the macOS batch read path (`get-batch`, see
 * getKeychainTokens) is newline-delimited, so a value with a newline would
 * corrupt record framing on read. Linux (secret-tool), Windows (Credential
 * Manager stores the raw UTF-8 blob and emits base64), and the encrypted-file
 * fallback all store raw bytes and round-trip multiline values (PEM / SSH keys)
 * faithfully, so they accept newlines. `platform` is injectable for tests.
 */
export declare function assertValueStorable(value: string, platform?: NodeJS.Platform): void;
/** Build the keychain item name for a profile provider token. */
export declare function profileKeychainItem(provider: string): string;
/** Build the keychain item name for a secrets-bundle key. */
export declare function secretsKeychainItem(bundle: string, key: string): string;
/**
 * Test seam: lets bundle storage tests swap the keychain backend for an
 * in-memory map without touching the user's real keychain. Mocking is
 * justified here because the alternative (touching real keychain in unit
 * tests) is destructive and would require an interactive Keychain unlock.
 */
export interface KeychainBackend {
    has(item: string): boolean;
    get(item: string): string;
    /** `opts.noAcl` mirrors setKeychainToken's no-ACL write path so tests can
     * assert which path a write took; in-memory backends may ignore it. */
    set(item: string, value: string, opts?: {
        noAcl?: boolean;
    }): void;
    delete(item: string): boolean;
    list(prefix: string): string[];
}
/** Install a custom keychain backend (test only). Returns the previous backend so callers can restore. */
export declare function setKeychainBackendForTest(b: KeychainBackend | null): KeychainBackend | null;
/** True when a test backend is installed (real keychain / biometry bypassed).
 * Callers that gate on the live secrets-agent broker use this to stay hermetic —
 * with an in-memory backend there is no real keychain to dedup, so the broker
 * fast-path must not engage. Always false in production (`backend` is null). */
export declare function isKeychainBackendOverridden(): boolean;
export declare const HMAC_KEY_ITEM = "agents-cli.hmackey";
interface HmacKeyRecord {
    v: number;
    /** 64 hex chars — the raw HMAC-SHA256 key. */
    k: string;
    /** True once the one-time re-key has moved every cleartext-named item. */
    migrated: boolean;
    /** Old cleartext services whose hashed copies are verified but whose
     * originals are not yet deleted (crash-resume list; deletes are silent). */
    pendingDeletes?: string[];
    /** True once this record has been re-stored no-ACL to heal a hmackey item that
     * an OLD helper (pre the metadata/hmackey no-ACL migration fix) re-stamped with
     * a biometry ACL. Set on the first read that heals it, so the heal runs exactly
     * once per machine and never churns the keychain afterward. */
    healedNoAcl?: boolean;
}
/** Force hashed service names on with a fixed key (test only). Pass null to
 * restore lazy production resolution. Composes with setKeychainBackendForTest
 * so unit tests exercise the exact transform production uses. */
export declare function setKeychainServiceHashingForTest(key: Buffer | null): void;
/**
 * Run `fn` with service-name hashing suspended: every primitive uses the
 * literal names it is given. For migration flows ONLY — they enumerate raw
 * names from the helper (which may be pre-re-key cleartext leftovers) and must
 * read/delete those exact items, not their hashed transforms.
 */
export declare function withRawKeychainServiceNames<T>(fn: () => T): T;
/** The hashed (storage) service name for a cleartext item name. Exported for
 * the re-key migration and tests; runtime callers go through the primitives,
 * which apply this transparently. */
export declare function hashedServiceName(item: string, key: Buffer): string;
export declare function readHmacKeyRecord(): HmacKeyRecord | null;
/**
 * Heal a `hmackey` item that an OLD helper (pre the metadata/hmackey no-ACL
 * migration fix) re-stamped with a biometry ACL. Such an item makes EVERY hashed
 * keychain lookup pop the generic "Agents CLI needs to authenticate" sheet,
 * because the HMAC key is read before every hashed name resolves. The migration
 * fix stopped the re-stamping but never un-stamped an already-damaged item, and
 * nothing else re-stores it once hashing is already active — so it prompts forever.
 *
 * This re-stores the record no-ACL exactly once per machine (guarded by
 * `healedNoAcl`), turning every future read silent. The read that produced `rec`
 * has already happened (and already prompted if it was ACL'd); this only writes.
 * Returns true if it healed. Exported for tests. No-op when already healed.
 */
export declare function healHmacKeyNoAclOnce(rec: HmacKeyRecord): boolean;
/**
 * The storage-layer service name for `item`: hashed when hashing is active,
 * the item itself otherwise. For callers that mix helper-enumerated
 * (already-hashed) names with computed cleartext names in one lookup map —
 * see readAndResolveBundleEnv.
 */
export declare function keychainServiceAlias(item: string): string;
/**
 * One-shot per process: activate hashing on machines with nothing to move,
 * finish a crash-interrupted delete phase (silent), and run the interactive
 * one-time re-key when cleartext-named items exist and a human is present.
 * Never throws — a failed attempt leaves the process on cleartext names
 * (exact pre-#316 behavior) and the next process retries.
 */
export declare function maybeAutoRekey(): void;
/** One re-keyed (or failed) item, by its old cleartext service name. */
export interface RekeyPlanItem {
    oldService: string;
    newService: string;
    /** Preserve the no-ACL write path for `never`-policy bundle items. */
    noAcl: boolean;
    /** Replacement payload (bundle metadata gets `name` injected); absent = copy verbatim. */
    payload?: string;
}
/**
 * Build the old→new mapping for a set of cleartext services. Bundle metadata
 * is parsed first to (a) recover each bundle's prompt policy — the persisted
 * `tier` token, where `none`/`never` means the item must be re-written through
 * the no-ACL path — and (b) inject the cleartext `name` into the JSON, because
 * after hashing the service name can no longer carry it (listBundles reads it
 * back from the payload). A value item's tier is resolved from its bundle's
 * metadata PAYLOAD in `values` — under the cleartext metadata name or its
 * hashed transform — never from the metadata item being part of the same
 * `services` batch: a --prefix run can scope a bundle's value items alone, and
 * rekeyServiceNames supplies the out-of-scope metadata reads (see the
 * supplemental batch there). Exported for unit tests.
 */
export declare function computeRekeyPlan(services: string[], values: Map<string, string>, key: Buffer): {
    items: RekeyPlanItem[];
    unreadable: string[];
};
/** Outcome of one rekeyServiceNames run. */
export interface RekeyReport {
    /** Old cleartext services whose items now live under hashed names. */
    migrated: string[];
    failed: Array<{
        item: string;
        detail: string;
    }>;
    /** True when hashed naming is on after this run. */
    activated: boolean;
    nothingToDo: boolean;
}
/**
 * The one-time re-key: move every cleartext-named `agents-cli.*` item to its
 * hashed service name. Composed entirely from the existing helper primitives —
 * no new Swift command:
 *
 *   1. Enumerate cleartext services (silent) and batch-read every value behind
 *      ONE Touch ID (`get-batch`; readItem also sweeps legacy/orphaned copies).
 *   2. Write each hashed copy (`set`/`set-no-acl` never prompt), preserving
 *      the no-ACL tier for `never`-policy bundles.
 *   3. Batch-verify every copy round-trips (second Touch ID).
 *   4. Only then activate hashing (sentinel + pendingDeletes) and delete the
 *      old items (silent).
 *
 * Add-before-delete throughout: a cancel/crash/failure anywhere before step 4
 * leaves every old item intact and hashing OFF — same rationale as the
 * helper's migrate-orphans, which is also why no pre-write backup is taken.
 * On ANY per-item failure nothing is deleted and the sentinel stays off
 * (all-or-nothing activation); the report names every failed item. A crash
 * between the sentinel write and the deletes is resumed silently by the next
 * process (pendingDeletes). Idempotent: re-running converges.
 */
export declare function rekeyServiceNames(opts?: {
    prefixes?: string[];
    announce?: boolean;
    log?: (line: string) => void;
}): RekeyReport;
/** Re-key state snapshot for `agents secrets rekey --status`. */
export declare function rekeyStatus(): {
    migrated: boolean;
    hasKey: boolean;
    pendingDeletes: number;
    cleartext: string[];
    /** False when the enumeration probe fails (locked keybag) — the cleartext
     * count is then meaningless. Only probeable once the key record exists. */
    enumerationOk: boolean;
};
/**
 * Check if a keychain/keyring item exists. Never prompts for biometry.
 *
 * Throws when the item cannot be reached — on macOS, when the signed helper is
 * unavailable. That is deliberate: this primitive gates destructive writes as
 * well as reads. Through `bundleExists()` it guards the `--force` overwrite
 * checks in `agents secrets create` (`../../commands/secrets.ts`) and the
 * bundle-rename purge (`./bundles.ts`), and the pull-rollback bookkeeping
 * (`./sync.ts`). A false "absent" silently disarms every one of them, so an
 * unreachable keychain must fail loudly rather than answer "no".
 *
 * Tests needing this path without a helper install a backend via
 * `setKeychainBackendForTest()`, which short-circuits on the next line.
 */
export declare function hasKeychainToken(item: string): boolean;
/**
 * Retrieve a secret value from the keychain/keyring. Throws if not found.
 *
 * On macOS this triggers Touch ID (or reuses an assertion held by an earlier
 * call in the same process). For bundles, prefer getKeychainTokens() so a
 * single biometric prompt covers every key in the batch.
 */
export interface KeychainReadContext {
    agent?: string;
    bundle?: string;
    sessionId?: string;
    reason?: string;
    duration?: string;
    defaultPolicy?: 'hold' | 'always' | 'never';
    forceDuration?: boolean;
    /**
     * The caller attests the item(s) carry NO biometry ACL (it wrote them with
     * `setKeychainToken(..., { noAcl: true })`, or they are bundle metadata /
     * `never`-policy bundle items, which are no-ACL by contract) — so the read
     * is silent even when no one is at the screen. Skips the headless fail-fast
     * and the back-off memo. Never pass this for an ACL-protected item: that
     * re-opens the background Touch ID storm the guard exists to stop.
     */
    silentNoAcl?: boolean;
}
export declare function setKeychainHeadlessDetectorForTest(detector: (() => boolean) | null): void;
export declare function keychainOperationPrompt(context?: KeychainReadContext): string;
export declare function getKeychainToken(item: string, context?: KeychainReadContext): string;
/**
 * Batch-read multiple keychain items behind a single Touch ID prompt. The
 * macOS helper holds one LAContext for its whole process: the first protected
 * item triggers Touch ID, every later item in the same invocation reuses the
 * assertion. Missing items are absent from the returned map (caller decides
 * whether that's an error).
 *
 * On Linux or when a test backend is installed, falls back to individual
 * lookups — no biometric prompt path on those platforms.
 */
export declare function getKeychainTokens(items: string[], context?: KeychainReadContext): Map<string, string>;
/** Store or update a secret value in the keychain/keyring. Device-local;
 * biometry-gated on macOS. `opts.noAcl` (the `never` prompt-policy) writes our
 * item WITHOUT the biometry access control so later reads are fully silent — it
 * routes through the signed helper's `set-no-acl` path. A pinned helper that
 * predates that path rejects the unknown command (exit 2) and this throws,
 * rather than silently falling back to an ACL'd `set` (which would behave like
 * `always`). Ignored by the Linux/Windows/test backends, which have no ACL. */
/**
 * argv for writing a bare (non-`agents-cli.`) keychain item via
 * `/usr/bin/security add-generic-password`, deliberately WITHOUT the value: the
 * secret travels over stdin (see setKeychainToken) so it never lands in argv or
 * a `ps` snapshot. Exported so a test can assert the value is absent from argv.
 */
export declare function buildAddGenericPasswordArgs(account: string, item: string): string[];
/**
 * spawnSync options for the bare `-w` keychain write. Pure so the two
 * load-bearing properties are unit-testable without touching the real keychain:
 *   - `input` pipes the value TWICE (bare `-w` prompts enter+confirm; one line
 *     fails the confirm and stores an empty secret).
 *   - `detached: true` runs `security` in a new session with no controlling
 *     terminal, so readpassphrase(3) falls back to our piped stdin instead of
 *     prompting the user's `/dev/tty` in an interactive shell (see setKeychainToken).
 */
export declare function buildAddGenericPasswordSpawnOptions(value: string): SpawnSyncOptions & {
    input: string;
    detached: boolean;
};
export declare function setKeychainToken(item: string, value: string, opts?: {
    noAcl?: boolean;
}): void;
/** Delete a keychain/keyring item. Returns true if it existed. Never prompts for biometry. */
export declare function deleteKeychainToken(item: string): boolean;
/**
 * True when the active keychain backend transparently routes reads/writes to
 * the encrypted-file store instead of the OS credential store. This only
 * happens on Linux under the headless / locked-collection fallback
 * (src/lib/secrets/linux.ts); macOS and the test backend always return false.
 *
 * Callers that ALSO enumerate the file store directly (e.g. `listBundles`)
 * use this to avoid double-counting: under the fallback `listKeychainItems`
 * and the direct file enumeration return the same items.
 */
export declare function keychainUsesFileFallback(): boolean;
/** Enumerate keychain/keyring item names starting with the given prefix.
 * With hashed service names active, the two sub-namespace prefixes callers
 * use (bundle metadata; one bundle's value items) are mapped to their hashed
 * shapes — the returned names are then storage (opaque) names. */
export declare function listKeychainItems(prefix: string): string[];
/**
 * Enumerate ONLY legacy file-based-keychain item names with the given prefix —
 * the items that still carry a pre-migration (trusted-app) ACL and pop a
 * separate auth sheet on read. Items already in the data-protection keychain are
 * excluded (they need no migration). Silent (attributes only, never decrypts).
 *
 * macOS only: on Linux / the test backend there is no separate legacy keychain,
 * so this returns []. Used by `agents secrets migrate-acl` to rewrite only the
 * stragglers instead of every item (which would be a Touch ID storm).
 */
export declare function listLegacyKeychainItems(prefix: string): string[];
/**
 * Test seam for the LEGACY SYNCHRONIZABLE (iCloud Keychain) recovery path.
 * The main `KeychainBackend` seam models the live device-local store; this one
 * models the orphaned iCloud items that `secrets import --from icloud` reads.
 * Kept separate so a test can populate both sides independently.
 */
export interface SyncedKeychainBackend {
    list(prefix: string): string[];
    getBatch(items: string[]): Map<string, string>;
    delete(item: string): boolean;
}
export declare function setSyncedKeychainBackendForTest(b: SyncedKeychainBackend | null): SyncedKeychainBackend | null;
/**
 * Enumerate LEGACY SYNCHRONIZABLE (iCloud Keychain) item names with the given
 * prefix — bundles written by the pre-biometry helper era, which defaulted
 * secrets to iCloud Keychain sync. The device-local cutover orphaned them:
 * every modern query pins synchronizable=false, so only the helper's
 * `list-synced` verb can see them. Silent (attributes only, never decrypts).
 * macOS only — Linux/Windows never had iCloud Keychain sync, so this returns [].
 */
export declare function listSyncedKeychainItems(prefix: string): string[];
/**
 * Batch-read LEGACY SYNCHRONIZABLE (iCloud Keychain) items. Returns a map of
 * item name → value; missing items are simply absent. Pre-biometry items carry
 * no biometry ACL, so this does not normally prompt. macOS only — returns an
 * empty map on Linux/Windows.
 */
export declare function getSyncedKeychainTokens(items: string[]): Map<string, string>;
/**
 * Delete a LEGACY SYNCHRONIZABLE (iCloud Keychain) item after a successful
 * import (`--purge`). Matches synchronizable items only — the device-local
 * copy the import wrote is untouched. iCloud propagates the deletion to the
 * user's other devices. Returns true if a copy was removed.
 */
export declare function deleteSyncedKeychainItem(item: string): boolean;
/**
 * One-time upgrade for a keychain item that was written by a previous helper
 * generation with a trusted-app ACL. The helper reads the legacy item
 * (which may pop the password sheet once), then deletes and re-adds it with
 * the biometry access control. Returns true if the item was rewritten, false
 * if no item by that name exists. macOS only — Linux backends have no ACL
 * concept, so the call is a no-op there.
 */
export declare function migrateKeychainItem(item: string): boolean;
/**
 * Enumerate data-protection items whose service starts with `prefix` that live
 * under a NON-concrete access group — pre-#279 "orphans" filed under the implicit
 * default group (the literal `2HTP252L87.*`) that the pinned-group queries can't
 * see. Attributes only: never decrypts, never prompts. macOS only — Linux/Windows
 * and the test backend have no access-group concept, so this returns [].
 */
export declare function listOrphanedKeychainItems(prefix: string): string[];
/** Outcome of re-homing one orphaned keychain item. */
export interface OrphanMigrationResult {
    item: string;
    status: 'ok' | 'warn' | 'fail';
    detail?: string;
}
/**
 * Parse the `migrate-orphans` helper summary (one record per line):
 *   OK <service>               re-homed
 *   WARN <service> <detail>    pinned copy written but orphan not removed
 *   FAIL <service> <detail>    could not re-home (orphan left intact)
 * Unknown lines are ignored. Exported for unit testing without a keychain.
 */
export declare function parseOrphanMigrationOutput(stdout: string): OrphanMigrationResult[];
/**
 * Re-home every pre-#279 orphaned data-protection item under `prefix` into the
 * concrete access group, behind a SINGLE Touch ID prompt for the whole batch.
 * The helper reads each orphan by its exact persistent ref, adds the pinned copy
 * (add-before-delete: a failed add leaves the orphan intact), then deletes the
 * orphan by ref. Returns one result per item. macOS only — no-op elsewhere.
 *
 * Throws on Touch ID cancellation (exit 4) so callers can distinguish "user
 * aborted" from "nothing to do" (empty array).
 */
export declare function migrateOrphanedKeychainItems(prefix: string): OrphanMigrationResult[];
/**
 * Import agents-cli secrets from the native store (GNOME Keyring / Windows
 * Credential Manager) into the encrypted file store — the Linux/Windows
 * analogue of the macOS orphan/legacy migration, exposed as
 * `agents secrets import-keyring`. Requires the native store to be
 * reachable/unlocked; `commit=false` is a dry-run. macOS returns an empty
 * report (it has no file fallback and uses `migrate-acl` instead).
 */
export declare function importNativeItems(prefix: string, commit: boolean): NativeImportReport;
/** Options controlling how secret refs are resolved. */
export interface ResolveOptions {
    /** Translate a short keychain ID to a fully namespaced item name. */
    keychainItemFor?: (shortId: string) => string;
    /** Allow exec: refs. When false (default), exec refs throw. */
    allowExec?: boolean;
    /** Restrict env: refs to this allowlist. When undefined, any env var may be read. */
    envAllowlist?: string[];
}
/** Resolve a secret ref to its plaintext value using the appropriate provider. */
export declare function resolveRef(ref: SecretRef, opts?: ResolveOptions): string;
