Phase 3: usage screen
GET /usage serves the numbers behind Claude Code's /usage, read with the CLI's own stored OAuth credentials (nothing to configure). The endpoint is undocumented, so parsing is defensive -- the generic limits[] array becomes labeled window bars, unknown kinds surface under their raw name, and any failure degrades to an 'unavailable' snapshot with the reason. One UsageProvider per paid service behind a caching monitor that enforces the >=180s minimum poll regardless of phone refreshes; no background polling at all. ureq (rustls) does the outbound call, with the process-level CryptoProvider now chosen explicitly in main -- ureq brings ring while axum-server brings aws-lc-rs, and with both in the graph rustls refuses to guess. App: a Usage screen off the session list -- per-window bars colored by utilization with relative reset times. Verified live: 74%/26%/16% windows rendered against the real endpoint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
This commit is contained in:
1 parent
f2430671a2
commit
3c97a5ef28
10 files changed
+606
-10
No files matched your search
+20
-4
@@ -17,6 +17,7 @@ mod auth;
|
||||
mod config;
|
||||
mod routes;
|
||||
mod session;
|
||||
mod usage;
|
||||
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -119,6 +120,13 @@ fn print_enrollment(host: IpAddr, port: u16, token: &str) -> Result<()> {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// Both rustls crypto providers are in the dependency graph (ureq
|
||||
// brings ring, axum-server brings aws-lc-rs), so rustls refuses to
|
||||
// pick one itself; choose before anything touches TLS.
|
||||
rustls::crypto::aws_lc_rs::default_provider()
|
||||
.install_default()
|
||||
.expect("no other TLS crypto provider is installed before main");
|
||||
|
||||
tracing_subscriber::fmt().with_env_filter("info").init();
|
||||
let args = Args::parse();
|
||||
|
||||
@@ -174,13 +182,21 @@ async fn main() -> Result<()> {
|
||||
.await
|
||||
.context("failed to load TLS cert/key")?;
|
||||
|
||||
let monitor = Arc::new(usage::UsageMonitor::new(vec![Box::new(usage::ClaudeUsage {
|
||||
credentials_path: std::env::home_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("/"))
|
||||
.join(".claude/.credentials.json"),
|
||||
})]));
|
||||
|
||||
// The bearer-token middleware wraps the entire router -- routes and
|
||||
// fallback alike -- here and only here, so a new route can't forget
|
||||
// auth. Zero unauthenticated endpoints.
|
||||
let app = routes::router(Arc::clone(&manager)).layer(axum::middleware::from_fn_with_state(
|
||||
Arc::clone(&manager),
|
||||
auth::require_token,
|
||||
));
|
||||
let app = routes::router(Arc::clone(&manager))
|
||||
.merge(routes::usage_router(monitor))
|
||||
.layer(axum::middleware::from_fn_with_state(
|
||||
Arc::clone(&manager),
|
||||
auth::require_token,
|
||||
));
|
||||
|
||||
let addr = SocketAddr::new(bind_ip, args.port);
|
||||
tracing::info!("serving https://{addr}");
|
||||
|
||||
Reference in new issue
Block a user