axusage 3.0.0 → 3.2.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 (84) 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 +18 -38
  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 +12 -12
  22. package/dist/config/credential-sources.js +15 -2
  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/supported-service.d.ts +6 -2
  28. package/dist/services/supported-service.js +2 -6
  29. package/dist/types/{chatgpt.d.ts → codex.d.ts} +4 -4
  30. package/dist/types/{chatgpt.js → codex.js} +6 -6
  31. package/dist/types/copilot.d.ts +14 -0
  32. package/dist/types/copilot.js +21 -0
  33. package/dist/utils/check-cli-dependency.d.ts +2 -4
  34. package/dist/utils/check-cli-dependency.js +7 -4
  35. package/dist/utils/copilot-gh-token.d.ts +1 -0
  36. package/dist/utils/copilot-gh-token.js +38 -0
  37. package/dist/utils/validate-root-options.d.ts +0 -3
  38. package/dist/utils/validate-root-options.js +2 -6
  39. package/package.json +15 -19
  40. package/dist/adapters/github-copilot.d.ts +0 -6
  41. package/dist/adapters/github-copilot.js +0 -57
  42. package/dist/adapters/parse-github-copilot-usage.d.ts +0 -23
  43. package/dist/adapters/parse-github-copilot-usage.js +0 -78
  44. package/dist/commands/auth-clear-command.d.ts +0 -7
  45. package/dist/commands/auth-clear-command.js +0 -84
  46. package/dist/commands/fetch-service-usage-with-reauth.d.ts +0 -7
  47. package/dist/commands/fetch-service-usage-with-reauth.js +0 -45
  48. package/dist/services/app-paths.d.ts +0 -9
  49. package/dist/services/app-paths.js +0 -39
  50. package/dist/services/auth-storage-path.d.ts +0 -3
  51. package/dist/services/auth-storage-path.js +0 -7
  52. package/dist/services/auth-timeouts.d.ts +0 -4
  53. package/dist/services/auth-timeouts.js +0 -4
  54. package/dist/services/browser-auth-manager.d.ts +0 -49
  55. package/dist/services/browser-auth-manager.js +0 -113
  56. package/dist/services/create-auth-context.d.ts +0 -8
  57. package/dist/services/create-auth-context.js +0 -35
  58. package/dist/services/do-setup-auth.d.ts +0 -3
  59. package/dist/services/do-setup-auth.js +0 -19
  60. package/dist/services/fetch-json-with-context.d.ts +0 -5
  61. package/dist/services/fetch-json-with-context.js +0 -37
  62. package/dist/services/launch-chromium.d.ts +0 -6
  63. package/dist/services/launch-chromium.js +0 -20
  64. package/dist/services/persist-storage-state.d.ts +0 -6
  65. package/dist/services/persist-storage-state.js +0 -16
  66. package/dist/services/request-service.d.ts +0 -3
  67. package/dist/services/request-service.js +0 -4
  68. package/dist/services/service-auth-configs.d.ts +0 -15
  69. package/dist/services/service-auth-configs.js +0 -26
  70. package/dist/services/setup-auth-flow.d.ts +0 -3
  71. package/dist/services/setup-auth-flow.js +0 -67
  72. package/dist/services/shared-browser-auth-manager.d.ts +0 -4
  73. package/dist/services/shared-browser-auth-manager.js +0 -80
  74. package/dist/services/verify-session.d.ts +0 -2
  75. package/dist/services/verify-session.js +0 -25
  76. package/dist/services/wait-for-login.d.ts +0 -6
  77. package/dist/services/wait-for-login.js +0 -115
  78. package/dist/types/github-copilot.d.ts +0 -21
  79. package/dist/types/github-copilot.js +0 -27
  80. package/dist/utils/resolve-prompt-capability.d.ts +0 -1
  81. package/dist/utils/resolve-prompt-capability.js +0 -3
  82. package/dist/utils/write-atomic-json.d.ts +0 -1
  83. package/dist/utils/write-atomic-json.js +0 -56
  84. /package/dist/adapters/{parse-chatgpt-usage.js → parse-codex-usage.js} +0 -0
@@ -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
  /**
@@ -1,14 +1,14 @@
1
- import { chatGPTAdapter } from "../adapters/chatgpt.js";
1
+ import { codexAdapter } from "../adapters/codex.js";
2
2
  import { claudeAdapter } from "../adapters/claude.js";
3
3
  import { geminiAdapter } from "../adapters/gemini.js";
4
- import { githubCopilotAdapter } from "../adapters/github-copilot.js";
4
+ import { copilotAdapter } from "../adapters/copilot.js";
5
5
  /**
6
6
  * Registry of available service adapters
7
7
  */
8
8
  export const SERVICE_ADAPTERS = {
9
9
  claude: claudeAdapter,
10
- chatgpt: chatGPTAdapter,
11
- "github-copilot": githubCopilotAdapter,
10
+ codex: codexAdapter,
11
+ copilot: copilotAdapter,
12
12
  gemini: geminiAdapter,
13
13
  };
14
14
  /**
@@ -1,6 +1,10 @@
1
+ import type { AgentCli } from "axauth";
1
2
  /**
2
- * Supported service names for usage tracking
3
+ * Supported service names for usage tracking.
4
+ *
5
+ * Derived from the canonical AGENT_CLIS list, excluding agents
6
+ * that axusage doesn't yet support (opencode).
3
7
  */
4
- export type SupportedService = "claude" | "chatgpt" | "github-copilot" | "gemini";
8
+ export type SupportedService = Exclude<AgentCli, "opencode">;
5
9
  export declare const SUPPORTED_SERVICES: SupportedService[];
6
10
  export declare function validateService(service: string | undefined): SupportedService;
@@ -1,9 +1,5 @@
1
- export const SUPPORTED_SERVICES = [
2
- "claude",
3
- "chatgpt",
4
- "github-copilot",
5
- "gemini",
6
- ];
1
+ import { AGENT_CLIS } from "axauth";
2
+ export const SUPPORTED_SERVICES = AGENT_CLIS.filter((cli) => cli !== "opencode");
7
3
  export function validateService(service) {
8
4
  if (!service) {
9
5
  throw new Error(`Service is required. Supported services: ${SUPPORTED_SERVICES.join(", ")}. ` +
@@ -2,14 +2,14 @@ import { z } from "zod";
2
2
  /**
3
3
  * ChatGPT API response schemas
4
4
  */
5
- export declare const ChatGPTRateLimitWindow: z.ZodObject<{
5
+ export declare const CodexRateLimitWindow: z.ZodObject<{
6
6
  used_percent: z.ZodNumber;
7
7
  limit_window_seconds: z.ZodNumber;
8
8
  reset_after_seconds: z.ZodNumber;
9
9
  reset_at: z.ZodNumber;
10
10
  }, z.core.$strip>;
11
- export type ChatGPTRateLimitWindow = z.infer<typeof ChatGPTRateLimitWindow>;
12
- export declare const ChatGPTUsageResponse: z.ZodObject<{
11
+ export type CodexRateLimitWindow = z.infer<typeof CodexRateLimitWindow>;
12
+ export declare const CodexUsageResponse: z.ZodObject<{
13
13
  plan_type: z.ZodString;
14
14
  rate_limit: z.ZodObject<{
15
15
  allowed: z.ZodBoolean;
@@ -29,4 +29,4 @@ export declare const ChatGPTUsageResponse: z.ZodObject<{
29
29
  }, z.core.$strip>;
30
30
  credits: z.ZodNullable<z.ZodUnknown>;
31
31
  }, z.core.$strip>;
32
- export type ChatGPTUsageResponse = z.infer<typeof ChatGPTUsageResponse>;
32
+ export type CodexUsageResponse = z.infer<typeof CodexUsageResponse>;
@@ -2,20 +2,20 @@ import { z } from "zod";
2
2
  /**
3
3
  * ChatGPT API response schemas
4
4
  */
5
- export const ChatGPTRateLimitWindow = z.object({
5
+ export const CodexRateLimitWindow = z.object({
6
6
  used_percent: z.number(),
7
7
  limit_window_seconds: z.number(),
8
8
  reset_after_seconds: z.number(),
9
9
  reset_at: z.number(), // Unix timestamp
10
10
  });
11
- const ChatGPTRateLimit = z.object({
11
+ const CodexRateLimit = z.object({
12
12
  allowed: z.boolean(),
13
13
  limit_reached: z.boolean(),
14
- primary_window: ChatGPTRateLimitWindow,
15
- secondary_window: ChatGPTRateLimitWindow,
14
+ primary_window: CodexRateLimitWindow,
15
+ secondary_window: CodexRateLimitWindow,
16
16
  });
17
- export const ChatGPTUsageResponse = z.object({
17
+ export const CodexUsageResponse = z.object({
18
18
  plan_type: z.string(),
19
- rate_limit: ChatGPTRateLimit,
19
+ rate_limit: CodexRateLimit,
20
20
  credits: z.unknown().nullable(),
21
21
  });
@@ -0,0 +1,14 @@
1
+ import { z } from "zod";
2
+ export declare const CopilotUsageResponse: z.ZodObject<{
3
+ quota_reset_date_utc: z.ZodString;
4
+ copilot_plan: z.ZodString;
5
+ quota_snapshots: z.ZodObject<{
6
+ premium_interactions: z.ZodObject<{
7
+ entitlement: z.ZodNumber;
8
+ remaining: z.ZodNumber;
9
+ percent_remaining: z.ZodNumber;
10
+ unlimited: z.ZodOptional<z.ZodBoolean>;
11
+ }, z.core.$strip>;
12
+ }, z.core.$strip>;
13
+ }, z.core.$strip>;
14
+ export type CopilotUsageResponse = z.infer<typeof CopilotUsageResponse>;
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * GitHub Copilot internal API response schema
4
+ * Endpoint: GET https://api.github.com/copilot_internal/user
5
+ */
6
+ const PremiumInteractionsSnapshot = z.object({
7
+ entitlement: z.number(),
8
+ remaining: z.number(),
9
+ percent_remaining: z.number(),
10
+ unlimited: z.boolean().optional(),
11
+ });
12
+ const QuotaSnapshots = z.object({
13
+ premium_interactions: PremiumInteractionsSnapshot,
14
+ });
15
+ export const CopilotUsageResponse = z.object({
16
+ quota_reset_date_utc: z
17
+ .string()
18
+ .refine((s) => !Number.isNaN(new Date(s).getTime()), "Invalid date"),
19
+ copilot_plan: z.string(),
20
+ quota_snapshots: QuotaSnapshots,
21
+ });
@@ -3,8 +3,7 @@ type CliDependency = {
3
3
  readonly envVar: string;
4
4
  readonly installHint: string;
5
5
  };
6
- declare const AUTH_CLI_SERVICES: readonly ["claude", "chatgpt", "gemini"];
7
- type AuthCliService = (typeof AUTH_CLI_SERVICES)[number];
6
+ type AuthCliService = "claude" | "codex" | "copilot" | "gemini";
8
7
  export declare function getAuthCliDependency(service: AuthCliService): CliDependency;
9
8
  export declare function checkCliDependency(dep: CliDependency): {
10
9
  ok: boolean;
@@ -21,5 +20,4 @@ export declare function ensureAuthCliDependency(service: AuthCliService): {
21
20
  export declare function resolveAuthCliDependencyOrReport(service: AuthCliService, options?: {
22
21
  readonly setExitCode?: boolean;
23
22
  }): string | undefined;
24
- export { AUTH_CLI_SERVICES };
25
- export type { AuthCliService };
23
+ export {};
@@ -11,13 +11,17 @@ const CLI_DEPENDENCIES = {
11
11
  envVar: "AXUSAGE_CODEX_PATH",
12
12
  installHint: "npm install -g @openai/codex",
13
13
  },
14
+ gh: {
15
+ command: "gh",
16
+ envVar: "AXUSAGE_GH_PATH",
17
+ installHint: "https://cli.github.com/ or brew install gh",
18
+ },
14
19
  gemini: {
15
20
  command: "gemini",
16
21
  envVar: "AXUSAGE_GEMINI_PATH",
17
22
  installHint: "npm install -g @google/gemini-cli",
18
23
  },
19
24
  };
20
- const AUTH_CLI_SERVICES = ["claude", "chatgpt", "gemini"];
21
25
  function resolveCliDependencyTimeout() {
22
26
  const raw = process.env.AXUSAGE_CLI_TIMEOUT_MS;
23
27
  if (!raw)
@@ -28,8 +32,8 @@ function resolveCliDependencyTimeout() {
28
32
  return Math.round(parsed);
29
33
  }
30
34
  export function getAuthCliDependency(service) {
31
- if (service === "chatgpt")
32
- return CLI_DEPENDENCIES.codex;
35
+ if (service === "copilot")
36
+ return CLI_DEPENDENCIES.gh;
33
37
  return CLI_DEPENDENCIES[service];
34
38
  }
35
39
  function resolveCliDependencyPath(dep) {
@@ -78,4 +82,3 @@ function reportMissingCliDependency(dependency, path) {
78
82
  console.error(chalk.gray(` 2. Set ${dependency.envVar}=/path/to/${dependency.command}`));
79
83
  console.error(chalk.gray("Try 'axusage --help' for requirements and overrides."));
80
84
  }
81
- export { AUTH_CLI_SERVICES };
@@ -0,0 +1 @@
1
+ export declare function getCopilotTokenFromCustomGhPath(): string | undefined;
@@ -0,0 +1,38 @@
1
+ import { execFileSync } from "node:child_process";
2
+ const GH_PATH_OVERRIDE_ENV = "AXUSAGE_GH_PATH";
3
+ const DEFAULT_TIMEOUT_MS = 5000;
4
+ function resolveCliTimeoutMs() {
5
+ const rawValue = process.env.AXUSAGE_CLI_TIMEOUT_MS;
6
+ if (!rawValue)
7
+ return DEFAULT_TIMEOUT_MS;
8
+ const parsed = Number(rawValue);
9
+ if (!Number.isFinite(parsed) || parsed <= 0)
10
+ return DEFAULT_TIMEOUT_MS;
11
+ return Math.round(parsed);
12
+ }
13
+ function resolveGhOverridePath() {
14
+ const value = process.env[GH_PATH_OVERRIDE_ENV]?.trim();
15
+ if (!value)
16
+ return undefined;
17
+ return value;
18
+ }
19
+ export function getCopilotTokenFromCustomGhPath() {
20
+ const path = resolveGhOverridePath();
21
+ if (!path)
22
+ return undefined;
23
+ try {
24
+ const token = execFileSync(path, ["auth", "token"], {
25
+ encoding: "utf8",
26
+ stdio: ["pipe", "pipe", "pipe"],
27
+ timeout: resolveCliTimeoutMs(),
28
+ }).trim();
29
+ // Copilot requires fine-grained tokens; reject classic PATs.
30
+ if (token && !token.startsWith("ghp_")) {
31
+ return token;
32
+ }
33
+ }
34
+ catch {
35
+ // Ignore command failures and fall back to other auth sources.
36
+ }
37
+ return undefined;
38
+ }
@@ -2,10 +2,7 @@ import type { UsageCommandOptions } from "../commands/fetch-service-usage.js";
2
2
  export type RootOptions = {
3
3
  readonly authSetup?: string;
4
4
  readonly authStatus?: string | boolean;
5
- readonly authClear?: string;
6
- readonly force?: boolean;
7
5
  readonly service?: UsageCommandOptions["service"];
8
6
  readonly format?: UsageCommandOptions["format"];
9
- readonly interactive?: UsageCommandOptions["interactive"];
10
7
  };
11
8
  export declare function getRootOptionsError(options: RootOptions, formatSource?: string): string | undefined;
@@ -1,13 +1,9 @@
1
1
  export function getRootOptionsError(options, formatSource) {
2
2
  // Commander sets optional args to `true` when provided without a value.
3
3
  const authSelectionCount = Number(Boolean(options.authSetup)) +
4
- Number(options.authStatus !== undefined) +
5
- Number(Boolean(options.authClear));
4
+ Number(options.authStatus !== undefined);
6
5
  if (authSelectionCount > 1) {
7
- return "Use only one of --auth-setup, --auth-status, or --auth-clear.";
8
- }
9
- if (options.force && !options.authClear) {
10
- return "--force is only supported with --auth-clear.";
6
+ return "Use only one of --auth-setup or --auth-status.";
11
7
  }
12
8
  const hasExplicitFormat = formatSource === "cli";
13
9
  const hasUsageOptions = Boolean(options.service) || hasExplicitFormat;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "axusage",
3
3
  "author": "Łukasz Jerciński",
4
4
  "license": "MIT",
5
- "version": "3.0.0",
5
+ "version": "3.2.0",
6
6
  "description": "Monitor API usage across Claude, ChatGPT, GitHub Copilot, and Gemini from a single CLI",
7
7
  "repository": {
8
8
  "type": "git",
@@ -19,7 +19,6 @@
19
19
  "LICENSE"
20
20
  ],
21
21
  "scripts": {
22
- "postinstall": "playwright install chromium",
23
22
  "prepare": "git config core.hooksPath .githooks",
24
23
  "prepublishOnly": "pnpm run rebuild",
25
24
  "build": "tsc -p tsconfig.app.json",
@@ -48,35 +47,32 @@
48
47
  "llm",
49
48
  "monitoring"
50
49
  ],
51
- "packageManager": "pnpm@10.25.0",
50
+ "packageManager": "pnpm@10.30.1",
52
51
  "engines": {
53
52
  "node": ">=22.14.0"
54
53
  },
55
54
  "dependencies": {
56
55
  "@commander-js/extra-typings": "^14.0.0",
57
- "@inquirer/prompts": "^8.2.0",
58
- "axauth": "^1.11.2",
56
+ "axauth": "^3.1.6",
59
57
  "chalk": "^5.6.2",
60
- "commander": "^14.0.2",
61
- "conf": "^15.0.2",
62
- "env-paths": "^3.0.0",
63
- "playwright": "^1.57.0",
58
+ "commander": "^14.0.3",
59
+ "conf": "^15.1.0",
60
+ "env-paths": "^4.0.0",
64
61
  "prom-client": "^15.1.3",
65
- "trash": "^10.0.1",
66
- "zod": "^4.3.5"
62
+ "zod": "^4.3.6"
67
63
  },
68
64
  "devDependencies": {
69
65
  "@total-typescript/ts-reset": "^0.6.1",
70
- "@types/node": "^25.0.8",
71
- "@vitest/coverage-v8": "^4.0.17",
72
- "eslint": "^9.39.2",
73
- "eslint-config-axkit": "^1.0.0",
66
+ "@types/node": "^25.3.0",
67
+ "@vitest/coverage-v8": "^4.0.18",
68
+ "eslint": "^10.0.1",
69
+ "eslint-config-axkit": "^1.2.1",
74
70
  "fta-check": "^1.5.1",
75
71
  "fta-cli": "^3.0.0",
76
- "knip": "^5.81.0",
77
- "prettier": "3.7.4",
78
- "semantic-release": "^25.0.2",
72
+ "knip": "^5.85.0",
73
+ "prettier": "3.8.1",
74
+ "semantic-release": "^25.0.3",
79
75
  "typescript": "^5.9.3",
80
- "vitest": "^4.0.17"
76
+ "vitest": "^4.0.18"
81
77
  }
82
78
  }
@@ -1,6 +0,0 @@
1
- import type { ServiceAdapter } from "../types/domain.js";
2
- /** Functional core is extracted to ./parse-github-copilot-usage.ts */
3
- /**
4
- * GitHub Copilot service adapter
5
- */
6
- export declare const githubCopilotAdapter: ServiceAdapter;
@@ -1,57 +0,0 @@
1
- import { ApiError } from "../types/domain.js";
2
- import { GitHubCopilotUsageResponse as GitHubCopilotUsageResponseSchema } from "../types/github-copilot.js";
3
- import { toServiceUsageData } from "./parse-github-copilot-usage.js";
4
- import { acquireAuthManager, releaseAuthManager, } from "../services/shared-browser-auth-manager.js";
5
- // Copilot web fetches entitlements from this endpoint (requires GitHub session cookies)
6
- const API_URL = "https://github.com/github-copilot/chat/entitlement";
7
- /** Functional core is extracted to ./parse-github-copilot-usage.ts */
8
- /**
9
- * GitHub Copilot service adapter
10
- */
11
- export const githubCopilotAdapter = {
12
- name: "GitHub Copilot",
13
- async fetchUsage() {
14
- const manager = acquireAuthManager();
15
- try {
16
- if (!manager.hasAuth("github-copilot")) {
17
- return {
18
- ok: false,
19
- error: new ApiError("No saved authentication for github-copilot. " +
20
- "Run 'axusage --auth-setup github-copilot --interactive' first."),
21
- };
22
- }
23
- const body = await manager.makeAuthenticatedRequest("github-copilot", API_URL);
24
- const data = JSON.parse(body);
25
- const parseResult = GitHubCopilotUsageResponseSchema.safeParse(data);
26
- if (!parseResult.success) {
27
- return {
28
- ok: false,
29
- error: new ApiError(`Invalid response format: ${parseResult.error.message}`, undefined, data),
30
- };
31
- }
32
- try {
33
- return {
34
- ok: true,
35
- value: toServiceUsageData(parseResult.data),
36
- };
37
- }
38
- catch (error) {
39
- return {
40
- ok: false,
41
- error: new ApiError(error instanceof Error
42
- ? error.message
43
- : "Unable to parse GitHub Copilot reset date", undefined, parseResult.data.quotas.resetDate),
44
- };
45
- }
46
- }
47
- catch (error) {
48
- return {
49
- ok: false,
50
- error: new ApiError(`Browser authentication failed: ${error instanceof Error ? error.message : String(error)}`),
51
- };
52
- }
53
- finally {
54
- await releaseAuthManager();
55
- }
56
- },
57
- };
@@ -1,23 +0,0 @@
1
- import type { ServiceUsageData } from "../types/domain.js";
2
- import type { GitHubCopilotUsageResponse } from "../types/github-copilot.js";
3
- /**
4
- * Parses GitHub reset date (YYYY-MM-DD) into a UTC Date
5
- */
6
- export declare function parseResetDate(resetDateString: string): Date;
7
- /**
8
- * Calculates monthly period duration ending at the reset date
9
- *
10
- * Determines the period start by going back one month from the reset
11
- * date and clamping the day to the last valid day of that previous
12
- * month. This handles edge cases like Jan 31 → Feb 28/29 where the
13
- * target month has fewer days than the reset date's day component.
14
- *
15
- * Example:
16
- * For resetDate = 2025-03-31, calculates duration from 2025-02-28 (clamped)
17
- * to 2025-03-31.
18
- */
19
- export declare function calculatePeriodDuration(resetDate: Date): number;
20
- /**
21
- * Converts GitHub Copilot response to common domain model
22
- */
23
- export declare function toServiceUsageData(response: GitHubCopilotUsageResponse): ServiceUsageData;
@@ -1,78 +0,0 @@
1
- const MS_PER_DAY = 24 * 60 * 60 * 1000;
2
- /**
3
- * Parses GitHub reset date (YYYY-MM-DD) into a UTC Date
4
- */
5
- export function parseResetDate(resetDateString) {
6
- const parts = resetDateString.split("-");
7
- if (parts.length !== 3) {
8
- throw new Error(`Invalid reset date format: ${resetDateString}`);
9
- }
10
- const [yearString, monthString, dayString] = parts;
11
- if (!yearString || !monthString || !dayString) {
12
- throw new Error(`Invalid reset date components: ${resetDateString}`);
13
- }
14
- const year = Number(yearString);
15
- const month = Number(monthString);
16
- const day = Number(dayString);
17
- if (Number.isNaN(year) ||
18
- Number.isNaN(month) ||
19
- Number.isNaN(day) ||
20
- month < 1 ||
21
- month > 12 ||
22
- day < 1 ||
23
- day > 31) {
24
- throw new Error(`Invalid reset date components: ${resetDateString}`);
25
- }
26
- return new Date(Date.UTC(year, month - 1, day, 0, 0, 0));
27
- }
28
- /**
29
- * Calculates monthly period duration ending at the reset date
30
- *
31
- * Determines the period start by going back one month from the reset
32
- * date and clamping the day to the last valid day of that previous
33
- * month. This handles edge cases like Jan 31 → Feb 28/29 where the
34
- * target month has fewer days than the reset date's day component.
35
- *
36
- * Example:
37
- * For resetDate = 2025-03-31, calculates duration from 2025-02-28 (clamped)
38
- * to 2025-03-31.
39
- */
40
- export function calculatePeriodDuration(resetDate) {
41
- const periodEnd = resetDate.getTime();
42
- // Determine previous month and clamp day to its last day
43
- const year = resetDate.getUTCFullYear();
44
- const month = resetDate.getUTCMonth(); // 0-based
45
- const day = resetDate.getUTCDate();
46
- // First day of current month in UTC
47
- const firstOfCurrentMonth = Date.UTC(year, month, 1, 0, 0, 0);
48
- // Last day of previous month: subtract 1 day from first of current month
49
- const lastPreviousMonthDate = new Date(firstOfCurrentMonth - MS_PER_DAY);
50
- const lastPreviousMonthDay = lastPreviousMonthDate.getUTCDate();
51
- const previousMonth = lastPreviousMonthDate.getUTCMonth();
52
- const previousYear = lastPreviousMonthDate.getUTCFullYear();
53
- const targetDay = Math.min(day, lastPreviousMonthDay);
54
- const periodStart = Date.UTC(previousYear, previousMonth, targetDay, 0, 0, 0);
55
- return Math.max(periodEnd - periodStart, 0);
56
- }
57
- /**
58
- * Converts GitHub Copilot response to common domain model
59
- */
60
- export function toServiceUsageData(response) {
61
- const resetDate = parseResetDate(response.quotas.resetDate);
62
- const periodDurationMs = calculatePeriodDuration(resetDate);
63
- const used = response.quotas.limits.premiumInteractions -
64
- response.quotas.remaining.premiumInteractions;
65
- const utilization = (used / response.quotas.limits.premiumInteractions) * 100;
66
- return {
67
- service: "GitHub Copilot",
68
- planType: response.plan,
69
- windows: [
70
- {
71
- name: "Monthly Premium Interactions",
72
- utilization: Math.round(utilization * 100) / 100,
73
- resetsAt: resetDate,
74
- periodDurationMs,
75
- },
76
- ],
77
- };
78
- }
@@ -1,7 +0,0 @@
1
- type AuthClearOptions = {
2
- readonly service?: string;
3
- readonly interactive?: boolean;
4
- readonly force?: boolean;
5
- };
6
- export declare function authClearCommand(options: AuthClearOptions): Promise<void>;
7
- export {};
@@ -1,84 +0,0 @@
1
- import { confirm } from "@inquirer/prompts";
2
- import { existsSync, readdirSync } from "node:fs";
3
- import path from "node:path";
4
- import trash from "trash";
5
- import { validateService } from "../services/supported-service.js";
6
- import { getAuthMetaPathFor, getStorageStatePathFor, } from "../services/auth-storage-path.js";
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
- }
29
- export async function authClearCommand(options) {
30
- const service = validateService(options.service);
31
- const dataDirectory = getBrowserContextsDirectory();
32
- const storage = getStorageStatePathFor(dataDirectory, service);
33
- const meta = getAuthMetaPathFor(dataDirectory, service);
34
- try {
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])];
38
- if (targets.length === 0) {
39
- console.error(chalk.gray(`\nNo saved authentication found for ${service}.`));
40
- return;
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
- }
77
- await trash(targets, { glob: false });
78
- console.error(chalk.green(`\n✓ Cleared authentication for ${service}`));
79
- }
80
- catch (error) {
81
- console.error(chalk.red(`\n✗ Failed to clear authentication for ${service}: ${error instanceof Error ? error.message : String(error)}`));
82
- process.exitCode = 1;
83
- }
84
- }