/** * Harness credentials store — the harness logins Crewly itself holds. * * An Anthropic API key has to reach agents somehow, so it lives here, in * `/harness-credentials.json` with mode 0600. Crewly no longer * saves Claude.ai sign-in tokens: Claude Code stores its own login (config * dir or keychain) and Anthropic's rules say developers may not collect or * store those. Older versions did save a `claude setup-token` token here * (`claude.oauthToken`, `claudeAccounts..oauthToken`); that data is * only read to keep an account working until the owner signs it in again, * then deleted (services/harness/claude-token-migration.service.ts). Codex * writes its own credentials (`$CODEX_HOME/auth.json`), so nothing is stored * for it unless a future flow needs the key. Antigravity CLI reads its Gemini * API key only from the environment, so Crewly keeps that key here too. * * {@link harnessEnvForAgents} turns the stored credentials into the env vars * injected into agent PTYs (and adds the user npm prefix to PATH). * * Secrets are never logged, never returned by the REST API and never put in * an error message. * * @module services/harness/harness-credentials.store */ /** Stored credentials. Every field is optional. */ export interface HarnessCredentials { claude?: { /** LEGACY, read-only: token an older Crewly saved from `claude setup-token`; removed by the migration */ oauthToken?: string; /** Anthropic API key (exported as ANTHROPIC_API_KEY) */ anthropicApiKey?: string; updatedAt?: string; }; codex?: { /** OpenAI API key — only kept if a flow needs it (Codex stores its own login) */ openaiApiKey?: string; updatedAt?: string; }; antigravity?: { /** Gemini API key Antigravity CLI runs with (exported as GEMINI_API_KEY to antigravity-cli agents only) */ geminiApiKey?: string; updatedAt?: string; }; /** * The owner's other Claude Code accounts (issue #942), keyed by account * name. Each token goes only to sessions running on that account. */ claudeAccounts?: Record; } /** Which stored Claude credential is active. */ export type StoredClaudeCredential = 'oauth_token' | 'api_key' | null; /** * Reads and writes `harness-credentials.json`. * * Reads the file on every call (no cache), so the CLI and a running backend * always see each other's writes. */ export declare class HarnessCredentialsStore { private readonly filePath?; /** * @param filePath - Credentials file; defaults to `/harness-credentials.json` * (resolved per call so `CREWLY_HOME` changes in tests are honoured) */ constructor(filePath?: string | undefined); /** * Absolute path of the credentials file. * * @returns The path */ getFilePath(): string; /** * Read the stored credentials. * * @returns The credentials; an empty object when the file is missing or unreadable */ read(): HarnessCredentials; /** * Write credentials atomically with mode 0600. * * The temp file is created with 0600 before any secret is written to it, * then renamed over the target, and the target is chmod-ed again in case * it pre-existed with a wider mode. * * @param credentials - Full credentials object */ write(credentials: HarnessCredentials): void; /** * Store an Anthropic API key. Replaces a stored OAuth token. * * @param key - Anthropic API key * @throws Error when the key is empty */ setAnthropicApiKey(key: string): void; /** * Store an OpenAI API key (only when a flow needs Crewly to hold it). * * @param key - OpenAI API key * @throws Error when the key is empty */ setOpenaiApiKey(key: string): void; /** * Store the Gemini API key Antigravity CLI runs with. * * @param key - Gemini API key * @throws Error when the key is empty */ setAntigravityGeminiApiKey(key: string): void; /** * The stored Antigravity Gemini API key. * * @returns The key, or null when none is stored */ getAntigravityGeminiApiKey(): string | null; /** * Forget the stored Antigravity key. */ clearAntigravity(): void; /** * Forget the stored Claude credential. */ clearClaude(): void; /** * LEGACY: the token an older Crewly saved for one of the owner's other * Claude Code accounts (never written any more). * * @param account - Account name * @returns The token, or null when none is stored */ getClaudeAccountToken(account: string): string | null; /** * Account names with a stored token. * * @returns Names */ listClaudeAccountsWithToken(): string[]; /** * Forget the token of one of the owner's other Claude Code accounts. The * `claudeAccounts` key goes away with its last entry. * * @param account - Account name */ clearClaudeAccount(account: string): void; /** * LEGACY: the default login's token an older Crewly saved. * * @returns The token, or null */ getLegacyClaudeOauthToken(): string | null; /** * Delete the default login's legacy token. The `claude` key is removed * when nothing but bookkeeping is left (a stored API key stays). */ clearLegacyClaudeOauthToken(): void; /** * Remove what an older Crewly left for Claude sign-in tokens once no token * is stored: empty `claudeAccounts` entries or object, an empty * `claude.oauthToken`, and a `claude` object holding nothing but * bookkeeping. A stored API key and every other key stay untouched. Does * nothing (and does not rewrite the file) when there is nothing to remove. */ pruneLegacyClaudeKeys(): void; /** * Env that keeps an account working with the token an older Crewly saved * for it. LEGACY, transition only: empty once the account's native login * is verified and the token deleted. This is the only place the token is * handed to a Claude Code process. * * @param account - Named account; null = the default login * @returns `{ CLAUDE_CODE_OAUTH_TOKEN }`, or `{}` when no token is stored */ legacyClaudeTokenEnv(account: string | null): Record; /** * Which Claude credential Crewly holds (never the value). * * @returns `oauth_token`, `api_key` or null */ getClaudeCredentialKind(): StoredClaudeCredential; /** * Env vars to inject into agent sessions. * * Always sets PATH (user npm prefix bin first, see harness-exec.utils). * Adds `ANTHROPIC_API_KEY` when Crewly holds one (and, during the token * migration only, the legacy token of the default login). Nothing is added for Codex: it reads its own * `auth.json`. * * For an `antigravity-cli` session it also adds the stored Gemini key as * `GEMINI_API_KEY` (Antigravity reads the key only from the environment) * and `AGY_CLI_DISABLE_AUTO_UPDATE`. The key is scoped to that runtime on * purpose: in a Gemini CLI session an unexpected GEMINI_API_KEY brings up * Gemini's "Existing API key detected" dialog for Google-login users. * * @param baseEnv - Environment whose PATH is extended (defaults to process.env) * @param runtimeType - Runtime the session will run, when known * @returns Env map to merge into the agent's spawn env */ harnessEnvForAgents(baseEnv?: NodeJS.ProcessEnv, runtimeType?: string): Record; } /** * The default credentials store. * * @returns Store backed by `/harness-credentials.json` */ export declare function getHarnessCredentialsStore(): HarnessCredentialsStore; /** * Env vars to inject into agent sessions, from the default store. * * Never throws: a broken credentials file yields just the PATH. * * @param baseEnv - Environment whose PATH is extended (defaults to process.env) * @param runtimeType - Runtime the session will run (adds runtime-scoped vars such as Antigravity's key) * @returns Env map (PATH plus any credential env var) * * @example * ```ts * const env = { ...identityEnv, ...harnessEnvForAgents(process.env, 'antigravity-cli') }; * ``` */ export declare function harnessEnvForAgents(baseEnv?: NodeJS.ProcessEnv, runtimeType?: string): Record; //# sourceMappingURL=harness-credentials.store.d.ts.map