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/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 (CLI or browser-based)")
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
- .option("--auth-clear <service>", "clear saved browser authentication for a service (moves files to system Trash)")
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
- await authSetupCommand({
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): Promise<void>;
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
- import { resolvePromptCapability } from "../utils/resolve-prompt-capability.js";
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 async function authSetupCommand(options) {
45
+ export function authSetupCommand(options) {
10
46
  const service = validateService(options.service);
11
- // CLI-based auth - users should run the native CLI directly
12
- if (service === "gemini") {
13
- const cliPath = resolveAuthCliDependencyOrReport("gemini", {
14
- setExitCode: true,
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 { existsSync } from "node:fs";
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
- 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
- }
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
- console.log(` ${chalk.dim("Storage:")} ${chalk.dim(storagePath)}`);
46
- }
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")}`));
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 [...ALL_SERVICES];
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
- return (!result.ok &&
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 using hybrid strategy:
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 fetchServicesWithHybridStrategy(servicesToQuery: string[], interactive: boolean): Promise<ServiceResult[]>;
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 using hybrid strategy:
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 fetchServicesWithHybridStrategy(servicesToQuery, interactive) {
16
- // First attempt: fetch all services in parallel
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 interactive = options.interactive ?? false;
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 (!interactive && authFailureServices.size > 0) {
48
+ if (authFailureServices.size > 0) {
81
49
  const list = [...authFailureServices].join(", ");
82
50
  console.error(chalk.gray(`Authentication required for: ${list}. ` +
83
- "For GitHub Copilot, run 'axusage --auth-setup github-copilot --interactive'. " +
84
- "For CLI-auth services, run the provider CLI (claude/codex/gemini), or re-run with '--interactive' to re-authenticate during fetch."));
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", "chatgpt", "gemini")
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: ServiceId): ResolvedSourceConfig;
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", "chatgpt", "gemini")
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 { type VaultSupportedServiceId } from "../config/credential-sources.js";
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", "chatgpt", "gemini")
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: VaultSupportedServiceId): Promise<string | undefined>;
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, } from "../config/credential-sources.js";
11
- /** Map service IDs to agent IDs for axauth/vault */
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
- return await getAgentAccessToken(agentId);
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", "chatgpt", "gemini")
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 = SERVICE_TO_AGENT[service];
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 chatgpt: ServiceAdapter;
8
- readonly "github-copilot": ServiceAdapter;
7
+ readonly codex: ServiceAdapter;
8
+ readonly copilot: ServiceAdapter;
9
9
  readonly gemini: ServiceAdapter;
10
10
  };
11
11
  /**