/**
 * CredentialStore — short-lived, per-host credential handles.
 *
 * Handles live in memory only. Each has:
 *   - a unique id (e.g. "cred_3")
 *   - the host it is bound to (the password will ONLY be typed on this host)
 *   - the actual value (set once, never logged, never serialized)
 *   - an expiry timestamp (auto-wipe after TTL)
 *
 * The LLM / planner only ever sees the handle id — never the value.
 */
const DEFAULT_TTL_MS = 2 * 60 * 1000; // 2 minutes
export class CredentialStore {
    constructor(ttlMs = DEFAULT_TTL_MS) {
        this.ttlMs = ttlMs;
        this.creds = new Map();
        this.nextId = 0;
        this.sweeper = null;
        // Lazy sweeper — starts on first put.
    }
    /** Store a credential bound to a host. Returns a safe handle. */
    put(host, value, account) {
        this.sweep();
        if (!this.sweeper) {
            this.sweeper = setInterval(() => this.sweep(), 15_000);
            // Do not keep node alive just for the sweeper.
            this.sweeper.unref?.();
        }
        const id = `cred_${this.nextId++}`;
        const now = Date.now();
        const cred = {
            id,
            host: host.toLowerCase(),
            account,
            value,
            createdAt: now,
            expiresAt: now + this.ttlMs,
        };
        this.creds.set(id, cred);
        return this.publicView(cred);
    }
    /** Look up a credential handle (no value). */
    get(id) {
        this.sweep();
        const c = this.creds.get(id);
        return c ? this.publicView(c) : undefined;
    }
    /**
     * Resolve the actual value — the ONLY way to get it.
     * Caller must pass the current host; mismatch => null (and handle is wiped).
     */
    resolve(id, currentHost) {
        this.sweep();
        const c = this.creds.get(id);
        if (!c)
            return null;
        if (!this.hostMatches(c.host, currentHost)) {
            // Fail closed: wipe and return null.
            this.forget(id);
            return null;
        }
        return c.value;
    }
    /** Find an unexpired handle bound to this host (most recent wins). */
    findForHost(currentHost) {
        this.sweep();
        let best;
        for (const c of this.creds.values()) {
            if (this.hostMatches(c.host, currentHost)) {
                if (!best || c.createdAt > best.createdAt)
                    best = c;
            }
        }
        return best ? this.publicView(best) : undefined;
    }
    forget(id) {
        const c = this.creds.get(id);
        if (c) {
            // Overwrite value before delete to reduce lifetime in memory.
            c.value = "";
        }
        this.creds.delete(id);
    }
    clear() {
        for (const c of this.creds.values())
            c.value = "";
        this.creds.clear();
    }
    listPublic() {
        this.sweep();
        return Array.from(this.creds.values()).map((c) => this.publicView(c));
    }
    dispose() {
        if (this.sweeper)
            clearInterval(this.sweeper);
        this.sweeper = null;
        this.clear();
    }
    // ──────────────── internals ────────────────
    publicView(c) {
        return {
            id: c.id,
            host: c.host,
            account: c.account,
            createdAt: c.createdAt,
            expiresAt: c.expiresAt,
        };
    }
    hostMatches(bound, current) {
        const a = bound.toLowerCase();
        const b = current.toLowerCase();
        if (a === b)
            return true;
        // allow subdomain matches: handle bound to "github.com" works on "www.github.com"
        if (b.endsWith(`.${a}`))
            return true;
        // and vice versa: handle bound to "www.github.com" works on "github.com"
        if (a.endsWith(`.${b}`))
            return true;
        return false;
    }
    sweep() {
        const now = Date.now();
        for (const [id, c] of this.creds.entries()) {
            if (c.expiresAt <= now) {
                c.value = "";
                this.creds.delete(id);
            }
        }
    }
}
//# sourceMappingURL=credential-store.js.map