Skip to content

Feat/usage command - #3

Merged
shidevil merged 9 commits into
mainfrom
feat/usage-command
May 2, 2026
Merged

Feat/usage command#3
shidevil merged 9 commits into
mainfrom
feat/usage-command

Conversation

@shidevil

@shidevil shidevil commented May 2, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a tokscale usage CLI command and TUI Usage tab that displays live subscription quota and remaining usage for AI coding assistants.

Quick start

tokscale usage           # light-mode card output
tokscale usage --json    # JSON for scripting
tokscale tui             # switch to Usage tab (2nd tab)

Supported Providers (7)

Provider Auth Source Metrics
Claude ~/.claude/.credentials.json / macOS Keychain Session (5h), Weekly (7d), Opus (7d)
Codex CODEX_HOME/auth.json, ~/.config/codex/auth.json, ~/.codex/auth.json, macOS Keychain Session (5h), Weekly (7d)
Z.ai ZAI_API_KEY / GLM_API_KEY env var Session, Weekly, Web Search
Amp ~/.local/share/amp/secrets.json Free tier ($remaining/$total), Credits
GitHub Copilot macOS Keychain gh:github.com, hosts.yml (respects GH_CONFIG_DIR) Premium, Chat, Completions (paid + free)
Kimi Code ~/.kimi/credentials/kimi-code.json Session, Weekly
MiniMax MINIMAX_API_KEY / MINIMAX_API_TOKEN env var Session (prompts)

Only providers with valid credentials are queried — the rest are silently skipped.

Architecture

Refactored commands/usage.rs into a commands/usage/ module directory:

commands/usage/
├── mod.rs          # Shared types, fetch_all(), disk cache, CLI rendering
├── helpers.rs      # capitalize(), format_reset_time(), read_keychain(), render_ascii_bar()
├── claude.rs       # Claude OAuth provider
├── codex.rs        # Codex/OpenAI provider
├── zai.rs          # Z.ai provider
├── amp.rs          # Amp provider
├── copilot.rs      # GitHub Copilot provider
├── kimi.rs         # Kimi Code provider
└── minimax.rs      # MiniMax provider

Each provider exports has_credentials() -> bool and fetch() -> Result<UsageOutput>.

Performance

  • Credential pre-check: Fast local file/env checks skip providers without credentials entirely (no network calls)
  • Parallel fetching: Active providers run concurrently via std::thread::scope
  • Disk cache: Data cached to ~/.cache/tokscale/subscription-usage-cache.json with 5-minute TTL — the Usage tab loads instantly on startup like other tabs
  • No new dependencies: Uses only crates already in the workspace (reqwest, serde, serde_json, chrono, anyhow, dirs, tokio)

Bug fixes included

  • MiniMax: current_interval_usage_count is a remaining count despite its name — now handled correctly with current_interval_used_count preferred when available
  • Kimi: OAuth refresh tokens (which rotate) are now persisted back to disk after each refresh, preventing stale tokens on next run
  • Codex: Credential lookup now requires tokens.access_token to be present (not just the tokens object), supports CODEX_HOME env var and macOS Keychain fallback
  • Copilot: Respects GH_CONFIG_DIR env var for hosts.yml path; YAML parser correctly handles other fields appearing before oauth_token under github.com:
  • OAuth payloads: Kimi and Codex refresh payloads use reqwest .form() for proper URL encoding of tokens that may contain reserved characters
  • Platform compat: read_keychain() returns a clean error on non-macOS instead of spawning a missing binary
  • TUI: Empty usage state shows a proper message instead of the loading prompt; tab navigation tests updated for 7-tab layout
  • Z.ai: Metrics ordered as Session → Weekly → Web Search regardless of API response order; renamed Monthly→Weekly, Web Searches→Web Search

Files changed

  • New: commands/usage/ directory (9 provider + helper files, ~1700 lines)
  • New: tui/ui/usage.rs (TUI rendering for Usage tab)
  • Modified: tui/app.rs (Usage tab, disk cache, updated tab tests)
  • Modified: main.rs (Usage subcommand + --home rejection)
  • Modified: README.md (provider docs, corrected tab count)
  • Unchanged: Cargo.lock (no new dependencies

shidevil and others added 9 commits May 2, 2026 12:47
current_interval_usage_count is a remaining count despite its name.
Prefer explicit current_interval_used_count when available, and treat
usage_count as remaining in the fallback path. Also adds:
- remains_time fallback for reset timestamps
- data-level plan fields (data.current_subscribe_title, data.plan_name)
- Plan inference that divides total by MODEL_CALLS_PER_PROMPT (15)
- Separate is_auth_error() detection (status 1004)
- epoch_to_ms() helper for proper timestamp normalization
- normalize_plan_name() that strips "MiniMax Coding Plan" prefix

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The OAuth server rotates refresh tokens on each refresh, but the old
code only updated the in-memory access_token and discarded the new
refresh_token/expires_in from the response. This meant the on-disk
refresh token would go stale after one rotation, forcing re-login.

Add save_credentials() that writes the full credential JSON back to
~/.kimi/credentials/kimi-code.json after a successful token refresh.
Both the proactive (near-expiry) and reactive (401 fallback) refresh
paths now persist the updated tokens.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The gh CLI supports GH_CONFIG_DIR to override its config directory,
but read_token_from_hosts() hardcoded ~/.config/gh/hosts.yml. Users
with a custom GH_CONFIG_DIR would fail to find the token.

Extract gh_config_dir() that checks GH_CONFIG_DIR first, then falls
back to ~/.config/gh — matching the gh CLI own resolution logic.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ng it

The usage command accepted the global --home flag but did nothing with
it, silently ignoring the override. Add reject_unsupported_home_override()
to match the pattern used by other commands that don't support --home.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…OME/keychain

read_credentials() accepted the first file with any tokens value, even
if access_token was null inside. This meant a stale primary auth file
(Codex nulls out access_token when switching to keyring storage) would
prevent falling back to a valid secondary one.

Now only accepts a credential source if tokens.access_token is present.
Also adds:
- CODEX_HOME env var as first path to check (matches Codex CLI)
- macOS keychain fallback for service "Codex Auth" (matches openusage)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1. README: correct tab count from 6 to 7, remove non-existent 1-6
   numeric shortcut claim

2. app.rs tests: update tab assertions for 7 tabs (Usage added at
   index 1), fixing test_tab_all/next/prev expectations

3. usage.rs: add empty-state rendering so a successful no-data fetch
   shows "No subscription data available" instead of the loading prompt

4. helpers.rs: guard read_keychain with cfg!(not(target_os = "macos"))
   so it fails cleanly on non-macOS instead of spawning a missing binary

5. kimi.rs + codex.rs: use reqwest .form() for OAuth refresh payloads
   instead of manual string formatting, ensuring proper URL encoding of
   refresh tokens that may contain reserved characters

6. copilot.rs: fix hosts.yml parsing to handle other fields (user,
   git_protocol) appearing before oauth_token under github.com: — only
   exit the section on a non-indented top-level key

All 451 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previously fetch_all() tried all 7 providers sequentially, causing slow
tab switches and noisy error messages for providers the user does not
have credentials for.

Add has_credentials() fast local checks to each provider (file exists,
env var set, keychain lookup) that skip providers entirely when no
credentials are on disk. Active providers now run in parallel via
std::thread::scope, so multiple providers fetch simultaneously.

Also fix remaining tab navigation tests (backtab, left/right) that
missed the Usage tab insertion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Subscription usage now persists to the same cache directory as other
TUI data (~/.cache/tokscale/subscription-usage-cache.json). On startup
the cached data is loaded instantly, making the Usage tab appear
immediately on first switch. A fresh fetch is triggered when the cache
is older than 5 minutes or when the user presses u/r to refresh.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…column

Rename Monthly to Weekly and Web Searches to Web Search. Collect
metrics into named variables instead of pushing in API order, so the
output is always Session → Weekly → Web Search regardless of API
response ordering. Widen label column to 14 chars for cleaner bar
alignment across all providers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@shidevil
shidevil merged commit dcf905a into main May 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant