Metrics
All pages
Docs ยท Read and analyzeMarkdown

Command line

o41-metrics reads and organizes Metrics from a shell: sign-in, organizations, members and keys, folders, experiments, series as JSON and series as PNG charts. Every command prints newline-delimited JSON on standard output, reports errors on standard error with exit code 1, and addresses experiments by slug and folders by path, so an agent or a script can drive it without parsing text.

Install

curl -fsSL https://metrics.041.io/install.sh | sh
o41-metrics --version

The installer verifies SHA-256 and installs o41-metrics in ~/.local/bin for Linux and macOS on x64 and arm64. It needs no Node.js or npm. Set O41_METRICS_INSTALL_DIR to choose another directory and put it first on PATH. Windows is not supported. o41-metrics --help and o41-metrics <command> --help list every flag.

Update

Run o41-metrics update --check to check the release, or o41-metrics update to install it. The CLI verifies SHA-256 before replacing the binary.

Standalone installations check for updates in the background at most once every six hours. Later commands use the updated binary; running commands keep their version. Set O41_AUTO_UPDATE=0 for all 041 CLIs, or O41_METRICS_AUTO_UPDATE=0 for Metrics, to keep a fixed version. CI and npm installations skip automatic updates. If you installed with npm, run npm uninstall -g @o41/metrics after installing the standalone binary so the npm copy does not hide it on PATH.

Sign in

o41-metrics auth login            # choose browser sign-in or an API key
o41-metrics auth login --device   # browser sign-in without the prompt
printf '%s' "$KEY" | o41-metrics auth login --stdin
o41-metrics auth status
o41-metrics auth logout
flagcommandmeaning
--deviceauth loginsign in with a browser on any device
--org <org>auth loginwith --device, the organization id, slug or name to act in
--stdinauth loginread an organization API key from standard input
--host <origin>auth loginAPI origin to sign in to
--forceauth logoutdelete the login even if its API key cannot be revoked now

Browser sign-in prints a link and a short code on standard error. Open the link in a browser on any device, for example your laptop when the CLI runs over SSH, sign in or create an account, pick the organization, and confirm the code. The CLI waits for the approval, saves the login and refreshes its tokens as needed. Only a browser sign-in can manage organizations, members and API keys.

A browser sign-in also creates a personal API key of the active organization, named after you and the machine, and saves it with the login, because DuckDB and the Python API client read only an API key from it. The key works only while you are a member of that organization. org switch and org create replace it with a key of the new organization; auth logout revokes it. If the key cannot be revoked, logout keeps the login so you can retry; --force deletes the login anyway and names the key to revoke with api-key revoke. See organizations and keys.

auth login without --device or --stdin asks which method to use and needs a terminal; in a script, pass one of the two. auth login --stdin verifies the key before saving it and prints the host, the organization and the time.

Agents and CI usually skip the saved login and set the key in the environment:

export O41_METRICS_API_KEY=ak_...

Host

The CLI talks to https://metrics.041.io unless auth login --host or O41_METRICS_HOST names another origin; the older https://metrics.syvain.com is the same service. The host is saved with the login. Browser sign-in is accepted only for those two production hosts, and a saved browser sign-in refuses to send its tokens to a different O41_METRICS_HOST.

A key goes only to the host it is paired with. auth login logs in to --host, then O41_METRICS_HOST, then the saved login's host, then production, and verifies the new key there. Other commands use O41_METRICS_API_KEY, with O41_METRICS_HOST, then production; the saved login's host never applies to an environment key. Without an environment key they use the saved login with the host it was saved for. If O41_METRICS_HOST names another host, the command fails with host_mismatch instead of sending the saved key or tokens there. Usage reports follow the same rules. Empty or Unicode-whitespace-only values are unset, including API-key input from stdin.

For example, with a saved login for https://custom.example.test, setting O41_METRICS_API_KEY alone sends that key to https://metrics.041.io, not to the custom host.

Addressing

  • An experiment is its id or its slug: experiment get mamba-run-001.
  • A folder is its absolute path or its id: /models/mamba. The root is /. Paths come from the organization's folder tree, so quote names with spaces: "/ablations/no norm".

A command reads the folder list once to resolve folder references, and looks up an experiment by its id or slug alone. Only experiment list without --folder reads every experiment. An unknown reference fails with experiment_not_found or folder_not_found. An overloaded directory answers these reads with directory_busy; the command waits as long as it asks and retries for up to 20 seconds, then fails with directory_busy and how long to wait.

Folders

o41-metrics folder list                   # every folder with its path
o41-metrics folder list /models           # direct children of one folder
o41-metrics folder create /models/mamba   # creates missing parents; idempotent
o41-metrics folder move /scratch/run-1 /models
o41-metrics folder move /scratch/run-1 /  # to the root
o41-metrics folder rename /models/mamba mamba-v2

Each folder line is {folderId, name, parentFolderId, path}. folder create prints the leaf with created: true when it made any folder on the path and created: false when the whole path existed.

Experiments

o41-metrics experiment list --search mamba      # slug substring, case-insensitive
o41-metrics experiment list --folder /models    # direct members only
o41-metrics experiment get mamba-run-001
o41-metrics experiment move mamba-run-001 /models/mamba
o41-metrics experiment rename mamba-run-001 "Mamba, lr 3e-4"
o41-metrics experiment catalog mamba-run-001
o41-metrics experiment annotations mamba-run-001 --limit 20
commandprints one line per
experiment list [--folder <folder>] [--search <text>]experiment: experimentId, slug, displayName, folderId, folderPath
experiment get <experiment>the experiment: status, description, meta, error, lifecycle timestamps, folderPath and url in the app
experiment move <experiment> <folder>the moved experiment with its new folderPath
experiment rename <experiment> <name>the experiment with its new displayName; "" clears it
experiment catalog <experiment>series: seriesName and the metadata keys with every value seen
experiment annotations <experiment> [--limit N]annotation, newest first

The API returns at most 100,000 annotations of one experiment; when the reply reaches that count, a warning on standard error says older ones were omitted.

A display name is what the app shows for a run; the slug stays its identity, and every command still takes the slug or the id.

Series

series query prints one line per experiment, series and metadata partition, with the points as aligned columns:

{
  "experimentId": "019e...",
  "seriesName": "loss",
  "metadata": { "split": "valid" },
  "data": {
    "step": [200, 400],
    "timestampMs": [1789776000000, 1789776060000],
    "value": [2.91, 2.64]
  }
}
o41-metrics series query mamba-run-001 --name loss --filter split=valid
o41-metrics series query mamba-run-001 > run-001.jsonl    # every series
o41-metrics series query run-a run-b --name loss --x-axis timestamp
o41-metrics series latest run-a run-b --name loss --name tokens
o41-metrics series query run-a --name loss --from-step 1000 --to-step 2000
o41-metrics series query run-a --name loss --since 2026-11-05T00:00:00Z
flagmeaning
<experiment>...one or more experiment ids or slugs
--name, -nseries name, repeatable; omit to read every series in each experiment's catalog
--filter, -fkey (key present) or key=value (exact match), repeatable; all must match
--x-axisstep (default) or timestamp; the axis points are sorted by

step is null for points stored without one. For more than a few experiments, or anything you would group or join, use DuckDB.

series latest returns the highest-step point per metric and metadata partition, using the same JSON shape. Use --x-axis timestamp for the newest event timestamp. Omit --name to include all metrics. Both latest and query accept inclusive --from-step / exclusive --to-step, or inclusive --since / exclusive --until (ISO 8601 or epoch milliseconds). Use one kind of bounds per query. Latest reads with bounds choose the latest point within the window. Stale latest responses fail with stale_series; latest validates completion before printing each experiment.

series render draws the same selection as a PNG. Every experiment, series and metadata partition becomes one line, so narrow it with --filter.

o41-metrics series render run-a run-b --name loss --filter split=valid -o loss.png
o41-metrics series render run-a --name loss --display-mode subplots -o loss.png
flagmeaning
--name, -nseries name, repeatable; at least one is required
--filter, -f, --x-axisas for series query
--output, -oPNG path, or - for standard output; required
--titlechart title
--width320 to 2,400 px, default 1,200
--height240 to 1,600 px, default 720
--themelight (default) or dark
--display-modelines (default), one chart; or subplots, one per line

With a file path, series render prints {"path": ..., "bytes": ...}. The points are sent to the public Metrics renderer at https://metrics-renderer.syvain.com, which returns the image.

Views

A view is a saved app workspace: which runs, which charts, which axes. Views belong to the organization. link prints an app link to a state without saving it, the way to show someone a result.

o41-metrics view list
o41-metrics view get "LR sweep"
o41-metrics view create "LR sweep" --folder /mamba/lr-sweep --state '{"defaults":{"logY":true}}'
o41-metrics view update "LR sweep" --name "LR sweep, log y"
o41-metrics view delete "LR sweep, log y"
o41-metrics view link --experiment run-a --experiment run-b --state '{"groupBy":"experiment"}'
commandprints
view listone line per view: viewId, name, timestamps and its app url
view get <view>the view with its full state and url
view create <name> [--state <json>] [selection flags]the saved view: viewId, name, updatedAtMs, url
view update <view> [--name <name>] [--state <json>]the same; the state is replaced whole, the name kept unless given
view delete <view>viewId, name and deleted: true
view link [--state <json>] [selection flags]url and the full state it carries

A view is its id or its exact name; names are unique in the organization. --state is the workspace state as JSON, where every field is optional. The selection flags add runs to the state's selection by name instead of id: --folder <path or id> selects a folder with every run in it, including later ones, and --experiment <slug or id> one run. Both repeat.

Organizations, members and keys

These need a browser sign-in. Changing members, invitations and API keys needs the admin role.

o41-metrics org list                   # your organizations; current marks the active one
o41-metrics org switch my-lab          # id, slug or name
o41-metrics org create "My Lab"        # you become admin; switches to it

o41-metrics member list
o41-metrics member role ada@example.com admin
o41-metrics member remove ada@example.com

o41-metrics invitation create ada@example.com --role member
o41-metrics invitation list
o41-metrics invitation revoke orginv_...

o41-metrics api-key create ci --expires-in-days 90 | jq -r .secret
o41-metrics api-key list
o41-metrics api-key revoke ak_...
commandarguments and flags
member role<member> <role>: user id or email; admin, member or a role key such as org:admin
member remove<member>
invitation create<email> [--role <role>], default member
invitation revoke<invitation id>
api-key create<name> [--description <text>] [--expires-in-days N], default never expires
api-key revoke<api key id>

api-key create prints the key with its secret once; nothing can show it again. A revoked key can still be accepted for up to a minute. More on roles and keys in organizations and keys.

docs search finds the pages of these docs that answer a question, best first. It needs no login and asks O41_METRICS_HOST, or production when that is unset.

o41-metrics docs search "how do I compare runs in DuckDB"
o41-metrics docs search series render legend --limit 3

The words are joined with spaces into one query. --limit takes 1 to 20 pages, default 5. It prints one line, {"query", "site", "hits"}; each hit has title, url, markdownUrl, score from 0 to 1, and snippets, the matching passages. Fetch a hit's markdownUrl to read the whole page. An error from the site fails with http_error and the site's message.

Feedback

feedback sends a bug report or an idea to the 041 team. It needs no login, so it works when signing in is what broke, and it attaches what the team needs to reproduce: the CLI and Bun versions, the OS and its version, the host, and the login's organization and user (never a key). It sends no working directory, file or environment variable.

o41-metrics feedback --bug "series query --x-axis timestamp drops the last point"
o41-metrics feedback "a --since flag on experiment list would save a folder walk"
o41-metrics feedback --bug --reply-to me@example.com - < report.md   # long reports from stdin

It prints the receipt, {"id", "surface", "receivedAt"}. --reply-to takes an email address or a URL, such as an issue, where the team can answer.

Environment variables

variableeffect
O41_METRICS_API_KEYorganization API key; when set, the saved login is not read
O41_METRICS_HOSTAPI origin for the environment key; a saved login is used only if this names its host
O41_METRICS_ANALYTICSoff, 0 or false stops the usage report
DO_NOT_TRACKany value but empty, 0 or false stops the usage report
XDG_CONFIG_HOMEthe login is saved in $XDG_CONFIG_HOME/o41-metrics/auth.json, default ~/.config/o41-metrics/auth.json

The login file is written with mode 0600.

Usage analytics

After each command the CLI sends one usage report to the Metrics host it talks to: the command path, such as o41-metrics experiment list, never an argument or a value; whether it succeeded, and its error code if not; how long it took; the CLI version, operating system and architecture; whether CI is set; whether a coding agent runs it; and whether standard output is a terminal. The API records it for the user and organization of the credential the command uses, and without one for nobody. The report waits at most 1.5 seconds, and a failed report never changes the command's output or exit code. O41_METRICS_ANALYTICS=off or DO_NOT_TRACK=1 turns it off.

Output contract

  • Standard output carries only results: one JSON object per line, or PNG bytes for series render -o -. A command that finds nothing prints nothing and exits 0.
  • Standard error carries sign-in prompts, warnings and errors. An error is one line, Error (<code>): <message>, where <code> is stable, for example not_logged_in, experiment_not_found or an API error code.
  • The exit code is 0 on success and 1 on any failure.
  • Nothing asks a question except auth login without --device or --stdin, and that one refuses to run without a terminal.

Related: working as an agent, DuckDB, organizations and keys, HTTP API.