Give Claude Code a Gemini sidekick — image, video, and image-generation skills it doesn't have natively, one shell command away.
gemini-delegate is a small, deterministic CLI (plus a Claude Code subagent that
drives it) for handing the multimodal work Claude is weaker at —
image→text, video→text, and text→image — to the Google Gemini API, plus
grounded web search for the long tail. It works in any project, prints one
clean JSON envelope per call, and never puts your API key on the command line.
It's built as two strictly separated layers:
- Mechanism — the
gemini-delegateCLI. It only talks to Gemini: build the request, move the media, run the call, persist multi-turn state, print one JSON envelope. No judgment, no project awareness. - Policy — a Claude Code subagent that drives the CLI: it writes the prompt, validates Gemini's output (it'll even look at a generated image, since it's multimodal too), shapes results to your project, retries when unsure, and hands back only the clean artifact.
main Claude Code session
│ delegates a multimodal task
▼
gemini-delegate subagent (policy: prompt, validate, structure, follow up)
│ shell calls
▼
gemini-delegate CLI ──► core lib ──► Gemini API
│
▼
JSON envelope on stdout ──► subagent validates ──► clean result to main session
Status: built and live-verified — 122 offline unit tests (the Gemini client is mocked; no network, no key) plus a gated live smoke test across every subcommand.
| Capability | What it does | vs Claude Code | vs direct Python library | vs best free local model (RTX 3090) |
|---|---|---|---|---|
Image → text · describe |
Read, OCR, and analyze images | Some improvement (big on dense OCR / fine technical detail) | Significant improvement · pytesseract (OCR only) | Significant improvement · Qwen2.5‑VL‑7B |
Video → text · video |
Answer questions about a video file or YouTube URL | ❌ · no comparable option | ❌ · no comparable option | Significant improvement · Qwen2.5‑VL‑7B |
Text → image · image |
Generate / edit images (Nano Banana), incl. transparent PNGs | ❌ · no comparable option | ❌ · no comparable option | Significant improvement (in-image text, editing) · FLUX.1‑schnell |
Text / reasoning · ask |
Q&A, structured extraction, multi-turn follow-ups | Worse | ❌ · no comparable option | Equivalent · Qwen2.5‑32B |
Web search · search |
Grounded Google web search → answer + source URLs | Some improvement (niche / non-English / very recent) | ❌ · no comparable option | ❌ · no comparable option |
Each rating describes how the Gemini integration compares to that alternative (Worse / Equivalent / Some improvement / Significant improvement; ❌ = no comparable option). The Python column looks for a library that does the task directly — a classical/algorithmic approach, not a wrapper around the neural model in the next column — so it's ❌ for anything that fundamentally needs a model (video understanding, image generation, general reasoning). The local-model column targets a single NVIDIA RTX 3090 (24 GB VRAM). This is a rough, hand-made guide — not benchmarks — and there are surely capable libraries or models our search didn't surface. (More capabilities — speech, audio understanding, music, embeddings — are on the integration roadmap.)
1. Get a Gemini API key. Grab a free one from Google AI Studio — sign in with a Google account and click Create API key. Drop it in the key file (read once, never logged, never passed on the command line):
mkdir -p ~/.config/gemini-delegate
printf 'GEMINI_API_KEY=%s\n' "YOUR_KEY_HERE" > ~/.config/gemini-delegate/.env
chmod 600 ~/.config/gemini-delegate/.env(Or just export GEMINI_API_KEY=… in your shell — the CLI checks the environment
first, then $GEMINI_DELEGATE_ENV, then that key file.)
2. Install the CLI (Python ≥ 3.11):
pipx install --editable . # or: make install-editable
gemini-delegate --help3. Install the driving subagent (user scope → available in every project):
make install-agent # copies agents/gemini-delegate.md to ~/.claude/agents/4. Try it:
gemini-delegate describe photo.jpg --prompt "What's in this image? One sentence."
gemini-delegate image --transparent --prompt "a small orange mascot, pixel art" --out mascot.pngFrom inside Claude Code, just ask: "use the gemini-delegate subagent to describe these screenshots and pull out the error text as JSON."
gemini-delegate describe <image...> --prompt TEXT [--json] [--schema PATH] [--session PATH] [--model ROLE|ID]
gemini-delegate video <file|url> --prompt TEXT [--json] [--schema PATH] [--session PATH] [--model ROLE|ID]
gemini-delegate image --prompt TEXT --out PATH [--ref PATH ...] [--n INT] [--model ROLE|ID]
[--transparent] [--chroma-key COLOR] [--chroma-tolerance INT] [--keep-original]
gemini-delegate ask --prompt TEXT [--session PATH] [--json] [--schema PATH] [--model ROLE|ID]
gemini-delegate search --prompt TEXT [--prompt-file PATH] [--model ROLE|ID]
| Option | Meaning |
|---|---|
--prompt TEXT |
The instruction (one of --prompt/--prompt-file required everywhere). |
--prompt-file PATH |
Read the prompt from a file — handy for long/multi-line prompts (cleaner shell, fewer approval prompts). |
--json |
Request structured JSON (sets Gemini's JSON response mode). |
--schema PATH |
A JSON Schema file; implies --json; enforced at the API boundary. |
--session PATH |
Multi-turn: prior turns are read, the new turn sent, the response appended. |
--model ROLE|ID |
A logical role (text, vision, video, image, image_pro, reason) or an explicit model ID. |
--size SIZE |
(image only) Output resolution — one of 512, 1K, 2K, 4K. |
--aspect-ratio RATIO |
(image only) Aspect ratio hint: 1:1, 16:9, 4:3, etc. |
--endpoint VALUE |
(image only) Force the generation backend: auto (default), interactions, or generate_content. |
--transparent |
(image only) Generate on a flat key color and chroma-key it to a transparent PNG/WebP. |
--chroma-key COLOR |
(image only) The flat color to remove (e.g. #FF00FF or magenta). Default: #FF00FF. Implies keying without --transparent when used alone. |
--chroma-tolerance INT |
(image only) Per-channel tolerance for the chroma-key (0–255). Default: 60. |
--keep-original |
(image only) Also save the un-keyed original beside the output as <stem>.orig.jpg. |
--cleanup |
(session commands) Delete this session's uploaded Files API objects. |
--debug |
Print a traceback to stderr on failure (stdout stays clean JSON). |
The image subcommand has two generation backends, picked via --endpoint or
[image].endpoint in config:
| Value | Behaviour |
|---|---|
auto (default) |
Try the interactions surface first; fall back to generate_content automatically if it fails. |
interactions |
Force the Interactions API (Beta). Best quality and 4K support; no free tier for image_pro. |
generate_content |
Force the classic generate_content path. |
Both paths produce the same JSON envelope; a warnings entry is added when an
automatic fallback happens, so you always know which backend ran.
# Pro model, 4K, forced Interactions path
gemini-delegate image --model image_pro --endpoint interactions \
--size 4K --aspect-ratio 16:9 \
--prompt "A red maple leaf on white, studio lighting" --out leaf.png--transparent generates on a flat magenta key color (#FF00FF) and chroma-keys
it out to an alpha channel, so you get a PNG/WebP with true transparency. A few
things to know:
- Don't put a background in your prompt. Let the model fill the field with the key color; asking for a specific background fights the keying step.
- Output must be
.pngor.webp. JPEG can't store alpha — a.jpg/.jpeg--outis rejected with a usage error. - JPEG-source caveat. Gemini returns JPEG internally, so hard edges can pick
up faint fringing after keying. Tune
--chroma-tolerance, or use--keep-originalto grab the un-keyed image and clean up by hand.
gemini-delegate image --transparent --prompt "a small orange creature" --out creature.png
gemini-delegate image --transparent --chroma-tolerance 80 --keep-original --prompt "a small orange creature" --out creature.pngFun fact: this project's logo was generated by
gemini-delegateitself via the--transparentpath.
search runs a Google-grounded web query through Gemini and returns the answer
plus the source URLs it grounded on. Claude Code already has a solid native
web search, so reach for this on the long tail — niche, non-English, or
very-recent topics where the native search comes up short (e.g. hunting down
Japanese hardware/emulation forum threads).
gemini-delegate search --prompt "Sega Super Prologue 21 SKC-3000 service manual or teardown"text holds the grounded answer; json holds { sources: [{uri, title, domain}], queries: [...] } so you can go straight to the source links. A warnings entry appears if the model answered without actually searching.
Grounding can run slow. Every call uses a finite client-side timeout (default 120s, set
[client].timeout_secondsorGEMINI_DELEGATE_TIMEOUT=<seconds>;0disables) so a stalled request fails witherror.type: "timeout"rather than hanging forever — raise it if a legitimately heavy search needs longer.
Every invocation prints exactly one JSON object to stdout and sets its exit
code from the result: 0 ok, 1 failure, 2 usage/argument error.
On a JSON-parse failure the tool sets ok:false with an error — it never
silently falls back to text.
$ gemini-delegate ask --prompt "Reply with exactly one word: pong"
{"ok": true, "op": "ask", "model": "gemini-3.5-flash", "text": "pong", "json": null,
"files": [], "session": null, "usage": {"input_tokens": 8, "output_tokens": 1},
"warnings": [], "error": null}Model selection goes through logical roles resolved from TOML — model IDs are
config, never code, so when Google moves a model you edit one file, not source.
Resolution order: $GEMINI_DELEGATE_CONFIG →
~/.config/gemini-delegate/config.toml → packaged default
(config/gemini-delegate.toml). A single role can
also be pinned via GEMINI_DELEGATE_MODEL_<ROLE> for a one-off run. Costs default
to the cheap Flash-tier roles; image_pro (Nano Banana Pro) and reason are
opt-in.
A consuming project can steer the subagent's output without any code change:
drop a ./.claude/gemini.md describing the desired output shape (e.g. a JSON
Schema for extracted fields), naming conventions, and where generated assets go.
The subagent reads it — along with the project CLAUDE.md — before every
delegation.
mkdir -p .claude
cp examples/project-gemini.md .claude/gemini.md # then edit for your projectSee examples/project-gemini.md for a worked
example (schema, asset naming, model/cost policy, validation expectations).
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
make test # 122 offline unit tests; the Gemini client is mocked (no network, no key)The live smoke test is gated behind RUN_LIVE=1 and a present
GEMINI_API_KEY; it is never part of make test:
RUN_LIVE=1 python scripts/smoke_test.py # one real call per subcommandconfig/gemini-delegate.toml packaged default config (model roles, paths)
src/gemini_delegate/
config.py load TOML + env overrides, resolve model roles
media.py inline vs Files API, sha256 upload cache, cleanup
session.py session JSON read / append / write
core.py all Gemini logic; returns plain dicts
image_backends.py Interactions + generateContent image backends, endpoint dispatch
imaging.py transcode + chroma-key transparency (Pillow only)
cli.py Click commands; wraps core into the JSON envelope
agents/gemini-delegate.md the subagent (installed to ~/.claude/agents/)
examples/project-gemini.md sample .claude/gemini.md for a consuming project
docs/integration-roadmap.md planned capabilities (speech, audio, music, embeddings)
scripts/smoke_test.py gated live smoke test
tests/ offline unit tests (mocked client)
MIT

{ "ok": true, "op": "describe", "model": "gemini-3.5-flash", "text": "A red square.", "json": null, // populated when --json/--schema is used "files": [], // absolute paths to anything written "session": null, // absolute session path when --session is used "usage": { "input_tokens": 1104, "output_tokens": 11 }, "warnings": [], "error": null // { "type": "...", "message": "..." } on failure }