Skip to content

CLI Usage

github-actions[bot] edited this page Aug 8, 2026 · 8 revisions

ed usage

ed usage reports what your coding agents cost and how close you are to a provider's rate limit. Every number is read back out of the two files behind the app's dashboard, usage.json and limits-history.jsonl, so the reporting verbs never recompute anything and the CLI and the UI cannot disagree. Reach for it when you want a spend figure in a script, a per-project breakdown without opening the window, or a gate on how much session budget is left.

Both files live in Repo.dataDir, which is ~/Library/Application Support/Edith/data unless the repoPath setting names a confirmed development checkout, in which case it is apps/dashboard/data inside that checkout. Every read verb here works whether or not Edith is running. Two invocations go further. ed usage refresh runs the collection pipeline itself, in this process, and rewrites usage.json with the app open or closed. ed usage limits --refresh asks the app to poll the providers first, which makes it the one invocation here that needs Edith running and exits 4 when it is closed.

At a glance

Command What it does
ed usage Runs ed usage summary, the default subcommand
ed usage limits Session and weekly rate limits per provider, newest observation per provider
ed usage summary Cost and tokens over a window, in total and per source
ed usage daily Cost and tokens per calendar day, oldest first
ed usage models Cost and tokens per model, most expensive first
ed usage projects Cost and tokens per project, most expensive first
ed usage sources The agents that produced the history, with their ids
ed usage machines Runs ed usage machines ls, the default subcommand
ed usage machines ls Every configured machine, whether it is counted, and what it adds up to
ed usage machines collect Runs the collector on a machine over SSH and brings its numbers back
ed usage machines enable Counts a machine on every later refresh
ed usage machines disable Stops collecting from a machine, keeping what it already gave
ed usage machines forget Drops what a machine gave and stops counting it
ed usage refresh Re-collects usage data from every agent on this Mac

Commands

ed usage limits

Prints the most recent rate limit observation for each provider Edith tracks.

ed usage limits [--refresh] [--json]

Options

Name Type / values Default What it does
--refresh flag off Asks the app to poll the providers again and waits up to 20 seconds for it to say it did, before reading the file. Fails when nothing answers
--json flag off Emit JSON on stdout

--json shape

A top-level array, one object per provider that has ever been recorded, in the fixed order codex then claude. session and weekly are each either an object or null.

[
  {
    "label": "Codex",
    "observedAt": "2026-08-08T16:39:59Z",
    "provider": "codex",
    "session": null,
    "weekly": {
      "percent": 0,
      "resetsAt": "2026-08-15T16:39:59Z",
      "resetsInSeconds": 604797.62
    }
  },
  {
    "label": "Claude",
    "observedAt": "2026-08-08T16:39:58Z",
    "provider": "claude",
    "session": {
      "percent": 30,
      "resetsAt": "2026-08-08T19:50:00Z",
      "resetsInSeconds": 11402.481
    },
    "weekly": {
      "percent": 61,
      "resetsAt": "2026-08-13T08:00:00Z",
      "resetsInSeconds": 400798.117
    }
  }
]

Examples

ed usage limits
ed usage limits --json
ed usage limits --refresh
ed usage limits --json | jq -r '.[] | select(.provider == "claude") | .session.percent'

Behaviour

Without --refresh the command mutates nothing and needs no app: it reads the tail of limits-history.jsonl and reports the last line it finds for each provider. Only the final 8 KB of that file is read, so a provider whose newest row has scrolled out of that window is treated as never seen and is left out of the output entirely.

percent is what the provider reported, stored rounded to one decimal place. resetsAt is the reset time the provider gave, or null when it gave none, and resetsInSeconds is computed at print time from your clock, so it goes negative once the reset moment has passed. The human table shows the session reset as a coarse duration instead, 3h 10m or 2d 4h, clamped at zero, and a - in any column the provider has not reported.

--refresh is the refresh button on the rate limit cards. It needs the menu bar app and exits 4 with refreshing the rate limits needs the Edith menu bar app to be running when Edith is closed. The reply it waits for is only posted when a poll actually succeeds, so a provider that is failing to answer costs you the full 20 seconds and then the command fails rather than printing the old numbers: exit 4 with Edith did not answer for refreshing the rate limits in time, or with the extension behind refreshing the rate limits is off when tabUsageEnabled is false. After one second of waiting ed prints waiting for Edith to answer... once, on stderr.

The listener goes up before the request goes out, so an app that answers within the same instant cannot beat it and a poll that worked is never reported as silence. The numbers printed afterwards are read from the file rather than out of the reply.

Even a successful refresh does not guarantee a newer observedAt: the app appends a history row only when the values differ from the previous one, so polling twice inside a quiet window leaves the timestamp where it was.

When no provider has ever been recorded the command exits 4 with no limit history yet, hinted with enable the Agent Usage extension and let Edith poll once. That check comes after the refresh, so a --refresh the app answers on a fresh install reports the emptiness afterwards if nothing landed.

$ ed usage limits
PROVIDER  SESSION  WEEKLY  SESSION RESETS  OBSERVED
Codex     -        0.0%    -               2026-08-08T16:39:59Z
Claude    30.0%    61.0%   3h 10m          2026-08-08T16:39:58Z

ed usage summary

Totals cost and tokens over a window, then breaks the same totals down by source. This is what a bare ed usage runs.

ed usage summary [--range <range>] [--source <source>]... [--machine <machine>]... [--json]

Options

Name Type / values Default What it does
--range today, week, month, all all Which days to include: today only, the last 7 days, the last 30 days, or everything on file
--source string, repeatable every source Count only these source ids. Repeat the flag to include several. An id the file does not list is an error
--machine machine name, ssh alias, id, or local every machine Count only the agents that ran on these machines. local is this Mac. Repeat the flag to include several. Union with --source rather than an intersection
--json flag off Emit JSON on stdout

--json shape

{
  "bySource": {
    "cli": {
      "cacheCreationTokens": 175571245,
      "cacheReadTokens": 5445483888,
      "cost": 5094.730294150003,
      "inputTokens": 348913,
      "outputTokens": 16022050,
      "tokens": 5637426096
    },
    "codex": {
      "cacheCreationTokens": 0,
      "cacheReadTokens": 39030016,
      "cost": 28.771853,
      "inputTokens": 919731,
      "outputTokens": 155273,
      "tokens": 40105020
    }
  },
  "days": 7,
  "generatedAt": "2026-08-08T16:44:18Z",
  "range": "week",
  "totals": {
    "cacheCreationTokens": 175571245,
    "cacheReadTokens": 5484513904,
    "cost": 5123.502147150003,
    "inputTokens": 1268644,
    "outputTokens": 16177323,
    "tokens": 5677531116
  }
}

tokens is the sum of the other four token fields, not a separate figure from the collector. days counts the days in the window that exist in the file, not the length of the window, so a week range over four days of history reports 4. generatedAt is the string usage.json carries verbatim, and is null when the file has no such field; ed does not reformat it.

Examples

ed usage summary
ed usage summary --range week
ed usage summary --range month --source cli --source codex
ed usage summary --range today --json | jq .totals.cost

Behaviour

Reads only, mutates nothing, and needs no app. It exits 4 when usage.json is missing, 1 when the file is there but will not decode, and 3 when --range is not one of the four ranges.

--source is validated against the file. An id nobody recognises exits 3 with no usage source named <id>, hinted with the ids the file does list, or with a pointer to ed usage refresh when it lists none, so a typo can no longer come back as a confident all-zero report. Run ed usage sources first to get ids that exist. --machine is checked the same way: a machine nothing was collected from exits 3 with no collected usage from a machine called <name>.

The human output puts three lines above the table, a dollar sign only on the cost line, and orders the table by source id:

$ ed usage summary --range week
cost    $5123.50
tokens  5677531116
days    7

SOURCE  COST     TOKENS
cli     5094.73  5637426096
codex   28.77    40105020

ed usage daily

One row per day in the window, cost and tokens.

ed usage daily [--range <range>] [--source <source>]... [--machine <machine>]... [--json]

Options

Name Type / values Default What it does
--range today, week, month, all all Which days to include
--source string, repeatable every source Count only these source ids. Repeat the flag to include several. An id the file does not list is an error
--machine machine name, ssh alias, id, or local every machine Count only the agents that ran on these machines. local is this Mac. Repeat the flag to include several. Union with --source rather than an intersection
--json flag off Emit JSON on stdout

--json shape

A top-level array sorted by date ascending. totals is the same six-field object ed usage summary uses.

[
  {
    "date": "2026-08-07",
    "totals": {
      "cacheCreationTokens": 26904348,
      "cacheReadTokens": 670275994,
      "cost": 647.5789594999999,
      "inputTokens": 180723,
      "outputTokens": 3836938,
      "tokens": 701198003
    }
  },
  {
    "date": "2026-08-08",
    "totals": {
      "cacheCreationTokens": 15319939,
      "cacheReadTokens": 765744244,
      "cost": 587.5631597500012,
      "inputTokens": 97362,
      "outputTokens": 3066068,
      "tokens": 784227613
    }
  }
]

Examples

ed usage daily --range week
ed usage daily --range month --source codex
ed usage daily --json | jq -r '.[] | [.date, .totals.cost] | @tsv'

Behaviour

Reads only, mutates nothing, and needs no app. Same exit codes as ed usage summary: 4 with no usage.json, 1 on a file that will not decode, 3 on a bad --range or a --source the file does not list.

Days are not filtered out by --source. A day that exists in the window but has no rows for the sources you asked for still gets a row, with every total at zero, so the date sequence stays continuous over the days the collector saw. It is still not a calendar: days the collector never recorded are absent, not zero-filled.

$ ed usage daily --range week
DATE        COST     TOKENS
2026-08-02  620.79   877878788
2026-08-03  761.36   729558198
2026-08-04  547.72   705959878
2026-08-05  1035.51  535978079
2026-08-06  922.99   1342730557
2026-08-07  647.58   701198003
2026-08-08  587.56   784227613

ed usage models

Cost and tokens per model, so you can see which model is actually spending the money.

ed usage models [--range <range>] [--source <source>]... [--machine <machine>]... [--json]

Options

Name Type / values Default What it does
--range today, week, month, all all Which days to include
--source string, repeatable every source Count only these source ids. Repeat the flag to include several. An id the file does not list is an error
--machine machine name, ssh alias, id, or local every machine Count only the agents that ran on these machines. local is this Mac. Repeat the flag to include several. Union with --source rather than an intersection
--json flag off Emit JSON on stdout

--json shape

A top-level array sorted by totals.cost descending.

[
  {
    "model": "claude-opus-5",
    "totals": {
      "cacheCreationTokens": 153031472,
      "cacheReadTokens": 4093821515,
      "cost": 3747.5389512500024,
      "inputTokens": 335270,
      "outputTokens": 13006480,
      "tokens": 4260194737
    }
  },
  {
    "model": "gpt-5.6-sol",
    "totals": {
      "cacheCreationTokens": 0,
      "cacheReadTokens": 39030016,
      "cost": 28.771853,
      "inputTokens": 919731,
      "outputTokens": 155273,
      "tokens": 40105020
    }
  }
]

Examples

ed usage models
ed usage models --range week
ed usage models --range month --source cli
ed usage models --json | jq -r '.[0].model'

Behaviour

Reads only, mutates nothing, and needs no app. Same exit codes as ed usage summary.

A row whose model name is missing from the file is grouped under the literal name unknown rather than dropped, so the model totals always add up to the summary totals for the same window and sources.

$ ed usage models --range week
MODEL                      COST     TOKENS
claude-opus-5              3747.54  4260194737
claude-fable-5             1202.34  777247635
claude-sonnet-5            144.57   598911741
gpt-5.6-sol                28.77    40105020
claude-haiku-4-5-20251001  0.28     1071983

ed usage projects

Cost and tokens per project, from the per-project rollup the collector attaches to each day.

ed usage projects [--range <range>] [--limit <n>] [--json]

Options

Name Type / values Default What it does
--range today, week, month, all all Which days to include
--limit integer greater than zero 25 Show at most this many projects, taken from the top of the cost order
--json flag off Emit JSON on stdout

This is the one window command that does not take --source. It declares its own --range, so --source here is an unknown option and exits 2.

--json shape

A top-level array sorted by cost descending, truncated to --limit. The per-project rows carry only cost and tokens, not the six-field totals object the other commands use.

[
  {
    "cost": 1837.7667801071357,
    "project": "noveum-app-nextjs",
    "tokens": 1887083203
  },
  {
    "cost": 1340.774043018298,
    "project": "edith",
    "tokens": 1664124164
  },
  {
    "cost": 988.1540715389959,
    "project": "fable",
    "tokens": 919092634
  }
]

Examples

ed usage projects
ed usage projects --range week --limit 5
ed usage projects --range today --json | jq -r '.[] | .project'

Behaviour

Reads only, mutates nothing, and needs no app. It exits 4 with no usage.json, 1 on a file that will not decode, 3 on a bad --range, and 2 on --limit 0 or a negative limit, which is checked as "must be greater than zero" rather than read as "all of them".

A project's name is the collector's projectName, falling back to its path and then to the literal unknown. Names come from the git root of the working directory a chat ran in, which is why a chat run inside a worktree is attributed to the repository rather than to the worktree folder, and why a machine's remote projects arrive suffixed with the machine name.

These numbers come from a different part of the file than every other verb here: ed usage summary, daily and models read bySource, while projects reads the projects array. They are derived from the same transcripts but rolled up separately, so the project totals will not tie out to the summary totals to the cent, and no --source filter applies to them.

$ ed usage projects --range week --limit 5
PROJECT            COST     TOKENS
noveum-app-nextjs  1837.77  1887083203
edith              1340.77  1664124164
fable              988.15   919092634
macos              303.80   383653333
x-convo-exporter   228.97   191821868

ed usage sources

Lists the agents that produced the history, which is where the ids --source expects come from.

ed usage sources [--json]

Options

Name Type / values Default What it does
--json flag off Emit JSON on stdout

--json shape

A top-level array in the file's own order, not sorted. label, tool, machine and machineID are null when the file carries no such metadata for that id, and an agent that ran on this Mac carries no machine at all.

[
  {
    "default": true,
    "id": "cli",
    "label": "Claude Code",
    "machine": null,
    "machineID": null,
    "tool": "Claude Code"
  },
  {
    "default": true,
    "id": "codex",
    "label": "Codex",
    "machine": null,
    "machineID": null,
    "tool": "Codex"
  },
  {
    "default": true,
    "id": "commandcode",
    "label": "Command Code",
    "machine": null,
    "machineID": null,
    "tool": "Command Code"
  },
  {
    "default": true,
    "id": "asus-tuf-7:cli",
    "label": "Claude Code · Asus TUF 7",
    "machine": "Asus TUF 7",
    "machineID": "4303DCF1-52D8-4075-AE9B-C2FD86D3821A",
    "tool": "Claude Code"
  },
  {
    "default": true,
    "id": "opencode",
    "label": "OpenCode",
    "machine": null,
    "machineID": null,
    "tool": "OpenCode"
  },
  {
    "default": true,
    "id": "cowork",
    "label": "Cowork",
    "machine": null,
    "machineID": null,
    "tool": "Claude Code"
  }
]

default says whether the id is in the file's defaultSources, which is the set the dashboard pre-selects. It is not a filter ed applies anywhere: every read command counts every source unless you pass --source.

Examples

ed usage sources
ed usage sources --json
ed usage sources --json | jq -r '.[].id'

Behaviour

Reads only, mutates nothing, and needs no app. It exits 4 with no usage.json and 1 on a file that will not decode. It takes no window options, so there is no exit 3 here.

The human table falls back to the id in the LABEL column when the file has no label, and prints an empty TOOL cell when it has no tool. The MACHINE column reads this Mac for a source with no machine metadata and the machine's name for one the collector brought back over SSH; --json carries the same name as machine, alongside the machineID the machine directory knows it by. When the file lists no sources at all the command prints the header line by itself and exits 0.

$ ed usage sources
ID              LABEL                     TOOL          MACHINE
cli             Claude Code               Claude Code   this Mac
codex           Codex                     Codex         this Mac
commandcode     Command Code              Command Code  this Mac
asus-tuf-7:cli  Claude Code · Asus TUF 7  Claude Code   Asus TUF 7
opencode        OpenCode                  OpenCode      this Mac
cowork          Cowork                    Claude Code   this Mac

ed usage machines

Counts the agents running on your SSH machines alongside the ones on this Mac. ed usage machines on its own runs ls.

The collector Edith runs here is piped to the machine and run against that machine's home directory, and the numbers come back into the same usage.json the dashboard reads. Each agent on a machine arrives as its own source, named <machine-slug>:<agent>, so ed usage summary counts the fleet, --source asus-tuf-7:cli narrows to one agent on one machine, and --machine narrows to everything one machine ran.

Whatever the collector needs and cannot find there, jq, bun and ccusage, is installed under ~/.cache/edith on that machine. That is why collecting waits to be asked rather than happening for every machine you have configured, and why the first run on a machine can take minutes.

ed usage machines ls

Lists every configured machine, whether it is counted, and what it has given.

ed usage machines ls [--json]
Name Type / values Default What it does
--json flag off Emit JSON on stdout

--json shape, an array with one object per configured machine, whether or not it has ever been collected:

[
  {
    "collectedAt": "2026-08-08T16:14:51Z",
    "cost": 249.81,
    "counted": true,
    "days": 81,
    "host": "asus-tuf-7",
    "id": "1F0A9C22-4E64-4C63-9E0B-2F5A1E7D2C10",
    "machine": "Asus TUF 7",
    "sources": ["asus-tuf-7:cli"],
    "tokens": 321812580
  }
]

A machine that has never been collected still appears, with collectedAt and host as null, sources empty, and days, cost and tokens at zero. The human table writes - in those columns instead.

$ ed usage machines
MACHINE     COUNTED  COLLECTED             SOURCES  COST    TOKENS
Asus TUF 7  yes      2026-08-08T16:14:51Z  1        249.81  321812580

Reads only, mutates nothing, needs no app. With no machines configured at all it exits 3 with no machines are configured.

ed usage machines collect

Runs the collector on a machine over the shared SSH connection and folds the result in.

ed usage machines collect [<machine>] [--once] [--verbose] [--timeout <seconds>] [--json]
Name Type / values Default What it does
<machine> machine name, ssh alias or id every machine already taking part Which machine to collect. Naming one also signs it up for later refreshes unless --once is passed
--once flag off Collect without signing the machine up, so later refreshes skip it
--verbose flag off Print everything the collector said on the machine, on stderr
--timeout integer seconds, greater than 0 900 Give up on a machine after this long
--json flag off Emit JSON on stdout

--json shape:

{
  "collected": [
    {
      "cost": 249.81,
      "days": 81,
      "id": "1F0A9C22-4E64-4C63-9E0B-2F5A1E7D2C10",
      "machine": "Asus TUF 7",
      "sources": ["asus-tuf-7:cli"],
      "tokens": 321812580
    }
  ],
  "failed": [],
  "merging": true
}

merging says whether the menu bar app was running and was asked to fold the new numbers into the dashboard. When it is false the numbers are on disk but the dashboard will not show them until Edith starts or you run ed usage refresh.

ed usage machines collect "Asus TUF 7"
ed usage machines collect tuf --once
ed usage machines collect --timeout 1800 --verbose
ed usage machines collect --json | jq '.collected[].sources'

Failures are per machine rather than fatal: a machine that cannot be reached is listed in failed with its error while the others still count. The command only fails when nothing at all was collected, and then it exits 4 with the first error. With no machine named and none signed up yet it exits 3 with no machine is counted towards usage yet. A --timeout of zero or less exits 2.

Collecting also prunes stored usage for machines that are no longer in the directory, so removing a machine and collecting again forgets it.

ed usage machines enable

Signs a machine up so every later refresh collects it.

ed usage machines enable <machine> [--json]
Name Type / values Default What it does
<machine> machine name, ssh alias or id required Which machine to count
--json flag off Emit JSON on stdout
{
  "counted": true,
  "machine": "Asus TUF 7"
}

This only changes whether the machine takes part; it collects nothing by itself, so a machine enabled but never collected still reports nothing until a refresh runs. A name that matches no machine exits 3.

ed usage machines disable

Stops collecting from a machine while keeping the numbers it already gave.

ed usage machines disable <machine> [--json]
Name Type / values Default What it does
<machine> machine name, ssh alias or id required Which machine to stop collecting
--json flag off Emit JSON on stdout
{
  "counted": false,
  "machine": "Asus TUF 7"
}

The machine's existing sources stay in usage.json and keep counting towards every total. Use forget to drop them. A name that matches no machine exits 3.

ed usage machines forget

Drops everything a machine gave and stops counting it.

ed usage machines forget <machine> [--json]
Name Type / values Default What it does
<machine> machine name, ssh alias or id required Which machine to drop
--json flag off Emit JSON on stdout
{
  "dropped": true,
  "machine": "Asus TUF 7",
  "merging": true
}

dropped is false when there was nothing stored for that machine, and then merging is false too, because the app is only asked to re-fold when something actually went away. This is the one verb here that accepts a raw id for a machine that is no longer in the directory, so usage left behind by a deleted machine can still be cleared.

ed usage refresh

Re-collects usage data from every agent on this Mac and rewrites usage.json.

ed usage refresh [--follow] [--json]

Options

Name Type / values Default What it does
--follow flag off Attach to a refresh that is already running instead of starting one, and fail when there is nothing to watch
--json flag off Emit JSON on stdout

--json shape

{
  "completed": true,
  "followed": false,
  "phases": [
    {
      "detail": "cached",
      "name": "ccusage",
      "seconds": 0.01
    },
    {
      "detail": "27 days",
      "name": "cli",
      "seconds": 1.47
    },
    {
      "detail": "125311 messages",
      "name": "walk",
      "seconds": 3
    }
  ],
  "seconds": 9.98,
  "summary": {
    "machines": "Asus TUF 7",
    "sources": "cli, codex, commandcode, asus-tuf-7:cli",
    "spend": "$10876.85 · 378 sessions · 401 KB",
    "window": "2026-07-08 to 2026-08-08 · 27 days · 7 models"
  }
}

completed is always true: reaching the JSON at all means the pipeline finished, because every other outcome is an error. followed says whether this invocation watched a run someone else had started instead of starting its own. seconds is the pipeline's own total, phases carries every phase row in the order they landed, three of the eight above, and summary is the closing block keyed by label.

Examples

ed usage refresh
ed usage refresh --follow
ed usage refresh --json | jq -r '.summary.spend'
ed usage refresh && ed usage summary --range today

Behaviour

This is the only verb in the group that changes anything, and ed does the work itself. It runs the same collection pipeline EdithKit hands the app, in this process, so it needs no menu bar app and there is nothing to time out on. A first run takes noticeably longer, because the collector installs ccusage, and jq or bun when they are missing, before it can read anything.

Progress goes to stderr and stdout stays clean: one usage refreshed line at the end, or the JSON object. Each phase is printed as it lands, with a spinner on the phase in flight, and the whole display is skipped when --json is passed or stderr is not a terminal, so a pipe sees nothing extra. NO_COLOR and TERM=dumb switch it off too, rather than only dropping the colour.

A run holds a lock on refresh.lock in the data directory, so two of them cannot clobber usage.json. When one is already running, in the app or in another terminal, ed attaches to it instead of starting a second, printing a refresh is already running, attaching to it and reporting that run's phases as they land. --follow asks for that explicitly, and that is the difference between the two: with nothing running it exits 4 with no usage refresh is running, hinted drop --follow to start one, where a plain ed usage refresh would have started one.

A pipeline failure is an error rather than a quiet success. What the collector reported becomes the message, exit 4, hinted at data/refresh.log, which is where the same transcript is written line by line while the run happens. A run that was attached to and then stopped without finishing fails the same way.

$ ed usage refresh

  EDITH · refresh usage · 2026-08-08 23:57:31
  ────────────────────────────────────────────────────
  ▸ ccusage    cached                             0.01s
  · discovering sources
  ▸ cli        27 days                            1.47s
  ▸ codex      12 days                            0.83s
  ▸ commandcode 1 days                             0.85s
  · assembling usage.json
  · walking 900 transcript files
  ▸ walk       125311 messages                    3.00s
  ▸ projects   174 repos                          2.35s
  ▸ merge      hours + projects merged            1.32s
  ▸ machines   1 folded in                        0.05s
  ────────────────────────────────────────────────────
  ✓ sources   cli, codex, commandcode, asus-tuf-7:cli
  ✓ machines  Asus TUF 7
  ✓ window    2026-07-08 to 2026-08-08 · 27 days · 7 models
  ✓ spend     $10876.85 · 378 sessions · 401 KB
  ✓ done in 9.98s

usage refreshed

Exit codes

Code When this group produces it
0 The command printed its report, or the refresh finished. Also a read that legitimately found nothing to show
1 usage.json exists but will not decode: could not read <path>: <reason>
2 --limit 0 or a negative limit on ed usage projects, plus the usual parse failures, an unknown flag, a missing value, or --source passed to ed usage projects
3 --range is not today, week, month or all, and the hint lists the four. Also a --source id or a --machine the file knows nothing about
4 No usage.json at all; no rate limit history at all; a usage refresh whose pipeline failed, or --follow with nothing running; or Edith not running, or not answering, for ed usage limits --refresh

Notes and gotchas

  • ed usage with no subcommand runs ed usage summary, so a bare ed usage prints the all-time totals rather than a help screen. ed usage --help is still the help screen, and exits 0.
  • The two files are independent. ed usage limits reads only limits-history.jsonl and works with no usage.json at all; every other verb reads only usage.json and works with no limit history. Neither absence affects the other.
  • --range week means the last 7 days and --range month the last 30, both counted back from midnight today and both including today. The comparison is made on the YYYY-MM-DD string, so it is your local calendar day, and there is no upper bound: a day stamped in the future is always included.
  • Cost and token figures are doubles all the way through, and the serialiser prints an integral double as an integer. "percent": 30 is 30.0 and "cost": 0 is a genuine zero, not a missing field.
  • Token counts in the human tables are truncated to a whole number, not rounded, and costs are formatted to two decimal places. Only --json gives you the unrounded values.
  • Object keys in --json are sorted, arrays keep the order the command chose: fixed provider order for limits, date ascending for daily, cost descending for models and projects, and the file's own order for sources.
  • The read verbs never reach the network and only ever show the last thing that was written. ed usage limits --refresh posts a request and waits for the app to do the polling, while ed usage refresh runs the collector in this process, which makes it the one invocation here that goes out and fetches anything itself.
  • Both refreshing invocations fail rather than reporting stale numbers, so exit 0 from either does mean the work happened. observedAt can still repeat after ed usage limits --refresh, because the app appends a history row only when the values changed.
  • ed config set tabUsageEnabled false turns off the Agent Usage extension, and with it the app's own collection and the limit polling; claudeLimitsEnabled and codexLimitsEnabled do the same for a single provider's polling. ed usage refresh runs the pipeline itself and collects either way. The read verbs keep working against whatever was collected before that, so ed usage limits goes on printing a silenced provider's last row until it scrolls out of the 8 KB tail.

Where to go next

  • ed config for tabUsageEnabled, claudeLimitsEnabled, codexLimitsEnabled and repoPath, which decide what gets collected and where it lands
  • ed extensions for turning the Agent Usage extension on and off by id
  • ed permissions for the grants the app needs before it can collect anything
  • ed system for this Mac's metrics, the other read-only reporting group
  • All ed commands

CLI reference

Getting Started
Config
App
Extensions
Permissions
Usage
System
Music
Calendar
Clipboard
Color
Download
Apps
Tools
Shelf
Cleaner
Machines
Machines Remote
Machines Docker
Machines Files
Machines Power
Machines Workspace
Companion
Herdr
Lid Awake
Machines Thermal

Guides

Clone this wiki locally