axauth 1.11.1 → 1.12.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/refresh-credentials.js +9 -20
- package/dist/auth/spawn-agent-with-file-monitor.d.ts +20 -0
- package/dist/auth/spawn-agent-with-file-monitor.js +57 -0
- package/dist/auth/spawn-agent.d.ts +10 -4
- package/dist/auth/spawn-agent.js +27 -4
- package/dist/auth/wait-for-file-update.d.ts +6 -1
- package/dist/auth/wait-for-file-update.js +17 -0
- package/dist/cli.js +45 -11
- package/dist/commands/vault.d.ts +1 -0
- package/dist/commands/vault.js +20 -3
- package/dist/vault/vault-client.js +3 -0
- package/package.json +1 -1
|
@@ -9,8 +9,8 @@ import { tmpdir } from "node:os";
|
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { buildAgentRuntimeEnvironment, getAgent, getAgentConfigSubdirectory, } from "axshared";
|
|
11
11
|
import { buildRefreshedCredentials } from "./build-refreshed-credentials.js";
|
|
12
|
-
import {
|
|
13
|
-
import { getFileStats
|
|
12
|
+
import { spawnAgentWithFileMonitor } from "./spawn-agent-with-file-monitor.js";
|
|
13
|
+
import { getFileStats } from "./wait-for-file-update.js";
|
|
14
14
|
/** Default timeout for refresh operations in milliseconds */
|
|
15
15
|
const DEFAULT_REFRESH_TIMEOUT_MS = 30_000;
|
|
16
16
|
/**
|
|
@@ -95,30 +95,19 @@ async function refreshCredentials(creds, options) {
|
|
|
95
95
|
const cliArguments = agent.simplePromptArguments("ping", {
|
|
96
96
|
provider: options?.provider,
|
|
97
97
|
});
|
|
98
|
-
// 8. Spawn agent with
|
|
99
|
-
|
|
98
|
+
// 8. Spawn agent with file monitoring
|
|
99
|
+
// File monitoring starts before agent spawn to catch async writes that may
|
|
100
|
+
// occur during process execution (e.g., Gemini's async event handlers).
|
|
101
|
+
const { timedOut, exitCode, fileUpdated } = await spawnAgentWithFileMonitor(agent.cli, cliArguments, fullEnvironment, timeout, credentialFilePath, beforeStats);
|
|
100
102
|
if (timedOut) {
|
|
101
103
|
return { ok: false, error: "Refresh timed out" };
|
|
102
104
|
}
|
|
103
|
-
if (exitCode !== 0 && exitCode !==
|
|
105
|
+
if (exitCode !== 0 && exitCode !== undefined) {
|
|
104
106
|
return { ok: false, error: `Agent exited with code ${exitCode}` };
|
|
105
107
|
}
|
|
106
|
-
|
|
107
|
-
// Some agents (like Gemini) use async event handlers that may not complete
|
|
108
|
-
// before the process exits. Wait for the file to be created or modified.
|
|
109
|
-
//
|
|
110
|
-
// If the wait times out or errors, we still proceed to read credentials.
|
|
111
|
-
// This is intentional: even if monitoring failed, the agent may have written
|
|
112
|
-
// the file by now. Failing here would be worse than attempting to read.
|
|
113
|
-
const waitResult = await waitForFileUpdate(credentialFilePath, beforeStats, {
|
|
114
|
-
timeout: 5000,
|
|
115
|
-
});
|
|
116
|
-
if (!waitResult.ok) {
|
|
117
|
-
const errorDetail = waitResult.reason === "error" && waitResult.error
|
|
118
|
-
? ` - ${waitResult.error}`
|
|
119
|
-
: "";
|
|
108
|
+
if (!fileUpdated) {
|
|
120
109
|
console.error(`Warning: Credential file "${credentialFilePath}" was not observed ` +
|
|
121
|
-
`to be updated
|
|
110
|
+
`to be updated. Proceeding may read stale credentials.`);
|
|
122
111
|
}
|
|
123
112
|
// 9. Read refreshed credentials from temp directory
|
|
124
113
|
const { extractRawCredentialsFromDirectory } = await import("./registry.js");
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent subprocess spawning with file monitoring.
|
|
3
|
+
*
|
|
4
|
+
* Starts file monitoring before agent spawn to catch async writes that may
|
|
5
|
+
* occur during process execution (e.g., Gemini's async event handlers).
|
|
6
|
+
*/
|
|
7
|
+
import { type SpawnResult } from "./spawn-agent.js";
|
|
8
|
+
import { type FileStats } from "./wait-for-file-update.js";
|
|
9
|
+
/**
|
|
10
|
+
* Spawn agent and monitor a file for updates.
|
|
11
|
+
*
|
|
12
|
+
* File monitoring starts before agent spawn to catch writes that occur during
|
|
13
|
+
* process execution, not just after exit.
|
|
14
|
+
*
|
|
15
|
+
* @returns Object with spawn result and whether file was updated
|
|
16
|
+
*/
|
|
17
|
+
declare function spawnAgentWithFileMonitor(cli: string, arguments_: string[], environment: NodeJS.ProcessEnv, timeout: number, filePath: string, beforeStats: FileStats | undefined): Promise<SpawnResult & {
|
|
18
|
+
fileUpdated: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
export { spawnAgentWithFileMonitor };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent subprocess spawning with file monitoring.
|
|
3
|
+
*
|
|
4
|
+
* Starts file monitoring before agent spawn to catch async writes that may
|
|
5
|
+
* occur during process execution (e.g., Gemini's async event handlers).
|
|
6
|
+
*/
|
|
7
|
+
import { spawnAgent } from "./spawn-agent.js";
|
|
8
|
+
import { getFileStats, waitForFileUpdate, } from "./wait-for-file-update.js";
|
|
9
|
+
/**
|
|
10
|
+
* Spawn agent and monitor a file for updates.
|
|
11
|
+
*
|
|
12
|
+
* File monitoring starts before agent spawn to catch writes that occur during
|
|
13
|
+
* process execution, not just after exit.
|
|
14
|
+
*
|
|
15
|
+
* @returns Object with spawn result and whether file was updated
|
|
16
|
+
*/
|
|
17
|
+
async function spawnAgentWithFileMonitor(cli, arguments_, environment, timeout, filePath, beforeStats) {
|
|
18
|
+
const fileMonitorAbort = new AbortController();
|
|
19
|
+
// Start file monitoring BEFORE spawning agent
|
|
20
|
+
const fileMonitorPromise = waitForFileUpdate(filePath, beforeStats, {
|
|
21
|
+
timeout: timeout + 5000,
|
|
22
|
+
signal: fileMonitorAbort.signal,
|
|
23
|
+
});
|
|
24
|
+
try {
|
|
25
|
+
// Spawn agent
|
|
26
|
+
const spawnResult = await spawnAgent(cli, arguments_, environment, timeout);
|
|
27
|
+
// On early exit, abort file monitor
|
|
28
|
+
if (spawnResult.timedOut ||
|
|
29
|
+
(spawnResult.exitCode !== 0 && spawnResult.exitCode !== undefined)) {
|
|
30
|
+
return { ...spawnResult, fileUpdated: false };
|
|
31
|
+
}
|
|
32
|
+
// Agent exited successfully - wait for in-flight writes
|
|
33
|
+
// Use 5s grace period to match previous behavior (some agents like Gemini
|
|
34
|
+
// may write credentials several seconds after process exit)
|
|
35
|
+
const gracePeriodMs = 5000;
|
|
36
|
+
const fileResult = await Promise.race([
|
|
37
|
+
fileMonitorPromise,
|
|
38
|
+
new Promise((resolve) => {
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
resolve({ ok: false });
|
|
41
|
+
}, gracePeriodMs).unref();
|
|
42
|
+
}),
|
|
43
|
+
]);
|
|
44
|
+
// Final check in case write completed after race
|
|
45
|
+
const finalStats = getFileStats(filePath);
|
|
46
|
+
const fileUpdated = fileResult.ok ||
|
|
47
|
+
(finalStats !== undefined &&
|
|
48
|
+
(beforeStats === undefined ||
|
|
49
|
+
finalStats.mtimeMs !== beforeStats.mtimeMs ||
|
|
50
|
+
finalStats.size !== beforeStats.size));
|
|
51
|
+
return { ...spawnResult, fileUpdated };
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
fileMonitorAbort.abort();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export { spawnAgentWithFileMonitor };
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent subprocess spawning utility.
|
|
3
3
|
*/
|
|
4
|
+
/** Result of spawning an agent process */
|
|
5
|
+
interface SpawnResult {
|
|
6
|
+
timedOut: boolean;
|
|
7
|
+
exitCode: number | undefined;
|
|
8
|
+
}
|
|
4
9
|
/**
|
|
5
10
|
* Spawn agent and wait for completion.
|
|
6
11
|
*
|
|
12
|
+
* Enforces a hard timeout - if the process doesn't exit after SIGTERM,
|
|
13
|
+
* escalates to SIGKILL after a grace period and resolves anyway.
|
|
14
|
+
*
|
|
7
15
|
* @returns Object with timedOut flag and exit code
|
|
8
16
|
*/
|
|
9
|
-
declare function spawnAgent(binary: string, arguments_: string[], environment: NodeJS.ProcessEnv, timeout: number): Promise<
|
|
10
|
-
timedOut: boolean;
|
|
11
|
-
exitCode: number | null;
|
|
12
|
-
}>;
|
|
17
|
+
declare function spawnAgent(binary: string, arguments_: string[], environment: NodeJS.ProcessEnv, timeout: number): Promise<SpawnResult>;
|
|
13
18
|
export { spawnAgent };
|
|
19
|
+
export type { SpawnResult };
|
package/dist/auth/spawn-agent.js
CHANGED
|
@@ -2,37 +2,60 @@
|
|
|
2
2
|
* Agent subprocess spawning utility.
|
|
3
3
|
*/
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
+
/** Grace period before escalating SIGTERM to SIGKILL */
|
|
6
|
+
const SIGKILL_GRACE_MS = 5000;
|
|
5
7
|
/**
|
|
6
8
|
* Spawn agent and wait for completion.
|
|
7
9
|
*
|
|
10
|
+
* Enforces a hard timeout - if the process doesn't exit after SIGTERM,
|
|
11
|
+
* escalates to SIGKILL after a grace period and resolves anyway.
|
|
12
|
+
*
|
|
8
13
|
* @returns Object with timedOut flag and exit code
|
|
9
14
|
*/
|
|
10
15
|
async function spawnAgent(binary, arguments_, environment, timeout) {
|
|
11
16
|
return new Promise((resolve) => {
|
|
12
17
|
const child = spawn(binary, arguments_, {
|
|
13
18
|
env: environment,
|
|
14
|
-
|
|
19
|
+
// Ignore stdin/stdout to prevent pipe buffer deadlocks, but inherit
|
|
20
|
+
// stderr so agent error messages are visible for debugging
|
|
21
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
15
22
|
});
|
|
16
23
|
let timedOut = false;
|
|
17
24
|
let resolved = false;
|
|
25
|
+
let killTimer;
|
|
26
|
+
const cleanup = () => {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
if (killTimer)
|
|
29
|
+
clearTimeout(killTimer);
|
|
30
|
+
};
|
|
18
31
|
const timer = setTimeout(() => {
|
|
19
32
|
if (!resolved) {
|
|
20
33
|
timedOut = true;
|
|
21
34
|
child.kill("SIGTERM");
|
|
35
|
+
// If process doesn't exit after SIGTERM, escalate to SIGKILL
|
|
36
|
+
// and resolve anyway to prevent indefinite hangs
|
|
37
|
+
killTimer = setTimeout(() => {
|
|
38
|
+
if (!resolved) {
|
|
39
|
+
child.kill("SIGKILL");
|
|
40
|
+
resolved = true;
|
|
41
|
+
cleanup();
|
|
42
|
+
resolve({ timedOut: true, exitCode: undefined });
|
|
43
|
+
}
|
|
44
|
+
}, SIGKILL_GRACE_MS);
|
|
22
45
|
}
|
|
23
46
|
}, timeout);
|
|
24
47
|
child.on("error", () => {
|
|
25
48
|
if (!resolved) {
|
|
26
49
|
resolved = true;
|
|
27
|
-
|
|
50
|
+
cleanup();
|
|
28
51
|
resolve({ timedOut: false, exitCode: 1 });
|
|
29
52
|
}
|
|
30
53
|
});
|
|
31
54
|
child.on("close", (code) => {
|
|
32
55
|
if (!resolved) {
|
|
33
56
|
resolved = true;
|
|
34
|
-
|
|
35
|
-
resolve({ timedOut, exitCode: code });
|
|
57
|
+
cleanup();
|
|
58
|
+
resolve({ timedOut, exitCode: code ?? undefined });
|
|
36
59
|
}
|
|
37
60
|
});
|
|
38
61
|
});
|
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
* used by token refresh to wait for credential files to be written.
|
|
6
6
|
*/
|
|
7
7
|
import { type Stats } from "node:fs";
|
|
8
|
+
/** File stats type for external use */
|
|
9
|
+
type FileStats = Stats;
|
|
8
10
|
/** Options for waiting on file changes */
|
|
9
11
|
interface WaitOptions {
|
|
10
12
|
/** Maximum time to wait in milliseconds (default: 5000) */
|
|
11
13
|
timeout?: number;
|
|
14
|
+
/** AbortSignal to cancel waiting early */
|
|
15
|
+
signal?: AbortSignal;
|
|
12
16
|
}
|
|
13
17
|
/** Result of waiting for file change */
|
|
14
18
|
type WaitResult = {
|
|
@@ -16,7 +20,7 @@ type WaitResult = {
|
|
|
16
20
|
reason: "created" | "modified";
|
|
17
21
|
} | {
|
|
18
22
|
ok: false;
|
|
19
|
-
reason: "timeout" | "error";
|
|
23
|
+
reason: "timeout" | "error" | "aborted";
|
|
20
24
|
error?: string;
|
|
21
25
|
};
|
|
22
26
|
/**
|
|
@@ -35,3 +39,4 @@ declare function getFileStats(filePath: string): Stats | undefined;
|
|
|
35
39
|
*/
|
|
36
40
|
declare function waitForFileUpdate(filePath: string, beforeStats: Stats | undefined, options?: WaitOptions): Promise<WaitResult>;
|
|
37
41
|
export { getFileStats, waitForFileUpdate };
|
|
42
|
+
export type { FileStats };
|
|
@@ -47,8 +47,13 @@ function checkFileChange(filePath, beforeStats) {
|
|
|
47
47
|
*/
|
|
48
48
|
async function waitForFileUpdate(filePath, beforeStats, options) {
|
|
49
49
|
const timeout = options?.timeout ?? 5000;
|
|
50
|
+
const signal = options?.signal;
|
|
50
51
|
const parentDirectory = path.dirname(filePath);
|
|
51
52
|
const fileName = path.basename(filePath);
|
|
53
|
+
// Check if already aborted
|
|
54
|
+
if (signal?.aborted) {
|
|
55
|
+
return { ok: false, reason: "aborted" };
|
|
56
|
+
}
|
|
52
57
|
// Check immediately (file might already be written)
|
|
53
58
|
const immediate = checkFileChange(filePath, beforeStats);
|
|
54
59
|
if (immediate) {
|
|
@@ -57,6 +62,7 @@ async function waitForFileUpdate(filePath, beforeStats, options) {
|
|
|
57
62
|
return new Promise((resolve) => {
|
|
58
63
|
let watcher;
|
|
59
64
|
let resolved = false;
|
|
65
|
+
let abortHandler;
|
|
60
66
|
const cleanup = (timeoutId, pollId) => {
|
|
61
67
|
if (resolved)
|
|
62
68
|
return;
|
|
@@ -64,6 +70,9 @@ async function waitForFileUpdate(filePath, beforeStats, options) {
|
|
|
64
70
|
watcher?.close();
|
|
65
71
|
clearTimeout(timeoutId);
|
|
66
72
|
clearInterval(pollId);
|
|
73
|
+
if (abortHandler && signal) {
|
|
74
|
+
signal.removeEventListener("abort", abortHandler);
|
|
75
|
+
}
|
|
67
76
|
};
|
|
68
77
|
// Set timeout
|
|
69
78
|
const timeoutId = setTimeout(() => {
|
|
@@ -79,6 +88,14 @@ async function waitForFileUpdate(filePath, beforeStats, options) {
|
|
|
79
88
|
resolve(result);
|
|
80
89
|
}
|
|
81
90
|
}, 100);
|
|
91
|
+
// Handle abort signal
|
|
92
|
+
if (signal) {
|
|
93
|
+
abortHandler = () => {
|
|
94
|
+
cleanup(timeoutId, pollIntervalId);
|
|
95
|
+
resolve({ ok: false, reason: "aborted" });
|
|
96
|
+
};
|
|
97
|
+
signal.addEventListener("abort", abortHandler);
|
|
98
|
+
}
|
|
82
99
|
// Watch parent directory for changes (for faster responsiveness)
|
|
83
100
|
try {
|
|
84
101
|
watcher = watch(parentDirectory, (_eventType, changedFile) => {
|
package/dist/cli.js
CHANGED
|
@@ -28,24 +28,29 @@ Examples:
|
|
|
28
28
|
# List authenticated agents
|
|
29
29
|
axauth list
|
|
30
30
|
|
|
31
|
-
# Filter to show only authenticated agents
|
|
32
|
-
axauth list | tail -n +2 | awk -F'\t' '$2 == "authenticated"'
|
|
33
|
-
|
|
34
31
|
# Get access token for an agent
|
|
35
32
|
axauth token --agent claude
|
|
36
33
|
|
|
37
|
-
# Export credentials
|
|
38
|
-
axauth export --agent claude --output creds.json
|
|
34
|
+
# Export/import credentials (file-based)
|
|
35
|
+
axauth export --agent claude --output creds.json
|
|
36
|
+
axauth install-credentials --agent claude --input creds.json
|
|
37
|
+
|
|
38
|
+
# For vault operations: export raw format (--no-encrypt)
|
|
39
|
+
# Note: install-credentials cannot consume raw exports
|
|
40
|
+
axauth export --agent claude --output creds.json --no-encrypt
|
|
39
41
|
|
|
40
42
|
# Remove credentials (agent will prompt for login)
|
|
41
43
|
axauth remove-credentials --agent claude
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
CI/CD with axvault (recommended):
|
|
46
|
+
# One-time: push local credentials to vault
|
|
47
|
+
axauth vault push --agent claude --name ci
|
|
48
|
+
|
|
49
|
+
# In CI/CD: fetch and install credentials
|
|
50
|
+
AXVAULT_URL=https://vault.example.com AXVAULT_API_KEY=axv_sk_xxx \
|
|
51
|
+
axauth vault fetch --agent claude --name ci --install
|
|
45
52
|
|
|
46
|
-
#
|
|
47
|
-
AXVAULT='{"url":"https://vault.example.com","apiKey":"axv_sk_xxx"}' \
|
|
48
|
-
axauth vault fetch --agent claude --name ci --install`);
|
|
53
|
+
# See "axauth vault --help" for full workflow documentation`);
|
|
49
54
|
program
|
|
50
55
|
.command("list")
|
|
51
56
|
.description("List agents and their auth status")
|
|
@@ -118,7 +123,32 @@ program
|
|
|
118
123
|
// Vault subcommand
|
|
119
124
|
const vault = program
|
|
120
125
|
.command("vault")
|
|
121
|
-
.description("Manage credentials stored in axvault server")
|
|
126
|
+
.description("Manage credentials stored in axvault server")
|
|
127
|
+
.addHelpText("after", String.raw `
|
|
128
|
+
Workflow:
|
|
129
|
+
1. Push local credentials to vault (one-time setup from authenticated machine):
|
|
130
|
+
axauth vault push --agent claude --name ci
|
|
131
|
+
|
|
132
|
+
2. Fetch credentials in CI/CD (set AXVAULT_URL and AXVAULT_API_KEY):
|
|
133
|
+
axauth vault fetch --agent claude --name ci --install
|
|
134
|
+
|
|
135
|
+
Environment variables:
|
|
136
|
+
AXVAULT JSON config: {"url":"...","apiKey":"..."}
|
|
137
|
+
AXVAULT_URL Vault server URL
|
|
138
|
+
AXVAULT_API_KEY API key for authentication
|
|
139
|
+
|
|
140
|
+
OpenCode multi-provider support:
|
|
141
|
+
OpenCode stores credentials for multiple providers (anthropic, openai, google).
|
|
142
|
+
Use --provider to push a single provider (fetch uses the stored name):
|
|
143
|
+
|
|
144
|
+
axauth vault push --agent opencode --provider anthropic --name ci-anthropic
|
|
145
|
+
axauth vault fetch --agent opencode --name ci-anthropic --install
|
|
146
|
+
|
|
147
|
+
Quick reference:
|
|
148
|
+
vault push Upload local credentials to vault (requires write access)
|
|
149
|
+
vault fetch Download credentials from vault (requires read access)
|
|
150
|
+
|
|
151
|
+
Use "axauth vault <command> --help" for detailed options.`);
|
|
122
152
|
vault
|
|
123
153
|
.command("fetch")
|
|
124
154
|
.description("Fetch credentials from vault server")
|
|
@@ -167,6 +197,7 @@ vault
|
|
|
167
197
|
.description("Push local credentials to vault server")
|
|
168
198
|
.requiredOption("-a, --agent <agent>", `Agent to push credentials from (${AGENT_CLIS.join(", ")})`)
|
|
169
199
|
.requiredOption("-n, --name <name>", "Credential name in vault (e.g., ci, prod)")
|
|
200
|
+
.option("-p, --provider <provider>", "Provider to push (opencode only; pushes single provider instead of all)")
|
|
170
201
|
.addHelpText("after", String.raw `
|
|
171
202
|
Environment variables (option 1 - single JSON):
|
|
172
203
|
AXVAULT JSON config: {"url":"...","apiKey":"..."}
|
|
@@ -179,6 +210,9 @@ Examples:
|
|
|
179
210
|
# Push local Claude credentials to vault as "ci"
|
|
180
211
|
axauth vault push --agent claude --name ci
|
|
181
212
|
|
|
213
|
+
# Push a single OpenCode provider to vault
|
|
214
|
+
axauth vault push --agent opencode --provider anthropic --name ci-anthropic
|
|
215
|
+
|
|
182
216
|
# Push to a different vault instance
|
|
183
217
|
AXVAULT_URL=https://vault.example.com AXVAULT_API_KEY=axv_sk_xxx \
|
|
184
218
|
axauth vault push --agent gemini --name prod`)
|
package/dist/commands/vault.d.ts
CHANGED
package/dist/commands/vault.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Vault commands - fetch and push credentials to axvault server.
|
|
3
3
|
*/
|
|
4
4
|
import { credentialsToEnvironment, extractRawCredentials, installCredentials, } from "../auth/registry.js";
|
|
5
|
+
import { extractProviderCredentials } from "../auth/agents/opencode.js";
|
|
5
6
|
import { fetchVaultCredentials, pushVaultCredentials, } from "../vault/vault-client.js";
|
|
6
7
|
import { getVaultConfig } from "../vault/vault-config.js";
|
|
7
8
|
import { validateAgent } from "./validate-agent.js";
|
|
@@ -132,6 +133,12 @@ async function handleVaultPush(options) {
|
|
|
132
133
|
const agentId = validateAgent(options.agent);
|
|
133
134
|
if (!agentId)
|
|
134
135
|
return;
|
|
136
|
+
// Validate --provider is only used with opencode
|
|
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
|
+
}
|
|
135
142
|
// Check vault is configured
|
|
136
143
|
const vaultConfig = getVaultConfig();
|
|
137
144
|
if (!vaultConfig) {
|
|
@@ -140,13 +147,22 @@ async function handleVaultPush(options) {
|
|
|
140
147
|
return;
|
|
141
148
|
}
|
|
142
149
|
// Extract local credentials
|
|
143
|
-
const
|
|
144
|
-
if (!
|
|
150
|
+
const rawCredentials = extractRawCredentials(agentId);
|
|
151
|
+
if (!rawCredentials) {
|
|
145
152
|
console.error(`Error: No credentials found for ${agentId}`);
|
|
146
153
|
console.error("Hint: Authenticate with the agent first, or use 'axauth list' to check status");
|
|
147
154
|
process.exitCode = 1;
|
|
148
155
|
return;
|
|
149
156
|
}
|
|
157
|
+
// Handle per-provider extraction for OpenCode
|
|
158
|
+
const credentials = options.provider
|
|
159
|
+
? extractProviderCredentials(rawCredentials, options.provider)
|
|
160
|
+
: rawCredentials;
|
|
161
|
+
if (!credentials) {
|
|
162
|
+
console.error(`Error: No credentials found for provider '${options.provider}'`);
|
|
163
|
+
process.exitCode = 1;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
150
166
|
// Push to vault
|
|
151
167
|
const result = await pushVaultCredentials({
|
|
152
168
|
agentId,
|
|
@@ -158,6 +174,7 @@ async function handleVaultPush(options) {
|
|
|
158
174
|
process.exitCode = 1;
|
|
159
175
|
return;
|
|
160
176
|
}
|
|
161
|
-
|
|
177
|
+
const label = options.provider ? `${agentId}/${options.provider}` : agentId;
|
|
178
|
+
console.error(`Credentials pushed to vault: ${label} → ${options.name}`);
|
|
162
179
|
}
|
|
163
180
|
export { handleVaultFetch, handleVaultPush };
|
|
@@ -14,6 +14,7 @@ const VaultCredentialResponse = z.object({
|
|
|
14
14
|
name: z.string(),
|
|
15
15
|
type: CredentialType,
|
|
16
16
|
data: z.record(z.string(), z.unknown()),
|
|
17
|
+
provider: z.string().trim().min(1).optional(), // OpenCode provider support
|
|
17
18
|
expiresAt: z.string().nullish(), // optional for oauth-token type
|
|
18
19
|
updatedAt: z.string(),
|
|
19
20
|
});
|
|
@@ -88,6 +89,7 @@ async function fetchVaultCredentials(options) {
|
|
|
88
89
|
agent: options.agentId,
|
|
89
90
|
type: body.type,
|
|
90
91
|
data: body.data,
|
|
92
|
+
provider: body.provider,
|
|
91
93
|
};
|
|
92
94
|
return {
|
|
93
95
|
ok: true,
|
|
@@ -142,6 +144,7 @@ async function pushVaultCredentials(options) {
|
|
|
142
144
|
body: JSON.stringify({
|
|
143
145
|
type: options.credentials.type,
|
|
144
146
|
data: options.credentials.data,
|
|
147
|
+
provider: options.credentials.provider,
|
|
145
148
|
}),
|
|
146
149
|
});
|
|
147
150
|
// Handle error responses
|