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
@@ -195,6 +195,45 @@ fun fetchSessionFile(settings: ServerSettings, sessionId: String, name: String):
|
||||
it.inputStream.readBytes()
|
||||
}
|
||||
|
||||
// One rate-limit window, rendered as a labeled bar on the usage screen.
|
||||
data class UsageWindow(
|
||||
val label: String,
|
||||
val percent: Double,
|
||||
val resetsAt: String?,
|
||||
val active: Boolean,
|
||||
)
|
||||
|
||||
data class UsageSnapshot(
|
||||
val provider: String,
|
||||
val available: Boolean,
|
||||
val windows: List<UsageWindow>,
|
||||
val error: String?,
|
||||
)
|
||||
|
||||
/** The backend caches; refreshing more often than its poll interval just re-reads the cache. */
|
||||
fun fetchUsage(settings: ServerSettings): List<UsageSnapshot> =
|
||||
requestFromServer(settings, "/usage", readTimeoutMs = 30000) { connection ->
|
||||
val snapshots = JSONArray(connection.inputStream.bufferedReader().readText())
|
||||
(0 until snapshots.length()).map { i ->
|
||||
val snapshot = snapshots.getJSONObject(i)
|
||||
val windows = snapshot.getJSONArray("windows")
|
||||
UsageSnapshot(
|
||||
provider = snapshot.getString("provider"),
|
||||
available = snapshot.getBoolean("available"),
|
||||
error = snapshot.optString("error").ifEmpty { null },
|
||||
windows = (0 until windows.length()).map { j ->
|
||||
val window = windows.getJSONObject(j)
|
||||
UsageWindow(
|
||||
label = window.getString("label"),
|
||||
percent = window.getDouble("percent"),
|
||||
resetsAt = window.optString("resetsAt").ifEmpty { null },
|
||||
active = window.getBoolean("active"),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun answerQuestion(settings: ServerSettings, sessionId: String, questionId: String, answer: String) {
|
||||
requestFromServer(
|
||||
settings,
|
||||
|
||||
Reference in new issue
Block a user