@sandagent/runner-cli 0.1.2-beta.1 → 0.1.2-beta.3
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/LICENSE +201 -0
- package/README.md +39 -4
- package/dist/__tests__/runner-cli.integration.test.d.ts +6 -0
- package/dist/__tests__/runner-cli.integration.test.d.ts.map +1 -0
- package/dist/__tests__/runner-cli.integration.test.js +76 -0
- package/dist/__tests__/runner-cli.integration.test.js.map +1 -0
- package/dist/__tests__/runner.test.js +7 -3
- package/dist/__tests__/runner.test.js.map +1 -1
- package/dist/bundle.mjs +334 -198
- package/dist/cli.js +14 -0
- package/dist/cli.js.map +1 -1
- package/dist/runner.d.ts +2 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +31 -14
- package/dist/runner.js.map +1 -1
- package/package.json +29 -14
- package/dist/bundle.js +0 -377
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,11 @@ import { runAgent } from "./runner.js";
|
|
|
16
16
|
function parseCliArgs() {
|
|
17
17
|
const { values, positionals } = parseArgs({
|
|
18
18
|
options: {
|
|
19
|
+
runner: {
|
|
20
|
+
type: "string",
|
|
21
|
+
short: "r",
|
|
22
|
+
default: "claude",
|
|
23
|
+
},
|
|
19
24
|
model: {
|
|
20
25
|
type: "string",
|
|
21
26
|
short: "m",
|
|
@@ -81,6 +86,12 @@ function parseCliArgs() {
|
|
|
81
86
|
console.error('Usage: sandagent run [options] -- "<user input>"');
|
|
82
87
|
process.exit(1);
|
|
83
88
|
}
|
|
89
|
+
// Validate runner
|
|
90
|
+
const runner = values.runner;
|
|
91
|
+
if (!["claude", "codex", "copilot"].includes(runner)) {
|
|
92
|
+
console.error('Error: --runner must be one of: "claude", "codex", "copilot"');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
84
95
|
// Validate output-format
|
|
85
96
|
const outputFormat = values["output-format"];
|
|
86
97
|
if (outputFormat &&
|
|
@@ -89,6 +100,7 @@ function parseCliArgs() {
|
|
|
89
100
|
process.exit(1);
|
|
90
101
|
}
|
|
91
102
|
return {
|
|
103
|
+
runner,
|
|
92
104
|
model: values.model,
|
|
93
105
|
cwd: values.cwd,
|
|
94
106
|
systemPrompt: values["system-prompt"],
|
|
@@ -113,6 +125,7 @@ Usage:
|
|
|
113
125
|
sandagent run [options] -- "<user input>"
|
|
114
126
|
|
|
115
127
|
Options:
|
|
128
|
+
-r, --runner <runner> Runner to use: claude, codex, copilot (default: claude)
|
|
116
129
|
-m, --model <model> Model to use (default: claude-sonnet-4-20250514)
|
|
117
130
|
-c, --cwd <path> Working directory (default: current directory)
|
|
118
131
|
-s, --system-prompt <prompt> Custom system prompt
|
|
@@ -145,6 +158,7 @@ async function main() {
|
|
|
145
158
|
process.chdir(args.cwd);
|
|
146
159
|
// Run the agent and stream output to stdout
|
|
147
160
|
await runAgent({
|
|
161
|
+
runner: args.runner,
|
|
148
162
|
model: args.model,
|
|
149
163
|
userInput: args.userInput,
|
|
150
164
|
systemPrompt: args.systemPrompt,
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/runner.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { type BaseRunnerOptions } from "@sandagent/runner-claude";
|
|
|
4
4
|
* Extends BaseRunnerOptions with CLI-specific fields
|
|
5
5
|
*/
|
|
6
6
|
export interface RunAgentOptions extends BaseRunnerOptions {
|
|
7
|
+
/** Which runner to use: claude, codex, copilot */
|
|
8
|
+
runner: string;
|
|
7
9
|
/** User input/task */
|
|
8
10
|
userInput: string;
|
|
9
11
|
/** Template to use (e.g., "default", "coder", "analyst", "researcher") */
|
package/dist/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EAGvB,MAAM,0BAA0B,CAAC;AAElC;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EAGvB,MAAM,0BAA0B,CAAC;AAElC;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAkEtE"}
|
package/dist/runner.js
CHANGED
|
@@ -15,28 +15,45 @@ export async function runAgent(options) {
|
|
|
15
15
|
// Create an AbortController to handle process signals
|
|
16
16
|
const abortController = new AbortController();
|
|
17
17
|
// Handle SIGTERM and SIGINT signals
|
|
18
|
-
const signalHandler = () => {
|
|
18
|
+
const signalHandler = async () => {
|
|
19
19
|
console.error("[Runner] Received termination signal, stopping...");
|
|
20
|
+
// Note: abort() synchronously triggers all abort event listeners,
|
|
20
21
|
abortController.abort();
|
|
21
|
-
console.error("[Runner] AbortController.abort()
|
|
22
|
+
console.error("[Runner] AbortController.abort() completed (listeners triggered)");
|
|
22
23
|
};
|
|
23
24
|
process.on("SIGTERM", signalHandler);
|
|
24
25
|
process.on("SIGINT", signalHandler);
|
|
25
26
|
console.error("[Runner] Signal handlers registered");
|
|
26
27
|
try {
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
// Select the appropriate runner based on options.runner
|
|
29
|
+
let runner;
|
|
30
|
+
switch (options.runner) {
|
|
31
|
+
case "claude": {
|
|
32
|
+
// Build runner options - cwd is already set by cli.ts via process.chdir()
|
|
33
|
+
const runnerOptions = {
|
|
34
|
+
model: options.model,
|
|
35
|
+
systemPrompt: options.systemPrompt,
|
|
36
|
+
maxTurns: options.maxTurns,
|
|
37
|
+
allowedTools: options.allowedTools,
|
|
38
|
+
resume: options.resume,
|
|
39
|
+
approvalDir: options.approvalDir,
|
|
40
|
+
outputFormat: options.outputFormat,
|
|
41
|
+
abortController: abortController,
|
|
42
|
+
};
|
|
43
|
+
runner = createClaudeRunner(runnerOptions);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case "codex":
|
|
47
|
+
// TODO: Implement Codex runner
|
|
48
|
+
throw new Error("Codex runner not yet implemented. Use --runner=claude for now.");
|
|
49
|
+
case "copilot":
|
|
50
|
+
// TODO: Implement Copilot runner
|
|
51
|
+
throw new Error("Copilot runner not yet implemented. Use --runner=claude for now.");
|
|
52
|
+
default:
|
|
53
|
+
throw new Error(`Unknown runner: ${options.runner}. Supported runners: claude, codex, copilot`);
|
|
54
|
+
}
|
|
38
55
|
// Stream AI SDK UI messages to stdout
|
|
39
|
-
for await (const chunk of runner.run(options.userInput
|
|
56
|
+
for await (const chunk of runner.run(options.userInput)) {
|
|
40
57
|
// Write directly to stdout without modification
|
|
41
58
|
// This ensures the stream is a valid AI SDK UI stream
|
|
42
59
|
process.stdout.write(chunk);
|
package/dist/runner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AAelC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAwB;IACrD,sDAAsD;IACtD,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;IAE9C,oCAAoC;IACpC,MAAM,aAAa,GAAG,KAAK,IAAI,EAAE;QAC/B,OAAO,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACnE,kEAAkE;QAClE,eAAe,CAAC,KAAK,EAAE,CAAC;QACxB,OAAO,CAAC,KAAK,CACX,kEAAkE,CACnE,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IAEpC,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAErD,IAAI,CAAC;QACH,wDAAwD;QACxD,IAAI,MAAyD,CAAC;QAE9D,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;YACvB,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,0EAA0E;gBAC1E,MAAM,aAAa,GAAwB;oBACzC,KAAK,EAAE,OAAO,CAAC,KAAK;oBACpB,YAAY,EAAE,OAAO,CAAC,YAAY;oBAClC,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;oBAClC,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,WAAW,EAAE,OAAO,CAAC,WAAW;oBAChC,YAAY,EAAE,OAAO,CAAC,YAAY;oBAClC,eAAe,EAAE,eAAe;iBACjC,CAAC;gBACF,MAAM,GAAG,kBAAkB,CAAC,aAAa,CAAC,CAAC;gBAC3C,MAAM;YACR,CAAC;YACD,KAAK,OAAO;gBACV,+BAA+B;gBAC/B,MAAM,IAAI,KAAK,CACb,gEAAgE,CACjE,CAAC;YACJ,KAAK,SAAS;gBACZ,iCAAiC;gBACjC,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAC;YACJ;gBACE,MAAM,IAAI,KAAK,CACb,mBAAmB,OAAO,CAAC,MAAM,6CAA6C,CAC/E,CAAC;QACN,CAAC;QAED,sCAAsC;QACtC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YACxD,gDAAgD;YAChD,sDAAsD;YACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;YAAS,CAAC;QACT,2BAA2B;QAC3B,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IACvC,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sandagent/runner-cli",
|
|
3
|
-
"version": "0.1.2-beta.
|
|
3
|
+
"version": "0.1.2-beta.3",
|
|
4
4
|
"description": "SandAgent Runner CLI - Like gemini-cli or claude-code, runs in your local terminal with AI SDK UI streaming",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,28 +18,43 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"dist"
|
|
20
20
|
],
|
|
21
|
-
"scripts": {
|
|
22
|
-
"build": "tsc && pnpm bundle",
|
|
23
|
-
"build:bundle": "tsc && pnpm bundle",
|
|
24
|
-
"bundle": "esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/bundle.mjs",
|
|
25
|
-
"dev": "tsc --watch",
|
|
26
|
-
"clean": "rm -rf dist",
|
|
27
|
-
"typecheck": "tsc --noEmit",
|
|
28
|
-
"lint": "echo 'no lint configured'",
|
|
29
|
-
"test": "vitest run --passWithNoTests"
|
|
30
|
-
},
|
|
31
21
|
"repository": {
|
|
32
22
|
"type": "git",
|
|
33
23
|
"url": "https://github.com/vikadata/sandagent.git",
|
|
34
24
|
"directory": "apps/runner-cli"
|
|
35
25
|
},
|
|
36
26
|
"license": "Apache-2.0",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public",
|
|
29
|
+
"tag": "beta"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20.0.0"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"cli",
|
|
36
|
+
"agent",
|
|
37
|
+
"sandagent",
|
|
38
|
+
"runner",
|
|
39
|
+
"claude",
|
|
40
|
+
"anthropic"
|
|
41
|
+
],
|
|
37
42
|
"dependencies": {},
|
|
38
43
|
"devDependencies": {
|
|
39
|
-
"@sandagent/runner-claude": "workspace:*",
|
|
40
44
|
"@types/node": "^20.10.0",
|
|
41
45
|
"esbuild": "^0.27.2",
|
|
42
46
|
"typescript": "^5.3.0",
|
|
43
|
-
"vitest": "^1.6.1"
|
|
47
|
+
"vitest": "^1.6.1",
|
|
48
|
+
"@sandagent/runner-claude": "0.1.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc && pnpm bundle",
|
|
52
|
+
"build:bundle": "tsc && pnpm bundle",
|
|
53
|
+
"bundle": "esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/bundle.mjs",
|
|
54
|
+
"dev": "tsc --watch",
|
|
55
|
+
"clean": "rm -rf dist",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"lint": "echo 'no lint configured'",
|
|
58
|
+
"test": "vitest run --passWithNoTests"
|
|
44
59
|
}
|
|
45
|
-
}
|
|
60
|
+
}
|
package/dist/bundle.js
DELETED
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/cli.ts
|
|
4
|
-
import { parseArgs } from "node:util";
|
|
5
|
-
|
|
6
|
-
// src/runner.ts
|
|
7
|
-
import * as fs from "node:fs";
|
|
8
|
-
import * as path from "node:path";
|
|
9
|
-
|
|
10
|
-
// ../../packages/runner-claude/dist/claude-runner.js
|
|
11
|
-
var OPTIONAL_MODULES = {
|
|
12
|
-
"claude-agent-sdk": "@anthropic-ai/claude-agent-sdk"
|
|
13
|
-
};
|
|
14
|
-
function createClaudeRunner(options) {
|
|
15
|
-
return {
|
|
16
|
-
async *run(userInput) {
|
|
17
|
-
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
18
|
-
if (!apiKey) {
|
|
19
|
-
console.error("[SandAgent] Warning: ANTHROPIC_API_KEY not set. Using mock response.\nTo use the real Claude Agent SDK:\n1. Set ANTHROPIC_API_KEY environment variable\n2. Install the SDK: npm install @anthropic-ai/claude-agent-sdk");
|
|
20
|
-
yield* runMockAgent(options, userInput);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
const sdk = await loadClaudeAgentSDK();
|
|
24
|
-
if (sdk) {
|
|
25
|
-
yield* runWithClaudeAgentSDK(sdk, options, userInput);
|
|
26
|
-
} else {
|
|
27
|
-
console.error("[SandAgent] Warning: @anthropic-ai/claude-agent-sdk not installed. Using mock response.\nInstall the SDK: npm install @anthropic-ai/claude-agent-sdk");
|
|
28
|
-
yield* runMockAgent(options, userInput);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
async function loadClaudeAgentSDK() {
|
|
34
|
-
try {
|
|
35
|
-
const modulePath = OPTIONAL_MODULES["claude-agent-sdk"];
|
|
36
|
-
const module = await import(
|
|
37
|
-
/* webpackIgnore: true */
|
|
38
|
-
modulePath
|
|
39
|
-
);
|
|
40
|
-
return module;
|
|
41
|
-
} catch {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
async function* runWithClaudeAgentSDK(sdk, options, userInput) {
|
|
46
|
-
const sdkOptions = {
|
|
47
|
-
model: options.model,
|
|
48
|
-
systemPrompt: options.systemPrompt,
|
|
49
|
-
maxTurns: options.maxTurns,
|
|
50
|
-
allowedTools: options.allowedTools,
|
|
51
|
-
cwd: options.cwd,
|
|
52
|
-
env: options.env
|
|
53
|
-
};
|
|
54
|
-
try {
|
|
55
|
-
for await (const message of sdk.query({
|
|
56
|
-
prompt: userInput,
|
|
57
|
-
options: sdkOptions
|
|
58
|
-
})) {
|
|
59
|
-
const chunks = convertSDKMessageToAISDKUI(message);
|
|
60
|
-
for (const chunk of chunks) {
|
|
61
|
-
yield chunk;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
yield `d:{"finishReason":"stop"}
|
|
65
|
-
`;
|
|
66
|
-
} catch (error) {
|
|
67
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
68
|
-
yield `3:${JSON.stringify(errorMessage)}
|
|
69
|
-
`;
|
|
70
|
-
yield `d:{"finishReason":"error"}
|
|
71
|
-
`;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function convertSDKMessageToAISDKUI(message) {
|
|
75
|
-
const chunks = [];
|
|
76
|
-
switch (message.type) {
|
|
77
|
-
case "system":
|
|
78
|
-
if (message.subtype === "init") {
|
|
79
|
-
chunks.push(`0:${JSON.stringify(`[System initialized: ${message.model ?? "unknown model"}]
|
|
80
|
-
`)}
|
|
81
|
-
`);
|
|
82
|
-
}
|
|
83
|
-
break;
|
|
84
|
-
case "assistant":
|
|
85
|
-
const assistantMsg = message;
|
|
86
|
-
if (assistantMsg.message) {
|
|
87
|
-
chunks.push(`0:${JSON.stringify(assistantMsg.message)}
|
|
88
|
-
`);
|
|
89
|
-
}
|
|
90
|
-
break;
|
|
91
|
-
case "tool_use":
|
|
92
|
-
const toolUseMsg = message;
|
|
93
|
-
chunks.push(`9:${JSON.stringify({
|
|
94
|
-
toolCallId: toolUseMsg.tool_use_id,
|
|
95
|
-
toolName: toolUseMsg.tool_name,
|
|
96
|
-
args: toolUseMsg.tool_input
|
|
97
|
-
})}
|
|
98
|
-
`);
|
|
99
|
-
break;
|
|
100
|
-
case "tool_result":
|
|
101
|
-
const toolResultMsg = message;
|
|
102
|
-
chunks.push(`a:${JSON.stringify({
|
|
103
|
-
toolCallId: toolResultMsg.tool_use_id,
|
|
104
|
-
result: toolResultMsg.output
|
|
105
|
-
})}
|
|
106
|
-
`);
|
|
107
|
-
break;
|
|
108
|
-
case "result":
|
|
109
|
-
const resultMsg = message;
|
|
110
|
-
if (resultMsg.is_error && resultMsg.errors) {
|
|
111
|
-
chunks.push(`3:${JSON.stringify(resultMsg.errors.join(", "))}
|
|
112
|
-
`);
|
|
113
|
-
}
|
|
114
|
-
break;
|
|
115
|
-
default:
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
return chunks;
|
|
119
|
-
}
|
|
120
|
-
async function* runMockAgent(options, userInput) {
|
|
121
|
-
const response = `I received your request: "${userInput}"
|
|
122
|
-
|
|
123
|
-
Model: ${options.model}
|
|
124
|
-
|
|
125
|
-
This is a mock response because:
|
|
126
|
-
- ANTHROPIC_API_KEY is not set, OR
|
|
127
|
-
- @anthropic-ai/claude-agent-sdk is not installed
|
|
128
|
-
|
|
129
|
-
To use the real Claude Agent SDK:
|
|
130
|
-
1. Set ANTHROPIC_API_KEY environment variable
|
|
131
|
-
2. Install the SDK: npm install @anthropic-ai/claude-agent-sdk
|
|
132
|
-
|
|
133
|
-
Documentation: https://platform.claude.com/docs/en/agent-sdk/typescript-v2-preview`;
|
|
134
|
-
const words = response.split(" ");
|
|
135
|
-
for (const word of words) {
|
|
136
|
-
yield `0:${JSON.stringify(word + " ")}
|
|
137
|
-
`;
|
|
138
|
-
await new Promise((resolve2) => setTimeout(resolve2, 20));
|
|
139
|
-
}
|
|
140
|
-
yield `d:{"finishReason":"stop"}
|
|
141
|
-
`;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// src/runner.ts
|
|
145
|
-
function findTemplatesDir() {
|
|
146
|
-
const envPath = process.env.SANDAGENT_TEMPLATES_DIR;
|
|
147
|
-
if (envPath && fs.existsSync(envPath)) {
|
|
148
|
-
return envPath;
|
|
149
|
-
}
|
|
150
|
-
const sandboxPath = "/sandagent/templates";
|
|
151
|
-
if (fs.existsSync(sandboxPath)) {
|
|
152
|
-
return sandboxPath;
|
|
153
|
-
}
|
|
154
|
-
const devPath = path.resolve(__dirname, "../../../templates");
|
|
155
|
-
if (fs.existsSync(devPath)) {
|
|
156
|
-
return devPath;
|
|
157
|
-
}
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
function loadTemplate(templateName) {
|
|
161
|
-
const templatesDir = findTemplatesDir();
|
|
162
|
-
if (!templatesDir) {
|
|
163
|
-
return {};
|
|
164
|
-
}
|
|
165
|
-
const templateDir = path.join(templatesDir, templateName);
|
|
166
|
-
if (!fs.existsSync(templateDir)) {
|
|
167
|
-
if (templateName !== "default") {
|
|
168
|
-
console.error(
|
|
169
|
-
`Warning: Template "${templateName}" not found, using default`
|
|
170
|
-
);
|
|
171
|
-
return loadTemplate("default");
|
|
172
|
-
}
|
|
173
|
-
return {};
|
|
174
|
-
}
|
|
175
|
-
let systemPrompt;
|
|
176
|
-
let settings;
|
|
177
|
-
const claudeMdPath = path.join(templateDir, "CLAUDE.md");
|
|
178
|
-
if (fs.existsSync(claudeMdPath)) {
|
|
179
|
-
systemPrompt = fs.readFileSync(claudeMdPath, "utf-8");
|
|
180
|
-
}
|
|
181
|
-
const settingsPath = path.join(templateDir, ".claude", "settings.json");
|
|
182
|
-
if (fs.existsSync(settingsPath)) {
|
|
183
|
-
try {
|
|
184
|
-
const settingsContent = fs.readFileSync(settingsPath, "utf-8");
|
|
185
|
-
settings = JSON.parse(settingsContent);
|
|
186
|
-
} catch (error) {
|
|
187
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
188
|
-
console.error(
|
|
189
|
-
`Warning: Failed to parse ${settingsPath}: ${errorMessage}`
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
const skillsDir = path.join(templateDir, "skills");
|
|
194
|
-
if (fs.existsSync(skillsDir)) {
|
|
195
|
-
try {
|
|
196
|
-
const skillFiles = fs.readdirSync(skillsDir).filter((f) => f.endsWith(".md"));
|
|
197
|
-
if (skillFiles.length > 0) {
|
|
198
|
-
const skillsContent = skillFiles.map((file) => {
|
|
199
|
-
const content = fs.readFileSync(
|
|
200
|
-
path.join(skillsDir, file),
|
|
201
|
-
"utf-8"
|
|
202
|
-
);
|
|
203
|
-
return `
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
${content}`;
|
|
207
|
-
}).join("\n");
|
|
208
|
-
if (systemPrompt) {
|
|
209
|
-
systemPrompt += `
|
|
210
|
-
|
|
211
|
-
## Skills
|
|
212
|
-
${skillsContent}`;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
} catch (error) {
|
|
216
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
217
|
-
console.error(
|
|
218
|
-
`Warning: Failed to load skills from ${skillsDir}: ${errorMessage}`
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return { systemPrompt, settings };
|
|
223
|
-
}
|
|
224
|
-
async function runAgent(options) {
|
|
225
|
-
const template = loadTemplate(options.template ?? "default");
|
|
226
|
-
const runnerOptions = {
|
|
227
|
-
model: options.model,
|
|
228
|
-
// Explicit system prompt overrides template
|
|
229
|
-
systemPrompt: options.systemPrompt ?? template.systemPrompt,
|
|
230
|
-
// Explicit maxTurns overrides template
|
|
231
|
-
maxTurns: options.maxTurns ?? template.settings?.max_turns,
|
|
232
|
-
// Explicit allowedTools overrides template
|
|
233
|
-
allowedTools: options.allowedTools ?? template.settings?.allowed_tools
|
|
234
|
-
};
|
|
235
|
-
const runner = createClaudeRunner(runnerOptions);
|
|
236
|
-
for await (const chunk of runner.run(options.userInput)) {
|
|
237
|
-
process.stdout.write(chunk);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// src/cli.ts
|
|
242
|
-
function parseCliArgs() {
|
|
243
|
-
const { values, positionals } = parseArgs({
|
|
244
|
-
options: {
|
|
245
|
-
model: {
|
|
246
|
-
type: "string",
|
|
247
|
-
short: "m",
|
|
248
|
-
default: "claude-sonnet-4-20250514"
|
|
249
|
-
},
|
|
250
|
-
cwd: {
|
|
251
|
-
type: "string",
|
|
252
|
-
short: "c",
|
|
253
|
-
default: process.env.SANDAGENT_WORKSPACE ?? process.cwd()
|
|
254
|
-
},
|
|
255
|
-
template: {
|
|
256
|
-
type: "string",
|
|
257
|
-
short: "T",
|
|
258
|
-
default: process.env.SANDAGENT_TEMPLATE ?? "default"
|
|
259
|
-
},
|
|
260
|
-
"system-prompt": {
|
|
261
|
-
type: "string",
|
|
262
|
-
short: "s"
|
|
263
|
-
},
|
|
264
|
-
"max-turns": {
|
|
265
|
-
type: "string",
|
|
266
|
-
short: "t"
|
|
267
|
-
},
|
|
268
|
-
"allowed-tools": {
|
|
269
|
-
type: "string",
|
|
270
|
-
short: "a"
|
|
271
|
-
},
|
|
272
|
-
help: {
|
|
273
|
-
type: "boolean",
|
|
274
|
-
short: "h"
|
|
275
|
-
}
|
|
276
|
-
},
|
|
277
|
-
allowPositionals: true,
|
|
278
|
-
strict: true
|
|
279
|
-
});
|
|
280
|
-
if (values.help) {
|
|
281
|
-
printHelp();
|
|
282
|
-
process.exit(0);
|
|
283
|
-
}
|
|
284
|
-
if (positionals[0] !== "run") {
|
|
285
|
-
console.error('Error: Expected "run" command');
|
|
286
|
-
console.error('Usage: sandagent run [options] -- "<user input>"');
|
|
287
|
-
process.exit(1);
|
|
288
|
-
}
|
|
289
|
-
const dashIndex = process.argv.indexOf("--");
|
|
290
|
-
let userInput = "";
|
|
291
|
-
if (dashIndex !== -1 && dashIndex < process.argv.length - 1) {
|
|
292
|
-
userInput = process.argv.slice(dashIndex + 1).join(" ");
|
|
293
|
-
} else if (positionals.length > 1) {
|
|
294
|
-
userInput = positionals.slice(1).join(" ");
|
|
295
|
-
}
|
|
296
|
-
if (!userInput) {
|
|
297
|
-
console.error("Error: User input is required");
|
|
298
|
-
console.error('Usage: sandagent run [options] -- "<user input>"');
|
|
299
|
-
process.exit(1);
|
|
300
|
-
}
|
|
301
|
-
return {
|
|
302
|
-
model: values.model,
|
|
303
|
-
cwd: values.cwd,
|
|
304
|
-
template: values.template,
|
|
305
|
-
systemPrompt: values["system-prompt"],
|
|
306
|
-
maxTurns: values["max-turns"] ? Number.parseInt(values["max-turns"], 10) : void 0,
|
|
307
|
-
allowedTools: values["allowed-tools"]?.split(",").map((t) => t.trim()),
|
|
308
|
-
userInput
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
function printHelp() {
|
|
312
|
-
console.log(`
|
|
313
|
-
\u{1F916} SandAgent Runner CLI
|
|
314
|
-
|
|
315
|
-
Like gemini-cli or claude-code - runs locally in your terminal.
|
|
316
|
-
Streams AI SDK UI messages directly to stdout.
|
|
317
|
-
|
|
318
|
-
Usage:
|
|
319
|
-
sandagent run [options] -- "<user input>"
|
|
320
|
-
|
|
321
|
-
# Or run from a template directory:
|
|
322
|
-
cd templates/coder
|
|
323
|
-
sandagent run -- "Build a REST API"
|
|
324
|
-
|
|
325
|
-
Options:
|
|
326
|
-
-m, --model <model> Model to use (default: claude-sonnet-4-20250514)
|
|
327
|
-
-c, --cwd <path> Working directory (default: current directory)
|
|
328
|
-
-T, --template <name> Template to use (default: default)
|
|
329
|
-
Available: default, coder, analyst, researcher
|
|
330
|
-
-s, --system-prompt <prompt> Custom system prompt (overrides template)
|
|
331
|
-
-t, --max-turns <n> Maximum conversation turns
|
|
332
|
-
-a, --allowed-tools <tools> Comma-separated list of allowed tools
|
|
333
|
-
-h, --help Show this help message
|
|
334
|
-
|
|
335
|
-
Environment Variables:
|
|
336
|
-
ANTHROPIC_API_KEY Anthropic API key (required)
|
|
337
|
-
SANDAGENT_WORKSPACE Default workspace path
|
|
338
|
-
SANDAGENT_TEMPLATE Default template to use
|
|
339
|
-
SANDAGENT_LOG_LEVEL Logging level (debug, info, warn, error)
|
|
340
|
-
|
|
341
|
-
Templates:
|
|
342
|
-
default General-purpose assistant
|
|
343
|
-
coder Optimized for software development
|
|
344
|
-
analyst Optimized for data analysis
|
|
345
|
-
researcher Optimized for research tasks
|
|
346
|
-
|
|
347
|
-
Examples:
|
|
348
|
-
# Run with default template
|
|
349
|
-
sandagent run -- "Create a hello world script"
|
|
350
|
-
|
|
351
|
-
# Run from a template directory (recommended)
|
|
352
|
-
cd templates/coder
|
|
353
|
-
sandagent run -- "Build a REST API with Express"
|
|
354
|
-
|
|
355
|
-
# Use a specific template
|
|
356
|
-
sandagent run --template analyst -- "Analyze sales.csv"
|
|
357
|
-
|
|
358
|
-
# Specify working directory
|
|
359
|
-
sandagent run --cwd ./my-project -- "Fix the bug in main.ts"
|
|
360
|
-
`);
|
|
361
|
-
}
|
|
362
|
-
async function main() {
|
|
363
|
-
const args = parseCliArgs();
|
|
364
|
-
process.chdir(args.cwd);
|
|
365
|
-
await runAgent({
|
|
366
|
-
model: args.model,
|
|
367
|
-
template: args.template,
|
|
368
|
-
userInput: args.userInput,
|
|
369
|
-
systemPrompt: args.systemPrompt,
|
|
370
|
-
maxTurns: args.maxTurns,
|
|
371
|
-
allowedTools: args.allowedTools
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
main().catch((error) => {
|
|
375
|
-
console.error("Fatal error:", error.message);
|
|
376
|
-
process.exit(1);
|
|
377
|
-
});
|