@scrylog/cli 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/dist/assets/highlights-eq9cgrbb.scm +604 -0
  2. package/dist/assets/highlights-ghv9g403.scm +205 -0
  3. package/dist/assets/highlights-hk7bwhj4.scm +284 -0
  4. package/dist/assets/highlights-r812a2qc.scm +150 -0
  5. package/dist/assets/highlights-x6tmsnaa.scm +115 -0
  6. package/dist/assets/injections-73j83es3.scm +27 -0
  7. package/dist/assets/parser.worker-xyhf83rf.js +4563 -0
  8. package/dist/assets/tree-sitter-3jzf13jk.wasm +0 -0
  9. package/dist/assets/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  10. package/dist/assets/tree-sitter-markdown-411r6y9b.wasm +0 -0
  11. package/dist/assets/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  12. package/dist/assets/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  13. package/dist/assets/tree-sitter-zig-e78zbjpm.wasm +0 -0
  14. package/dist/cli/index.js +499 -0
  15. package/dist/cli/index.js.map +61 -0
  16. package/dist/daemon/index.js +670 -0
  17. package/dist/daemon/index.js.map +1475 -0
  18. package/dist/hooks/claude.js +6 -0
  19. package/dist/hooks/claude.js.map +11 -0
  20. package/dist/hooks/codex.js +10 -0
  21. package/dist/hooks/codex.js.map +11 -0
  22. package/dist/mcp/index.js +100 -0
  23. package/dist/mcp/index.js.map +231 -0
  24. package/dist/oc/index.js +21 -0
  25. package/dist/oc/index.js.map +10 -0
  26. package/package.json +16 -13
  27. package/src/api/docs/sessions.keys.ts +0 -6
  28. package/src/api/docs/sessions.ts +0 -82
  29. package/src/api/docs/system.keys.ts +0 -5
  30. package/src/api/docs/system.ts +0 -69
  31. package/src/api/docs/usage.keys.ts +0 -3
  32. package/src/api/docs/usage.ts +0 -24
  33. package/src/api/docs/webhooks.keys.ts +0 -5
  34. package/src/api/docs/webhooks.ts +0 -78
  35. package/src/api/index.ts +0 -18
  36. package/src/api/lib/auth.ts +0 -43
  37. package/src/api/lib/errors.ts +0 -28
  38. package/src/api/lib/route.ts +0 -18
  39. package/src/api/models/claude.ts +0 -4
  40. package/src/api/models/codex.ts +0 -4
  41. package/src/api/models/opencode.ts +0 -11
  42. package/src/api/routes/sessions.ts +0 -51
  43. package/src/api/routes/system.ts +0 -106
  44. package/src/api/routes/usage.ts +0 -15
  45. package/src/api/routes/webhooks/claude.ts +0 -149
  46. package/src/api/routes/webhooks/codex.ts +0 -175
  47. package/src/api/routes/webhooks/opencode.ts +0 -112
  48. package/src/api/routes.ts +0 -93
  49. package/src/api/schemas/common.ts +0 -29
  50. package/src/api/schemas/sessions.ts +0 -86
  51. package/src/api/schemas/usage.ts +0 -51
  52. package/src/api/schemas/webhooks/claude.ts +0 -32
  53. package/src/api/schemas/webhooks/codex.ts +0 -34
  54. package/src/api/schemas/webhooks/opencode.ts +0 -29
  55. package/src/api/services/claude-session.ts +0 -9
  56. package/src/api/services/codex-session.ts +0 -8
  57. package/src/api/services/opencode-session.ts +0 -73
  58. package/src/classifier.ts +0 -206
  59. package/src/cli/commands.ts +0 -714
  60. package/src/cli/index.ts +0 -18
  61. package/src/cli/program.ts +0 -175
  62. package/src/cli/prompt.ts +0 -44
  63. package/src/daemon/cli.ts +0 -27
  64. package/src/daemon/collectors/claude/README.md +0 -108
  65. package/src/daemon/collectors/claude/collector.ts +0 -423
  66. package/src/daemon/collectors/claude/discovery.ts +0 -127
  67. package/src/daemon/collectors/claude/hooks.ts +0 -181
  68. package/src/daemon/collectors/claude/index.ts +0 -8
  69. package/src/daemon/collectors/claude/jsonl.ts +0 -85
  70. package/src/daemon/collectors/claude/transcript.ts +0 -246
  71. package/src/daemon/collectors/codex/README.md +0 -147
  72. package/src/daemon/collectors/codex/collector.ts +0 -661
  73. package/src/daemon/collectors/codex/discovery.ts +0 -104
  74. package/src/daemon/collectors/codex/hooks.ts +0 -179
  75. package/src/daemon/collectors/codex/index.ts +0 -7
  76. package/src/daemon/collectors/codex/jsonl.ts +0 -108
  77. package/src/daemon/collectors/codex/transcript.ts +0 -299
  78. package/src/daemon/collectors/git.ts +0 -353
  79. package/src/daemon/collectors/herdr/collector.ts +0 -128
  80. package/src/daemon/collectors/herdr/index.ts +0 -2
  81. package/src/daemon/collectors/herdr/normalization.ts +0 -111
  82. package/src/daemon/collectors/opencode/README.md +0 -106
  83. package/src/daemon/collectors/opencode/collector.ts +0 -545
  84. package/src/daemon/collectors/opencode/discovery.ts +0 -257
  85. package/src/daemon/collectors/opencode/index.ts +0 -5
  86. package/src/daemon/collectors/opencode/session-builder.ts +0 -59
  87. package/src/daemon/collectors/opencode-db.ts +0 -294
  88. package/src/daemon/collectors/process.ts +0 -201
  89. package/src/daemon/collectors/tmux/README.md +0 -128
  90. package/src/daemon/collectors/tmux/collector.ts +0 -492
  91. package/src/daemon/collectors/tmux/enrichment.ts +0 -242
  92. package/src/daemon/collectors/tmux/index.ts +0 -4
  93. package/src/daemon/collectors/tmux/parse.ts +0 -128
  94. package/src/daemon/collectors/usage.ts +0 -259
  95. package/src/daemon/index.ts +0 -209
  96. package/src/daemon/scheduler.ts +0 -86
  97. package/src/daemon/store.ts +0 -630
  98. package/src/detect.ts +0 -143
  99. package/src/hooks/claude.ts +0 -596
  100. package/src/hooks/codex.ts +0 -517
  101. package/src/legacy/classifier.ts +0 -215
  102. package/src/legacy/detect.ts +0 -143
  103. package/src/mcp/client-server.ts +0 -21
  104. package/src/mcp/http-handler.ts +0 -126
  105. package/src/mcp/index.ts +0 -61
  106. package/src/mcp/resources/config-current.ts +0 -19
  107. package/src/mcp/resources/index.ts +0 -11
  108. package/src/mcp/resources/sessions-active.ts +0 -19
  109. package/src/mcp/resources/sessions-all.ts +0 -19
  110. package/src/mcp/schemas/sessions.ts +0 -3
  111. package/src/mcp/server.ts +0 -17
  112. package/src/mcp/services/api-adapter.ts +0 -48
  113. package/src/mcp/services/data-source.ts +0 -22
  114. package/src/mcp/services/store-adapter.ts +0 -40
  115. package/src/mcp/tools/get-active-sessions.ts +0 -16
  116. package/src/mcp/tools/get-session-children.ts +0 -24
  117. package/src/mcp/tools/get-session-messages.ts +0 -24
  118. package/src/mcp/tools/get-session.ts +0 -23
  119. package/src/mcp/tools/index.ts +0 -15
  120. package/src/mcp/tools/list-sessions.ts +0 -16
  121. package/src/oc/index.ts +0 -302
  122. package/src/service/manager.ts +0 -284
  123. package/src/shared/config.ts +0 -511
  124. package/src/shared/ipc.ts +0 -80
  125. package/src/shared/logger.ts +0 -248
  126. package/src/shared/paths.ts +0 -73
  127. package/src/shared/platform.ts +0 -110
  128. package/src/shared/session-exclusions.ts +0 -5
  129. package/src/shared/session-view.ts +0 -302
  130. package/src/shared/terminal-capabilities.ts +0 -130
  131. package/src/shared/types.ts +0 -330
  132. package/src/shared/utils.ts +0 -339
  133. package/src/shared/version.ts +0 -44
  134. package/src/tui/App.tsx +0 -240
  135. package/src/tui/client.ts +0 -163
  136. package/src/tui/components/BindingsModal.tsx +0 -82
  137. package/src/tui/components/DashboardHeader.tsx +0 -256
  138. package/src/tui/components/KeyBar.tsx +0 -67
  139. package/src/tui/components/SessionDetailModal.tsx +0 -169
  140. package/src/tui/components/SessionsView.tsx +0 -184
  141. package/src/tui/components/TabBar.tsx +0 -40
  142. package/src/tui/components/UsageSummaryPane.tsx +0 -272
  143. package/src/tui/components/UsageView.tsx +0 -242
  144. package/src/tui/components/tool-icons.ts +0 -24
  145. package/src/tui/index.tsx +0 -106
  146. package/src/tui/keybindings.ts +0 -49
  147. package/src/tui/model.ts +0 -295
  148. package/src/tui/state.ts +0 -77
  149. package/src/tui/theme.ts +0 -110
  150. package/src/types.ts +0 -113
  151. package/src/usage/README.md +0 -323
  152. package/src/usage/api.ts +0 -99
  153. package/src/usage/client.ts +0 -3
  154. package/src/usage/clients/anthropic-oauth-usage.ts +0 -258
  155. package/src/usage/clients/anthropic-web-usage.ts +0 -329
  156. package/src/usage/clients/claude-cli-probe.ts +0 -513
  157. package/src/usage/clients/codex-app-server.ts +0 -354
  158. package/src/usage/clients/codex-oauth-usage.ts +0 -240
  159. package/src/usage/clients/opencode-go-api.ts +0 -100
  160. package/src/usage/clients/opencode-go-usage.ts +0 -577
  161. package/src/usage/errors.ts +0 -33
  162. package/src/usage/index.ts +0 -20
  163. package/src/usage/local/opencode-go-local.ts +0 -194
  164. package/src/usage/mappers.ts +0 -282
  165. package/src/usage/planner.ts +0 -118
  166. package/src/usage/providers/claude.ts +0 -233
  167. package/src/usage/providers/codex.ts +0 -115
  168. package/src/usage/providers/opencode-go.ts +0 -115
  169. package/src/usage/resolver.ts +0 -4
  170. package/src/usage/resolvers/browser-cookies.ts +0 -808
  171. package/src/usage/resolvers/claude-auth.ts +0 -208
  172. package/src/usage/resolvers/claude-cli.ts +0 -24
  173. package/src/usage/resolvers/claude-desktop-auth.ts +0 -170
  174. package/src/usage/resolvers/claude-web.ts +0 -82
  175. package/src/usage/resolvers/codex-auth.ts +0 -174
  176. package/src/usage/resolvers/keychain-coordinator.ts +0 -397
  177. package/src/usage/resolvers/opencode-go-web.ts +0 -55
  178. package/src/usage/types.ts +0 -225
  179. package/src/usage/web-session-cache.ts +0 -37
  180. /package/{src/tui → dist/cli}/assets/providers/SOURCES.md +0 -0
  181. /package/{src/tui → dist/cli}/assets/providers/claude-terminal.png +0 -0
  182. /package/{src/tui → dist/cli}/assets/providers/claude-terminal.svg +0 -0
  183. /package/{src/tui → dist/cli}/assets/providers/claude.png +0 -0
  184. /package/{src/tui → dist/cli}/assets/providers/copilot.png +0 -0
  185. /package/{src/tui → dist/cli}/assets/providers/copilot.svg +0 -0
  186. /package/{src/tui → dist/cli}/assets/providers/openai-terminal.png +0 -0
  187. /package/{src/tui → dist/cli}/assets/providers/openai-terminal.svg +0 -0
  188. /package/{src/tui → dist/cli}/assets/providers/openai.png +0 -0
  189. /package/{src/tui → dist/cli}/assets/providers/opencode-terminal.png +0 -0
  190. /package/{src/tui → dist/cli}/assets/providers/opencode-terminal.svg +0 -0
  191. /package/{src/tui → dist/cli}/assets/providers/opencode.png +0 -0
  192. /package/{src/tui → dist/cli}/assets/providers/shell.png +0 -0
  193. /package/{src/tui → dist/cli}/assets/providers/shell.svg +0 -0
  194. /package/{src/tui → dist/cli}/assets/providers/unknown.png +0 -0
  195. /package/{src/tui → dist/cli}/assets/providers/unknown.svg +0 -0
@@ -1,323 +0,0 @@
1
- # Usage Limits API
2
-
3
- The Usage Limits API provides subscription usage data for AI coding agent providers (Claude Code, OpenAI Codex, OpenCode Go) via a single unified endpoint.
4
-
5
- ## Endpoint
6
-
7
- ```
8
- GET /api/usage
9
- ```
10
-
11
- Returns current usage data for all configured providers, sourced from the daemon's in-memory cache (refreshed every 5 minutes by default).
12
-
13
- Usage collection is optional. Set `usage.enabled` to `false` in the scrylog config to stop all periodic Claude/Codex/OpenCode Go usage probes while keeping session monitoring enabled:
14
-
15
- ```json
16
- {
17
- "usage": {
18
- "enabled": false
19
- }
20
- }
21
- ```
22
-
23
- When disabled, the daemon does not register the usage collector and does not make external usage requests. This is a normal operational state, not an error.
24
-
25
- ## Response format
26
-
27
- ```typescript
28
- interface UsageState {
29
- providers: UsageProviderEntry[];
30
- lastRefreshedAt: number; // Unix timestamp in milliseconds
31
- }
32
- ```
33
-
34
- ### `UsageProviderEntry`
35
-
36
- All providers share the same response shape. No discriminated union.
37
-
38
- ```typescript
39
- interface UsageProviderEntry {
40
- provider: string; // "claude" | "opencode-go"
41
- accountId: string | null; // stable account identifier for grouping across hosts
42
- available: boolean; // credentials detected on this machine?
43
- status: "ok" | "error" | "unavailable" | "pending";
44
- metadata?: {
45
- dataSource?: string; // "oauth", "web", "cli"
46
- accountEmail?: string | null;
47
- accountOrganization?: string | null;
48
- subscriptionType?: string | null; // "pro", "free"
49
- rateLimitTier?: string | null;
50
- workspaceId?: string | null; // OpenCode Go
51
- workspaceName?: string | null; // OpenCode Go
52
- };
53
- errors?: string[]; // only when status === "error"
54
- windows?: UsageWindow[]; // standardized usage windows
55
- cost?: ProviderCostSnapshot | null; // optional overage/cost data
56
- updatedAt?: string; // ISO 8601
57
- }
58
- ```
59
-
60
- ### `UsageWindow`
61
-
62
- ```typescript
63
- interface UsageWindow {
64
- id: string; // "session", "weekly", "monthly", "designs"
65
- title: string; // "Current Session", "Weekly"
66
- usedPercent: number; // 0-100
67
- used?: number; // absolute usage (requests, tokens, credits)
68
- limit?: number; // absolute limit
69
- windowMinutes?: number; // window duration
70
- resetsAt?: string | null; // ISO 8601 timestamp
71
- resetInSec?: number; // seconds until reset (computed if missing)
72
- }
73
- ```
74
-
75
- ### `ProviderCostSnapshot`
76
-
77
- ```typescript
78
- interface ProviderCostSnapshot {
79
- used: number;
80
- limit: number;
81
- currency: string; // "USD"
82
- period: string; // "Monthly"
83
- resetsAt?: string | null;
84
- updatedAt: string; // ISO 8601
85
- }
86
- ```
87
-
88
- ## Account identification (`accountId`)
89
-
90
- `accountId` is designed to be **stable across hosts** so that a future mobile/web app can group usage by account.
91
-
92
- | Provider | `accountId` strategy | Example |
93
- |---|---|---|
94
- | **Claude** | 1. Email (Web API) → 2. `claude-{subscriptionType}-{rateLimitTier}-{scopesHash}` (Keychain fallback) | `"user@acme.com"` or `"claude-pro-default_claude_ai-0db07b08"` |
95
- | **OpenCode Go** | `workspaceId` from `_server` response | `"wrk_01KQ0X20G68BQG4KV5Q969Z5GV"` |
96
- | **Codex** | 1. `account_id` from `tokens` object → 2. `sub` claim from access token JWT | `"388604f4-d26c-4167-bb85-13616dc80a39"` |
97
-
98
- When `status === "unavailable"` or `status === "error"` with no identity data, `accountId` is `null`.
99
-
100
- ## Status semantics
101
-
102
- | Status | Meaning | `windows` | `errors` | `accountId` |
103
- |---|---|---|---|---|
104
- | `ok` | Data fetched successfully | Present | Absent | Computed from best available source |
105
- | `error` | Credentials found but fetch failed | Absent | Present | From creds metadata if available |
106
- | `unavailable` | No credentials on this machine | Absent | Absent | `null` |
107
- | `pending` | First poll not yet complete | Absent | Absent | `null` |
108
-
109
- ## Example responses
110
-
111
- ### All providers available with data
112
-
113
- ```json
114
- {
115
- "providers": [
116
- {
117
- "provider": "claude",
118
- "accountId": "claude-pro-default_claude_ai-0db07b08",
119
- "available": true,
120
- "status": "ok",
121
- "metadata": {
122
- "dataSource": "oauth",
123
- "accountEmail": null,
124
- "subscriptionType": "pro",
125
- "rateLimitTier": "default_claude_ai"
126
- },
127
- "windows": [
128
- { "id": "session", "title": "Current Session", "usedPercent": 0, "windowMinutes": 300, "resetsAt": "2026-04-30T09:40:00Z", "resetInSec": 11537 },
129
- { "id": "weekly", "title": "Weekly", "usedPercent": 36, "windowMinutes": 10080, "resetsAt": "2026-04-30T17:00:00Z", "resetInSec": 37937 },
130
- { "id": "claude-design", "title": "Designs", "usedPercent": 0, "windowMinutes": 10080, "resetsAt": null }
131
- ],
132
- "cost": null,
133
- "updatedAt": "2026-04-30T06:27:42Z"
134
- },
135
- {
136
- "provider": "opencode-go",
137
- "accountId": "wrk_01KQ0X20G68BQG4KV5Q969Z5GV",
138
- "available": true,
139
- "status": "ok",
140
- "metadata": {
141
- "dataSource": "web",
142
- "workspaceId": "wrk_01KQ0X20G68BQG4KV5Q969Z5GV",
143
- "workspaceName": "Default"
144
- },
145
- "windows": [
146
- { "id": "session", "title": "Rolling", "usedPercent": 16, "resetInSec": 12094, "resetsAt": "2026-04-30T09:49:18Z" },
147
- { "id": "weekly", "title": "Weekly", "usedPercent": 29, "resetInSec": 322336, "resetsAt": "2026-05-04T00:00:00Z" },
148
- { "id": "monthly", "title": "Monthly", "usedPercent": 33, "resetInSec": 2134791, "resetsAt": "2026-05-24T23:27:35Z" }
149
- ],
150
- "updatedAt": "2026-04-30T06:27:44Z"
151
- }
152
- ],
153
- "lastRefreshedAt": 1777530462243
154
- }
155
- ```
156
-
157
- ### Provider unavailable (no credentials)
158
-
159
- ```json
160
- {
161
- "providers": [
162
- {
163
- "provider": "claude",
164
- "accountId": null,
165
- "available": false,
166
- "status": "unavailable"
167
- }
168
- ],
169
- "lastRefreshedAt": 1777528387102
170
- }
171
- ```
172
-
173
- ### Provider with fetch errors
174
-
175
- ```json
176
- {
177
- "providers": [
178
- {
179
- "provider": "claude",
180
- "accountId": null,
181
- "available": true,
182
- "status": "error",
183
- "errors": ["oauth: Server error: 401", "web: Session cookie invalid"]
184
- }
185
- ],
186
- "lastRefreshedAt": 1777528387102
187
- }
188
- ```
189
-
190
- ## Architecture
191
-
192
- ```
193
- DaemonStore
194
- └── usage: UsageState
195
- └── providers: UsageProviderEntry[]
196
-
197
- UsageCollector (polls every 5 min)
198
- ├── ClaudeUsageProvider
199
- │ ├── ClaudeSourcePlanner (decides oauth/web/cli/auto)
200
- │ ├── ClaudeKeychainResolver → AnthropicOAuthUsageClient
201
- │ ├── ClaudeWebResolver → AnthropicWebUsageClient
202
- │ └── ClaudeCliResolver → ClaudeCliProbeClient
203
- │ └── mapClaudeToEntry(oauthCreds) → UsageProviderEntry + accountId
204
- ├── CodexUsageProvider
205
- │ └── CodexAuthResolver → CodexOAuthUsageClient
206
- │ └── mapCodexToEntry() → UsageProviderEntry + accountId
207
- └── OpenCodeGoUsageProvider
208
- ├── OpenCodeGoWebResolver
209
- └── OpenCodeGoUsageClient (resolves workspace id+name)
210
- └── mapOpenCodeGoToEntry() → UsageProviderEntry + accountId
211
- ```
212
-
213
- ## Universal API (React Native / Testing)
214
-
215
- For consumers that already hold credentials (React Native apps, test scripts, external integrations):
216
-
217
- ```typescript
218
- import { UsageApi } from "scrylog/usage";
219
-
220
- // Claude OAuth — token from SecureStore / user input
221
- const entry = await UsageApi.claude.fromOAuthToken({
222
- accessToken: "sk-ant-oat01-...",
223
- });
224
-
225
- // Claude OAuth + Web extras (designs, routines, overage cost)
226
- const entry = await UsageApi.claude.fromOAuthTokenWithWebExtras(
227
- { accessToken: "sk-ant-oat01-..." },
228
- { cookieHeader: "sessionKey=abc; intercom-id=xyz" }
229
- );
230
-
231
- // Claude Web — session cookies from WebView / cookie jar
232
- const entry = await UsageApi.claude.fromWebSession({
233
- cookieHeader: "sessionKey=abc; ...",
234
- });
235
-
236
- // OpenCode Go — cookies + serverFnId
237
- const entry = await UsageApi.opencodeGo.fromCookies({
238
- cookies: "sessionId=abc; csrf=xyz",
239
- serverFnId: "def39973...",
240
- });
241
-
242
- // Codex OAuth — token from ~/.codex/auth.json
243
- const entry = await UsageApi.codex.fromOAuthToken({
244
- accessToken: "eyJhbGciOiJIUzI1NiIs...",
245
- });
246
- ```
247
-
248
- All methods return `Promise<UsageProviderEntry>`. They never throw — on failure they return `status: "error"` with `errors` populated.
249
-
250
- ### Raw clients (advanced)
251
-
252
- If you need the raw provider-specific snapshot before mapping:
253
-
254
- ```typescript
255
- import { AnthropicOAuthUsageClient, CodexOAuthUsageClient } from "scrylog/usage";
256
-
257
- const claudeClient = new AnthropicOAuthUsageClient();
258
- const claudeSnapshot = await claudeClient.fetch({ accessToken: "..." });
259
-
260
- const codexClient = new CodexOAuthUsageClient();
261
- const codexSnapshot = await codexClient.fetch({ accessToken: "...", accountId: "..." });
262
- ```
263
-
264
- These clients use the global `fetch` and have no Node.js or platform dependencies.
265
-
266
- ## Security
267
-
268
- - OAuth tokens and browser cookies are read from OS stores (macOS Keychain, Chrome/Safari cookie databases)
269
- - Credentials are held in memory **only** during the HTTP call
270
- - Never written to disk, never logged, never included in API responses
271
- - Chrome cookie decryption uses AES-128-CBC via Keychain "Chrome Safe Storage"
272
-
273
- ## Configuration
274
-
275
- ```typescript
276
- // scrylog.config.json
277
- {
278
- "usage": {
279
- "enabled": true,
280
- "pollIntervalMs": 300000,
281
- "providers": ["claude", "opencode-go", "codex"],
282
- "claude": {
283
- "dataSource": "auto",
284
- "webExtrasEnabled": true
285
- },
286
- "codex": {
287
- "dataSource": "auto"
288
- },
289
- "opencodeGo": {
290
- "serverFnId": "def39973159c7f0483d8793a822b8dbb10d067e12c65455fcb4608459ba0234f"
291
- }
292
- }
293
- }
294
- ```
295
-
296
- | Field | Default | Description |
297
- |---|---|---|
298
- | `enabled` | `true` | Enable usage collection |
299
- | `pollIntervalMs` | `300000` | Polling interval (5 minutes) |
300
- | `providers` | `["claude", "opencode-go", "codex"]` | Which providers to poll |
301
- | `claude.dataSource` | `"auto"` | `"auto"`, `"oauth"`, `"web"`, or `"cli"` |
302
- | `claude.webExtrasEnabled` | `true` | Enrich OAuth/CLI with Web API extras |
303
- | `codex.dataSource` | `"auto"` | `"auto"` or `"oauth"` |
304
- | `opencodeGo.serverFnId` | hardcoded | Server function ID for workspace resolution |
305
-
306
- ## Platform support
307
-
308
- | Feature | macOS | Linux | WSL | Windows |
309
- |---|---|---|---|---|
310
- | Claude OAuth (Keychain) | ✅ | ❌ | ❌ | ❌ |
311
- | Claude OAuth (`~/.claude/.credentials.json`) | ⚠️ (fallback only) | ✅ | ✅ | ✅ |
312
- | Claude Web (browser cookies) | ✅ (Chrome + Safari) | ✅ (Chrome/Chromium) | ✅ (Chrome/Chromium) | ❌ (Chrome: DPAPI not implemented) |
313
- | Claude CLI (PTY probe) | ✅ (`script` wrapper) | ⚠️ (no PTY wrapper) | ⚠️ (no PTY wrapper) | ⚠️ (no PTY wrapper) |
314
- | Codex OAuth + owner CLI recovery | ✅ | ✅ | ✅ | ⚠️ (CLI launch is experimental) |
315
- | OpenCode Go (browser cookies) | ✅ | ✅ | ✅ | ❌ (Chrome: DPAPI not implemented) |
316
-
317
- ## Adding a new provider
318
-
319
- 1. Create resolver + client in `src/usage/resolvers/` and `src/usage/clients/`
320
- 2. Create provider in `src/usage/providers/<name>.ts` that returns `{ entry: UsageProviderEntry, snapshot: any }`
321
- 3. Add mapper in `src/usage/mappers.ts`: `map<Name>ToEntry(snapshot, errors, available)` — include `accountId` computation
322
- 4. Register in `UsageCollector.poll()`; the provider returns an unavailable entry when credentials are absent
323
- 5. No changes needed to `UsageState`, Zod schemas, or API route
package/src/usage/api.ts DELETED
@@ -1,99 +0,0 @@
1
- import { AnthropicOAuthUsageClient } from "./clients/anthropic-oauth-usage";
2
- import { AnthropicWebUsageClient } from "./clients/anthropic-web-usage";
3
- import { CodexOAuthUsageClient } from "./clients/codex-oauth-usage";
4
- import { OpenCodeGoUsageClient } from "./clients/opencode-go-usage";
5
- import { mapClaudeToEntry, mapCodexToEntry, mapOpenCodeGoToEntry } from "./mappers";
6
- import type { ClaudeOAuthCredentials, ClaudeWebSession, CodexOAuthCredentials, UsageProviderEntry } from "./types";
7
-
8
- export const UsageApi = {
9
- claude: {
10
- async fromOAuthToken(creds: ClaudeOAuthCredentials): Promise<UsageProviderEntry> {
11
- const client = new AnthropicOAuthUsageClient();
12
- try {
13
- const snapshot = await client.fetch(creds);
14
- return mapClaudeToEntry(snapshot, [], true, creds);
15
- } catch (err) {
16
- const message = err instanceof Error ? err.message : String(err);
17
- return mapClaudeToEntry(null, [message], true, creds);
18
- }
19
- },
20
-
21
- async fromWebSession(session: ClaudeWebSession): Promise<UsageProviderEntry> {
22
- const client = new AnthropicWebUsageClient();
23
- try {
24
- const snapshot = await client.fetch(session);
25
- return mapClaudeToEntry(snapshot, [], true, null);
26
- } catch (err) {
27
- const message = err instanceof Error ? err.message : String(err);
28
- return mapClaudeToEntry(null, [message], true, null);
29
- }
30
- },
31
-
32
- async fromOAuthTokenWithWebExtras(
33
- oauthCreds: ClaudeOAuthCredentials,
34
- webSession: ClaudeWebSession,
35
- ): Promise<UsageProviderEntry> {
36
- const oauthClient = new AnthropicOAuthUsageClient();
37
- const webClient = new AnthropicWebUsageClient();
38
-
39
- let snapshot = await oauthClient.fetch(oauthCreds).catch((err: unknown) => {
40
- const message = err instanceof Error ? err.message : String(err);
41
- return null;
42
- });
43
-
44
- if (!snapshot) {
45
- const message = "OAuth fetch failed; cannot retrieve usage data";
46
- return mapClaudeToEntry(null, [message], true, oauthCreds);
47
- }
48
-
49
- // Merge OAuth credential metadata into snapshot for accountId generation
50
- if (snapshot.subscriptionType === null && oauthCreds.subscriptionType) {
51
- snapshot = { ...snapshot, subscriptionType: oauthCreds.subscriptionType };
52
- }
53
- if (snapshot.rateLimitTier === null && oauthCreds.rateLimitTier) {
54
- snapshot = { ...snapshot, rateLimitTier: oauthCreds.rateLimitTier };
55
- }
56
-
57
- // Best-effort web extras (designs, routines, cost)
58
- try {
59
- const webSnapshot = await webClient.fetch(webSession);
60
- if (snapshot.extraRateWindows.length === 0 && webSnapshot.extraRateWindows.length > 0) {
61
- snapshot = { ...snapshot, extraRateWindows: webSnapshot.extraRateWindows };
62
- }
63
- if (!snapshot.providerCost && webSnapshot.providerCost) {
64
- snapshot = { ...snapshot, providerCost: webSnapshot.providerCost };
65
- }
66
- } catch {
67
- // Ignore web extras failures — do not change status to error
68
- }
69
-
70
- return mapClaudeToEntry(snapshot, [], true, oauthCreds);
71
- },
72
- },
73
-
74
- opencodeGo: {
75
- async fromCookies(creds: { cookies: string; serverFnId: string }): Promise<UsageProviderEntry> {
76
- const client = new OpenCodeGoUsageClient();
77
- try {
78
- const snapshot = await client.fetch(creds);
79
- return mapOpenCodeGoToEntry(snapshot, [], true);
80
- } catch (err) {
81
- const message = err instanceof Error ? err.message : String(err);
82
- return mapOpenCodeGoToEntry(null, [message], true);
83
- }
84
- },
85
- },
86
-
87
- codex: {
88
- async fromOAuthToken(creds: CodexOAuthCredentials): Promise<UsageProviderEntry> {
89
- const client = new CodexOAuthUsageClient();
90
- try {
91
- const snapshot = await client.fetch(creds);
92
- return mapCodexToEntry(snapshot, [], true, creds);
93
- } catch (err) {
94
- const message = err instanceof Error ? err.message : String(err);
95
- return mapCodexToEntry(null, [message], true, creds);
96
- }
97
- },
98
- },
99
- };
@@ -1,3 +0,0 @@
1
- export interface UsageClient<T, R> {
2
- fetch(creds: T, options?: { signal?: AbortSignal }): Promise<R>;
3
- }
@@ -1,258 +0,0 @@
1
- import type { UsageClient } from "../client";
2
- import type { ClaudeOAuthCredentials, ClaudeUsageSnapshot, NamedRateWindowLegacy, ProviderCostSnapshotLegacy, RateWindowLegacy } from "../types";
3
- import { classifyUsageError, UsageError } from "../errors";
4
-
5
- export class AuthenticationError extends UsageError {
6
- constructor(message: string) {
7
- super("invalid-credentials", message);
8
- this.name = "AuthenticationError";
9
- }
10
- }
11
-
12
- export class ScopeError extends UsageError {
13
- constructor(message: string) {
14
- super("forbidden", message);
15
- this.name = "ScopeError";
16
- }
17
- }
18
-
19
- export class ParseError extends UsageError {
20
- constructor(message: string) {
21
- super("invalid-response", message);
22
- this.name = "ParseError";
23
- }
24
- }
25
-
26
- export interface OAuthProfileIdentity {
27
- email: string | null;
28
- organizationId: string | null;
29
- }
30
-
31
- function readTrimmedString(record: Record<string, unknown> | undefined | null, keys: string[]): string | null {
32
- if (!record || typeof record !== "object" || Array.isArray(record)) return null;
33
- for (const key of keys) {
34
- const value = record[key];
35
- if (typeof value === "string" && value.trim()) return value.trim();
36
- }
37
- return null;
38
- }
39
-
40
- export function parseOAuthProfile(data: unknown): OAuthProfileIdentity {
41
- if (!data || typeof data !== "object" || Array.isArray(data)) {
42
- return { email: null, organizationId: null };
43
- }
44
- const root = data as Record<string, unknown>;
45
- const account = root["account"] as Record<string, unknown> | null | undefined;
46
- const organization = root["organization"] as Record<string, unknown> | null | undefined;
47
- const email = readTrimmedString(account, ["emailAddress", "email_address", "email"])
48
- ?? readTrimmedString(root, ["emailAddress", "email_address", "email"]);
49
- const organizationId = readTrimmedString(organization, ["uuid"])
50
- ?? readTrimmedString(root, ["organizationUuid", "organization_uuid"]);
51
- return { email, organizationId };
52
- }
53
-
54
- export async function fetchOAuthProfile(
55
- accessToken: string,
56
- options?: { signal?: AbortSignal },
57
- ): Promise<OAuthProfileIdentity> {
58
- let res: Response;
59
- try {
60
- res = await fetch("https://api.anthropic.com/api/oauth/profile", {
61
- signal: options?.signal,
62
- headers: {
63
- Authorization: `Bearer ${accessToken}`,
64
- Accept: "application/json",
65
- "Content-Type": "application/json",
66
- },
67
- });
68
- } catch (error) {
69
- throw classifyUsageError(error);
70
- }
71
- if (res.status === 401) {
72
- throw new AuthenticationError("OAuth profile unauthorized");
73
- }
74
- if (res.status >= 500) {
75
- throw new UsageError("transient", `OAuth profile server error: ${res.status}`);
76
- }
77
- if (!res.ok) {
78
- throw new UsageError("invalid-response", `Unexpected OAuth profile response: ${res.status}`);
79
- }
80
- let data: unknown;
81
- try {
82
- data = await res.json();
83
- } catch {
84
- throw new ParseError("OAuth profile response was not valid JSON");
85
- }
86
- return parseOAuthProfile(data);
87
- }
88
-
89
- interface OAuthUsageResponse {
90
- five_hour?: { utilization?: number | null; resets_at?: string | null } | null;
91
- seven_day?: { utilization?: number | null; resets_at?: string | null } | null;
92
- seven_day_sonnet?: { utilization?: number | null; resets_at?: string | null } | null;
93
- seven_day_opus?: { utilization?: number | null; resets_at?: string | null } | null;
94
- seven_day_design?: { utilization?: number | null; resets_at?: string | null } | null;
95
- seven_day_routines?: { utilization?: number | null; resets_at?: string | null } | null;
96
- extra_usage?: {
97
- is_enabled?: boolean;
98
- monthly_limit?: number;
99
- used_credits?: number;
100
- currency?: string;
101
- utilization?: number;
102
- } | null;
103
- [key: string]: unknown;
104
- }
105
-
106
- const DESIGN_ALTERNATES = [
107
- "seven_day_design",
108
- "seven_day_claude_design",
109
- "claude_design",
110
- "design",
111
- "seven_day_omelette",
112
- "omelette",
113
- "omelette_promotional",
114
- ];
115
-
116
- const ROUTINES_ALTERNATES = [
117
- "seven_day_routines",
118
- "seven_day_claude_routines",
119
- "claude_routines",
120
- "routines",
121
- "routine",
122
- "seven_day_cowork",
123
- "cowork",
124
- ];
125
-
126
- function pickWindow(data: OAuthUsageResponse, keys: string[]): { utilization?: number | null; resets_at?: string | null } | null {
127
- for (const key of keys) {
128
- const val = data[key];
129
- if (val && typeof val === "object" && "utilization" in val) {
130
- return val as { utilization?: number | null; resets_at?: string | null };
131
- }
132
- }
133
- return null;
134
- }
135
-
136
- function toRateWindow(
137
- utilization: number | null | undefined,
138
- resetsAt: string | null | undefined,
139
- windowMinutes: number,
140
- ): RateWindowLegacy {
141
- const usedPercent = typeof utilization === "number" ? Math.max(0, Math.min(100, utilization)) : 0;
142
- const resetsAtDate = resetsAt ? new Date(resetsAt) : null;
143
- const resetDescription = resetsAtDate && !Number.isNaN(resetsAtDate.getTime())
144
- ? resetsAtDate.toLocaleString()
145
- : null;
146
- return {
147
- usedPercent,
148
- windowMinutes,
149
- resetsAt: resetsAtDate && !Number.isNaN(resetsAtDate.getTime()) ? resetsAtDate : null,
150
- resetDescription,
151
- };
152
- }
153
-
154
- export class AnthropicOAuthUsageClient implements UsageClient<ClaudeOAuthCredentials, ClaudeUsageSnapshot> {
155
- async fetch(creds: ClaudeOAuthCredentials, options?: { signal?: AbortSignal }): Promise<ClaudeUsageSnapshot> {
156
- let res: Response;
157
- try {
158
- res = await fetch("https://api.anthropic.com/api/oauth/usage", {
159
- signal: options?.signal,
160
- headers: {
161
- Authorization: `Bearer ${creds.accessToken}`,
162
- Accept: "application/json",
163
- "Content-Type": "application/json",
164
- "anthropic-beta": "oauth-2025-04-20",
165
- },
166
- });
167
- } catch (error) {
168
- throw classifyUsageError(error);
169
- }
170
-
171
- if (res.status === 401) {
172
- throw new AuthenticationError("OAuth token expired or invalid");
173
- }
174
- if (res.status === 403) {
175
- const body = await res.text();
176
- if (body.includes("user:profile")) {
177
- throw new ScopeError("Token missing required scope user:profile");
178
- }
179
- throw new ScopeError(`OAuth access forbidden (${res.status})`);
180
- }
181
- if (res.status === 429) {
182
- const retryAfter = Number(res.headers.get("retry-after"));
183
- throw new UsageError("rate-limited", "OAuth usage request rate limited", Number.isFinite(retryAfter) ? retryAfter * 1000 : undefined);
184
- }
185
- if (res.status >= 500) throw new UsageError("transient", `OAuth usage server error: ${res.status}`);
186
- if (!res.ok) {
187
- throw new UsageError("invalid-response", `Unexpected OAuth usage response: ${res.status}`);
188
- }
189
-
190
- let data: OAuthUsageResponse;
191
- try {
192
- data = (await res.json()) as OAuthUsageResponse;
193
- } catch {
194
- throw new ParseError("OAuth usage response was not valid JSON");
195
- }
196
-
197
- if (!data.five_hour || typeof data.five_hour.utilization !== "number") {
198
- throw new ParseError("Missing five_hour.utilization");
199
- }
200
-
201
- const primary = toRateWindow(data.five_hour.utilization, data.five_hour.resets_at, 300);
202
- const secondary = data.seven_day
203
- ? toRateWindow(data.seven_day.utilization, data.seven_day.resets_at, 10080)
204
- : null;
205
-
206
- const opusWindow = pickWindow(data, ["seven_day_sonnet", "seven_day_opus"]);
207
- const opus = opusWindow ? toRateWindow(opusWindow.utilization, opusWindow.resets_at, 10080) : null;
208
-
209
- const extraRateWindows: NamedRateWindowLegacy[] = [];
210
- const designWindow = pickWindow(data, DESIGN_ALTERNATES);
211
- if (designWindow && typeof designWindow.utilization === "number") {
212
- extraRateWindows.push({
213
- id: "claude-design",
214
- title: "Designs",
215
- window: toRateWindow(designWindow.utilization, designWindow.resets_at, 10080),
216
- });
217
- }
218
- const routinesWindow = pickWindow(data, ROUTINES_ALTERNATES);
219
- if (routinesWindow && typeof routinesWindow.utilization === "number") {
220
- extraRateWindows.push({
221
- id: "claude-routines",
222
- title: "Daily Routines",
223
- window: toRateWindow(routinesWindow.utilization, routinesWindow.resets_at, 10080),
224
- });
225
- }
226
-
227
- let providerCost: ProviderCostSnapshotLegacy | null = null;
228
- if (data.extra_usage?.is_enabled) {
229
- const used = (data.extra_usage.used_credits ?? 0) / 100;
230
- const limit = (data.extra_usage.monthly_limit ?? 0) / 100;
231
- const currency = (data.extra_usage.currency || "USD").trim();
232
- providerCost = {
233
- used,
234
- limit,
235
- currencyCode: currency || "USD",
236
- period: "Monthly",
237
- resetsAt: null,
238
- updatedAt: new Date(),
239
- };
240
- }
241
-
242
- return {
243
- source: "oauth",
244
- primary,
245
- secondary,
246
- opus,
247
- extraRateWindows,
248
- providerCost,
249
- updatedAt: new Date(),
250
- accountEmail: null,
251
- accountOrganization: null,
252
- accountOrganizationId: null,
253
- loginMethod: null,
254
- subscriptionType: creds.subscriptionType ?? null,
255
- rateLimitTier: creds.rateLimitTier ?? null,
256
- };
257
- }
258
- }