axusage 2.1.0 → 2.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.
- package/dist/adapters/chatgpt.js +2 -2
- package/dist/adapters/claude.js +2 -2
- package/dist/adapters/gemini.js +2 -2
- package/dist/config/credential-sources.d.ts +38 -0
- package/dist/config/credential-sources.js +95 -0
- package/dist/services/get-service-access-token.d.ts +28 -0
- package/dist/services/get-service-access-token.js +146 -0
- package/package.json +10 -15
package/dist/adapters/chatgpt.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getAgentAccessToken } from "axauth";
|
|
2
1
|
import { ApiError } from "../types/domain.js";
|
|
2
|
+
import { getServiceAccessToken } from "../services/get-service-access-token.js";
|
|
3
3
|
import { ChatGPTUsageResponse as ChatGPTUsageResponseSchema } from "../types/chatgpt.js";
|
|
4
4
|
import { toServiceUsageData } from "./parse-chatgpt-usage.js";
|
|
5
5
|
const API_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
@@ -12,7 +12,7 @@ const API_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
|
12
12
|
export const chatGPTAdapter = {
|
|
13
13
|
name: "ChatGPT",
|
|
14
14
|
async fetchUsage() {
|
|
15
|
-
const accessToken = await
|
|
15
|
+
const accessToken = await getServiceAccessToken("chatgpt");
|
|
16
16
|
if (!accessToken) {
|
|
17
17
|
return {
|
|
18
18
|
ok: false,
|
package/dist/adapters/claude.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getAgentAccessToken } from "axauth";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
import { ApiError } from "../types/domain.js";
|
|
3
|
+
import { getServiceAccessToken } from "../services/get-service-access-token.js";
|
|
4
4
|
import { UsageResponse as UsageResponseSchema } from "../types/usage.js";
|
|
5
5
|
import { coalesceClaudeUsageResponse } from "./coalesce-claude-usage-response.js";
|
|
6
6
|
import { toServiceUsageData } from "./parse-claude-usage.js";
|
|
@@ -43,7 +43,7 @@ async function fetchPlanType(accessToken) {
|
|
|
43
43
|
export const claudeAdapter = {
|
|
44
44
|
name: "Claude",
|
|
45
45
|
async fetchUsage() {
|
|
46
|
-
const accessToken = await
|
|
46
|
+
const accessToken = await getServiceAccessToken("claude");
|
|
47
47
|
if (!accessToken) {
|
|
48
48
|
return {
|
|
49
49
|
ok: false,
|
package/dist/adapters/gemini.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getAgentAccessToken } from "axauth";
|
|
2
1
|
import { ApiError } from "../types/domain.js";
|
|
3
2
|
import { fetchGeminiQuota, fetchGeminiProject, } from "../services/gemini-api.js";
|
|
3
|
+
import { getServiceAccessToken } from "../services/get-service-access-token.js";
|
|
4
4
|
import { toServiceUsageData } from "./parse-gemini-usage.js";
|
|
5
5
|
/**
|
|
6
6
|
* Gemini service adapter using direct API access.
|
|
@@ -11,7 +11,7 @@ import { toServiceUsageData } from "./parse-gemini-usage.js";
|
|
|
11
11
|
export const geminiAdapter = {
|
|
12
12
|
name: "Gemini",
|
|
13
13
|
async fetchUsage() {
|
|
14
|
-
const accessToken = await
|
|
14
|
+
const accessToken = await getServiceAccessToken("gemini");
|
|
15
15
|
if (!accessToken) {
|
|
16
16
|
return {
|
|
17
17
|
ok: false,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for credential sources per service.
|
|
3
|
+
*
|
|
4
|
+
* Supports three modes:
|
|
5
|
+
* - "local": Use local credentials from axauth (default behavior)
|
|
6
|
+
* - "vault": Fetch credentials from axvault server
|
|
7
|
+
* - "auto": Try vault first if configured and credential name provided, fallback to local
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
/** Credential source type */
|
|
11
|
+
declare const CredentialSourceType: z.ZodEnum<{
|
|
12
|
+
auto: "auto";
|
|
13
|
+
local: "local";
|
|
14
|
+
vault: "vault";
|
|
15
|
+
}>;
|
|
16
|
+
type CredentialSourceType = z.infer<typeof CredentialSourceType>;
|
|
17
|
+
/** Resolved source config with normalized fields */
|
|
18
|
+
interface ResolvedSourceConfig {
|
|
19
|
+
source: CredentialSourceType;
|
|
20
|
+
name: string | undefined;
|
|
21
|
+
}
|
|
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
|
+
/**
|
|
31
|
+
* Get the resolved source config for a specific service.
|
|
32
|
+
*
|
|
33
|
+
* @param service - Service ID (e.g., "claude", "chatgpt", "gemini")
|
|
34
|
+
* @returns Resolved config with source type and optional credential name
|
|
35
|
+
*/
|
|
36
|
+
declare function getServiceSourceConfig(service: ServiceId): ResolvedSourceConfig;
|
|
37
|
+
export type { ServiceId, VaultSupportedServiceId };
|
|
38
|
+
export { getServiceSourceConfig };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for credential sources per service.
|
|
3
|
+
*
|
|
4
|
+
* Supports three modes:
|
|
5
|
+
* - "local": Use local credentials from axauth (default behavior)
|
|
6
|
+
* - "vault": Fetch credentials from axvault server
|
|
7
|
+
* - "auto": Try vault first if configured and credential name provided, fallback to local
|
|
8
|
+
*/
|
|
9
|
+
import Conf from "conf";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
/** Credential source type */
|
|
12
|
+
const CredentialSourceType = z.enum(["auto", "local", "vault"]);
|
|
13
|
+
/** Service source config - either a string shorthand or object with name */
|
|
14
|
+
const ServiceSourceConfig = z.union([
|
|
15
|
+
CredentialSourceType,
|
|
16
|
+
z.object({
|
|
17
|
+
source: CredentialSourceType,
|
|
18
|
+
name: z.string().optional(),
|
|
19
|
+
}),
|
|
20
|
+
]);
|
|
21
|
+
/** Full sources config - map of service ID to source config */
|
|
22
|
+
const SourcesConfig = z.record(z.string(), ServiceSourceConfig);
|
|
23
|
+
// Lazy-initialized config instance
|
|
24
|
+
let configInstance;
|
|
25
|
+
function getConfig() {
|
|
26
|
+
if (!configInstance) {
|
|
27
|
+
configInstance = new Conf({
|
|
28
|
+
projectName: "axusage",
|
|
29
|
+
schema: {
|
|
30
|
+
sources: {
|
|
31
|
+
type: "object",
|
|
32
|
+
additionalProperties: true,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return configInstance;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the full credential source configuration.
|
|
41
|
+
*
|
|
42
|
+
* Priority:
|
|
43
|
+
* 1. AXUSAGE_SOURCES environment variable (flat JSON)
|
|
44
|
+
* 2. Config file sources key
|
|
45
|
+
* 3. Empty object (defaults apply per-service)
|
|
46
|
+
*/
|
|
47
|
+
function getCredentialSourceConfig() {
|
|
48
|
+
// Priority 1: Environment variable
|
|
49
|
+
const environmentVariable = process.env.AXUSAGE_SOURCES;
|
|
50
|
+
if (environmentVariable) {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = SourcesConfig.parse(JSON.parse(environmentVariable));
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const reason = error instanceof SyntaxError
|
|
57
|
+
? "invalid JSON syntax"
|
|
58
|
+
: "schema validation failed";
|
|
59
|
+
console.error(`Warning: AXUSAGE_SOURCES ${reason}, falling back to config file`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Priority 2: Config file
|
|
63
|
+
const config = getConfig();
|
|
64
|
+
const fileConfig = config.get("sources");
|
|
65
|
+
if (fileConfig) {
|
|
66
|
+
const parsed = SourcesConfig.safeParse(fileConfig);
|
|
67
|
+
if (parsed.success) {
|
|
68
|
+
return parsed.data;
|
|
69
|
+
}
|
|
70
|
+
console.error("Warning: Config file contains invalid sources, using defaults");
|
|
71
|
+
}
|
|
72
|
+
// Priority 3: Empty (defaults apply)
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get the resolved source config for a specific service.
|
|
77
|
+
*
|
|
78
|
+
* @param service - Service ID (e.g., "claude", "chatgpt", "gemini")
|
|
79
|
+
* @returns Resolved config with source type and optional credential name
|
|
80
|
+
*/
|
|
81
|
+
function getServiceSourceConfig(service) {
|
|
82
|
+
const config = getCredentialSourceConfig();
|
|
83
|
+
const serviceConfig = config[service];
|
|
84
|
+
// Default: auto mode with no credential name
|
|
85
|
+
if (serviceConfig === undefined) {
|
|
86
|
+
return { source: "auto", name: undefined };
|
|
87
|
+
}
|
|
88
|
+
// String shorthand: just the source type
|
|
89
|
+
if (typeof serviceConfig === "string") {
|
|
90
|
+
return { source: serviceConfig, name: undefined };
|
|
91
|
+
}
|
|
92
|
+
// Object: source and name
|
|
93
|
+
return { source: serviceConfig.source, name: serviceConfig.name };
|
|
94
|
+
}
|
|
95
|
+
export { getServiceSourceConfig };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified credential fetcher for services.
|
|
3
|
+
*
|
|
4
|
+
* Fetches access tokens based on per-service configuration:
|
|
5
|
+
* - "local": From local axauth credential store
|
|
6
|
+
* - "vault": From axvault server
|
|
7
|
+
* - "auto": Try vault first if configured, fallback to local
|
|
8
|
+
*/
|
|
9
|
+
import { type VaultSupportedServiceId } from "../config/credential-sources.js";
|
|
10
|
+
/**
|
|
11
|
+
* Get access token for a service.
|
|
12
|
+
*
|
|
13
|
+
* Uses the configured credential source for the service:
|
|
14
|
+
* - "local": Fetch from local axauth credential store
|
|
15
|
+
* - "vault": Fetch from axvault server (requires credential name)
|
|
16
|
+
* - "auto": Try vault if configured and name provided, fallback to local
|
|
17
|
+
*
|
|
18
|
+
* @param service - Service ID (e.g., "claude", "chatgpt", "gemini")
|
|
19
|
+
* @returns Access token string or undefined if not available
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const token = await getServiceAccessToken("claude");
|
|
23
|
+
* if (!token) {
|
|
24
|
+
* console.error("No credentials found for Claude");
|
|
25
|
+
* }
|
|
26
|
+
*/
|
|
27
|
+
declare function getServiceAccessToken(service: VaultSupportedServiceId): Promise<string | undefined>;
|
|
28
|
+
export { getServiceAccessToken };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified credential fetcher for services.
|
|
3
|
+
*
|
|
4
|
+
* Fetches access tokens based on per-service configuration:
|
|
5
|
+
* - "local": From local axauth credential store
|
|
6
|
+
* - "vault": From axvault server
|
|
7
|
+
* - "auto": Try vault first if configured, fallback to local
|
|
8
|
+
*/
|
|
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
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Extract access token from vault credentials.
|
|
19
|
+
*
|
|
20
|
+
* Different credential types store tokens differently:
|
|
21
|
+
* - oauth-credentials: access_token (Gemini style) or tokens.access_token (Codex/OpenAI style)
|
|
22
|
+
* - oauth-token: accessToken field (Claude style)
|
|
23
|
+
* - api-key: apiKey field
|
|
24
|
+
*/
|
|
25
|
+
function extractAccessToken(credentials) {
|
|
26
|
+
if (!credentials)
|
|
27
|
+
return undefined;
|
|
28
|
+
const { data } = credentials;
|
|
29
|
+
// Try accessToken first (Claude oauth-token style, camelCase)
|
|
30
|
+
if (typeof data.accessToken === "string") {
|
|
31
|
+
return data.accessToken;
|
|
32
|
+
}
|
|
33
|
+
// Try access_token at top level (Gemini oauth-credentials style, snake_case)
|
|
34
|
+
if (typeof data.access_token === "string") {
|
|
35
|
+
return data.access_token;
|
|
36
|
+
}
|
|
37
|
+
// Try tokens.access_token (Codex/OpenAI oauth-credentials style)
|
|
38
|
+
if (data.tokens &&
|
|
39
|
+
typeof data.tokens === "object" &&
|
|
40
|
+
"access_token" in data.tokens &&
|
|
41
|
+
typeof data.tokens.access_token === "string") {
|
|
42
|
+
return data.tokens.access_token;
|
|
43
|
+
}
|
|
44
|
+
// Try apiKey as fallback (api-key type)
|
|
45
|
+
if (typeof data.apiKey === "string") {
|
|
46
|
+
return data.apiKey;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Fetch access token from vault.
|
|
52
|
+
*
|
|
53
|
+
* @returns Access token string or undefined if not available
|
|
54
|
+
*/
|
|
55
|
+
async function fetchFromVault(agentId, credentialName) {
|
|
56
|
+
try {
|
|
57
|
+
const result = await fetchVaultCredentials({
|
|
58
|
+
agentId,
|
|
59
|
+
name: credentialName,
|
|
60
|
+
});
|
|
61
|
+
if (!result.ok) {
|
|
62
|
+
// Log warning for debugging, but don't fail hard
|
|
63
|
+
if (result.reason !== "not-configured" && result.reason !== "not-found") {
|
|
64
|
+
console.error(`[axusage] Vault fetch failed for ${agentId}/${credentialName}: ${result.reason}`);
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
const token = extractAccessToken(result.credentials);
|
|
69
|
+
if (!token) {
|
|
70
|
+
console.error(`[axusage] Vault credentials for ${agentId}/${credentialName} missing access token. ` +
|
|
71
|
+
`Credential type: ${result.credentials.type}`);
|
|
72
|
+
}
|
|
73
|
+
return token;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error(`[axusage] Vault fetch error for ${agentId}/${credentialName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Fetch access token from local credential store.
|
|
82
|
+
*
|
|
83
|
+
* @returns Access token string or undefined if not available
|
|
84
|
+
*/
|
|
85
|
+
async function fetchFromLocal(agentId) {
|
|
86
|
+
try {
|
|
87
|
+
return await getAgentAccessToken(agentId);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.error(`[axusage] Local credential fetch error for ${agentId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get access token for a service.
|
|
96
|
+
*
|
|
97
|
+
* Uses the configured credential source for the service:
|
|
98
|
+
* - "local": Fetch from local axauth credential store
|
|
99
|
+
* - "vault": Fetch from axvault server (requires credential name)
|
|
100
|
+
* - "auto": Try vault if configured and name provided, fallback to local
|
|
101
|
+
*
|
|
102
|
+
* @param service - Service ID (e.g., "claude", "chatgpt", "gemini")
|
|
103
|
+
* @returns Access token string or undefined if not available
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* const token = await getServiceAccessToken("claude");
|
|
107
|
+
* if (!token) {
|
|
108
|
+
* console.error("No credentials found for Claude");
|
|
109
|
+
* }
|
|
110
|
+
*/
|
|
111
|
+
async function getServiceAccessToken(service) {
|
|
112
|
+
const config = getServiceSourceConfig(service);
|
|
113
|
+
const agentId = SERVICE_TO_AGENT[service];
|
|
114
|
+
switch (config.source) {
|
|
115
|
+
case "local": {
|
|
116
|
+
return fetchFromLocal(agentId);
|
|
117
|
+
}
|
|
118
|
+
case "vault": {
|
|
119
|
+
if (!config.name) {
|
|
120
|
+
console.error(`[axusage] Vault source requires credential name for ${service}. ` +
|
|
121
|
+
`Set {"${service}": {"source": "vault", "name": "your-name"}} in config.`);
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
const token = await fetchFromVault(agentId, config.name);
|
|
125
|
+
if (!token) {
|
|
126
|
+
// User explicitly selected vault but it failed - provide clear feedback
|
|
127
|
+
console.error(`[axusage] Vault credential fetch failed for ${service}. ` +
|
|
128
|
+
`Check that vault is configured (AXVAULT env) and credential "${config.name}" exists.`);
|
|
129
|
+
}
|
|
130
|
+
return token;
|
|
131
|
+
}
|
|
132
|
+
case "auto": {
|
|
133
|
+
// Auto mode: try vault first if configured and name provided
|
|
134
|
+
if (config.name && isVaultConfigured()) {
|
|
135
|
+
const vaultToken = await fetchFromVault(agentId, config.name);
|
|
136
|
+
if (vaultToken) {
|
|
137
|
+
return vaultToken;
|
|
138
|
+
}
|
|
139
|
+
// Fallback to local if vault failed
|
|
140
|
+
}
|
|
141
|
+
// No credential name or vault not configured: use local only
|
|
142
|
+
return fetchFromLocal(agentId);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export { getServiceAccessToken };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "axusage",
|
|
3
3
|
"author": "Łukasz Jerciński",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.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",
|
|
@@ -54,33 +54,28 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@commander-js/extra-typings": "^14.0.0",
|
|
57
|
-
"axauth": "^1.
|
|
57
|
+
"axauth": "^1.11.2",
|
|
58
58
|
"chalk": "^5.6.2",
|
|
59
59
|
"commander": "^14.0.2",
|
|
60
|
+
"conf": "^15.0.2",
|
|
60
61
|
"env-paths": "^3.0.0",
|
|
61
62
|
"playwright": "^1.57.0",
|
|
62
63
|
"prom-client": "^15.1.3",
|
|
63
64
|
"trash": "^10.0.1",
|
|
64
|
-
"zod": "^4.
|
|
65
|
+
"zod": "^4.3.5"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
67
|
-
"@eslint/compat": "^2.0.0",
|
|
68
|
-
"@eslint/js": "^9.39.2",
|
|
69
68
|
"@total-typescript/ts-reset": "^0.6.1",
|
|
70
|
-
"@types/node": "^25.0.
|
|
71
|
-
"@vitest/coverage-v8": "^4.0.
|
|
72
|
-
"@vitest/eslint-plugin": "^1.5.2",
|
|
69
|
+
"@types/node": "^25.0.8",
|
|
70
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
73
71
|
"eslint": "^9.39.2",
|
|
74
|
-
"eslint-config-
|
|
75
|
-
"
|
|
76
|
-
"fta-check": "^1.5.0",
|
|
72
|
+
"eslint-config-axkit": "^1.0.0",
|
|
73
|
+
"fta-check": "^1.5.1",
|
|
77
74
|
"fta-cli": "^3.0.0",
|
|
78
|
-
"
|
|
79
|
-
"knip": "^5.73.4",
|
|
75
|
+
"knip": "^5.81.0",
|
|
80
76
|
"prettier": "3.7.4",
|
|
81
77
|
"semantic-release": "^25.0.2",
|
|
82
78
|
"typescript": "^5.9.3",
|
|
83
|
-
"
|
|
84
|
-
"vitest": "^4.0.15"
|
|
79
|
+
"vitest": "^4.0.17"
|
|
85
80
|
}
|
|
86
81
|
}
|