Skip to content

Feat/usage command - #500

Closed
shidevil wants to merge 19 commits into
junhoyeo:mainfrom
shidevil:feat/usage-command
Closed

Feat/usage command#500
shidevil wants to merge 19 commits into
junhoyeo:mainfrom
shidevil:feat/usage-command

Conversation

@shidevil

@shidevil shidevil commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary by cubic

Adds a tokscale usage command and a TUI Usage tab to show live subscription quotas across providers, with fast loads via caching and parallel fetching.

  • New Features

    • CLI: tokscale usage with --json and --light.
    • TUI: new Usage tab; press u/r to refresh; loads from ~/.cache/tokscale/subscription-usage-cache.json (5‑min TTL).
    • Providers (auto-detected; others skipped): Claude, Codex, Z.ai, Amp, GitHub Copilot, Kimi, MiniMax.
    • Internals: new commands/usage/ module per provider, background fetch in TUI, parallel requests, light-mode card output.
  • Bug Fixes

    • MiniMax: treat current_interval_usage_count as remaining; prefer current_interval_used_count; better reset time handling and plan inference.
    • Token lifecycles: persist refreshed tokens (Claude, Codex, Kimi) with atomic writes; proper URL-encoded refresh payloads.
    • Copilot: respect GH_CONFIG_DIR; robust hosts.yml parsing; compute percent from remaining/entitlement; support Windows/XDG paths.
    • UX/compat: clean error on non-macOS keychain; proper empty state; clear cache on empty results; r refresh includes Usage; reject unsupported --home; Z.ai metrics ordered and relabeled.

Written for commit dcf8c25. Summary will update on new commits.

shidevil and others added 19 commits May 1, 2026 13:22
…viders

Adds tokscale usage CLI command and TUI tab for monitoring subscription
quotas across Claude, Codex, and Z.ai. Supports --json and --light output
modes, token refresh, and keychain credential fallback.

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

Refactor usage.rs into commands/usage/ directory with per-provider modules
for better maintainability. Add four new subscription quota providers:

- Amp: reads API key from ~/.local/share/amp/secrets.json, parses
  displayText from ampcode.com/api/internal
- GitHub Copilot: reads token from macOS keychain or ~/.config/gh/hosts.yml,
  fetches quota from api.github.com/copilot_internal/user
- Kimi: reads OAuth credentials from ~/.kimi/credentials/kimi-code.json,
  fetches usage from api.kimi.com/coding/v1/usages with token refresh
- MiniMax: reads API key from MINIMAX_API_KEY env var, fetches from
  api.minimax.io/v1/api/openplatform/coding_plan/remains

Update README.md with subscription usage documentation and provider table.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
- Persist OAuth refresh tokens to disk (Claude, Codex, Kimi)
- Atomic credential writes with 0o600 permissions
- Fix copilot has_credentials() to properly parse hosts.yml
- Fix copilot percent_remaining to use f64 not i64
- Fix minimax epoch_to_ms() heuristic for 2026 timestamps
- Add is_finite() guards on f64 arithmetic (amp.rs)
- Make helpers module public for TUI import
- Remove duplicate helpers from tui/ui/usage.rs
- Remove eprintln! error spam from worker threads
- Add provider name header + truncation to light-mode cards
- Fix README Usage tab shortcut instruction
- Make TUI tests hermetic (skip real cache in test builds)
- Remove auto-fetch on tab switch (cache + u key)
- Clear cache when fetch returns empty results
- Fix codex CredentialSource Copy derive (PathBuf not Copy)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Move atomic_write_secret to shared helpers with mode(0o600) at open time (P1)
- Add temp file cleanup on rename failure
- Check Amp ok field instead of silently returning empty metrics
- Persist Codex access token even when no new refresh token returned
- Fix TUI empty state to distinguish never-fetched from fetched-empty
- Fix truncate() off-by-one with ellipsis
- Fix light-mode card width consistency across header/metrics/border
- Deduplicate copilot hosts.yml parsing into shared helper

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pressing 'u' now spawns fetch_all() on a background thread and shows
a spinner while waiting, instead of blocking the event loop.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Switch all 7 providers to current-thread Tokio runtime to reduce
  thread count when fetched in parallel
- Claude: fallback from stale/corrupt credentials file to keychain
- Claude: always persist refreshed tokens (not just file-sourced)
- atomic_write_secret: use PID-scoped temp file to avoid concurrent
  write collisions

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The reactive retry path was using the original cached refresh token
instead of the freshly rotated one from the proactive refresh.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Kimi: use window duration metadata instead of index for labels
- Kimi: include label in dedup key to avoid collapsing distinct windows
- TUI: handle Disconnected from background fetch thread (prevents stuck spinner)
- Copilot: compute pct from remaining/entitlement when percent_remaining missing
- Light-mode: truncate metric labels to prevent column overflow

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@shidevil shidevil closed this May 4, 2026
@shidevil
shidevil deleted the feat/usage-command branch May 4, 2026 05:26
@shidevil
shidevil restored the feat/usage-command branch May 4, 2026 05:26
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