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
@@ -1,32 +1,16 @@
1
1
  /**
2
2
  * Agent execution with credential isolation and config generation.
3
3
  */
4
- import type { AxexecEvent } from "./types/events.js";
5
- import { type OutputFormat } from "./resolve-output-mode.js";
6
- interface RunAgentOptions {
7
- prompt: string;
8
- model?: string;
9
- allow?: string;
10
- deny?: string;
11
- format?: OutputFormat;
12
- rawLog?: string;
13
- debug?: boolean;
14
- verbose?: boolean;
15
- preserveGithubSha?: boolean;
16
- }
17
- interface RunResult {
18
- events: AxexecEvent[];
19
- success: boolean;
20
- }
4
+ import type { RunAgentOptions, RunResult } from "./types/run-result.js";
21
5
  /**
22
6
  * Runs an agent with full isolation.
23
7
  *
24
8
  * 1. Validates the agent ID
25
- * 2. Parses credentials from environment
26
- * 3. Installs credentials to temp directory
9
+ * 2. Uses provided credentials or parses from environment
10
+ * 3. Creates isolated temp directories and installs credentials if provided
27
11
  * 4. Builds config if permissions specified
28
12
  * 5. Streams events from agent
29
- * 6. Cleans up temp directory
13
+ * 6. Cleans up temp directory (unless preserveConfigDirectory is true)
30
14
  */
31
15
  declare function runAgent(agentId: string, options: RunAgentOptions): Promise<RunResult>;
32
16
  export { runAgent };
package/dist/run-agent.js CHANGED
@@ -1,146 +1,104 @@
1
1
  /**
2
2
  * Agent execution with credential isolation and config generation.
3
3
  */
4
- import { buildAgentConfig } from "axconfig";
5
- import { isStreamableAdapter } from "./types/adapter.js";
6
4
  import { installCredentials, cleanupCredentials, } from "./credentials/install-credentials.js";
7
- import { streamAgent } from "./stream-agent.js";
5
+ import { buildPermissionsConfig } from "./build-permissions-config.js";
8
6
  import { validateAgent } from "./validate-agent.js";
9
- import { resolveOutputMode } from "./resolve-output-mode.js";
10
- import { createEventWriter } from "./write-event.js";
11
7
  import { parseCredentialsFromEnvironment } from "./parse-credentials.js";
12
- import { DependencyError } from "./resolve-binary.js";
8
+ import { buildExecutionMetadata } from "./build-execution-metadata.js";
9
+ import { executeAgent } from "./execute-agent.js";
10
+ import { resolveDiagnostics, resolveExitCodeSetter, } from "./resolve-run-diagnostics.js";
13
11
  /**
14
12
  * Runs an agent with full isolation.
15
13
  *
16
14
  * 1. Validates the agent ID
17
- * 2. Parses credentials from environment
18
- * 3. Installs credentials to temp directory
15
+ * 2. Uses provided credentials or parses from environment
16
+ * 3. Creates isolated temp directories and installs credentials if provided
19
17
  * 4. Builds config if permissions specified
20
18
  * 5. Streams events from agent
21
- * 6. Cleans up temp directory
19
+ * 6. Cleans up temp directory (unless preserveConfigDirectory is true)
22
20
  */
23
21
  async function runAgent(agentId, options) {
22
+ const diagnostics = resolveDiagnostics(options);
23
+ const setExitCode = resolveExitCodeSetter(options);
24
+ const preserveConfigDirectory = options.preserveConfigDirectory ?? false;
24
25
  // Validate agent
25
26
  const validation = validateAgent(agentId);
26
27
  if (!validation.ok) {
27
- process.stderr.write(validation.message + "\n");
28
- process.exitCode = validation.exitCode;
29
- return { events: [], success: false };
28
+ diagnostics.error(validation.message);
29
+ setExitCode(validation.exitCode);
30
+ // Return minimal execution metadata for failed validation
31
+ return {
32
+ events: [],
33
+ success: false,
34
+ execution: { agent: "unknown", invalidAgentId: agentId },
35
+ };
30
36
  }
31
37
  const { adapter } = validation;
32
- // Parse credentials from environment
33
- const credentials = parseCredentialsFromEnvironment(agentId);
34
- // Install credentials to temp directory
38
+ // Use provided credentials or parse from environment
39
+ const credentialsResult = options.credentials
40
+ ? { ok: true, credentials: options.credentials }
41
+ : parseCredentialsFromEnvironment(validation.agentId, options.model);
42
+ if (!credentialsResult.ok) {
43
+ diagnostics.error(credentialsResult.message);
44
+ setExitCode(credentialsResult.exitCode);
45
+ return {
46
+ events: [],
47
+ success: false,
48
+ execution: { agent: validation.agentId, model: options.model },
49
+ };
50
+ }
51
+ // Credentials are optional; isolation still applies even when none are found.
52
+ const credentials = credentialsResult.credentials;
53
+ if (credentials && credentials.agent !== validation.agentId) {
54
+ diagnostics.error("Error: Credentials agent mismatch. " +
55
+ `Expected ${validation.agentId}, got ${credentials.agent}.`);
56
+ setExitCode(2);
57
+ return {
58
+ events: [],
59
+ success: false,
60
+ execution: { agent: validation.agentId, model: options.model },
61
+ };
62
+ }
63
+ // Always create isolated runtime directories; install credentials if provided.
35
64
  let credentialResult;
36
65
  try {
37
- credentialResult = await installCredentials(validation.agentId, credentials);
66
+ credentialResult = await installCredentials(validation.agentId, credentials, diagnostics.warn);
38
67
  }
39
68
  catch (error) {
40
69
  const message = error instanceof Error ? error.message : String(error);
41
- process.stderr.write(`Error installing credentials: ${message}\n`);
42
- process.exitCode = 1;
43
- return { events: [], success: false };
70
+ diagnostics.error(`Error installing credentials: ${message}`);
71
+ setExitCode(1);
72
+ return {
73
+ events: [],
74
+ success: false,
75
+ execution: { agent: validation.agentId, model: options.model },
76
+ };
44
77
  }
45
78
  // Build config if permissions specified
46
79
  let configEnvironment = credentialResult.env;
47
80
  if (options.allow || options.deny) {
48
- const configResult = buildConfig(validation.agentId, options, credentialResult);
49
- if (!configResult.ok) {
50
- await cleanupCredentials(credentialResult.baseDirectory);
51
- process.exitCode = configResult.exitCode;
52
- return { events: [], success: false };
53
- }
54
- configEnvironment = configResult.env;
55
- }
56
- // Execute agent
57
- return executeAgent(adapter, options, configEnvironment, credentialResult);
58
- }
59
- /**
60
- * Builds agent config with permissions.
61
- */
62
- function buildConfig(agentId, options, credentialResult) {
63
- try {
64
- const configResult = buildAgentConfig({
65
- agentId,
66
- allow: options.allow,
67
- deny: options.deny,
68
- output: credentialResult.configDirectory,
69
- });
81
+ const configResult = buildPermissionsConfig(validation.agentId, options, credentialResult);
70
82
  if (!configResult.ok) {
71
- if ("message" in configResult) {
72
- process.stderr.write(`Error: ${configResult.message}\n`);
83
+ for (const error of configResult.errors) {
84
+ diagnostics.error(error);
73
85
  }
74
- else {
75
- process.stderr.write(`Error building config: permission errors\n`);
76
- for (const error of configResult.errors) {
77
- process.stderr.write(` - ${error.reason}\n`);
78
- }
86
+ if (!preserveConfigDirectory) {
87
+ await cleanupCredentials(credentialResult.baseDirectory);
79
88
  }
80
- return { ok: false, exitCode: configResult.exitCode };
89
+ setExitCode(configResult.exitCode);
90
+ return {
91
+ events: [],
92
+ success: false,
93
+ execution: buildExecutionMetadata(validation.agentId, options, credentialResult, credentials, preserveConfigDirectory),
94
+ };
81
95
  }
82
- // Log warnings
83
96
  for (const warning of configResult.warnings) {
84
- process.stderr.write(`Warning: ${warning.reason}\n`);
97
+ diagnostics.warn(warning);
85
98
  }
86
- // Merge config env with credential env (config env takes precedence)
87
- return {
88
- ok: true,
89
- env: { ...credentialResult.env, ...configResult.env },
90
- };
91
- }
92
- catch (error) {
93
- const message = error instanceof Error ? error.message : String(error);
94
- process.stderr.write(`Error parsing permissions: ${message}\n`);
95
- return { ok: false, exitCode: 1 };
96
- }
97
- }
98
- /**
99
- * Executes agent and streams events.
100
- */
101
- async function executeAgent(adapter, options, configEnvironment, credentialResult) {
102
- const runOptions = {
103
- prompt: options.prompt,
104
- verbose: options.verbose ?? false,
105
- model: options.model,
106
- rawLogPath: options.rawLog,
107
- debug: options.debug,
108
- configEnv: configEnvironment,
109
- preserveGithubSha: options.preserveGithubSha,
110
- };
111
- const outputMode = resolveOutputMode(options.format, process.stdout.isTTY);
112
- const writeEvent = createEventWriter(outputMode);
113
- const events = [];
114
- try {
115
- const eventStream = isStreamableAdapter(adapter)
116
- ? adapter.streamSession(runOptions)
117
- : streamAgent(adapter, runOptions);
118
- for await (const event of eventStream) {
119
- events.push(event);
120
- writeEvent(event);
121
- }
122
- const success = adapter.isSuccess(events);
123
- return { events, success };
124
- }
125
- catch (error) {
126
- if (error instanceof DependencyError) {
127
- process.stderr.write(error.message + "\n");
128
- return { events, success: false };
129
- }
130
- const message = error instanceof Error ? error.message : String(error);
131
- const errorEvent = {
132
- type: "session.error",
133
- code: "SPAWN_ERROR",
134
- message,
135
- timestamp: Date.now(),
136
- };
137
- events.push(errorEvent);
138
- writeEvent(errorEvent);
139
- return { events, success: false };
140
- }
141
- finally {
142
- // Safe to await - executeAgent is async so finally block can use await
143
- await cleanupCredentials(credentialResult.baseDirectory);
99
+ configEnvironment = configResult.env;
144
100
  }
101
+ // Execute agent
102
+ return executeAgent(validation.agentId, adapter, options, configEnvironment, credentialResult, credentials, preserveConfigDirectory, diagnostics.error);
145
103
  }
146
104
  export { runAgent };
@@ -7,14 +7,7 @@
7
7
  import { spawn } from "node:child_process";
8
8
  import { createWriteStream } from "node:fs";
9
9
  import { createInterface } from "node:readline";
10
- /**
11
- * Environment variables to always exclude from agent subprocess.
12
- *
13
- * Vault credentials are excluded to prevent prompt injection attacks from
14
- * exfiltrating secrets. The credentials are resolved before spawning and
15
- * passed via agent-specific env vars (e.g., ANTHROPIC_API_KEY).
16
- */
17
- const VAULT_ENV_EXCLUSIONS = ["AXVAULT", "AXVAULT_URL", "AXVAULT_API_KEY"];
10
+ import { buildBaseEnvironment } from "./build-agent-environment.js";
18
11
  /** Error thrown when agent binary is not found */
19
12
  class AgentNotFoundError extends Error {
20
13
  bin;
@@ -56,13 +49,10 @@ async function* streamAgent(adapter, options) {
56
49
  process.stderr.write(`Warning: raw log error: ${error.message}\n`);
57
50
  });
58
51
  }
59
- // Build environment: start with process.env, exclude vault vars and any
60
- // adapter-specified vars, then merge adapter env and config env.
61
- const allExclusions = new Set([
62
- ...VAULT_ENV_EXCLUSIONS,
63
- ...(environmentExclusions ?? []),
64
- ]);
65
- const baseEnvironment = Object.fromEntries(Object.entries(process.env).filter(([key]) => !allExclusions.has(key)));
52
+ // Build environment: start with process.env, exclude vault vars,
53
+ // AX_*_CREDENTIALS, and any adapter-specified vars, then merge adapter
54
+ // env and config env.
55
+ const baseEnvironment = buildBaseEnvironment(environmentExclusions ?? []);
66
56
  const child = spawn(bin, args, {
67
57
  env: { ...baseEnvironment, ...env, ...options.configEnv },
68
58
  cwd: options.cwd,
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Types for run-agent results and options.
3
+ */
4
+ import type { AgentCli, AxexecEvent } from "./events.js";
5
+ import type { OutputFormat } from "../resolve-output-mode.js";
6
+ import type { Credentials } from "../credentials/credentials.js";
7
+ interface RunAgentDiagnostics {
8
+ error: (message: string) => void;
9
+ warn: (message: string) => void;
10
+ }
11
+ interface RunAgentOptions {
12
+ prompt: string;
13
+ /**
14
+ * Credentials to use. If not provided, parses from environment variables.
15
+ * Accepts the canonical Credentials shape shared across ax* packages.
16
+ */
17
+ credentials?: Credentials;
18
+ model?: string;
19
+ allow?: string;
20
+ deny?: string;
21
+ format?: OutputFormat;
22
+ rawLog?: string;
23
+ debug?: boolean;
24
+ verbose?: boolean;
25
+ preserveGithubSha?: boolean;
26
+ /**
27
+ * Override diagnostic output (errors, warnings) emitted during setup.
28
+ * Messages include a trailing newline.
29
+ */
30
+ diagnostics?: RunAgentDiagnostics;
31
+ /**
32
+ * When false, runAgent will not mutate process.exitCode on failures.
33
+ */
34
+ setExitCode?: boolean;
35
+ /**
36
+ * When true, the temporary config directory is NOT cleaned up after execution.
37
+ * The caller is responsible for cleanup via `cleanupCredentials()`.
38
+ * Use this when you need to read files from the config directory after
39
+ * execution (e.g., refreshed credentials).
40
+ */
41
+ preserveConfigDirectory?: boolean;
42
+ }
43
+ /** Directory information from execution */
44
+ interface ExecutionDirectories {
45
+ /** Base temp directory (parent of config/data dirs) */
46
+ base: string;
47
+ /** Config directory where settings/credentials were installed */
48
+ config: string;
49
+ /** Data directory (may be same as config for some agents) */
50
+ data: string;
51
+ /** Whether directories were preserved (not cleaned up) */
52
+ preserved: boolean;
53
+ }
54
+ /** Credential information from execution */
55
+ interface ExecutionCredentials {
56
+ /** Type of credential used */
57
+ type: "api-key" | "oauth-credentials" | "oauth-token";
58
+ /** Provider (for multi-provider agents like OpenCode) */
59
+ provider?: string;
60
+ }
61
+ type ExecutionAgent = AgentCli | "unknown";
62
+ /** Execution metadata - always present in RunResult */
63
+ interface ExecutionMetadata {
64
+ /** Agent that was executed */
65
+ agent: ExecutionAgent;
66
+ /** Invalid agent ID when validation fails */
67
+ invalidAgentId?: string;
68
+ /** Temporary directory paths (present when isolation dirs were created) */
69
+ directories?: ExecutionDirectories;
70
+ /** Credential information (present if credentials were provided) */
71
+ credentials?: ExecutionCredentials;
72
+ /** Model used (if specified) */
73
+ model?: string;
74
+ }
75
+ interface RunResult {
76
+ events: AxexecEvent[];
77
+ success: boolean;
78
+ /** Execution metadata - always present */
79
+ execution: ExecutionMetadata;
80
+ }
81
+ export type { ExecutionCredentials, ExecutionDirectories, ExecutionMetadata, RunAgentDiagnostics, RunAgentOptions, RunResult, };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Types for run-agent results and options.
3
+ */
4
+ export {};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "axexec",
3
3
  "author": "Łukasz Jerciński",
4
4
  "license": "MIT",
5
- "version": "1.0.0",
5
+ "version": "1.1.0",
6
6
  "description": "Unified CLI runner for AI coding agents with normalized event streaming",
7
7
  "repository": {
8
8
  "type": "git",
@@ -13,6 +13,14 @@
13
13
  "url": "https://github.com/Jercik/axexec/issues"
14
14
  },
15
15
  "type": "module",
16
+ "main": "dist/index.js",
17
+ "types": "dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
16
24
  "bin": {
17
25
  "axexec": "bin/axexec"
18
26
  },
@@ -58,7 +66,7 @@
58
66
  "dependencies": {
59
67
  "@commander-js/extra-typings": "^14.0.0",
60
68
  "axconfig": "^3.6.0",
61
- "axshared": "^1.9.0",
69
+ "axshared": "^2.0.0",
62
70
  "commander": "^14.0.2",
63
71
  "zod": "^4.3.5"
64
72
  },