llm-gemini: a CLI plugin that puts Gemini models behind the LLM command
LLM plugin to access Google's Gemini family of models
At a glance
- What is it?
- llm-gemini is an Apache-2.0 Python plugin that registers Google's Gemini family with the LLM command line tool. It is a thin adapter, and the interesting parts are the multimodal attachments, the server-side tools, and the model list that moves faster than the plugin's own release cycle.
- Who is it for?
- Adopt llm-gemini if you already work inside the LLM CLI and want Gemini's multimodal input, CodeExecution and GoogleSearch without writing a Google SDK client. Do not adopt it if you need Vertex AI service accounts, regional endpoints, or a stable pinned model name in production; the README's own model list shows names such as gemini-flash-latest and several previews, and previews get retired.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 13 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap llm-gemini fills between the Gemini API and a shell prompt
Google ships SDKs, not a general-purpose command line client. If you want to send a prompt to a Gemini model from a shell script, a cron job, or an interactive terminal, you either write a small Python program against the Google client library or you use a wrapper that already speaks to many providers. llm-gemini takes the second route. It is a plugin for LLM, Simon Willison's CLI for talking to language models, and its job is to register Gemini models as LLM model IDs so that the rest of LLM's machinery (keys, logs, attachments, options, schemas) works unchanged.
That framing matters for who this is for. The audience is people who already have LLM installed and want Gemini to be one more `-m` flag away, not people looking for a standalone Gemini tool. The README is explicit about the install constraint: install the plugin in the same environment as LLM. Everything else in the plugin is downstream of that decision, including the fact that the model list is produced by running the installed LLM CLI, which is why the README's table is generated rather than hand-written.
How the plugin registers models and routes prompts to Gemini
The mechanism visible in the README is a plugin registration plus a generated model table. The README embeds a cog block that imports `llm.cli`, constructs a `CliRunner`, and invokes `["models", "-q", "gemini/"]`, then reverses the output and formats each line as a markdown bullet with an optional human note. That tells you two things about the architecture: model IDs are namespaced with a `gemini/` prefix inside LLM, and the plugin exposes them to LLM's model registry rather than maintaining a separate lookup table.
Because the models are registered with LLM, the data flow is LLM's, not the plugin's. LLM resolves the model ID, reads the key from its own key store or from `LLM_GEMINI_KEY`, builds the request, and hands it to the plugin's implementation, which calls the Gemini API. Attachments are handled by LLM's `-a` flag and passed through. Options such as `-o json_object 1` and `-o media_resolution X` are plugin-specific options layered onto the same call. Server-side tools are enabled with `-T CodeExecution` and `-T GoogleSearch`, which means the plugin maps LLM's tool flag onto Gemini's native tool declarations rather than implementing the tool loop itself.
The GoogleSearch section describes one detail worth noting: the plugin leaves the model's response text unchanged and retains Gemini's raw `groundingMetadata` on the response part, and the README points at `llm logs -c --json` as the way to inspect it. It also says that when Gemini returns native server-side tool invocation parts, the plugin exposes them as structured server-side tool call and result events. That is a deliberate choice to preserve provider-specific payloads instead of flattening them into prose, and it means the useful grounding data lives in the log record, not in the terminal output.
Getting it running: install, key, model alias, first prompt
The README gives a short path. Install with `llm install llm-gemini`. Set the key under the name `gemini` with `llm keys set gemini`, which prompts you to paste it, or skip the key store and assign the key to the environment variable `LLM_GEMINI_KEY`. Then run a model with the alias form: `llm -m gemini-flash-latest "A short joke about a pelican and a walrus"`.
If you would rather not type `-m` every time, the README shows `llm models default gemini-flash-latest` followed by a bare `llm "A joke about a pelican and a walrus"`. Model IDs appear in two forms: the namespaced `gemini/gemini-flash-latest` and an alias without the prefix. The README states that all listed models have aliases omitting the `gemini/` prefix, and its structured-output example uses the alias form directly: `llm -m gemini-flash-latest --schema 'name,age int,bio' 'invent a dog'`.
The model list in the README is long and includes both stable-looking names (gemini-2.5-flash) and moving ones (gemini-flash-latest, gemini-flash-lite-latest) alongside several previews such as gemini-3.1-pro-preview and gemini-3-flash-preview. One entry, `gemini/gemini-3.1-pro-preview-customtools`, has no human note attached in the generated table. That is not a defect in the README so much as a signal: the list is generated from whatever the installed plugin registers, so it will differ between versions.
Attachments, YouTube URLs and the media_resolution knob
This is where the plugin earns its place. Gemini models are multimodal, and the README shows attachments working for images, audio and video through LLM's existing `-a` flag. Local files: `llm -m gemini-flash-latest 'extract text' -a image.jpg`. Remote images: pass a URL to `-a` and ask for a description. Audio and video follow the same shape, `-a audio.mp3` for transcription and `-a video.mp4` with a prompt about what happens.
YouTube is treated as a special case worth calling out. You can pass a YouTube URL as the attachment and ask for a summary with relevant URLs and code snippets plus a timestamped transcript. The README notes that YouTube attachments are processed with media resolution `low` by default and that `-o media_resolution X` accepts `medium`, `high`, or `unspecified`. That default is a real constraint, not a footnote: if you are using the plugin to pull text out of a dense slide deck or a long recording, the low default is the setting most likely to cost you accuracy, and you have to remember to raise it per invocation. There is no config key mentioned for changing the default globally, so it is a per-command flag as documented.
The README also links to Google's file prompting strategies guide for multimodal advice, which is a sensible division of labour: the plugin handles transport, Google documents the prompting.
CodeExecution, GoogleSearch and what the plugin does not abstract away
Two server-side tools are exposed. `-T CodeExecution` lets the model write and run Python in a sandbox and use the result in its answer; the README's example asks for `(factorial of 13) * 3` computed in Python. `-T GoogleSearch` enables grounding with Google Search on models that support it, with the example `llm -m gemini-3.6-flash -T GoogleSearch 'What happened in Ireland today?'`.
The GoogleSearch documentation is where the plugin is most honest about its boundaries. The README states that using the feature may incur additional requirements in terms of how you use the results and points to Google's own documentation, rather than restating the terms. It then explains the design decision described above: response text is left unchanged, raw `groundingMetadata` is retained on the response part, and `llm logs -c --json` is the way to read it. There is a linked pull request comment for additional information about grounded results.
What the plugin does not do is normalize any of this. You get Gemini's tool semantics, Gemini's metadata shape, and Gemini's naming. If you are building something that needs a provider-neutral tool interface, this is the wrong layer to build it on; the plugin is a faithful pass-through, not an abstraction. The README also does not document error handling, retry behaviour, rate limit responses, or streaming, so anyone planning to run this inside an automated pipeline should treat those as unverified from the supplied material.
Where llm-gemini is the wrong tool
The clearest limitation is scope. This is an LLM plugin, so it inherits LLM's assumptions: a single API key per provider, a local key store, a local log database. Teams that authenticate to Google through Vertex AI service accounts, that need regional endpoints for data residency, or that route model calls through a gateway for auditing will not find those concerns addressed in the README. The documented credential paths are the `gemini` key in LLM's store and the `LLM_GEMINI_KEY` environment variable, and nothing else is mentioned.
The second limitation is model naming. The README's own list mixes `-latest` aliases, dated versions, and previews. An alias like `gemini-flash-latest` is convenient interactively and hostile to reproducibility, because the model behind it can change without any change on your side. Previews are announced as previews and can be withdrawn. If you pin a preview ID in a script, you have pinned something with a shorter expected life than the script. The README does not state a deprecation policy for the plugin's model list, so the safe assumption is that the list tracks Google's catalogue and that your pinned IDs need periodic checking.
The third is the thinness of the documentation around failure. There is no section on what happens when the key is missing, when a model ID is not available to your account, or when an attachment exceeds a size limit. Those are the situations where a CLI wrapper is most likely to be used, and the README does not cover them. That is not a reason to avoid the plugin, but it is a reason to test those paths yourself before depending on it.
The alternative: calling the Google SDK directly
The obvious alternative is the official Google Python client library for the Gemini API, used directly. The difference in approach is not features, since the plugin is calling that API anyway; it is where the code lives. With the SDK, you write a Python script per task, manage your own argument parsing, your own file handling, your own output formatting, and your own logging. With llm-gemini, all of that is LLM's, and the plugin contributes model registration and option mapping. You also get LLM's log database, which is what makes the `llm logs -c --json` inspection of `groundingMetadata` possible at all.
The trade-off runs the other way too. The SDK exposes the full API surface, including parameters the plugin may not map to a `-o` option, and it lets you control retries, timeouts and streaming in code. If your use of Gemini is embedded in an application rather than invoked from a terminal, the SDK is the more direct path and the plugin adds a dependency on LLM's release cadence on top of Google's. If your use is exploratory, scripted from a shell, or benefits from a uniform interface across providers, the plugin is the smaller amount of code to own. A third option worth naming is that LLM itself supports other providers through their own plugins, so the same command shape can reach a different model family by swapping the model ID and key, which is the main reason to prefer this over a bespoke script.
Maintenance, releases and the Apache-2.0 licence
The release history supplied shows 0.34 on 2026-09-02, 0.33 on 2026-08-13, and 0.32 on 2026-05-19. The gap between 0.32 and 0.33 is roughly three months, and 0.33 to 0.34 is about three weeks. That is an irregular cadence, which fits a plugin whose main job is to track a provider's model catalogue: releases cluster when Google ships models. Upgrading is a single command, `llm install llm-gemini`, or `llm install -U llm-gemini` to move to the newest version. There is no migration step documented, no config file to edit, and no database schema owned by the plugin, so the upgrade cost is close to zero unless a release changes how an option is spelled.
That last point is the real maintenance risk. Because the plugin's surface is largely a mapping onto Gemini's own option names, a change in Google's API can force a change in the plugin's `-o` and `-T` vocabulary. The README documents `-o json_object 1`, `-o media_resolution X`, `-T CodeExecution` and `-T GoogleSearch`; those strings are the contract your scripts depend on, and they are worth grepping for before you upgrade. The changelog is the place to check, and the README links to it from the release badge.
The licence is Apache-2.0, which permits commercial and private use, modification, and redistribution provided the licence and notices are preserved, and it includes an express grant of patent rights from contributors. It does not grant trademark rights, and it does not cover Google's API terms, which are a separate agreement between you and Google. Nothing here is legal advice; if you are redistributing the plugin inside a product, read the LICENSE file in the repository and take your own counsel on the API terms that sit underneath it.
Editorial conclusion
Adopt llm-gemini if you already work inside the LLM CLI and want Gemini's multimodal input, CodeExecution and GoogleSearch without writing a Google SDK client. Do not adopt it if you need Vertex AI service accounts, regional endpoints, or a stable pinned model name in production; the README's own model list shows names such as gemini-flash-latest and several previews, and previews get retired. Before committing, run llm models -q gemini/ in your installed environment to see which of the listed IDs your llm-gemini version actually registers, and check the changelog between your version and 0.34, since the model table is generated by cog from the installed plugin rather than from a static list.
Community notes