axusage 3.1.0 → 3.3.0

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 (88) hide show
  1. package/README.md +78 -194
  2. package/dist/adapters/claude.js +9 -8
  3. package/dist/adapters/coalesce-claude-usage-response.js +5 -7
  4. package/dist/adapters/{chatgpt.d.ts → codex.d.ts} +1 -1
  5. package/dist/adapters/{chatgpt.js → codex.js} +5 -5
  6. package/dist/adapters/copilot.d.ts +7 -0
  7. package/dist/adapters/copilot.js +58 -0
  8. package/dist/adapters/{parse-chatgpt-usage.d.ts → parse-codex-usage.d.ts} +3 -3
  9. package/dist/adapters/parse-copilot-usage.d.ts +15 -0
  10. package/dist/adapters/parse-copilot-usage.js +61 -0
  11. package/dist/cli.js +4 -21
  12. package/dist/commands/auth-setup-command.d.ts +1 -2
  13. package/dist/commands/auth-setup-command.js +44 -67
  14. package/dist/commands/auth-status-command.js +9 -40
  15. package/dist/commands/fetch-service-usage.d.ts +0 -1
  16. package/dist/commands/fetch-service-usage.js +1 -2
  17. package/dist/commands/run-auth-setup.d.ts +0 -10
  18. package/dist/commands/run-auth-setup.js +3 -80
  19. package/dist/commands/usage-command.d.ts +2 -7
  20. package/dist/commands/usage-command.js +7 -39
  21. package/dist/config/credential-sources.d.ts +3 -11
  22. package/dist/config/credential-sources.js +1 -1
  23. package/dist/services/get-service-access-token.d.ts +3 -3
  24. package/dist/services/get-service-access-token.js +11 -11
  25. package/dist/services/service-adapter-registry.d.ts +2 -2
  26. package/dist/services/service-adapter-registry.js +4 -4
  27. package/dist/services/service-diagnostics.d.ts +11 -0
  28. package/dist/services/service-diagnostics.js +29 -0
  29. package/dist/services/supported-service.d.ts +6 -2
  30. package/dist/services/supported-service.js +2 -6
  31. package/dist/types/{chatgpt.d.ts → codex.d.ts} +4 -4
  32. package/dist/types/{chatgpt.js → codex.js} +6 -6
  33. package/dist/types/copilot.d.ts +14 -0
  34. package/dist/types/copilot.js +21 -0
  35. package/dist/utils/check-cli-dependency.d.ts +2 -4
  36. package/dist/utils/check-cli-dependency.js +7 -4
  37. package/dist/utils/copilot-gh-token.d.ts +1 -0
  38. package/dist/utils/copilot-gh-token.js +38 -0
  39. package/dist/utils/format-requires-help-text.d.ts +17 -0
  40. package/dist/utils/format-requires-help-text.js +62 -0
  41. package/dist/utils/validate-root-options.d.ts +0 -3
  42. package/dist/utils/validate-root-options.js +2 -6
  43. package/package.json +15 -19
  44. package/dist/adapters/github-copilot.d.ts +0 -6
  45. package/dist/adapters/github-copilot.js +0 -57
  46. package/dist/adapters/parse-github-copilot-usage.d.ts +0 -23
  47. package/dist/adapters/parse-github-copilot-usage.js +0 -78
  48. package/dist/commands/auth-clear-command.d.ts +0 -7
  49. package/dist/commands/auth-clear-command.js +0 -84
  50. package/dist/commands/fetch-service-usage-with-reauth.d.ts +0 -7
  51. package/dist/commands/fetch-service-usage-with-reauth.js +0 -45
  52. package/dist/services/app-paths.d.ts +0 -9
  53. package/dist/services/app-paths.js +0 -39
  54. package/dist/services/auth-storage-path.d.ts +0 -3
  55. package/dist/services/auth-storage-path.js +0 -7
  56. package/dist/services/auth-timeouts.d.ts +0 -4
  57. package/dist/services/auth-timeouts.js +0 -4
  58. package/dist/services/browser-auth-manager.d.ts +0 -49
  59. package/dist/services/browser-auth-manager.js +0 -113
  60. package/dist/services/create-auth-context.d.ts +0 -8
  61. package/dist/services/create-auth-context.js +0 -35
  62. package/dist/services/do-setup-auth.d.ts +0 -3
  63. package/dist/services/do-setup-auth.js +0 -19
  64. package/dist/services/fetch-json-with-context.d.ts +0 -5
  65. package/dist/services/fetch-json-with-context.js +0 -37
  66. package/dist/services/launch-chromium.d.ts +0 -6
  67. package/dist/services/launch-chromium.js +0 -20
  68. package/dist/services/persist-storage-state.d.ts +0 -6
  69. package/dist/services/persist-storage-state.js +0 -16
  70. package/dist/services/request-service.d.ts +0 -3
  71. package/dist/services/request-service.js +0 -4
  72. package/dist/services/service-auth-configs.d.ts +0 -15
  73. package/dist/services/service-auth-configs.js +0 -26
  74. package/dist/services/setup-auth-flow.d.ts +0 -3
  75. package/dist/services/setup-auth-flow.js +0 -67
  76. package/dist/services/shared-browser-auth-manager.d.ts +0 -4
  77. package/dist/services/shared-browser-auth-manager.js +0 -87
  78. package/dist/services/verify-session.d.ts +0 -2
  79. package/dist/services/verify-session.js +0 -27
  80. package/dist/services/wait-for-login.d.ts +0 -6
  81. package/dist/services/wait-for-login.js +0 -115
  82. package/dist/types/github-copilot.d.ts +0 -21
  83. package/dist/types/github-copilot.js +0 -27
  84. package/dist/utils/resolve-prompt-capability.d.ts +0 -1
  85. package/dist/utils/resolve-prompt-capability.js +0 -3
  86. package/dist/utils/write-atomic-json.d.ts +0 -1
  87. package/dist/utils/write-atomic-json.js +0 -56
  88. /package/dist/adapters/{parse-chatgpt-usage.js → parse-codex-usage.js} +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # axusage
2
2
 
3
- Monitor AI usage across Claude, ChatGPT, GitHub Copilot, and Gemini from a single command.
3
+ Monitor API usage across Claude, ChatGPT, GitHub Copilot, and Gemini from a single CLI.
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -8,127 +8,72 @@ Monitor AI usage across Claude, ChatGPT, GitHub Copilot, and Gemini from a singl
8
8
  # Install globally
9
9
  npm install -g axusage
10
10
 
11
- # Set up authentication (one-time setup per service)
11
+ # One-time authentication per provider
12
12
  claude
13
13
  codex
14
14
  gemini
15
- axusage --auth-setup github-copilot --interactive
15
+ gh auth login
16
16
 
17
- # Check authentication status
17
+ # Optional: print service-specific auth instructions
18
+ axusage --auth-setup copilot
19
+
20
+ # Check auth status
18
21
  axusage --auth-status
19
- # For CLI-auth services, this reports CLI availability; use the CLI to confirm login.
20
22
 
21
- # Fetch usage
23
+ # Fetch usage for all services
22
24
  axusage
23
25
  ```
24
26
 
25
27
  ## Requirements
26
28
 
27
- - `claude` CLI (Claude auth) `npm install -g @anthropic-ai/claude-code`
28
- - `codex` CLI (ChatGPT auth) `npm install -g @openai/codex`
29
- - `gemini` CLI (Gemini auth) `npm install -g @google/gemini-cli`
30
- - Playwright Chromium for GitHub Copilot auth (see "Browser installation" below for global install steps)
29
+ - `claude` CLI (Claude auth) - `npm install -g @anthropic-ai/claude-code`
30
+ - `codex` CLI (ChatGPT auth) - `npm install -g @openai/codex`
31
+ - `gemini` CLI (Gemini auth) - `npm install -g @google/gemini-cli`
32
+ - `gh` CLI (GitHub Copilot auth) - `https://cli.github.com/` or `brew install gh`
31
33
 
32
34
  ### Custom Paths
33
35
 
34
- If the CLIs are not on your `PATH`, override the binaries:
36
+ If CLIs are not on your `PATH`, override binary paths:
35
37
 
36
38
  ```bash
37
39
  export AXUSAGE_CLAUDE_PATH=/path/to/claude
38
40
  export AXUSAGE_CODEX_PATH=/path/to/codex
39
41
  export AXUSAGE_GEMINI_PATH=/path/to/gemini
42
+ export AXUSAGE_GH_PATH=/path/to/gh
40
43
 
41
- # Optional: adjust CLI dependency check timeout (milliseconds)
44
+ # Optional: dependency check timeout (milliseconds, default: 5000)
42
45
  export AXUSAGE_CLI_TIMEOUT_MS=5000
43
46
  ```
44
47
 
45
- For Playwright-managed browsers:
46
-
47
- ```bash
48
- export PLAYWRIGHT_BROWSERS_PATH=/path/to/playwright-browsers
49
- ```
50
-
51
- ### Exit Codes
52
-
53
- - `0`: success
54
- - `1`: errors, including partial failures
48
+ `AXUSAGE_GH_PATH` is also used as a Copilot fallback when resolving a token
49
+ via `gh auth token`.
55
50
 
56
51
  ## Authentication
57
52
 
58
- Claude, ChatGPT, and Gemini use their respective CLI OAuth sessions. GitHub
59
- Copilot uses browser-based authentication for persistent, long-lived sessions.
60
-
61
- **Setup (one-time per service):**
62
-
63
- ```bash
64
- # Set up authentication for each service
65
- claude
66
- codex
67
- gemini
68
- axusage --auth-setup github-copilot --interactive
69
-
70
- # Check authentication status
71
- axusage --auth-status
72
- ```
73
-
74
- `--auth-status` reports browser-auth status for GitHub Copilot and CLI availability
75
- for Claude/ChatGPT/Gemini. Use `claude`, `codex`, or `gemini` to confirm CLI login.
76
-
77
- When you run `axusage --auth-setup github-copilot --interactive`, a browser window will open.
78
- Simply log in to GitHub as you normally would. Your authentication will be
79
- saved and automatically used for future requests.
80
-
81
- **Authenticated sessions directory (via [`env-paths`](https://github.com/sindresorhus/env-paths)):**
82
-
83
- - Linux: `~/.local/share/axusage/browser-contexts/` (or `$XDG_DATA_HOME/axusage/browser-contexts/`)
84
- - macOS: `~/Library/Application Support/axusage/browser-contexts/`
85
- - Windows: `%LOCALAPPDATA%\axusage\Data\browser-contexts\`
53
+ Authentication is managed by provider CLIs for all services.
86
54
 
87
- You can override the location by providing `BrowserAuthConfig.dataDir`, but the CLI defaults to these platform-appropriate directories.
55
+ - Claude: `claude`
56
+ - ChatGPT: `codex`
57
+ - Gemini: `gemini`
58
+ - GitHub Copilot: `gh auth login`
88
59
 
89
- > **Migration from `agent-usage`:** If upgrading from the old `agent-usage` package, copy your authentication contexts:
90
- >
91
- > - Linux/macOS: `mkdir -p ~/.local/share/axusage/ && cp -r ~/.local/share/agent-usage/* ~/.local/share/axusage/`
92
- > - Windows: Copy from `%LOCALAPPDATA%\agent-usage\` to `%LOCALAPPDATA%\axusage\`
93
-
94
- Security notes:
95
-
96
- - Files in this directory contain sensitive session data. They are created with owner-only permissions (0600 for files, 0700 for the directory) where possible.
97
- - To revoke access for GitHub Copilot, clear saved browser auth:
98
-
99
- ```bash
100
- axusage --auth-clear github-copilot --interactive
101
- ```
102
-
103
- This moves the saved browser files to your system Trash/Recycle Bin (recoverable).
104
-
105
- Use `--force` to skip confirmation in scripts.
106
-
107
- Browser installation:
108
-
109
- - Playwright Chromium is installed automatically on `pnpm install` via a postinstall script. If this fails in your environment, install manually:
60
+ Use:
110
61
 
111
62
  ```bash
112
- pnpm exec playwright install chromium --with-deps
63
+ axusage --auth-setup <service>
113
64
  ```
114
65
 
115
- **Global installation with pnpm:**
66
+ to print the correct command for a given service.
116
67
 
117
- pnpm blocks postinstall scripts for global packages by default (npm runs them automatically). After installing globally, approve and run the postinstall script:
68
+ Check auth status with:
118
69
 
119
70
  ```bash
120
- pnpm add -g axusage
121
- pnpm approve-builds -g # Select axusage when prompted
122
- pnpm add -g axusage # Reinstall to run postinstall
71
+ axusage --auth-status
72
+ axusage --auth-status claude
123
73
  ```
124
74
 
125
- Alternatively, install the browser manually after global installation. Use the Playwright binary that ships with the global package so the browser is installed in the right location:
126
-
127
- ```bash
128
- pnpm add -g axusage
129
- PLAYWRIGHT_BIN="$(pnpm root -g)/axusage/node_modules/.bin/playwright"
130
- "$PLAYWRIGHT_BIN" install chromium --with-deps
131
- ```
75
+ `--auth-status` checks whether each service is authenticated and reports the
76
+ detected auth method. It does not validate API token freshness.
132
77
 
133
78
  ## Usage
134
79
 
@@ -136,165 +81,104 @@ PLAYWRIGHT_BIN="$(pnpm root -g)/axusage/node_modules/.bin/playwright"
136
81
  # Query all services
137
82
  axusage
138
83
 
139
- # Allow interactive re-authentication during usage fetch
140
- axusage --interactive
141
-
142
- # Single service
84
+ # Query a single service
143
85
  axusage --service claude
144
- axusage --service chatgpt
145
- axusage --service github-copilot
86
+ axusage -s codex
87
+ axusage -s copilot
146
88
 
147
- # JSON output
148
- axusage --format=json
149
- axusage --service claude --format=json
89
+ # Output formats
90
+ axusage --format text
91
+ axusage --format tsv
92
+ axusage --format json
93
+ axusage --format prometheus
150
94
 
151
- # TSV output (parseable with cut, awk, sort)
152
- axusage --format=tsv
95
+ # Auth utilities
96
+ axusage --auth-setup claude
97
+ axusage --auth-status
153
98
 
154
99
  # Disable color output
155
100
  axusage --no-color
156
101
  ```
157
102
 
103
+ ### Exit Codes
104
+
105
+ - `0`: Success
106
+ - `1`: One or more failures (including partial failures)
107
+
108
+ ## Credential Sources
109
+
110
+ Credential source config is read from:
111
+
112
+ - Config file path shown in `axusage --help`
113
+ - `AXUSAGE_SOURCES` environment variable (JSON), which overrides file config
114
+
158
115
  ## Examples
159
116
 
160
117
  ### Extract service and utilization (TSV + awk)
161
118
 
162
119
  ```bash
163
- axusage --format=tsv | tail -n +2 | awk -F'\t' '{print $1, $4"%"}'
120
+ axusage --format tsv | tail -n +2 | awk -F'\t' '{print $1, $4"%"}'
164
121
  ```
165
122
 
166
123
  ### Count windows by service (TSV + cut/sort/uniq)
167
124
 
168
125
  ```bash
169
- axusage --format=tsv | tail -n +2 | cut -f1 | sort | uniq -c
126
+ axusage --format tsv | tail -n +2 | cut -f1 | sort | uniq -c
170
127
  ```
171
128
 
172
129
  ### Filter by utilization threshold (TSV + awk)
173
130
 
174
131
  ```bash
175
- axusage --format=tsv | tail -n +2 | awk -F'\t' '$4 > 50 {print $1, $3, $4"%"}'
132
+ axusage --format tsv | tail -n +2 | awk -F'\t' '$4 > 50 {print $1, $3, $4"%"}'
176
133
  ```
177
134
 
178
135
  ### Extract utilization as JSON (JSON + jq)
179
136
 
180
137
  ```bash
181
- axusage --format=json \
138
+ axusage --format json \
182
139
  | jq -r '(.results? // .) | (if type=="array" then . else [.] end) | .[] | .windows[] | [.name, (.utilization|tostring)] | @tsv'
183
140
  ```
184
141
 
185
142
  ## Output
186
143
 
187
- Human-readable format shows:
144
+ Human-readable output includes:
188
145
 
189
- - Utilization percentage per window (5-hour, 7-day, monthly)
146
+ - Utilization percentage per window
190
147
  - Usage rate vs expected rate
191
148
  - Reset times
192
- - Color coding: 🟢 on track | 🟡 over budget | 🔴 significantly over
193
-
194
- JSON format returns structured data for programmatic use.
195
-
196
- ## Agent Rule
149
+ - Color coding: on track / over budget / significantly over
197
150
 
198
- Add to your `CLAUDE.md` or `AGENTS.md`:
199
-
200
- ```markdown
201
- # Rule: `axusage` Usage
202
-
203
- Run `npx -y axusage --help` to learn available options.
204
-
205
- Use `axusage` when you need a quick, scriptable snapshot of API usage across Claude, ChatGPT, GitHub Copilot, and Gemini. It standardizes output (text, JSON, Prometheus) so you can alert, dashboard, or pipe it into other Unix tools.
206
- ```
151
+ JSON output provides structured data for automation.
152
+ Prometheus output emits text metrics suitable for scraping.
207
153
 
208
154
  ## Troubleshooting
209
155
 
210
- ### Authentication setup hangs
211
-
212
- - The CLI shows a countdown while waiting for login.
213
- - If you have completed login, press Enter in the terminal to continue.
214
- - If it still fails, run `axusage --auth-clear <service> --interactive` and retry.
215
-
216
- ### "No saved authentication" error
217
-
218
- - Check which services are authenticated: `axusage --auth-status`.
219
- - For GitHub Copilot, set up the missing service: `axusage --auth-setup <service> --interactive`.
220
- - For Claude/ChatGPT/Gemini, run the provider CLI to authenticate.
221
-
222
- ### Sessions expire
223
-
224
- - Browser sessions can expire based on provider policy. Re-run `axusage --auth-setup <service> --interactive` for the affected service when you see authentication errors.
156
+ ### "Required dependency '... not found'"
225
157
 
226
- ## Remote authentication and Prometheus export
158
+ Install the missing CLI or set the corresponding override env var (for example, `AXUSAGE_GH_PATH`).
227
159
 
228
- You can perform the interactive login flow on a workstation (for example, a local macOS laptop) and reuse the resulting browser session on a headless Linux server that collects usage and exports it for Prometheus.
160
+ ### Authentication errors (401 / unauthorized / no saved authentication)
229
161
 
230
- ### 1. Authenticate on a workstation
162
+ 1. Run `axusage --auth-status` to see which services are not authenticated.
163
+ 2. Re-authenticate in the provider CLI (`claude`, `codex`, `gemini`, `gh auth login`).
164
+ 3. Retry `axusage`.
231
165
 
232
- 1. Install globally and authenticate the CLIs you need, then set up browser
233
- auth for GitHub Copilot:
166
+ ### Partial failures
234
167
 
235
- ```bash
236
- npm install -g axusage
168
+ `axusage` exits with code `1` if any service fails, even when other services succeed. Check warnings in stderr for the failed service(s).
237
169
 
238
- claude
239
- codex
240
- gemini
241
- axusage --auth-setup github-copilot --interactive
242
- ```
243
-
244
- 2. Confirm the workstation has valid sessions:
245
-
246
- ```bash
247
- axusage --auth-status
248
- ```
249
-
250
- For CLI-auth services, run `claude`, `codex`, or `gemini` to confirm login.
251
-
252
- 3. Package the saved contexts so they can be transferred. Set `CONTEXT_DIR` to the path for your platform (see the table above):
253
-
254
- ```bash
255
- CONTEXT_DIR="$HOME/.local/share/axusage/browser-contexts" # Linux default; adjust on macOS/Windows
256
- tar czf axusage-contexts.tgz -C "$(dirname "$CONTEXT_DIR")" "$(basename "$CONTEXT_DIR")"
257
- ```
258
-
259
- Archive structure: `browser-contexts/github-copilot-auth.json` plus matching
260
- `github-copilot-auth.meta.json` (CLI-auth services do not use browser contexts).
261
-
262
- ### 2. Transfer the browser contexts to the Linux server
263
-
264
- 1. Copy the archive to the server with `scp` (replace `user@server` with your login):
265
-
266
- ```bash
267
- scp axusage-contexts.tgz user@server:~/
268
- ```
269
-
270
- 2. On the server, create the target directory if it does not already exist, unpack the archive, and lock down the permissions:
271
-
272
- ```bash
273
- ssh user@server
274
- CONTEXT_DIR="$HOME/.local/share/axusage/browser-contexts" # Linux default; adjust per platform
275
- AXUSAGE_DIR="$(dirname "$CONTEXT_DIR")"
276
- mkdir -p "$CONTEXT_DIR"
277
- tar xzf ~/axusage-contexts.tgz -C "$AXUSAGE_DIR"
278
- # Directories 700, files 600
279
- find "$AXUSAGE_DIR" -type d -exec chmod 700 {} +
280
- find "$CONTEXT_DIR" -type f -exec chmod 600 {} +
281
- ```
282
-
283
- 3. Verify that the sessions are available on the server:
170
+ ## Agent Rule
284
171
 
285
- ```bash
286
- axusage --auth-status
287
- ```
172
+ Add to your `CLAUDE.md` or `AGENTS.md`:
288
173
 
289
- If the server does not yet have the tool installed, run `npm install -g axusage` before checking the status.
174
+ ```markdown
175
+ # Rule: `axusage` Usage
290
176
 
291
- Notes:
177
+ Run `npx -y axusage --help` to learn available options.
292
178
 
293
- - Use `--service <name>` to restrict services.
294
- - Sessions may expire or become invalid if you change your password or log out of the service in another browser. Re-run `axusage --auth-setup <service> --interactive` as needed.
295
- - If you transfer browser contexts between machines, ensure the target system is secure and permissions are restricted to the intended user.
296
- - The CLI stores authentication data in the platform-specific directories listed above; protect that directory to prevent unauthorized access.
179
+ Use `axusage` when you need a quick, scriptable snapshot of API usage across Claude, ChatGPT, GitHub Copilot, and Gemini. It standardizes output (text, JSON, Prometheus) so you can alert, dashboard, or pipe it into other Unix tools.
180
+ ```
297
181
 
298
182
  ## Development
299
183
 
300
- For local development in this repository, `pnpm run start` triggers a clean rebuild before executing the CLI. Use `pnpm run usage` only when `dist/` is already up to date. End users installing globally should run the `axusage` binary directly.
184
+ For local development in this repository, `pnpm run start` triggers a clean rebuild before executing the CLI. Use `node bin/axusage` only when `dist/` is already up to date. End users installing globally should run the `axusage` binary directly.
@@ -51,12 +51,15 @@ export const claudeAdapter = {
51
51
  };
52
52
  }
53
53
  try {
54
- const response = await fetch(USAGE_API_URL, {
55
- headers: {
56
- Authorization: `Bearer ${accessToken}`,
57
- "anthropic-beta": ANTHROPIC_BETA_HEADER,
58
- },
59
- });
54
+ const [response, planType] = await Promise.all([
55
+ fetch(USAGE_API_URL, {
56
+ headers: {
57
+ Authorization: `Bearer ${accessToken}`,
58
+ "anthropic-beta": ANTHROPIC_BETA_HEADER,
59
+ },
60
+ }),
61
+ fetchPlanType(accessToken),
62
+ ]);
60
63
  if (!response.ok) {
61
64
  const errorText = await response.text().catch(() => "");
62
65
  return {
@@ -76,8 +79,6 @@ export const claudeAdapter = {
76
79
  error: new ApiError(`Invalid response format: ${parseResult.error.message}`, undefined, data),
77
80
  };
78
81
  }
79
- // Fetch plan type (best effort, don't fail if unavailable)
80
- const planType = await fetchPlanType(accessToken);
81
82
  const usageData = toServiceUsageData(parseResult.data);
82
83
  return {
83
84
  ok: true,
@@ -47,12 +47,15 @@ const resolveUtilization = (candidate) => {
47
47
  return candidate.utilization;
48
48
  return candidate.percentage ?? candidate.percent ?? 0;
49
49
  };
50
- const selectMetric = (candidates, matchers) => {
50
+ const selectMetric = (candidates, matchers, exclude = []) => {
51
51
  for (const candidate of candidates) {
52
52
  const label = normalizeLabel(candidate);
53
53
  if (!label)
54
54
  continue;
55
55
  const tokens = tokenizeLabel(label);
56
+ const hasExcludedToken = exclude.some((ex) => tokens.has(ex) || label.includes(ex));
57
+ if (hasExcludedToken)
58
+ continue;
56
59
  const matches = matchers.includes(label) ||
57
60
  matchers.some((matcher) => tokens.has(matcher));
58
61
  if (!matches)
@@ -88,12 +91,7 @@ export function coalesceClaudeUsageResponse(data) {
88
91
  "5",
89
92
  "5hour",
90
93
  ]);
91
- const sevenDay = selectMetric(candidates, [
92
- "seven_day",
93
- "seven",
94
- "7",
95
- "week",
96
- ]);
94
+ const sevenDay = selectMetric(candidates, ["seven_day", "seven", "7", "week"], ["opus", "sonnet", "oauth"]);
97
95
  const sevenDayOpus = selectMetric(candidates, ["seven_day_opus", "opus"]);
98
96
  const sevenDaySonnet = selectMetric(candidates, [
99
97
  "seven_day_sonnet",
@@ -5,4 +5,4 @@ import type { ServiceAdapter } from "../types/domain.js";
5
5
  * Uses the OAuth token from Codex CLI's credential store (~/.codex/auth.json)
6
6
  * to make direct API calls to ChatGPT's usage endpoint.
7
7
  */
8
- export declare const chatGPTAdapter: ServiceAdapter;
8
+ export declare const codexAdapter: ServiceAdapter;
@@ -1,7 +1,7 @@
1
1
  import { ApiError } from "../types/domain.js";
2
2
  import { getServiceAccessToken } from "../services/get-service-access-token.js";
3
- import { ChatGPTUsageResponse as ChatGPTUsageResponseSchema } from "../types/chatgpt.js";
4
- import { toServiceUsageData } from "./parse-chatgpt-usage.js";
3
+ import { CodexUsageResponse as CodexUsageResponseSchema } from "../types/codex.js";
4
+ import { toServiceUsageData } from "./parse-codex-usage.js";
5
5
  const API_URL = "https://chatgpt.com/backend-api/wham/usage";
6
6
  /**
7
7
  * ChatGPT service adapter using direct API access.
@@ -9,10 +9,10 @@ const API_URL = "https://chatgpt.com/backend-api/wham/usage";
9
9
  * Uses the OAuth token from Codex CLI's credential store (~/.codex/auth.json)
10
10
  * to make direct API calls to ChatGPT's usage endpoint.
11
11
  */
12
- export const chatGPTAdapter = {
12
+ export const codexAdapter = {
13
13
  name: "ChatGPT",
14
14
  async fetchUsage() {
15
- const accessToken = await getServiceAccessToken("chatgpt");
15
+ const accessToken = await getServiceAccessToken("codex");
16
16
  if (!accessToken) {
17
17
  return {
18
18
  ok: false,
@@ -33,7 +33,7 @@ export const chatGPTAdapter = {
33
33
  };
34
34
  }
35
35
  const data = await response.json();
36
- const parseResult = ChatGPTUsageResponseSchema.safeParse(data);
36
+ const parseResult = CodexUsageResponseSchema.safeParse(data);
37
37
  if (!parseResult.success) {
38
38
  return {
39
39
  ok: false,
@@ -0,0 +1,7 @@
1
+ import type { ServiceAdapter } from "../types/domain.js";
2
+ /**
3
+ * GitHub Copilot service adapter using token-based API access.
4
+ *
5
+ * Credentials resolved via getServiceAccessToken (vault, local axauth, gh CLI).
6
+ */
7
+ export declare const copilotAdapter: ServiceAdapter;
@@ -0,0 +1,58 @@
1
+ import { ApiError } from "../types/domain.js";
2
+ import { CopilotUsageResponse as CopilotUsageResponseSchema } from "../types/copilot.js";
3
+ import { toServiceUsageData } from "./parse-copilot-usage.js";
4
+ import { getServiceAccessToken } from "../services/get-service-access-token.js";
5
+ // Internal/undocumented GitHub API used by VS Code, JetBrains, and other
6
+ // first-party Copilot integrations. May change without notice.
7
+ const API_URL = "https://api.github.com/copilot_internal/user";
8
+ /**
9
+ * GitHub Copilot service adapter using token-based API access.
10
+ *
11
+ * Credentials resolved via getServiceAccessToken (vault, local axauth, gh CLI).
12
+ */
13
+ export const copilotAdapter = {
14
+ name: "GitHub Copilot",
15
+ async fetchUsage() {
16
+ const accessToken = await getServiceAccessToken("copilot");
17
+ if (!accessToken) {
18
+ return {
19
+ ok: false,
20
+ error: new ApiError("No GitHub Copilot credentials found. Run 'gh auth login' to authenticate."),
21
+ };
22
+ }
23
+ try {
24
+ const response = await fetch(API_URL, {
25
+ headers: {
26
+ Authorization: `Bearer ${accessToken}`,
27
+ Accept: "application/json",
28
+ },
29
+ });
30
+ if (!response.ok) {
31
+ const errorText = await response.text().catch(() => "");
32
+ return {
33
+ ok: false,
34
+ error: new ApiError(`GitHub Copilot API request failed: ${String(response.status)} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`, response.status),
35
+ };
36
+ }
37
+ const data = await response.json();
38
+ const parseResult = CopilotUsageResponseSchema.safeParse(data);
39
+ if (!parseResult.success) {
40
+ return {
41
+ ok: false,
42
+ error: new ApiError(`Invalid response format: ${parseResult.error.message}`, undefined, data),
43
+ };
44
+ }
45
+ return {
46
+ ok: true,
47
+ value: toServiceUsageData(parseResult.data),
48
+ };
49
+ }
50
+ catch (error) {
51
+ const message = error instanceof Error ? error.message : String(error);
52
+ return {
53
+ ok: false,
54
+ error: new ApiError(`Failed to fetch GitHub Copilot usage: ${message}`),
55
+ };
56
+ }
57
+ },
58
+ };
@@ -1,9 +1,9 @@
1
1
  import type { ServiceUsageData } from "../types/domain.js";
2
- import type { ChatGPTUsageResponse, ChatGPTRateLimitWindow } from "../types/chatgpt.js";
2
+ import type { CodexUsageResponse, CodexRateLimitWindow } from "../types/codex.js";
3
3
  /**
4
4
  * Converts a ChatGPT rate limit window to common usage window
5
5
  */
6
- export declare function toUsageWindow(name: string, window: ChatGPTRateLimitWindow): {
6
+ export declare function toUsageWindow(name: string, window: CodexRateLimitWindow): {
7
7
  name: string;
8
8
  utilization: number;
9
9
  resetsAt: Date;
@@ -12,4 +12,4 @@ export declare function toUsageWindow(name: string, window: ChatGPTRateLimitWind
12
12
  /**
13
13
  * Converts ChatGPT response to common domain model
14
14
  */
15
- export declare function toServiceUsageData(response: ChatGPTUsageResponse): ServiceUsageData;
15
+ export declare function toServiceUsageData(response: CodexUsageResponse): ServiceUsageData;
@@ -0,0 +1,15 @@
1
+ import type { ServiceUsageData } from "../types/domain.js";
2
+ import type { CopilotUsageResponse } from "../types/copilot.js";
3
+ /**
4
+ * Calculates monthly period duration ending at the reset date.
5
+ *
6
+ * Determines the period start by going back one month from the reset
7
+ * date and clamping the day to the last valid day of that previous
8
+ * month. This handles edge cases like Jan 31 → Feb 28/29 where the
9
+ * target month has fewer days than the reset date's day component.
10
+ */
11
+ export declare function calculatePeriodDuration(resetDate: Date): number;
12
+ /**
13
+ * Converts GitHub Copilot API response to common domain model.
14
+ */
15
+ export declare function toServiceUsageData(response: CopilotUsageResponse): ServiceUsageData;
@@ -0,0 +1,61 @@
1
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
2
+ /**
3
+ * Calculates monthly period duration ending at the reset date.
4
+ *
5
+ * Determines the period start by going back one month from the reset
6
+ * date and clamping the day to the last valid day of that previous
7
+ * month. This handles edge cases like Jan 31 → Feb 28/29 where the
8
+ * target month has fewer days than the reset date's day component.
9
+ */
10
+ export function calculatePeriodDuration(resetDate) {
11
+ const periodEnd = resetDate.getTime();
12
+ const year = resetDate.getUTCFullYear();
13
+ const month = resetDate.getUTCMonth();
14
+ const day = resetDate.getUTCDate();
15
+ const firstOfCurrentMonth = Date.UTC(year, month, 1, 0, 0, 0);
16
+ const lastPreviousMonthDate = new Date(firstOfCurrentMonth - MS_PER_DAY);
17
+ const lastPreviousMonthDay = lastPreviousMonthDate.getUTCDate();
18
+ const previousMonth = lastPreviousMonthDate.getUTCMonth();
19
+ const previousYear = lastPreviousMonthDate.getUTCFullYear();
20
+ const targetDay = Math.min(day, lastPreviousMonthDay);
21
+ const periodStart = Date.UTC(previousYear, previousMonth, targetDay, resetDate.getUTCHours(), resetDate.getUTCMinutes(), resetDate.getUTCSeconds(), resetDate.getUTCMilliseconds());
22
+ return Math.max(periodEnd - periodStart, 0);
23
+ }
24
+ /**
25
+ * Converts GitHub Copilot API response to common domain model.
26
+ */
27
+ export function toServiceUsageData(response) {
28
+ const { premium_interactions } = response.quota_snapshots;
29
+ if (premium_interactions.unlimited) {
30
+ return {
31
+ service: "GitHub Copilot",
32
+ planType: response.copilot_plan,
33
+ windows: [
34
+ {
35
+ name: "Monthly Premium Interactions",
36
+ utilization: 0,
37
+ resetsAt: undefined,
38
+ periodDurationMs: 0,
39
+ },
40
+ ],
41
+ };
42
+ }
43
+ const resetDate = new Date(response.quota_reset_date_utc);
44
+ const periodDurationMs = calculatePeriodDuration(resetDate);
45
+ const used = premium_interactions.entitlement - premium_interactions.remaining;
46
+ const utilization = premium_interactions.entitlement === 0
47
+ ? 0
48
+ : (used / premium_interactions.entitlement) * 100;
49
+ return {
50
+ service: "GitHub Copilot",
51
+ planType: response.copilot_plan,
52
+ windows: [
53
+ {
54
+ name: "Monthly Premium Interactions",
55
+ utilization: Math.round(utilization * 100) / 100,
56
+ resetsAt: resetDate,
57
+ periodDurationMs,
58
+ },
59
+ ],
60
+ };
61
+ }