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/cli.js CHANGED
@@ -1,12 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * axexec CLI - Unified agent execution with isolation.
3
+ * axexec CLI - Unified agent execution with credential/config isolation.
4
4
  *
5
- * Executes AI coding agents with complete environment isolation:
6
- * - Credentials written to temp directory
7
- * - Config/permissions written to temp directory
8
- * - Agent pointed at temp directory via env vars
9
- * - Normalized event stream output
5
+ * axexec is a headless runner and normalization layer. It isolates agent
6
+ * credentials/config from your local environment via temp directories, but it
7
+ * is not an OS sandbox and is intentionally permissive/non-interactive by
8
+ * default. Use an external sandbox (container/VM) for real enforcement.
10
9
  */
11
10
  import { Command } from "@commander-js/extra-typings";
12
11
  import packageJson from "../package.json" with { type: "json" };
@@ -17,14 +16,15 @@ import "./agents/gemini/adapter.js";
17
16
  import "./agents/opencode/adapter.js";
18
17
  import "./agents/copilot/adapter.js";
19
18
  import { listAdapters } from "./agents/registry.js";
19
+ import { readStdin } from "./read-stdin.js";
20
+ import { resolveCredentials } from "./resolve-credentials.js";
21
+ import { parseOutputFormat } from "./resolve-output-mode.js";
22
+ import { resolvePrompt } from "./resolve-prompt.js";
20
23
  import { runAgent } from "./run-agent.js";
21
- async function readStdin() {
22
- let data = "";
23
- for await (const chunk of process.stdin) {
24
- data += typeof chunk === "string" ? chunk : chunk.toString("utf8");
25
- }
26
- return data.trimEnd();
27
- }
24
+ import { validateAgent } from "./validate-agent.js";
25
+ import { validateCwd } from "./validate-cwd.js";
26
+ import { validateProviderOptions } from "./validate-opencode-options.js";
27
+ import { validateStdinUsage } from "./validate-stdin-usage.js";
28
28
  const program = new Command()
29
29
  .name(packageJson.name)
30
30
  .description(packageJson.description)
@@ -33,12 +33,14 @@ const program = new Command()
33
33
  .showSuggestionAfterError()
34
34
  .option("--list-agents", "List available agents")
35
35
  .option("-a, --agent <id>", "Agent to use (required)")
36
+ .option("--cwd <path>", "Working directory for the agent process")
37
+ .option("--credentials-file <path|->", "Read credentials JSON from file (or '-' for stdin)")
36
38
  .option("-p, --prompt <text>", "Prompt text")
37
39
  .option("-m, --model <model>", "Model to use")
38
40
  .option("--provider <provider>", "Provider for OpenCode (required for OpenCode)")
39
- .option("--allow <perms>", "Permission rules to allow (comma-separated)")
40
- .option("--deny <perms>", "Permission rules to deny (comma-separated)")
41
- .option("-f, --format <fmt>", "Output format: jsonl, tsv (default: tsv, truncated on TTY)")
41
+ .option("--allow <perms>", "Allow permissions (best-effort, comma-separated)")
42
+ .option("--deny <perms>", "Deny permissions (best-effort, comma-separated)")
43
+ .option("-f, --format <fmt>", "Output format: jsonl, tsv (default when omitted: truncated tsv on TTY, full tsv otherwise)")
42
44
  .option("--raw-log <file>", "Write raw agent output to file")
43
45
  .option("--debug", "Enable debug mode")
44
46
  .option("--verbose", "Show agent stderr output")
@@ -48,9 +50,11 @@ const program = new Command()
48
50
  Requirements:
49
51
  Install the agent CLIs you plan to use: claude, codex, gemini, opencode, copilot.
50
52
  Override paths: AXEXEC_CLAUDE_PATH, AXEXEC_CODEX_PATH, AXEXEC_GEMINI_PATH, AXEXEC_OPENCODE_PATH, AXEXEC_COPILOT_PATH
53
+ Security: axexec is not a sandbox. Run inside an external sandbox for enforcement.
51
54
 
52
55
  Examples:
53
56
  axexec -a claude "Refactor auth flow"
57
+ axexec -a claude --credentials-file ./creds.json "Review this PR"
54
58
  axexec -a opencode --provider anthropic -m claude-sonnet-4 "Hello"
55
59
  axexec -a claude -f jsonl "Audit deps" | jq 'select(.type=="tool.call")'
56
60
  axexec --list-agents | tail -n +2 | cut -f1
@@ -65,71 +69,89 @@ Examples:
65
69
  }
66
70
  return;
67
71
  }
68
- // Get prompt from flag, positional argument, or stdin (in priority order)
69
- // Only read stdin if no prompt was provided via CLI arguments to avoid
70
- // blocking in CI environments with open but unused stdin.
71
- const needsStdinPrompt = !options.prompt && !positionalPrompt;
72
- const stdinPrompt = needsStdinPrompt && !process.stdin.isTTY ? await readStdin() : undefined;
73
- const prompt = (options.prompt ??
74
- positionalPrompt ??
75
- stdinPrompt)?.trimEnd();
76
- if (!prompt) {
77
- process.stderr.write("Error: prompt is required\n");
72
+ if (!options.agent) {
73
+ process.stderr.write("Error: --agent is required\n");
78
74
  process.stderr.write("Usage: axexec --agent <id> <prompt>\n");
79
75
  process.stderr.write("Try 'axexec --help' for more information.\n");
80
76
  process.exitCode = 2;
81
77
  return;
82
78
  }
83
- if (!options.agent) {
84
- process.stderr.write("Error: --agent is required\n");
85
- process.stderr.write("Usage: axexec --agent <id> <prompt>\n");
86
- process.stderr.write("Try 'axexec --help' for more information.\n");
79
+ // Validate agent ID early to provide clear error before processing credentials
80
+ const agentValidation = validateAgent(options.agent);
81
+ if (!agentValidation.ok) {
82
+ process.stderr.write(`${agentValidation.message}\n`);
83
+ process.exitCode = agentValidation.exitCode;
84
+ return;
85
+ }
86
+ // Validate --cwd if provided
87
+ if (options.cwd) {
88
+ const cwdResult = await validateCwd(options.cwd);
89
+ if (!cwdResult.ok) {
90
+ process.stderr.write(`Error: ${cwdResult.error}\n`);
91
+ process.exitCode = 2;
92
+ return;
93
+ }
94
+ }
95
+ const needsStdinPrompt = !options.prompt && !positionalPrompt;
96
+ const stdinResult = validateStdinUsage(options.credentialsFile, needsStdinPrompt, process.stdin.isTTY);
97
+ if (!stdinResult.ok) {
98
+ process.stderr.write(`Error: ${stdinResult.error}\n`);
87
99
  process.exitCode = 2;
88
100
  return;
89
101
  }
102
+ // Get prompt from flag, positional argument, or stdin (in priority order)
103
+ // Only read stdin if no prompt was provided via CLI arguments to avoid
104
+ // blocking in CI environments with open but unused stdin.
105
+ const stdinPrompt = needsStdinPrompt &&
106
+ !process.stdin.isTTY &&
107
+ options.credentialsFile !== "-"
108
+ ? await readStdin()
109
+ : undefined;
110
+ const promptResult = resolvePrompt({
111
+ promptFlag: options.prompt,
112
+ positionalPrompt,
113
+ stdinPrompt,
114
+ });
115
+ if (!promptResult.ok) {
116
+ process.stderr.write(`Error: ${promptResult.error}\n`);
117
+ process.exitCode = 2;
118
+ return;
119
+ }
120
+ const { prompt } = promptResult;
90
121
  // Validate format option
91
122
  let format;
92
123
  if (options.format) {
93
- if (options.format === "jsonl" || options.format === "tsv") {
94
- format = options.format;
95
- }
96
- else {
97
- process.stderr.write(`Error: Invalid format '${options.format}'\n`);
98
- process.stderr.write("Valid formats: jsonl, tsv\n");
124
+ const formatResult = parseOutputFormat(options.format);
125
+ if (!formatResult.ok) {
126
+ process.stderr.write(`Error: ${formatResult.error}\n`);
99
127
  process.exitCode = 2;
100
128
  return;
101
129
  }
130
+ format = formatResult.format;
102
131
  }
103
- // Validate --provider is only used with OpenCode
104
- if (options.provider && options.agent !== "opencode") {
105
- process.stderr.write("Error: --provider is only supported for OpenCode agent\n");
132
+ // Resolve credentials from --credentials-file if specified.
133
+ const credentialsResult = await resolveCredentials(options.credentialsFile, readStdin);
134
+ if (!credentialsResult.ok) {
135
+ process.stderr.write(`Error: ${credentialsResult.error}\n`);
106
136
  process.exitCode = 2;
107
137
  return;
108
138
  }
109
- // Validate OpenCode requires --provider
110
- if (options.agent === "opencode") {
111
- // Check for deprecated provider/model format
112
- if (options.model?.includes("/")) {
113
- const [provider, model] = options.model.split("/", 2);
114
- process.stderr.write("Error: Model format 'provider/model' is no longer supported\n");
115
- process.stderr.write("Use separate --provider and --model flags:\n");
116
- process.stderr.write(` axexec -a opencode --provider ${provider} -m ${model} ...\n`);
117
- process.exitCode = 2;
118
- return;
119
- }
120
- // Require --provider for OpenCode
121
- if (!options.provider) {
122
- process.stderr.write("Error: OpenCode requires --provider\n");
123
- process.stderr.write(" axexec -a opencode --provider anthropic -m claude-sonnet-4 ...\n");
124
- process.exitCode = 2;
125
- return;
126
- }
139
+ const { credentials } = credentialsResult;
140
+ // Validate provider options
141
+ const providerResult = validateProviderOptions(options.agent, options.model, options.provider);
142
+ if (!providerResult.ok) {
143
+ process.stderr.write(`Error: ${providerResult.error}\n`);
144
+ process.exitCode = 2;
145
+ return;
127
146
  }
147
+ const { normalizedProvider } = providerResult;
128
148
  // Run agent
129
149
  const result = await runAgent(options.agent, {
130
150
  prompt,
151
+ cwd: options.cwd,
152
+ credentials,
131
153
  model: options.model,
132
- provider: options.provider,
154
+ provider: normalizedProvider,
133
155
  allow: options.allow,
134
156
  deny: options.deny,
135
157
  format,
@@ -138,9 +160,8 @@ Examples:
138
160
  verbose: options.verbose,
139
161
  preserveGithubSha: options.preserveGithubSha,
140
162
  });
141
- // Set exit code based on success
142
- if (!result.success) {
143
- // eslint-disable-next-line require-atomic-updates
163
+ // Set exit code based on success (only if not already set to a more specific code)
164
+ if (!result.success && process.exitCode === undefined) {
144
165
  process.exitCode = 1;
145
166
  }
146
167
  });
@@ -4,12 +4,12 @@
4
4
  * Each agent has its own env var requirements. This module extracts
5
5
  * the appropriate values from Credentials and returns them as env vars.
6
6
  */
7
- import type { Credentials } from "axshared";
7
+ import type { AgentCli, Credentials } from "axshared";
8
8
  /**
9
9
  * Gets additional environment variables for credential-based auth.
10
10
  *
11
11
  * Some agents use environment variables for API keys or OAuth tokens
12
12
  * in addition to or instead of file-based credentials.
13
13
  */
14
- declare function getCredentialEnvironment(credentials: Credentials): Record<string, string>;
14
+ declare function getCredentialEnvironment(agentId: AgentCli, credentials: Credentials, provider?: string): Record<string, string>;
15
15
  export { getCredentialEnvironment };
@@ -11,7 +11,7 @@ import { resolveStringField } from "./resolve-string-field.js";
11
11
  * Some agents use environment variables for API keys or OAuth tokens
12
12
  * in addition to or instead of file-based credentials.
13
13
  */
14
- function getCredentialEnvironment(credentials) {
14
+ function getCredentialEnvironment(agentId, credentials, provider) {
15
15
  const environment = {};
16
16
  // Extract API key from credentials data
17
17
  const apiKey = resolveStringField(credentials.data, "apiKey", "key");
@@ -19,7 +19,7 @@ function getCredentialEnvironment(credentials) {
19
19
  // axauth uses "accessToken", but also check legacy "oauthToken" and "token" keys
20
20
  const oauthToken = resolveStringField(credentials.data, "accessToken", "oauthToken") ??
21
21
  resolveStringField(credentials.data, "token", "token");
22
- switch (credentials.agent) {
22
+ switch (agentId) {
23
23
  case "claude": {
24
24
  if (credentials.type === "api-key" && apiKey) {
25
25
  environment["ANTHROPIC_API_KEY"] = apiKey;
@@ -51,7 +51,6 @@ function getCredentialEnvironment(credentials) {
51
51
  }
52
52
  case "opencode": {
53
53
  // OpenCode supports multiple providers, env var depends on provider
54
- const provider = credentials.provider;
55
54
  if (credentials.type === "api-key" && apiKey) {
56
55
  switch (provider) {
57
56
  case "anthropic": {
@@ -71,6 +70,9 @@ function getCredentialEnvironment(credentials) {
71
70
  environment["OPENCODE_API_KEY"] = apiKey;
72
71
  break;
73
72
  }
73
+ case undefined: {
74
+ break;
75
+ }
74
76
  }
75
77
  }
76
78
  break;
@@ -3,8 +3,8 @@
3
3
  *
4
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
- * directory. This ensures complete isolation - agents never discover or use
7
- * locally installed credentials.
6
+ * directory. This ensures credential isolation: agents don't discover or use
7
+ * locally installed credentials/config.
8
8
  */
9
9
  import { type AgentCli, type Credentials } from "axshared";
10
10
  import type { InstallResult } from "./types.js";
@@ -19,7 +19,7 @@ type WarningWriter = (message: string) => void;
19
19
  *
20
20
  * The caller is responsible for cleaning up the temp directory after use.
21
21
  */
22
- declare function installCredentials(agentId: AgentCli, credentials?: Credentials, warn?: WarningWriter): Promise<InstallResult>;
22
+ declare function installCredentials(agentId: AgentCli, credentials?: Credentials, provider?: string, warn?: WarningWriter): Promise<InstallResult>;
23
23
  /**
24
24
  * Cleanup for use in finally blocks.
25
25
  */
@@ -3,8 +3,8 @@
3
3
  *
4
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
- * directory. This ensures complete isolation - agents never discover or use
7
- * locally installed credentials.
6
+ * directory. This ensures credential isolation: agents don't discover or use
7
+ * locally installed credentials/config.
8
8
  */
9
9
  import { mkdirSync } from "node:fs";
10
10
  import { mkdtemp, rm } from "node:fs/promises";
@@ -42,7 +42,7 @@ 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, warn) {
45
+ function installAgentCredentials(agentId, configDirectory, dataDirectory, credentials, provider, warn) {
46
46
  switch (agentId) {
47
47
  case "claude": {
48
48
  installClaudeCredentials(configDirectory, credentials);
@@ -57,8 +57,8 @@ function installAgentCredentials(agentId, configDirectory, dataDirectory, creden
57
57
  break;
58
58
  }
59
59
  case "opencode": {
60
- if (credentials.agent === "opencode") {
61
- installOpenCodeCredentials(dataDirectory, credentials, warn);
60
+ if (provider) {
61
+ installOpenCodeCredentials(dataDirectory, credentials, provider, warn);
62
62
  }
63
63
  break;
64
64
  }
@@ -78,15 +78,15 @@ function installAgentCredentials(agentId, configDirectory, dataDirectory, creden
78
78
  *
79
79
  * The caller is responsible for cleaning up the temp directory after use.
80
80
  */
81
- async function installCredentials(agentId, credentials, warn) {
81
+ async function installCredentials(agentId, credentials, provider, warn) {
82
82
  const { baseDirectory, configDirectory, dataDirectory } = await createTemporaryDirectories(agentId);
83
83
  try {
84
84
  if (credentials) {
85
- installAgentCredentials(agentId, configDirectory, dataDirectory, credentials, warn);
85
+ installAgentCredentials(agentId, configDirectory, dataDirectory, credentials, provider, warn);
86
86
  }
87
87
  const runtimeEnvironment = buildAgentRuntimeEnvironment(agentId, configDirectory, dataDirectory);
88
88
  const credentialEnvironment = credentials
89
- ? getCredentialEnvironment(credentials)
89
+ ? getCredentialEnvironment(agentId, credentials, provider)
90
90
  : {};
91
91
  return {
92
92
  env: { ...runtimeEnvironment, ...credentialEnvironment },
@@ -54,7 +54,5 @@ declare function installGeminiCredentials(configDirectory: string, credentials:
54
54
  * - oauth-credentials: Written as { [provider]: { type: "oauth", ...data } }
55
55
  * - api-key: Written as { [provider]: { type: "api", key: "..." } }
56
56
  */
57
- declare function installOpenCodeCredentials(dataDirectory: string, credentials: Extract<Credentials, {
58
- agent: "opencode";
59
- }>, warn?: WarningWriter): void;
57
+ declare function installOpenCodeCredentials(dataDirectory: string, credentials: Credentials, provider: string, warn?: WarningWriter): void;
60
58
  export { installClaudeCredentials, installCodexCredentials, installGeminiCredentials, installOpenCodeCredentials, };
@@ -100,9 +100,8 @@ function installGeminiCredentials(configDirectory, credentials) {
100
100
  * - oauth-credentials: Written as { [provider]: { type: "oauth", ...data } }
101
101
  * - api-key: Written as { [provider]: { type: "api", key: "..." } }
102
102
  */
103
- function installOpenCodeCredentials(dataDirectory, credentials, warn = defaultWarningWriter) {
103
+ function installOpenCodeCredentials(dataDirectory, credentials, provider, warn = defaultWarningWriter) {
104
104
  const authPath = path.join(dataDirectory, "auth.json");
105
- const provider = credentials.provider;
106
105
  switch (credentials.type) {
107
106
  case "oauth-credentials": {
108
107
  saveJsonFile(authPath, {
@@ -12,6 +12,7 @@ async function executeAgent(agentId, adapter, options, configEnvironment, creden
12
12
  const runOptions = {
13
13
  prompt: options.prompt,
14
14
  verbose: options.verbose ?? false,
15
+ cwd: options.cwd,
15
16
  model: options.model,
16
17
  provider: options.provider,
17
18
  rawLogPath: options.rawLog,
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Format Zod validation errors for human-readable display.
3
+ */
4
+ import type { ZodError } from "zod";
5
+ declare function formatZodError(error: ZodError): string;
6
+ export { formatZodError };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Format Zod validation errors for human-readable display.
3
+ */
4
+ function formatZodError(error) {
5
+ return error.issues
6
+ .map((issue) => {
7
+ const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
8
+ return `${path}: ${issue.message}`;
9
+ })
10
+ .join("; ");
11
+ }
12
+ export { formatZodError };
@@ -6,14 +6,7 @@
6
6
  */
7
7
  import { parseCredentialsResult, } from "axshared";
8
8
  import { getEnvironmentTrimmed } from "./credentials/get-environment-trimmed.js";
9
- function formatZodError(error) {
10
- return error.issues
11
- .map((issue) => {
12
- const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
13
- return `${path}: ${issue.message}`;
14
- })
15
- .join("; ");
16
- }
9
+ import { formatZodError } from "./format-zod-error.js";
17
10
  /**
18
11
  * Parses credentials from environment variables.
19
12
  *
@@ -32,14 +25,6 @@ function parseCredentialsFromEnvironment(agentId) {
32
25
  const parsed = JSON.parse(credEnvironmentValue);
33
26
  const parseResult = parseCredentialsResult(parsed);
34
27
  if (parseResult.ok) {
35
- if (parseResult.value.agent !== agentId) {
36
- return {
37
- ok: false,
38
- exitCode: 2,
39
- message: `Error: ${credEnvironmentVariable} agent mismatch. ` +
40
- `Expected ${agentId}, got ${parseResult.value.agent}.`,
41
- };
42
- }
43
28
  return { ok: true, credentials: parseResult.value };
44
29
  }
45
30
  return {
@@ -74,7 +59,6 @@ function parseApiKeyFromEnvironment(agentId) {
74
59
  return {
75
60
  ok: true,
76
61
  credentials: {
77
- agent: agentId,
78
62
  type: "oauth-token",
79
63
  data: { oauthToken },
80
64
  },
@@ -109,7 +93,6 @@ function parseApiKeyFromEnvironment(agentId) {
109
93
  return {
110
94
  ok: true,
111
95
  credentials: {
112
- agent: agentId,
113
96
  type: "api-key",
114
97
  data: { apiKey },
115
98
  },
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Read and parse credentials from a file or stdin.
3
+ */
4
+ import { type Credentials } from "axshared";
5
+ type ReadCredentialsResult = {
6
+ ok: true;
7
+ credentials: Credentials;
8
+ } | {
9
+ ok: false;
10
+ error: string;
11
+ };
12
+ declare function readCredentialsFile(path: string, readStdin: () => Promise<string>): Promise<ReadCredentialsResult>;
13
+ export { readCredentialsFile };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Read and parse credentials from a file or stdin.
3
+ */
4
+ import { readFile } from "node:fs/promises";
5
+ import { parseCredentialsResult } from "axshared";
6
+ import { formatZodError } from "./format-zod-error.js";
7
+ async function readCredentialsFile(path, readStdin) {
8
+ let raw;
9
+ try {
10
+ raw = path === "-" ? await readStdin() : await readFile(path, "utf8");
11
+ }
12
+ catch (error) {
13
+ const message = error instanceof Error ? error.message : String(error);
14
+ return { ok: false, error: `Failed to read credentials file: ${message}` };
15
+ }
16
+ let parsed;
17
+ try {
18
+ parsed = JSON.parse(raw);
19
+ }
20
+ catch (error) {
21
+ const message = error instanceof Error ? error.message : String(error);
22
+ return { ok: false, error: `Failed to parse credentials JSON: ${message}` };
23
+ }
24
+ const parseResult = parseCredentialsResult(parsed);
25
+ if (!parseResult.ok) {
26
+ return {
27
+ ok: false,
28
+ error: `Invalid credentials format: ${formatZodError(parseResult.error)}`,
29
+ };
30
+ }
31
+ return { ok: true, credentials: parseResult.value };
32
+ }
33
+ export { readCredentialsFile };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Read all data from stdin as a string.
3
+ */
4
+ declare function readStdin(): Promise<string>;
5
+ export { readStdin };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Read all data from stdin as a string.
3
+ */
4
+ async function readStdin() {
5
+ let data = "";
6
+ for await (const chunk of process.stdin) {
7
+ data += typeof chunk === "string" ? chunk : chunk.toString("utf8");
8
+ }
9
+ return data.trimEnd();
10
+ }
11
+ export { readStdin };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Resolve credentials from --credentials-file option.
3
+ */
4
+ import type { Credentials } from "axshared";
5
+ type ResolveCredentialsResult = {
6
+ ok: true;
7
+ credentials: Credentials | undefined;
8
+ } | {
9
+ ok: false;
10
+ error: string;
11
+ };
12
+ /**
13
+ * Resolve credentials from --credentials-file if specified.
14
+ *
15
+ * @param credentialsFile - Path to credentials file or "-" for stdin
16
+ * @param readStdin - Function to read stdin
17
+ * @returns Credentials or undefined if no file specified
18
+ */
19
+ declare function resolveCredentials(credentialsFile: string | undefined, readStdin: () => Promise<string>): Promise<ResolveCredentialsResult>;
20
+ export { resolveCredentials };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Resolve credentials from --credentials-file option.
3
+ */
4
+ import { readCredentialsFile } from "./read-credentials-file.js";
5
+ /**
6
+ * Resolve credentials from --credentials-file if specified.
7
+ *
8
+ * @param credentialsFile - Path to credentials file or "-" for stdin
9
+ * @param readStdin - Function to read stdin
10
+ * @returns Credentials or undefined if no file specified
11
+ */
12
+ async function resolveCredentials(credentialsFile, readStdin) {
13
+ if (!credentialsFile) {
14
+ return { ok: true, credentials: undefined };
15
+ }
16
+ const result = await readCredentialsFile(credentialsFile, readStdin);
17
+ if (!result.ok) {
18
+ return { ok: false, error: result.error };
19
+ }
20
+ return { ok: true, credentials: result.credentials };
21
+ }
22
+ export { resolveCredentials };
@@ -35,5 +35,19 @@ type OutputMode = "jsonl" | "tsv" | "tsv-truncated";
35
35
  * resolveOutputMode(undefined, false) // → "tsv"
36
36
  */
37
37
  declare function resolveOutputMode(format: OutputFormat | undefined, isTTY: boolean): OutputMode;
38
- export { resolveOutputMode };
38
+ type ParseFormatResult = {
39
+ ok: true;
40
+ format: OutputFormat;
41
+ } | {
42
+ ok: false;
43
+ error: string;
44
+ };
45
+ /**
46
+ * Parse and validate a format string from CLI input.
47
+ *
48
+ * @param input - Raw format string from --format flag
49
+ * @returns Result with validated format or error message
50
+ */
51
+ declare function parseOutputFormat(input: string): ParseFormatResult;
52
+ export { resolveOutputMode, parseOutputFormat };
39
53
  export type { OutputFormat, OutputMode };
@@ -36,4 +36,19 @@ function resolveOutputMode(format, isTTY) {
36
36
  // Auto-detect based on TTY
37
37
  return isTTY ? "tsv-truncated" : "tsv";
38
38
  }
39
- export { resolveOutputMode };
39
+ /**
40
+ * Parse and validate a format string from CLI input.
41
+ *
42
+ * @param input - Raw format string from --format flag
43
+ * @returns Result with validated format or error message
44
+ */
45
+ function parseOutputFormat(input) {
46
+ if (input === "jsonl" || input === "tsv") {
47
+ return { ok: true, format: input };
48
+ }
49
+ return {
50
+ ok: false,
51
+ error: `Invalid format '${input}'\nValid formats: jsonl, tsv`,
52
+ };
53
+ }
54
+ export { resolveOutputMode, parseOutputFormat };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Resolve prompt from CLI options, positional argument, or stdin.
3
+ */
4
+ type ResolvePromptResult = {
5
+ ok: true;
6
+ prompt: string;
7
+ } | {
8
+ ok: false;
9
+ error: string;
10
+ };
11
+ interface ResolvePromptOptions {
12
+ promptFlag: string | undefined;
13
+ positionalPrompt: string | undefined;
14
+ stdinPrompt: string | undefined;
15
+ }
16
+ /**
17
+ * Resolve prompt from multiple sources in priority order.
18
+ *
19
+ * Priority: --prompt flag > positional argument > stdin
20
+ */
21
+ declare function resolvePrompt(options: ResolvePromptOptions): ResolvePromptResult;
22
+ export { resolvePrompt };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Resolve prompt from CLI options, positional argument, or stdin.
3
+ */
4
+ /**
5
+ * Resolve prompt from multiple sources in priority order.
6
+ *
7
+ * Priority: --prompt flag > positional argument > stdin
8
+ */
9
+ function resolvePrompt(options) {
10
+ const prompt = (options.promptFlag ??
11
+ options.positionalPrompt ??
12
+ options.stdinPrompt)?.trimEnd();
13
+ if (!prompt) {
14
+ return {
15
+ ok: false,
16
+ error: "prompt is required\n" +
17
+ "Usage: axexec --agent <id> <prompt>\n" +
18
+ "Try 'axexec --help' for more information.",
19
+ };
20
+ }
21
+ return { ok: true, prompt };
22
+ }
23
+ export { resolvePrompt };
@@ -3,7 +3,10 @@
3
3
  */
4
4
  import type { RunAgentOptions, RunResult } from "./types/run-result.js";
5
5
  /**
6
- * Runs an agent with full isolation.
6
+ * Runs an agent with credential/config isolation.
7
+ *
8
+ * Note: axexec is not an OS sandbox. It runs agents in a non-interactive,
9
+ * permissive mode by default and should not be treated as a security boundary.
7
10
  *
8
11
  * 1. Validates the agent ID
9
12
  * 2. Uses provided credentials or parses from environment