Decision 0015 rejected the account usage endpoint because AGENTS.md forbade reading a harness's credential. The rule was written to stop one program helping itself to another's secrets, and it was catching a legitimate use with it: the user asking about their own subscription, through software they installed to do that. AGENTS.md now states the narrow allowance instead of an absolute the project does not hold, and 0016 records it. The status line stays. It is free and it speaks every turn. What it cannot do is report the per-model weekly limits a Max plan meters separately, or answer at all before a session has taken a turn. The first live reading found the account-wide seven-day window at 38% left and a per-model weekly window at 77% left — a second ceiling the footer previously could not see. Constraints the credential is read under, all enforced in code: access token only, never the refresh token; zeroed on drop, along with the file buffer it was borrowed out of; unprintable by construction, since HarnessError carries no owned strings and AccessToken's Debug is hand-written; identified as lumbridge/<version>, because sending claude-code/2.1.0 would make our traffic indistinguishable from the harness's in Anthropic's logs; and off entirely under LUMBRIDGE_CLAUDE_OAUTH=0. The request runs on a detached thread with a slow refresh and a 429 backoff, so a ten-second round trip cannot stall the transcript follower or make quitting wait on the network, and one surface failing does not fault the other two. Footer polish on top: the harness name prints once per group instead of in front of each of its four windows, each quota carries a short scope pill (5h, 7d, Fable wk, tokens) where an invisible BORDER-weight label used to be, quotas sort ahead of spend, and a window under ten percent turns its headline amber — value colour on the number, provenance colour on the meter, never mixed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
58 lines
2.3 KiB
Rust
58 lines
2.3 KiB
Rust
//! Takes one reading from Claude Code's account usage endpoint and prints it.
|
|
//!
|
|
//! `cargo run -p lumbridge-harness --example claude_usage`
|
|
//!
|
|
//! Useful for answering "what does the provider actually say right now?"
|
|
//! without launching the shell. It performs exactly one request — the endpoint
|
|
//! rate-limits under polling — and prints only the parsed windows. The token is
|
|
//! never printed, because there is no code path here that could reach it.
|
|
|
|
use lumbridge_harness::MonotonicWallClock;
|
|
use lumbridge_harness::claude::WindowReading;
|
|
use lumbridge_harness::claude::oauth::{ClaudeOauthOptions, RefreshOutcome, refresh};
|
|
|
|
fn main() {
|
|
let options = ClaudeOauthOptions::default();
|
|
if !options.enabled {
|
|
println!("the usage endpoint is disabled (LUMBRIDGE_CLAUDE_OAUTH=0)");
|
|
return;
|
|
}
|
|
let now_ms = MonotonicWallClock::start().now_ms();
|
|
println!("GET {}", options.endpoint);
|
|
|
|
match refresh(&options, now_ms) {
|
|
RefreshOutcome::Snapshot(snapshot, plan) => {
|
|
println!("plan: {}", plan.label.as_deref().unwrap_or("unreported"));
|
|
print_window("five hour", snapshot.five_hour, now_ms);
|
|
print_window("seven day", snapshot.seven_day, now_ms);
|
|
for scoped in snapshot.scoped {
|
|
print_window(&scoped.model, scoped.reading, now_ms);
|
|
}
|
|
}
|
|
RefreshOutcome::RateLimited => println!("rate limited; try again later"),
|
|
RefreshOutcome::Unauthenticated => {
|
|
println!("no usable credential (not signed in, expired, or disabled)");
|
|
}
|
|
RefreshOutcome::Failed(error) => println!("failed: {error}"),
|
|
}
|
|
}
|
|
|
|
fn print_window(label: &str, reading: WindowReading, now_ms: u64) {
|
|
match reading {
|
|
WindowReading::Usable { permille, window } => {
|
|
let left = (1_000 - permille.min(1_000)) / 10;
|
|
let resets = window.map_or_else(
|
|
|| "no reset reported".to_owned(),
|
|
|window| {
|
|
format!(
|
|
"resets in {}",
|
|
lumbridge_core::format_duration_ms(window.remaining_ms(now_ms))
|
|
)
|
|
},
|
|
);
|
|
println!("{label:>22}: {left}% left · {resets}");
|
|
}
|
|
WindowReading::Absent => println!("{label:>22}: not reported"),
|
|
}
|
|
}
|