@atrvd/trigger 0.1.6 → 1.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.
- package/README.md +215 -0
- package/dist/index.d.mts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +18 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +17 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# @atrvd/trigger
|
|
2
|
+
|
|
3
|
+
Shared run-functions for [trigger.dev](https://trigger.dev) tasks. Wraps the Claude Code CLI (`claude --print`) as an async function you can call from any trigger.dev task, with structured output, schema validation, auto-fix retries, and account rotation built in.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @atrvd/trigger
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { runClaudeCode } from "@atrvd/trigger";
|
|
15
|
+
|
|
16
|
+
const result = await runClaudeCode({
|
|
17
|
+
prompt: "Summarize the README in this repo",
|
|
18
|
+
systemPrompt: "You are a technical writer. Be concise.",
|
|
19
|
+
addDir: "/path/to/repo",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
console.log(result.text); // Claude's plain-text response
|
|
23
|
+
console.log(result.parsed); // JSON-parsed value, or null for plain text
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Structured output with Zod
|
|
27
|
+
|
|
28
|
+
Pass a Zod v4+ schema to get typed, validated responses. The schema is converted to JSON Schema and enforced at the CLI level (`--json-schema`), then validated again at runtime. If validation fails, an auto-fix retry is sent to a cheap model.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
import { runClaudeCode } from "@atrvd/trigger";
|
|
33
|
+
|
|
34
|
+
const ReviewSchema = z.object({
|
|
35
|
+
summary: z.string(),
|
|
36
|
+
score: z.number().min(0).max(10),
|
|
37
|
+
issues: z.array(z.string()),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const result = await runClaudeCode({
|
|
41
|
+
prompt: "Review this PR diff",
|
|
42
|
+
systemPrompt: "You are a senior code reviewer. Return structured JSON.",
|
|
43
|
+
addDir: "/repo",
|
|
44
|
+
schema: ReviewSchema,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// result.parsed is typed as { summary: string; score: number; issues: string[] }
|
|
48
|
+
console.log(result.parsed.score);
|
|
49
|
+
console.log(result.fixAttempts); // 0 = first response was valid
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Raw JSON Schema (no validation)
|
|
53
|
+
|
|
54
|
+
For CLI-level enforcement without runtime validation or auto-fix:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const result = await runClaudeCode({
|
|
58
|
+
prompt: "Return the config",
|
|
59
|
+
systemPrompt: "Return raw JSON only.",
|
|
60
|
+
addDir: "/repo",
|
|
61
|
+
jsonSchema: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: { name: { type: "string" }, version: { type: "number" } },
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// result.parsed is `unknown` — no runtime validation
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
> `schema` and `jsonSchema` are mutually exclusive.
|
|
71
|
+
|
|
72
|
+
## API
|
|
73
|
+
|
|
74
|
+
### `runClaudeCode(input: ClaudeCodeInput<T>): Promise<ClaudeCodeResult<T>>`
|
|
75
|
+
|
|
76
|
+
Spawns the Claude CLI as a subprocess and returns the parsed response.
|
|
77
|
+
|
|
78
|
+
### `ClaudeCodeInput`
|
|
79
|
+
|
|
80
|
+
| Field | Type | Default | Description |
|
|
81
|
+
|-------|------|---------|-------------|
|
|
82
|
+
| `prompt` | `string` | *required* | The prompt sent to Claude (delivered via stdin). |
|
|
83
|
+
| `addDir` | `string` | *required* | Absolute path to the repo directory. Sets `--add-dir` and `cwd`. |
|
|
84
|
+
| `model` | `string` | `"claude-sonnet-4-6"` | Model ID passed to `--model`. |
|
|
85
|
+
| `timeout` | `number` | `300_000` | Timeout in milliseconds. Process is killed on expiry. |
|
|
86
|
+
| `maxOutputBytes` | `number` | `10_485_760` | Max stdout size (10 MB). Process killed if exceeded. |
|
|
87
|
+
| `env` | `NodeJS.ProcessEnv` | `process.env` | Environment variables for the subprocess. |
|
|
88
|
+
| `binary` | `string` | `"ccs"` | Binary to execute. Use `"claude"` for direct CLI invocation. |
|
|
89
|
+
| `schema` | `z.ZodType<T>` | — | Zod v4+ schema. Enables CLI enforcement + runtime validation + auto-fix. Mutually exclusive with `jsonSchema`. |
|
|
90
|
+
| `jsonSchema` | `Record<string, unknown>` | — | Raw JSON Schema for CLI enforcement only. No runtime validation. Mutually exclusive with `schema`. |
|
|
91
|
+
| `fixModel` | `string` | `"claude-haiku-4-5-20251001"` | Model for auto-fix retries on schema validation failure. |
|
|
92
|
+
| `maxFixes` | `number` | `1` | Max auto-fix attempts. |
|
|
93
|
+
| `tools` | `string[]` | `["Read", "Grep", "Glob"]` | Tool list passed via `--tools`. Empty array falls back to default. Read-only by default. |
|
|
94
|
+
| `allowedTools` | `string[]` | — | Restrict Bash commands via `--allowed-tools`. E.g. `["Bash(git log*)"]`. Requires `"Bash"` in `tools`. |
|
|
95
|
+
| `mcpConfigPath` | `string` | — | Path to MCP config JSON. Adds `--mcp-config`. |
|
|
96
|
+
| `ccsAccounts` | `string[]` | — | CCS account names for rotation on `is_error` responses. |
|
|
97
|
+
| `systemPrompt` | `string` | *required* | System instructions for the agent (`--system-prompt`). Always combined with `--bare` — global CLAUDE.md and all side effects are suppressed. |
|
|
98
|
+
|
|
99
|
+
### `ClaudeCodeResult<T>`
|
|
100
|
+
|
|
101
|
+
| Field | Type | Description |
|
|
102
|
+
|-------|------|-------------|
|
|
103
|
+
| `text` | `string` | The raw text content from Claude's response. |
|
|
104
|
+
| `parsed` | `T \| null` | Typed value when schema is provided; `null` for plain-text responses. |
|
|
105
|
+
| `envelope` | `ClaudeEnvelope` | The full JSON envelope from `claude --output-format json`. |
|
|
106
|
+
| `fixAttempts` | `number` | Number of auto-fix retries used. `0` = first response was valid. |
|
|
107
|
+
|
|
108
|
+
## System prompt
|
|
109
|
+
|
|
110
|
+
`systemPrompt` is required. Every invocation runs with `--bare` which disables global CLAUDE.md auto-discovery, hooks, LSP, and auto-memory. Your `systemPrompt` is the only system instruction Claude receives — full, isolated control over agent behavior per task.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
await runClaudeCode({
|
|
114
|
+
prompt: "Analyze this code for security issues",
|
|
115
|
+
systemPrompt: "You are a security auditor. Only report vulnerabilities. Respond in JSON.",
|
|
116
|
+
addDir: "/repo",
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Error handling
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { runClaudeCode, SchemaValidationError } from "@atrvd/trigger";
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const result = await runClaudeCode({
|
|
127
|
+
prompt: "Analyze code",
|
|
128
|
+
systemPrompt: "You are a code reviewer. Return JSON.",
|
|
129
|
+
addDir: "/repo",
|
|
130
|
+
schema: ReviewSchema,
|
|
131
|
+
});
|
|
132
|
+
console.log(result.parsed);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
if (err instanceof SchemaValidationError) {
|
|
135
|
+
// Schema validation failed after all auto-fix attempts
|
|
136
|
+
console.error("Schema validation failed:", err.message);
|
|
137
|
+
} else {
|
|
138
|
+
// Process timeout, non-zero exit code, spawn failure, etc.
|
|
139
|
+
console.error("Execution failed:", err.message);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Possible errors:
|
|
145
|
+
- **Validation**: empty `prompt`, `systemPrompt`, or `addDir`; `allowedTools` without `Bash` in `tools`; `schema` + `jsonSchema` together
|
|
146
|
+
- **Process**: timeout, non-zero exit code, output exceeds `maxOutputBytes`, spawn failure
|
|
147
|
+
- **Schema**: `SchemaValidationError` after exhausting `maxFixes` auto-fix attempts
|
|
148
|
+
|
|
149
|
+
## Tool permissions
|
|
150
|
+
|
|
151
|
+
By default, Claude gets read-only access: `Read`, `Grep`, `Glob`. To allow code execution:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
await runClaudeCode({
|
|
155
|
+
prompt: "Run the tests and report results",
|
|
156
|
+
systemPrompt: "You are a test runner. Report pass/fail counts.",
|
|
157
|
+
addDir: "/repo",
|
|
158
|
+
tools: ["Bash", "Read", "Grep", "Glob"],
|
|
159
|
+
allowedTools: ["Bash(npm test*)", "Bash(git log*)"],
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## MCP servers
|
|
164
|
+
|
|
165
|
+
Pass an MCP config file to give Claude access to external tools:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { writeFileSync, unlinkSync } from "node:fs";
|
|
169
|
+
|
|
170
|
+
const mcpConfig = {
|
|
171
|
+
mcpServers: {
|
|
172
|
+
filesystem: {
|
|
173
|
+
command: "npx",
|
|
174
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/data"],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
writeFileSync("/tmp/mcp.json", JSON.stringify(mcpConfig));
|
|
180
|
+
|
|
181
|
+
const result = await runClaudeCode({
|
|
182
|
+
prompt: "List files in /data",
|
|
183
|
+
systemPrompt: "List directory contents.",
|
|
184
|
+
addDir: "/repo",
|
|
185
|
+
mcpConfigPath: "/tmp/mcp.json",
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
unlinkSync("/tmp/mcp.json");
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Account rotation
|
|
192
|
+
|
|
193
|
+
When using [CCS](https://github.com/nicobailey/ccs) (Claude Code Switcher), rotate through accounts on `is_error` responses:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const result = await runClaudeCode({
|
|
197
|
+
prompt: "Complex analysis task",
|
|
198
|
+
systemPrompt: "Perform deep code analysis.",
|
|
199
|
+
addDir: "/repo",
|
|
200
|
+
ccsAccounts: ["account-a", "account-b", "account-c"],
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Each account maps to `CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>`. On `is_error`, the next account is tried automatically. Non-error failures (timeouts, exit codes) are thrown immediately.
|
|
205
|
+
|
|
206
|
+
## Exports
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { runClaudeCode, SchemaValidationError } from "@atrvd/trigger";
|
|
210
|
+
import type { ClaudeCodeInput, ClaudeCodeResult, ClaudeEnvelope } from "@atrvd/trigger";
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -82,6 +82,12 @@ interface ClaudeCodeInput<T = unknown> {
|
|
|
82
82
|
* will exit with a non-zero code and the promise will reject.
|
|
83
83
|
*/
|
|
84
84
|
mcpConfigPath?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Replaces the default system prompt entirely.
|
|
87
|
+
* Adds `--system-prompt <text>` to spawn args.
|
|
88
|
+
* CLAUDE.md is ignored when this is set.
|
|
89
|
+
*/
|
|
90
|
+
systemPrompt: string;
|
|
85
91
|
/**
|
|
86
92
|
* Absolute path to the repository directory Claude should work in.
|
|
87
93
|
* Required — without it Claude would inherit the worker CWD, exposing
|
|
@@ -123,10 +129,18 @@ interface ClaudeCodeResult<T = unknown> {
|
|
|
123
129
|
/** Number of auto-fix retries used. 0 means the first response was valid. */
|
|
124
130
|
fixAttempts: number;
|
|
125
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Thrown when schema validation fails after all auto-fix attempts are exhausted.
|
|
134
|
+
*/
|
|
135
|
+
declare class SchemaValidationError extends Error {
|
|
136
|
+
constructor(message: string);
|
|
137
|
+
}
|
|
126
138
|
/**
|
|
127
139
|
* Runs the configured binary (default: ccs) with --print flag as a subprocess.
|
|
128
|
-
* Command: `<binary> --print --output-format json --model <model> [--json-schema <schema>]`
|
|
140
|
+
* Command: `<binary> --print --bare --output-format json --model <model> [--json-schema <schema>]`
|
|
129
141
|
* The prompt is written to the process stdin to avoid OS ARG_MAX limits (~2MB on Linux).
|
|
142
|
+
* --bare suppresses global CLAUDE.md auto-discovery, hooks, LSP, auto-memory and other
|
|
143
|
+
* side effects — ensuring the agent runs only with the provided systemPrompt.
|
|
130
144
|
*
|
|
131
145
|
* When a `schema` is provided (Zod v4+):
|
|
132
146
|
* - Its JSON Schema is passed via `--json-schema` CLI flag (hard enforcement).
|
|
@@ -148,4 +162,4 @@ declare function runClaudeCode<T>(input: ClaudeCodeInput<T> & {
|
|
|
148
162
|
schema: z.ZodType<T>;
|
|
149
163
|
}): Promise<ClaudeCodeResult<T>>;
|
|
150
164
|
|
|
151
|
-
export { type ClaudeCodeInput, type ClaudeCodeResult, type ClaudeEnvelope, runClaudeCode };
|
|
165
|
+
export { type ClaudeCodeInput, type ClaudeCodeResult, type ClaudeEnvelope, SchemaValidationError, runClaudeCode };
|
package/dist/index.d.ts
CHANGED
|
@@ -82,6 +82,12 @@ interface ClaudeCodeInput<T = unknown> {
|
|
|
82
82
|
* will exit with a non-zero code and the promise will reject.
|
|
83
83
|
*/
|
|
84
84
|
mcpConfigPath?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Replaces the default system prompt entirely.
|
|
87
|
+
* Adds `--system-prompt <text>` to spawn args.
|
|
88
|
+
* CLAUDE.md is ignored when this is set.
|
|
89
|
+
*/
|
|
90
|
+
systemPrompt: string;
|
|
85
91
|
/**
|
|
86
92
|
* Absolute path to the repository directory Claude should work in.
|
|
87
93
|
* Required — without it Claude would inherit the worker CWD, exposing
|
|
@@ -123,10 +129,18 @@ interface ClaudeCodeResult<T = unknown> {
|
|
|
123
129
|
/** Number of auto-fix retries used. 0 means the first response was valid. */
|
|
124
130
|
fixAttempts: number;
|
|
125
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Thrown when schema validation fails after all auto-fix attempts are exhausted.
|
|
134
|
+
*/
|
|
135
|
+
declare class SchemaValidationError extends Error {
|
|
136
|
+
constructor(message: string);
|
|
137
|
+
}
|
|
126
138
|
/**
|
|
127
139
|
* Runs the configured binary (default: ccs) with --print flag as a subprocess.
|
|
128
|
-
* Command: `<binary> --print --output-format json --model <model> [--json-schema <schema>]`
|
|
140
|
+
* Command: `<binary> --print --bare --output-format json --model <model> [--json-schema <schema>]`
|
|
129
141
|
* The prompt is written to the process stdin to avoid OS ARG_MAX limits (~2MB on Linux).
|
|
142
|
+
* --bare suppresses global CLAUDE.md auto-discovery, hooks, LSP, auto-memory and other
|
|
143
|
+
* side effects — ensuring the agent runs only with the provided systemPrompt.
|
|
130
144
|
*
|
|
131
145
|
* When a `schema` is provided (Zod v4+):
|
|
132
146
|
* - Its JSON Schema is passed via `--json-schema` CLI flag (hard enforcement).
|
|
@@ -148,4 +162,4 @@ declare function runClaudeCode<T>(input: ClaudeCodeInput<T> & {
|
|
|
148
162
|
schema: z.ZodType<T>;
|
|
149
163
|
}): Promise<ClaudeCodeResult<T>>;
|
|
150
164
|
|
|
151
|
-
export { type ClaudeCodeInput, type ClaudeCodeResult, type ClaudeEnvelope, runClaudeCode };
|
|
165
|
+
export { type ClaudeCodeInput, type ClaudeCodeResult, type ClaudeEnvelope, SchemaValidationError, runClaudeCode };
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
SchemaValidationError: () => SchemaValidationError,
|
|
23
24
|
runClaudeCode: () => runClaudeCode
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -41,12 +42,13 @@ function killGracefully(proc) {
|
|
|
41
42
|
proc.kill("SIGKILL");
|
|
42
43
|
}, 5e3).unref();
|
|
43
44
|
}
|
|
44
|
-
function spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools) {
|
|
45
|
+
function spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt) {
|
|
45
46
|
const toolsList = tools?.length ? tools.join(",") : "Read,Grep,Glob";
|
|
46
|
-
const args = ["--print", "--output-format", "json", "--model", model, "--tools", toolsList];
|
|
47
|
+
const args = ["--print", "--bare", "--output-format", "json", "--model", model, "--tools", toolsList];
|
|
47
48
|
if (allowedTools?.length) args.push("--allowed-tools", ...allowedTools);
|
|
48
49
|
if (mcpConfigPath) args.push("--mcp-config", mcpConfigPath);
|
|
49
50
|
args.push("--add-dir", addDir);
|
|
51
|
+
if (systemPrompt) args.push("--system-prompt", systemPrompt);
|
|
50
52
|
if (jsonSchemaStr) {
|
|
51
53
|
args.push("--json-schema", jsonSchemaStr);
|
|
52
54
|
}
|
|
@@ -145,9 +147,9 @@ var IsErrorResponse = class extends Error {
|
|
|
145
147
|
this.name = "IsErrorResponse";
|
|
146
148
|
}
|
|
147
149
|
};
|
|
148
|
-
function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, accounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools) {
|
|
150
|
+
function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, accounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt) {
|
|
149
151
|
if (!accounts || accounts.length === 0) {
|
|
150
|
-
return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);
|
|
152
|
+
return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);
|
|
151
153
|
}
|
|
152
154
|
return (async () => {
|
|
153
155
|
let lastIsError;
|
|
@@ -157,7 +159,7 @@ function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env,
|
|
|
157
159
|
CLAUDE_CONFIG_DIR: `${(0, import_node_os.homedir)()}/.ccs/instances/${account}`
|
|
158
160
|
};
|
|
159
161
|
try {
|
|
160
|
-
return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);
|
|
162
|
+
return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);
|
|
161
163
|
} catch (err) {
|
|
162
164
|
if (err instanceof IsErrorResponse) {
|
|
163
165
|
lastIsError = err;
|
|
@@ -204,7 +206,8 @@ async function runClaudeCode(input) {
|
|
|
204
206
|
mcpConfigPath,
|
|
205
207
|
addDir,
|
|
206
208
|
tools,
|
|
207
|
-
allowedTools
|
|
209
|
+
allowedTools,
|
|
210
|
+
systemPrompt
|
|
208
211
|
} = input;
|
|
209
212
|
if (!prompt || prompt.trim().length === 0) {
|
|
210
213
|
throw new Error("prompt cannot be empty");
|
|
@@ -218,6 +221,12 @@ async function runClaudeCode(input) {
|
|
|
218
221
|
if (jsonSchema && schema) {
|
|
219
222
|
throw new Error("cannot use jsonSchema with schema");
|
|
220
223
|
}
|
|
224
|
+
if (!systemPrompt || systemPrompt.trim().length === 0) {
|
|
225
|
+
throw new Error("systemPrompt cannot be empty");
|
|
226
|
+
}
|
|
227
|
+
if (allowedTools?.length && (!tools || !tools.includes("Bash"))) {
|
|
228
|
+
throw new Error("allowedTools requires 'Bash' in tools array");
|
|
229
|
+
}
|
|
221
230
|
let jsonSchemaStr;
|
|
222
231
|
if (schema) {
|
|
223
232
|
try {
|
|
@@ -234,7 +243,7 @@ async function runClaudeCode(input) {
|
|
|
234
243
|
throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);
|
|
235
244
|
}
|
|
236
245
|
}
|
|
237
|
-
const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);
|
|
246
|
+
const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);
|
|
238
247
|
if (!schema) {
|
|
239
248
|
return { text: raw.text, parsed: raw.parsed, envelope: raw.envelope, fixAttempts: 0 };
|
|
240
249
|
}
|
|
@@ -248,7 +257,7 @@ async function runClaudeCode(input) {
|
|
|
248
257
|
}
|
|
249
258
|
for (let attempt = 1; attempt <= maxFixes; attempt++) {
|
|
250
259
|
const fixPromptStr = buildFixPrompt(lastText, lastError);
|
|
251
|
-
const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);
|
|
260
|
+
const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);
|
|
252
261
|
try {
|
|
253
262
|
const parsed = schema.parse(fixRaw.parsed);
|
|
254
263
|
return { ...fixRaw, parsed, fixAttempts: attempt };
|
|
@@ -264,6 +273,7 @@ async function runClaudeCode(input) {
|
|
|
264
273
|
}
|
|
265
274
|
// Annotate the CommonJS export names for ESM import in node:
|
|
266
275
|
0 && (module.exports = {
|
|
276
|
+
SchemaValidationError,
|
|
267
277
|
runClaudeCode
|
|
268
278
|
});
|
|
269
279
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/runners/claude-code.ts"],"sourcesContent":["export { runClaudeCode } from \"./runners/claude-code\";\nexport type { ClaudeCodeInput, ClaudeCodeResult, ClaudeEnvelope } from \"./runners/claude-code\";\n","import { spawn } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { z } from \"zod\";\n\nexport interface ClaudeCodeInput<T = unknown> {\n prompt: string;\n model?: string;\n /** Timeout in milliseconds. Default: 300_000 (5 min) */\n timeout?: number;\n /**\n * Maximum allowed stdout size in bytes. Default: 10_485_760 (10 MB).\n * If claude output exceeds this limit the process is killed and the promise rejects.\n */\n maxOutputBytes?: number;\n /**\n * Environment variables passed to the subprocess.\n * Defaults to the full process.env so that the binary can access its auth\n * tokens and config (ANTHROPIC_API_KEY, HOME, etc.).\n * Override if you need to restrict or augment the environment.\n */\n env?: NodeJS.ProcessEnv;\n /**\n * Binary to execute. Default: \"ccs\" (Claude Code Profile & Model Switcher).\n * Use \"claude\" to invoke the Claude CLI directly without profile switching.\n * Using `--print` (not `-p`) avoids the ccs delegation system while\n * preserving the standard `--output-format json` envelope output.\n */\n binary?: string;\n /**\n * Optional Zod schema (requires Zod v4+). When provided:\n * - Its JSON Schema representation is passed via `--json-schema` CLI flag\n * (CLI-level enforcement — Claude physically cannot return non-JSON).\n * - The response is validated with `schema.parse()`.\n * - On failure, an auto-fix retry is sent to `fixModel`.\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n * - Mutually exclusive with `jsonSchema`.\n */\n schema?: z.ZodType<T>;\n /**\n * Model used for auto-fix retries. Default: \"claude-haiku-4-5-20251001\"\n * (cheap and fast, good for JSON correction tasks).\n */\n fixModel?: string;\n /**\n * Maximum number of auto-fix attempts on schema validation failure. Default: 1.\n */\n maxFixes?: number;\n /**\n * Raw JSON Schema for CLI-level structured output enforcement.\n * Adds `--json-schema <schema>` to spawn args.\n * Claude returns structured_output in the envelope — guaranteed JSON.\n * No runtime validation or auto-fix. `result.parsed` is `unknown`.\n * Mutually exclusive with `schema`.\n */\n jsonSchema?: Record<string, unknown>;\n /**\n * Override the default tool list passed via `--tools`.\n * Default: `[\"Read\", \"Grep\", \"Glob\"]` (read-only by design).\n * Pass `[\"Bash\", \"Read\", \"Grep\", \"Glob\"]` to also allow Bash execution.\n */\n tools?: string[];\n /**\n * Restrict Bash commands via `--allowed-tools` flag.\n * Each entry is a pattern like `\"Bash(git log*)\"`.\n * Only meaningful when `tools` includes `\"Bash\"`.\n * When omitted, no `--allowed-tools` flag is added.\n */\n allowedTools?: string[];\n /**\n * List of CCS account names to rotate through on `is_error: true` responses.\n * Each account maps to `CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>`.\n * On `is_error`, the next account in the list is tried automatically.\n * Non-is_error failures (timeouts, non-zero exit, etc.) are thrown immediately.\n * If all accounts return `is_error`, throws with the last error message.\n */\n ccsAccounts?: string[];\n /**\n * Path to an MCP config JSON file.\n * When provided, adds `--mcp-config <path>` to the spawn args so the\n * Claude CLI can load MCP servers (filesystem, supabase, etc.).\n * The caller is responsible for writing the file before calling\n * `runClaudeCode` and deleting it afterwards.\n * If the file does not exist or is not accessible, the Claude CLI\n * will exit with a non-zero code and the promise will reject.\n */\n mcpConfigPath?: string;\n /**\n * Absolute path to the repository directory Claude should work in.\n * Required — without it Claude would inherit the worker CWD, exposing\n * worker source files, env vars, and credentials to the model.\n * Adds `--add-dir <path>` and sets `cwd: addDir` on the spawned process\n * so relative paths (e.g. Read(\"src/foo.ts\")) resolve inside the repo.\n * Throws if empty. If the directory does not exist, the Claude CLI will\n * exit with a non-zero code and the promise will reject.\n */\n addDir: string;\n}\n\n/**\n * The JSON envelope that `claude --output-format json` wraps every response in.\n * Only the fields we care about are typed; the rest are captured in the index signature.\n */\nexport interface ClaudeEnvelope {\n type: string;\n subtype: string;\n /** The actual text response from the model (plain text / raw JSON string) */\n result: string;\n is_error: boolean;\n /**\n * Present when `--json-schema` flag is used.\n * Contains the structured output object — use this instead of `result`.\n */\n structured_output?: unknown;\n [key: string]: unknown;\n}\n\nexport interface ClaudeCodeResult<T = unknown> {\n /** The actual text content of claude's response (extracted from envelope.result) */\n text: string;\n /**\n * Parsed value. Typed as `T` when a schema is provided and validation passes.\n * Without schema: `unknown` (will be `null` when the response is not valid JSON).\n */\n parsed: T;\n /** The full response envelope from `claude --output-format json` */\n envelope: ClaudeEnvelope;\n /** Number of auto-fix retries used. 0 means the first response was valid. */\n fixAttempts: number;\n}\n\n/**\n * Thrown when schema validation fails after all auto-fix attempts are exhausted.\n */\nexport class SchemaValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SchemaValidationError\";\n }\n}\n\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB\n\n/**\n * Kills a process gracefully: SIGTERM first, SIGKILL after 5 seconds if still alive.\n */\nfunction killGracefully(proc: ReturnType<typeof spawn>): void {\n proc.kill(\"SIGTERM\");\n setTimeout(() => {\n proc.kill(\"SIGKILL\");\n }, 5000).unref();\n}\n\n/** Internal raw result from a single spawn cycle (before schema validation). */\ninterface RawResult {\n text: string;\n parsed: unknown | null;\n envelope: ClaudeEnvelope;\n}\n\n/**\n * Spawns the binary once and collects the JSON envelope result.\n * All validation/retry logic lives in `runClaudeCode`.\n */\nfunction spawnOnce(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string,\n tools?: string[],\n allowedTools?: string[]\n): Promise<RawResult> {\n // Use --print (not -p) so that ccs passes args through to claude without\n // triggering its delegation system. Prompt is the final positional argument.\n const toolsList = tools?.length ? tools.join(\",\") : \"Read,Grep,Glob\";\n const args = [\"--print\", \"--output-format\", \"json\", \"--model\", model, \"--tools\", toolsList];\n if (allowedTools?.length) args.push(\"--allowed-tools\", ...allowedTools);\n if (mcpConfigPath) args.push(\"--mcp-config\", mcpConfigPath);\n args.push(\"--add-dir\", addDir);\n if (jsonSchemaStr) {\n args.push(\"--json-schema\", jsonSchemaStr);\n }\n // Prompt is delivered via stdin — avoids OS ARG_MAX limits (~2MB on Linux)\n\n return new Promise((resolve, reject) => {\n const proc = spawn(binary, args, { env, cwd: addDir });\n\n if (!proc.stdout || !proc.stderr || !proc.stdin) {\n reject(new Error(`Failed to spawn ${binary}: stdio streams not available`));\n return;\n }\n\n // Suppress all stdin errors — EPIPE is expected when the process exits before reading\n // all of stdin; other stdin errors are extremely rare and non-fatal (process already spawned).\n proc.stdin.on(\"error\", () => {});\n // If the internal buffer is full (write returns false), wait for drain before closing.\n // This handles extremely large prompts where the OS pipe buffer fills up.\n if (proc.stdin.write(prompt, \"utf8\")) {\n proc.stdin.end();\n } else {\n proc.stdin.once(\"drain\", () => proc.stdin!.end());\n }\n\n // settled prevents double-resolve/reject between timeout and close handlers\n let settled = false;\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalStdoutSize = 0;\n\n const timer = setTimeout(() => {\n settled = true;\n killGracefully(proc);\n reject(new Error(`${binary} process timed out after ${timeout}ms`));\n }, timeout);\n\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n totalStdoutSize += chunk.length;\n if (totalStdoutSize > maxOutputBytes) {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n killGracefully(proc);\n reject(new Error(`${binary} output exceeded ${maxOutputBytes} bytes`));\n return;\n }\n stdoutChunks.push(chunk);\n });\n\n proc.stderr.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk);\n });\n\n proc.on(\"close\", (code) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n\n const stdout = Buffer.concat(stdoutChunks).toString();\n const stderr = Buffer.concat(stderrChunks).toString();\n\n if (code !== 0) {\n reject(\n new Error(\n `${binary} exited with code ${code}${stderr ? `\\nstderr: ${stderr}` : \"\"}`\n )\n );\n return;\n }\n\n let envelope: ClaudeEnvelope;\n try {\n envelope = JSON.parse(stdout.trim()) as ClaudeEnvelope;\n } catch {\n reject(new Error(`${binary} output is not valid JSON: ${stdout.trim().slice(0, 200)}`));\n return;\n }\n\n if (envelope.is_error) {\n reject(new IsErrorResponse(envelope.result));\n return;\n }\n\n // --json-schema mode: result comes in structured_output, not result field\n if (envelope.structured_output !== undefined) {\n const text = JSON.stringify(envelope.structured_output);\n resolve({ text, parsed: envelope.structured_output, envelope });\n return;\n }\n\n if (typeof envelope.result !== \"string\") {\n reject(new Error(\"Invalid envelope: result field is not a string\"));\n return;\n }\n\n const text = envelope.result;\n\n let parsed: unknown | null = null;\n try {\n parsed = JSON.parse(text);\n } catch {\n // text is not JSON — that's fine\n }\n\n resolve({ text, parsed, envelope });\n });\n\n proc.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Failed to spawn ${binary}: ${err.message}`));\n });\n });\n}\n\n/**\n * Thrown internally by spawnOnce when the Claude envelope contains is_error=true.\n * Caught by spawnWithAccounts to distinguish account-level errors from other failures.\n */\nclass IsErrorResponse extends Error {\n constructor(public readonly result: string) {\n super(`returned is_error=true: ${result}`);\n this.name = \"IsErrorResponse\";\n }\n}\n\n/**\n * Calls spawnOnce with optional CCS account rotation.\n * When `accounts` is provided, tries each account in order by setting\n * CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>. Rotates only on is_error=true;\n * any other error (timeout, non-zero exit, ENOENT) is thrown immediately.\n * If all accounts return is_error, throws with the last error message.\n *\n * Implemented as a regular (non-async) function so the no-rotation path\n * returns spawnOnce's promise directly without adding an extra microtask tick.\n */\nfunction spawnWithAccounts(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n accounts: string[] | undefined,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string,\n tools?: string[],\n allowedTools?: string[]\n): Promise<RawResult> {\n // Fast path: return promise directly — no extra microtask overhead\n if (!accounts || accounts.length === 0) {\n return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);\n }\n\n // Rotation path: async IIFE to iterate through accounts\n return (async () => {\n let lastIsError: IsErrorResponse | undefined;\n for (const account of accounts) {\n const accountEnv: NodeJS.ProcessEnv = {\n ...env,\n CLAUDE_CONFIG_DIR: `${homedir()}/.ccs/instances/${account}`,\n };\n try {\n return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);\n } catch (err) {\n if (err instanceof IsErrorResponse) {\n lastIsError = err;\n continue;\n }\n throw err;\n }\n }\n throw new Error(lastIsError!.result);\n })();\n}\n\n/**\n * Builds the auto-fix prompt sent to the fixModel when schema validation fails.\n * Includes structured ZodError issues (when available) and the original invalid\n * response so the model can self-correct.\n */\nfunction buildFixPrompt(invalidText: string, error: unknown): string {\n let errorMessage: string;\n if (error instanceof z.ZodError) {\n errorMessage = error.issues\n .map((i) => `Path: ${i.path.length ? i.path.join(\".\") : \"(root)\"}, ${i.message}`)\n .join(\"\\n\");\n } else if (error instanceof Error) {\n errorMessage = error.message;\n } else {\n errorMessage = String(error);\n }\n return (\n `The following JSON failed schema validation.\\n\\n` +\n `Errors:\\n${errorMessage}\\n\\n` +\n `Invalid response:\\n${invalidText}\\n\\n` +\n `Output ONLY corrected JSON. No explanation, no markdown.`\n );\n}\n\n/**\n * Runs the configured binary (default: ccs) with --print flag as a subprocess.\n * Command: `<binary> --print --output-format json --model <model> [--json-schema <schema>]`\n * The prompt is written to the process stdin to avoid OS ARG_MAX limits (~2MB on Linux).\n *\n * When a `schema` is provided (Zod v4+):\n * - Its JSON Schema is passed via `--json-schema` CLI flag (hard enforcement).\n * - Claude returns structured_output in the envelope — guaranteed JSON.\n * - The response is validated; on failure an auto-fix retry is sent to `fixModel`.\n * - `result.fixAttempts` indicates how many retries were used (0 = first response valid).\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n *\n * When `jsonSchema` is provided:\n * - Passed via `--json-schema` CLI flag (hard enforcement, no validation or retry).\n * - `result.parsed` is `unknown`.\n *\n * Without schema: `result.parsed` is `unknown` (may be `null` for plain-text responses).\n *\n * Designed to be used as a plain async function inside a trigger.dev task `run()`.\n */\nexport function runClaudeCode(input: ClaudeCodeInput): Promise<ClaudeCodeResult<unknown>>;\nexport function runClaudeCode<T>(\n input: ClaudeCodeInput<T> & { schema: z.ZodType<T> }\n): Promise<ClaudeCodeResult<T>>;\nexport async function runClaudeCode<T = unknown>(\n input: ClaudeCodeInput<T>\n): Promise<ClaudeCodeResult<T | null>> {\n const {\n prompt,\n model = \"claude-sonnet-4-6\",\n timeout = 300_000,\n maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,\n env = process.env,\n binary = \"ccs\",\n schema,\n fixModel = \"claude-haiku-4-5-20251001\",\n maxFixes = 1,\n jsonSchema,\n ccsAccounts,\n mcpConfigPath,\n addDir,\n tools,\n allowedTools,\n } = input;\n\n if (!prompt || prompt.trim().length === 0) {\n throw new Error(\"prompt cannot be empty\");\n }\n\n if (!addDir || addDir.trim().length === 0) {\n throw new Error(\"addDir is required: omitting it exposes the worker CWD to the model\");\n }\n\n if (!binary || binary.trim().length === 0) {\n throw new Error(\"binary cannot be empty\");\n }\n\n if (jsonSchema && schema) {\n throw new Error(\"cannot use jsonSchema with schema\");\n }\n\n // Build jsonSchemaStr from Zod schema or raw jsonSchema — passed as CLI flag, not prompt text\n let jsonSchemaStr: string | undefined;\n if (schema) {\n try {\n jsonSchemaStr = JSON.stringify(z.toJSONSchema(schema));\n } catch (err) {\n throw new Error(\n `Failed to convert Zod schema to JSON Schema (requires Zod v4+): ${String(err)}`\n );\n }\n } else if (jsonSchema) {\n try {\n jsonSchemaStr = JSON.stringify(jsonSchema);\n } catch (err) {\n throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);\n }\n }\n\n const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);\n\n // No schema — return raw result. parsed may be null for plain-text responses.\n // Cast is safe: overload 1 constrains T = unknown, so T | null = unknown.\n if (!schema) {\n return { text: raw.text, parsed: raw.parsed as T | null, envelope: raw.envelope, fixAttempts: 0 };\n }\n\n // Try to validate the initial response.\n // Note: if raw.parsed is null (plain-text response), schema.parse(null) throws ZodError,\n // which correctly triggers the auto-fix flow.\n let lastText = raw.text;\n let lastError: unknown;\n try {\n const parsed = schema.parse(raw.parsed);\n return { ...raw, parsed, fixAttempts: 0 };\n } catch (err) {\n lastError = err;\n }\n\n // Auto-fix loop: send errors + original output back to fixModel for self-correction.\n // jsonSchemaStr is passed so the fix spawn also uses --json-schema enforcement.\n for (let attempt = 1; attempt <= maxFixes; attempt++) {\n const fixPromptStr = buildFixPrompt(lastText, lastError);\n const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);\n try {\n const parsed = schema.parse(fixRaw.parsed);\n return { ...fixRaw, parsed, fixAttempts: attempt };\n } catch (err) {\n lastText = fixRaw.text;\n lastError = err;\n }\n }\n\n const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);\n throw new SchemaValidationError(\n `SchemaValidationError: validation failed after ${maxFixes} fix attempt(s): ${errorMessage}`\n );\n}\n"],"mappings":"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","names":["text"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/runners/claude-code.ts"],"sourcesContent":["export { runClaudeCode, SchemaValidationError } from \"./runners/claude-code\";\nexport type { ClaudeCodeInput, ClaudeCodeResult, ClaudeEnvelope } from \"./runners/claude-code\";\n","import { spawn } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { z } from \"zod\";\n\nexport interface ClaudeCodeInput<T = unknown> {\n prompt: string;\n model?: string;\n /** Timeout in milliseconds. Default: 300_000 (5 min) */\n timeout?: number;\n /**\n * Maximum allowed stdout size in bytes. Default: 10_485_760 (10 MB).\n * If claude output exceeds this limit the process is killed and the promise rejects.\n */\n maxOutputBytes?: number;\n /**\n * Environment variables passed to the subprocess.\n * Defaults to the full process.env so that the binary can access its auth\n * tokens and config (ANTHROPIC_API_KEY, HOME, etc.).\n * Override if you need to restrict or augment the environment.\n */\n env?: NodeJS.ProcessEnv;\n /**\n * Binary to execute. Default: \"ccs\" (Claude Code Profile & Model Switcher).\n * Use \"claude\" to invoke the Claude CLI directly without profile switching.\n * Using `--print` (not `-p`) avoids the ccs delegation system while\n * preserving the standard `--output-format json` envelope output.\n */\n binary?: string;\n /**\n * Optional Zod schema (requires Zod v4+). When provided:\n * - Its JSON Schema representation is passed via `--json-schema` CLI flag\n * (CLI-level enforcement — Claude physically cannot return non-JSON).\n * - The response is validated with `schema.parse()`.\n * - On failure, an auto-fix retry is sent to `fixModel`.\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n * - Mutually exclusive with `jsonSchema`.\n */\n schema?: z.ZodType<T>;\n /**\n * Model used for auto-fix retries. Default: \"claude-haiku-4-5-20251001\"\n * (cheap and fast, good for JSON correction tasks).\n */\n fixModel?: string;\n /**\n * Maximum number of auto-fix attempts on schema validation failure. Default: 1.\n */\n maxFixes?: number;\n /**\n * Raw JSON Schema for CLI-level structured output enforcement.\n * Adds `--json-schema <schema>` to spawn args.\n * Claude returns structured_output in the envelope — guaranteed JSON.\n * No runtime validation or auto-fix. `result.parsed` is `unknown`.\n * Mutually exclusive with `schema`.\n */\n jsonSchema?: Record<string, unknown>;\n /**\n * Override the default tool list passed via `--tools`.\n * Default: `[\"Read\", \"Grep\", \"Glob\"]` (read-only by design).\n * Pass `[\"Bash\", \"Read\", \"Grep\", \"Glob\"]` to also allow Bash execution.\n */\n tools?: string[];\n /**\n * Restrict Bash commands via `--allowed-tools` flag.\n * Each entry is a pattern like `\"Bash(git log*)\"`.\n * Only meaningful when `tools` includes `\"Bash\"`.\n * When omitted, no `--allowed-tools` flag is added.\n */\n allowedTools?: string[];\n /**\n * List of CCS account names to rotate through on `is_error: true` responses.\n * Each account maps to `CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>`.\n * On `is_error`, the next account in the list is tried automatically.\n * Non-is_error failures (timeouts, non-zero exit, etc.) are thrown immediately.\n * If all accounts return `is_error`, throws with the last error message.\n */\n ccsAccounts?: string[];\n /**\n * Path to an MCP config JSON file.\n * When provided, adds `--mcp-config <path>` to the spawn args so the\n * Claude CLI can load MCP servers (filesystem, supabase, etc.).\n * The caller is responsible for writing the file before calling\n * `runClaudeCode` and deleting it afterwards.\n * If the file does not exist or is not accessible, the Claude CLI\n * will exit with a non-zero code and the promise will reject.\n */\n mcpConfigPath?: string;\n /**\n * Replaces the default system prompt entirely.\n * Adds `--system-prompt <text>` to spawn args.\n * CLAUDE.md is ignored when this is set.\n */\n systemPrompt: string;\n /**\n * Absolute path to the repository directory Claude should work in.\n * Required — without it Claude would inherit the worker CWD, exposing\n * worker source files, env vars, and credentials to the model.\n * Adds `--add-dir <path>` and sets `cwd: addDir` on the spawned process\n * so relative paths (e.g. Read(\"src/foo.ts\")) resolve inside the repo.\n * Throws if empty. If the directory does not exist, the Claude CLI will\n * exit with a non-zero code and the promise will reject.\n */\n addDir: string;\n}\n\n/**\n * The JSON envelope that `claude --output-format json` wraps every response in.\n * Only the fields we care about are typed; the rest are captured in the index signature.\n */\nexport interface ClaudeEnvelope {\n type: string;\n subtype: string;\n /** The actual text response from the model (plain text / raw JSON string) */\n result: string;\n is_error: boolean;\n /**\n * Present when `--json-schema` flag is used.\n * Contains the structured output object — use this instead of `result`.\n */\n structured_output?: unknown;\n [key: string]: unknown;\n}\n\nexport interface ClaudeCodeResult<T = unknown> {\n /** The actual text content of claude's response (extracted from envelope.result) */\n text: string;\n /**\n * Parsed value. Typed as `T` when a schema is provided and validation passes.\n * Without schema: `unknown` (will be `null` when the response is not valid JSON).\n */\n parsed: T;\n /** The full response envelope from `claude --output-format json` */\n envelope: ClaudeEnvelope;\n /** Number of auto-fix retries used. 0 means the first response was valid. */\n fixAttempts: number;\n}\n\n/**\n * Thrown when schema validation fails after all auto-fix attempts are exhausted.\n */\nexport class SchemaValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SchemaValidationError\";\n }\n}\n\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB\n\n/**\n * Kills a process gracefully: SIGTERM first, SIGKILL after 5 seconds if still alive.\n */\nfunction killGracefully(proc: ReturnType<typeof spawn>): void {\n proc.kill(\"SIGTERM\");\n setTimeout(() => {\n proc.kill(\"SIGKILL\");\n }, 5000).unref();\n}\n\n/** Internal raw result from a single spawn cycle (before schema validation). */\ninterface RawResult {\n text: string;\n parsed: unknown | null;\n envelope: ClaudeEnvelope;\n}\n\n/**\n * Spawns the binary once and collects the JSON envelope result.\n * All validation/retry logic lives in `runClaudeCode`.\n */\nfunction spawnOnce(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string,\n tools?: string[],\n allowedTools?: string[],\n systemPrompt?: string\n): Promise<RawResult> {\n // Use --print (not -p) so that ccs passes args through to claude without\n // triggering its delegation system. Prompt is the final positional argument.\n const toolsList = tools?.length ? tools.join(\",\") : \"Read,Grep,Glob\";\n const args = [\"--print\", \"--bare\", \"--output-format\", \"json\", \"--model\", model, \"--tools\", toolsList];\n if (allowedTools?.length) args.push(\"--allowed-tools\", ...allowedTools);\n if (mcpConfigPath) args.push(\"--mcp-config\", mcpConfigPath);\n args.push(\"--add-dir\", addDir);\n if (systemPrompt) args.push(\"--system-prompt\", systemPrompt);\n if (jsonSchemaStr) {\n args.push(\"--json-schema\", jsonSchemaStr);\n }\n // Prompt is delivered via stdin — avoids OS ARG_MAX limits (~2MB on Linux)\n\n return new Promise((resolve, reject) => {\n const proc = spawn(binary, args, { env, cwd: addDir });\n\n if (!proc.stdout || !proc.stderr || !proc.stdin) {\n reject(new Error(`Failed to spawn ${binary}: stdio streams not available`));\n return;\n }\n\n // Suppress all stdin errors — EPIPE is expected when the process exits before reading\n // all of stdin; other stdin errors are extremely rare and non-fatal (process already spawned).\n proc.stdin.on(\"error\", () => {});\n // If the internal buffer is full (write returns false), wait for drain before closing.\n // This handles extremely large prompts where the OS pipe buffer fills up.\n if (proc.stdin.write(prompt, \"utf8\")) {\n proc.stdin.end();\n } else {\n proc.stdin.once(\"drain\", () => proc.stdin!.end());\n }\n\n // settled prevents double-resolve/reject between timeout and close handlers\n let settled = false;\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalStdoutSize = 0;\n\n const timer = setTimeout(() => {\n settled = true;\n killGracefully(proc);\n reject(new Error(`${binary} process timed out after ${timeout}ms`));\n }, timeout);\n\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n totalStdoutSize += chunk.length;\n if (totalStdoutSize > maxOutputBytes) {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n killGracefully(proc);\n reject(new Error(`${binary} output exceeded ${maxOutputBytes} bytes`));\n return;\n }\n stdoutChunks.push(chunk);\n });\n\n proc.stderr.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk);\n });\n\n proc.on(\"close\", (code) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n\n const stdout = Buffer.concat(stdoutChunks).toString();\n const stderr = Buffer.concat(stderrChunks).toString();\n\n if (code !== 0) {\n reject(\n new Error(\n `${binary} exited with code ${code}${stderr ? `\\nstderr: ${stderr}` : \"\"}`\n )\n );\n return;\n }\n\n let envelope: ClaudeEnvelope;\n try {\n envelope = JSON.parse(stdout.trim()) as ClaudeEnvelope;\n } catch {\n reject(new Error(`${binary} output is not valid JSON: ${stdout.trim().slice(0, 200)}`));\n return;\n }\n\n if (envelope.is_error) {\n reject(new IsErrorResponse(envelope.result));\n return;\n }\n\n // --json-schema mode: result comes in structured_output, not result field\n if (envelope.structured_output !== undefined) {\n const text = JSON.stringify(envelope.structured_output);\n resolve({ text, parsed: envelope.structured_output, envelope });\n return;\n }\n\n if (typeof envelope.result !== \"string\") {\n reject(new Error(\"Invalid envelope: result field is not a string\"));\n return;\n }\n\n const text = envelope.result;\n\n let parsed: unknown | null = null;\n try {\n parsed = JSON.parse(text);\n } catch {\n // text is not JSON — that's fine\n }\n\n resolve({ text, parsed, envelope });\n });\n\n proc.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Failed to spawn ${binary}: ${err.message}`));\n });\n });\n}\n\n/**\n * Thrown internally by spawnOnce when the Claude envelope contains is_error=true.\n * Caught by spawnWithAccounts to distinguish account-level errors from other failures.\n */\nclass IsErrorResponse extends Error {\n constructor(public readonly result: string) {\n super(`returned is_error=true: ${result}`);\n this.name = \"IsErrorResponse\";\n }\n}\n\n/**\n * Calls spawnOnce with optional CCS account rotation.\n * When `accounts` is provided, tries each account in order by setting\n * CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>. Rotates only on is_error=true;\n * any other error (timeout, non-zero exit, ENOENT) is thrown immediately.\n * If all accounts return is_error, throws with the last error message.\n *\n * Implemented as a regular (non-async) function so the no-rotation path\n * returns spawnOnce's promise directly without adding an extra microtask tick.\n */\nfunction spawnWithAccounts(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n accounts: string[] | undefined,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string,\n tools?: string[],\n allowedTools?: string[],\n systemPrompt?: string\n): Promise<RawResult> {\n // Fast path: return promise directly — no extra microtask overhead\n if (!accounts || accounts.length === 0) {\n return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);\n }\n\n // Rotation path: async IIFE to iterate through accounts\n return (async () => {\n let lastIsError: IsErrorResponse | undefined;\n for (const account of accounts) {\n const accountEnv: NodeJS.ProcessEnv = {\n ...env,\n CLAUDE_CONFIG_DIR: `${homedir()}/.ccs/instances/${account}`,\n };\n try {\n return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);\n } catch (err) {\n if (err instanceof IsErrorResponse) {\n lastIsError = err;\n continue;\n }\n throw err;\n }\n }\n throw new Error(lastIsError!.result);\n })();\n}\n\n/**\n * Builds the auto-fix prompt sent to the fixModel when schema validation fails.\n * Includes structured ZodError issues (when available) and the original invalid\n * response so the model can self-correct.\n */\nfunction buildFixPrompt(invalidText: string, error: unknown): string {\n let errorMessage: string;\n if (error instanceof z.ZodError) {\n errorMessage = error.issues\n .map((i) => `Path: ${i.path.length ? i.path.join(\".\") : \"(root)\"}, ${i.message}`)\n .join(\"\\n\");\n } else if (error instanceof Error) {\n errorMessage = error.message;\n } else {\n errorMessage = String(error);\n }\n return (\n `The following JSON failed schema validation.\\n\\n` +\n `Errors:\\n${errorMessage}\\n\\n` +\n `Invalid response:\\n${invalidText}\\n\\n` +\n `Output ONLY corrected JSON. No explanation, no markdown.`\n );\n}\n\n/**\n * Runs the configured binary (default: ccs) with --print flag as a subprocess.\n * Command: `<binary> --print --bare --output-format json --model <model> [--json-schema <schema>]`\n * The prompt is written to the process stdin to avoid OS ARG_MAX limits (~2MB on Linux).\n * --bare suppresses global CLAUDE.md auto-discovery, hooks, LSP, auto-memory and other\n * side effects — ensuring the agent runs only with the provided systemPrompt.\n *\n * When a `schema` is provided (Zod v4+):\n * - Its JSON Schema is passed via `--json-schema` CLI flag (hard enforcement).\n * - Claude returns structured_output in the envelope — guaranteed JSON.\n * - The response is validated; on failure an auto-fix retry is sent to `fixModel`.\n * - `result.fixAttempts` indicates how many retries were used (0 = first response valid).\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n *\n * When `jsonSchema` is provided:\n * - Passed via `--json-schema` CLI flag (hard enforcement, no validation or retry).\n * - `result.parsed` is `unknown`.\n *\n * Without schema: `result.parsed` is `unknown` (may be `null` for plain-text responses).\n *\n * Designed to be used as a plain async function inside a trigger.dev task `run()`.\n */\nexport function runClaudeCode(input: ClaudeCodeInput): Promise<ClaudeCodeResult<unknown>>;\nexport function runClaudeCode<T>(\n input: ClaudeCodeInput<T> & { schema: z.ZodType<T> }\n): Promise<ClaudeCodeResult<T>>;\nexport async function runClaudeCode<T = unknown>(\n input: ClaudeCodeInput<T>\n): Promise<ClaudeCodeResult<T | null>> {\n const {\n prompt,\n model = \"claude-sonnet-4-6\",\n timeout = 300_000,\n maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,\n env = process.env,\n binary = \"ccs\",\n schema,\n fixModel = \"claude-haiku-4-5-20251001\",\n maxFixes = 1,\n jsonSchema,\n ccsAccounts,\n mcpConfigPath,\n addDir,\n tools,\n allowedTools,\n systemPrompt,\n } = input;\n\n if (!prompt || prompt.trim().length === 0) {\n throw new Error(\"prompt cannot be empty\");\n }\n\n if (!addDir || addDir.trim().length === 0) {\n throw new Error(\"addDir is required: omitting it exposes the worker CWD to the model\");\n }\n\n if (!binary || binary.trim().length === 0) {\n throw new Error(\"binary cannot be empty\");\n }\n\n if (jsonSchema && schema) {\n throw new Error(\"cannot use jsonSchema with schema\");\n }\n\n if (!systemPrompt || systemPrompt.trim().length === 0) {\n throw new Error(\"systemPrompt cannot be empty\");\n }\n\n if (allowedTools?.length && (!tools || !tools.includes(\"Bash\"))) {\n throw new Error(\"allowedTools requires 'Bash' in tools array\");\n }\n\n // Build jsonSchemaStr from Zod schema or raw jsonSchema — passed as CLI flag, not prompt text\n let jsonSchemaStr: string | undefined;\n if (schema) {\n try {\n jsonSchemaStr = JSON.stringify(z.toJSONSchema(schema));\n } catch (err) {\n throw new Error(\n `Failed to convert Zod schema to JSON Schema (requires Zod v4+): ${String(err)}`\n );\n }\n } else if (jsonSchema) {\n try {\n jsonSchemaStr = JSON.stringify(jsonSchema);\n } catch (err) {\n throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);\n }\n }\n\n const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);\n\n // No schema — return raw result. parsed may be null for plain-text responses.\n // Cast is safe: overload 1 constrains T = unknown, so T | null = unknown.\n if (!schema) {\n return { text: raw.text, parsed: raw.parsed as T | null, envelope: raw.envelope, fixAttempts: 0 };\n }\n\n // Try to validate the initial response.\n // Note: if raw.parsed is null (plain-text response), schema.parse(null) throws ZodError,\n // which correctly triggers the auto-fix flow.\n let lastText = raw.text;\n let lastError: unknown;\n try {\n const parsed = schema.parse(raw.parsed);\n return { ...raw, parsed, fixAttempts: 0 };\n } catch (err) {\n lastError = err;\n }\n\n // Auto-fix loop: send errors + original output back to fixModel for self-correction.\n // jsonSchemaStr is passed so the fix spawn also uses --json-schema enforcement.\n for (let attempt = 1; attempt <= maxFixes; attempt++) {\n const fixPromptStr = buildFixPrompt(lastText, lastError);\n const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);\n try {\n const parsed = schema.parse(fixRaw.parsed);\n return { ...fixRaw, parsed, fixAttempts: attempt };\n } catch (err) {\n lastText = fixRaw.text;\n lastError = err;\n }\n }\n\n const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);\n throw new SchemaValidationError(\n `SchemaValidationError: validation failed after ${maxFixes} fix attempt(s): ${errorMessage}`\n );\n}\n"],"mappings":"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","names":["text"]}
|
package/dist/index.mjs
CHANGED
|
@@ -15,12 +15,13 @@ function killGracefully(proc) {
|
|
|
15
15
|
proc.kill("SIGKILL");
|
|
16
16
|
}, 5e3).unref();
|
|
17
17
|
}
|
|
18
|
-
function spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools) {
|
|
18
|
+
function spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt) {
|
|
19
19
|
const toolsList = tools?.length ? tools.join(",") : "Read,Grep,Glob";
|
|
20
|
-
const args = ["--print", "--output-format", "json", "--model", model, "--tools", toolsList];
|
|
20
|
+
const args = ["--print", "--bare", "--output-format", "json", "--model", model, "--tools", toolsList];
|
|
21
21
|
if (allowedTools?.length) args.push("--allowed-tools", ...allowedTools);
|
|
22
22
|
if (mcpConfigPath) args.push("--mcp-config", mcpConfigPath);
|
|
23
23
|
args.push("--add-dir", addDir);
|
|
24
|
+
if (systemPrompt) args.push("--system-prompt", systemPrompt);
|
|
24
25
|
if (jsonSchemaStr) {
|
|
25
26
|
args.push("--json-schema", jsonSchemaStr);
|
|
26
27
|
}
|
|
@@ -119,9 +120,9 @@ var IsErrorResponse = class extends Error {
|
|
|
119
120
|
this.name = "IsErrorResponse";
|
|
120
121
|
}
|
|
121
122
|
};
|
|
122
|
-
function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, accounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools) {
|
|
123
|
+
function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, accounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt) {
|
|
123
124
|
if (!accounts || accounts.length === 0) {
|
|
124
|
-
return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);
|
|
125
|
+
return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);
|
|
125
126
|
}
|
|
126
127
|
return (async () => {
|
|
127
128
|
let lastIsError;
|
|
@@ -131,7 +132,7 @@ function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env,
|
|
|
131
132
|
CLAUDE_CONFIG_DIR: `${homedir()}/.ccs/instances/${account}`
|
|
132
133
|
};
|
|
133
134
|
try {
|
|
134
|
-
return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);
|
|
135
|
+
return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);
|
|
135
136
|
} catch (err) {
|
|
136
137
|
if (err instanceof IsErrorResponse) {
|
|
137
138
|
lastIsError = err;
|
|
@@ -178,7 +179,8 @@ async function runClaudeCode(input) {
|
|
|
178
179
|
mcpConfigPath,
|
|
179
180
|
addDir,
|
|
180
181
|
tools,
|
|
181
|
-
allowedTools
|
|
182
|
+
allowedTools,
|
|
183
|
+
systemPrompt
|
|
182
184
|
} = input;
|
|
183
185
|
if (!prompt || prompt.trim().length === 0) {
|
|
184
186
|
throw new Error("prompt cannot be empty");
|
|
@@ -192,6 +194,12 @@ async function runClaudeCode(input) {
|
|
|
192
194
|
if (jsonSchema && schema) {
|
|
193
195
|
throw new Error("cannot use jsonSchema with schema");
|
|
194
196
|
}
|
|
197
|
+
if (!systemPrompt || systemPrompt.trim().length === 0) {
|
|
198
|
+
throw new Error("systemPrompt cannot be empty");
|
|
199
|
+
}
|
|
200
|
+
if (allowedTools?.length && (!tools || !tools.includes("Bash"))) {
|
|
201
|
+
throw new Error("allowedTools requires 'Bash' in tools array");
|
|
202
|
+
}
|
|
195
203
|
let jsonSchemaStr;
|
|
196
204
|
if (schema) {
|
|
197
205
|
try {
|
|
@@ -208,7 +216,7 @@ async function runClaudeCode(input) {
|
|
|
208
216
|
throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);
|
|
209
217
|
}
|
|
210
218
|
}
|
|
211
|
-
const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);
|
|
219
|
+
const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);
|
|
212
220
|
if (!schema) {
|
|
213
221
|
return { text: raw.text, parsed: raw.parsed, envelope: raw.envelope, fixAttempts: 0 };
|
|
214
222
|
}
|
|
@@ -222,7 +230,7 @@ async function runClaudeCode(input) {
|
|
|
222
230
|
}
|
|
223
231
|
for (let attempt = 1; attempt <= maxFixes; attempt++) {
|
|
224
232
|
const fixPromptStr = buildFixPrompt(lastText, lastError);
|
|
225
|
-
const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);
|
|
233
|
+
const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);
|
|
226
234
|
try {
|
|
227
235
|
const parsed = schema.parse(fixRaw.parsed);
|
|
228
236
|
return { ...fixRaw, parsed, fixAttempts: attempt };
|
|
@@ -237,6 +245,7 @@ async function runClaudeCode(input) {
|
|
|
237
245
|
);
|
|
238
246
|
}
|
|
239
247
|
export {
|
|
248
|
+
SchemaValidationError,
|
|
240
249
|
runClaudeCode
|
|
241
250
|
};
|
|
242
251
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runners/claude-code.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { z } from \"zod\";\n\nexport interface ClaudeCodeInput<T = unknown> {\n prompt: string;\n model?: string;\n /** Timeout in milliseconds. Default: 300_000 (5 min) */\n timeout?: number;\n /**\n * Maximum allowed stdout size in bytes. Default: 10_485_760 (10 MB).\n * If claude output exceeds this limit the process is killed and the promise rejects.\n */\n maxOutputBytes?: number;\n /**\n * Environment variables passed to the subprocess.\n * Defaults to the full process.env so that the binary can access its auth\n * tokens and config (ANTHROPIC_API_KEY, HOME, etc.).\n * Override if you need to restrict or augment the environment.\n */\n env?: NodeJS.ProcessEnv;\n /**\n * Binary to execute. Default: \"ccs\" (Claude Code Profile & Model Switcher).\n * Use \"claude\" to invoke the Claude CLI directly without profile switching.\n * Using `--print` (not `-p`) avoids the ccs delegation system while\n * preserving the standard `--output-format json` envelope output.\n */\n binary?: string;\n /**\n * Optional Zod schema (requires Zod v4+). When provided:\n * - Its JSON Schema representation is passed via `--json-schema` CLI flag\n * (CLI-level enforcement — Claude physically cannot return non-JSON).\n * - The response is validated with `schema.parse()`.\n * - On failure, an auto-fix retry is sent to `fixModel`.\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n * - Mutually exclusive with `jsonSchema`.\n */\n schema?: z.ZodType<T>;\n /**\n * Model used for auto-fix retries. Default: \"claude-haiku-4-5-20251001\"\n * (cheap and fast, good for JSON correction tasks).\n */\n fixModel?: string;\n /**\n * Maximum number of auto-fix attempts on schema validation failure. Default: 1.\n */\n maxFixes?: number;\n /**\n * Raw JSON Schema for CLI-level structured output enforcement.\n * Adds `--json-schema <schema>` to spawn args.\n * Claude returns structured_output in the envelope — guaranteed JSON.\n * No runtime validation or auto-fix. `result.parsed` is `unknown`.\n * Mutually exclusive with `schema`.\n */\n jsonSchema?: Record<string, unknown>;\n /**\n * Override the default tool list passed via `--tools`.\n * Default: `[\"Read\", \"Grep\", \"Glob\"]` (read-only by design).\n * Pass `[\"Bash\", \"Read\", \"Grep\", \"Glob\"]` to also allow Bash execution.\n */\n tools?: string[];\n /**\n * Restrict Bash commands via `--allowed-tools` flag.\n * Each entry is a pattern like `\"Bash(git log*)\"`.\n * Only meaningful when `tools` includes `\"Bash\"`.\n * When omitted, no `--allowed-tools` flag is added.\n */\n allowedTools?: string[];\n /**\n * List of CCS account names to rotate through on `is_error: true` responses.\n * Each account maps to `CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>`.\n * On `is_error`, the next account in the list is tried automatically.\n * Non-is_error failures (timeouts, non-zero exit, etc.) are thrown immediately.\n * If all accounts return `is_error`, throws with the last error message.\n */\n ccsAccounts?: string[];\n /**\n * Path to an MCP config JSON file.\n * When provided, adds `--mcp-config <path>` to the spawn args so the\n * Claude CLI can load MCP servers (filesystem, supabase, etc.).\n * The caller is responsible for writing the file before calling\n * `runClaudeCode` and deleting it afterwards.\n * If the file does not exist or is not accessible, the Claude CLI\n * will exit with a non-zero code and the promise will reject.\n */\n mcpConfigPath?: string;\n /**\n * Absolute path to the repository directory Claude should work in.\n * Required — without it Claude would inherit the worker CWD, exposing\n * worker source files, env vars, and credentials to the model.\n * Adds `--add-dir <path>` and sets `cwd: addDir` on the spawned process\n * so relative paths (e.g. Read(\"src/foo.ts\")) resolve inside the repo.\n * Throws if empty. If the directory does not exist, the Claude CLI will\n * exit with a non-zero code and the promise will reject.\n */\n addDir: string;\n}\n\n/**\n * The JSON envelope that `claude --output-format json` wraps every response in.\n * Only the fields we care about are typed; the rest are captured in the index signature.\n */\nexport interface ClaudeEnvelope {\n type: string;\n subtype: string;\n /** The actual text response from the model (plain text / raw JSON string) */\n result: string;\n is_error: boolean;\n /**\n * Present when `--json-schema` flag is used.\n * Contains the structured output object — use this instead of `result`.\n */\n structured_output?: unknown;\n [key: string]: unknown;\n}\n\nexport interface ClaudeCodeResult<T = unknown> {\n /** The actual text content of claude's response (extracted from envelope.result) */\n text: string;\n /**\n * Parsed value. Typed as `T` when a schema is provided and validation passes.\n * Without schema: `unknown` (will be `null` when the response is not valid JSON).\n */\n parsed: T;\n /** The full response envelope from `claude --output-format json` */\n envelope: ClaudeEnvelope;\n /** Number of auto-fix retries used. 0 means the first response was valid. */\n fixAttempts: number;\n}\n\n/**\n * Thrown when schema validation fails after all auto-fix attempts are exhausted.\n */\nexport class SchemaValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SchemaValidationError\";\n }\n}\n\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB\n\n/**\n * Kills a process gracefully: SIGTERM first, SIGKILL after 5 seconds if still alive.\n */\nfunction killGracefully(proc: ReturnType<typeof spawn>): void {\n proc.kill(\"SIGTERM\");\n setTimeout(() => {\n proc.kill(\"SIGKILL\");\n }, 5000).unref();\n}\n\n/** Internal raw result from a single spawn cycle (before schema validation). */\ninterface RawResult {\n text: string;\n parsed: unknown | null;\n envelope: ClaudeEnvelope;\n}\n\n/**\n * Spawns the binary once and collects the JSON envelope result.\n * All validation/retry logic lives in `runClaudeCode`.\n */\nfunction spawnOnce(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string,\n tools?: string[],\n allowedTools?: string[]\n): Promise<RawResult> {\n // Use --print (not -p) so that ccs passes args through to claude without\n // triggering its delegation system. Prompt is the final positional argument.\n const toolsList = tools?.length ? tools.join(\",\") : \"Read,Grep,Glob\";\n const args = [\"--print\", \"--output-format\", \"json\", \"--model\", model, \"--tools\", toolsList];\n if (allowedTools?.length) args.push(\"--allowed-tools\", ...allowedTools);\n if (mcpConfigPath) args.push(\"--mcp-config\", mcpConfigPath);\n args.push(\"--add-dir\", addDir);\n if (jsonSchemaStr) {\n args.push(\"--json-schema\", jsonSchemaStr);\n }\n // Prompt is delivered via stdin — avoids OS ARG_MAX limits (~2MB on Linux)\n\n return new Promise((resolve, reject) => {\n const proc = spawn(binary, args, { env, cwd: addDir });\n\n if (!proc.stdout || !proc.stderr || !proc.stdin) {\n reject(new Error(`Failed to spawn ${binary}: stdio streams not available`));\n return;\n }\n\n // Suppress all stdin errors — EPIPE is expected when the process exits before reading\n // all of stdin; other stdin errors are extremely rare and non-fatal (process already spawned).\n proc.stdin.on(\"error\", () => {});\n // If the internal buffer is full (write returns false), wait for drain before closing.\n // This handles extremely large prompts where the OS pipe buffer fills up.\n if (proc.stdin.write(prompt, \"utf8\")) {\n proc.stdin.end();\n } else {\n proc.stdin.once(\"drain\", () => proc.stdin!.end());\n }\n\n // settled prevents double-resolve/reject between timeout and close handlers\n let settled = false;\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalStdoutSize = 0;\n\n const timer = setTimeout(() => {\n settled = true;\n killGracefully(proc);\n reject(new Error(`${binary} process timed out after ${timeout}ms`));\n }, timeout);\n\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n totalStdoutSize += chunk.length;\n if (totalStdoutSize > maxOutputBytes) {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n killGracefully(proc);\n reject(new Error(`${binary} output exceeded ${maxOutputBytes} bytes`));\n return;\n }\n stdoutChunks.push(chunk);\n });\n\n proc.stderr.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk);\n });\n\n proc.on(\"close\", (code) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n\n const stdout = Buffer.concat(stdoutChunks).toString();\n const stderr = Buffer.concat(stderrChunks).toString();\n\n if (code !== 0) {\n reject(\n new Error(\n `${binary} exited with code ${code}${stderr ? `\\nstderr: ${stderr}` : \"\"}`\n )\n );\n return;\n }\n\n let envelope: ClaudeEnvelope;\n try {\n envelope = JSON.parse(stdout.trim()) as ClaudeEnvelope;\n } catch {\n reject(new Error(`${binary} output is not valid JSON: ${stdout.trim().slice(0, 200)}`));\n return;\n }\n\n if (envelope.is_error) {\n reject(new IsErrorResponse(envelope.result));\n return;\n }\n\n // --json-schema mode: result comes in structured_output, not result field\n if (envelope.structured_output !== undefined) {\n const text = JSON.stringify(envelope.structured_output);\n resolve({ text, parsed: envelope.structured_output, envelope });\n return;\n }\n\n if (typeof envelope.result !== \"string\") {\n reject(new Error(\"Invalid envelope: result field is not a string\"));\n return;\n }\n\n const text = envelope.result;\n\n let parsed: unknown | null = null;\n try {\n parsed = JSON.parse(text);\n } catch {\n // text is not JSON — that's fine\n }\n\n resolve({ text, parsed, envelope });\n });\n\n proc.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Failed to spawn ${binary}: ${err.message}`));\n });\n });\n}\n\n/**\n * Thrown internally by spawnOnce when the Claude envelope contains is_error=true.\n * Caught by spawnWithAccounts to distinguish account-level errors from other failures.\n */\nclass IsErrorResponse extends Error {\n constructor(public readonly result: string) {\n super(`returned is_error=true: ${result}`);\n this.name = \"IsErrorResponse\";\n }\n}\n\n/**\n * Calls spawnOnce with optional CCS account rotation.\n * When `accounts` is provided, tries each account in order by setting\n * CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>. Rotates only on is_error=true;\n * any other error (timeout, non-zero exit, ENOENT) is thrown immediately.\n * If all accounts return is_error, throws with the last error message.\n *\n * Implemented as a regular (non-async) function so the no-rotation path\n * returns spawnOnce's promise directly without adding an extra microtask tick.\n */\nfunction spawnWithAccounts(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n accounts: string[] | undefined,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string,\n tools?: string[],\n allowedTools?: string[]\n): Promise<RawResult> {\n // Fast path: return promise directly — no extra microtask overhead\n if (!accounts || accounts.length === 0) {\n return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);\n }\n\n // Rotation path: async IIFE to iterate through accounts\n return (async () => {\n let lastIsError: IsErrorResponse | undefined;\n for (const account of accounts) {\n const accountEnv: NodeJS.ProcessEnv = {\n ...env,\n CLAUDE_CONFIG_DIR: `${homedir()}/.ccs/instances/${account}`,\n };\n try {\n return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);\n } catch (err) {\n if (err instanceof IsErrorResponse) {\n lastIsError = err;\n continue;\n }\n throw err;\n }\n }\n throw new Error(lastIsError!.result);\n })();\n}\n\n/**\n * Builds the auto-fix prompt sent to the fixModel when schema validation fails.\n * Includes structured ZodError issues (when available) and the original invalid\n * response so the model can self-correct.\n */\nfunction buildFixPrompt(invalidText: string, error: unknown): string {\n let errorMessage: string;\n if (error instanceof z.ZodError) {\n errorMessage = error.issues\n .map((i) => `Path: ${i.path.length ? i.path.join(\".\") : \"(root)\"}, ${i.message}`)\n .join(\"\\n\");\n } else if (error instanceof Error) {\n errorMessage = error.message;\n } else {\n errorMessage = String(error);\n }\n return (\n `The following JSON failed schema validation.\\n\\n` +\n `Errors:\\n${errorMessage}\\n\\n` +\n `Invalid response:\\n${invalidText}\\n\\n` +\n `Output ONLY corrected JSON. No explanation, no markdown.`\n );\n}\n\n/**\n * Runs the configured binary (default: ccs) with --print flag as a subprocess.\n * Command: `<binary> --print --output-format json --model <model> [--json-schema <schema>]`\n * The prompt is written to the process stdin to avoid OS ARG_MAX limits (~2MB on Linux).\n *\n * When a `schema` is provided (Zod v4+):\n * - Its JSON Schema is passed via `--json-schema` CLI flag (hard enforcement).\n * - Claude returns structured_output in the envelope — guaranteed JSON.\n * - The response is validated; on failure an auto-fix retry is sent to `fixModel`.\n * - `result.fixAttempts` indicates how many retries were used (0 = first response valid).\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n *\n * When `jsonSchema` is provided:\n * - Passed via `--json-schema` CLI flag (hard enforcement, no validation or retry).\n * - `result.parsed` is `unknown`.\n *\n * Without schema: `result.parsed` is `unknown` (may be `null` for plain-text responses).\n *\n * Designed to be used as a plain async function inside a trigger.dev task `run()`.\n */\nexport function runClaudeCode(input: ClaudeCodeInput): Promise<ClaudeCodeResult<unknown>>;\nexport function runClaudeCode<T>(\n input: ClaudeCodeInput<T> & { schema: z.ZodType<T> }\n): Promise<ClaudeCodeResult<T>>;\nexport async function runClaudeCode<T = unknown>(\n input: ClaudeCodeInput<T>\n): Promise<ClaudeCodeResult<T | null>> {\n const {\n prompt,\n model = \"claude-sonnet-4-6\",\n timeout = 300_000,\n maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,\n env = process.env,\n binary = \"ccs\",\n schema,\n fixModel = \"claude-haiku-4-5-20251001\",\n maxFixes = 1,\n jsonSchema,\n ccsAccounts,\n mcpConfigPath,\n addDir,\n tools,\n allowedTools,\n } = input;\n\n if (!prompt || prompt.trim().length === 0) {\n throw new Error(\"prompt cannot be empty\");\n }\n\n if (!addDir || addDir.trim().length === 0) {\n throw new Error(\"addDir is required: omitting it exposes the worker CWD to the model\");\n }\n\n if (!binary || binary.trim().length === 0) {\n throw new Error(\"binary cannot be empty\");\n }\n\n if (jsonSchema && schema) {\n throw new Error(\"cannot use jsonSchema with schema\");\n }\n\n // Build jsonSchemaStr from Zod schema or raw jsonSchema — passed as CLI flag, not prompt text\n let jsonSchemaStr: string | undefined;\n if (schema) {\n try {\n jsonSchemaStr = JSON.stringify(z.toJSONSchema(schema));\n } catch (err) {\n throw new Error(\n `Failed to convert Zod schema to JSON Schema (requires Zod v4+): ${String(err)}`\n );\n }\n } else if (jsonSchema) {\n try {\n jsonSchemaStr = JSON.stringify(jsonSchema);\n } catch (err) {\n throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);\n }\n }\n\n const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);\n\n // No schema — return raw result. parsed may be null for plain-text responses.\n // Cast is safe: overload 1 constrains T = unknown, so T | null = unknown.\n if (!schema) {\n return { text: raw.text, parsed: raw.parsed as T | null, envelope: raw.envelope, fixAttempts: 0 };\n }\n\n // Try to validate the initial response.\n // Note: if raw.parsed is null (plain-text response), schema.parse(null) throws ZodError,\n // which correctly triggers the auto-fix flow.\n let lastText = raw.text;\n let lastError: unknown;\n try {\n const parsed = schema.parse(raw.parsed);\n return { ...raw, parsed, fixAttempts: 0 };\n } catch (err) {\n lastError = err;\n }\n\n // Auto-fix loop: send errors + original output back to fixModel for self-correction.\n // jsonSchemaStr is passed so the fix spawn also uses --json-schema enforcement.\n for (let attempt = 1; attempt <= maxFixes; attempt++) {\n const fixPromptStr = buildFixPrompt(lastText, lastError);\n const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);\n try {\n const parsed = schema.parse(fixRaw.parsed);\n return { ...fixRaw, parsed, fixAttempts: attempt };\n } catch (err) {\n lastText = fixRaw.text;\n lastError = err;\n }\n }\n\n const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);\n throw new SchemaValidationError(\n `SchemaValidationError: validation failed after ${maxFixes} fix attempt(s): ${errorMessage}`\n );\n}\n"],"mappings":"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","names":["text"]}
|
|
1
|
+
{"version":3,"sources":["../src/runners/claude-code.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { z } from \"zod\";\n\nexport interface ClaudeCodeInput<T = unknown> {\n prompt: string;\n model?: string;\n /** Timeout in milliseconds. Default: 300_000 (5 min) */\n timeout?: number;\n /**\n * Maximum allowed stdout size in bytes. Default: 10_485_760 (10 MB).\n * If claude output exceeds this limit the process is killed and the promise rejects.\n */\n maxOutputBytes?: number;\n /**\n * Environment variables passed to the subprocess.\n * Defaults to the full process.env so that the binary can access its auth\n * tokens and config (ANTHROPIC_API_KEY, HOME, etc.).\n * Override if you need to restrict or augment the environment.\n */\n env?: NodeJS.ProcessEnv;\n /**\n * Binary to execute. Default: \"ccs\" (Claude Code Profile & Model Switcher).\n * Use \"claude\" to invoke the Claude CLI directly without profile switching.\n * Using `--print` (not `-p`) avoids the ccs delegation system while\n * preserving the standard `--output-format json` envelope output.\n */\n binary?: string;\n /**\n * Optional Zod schema (requires Zod v4+). When provided:\n * - Its JSON Schema representation is passed via `--json-schema` CLI flag\n * (CLI-level enforcement — Claude physically cannot return non-JSON).\n * - The response is validated with `schema.parse()`.\n * - On failure, an auto-fix retry is sent to `fixModel`.\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n * - Mutually exclusive with `jsonSchema`.\n */\n schema?: z.ZodType<T>;\n /**\n * Model used for auto-fix retries. Default: \"claude-haiku-4-5-20251001\"\n * (cheap and fast, good for JSON correction tasks).\n */\n fixModel?: string;\n /**\n * Maximum number of auto-fix attempts on schema validation failure. Default: 1.\n */\n maxFixes?: number;\n /**\n * Raw JSON Schema for CLI-level structured output enforcement.\n * Adds `--json-schema <schema>` to spawn args.\n * Claude returns structured_output in the envelope — guaranteed JSON.\n * No runtime validation or auto-fix. `result.parsed` is `unknown`.\n * Mutually exclusive with `schema`.\n */\n jsonSchema?: Record<string, unknown>;\n /**\n * Override the default tool list passed via `--tools`.\n * Default: `[\"Read\", \"Grep\", \"Glob\"]` (read-only by design).\n * Pass `[\"Bash\", \"Read\", \"Grep\", \"Glob\"]` to also allow Bash execution.\n */\n tools?: string[];\n /**\n * Restrict Bash commands via `--allowed-tools` flag.\n * Each entry is a pattern like `\"Bash(git log*)\"`.\n * Only meaningful when `tools` includes `\"Bash\"`.\n * When omitted, no `--allowed-tools` flag is added.\n */\n allowedTools?: string[];\n /**\n * List of CCS account names to rotate through on `is_error: true` responses.\n * Each account maps to `CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>`.\n * On `is_error`, the next account in the list is tried automatically.\n * Non-is_error failures (timeouts, non-zero exit, etc.) are thrown immediately.\n * If all accounts return `is_error`, throws with the last error message.\n */\n ccsAccounts?: string[];\n /**\n * Path to an MCP config JSON file.\n * When provided, adds `--mcp-config <path>` to the spawn args so the\n * Claude CLI can load MCP servers (filesystem, supabase, etc.).\n * The caller is responsible for writing the file before calling\n * `runClaudeCode` and deleting it afterwards.\n * If the file does not exist or is not accessible, the Claude CLI\n * will exit with a non-zero code and the promise will reject.\n */\n mcpConfigPath?: string;\n /**\n * Replaces the default system prompt entirely.\n * Adds `--system-prompt <text>` to spawn args.\n * CLAUDE.md is ignored when this is set.\n */\n systemPrompt: string;\n /**\n * Absolute path to the repository directory Claude should work in.\n * Required — without it Claude would inherit the worker CWD, exposing\n * worker source files, env vars, and credentials to the model.\n * Adds `--add-dir <path>` and sets `cwd: addDir` on the spawned process\n * so relative paths (e.g. Read(\"src/foo.ts\")) resolve inside the repo.\n * Throws if empty. If the directory does not exist, the Claude CLI will\n * exit with a non-zero code and the promise will reject.\n */\n addDir: string;\n}\n\n/**\n * The JSON envelope that `claude --output-format json` wraps every response in.\n * Only the fields we care about are typed; the rest are captured in the index signature.\n */\nexport interface ClaudeEnvelope {\n type: string;\n subtype: string;\n /** The actual text response from the model (plain text / raw JSON string) */\n result: string;\n is_error: boolean;\n /**\n * Present when `--json-schema` flag is used.\n * Contains the structured output object — use this instead of `result`.\n */\n structured_output?: unknown;\n [key: string]: unknown;\n}\n\nexport interface ClaudeCodeResult<T = unknown> {\n /** The actual text content of claude's response (extracted from envelope.result) */\n text: string;\n /**\n * Parsed value. Typed as `T` when a schema is provided and validation passes.\n * Without schema: `unknown` (will be `null` when the response is not valid JSON).\n */\n parsed: T;\n /** The full response envelope from `claude --output-format json` */\n envelope: ClaudeEnvelope;\n /** Number of auto-fix retries used. 0 means the first response was valid. */\n fixAttempts: number;\n}\n\n/**\n * Thrown when schema validation fails after all auto-fix attempts are exhausted.\n */\nexport class SchemaValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SchemaValidationError\";\n }\n}\n\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB\n\n/**\n * Kills a process gracefully: SIGTERM first, SIGKILL after 5 seconds if still alive.\n */\nfunction killGracefully(proc: ReturnType<typeof spawn>): void {\n proc.kill(\"SIGTERM\");\n setTimeout(() => {\n proc.kill(\"SIGKILL\");\n }, 5000).unref();\n}\n\n/** Internal raw result from a single spawn cycle (before schema validation). */\ninterface RawResult {\n text: string;\n parsed: unknown | null;\n envelope: ClaudeEnvelope;\n}\n\n/**\n * Spawns the binary once and collects the JSON envelope result.\n * All validation/retry logic lives in `runClaudeCode`.\n */\nfunction spawnOnce(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string,\n tools?: string[],\n allowedTools?: string[],\n systemPrompt?: string\n): Promise<RawResult> {\n // Use --print (not -p) so that ccs passes args through to claude without\n // triggering its delegation system. Prompt is the final positional argument.\n const toolsList = tools?.length ? tools.join(\",\") : \"Read,Grep,Glob\";\n const args = [\"--print\", \"--bare\", \"--output-format\", \"json\", \"--model\", model, \"--tools\", toolsList];\n if (allowedTools?.length) args.push(\"--allowed-tools\", ...allowedTools);\n if (mcpConfigPath) args.push(\"--mcp-config\", mcpConfigPath);\n args.push(\"--add-dir\", addDir);\n if (systemPrompt) args.push(\"--system-prompt\", systemPrompt);\n if (jsonSchemaStr) {\n args.push(\"--json-schema\", jsonSchemaStr);\n }\n // Prompt is delivered via stdin — avoids OS ARG_MAX limits (~2MB on Linux)\n\n return new Promise((resolve, reject) => {\n const proc = spawn(binary, args, { env, cwd: addDir });\n\n if (!proc.stdout || !proc.stderr || !proc.stdin) {\n reject(new Error(`Failed to spawn ${binary}: stdio streams not available`));\n return;\n }\n\n // Suppress all stdin errors — EPIPE is expected when the process exits before reading\n // all of stdin; other stdin errors are extremely rare and non-fatal (process already spawned).\n proc.stdin.on(\"error\", () => {});\n // If the internal buffer is full (write returns false), wait for drain before closing.\n // This handles extremely large prompts where the OS pipe buffer fills up.\n if (proc.stdin.write(prompt, \"utf8\")) {\n proc.stdin.end();\n } else {\n proc.stdin.once(\"drain\", () => proc.stdin!.end());\n }\n\n // settled prevents double-resolve/reject between timeout and close handlers\n let settled = false;\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalStdoutSize = 0;\n\n const timer = setTimeout(() => {\n settled = true;\n killGracefully(proc);\n reject(new Error(`${binary} process timed out after ${timeout}ms`));\n }, timeout);\n\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n totalStdoutSize += chunk.length;\n if (totalStdoutSize > maxOutputBytes) {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n killGracefully(proc);\n reject(new Error(`${binary} output exceeded ${maxOutputBytes} bytes`));\n return;\n }\n stdoutChunks.push(chunk);\n });\n\n proc.stderr.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk);\n });\n\n proc.on(\"close\", (code) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n\n const stdout = Buffer.concat(stdoutChunks).toString();\n const stderr = Buffer.concat(stderrChunks).toString();\n\n if (code !== 0) {\n reject(\n new Error(\n `${binary} exited with code ${code}${stderr ? `\\nstderr: ${stderr}` : \"\"}`\n )\n );\n return;\n }\n\n let envelope: ClaudeEnvelope;\n try {\n envelope = JSON.parse(stdout.trim()) as ClaudeEnvelope;\n } catch {\n reject(new Error(`${binary} output is not valid JSON: ${stdout.trim().slice(0, 200)}`));\n return;\n }\n\n if (envelope.is_error) {\n reject(new IsErrorResponse(envelope.result));\n return;\n }\n\n // --json-schema mode: result comes in structured_output, not result field\n if (envelope.structured_output !== undefined) {\n const text = JSON.stringify(envelope.structured_output);\n resolve({ text, parsed: envelope.structured_output, envelope });\n return;\n }\n\n if (typeof envelope.result !== \"string\") {\n reject(new Error(\"Invalid envelope: result field is not a string\"));\n return;\n }\n\n const text = envelope.result;\n\n let parsed: unknown | null = null;\n try {\n parsed = JSON.parse(text);\n } catch {\n // text is not JSON — that's fine\n }\n\n resolve({ text, parsed, envelope });\n });\n\n proc.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Failed to spawn ${binary}: ${err.message}`));\n });\n });\n}\n\n/**\n * Thrown internally by spawnOnce when the Claude envelope contains is_error=true.\n * Caught by spawnWithAccounts to distinguish account-level errors from other failures.\n */\nclass IsErrorResponse extends Error {\n constructor(public readonly result: string) {\n super(`returned is_error=true: ${result}`);\n this.name = \"IsErrorResponse\";\n }\n}\n\n/**\n * Calls spawnOnce with optional CCS account rotation.\n * When `accounts` is provided, tries each account in order by setting\n * CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>. Rotates only on is_error=true;\n * any other error (timeout, non-zero exit, ENOENT) is thrown immediately.\n * If all accounts return is_error, throws with the last error message.\n *\n * Implemented as a regular (non-async) function so the no-rotation path\n * returns spawnOnce's promise directly without adding an extra microtask tick.\n */\nfunction spawnWithAccounts(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n accounts: string[] | undefined,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string,\n tools?: string[],\n allowedTools?: string[],\n systemPrompt?: string\n): Promise<RawResult> {\n // Fast path: return promise directly — no extra microtask overhead\n if (!accounts || accounts.length === 0) {\n return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);\n }\n\n // Rotation path: async IIFE to iterate through accounts\n return (async () => {\n let lastIsError: IsErrorResponse | undefined;\n for (const account of accounts) {\n const accountEnv: NodeJS.ProcessEnv = {\n ...env,\n CLAUDE_CONFIG_DIR: `${homedir()}/.ccs/instances/${account}`,\n };\n try {\n return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);\n } catch (err) {\n if (err instanceof IsErrorResponse) {\n lastIsError = err;\n continue;\n }\n throw err;\n }\n }\n throw new Error(lastIsError!.result);\n })();\n}\n\n/**\n * Builds the auto-fix prompt sent to the fixModel when schema validation fails.\n * Includes structured ZodError issues (when available) and the original invalid\n * response so the model can self-correct.\n */\nfunction buildFixPrompt(invalidText: string, error: unknown): string {\n let errorMessage: string;\n if (error instanceof z.ZodError) {\n errorMessage = error.issues\n .map((i) => `Path: ${i.path.length ? i.path.join(\".\") : \"(root)\"}, ${i.message}`)\n .join(\"\\n\");\n } else if (error instanceof Error) {\n errorMessage = error.message;\n } else {\n errorMessage = String(error);\n }\n return (\n `The following JSON failed schema validation.\\n\\n` +\n `Errors:\\n${errorMessage}\\n\\n` +\n `Invalid response:\\n${invalidText}\\n\\n` +\n `Output ONLY corrected JSON. No explanation, no markdown.`\n );\n}\n\n/**\n * Runs the configured binary (default: ccs) with --print flag as a subprocess.\n * Command: `<binary> --print --bare --output-format json --model <model> [--json-schema <schema>]`\n * The prompt is written to the process stdin to avoid OS ARG_MAX limits (~2MB on Linux).\n * --bare suppresses global CLAUDE.md auto-discovery, hooks, LSP, auto-memory and other\n * side effects — ensuring the agent runs only with the provided systemPrompt.\n *\n * When a `schema` is provided (Zod v4+):\n * - Its JSON Schema is passed via `--json-schema` CLI flag (hard enforcement).\n * - Claude returns structured_output in the envelope — guaranteed JSON.\n * - The response is validated; on failure an auto-fix retry is sent to `fixModel`.\n * - `result.fixAttempts` indicates how many retries were used (0 = first response valid).\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n *\n * When `jsonSchema` is provided:\n * - Passed via `--json-schema` CLI flag (hard enforcement, no validation or retry).\n * - `result.parsed` is `unknown`.\n *\n * Without schema: `result.parsed` is `unknown` (may be `null` for plain-text responses).\n *\n * Designed to be used as a plain async function inside a trigger.dev task `run()`.\n */\nexport function runClaudeCode(input: ClaudeCodeInput): Promise<ClaudeCodeResult<unknown>>;\nexport function runClaudeCode<T>(\n input: ClaudeCodeInput<T> & { schema: z.ZodType<T> }\n): Promise<ClaudeCodeResult<T>>;\nexport async function runClaudeCode<T = unknown>(\n input: ClaudeCodeInput<T>\n): Promise<ClaudeCodeResult<T | null>> {\n const {\n prompt,\n model = \"claude-sonnet-4-6\",\n timeout = 300_000,\n maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,\n env = process.env,\n binary = \"ccs\",\n schema,\n fixModel = \"claude-haiku-4-5-20251001\",\n maxFixes = 1,\n jsonSchema,\n ccsAccounts,\n mcpConfigPath,\n addDir,\n tools,\n allowedTools,\n systemPrompt,\n } = input;\n\n if (!prompt || prompt.trim().length === 0) {\n throw new Error(\"prompt cannot be empty\");\n }\n\n if (!addDir || addDir.trim().length === 0) {\n throw new Error(\"addDir is required: omitting it exposes the worker CWD to the model\");\n }\n\n if (!binary || binary.trim().length === 0) {\n throw new Error(\"binary cannot be empty\");\n }\n\n if (jsonSchema && schema) {\n throw new Error(\"cannot use jsonSchema with schema\");\n }\n\n if (!systemPrompt || systemPrompt.trim().length === 0) {\n throw new Error(\"systemPrompt cannot be empty\");\n }\n\n if (allowedTools?.length && (!tools || !tools.includes(\"Bash\"))) {\n throw new Error(\"allowedTools requires 'Bash' in tools array\");\n }\n\n // Build jsonSchemaStr from Zod schema or raw jsonSchema — passed as CLI flag, not prompt text\n let jsonSchemaStr: string | undefined;\n if (schema) {\n try {\n jsonSchemaStr = JSON.stringify(z.toJSONSchema(schema));\n } catch (err) {\n throw new Error(\n `Failed to convert Zod schema to JSON Schema (requires Zod v4+): ${String(err)}`\n );\n }\n } else if (jsonSchema) {\n try {\n jsonSchemaStr = JSON.stringify(jsonSchema);\n } catch (err) {\n throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);\n }\n }\n\n const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);\n\n // No schema — return raw result. parsed may be null for plain-text responses.\n // Cast is safe: overload 1 constrains T = unknown, so T | null = unknown.\n if (!schema) {\n return { text: raw.text, parsed: raw.parsed as T | null, envelope: raw.envelope, fixAttempts: 0 };\n }\n\n // Try to validate the initial response.\n // Note: if raw.parsed is null (plain-text response), schema.parse(null) throws ZodError,\n // which correctly triggers the auto-fix flow.\n let lastText = raw.text;\n let lastError: unknown;\n try {\n const parsed = schema.parse(raw.parsed);\n return { ...raw, parsed, fixAttempts: 0 };\n } catch (err) {\n lastError = err;\n }\n\n // Auto-fix loop: send errors + original output back to fixModel for self-correction.\n // jsonSchemaStr is passed so the fix spawn also uses --json-schema enforcement.\n for (let attempt = 1; attempt <= maxFixes; attempt++) {\n const fixPromptStr = buildFixPrompt(lastText, lastError);\n const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools, systemPrompt);\n try {\n const parsed = schema.parse(fixRaw.parsed);\n return { ...fixRaw, parsed, fixAttempts: attempt };\n } catch (err) {\n lastText = fixRaw.text;\n lastError = err;\n }\n }\n\n const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);\n throw new SchemaValidationError(\n `SchemaValidationError: validation failed after ${maxFixes} fix attempt(s): ${errorMessage}`\n );\n}\n"],"mappings":"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","names":["text"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atrvd/trigger",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Shared run-functions for trigger.dev tasks",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ATRVD",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
],
|
|
26
26
|
"exports": {
|
|
27
27
|
".": {
|
|
28
|
-
"import": "./dist/index.
|
|
29
|
-
"require": "./dist/index.
|
|
28
|
+
"import": "./dist/index.mjs",
|
|
29
|
+
"require": "./dist/index.js",
|
|
30
30
|
"types": "./dist/index.d.ts"
|
|
31
31
|
}
|
|
32
32
|
},
|