/** * Anthropic OAuth token write-back — the canonical refresh-time persistence * path for the unified credential pool (E-CONFIG-AUTH-UNIFY E2a / T9411). * * On every successful OAuth refresh, CLEO ALWAYS writes the new tokens * to its own canonical file at `${getCleoHome()}/anthropic-oauth.json`. * When the operator has enabled cooperative write-back (default per OQ-1) * AND Claude Code's credential file either already exists OR the operator * has consented to Claude Code import, CLEO ALSO mirrors the refreshed * tokens into `~/.claude/.credentials.json`. * * ## Why cooperate * * Operators routinely run both CLEO and Claude Code from the same machine * against the same Anthropic account. Without cooperative write-back, the * first CLI to refresh wins and the other rejects subsequent requests with * `401 invalid_token` until the operator manually re-logs. The OQ-1 * decision in `docs/plans/E-CONFIG-AUTH-UNIFY.md` resolves this by making * write-back ON by default — both CLIs see the freshest token at all * times — but never auto-creating Claude Code's file (consent-respecting). * * ## Atomicity + permissions * * Both writes use a temp-file-then-rename strategy with `mode: 0o600` set * at temp-file creation time so: * * 1. The post-rename file is born with the strict permission — there is * no instant at which a 0o644 default file holds secrets on disk. * 2. A crash mid-write never leaves the live file truncated. * * After the rename succeeds, a defensive `chmod 0o600` is applied to the * live file. This is redundant on POSIX (the rename preserves the temp's * mode) but guards against Windows behavior where the open-mode flag on * `writeFileSync` is silently ignored. * * ## Scope preservation * * Claude Code >= 2.1.81 requires the `user:inference` scope on its OAuth * tokens; refreshes from the Anthropic API do NOT necessarily return the * scopes string, so this module MUST read the existing Claude Code file * (when present) and carry the scopes array forward verbatim. CLEO's own * file behaves the same way — if a `scopes` array is present we preserve * it; if the refresh provided new scopes those override. * * @module llm/credential-writeback * @task T9411 * @epic E-CONFIG-AUTH-UNIFY (E2a) */ /** * Refreshed OAuth token bundle handed to {@link writeBackAnthropicTokens}. * * `scopes` is optional — the Anthropic refresh endpoint does not always * return it. When omitted, the write-back handler reads the existing * on-disk file (CLEO's own and Claude Code's) and carries the previous * scopes forward so Claude Code's `user:inference` requirement is honored. */ export interface RefreshedAnthropicTokens { /** New short-lived bearer token returned by the refresh exchange. */ accessToken: string; /** New refresh token to use for the next refresh. */ refreshToken: string; /** Unix-millisecond expiry timestamp of the new access token. */ expiresAt: number; /** * Optional scopes string array returned by the refresh exchange. * * When omitted, the write-back preserves whatever scopes were on disk. */ scopes?: string[]; } /** * Result of {@link writeBackAnthropicTokens}. * * `written` lists the absolute paths that were successfully written. * `skipped` lists paths that were intentionally NOT written, paired with * a human-readable reason (e.g. `"cooperativeWriteBack=false"`). Failed * writes throw — they never appear in either list. */ export interface WriteBackResult { /** Absolute paths of files that were written. */ written: string[]; /** Absolute paths NOT written, paired with a `reason`. */ skipped: Array<{ path: string; reason: string; }>; } /** * Persist refreshed Anthropic OAuth tokens to disk. * * Always writes to `${getCleoHome()}/anthropic-oauth.json`. Additionally * writes to `~/.claude/.credentials.json` IFF: * * 1. The config flag `auth.cooperativeWriteBack` is `true` (default), AND * 2. The Claude Code file already exists on disk OR the operator has set * `auth.claudeCodeConsentGiven = true`. * * Both writes are atomic (temp file + rename) and mode 0o600. The * `claudeAiOauth.scopes` field is preserved verbatim across writes so * Claude Code >= 2.1.81 continues to accept the token. * * @param refreshed - Token bundle returned by the Anthropic refresh exchange. * @returns Lists of written + skipped paths with reasons. * @throws If a write fails after the gating checks pass — callers can * surface the error to the operator (refresh succeeded but persistence * failed; the in-memory tokens are still usable until process exit). * * @task T9411 */ export declare function writeBackAnthropicTokens(refreshed: RefreshedAnthropicTokens): Promise; //# sourceMappingURL=credential-writeback.d.ts.map