/** * Gemini CLI Workspace Credential Helper * * Piggybacks on the Google Workspace extension for Gemini CLI * (https://github.com/gemini-cli-extensions/workspace) for Google OAuth * credential acquisition and refresh. * * **Capture** reads the extension's file-storage token file (written when * the user runs the extension with `GEMINI_CLI_WORKSPACE_FORCE_FILE_STORAGE=true`) * and imports the tokens into Crewly's own encrypted store. * * **Refresh** POSTs the stored `refresh_token` to the extension's Cloud * Function `/refreshToken` endpoint — no client_secret needed on our side. * * After capture, Crewly owns the tokens; the extension's own state is not * depended on for day-to-day operation. * * @module services/credential/helpers/gemini-cli-workspace.helper */ import { CredentialHelper, CredentialHelperName, CredentialRegistryEntry, GoogleOAuthPayload } from '../../../types/credential.types.js'; import { CredentialStoreService } from '../credential-store.service.js'; import { FetchLike as UserinfoFetchLike } from '../../../utils/google-userinfo.utils.js'; /** * Minimal fetch-compatible function shape (for test injection). Re-exported * from the google-userinfo utility so callers can import a single type. */ export type FetchLike = UserinfoFetchLike; /** * Helper configuration — all fields optional; defaults match production. */ export interface GeminiCliHelperConfig { /** Path to the extension install directory (default: `~/.gemini/extensions/google-workspace`). */ extensionPath?: string; /** Cloud Function base URL. */ cloudFunctionUrl?: string; /** Path on the cloud function for refresh calls. */ refreshPath?: string; /** Client ID to store on the credential (for later refresh identification). */ clientId?: string; /** Refresh buffer in ms — refresh if token expires within this window. */ expiryBufferMs?: number; /** Fetch implementation (injectable for tests). */ fetch?: FetchLike; } /** * Credential helper that reads tokens from the gemini-cli-workspace extension * and refreshes them via the extension's Cloud Function. */ export declare class GeminiCliWorkspaceHelper implements CredentialHelper { readonly name: CredentialHelperName; private readonly extensionPath; private readonly cloudFunctionUrl; private readonly refreshPath; private readonly clientId; private readonly expiryBufferMs; private readonly fetchFn; private readonly store; /** Per-credential refresh in-flight promises (serializes concurrent refreshes). */ private readonly refreshInFlight; /** Per-credential last refresh attempt timestamp (for cooldown). */ private readonly lastRefreshAttempt; constructor(store: CredentialStoreService, config?: GeminiCliHelperConfig); /** * Read the extension's current token file and return its contents as a * `GoogleOAuthPayload` ready to persist in Crewly's store. * * The user must have completed extension login with * `GEMINI_CLI_WORKSPACE_FORCE_FILE_STORAGE=true` before calling this. */ captureFromFile(): Promise; /** * Remove the extension's token file. Call after `captureFromFile()` to * prepare for the next account's login (the extension otherwise returns * cached credentials if scopes match, skipping the login prompt). * Leaves the master.key file untouched so existing ciphertexts remain * decryptable if needed. */ clearExtensionFile(): Promise; /** * Return a valid `GoogleOAuthPayload`, refreshing via the extension's * cloud function if the access token is within the expiry buffer. * * Concurrent calls for the same credential share a single refresh. * * @throws CredentialRevokedError if the refresh token is no longer valid */ getAccessToken(entry: CredentialRegistryEntry, payload: GoogleOAuthPayload): Promise; private doRefresh; /** * Decrypt the extension's `{iv_hex}:{authTag_hex}:{ciphertext_hex}` * format (AES-256-GCM). */ private decryptExtensionFormat; } /** * Encrypt a plaintext string with the extension's file format. Used in * tests to generate fake token files. */ export declare function encryptExtensionFormatForTesting(plaintext: string, key: Buffer): string; /** * Derive the extension's encryption key using the real scrypt + salt. * Test-only export. */ export declare function deriveExtensionKeyForTesting(masterKey: Buffer): Buffer; //# sourceMappingURL=gemini-cli-workspace.helper.d.ts.map