import { T as ToolHost } from '../tool-host-BBSaU7IJ.cjs'; import { B as Bridge } from '../types-D_H2f-wF.cjs'; import '../types-ITRQFAir.cjs'; /** * Bridge for `@particle-academy/fancy-passkeys-ui` — the **management** half of * a passkey (WebAuthn) surface, and only that half. * * ## The absence is the design * * There is **no tool here that completes an authentication or registration * ceremony**, and there must never be one. Not `passkey_authenticate`, not * `passkey_sign_in`, not `passkey_complete`, not a "headless" variant behind a * flag. * * The reason is construction, not policy. `navigator.credentials.get()` requires * a user gesture and a biometric or PIN — both things only the person at the * keyboard has. An agent-drivable passkey login would be a bypass of the exact * property that makes a passkey worth more than a password: that possession of * the credential cannot be delegated, replayed, or phished. A bridge offering * one would quietly turn a phishing-resistant factor back into a bearer token. * * So the boundary sits where the cryptography already puts it, and the tool set * is drawn to match: **read the management surface, rename, propose, ask the * human to start a ceremony.** `bridges/__tests__/passkeys.test.ts` asserts the * registered tool names against a closed list, so "finishing" this bridge with a * sign-in tool fails CI rather than shipping. * * ## What it does do * * passkey_list the user's credentials (whitelisted fields only) * passkey_status browser support + what the surface currently shows * passkey_rename relabel one credential, by credential ID (undoable) * passkey_revoke **STAGES** a revoke for the human to confirm * passkey_begin_enrollment opens the enrollment prompt; returns immediately * * `passkey_revoke` never revokes. Revoking a passkey is destructive and revoking * the last one is a lockout, so the confirming click comes from the human at the * surface — there is deliberately **no `pendingMode: false`**, no `confirm: true` * argument, and no host hook that turns staging off. On this bridge staging is * not a mode. * * The adapter type is declared LOCALLY (like `features` / `catalog`) so the * bridge builds and type-checks with the sibling package absent. A host wiring a * live `PasskeyManager` satisfies it structurally, with no import. */ /** * One credential as the bridge is willing to describe it — the `PasskeySummary` * shape both passkey backends already serialise. * * Everything the server stores that is NOT on this list — the COSE public key, * the user handle, the signature counter, the attestation statement and trust * path — is absent on purpose, and {@link toSummary} re-projects onto exactly * these keys rather than trusting the adapter to have withheld them. A host that * hands `list()` its raw ORM model (the obvious thing to do) therefore cannot * leak credential material through an MCP tool by accident. */ type PasskeySummaryLike = { /** base64url credential ID — the handle every action here is keyed by. */ id: string; /** Human-chosen label, or null when the user never named it. */ name: string | null; /** ISO-8601. */ createdAt: string; /** ISO-8601, or null when never used to sign in. */ lastUsedAt: string | null; /** Transport hints ("internal", "hybrid", "usb", …). */ transports: string[]; /** True for a synced passkey (BE/BS flags); false means this device only. */ backedUp: boolean; /** Authenticator model id. Stored, never a trust decision. */ aaguid: string; /** ISO-8601 when the signature counter regressed — a suspected clone. */ clonedAt: string | null; }; /** What the browser can do, as the three `./client` predicates report it. */ type PasskeySupport = { supported: boolean; /** Touch ID / Windows Hello present? `null` while unknown. */ platformAuthenticator?: boolean | null; /** Can the browser offer a passkey from a username field? `null` while unknown. */ conditionalUi?: boolean | null; }; /** A read of what the management surface is currently showing. */ type PasskeySurfaceState = { status?: "idle" | "busy" | "error"; /** `{ code, message }` as carried in surface state — never an Error. */ error?: { code: string; message: string; } | null; /** The revoke awaiting a human's confirmation, if any. */ pendingRevoke?: { id: string; isLastPasskey: boolean; } | null; /** Credential ID currently being renamed, if any. */ renamingId?: string | null; }; /** * Host-provided access to the management surface. Mirrors the props a * `PasskeyManager` is already wired with, so a host usually passes the same four * callbacks it passed the component. */ type PasskeyBridgeAdapter = { /** Stable id for this surface instance. Surfaces in activity logs. */ id?: string; title?: string; screenId?: string; /** The signed-in user's credentials. */ list(): PasskeySummaryLike[] | Promise; /** Relabel one credential. Cosmetic, reversible, and undoable here. */ rename(input: { id: string; name: string; }): void | Promise; /** * **Stage** a revoke for human confirmation — the `pendingRevoke` the surface * renders a confirm/cancel dialog for. It must NOT revoke; a host that wires * this to a delete call has removed the human from the loop the bridge was * built around. */ proposeRevoke(input: { id: string; }): PasskeyProposal | Promise; /** * Open the enrollment prompt on the human's screen and return immediately. * The ceremony completes on their authenticator or not at all. */ beginEnrollment?(input: { name?: string; }): void | Promise; /** Browser capability flags, if the host tracks them. */ support?(): PasskeySupport | Promise; /** What the surface currently shows. */ state?(): PasskeySurfaceState | Promise; }; type PasskeyProposal = { staged: true; isLastPasskey: boolean; }; type PasskeyBridgeOptions = { adapter: PasskeyBridgeAdapter; agent?: { id: string; name?: string; color?: string; }; /** * Also route `passkey_rename` through {@link confirm}. Off by default: a * rename is cosmetic, reversible and pushed onto the undo stack. Worth turning * on where the label itself carries trust — a rogue credential relabelled * "Avery's iPhone" survives a human's audit of the list. */ confirmRename?: boolean; /** * Host confirmation hook for renames, when {@link confirmRename} is on. * * Note there is no revoke case: a revoke is staged into the surface and * confirmed there, never through a hook the agent's own host supplies. */ confirm?: (req: { action: "passkey_rename"; id: string; name: string; }) => boolean | Promise; }; /** * The complete set of tools this bridge may ever register. * * Exported so the test suite — and anyone auditing what an agent can reach — * can assert the registered names against it rather than eyeballing the file. */ declare const PASSKEY_BRIDGE_TOOLS: readonly ["passkey_list", "passkey_status", "passkey_rename", "passkey_revoke", "passkey_begin_enrollment"]; /** * registerPasskeyBridge — MCP tools over a passkey **management** surface. * * Read the credential list, read browser support and surface state, rename a * credential, propose a revoke for the human to confirm, and ask for an * enrollment prompt. Nothing signs anyone in; see the file header for why that * is a property rather than a gap. */ declare function registerPasskeyBridge(host: ToolHost, options: PasskeyBridgeOptions): Bridge; export { PASSKEY_BRIDGE_TOOLS, type PasskeyBridgeAdapter, type PasskeyBridgeOptions, type PasskeyProposal, type PasskeySummaryLike, type PasskeySupport, type PasskeySurfaceState, registerPasskeyBridge };