axusage 2.2.0 → 3.0.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.
- package/README.md +61 -15
- package/dist/adapters/github-copilot.js +2 -1
- package/dist/cli.js +57 -41
- package/dist/commands/auth-clear-command.d.ts +2 -0
- package/dist/commands/auth-clear-command.js +62 -3
- package/dist/commands/auth-setup-command.d.ts +1 -0
- package/dist/commands/auth-setup-command.js +36 -5
- package/dist/commands/auth-status-command.js +36 -4
- package/dist/commands/fetch-service-usage-with-reauth.js +2 -2
- package/dist/commands/fetch-service-usage.js +4 -2
- package/dist/commands/run-auth-setup.js +26 -5
- package/dist/commands/usage-command.js +5 -3
- package/dist/config/credential-sources.js +22 -0
- package/dist/services/create-auth-context.js +2 -1
- package/dist/services/do-setup-auth.js +2 -8
- package/dist/services/persist-storage-state.d.ts +1 -1
- package/dist/services/persist-storage-state.js +8 -8
- package/dist/services/setup-auth-flow.js +38 -11
- package/dist/services/supported-service.js +4 -2
- package/dist/services/wait-for-login.d.ts +3 -2
- package/dist/services/wait-for-login.js +89 -18
- package/dist/utils/check-cli-dependency.d.ts +25 -0
- package/dist/utils/check-cli-dependency.js +81 -0
- package/dist/utils/color.d.ts +5 -0
- package/dist/utils/color.js +27 -0
- package/dist/utils/format-service-usage.js +1 -1
- package/dist/utils/resolve-prompt-capability.d.ts +1 -0
- package/dist/utils/resolve-prompt-capability.js +3 -0
- package/dist/utils/validate-root-options.d.ts +11 -0
- package/dist/utils/validate-root-options.js +18 -0
- package/dist/utils/write-atomic-json.d.ts +1 -0
- package/dist/utils/write-atomic-json.js +56 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -12,15 +12,47 @@ npm install -g axusage
|
|
|
12
12
|
claude
|
|
13
13
|
codex
|
|
14
14
|
gemini
|
|
15
|
-
axusage auth
|
|
15
|
+
axusage --auth-setup github-copilot --interactive
|
|
16
16
|
|
|
17
17
|
# Check authentication status
|
|
18
|
-
axusage auth
|
|
18
|
+
axusage --auth-status
|
|
19
|
+
# For CLI-auth services, this reports CLI availability; use the CLI to confirm login.
|
|
19
20
|
|
|
20
21
|
# Fetch usage
|
|
21
22
|
axusage
|
|
22
23
|
```
|
|
23
24
|
|
|
25
|
+
## Requirements
|
|
26
|
+
|
|
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)
|
|
31
|
+
|
|
32
|
+
### Custom Paths
|
|
33
|
+
|
|
34
|
+
If the CLIs are not on your `PATH`, override the binaries:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
export AXUSAGE_CLAUDE_PATH=/path/to/claude
|
|
38
|
+
export AXUSAGE_CODEX_PATH=/path/to/codex
|
|
39
|
+
export AXUSAGE_GEMINI_PATH=/path/to/gemini
|
|
40
|
+
|
|
41
|
+
# Optional: adjust CLI dependency check timeout (milliseconds)
|
|
42
|
+
export AXUSAGE_CLI_TIMEOUT_MS=5000
|
|
43
|
+
```
|
|
44
|
+
|
|
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
|
|
55
|
+
|
|
24
56
|
## Authentication
|
|
25
57
|
|
|
26
58
|
Claude, ChatGPT, and Gemini use their respective CLI OAuth sessions. GitHub
|
|
@@ -33,13 +65,16 @@ Copilot uses browser-based authentication for persistent, long-lived sessions.
|
|
|
33
65
|
claude
|
|
34
66
|
codex
|
|
35
67
|
gemini
|
|
36
|
-
axusage auth
|
|
68
|
+
axusage --auth-setup github-copilot --interactive
|
|
37
69
|
|
|
38
70
|
# Check authentication status
|
|
39
|
-
axusage auth
|
|
71
|
+
axusage --auth-status
|
|
40
72
|
```
|
|
41
73
|
|
|
42
|
-
|
|
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.
|
|
43
78
|
Simply log in to GitHub as you normally would. Your authentication will be
|
|
44
79
|
saved and automatically used for future requests.
|
|
45
80
|
|
|
@@ -62,9 +97,13 @@ Security notes:
|
|
|
62
97
|
- To revoke access for GitHub Copilot, clear saved browser auth:
|
|
63
98
|
|
|
64
99
|
```bash
|
|
65
|
-
axusage auth
|
|
100
|
+
axusage --auth-clear github-copilot --interactive
|
|
66
101
|
```
|
|
67
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
|
+
|
|
68
107
|
Browser installation:
|
|
69
108
|
|
|
70
109
|
- Playwright Chromium is installed automatically on `pnpm install` via a postinstall script. If this fails in your environment, install manually:
|
|
@@ -111,6 +150,9 @@ axusage --service claude --format=json
|
|
|
111
150
|
|
|
112
151
|
# TSV output (parseable with cut, awk, sort)
|
|
113
152
|
axusage --format=tsv
|
|
153
|
+
|
|
154
|
+
# Disable color output
|
|
155
|
+
axusage --no-color
|
|
114
156
|
```
|
|
115
157
|
|
|
116
158
|
## Examples
|
|
@@ -169,16 +211,17 @@ Use `axusage` when you need a quick, scriptable snapshot of API usage across Cla
|
|
|
169
211
|
|
|
170
212
|
- The CLI shows a countdown while waiting for login.
|
|
171
213
|
- If you have completed login, press Enter in the terminal to continue.
|
|
172
|
-
- If it still fails, run `axusage auth
|
|
214
|
+
- If it still fails, run `axusage --auth-clear <service> --interactive` and retry.
|
|
173
215
|
|
|
174
216
|
### "No saved authentication" error
|
|
175
217
|
|
|
176
|
-
- Check which services are authenticated: `axusage auth
|
|
177
|
-
-
|
|
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.
|
|
178
221
|
|
|
179
222
|
### Sessions expire
|
|
180
223
|
|
|
181
|
-
- Browser sessions can expire based on provider policy. Re-run `auth
|
|
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.
|
|
182
225
|
|
|
183
226
|
## Remote authentication and Prometheus export
|
|
184
227
|
|
|
@@ -195,15 +238,17 @@ You can perform the interactive login flow on a workstation (for example, a loca
|
|
|
195
238
|
claude
|
|
196
239
|
codex
|
|
197
240
|
gemini
|
|
198
|
-
axusage auth
|
|
241
|
+
axusage --auth-setup github-copilot --interactive
|
|
199
242
|
```
|
|
200
243
|
|
|
201
244
|
2. Confirm the workstation has valid sessions:
|
|
202
245
|
|
|
203
246
|
```bash
|
|
204
|
-
axusage auth
|
|
247
|
+
axusage --auth-status
|
|
205
248
|
```
|
|
206
249
|
|
|
250
|
+
For CLI-auth services, run `claude`, `codex`, or `gemini` to confirm login.
|
|
251
|
+
|
|
207
252
|
3. Package the saved contexts so they can be transferred. Set `CONTEXT_DIR` to the path for your platform (see the table above):
|
|
208
253
|
|
|
209
254
|
```bash
|
|
@@ -211,7 +256,8 @@ You can perform the interactive login flow on a workstation (for example, a loca
|
|
|
211
256
|
tar czf axusage-contexts.tgz -C "$(dirname "$CONTEXT_DIR")" "$(basename "$CONTEXT_DIR")"
|
|
212
257
|
```
|
|
213
258
|
|
|
214
|
-
Archive structure: `browser-contexts/
|
|
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).
|
|
215
261
|
|
|
216
262
|
### 2. Transfer the browser contexts to the Linux server
|
|
217
263
|
|
|
@@ -237,7 +283,7 @@ You can perform the interactive login flow on a workstation (for example, a loca
|
|
|
237
283
|
3. Verify that the sessions are available on the server:
|
|
238
284
|
|
|
239
285
|
```bash
|
|
240
|
-
axusage auth
|
|
286
|
+
axusage --auth-status
|
|
241
287
|
```
|
|
242
288
|
|
|
243
289
|
If the server does not yet have the tool installed, run `npm install -g axusage` before checking the status.
|
|
@@ -245,7 +291,7 @@ You can perform the interactive login flow on a workstation (for example, a loca
|
|
|
245
291
|
Notes:
|
|
246
292
|
|
|
247
293
|
- Use `--service <name>` to restrict services.
|
|
248
|
-
- Sessions may expire or become invalid if you change your password or log out of the service in another browser. Re-run `auth
|
|
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.
|
|
249
295
|
- If you transfer browser contexts between machines, ensure the target system is secure and permissions are restricted to the intended user.
|
|
250
296
|
- The CLI stores authentication data in the platform-specific directories listed above; protect that directory to prevent unauthorized access.
|
|
251
297
|
|
|
@@ -16,7 +16,8 @@ export const githubCopilotAdapter = {
|
|
|
16
16
|
if (!manager.hasAuth("github-copilot")) {
|
|
17
17
|
return {
|
|
18
18
|
ok: false,
|
|
19
|
-
error: new ApiError("No saved authentication for github-copilot.
|
|
19
|
+
error: new ApiError("No saved authentication for github-copilot. " +
|
|
20
|
+
"Run 'axusage --auth-setup github-copilot --interactive' first."),
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
23
|
const body = await manager.makeAuthenticatedRequest("github-copilot", API_URL);
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command, Option } from "@commander-js/extra-typings";
|
|
3
3
|
import packageJson from "../package.json" with { type: "json" };
|
|
4
|
-
import {
|
|
4
|
+
import { authClearCommand } from "./commands/auth-clear-command.js";
|
|
5
5
|
import { authSetupCommand } from "./commands/auth-setup-command.js";
|
|
6
6
|
import { authStatusCommand } from "./commands/auth-status-command.js";
|
|
7
|
-
import {
|
|
7
|
+
import { usageCommand } from "./commands/usage-command.js";
|
|
8
|
+
import { getBrowserContextsDirectory } from "./services/app-paths.js";
|
|
8
9
|
import { getAvailableServices } from "./services/service-adapter-registry.js";
|
|
9
10
|
import { installAuthManagerCleanup } from "./services/shared-browser-auth-manager.js";
|
|
10
|
-
import {
|
|
11
|
+
import { configureColor } from "./utils/color.js";
|
|
12
|
+
import { getRootOptionsError, } from "./utils/validate-root-options.js";
|
|
13
|
+
// Parse --no-color early so help/error output is consistently uncolored.
|
|
14
|
+
const shouldDisableColor = process.argv.includes("--no-color");
|
|
15
|
+
configureColor({ enabled: shouldDisableColor ? false : undefined });
|
|
11
16
|
const program = new Command()
|
|
12
17
|
.name(packageJson.name)
|
|
13
18
|
.description(packageJson.description)
|
|
@@ -15,49 +20,60 @@ const program = new Command()
|
|
|
15
20
|
.showHelpAfterError("(add --help for additional information)")
|
|
16
21
|
.showSuggestionAfterError()
|
|
17
22
|
.helpCommand(false)
|
|
18
|
-
.
|
|
19
|
-
// Ensure browser resources are cleaned when process exits
|
|
20
|
-
installAuthManagerCleanup();
|
|
21
|
-
// Usage command (default)
|
|
22
|
-
program
|
|
23
|
-
.command("usage", { isDefault: true })
|
|
24
|
-
.description("Fetch API usage statistics (defaults to all: Claude, ChatGPT, GitHub Copilot)")
|
|
23
|
+
.option("--no-color", "disable color output")
|
|
25
24
|
.option("-s, --service <service>", `Service to query (${getAvailableServices().join(", ")}, all) - defaults to all`)
|
|
26
|
-
.option("-i, --interactive", "allow interactive
|
|
25
|
+
.option("-i, --interactive", "allow interactive authentication prompts (usage reauth, --auth-setup/--auth-clear; ignored with --auth-status)")
|
|
27
26
|
.addOption(new Option("-o, --format <format>", "Output format")
|
|
28
27
|
.choices(["text", "tsv", "json", "prometheus"])
|
|
29
28
|
.default("text"))
|
|
30
|
-
.
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.
|
|
37
|
-
.
|
|
38
|
-
.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
29
|
+
.option("--auth-setup <service>", "set up authentication for a service (CLI or browser-based)")
|
|
30
|
+
.option("--auth-status [service]", "check authentication status for services")
|
|
31
|
+
.option("--auth-clear <service>", "clear saved browser authentication for a service (moves files to system Trash)")
|
|
32
|
+
.option("-f, --force", "skip confirmation for destructive actions")
|
|
33
|
+
.addHelpText("after", `\nExamples:\n # Fetch usage for all services\n ${packageJson.name}\n\n # JSON output for a single service\n ${packageJson.name} --service claude --format=json\n\n # TSV output for piping to cut, awk, sort\n ${packageJson.name} --format=tsv | tail -n +2 | awk -F'\\t' '{print $1, $4"%"}'\n\n # Filter Prometheus metrics with standard tools\n ${packageJson.name} --format=prometheus | grep axusage_utilization_percent\n\n # Check authentication status for all services\n ${packageJson.name} --auth-status\n\nStorage: ${getBrowserContextsDirectory()}\n(respects XDG_DATA_HOME and platform defaults)\n\nRequires: claude, codex (ChatGPT), gemini (CLI auth); Playwright Chromium (GitHub Copilot auth)\nOverride CLI paths: AXUSAGE_CLAUDE_PATH, AXUSAGE_CODEX_PATH, AXUSAGE_GEMINI_PATH\nPlaywright: PLAYWRIGHT_BROWSERS_PATH\n`);
|
|
34
|
+
function fail(message) {
|
|
35
|
+
console.error(`Error: ${message}`);
|
|
36
|
+
console.error("Try 'axusage --help' for details.");
|
|
37
|
+
if (process.exitCode === undefined)
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
}
|
|
40
|
+
program.action(async (options, command) => {
|
|
41
|
+
const errorMessage = getRootOptionsError(options, command.getOptionValueSource("format"));
|
|
42
|
+
if (errorMessage) {
|
|
43
|
+
fail(errorMessage);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (options.authSetup) {
|
|
47
|
+
await authSetupCommand({
|
|
48
|
+
service: options.authSetup,
|
|
49
|
+
interactive: options.interactive,
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (options.authStatus !== undefined) {
|
|
54
|
+
const service = typeof options.authStatus === "string" && options.authStatus.length > 0
|
|
55
|
+
? options.authStatus
|
|
56
|
+
: undefined;
|
|
57
|
+
authStatusCommand({ service });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (options.authClear) {
|
|
61
|
+
await authClearCommand({
|
|
62
|
+
service: options.authClear,
|
|
63
|
+
interactive: options.interactive,
|
|
64
|
+
force: options.force,
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const usageOptions = {
|
|
69
|
+
service: options.service,
|
|
70
|
+
format: options.format,
|
|
71
|
+
interactive: options.interactive,
|
|
72
|
+
};
|
|
73
|
+
await usageCommand(usageOptions);
|
|
60
74
|
});
|
|
75
|
+
// Ensure browser resources are cleaned when process exits
|
|
76
|
+
installAuthManagerCleanup();
|
|
61
77
|
try {
|
|
62
78
|
await program.parseAsync();
|
|
63
79
|
}
|
|
@@ -1,20 +1,79 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { confirm } from "@inquirer/prompts";
|
|
2
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
3
4
|
import trash from "trash";
|
|
4
5
|
import { validateService } from "../services/supported-service.js";
|
|
5
6
|
import { getAuthMetaPathFor, getStorageStatePathFor, } from "../services/auth-storage-path.js";
|
|
6
7
|
import { getBrowserContextsDirectory } from "../services/app-paths.js";
|
|
8
|
+
import { chalk } from "../utils/color.js";
|
|
9
|
+
import { resolvePromptCapability } from "../utils/resolve-prompt-capability.js";
|
|
10
|
+
function isPromptCancellation(error) {
|
|
11
|
+
return (error instanceof Error &&
|
|
12
|
+
(error.name === "AbortPromptError" ||
|
|
13
|
+
error.name === "CancelPromptError" ||
|
|
14
|
+
error.name === "ExitPromptError"));
|
|
15
|
+
}
|
|
16
|
+
function collectRelatedArtifacts(filePath) {
|
|
17
|
+
const directory = path.dirname(filePath);
|
|
18
|
+
const baseName = path.basename(filePath);
|
|
19
|
+
try {
|
|
20
|
+
return readdirSync(directory)
|
|
21
|
+
.filter((entry) => entry.startsWith(`${baseName}.`) &&
|
|
22
|
+
(entry.endsWith(".bak") || entry.endsWith(".tmp")))
|
|
23
|
+
.map((entry) => path.join(directory, entry));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
7
29
|
export async function authClearCommand(options) {
|
|
8
30
|
const service = validateService(options.service);
|
|
9
31
|
const dataDirectory = getBrowserContextsDirectory();
|
|
10
32
|
const storage = getStorageStatePathFor(dataDirectory, service);
|
|
11
33
|
const meta = getAuthMetaPathFor(dataDirectory, service);
|
|
12
34
|
try {
|
|
13
|
-
const
|
|
35
|
+
const artifactTargets = [storage, meta].filter((p) => existsSync(p));
|
|
36
|
+
const backupTargets = [storage, meta].flatMap((filePath) => collectRelatedArtifacts(filePath));
|
|
37
|
+
const targets = [...new Set([...artifactTargets, ...backupTargets])];
|
|
14
38
|
if (targets.length === 0) {
|
|
15
39
|
console.error(chalk.gray(`\nNo saved authentication found for ${service}.`));
|
|
16
40
|
return;
|
|
17
41
|
}
|
|
42
|
+
if (!options.force) {
|
|
43
|
+
if (!options.interactive) {
|
|
44
|
+
console.error(chalk.red("Error: Clearing saved authentication requires confirmation."));
|
|
45
|
+
console.error(chalk.gray("Re-run with --interactive to confirm, or use --force to skip confirmation."));
|
|
46
|
+
console.error(chalk.gray("Try 'axusage --help' for details."));
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!resolvePromptCapability()) {
|
|
51
|
+
console.error(chalk.red("Error: --interactive requires a TTY-enabled terminal."));
|
|
52
|
+
console.error(chalk.gray("Re-run in a terminal or pass --force instead."));
|
|
53
|
+
console.error(chalk.gray("Try 'axusage --help' for details."));
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let confirmed = false;
|
|
58
|
+
try {
|
|
59
|
+
confirmed = await confirm({
|
|
60
|
+
message: `Remove saved authentication for ${service}?`,
|
|
61
|
+
default: false,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (isPromptCancellation(error)) {
|
|
66
|
+
console.error(chalk.gray("Aborted."));
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
if (!confirmed) {
|
|
73
|
+
console.error(chalk.gray("Aborted."));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
18
77
|
await trash(targets, { glob: false });
|
|
19
78
|
console.error(chalk.green(`\n✓ Cleared authentication for ${service}`));
|
|
20
79
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
1
|
import { BrowserAuthManager } from "../services/browser-auth-manager.js";
|
|
3
2
|
import { validateService } from "../services/supported-service.js";
|
|
3
|
+
import { resolveAuthCliDependencyOrReport } from "../utils/check-cli-dependency.js";
|
|
4
|
+
import { chalk } from "../utils/color.js";
|
|
5
|
+
import { resolvePromptCapability } from "../utils/resolve-prompt-capability.js";
|
|
4
6
|
/**
|
|
5
7
|
* Set up authentication for a service
|
|
6
8
|
*/
|
|
@@ -8,32 +10,61 @@ export async function authSetupCommand(options) {
|
|
|
8
10
|
const service = validateService(options.service);
|
|
9
11
|
// CLI-based auth - users should run the native CLI directly
|
|
10
12
|
if (service === "gemini") {
|
|
13
|
+
const cliPath = resolveAuthCliDependencyOrReport("gemini", {
|
|
14
|
+
setExitCode: true,
|
|
15
|
+
});
|
|
16
|
+
if (!cliPath)
|
|
17
|
+
return;
|
|
11
18
|
console.error(chalk.yellow("\nGemini uses CLI-based authentication managed by the Gemini CLI."));
|
|
12
19
|
console.error(chalk.gray("\nTo authenticate, run:"));
|
|
13
|
-
console.error(chalk.cyan(
|
|
20
|
+
console.error(chalk.cyan(` ${cliPath}`));
|
|
14
21
|
console.error(chalk.gray("\nThe Gemini CLI will guide you through the OAuth login process.\n"));
|
|
15
22
|
return;
|
|
16
23
|
}
|
|
17
24
|
if (service === "claude") {
|
|
25
|
+
const cliPath = resolveAuthCliDependencyOrReport("claude", {
|
|
26
|
+
setExitCode: true,
|
|
27
|
+
});
|
|
28
|
+
if (!cliPath)
|
|
29
|
+
return;
|
|
18
30
|
console.error(chalk.yellow("\nClaude uses CLI-based authentication managed by Claude Code."));
|
|
19
31
|
console.error(chalk.gray("\nTo authenticate, run:"));
|
|
20
|
-
console.error(chalk.cyan(
|
|
32
|
+
console.error(chalk.cyan(` ${cliPath}`));
|
|
21
33
|
console.error(chalk.gray("\nClaude Code will guide you through authentication.\n"));
|
|
22
34
|
return;
|
|
23
35
|
}
|
|
24
36
|
if (service === "chatgpt") {
|
|
37
|
+
const cliPath = resolveAuthCliDependencyOrReport("chatgpt", {
|
|
38
|
+
setExitCode: true,
|
|
39
|
+
});
|
|
40
|
+
if (!cliPath)
|
|
41
|
+
return;
|
|
25
42
|
console.error(chalk.yellow("\nChatGPT uses CLI-based authentication managed by Codex."));
|
|
26
43
|
console.error(chalk.gray("\nTo authenticate, run:"));
|
|
27
|
-
console.error(chalk.cyan(
|
|
44
|
+
console.error(chalk.cyan(` ${cliPath}`));
|
|
28
45
|
console.error(chalk.gray("\nCodex will guide you through authentication.\n"));
|
|
29
46
|
return;
|
|
30
47
|
}
|
|
48
|
+
if (!options.interactive) {
|
|
49
|
+
console.error(chalk.red("Error: Authentication setup requires --interactive."));
|
|
50
|
+
console.error(chalk.gray("Re-run with --interactive in a TTY-enabled terminal to continue."));
|
|
51
|
+
console.error(chalk.gray("Try 'axusage --help' for details."));
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (!resolvePromptCapability()) {
|
|
56
|
+
console.error(chalk.red("Error: --interactive requires a TTY-enabled terminal."));
|
|
57
|
+
console.error(chalk.gray("Re-run in a terminal session to complete authentication."));
|
|
58
|
+
console.error(chalk.gray("Try 'axusage --help' for details."));
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
31
62
|
const manager = new BrowserAuthManager({ headless: false });
|
|
32
63
|
try {
|
|
33
64
|
console.error(chalk.blue(`\nSetting up authentication for ${service}...\n`));
|
|
34
65
|
await manager.setupAuth(service);
|
|
35
66
|
console.error(chalk.green(`\n✓ Authentication for ${service} is complete!`));
|
|
36
|
-
console.error(chalk.gray(`\nYou can now run: ${chalk.cyan(`axusage
|
|
67
|
+
console.error(chalk.gray(`\nYou can now run: ${chalk.cyan(`axusage --service ${service}`)}`));
|
|
37
68
|
}
|
|
38
69
|
catch (error) {
|
|
39
70
|
console.error(chalk.red(`\n✗ Failed to set up authentication for ${service}: ${error instanceof Error ? error.message : String(error)}`));
|
|
@@ -1,25 +1,57 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
1
|
import { existsSync } from "node:fs";
|
|
3
2
|
import { SUPPORTED_SERVICES, validateService, } from "../services/supported-service.js";
|
|
4
3
|
import { getStorageStatePathFor } from "../services/auth-storage-path.js";
|
|
5
4
|
import { getBrowserContextsDirectory } from "../services/app-paths.js";
|
|
5
|
+
import { AUTH_CLI_SERVICES, checkCliDependency, getAuthCliDependency, } from "../utils/check-cli-dependency.js";
|
|
6
|
+
import { chalk } from "../utils/color.js";
|
|
6
7
|
export function authStatusCommand(options) {
|
|
7
8
|
const servicesToCheck = options.service
|
|
8
9
|
? [validateService(options.service)]
|
|
9
10
|
: SUPPORTED_SERVICES;
|
|
11
|
+
const cliAuthServices = new Set(AUTH_CLI_SERVICES);
|
|
10
12
|
const dataDirectory = getBrowserContextsDirectory();
|
|
13
|
+
let hasFailures = false;
|
|
11
14
|
console.log(chalk.blue("\nAuthentication Status:\n"));
|
|
12
15
|
for (const service of servicesToCheck) {
|
|
16
|
+
if (cliAuthServices.has(service)) {
|
|
17
|
+
const dependency = getAuthCliDependency(service);
|
|
18
|
+
const result = checkCliDependency(dependency);
|
|
19
|
+
const status = result.ok
|
|
20
|
+
? chalk.green("↪ CLI-managed")
|
|
21
|
+
: chalk.red("✗ CLI missing");
|
|
22
|
+
if (!result.ok) {
|
|
23
|
+
hasFailures = true;
|
|
24
|
+
}
|
|
25
|
+
console.log(`${chalk.bold(service)}: ${status}`);
|
|
26
|
+
console.log(` ${chalk.dim("CLI:")} ${chalk.dim(result.path)}`);
|
|
27
|
+
if (result.ok) {
|
|
28
|
+
console.log(` ${chalk.dim("Auth:")} ${chalk.dim(`run ${result.path} to check/login`)}`);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.log(` ${chalk.dim("Install:")} ${chalk.dim(dependency.installHint)}`);
|
|
32
|
+
console.log(` ${chalk.dim("Override:")} ${chalk.dim(`${dependency.envVar}=/path/to/${dependency.command}`)}`);
|
|
33
|
+
}
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
13
36
|
const storagePath = getStorageStatePathFor(dataDirectory, service);
|
|
14
37
|
const hasAuth = existsSync(storagePath);
|
|
15
38
|
const status = hasAuth
|
|
16
39
|
? chalk.green("✓ Authenticated")
|
|
17
40
|
: chalk.gray("✗ Not authenticated");
|
|
41
|
+
if (!hasAuth) {
|
|
42
|
+
hasFailures = true;
|
|
43
|
+
}
|
|
18
44
|
console.log(`${chalk.bold(service)}: ${status}`);
|
|
19
45
|
console.log(` ${chalk.dim("Storage:")} ${chalk.dim(storagePath)}`);
|
|
20
46
|
}
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
47
|
+
const browserServices = servicesToCheck.filter((service) => !cliAuthServices.has(service));
|
|
48
|
+
const copilotService = "github-copilot";
|
|
49
|
+
const needsCopilotSetup = browserServices.includes(copilotService) &&
|
|
50
|
+
!existsSync(getStorageStatePathFor(dataDirectory, copilotService));
|
|
51
|
+
if (needsCopilotSetup) {
|
|
52
|
+
console.error(chalk.gray(`\nTo set up authentication, run: ${chalk.cyan("axusage --auth-setup github-copilot --interactive")}`));
|
|
53
|
+
}
|
|
54
|
+
if (hasFailures) {
|
|
55
|
+
process.exitCode = 1;
|
|
24
56
|
}
|
|
25
57
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
1
|
import { fetchServiceUsage } from "./fetch-service-usage.js";
|
|
3
2
|
import { isAuthFailure, runAuthSetup } from "./run-auth-setup.js";
|
|
4
3
|
import { validateService } from "../services/supported-service.js";
|
|
4
|
+
import { chalk } from "../utils/color.js";
|
|
5
5
|
/**
|
|
6
6
|
* Fetch usage for a service, with automatic re-authentication on auth errors.
|
|
7
7
|
* Prompts the user to re-authenticate if the initial fetch fails with an auth error,
|
|
@@ -14,7 +14,7 @@ export async function fetchServiceUsageWithAutoReauth(serviceName, interactive)
|
|
|
14
14
|
}
|
|
15
15
|
// If auth error, try to re-authenticate and retry
|
|
16
16
|
if (isAuthFailure(result)) {
|
|
17
|
-
console.error(chalk.yellow(`⚠ Authentication failed for ${serviceName}.
|
|
17
|
+
console.error(chalk.yellow(`⚠ Authentication failed for ${serviceName}. Attempting to re-authenticate...`));
|
|
18
18
|
try {
|
|
19
19
|
const service = validateService(serviceName);
|
|
20
20
|
const authSuccess = await runAuthSetup(service);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ApiError as ApiErrorClass } from "../types/domain.js";
|
|
2
|
-
import { getServiceAdapter } from "../services/service-adapter-registry.js";
|
|
2
|
+
import { getAvailableServices, getServiceAdapter, } from "../services/service-adapter-registry.js";
|
|
3
3
|
const ALL_SERVICES = ["claude", "chatgpt", "github-copilot", "gemini"];
|
|
4
4
|
export function selectServicesToQuery(service) {
|
|
5
5
|
const normalized = service?.toLowerCase();
|
|
@@ -10,9 +10,11 @@ export function selectServicesToQuery(service) {
|
|
|
10
10
|
export async function fetchServiceUsage(serviceName) {
|
|
11
11
|
const adapter = getServiceAdapter(serviceName);
|
|
12
12
|
if (!adapter) {
|
|
13
|
+
const available = getAvailableServices().join(", ");
|
|
13
14
|
return {
|
|
14
15
|
ok: false,
|
|
15
|
-
error: new ApiErrorClass(`Unknown service "${serviceName}"`
|
|
16
|
+
error: new ApiErrorClass(`Unknown service "${serviceName}". Supported services: ${available}. ` +
|
|
17
|
+
"Run 'axusage --help' for usage."),
|
|
16
18
|
};
|
|
17
19
|
}
|
|
18
20
|
return await adapter.fetchUsage();
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
1
|
import { BrowserAuthManager } from "../services/browser-auth-manager.js";
|
|
2
|
+
import { resolveAuthCliDependencyOrReport } from "../utils/check-cli-dependency.js";
|
|
3
|
+
import { chalk } from "../utils/color.js";
|
|
4
|
+
import { resolvePromptCapability } from "../utils/resolve-prompt-capability.js";
|
|
3
5
|
/** Timeout for authentication setup (5 minutes) */
|
|
4
6
|
const AUTH_SETUP_TIMEOUT_MS = 300_000;
|
|
5
7
|
/**
|
|
@@ -46,31 +48,46 @@ export function isAuthFailure(result) {
|
|
|
46
48
|
export async function runAuthSetup(service) {
|
|
47
49
|
// CLI-based auth cannot use browser auth flow
|
|
48
50
|
if (service === "gemini") {
|
|
51
|
+
const cliPath = resolveAuthCliDependencyOrReport("gemini");
|
|
52
|
+
if (!cliPath)
|
|
53
|
+
return false;
|
|
49
54
|
console.error(chalk.yellow("\nGemini uses CLI-based authentication managed by the Gemini CLI."));
|
|
50
55
|
console.error(chalk.gray("\nTo re-authenticate, run:"));
|
|
51
|
-
console.error(chalk.cyan(
|
|
56
|
+
console.error(chalk.cyan(` ${cliPath}`));
|
|
52
57
|
console.error(chalk.gray("\nThe Gemini CLI will guide you through the OAuth login process.\n"));
|
|
53
58
|
return false;
|
|
54
59
|
}
|
|
55
60
|
if (service === "claude") {
|
|
61
|
+
const cliPath = resolveAuthCliDependencyOrReport("claude");
|
|
62
|
+
if (!cliPath)
|
|
63
|
+
return false;
|
|
56
64
|
console.error(chalk.yellow("\nClaude uses CLI-based authentication managed by Claude Code."));
|
|
57
65
|
console.error(chalk.gray("\nTo re-authenticate, run:"));
|
|
58
|
-
console.error(chalk.cyan(
|
|
66
|
+
console.error(chalk.cyan(` ${cliPath}`));
|
|
59
67
|
console.error(chalk.gray("\nClaude Code will guide you through authentication.\n"));
|
|
60
68
|
return false;
|
|
61
69
|
}
|
|
62
70
|
if (service === "chatgpt") {
|
|
71
|
+
const cliPath = resolveAuthCliDependencyOrReport("chatgpt");
|
|
72
|
+
if (!cliPath)
|
|
73
|
+
return false;
|
|
63
74
|
console.error(chalk.yellow("\nChatGPT uses CLI-based authentication managed by Codex."));
|
|
64
75
|
console.error(chalk.gray("\nTo re-authenticate, run:"));
|
|
65
|
-
console.error(chalk.cyan(
|
|
76
|
+
console.error(chalk.cyan(` ${cliPath}`));
|
|
66
77
|
console.error(chalk.gray("\nCodex will guide you through authentication.\n"));
|
|
67
78
|
return false;
|
|
68
79
|
}
|
|
80
|
+
if (!resolvePromptCapability()) {
|
|
81
|
+
console.error(chalk.red("Error: Interactive authentication requires a TTY terminal."));
|
|
82
|
+
console.error(chalk.gray("Re-run in a TTY terminal (avoid piping stdin/stdout) with --interactive to complete authentication."));
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
69
85
|
const manager = new BrowserAuthManager({ headless: false });
|
|
86
|
+
let setupPromise;
|
|
70
87
|
let timeoutId;
|
|
71
88
|
try {
|
|
72
89
|
console.error(chalk.blue(`\nOpening browser for ${service} authentication...\n`));
|
|
73
|
-
|
|
90
|
+
setupPromise = manager.setupAuth(service);
|
|
74
91
|
const timeoutPromise = new Promise((_, reject) => {
|
|
75
92
|
timeoutId = setTimeout(() => {
|
|
76
93
|
reject(new Error("Authentication setup timed out after 5 minutes"));
|
|
@@ -86,6 +103,10 @@ export async function runAuthSetup(service) {
|
|
|
86
103
|
}
|
|
87
104
|
finally {
|
|
88
105
|
clearTimeout(timeoutId);
|
|
106
|
+
if (setupPromise) {
|
|
107
|
+
// Avoid unhandled rejections if the timeout wins the race.
|
|
108
|
+
void setupPromise.catch(() => { });
|
|
109
|
+
}
|
|
89
110
|
await manager.close();
|
|
90
111
|
}
|
|
91
112
|
}
|