axauth 3.1.6 → 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.
@@ -241,6 +241,22 @@ interface AuthAdapter {
241
241
  * to specify which provider's token to extract.
242
242
  */
243
243
  getAccessToken(creds: Credentials, options?: TokenOptions): string | undefined;
244
+ /**
245
+ * Check whether credentials are expired.
246
+ *
247
+ * Returns:
248
+ * - `true` when credentials are expired (or inside refresh buffer)
249
+ * - `false` when credentials are valid
250
+ * - `undefined` when expiry cannot be determined
251
+ */
252
+ isExpired(creds: Credentials, bufferSeconds?: number): boolean | undefined;
253
+ /**
254
+ * Check whether credentials can be refreshed.
255
+ *
256
+ * Returns true when credentials contain a refresh token compatible with
257
+ * the adapter's underlying auth format.
258
+ */
259
+ isRefreshable(creds: Credentials): boolean;
244
260
  /**
245
261
  * Convert credentials to environment variables.
246
262
  *
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import path from "node:path";
7
7
  import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
8
+ import { isCredentialExpired, isTokenExpired } from "../is-token-expired.js";
8
9
  import { CREDS_FILE_NAME, installCredentials, removeCredentials, } from "./claude-install.js";
9
10
  import { AGENT_ID, checkAuth, findStoredCredentials as findStoredCredentialsInternal, getEnvironmentCredentials as getEnvironmentCredentialsInternal, } from "./claude-storage.js";
10
11
  /** Claude Code authentication adapter */
@@ -61,6 +62,23 @@ const claudeCodeAdapter = {
61
62
  }
62
63
  return undefined;
63
64
  },
65
+ isExpired(creds, bufferSeconds = 60) {
66
+ const data = creds.data;
67
+ const token = (creds.type === "oauth-credentials" || creds.type === "oauth-token") &&
68
+ typeof data.accessToken === "string"
69
+ ? data.accessToken
70
+ : undefined;
71
+ if (token) {
72
+ const expired = isTokenExpired(token, bufferSeconds);
73
+ if (expired !== undefined)
74
+ return expired;
75
+ }
76
+ return isCredentialExpired(data, bufferSeconds);
77
+ },
78
+ isRefreshable(creds) {
79
+ return (creds.type === "oauth-credentials" &&
80
+ typeof creds.data.refreshToken === "string");
81
+ },
64
82
  credentialsToEnvironment(creds) {
65
83
  const environment = {};
66
84
  const data = creds.data;
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { existsSync, readFileSync } from "node:fs";
7
7
  import path from "node:path";
8
+ import { isCredentialExpired, isTokenExpired } from "../is-token-expired.js";
8
9
  import { checkAuth, findStoredCredentials, getEnvironmentCredentials, } from "./codex-auth-check.js";
9
10
  import { CREDS_FILE_NAME, installCodexCredentials, removeCodexCredentials, } from "./codex-install.js";
10
11
  const AGENT_ID = "codex";
@@ -61,6 +62,36 @@ const codexAdapter = {
61
62
  }
62
63
  return undefined;
63
64
  },
65
+ isExpired(creds, bufferSeconds = 60) {
66
+ const data = creds.data;
67
+ const token = (creds.type === "api-key" && typeof data.apiKey === "string"
68
+ ? data.apiKey
69
+ : undefined) ??
70
+ (creds.type === "oauth-credentials" &&
71
+ typeof data.access_token === "string"
72
+ ? data.access_token
73
+ : undefined) ??
74
+ (creds.type === "oauth-credentials" &&
75
+ typeof data.tokens === "object" &&
76
+ data.tokens !== null &&
77
+ typeof data.tokens.access_token === "string"
78
+ ? data.tokens.access_token
79
+ : undefined);
80
+ if (token) {
81
+ const expired = isTokenExpired(token, bufferSeconds);
82
+ if (expired !== undefined)
83
+ return expired;
84
+ }
85
+ return isCredentialExpired(data, bufferSeconds);
86
+ },
87
+ isRefreshable(creds) {
88
+ if (creds.type !== "oauth-credentials")
89
+ return false;
90
+ const tokens = typeof creds.data.tokens === "object" && creds.data.tokens !== null
91
+ ? creds.data.tokens
92
+ : undefined;
93
+ return typeof tokens?.refresh_token === "string";
94
+ },
64
95
  credentialsToEnvironment(creds) {
65
96
  const environment = {};
66
97
  const data = creds.data;
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import path from "node:path";
8
8
  import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
9
+ import { isCredentialExpired, isTokenExpired } from "../is-token-expired.js";
9
10
  import { resolveCustomDirectory } from "../validate-directories.js";
10
11
  import { findFirstAvailableToken } from "./copilot-auth-check.js";
11
12
  import { CREDS_FILE_NAME, installCopilotCredentials, } from "./copilot-install.js";
@@ -109,6 +110,20 @@ const copilotAdapter = {
109
110
  }
110
111
  return undefined;
111
112
  },
113
+ isExpired(creds, bufferSeconds = 60) {
114
+ const token = typeof creds.data.accessToken === "string"
115
+ ? creds.data.accessToken
116
+ : undefined;
117
+ if (token) {
118
+ const expired = isTokenExpired(token, bufferSeconds);
119
+ if (expired !== undefined)
120
+ return expired;
121
+ }
122
+ return isCredentialExpired(creds.data, bufferSeconds);
123
+ },
124
+ isRefreshable() {
125
+ return false;
126
+ },
112
127
  credentialsToEnvironment(creds) {
113
128
  const environment = {};
114
129
  const data = creds.data;
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import path from "node:path";
7
7
  import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
8
+ import { isCredentialExpired, isTokenExpired } from "../is-token-expired.js";
8
9
  import { checkEnvironmentAuth, findCredentials } from "./gemini-auth-check.js";
9
10
  import { installOAuthCredentials, removeGeminiCredentials, } from "./gemini-install.js";
10
11
  const AGENT_ID = "gemini";
@@ -91,6 +92,26 @@ const geminiAdapter = {
91
92
  }
92
93
  return undefined;
93
94
  },
95
+ isExpired(creds, bufferSeconds = 60) {
96
+ const data = creds.data;
97
+ const token = (creds.type === "api-key" && typeof data.apiKey === "string"
98
+ ? data.apiKey
99
+ : undefined) ??
100
+ (creds.type === "oauth-credentials" &&
101
+ typeof data.access_token === "string"
102
+ ? data.access_token
103
+ : undefined);
104
+ if (token) {
105
+ const expired = isTokenExpired(token, bufferSeconds);
106
+ if (expired !== undefined)
107
+ return expired;
108
+ }
109
+ return isCredentialExpired(data, bufferSeconds);
110
+ },
111
+ isRefreshable(creds) {
112
+ return (creds.type === "oauth-credentials" &&
113
+ typeof creds.data.refresh_token === "string");
114
+ },
94
115
  credentialsToEnvironment(creds) {
95
116
  const environment = {};
96
117
  const data = creds.data;
@@ -8,6 +8,7 @@
8
8
  * - Data (auth): ~/.local/share/opencode (XDG_DATA_HOME/opencode)
9
9
  */
10
10
  import path from "node:path";
11
+ import { isCredentialExpired, isTokenExpired } from "../is-token-expired.js";
11
12
  import { extractTokenFromEntry, OpenCodeAuth } from "./opencode-schema.js";
12
13
  import { AGENT_ID, CREDS_FILE_NAME, getDefaultAuthFilePath, mapToCredentialType, readAuthFile, } from "./opencode-credentials.js";
13
14
  import { installCredentials, removeCredentials } from "./opencode-storage.js";
@@ -122,6 +123,21 @@ const opencodeAdapter = {
122
123
  return undefined;
123
124
  return extractTokenFromEntry(parsed.data);
124
125
  },
126
+ isExpired(creds, bufferSeconds = 60) {
127
+ const parsed = OpenCodeAuth.safeParse(creds.data);
128
+ if (!parsed.success)
129
+ return undefined;
130
+ if (parsed.data.type !== "oauth")
131
+ return undefined;
132
+ const tokenExpired = isTokenExpired(parsed.data.access, bufferSeconds);
133
+ if (tokenExpired !== undefined)
134
+ return tokenExpired;
135
+ return isCredentialExpired({ expires: parsed.data.expires }, bufferSeconds);
136
+ },
137
+ isRefreshable(creds) {
138
+ const parsed = OpenCodeAuth.safeParse(creds.data);
139
+ return parsed.success && parsed.data.type === "oauth";
140
+ },
125
141
  credentialsToEnvironment() {
126
142
  // OpenCode requires auth.json file, no env var support
127
143
  return {};
@@ -0,0 +1,12 @@
1
+ import type { AgentCli } from "axshared";
2
+ import type { AuthAdapter, FindStoredCredentialsOptions, InstallOptions, OperationResult, TokenOptions } from "./adapter.js";
3
+ import type { Credentials } from "./types.js";
4
+ interface AccessTokenDependencies {
5
+ adapter: AuthAdapter;
6
+ installCredentials: (agentId: AgentCli, creds: Credentials, options?: InstallOptions) => OperationResult;
7
+ }
8
+ declare function findCredentialsWithEnvironmentPriority(adapter: AuthAdapter, options?: FindStoredCredentialsOptions): Credentials | undefined;
9
+ type AccessTokenGetter = (agentId: AgentCli, creds: Credentials, options?: TokenOptions) => Promise<string | undefined>;
10
+ declare function getAgentAccessTokenFromStoredCredentials(agentId: AgentCli, adapter: AuthAdapter, options: TokenOptions | undefined, getAccessToken: AccessTokenGetter): Promise<string | undefined>;
11
+ declare function getAccessTokenWithRefresh(agentId: AgentCli, creds: Credentials, options: TokenOptions | undefined, dependencies: AccessTokenDependencies): Promise<string | undefined>;
12
+ export { findCredentialsWithEnvironmentPriority, getAccessTokenWithRefresh, getAgentAccessTokenFromStoredCredentials, };
@@ -0,0 +1,44 @@
1
+ import { refreshAndPersist } from "./refresh-credentials.js";
2
+ function findCredentialsWithEnvironmentPriority(adapter, options) {
3
+ const environmentCredentials = adapter.getEnvironmentCredentials?.();
4
+ if (environmentCredentials)
5
+ return environmentCredentials;
6
+ const stored = adapter.findStoredCredentials(options);
7
+ return stored?.credentials;
8
+ }
9
+ function shouldRefreshToken(adapter, creds, options) {
10
+ if (creds.type === "api-key")
11
+ return false;
12
+ if (options?.skipRefresh)
13
+ return false;
14
+ if (!adapter.isRefreshable(creds))
15
+ return false;
16
+ return adapter.isExpired(creds) === true;
17
+ }
18
+ async function getAgentAccessTokenFromStoredCredentials(agentId, adapter, options, getAccessToken) {
19
+ const stored = adapter.findStoredCredentials({
20
+ provider: options?.provider,
21
+ });
22
+ if (!stored)
23
+ return undefined;
24
+ return getAccessToken(agentId, stored.credentials, {
25
+ ...options,
26
+ storage: options?.storage ?? stored.source,
27
+ });
28
+ }
29
+ async function getAccessTokenWithRefresh(agentId, creds, options, dependencies) {
30
+ const token = dependencies.adapter.getAccessToken(creds, options);
31
+ if (!token)
32
+ return undefined;
33
+ if (!shouldRefreshToken(dependencies.adapter, creds, options)) {
34
+ return token;
35
+ }
36
+ const result = await refreshAndPersist(agentId, creds, (resolvedAgentId, refreshedCreds, installOptions) => dependencies.installCredentials(resolvedAgentId, refreshedCreds, installOptions), {
37
+ timeout: options?.refreshTimeout,
38
+ provider: options?.provider,
39
+ storage: options?.storage,
40
+ });
41
+ const finalCreds = result.ok ? result.credentials : result.staleCredentials;
42
+ return dependencies.adapter.getAccessToken(finalCreds, options);
43
+ }
44
+ export { findCredentialsWithEnvironmentPriority, getAccessTokenWithRefresh, getAgentAccessTokenFromStoredCredentials, };
@@ -18,18 +18,15 @@
18
18
  */
19
19
  const SECONDS_THRESHOLD = 10_000_000_000;
20
20
  /**
21
- * Check if a JWT token is expired.
22
- *
23
- * Decodes the JWT payload and checks the `exp` claim against current time.
21
+ * Extract the `exp` claim from a JWT as milliseconds.
24
22
  *
25
- * @param token - The JWT token string
26
- * @param bufferSeconds - Buffer before actual expiry (default: 60s)
27
- * @returns true if expired, false if valid, undefined if not a valid JWT
23
+ * Decodes the base64url payload and reads the `exp` field (seconds -> ms).
24
+ * Returns undefined if the string is not a valid JWT or has no `exp` claim.
28
25
  */
29
- function isTokenExpired(token, bufferSeconds = 60) {
26
+ function jwtExpiryMs(token) {
30
27
  const parts = token.split(".");
31
28
  if (parts.length !== 3)
32
- return undefined; // Not a JWT
29
+ return undefined;
33
30
  const payloadPart = parts[1];
34
31
  if (!payloadPart)
35
32
  return undefined;
@@ -40,14 +37,30 @@ function isTokenExpired(token, bufferSeconds = 60) {
40
37
  const payload = JSON.parse(atob(padded));
41
38
  const exp = payload.exp;
42
39
  if (typeof exp !== "number")
43
- return undefined; // No exp claim
44
- const nowSeconds = Date.now() / 1000;
45
- return nowSeconds > exp - bufferSeconds;
40
+ return undefined;
41
+ return exp * 1000;
46
42
  }
47
43
  catch {
48
- return undefined; // Invalid JWT
44
+ return undefined;
49
45
  }
50
46
  }
47
+ /**
48
+ * Check if a JWT token is expired.
49
+ *
50
+ * Decodes the JWT payload and checks the `exp` claim against current time.
51
+ *
52
+ * @param token - The JWT token string
53
+ * @param bufferSeconds - Buffer before actual expiry (default: 60s)
54
+ * @returns true if expired, false if valid, undefined if not a valid JWT
55
+ */
56
+ function isTokenExpired(token, bufferSeconds = 60) {
57
+ const expiryMs = jwtExpiryMs(token);
58
+ if (expiryMs === undefined)
59
+ return undefined;
60
+ const nowMs = Date.now();
61
+ const bufferMs = bufferSeconds * 1000;
62
+ return nowMs > expiryMs - bufferMs;
63
+ }
51
64
  /**
52
65
  * Check if credentials have expired using explicit timestamp fields.
53
66
  *
@@ -103,6 +116,13 @@ function extractExpiryTimestamp(data) {
103
116
  ? data.expires * 1000
104
117
  : data.expires;
105
118
  }
119
+ // JWT fallback: decode flat access_token for exp claim.
120
+ const accessToken = typeof data.access_token === "string" ? data.access_token : undefined;
121
+ if (accessToken !== undefined) {
122
+ const expMs = jwtExpiryMs(accessToken);
123
+ if (expMs !== undefined)
124
+ return expMs;
125
+ }
106
126
  return undefined;
107
127
  }
108
128
  /**
@@ -9,9 +9,8 @@ import { codexAdapter } from "./agents/codex.js";
9
9
  import { copilotAdapter } from "./agents/copilot.js";
10
10
  import { geminiAdapter } from "./agents/gemini.js";
11
11
  import { opencodeAdapter } from "./agents/opencode.js";
12
+ import { findCredentialsWithEnvironmentPriority, getAccessTokenWithRefresh, getAgentAccessTokenFromStoredCredentials, } from "./get-access-token-with-refresh.js";
12
13
  import { installCredentialsFromEnvironmentCore } from "./install-from-environment.js";
13
- import { isCredentialExpired, isTokenExpired } from "./is-token-expired.js";
14
- import { refreshAndPersist } from "./refresh-credentials.js";
15
14
  /** Registry of all adapters by agent ID */
16
15
  const ADAPTERS = {
17
16
  claude: claudeCodeAdapter,
@@ -92,14 +91,7 @@ function checkAllAuth() {
92
91
  * const creds = findCredentials("opencode", { provider: "anthropic" });
93
92
  */
94
93
  function findCredentials(agentId, options) {
95
- const adapter = ADAPTERS[agentId];
96
- // Check environment credentials first (allows easy overrides in CI/CD)
97
- const environmentCredentials = adapter.getEnvironmentCredentials?.();
98
- if (environmentCredentials)
99
- return environmentCredentials;
100
- // Fall back to stored credentials (keychain/file)
101
- const stored = adapter.findStoredCredentials(options);
102
- return stored?.credentials;
94
+ return findCredentialsWithEnvironmentPriority(ADAPTERS[agentId], options);
103
95
  }
104
96
  /**
105
97
  * Extract raw credentials from custom directories.
@@ -158,32 +150,10 @@ function removeCredentials(agentId, options) {
158
150
  * const token = await getAccessToken(creds, { skipRefresh: true });
159
151
  */
160
152
  async function getAccessToken(agentId, creds, options) {
161
- const token = ADAPTERS[agentId].getAccessToken(creds, options);
162
- // No token found
163
- if (!token)
164
- return undefined;
165
- // API keys don't expire
166
- if (creds.type === "api-key")
167
- return token;
168
- // Skip refresh if requested
169
- if (options?.skipRefresh)
170
- return token;
171
- // Check if token is expired (try JWT first, then credential fields)
172
- let expired = isTokenExpired(token);
173
- if (expired === undefined) {
174
- // Token isn't a JWT - check credential's explicit expiry fields
175
- expired = isCredentialExpired(creds.data);
176
- }
177
- if (expired !== true)
178
- return token; // Valid or no expiry info
179
- // Refresh and persist
180
- const result = await refreshAndPersist(agentId, creds, (aid, c, installOptions) => installCredentials(aid, c, installOptions), {
181
- timeout: options?.refreshTimeout,
182
- provider: options?.provider,
183
- storage: options?.storage,
153
+ return getAccessTokenWithRefresh(agentId, creds, options, {
154
+ adapter: ADAPTERS[agentId],
155
+ installCredentials,
184
156
  });
185
- const finalCreds = result.ok ? result.credentials : result.staleCredentials;
186
- return ADAPTERS[agentId].getAccessToken(finalCreds, options);
187
157
  }
188
158
  /**
189
159
  * Get access token for an agent by ID.
@@ -196,16 +166,7 @@ async function getAccessToken(agentId, creds, options) {
196
166
  * const token = await getAgentAccessToken("claude");
197
167
  */
198
168
  async function getAgentAccessToken(agentId, options) {
199
- const stored = ADAPTERS[agentId].findStoredCredentials({
200
- provider: options?.provider,
201
- });
202
- if (!stored)
203
- return undefined;
204
- // Pass the source to getAccessToken so refresh preserves storage location
205
- return getAccessToken(agentId, stored.credentials, {
206
- ...options,
207
- storage: options?.storage ?? stored.source,
208
- });
169
+ return getAgentAccessTokenFromStoredCredentials(agentId, ADAPTERS[agentId], options, getAccessToken);
209
170
  }
210
171
  /**
211
172
  * Convert credentials to environment variables.
package/dist/cli.js CHANGED
@@ -9,7 +9,8 @@ import packageJson from "../package.json" with { type: "json" };
9
9
  import { handleAuthExport, handleAuthInstall, handleAuthList, handleAuthRemove, handleAuthToken, } from "./commands/auth.js";
10
10
  import { handleDecrypt } from "./commands/decrypt.js";
11
11
  import { handleEncrypt } from "./commands/encrypt.js";
12
- import { handleVaultFetch, handleVaultPush } from "./commands/vault.js";
12
+ import { handleVaultFetch } from "./commands/vault-fetch.js";
13
+ import { handleVaultPush } from "./commands/vault-push.js";
13
14
  import { AGENT_CLIS } from "axshared";
14
15
  // Handle SIGINT gracefully
15
16
  process.on("SIGINT", () => {
@@ -198,6 +199,8 @@ vault
198
199
  .requiredOption("-a, --agent <agent>", `Agent to push credentials from (${AGENT_CLIS.join(", ")})`)
199
200
  .requiredOption("-n, --name <name>", "Credential name in vault (e.g., ci, prod)")
200
201
  .option("-p, --provider <provider>", "Provider to push (opencode only; pushes single provider instead of all)")
202
+ .option("--display-name <name>", "Human-readable display name for this credential (e.g., 'Claude (Work)')")
203
+ .option("--notes <text>", "Additional notes about this credential")
201
204
  .addHelpText("after", String.raw `
202
205
  Environment variables (option 1 - single JSON):
203
206
  AXVAULT JSON config: {"url":"...","apiKey":"..."}
@@ -210,6 +213,9 @@ Examples:
210
213
  # Push local Claude credentials to vault as "ci"
211
214
  axauth vault push --agent claude --name ci
212
215
 
216
+ # Push with a display name for multi-instance identification
217
+ axauth vault push --agent claude --name work-claude --display-name "Claude (Work)"
218
+
213
219
  # Push a single OpenCode provider to vault
214
220
  axauth vault push --agent opencode --provider anthropic --name ci-anthropic
215
221
 
@@ -0,0 +1,4 @@
1
+ import type { VaultFailureReason } from "../vault/vault-client.js";
2
+ /** Human-readable error messages for vault failures */
3
+ declare const FAILURE_MESSAGES: Record<VaultFailureReason, string>;
4
+ export { FAILURE_MESSAGES };
@@ -0,0 +1,12 @@
1
+ /** Human-readable error messages for vault failures */
2
+ const FAILURE_MESSAGES = {
3
+ "not-configured": "Vault not configured. Set AXVAULT (JSON) or AXVAULT_URL + AXVAULT_API_KEY.",
4
+ unreachable: "Vault server is unreachable. Check vault URL and network.",
5
+ unauthorized: "Invalid API key. Check apiKey in vault config.",
6
+ forbidden: "Access denied. API key doesn't have required access to this credential.",
7
+ "not-found": "Credential not found in vault.",
8
+ "legacy-credential": "Credential uses legacy encryption. Re-upload it to vault.",
9
+ "client-error": "Invalid request. Check credential name and try again.",
10
+ "server-error": "Vault server error. Check server logs.",
11
+ };
12
+ export { FAILURE_MESSAGES };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Vault commands - fetch and push credentials to axvault server.
2
+ * Vault fetch command - download credentials from axvault server.
3
3
  */
4
4
  interface VaultFetchOptions {
5
5
  agent: string;
@@ -19,17 +19,4 @@ interface VaultFetchOptions {
19
19
  * - Outputs shell export commands with --env flag (for eval/source)
20
20
  */
21
21
  declare function handleVaultFetch(options: VaultFetchOptions): Promise<void>;
22
- interface VaultPushOptions {
23
- agent: string;
24
- name: string;
25
- provider?: string;
26
- }
27
- /**
28
- * Handle vault push command.
29
- *
30
- * Reads local credentials for an agent and pushes them to the vault server
31
- * in raw (unencrypted) format. This allows the vault to handle encryption
32
- * and automatic token refresh.
33
- */
34
- declare function handleVaultPush(options: VaultPushOptions): Promise<void>;
35
- export { handleVaultFetch, handleVaultPush };
22
+ export { handleVaultFetch };
@@ -1,22 +1,11 @@
1
1
  /**
2
- * Vault commands - fetch and push credentials to axvault server.
2
+ * Vault fetch command - download credentials from axvault server.
3
3
  */
4
- import { credentialsToEnvironment, findCredentials, installCredentials, } from "../auth/registry.js";
5
- import { getStoredCredentialsForProvider } from "../auth/agents/opencode.js";
6
- import { fetchVaultCredentials, pushVaultCredentials, } from "../vault/vault-client.js";
4
+ import { credentialsToEnvironment, installCredentials, } from "../auth/registry.js";
5
+ import { fetchVaultCredentials } from "../vault/vault-client.js";
7
6
  import { getVaultConfig } from "../vault/vault-config.js";
7
+ import { FAILURE_MESSAGES } from "./vault-failure-messages.js";
8
8
  import { validateAgent } from "./validate-agent.js";
9
- /** Human-readable error messages for vault failures */
10
- const FAILURE_MESSAGES = {
11
- "not-configured": "Vault not configured. Set AXVAULT (JSON) or AXVAULT_URL + AXVAULT_API_KEY.",
12
- unreachable: "Vault server is unreachable. Check vault URL and network.",
13
- unauthorized: "Invalid API key. Check apiKey in vault config.",
14
- forbidden: "Access denied. API key doesn't have read access to this credential.",
15
- "not-found": "Credential not found in vault.",
16
- "legacy-credential": "Credential uses legacy encryption. Re-upload it to vault.",
17
- "client-error": "Invalid request. Check credential name and try again.",
18
- "server-error": "Vault server error. Check server logs.",
19
- };
20
9
  /**
21
10
  * Escape a value for safe use in shell export statement.
22
11
  * Uses single quotes and escapes any single quotes in the value.
@@ -66,7 +55,7 @@ async function handleVaultFetch(options) {
66
55
  process.exitCode = 1;
67
56
  return;
68
57
  }
69
- const { credentials, refreshed } = result;
58
+ const { credentials, refreshed, displayName, notes } = result;
70
59
  // Log refresh status to stderr (not stdout, to allow piping)
71
60
  if (refreshed) {
72
61
  console.error("Note: Credentials were auto-refreshed by vault");
@@ -112,71 +101,18 @@ async function handleVaultFetch(options) {
112
101
  }
113
102
  else {
114
103
  // Output credentials as JSON (default)
104
+ const output = {
105
+ ...credentials,
106
+ ...(displayName !== undefined && { displayName }),
107
+ ...(notes !== undefined && { notes }),
108
+ };
115
109
  if (options.json) {
116
- console.log(JSON.stringify(credentials, undefined, 2));
110
+ console.log(JSON.stringify(output, undefined, 2));
117
111
  }
118
112
  else {
119
113
  // Compact JSON for piping
120
- console.log(JSON.stringify(credentials));
114
+ console.log(JSON.stringify(output));
121
115
  }
122
116
  }
123
117
  }
124
- /**
125
- * Handle vault push command.
126
- *
127
- * Reads local credentials for an agent and pushes them to the vault server
128
- * in raw (unencrypted) format. This allows the vault to handle encryption
129
- * and automatic token refresh.
130
- */
131
- async function handleVaultPush(options) {
132
- // Validate agent
133
- const agentId = validateAgent(options.agent);
134
- if (!agentId)
135
- return;
136
- // Validate provider flag usage
137
- if (options.provider && agentId !== "opencode") {
138
- console.error("Error: --provider flag is only supported for opencode agent");
139
- process.exitCode = 2;
140
- return;
141
- }
142
- if (agentId === "opencode" && !options.provider) {
143
- console.error("Error: --provider is required for opencode");
144
- console.error("Hint: Use 'axauth list --json' to see available providers");
145
- process.exitCode = 1;
146
- return;
147
- }
148
- // Check vault is configured
149
- const vaultConfig = getVaultConfig();
150
- if (!vaultConfig) {
151
- console.error(`Error: ${FAILURE_MESSAGES["not-configured"]}`);
152
- process.exitCode = 1;
153
- return;
154
- }
155
- // Extract local credentials
156
- const credentials = agentId === "opencode" && options.provider
157
- ? getStoredCredentialsForProvider(options.provider)
158
- : findCredentials(agentId);
159
- if (!credentials) {
160
- const hint = agentId === "opencode"
161
- ? `No credentials found for provider '${options.provider}'`
162
- : `No credentials found for ${agentId}\nHint: Authenticate with the agent first, or use 'axauth list' to check status`;
163
- console.error(`Error: ${hint}`);
164
- process.exitCode = 1;
165
- return;
166
- }
167
- // Push to vault
168
- const result = await pushVaultCredentials({
169
- agentId,
170
- name: options.name,
171
- credentials,
172
- provider: options.provider,
173
- });
174
- if (!result.ok) {
175
- console.error(`Error: ${FAILURE_MESSAGES[result.reason]}`);
176
- process.exitCode = 1;
177
- return;
178
- }
179
- const label = options.provider ? `${agentId}/${options.provider}` : agentId;
180
- console.error(`Credentials pushed to vault: ${label} → ${options.name}`);
181
- }
182
- export { handleVaultFetch, handleVaultPush };
118
+ export { handleVaultFetch };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Vault push command - upload local credentials to axvault server.
3
+ */
4
+ interface VaultPushOptions {
5
+ agent: string;
6
+ name: string;
7
+ provider?: string;
8
+ displayName?: string;
9
+ notes?: string;
10
+ }
11
+ /**
12
+ * Handle vault push command.
13
+ *
14
+ * Reads local credentials for an agent and pushes them to the vault server
15
+ * in raw (unencrypted) format. This allows the vault to handle encryption
16
+ * and automatic token refresh.
17
+ */
18
+ declare function handleVaultPush(options: VaultPushOptions): Promise<void>;
19
+ export { handleVaultPush };
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Vault push command - upload local credentials to axvault server.
3
+ */
4
+ import { findCredentials } from "../auth/registry.js";
5
+ import { getStoredCredentialsForProvider } from "../auth/agents/opencode.js";
6
+ import { pushVaultCredentials } from "../vault/vault-client.js";
7
+ import { getVaultConfig } from "../vault/vault-config.js";
8
+ import { FAILURE_MESSAGES } from "./vault-failure-messages.js";
9
+ import { validateAgent } from "./validate-agent.js";
10
+ /**
11
+ * Handle vault push command.
12
+ *
13
+ * Reads local credentials for an agent and pushes them to the vault server
14
+ * in raw (unencrypted) format. This allows the vault to handle encryption
15
+ * and automatic token refresh.
16
+ */
17
+ async function handleVaultPush(options) {
18
+ // Validate agent
19
+ const agentId = validateAgent(options.agent);
20
+ if (!agentId)
21
+ return;
22
+ // Validate provider flag usage
23
+ if (options.provider && agentId !== "opencode") {
24
+ console.error("Error: --provider flag is only supported for opencode agent");
25
+ process.exitCode = 2;
26
+ return;
27
+ }
28
+ if (agentId === "opencode" && !options.provider) {
29
+ console.error("Error: --provider is required for opencode");
30
+ console.error("Hint: Use 'axauth list --json' to see available providers");
31
+ process.exitCode = 1;
32
+ return;
33
+ }
34
+ // Check vault is configured
35
+ const vaultConfig = getVaultConfig();
36
+ if (!vaultConfig) {
37
+ console.error(`Error: ${FAILURE_MESSAGES["not-configured"]}`);
38
+ process.exitCode = 1;
39
+ return;
40
+ }
41
+ // Extract local credentials
42
+ const credentials = agentId === "opencode" && options.provider
43
+ ? getStoredCredentialsForProvider(options.provider)
44
+ : findCredentials(agentId);
45
+ if (!credentials) {
46
+ const hint = agentId === "opencode"
47
+ ? `No credentials found for provider '${options.provider}'`
48
+ : `No credentials found for ${agentId}\nHint: Authenticate with the agent first, or use 'axauth list' to check status`;
49
+ console.error(`Error: ${hint}`);
50
+ process.exitCode = 1;
51
+ return;
52
+ }
53
+ // Push to vault
54
+ const result = await pushVaultCredentials({
55
+ agentId,
56
+ name: options.name,
57
+ credentials,
58
+ provider: options.provider,
59
+ displayName: options.displayName,
60
+ notes: options.notes,
61
+ });
62
+ if (!result.ok) {
63
+ console.error(`Error: ${FAILURE_MESSAGES[result.reason]}`);
64
+ process.exitCode = 1;
65
+ return;
66
+ }
67
+ const label = options.provider ? `${agentId}/${options.provider}` : agentId;
68
+ console.error(`Credentials pushed to vault: ${label} → ${options.name}`);
69
+ }
70
+ export { handleVaultPush };
@@ -14,6 +14,8 @@ type VaultResult = {
14
14
  ok: true;
15
15
  credentials: Credentials;
16
16
  refreshed: boolean;
17
+ displayName: string | undefined;
18
+ notes: string | undefined;
17
19
  } | {
18
20
  ok: false;
19
21
  reason: VaultFailureReason;
@@ -61,6 +63,10 @@ interface VaultPushOptions {
61
63
  credentials: Credentials;
62
64
  /** Provider for multi-provider agents (e.g., "anthropic" for OpenCode) */
63
65
  provider?: string;
66
+ /** Human-readable display name for this credential */
67
+ displayName?: string;
68
+ /** Additional notes about this credential */
69
+ notes?: string;
64
70
  }
65
71
  /**
66
72
  * Push credentials to the axvault server.
@@ -11,6 +11,14 @@ import { getVaultConfig } from "./vault-config.js";
11
11
  const VaultCredentialResponse = z.object({
12
12
  name: z.string(),
13
13
  credential: z.record(z.string(), z.unknown()),
14
+ displayName: z
15
+ .string()
16
+ .nullish()
17
+ .transform((value) => value ?? undefined),
18
+ notes: z
19
+ .string()
20
+ .nullish()
21
+ .transform((value) => value ?? undefined),
14
22
  updatedAt: z.string(),
15
23
  });
16
24
  /**
@@ -84,6 +92,8 @@ async function fetchVaultCredentials(options) {
84
92
  ok: true,
85
93
  credentials: body.credential,
86
94
  refreshed: wasRefreshed,
95
+ displayName: body.displayName,
96
+ notes: body.notes,
87
97
  };
88
98
  }
89
99
  catch {
@@ -133,6 +143,10 @@ async function pushVaultCredentials(options) {
133
143
  type: options.credentials.type,
134
144
  data: options.credentials.data,
135
145
  ...(options.provider !== undefined && { provider: options.provider }),
146
+ ...(options.displayName !== undefined && {
147
+ displayName: options.displayName,
148
+ }),
149
+ ...(options.notes !== undefined && { notes: options.notes }),
136
150
  }),
137
151
  });
138
152
  // Handle error responses
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "axauth",
3
3
  "author": "Łukasz Jerciński",
4
4
  "license": "MIT",
5
- "version": "3.1.6",
5
+ "version": "3.3.0",
6
6
  "description": "Authentication management library and CLI for AI coding agents",
7
7
  "repository": {
8
8
  "type": "git",
@@ -62,31 +62,31 @@
62
62
  "automation",
63
63
  "coding-assistant"
64
64
  ],
65
- "packageManager": "pnpm@10.28.1",
65
+ "packageManager": "pnpm@10.30.1",
66
66
  "engines": {
67
67
  "node": ">=22.14.0"
68
68
  },
69
69
  "dependencies": {
70
70
  "@commander-js/extra-typings": "^14.0.0",
71
- "@inquirer/password": "^5.0.4",
71
+ "@inquirer/password": "^5.0.7",
72
72
  "axconfig": "^3.6.3",
73
- "axexec": "^2.0.0",
73
+ "axexec": "^2.0.1",
74
74
  "axshared": "^5.0.0",
75
- "commander": "^14.0.2",
76
- "zod": "^4.3.5"
75
+ "commander": "^14.0.3",
76
+ "zod": "^4.3.6"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@total-typescript/ts-reset": "^0.6.1",
80
- "@types/node": "^25.0.10",
81
- "@vitest/coverage-v8": "^4.0.17",
82
- "eslint": "^9.39.2",
83
- "eslint-config-axkit": "^1.1.0",
80
+ "@types/node": "^25.3.0",
81
+ "@vitest/coverage-v8": "^4.0.18",
82
+ "eslint": "^10.0.1",
83
+ "eslint-config-axkit": "^1.2.1",
84
84
  "fta-check": "^1.5.1",
85
85
  "fta-cli": "^3.0.0",
86
- "knip": "^5.82.1",
86
+ "knip": "^5.85.0",
87
87
  "prettier": "3.8.1",
88
- "semantic-release": "^25.0.2",
88
+ "semantic-release": "^25.0.3",
89
89
  "typescript": "^5.9.3",
90
- "vitest": "^4.0.17"
90
+ "vitest": "^4.0.18"
91
91
  }
92
92
  }