axauth 1.3.0 → 1.5.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/auth/adapter.d.ts +11 -0
- package/dist/auth/agents/claude.js +10 -0
- package/dist/auth/agents/codex.js +18 -0
- package/dist/auth/agents/copilot.js +4 -0
- package/dist/auth/agents/gemini.js +4 -0
- package/dist/auth/agents/opencode.js +5 -0
- package/dist/auth/extract-creds-from-directory.d.ts +16 -0
- package/dist/auth/extract-creds-from-directory.js +37 -0
- package/dist/auth/is-token-expired.d.ts +25 -0
- package/dist/auth/is-token-expired.js +77 -0
- package/dist/auth/refresh-credentials.d.ts +60 -0
- package/dist/auth/refresh-credentials.js +150 -0
- package/dist/auth/registry.d.ts +22 -15
- package/dist/auth/registry.js +53 -17
- package/dist/auth/spawn-agent.d.ts +13 -0
- package/dist/auth/spawn-agent.js +40 -0
- package/dist/commands/auth.d.ts +1 -1
- package/dist/commands/auth.js +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/package.json +2 -2
package/dist/auth/adapter.d.ts
CHANGED
|
@@ -73,6 +73,10 @@ interface RemoveOptions {
|
|
|
73
73
|
interface TokenOptions {
|
|
74
74
|
/** Provider ID for multi-provider agents (e.g., "anthropic", "openai") */
|
|
75
75
|
provider?: string;
|
|
76
|
+
/** Skip auto-refresh even for expired tokens (default: false) */
|
|
77
|
+
skipRefresh?: boolean;
|
|
78
|
+
/** Timeout for refresh operation in ms (default: 10000) */
|
|
79
|
+
refreshTimeout?: number;
|
|
76
80
|
}
|
|
77
81
|
/** Capabilities that an adapter supports */
|
|
78
82
|
interface AdapterCapabilities {
|
|
@@ -113,6 +117,13 @@ interface AuthAdapter {
|
|
|
113
117
|
* Use {@link getAccessToken} to extract the token in a uniform way.
|
|
114
118
|
*/
|
|
115
119
|
extractRawCredentials(): Credentials | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* Extract raw credentials from a specific directory.
|
|
122
|
+
*
|
|
123
|
+
* Used by token refresh to read credentials from a temp directory.
|
|
124
|
+
* Returns undefined if no credentials found at that location.
|
|
125
|
+
*/
|
|
126
|
+
extractRawCredentialsFromDirectory?(directory: string): Credentials | undefined;
|
|
116
127
|
/**
|
|
117
128
|
* Install credentials to storage.
|
|
118
129
|
*
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Supports OAuth via keychain (macOS) or file, and API key via env var.
|
|
5
5
|
*/
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
|
|
7
8
|
import { isMacOS } from "../keychain.js";
|
|
8
9
|
import { resolveCustomDirectory } from "../validate-directories.js";
|
|
9
10
|
import { AGENT_ID, checkAuth, deleteFileCreds, deleteKeychainCreds, extractRawCredentials, getDefaultCredsFilePath, saveFileCreds, saveKeychainCreds, } from "./claude-storage.js";
|
|
@@ -27,6 +28,15 @@ const claudeCodeAdapter = {
|
|
|
27
28
|
return undefined;
|
|
28
29
|
return { agent: AGENT_ID, ...result };
|
|
29
30
|
},
|
|
31
|
+
extractRawCredentialsFromDirectory(directory) {
|
|
32
|
+
// Unwrap claudeAiOauth wrapper (matches loadFileCreds behavior)
|
|
33
|
+
return extractCredsFromDirectory(AGENT_ID, directory, CREDS_FILE_NAME, (file) => {
|
|
34
|
+
const data = file.claudeAiOauth;
|
|
35
|
+
return typeof data === "object" && data !== null
|
|
36
|
+
? data
|
|
37
|
+
: undefined;
|
|
38
|
+
});
|
|
39
|
+
},
|
|
30
40
|
installCredentials(creds, options) {
|
|
31
41
|
// Resolve custom directory with validation
|
|
32
42
|
const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Supports API key and OAuth via keychain (macOS) or file.
|
|
5
5
|
*/
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import { ensureDirectory } from "../file-storage.js";
|
|
8
9
|
import { isMacOS } from "../keychain.js";
|
|
@@ -23,6 +24,23 @@ const codexAdapter = {
|
|
|
23
24
|
},
|
|
24
25
|
checkAuth,
|
|
25
26
|
extractRawCredentials,
|
|
27
|
+
extractRawCredentialsFromDirectory(directory) {
|
|
28
|
+
const credentialsPath = path.join(directory, CREDS_FILE_NAME);
|
|
29
|
+
try {
|
|
30
|
+
if (!existsSync(credentialsPath))
|
|
31
|
+
return undefined;
|
|
32
|
+
const parsed = JSON.parse(readFileSync(credentialsPath, "utf8"));
|
|
33
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
34
|
+
return undefined;
|
|
35
|
+
const data = parsed;
|
|
36
|
+
// Determine type from data structure
|
|
37
|
+
const type = data.api_key ? "api-key" : "oauth";
|
|
38
|
+
return { agent: AGENT_ID, type, data };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
26
44
|
installCredentials(creds, options) {
|
|
27
45
|
// Resolve custom directory with validation
|
|
28
46
|
const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Also supports GitHub CLI (`gh auth login`) as a fallback.
|
|
6
6
|
*/
|
|
7
7
|
import path from "node:path";
|
|
8
|
+
import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
|
|
8
9
|
import { ensureDirectory } from "../file-storage.js";
|
|
9
10
|
import { isMacOS } from "../keychain.js";
|
|
10
11
|
import { resolveCustomDirectory } from "../validate-directories.js";
|
|
@@ -48,6 +49,9 @@ const copilotAdapter = {
|
|
|
48
49
|
data: { accessToken: result.token, _source: result.source },
|
|
49
50
|
};
|
|
50
51
|
},
|
|
52
|
+
extractRawCredentialsFromDirectory(directory) {
|
|
53
|
+
return extractCredsFromDirectory(AGENT_ID, directory, CREDS_FILE_NAME);
|
|
54
|
+
},
|
|
51
55
|
installCredentials(creds, options) {
|
|
52
56
|
// Resolve custom directory with validation
|
|
53
57
|
const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Supports OAuth via keychain (macOS) or file. API key auth is env-var only.
|
|
5
5
|
*/
|
|
6
|
+
import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
|
|
6
7
|
import { findCredentials } from "./gemini-auth-check.js";
|
|
7
8
|
import { installOAuthCredentials, removeGeminiCredentials, } from "./gemini-install.js";
|
|
8
9
|
const AGENT_ID = "gemini";
|
|
@@ -48,6 +49,9 @@ const geminiAdapter = {
|
|
|
48
49
|
},
|
|
49
50
|
};
|
|
50
51
|
},
|
|
52
|
+
extractRawCredentialsFromDirectory(directory) {
|
|
53
|
+
return extractCredsFromDirectory(AGENT_ID, directory, "oauth_creds.json");
|
|
54
|
+
},
|
|
51
55
|
installCredentials(creds, options) {
|
|
52
56
|
if (creds.type === "api-key") {
|
|
53
57
|
return {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
11
11
|
import path from "node:path";
|
|
12
|
+
import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
|
|
12
13
|
import { resolveAgentDataDirectory } from "axshared";
|
|
13
14
|
import { resolveCustomDirectory } from "../validate-directories.js";
|
|
14
15
|
import { extractTokenFromEntry, OpenCodeAuth } from "./opencode-schema.js";
|
|
@@ -82,6 +83,10 @@ const opencodeAdapter = {
|
|
|
82
83
|
}
|
|
83
84
|
return undefined;
|
|
84
85
|
},
|
|
86
|
+
extractRawCredentialsFromDirectory(directory) {
|
|
87
|
+
// Only return if there's at least one provider
|
|
88
|
+
return extractCredsFromDirectory(AGENT_ID, directory, CREDS_FILE_NAME, (data) => (Object.keys(data).length > 0 ? data : undefined));
|
|
89
|
+
},
|
|
85
90
|
installCredentials(creds, options) {
|
|
86
91
|
// Resolve custom directory with validation (OpenCode supports separation)
|
|
87
92
|
const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common utility for extracting credentials from a directory.
|
|
3
|
+
*/
|
|
4
|
+
import type { AgentCli } from "axshared";
|
|
5
|
+
import type { Credentials } from "./types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Extract credentials from a JSON file in a directory.
|
|
8
|
+
*
|
|
9
|
+
* @param agentId - The agent ID
|
|
10
|
+
* @param directory - Directory to read from
|
|
11
|
+
* @param fileName - Name of the credentials file
|
|
12
|
+
* @param transform - Optional transform to apply to file contents
|
|
13
|
+
* @returns Credentials or undefined if not found
|
|
14
|
+
*/
|
|
15
|
+
declare function extractCredsFromDirectory(agentId: AgentCli, directory: string, fileName: string, transform?: (data: Record<string, unknown>) => Record<string, unknown> | undefined): Credentials | undefined;
|
|
16
|
+
export { extractCredsFromDirectory };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common utility for extracting credentials from a directory.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
/**
|
|
7
|
+
* Extract credentials from a JSON file in a directory.
|
|
8
|
+
*
|
|
9
|
+
* @param agentId - The agent ID
|
|
10
|
+
* @param directory - Directory to read from
|
|
11
|
+
* @param fileName - Name of the credentials file
|
|
12
|
+
* @param transform - Optional transform to apply to file contents
|
|
13
|
+
* @returns Credentials or undefined if not found
|
|
14
|
+
*/
|
|
15
|
+
function extractCredsFromDirectory(agentId, directory, fileName, transform) {
|
|
16
|
+
const credentialsPath = path.join(directory, fileName);
|
|
17
|
+
try {
|
|
18
|
+
if (!existsSync(credentialsPath))
|
|
19
|
+
return undefined;
|
|
20
|
+
const fileContent = JSON.parse(readFileSync(credentialsPath, "utf8"));
|
|
21
|
+
// JSON.parse can return null, primitives, or arrays - we need an object
|
|
22
|
+
if (typeof fileContent !== "object" ||
|
|
23
|
+
fileContent === null ||
|
|
24
|
+
Array.isArray(fileContent)) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const record = fileContent;
|
|
28
|
+
const data = transform ? transform(record) : record;
|
|
29
|
+
if (!data)
|
|
30
|
+
return undefined;
|
|
31
|
+
return { agent: agentId, type: "oauth", data };
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export { extractCredsFromDirectory };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token and credential expiry check utilities.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Check if a JWT token is expired.
|
|
6
|
+
*
|
|
7
|
+
* Decodes the JWT payload and checks the `exp` claim against current time.
|
|
8
|
+
*
|
|
9
|
+
* @param token - The JWT token string
|
|
10
|
+
* @param bufferSeconds - Buffer before actual expiry (default: 60s)
|
|
11
|
+
* @returns true if expired, false if valid, undefined if not a valid JWT
|
|
12
|
+
*/
|
|
13
|
+
declare function isTokenExpired(token: string, bufferSeconds?: number): boolean | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Check if credentials have expired using explicit timestamp fields.
|
|
16
|
+
*
|
|
17
|
+
* Checks common OAuth credential expiry fields like `expiry_date` (Google)
|
|
18
|
+
* or `expires_at` (generic). Useful for opaque tokens that aren't JWTs.
|
|
19
|
+
*
|
|
20
|
+
* @param data - The credential data object
|
|
21
|
+
* @param bufferSeconds - Buffer before actual expiry (default: 60s)
|
|
22
|
+
* @returns true if expired, false if valid, undefined if no expiry field found
|
|
23
|
+
*/
|
|
24
|
+
declare function isCredentialExpired(data: Record<string, unknown>, bufferSeconds?: number): boolean | undefined;
|
|
25
|
+
export { isCredentialExpired, isTokenExpired };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token and credential expiry check utilities.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Timestamp threshold to distinguish seconds from milliseconds.
|
|
6
|
+
*
|
|
7
|
+
* Values below this are assumed to be Unix timestamps in seconds,
|
|
8
|
+
* values above are assumed to be milliseconds.
|
|
9
|
+
*
|
|
10
|
+
* 10 billion seconds ≈ year 2286, so any current timestamp in
|
|
11
|
+
* seconds will be below this threshold.
|
|
12
|
+
*/
|
|
13
|
+
const SECONDS_THRESHOLD = 10_000_000_000;
|
|
14
|
+
/**
|
|
15
|
+
* Check if a JWT token is expired.
|
|
16
|
+
*
|
|
17
|
+
* Decodes the JWT payload and checks the `exp` claim against current time.
|
|
18
|
+
*
|
|
19
|
+
* @param token - The JWT token string
|
|
20
|
+
* @param bufferSeconds - Buffer before actual expiry (default: 60s)
|
|
21
|
+
* @returns true if expired, false if valid, undefined if not a valid JWT
|
|
22
|
+
*/
|
|
23
|
+
function isTokenExpired(token, bufferSeconds = 60) {
|
|
24
|
+
const parts = token.split(".");
|
|
25
|
+
if (parts.length !== 3)
|
|
26
|
+
return undefined; // Not a JWT
|
|
27
|
+
const payloadPart = parts[1];
|
|
28
|
+
if (!payloadPart)
|
|
29
|
+
return undefined;
|
|
30
|
+
try {
|
|
31
|
+
// Convert base64url to standard base64 (JWTs use base64url encoding)
|
|
32
|
+
const base64 = payloadPart.replaceAll("-", "+").replaceAll("_", "/");
|
|
33
|
+
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "=");
|
|
34
|
+
const payload = JSON.parse(atob(padded));
|
|
35
|
+
const exp = payload.exp;
|
|
36
|
+
if (typeof exp !== "number")
|
|
37
|
+
return undefined; // No exp claim
|
|
38
|
+
const nowSeconds = Date.now() / 1000;
|
|
39
|
+
return nowSeconds > exp - bufferSeconds;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined; // Invalid JWT
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if credentials have expired using explicit timestamp fields.
|
|
47
|
+
*
|
|
48
|
+
* Checks common OAuth credential expiry fields like `expiry_date` (Google)
|
|
49
|
+
* or `expires_at` (generic). Useful for opaque tokens that aren't JWTs.
|
|
50
|
+
*
|
|
51
|
+
* @param data - The credential data object
|
|
52
|
+
* @param bufferSeconds - Buffer before actual expiry (default: 60s)
|
|
53
|
+
* @returns true if expired, false if valid, undefined if no expiry field found
|
|
54
|
+
*/
|
|
55
|
+
function isCredentialExpired(data, bufferSeconds = 60) {
|
|
56
|
+
// Check common expiry field names (in order of preference)
|
|
57
|
+
// expiry_date: Google OAuth (milliseconds)
|
|
58
|
+
// expires_at: Generic OAuth (seconds or milliseconds)
|
|
59
|
+
const expiryDate = data.expiry_date;
|
|
60
|
+
const expiresAt = data.expires_at;
|
|
61
|
+
let expiryTimestampMs;
|
|
62
|
+
if (typeof expiryDate === "number") {
|
|
63
|
+
// Google uses milliseconds
|
|
64
|
+
expiryTimestampMs = expiryDate;
|
|
65
|
+
}
|
|
66
|
+
else if (typeof expiresAt === "number") {
|
|
67
|
+
// Could be seconds or milliseconds - heuristic based on SECONDS_THRESHOLD
|
|
68
|
+
expiryTimestampMs =
|
|
69
|
+
expiresAt < SECONDS_THRESHOLD ? expiresAt * 1000 : expiresAt;
|
|
70
|
+
}
|
|
71
|
+
if (expiryTimestampMs === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
const nowMs = Date.now();
|
|
74
|
+
const bufferMs = bufferSeconds * 1000;
|
|
75
|
+
return nowMs > expiryTimestampMs - bufferMs;
|
|
76
|
+
}
|
|
77
|
+
export { isCredentialExpired, isTokenExpired };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token refresh via agent subprocess.
|
|
3
|
+
*
|
|
4
|
+
* Refreshes credentials by running the agent in an isolated temp config,
|
|
5
|
+
* triggering its internal auth refresh mechanism.
|
|
6
|
+
*/
|
|
7
|
+
import type { Credentials } from "./types.js";
|
|
8
|
+
/** Options for credential refresh */
|
|
9
|
+
interface RefreshOptions {
|
|
10
|
+
/** Timeout in ms (default: 30000) */
|
|
11
|
+
timeout?: number;
|
|
12
|
+
/** Provider for multi-provider agents (OpenCode) */
|
|
13
|
+
provider?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Result of a credential refresh attempt */
|
|
16
|
+
type RefreshResult = {
|
|
17
|
+
ok: true;
|
|
18
|
+
credentials: Credentials;
|
|
19
|
+
} | {
|
|
20
|
+
ok: false;
|
|
21
|
+
error: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Refresh credentials by spawning the agent briefly.
|
|
25
|
+
*
|
|
26
|
+
* Creates an isolated temp config directory, installs credentials there,
|
|
27
|
+
* and runs the agent with a minimal prompt to trigger its internal refresh.
|
|
28
|
+
*
|
|
29
|
+
* @param creds - The credentials to refresh (must contain refresh_token for OAuth)
|
|
30
|
+
* @param options - Refresh options (timeout, provider for multi-provider agents)
|
|
31
|
+
* @returns RefreshResult with new credentials or error
|
|
32
|
+
*/
|
|
33
|
+
declare function refreshCredentials(creds: Credentials, options?: RefreshOptions): Promise<RefreshResult>;
|
|
34
|
+
/** Result of refresh and persist operation */
|
|
35
|
+
type RefreshAndPersistResult = {
|
|
36
|
+
ok: true;
|
|
37
|
+
credentials: Credentials;
|
|
38
|
+
} | {
|
|
39
|
+
ok: false;
|
|
40
|
+
staleCredentials: Credentials;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Refresh credentials and persist to original storage.
|
|
44
|
+
*
|
|
45
|
+
* Higher-level function that:
|
|
46
|
+
* 1. Refreshes credentials via agent subprocess
|
|
47
|
+
* 2. Preserves _source marker from original credentials
|
|
48
|
+
* 3. Persists refreshed credentials to original storage location
|
|
49
|
+
*
|
|
50
|
+
* @param creds - Original credentials with _source marker
|
|
51
|
+
* @param installFunction - Function to install credentials (avoids circular import)
|
|
52
|
+
* @param options - Refresh options
|
|
53
|
+
* @returns Refreshed credentials or original credentials on failure
|
|
54
|
+
*/
|
|
55
|
+
declare function refreshAndPersist(creds: Credentials, installFunction: (c: Credentials) => {
|
|
56
|
+
ok: boolean;
|
|
57
|
+
message: string;
|
|
58
|
+
}, options?: RefreshOptions): Promise<RefreshAndPersistResult>;
|
|
59
|
+
export { refreshAndPersist, refreshCredentials };
|
|
60
|
+
export type { RefreshAndPersistResult, RefreshOptions, RefreshResult };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token refresh via agent subprocess.
|
|
3
|
+
*
|
|
4
|
+
* Refreshes credentials by running the agent in an isolated temp config,
|
|
5
|
+
* triggering its internal auth refresh mechanism.
|
|
6
|
+
*/
|
|
7
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { buildAgentRuntimeEnvironment, getAgent, getAgentConfigSubdirectory, } from "axshared";
|
|
11
|
+
import { spawnAgent } from "./spawn-agent.js";
|
|
12
|
+
/** Default timeout for refresh operations in milliseconds */
|
|
13
|
+
const DEFAULT_REFRESH_TIMEOUT_MS = 30_000;
|
|
14
|
+
/**
|
|
15
|
+
* Write agent-specific config to force file-based storage.
|
|
16
|
+
*
|
|
17
|
+
* This prevents the agent from using keychain credentials,
|
|
18
|
+
* ensuring it reads/writes from our temp directory.
|
|
19
|
+
*/
|
|
20
|
+
function writeForceFileConfig(agentId, temporaryDirectory) {
|
|
21
|
+
switch (agentId) {
|
|
22
|
+
case "codex": {
|
|
23
|
+
// Codex uses config.toml with cli_auth_credentials_store setting
|
|
24
|
+
writeFileSync(path.join(temporaryDirectory, "config.toml"), 'cli_auth_credentials_store = "file"\nmcp_oauth_credentials_store = "file"\n');
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
case "copilot": {
|
|
28
|
+
// Copilot uses JSON config with store_token_plaintext flag
|
|
29
|
+
writeFileSync(path.join(temporaryDirectory, "config.json"), JSON.stringify({ store_token_plaintext: true }, undefined, 2));
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
// Claude: keychain service name includes hash of CLAUDE_CONFIG_DIR,
|
|
33
|
+
// so unique temp dir = unique keychain entry that won't exist
|
|
34
|
+
// Gemini: file storage behavior is controlled via the GEMINI_FORCE_FILE_STORAGE
|
|
35
|
+
// environment variable, which is set by buildAgentRuntimeEnvironment and
|
|
36
|
+
// later included in fullEnvironment when spawning the agent.
|
|
37
|
+
// OpenCode: file-only by design, no action needed
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Refresh credentials by spawning the agent briefly.
|
|
42
|
+
*
|
|
43
|
+
* Creates an isolated temp config directory, installs credentials there,
|
|
44
|
+
* and runs the agent with a minimal prompt to trigger its internal refresh.
|
|
45
|
+
*
|
|
46
|
+
* @param creds - The credentials to refresh (must contain refresh_token for OAuth)
|
|
47
|
+
* @param options - Refresh options (timeout, provider for multi-provider agents)
|
|
48
|
+
* @returns RefreshResult with new credentials or error
|
|
49
|
+
*/
|
|
50
|
+
async function refreshCredentials(creds, options) {
|
|
51
|
+
const timeout = options?.timeout ?? DEFAULT_REFRESH_TIMEOUT_MS;
|
|
52
|
+
const agent = getAgent(creds.agent);
|
|
53
|
+
// 1. Create temp config directory
|
|
54
|
+
const temporaryDirectory = mkdtempSync(path.join(tmpdir(), `axauth-refresh-${creds.agent}-`));
|
|
55
|
+
try {
|
|
56
|
+
// 2. Determine where credentials should be installed
|
|
57
|
+
// Some agents (Gemini, Copilot, OpenCode) require a subdirectory structure
|
|
58
|
+
const subdirectory = getAgentConfigSubdirectory(creds.agent);
|
|
59
|
+
const credentialsDirectory = subdirectory
|
|
60
|
+
? path.join(temporaryDirectory, subdirectory)
|
|
61
|
+
: temporaryDirectory;
|
|
62
|
+
// 3. Ensure credentials directory exists before writing config
|
|
63
|
+
mkdirSync(credentialsDirectory, { recursive: true });
|
|
64
|
+
// 4. Write agent-specific config to force file storage
|
|
65
|
+
writeForceFileConfig(creds.agent, credentialsDirectory);
|
|
66
|
+
// 5. Install credentials to the appropriate directory
|
|
67
|
+
// Import dynamically to avoid circular dependency
|
|
68
|
+
const { installCredentials } = await import("./registry.js");
|
|
69
|
+
const installResult = installCredentials(creds, {
|
|
70
|
+
configDir: credentialsDirectory,
|
|
71
|
+
dataDir: credentialsDirectory,
|
|
72
|
+
});
|
|
73
|
+
if (!installResult.ok) {
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
error: `Failed to install credentials: ${installResult.message}`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// 6. Build runtime environment
|
|
80
|
+
// Pass the full credentialsDirectory (which may include an agent-specific
|
|
81
|
+
// subdirectory, e.g. "~/.gemini/"); buildAgentRuntimeEnvironment will
|
|
82
|
+
// derive the parent directory internally for agents that use subdirectories.
|
|
83
|
+
const runtimeEnvironment = buildAgentRuntimeEnvironment(creds.agent, credentialsDirectory);
|
|
84
|
+
const fullEnvironment = {
|
|
85
|
+
...process.env,
|
|
86
|
+
...runtimeEnvironment,
|
|
87
|
+
};
|
|
88
|
+
// 7. Build CLI arguments using simplePromptArguments from agent
|
|
89
|
+
const cliArguments = agent.simplePromptArguments("ping", {
|
|
90
|
+
provider: options?.provider,
|
|
91
|
+
});
|
|
92
|
+
// 8. Spawn agent with minimal prompt
|
|
93
|
+
const { timedOut } = await spawnAgent(agent.cli, cliArguments, fullEnvironment, timeout);
|
|
94
|
+
if (timedOut) {
|
|
95
|
+
return { ok: false, error: "Refresh timed out" };
|
|
96
|
+
}
|
|
97
|
+
// 9. Read refreshed credentials from temp directory
|
|
98
|
+
const { extractRawCredentialsFromDirectory } = await import("./registry.js");
|
|
99
|
+
const refreshedCredentials = extractRawCredentialsFromDirectory(creds.agent, credentialsDirectory);
|
|
100
|
+
if (!refreshedCredentials) {
|
|
101
|
+
return { ok: false, error: "No credentials found after refresh" };
|
|
102
|
+
}
|
|
103
|
+
return { ok: true, credentials: refreshedCredentials };
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
// 10. Clean up temp directory
|
|
107
|
+
try {
|
|
108
|
+
rmSync(temporaryDirectory, { recursive: true, force: true });
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
+
console.error(`Warning: Failed to clean up temp directory '${temporaryDirectory}': ${message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Refresh credentials and persist to original storage.
|
|
118
|
+
*
|
|
119
|
+
* Higher-level function that:
|
|
120
|
+
* 1. Refreshes credentials via agent subprocess
|
|
121
|
+
* 2. Preserves _source marker from original credentials
|
|
122
|
+
* 3. Persists refreshed credentials to original storage location
|
|
123
|
+
*
|
|
124
|
+
* @param creds - Original credentials with _source marker
|
|
125
|
+
* @param installFunction - Function to install credentials (avoids circular import)
|
|
126
|
+
* @param options - Refresh options
|
|
127
|
+
* @returns Refreshed credentials or original credentials on failure
|
|
128
|
+
*/
|
|
129
|
+
async function refreshAndPersist(creds, installFunction, options) {
|
|
130
|
+
const result = await refreshCredentials(creds, options);
|
|
131
|
+
if (!result.ok) {
|
|
132
|
+
console.error(`Warning: Token refresh failed: ${result.error}`);
|
|
133
|
+
return { ok: false, staleCredentials: creds };
|
|
134
|
+
}
|
|
135
|
+
// Preserve _source marker so credentials are saved to the same location
|
|
136
|
+
const refreshedCreds = {
|
|
137
|
+
...result.credentials,
|
|
138
|
+
data: {
|
|
139
|
+
...result.credentials.data,
|
|
140
|
+
_source: creds.data._source,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
// Persist refreshed credentials back to storage
|
|
144
|
+
const installResult = installFunction(refreshedCreds);
|
|
145
|
+
if (!installResult.ok) {
|
|
146
|
+
console.error(`Warning: Failed to save refreshed credentials: ${installResult.message}`);
|
|
147
|
+
}
|
|
148
|
+
return { ok: true, credentials: refreshedCreds };
|
|
149
|
+
}
|
|
150
|
+
export { refreshAndPersist, refreshCredentials };
|
package/dist/auth/registry.d.ts
CHANGED
|
@@ -59,6 +59,16 @@ declare function checkAllAuth(): AuthStatus[];
|
|
|
59
59
|
* if (creds) { await exportCredentials(creds); }
|
|
60
60
|
*/
|
|
61
61
|
declare function extractRawCredentials(agentId: AgentCli): Credentials | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Extract raw credentials from a specific directory.
|
|
64
|
+
*
|
|
65
|
+
* Used by token refresh to read credentials from a temp directory.
|
|
66
|
+
* Returns undefined if no credentials found or adapter doesn't support it.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* const creds = extractRawCredentialsFromDirectory("claude", "/tmp/refresh-123");
|
|
70
|
+
*/
|
|
71
|
+
declare function extractRawCredentialsFromDirectory(agentId: AgentCli, directory: string): Credentials | undefined;
|
|
62
72
|
/**
|
|
63
73
|
* Install credentials for an agent.
|
|
64
74
|
*
|
|
@@ -82,29 +92,26 @@ declare function installCredentials(creds: Credentials, options?: InstallOptions
|
|
|
82
92
|
*/
|
|
83
93
|
declare function removeCredentials(agentId: AgentCli, options?: RemoveOptions): OperationResult;
|
|
84
94
|
/**
|
|
85
|
-
*
|
|
95
|
+
* Extract access token from credentials.
|
|
86
96
|
*
|
|
87
|
-
* For
|
|
88
|
-
*
|
|
97
|
+
* For OAuth tokens, automatically refreshes if expired.
|
|
98
|
+
* For API keys, returns immediately (no expiry).
|
|
89
99
|
*
|
|
90
100
|
* @example
|
|
91
|
-
* const token = getAccessToken(creds);
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* // For multi-provider agents
|
|
95
|
-
* const token = getAccessToken(creds, { provider: "anthropic" });
|
|
101
|
+
* const token = await getAccessToken(creds);
|
|
102
|
+
* const token = await getAccessToken(creds, { skipRefresh: true });
|
|
96
103
|
*/
|
|
97
|
-
declare function getAccessToken(creds: Credentials, options?: TokenOptions): string | undefined
|
|
104
|
+
declare function getAccessToken(creds: Credentials, options?: TokenOptions): Promise<string | undefined>;
|
|
98
105
|
/**
|
|
99
|
-
* Get access token for an agent
|
|
106
|
+
* Get access token for an agent by ID.
|
|
100
107
|
*
|
|
101
|
-
*
|
|
108
|
+
* Convenience function that extracts credentials and gets the token.
|
|
109
|
+
* Automatically refreshes expired OAuth tokens.
|
|
102
110
|
*
|
|
103
111
|
* @example
|
|
104
|
-
* const token = getAgentAccessToken("claude");
|
|
105
|
-
* if (token) { ... }
|
|
112
|
+
* const token = await getAgentAccessToken("claude");
|
|
106
113
|
*/
|
|
107
|
-
declare function getAgentAccessToken(agentId: AgentCli): string | undefined
|
|
114
|
+
declare function getAgentAccessToken(agentId: AgentCli, options?: TokenOptions): Promise<string | undefined>;
|
|
108
115
|
/**
|
|
109
116
|
* Convert credentials to environment variables.
|
|
110
117
|
*
|
|
@@ -153,4 +160,4 @@ declare function installCredentialsFromEnvironmentVariable(agent: AgentCli, opti
|
|
|
153
160
|
error: string;
|
|
154
161
|
}>;
|
|
155
162
|
export type { InstallFromEnvironmentOptions };
|
|
156
|
-
export { checkAllAuth, checkAuth, credentialsToEnvironment, extractRawCredentials, getAccessToken, getAdapter, getAgentAccessToken, getAllAdapters, getCapabilities, getCredentialsEnvironmentVariableName, installCredentials, installCredentialsFromEnvironmentVariable, removeCredentials, };
|
|
163
|
+
export { checkAllAuth, checkAuth, credentialsToEnvironment, extractRawCredentials, extractRawCredentialsFromDirectory, getAccessToken, getAdapter, getAgentAccessToken, getAllAdapters, getCapabilities, getCredentialsEnvironmentVariableName, installCredentials, installCredentialsFromEnvironmentVariable, removeCredentials, };
|
package/dist/auth/registry.js
CHANGED
|
@@ -10,6 +10,8 @@ import { codexAdapter } from "./agents/codex.js";
|
|
|
10
10
|
import { copilotAdapter } from "./agents/copilot.js";
|
|
11
11
|
import { geminiAdapter } from "./agents/gemini.js";
|
|
12
12
|
import { opencodeAdapter } from "./agents/opencode.js";
|
|
13
|
+
import { isCredentialExpired, isTokenExpired } from "./is-token-expired.js";
|
|
14
|
+
import { refreshAndPersist } from "./refresh-credentials.js";
|
|
13
15
|
import { parseCredentials } from "./types.js";
|
|
14
16
|
/** Registry of all adapters by agent ID */
|
|
15
17
|
const ADAPTERS = {
|
|
@@ -83,6 +85,19 @@ function checkAllAuth() {
|
|
|
83
85
|
function extractRawCredentials(agentId) {
|
|
84
86
|
return ADAPTERS[agentId].extractRawCredentials();
|
|
85
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Extract raw credentials from a specific directory.
|
|
90
|
+
*
|
|
91
|
+
* Used by token refresh to read credentials from a temp directory.
|
|
92
|
+
* Returns undefined if no credentials found or adapter doesn't support it.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* const creds = extractRawCredentialsFromDirectory("claude", "/tmp/refresh-123");
|
|
96
|
+
*/
|
|
97
|
+
function extractRawCredentialsFromDirectory(agentId, directory) {
|
|
98
|
+
const adapter = ADAPTERS[agentId];
|
|
99
|
+
return adapter.extractRawCredentialsFromDirectory?.(directory);
|
|
100
|
+
}
|
|
86
101
|
/**
|
|
87
102
|
* Install credentials for an agent.
|
|
88
103
|
*
|
|
@@ -110,35 +125,56 @@ function removeCredentials(agentId, options) {
|
|
|
110
125
|
return ADAPTERS[agentId].removeCredentials(options);
|
|
111
126
|
}
|
|
112
127
|
/**
|
|
113
|
-
*
|
|
128
|
+
* Extract access token from credentials.
|
|
114
129
|
*
|
|
115
|
-
* For
|
|
116
|
-
*
|
|
130
|
+
* For OAuth tokens, automatically refreshes if expired.
|
|
131
|
+
* For API keys, returns immediately (no expiry).
|
|
117
132
|
*
|
|
118
133
|
* @example
|
|
119
|
-
* const token = getAccessToken(creds);
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* // For multi-provider agents
|
|
123
|
-
* const token = getAccessToken(creds, { provider: "anthropic" });
|
|
134
|
+
* const token = await getAccessToken(creds);
|
|
135
|
+
* const token = await getAccessToken(creds, { skipRefresh: true });
|
|
124
136
|
*/
|
|
125
|
-
function getAccessToken(creds, options) {
|
|
126
|
-
|
|
137
|
+
async function getAccessToken(creds, options) {
|
|
138
|
+
const token = ADAPTERS[creds.agent].getAccessToken(creds, options);
|
|
139
|
+
// No token found
|
|
140
|
+
if (!token)
|
|
141
|
+
return undefined;
|
|
142
|
+
// API keys don't expire
|
|
143
|
+
if (creds.type === "api-key")
|
|
144
|
+
return token;
|
|
145
|
+
// Skip refresh if requested
|
|
146
|
+
if (options?.skipRefresh)
|
|
147
|
+
return token;
|
|
148
|
+
// Check if token is expired (try JWT first, then credential fields)
|
|
149
|
+
let expired = isTokenExpired(token);
|
|
150
|
+
if (expired === undefined) {
|
|
151
|
+
// Token isn't a JWT - check credential's explicit expiry fields
|
|
152
|
+
expired = isCredentialExpired(creds.data);
|
|
153
|
+
}
|
|
154
|
+
if (expired !== true)
|
|
155
|
+
return token; // Valid or no expiry info
|
|
156
|
+
// Refresh and persist
|
|
157
|
+
const result = await refreshAndPersist(creds, installCredentials, {
|
|
158
|
+
timeout: options?.refreshTimeout,
|
|
159
|
+
provider: options?.provider,
|
|
160
|
+
});
|
|
161
|
+
const finalCreds = result.ok ? result.credentials : result.staleCredentials;
|
|
162
|
+
return ADAPTERS[creds.agent].getAccessToken(finalCreds, options);
|
|
127
163
|
}
|
|
128
164
|
/**
|
|
129
|
-
* Get access token for an agent
|
|
165
|
+
* Get access token for an agent by ID.
|
|
130
166
|
*
|
|
131
|
-
*
|
|
167
|
+
* Convenience function that extracts credentials and gets the token.
|
|
168
|
+
* Automatically refreshes expired OAuth tokens.
|
|
132
169
|
*
|
|
133
170
|
* @example
|
|
134
|
-
* const token = getAgentAccessToken("claude");
|
|
135
|
-
* if (token) { ... }
|
|
171
|
+
* const token = await getAgentAccessToken("claude");
|
|
136
172
|
*/
|
|
137
|
-
function getAgentAccessToken(agentId) {
|
|
173
|
+
async function getAgentAccessToken(agentId, options) {
|
|
138
174
|
const creds = extractRawCredentials(agentId);
|
|
139
175
|
if (!creds)
|
|
140
176
|
return undefined;
|
|
141
|
-
return getAccessToken(creds);
|
|
177
|
+
return getAccessToken(creds, options);
|
|
142
178
|
}
|
|
143
179
|
/**
|
|
144
180
|
* Convert credentials to environment variables.
|
|
@@ -228,4 +264,4 @@ async function installCredentialsFromEnvironmentVariable(agent, options) {
|
|
|
228
264
|
}
|
|
229
265
|
return { ok: true };
|
|
230
266
|
}
|
|
231
|
-
export { checkAllAuth, checkAuth, credentialsToEnvironment, extractRawCredentials, getAccessToken, getAdapter, getAgentAccessToken, getAllAdapters, getCapabilities, getCredentialsEnvironmentVariableName, installCredentials, installCredentialsFromEnvironmentVariable, removeCredentials, };
|
|
267
|
+
export { checkAllAuth, checkAuth, credentialsToEnvironment, extractRawCredentials, extractRawCredentialsFromDirectory, getAccessToken, getAdapter, getAgentAccessToken, getAllAdapters, getCapabilities, getCredentialsEnvironmentVariableName, installCredentials, installCredentialsFromEnvironmentVariable, removeCredentials, };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent subprocess spawning utility.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Spawn agent and wait for completion.
|
|
6
|
+
*
|
|
7
|
+
* @returns Object with timedOut flag and exit code
|
|
8
|
+
*/
|
|
9
|
+
declare function spawnAgent(binary: string, arguments_: string[], environment: NodeJS.ProcessEnv, timeout: number): Promise<{
|
|
10
|
+
timedOut: boolean;
|
|
11
|
+
exitCode: number | null;
|
|
12
|
+
}>;
|
|
13
|
+
export { spawnAgent };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent subprocess spawning utility.
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
/**
|
|
6
|
+
* Spawn agent and wait for completion.
|
|
7
|
+
*
|
|
8
|
+
* @returns Object with timedOut flag and exit code
|
|
9
|
+
*/
|
|
10
|
+
async function spawnAgent(binary, arguments_, environment, timeout) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
const child = spawn(binary, arguments_, {
|
|
13
|
+
env: environment,
|
|
14
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
15
|
+
});
|
|
16
|
+
let timedOut = false;
|
|
17
|
+
let resolved = false;
|
|
18
|
+
const timer = setTimeout(() => {
|
|
19
|
+
if (!resolved) {
|
|
20
|
+
timedOut = true;
|
|
21
|
+
child.kill("SIGTERM");
|
|
22
|
+
}
|
|
23
|
+
}, timeout);
|
|
24
|
+
child.on("error", () => {
|
|
25
|
+
if (!resolved) {
|
|
26
|
+
resolved = true;
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
resolve({ timedOut: false, exitCode: 1 });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
child.on("close", (code) => {
|
|
32
|
+
if (!resolved) {
|
|
33
|
+
resolved = true;
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
resolve({ timedOut, exitCode: code });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export { spawnAgent };
|
package/dist/commands/auth.d.ts
CHANGED
package/dist/commands/auth.js
CHANGED
|
@@ -18,7 +18,7 @@ function handleAuthList(options) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
/** Handle auth token command */
|
|
21
|
-
function handleAuthToken(options) {
|
|
21
|
+
async function handleAuthToken(options) {
|
|
22
22
|
const agentId = validateAgent(options.agent);
|
|
23
23
|
if (!agentId)
|
|
24
24
|
return;
|
|
@@ -29,7 +29,7 @@ function handleAuthToken(options) {
|
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
// Extract the token based on credential type
|
|
32
|
-
const token = getAccessToken(creds, { provider: options.provider });
|
|
32
|
+
const token = await getAccessToken(creds, { provider: options.provider });
|
|
33
33
|
if (!token) {
|
|
34
34
|
const providerHint = options.provider
|
|
35
35
|
? ` for provider '${options.provider}'`
|
package/dist/index.d.ts
CHANGED
|
@@ -35,3 +35,5 @@ export { AGENT_CLIS } from "axshared";
|
|
|
35
35
|
export type { AdapterCapabilities, AuthAdapter, InstallOptions, OperationResult, RemoveOptions, } from "./auth/adapter.js";
|
|
36
36
|
export type { AuthStatus, Credentials } from "./auth/types.js";
|
|
37
37
|
export { checkAllAuth, checkAuth, credentialsToEnvironment, extractRawCredentials, getAccessToken, getAgentAccessToken, getCredentialsEnvironmentVariableName, installCredentials, installCredentialsFromEnvironmentVariable, removeCredentials, getAdapter, getAllAdapters, getCapabilities, type InstallFromEnvironmentOptions, } from "./auth/registry.js";
|
|
38
|
+
export { isCredentialExpired, isTokenExpired, } from "./auth/is-token-expired.js";
|
|
39
|
+
export { refreshAndPersist, refreshCredentials, type RefreshAndPersistResult, type RefreshOptions, type RefreshResult, } from "./auth/refresh-credentials.js";
|
package/dist/index.js
CHANGED
|
@@ -37,3 +37,6 @@ export {
|
|
|
37
37
|
checkAllAuth, checkAuth, credentialsToEnvironment, extractRawCredentials, getAccessToken, getAgentAccessToken, getCredentialsEnvironmentVariableName, installCredentials, installCredentialsFromEnvironmentVariable, removeCredentials,
|
|
38
38
|
// Adapter access
|
|
39
39
|
getAdapter, getAllAdapters, getCapabilities, } from "./auth/registry.js";
|
|
40
|
+
// Token refresh utilities
|
|
41
|
+
export { isCredentialExpired, isTokenExpired, } from "./auth/is-token-expired.js";
|
|
42
|
+
export { refreshAndPersist, refreshCredentials, } from "./auth/refresh-credentials.js";
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "axauth",
|
|
3
3
|
"author": "Łukasz Jerciński",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.5.0",
|
|
6
6
|
"description": "Authentication management library and CLI for AI coding agents",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@commander-js/extra-typings": "^14.0.0",
|
|
71
71
|
"@inquirer/password": "^5.0.3",
|
|
72
72
|
"axconfig": "^3.2.0",
|
|
73
|
-
"axshared": "^1.
|
|
73
|
+
"axshared": "^1.7.0",
|
|
74
74
|
"commander": "^14.0.2",
|
|
75
75
|
"zod": "^4.3.4"
|
|
76
76
|
},
|