diff --git a/messages/en-US.json b/messages/en-US.json index 02c022e35..46f255c39 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1785,6 +1785,7 @@ "aiClientConfigDescriptionClaude": "Anthropic's agentic coding tool for the terminal.", "aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.", "aiClientConfigDescriptionOpencode": "Open source terminal coding agent.", + "aiClientConfigDescriptionGemini": "Google's agentic coding tool for the terminal.", "aiClientConfigSetup": "Setup", "aiClientConfigTabCli": "Automatic (CLI)", "aiClientConfigTabManual": "Manual Configuration", diff --git a/public/third-party/gemini-dark.svg b/public/third-party/gemini-dark.svg new file mode 100644 index 000000000..03c310a83 --- /dev/null +++ b/public/third-party/gemini-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/third-party/gemini-light.svg b/public/third-party/gemini-light.svg new file mode 100644 index 000000000..46647c7ff --- /dev/null +++ b/public/third-party/gemini-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/ai-client-config/AiClientConfigSection.tsx b/src/components/ai-client-config/AiClientConfigSection.tsx index 955dfb4a9..d4f97e5f9 100644 --- a/src/components/ai-client-config/AiClientConfigSection.tsx +++ b/src/components/ai-client-config/AiClientConfigSection.tsx @@ -49,6 +49,10 @@ const CLIENT_LOGOS = { opencode: { light: "/third-party/opencode-dark.svg", dark: "/third-party/opencode-light.svg" + }, + gemini: { + light: "/third-party/gemini-dark.svg", + dark: "/third-party/gemini-light.svg" } } as const; @@ -65,7 +69,8 @@ export function AiClientConfigSection({ const descriptions: Record = { claude: t("aiClientConfigDescriptionClaude"), codex: t("aiClientConfigDescriptionCodex"), - opencode: t("aiClientConfigDescriptionOpencode") + opencode: t("aiClientConfigDescriptionOpencode"), + gemini: t("aiClientConfigDescriptionGemini") }; return ( diff --git a/src/lib/aiClientConfig.ts b/src/lib/aiClientConfig.ts index 37fe7cd16..1950c4e6c 100644 --- a/src/lib/aiClientConfig.ts +++ b/src/lib/aiClientConfig.ts @@ -1,10 +1,16 @@ -export const AI_CLIENT_IDS = ["claude", "codex", "opencode"] as const; +export const AI_CLIENT_IDS = [ + "claude", + "codex", + "opencode", + "gemini" +] as const; export type AiClientId = (typeof AI_CLIENT_IDS)[number]; export const AI_CLIENT_NAMES: Record = { claude: "Claude Code", codex: "Codex", - opencode: "OpenCode" + opencode: "OpenCode", + gemini: "Gemini CLI" }; /** Auth as supplied by callers: the real key isn't fetched yet. */ @@ -74,7 +80,7 @@ export function aiConfigBlockHasPlaceholders(block: AiConfigBlock): boolean { } function buildCli( - clientArg: "claude" | "codex" | "opencode", + clientArg: "claude" | "codex" | "opencode" | "gemini", auth: AiClientAuth, resourceNiceId?: string ): AiConfigBlock[] { @@ -351,6 +357,49 @@ function buildOpencodeGuide( }; } +function buildGeminiGuide( + endpoint: string, + auth: AiClientAuth, + resourceNiceId?: string +): AiClientGuide { + const defaultEnv = block( + "gemini-default-env", + "~/.gemini/.env", + (key) => + [ + `GOOGLE_GEMINI_BASE_URL=${endpoint}`, + `GEMINI_API_KEY=${auth.mode === "keyed" ? key : "none"}` + ].join("\n"), + auth + ); + + const defaultShell = block( + "gemini-default-shell", + "Shell", + (key) => + [ + `export GOOGLE_GEMINI_BASE_URL=${endpoint}`, + `export GEMINI_API_KEY=${auth.mode === "keyed" ? key : "none"}`, + "gemini" + ].join("\n"), + auth + ); + + return { + id: "gemini", + name: AI_CLIENT_NAMES.gemini, + cli: buildCli("gemini", auth, resourceNiceId), + presets: [ + { + id: "default", + label: "Default", + relation: "options", + blocks: [defaultEnv, defaultShell] + } + ] + }; +} + const GUIDE_BUILDERS: Record< AiClientId, ( @@ -361,7 +410,8 @@ const GUIDE_BUILDERS: Record< > = { claude: buildClaudeGuide, codex: buildCodexGuide, - opencode: buildOpencodeGuide + opencode: buildOpencodeGuide, + gemini: buildGeminiGuide }; export function buildAiClientGuide(