Advanced Features

Since v2.2.0

Command line

Fuego 2.2 adds a fuego command: the same app, the same projects, license and settings, usable from a terminal with or without the desktop app open. Documents, queries, exports and imports, Authentication users, fuego:// links, the MCP server and the AI assistant, with JSON output and exit codes made for scripts.

What it is

fuego is Fuego from a terminal. It is the same application, started with a command instead of a double-click, so it uses the projects, credentials, license and settings you already configured in the app — there is nothing to sign in to twice, and a project that is read-only or marked as production in Fuego stays that way on the command line.

It works with or without the desktop app open. Reading and writing documents, queries, exports, imports and Authentication users run on their own. A few commands talk to the running app — opening a resource in a window, controlling the MCP server built into Fuego — and start the app when it is not running.

fuego doc get users/alice profile.name -p my-project
fuego query orders --where 'status == paid' --order created:desc --limit 20
fuego user get alice@example.com
fuego ai "how many orders were paid last week?"
fuego open users/alice

fuego --help in a terminal: usage, examples and the commands grouped by area

Install

Choose Help ▸ Install Command Line Tool… in the app menu. The item disappears once the command is installed. The Home page offers the same Install button in a banner, shown until the command is installed or you choose Don’t show again.

  • macOS and Linux — a fuego symlink to the Fuego executable, placed in the first writable of /usr/local/bin, /opt/homebrew/bin (when it exists) and ~/.local/bin. When none is writable the app asks for an administrator password and uses /usr/local/bin. ~/.local/bin may need a line in your shell profile to be on the PATH; fuego cli status tells you.
  • Windows — a small fuego.cmd in the WindowsApps folder of your local application data (already on every user’s PATH), or in %LOCALAPPDATA%\Programs\Fuego\bin, which is then added to your user PATH. Open a new terminal afterwards.

The same thing is available as a command, useful on a machine you reach over SSH or in a setup script:

fuego cli status               # installed? where? on the PATH?
fuego cli install              # --dir to pick the folder, --force to replace another fuego
fuego cli uninstall

From a terminal cli install never asks for a password: when no folder is writable it prints the sudo line to run instead.

fuego and Fuego. The application binary is Fuego (Fuego.exe on Windows); the command is the lowercase fuego link. The name decides what the process does: typing fuego … never opens a window, and double-clicking the app, or opening a fuego:// link or a .fuego file from the desktop, never opens a terminal. Running fuego with no arguments prints the help.

Development builds of Fuego keep their data in a separate configuration folder (fuego version shows which one) and fuego cli install refuses to link them unless you pass --force.

First steps

fuego project list
fuego project get shop-prod
fuego project databases shop-prod

Projects are added and edited in the app; the command line uses them as they are. project list shows each project with its Firebase project id, how it signs in, its databases and a Status column carrying the markers set in Fuego — production, read-only, emulator — plus disabled for a project beyond your plan’s limit. Every other command takes the project from, in this order:

  1. -p / --project — a label, a Firebase project id, emulator:<id> or the host:port of a running Firebase emulator;
  2. the FUEGO_PROJECT environment variable;
  3. the default saved with fuego config set project <ref>;
  4. the only configured project, when there is just one;
  5. when you are at a terminal and Fuego is open, the project currently selected in Fuego — the command says so: Using project Shop (current in Fuego).

Otherwise the command stops and lists the configured projects. The database follows the same idea: -d / --database, then FUEGO_DATABASE, then fuego config set database, then the database open in Fuego when Fuego is on that same project, then (default).

fuego config set project shop-prod
fuego config set database analytics
fuego config get
fuego config unset database

Emulator projects appear in project list only while Fuego is running and has found them; from the command line you can always refer to an emulator by its host:port, for example -p 127.0.0.1:8080.

Reading data

A document

fuego doc get users/alice                       # a readable tree
fuego doc get users/alice profile.name email    # only these fields
fuego doc get users/alice email                 # one field: prints just the value
fuego doc get users/alice --json | jq .age
fuego doc get users/alice --at 2026-09-01T10:00:00Z

Paths are relative to the database root — users/alice, users/alice/orders/42 — and fields use dot notation. Only the fields you name are read from Firestore. --at shows the document as it was at a past moment, within the database’s point-in-time recovery window. A document that does not exist ends with exit code 3.

Without --json the document is printed as a short header — the id, the path, when it was created, updated and read, its size — followed by a tree of the fields, with timestamps, references, geopoints and bytes shown as the values they are rather than as nested objects. What Fuego notes for itself while the app is open, such as whether a document is being edited, is never shown as a row: those keys only exist in the --json form, and doc set ignores them.

--json prints the document in the Fuego wire form: timestamps as {"__time__": …}, references as {"__ref__": …}, geopoints as {"__lat__": …, "__lng__": …}, bytes as {"__bytes__": …}. It is the form doc set and doc create accept back unchanged, so a document can be copied from one place to another without losing types. --simple prints a readable JSON where those become plain strings — easier to read, not round-trippable — and cannot be combined with --json.

Ids and collections

fuego doc list users                            # ids only, one per line, 100 per page
fuego doc list users --limit 500 --page-token "$TOKEN"
fuego collection list                           # root collections
fuego collection list users/alice               # subcollections of a document
fuego col list --refresh --json                 # read the list from Firestore again

doc list prints ids without reading the documents, including the ids of documents that only exist as parents of subcollections. When a page is not the last, the token for the next one is printed on standard error (and in nextPageToken with --json).

Queries

fuego query orders --where 'status == paid' --order created:desc --limit 20
fuego query users --where 'age >= 18' --where 'country in ["IT","FR"]' --select name,email
fuego query orders --group --where 'total > 100' --count
fuego query users --jsonl | jq -c 'select(.age > 30)'

Each --where is one condition, all combined with AND, written as 'field op value' or, for the comparisons, as field=value (==), field!=value, field<value, field<=value, field>value, field>=value. The operators are the ones of the query builder:

OperatorExample
== != < <= > >='age > 18', 'name == "Ann Lee"'
in not-in'role in ["admin","staff"]'
array-contains'tags array-contains go'
array-contains-any'tags array-contains-any ["go","rust"]'
starts-with'name starts-with Jo'
between'age between [18,30]'

Values are read the way --set values are: valid JSON is JSON (18, true, "quoted text", [1,2]), 10.5 is a double, anything else is a string, and the ts:, ref:, geo:, bytes: and str: prefixes force a type — 'createdAt > ts:2026-01-01', 'owner == ref:users/alice'.

  • --order field, field:desc or -field, repeatable.
  • --select name,email reads only those fields. --offset skips documents.
  • --limit is how many documents come back. Leave it out and you get 50; give it a number and that is what you get, up to 500 — a larger number is cut to 500 with a warning on standard error, and 0 or a negative number is refused with exit code 2 rather than quietly becoming the default, so a script that computes a wrong limit finds out.
  • --group queries every collection with that id (a collection group).
  • --count prints only how many documents match ({"count": n} with --json); without --limit it counts them all, not just the first 50.
  • Without --json the result is a table: the id, then the fields that fit the width of the terminal — plain values (text, numbers, booleans, timestamps, references) before maps and arrays, long cells cut with an ellipsis. When fields are left out, a note on standard error says how many: pick them with --select, or use --json for everything.
  • --json prints {"docs": […], "count": n, "truncated": true|false, "mode": "classic"} ("pipeline" for a pipeline query); --jsonl streams one document per line, ready for jq or a file. When more documents match than came back, truncated is true and a note on standard error suggests a larger --limit; for the full set use fuego export. Two output shapes at once — --json --jsonl, --count --jsonl — are refused with exit code 2.
  • --query takes a whole query as JSON, in the form Fuego saves and exports (filters, orders, selection, limit, offset, or the pipeline stages of an Enterprise edition database); the other flags are added on top of it, and a limit inside it is kept unless --limit is given. A pipeline query takes --limit as its row cap and no other flag.

Changing data

Create, set, update

fuego doc create users --id alice --data '{"name": "Alice", "age": 36}'
fuego doc create users -f alice.json              # id chosen by Firestore
cat alice.json | fuego doc create users           # the JSON piped in

fuego doc set users/alice --data '{"name": "Alice", "age": 37}'   # replaces the document
fuego doc set users/alice --merge --data '{"age": 37}'            # keeps the other fields
fuego doc set users/alice -f alice.json --no-create               # fails when missing

fuego doc update users/alice --set age=37 --set profile.city=Milan
fuego doc update users/alice --set lastSeen=ts:now --unset legacyField
fuego doc update users/alice --data '{"tags": ["a", "b"]}'

The document comes from --data, from a file with -f (-f - for standard input), or from whatever is piped in. doc create fails when --id names a document that already exists and prints the created path. doc set replaces the stored document unless --merge is given, and creates a missing one unless --no-create is. doc update changes only the fields you name and requires the document to exist. Metadata keys pasted from a doc get --json output (__path__, __create_time__, …) are ignored, so a document can be copied with doc get --json | doc set.

Values

A value after --set (and in --where) is read as JSON when it is valid JSON, and as a string otherwise: age=37 stores the integer 37, city=Milan the string Milan, tags='["a","b"]' an array, active=true a boolean, note=null a null. Numbers: a whole literal is stored as an integer, a literal with a fraction or an exponent (10.0, 1e3) as a double. Five prefixes force a type when the text alone would be read differently:

PrefixExampleStored as
ts:ts:2026-01-31T12:00:00Z, ts:2026-01-31, ts:nowtimestamp
ref:ref:users/bobdocument reference
geo:geo:45.46,9.19geopoint (latitude, longitude)
bytes:bytes:SGVsbG8=bytes (base64)
str:str:018, str:truethe string, verbatim

Inside a JSON document (--data, -f) the same types are written in the wire form: {"__double__": 10} for a double, {"__time__": "2026-01-01T00:00:00Z"} for a timestamp, {"__ref__": "users/bob"} for a reference, {"__lat__": 45.46, "__lng__": 9.19} for a geopoint and {"__bytes__": "SGVsbG8="} for bytes.

Delete

fuego doc delete users/alice
fuego doc delete users/alice -r -y                  # subcollections too, no questions
fuego doc delete users/alice --no-recursive --json  # keep the subcollections

doc delete lists the document’s subcollections first. With -r / --recursive they are deleted too; with --no-recursive they are kept (Firestore leaves them reachable under the deleted document); with neither flag you are asked, and the default answer is yes. The deletion is then confirmed unless -y / --yes is given, and a line saying what is being deleted, in which project, is always printed on standard error. A document that does not exist ends with exit code 3 before anything is asked. Deleting subcollections runs as a job whose counts are reported at the end.

WARNING

Without a terminal — in a script, in CI — the subcollection question is not asked and the subcollections are deleted. Pass --no-recursive when a script must keep them. The confirmation itself is different: without a terminal it stops the command with exit code 4 until you pass --yes.

Export and import

fuego export users -o users.jsonl
fuego export orders -o paid.csv --format csv --where 'status == paid'
fuego export comments -o all-comments.json --format json --group

fuego import users -f users.jsonl --dry-run
fuego import users -f users.jsonl
fuego import products -f catalog.csv -y

export writes a collection (or a collection group) with the same defaults as the app’s export dialog: every field, the document paths and the create/update times, special values in the wire form so the file can be imported back unchanged. Formats are json, json-array, jsonl (the default), csv and yaml; the extension is added when the file name lacks it and an existing file is overwritten. --where narrows the export with the syntax of fuego query.

import reads a file — the format follows its extension — with the defaults of the import dialog: every attribute, documents that do not exist are created, existing ones are updated field by field, and a document keeps the id of its __path__ or __name__ field when the file has one. --dry-run reads the file and reports what the import would do (rows, documents to create and to update, errors) without writing anything.

Both run as jobs: progress is shown on standard error, and Ctrl+C stops the job before the command exits. The jobs the desktop app runs are shown in the app; the command line has no list of them.

Authentication users

fuego user list --limit 20
fuego user list --all --jsonl > users.jsonl
fuego user list --filter email=alice@example.com --filter provider:google.com=1234567890
fuego user count
fuego user get alice@example.com
fuego user get +14155552671 --json

Every command that takes a <user> accepts a uid, an email address or an E.164 phone number. When more than one user matches (an email address that is also somebody’s uid) the command stops and lists them: pass the uid instead. user list pages with --limit and --page-token, or follows the pages to the end with --all; --filter looks up exact matches instead (email=, phone=, uid=, provider:<providerId>=<provider uid>, several are OR-ed).

# Create — the password from a secret manager, never on the command line
op read op://vault/alice/password | fuego user create --email alice@example.com --password-stdin
fuego user create --email bob@example.com --display-name "Bob" --email-verified
fuego user create --phone +14155552671

# Change
fuego user update alice@example.com --display-name "Alice Liddell" --email-verified
fuego user update alice@example.com --set-claim role=admin --set-claim level=3
fuego user update alice@example.com --claims '{"role":"viewer"}'     # replaces every claim
fuego user disable alice@example.com
fuego user enable alice@example.com
fuego user revoke alice@example.com          # signs the user out everywhere
fuego user delete alice@example.com          # asks first; -y to skip

Passwords come from --password-stdin (one line of standard input), from --password (visible to other users and kept in the shell history — a warning says so) or from a masked prompt when you are at a terminal. --set-claim merges into the current custom claims (a dotted name sets a nested claim); --claims replaces the whole object, and {} clears it. Claim values follow the value rules: role=admin is a string, level=3 a number, str:018 keeps a string that looks like a number.

# Tokens and links
fuego user token alice@example.com
fuego user token alice@example.com --claims '{"role":"admin"}' --id-token --json
fuego user token alice@example.com --id-token --api-key AIza...
fuego user link alice@example.com --type reset
fuego user link alice@example.com --type signin --url https://app.example.com/finish
fuego user tenants

user token mints a custom token; with --id-token it is exchanged for an ID token right away, through the project’s Web API key — the one stored in Fuego for the project, or --api-key — and the ID token, the refresh token and their lifetime are printed. Emulator projects cannot exchange tokens from here, and signing needs a credential with a private key (a project using your Google account may be refused). user link generates the password-reset, email verification or sign-in link Firebase would send, to deliver it through your own channel.

Multi-tenant projects (Identity Platform) take --tenant <id> on every user command; without it the commands work on the project’s default user pool.

Opening things in Fuego

fuego open                                   # bring Fuego to the front, starting it when needed
fuego open users/alice -p my-project         # a document
fuego open users                             # a collection
fuego open comments --group                  # a collection group
fuego open gs://my-project.appspot.com/photos/
fuego open user:8fJk2…                       # an Authentication user (--tenant for a tenant)
fuego open --query '{"collectionPath":"users", ...}'
fuego open fuego://…                         # a link, as it is
fuego open backup.fuego                      # a saved view, query, macro or backup
fuego open users/alice --print               # print the fuego:// link instead

open turns the resource into a fuego:// link and hands it to the running app — or starts Fuego with it. Firestore paths, Storage paths, users and queries belong to the project selected with -p (and -d), like every other command; the link carries the Firebase project id, so it works on any computer where that project is configured in Fuego.

--print writes the link on standard output instead of opening it, ready to paste into a ticket, a pull request or a chat message. fuego app is an alias of fuego open, and a bare fuego fuego://… opens the link too.

MCP

Fuego’s MCP server exposes your Firestore, Authentication and Storage data to AI tools such as Claude Code, Claude Desktop or Cursor, within the project scope and write mode configured in Settings › AI. The command line controls the server built into the app and can also serve the same tools itself.

fuego mcp status        # running? on which port?
fuego mcp start         # start it in the running app (switching it on when needed)
fuego mcp stop          # stop it; it stays enabled in Settings
fuego mcp enable        # switch it on in Settings and start it
fuego mcp disable       # switch it off and stop it
fuego mcp info          # URL, access token and ready-to-paste client snippets

status, start, stop, enable, disable and info talk to the running Fuego app; when it is not running, status and info still answer from the saved settings so the snippets can be prepared ahead, and the others end with exit code 6.

Serving MCP from the terminal

fuego mcp serve runs the server in the terminal process instead, without the desktop app. By default it talks MCP over standard input and output, which is how MCP clients start a local server — the client runs the command itself, on demand, and no token is needed:

claude mcp add fuego -- fuego mcp serve

For config-file clients (Claude Desktop, Cursor, …):

{
  "mcpServers": {
    "fuego": {
      "command": "fuego",
      "args": ["mcp", "serve"]
    }
  }
}

fuego mcp info prints both snippets, plus the ones for the server built into the app (claude mcp add --transport http fuego http://127.0.0.1:<port>/mcp --header "Authorization: Bearer …", the matching JSON with "type": "http", and a Codex line when the server is set to accept connections without a token). When the fuego command is not installed, the snippets use the full path of the executable.

With --http the same tools are served on an HTTP endpoint bound to this computer only, protected by an access token created for that run and printed once on standard error (on standard output as JSON with --json); --port N picks the port, otherwise a free one is used and printed at start. Fuego’s own MCP settings are not touched — the app’s port, token and network options stay as they are — while its project scope, write mode and anonymization settings apply here too.

fuego mcp serve --http
fuego mcp serve --http --port 43111

AI assistant

fuego ai "how many orders were paid last week?" -p shop
echo "list the collections" | fuego ai --json
fuego ai                                              # interactive chat
fuego ai --conversation 4f2c… "and by country?"      # continue a saved conversation
fuego ai --new "start over, ignore the last one"      # a fresh conversation (the default)
fuego ai "delete the test orders" --approve-tools      # approve every question in advance
fuego ai providers
fuego ai conversations

It is the same assistant as in the app, with the same tools, safety rules and saved conversations. With a message (or one piped on standard input) the reply is streamed to standard output and the command ends; tool steps are shown on standard error. Without a message, at a terminal, an interactive chat opens:

  • Enter sends, Shift+Enter (or Alt+Enter, Ctrl+J) adds a line, Esc clears the input, PgUp / PgDn scroll the transcript.
  • Ctrl+C stops the answer being written; pressed again, or when nothing is running, it quits.
  • Before a tool writes data, sends real data to the provider or acts outside the project you are on, the assistant asks inline: [y]es, [n]o, [a]lways for this conversation, and [z] anonymized where masking is an option. When it needs a choice, press the option’s number or type an answer.

Without a terminal the answer to every one of those confirmations is no, said out loud on standard error so a log explains the assistant’s reply. To let it work unattended, pass --approve-tools, which answers yes to every confirmation before it is asked — use it only when you know what the assistant is about to do. -y / --yes, which answers the confirmations of the other commands, deliberately does not approve these: a flag added to skip a “delete this document?” prompt must never hand the assistant a free pass.

A question the assistant asks you — as opposed to a confirmation — ends the command with exit code 4 when there is no terminal to answer on, when --json is in use, or when the question has no ready-made options; at a terminal you pick one of the options and the conversation goes on. Writes stay off unless enabled in Fuego › Settings › AI, and a production project is never written; the command prints the provider, the model and the write mode on standard error when it starts.

The provider is --provider (by name or id), then FUEGO_AI_PROVIDER, then the first configured provider that supports tool calling; --model picks the chat model. With -p / -d (or a saved default project) the assistant starts scoped to that project and database. --conversation <id> continues a saved conversation, keeping its provider and model unless the flags say otherwise; --new starts a fresh one, which is what happens anyway, and the two together are refused. --json prints the reply, the steps and the artifacts as one document.

Scripting

  • Standard output carries data only — documents, ids, tables, JSON, links, tokens. Progress, prompts, warnings and errors go to standard error. This is what makes fuego … | jq and fuego … > file safe.
  • --json prints results as JSON, and errors as {"error": {"code": "…", "message": "…"}} on standard error. --jsonl (on query and user list) streams one record per line.
  • -y / --yes answers every confirmation a command asks; without it, a command that must ask and has no terminal stops with exit code 4. The one thing it does not cover is the AI assistant’s tool confirmations, which have their own --approve-tools.
  • -q / --quiet silences progress and informational lines; --no-color, or NO_COLOR in the environment, drops the colors and keeps bold and underline. Everything is stripped when the output is not a terminal.
  • Nothing but the command’s own messages reaches standard error: Fuego’s diagnostic logging is switched off for a command, so one failure is always one line (or one JSON record). Set FUEGO_DEBUG=1 to see the log lines too when something needs investigating.
  • Flags that contradict each other are refused with exit code 2 rather than one of them being quietly ignored: two output shapes at once (query --json --jsonl, query --count --jsonl, doc get --simple --json), two opposite answers (doc delete -r --no-recursive, user update --claims … --set-claim …, user create --password … --password-stdin, ai --conversation … --new), or a flag that needs another one (user list --filter with --all or --page-token, user token --api-key without --id-token, mcp serve --port without --http).
  • Ctrl+C stops the running job cleanly and exits with 130; a second Ctrl+C exits immediately.
Exit codeMeaning
0Success
1The command failed for another reason
2Wrong command line: unknown command or flag, missing argument, no project selected
3The document, user or project does not exist
4A confirmation or a choice was needed and there was no terminal: rerun with --yes or the missing flag
5The license or plan does not allow the action (invalid or expired license, machine limit, read-only database, plan feature)
6The command needs the Fuego desktop app and it is not running
130Interrupted with Ctrl+C

Shell completion is built in:

source <(fuego completion zsh)                       # this session
fuego completion zsh > "${fpath[1]}/_fuego"         # every new zsh session
fuego completion bash > /etc/bash_completion.d/fuego
fuego completion fish > ~/.config/fish/completions/fuego.fish
fuego completion powershell | Out-String | Invoke-Expression

fuego completion <shell> --help has the details for each shell.

Plans

The command line uses the license activated in the app: on the free plan, accept the terms in Fuego once; with a paid license, the command verifies it when it starts (and tells you when the license service cannot be reached). The plan features are the same as in the app:

NeedsCommands
MCPfuego mcp serve, and the server built into the app that mcp start / mcp enable control
Exportfuego export
Importfuego import (the --dry-run preview does not)
Point-in-time recoveryfuego doc get --at
Multi-tenant--tenant on the user commands, fuego user tenants

A command that needs a feature your plan lacks says which one and ends with exit code 5. Projects beyond the plan’s limit show as disabled in project list and cannot be used until the plan allows them.