axexec 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +21 -0
  2. package/dist/agents/claude-code/types.d.ts +2 -2
  3. package/dist/agents/codex/adapter.js +8 -13
  4. package/dist/agents/gemini/types.d.ts +4 -4
  5. package/dist/build-agent-environment.d.ts +2 -0
  6. package/dist/build-agent-environment.js +19 -0
  7. package/dist/build-execution-metadata.d.ts +10 -0
  8. package/dist/build-execution-metadata.js +27 -0
  9. package/dist/build-permissions-config.d.ts +24 -0
  10. package/dist/build-permissions-config.js +48 -0
  11. package/dist/credentials/credentials.d.ts +39 -0
  12. package/dist/credentials/credentials.js +73 -0
  13. package/dist/credentials/get-credential-environment.d.ts +7 -5
  14. package/dist/credentials/get-credential-environment.js +44 -14
  15. package/dist/credentials/get-environment-trimmed.d.ts +5 -0
  16. package/dist/credentials/get-environment-trimmed.js +8 -0
  17. package/dist/credentials/install-credentials.d.ts +7 -6
  18. package/dist/credentials/install-credentials.js +26 -16
  19. package/dist/credentials/resolve-string-field.d.ts +7 -0
  20. package/dist/credentials/resolve-string-field.js +21 -0
  21. package/dist/credentials/types.d.ts +1 -10
  22. package/dist/credentials/write-agent-credentials.d.ts +28 -6
  23. package/dist/credentials/write-agent-credentials.js +88 -43
  24. package/dist/execute-agent.d.ts +12 -0
  25. package/dist/execute-agent.js +68 -0
  26. package/dist/index.d.ts +11 -0
  27. package/dist/index.js +10 -0
  28. package/dist/parse-credentials.d.ts +17 -3
  29. package/dist/parse-credentials.js +146 -27
  30. package/dist/resolve-run-diagnostics.d.ts +9 -0
  31. package/dist/resolve-run-diagnostics.js +35 -0
  32. package/dist/run-agent.d.ts +4 -20
  33. package/dist/run-agent.js +67 -109
  34. package/dist/stream-agent.js +5 -15
  35. package/dist/types/run-result.d.ts +81 -0
  36. package/dist/types/run-result.js +4 -0
  37. package/package.json +10 -2
package/README.md CHANGED
@@ -162,6 +162,23 @@ GITHUB_TOKEN=ghp_... axexec -a copilot "Write tests"
162
162
  ```
163
163
 
164
164
  For Claude, `CLAUDE_CODE_OAUTH_TOKEN` (generated via `claude setup-token`) also works.
165
+ For Copilot, token precedence is `COPILOT_GITHUB_TOKEN`, then `GH_TOKEN`, then
166
+ `GITHUB_TOKEN`.
167
+
168
+ For OpenCode, if multiple provider API keys are set, axexec uses the model
169
+ prefix (`anthropic/...`, `openai/...`, `google/...`) to disambiguate. When
170
+ multiple keys are present and no provider prefix is available, set
171
+ `AX_OPENCODE_CREDENTIALS` explicitly. This value must be a JSON-encoded
172
+ Credentials object (not a raw API key), for example:
173
+
174
+ ```json
175
+ {
176
+ "agent": "opencode",
177
+ "type": "api-key",
178
+ "provider": "openai",
179
+ "data": { "apiKey": "sk-..." }
180
+ }
181
+ ```
165
182
 
166
183
  ## Isolation
167
184
 
@@ -173,6 +190,10 @@ axexec provides complete environment isolation:
173
190
  4. Sets environment variables to point agents at the temp directory
174
191
  5. Cleans up the temp directory after execution
175
192
 
193
+ Codex runs with `--dangerously-bypass-approvals-and-sandbox`, so `--allow` /
194
+ `--deny` do not constrain Codex. Run axexec inside an external sandbox if you
195
+ need enforcement.
196
+
176
197
  Agents **never** access:
177
198
 
178
199
  - User's home directory config (`~/.claude`, `~/.codex`, `~/.gemini`)
@@ -77,8 +77,8 @@ type ClaudeUserEvent = z.infer<typeof ClaudeUserEvent>;
77
77
  declare const ClaudeResultEvent: z.ZodObject<{
78
78
  type: z.ZodLiteral<"result">;
79
79
  subtype: z.ZodEnum<{
80
- success: "success";
81
80
  error: "error";
81
+ success: "success";
82
82
  cancelled: "cancelled";
83
83
  }>;
84
84
  timestamp: z.ZodOptional<z.ZodString>;
@@ -153,8 +153,8 @@ declare const ClaudeEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
153
153
  }, z.core.$strip>, z.ZodObject<{
154
154
  type: z.ZodLiteral<"result">;
155
155
  subtype: z.ZodEnum<{
156
- success: "success";
157
156
  error: "error";
157
+ success: "success";
158
158
  cancelled: "cancelled";
159
159
  }>;
160
160
  timestamp: z.ZodOptional<z.ZodString>;
@@ -16,23 +16,18 @@ const CODEX_INFO = {
16
16
  * Prepares the command to spawn Codex CLI.
17
17
  */
18
18
  function prepareCommand(options) {
19
- // Build CLI arguments.
20
- // Enable network access in sandbox: Codex blocks network by default in
21
- // workspace-write mode, but other agents (Gemini, OpenCode) allow it by
22
- // default. For unified behavior, enable it. This keeps file-level sandbox
23
- // protections while allowing gh, curl, etc.
24
19
  const cliArguments = [
25
20
  "exec",
26
21
  "--json",
27
- "--full-auto",
28
- "-c",
29
- "sandbox_workspace_write.network_access=true",
30
- // Add model flag if specified (o4-mini, gpt-5.1-codex, gpt-5.2, etc.)
31
- ...(options.model ? ["--model", options.model] : []),
32
- // Use "--" to prevent prompt from being misinterpreted as a flag
33
- "--",
34
- options.prompt,
22
+ // Run Codex without internal approvals/sandbox; no Codex-enforced restrictions apply.
23
+ "--dangerously-bypass-approvals-and-sandbox",
35
24
  ];
25
+ // Add model flag if specified (o4-mini, gpt-5.1-codex, gpt-5.2, etc.)
26
+ if (options.model) {
27
+ cliArguments.push("--model", options.model);
28
+ }
29
+ // Use "--" to prevent prompt from being misinterpreted as a flag
30
+ cliArguments.push("--", options.prompt);
36
31
  const environment = {};
37
32
  // Pass through authentication environment variable.
38
33
  // If not set, Codex CLI will handle auth errors on its own.
@@ -41,8 +41,8 @@ declare const GeminiToolResultEvent: z.ZodObject<{
41
41
  type: z.ZodLiteral<"tool_result">;
42
42
  tool_id: z.ZodString;
43
43
  status: z.ZodEnum<{
44
- success: "success";
45
44
  error: "error";
45
+ success: "success";
46
46
  }>;
47
47
  output: z.ZodOptional<z.ZodString>;
48
48
  error: z.ZodOptional<z.ZodObject<{
@@ -67,8 +67,8 @@ type GeminiErrorEvent = z.infer<typeof GeminiErrorEvent>;
67
67
  declare const GeminiResultEvent: z.ZodObject<{
68
68
  type: z.ZodLiteral<"result">;
69
69
  status: z.ZodEnum<{
70
- success: "success";
71
70
  error: "error";
71
+ success: "success";
72
72
  }>;
73
73
  error: z.ZodOptional<z.ZodObject<{
74
74
  type: z.ZodString;
@@ -109,8 +109,8 @@ declare const GeminiEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
109
109
  type: z.ZodLiteral<"tool_result">;
110
110
  tool_id: z.ZodString;
111
111
  status: z.ZodEnum<{
112
- success: "success";
113
112
  error: "error";
113
+ success: "success";
114
114
  }>;
115
115
  output: z.ZodOptional<z.ZodString>;
116
116
  error: z.ZodOptional<z.ZodObject<{
@@ -129,8 +129,8 @@ declare const GeminiEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
129
129
  }, z.core.$strip>, z.ZodObject<{
130
130
  type: z.ZodLiteral<"result">;
131
131
  status: z.ZodEnum<{
132
- success: "success";
133
132
  error: "error";
133
+ success: "success";
134
134
  }>;
135
135
  error: z.ZodOptional<z.ZodObject<{
136
136
  type: z.ZodString;
@@ -0,0 +1,2 @@
1
+ declare function buildBaseEnvironment(additionalExclusions?: string[]): Record<string, string>;
2
+ export { buildBaseEnvironment };
@@ -0,0 +1,19 @@
1
+ const VAULT_ENV_EXCLUSIONS = ["AXVAULT", "AXVAULT_URL", "AXVAULT_API_KEY"];
2
+ function isAxCredentialEnvironment(key) {
3
+ return key.startsWith("AX_") && key.endsWith("_CREDENTIALS");
4
+ }
5
+ function buildBaseEnvironment(additionalExclusions = []) {
6
+ const allExclusions = new Set([
7
+ ...VAULT_ENV_EXCLUSIONS,
8
+ ...additionalExclusions,
9
+ ]);
10
+ const entries = Object.entries(process.env).flatMap(([key, value]) => {
11
+ if (value === undefined)
12
+ return [];
13
+ if (allExclusions.has(key) || isAxCredentialEnvironment(key))
14
+ return [];
15
+ return [[key, value]];
16
+ });
17
+ return Object.fromEntries(entries);
18
+ }
19
+ export { buildBaseEnvironment };
@@ -0,0 +1,10 @@
1
+ import type { AgentCli } from "./types/events.js";
2
+ import type { RunAgentOptions, ExecutionMetadata } from "./types/run-result.js";
3
+ import type { Credentials } from "./credentials/credentials.js";
4
+ import type { installCredentials } from "./credentials/install-credentials.js";
5
+ type CredentialInstallResult = Awaited<ReturnType<typeof installCredentials>>;
6
+ /**
7
+ * Builds the execution metadata for the result.
8
+ */
9
+ declare function buildExecutionMetadata(agentId: AgentCli, options: RunAgentOptions, credentialResult: CredentialInstallResult | undefined, credentials: Credentials | undefined, preserved: boolean): ExecutionMetadata;
10
+ export { buildExecutionMetadata };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Builds the execution metadata for the result.
3
+ */
4
+ function buildExecutionMetadata(agentId, options, credentialResult, credentials, preserved) {
5
+ const execution = {
6
+ agent: agentId,
7
+ model: options.model,
8
+ };
9
+ // Add directory info if an isolated directory was created
10
+ if (credentialResult) {
11
+ execution.directories = {
12
+ base: credentialResult.baseDirectory,
13
+ config: credentialResult.configDirectory,
14
+ data: credentialResult.dataDirectory,
15
+ preserved,
16
+ };
17
+ }
18
+ // Add credential info if credentials were used
19
+ if (credentials) {
20
+ execution.credentials = {
21
+ type: credentials.type,
22
+ provider: credentials.provider,
23
+ };
24
+ }
25
+ return execution;
26
+ }
27
+ export { buildExecutionMetadata };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Permission config building for agent execution.
3
+ */
4
+ import type { AgentCli } from "./types/events.js";
5
+ import type { RunAgentOptions } from "./types/run-result.js";
6
+ import type { installCredentials } from "./credentials/install-credentials.js";
7
+ type CredentialInstallResult = Awaited<ReturnType<typeof installCredentials>>;
8
+ /** Result of building permissions config */
9
+ type BuildConfigResult = {
10
+ ok: true;
11
+ env: Record<string, string>;
12
+ warnings: string[];
13
+ } | {
14
+ ok: false;
15
+ exitCode: number;
16
+ errors: string[];
17
+ };
18
+ /**
19
+ * Builds agent config with permissions.
20
+ *
21
+ * Requires credentialResult for the config directory.
22
+ */
23
+ declare function buildPermissionsConfig(agentId: AgentCli, options: Pick<RunAgentOptions, "allow" | "deny">, credentialResult: CredentialInstallResult): BuildConfigResult;
24
+ export { buildPermissionsConfig };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Permission config building for agent execution.
3
+ */
4
+ import { buildAgentConfig } from "axconfig";
5
+ /**
6
+ * Builds agent config with permissions.
7
+ *
8
+ * Requires credentialResult for the config directory.
9
+ */
10
+ function buildPermissionsConfig(agentId, options, credentialResult) {
11
+ try {
12
+ const configResult = buildAgentConfig({
13
+ agentId,
14
+ allow: options.allow,
15
+ deny: options.deny,
16
+ output: credentialResult.configDirectory,
17
+ });
18
+ if (!configResult.ok) {
19
+ const errors = [];
20
+ if ("message" in configResult) {
21
+ errors.push(`Error: ${configResult.message}`);
22
+ }
23
+ else {
24
+ errors.push("Error building config: permission errors");
25
+ for (const error of configResult.errors) {
26
+ errors.push(` - ${error.reason}`);
27
+ }
28
+ }
29
+ return { ok: false, exitCode: configResult.exitCode, errors };
30
+ }
31
+ const warnings = configResult.warnings.map((warning) => `Warning: ${warning.reason}`);
32
+ // Merge config env with credential env (config env takes precedence)
33
+ return {
34
+ ok: true,
35
+ env: { ...credentialResult.env, ...configResult.env },
36
+ warnings,
37
+ };
38
+ }
39
+ catch (error) {
40
+ const message = error instanceof Error ? error.message : String(error);
41
+ return {
42
+ ok: false,
43
+ exitCode: 1,
44
+ errors: [`Error parsing permissions: ${message}`],
45
+ };
46
+ }
47
+ }
48
+ export { buildPermissionsConfig };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Credential schema and parser.
3
+ *
4
+ * Temporary local copy of the axshared credentials schema.
5
+ * Remove once axshared exports an equivalent parser with matching validation.
6
+ */
7
+ import { z } from "zod";
8
+ /** Zod schema for Credentials */
9
+ declare const Credentials: z.ZodObject<{
10
+ agent: z.ZodEnum<{
11
+ claude: "claude";
12
+ codex: "codex";
13
+ gemini: "gemini";
14
+ opencode: "opencode";
15
+ copilot: "copilot";
16
+ }>;
17
+ type: z.ZodEnum<{
18
+ "oauth-credentials": "oauth-credentials";
19
+ "oauth-token": "oauth-token";
20
+ "api-key": "api-key";
21
+ }>;
22
+ provider: z.ZodOptional<z.ZodString>;
23
+ data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
24
+ }, z.core.$strip>;
25
+ type Credentials = z.infer<typeof Credentials>;
26
+ type ParseCredentialsResult = {
27
+ ok: true;
28
+ credentials: Credentials;
29
+ } | {
30
+ ok: false;
31
+ error: string;
32
+ };
33
+ /**
34
+ * Parse and validate credentials from unknown input.
35
+ *
36
+ * @returns Validated Credentials or error details
37
+ */
38
+ declare function parseCredentials(input: unknown): ParseCredentialsResult;
39
+ export { Credentials, parseCredentials };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Credential schema and parser.
3
+ *
4
+ * Temporary local copy of the axshared credentials schema.
5
+ * Remove once axshared exports an equivalent parser with matching validation.
6
+ */
7
+ import { z } from "zod";
8
+ import { AGENT_CLIS } from "axshared";
9
+ import { resolveStringField } from "./resolve-string-field.js";
10
+ /** Credential storage types */
11
+ const CREDENTIAL_TYPES = [
12
+ "oauth-credentials",
13
+ "oauth-token",
14
+ "api-key",
15
+ ];
16
+ /** Zod schema for credential type validation and parsing */
17
+ const CredentialType = z.enum(CREDENTIAL_TYPES);
18
+ /** Zod schema for Credentials */
19
+ const Credentials = z
20
+ .object({
21
+ /** Agent this credential belongs to */
22
+ agent: z.enum(AGENT_CLIS),
23
+ /** Credential storage type */
24
+ type: CredentialType,
25
+ /** Provider identifier for multi-provider agents like OpenCode */
26
+ provider: z.string().trim().min(1).optional(),
27
+ /** The actual credential data (format depends on type and agent) */
28
+ data: z.record(z.string(), z.unknown()),
29
+ })
30
+ .superRefine((value, context) => {
31
+ if (value.type === "api-key") {
32
+ const apiKey = resolveStringField(value.data, "apiKey", "key");
33
+ if (!apiKey) {
34
+ context.addIssue({
35
+ code: "custom",
36
+ path: ["data"],
37
+ message: 'api-key credentials require "apiKey" or "key"',
38
+ });
39
+ }
40
+ }
41
+ if (value.type === "oauth-token") {
42
+ const oauthToken = resolveStringField(value.data, "oauthToken", "token");
43
+ if (!oauthToken) {
44
+ context.addIssue({
45
+ code: "custom",
46
+ path: ["data"],
47
+ message: 'oauth-token credentials require "oauthToken" or "token"',
48
+ });
49
+ }
50
+ }
51
+ // oauth-credentials payloads are provider-specific and intentionally
52
+ // not validated beyond basic schema checks here.
53
+ });
54
+ function formatParseError(error) {
55
+ return error.issues
56
+ .map((issue) => {
57
+ const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
58
+ return `${path}: ${issue.message}`;
59
+ })
60
+ .join("; ");
61
+ }
62
+ /**
63
+ * Parse and validate credentials from unknown input.
64
+ *
65
+ * @returns Validated Credentials or error details
66
+ */
67
+ function parseCredentials(input) {
68
+ const result = Credentials.safeParse(input);
69
+ if (result.success)
70
+ return { ok: true, credentials: result.data };
71
+ return { ok: false, error: formatParseError(result.error) };
72
+ }
73
+ export { Credentials, parseCredentials };
@@ -1,13 +1,15 @@
1
1
  /**
2
2
  * Environment variable helpers for credential-based auth.
3
+ *
4
+ * Each agent has its own env var requirements. This module extracts
5
+ * the appropriate values from Credentials and returns them as env vars.
3
6
  */
4
- import type { AgentCli } from "axshared";
5
- import type { RawCredentials } from "./types.js";
7
+ import type { Credentials } from "./credentials.js";
6
8
  /**
7
9
  * Gets additional environment variables for credential-based auth.
8
10
  *
9
- * Some agents use environment variables for API keys in addition to
10
- * or instead of file-based credentials.
11
+ * Some agents use environment variables for API keys or OAuth tokens
12
+ * in addition to or instead of file-based credentials.
11
13
  */
12
- declare function getCredentialEnvironment(agentId: AgentCli, credentials: RawCredentials): Record<string, string>;
14
+ declare function getCredentialEnvironment(credentials: Credentials): Record<string, string>;
13
15
  export { getCredentialEnvironment };
@@ -1,42 +1,72 @@
1
1
  /**
2
2
  * Environment variable helpers for credential-based auth.
3
+ *
4
+ * Each agent has its own env var requirements. This module extracts
5
+ * the appropriate values from Credentials and returns them as env vars.
3
6
  */
7
+ import { resolveStringField } from "./resolve-string-field.js";
4
8
  /**
5
9
  * Gets additional environment variables for credential-based auth.
6
10
  *
7
- * Some agents use environment variables for API keys in addition to
8
- * or instead of file-based credentials.
11
+ * Some agents use environment variables for API keys or OAuth tokens
12
+ * in addition to or instead of file-based credentials.
9
13
  */
10
- function getCredentialEnvironment(agentId, credentials) {
14
+ function getCredentialEnvironment(credentials) {
11
15
  const environment = {};
12
- switch (agentId) {
16
+ // Extract API key from credentials data
17
+ const apiKey = resolveStringField(credentials.data, "apiKey", "key");
18
+ // Extract OAuth token from credentials data
19
+ const oauthToken = resolveStringField(credentials.data, "oauthToken", "token");
20
+ switch (credentials.agent) {
13
21
  case "claude": {
14
- if (credentials.apiKey) {
15
- environment["ANTHROPIC_API_KEY"] = credentials.apiKey;
22
+ if (credentials.type === "api-key" && apiKey) {
23
+ environment["ANTHROPIC_API_KEY"] = apiKey;
24
+ }
25
+ else if (credentials.type === "oauth-token" && oauthToken) {
26
+ environment["CLAUDE_CODE_OAUTH_TOKEN"] = oauthToken;
16
27
  }
17
28
  break;
18
29
  }
19
30
  case "codex": {
20
- if (credentials.apiKey) {
21
- environment["OPENAI_API_KEY"] = credentials.apiKey;
31
+ if (credentials.type === "api-key" && apiKey) {
32
+ environment["OPENAI_API_KEY"] = apiKey;
22
33
  }
23
34
  break;
24
35
  }
25
36
  case "gemini": {
26
- if (credentials.apiKey) {
27
- environment["GEMINI_API_KEY"] = credentials.apiKey;
37
+ if (credentials.type === "api-key" && apiKey) {
38
+ environment["GEMINI_API_KEY"] = apiKey;
28
39
  }
29
40
  break;
30
41
  }
31
42
  case "opencode": {
32
- if (credentials.apiKey) {
33
- environment["ANTHROPIC_API_KEY"] = credentials.apiKey;
43
+ // OpenCode supports multiple providers, env var depends on provider
44
+ const provider = credentials.provider ?? "anthropic";
45
+ if (credentials.type === "api-key" && apiKey) {
46
+ switch (provider) {
47
+ case "anthropic": {
48
+ environment["ANTHROPIC_API_KEY"] = apiKey;
49
+ break;
50
+ }
51
+ case "openai": {
52
+ environment["OPENAI_API_KEY"] = apiKey;
53
+ break;
54
+ }
55
+ case "gemini":
56
+ case "google": {
57
+ environment["GEMINI_API_KEY"] = apiKey;
58
+ break;
59
+ }
60
+ }
34
61
  }
35
62
  break;
36
63
  }
37
64
  case "copilot": {
38
- if (credentials.githubToken) {
39
- environment["GITHUB_TOKEN"] = credentials.githubToken;
65
+ // Copilot uses GitHub token as its credential
66
+ if (credentials.type === "api-key" && apiKey) {
67
+ environment["COPILOT_GITHUB_TOKEN"] = apiKey;
68
+ environment["GH_TOKEN"] = apiKey;
69
+ environment["GITHUB_TOKEN"] = apiKey;
40
70
  }
41
71
  break;
42
72
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Gets trimmed environment variable value, treating empty/whitespace as undefined.
3
+ */
4
+ declare function getEnvironmentTrimmed(key: string): string | undefined;
5
+ export { getEnvironmentTrimmed };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Gets trimmed environment variable value, treating empty/whitespace as undefined.
3
+ */
4
+ function getEnvironmentTrimmed(key) {
5
+ const value = process.env[key]?.trim();
6
+ return value || undefined;
7
+ }
8
+ export { getEnvironmentTrimmed };
@@ -1,25 +1,26 @@
1
1
  /**
2
2
  * Credential installation for isolated agent execution.
3
3
  *
4
- * Writes raw credentials to a temporary directory in agent-specific formats,
4
+ * Writes credentials to a temporary directory in agent-specific formats,
5
5
  * then returns the environment variables needed to point the agent at that
6
6
  * directory. This ensures complete isolation - agents never discover or use
7
7
  * locally installed credentials.
8
8
  */
9
9
  import { type AgentCli } from "axshared";
10
- import type { InstallResult, RawCredentials } from "./types.js";
11
- export type { RawCredentials } from "./types.js";
10
+ import type { Credentials } from "./credentials.js";
11
+ import type { InstallResult } from "./types.js";
12
+ type WarningWriter = (message: string) => void;
12
13
  /**
13
- * Installs credentials to a temporary directory and returns isolation env vars.
14
+ * Installs credentials (if provided) to a temporary directory and returns isolation env vars.
14
15
  *
15
16
  * This function:
16
17
  * 1. Creates a temp directory structure appropriate for the agent
17
- * 2. Writes credential files in the agent-specific format
18
+ * 2. Writes credential files in the agent-specific format (if provided)
18
19
  * 3. Returns environment variables that point the agent at the temp directory
19
20
  *
20
21
  * The caller is responsible for cleaning up the temp directory after use.
21
22
  */
22
- declare function installCredentials(agentId: AgentCli, credentials: RawCredentials): Promise<InstallResult>;
23
+ declare function installCredentials(agentId: AgentCli, credentials?: Credentials, warn?: WarningWriter): Promise<InstallResult>;
23
24
  /**
24
25
  * Cleanup for use in finally blocks.
25
26
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Credential installation for isolated agent execution.
3
3
  *
4
- * Writes raw credentials to a temporary directory in agent-specific formats,
4
+ * Writes credentials to a temporary directory in agent-specific formats,
5
5
  * then returns the environment variables needed to point the agent at that
6
6
  * directory. This ensures complete isolation - agents never discover or use
7
7
  * locally installed credentials.
@@ -42,14 +42,14 @@ async function createTemporaryDirectories(agentId) {
42
42
  /**
43
43
  * Installs agent-specific credentials to the appropriate directory.
44
44
  */
45
- function installAgentCredentials(agentId, configDirectory, dataDirectory, credentials) {
45
+ function installAgentCredentials(agentId, configDirectory, dataDirectory, credentials, warn) {
46
46
  switch (agentId) {
47
47
  case "claude": {
48
48
  installClaudeCredentials(configDirectory, credentials);
49
49
  break;
50
50
  }
51
51
  case "codex": {
52
- installCodexCredentials(configDirectory, credentials);
52
+ installCodexCredentials(configDirectory, credentials, warn);
53
53
  break;
54
54
  }
55
55
  case "gemini": {
@@ -57,7 +57,7 @@ function installAgentCredentials(agentId, configDirectory, dataDirectory, creden
57
57
  break;
58
58
  }
59
59
  case "opencode": {
60
- installOpenCodeCredentials(dataDirectory, credentials);
60
+ installOpenCodeCredentials(dataDirectory, credentials, warn);
61
61
  break;
62
62
  }
63
63
  case "copilot": {
@@ -67,26 +67,36 @@ function installAgentCredentials(agentId, configDirectory, dataDirectory, creden
67
67
  }
68
68
  }
69
69
  /**
70
- * Installs credentials to a temporary directory and returns isolation env vars.
70
+ * Installs credentials (if provided) to a temporary directory and returns isolation env vars.
71
71
  *
72
72
  * This function:
73
73
  * 1. Creates a temp directory structure appropriate for the agent
74
- * 2. Writes credential files in the agent-specific format
74
+ * 2. Writes credential files in the agent-specific format (if provided)
75
75
  * 3. Returns environment variables that point the agent at the temp directory
76
76
  *
77
77
  * The caller is responsible for cleaning up the temp directory after use.
78
78
  */
79
- async function installCredentials(agentId, credentials) {
79
+ async function installCredentials(agentId, credentials, warn) {
80
80
  const { baseDirectory, configDirectory, dataDirectory } = await createTemporaryDirectories(agentId);
81
- installAgentCredentials(agentId, configDirectory, dataDirectory, credentials);
82
- const runtimeEnvironment = buildAgentRuntimeEnvironment(agentId, configDirectory, dataDirectory);
83
- const credentialEnvironment = getCredentialEnvironment(agentId, credentials);
84
- return {
85
- env: { ...runtimeEnvironment, ...credentialEnvironment },
86
- configDirectory,
87
- dataDirectory,
88
- baseDirectory,
89
- };
81
+ try {
82
+ if (credentials) {
83
+ installAgentCredentials(agentId, configDirectory, dataDirectory, credentials, warn);
84
+ }
85
+ const runtimeEnvironment = buildAgentRuntimeEnvironment(agentId, configDirectory, dataDirectory);
86
+ const credentialEnvironment = credentials
87
+ ? getCredentialEnvironment(credentials)
88
+ : {};
89
+ return {
90
+ env: { ...runtimeEnvironment, ...credentialEnvironment },
91
+ configDirectory,
92
+ dataDirectory,
93
+ baseDirectory,
94
+ };
95
+ }
96
+ catch (error) {
97
+ await cleanupCredentials(baseDirectory);
98
+ throw error;
99
+ }
90
100
  }
91
101
  /**
92
102
  * Cleanup for use in finally blocks.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Resolves a string field from a data object, checking primary and fallback keys.
3
+ * Returns trimmed value if non-empty, undefined otherwise.
4
+ * Empty strings and whitespace-only values are treated as missing.
5
+ */
6
+ declare function resolveStringField(data: Record<string, unknown>, primaryKey: string, fallbackKey: string): string | undefined;
7
+ export { resolveStringField };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Resolves a string field from a data object, checking primary and fallback keys.
3
+ * Returns trimmed value if non-empty, undefined otherwise.
4
+ * Empty strings and whitespace-only values are treated as missing.
5
+ */
6
+ function resolveStringField(data, primaryKey, fallbackKey) {
7
+ const primary = data[primaryKey];
8
+ if (typeof primary === "string") {
9
+ const trimmed = primary.trim();
10
+ if (trimmed)
11
+ return trimmed;
12
+ }
13
+ const fallback = data[fallbackKey];
14
+ if (typeof fallback === "string") {
15
+ const trimmed = fallback.trim();
16
+ if (trimmed)
17
+ return trimmed;
18
+ }
19
+ return undefined;
20
+ }
21
+ export { resolveStringField };