Kiyo AI Status
Claude Code and Codex CLI usage in the VS Code status bar, read entirely from files
the two CLIs already write on your machine.
No network requests. No credential stores read. Nothing leaves your computer.
[claude] Claude Code 5h ■■■□□ 54% · 15m 7d ■■□□□ 46% · 3d
──────────────────────────────────────────────────────────────
Each window shows a bar, the percentage consumed, and how long until it resets — in a single
unit: 3d once a day or more remains, then 15h, then 42m.
Hover for the full picture; click for a detail panel.
What it shows
|
Claude Code |
Codex |
| 5-hour window |
✅ |
plan dependent |
| 7-day window |
✅ |
✅ |
| Reset countdown |
✅ |
✅ |
| Plan type |
✅ |
✅ |
| Usage credits spent |
✅ |
— |
| Context window of last turn |
tokens only |
tokens + % |
The percentages are the providers' own numbers — the same ones /usage prints in the CLI. A
percentage written ~42% is that figure projected forward from local token counts, because the
CLIs refresh their own only occasionally; see Keeping it fresh.
Where the data comes from
Both CLIs already record their own rate limits on disk. This extension reads those records
and nothing else.
Claude Code caches the usage bars that /usage renders:
~/.claude.json → cachedUsageUtilization (respects CLAUDE_CONFIG_DIR)
- Context tokens: the tail of the newest log under
~/.claude/projects/**/*.jsonl
Codex writes its rate limits into every session log:
~/.codex/sessions/**/rollout-*.jsonl → the last token_count event's rate_limits
(respects CODEX_HOME)
Only the last 512 KB of a session log is read, so multi-megabyte files stay cheap.
Usage log
The panel's Usage log tab breaks token spend down by project, model, day,
hour, session or source, over today / 7 days / 30 days / all time. It answers "where
did my usage actually go", which the percentages alone cannot.
| Column |
What it is |
| Turns |
API responses counted |
| Fresh in |
Input tokens not served from cache |
| Output |
Tokens generated |
| Cache read |
Context re-read from cache |
| Cache write |
Context written into cache |
| Fresh + out |
The two columns that reflect work done, and what rows are ranked on |
| Share |
That row's portion of Fresh + out for the range |
Which axis answers what
project, model and day answer "what have I been spending on". The other three answer
"where is this coming from", which is a different question and the harder one.
| Axis |
Row is |
Answers |
hour |
2026-08-05 14:00 |
Spend at hours nobody was typing |
session |
my-project · 019f829c |
What one task actually cost |
source |
claude-vscode · subagent |
What drove the turn |
source is built from three things, and they are not equally reliable:
- entrypoint —
claude-vscode, sdk-cli, cli, or Codex's originator such as
codex_vscode and codex_exec. Written on every response that carries a usage payload, so
this part is exact.
subagent — from isSidechain, also on the response itself, also exact.
- origin —
human, task-notification, coordinator. This one is written on the prompt,
not on the response that spends the tokens, so it is carried forward from the last genuine
prompt and cleared by the next one that does not carry an origin.
That last part is why a row naming an origin is a floor rather than a total. Measured on one
machine, origin was present on 66 of 2,187 prompts; carrying it forward labelled 37% of turns and
left the rest under the bare entrypoint. A turn genuinely driven by a notification can land in the
unlabelled row, but nothing is ever filed under an origin it did not record. Tool results are
skipped without being parsed — toolUseResult marks every one, and they are the large lines.
Rows are ranked by spend on every axis, hour included, so the top of the table is the hour that
cost the most rather than the most recent one.
Cache reads are shown but excluded from the ranking on purpose. A long session re-reads its whole
context every turn, so cache reads are both the largest and the cheapest number in the row —
ranking on the grand total would rank by session length rather than by work done. In one real
sample, 3,681 turns came to 15.6M fresh + output tokens against 420M cache reads.
Three quirks of the logs are handled, because ignoring any of them skews the totals:
- Claude Code writes one log entry per content block, repeating the same
usage payload on
each. Consecutive entries sharing a requestId are one response, counted once — without this
the totals run roughly 2.2× high.
- Codex counts cached tokens inside
input_tokens, where Claude reports them separately, so
the cached part is subtracted back out or it lands in two columns at once.
- Codex reports cumulative counters, so a turn's cost is the difference between consecutive
token_count events. Diffing the running total reproduces the CLI's own figure exactly
(verified across 188 sessions, zero drift) and puts each turn on its own calendar day.
Subagent logs are included — that work spends quota too, and on one machine it came to 478 turns.
The walk goes six levels deep rather than looking in one place, because where they sit under
~/.claude/projects varies.
The scan runs when the tab is first opened, never at startup. Logs are append-only, so a byte
offset per file is remembered and later scans read only what is new: a full first pass over 91 MB
takes about 0.3 s, and a rescan with nothing changed about 10 ms.
Is it worth it?
The Summary tab's Worth it? cards compare each plan's monthly price against what its last
30 days of usage would have cost at public API rates — the same per-token estimate the cost note
below uses, just totalled per provider over a fixed 30-day window rather than whatever range is
selected elsewhere on the page. A monthly plan only means anything measured against a monthly
window.
Enter a price directly in the card (or set claude.subscriptionPriceUsd /
codex.subscriptionPriceUsd) and a pill marks the plan Worth it or Not yet. Left at 0,
that provider's card shows the 30-day estimate without a verdict. Prices you type in the panel are
written back to the same settings, so they persist between sessions.
Why it works this way
Reading local files is the only approach that is unambiguously within both vendors' terms.
Anthropic's Consumer Terms §3 prohibit
accessing the Services "through automated or non-human means, whether through a bot, script,
or otherwise", except via an Anthropic API Key. OpenAI's
Terms of Use prohibit using "any automated or
programmatic method to extract data or output from the Services" except through their API.
A third-party tool that calls api.anthropic.com/api/oauth/usage or
chatgpt.com/backend-api/wham/usage with the CLI's OAuth token — which is what comparable
taskbar monitors do — runs against those clauses. This extension does not do that. The CLIs
perform their own authorised fetches; the extension only reads the results they leave behind.
The trade-off is freshness: figures are only as current as the last time each CLI recorded
them. That is surfaced rather than hidden — see below.
Keeping it fresh
The CLIs rewrite their cached percentages only occasionally — measured on one machine, Claude
Code refreshed roughly hourly even under continuous use, and Codex only while a session is
running. Re-reading the files more often cannot help: the number on disk has not changed. Two
things address that, both entirely local.
A watcher (watchFiles, on) reads the moment a CLI writes, rather than waiting out
refreshInterval. The interval keeps running as a backstop, since a watcher can miss events on
network drives.
A projection (estimate.enabled, on) carries the last authoritative figure forward:
estimate = last recorded % + (tokens spent since) × points-per-token
The rate is measured, not assumed. Two consecutive readings an hour apart, with the tokens spent
between them from the session logs, give points-per-token directly; every new reading re-measures
it, so drift from a model switch corrects itself within the hour. The median of the last few
measurements is used, so one skewed interval cannot move the number far. Projected figures are
always written ~42%, and the provider's own figure stays visible beside them.
Measuring instead against the start of a window — treating a reset as a known zero, so a single
reading would give a rate — was tried and discarded: on real data it disagreed with the
differential measurement by 6.6×, because a window's usage is not all visible locally. The
consequence is that a window has no projection until two readings land inside it, which is why a
freshly reset 5-hour window shows the recorded figure and a staleness marker instead of a guess.
What the projection cannot know:
- Only this machine. Usage from another device or from claude.ai is invisible to it. Claude
Code's own usage breakdown carries the same caveat.
- Model weighting. A session that switches models mid-interval skews the measured rate until
the next reading corrects it.
- Cache reads. Whatever weight they carry toward a limit is folded into the measured rate
rather than modelled, since the logs do not say.
Set estimate.enabled to false to show only the providers' own figures.
Fetching on demand
AI Status: Fetch Current Usage from CLI runs claude -p /usage and takes the percentages
from what it prints — the exact figures, right now. Bind it to a status bar click by setting
statusBar.click to fetch, or use the Fetch now link in the tooltip.
Measured on one machine, the call reports num_turns: 0 and total_cost_usd: 0: /usage is
handled locally by the CLI rather than by the model, so asking does not consume any of the
allowance it reports on. It takes about 9 seconds, and the session log it leaves behind contains
no assistant turns, so it never appears in the usage table.
It does not always answer. The CLI includes the two rate-limit lines on the first call after a
pause and leaves them out of the ones that follow — its own formatRateLimits returns null when it
has no rate limits in hand, which drops the whole block. Measured on CLI 2.1.199, six consecutive
calls came back 1 reporting and 5 not, and the ones that reported nothing returned in ~90ms against
~500ms for the one that did, so retrying straight away does not help.
A call like that is not treated as a failure, because it is the CLI behaving normally: nothing is
carried forward, the figures already on screen stay as they are, and asking for the fetch by hand
says so rather than raising a warning. Earlier versions reported it as `/usage` output had no percentages in it, which read like a break in this extension and was the most common thing it
ever said.
It is also the only path here that makes the CLI talk to Anthropic, which is why by default it
only runs when you ask for it. Anthropic's
Consumer Terms §3 prohibit accessing the
Services "through automated or non-human means, whether through a bot, script, or otherwise" —
a timer doing this would be exactly that, while a person pressing a button is a person using
their own account.
autoFetchFromCli (default off) puts it on a timer regardless, every autoFetchInterval
seconds (default 300, minimum 60). Turning it on means the exact percentages stay current on their
own, with no projection needed — and it means accepting the clause above on your own account,
which is why it ships off rather than on. Switching it on from the panel's checkbox asks for
confirmation first, citing that same clause, before the setting changes; declining leaves it off
and just unchecks the box. A background fetch shows no progress indicator, and a failure goes to
the diagnostic log instead of a notification. Each open VS Code window fetches on its own timer,
so several windows means several calls per interval.
It also changes what AI Status: Refresh Usage does. By default that command re-reads the files
and nothing more, which is honest but looks inert: the CLIs rewrite their percentages roughly
hourly, so pressing it normally repaints the figure already on screen. With autoFetchFromCli on,
the decision to let the CLI be driven automatically has already been made, so the command runs
/usage instead — the same thing the timer does, only sooner. This covers the Refresh link in
the tooltip and statusBar.click: refresh as well, since both go through that command.
Opening the detail panel takes the same path, so the default statusBar.click: panel both shows the
panel and brings the figures behind it up to date. The panel appears immediately either way — the
fetch is not waited on, and the panel updates when it lands.
Each fetch also becomes a true anchor for the projection above, so clicking it twice inside a
window is the fastest way to get ~% working after a reset.
Note that /usage does not rewrite the CLI's own cache, so the fetched figures are held in
memory and shown until the CLI writes something at least as new.
Staleness
When a CLI has not refreshed its own figures for kiyoAiStatus.staleAfter minutes (default 60),
the status bar appends a history icon and the tooltip says how old the numbers are and how to
refresh them:
- Claude Code — run
/usage in the CLI
- Codex — send any message in
codex
Language
The detail panel is available in Thai and English, set by language (default th) or by the picker
in the panel's own header. Each language is labelled in itself — ไทย, EN — so it can be found
without being able to read the current one. Relative times follow the copy (2h ago becomes
2 ชม.ที่แล้ว), while absolute reset times keep the host's locale.
Terms Thai developers normally leave in English — token, cache, session, context, turn, CLI — are
kept in English rather than translated. The status bar and its tooltip are English only, as are each
provider's own diagnostic messages and source lines, which are mostly file paths and CLI commands
and are shared with the tooltip.
Privacy
The extension opens no network connections of its own. The bundle imports fs, fs/promises,
os, path, crypto, child_process and vscode — there is no HTTP client in it, and it never
reads ~/.claude/.credentials.json, ~/.codex/auth.json, or any other credential store.
child_process is there for one thing only: fetching on demand, which
spawns claude -p /usage when you ask it to. The argument list is fixed and no shell is used, so
nothing that could be configured gets interpreted as a command. Nothing else in the extension
starts a process.
Reading the OAuth token out of the CLI's credential file and calling Anthropic's endpoints
directly — which is how comparable taskbar monitors get a live figure — is not something this
extension does, and not something it should. Anthropic
confirmed in February 2026
that subscription OAuth tokens are only authorised for Claude Code itself, third-party requests
now get This credential is only authorized for use with Claude Code, and accounts have been
banned over it. Asking the official client to run its own command is the supported way to get the
same number.
Being precise about what it does read: to reach cachedUsageUtilization it parses the whole of
~/.claude.json, a file that also holds your account details, project history and any secrets
you have put in an MCP server's env; and it reads the last 512 KB of the newest session log,
which is conversation text. The usage log tab goes further and reads session logs in full. Only
the usage figures are taken out of any of it. Nothing else is kept, displayed, written anywhere,
or sent anywhere — the parsed data goes out of scope as soon as the numbers have been extracted.
The usage log's index, in the extension's own global storage directory, holds only token
counters, project paths, model names, session ids, the entrypoint/origin strings behind the
source axis, and a byte offset per file. No part of a conversation is written to it — a session
id is the CLI's own opaque identifier for a log, not anything from inside it. Deleting that
directory just makes the next scan a full one.
The two path settings are machine-scoped, so only your user settings can change which files
are read; a workspace cannot redirect them. Values that do come off disk — the Codex plan type
and log filename — are stripped of markdown syntax before they reach the status bar tooltip,
and that tooltip's links are restricted to this extension's own three commands.
Settings
All settings live under kiyoAiStatus.*.
Display
| Setting |
Default |
Notes |
statusBar.window |
both |
5h, 7d, both, or max (whichever is closest to the limit) |
statusBar.alignment |
right |
left or right |
statusBar.priority |
100 |
Higher sits further left |
statusBar.showIcon |
true |
Logo before the percentage |
statusBar.labelStyle |
full |
full (Claude Code 42%), short (CC 42%), none (42%) |
statusBar.showBar |
true |
The ■■■□□ meter |
statusBar.barStyle |
squares |
squares (■■■□□), blocks (███░░), bars (▰▰▰▱▱) |
statusBar.barWidth |
5 |
Cells per bar |
statusBar.showWindowLabels |
true |
The 5h / 7d prefixes |
statusBar.showReset |
true |
The reset countdown |
To shorten the status bar, turn off showBar, set labelStyle to short or none, or set
window to max so only the window nearest its limit is shown.
| statusBar.click | panel | panel, refresh, fetch, settings, none |
| hideWhenUnavailable | false | Hide rather than show a hint |
Thresholds
| Setting |
Default |
thresholds.warning |
75 |
thresholds.critical |
90 |
statusBar.useBackgroundColor |
false — tint the text only; true highlights the whole item |
Data
| Setting |
Default |
Notes |
refreshInterval |
300 |
Seconds between disk reads (5 minutes) |
claude.enabled / codex.enabled |
true |
|
claude.configPath |
(auto) |
Override ~/.claude.json (user settings only) |
claude.cliPath |
(auto) |
Path to the claude executable for fetching on demand; leave empty to find it on PATH |
codex.home |
(auto) |
Override ~/.codex (user settings only) |
claude.subscriptionPriceUsd / codex.subscriptionPriceUsd |
0 |
Monthly plan price in USD, for the Summary tab's Worth it? cards; 0 hides that provider's card |
staleAfter |
60 |
Minutes before the stale warning appears |
watchFiles |
true |
Read the moment a CLI writes, not just on the interval |
estimate.enabled |
true |
Project the last figure forward as ~42% |
language |
th |
Panel language, th or en — also switchable in the panel header |
autoFetchFromCli |
false |
Run claude -p /usage on a timer — read this first |
autoFetchInterval |
300 |
Seconds between those fetches (minimum 60) |
Re-reads also happen when the VS Code window regains focus, so the numbers are current when
you come back to the editor without waiting out the interval. Reset countdowns are recomputed
every 30 seconds from the already-loaded data, which costs no disk access.
Commands
AI Status: Refresh Usage — re-read what the CLIs have written
AI Status: Fetch Current Usage from CLI — run claude -p /usage for the exact figures now
AI Status: Show Usage Details
AI Status: Open Settings
AI Status: Show Diagnostic Log
Build
npm install
npm run compile # typecheck + bundle to dist/
npm run watch # rebuild on change
npm run build-icons # rebuild media/kiyo-ai-icons.woff from media/svg/*.svg
npm run package # produce a .vsix
The icon font is committed, so build-icons only needs re-running when the SVGs change.
Logo sources and their trademark note are in media/svg/ATTRIBUTION.md.
Press F5 to launch an Extension Development Host.
Notes and limits
- Claude Code's own VS Code extension has an Account & usage dialog with the same 5-hour
and 7-day breakdown (Claude Code v2.1.174+). This extension puts the figures on the status
bar so they are visible without opening anything.
- On plans that report only one Codex window (Team accounts report a 7-day
primary and no
secondary), the missing window shows as —, and the status bar falls back to the window
that does exist. Windows are classified by declared length rather than by Codex's
primary/secondary labels, so this stays correct across plans.
- Claude Code's session log does not record the model's context limit, so the tooltip reports
the last turn's token count without a percentage rather than guessing a denominator.
License
MIT