Skip to content

Latest commit

 

History

History
43 lines (28 loc) · 9.86 KB

File metadata and controls

43 lines (28 loc) · 9.86 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

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.

Commands

# 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 package

For local installation, symlink the project directory to ~/.vscode/extensions/codex-claude-monitor, then run Developer: Reload Window in VS Code. See README.md.

Architecture

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 with walkFiles, descending mtime sorting, parseJsonLine, calculateContextPercent, and readLastMatchingEvent, which parses JSONL line by line, returns the last matched entry, and caches by mtime plus size.
  • src/codexUsage.js: recursively scans ~/.codex/sessions for rollout-*.jsonl, picks the newest file by mtime, and reads the last token_count event. Percentage = last_token_usage.input_tokens / model_context_window.
  • src/claudeUsage.js: recursively scans ~/.claude/projects for .jsonl, picks the newest file, and reads the last type: "assistant" entry with message.usage. Context tokens = input_tokens + cache_read_input_tokens + cache_creation_input_tokens; the context window is inferred from the model name by inferClaudeContextWindow: model names containing 1m or fable, or claude-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-interactive claude -p sessions), 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 in CACHE_FILE_NAME) written by scripts/usage-cache.sh. Claude Code passes a JSON blob on stdin to the configured statusline command; for subscribers it includes a rate_limits block (the OAuth /api/oauth/usage 5-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) maps five_hourprimary (window_minutes 300) and seven_daysecondary (10080), converting used_percentageused_percent and passing resets_at (Unix seconds) through, and returns { rateLimits, capturedAt } or null. A present-but-malformed window (non-numeric used_percentage) invalidates the whole read; a missing window is simply omitted. Results are cached by (mtime, size) like readLastMatchingEvent. This replaced the removed claudeRateLimits.js, which parsed claude -p "/usage" text output — that command no longer emits subscription limits (its -p output 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 (via jq) plus a capturedAt Unix-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 (missing jq, absent rate_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 by updatedAt, 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 from formatRateLimitsStatusBar using 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 JSONL rate_limits; Claude segments come from options.claudeRateLimits injected by extension.js (read from the statusline usage cache via src/claudeStatuslineUsage.js), attached in readLatestAgentUsage only when Claude wins and never overwriting provider-supplied data, with options.claudeRateLimitsCapturedAt riding along as usage.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. getUsageSeverity maps context percent to low (<50%), medium (50–79%), or high (>=80%) for status bar coloring. Tooltip rows are ordered as context row, model row (friendly name with (1M context) marker), Claude token-composition rows from formatClaudeTokenDetail (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 by formatRateLimits as 5h usage: 21% · Reset at 14:32 (in 2h 13m); the (in …) countdown comes from formatTimeLeft (total minutes rounded up, at most two units, zero minor unit omitted) and is dropped once resets_at is in the past, so stale sessions keep the absolute time only. When rate-limit rows are present and usage.rateLimitsCapturedAt is set, a final Usage updated 2m ago (11:58) row is appended by formatUsageCaptureRow; its relative part comes from formatTimeAgo (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 the vscode API. It owns the main status bar item (priority 90; click runs agentTokenStatus.refresh) and the refresh timer with default 10s interval. Each refresh reads the Claude subscription rate limits from the statusline usage cache via readClaudeStatuslineUsage (lightweight file read, no separate timer — the data is pushed by Claude Code's statusline, so there is no claude -p probe process) and forwards claudeRateLimits / claudeRateLimitsCapturedAt to readLatestAgentUsage. Configuration reads cover agentTokenStatus.sessionsRoot, claudeRoot, and refreshIntervalMs, plus the severity-to-theme-color mapping via SEVERITY_COLORS (charts.green / charts.yellow / charts.red). It also owns a second status bar item handoffItem (priority 89, $(export) Handoff) shown only when contextPercent > HANDOFF_THRESHOLD (50); clicking it runs agentTokenStatus.handoff, which composes a handoff prompt via src/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 latest usage is cached in latestUsage so the command does not need to re-read JSONL.
  • src/handoff.js: pure logic layer for the handoff feature, decoupled from vscode. 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) runs git through child_process and returns { branch, status, recentCommits } or null when cwd is not a repo (only branch is 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.