How Gemini CLI Commands Are Structured Using TOML: A Complete Guide
Gemini CLI commands are structured as individual TOML files placed in the .gemini/commands/ directory, with each file containing a description field and a multiline prompt field that supports runtime placeholders like {{args}}.
The EveryInc/compound-engineering-plugin repository provides a complete implementation for converting Claude Code commands into Gemini CLI's TOML-based format. Understanding this structure is essential for developers migrating existing AI-assisted workflows or building custom command suites for Google's Gemini CLI.
Understanding the TOML File Structure for Gemini CLI Commands
Required Fields
Each Gemini CLI command TOML file must contain two specific fields according to the specification in docs/specs/gemini.md (lines 30-38):
description: A concise, one-line string summary displayed when users run/helpprompt: A multiline string (using TOML's triple-quote syntax) containing the actual instructions sent to the model
The prompt field supports template variables that expand at runtime. The most common placeholder is {{args}}, which injects user-provided arguments into the prompt context.
File Path Conventions and Namespaces
The physical location of each TOML file determines its invocation syntax. Files reside under .gemini/commands/ and use directory nesting to create command namespaces:
.gemini/commands/git/commit.toml → /git:commit
.gemini/commands/workflows/plan.toml → /workflows:plan
As implemented in src/converters/claude-to-gemini.ts (lines 36-38), the resolveCommandPath function converts colon-separated command names into directory paths by splitting on the delimiter and joining with forward slashes.
How Commands Are Converted from Claude Code to Gemini TOML
The conversion pipeline in src/converters/claude-to-gemini.ts handles the transformation of Claude Code command definitions into Gemini-compatible TOML files through three key functions:
Name Resolution and Path Mapping
The resolveCommandPath function normalizes command names to filesystem-safe paths. It processes colon-separated identifiers (e.g., git:commit) into array segments (["git", "commit"]), then joins them for the final file location.
TOML Serialization
The toToml function (lines 44-53) constructs the final TOML content:
function toToml(description: string, prompt: string): string {
// Escapes backslashes and quotes for TOML safety
const escapedPrompt = prompt
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
return `description = "${description}"
prompt = """
${escapedPrompt}
"""
`
}
This implementation uses TOML's triple-quoted literal strings for the prompt field to preserve formatting and newlines while escaping internal quotes and backslashes to prevent parsing errors.
File System Writing
The writeGeminiBundle function in src/targets/gemini.ts (lines 21-24) persists each command to disk:
if (bundle.commands.length > 0) {
for (const command of bundle.commands) {
await writeText(
path.join(paths.commandsDir, `${command.name}.toml`),
command.content + "\n"
)
}
}
This ensures each command receives a .toml extension and resides in the correct subdirectory based on its namespace.
Practical Examples of Gemini CLI TOML Commands
Example TOML Command File
Here is a complete example of a generated command file located at .gemini/commands/git/commit.toml:
description = "Create a git commit with a good message"
prompt = """
Look at the current git diff and create a commit with a descriptive message.
User request: {{args}}
"""
The description appears when users list available commands, while the prompt contains the full instructions sent to the Gemini model, including the {{args}} placeholder that injects user input.
Conversion Implementation Example
The following TypeScript demonstrates how the plugin converts a Claude Code command definition into the Gemini TOML structure:
// src/converters/claude-to-gemini.ts
function convertCommand(command: ClaudeCommand, usedNames: Set<string>): GeminiCommand {
const commandPath = resolveCommandPath(command.name) // ["git","commit"]
const pathKey = commandPath.join("/") // "git/commit"
uniqueName(pathKey, usedNames) // track for deduplication
const description = command.description ?? `Converted from Claude command ${command.name}`
const prompt = command.argumentHint
? `${transformContentForGemini(command.body.trim())}\n\nUser request: {{args}}`
: transformContentForGemini(command.body.trim())
const content = toToml(description, prompt) // serialises to TOML
return { name: pathKey, content }
}
This function handles name resolution, description fallback logic, prompt transformation with optional argument injection, and final TOML serialization.
Summary
- Gemini CLI commands use TOML files stored in
.gemini/commands/with adescriptionstring and multilinepromptfield. - File paths define command namespaces: subdirectories create colon-separated command names (e.g.,
git/commit.tomlbecomes/git:commit). - The conversion pipeline in
src/converters/claude-to-gemini.tsusesresolveCommandPathfor name mapping andtoTomlfor serialization. - Runtime placeholders like
{{args}}in thepromptfield allow dynamic user input injection when commands execute.
Frequently Asked Questions
What fields are required in a Gemini CLI TOML command file?
Every TOML command file must contain exactly two top-level fields: description, which is a single-line string displayed in help menus, and prompt, which is a multiline string containing the instructions sent to the Gemini model. According to the specification in docs/specs/gemini.md, these fields are mandatory for the CLI to recognize and execute the command.
How does the file path determine the command name?
The Gemini CLI derives the invocation syntax from the relative path under .gemini/commands/. Directory separators become namespace colons, and the filename (minus extension) becomes the final command segment. As implemented in src/converters/claude-to-gemini.ts, the resolveCommandPath function splits colon-separated names and joins them with forward slashes, meaning git:commit maps to .gemini/commands/git/commit.toml.
Can I use variables or placeholders in the prompt field?
Yes, the prompt field supports template variables that expand at runtime. The most common placeholder is {{args}}, which injects any arguments the user provides after the command invocation. During conversion in src/converters/claude-to-gemini.ts, the pipeline appends \n\nUser request: {{args}} to prompts when the original Claude command accepts arguments, ensuring user input reaches the model context.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →