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.
- package/README.md +78 -194
- package/dist/adapters/claude.js +9 -8
- package/dist/adapters/coalesce-claude-usage-response.js +5 -7
- package/dist/adapters/{chatgpt.d.ts → codex.d.ts} +1 -1
- package/dist/adapters/{chatgpt.js → codex.js} +5 -5
- package/dist/adapters/copilot.d.ts +7 -0
- package/dist/adapters/copilot.js +58 -0
- package/dist/adapters/{parse-chatgpt-usage.d.ts → parse-codex-usage.d.ts} +3 -3
- package/dist/adapters/parse-copilot-usage.d.ts +15 -0
- package/dist/adapters/parse-copilot-usage.js +61 -0
- package/dist/cli.js +4 -21
- package/dist/commands/auth-setup-command.d.ts +1 -2
- package/dist/commands/auth-setup-command.js +44 -67
- package/dist/commands/auth-status-command.js +9 -40
- package/dist/commands/fetch-service-usage.d.ts +0 -1
- package/dist/commands/fetch-service-usage.js +1 -2
- package/dist/commands/run-auth-setup.d.ts +0 -10
- package/dist/commands/run-auth-setup.js +3 -80
- package/dist/commands/usage-command.d.ts +2 -7
- package/dist/commands/usage-command.js +7 -39
- package/dist/config/credential-sources.d.ts +3 -11
- package/dist/config/credential-sources.js +1 -1
- package/dist/services/get-service-access-token.d.ts +3 -3
- package/dist/services/get-service-access-token.js +11 -11
- package/dist/services/service-adapter-registry.d.ts +2 -2
- package/dist/services/service-adapter-registry.js +4 -4
- package/dist/services/service-diagnostics.d.ts +11 -0
- package/dist/services/service-diagnostics.js +29 -0
- package/dist/services/supported-service.d.ts +6 -2
- package/dist/services/supported-service.js +2 -6
- package/dist/types/{chatgpt.d.ts → codex.d.ts} +4 -4
- package/dist/types/{chatgpt.js → codex.js} +6 -6
- package/dist/types/copilot.d.ts +14 -0
- package/dist/types/copilot.js +21 -0
- package/dist/utils/check-cli-dependency.d.ts +2 -4
- package/dist/utils/check-cli-dependency.js +7 -4
- package/dist/utils/copilot-gh-token.d.ts +1 -0
- package/dist/utils/copilot-gh-token.js +38 -0
- package/dist/utils/format-requires-help-text.d.ts +17 -0
- package/dist/utils/format-requires-help-text.js +62 -0
- package/dist/utils/validate-root-options.d.ts +0 -3
- package/dist/utils/validate-root-options.js +2 -6
- package/package.json +15 -19
- package/dist/adapters/github-copilot.d.ts +0 -6
- package/dist/adapters/github-copilot.js +0 -57
- package/dist/adapters/parse-github-copilot-usage.d.ts +0 -23
- package/dist/adapters/parse-github-copilot-usage.js +0 -78
- package/dist/commands/auth-clear-command.d.ts +0 -7
- package/dist/commands/auth-clear-command.js +0 -84
- package/dist/commands/fetch-service-usage-with-reauth.d.ts +0 -7
- package/dist/commands/fetch-service-usage-with-reauth.js +0 -45
- package/dist/services/app-paths.d.ts +0 -9
- package/dist/services/app-paths.js +0 -39
- package/dist/services/auth-storage-path.d.ts +0 -3
- package/dist/services/auth-storage-path.js +0 -7
- package/dist/services/auth-timeouts.d.ts +0 -4
- package/dist/services/auth-timeouts.js +0 -4
- package/dist/services/browser-auth-manager.d.ts +0 -49
- package/dist/services/browser-auth-manager.js +0 -113
- package/dist/services/create-auth-context.d.ts +0 -8
- package/dist/services/create-auth-context.js +0 -35
- package/dist/services/do-setup-auth.d.ts +0 -3
- package/dist/services/do-setup-auth.js +0 -19
- package/dist/services/fetch-json-with-context.d.ts +0 -5
- package/dist/services/fetch-json-with-context.js +0 -37
- package/dist/services/launch-chromium.d.ts +0 -6
- package/dist/services/launch-chromium.js +0 -20
- package/dist/services/persist-storage-state.d.ts +0 -6
- package/dist/services/persist-storage-state.js +0 -16
- package/dist/services/request-service.d.ts +0 -3
- package/dist/services/request-service.js +0 -4
- package/dist/services/service-auth-configs.d.ts +0 -15
- package/dist/services/service-auth-configs.js +0 -26
- package/dist/services/setup-auth-flow.d.ts +0 -3
- package/dist/services/setup-auth-flow.js +0 -67
- package/dist/services/shared-browser-auth-manager.d.ts +0 -4
- package/dist/services/shared-browser-auth-manager.js +0 -87
- package/dist/services/verify-session.d.ts +0 -2
- package/dist/services/verify-session.js +0 -27
- package/dist/services/wait-for-login.d.ts +0 -6
- package/dist/services/wait-for-login.js +0 -115
- package/dist/types/github-copilot.d.ts +0 -21
- package/dist/types/github-copilot.js +0 -27
- package/dist/utils/resolve-prompt-capability.d.ts +0 -1
- package/dist/utils/resolve-prompt-capability.js +0 -3
- package/dist/utils/write-atomic-json.d.ts +0 -1
- package/dist/utils/write-atomic-json.js +0 -56
- /package/dist/adapters/{parse-chatgpt-usage.js → parse-codex-usage.js} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
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 { authClearCommand } from "./commands/auth-clear-command.js";
|
|
5
4
|
import { authSetupCommand } from "./commands/auth-setup-command.js";
|
|
6
5
|
import { authStatusCommand } from "./commands/auth-status-command.js";
|
|
7
6
|
import { usageCommand } from "./commands/usage-command.js";
|
|
8
7
|
import { getCredentialSourcesPath } from "./config/credential-sources.js";
|
|
9
|
-
import { getBrowserContextsDirectory } from "./services/app-paths.js";
|
|
10
8
|
import { getAvailableServices } from "./services/service-adapter-registry.js";
|
|
11
|
-
import { installAuthManagerCleanup } from "./services/shared-browser-auth-manager.js";
|
|
12
9
|
import { configureColor } from "./utils/color.js";
|
|
10
|
+
import { formatRequiresHelpText } from "./utils/format-requires-help-text.js";
|
|
13
11
|
import { getRootOptionsError, } from "./utils/validate-root-options.js";
|
|
14
12
|
// Parse --no-color early so help/error output is consistently uncolored.
|
|
15
13
|
const shouldDisableColor = process.argv.includes("--no-color");
|
|
@@ -23,15 +21,12 @@ const program = new Command()
|
|
|
23
21
|
.helpCommand(false)
|
|
24
22
|
.option("--no-color", "disable color output")
|
|
25
23
|
.option("-s, --service <service>", `Service to query (${getAvailableServices().join(", ")}, all) - defaults to all`)
|
|
26
|
-
.option("-i, --interactive", "allow interactive authentication prompts (usage reauth, --auth-setup/--auth-clear; ignored with --auth-status)")
|
|
27
24
|
.addOption(new Option("-o, --format <format>", "Output format")
|
|
28
25
|
.choices(["text", "tsv", "json", "prometheus"])
|
|
29
26
|
.default("text"))
|
|
30
|
-
.option("--auth-setup <service>", "set up authentication for a service (
|
|
27
|
+
.option("--auth-setup <service>", "set up authentication for a service (directs to appropriate CLI)")
|
|
31
28
|
.option("--auth-status [service]", "check authentication status for services")
|
|
32
|
-
.
|
|
33
|
-
.option("-f, --force", "skip confirmation for destructive actions")
|
|
34
|
-
.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)\nSources config file: ${getCredentialSourcesPath()}\n(or set AXUSAGE_SOURCES to JSON to bypass file)\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`);
|
|
29
|
+
.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\nSources config file: ${getCredentialSourcesPath()}\n(or set AXUSAGE_SOURCES to JSON to bypass file)\n\n${formatRequiresHelpText()}\nOverride CLI paths: AXUSAGE_CLAUDE_PATH, AXUSAGE_CODEX_PATH, AXUSAGE_GEMINI_PATH, AXUSAGE_GH_PATH\n`);
|
|
35
30
|
function fail(message) {
|
|
36
31
|
console.error(`Error: ${message}`);
|
|
37
32
|
console.error("Try 'axusage --help' for details.");
|
|
@@ -45,9 +40,8 @@ program.action(async (options, command) => {
|
|
|
45
40
|
return;
|
|
46
41
|
}
|
|
47
42
|
if (options.authSetup) {
|
|
48
|
-
|
|
43
|
+
authSetupCommand({
|
|
49
44
|
service: options.authSetup,
|
|
50
|
-
interactive: options.interactive,
|
|
51
45
|
});
|
|
52
46
|
return;
|
|
53
47
|
}
|
|
@@ -58,23 +52,12 @@ program.action(async (options, command) => {
|
|
|
58
52
|
authStatusCommand({ service });
|
|
59
53
|
return;
|
|
60
54
|
}
|
|
61
|
-
if (options.authClear) {
|
|
62
|
-
await authClearCommand({
|
|
63
|
-
service: options.authClear,
|
|
64
|
-
interactive: options.interactive,
|
|
65
|
-
force: options.force,
|
|
66
|
-
});
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
55
|
const usageOptions = {
|
|
70
56
|
service: options.service,
|
|
71
57
|
format: options.format,
|
|
72
|
-
interactive: options.interactive,
|
|
73
58
|
};
|
|
74
59
|
await usageCommand(usageOptions);
|
|
75
60
|
});
|
|
76
|
-
// Ensure browser resources are cleaned when process exits
|
|
77
|
-
installAuthManagerCleanup();
|
|
78
61
|
try {
|
|
79
62
|
await program.parseAsync();
|
|
80
63
|
}
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
type AuthSetupOptions = {
|
|
5
5
|
readonly service?: string;
|
|
6
|
-
readonly interactive?: boolean;
|
|
7
6
|
};
|
|
8
7
|
/**
|
|
9
8
|
* Set up authentication for a service
|
|
10
9
|
*/
|
|
11
|
-
export declare function authSetupCommand(options: AuthSetupOptions):
|
|
10
|
+
export declare function authSetupCommand(options: AuthSetupOptions): void;
|
|
12
11
|
export {};
|
|
@@ -1,76 +1,53 @@
|
|
|
1
|
-
import { BrowserAuthManager } from "../services/browser-auth-manager.js";
|
|
2
1
|
import { validateService } from "../services/supported-service.js";
|
|
3
2
|
import { resolveAuthCliDependencyOrReport } from "../utils/check-cli-dependency.js";
|
|
4
3
|
import { chalk } from "../utils/color.js";
|
|
5
|
-
|
|
4
|
+
function assertNever(value) {
|
|
5
|
+
throw new Error(`Unhandled service: ${String(value)}`);
|
|
6
|
+
}
|
|
7
|
+
function printCliAuthInstructions(service, cliPath) {
|
|
8
|
+
switch (service) {
|
|
9
|
+
case "gemini": {
|
|
10
|
+
console.error(chalk.yellow("\nGemini uses CLI-based authentication managed by the Gemini CLI."));
|
|
11
|
+
console.error(chalk.gray("\nTo authenticate, run:"));
|
|
12
|
+
console.error(chalk.cyan(` ${cliPath}`));
|
|
13
|
+
console.error(chalk.gray("\nThe Gemini CLI will guide you through the OAuth login process.\n"));
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
case "claude": {
|
|
17
|
+
console.error(chalk.yellow("\nClaude uses CLI-based authentication managed by Claude Code."));
|
|
18
|
+
console.error(chalk.gray("\nTo authenticate, run:"));
|
|
19
|
+
console.error(chalk.cyan(` ${cliPath}`));
|
|
20
|
+
console.error(chalk.gray("\nClaude Code will guide you through authentication.\n"));
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
case "codex": {
|
|
24
|
+
console.error(chalk.yellow("\nChatGPT uses CLI-based authentication managed by Codex."));
|
|
25
|
+
console.error(chalk.gray("\nTo authenticate, run:"));
|
|
26
|
+
console.error(chalk.cyan(` ${cliPath}`));
|
|
27
|
+
console.error(chalk.gray("\nCodex will guide you through authentication.\n"));
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
case "copilot": {
|
|
31
|
+
console.error(chalk.yellow("\nGitHub Copilot uses CLI-based authentication managed by the GitHub CLI."));
|
|
32
|
+
console.error(chalk.gray("\nTo authenticate, run:"));
|
|
33
|
+
console.error(chalk.cyan(` ${cliPath} auth login`));
|
|
34
|
+
console.error(chalk.gray("\nThe GitHub CLI will guide you through the OAuth login process.\n"));
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
default: {
|
|
38
|
+
assertNever(service);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
6
42
|
/**
|
|
7
43
|
* Set up authentication for a service
|
|
8
44
|
*/
|
|
9
|
-
export
|
|
45
|
+
export function authSetupCommand(options) {
|
|
10
46
|
const service = validateService(options.service);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
});
|
|
16
|
-
if (!cliPath)
|
|
17
|
-
return;
|
|
18
|
-
console.error(chalk.yellow("\nGemini uses CLI-based authentication managed by the Gemini CLI."));
|
|
19
|
-
console.error(chalk.gray("\nTo authenticate, run:"));
|
|
20
|
-
console.error(chalk.cyan(` ${cliPath}`));
|
|
21
|
-
console.error(chalk.gray("\nThe Gemini CLI will guide you through the OAuth login process.\n"));
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
if (service === "claude") {
|
|
25
|
-
const cliPath = resolveAuthCliDependencyOrReport("claude", {
|
|
26
|
-
setExitCode: true,
|
|
27
|
-
});
|
|
28
|
-
if (!cliPath)
|
|
29
|
-
return;
|
|
30
|
-
console.error(chalk.yellow("\nClaude uses CLI-based authentication managed by Claude Code."));
|
|
31
|
-
console.error(chalk.gray("\nTo authenticate, run:"));
|
|
32
|
-
console.error(chalk.cyan(` ${cliPath}`));
|
|
33
|
-
console.error(chalk.gray("\nClaude Code will guide you through authentication.\n"));
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
if (service === "chatgpt") {
|
|
37
|
-
const cliPath = resolveAuthCliDependencyOrReport("chatgpt", {
|
|
38
|
-
setExitCode: true,
|
|
39
|
-
});
|
|
40
|
-
if (!cliPath)
|
|
41
|
-
return;
|
|
42
|
-
console.error(chalk.yellow("\nChatGPT uses CLI-based authentication managed by Codex."));
|
|
43
|
-
console.error(chalk.gray("\nTo authenticate, run:"));
|
|
44
|
-
console.error(chalk.cyan(` ${cliPath}`));
|
|
45
|
-
console.error(chalk.gray("\nCodex will guide you through authentication.\n"));
|
|
47
|
+
const cliPath = resolveAuthCliDependencyOrReport(service, {
|
|
48
|
+
setExitCode: true,
|
|
49
|
+
});
|
|
50
|
+
if (!cliPath)
|
|
46
51
|
return;
|
|
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
|
-
}
|
|
62
|
-
const manager = new BrowserAuthManager({ headless: false });
|
|
63
|
-
try {
|
|
64
|
-
console.error(chalk.blue(`\nSetting up authentication for ${service}...\n`));
|
|
65
|
-
await manager.setupAuth(service);
|
|
66
|
-
console.error(chalk.green(`\n✓ Authentication for ${service} is complete!`));
|
|
67
|
-
console.error(chalk.gray(`\nYou can now run: ${chalk.cyan(`axusage --service ${service}`)}`));
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
console.error(chalk.red(`\n✗ Failed to set up authentication for ${service}: ${error instanceof Error ? error.message : String(error)}`));
|
|
71
|
-
process.exitCode = 1;
|
|
72
|
-
}
|
|
73
|
-
finally {
|
|
74
|
-
await manager.close();
|
|
75
|
-
}
|
|
52
|
+
printCliAuthInstructions(service, cliPath);
|
|
76
53
|
}
|
|
@@ -1,55 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getServiceDiagnostic } from "../services/service-diagnostics.js";
|
|
2
2
|
import { SUPPORTED_SERVICES, validateService, } from "../services/supported-service.js";
|
|
3
|
-
import { getStorageStatePathFor } from "../services/auth-storage-path.js";
|
|
4
|
-
import { getBrowserContextsDirectory } from "../services/app-paths.js";
|
|
5
|
-
import { AUTH_CLI_SERVICES, checkCliDependency, getAuthCliDependency, } from "../utils/check-cli-dependency.js";
|
|
6
3
|
import { chalk } from "../utils/color.js";
|
|
7
4
|
export function authStatusCommand(options) {
|
|
8
5
|
const servicesToCheck = options.service
|
|
9
6
|
? [validateService(options.service)]
|
|
10
7
|
: SUPPORTED_SERVICES;
|
|
11
|
-
const cliAuthServices = new Set(AUTH_CLI_SERVICES);
|
|
12
|
-
const dataDirectory = getBrowserContextsDirectory();
|
|
13
8
|
let hasFailures = false;
|
|
14
9
|
console.log(chalk.blue("\nAuthentication Status:\n"));
|
|
15
10
|
for (const service of servicesToCheck) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
}
|
|
36
|
-
const storagePath = getStorageStatePathFor(dataDirectory, service);
|
|
37
|
-
const hasAuth = existsSync(storagePath);
|
|
38
|
-
const status = hasAuth
|
|
39
|
-
? chalk.green("✓ Authenticated")
|
|
40
|
-
: chalk.gray("✗ Not authenticated");
|
|
41
|
-
if (!hasAuth) {
|
|
11
|
+
const diagnostic = getServiceDiagnostic(service);
|
|
12
|
+
const status = diagnostic.authenticated
|
|
13
|
+
? chalk.green("✓ authenticated")
|
|
14
|
+
: chalk.red("✗ not authenticated");
|
|
15
|
+
if (!diagnostic.authenticated) {
|
|
42
16
|
hasFailures = true;
|
|
43
17
|
}
|
|
44
18
|
console.log(`${chalk.bold(service)}: ${status}`);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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")}`));
|
|
19
|
+
if (diagnostic.authMethod) {
|
|
20
|
+
console.log(` ${chalk.dim("Method:")} ${chalk.dim(diagnostic.authMethod)}`);
|
|
21
|
+
}
|
|
53
22
|
}
|
|
54
23
|
if (hasFailures) {
|
|
55
24
|
process.exitCode = 1;
|
|
@@ -2,7 +2,6 @@ import type { ApiError, Result, ServiceUsageData } from "../types/domain.js";
|
|
|
2
2
|
export type UsageCommandOptions = {
|
|
3
3
|
readonly service?: string;
|
|
4
4
|
readonly format?: "text" | "tsv" | "json" | "prometheus";
|
|
5
|
-
readonly interactive?: boolean;
|
|
6
5
|
};
|
|
7
6
|
export declare function selectServicesToQuery(service?: string): string[];
|
|
8
7
|
export declare function fetchServiceUsage(serviceName: string): Promise<Result<ServiceUsageData, ApiError>>;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { ApiError as ApiErrorClass } from "../types/domain.js";
|
|
2
2
|
import { getAvailableServices, getServiceAdapter, } from "../services/service-adapter-registry.js";
|
|
3
|
-
const ALL_SERVICES = ["claude", "chatgpt", "github-copilot", "gemini"];
|
|
4
3
|
export function selectServicesToQuery(service) {
|
|
5
4
|
const normalized = service?.toLowerCase();
|
|
6
5
|
if (!service || normalized === "all")
|
|
7
|
-
return
|
|
6
|
+
return getAvailableServices();
|
|
8
7
|
return [service];
|
|
9
8
|
}
|
|
10
9
|
export async function fetchServiceUsage(serviceName) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { SupportedService } from "../services/supported-service.js";
|
|
2
1
|
import type { ApiError, Result, ServiceUsageData } from "../types/domain.js";
|
|
3
2
|
/**
|
|
4
3
|
* Check if an error message indicates an authentication issue.
|
|
@@ -18,12 +17,3 @@ export declare function isAuthError(message: string): boolean;
|
|
|
18
17
|
* Combines the result error check with auth error pattern matching.
|
|
19
18
|
*/
|
|
20
19
|
export declare function isAuthFailure(result: Result<ServiceUsageData, ApiError>): boolean;
|
|
21
|
-
/**
|
|
22
|
-
* Run auth setup for a service programmatically.
|
|
23
|
-
* Returns true if auth setup completed successfully.
|
|
24
|
-
* Times out after 5 minutes to prevent indefinite hangs.
|
|
25
|
-
*
|
|
26
|
-
* Note: Gemini uses CLI-based auth and cannot use browser-based re-auth.
|
|
27
|
-
* This function prints instructions and returns false for Gemini.
|
|
28
|
-
*/
|
|
29
|
-
export declare function runAuthSetup(service: SupportedService): Promise<boolean>;
|
|
@@ -1,9 +1,3 @@
|
|
|
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";
|
|
5
|
-
/** Timeout for authentication setup (5 minutes) */
|
|
6
|
-
const AUTH_SETUP_TIMEOUT_MS = 300_000;
|
|
7
1
|
/**
|
|
8
2
|
* Check if an error message indicates an authentication issue.
|
|
9
3
|
* Matches common authentication error patterns like "unauthorized", "401",
|
|
@@ -33,80 +27,9 @@ export function isAuthError(message) {
|
|
|
33
27
|
* Combines the result error check with auth error pattern matching.
|
|
34
28
|
*/
|
|
35
29
|
export function isAuthFailure(result) {
|
|
36
|
-
|
|
37
|
-
Boolean(result.error.message) &&
|
|
38
|
-
isAuthError(result.error.message));
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Run auth setup for a service programmatically.
|
|
42
|
-
* Returns true if auth setup completed successfully.
|
|
43
|
-
* Times out after 5 minutes to prevent indefinite hangs.
|
|
44
|
-
*
|
|
45
|
-
* Note: Gemini uses CLI-based auth and cannot use browser-based re-auth.
|
|
46
|
-
* This function prints instructions and returns false for Gemini.
|
|
47
|
-
*/
|
|
48
|
-
export async function runAuthSetup(service) {
|
|
49
|
-
// CLI-based auth cannot use browser auth flow
|
|
50
|
-
if (service === "gemini") {
|
|
51
|
-
const cliPath = resolveAuthCliDependencyOrReport("gemini");
|
|
52
|
-
if (!cliPath)
|
|
53
|
-
return false;
|
|
54
|
-
console.error(chalk.yellow("\nGemini uses CLI-based authentication managed by the Gemini CLI."));
|
|
55
|
-
console.error(chalk.gray("\nTo re-authenticate, run:"));
|
|
56
|
-
console.error(chalk.cyan(` ${cliPath}`));
|
|
57
|
-
console.error(chalk.gray("\nThe Gemini CLI will guide you through the OAuth login process.\n"));
|
|
30
|
+
if (result.ok)
|
|
58
31
|
return false;
|
|
59
|
-
|
|
60
|
-
if (service === "claude") {
|
|
61
|
-
const cliPath = resolveAuthCliDependencyOrReport("claude");
|
|
62
|
-
if (!cliPath)
|
|
63
|
-
return false;
|
|
64
|
-
console.error(chalk.yellow("\nClaude uses CLI-based authentication managed by Claude Code."));
|
|
65
|
-
console.error(chalk.gray("\nTo re-authenticate, run:"));
|
|
66
|
-
console.error(chalk.cyan(` ${cliPath}`));
|
|
67
|
-
console.error(chalk.gray("\nClaude Code will guide you through authentication.\n"));
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
if (service === "chatgpt") {
|
|
71
|
-
const cliPath = resolveAuthCliDependencyOrReport("chatgpt");
|
|
72
|
-
if (!cliPath)
|
|
73
|
-
return false;
|
|
74
|
-
console.error(chalk.yellow("\nChatGPT uses CLI-based authentication managed by Codex."));
|
|
75
|
-
console.error(chalk.gray("\nTo re-authenticate, run:"));
|
|
76
|
-
console.error(chalk.cyan(` ${cliPath}`));
|
|
77
|
-
console.error(chalk.gray("\nCodex will guide you through authentication.\n"));
|
|
78
|
-
return false;
|
|
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
|
-
}
|
|
85
|
-
const manager = new BrowserAuthManager({ headless: false });
|
|
86
|
-
let setupPromise;
|
|
87
|
-
let timeoutId;
|
|
88
|
-
try {
|
|
89
|
-
console.error(chalk.blue(`\nOpening browser for ${service} authentication...\n`));
|
|
90
|
-
setupPromise = manager.setupAuth(service);
|
|
91
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
92
|
-
timeoutId = setTimeout(() => {
|
|
93
|
-
reject(new Error("Authentication setup timed out after 5 minutes"));
|
|
94
|
-
}, AUTH_SETUP_TIMEOUT_MS);
|
|
95
|
-
});
|
|
96
|
-
await Promise.race([setupPromise, timeoutPromise]);
|
|
97
|
-
console.error(chalk.green(`\n✓ Authentication for ${service} is complete!\n`));
|
|
32
|
+
if (result.error.status === 401)
|
|
98
33
|
return true;
|
|
99
|
-
|
|
100
|
-
catch (error) {
|
|
101
|
-
console.error(chalk.red(`\n✗ Failed to set up authentication: ${error instanceof Error ? error.message : String(error)}`));
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
finally {
|
|
105
|
-
clearTimeout(timeoutId);
|
|
106
|
-
if (setupPromise) {
|
|
107
|
-
// Avoid unhandled rejections if the timeout wins the race.
|
|
108
|
-
void setupPromise.catch(() => { });
|
|
109
|
-
}
|
|
110
|
-
await manager.close();
|
|
111
|
-
}
|
|
34
|
+
return Boolean(result.error.message) && isAuthError(result.error.message);
|
|
112
35
|
}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import type { ServiceResult } from "../types/domain.js";
|
|
2
2
|
import type { UsageCommandOptions } from "./fetch-service-usage.js";
|
|
3
3
|
/**
|
|
4
|
-
* Fetches usage for services
|
|
5
|
-
* 1. Try all services in parallel first (fast path for valid credentials)
|
|
6
|
-
* 2. If any service fails with auth error, retry those sequentially with re-auth
|
|
7
|
-
*
|
|
8
|
-
* This maintains ~2s response time when credentials are valid while gracefully
|
|
9
|
-
* handling authentication failures that require interactive prompts.
|
|
4
|
+
* Fetches usage for all requested services in parallel.
|
|
10
5
|
*/
|
|
11
|
-
export declare function
|
|
6
|
+
export declare function fetchServicesInParallel(servicesToQuery: string[]): Promise<ServiceResult[]>;
|
|
12
7
|
/**
|
|
13
8
|
* Executes the usage command
|
|
14
9
|
*/
|
|
@@ -1,55 +1,23 @@
|
|
|
1
1
|
import { formatServiceUsageData, formatServiceUsageDataAsJson, formatServiceUsageAsTsv, toJsonObject, } from "../utils/format-service-usage.js";
|
|
2
2
|
import { formatPrometheusMetrics } from "../utils/format-prometheus-metrics.js";
|
|
3
3
|
import { fetchServiceUsage, selectServicesToQuery, } from "./fetch-service-usage.js";
|
|
4
|
-
import { fetchServiceUsageWithAutoReauth } from "./fetch-service-usage-with-reauth.js";
|
|
5
4
|
import { isAuthFailure } from "./run-auth-setup.js";
|
|
6
5
|
import { chalk } from "../utils/color.js";
|
|
7
6
|
/**
|
|
8
|
-
* Fetches usage for services
|
|
9
|
-
* 1. Try all services in parallel first (fast path for valid credentials)
|
|
10
|
-
* 2. If any service fails with auth error, retry those sequentially with re-auth
|
|
11
|
-
*
|
|
12
|
-
* This maintains ~2s response time when credentials are valid while gracefully
|
|
13
|
-
* handling authentication failures that require interactive prompts.
|
|
7
|
+
* Fetches usage for all requested services in parallel.
|
|
14
8
|
*/
|
|
15
|
-
export async function
|
|
16
|
-
|
|
17
|
-
const parallelResults = await Promise.all(servicesToQuery.map(async (serviceName) => {
|
|
9
|
+
export async function fetchServicesInParallel(servicesToQuery) {
|
|
10
|
+
return await Promise.all(servicesToQuery.map(async (serviceName) => {
|
|
18
11
|
const result = await fetchServiceUsage(serviceName);
|
|
19
12
|
return { service: serviceName, result };
|
|
20
13
|
}));
|
|
21
|
-
// Check for auth errors
|
|
22
|
-
const authFailures = parallelResults.filter(({ result }) => isAuthFailure(result));
|
|
23
|
-
// If no auth failures, return parallel results
|
|
24
|
-
if (authFailures.length === 0 || !interactive) {
|
|
25
|
-
return parallelResults;
|
|
26
|
-
}
|
|
27
|
-
const shouldShowProgress = process.stderr.isTTY;
|
|
28
|
-
// Retry auth failures sequentially with re-authentication
|
|
29
|
-
const retryResults = [];
|
|
30
|
-
for (const [index, { service }] of authFailures.entries()) {
|
|
31
|
-
if (shouldShowProgress) {
|
|
32
|
-
console.error(chalk.dim(`[${String(index + 1)}/${String(authFailures.length)}] Re-authenticating ${service}...`));
|
|
33
|
-
}
|
|
34
|
-
const result = await fetchServiceUsageWithAutoReauth(service, interactive);
|
|
35
|
-
retryResults.push(result);
|
|
36
|
-
}
|
|
37
|
-
if (shouldShowProgress) {
|
|
38
|
-
console.error(chalk.green(`✓ Completed ${String(retryResults.length)} re-authentication${retryResults.length === 1 ? "" : "s"}\n`));
|
|
39
|
-
}
|
|
40
|
-
// Merge results: keep successful parallel results, replace auth failures with retries
|
|
41
|
-
// Build a map for O(1) lookups instead of O(n²) find() calls
|
|
42
|
-
const retryMap = new Map(retryResults.map((r) => [r.service, r]));
|
|
43
|
-
return parallelResults.map((parallelResult) => retryMap.get(parallelResult.service) ?? parallelResult);
|
|
44
14
|
}
|
|
45
15
|
/**
|
|
46
16
|
* Executes the usage command
|
|
47
17
|
*/
|
|
48
18
|
export async function usageCommand(options) {
|
|
49
19
|
const servicesToQuery = selectServicesToQuery(options.service);
|
|
50
|
-
const
|
|
51
|
-
// Fetch usage data using hybrid parallel/sequential strategy
|
|
52
|
-
const results = await fetchServicesWithHybridStrategy(servicesToQuery, interactive);
|
|
20
|
+
const results = await fetchServicesInParallel(servicesToQuery);
|
|
53
21
|
// Collect successful results and errors
|
|
54
22
|
const successes = [];
|
|
55
23
|
const errors = [];
|
|
@@ -77,11 +45,11 @@ export async function usageCommand(options) {
|
|
|
77
45
|
console.error(); // Empty line for spacing
|
|
78
46
|
}
|
|
79
47
|
}
|
|
80
|
-
if (
|
|
48
|
+
if (authFailureServices.size > 0) {
|
|
81
49
|
const list = [...authFailureServices].join(", ");
|
|
82
50
|
console.error(chalk.gray(`Authentication required for: ${list}. ` +
|
|
83
|
-
"
|
|
84
|
-
"
|
|
51
|
+
"Run 'axusage --auth-setup <service>' for setup instructions, " +
|
|
52
|
+
"or authenticate directly with provider CLIs (claude/codex/gemini/gh auth login)."));
|
|
85
53
|
if (successes.length > 0) {
|
|
86
54
|
console.error();
|
|
87
55
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - "auto": Try vault first if configured and credential name provided, fallback to local
|
|
8
8
|
*/
|
|
9
9
|
import { z } from "zod";
|
|
10
|
+
import type { SupportedService } from "../services/supported-service.js";
|
|
10
11
|
/** Credential source type */
|
|
11
12
|
declare const CredentialSourceType: z.ZodEnum<{
|
|
12
13
|
auto: "auto";
|
|
@@ -19,21 +20,13 @@ interface ResolvedSourceConfig {
|
|
|
19
20
|
source: CredentialSourceType;
|
|
20
21
|
name: string | undefined;
|
|
21
22
|
}
|
|
22
|
-
/** Service IDs that support vault credentials (API-based services) */
|
|
23
|
-
type VaultSupportedServiceId = "claude" | "chatgpt" | "gemini";
|
|
24
|
-
/**
|
|
25
|
-
* All service IDs.
|
|
26
|
-
* Note: github-copilot uses GitHub token auth, not vault credentials,
|
|
27
|
-
* so it's excluded from VaultSupportedServiceId.
|
|
28
|
-
*/
|
|
29
|
-
type ServiceId = VaultSupportedServiceId | "github-copilot";
|
|
30
23
|
/**
|
|
31
24
|
* Get the resolved source config for a specific service.
|
|
32
25
|
*
|
|
33
|
-
* @param service - Service ID (e.g., "claude", "
|
|
26
|
+
* @param service - Service ID (e.g., "claude", "codex", "gemini")
|
|
34
27
|
* @returns Resolved config with source type and optional credential name
|
|
35
28
|
*/
|
|
36
|
-
declare function getServiceSourceConfig(service:
|
|
29
|
+
declare function getServiceSourceConfig(service: SupportedService): ResolvedSourceConfig;
|
|
37
30
|
/**
|
|
38
31
|
* Get the credential sources config file path.
|
|
39
32
|
*
|
|
@@ -42,5 +35,4 @@ declare function getServiceSourceConfig(service: ServiceId): ResolvedSourceConfi
|
|
|
42
35
|
* directory during construction).
|
|
43
36
|
*/
|
|
44
37
|
declare function getCredentialSourcesPath(): string;
|
|
45
|
-
export type { ServiceId, VaultSupportedServiceId };
|
|
46
38
|
export { getServiceSourceConfig, getCredentialSourcesPath };
|
|
@@ -99,7 +99,7 @@ function getCredentialSourceConfig() {
|
|
|
99
99
|
/**
|
|
100
100
|
* Get the resolved source config for a specific service.
|
|
101
101
|
*
|
|
102
|
-
* @param service - Service ID (e.g., "claude", "
|
|
102
|
+
* @param service - Service ID (e.g., "claude", "codex", "gemini")
|
|
103
103
|
* @returns Resolved config with source type and optional credential name
|
|
104
104
|
*/
|
|
105
105
|
function getServiceSourceConfig(service) {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - "vault": From axvault server
|
|
7
7
|
* - "auto": Try vault first if configured, fallback to local
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import type { SupportedService } from "./supported-service.js";
|
|
10
10
|
/**
|
|
11
11
|
* Get access token for a service.
|
|
12
12
|
*
|
|
@@ -15,7 +15,7 @@ import { type VaultSupportedServiceId } from "../config/credential-sources.js";
|
|
|
15
15
|
* - "vault": Fetch from axvault server (requires credential name)
|
|
16
16
|
* - "auto": Try vault if configured and name provided, fallback to local
|
|
17
17
|
*
|
|
18
|
-
* @param service - Service ID (e.g., "claude", "
|
|
18
|
+
* @param service - Service ID (e.g., "claude", "codex", "gemini")
|
|
19
19
|
* @returns Access token string or undefined if not available
|
|
20
20
|
*
|
|
21
21
|
* @example
|
|
@@ -24,5 +24,5 @@ import { type VaultSupportedServiceId } from "../config/credential-sources.js";
|
|
|
24
24
|
* console.error("No credentials found for Claude");
|
|
25
25
|
* }
|
|
26
26
|
*/
|
|
27
|
-
declare function getServiceAccessToken(service:
|
|
27
|
+
declare function getServiceAccessToken(service: SupportedService): Promise<string | undefined>;
|
|
28
28
|
export { getServiceAccessToken };
|
|
@@ -7,13 +7,8 @@
|
|
|
7
7
|
* - "auto": Try vault first if configured, fallback to local
|
|
8
8
|
*/
|
|
9
9
|
import { fetchVaultCredentials, getAgentAccessToken, isVaultConfigured, } from "axauth";
|
|
10
|
-
import { getServiceSourceConfig
|
|
11
|
-
|
|
12
|
-
const SERVICE_TO_AGENT = {
|
|
13
|
-
claude: "claude",
|
|
14
|
-
chatgpt: "codex", // ChatGPT and Codex both use OpenAI API credentials
|
|
15
|
-
gemini: "gemini",
|
|
16
|
-
};
|
|
10
|
+
import { getServiceSourceConfig } from "../config/credential-sources.js";
|
|
11
|
+
import { getCopilotTokenFromCustomGhPath } from "../utils/copilot-gh-token.js";
|
|
17
12
|
/**
|
|
18
13
|
* Extract access token from vault credentials.
|
|
19
14
|
*
|
|
@@ -84,12 +79,17 @@ async function fetchFromVault(agentId, credentialName) {
|
|
|
84
79
|
*/
|
|
85
80
|
async function fetchFromLocal(agentId) {
|
|
86
81
|
try {
|
|
87
|
-
|
|
82
|
+
const token = await getAgentAccessToken(agentId);
|
|
83
|
+
if (token)
|
|
84
|
+
return token;
|
|
88
85
|
}
|
|
89
86
|
catch (error) {
|
|
90
87
|
console.error(`[axusage] Local credential fetch error for ${agentId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
91
|
-
return undefined;
|
|
92
88
|
}
|
|
89
|
+
if (agentId === "copilot") {
|
|
90
|
+
return getCopilotTokenFromCustomGhPath();
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
95
95
|
* Get access token for a service.
|
|
@@ -99,7 +99,7 @@ async function fetchFromLocal(agentId) {
|
|
|
99
99
|
* - "vault": Fetch from axvault server (requires credential name)
|
|
100
100
|
* - "auto": Try vault if configured and name provided, fallback to local
|
|
101
101
|
*
|
|
102
|
-
* @param service - Service ID (e.g., "claude", "
|
|
102
|
+
* @param service - Service ID (e.g., "claude", "codex", "gemini")
|
|
103
103
|
* @returns Access token string or undefined if not available
|
|
104
104
|
*
|
|
105
105
|
* @example
|
|
@@ -110,7 +110,7 @@ async function fetchFromLocal(agentId) {
|
|
|
110
110
|
*/
|
|
111
111
|
async function getServiceAccessToken(service) {
|
|
112
112
|
const config = getServiceSourceConfig(service);
|
|
113
|
-
const agentId =
|
|
113
|
+
const agentId = service;
|
|
114
114
|
switch (config.source) {
|
|
115
115
|
case "local": {
|
|
116
116
|
return fetchFromLocal(agentId);
|
|
@@ -4,8 +4,8 @@ import type { ServiceAdapter } from "../types/domain.js";
|
|
|
4
4
|
*/
|
|
5
5
|
export declare const SERVICE_ADAPTERS: {
|
|
6
6
|
readonly claude: ServiceAdapter;
|
|
7
|
-
readonly
|
|
8
|
-
readonly
|
|
7
|
+
readonly codex: ServiceAdapter;
|
|
8
|
+
readonly copilot: ServiceAdapter;
|
|
9
9
|
readonly gemini: ServiceAdapter;
|
|
10
10
|
};
|
|
11
11
|
/**
|