This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a local VS Code extension, not intended for marketplace publishing. It shows token/context usage percentages for the current Codex or Claude Code session in the status bar. The project is plain CommonJS with no build step and no dependencies beyond Node built-ins and the vscode API.
# Run all tests with Node's built-in test runner. No npm install is required.
npm test
# Run one test file.
node --test test/claudeUsage.test.js
# Package the extension into dist/*.vsix (fetches @vscode/vsce via npx).
npm run packageFor local installation, symlink the project directory to ~/.vscode/extensions/codex-claude-monitor, then run Developer: Reload Window in VS Code. See README.md.
Data flow: local session JSONL files -> provider parsers with workspace filtering -> latest active provider selection -> status bar.
Workspace filtering: readLatestAgentUsage accepts optional workspaceFolders as absolute paths. Only sessions whose cwd is inside a workspace folder, including subdirectories, are counted. Empty arrays disable filtering. Claude is filtered by munged directory names, where non-alphanumeric characters are replaced with - and prefix matching supports subdirectories, so files do not need to be opened. Codex candidates are sorted by descending mtime, then each first line is read until session_meta.payload.cwd matches. See docs/specs/2026-06-03-workspace-filter-design.md.
src/sessionFiles.js: shared provider utility layer for recursive scanning withwalkFiles, descending mtime sorting,parseJsonLine,calculateContextPercent, andreadLastMatchingEvent, which parses JSONL line by line, returns the last matched entry, and caches by mtime plus size.src/codexUsage.js: recursively scans~/.codex/sessionsforrollout-*.jsonl, picks the newest file by mtime, and reads the lasttoken_countevent. Percentage =last_token_usage.input_tokens / model_context_window.src/claudeUsage.js: recursively scans~/.claude/projectsfor.jsonl, picks the newest file, and reads the lasttype: "assistant"entry withmessage.usage. Context tokens =input_tokens + cache_read_input_tokens + cache_creation_input_tokens; the context window is inferred from the model name byinferClaudeContextWindow: model names containing1morfable, orclaude-opus-4-7/claude-opus-4-8, use 1M, otherwise 200k; when the observed context tokens exceed the inferred window (unlisted big-window models like Sonnet 5), it upgrades to 1M so the percent never exceeds 100. File selection skips files without an assistant usage entry (freshly started sessions, non-interactiveclaude -psessions), trying the newest 10 by mtime.src/claudeStatuslineUsage.js: reads Claude subscription rate limits from the statusline usage cache file (~/.claude/.usage-cache.json, name inCACHE_FILE_NAME) written byscripts/usage-cache.sh. Claude Code passes a JSON blob on stdin to the configured statusline command; for subscribers it includes arate_limitsblock (the OAuth/api/oauth/usage5-hour and 7-day windows — the only place a user script can read those limits, and absent until the first API response of a session).readClaudeStatuslineUsage(claudeRoot)mapsfive_hour→primary(window_minutes300) andseven_day→secondary(10080), convertingused_percentage→used_percentand passingresets_at(Unix seconds) through, and returns{ rateLimits, capturedAt }ornull. A present-but-malformed window (non-numericused_percentage) invalidates the whole read; a missing window is simply omitted. Results are cached by (mtime, size) likereadLastMatchingEvent. This replaced the removedclaudeRateLimits.js, which parsedclaude -p "/usage"text output — that command no longer emits subscription limits (its-poutput is now a usage-behavior report), so the statusline stdin is the supported channel.scripts/usage-cache.sh: the statusline bridge. Reads the statusline stdin JSON, snapshots.rate_limits(viajq) plus acapturedAtUnix-seconds timestamp to${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.usage-cache.json(atomic write: temp file +mv), then passes stdin through unchanged so an existing statusline command can pipe through it. Every failure (missingjq, absentrate_limits, unwritable dir) is swallowed so the status line still renders. Wire it into an existing statusline script by piping stdin through the helper in the background.src/agentUsage.js: aggregation layer. It reads both providers, selects the most recently active one byupdatedAt, and formats status bar text and tooltip text. Status bar text is{provider} $(comment) {percent}plus compact rate-limit segments joined with·(e.g.Codex $(comment) 3% · $(history) 45% · $(calendar) 23%,Claude $(comment) 50% · $(history) 25% · $(calendar) 8%). Segments come fromformatRateLimitsStatusBarusing codicon icons:$(history)for hour-scale windows (e.g. 5h) and$(calendar)for multi-day windows (weekly / Nd);$(comment)(a speech bubble) marks the context percent in place of an emoji so it renders consistently across themes and needs no translation. Codex segments come from session JSONLrate_limits; Claude segments come fromoptions.claudeRateLimitsinjected byextension.js(read from the statusline usage cache viasrc/claudeStatuslineUsage.js), attached inreadLatestAgentUsageonly when Claude wins and never overwriting provider-supplied data, withoptions.claudeRateLimitsCapturedAtriding along asusage.rateLimitsCapturedAt. The friendly Claude model name (with(1M)marker) and rate-limit reset times/countdowns are intentionally kept out of the status bar and surfaced only in the tooltip.getUsageSeveritymaps context percent tolow(<50%),medium(50–79%), orhigh(>=80%) for status bar coloring. Tooltip rows are ordered as context row, model row (friendly name with(1M context)marker), Claude token-composition rows fromformatClaudeTokenDetail(input/cache-read/cache-create plus a cache-hit percent; empty for providers like Codex without this breakdown), then rate-limit rows. Rate-limit rows for both providers are formatted byformatRateLimitsas5h usage: 21% · Reset at 14:32 (in 2h 13m); the(in …)countdown comes fromformatTimeLeft(total minutes rounded up, at most two units, zero minor unit omitted) and is dropped onceresets_atis in the past, so stale sessions keep the absolute time only. When rate-limit rows are present andusage.rateLimitsCapturedAtis set, a finalUsage updated 2m ago (11:58)row is appended byformatUsageCaptureRow; its relative part comes fromformatTimeAgo(elapsed since a past timestamp, "just now" under a minute or for future/skewed times, at most one unit).src/extension.js: VS Code entry point and the only file that depends on thevscodeAPI. It owns the main status bar item (priority 90; click runsagentTokenStatus.refresh) and the refresh timer with default 10s interval. Each refresh reads the Claude subscription rate limits from the statusline usage cache viareadClaudeStatuslineUsage(lightweight file read, no separate timer — the data is pushed by Claude Code's statusline, so there is noclaude -pprobe process) and forwardsclaudeRateLimits/claudeRateLimitsCapturedAttoreadLatestAgentUsage. Configuration reads coveragentTokenStatus.sessionsRoot,claudeRoot, andrefreshIntervalMs, plus the severity-to-theme-color mapping viaSEVERITY_COLORS(charts.green/charts.yellow/charts.red). It also owns a second status bar itemhandoffItem(priority 89,$(export) Handoff) shown only whencontextPercent > HANDOFF_THRESHOLD(50); clicking it runsagentTokenStatus.handoff, which composes a handoff prompt viasrc/handoff.js, writes it to the clipboard, and shows an information message. Surfacing the suffix is the only automatic behavior — generating and copying the prompt is strictly user-triggered. The latestusageis cached inlatestUsageso the command does not need to re-read JSONL.src/handoff.js: pure logic layer for the handoff feature, decoupled fromvscode.buildHandoffPrompt(usage, gitInfo)composes a compact, paste-ready continuation prompt: a one-line header noting the previous context percent, the session file path and git branch (omitted when missing), then a minimal four-line skeleton (Task/Done/Next/Constraints) whose placeholders are filled by the session's Claude after paste. Only facts the model cannot infer from the session are included; per-token breakdown and verbose promptify sections are intentionally omitted.collectGitInfo(cwd)runsgitthroughchild_processand returns{ branch, status, recentCommits }ornullwhencwdis not a repo (onlybranchis surfaced in the prompt).
Provider contract: every provider's readLatestXxxUsage(root, workspaceFolders) returns the unified shape { provider, sessionFile, updatedAt, contextTokens, contextWindow, contextPercent, ... }. Codex uses file mtime for updatedAt; Claude uses message timestamp. To add a provider, implement this contract and append it to the candidates array in agentUsage.js. Formatting belongs only in the aggregation layer through formatAgentUsage, formatCount, and formatRateLimits.
Key design: parsing logic in sessionFiles, codexUsage, claudeUsage, and agentUsage, plus the handoff prompt builder in handoff, is fully decoupled from the vscode API, so it can be tested directly with Node's built-in test runner without a VS Code test environment. Tests use test/testUtils.js helpers such as makeTempDir, writeJsonl, and setMtime to create temporary JSONL fixtures for parser and selection behavior.
When adding support for a new model context window, update inferClaudeContextWindow in claudeUsage.js.