# Command line

`syvain-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

```bash
npm install -g syvain-metrics
syvain-metrics --version
```

The package installs a standalone `syvain-metrics` binary for Linux, macOS and
Windows on x64 and arm64; it needs no Node.js at run time.
`syvain-metrics --help` and `syvain-metrics <command> --help` list every flag.

## Sign in

```bash
syvain-metrics auth login            # choose browser sign-in or an API key
syvain-metrics auth login --device   # browser sign-in without the prompt
printf '%s' "$KEY" | syvain-metrics auth login --stdin
syvain-metrics auth status
syvain-metrics auth logout
```

| flag              | command       | meaning                                                      |
| ----------------- | ------------- | ------------------------------------------------------------ |
| `--device`        | `auth login`  | sign in with a browser on any device                         |
| `--org <org>`     | `auth login`  | with `--device`, the organization id, slug or name to act in |
| `--stdin`         | `auth login`  | read an organization API key from standard input             |
| `--host <origin>` | `auth login`  | API origin to sign in to                                     |
| `--force`         | `auth logout` | delete 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](https://metrics.041.io/docs/duckdb.md) and the [Python API client](https://metrics.041.io/docs/python-client.md) 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](https://metrics.041.io/docs/organizations.md).

`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:

```bash
export SYVAIN_METRICS_API_KEY=ak_...
```

## Host

The CLI talks to `https://metrics.syvain.com` unless `auth login --host` or
`SYVAIN_METRICS_HOST` names another origin; `https://metrics.041.io` is the same
service. The host is saved with the login. Browser sign-in is accepted only for
the default host, and a saved browser sign-in refuses to send its tokens to a
different `SYVAIN_METRICS_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"`.

Every command that takes a reference reads the folder tree once to resolve it.
An unknown reference fails with `experiment_not_found` or `folder_not_found`.

## Folders

```bash
syvain-metrics folder list                   # every folder with its path
syvain-metrics folder list /models           # direct children of one folder
syvain-metrics folder create /models/mamba   # creates missing parents; idempotent
syvain-metrics folder move /scratch/run-1 /models
syvain-metrics folder move /scratch/run-1 /  # to the root
syvain-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

```bash
syvain-metrics experiment list --search mamba      # slug substring, case-insensitive
syvain-metrics experiment list --folder /models    # direct members only
syvain-metrics experiment get mamba-run-001
syvain-metrics experiment move mamba-run-001 /models/mamba
syvain-metrics experiment rename mamba-run-001 "Mamba, lr 3e-4"
syvain-metrics experiment catalog mamba-run-001
syvain-metrics experiment annotations mamba-run-001 --limit 20
```

| command                                                 | prints 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:

```json
{
  "experimentId": "019e...",
  "seriesName": "loss",
  "metadata": { "split": "valid" },
  "data": {
    "step": [200, 400],
    "timestampMs": [1789776000000, 1789776060000],
    "value": [2.91, 2.64]
  }
}
```

```bash
syvain-metrics series query mamba-run-001 --name loss --filter split=valid
syvain-metrics series query mamba-run-001 > run-001.jsonl    # every series
syvain-metrics series query run-a run-b --name loss --x-axis timestamp
```

| flag              | meaning                                                                         |
| ----------------- | ------------------------------------------------------------------------------- |
| `<experiment>...` | one or more experiment ids or slugs                                             |
| `--name`, `-n`    | series name, repeatable; omit to read every series in each experiment's catalog |
| `--filter`, `-f`  | `key` (key present) or `key=value` (exact match), repeatable; all must match    |
| `--x-axis`        | `step` (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](https://metrics.041.io/docs/duckdb.md).

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

```bash
syvain-metrics series render run-a run-b --name loss --filter split=valid -o loss.png
syvain-metrics series render run-a --name loss --display-mode subplots -o loss.png
```

| flag                         | meaning                                                   |
| ---------------------------- | --------------------------------------------------------- |
| `--name`, `-n`               | series name, repeatable; at least one is required         |
| `--filter`, `-f`, `--x-axis` | as for `series query`                                     |
| `--output`, `-o`             | PNG path, or `-` for standard output; required            |
| `--title`                    | chart title                                               |
| `--width`                    | 320 to 2,400 px, default 1,200                            |
| `--height`                   | 240 to 1,600 px, default 720                              |
| `--theme`                    | `light` (default) or `dark`                               |
| `--display-mode`             | `lines` (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.

```bash
syvain-metrics view list
syvain-metrics view get "LR sweep"
syvain-metrics view create "LR sweep" --folder /mamba/lr-sweep --state '{"defaults":{"logY":true}}'
syvain-metrics view update "LR sweep" --name "LR sweep, log y"
syvain-metrics view delete "LR sweep, log y"
syvain-metrics view link --experiment run-a --experiment run-b --state '{"groupBy":"experiment"}'
```

| command                                                 | prints                                                            |
| ------------------------------------------------------- | ----------------------------------------------------------------- |
| `view list`                                             | one 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](https://metrics.041.io/docs/app.md#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.

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

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

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

syvain-metrics api-key create ci --expires-in-days 90 | jq -r .secret
syvain-metrics api-key list
syvain-metrics api-key revoke ak_...
```

| command             | arguments 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](https://metrics.041.io/docs/organizations.md).

## Environment variables

| variable                 | effect                                                                                                          |
| ------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `SYVAIN_METRICS_API_KEY` | organization API key; when set, the saved login is not read                                                     |
| `SYVAIN_METRICS_HOST`    | API origin; overrides the saved host                                                                            |
| `XDG_CONFIG_HOME`        | the login is saved in `$XDG_CONFIG_HOME/syvain-metrics/auth.json`, default `~/.config/syvain-metrics/auth.json` |

The login file is written with mode `0600`. On Windows it lives under
`%APPDATA%\syvain-metrics`.

## 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](https://metrics.041.io/docs/agents.md), [DuckDB](https://metrics.041.io/docs/duckdb.md),
[organizations and keys](https://metrics.041.io/docs/organizations.md), [HTTP API](https://metrics.041.io/docs/api.md).

---

Metrics by 041 documentation. Every page: https://metrics.041.io/llms.txt
