Originally created by: kumaakh
provision_auth currently has two hard-coded paths:
provider.supportsOAuthCopy() returns true only for Claude). Copies ~/.claude/.credentials.json to the remote member.api_key param or OOB terminal prompt.Gemini, Codex, and Copilot all have OAuth credential files too (e.g. ~/.gemini/oauth_creds.json), but the server has no mechanism to copy them the way it does for Claude. Users who authenticate their local Gemini CLI via OAuth have no automated way to propagate those credentials to remote members — they are forced to use an API key instead.
This session exposed the problem directly: deploying Gemini OAuth creds to a remote macOS member required manually reading the local file, base64-encoding its contents, and piping it via execute_command. This should be a one-line provision_auth call.
Every provider that supports OAuth should have its credentials automatically copyable via provision_auth — the same way Claude's are. The implementation should be DRY: provider-specific details (credential file path, env var name, verification method) live in the ProviderAdapter, and the orchestration logic in provision_auth is shared.
| Provider | OAuth file | API key env var | Both supported? |
|---|---|---|---|
| Claude | ~/.claude/.credentials.json |
ANTHROPIC_API_KEY |
Yes |
| Gemini | ~/.gemini/oauth_creds.json |
GEMINI_API_KEY |
Yes |
| Codex | (none / GitHub OAuth via device flow?) | OPENAI_API_KEY |
TBD — investigate |
| Copilot | (GitHub token file?) | GITHUB_TOKEN / COPILOT_GITHUB_TOKEN |
TBD — investigate |
The exact paths and mechanisms for Codex and Copilot need to be researched during implementation.
ProviderAdapter interfaceinterface ProviderAdapter {
// existing
readonly authEnvVar: string;
supportsOAuthCopy(): boolean;
// new
oauthCredentialPath(): string | null; // local path to OAuth creds file, null if not supported
remoteOauthCredentialPath(): string | null; // where to write it on the remote (may differ by OS)
verifyOAuth(agent: Agent, envPrefix?: string): Promise<boolean>; // reuse existing verify logic
}
provision_auth orchestrationif (input.api_key) → provisionApiKey (existing, no change)
else if provider.oauthCredentialPath() exists locally → provisionOAuthCopy (generalized from current Claude path)
else → collectOobApiKey (existing fallback)
provisionOAuthCopy(agent, provider):
provider.oauthCredentialPath() — same logic as current readMasterCredentials()validateCredentials()provider.remoteOauthCredentialPath() on remote — same as current credentialFileWrite()provider.verifyOAuth(agent)Currently steps 1-5 are Claude-only in provisionMasterToken(). The refactor extracts them into a generic function parameterized by provider.
credentialFileWrite(content) and credentialFileRemove() in os-commands.ts are currently Claude-specific (hardcoded path). They need to accept the destination path as a parameter:
credentialFileWrite(content: string, destPath: string): string;
credentialFileRemove(destPath: string): string;
remove_member.ts calls credentialFileRemove() for cleanup — update it to pass provider.remoteOauthCredentialPath().
list_members / member_detail outputSurface the active auth mode (oauth vs api_key) per member so the PM can see which auth each member is using — already partially shown in member_detail (auth=OAuth credentials file vs auth=API key (env)).
provision_auth on a Gemini member (no api_key param) copies ~/.gemini/oauth_creds.json if it exists locally, exactly as it does for Claudeprovision_auth on a Claude member is unchangedremove_member cleans up the remote OAuth credential file for any provider that has oneProviderAdapter — zero hardcoding in provision_auth.ts or remove_member.ts/login territory)
Originally posted by: kumaakh
Implementation details for Gemini OAuth (3 required files, env var override issue, first-run trust prompt) are documented in [#63].
Related
Tickets: #63
Originally posted by: kumaakh
Closed based on live testing against v0.1.3_d10302:
provision_auth(member_name="focus-dev2")on Gemini member with no api_key → "✅ OAuth credentials for gemini deployed to focus-dev2. Auth: verified with a successful gemini API call."member_detail(member_name="focus-dev2", format="json")→ auth field shows oauthDRY OAuth implemented via ProviderAdapter interface: oauthCredentialFiles(), oauthSettingsMerge(), oauthEnvVarsToUnset(). Works for Gemini (3 files) and Claude. Codex/Copilot return null (no OAuth file copy supported).
Ticket changed by: kumaakh