axexec 1.6.1 → 2.0.1

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 (44) hide show
  1. package/README.md +69 -48
  2. package/dist/agents/claude-code/types.d.ts +2 -2
  3. package/dist/agents/copilot/stream-session.js +8 -7
  4. package/dist/agents/copilot/watch-session.js +3 -1
  5. package/dist/agents/gemini/types.d.ts +4 -4
  6. package/dist/agents/opencode/adapter.js +8 -1
  7. package/dist/agents/opencode/build-permission-environment.d.ts +17 -0
  8. package/dist/agents/opencode/build-permission-environment.js +31 -0
  9. package/dist/agents/opencode/parse-sse-event.js +3 -1
  10. package/dist/agents/opencode/spawn-server.js +12 -1
  11. package/dist/build-agent-environment.js +8 -0
  12. package/dist/build-execution-metadata.js +1 -1
  13. package/dist/cli.d.ts +5 -6
  14. package/dist/cli.js +82 -61
  15. package/dist/credentials/get-credential-environment.d.ts +2 -2
  16. package/dist/credentials/get-credential-environment.js +5 -3
  17. package/dist/credentials/install-credentials.d.ts +3 -3
  18. package/dist/credentials/install-credentials.js +8 -8
  19. package/dist/credentials/write-agent-credentials.d.ts +1 -3
  20. package/dist/credentials/write-agent-credentials.js +1 -2
  21. package/dist/execute-agent.js +1 -0
  22. package/dist/format-zod-error.d.ts +6 -0
  23. package/dist/format-zod-error.js +12 -0
  24. package/dist/parse-credentials.js +1 -18
  25. package/dist/read-credentials-file.d.ts +13 -0
  26. package/dist/read-credentials-file.js +33 -0
  27. package/dist/read-stdin.d.ts +5 -0
  28. package/dist/read-stdin.js +11 -0
  29. package/dist/resolve-credentials.d.ts +20 -0
  30. package/dist/resolve-credentials.js +22 -0
  31. package/dist/resolve-output-mode.d.ts +15 -1
  32. package/dist/resolve-output-mode.js +16 -1
  33. package/dist/resolve-prompt.d.ts +22 -0
  34. package/dist/resolve-prompt.js +23 -0
  35. package/dist/run-agent.d.ts +4 -1
  36. package/dist/run-agent.js +32 -17
  37. package/dist/types/run-result.d.ts +4 -0
  38. package/dist/validate-cwd.d.ts +11 -0
  39. package/dist/validate-cwd.js +24 -0
  40. package/dist/validate-opencode-options.d.ts +18 -0
  41. package/dist/validate-opencode-options.js +50 -0
  42. package/dist/validate-stdin-usage.d.ts +18 -0
  43. package/dist/validate-stdin-usage.js +28 -0
  44. package/package.json +6 -6
package/dist/run-agent.js CHANGED
@@ -4,12 +4,16 @@
4
4
  import { installCredentials, cleanupCredentials, } from "./credentials/install-credentials.js";
5
5
  import { buildPermissionsConfig } from "./build-permissions-config.js";
6
6
  import { validateAgent } from "./validate-agent.js";
7
+ import { validateCwd } from "./validate-cwd.js";
7
8
  import { parseCredentialsFromEnvironment } from "./parse-credentials.js";
8
9
  import { buildExecutionMetadata } from "./build-execution-metadata.js";
9
10
  import { executeAgent } from "./execute-agent.js";
10
11
  import { resolveDiagnostics, resolveExitCodeSetter, } from "./resolve-run-diagnostics.js";
11
12
  /**
12
- * Runs an agent with full isolation.
13
+ * Runs an agent with credential/config isolation.
14
+ *
15
+ * Note: axexec is not an OS sandbox. It runs agents in a non-interactive,
16
+ * permissive mode by default and should not be treated as a security boundary.
13
17
  *
14
18
  * 1. Validates the agent ID
15
19
  * 2. Uses provided credentials or parses from environment
@@ -35,12 +39,28 @@ async function runAgent(agentId, options) {
35
39
  };
36
40
  }
37
41
  const { adapter } = validation;
38
- // Validate --model format for OpenCode (reject "provider/model" format)
39
- if (validation.agentId === "opencode" && options.model?.includes("/")) {
40
- const [provider, model] = options.model.split("/", 2);
42
+ // Validate cwd if provided (for programmatic API consumers)
43
+ if (options.cwd) {
44
+ const cwdValidation = await validateCwd(options.cwd);
45
+ if (!cwdValidation.ok) {
46
+ diagnostics.error(`Error: ${cwdValidation.error}`);
47
+ setExitCode(2);
48
+ return {
49
+ events: [],
50
+ success: false,
51
+ execution: { agent: validation.agentId, model: options.model },
52
+ };
53
+ }
54
+ }
55
+ // Validate --model format for OpenCode (reject "provider/model" format only when --provider is missing).
56
+ // Many legitimate model IDs contain slashes (e.g., nvidia/nemotron, google/gemma).
57
+ if (validation.agentId === "opencode" &&
58
+ !options.provider &&
59
+ options.model?.includes("/")) {
60
+ const [providerPart, modelPart] = options.model.split("/", 2);
41
61
  diagnostics.error(`Error: Model format 'provider/model' is no longer supported.\n` +
42
62
  `Use separate --provider and --model flags instead:\n` +
43
- ` --provider ${provider} --model ${model}`);
63
+ ` --provider ${providerPart} --model ${modelPart}`);
44
64
  setExitCode(2);
45
65
  return {
46
66
  events: [],
@@ -63,20 +83,15 @@ async function runAgent(agentId, options) {
63
83
  }
64
84
  // Credentials are optional; isolation still applies even when none are found.
65
85
  const credentials = credentialsResult.credentials;
66
- if (credentials && credentials.agent !== validation.agentId) {
67
- diagnostics.error("Error: Credentials agent mismatch. " +
68
- `Expected ${validation.agentId}, got ${credentials.agent}.`);
69
- setExitCode(2);
70
- return {
71
- events: [],
72
- success: false,
73
- execution: { agent: validation.agentId, model: options.model },
74
- };
75
- }
86
+ // Normalize provider to lowercase for OpenCode (all OpenCode provider IDs are lowercase).
87
+ // This ensures consistent behavior for both CLI and programmatic API users.
88
+ const normalizedOptions = validation.agentId === "opencode" && options.provider
89
+ ? { ...options, provider: options.provider.toLowerCase() }
90
+ : options;
76
91
  // Always create isolated runtime directories; install credentials if provided.
77
92
  let credentialResult;
78
93
  try {
79
- credentialResult = await installCredentials(validation.agentId, credentials, diagnostics.warn);
94
+ credentialResult = await installCredentials(validation.agentId, credentials, normalizedOptions.provider, diagnostics.warn);
80
95
  }
81
96
  catch (error) {
82
97
  const message = error instanceof Error ? error.message : String(error);
@@ -112,6 +127,6 @@ async function runAgent(agentId, options) {
112
127
  configEnvironment = configResult.env;
113
128
  }
114
129
  // Execute agent
115
- return executeAgent(validation.agentId, adapter, options, configEnvironment, credentialResult, credentials, preserveConfigDirectory, diagnostics.error);
130
+ return executeAgent(validation.agentId, adapter, normalizedOptions, configEnvironment, credentialResult, credentials, preserveConfigDirectory, diagnostics.error);
116
131
  }
117
132
  export { runAgent };
@@ -10,6 +10,10 @@ interface RunAgentDiagnostics {
10
10
  }
11
11
  interface RunAgentOptions {
12
12
  prompt: string;
13
+ /**
14
+ * Working directory for the agent process.
15
+ */
16
+ cwd?: string;
13
17
  /**
14
18
  * Credentials to use. If not provided, parses from environment variables.
15
19
  * Accepts the canonical Credentials shape shared across ax* packages.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Validate --cwd option points to an existing directory.
3
+ */
4
+ type ValidateCwdResult = {
5
+ ok: true;
6
+ } | {
7
+ ok: false;
8
+ error: string;
9
+ };
10
+ declare function validateCwd(cwd: string): Promise<ValidateCwdResult>;
11
+ export { validateCwd };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Validate --cwd option points to an existing directory.
3
+ */
4
+ import { stat } from "node:fs/promises";
5
+ async function validateCwd(cwd) {
6
+ try {
7
+ const stats = await stat(cwd);
8
+ if (!stats.isDirectory()) {
9
+ return {
10
+ ok: false,
11
+ error: `--cwd path is not a directory: ${cwd}`,
12
+ };
13
+ }
14
+ return { ok: true };
15
+ }
16
+ catch (error) {
17
+ const message = error instanceof Error ? error.message : String(error);
18
+ return {
19
+ ok: false,
20
+ error: `--cwd directory error: ${message}`,
21
+ };
22
+ }
23
+ }
24
+ export { validateCwd };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Validate OpenCode-specific CLI options.
3
+ */
4
+ type ProviderValidationResult = {
5
+ ok: true;
6
+ normalizedProvider: string | undefined;
7
+ } | {
8
+ ok: false;
9
+ error: string;
10
+ };
11
+ /**
12
+ * Validate provider usage and OpenCode-specific options.
13
+ *
14
+ * - For non-OpenCode agents: provider must not be specified
15
+ * - For OpenCode: validates model format, requires provider
16
+ */
17
+ declare function validateProviderOptions(agentId: string, model: string | undefined, provider: string | undefined): ProviderValidationResult;
18
+ export { validateProviderOptions };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Validate OpenCode-specific CLI options.
3
+ */
4
+ /**
5
+ * Validate provider usage and OpenCode-specific options.
6
+ *
7
+ * - For non-OpenCode agents: provider must not be specified
8
+ * - For OpenCode: validates model format, requires provider
9
+ */
10
+ function validateProviderOptions(agentId, model, provider) {
11
+ // --provider is only valid for OpenCode
12
+ if (provider && agentId !== "opencode") {
13
+ return {
14
+ ok: false,
15
+ error: "--provider is only supported for OpenCode agent",
16
+ };
17
+ }
18
+ // Non-OpenCode agents don't need further validation
19
+ if (agentId !== "opencode") {
20
+ return { ok: true, normalizedProvider: undefined };
21
+ }
22
+ // OpenCode-specific validation
23
+ return validateOpenCodeOptions(model, provider);
24
+ }
25
+ function validateOpenCodeOptions(model, provider) {
26
+ // Require --provider for OpenCode
27
+ if (!provider) {
28
+ // If model contains "/" and no provider given, suggest they might be using
29
+ // the old provider/model format, but primarily tell them --provider is required.
30
+ if (model?.includes("/")) {
31
+ const [providerPart, modelPart] = model.split("/", 2);
32
+ return {
33
+ ok: false,
34
+ error: `OpenCode requires --provider\n` +
35
+ `If using the old 'provider/model' format, use separate flags:\n` +
36
+ ` axexec -a opencode --provider ${providerPart} -m ${modelPart} ...`,
37
+ };
38
+ }
39
+ return {
40
+ ok: false,
41
+ error: `OpenCode requires --provider\n` +
42
+ ` axexec -a opencode --provider anthropic -m claude-sonnet-4 ...`,
43
+ };
44
+ }
45
+ // Normalize to lowercase to match OpenCode's provider ID format.
46
+ // OpenCode stores all provider IDs in lowercase (anthropic, openai, etc).
47
+ const normalizedProvider = provider.toLowerCase();
48
+ return { ok: true, normalizedProvider };
49
+ }
50
+ export { validateProviderOptions };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Validate stdin usage when credentials and prompt may both need stdin.
3
+ */
4
+ type ValidateStdinResult = {
5
+ ok: true;
6
+ } | {
7
+ ok: false;
8
+ error: string;
9
+ };
10
+ /**
11
+ * Validates that stdin usage is not conflicting.
12
+ *
13
+ * Returns an error if:
14
+ * - --credentials-file - is used with no prompt (both would read stdin)
15
+ * - --credentials-file - is used in interactive TTY mode (requires piping)
16
+ */
17
+ declare function validateStdinUsage(credentialsFile: string | undefined, needsStdinPrompt: boolean, isTTY: boolean | undefined): ValidateStdinResult;
18
+ export { validateStdinUsage };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Validate stdin usage when credentials and prompt may both need stdin.
3
+ */
4
+ /**
5
+ * Validates that stdin usage is not conflicting.
6
+ *
7
+ * Returns an error if:
8
+ * - --credentials-file - is used with no prompt (both would read stdin)
9
+ * - --credentials-file - is used in interactive TTY mode (requires piping)
10
+ */
11
+ function validateStdinUsage(credentialsFile, needsStdinPrompt, isTTY) {
12
+ if (credentialsFile === "-" && needsStdinPrompt) {
13
+ return {
14
+ ok: false,
15
+ error: "Cannot read both prompt and credentials from stdin\n" +
16
+ "Provide the prompt via --prompt or positional argument when using --credentials-file -",
17
+ };
18
+ }
19
+ if (credentialsFile === "-" && isTTY) {
20
+ return {
21
+ ok: false,
22
+ error: "Cannot read credentials from stdin in interactive mode\n" +
23
+ "Pipe credentials JSON to stdin or use a file path instead of '-'",
24
+ };
25
+ }
26
+ return { ok: true };
27
+ }
28
+ export { validateStdinUsage };
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.6.1",
5
+ "version": "2.0.1",
6
6
  "description": "Unified CLI runner for AI coding agents with normalized event streaming",
7
7
  "repository": {
8
8
  "type": "git",
@@ -59,27 +59,27 @@
59
59
  "automation",
60
60
  "coding-assistant"
61
61
  ],
62
- "packageManager": "pnpm@10.28.0",
62
+ "packageManager": "pnpm@10.28.1",
63
63
  "engines": {
64
64
  "node": ">=22.14.0"
65
65
  },
66
66
  "dependencies": {
67
67
  "@commander-js/extra-typings": "^14.0.0",
68
68
  "axconfig": "^3.6.3",
69
- "axshared": "^4.0.0",
69
+ "axshared": "^5.0.0",
70
70
  "commander": "^14.0.2",
71
71
  "zod": "^4.3.5"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@total-typescript/ts-reset": "^0.6.1",
75
- "@types/node": "^25.0.9",
75
+ "@types/node": "^25.0.10",
76
76
  "@vitest/coverage-v8": "^4.0.17",
77
77
  "eslint": "^9.39.2",
78
78
  "eslint-config-axkit": "^1.1.0",
79
79
  "fta-check": "^1.5.1",
80
80
  "fta-cli": "^3.0.0",
81
- "knip": "^5.82.0",
82
- "prettier": "3.8.0",
81
+ "knip": "^5.82.1",
82
+ "prettier": "3.8.1",
83
83
  "semantic-release": "^25.0.2",
84
84
  "typescript": "^5.9.3",
85
85
  "vitest": "^4.0.17"