/** * The single source of truth for both Google setup paths. * * The executor (`google-setup-flow.ts`) runs these steps in order. The runbook * generator (`google-setup-runbook.ts`) renders the very same records into the * written fallback in `docs/google-setup-runbook.md`. Because there is exactly * one list, the automation and the written instructions cannot drift; a test * regenerates the doc and fails if the checked-in copy is stale. * * Facts encoded here were verified against Google's live documentation on * 2026-07-26, not from memory. The two that shape the design: * * 1. Google's CalDAV endpoint refuses HTTP Basic authentication outright. * "The CalDAV server refuses to authenticate a request unless it arrives * over HTTPS with OAuth 2.0 authentication of a Google Account. Attempting * to connect over HTTP or using Basic Authentication results in an HTTP * 401 Unauthorized status code." * Source: https://developers.google.com/workspace/calendar/caldav/v2/guide * So an app password cannot reach Google Calendar over CalDAV. The * app-password path uses the private iCal address instead, which is * read-only. Calendar writes require Path B. * * 2. An OAuth app left in "Testing" publishing status issues refresh tokens * that expire after seven days: "A Google Cloud Platform project with an * OAuth consent screen configured for an external user type and a * publishing status of 'Testing' is issued a refresh token expiring in 7 * days." Source: https://developers.google.com/identity/protocols/oauth2 * The exemption for openid/email/profile-only apps does not apply to us; * Gmail and Calendar scopes are sensitive or restricted. Path B therefore * treats reaching "In production" as a first-class, verified step. */ import type { GoogleSetupStepSpec, GoogleStepId, GoogleSetupPath } from './types.js'; /** Where the human creates an app password. Requires 2-Step Verification. */ export declare const APP_PASSWORD_URL = "https://myaccount.google.com/apppasswords"; /** 2-Step Verification settings. */ export declare const TWO_STEP_URL = "https://myaccount.google.com/signinoptions/twosv"; /** Google Calendar settings root; per-calendar "Integrate calendar" holds the iCal address. */ export declare const CALENDAR_SETTINGS_URL = "https://calendar.google.com/calendar/u/0/r/settings"; /** Google Auth Platform, audience tab: where publishing status is changed. */ export declare const AUTH_AUDIENCE_URL = "https://console.cloud.google.com/auth/audience"; /** Google Auth Platform, branding tab (app name, support email). */ export declare const AUTH_BRANDING_URL = "https://console.cloud.google.com/auth/branding"; /** Google Auth Platform, clients tab: where the Desktop app client is created. */ export declare const AUTH_CLIENTS_URL = "https://console.cloud.google.com/auth/clients"; /** Gmail IMAP/SMTP endpoints, per Google's IMAP/SMTP guide. */ export declare const GMAIL_IMAP_HOST = "imap.gmail.com"; export declare const GMAIL_IMAP_PORT = 993; export declare const GMAIL_SMTP_HOST = "smtp.gmail.com"; export declare const GMAIL_SMTP_PORT = 587; /** The label the app password is created under, so re-runs can find it again. */ export declare const APP_PASSWORD_LABEL = "goodvibes-agent"; /** * OAuth scopes requested at consent. * * ONE consent covers every Google feature the platform has. That is the whole * point of this list and it is why it is not shorter. * * The defect it fixes: a token was minted carrying Gmail scopes only, and the * first calendar call afterwards failed with "insufficient authentication * scopes". A grant carries exactly the scopes it was asked for, so a consent * that omits a scope produces a credential that looks connected, reports * connected, and then refuses one specific feature at the moment it is used. * Splitting a person's consent across two screens to save a line on the * permissions list is a bad trade; asking once for everything the product can * do is the honest one. * * Why each entry is here: every one has a live caller, none is speculative: * * - `gmail.readonly`: `api-client.ts` reads messages through * `GET gmail/v1/users/me/messages`, and `history-delta.ts` gates inbound * mail on `GMAIL_HISTORY_SCOPES`. Without it inbound mail reports * `no-gmail-scope` and reads nothing. * - `gmail.send`: `api-client.ts` posts to `gmail/v1/users/me/messages/send`. * - `calendar.events`: event read AND write, which is the one thing an app * password genuinely cannot do. Narrower than full `calendar`, which would * also grant calendar-list management the product never uses. * * On Google's scope tiers: `gmail.readonly` is *restricted* and the other two * are *sensitive*. The restricted tier matters when an app is published for * OTHER people's users; that is what triggers the third-party security * assessment. It does not apply here, because the model this connector is * built on is that each person creates the OAuth client in their own Google * Cloud account and is its only user: there is nobody for Google to vouch to, * the app is self-certified rather than verified, and the 100-user cap is * irrelevant to a one-user app. The consent screen shows the unverified-app * warning once, which the flow tells the person to expect. * * The guard that still applies is on WIDTH, not on tier: `gmail.modify` and * `https://mail.google.com/` grant deletion and full mailbox write, which * nothing in this product does. A test pins that neither is ever added. */ export declare const OAUTH_SCOPES: readonly string[]; /** * Scopes this product must never request, whatever else changes. * * Both grant destructive mailbox access. `gmail.modify` can delete messages * and `https://mail.google.com/` is full IMAP-equivalent control. No feature * in this platform performs either, so requesting one would widen the blast * radius of a leaked credential for nothing. Exported so the guard test and * the runbook read the same list. */ export declare const FORBIDDEN_OAUTH_SCOPES: readonly string[]; /** * Every command invocation named in a user-facing string anywhere in this * connector, command AND subcommand, because the subcommand is what broke. * * This exists because of a specific failure: an error told the owner to * re-authorize with a missing scope by running the oauth setup subcommand, and * the command surface answered "Unknown setup item google". The command * existed; the subcommand did not. A fix line that names something which does * not resolve is worse than no fix line. It sends a person to a dead end * while sounding authoritative. Five more attempts died the same way on * "Unknown setting calendar". * * The contract is two-sided and both sides are tested. In this repo a test * scans every source file in `platform/google` for these invocations and fails * on one that is not listed here. In the agent repo a test walks this list and * fails if any entry does not resolve to a registered command with that * subcommand actually handled. Neither side can drift without going red. */ /** * What a turn must do when the person answers the walkthrough with values. * * The guided path walks someone to a Google console dialog and asks them to * copy two strings out of it. What happens next used to be "now go and type * /google client ", which is a chore handed over at the exact * moment the platform had everything it needed. The values ARE the answer to * the question the flow just asked, and a turn that receives them and asks * what to do with them has dropped the thread. * * Carried here, beside the step plan, so every surface that emits the * walkthrough emits the same contract for finishing it. */ export declare const GOOGLE_WALKTHROUGH_CONTINUATION_PROMPT: string; export declare const GOOGLE_REFERENCED_COMMANDS: readonly string[]; /** * Google APIs the OAuth path enables. * * Both, now. Gmail's API used to be absent here on the reasoning that mail * went over IMAP/SMTP, but the platform reads mail through the Gmail API in * `api-client.ts` and `history-delta.ts`, and an API that is not enabled fails * with a service-disabled error rather than an auth error, which is a * genuinely confusing thing to debug. A scope without its API enabled is a * credential that passes every check and then refuses the first real call. * * These are enabled through gcloud with no clicking, so adding one costs the * person nothing. */ export declare const REQUIRED_SERVICES: readonly string[]; /** * Seed every config section this connector touches. * * All three, not just `google`. The flow writes `email.*`, `calendar.google.*` * and `google.oauth.*`, and ConfigManager throws on a section that is not on * the live config object, so seeding only `google` left the first * `calendar.google.clientId` write throwing "section 'calendar' does not * exist" in every product that did not separately carry a calendar seeder. One * did (goodvibes-agent, locally); the daemon, the TUI and the web UI did not, * which meant the connector could only ever run in one place. See * config/connector-config-sections.ts. */ export declare function ensureGoogleConfigDefaults(configManager: object): void; /** Config keys written by the app-password path. */ export declare const GOOGLE_CONFIG_KEYS: { readonly emailEnabled: "email.enabled"; readonly emailImapHost: "email.imapHost"; readonly emailImapPort: "email.imapPort"; readonly emailSmtpHost: "email.smtpHost"; readonly emailSmtpPort: "email.smtpPort"; readonly emailSmtpSecurity: "email.smtpSecurity"; readonly emailUsername: "email.username"; readonly emailFromAddress: "email.fromAddress"; readonly emailPasswordRef: "email.passwordRef"; readonly oauthClientId: "calendar.google.clientId"; readonly oauthClientSecretRef: "calendar.google.clientSecretRef"; readonly oauthProjectId: "google.oauth.projectId"; readonly oauthPublishingStatus: "google.oauth.publishingStatus"; readonly oauthRefreshToken: "google.oauth.refreshToken"; readonly calendarIcsUrl: "calendar.google.icsUrl"; }; /** Secret-store keys. Values live only in the encrypted store. */ /** * Secret-store names, DERIVED from the config paths above rather than written * out by hand. * * The derivation is the platform-wide one, literally `daemonSecretKeyFor`, * the same function the daemon uses, and matching it is load-bearing rather * than cosmetic: the daemon decides which credentials it owns, and therefore * which replicate to another node, by deriving names from daemon-owned config * paths with exactly this call. A hand-written name like * `goodvibes.email.passwordRef` matches nothing that derivation produces, so * the credential would silently sit outside daemon ownership and fail to * follow a handover. The symptom would be email going quiet on the node that * took over, with nothing in the logs to explain it. */ export declare const GOOGLE_SECRET_KEYS: { readonly appPassword: string; readonly oauthClientSecret: string; readonly oauthRefreshToken: string; readonly calendarIcsUrl: string; }; /** Every step, all paths, in execution order. */ export declare const GOOGLE_SETUP_STEPS: readonly GoogleSetupStepSpec[]; /** * The ordered steps for one path, with dependencies pruned to that path. * * The pruning is what makes `existing-client` work. `oauth-authorize` requires * `oauth-client` on the full OAuth path, but on the existing-client path the * client is already stored, which is the entire premise of the path, so the * requirement names a step that is deliberately absent. Left unpruned, the * executor would see an unmet dependency and skip the only step that matters, * which is how a "go straight to consent" path would quietly do nothing. */ export declare function stepsForPath(path: GoogleSetupPath): readonly GoogleSetupStepSpec[]; /** Look up one step by id. Throws on an unknown id so typos fail loudly. */ export declare function stepSpec(id: GoogleStepId): GoogleSetupStepSpec; /** The anchor a step's section carries in the generated runbook. */ export declare function runbookAnchor(id: GoogleStepId): string; //# sourceMappingURL=setup-plan.d.ts.map