--- title: Add Gemini CLI as a Target Provider type: feat status: completed completed_date: 2026-02-14 completed_by: "Claude Opus 4.6" actual_effort: "Completed in one session" date: 2026-02-14 --- # Add Gemini CLI as a Target Provider ## Overview Add `gemini` as a sixth target provider in the converter CLI, alongside `opencode`, `codex`, `droid`, `cursor`, and `pi`. This enables `--to gemini` for both `convert` and `install` commands, converting Claude Code plugins into Gemini CLI-compatible format. Gemini CLI ([google-gemini/gemini-cli](https://github.com/google-gemini/gemini-cli)) is Google's open-source AI agent for the terminal. It supports GEMINI.md context files, custom commands (TOML format), agent skills (SKILL.md standard), MCP servers, and extensions -- making it a strong conversion target with good coverage of Claude Code plugin concepts. ## Component Mapping | Claude Code | Gemini Equivalent | Notes | |---|---|---| | `agents/*.md` | `.gemini/skills/*/SKILL.md` | Agents become skills -- Gemini activates them on demand via `activate_skill` tool based on description matching | | `commands/*.md` | `.gemini/commands/*.toml` | TOML format with `prompt` and `description` fields; namespaced via directory structure | | `skills/*/SKILL.md` | `.gemini/skills/*/SKILL.md` | **Identical standard** -- copy directly | | MCP servers | `settings.json` `mcpServers` | Same MCP protocol; different config location (`settings.json` vs `.mcp.json`) | | `hooks/` | `settings.json` hooks | Gemini has hooks (`BeforeTool`, `AfterTool`, `SessionStart`, etc.) but different format; emit `console.warn` and skip for now | | `.claude/` paths | `.gemini/` paths | Content rewriting needed | ### Key Design Decisions **1. Agents become skills (not GEMINI.md context)** With 29 agents, dumping them into GEMINI.md would flood every session's context. Instead, agents convert to skills -- Gemini autonomously activates them based on the skill description when relevant. This matches how Claude Code agents are invoked on demand via the Task tool. **2. Commands use TOML format with directory-based namespacing** Gemini CLI commands are `.toml` files where the path determines the command name: `.gemini/commands/git/commit.toml` becomes `/git:commit`. This maps cleanly from Claude Code's colon-namespaced commands (`workflows:plan` -> `.gemini/commands/workflows/plan.toml`). **3. Commands use `{{args}}` placeholder** Gemini's TOML commands support `{{args}}` for argument injection, mapping from Claude Code's `argument-hint` field. Commands with `argument-hint` get `{{args}}` appended to the prompt. **4. MCP servers go into project-level settings.json** Gemini CLI reads MCP config from `.gemini/settings.json` under the `mcpServers` key. The format is compatible -- same `command`, `args`, `env` fields, plus Gemini-specific `cwd`, `timeout`, `trust`, `includeTools`, `excludeTools`. **5. Skills pass through unchanged** Gemini adopted the same SKILL.md standard (YAML frontmatter with `name` and `description`, markdown body). Skills copy directly. ### TOML Command Format ```toml description = "Brief description of the command" prompt = """ The prompt content that will be sent to Gemini. User request: {{args}} """ ``` - `description` (string): One-line description shown in `/help` - `prompt` (string): The prompt sent to the model; supports `{{args}}`, `!{shell}`, `@{file}` placeholders ### Skill (SKILL.md) Format ```yaml --- name: skill-name description: When and how Gemini should use this skill --- # Skill Title Detailed instructions... ``` Identical to Claude Code's format. The `description` field is critical -- Gemini uses it to decide when to activate the skill. ### MCP Server Format (settings.json) ```json { "mcpServers": { "server-name": { "command": "npx", "args": ["-y", "package-name"], "env": { "KEY": "value" } } } } ``` ## Acceptance Criteria - [x] `bun run src/index.ts convert --to gemini ./plugins/compound-engineering` produces valid Gemini config - [x] Agents convert to `.gemini/skills/*/SKILL.md` with populated `description` in frontmatter - [x] Commands convert to `.gemini/commands/*.toml` with `prompt` and `description` fields - [x] Namespaced commands create directory structure (`workflows:plan` -> `commands/workflows/plan.toml`) - [x] Commands with `argument-hint` include `{{args}}` placeholder in prompt - [x] Commands with `disable-model-invocation: true` are still included (TOML commands are prompts, not code) - [x] Skills copied to `.gemini/skills/` (identical format) - [x] MCP servers written to `.gemini/settings.json` under `mcpServers` key - [x] Existing `.gemini/settings.json` is backed up before overwrite, and MCP config is merged (not clobbered) - [x] Content transformation rewrites `.claude/` and `~/.claude/` paths to `.gemini/` and `~/.gemini/` - [x] `/workflows:plan` transformed to `/workflows:plan` (Gemini preserves colon namespacing via directories) - [x] `Task agent-name(args)` transformed to `Use the agent-name skill to: args` - [x] Plugins with hooks emit `console.warn` about format differences - [x] Writer does not double-nest `.gemini/.gemini/` - [x] `model` and `allowedTools` fields silently dropped (no Gemini equivalent in skills/commands) - [x] Converter and writer tests pass - [x] Existing tests still pass (`bun test`) ## Implementation ### Phase 1: Types **Create `src/types/gemini.ts`** ```typescript export type GeminiSkill = { name: string content: string // Full SKILL.md with YAML frontmatter } export type GeminiSkillDir = { name: string sourceDir: string } export type GeminiCommand = { name: string // e.g. "plan" or "workflows/plan" content: string // Full TOML content } export type GeminiBundle = { generatedSkills: GeminiSkill[] // From agents skillDirs: GeminiSkillDir[] // From skills (pass-through) commands: GeminiCommand[] mcpServers?: Record url?: string headers?: Record }> } ``` ### Phase 2: Converter **Create `src/converters/claude-to-gemini.ts`** Core functions: 1. **`convertClaudeToGemini(plugin, options)`** -- main entry point - Convert each agent to a skill via `convertAgentToSkill()` - Convert each command via `convertCommand()` - Pass skills through as directory references - Convert MCP servers to settings-compatible object - Emit `console.warn` if `plugin.hooks` has entries 2. **`convertAgentToSkill(agent)`** -- agent -> SKILL.md - Frontmatter: `name` (from agent name), `description` (from agent description, max ~300 chars) - Body: agent body with content transformations applied - Prepend capabilities section if present - Silently drop `model` field (no Gemini equivalent) - If description is empty, generate from agent name: `"Use this skill for ${agent.name} tasks"` 3. **`convertCommand(command, usedNames)`** -- command -> TOML file - Preserve namespace structure: `workflows:plan` -> path `workflows/plan` - `description` field from command description - `prompt` field from command body with content transformations - If command has `argument-hint`, append `\n\nUser request: {{args}}` to prompt - Body: apply `transformContentForGemini()` transformations - Silently drop `allowedTools` (no Gemini equivalent) 4. **`transformContentForGemini(body)`** -- content rewriting - `.claude/` -> `.gemini/` and `~/.claude/` -> `~/.gemini/` - `Task agent-name(args)` -> `Use the agent-name skill to: args` - `@agent-name` references -> `the agent-name skill` - Skip file paths (containing `/`) and common non-command patterns 5. **`convertMcpServers(servers)`** -- MCP config - Map each `ClaudeMcpServer` entry to Gemini-compatible JSON - Pass through: `command`, `args`, `env`, `url`, `headers` - Drop `type` field (Gemini infers transport) 6. **`toToml(description, prompt)`** -- TOML serializer - Escape TOML strings properly - Use multi-line strings (`"""`) for prompt field - Simple string for description ### Phase 3: Writer **Create `src/targets/gemini.ts`** Output structure: ``` .gemini/ ├── commands/ │ ├── plan.toml │ └── workflows/ │ └── plan.toml ├── skills/ │ ├── agent-name-1/ │ │ └── SKILL.md │ ├── agent-name-2/ │ │ └── SKILL.md │ └── original-skill/ │ └── SKILL.md └── settings.json (only mcpServers key) ``` Core function: `writeGeminiBundle(outputRoot, bundle)` - `resolveGeminiPaths(outputRoot)` -- detect if path already ends in `.gemini` to avoid double-nesting (follow droid writer pattern) - Write generated skills to `skills//SKILL.md` - Copy original skill directories to `skills/` via `copyDir()` - Write commands to `commands/` as `.toml` files, creating subdirectories for namespaced commands - Write `settings.json` with `{ "mcpServers": {...} }` via `writeJson()` with `backupFile()` for existing files - If settings.json exists, read it first and merge `mcpServers` key (don't clobber other settings) ### Phase 4: Wire into CLI **Modify `src/targets/index.ts`** ```typescript import { convertClaudeToGemini } from "../converters/claude-to-gemini" import { writeGeminiBundle } from "./gemini" import type { GeminiBundle } from "../types/gemini" // Add to targets: gemini: { name: "gemini", implemented: true, convert: convertClaudeToGemini as TargetHandler["convert"], write: writeGeminiBundle as TargetHandler["write"], }, ``` **Modify `src/commands/convert.ts`** - Update `--to` description: `"Target format (opencode | codex | droid | cursor | pi | gemini)"` - Add to `resolveTargetOutputRoot`: `if (targetName === "gemini") return path.join(outputRoot, ".gemini")` **Modify `src/commands/install.ts`** - Same two changes as convert.ts ### Phase 5: Tests **Create `tests/gemini-converter.test.ts`** Test cases (use inline `ClaudePlugin` fixtures, following existing converter test patterns): - Agent converts to skill with SKILL.md frontmatter (`name` and `description` populated) - Agent with empty description gets default description text - Agent with capabilities prepended to body - Agent `model` field silently dropped - Agent with empty body gets default body text - Command converts to TOML with `prompt` and `description` fields - Namespaced command creates correct path (`workflows:plan` -> `workflows/plan`) - Command with `disable-model-invocation` is still included - Command `allowedTools` silently dropped - Command with `argument-hint` gets `{{args}}` placeholder in prompt - Skills pass through as directory references - MCP servers convert to settings.json-compatible config - Content transformation: `.claude/` paths -> `.gemini/` - Content transformation: `~/.claude/` paths -> `~/.gemini/` - Content transformation: `Task agent(args)` -> natural language skill reference - Hooks present -> `console.warn` emitted - Plugin with zero agents produces empty generatedSkills array - Plugin with only skills works correctly - TOML output is valid (description and prompt properly escaped) **Create `tests/gemini-writer.test.ts`** Test cases (use temp directories, following existing writer test patterns): - Full bundle writes skills, commands, settings.json - Generated skills written as `skills//SKILL.md` - Original skills copied to `skills/` directory - Commands written as `.toml` files in `commands/` directory - Namespaced commands create subdirectories (`commands/workflows/plan.toml`) - MCP config written as valid JSON `settings.json` with `mcpServers` key - Existing `settings.json` is backed up before overwrite - Output root already ending in `.gemini` does NOT double-nest - Empty bundle produces no output ### Phase 6: Documentation **Create `docs/specs/gemini.md`** Document the Gemini CLI spec as reference, following existing `docs/specs/codex.md` pattern: - GEMINI.md context file format - Custom commands format (TOML with `prompt`, `description`) - Skills format (identical SKILL.md standard) - MCP server configuration (`settings.json`) - Extensions system (for reference, not converted) - Hooks system (for reference, format differences noted) - Config file locations (user-level `~/.gemini/` vs project-level `.gemini/`) - Directory layout conventions **Update `README.md`** Add `gemini` to the supported targets in the CLI usage section. ## What We're NOT Doing - Not converting hooks (Gemini has hooks but different format -- `BeforeTool`/`AfterTool` with matchers -- warn and skip) - Not generating full `settings.json` (only `mcpServers` key -- user-specific settings like `model`, `tools.sandbox` are out of scope) - Not creating extensions (extension format is for distributing packages, not for converted plugins) - Not using `@{file}` or `!{shell}` placeholders in converted commands (would require analyzing command intent) - Not transforming content inside copied SKILL.md files (known limitation -- skills may reference `.claude/` paths internally) - Not clearing old output before writing (matches existing target behavior) - Not merging into existing settings.json intelligently beyond `mcpServers` key (too risky to modify user config) ## Complexity Assessment This is a **medium change**. The converter architecture is well-established with five existing targets, so this is mostly pattern-following. The key novelties are: 1. The TOML command format (unique among all targets -- need simple TOML serializer) 2. Agents map to skills rather than a direct 1:1 concept (but this is the same pattern as codex) 3. Namespaced commands use directory structure (new approach vs flattening in cursor/codex) 4. MCP config goes into a broader `settings.json` file (need to merge, not clobber) Skills being identical across platforms simplifies things significantly. The TOML serialization is simple (only two fields: `description` string and `prompt` multi-line string). ## References - [Gemini CLI Repository](https://github.com/google-gemini/gemini-cli) - [Gemini CLI Configuration](https://geminicli.com/docs/get-started/configuration/) - [Custom Commands (TOML)](https://geminicli.com/docs/cli/custom-commands/) - [Agent Skills](https://geminicli.com/docs/cli/skills/) - [Creating Skills](https://geminicli.com/docs/cli/creating-skills/) - [Extensions](https://geminicli.com/docs/extensions/writing-extensions/) - [MCP Servers](https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html) - Existing cursor plan: `docs/plans/2026-02-12-feat-add-cursor-cli-target-provider-plan.md` - Existing codex converter: `src/converters/claude-to-codex.ts` (has `uniqueName()` and skill generation patterns) - Existing droid writer: `src/targets/droid.ts` (has double-nesting guard pattern) - Target registry: `src/targets/index.ts` ## Completion Summary ### What Was Delivered - [x] Phase 1: Types (`src/types/gemini.ts`) - [x] Phase 2: Converter (`src/converters/claude-to-gemini.ts`) - [x] Phase 3: Writer (`src/targets/gemini.ts`) - [x] Phase 4: CLI wiring (`src/targets/index.ts`, `src/commands/convert.ts`, `src/commands/install.ts`) - [x] Phase 5: Tests (`tests/gemini-converter.test.ts`, `tests/gemini-writer.test.ts`) - [x] Phase 6: Documentation (`docs/specs/gemini.md`, `README.md`) ### Implementation Statistics - 10 files changed - 27 new tests added (129 total, all passing) - 148 output files generated from compound-engineering plugin conversion - 0 dependencies added ### Git Commits - `201ad6d` feat(gemini): add Gemini CLI as sixth target provider - `8351851` docs: add Gemini CLI spec and update README with gemini target ### Completion Details - **Completed By:** Claude Opus 4.6 - **Date:** 2026-02-14 - **Session:** Single session