16 KiB
title, type, status, completed_date, completed_by, actual_effort, date
| title | type | status | completed_date | completed_by | actual_effort | date |
|---|---|---|---|---|---|---|
| Add Gemini CLI as a Target Provider | feat | completed | 2026-02-14 | Claude Opus 4.6 | Completed in one session | 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) 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
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/helpprompt(string): The prompt sent to the model; supports{{args}},!{shell},@{file}placeholders
Skill (SKILL.md) Format
---
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)
{
"mcpServers": {
"server-name": {
"command": "npx",
"args": ["-y", "package-name"],
"env": { "KEY": "value" }
}
}
}
Acceptance Criteria
bun run src/index.ts convert --to gemini ./plugins/compound-engineeringproduces valid Gemini config- Agents convert to
.gemini/skills/*/SKILL.mdwith populateddescriptionin frontmatter - Commands convert to
.gemini/commands/*.tomlwithpromptanddescriptionfields - Namespaced commands create directory structure (
workflows:plan->commands/workflows/plan.toml) - Commands with
argument-hintinclude{{args}}placeholder in prompt - Commands with
disable-model-invocation: trueare still included (TOML commands are prompts, not code) - Skills copied to
.gemini/skills/(identical format) - MCP servers written to
.gemini/settings.jsonundermcpServerskey - Existing
.gemini/settings.jsonis backed up before overwrite, and MCP config is merged (not clobbered) - Content transformation rewrites
.claude/and~/.claude/paths to.gemini/and~/.gemini/ /workflows:plantransformed to/workflows:plan(Gemini preserves colon namespacing via directories)Task agent-name(args)transformed toUse the agent-name skill to: args- Plugins with hooks emit
console.warnabout format differences - Writer does not double-nest
.gemini/.gemini/ modelandallowedToolsfields silently dropped (no Gemini equivalent in skills/commands)- Converter and writer tests pass
- Existing tests still pass (
bun test)
Implementation
Phase 1: Types
Create src/types/gemini.ts
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<string, {
command?: string
args?: string[]
env?: Record<string, string>
url?: string
headers?: Record<string, string>
}>
}
Phase 2: Converter
Create src/converters/claude-to-gemini.ts
Core functions:
-
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.warnifplugin.hookshas entries
- Convert each agent to a skill via
-
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
modelfield (no Gemini equivalent) - If description is empty, generate from agent name:
"Use this skill for ${agent.name} tasks"
- Frontmatter:
-
convertCommand(command, usedNames)-- command -> TOML file- Preserve namespace structure:
workflows:plan-> pathworkflows/plan descriptionfield from command descriptionpromptfield 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)
- Preserve namespace structure:
-
transformContentForGemini(body)-- content rewriting.claude/->.gemini/and~/.claude/->~/.gemini/Task agent-name(args)->Use the agent-name skill to: args@agent-namereferences ->the agent-name skill- Skip file paths (containing
/) and common non-command patterns
-
convertMcpServers(servers)-- MCP config- Map each
ClaudeMcpServerentry to Gemini-compatible JSON - Pass through:
command,args,env,url,headers - Drop
typefield (Gemini infers transport)
- Map each
-
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.geminito avoid double-nesting (follow droid writer pattern)- Write generated skills to
skills/<name>/SKILL.md - Copy original skill directories to
skills/viacopyDir() - Write commands to
commands/as.tomlfiles, creating subdirectories for namespaced commands - Write
settings.jsonwith{ "mcpServers": {...} }viawriteJson()withbackupFile()for existing files - If settings.json exists, read it first and merge
mcpServerskey (don't clobber other settings)
Phase 4: Wire into CLI
Modify src/targets/index.ts
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<GeminiBundle>["convert"],
write: writeGeminiBundle as TargetHandler<GeminiBundle>["write"],
},
Modify src/commands/convert.ts
- Update
--todescription:"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 (
nameanddescriptionpopulated) - Agent with empty description gets default description text
- Agent with capabilities prepended to body
- Agent
modelfield silently dropped - Agent with empty body gets default body text
- Command converts to TOML with
promptanddescriptionfields - Namespaced command creates correct path (
workflows:plan->workflows/plan) - Command with
disable-model-invocationis still included - Command
allowedToolssilently dropped - Command with
argument-hintgets{{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.warnemitted - 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/<name>/SKILL.md - Original skills copied to
skills/directory - Commands written as
.tomlfiles incommands/directory - Namespaced commands create subdirectories (
commands/workflows/plan.toml) - MCP config written as valid JSON
settings.jsonwithmcpServerskey - Existing
settings.jsonis backed up before overwrite - Output root already ending in
.geminidoes 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/AfterToolwith matchers -- warn and skip) - Not generating full
settings.json(onlymcpServerskey -- user-specific settings likemodel,tools.sandboxare 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
mcpServerskey (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:
- The TOML command format (unique among all targets -- need simple TOML serializer)
- Agents map to skills rather than a direct 1:1 concept (but this is the same pattern as codex)
- Namespaced commands use directory structure (new approach vs flattening in cursor/codex)
- MCP config goes into a broader
settings.jsonfile (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
- Gemini CLI Configuration
- Custom Commands (TOML)
- Agent Skills
- Creating Skills
- Extensions
- MCP Servers
- 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(hasuniqueName()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
- Phase 1: Types (
src/types/gemini.ts) - Phase 2: Converter (
src/converters/claude-to-gemini.ts) - Phase 3: Writer (
src/targets/gemini.ts) - Phase 4: CLI wiring (
src/targets/index.ts,src/commands/convert.ts,src/commands/install.ts) - Phase 5: Tests (
tests/gemini-converter.test.ts,tests/gemini-writer.test.ts) - 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
201ad6dfeat(gemini): add Gemini CLI as sixth target provider8351851docs: 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