export declare const CALENDAR_SCOPE = "https://www.googleapis.com/auth/calendar"; /** Which of the two register routes this result resolved to. `auto-open` when * the consent URL was opened in the install's own browser (Task 1862) and the * loopback listener will complete it; `paste` otherwise, where the user copies * the address-bar URL back into submit. Exactly one applies per register. */ export type RegisterPath = "auto-open" | "paste"; /** The single resolved instruction a register result carries. One path only, * never a two-branch menu, so the agent relays it verbatim rather than picking * prose per turn (Task 1863). */ export declare function registerInstruction(path: RegisterPath): string; /** The readback contract every register result carries: completion is read from * the poll, not asserted. Sourced from google-account-register-poll's * authoritative status so the agent never reports success it did not read * (Task 1863). */ export declare function registerReadback(): string; /** Google refuses an authorization code older than roughly ten minutes; a * pending entry past that is stranded, not live. */ export declare const PENDING_LIFETIME_SEC = 600; export declare function newPkce(): { verifier: string; challenge: string; }; /** Desktop (installed app) clients permit ONLY loopback redirects. https is * rejected outright and custom URI schemes are withdrawn, so this is the whole * option space. * * The IP literal is mandatory. RFC 8252 s7.3 makes the port irrelevant when * matching a loopback redirect, but only for `127.0.0.1` / `::1` — NOT for the * hostname `localhost`, which strict servers match literally. * * Nothing listens on this port, by design: the consenting browser is on the * user's own phone, so the redirect fails to load and the user copies the code * out of the address bar. The value is fixed so the consent URL is * deterministic and one register's logs compare against another's. */ export declare const LOOPBACK_REDIRECT_URI = "http://127.0.0.1:8571"; /** `state` is an opaque CSRF nonce and nothing more. * * It deliberately does NOT carry the account id. Under the Desktop flow the * pending entry is keyed on the spawn's own ACCOUNT_ID, so submit can only ever * read its own account's pending, and PKCE binds the code to that entry's * verifier. Routing information in `state` would therefore buy nothing, while * putting an account uuid into a URL opened on a personal phone, stored in * browser history and Google's logs, and pasted back through chat. * * (Task 1761 encoded `.` because a shared hosted callback had * to route an inbound request to an account. That callback is gone.) */ export declare function buildConsentUrl(args: { clientId: string; state: string; challenge: string; /** Whatever the loopback listener bound for this registration, or the fixed * constant when the bind failed. Persisted alongside, because the exchange * must send this same value back. */ redirectUri: string; }): string; export interface ConnectedAccount { email: string; status: "ok" | "unreadable"; } /** Enumerate connected Google accounts from local stores only — no network. * A count alone is not "connected": each entry carries its own status so an * unreadable blob is distinguishable from a healthy one. */ export declare function listConnected(accountId: string, accountsDir: string): ConnectedAccount[]; /** Minted by the caller rather than here, because the loopback listener must be * started with this same value BEFORE the consent URL exists — the listener * verifies it on the callback. */ export declare function newState(): string; export declare function runAccountRegisterStart(args: { accountId: string; accountsDir: string; clientId: string; /** Minted by the caller via `newState` and shared with the listener. */ state: string; /** The listener's bound URI, or `LOOPBACK_REDIRECT_URI` when the bind * failed. Persisted so the exchange can send it back unchanged. */ redirectUri: string; }): Promise<{ status: "pending" | "in-progress"; consentUrl?: string; ageSec?: number; }>; export declare function runAccountRegisterPoll(args: { accountId: string; accountsDir: string; }): Promise<{ status: "registered" | "pending" | "expired" | "none"; accounts: ConnectedAccount[]; ageSec?: number; }>; /** Users paste what they can see, and what they can see is Google's loopback * redirect — which ALWAYS carries `scope` alongside `code`, and often * `authuser` and `prompt` too. So the realistic pastes are a whole URL or a * `code=…&scope=…` fragment, not a bare value. * * Extracting `code` properly matters because a mangled value reaches Google as * an opaque string and comes back `invalid_grant`, which reads to the user as * "your code expired" and sends them round the consent loop again. It never * says the paste was the problem. * * A malformed percent escape must not throw either: `decodeURIComponent` raises * URIError on a stray `%`, and surfacing that instead of Google's own error * would be our parser blaming the user for a value Google would have explained. */ export declare function normalisePastedCode(raw: string): string; /** * Finish a registration from the code the user pasted back. * * The exchange happens HERE, in the plugin, because a Desktop client's secret * ships in brand config — there is no server-side holder to delegate to. * * The redirect_uri sent here is the one the PENDING ENTRY holds, not a * constant: since Task 1799 the consent URL carries whatever port the loopback * listener bound, and Google compares the two byte for byte. Sending the * constant instead would fail every registration whose listener bound a port, * with a redirect_uri_mismatch that reads like a wrong client type. */ export declare function runAccountRegisterSubmit(args: { accountId: string; accountsDir: string; clientId: string; clientSecret: string; code: string; fetchImpl?: typeof fetch; }): Promise<{ status: "registered"; email: string; refreshTokenIssued: boolean; warning?: string; }>; //# sourceMappingURL=account-register.d.ts.map