feat(cli): add models command and --list-models flag for model discovery

2 hours ago

This change adds a models command to Gemini CLI. Run gemini models and it prints every model the CLI can use, with its context limits. Ask for JSON output and the same list comes back as an array you can pipe straight into jq. A matching flag, list models, does the same thing.

Ask about this presentation

Chapters

  1. 0:00gemini models
  2. 0:19Why a models flag
  3. 0:35Three ways in
  4. 0:52Text output
  5. 1:11JSON output
  6. 1:31One function behind both
  7. 1:54Exit before the sandbox
  8. 2:15Clean stdout for jq
  9. 2:40What review changed
  10. 3:04Evidence: the tests
  11. 3:24What the listing doesn't know yet
  12. 3:43Try it
Show transcript

gemini models

Gemini CLI packages/cli

gemini models lists every model the CLI offers, as text or JSON

> gemini models -o json | jq . > gemini --list-models

This change adds a models command to Gemini CLI. Run gemini models and it prints every model the CLI can use, with its context limits. Ask for JSON output and the same list comes back as an array you can pipe straight into jq. A matching flag, list models, does the same thing.

Why a models flag

Gemini CLI src/config/config.ts

Models get the list-and-exit flag that extensions already had

--list-extensionsList all available extensions and exit. + --list-modelsList available Gemini models and exit.

Closes #27847

The author wants programmatic model discovery: JSON for scripts, and readable text for people. The CLI already had a flag that lists extensions and exits. This adds the matching flag for models, with the same style of help text.

Three ways in

Gemini CLI src/commands/models/list.ts

models, models list and --list-models print the same list

> gemini models > gemini models list > gemini --list-models

listCommand registers both names

command: ['$0', 'list']

Each takes -o json

You can reach the list three ways. Gemini models, gemini models list, or the list models flag, and all three land in the same handler. The bare command works because the list command registers as both $0 and list. Every form takes the JSON output format.

Text output

Gemini CLI src/commands/models/list.ts

By default, a list a person can read

> gemini models Available Gemini Models: auto (Auto) Description: Automatic model selection Context limit: 1,048,576 input tokens / 8,192 output tokens gemini-2.5-pro (Gemini 2.5 Pro) Description: Advanced reasoning model Context limit: 1,048,576 input tokens / 8,192 output tokens

Models from the fixture in models/list.test.ts

With no format option you get text. A bold heading, then each model's ID and display name, its description, and its context limit in input and output tokens. These rows use the test fixture's models, auto and Gemini 2.5 Pro, each with about a million input tokens.

JSON output

Gemini CLI src/commands/models/list.ts

With -o json, the same list is a JSON array

One ModelListing per model, limits as plain integers

[
  {
    "modelId": "auto",
    "id": "auto",
    "name": "Auto",
    "displayName": "Auto",
    "description": "Automatic model selection",
    "contextLimits": {
      "inputTokens": 1048576,
      "outputTokens": 8192
    }
  },
  …
]

Ask for JSON and you get an array, one object per model. Each object carries the model ID twice, as model ID and as id. The name appears twice as well, as name and display name. Then the context limits, written as plain integers, so jq can compare them without parsing commas.

One function behind both

Gemini CLI packages/cli/src

Both entry points end in handleList()

gemini models
subcommand
→
models.ts
middleware marks it a command, defers to listCommand
→
handleList()
list.ts loads config with extensions skipped, maps the models, writes stdout
--list-models
top-level flag
→
gemini.tsx
main() branches on argv.listModels

Here's how the pieces connect. The subcommand lives in models.ts, a yargs module whose middleware marks the run as a command and defers to the list command. The flag is parsed in config, and main, in gemini.tsx, branches on it. Both paths call handleList, which loads config with extensions skipped, maps the models, and writes the listing.

Exit before the sandbox

Gemini CLI src/gemini.tsx

--list-models exits before the sandbox starts

No sandbox, no interactive UI

await runDeferredCommand(settings.merged);

if (argv.listModels) {
  try {
    await handleListModels({
      outputFormat,
      loadedSettings: settings,
      argv,
    });
    await runExitCleanup();
    process.exit(ExitCodes.SUCCESS);
  } catch (error) {
    process.stderr.write(…);
    process.exit(ExitCodes.FATAL_INPUT_ERROR);
  }
}

// hop into sandbox if … sandboxing is enabled
if (!process.env['SANDBOX'] && !argv.isCommand) {

The flag's branch sits in main, right after deferred commands run. When it's set, it lists the models, cleans up, and exits with success. A failure goes to standard error and exits with the fatal input error code. All of this runs before the sandbox check, so listing models never starts a sandbox or the interactive UI.

Clean stdout for jq

Gemini CLI src/commands/models/list.ts

JSON goes to stdout, and everything else goes to stderr

> gemini models -o json | jq .
stdout
the JSON array, and nothing else, for jq
stderr
warnings, logs and errors
initializeOutputListenersAndFlush(config) skipExtensions: true

Piping into jq only works if standard output carries nothing but JSON. The listing is the only thing written there, and everything else goes to standard error. The handler sets up the CLI's output listeners right after loading config, which routes the rest to standard error. Config loads with extensions skipped, and listing bypasses the non-interactive auth refresh, so credential logs stay out of the pipe.

What review changed

Gemini CLI src/commands/models/list.ts

Review flagged five edge cases, and each one got a fix

Unknown model
tokenLimit(m.modelId) TypeError
tokenLimit(m.modelId) ?? DEFAULT_TOKEN_LIMIT
Missing name
m.name ?? m.modelId
Missing list
(availableModels ?? []).map(…)
Handler throws
process.exitCode = 1; finally exitCli()
Shared defaults
{ ...DEFAULT_CLI_ARGS, ...options?.argv }

The automated reviewer, Gemini Code Assist, flagged several edge cases. The sharpest: a model missing from the token table could get an undefined limit, and the text formatter would crash calling to locale string on it. It now falls back to the default token limit. Missing names and lists got defaults too, failures now set an exit code, and the shared default arguments are copied.

Evidence: the tests

Gemini CLI models.test.ts · models/list.test.ts
17

unit tests in 2 new files

Validated on Linux with npm run

  • recognizes "gemini models" as a command without falling through to a prompt query
  • accepts "gemini --list-models" flag without throwing unknown argument error
  • should fall back to DEFAULT_TOKEN_LIMIT for unknown or custom models
  • should output models in JSON format when outputFormat is json
  • should write error to stderr and still call exitCli on failure

Seventeen unit tests across two new files back this up. They check that gemini models parses as a command rather than a prompt, that the flag is accepted, that unknown models get the default limit, that JSON output parses, and that a failure still exits cleanly. The author validated on Linux with npm run.

What the listing doesn't know yet

Gemini CLI src/commands/models/list.ts

Every model reports 8,192 output tokens

const outputTokens = 8192;

DEFAULT_TOKEN_LIMIT

input limit for models the token table doesn't know

tier?

declared on ModelListing, never filled

docs

README and docs unchanged

Two values in the listing are fixed rather than looked up. Output tokens is the constant 8,192 for every model. A model missing from the token table reports the default input limit. The tier field is declared but unused, and the docs and README are unchanged.

Try it

Gemini CLI #29880

gemini models lists every model the CLI offers, as text or JSON

> npm run build > node packages/cli/dist/index.js models -o json | jq .

To try it from a checkout, follow the pull request's validation steps. Build, then run the models command from the built package with JSON output, piped into jq. Gemini models lists every model the CLI offers, as text or JSON.

What happened in AI today, in your inbox

One short email each evening. Read it and you’re caught up.

Built in New York with ❤️