@sandagent/runner-cli 0.2.9 → 0.2.11

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/dist/bundle.mjs CHANGED
@@ -3,9 +3,136 @@
3
3
  // src/cli.ts
4
4
  import { parseArgs } from "node:util";
5
5
 
6
+ // src/build-image.ts
7
+ import { execSync } from "node:child_process";
8
+ import {
9
+ copyFileSync,
10
+ existsSync,
11
+ mkdirSync,
12
+ readFileSync,
13
+ readdirSync,
14
+ statSync,
15
+ writeFileSync
16
+ } from "node:fs";
17
+ import { basename, dirname, join, resolve } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+ function getPackageRoot() {
20
+ const thisDir = dirname(fileURLToPath(import.meta.url));
21
+ return resolve(thisDir, "..");
22
+ }
23
+ function getShippedDockerfile() {
24
+ const packageRoot = getPackageRoot();
25
+ const candidates = [
26
+ join(packageRoot, "Dockerfile"),
27
+ resolve(
28
+ packageRoot,
29
+ "..",
30
+ "..",
31
+ "docker",
32
+ "sandagent-claude",
33
+ "Dockerfile"
34
+ )
35
+ ];
36
+ for (const p of candidates) {
37
+ if (existsSync(p)) return p;
38
+ }
39
+ console.error(
40
+ `\u274C Dockerfile not found. Searched:
41
+ ${candidates.map((c) => ` ${c}`).join("\n")}`
42
+ );
43
+ process.exit(1);
44
+ }
45
+ function run(cmd, cwd) {
46
+ execSync(cmd, { stdio: "inherit", cwd });
47
+ }
48
+ function ensureDocker() {
49
+ try {
50
+ execSync("docker info", { stdio: "ignore" });
51
+ } catch {
52
+ console.error("\u274C Docker is not running. Please start Docker first.");
53
+ process.exit(1);
54
+ }
55
+ }
56
+ function resolveTemplatePath(template) {
57
+ const abs = resolve(process.cwd(), template);
58
+ if (!existsSync(abs)) {
59
+ console.error(`\u274C Template directory not found: ${abs}`);
60
+ process.exit(1);
61
+ }
62
+ return abs;
63
+ }
64
+ function copyDirSync(src, dest) {
65
+ mkdirSync(dest, { recursive: true });
66
+ for (const entry of readdirSync(src)) {
67
+ const srcPath = join(src, entry);
68
+ const destPath = join(dest, entry);
69
+ if (statSync(srcPath).isDirectory()) {
70
+ copyDirSync(srcPath, destPath);
71
+ } else {
72
+ copyFileSync(srcPath, destPath);
73
+ }
74
+ }
75
+ }
76
+ async function buildImage(opts) {
77
+ const templatePath = opts.template ? resolveTemplatePath(opts.template) : null;
78
+ const templateName = templatePath ? basename(templatePath) : null;
79
+ const localImage = opts.image ?? `${opts.name}:${opts.tag}`;
80
+ console.log("\u{1F4E6} SandAgent Docker Image Builder");
81
+ console.log("========================");
82
+ console.log(` Image: ${localImage}`);
83
+ console.log(` Platform: ${opts.platform}`);
84
+ console.log(` Template: ${templateName ?? "(none)"}`);
85
+ console.log(` Push: ${opts.push}`);
86
+ console.log("");
87
+ ensureDocker();
88
+ const buildContext = join(process.cwd(), ".docker-staging");
89
+ mkdirSync(buildContext, { recursive: true });
90
+ let dockerfile = readFileSync(getShippedDockerfile(), "utf8");
91
+ if (templatePath && templateName) {
92
+ const destDir = join(buildContext, "templates", templateName);
93
+ mkdirSync(destDir, { recursive: true });
94
+ const claudeMd = join(templatePath, "CLAUDE.md");
95
+ if (existsSync(claudeMd))
96
+ copyFileSync(claudeMd, join(destDir, "CLAUDE.md"));
97
+ const claudeDir = join(templatePath, ".claude");
98
+ if (existsSync(claudeDir)) copyDirSync(claudeDir, join(destDir, ".claude"));
99
+ let copyLines = "\n# Template files\nRUN mkdir -p /opt/sandagent/templates";
100
+ if (existsSync(join(destDir, "CLAUDE.md"))) {
101
+ copyLines += `
102
+ COPY templates/${templateName}/CLAUDE.md /opt/sandagent/templates/CLAUDE.md`;
103
+ }
104
+ if (existsSync(join(destDir, ".claude"))) {
105
+ copyLines += `
106
+ COPY templates/${templateName}/.claude /opt/sandagent/templates/.claude`;
107
+ }
108
+ dockerfile = dockerfile.replace(/^CMD /m, `${copyLines}
109
+
110
+ CMD `);
111
+ console.log("\u{1F9E9} Injected template files into Dockerfile");
112
+ }
113
+ writeFileSync(join(buildContext, "Dockerfile"), dockerfile);
114
+ console.log("\u{1F433} Building Docker image...");
115
+ run(
116
+ `docker build --platform=${opts.platform} -t ${localImage} -f ${join(buildContext, "Dockerfile")} ${buildContext}`
117
+ );
118
+ console.log(`
119
+ \u2705 Image built: ${localImage}`);
120
+ if (!opts.push) return;
121
+ if (!localImage.includes("/")) {
122
+ console.error(
123
+ "\u274C --push requires --name to include namespace (e.g. vikadata/sandagent-seo)"
124
+ );
125
+ process.exit(1);
126
+ }
127
+ console.log("\u{1F680} Pushing image...");
128
+ run(`docker push ${localImage}`);
129
+ console.log(`
130
+ \u2705 Image pushed: ${localImage}`);
131
+ }
132
+
6
133
  // ../../packages/runner-claude/dist/ai-sdk-stream.js
7
134
  import { writeFile } from "node:fs/promises";
8
- import { join } from "node:path";
135
+ import { join as join2 } from "node:path";
9
136
  var UNKNOWN_TOOL_NAME = "unknown-tool";
10
137
  function formatDataStream(data) {
11
138
  return `data: ${JSON.stringify(data)}
@@ -233,7 +360,7 @@ var AISDKStreamConverter = class {
233
360
  } finally {
234
361
  if (debugMessages.length > 0 && process.env.DEBUG === "true") {
235
362
  const debugDir = options?.cwd ?? process.cwd();
236
- const debugFile = join(debugDir, `claude-message-stream-debug.json`);
363
+ const debugFile = join2(debugDir, `claude-message-stream-debug.json`);
237
364
  writeFile(debugFile, JSON.stringify(debugMessages, null, 2), "utf-8").catch((writeError) => {
238
365
  console.error(`[AISDKStream] Failed to write debug file:`, writeError);
239
366
  });
@@ -286,7 +413,7 @@ function createCanUseToolCallback(claudeOptions) {
286
413
  }
287
414
  } catch {
288
415
  }
289
- await new Promise((resolve) => setTimeout(resolve, 500));
416
+ await new Promise((resolve2) => setTimeout(resolve2, 500));
290
417
  }
291
418
  try {
292
419
  fs.unlinkSync(approvalFile);
@@ -506,7 +633,7 @@ Documentation: https://platform.claude.com/docs/en/agent-sdk/typescript-v2-previ
506
633
  id: textId,
507
634
  delta: word + " "
508
635
  });
509
- await new Promise((resolve) => setTimeout(resolve, 20));
636
+ await new Promise((resolve2) => setTimeout(resolve2, 20));
510
637
  }
511
638
  yield formatDataStream({ type: "text-end", id: textId });
512
639
  yield formatDataStream({
@@ -594,14 +721,41 @@ async function runAgent(options) {
594
721
  }
595
722
 
596
723
  // src/cli.ts
597
- function parseCliArgs() {
724
+ function getSubcommand() {
725
+ for (let i = 2; i < process.argv.length; i++) {
726
+ const a = process.argv[i];
727
+ if (a === "--") break;
728
+ if (!a.startsWith("-")) return a;
729
+ }
730
+ return void 0;
731
+ }
732
+ function getSubSubcommand() {
733
+ let found = 0;
734
+ for (let i = 2; i < process.argv.length; i++) {
735
+ const a = process.argv[i];
736
+ if (a === "--") break;
737
+ if (!a.startsWith("-")) {
738
+ found++;
739
+ if (found === 2) return a;
740
+ }
741
+ }
742
+ return void 0;
743
+ }
744
+ function argsAfterPositionals(n) {
745
+ let found = 0;
746
+ for (let i = 2; i < process.argv.length; i++) {
747
+ if (!process.argv[i].startsWith("-") && process.argv[i] !== "--") {
748
+ found++;
749
+ if (found === n) return process.argv.slice(i + 1);
750
+ }
751
+ }
752
+ return [];
753
+ }
754
+ function parseRunArgs() {
598
755
  const { values, positionals } = parseArgs({
756
+ args: argsAfterPositionals(1),
599
757
  options: {
600
- runner: {
601
- type: "string",
602
- short: "r",
603
- default: "claude"
604
- },
758
+ runner: { type: "string", short: "r", default: "claude" },
605
759
  model: {
606
760
  type: "string",
607
761
  short: "m",
@@ -612,49 +766,26 @@ function parseCliArgs() {
612
766
  short: "c",
613
767
  default: process.env.SANDAGENT_WORKSPACE ?? process.cwd()
614
768
  },
615
- "system-prompt": {
616
- type: "string",
617
- short: "s"
618
- },
619
- "max-turns": {
620
- type: "string",
621
- short: "t"
622
- },
623
- "allowed-tools": {
624
- type: "string",
625
- short: "a"
626
- },
627
- resume: {
628
- type: "string",
629
- short: "r"
630
- },
631
- "output-format": {
632
- type: "string",
633
- short: "o"
634
- },
635
- help: {
636
- type: "boolean",
637
- short: "h"
638
- }
769
+ "system-prompt": { type: "string", short: "s" },
770
+ "max-turns": { type: "string", short: "t" },
771
+ "allowed-tools": { type: "string", short: "a" },
772
+ resume: { type: "string" },
773
+ "output-format": { type: "string", short: "o" },
774
+ help: { type: "boolean", short: "h" }
639
775
  },
640
776
  allowPositionals: true,
641
777
  strict: true
642
778
  });
643
779
  if (values.help) {
644
- printHelp();
780
+ printRunHelp();
645
781
  process.exit(0);
646
782
  }
647
- if (positionals[0] !== "run") {
648
- console.error('Error: Expected "run" command');
649
- console.error('Usage: sandagent run [options] -- "<user input>"');
650
- process.exit(1);
651
- }
652
783
  const dashIndex = process.argv.indexOf("--");
653
784
  let userInput = "";
654
785
  if (dashIndex !== -1 && dashIndex < process.argv.length - 1) {
655
786
  userInput = process.argv.slice(dashIndex + 1).join(" ");
656
- } else if (positionals.length > 1) {
657
- userInput = positionals.slice(1).join(" ");
787
+ } else if (positionals.length > 0) {
788
+ userInput = positionals.join(" ");
658
789
  }
659
790
  if (!userInput) {
660
791
  console.error("Error: User input is required");
@@ -687,57 +818,156 @@ function parseCliArgs() {
687
818
  userInput
688
819
  };
689
820
  }
690
- function printHelp() {
821
+ function parseImageBuildArgs() {
822
+ const { values } = parseArgs({
823
+ args: argsAfterPositionals(2),
824
+ options: {
825
+ name: { type: "string", default: "sandagent" },
826
+ tag: { type: "string", default: "latest" },
827
+ image: { type: "string" },
828
+ platform: { type: "string", default: "linux/amd64" },
829
+ template: { type: "string" },
830
+ push: { type: "boolean", default: false },
831
+ help: { type: "boolean", short: "h" }
832
+ },
833
+ allowPositionals: false,
834
+ strict: true
835
+ });
836
+ if (values.help) {
837
+ printImageBuildHelp();
838
+ process.exit(0);
839
+ }
840
+ return {
841
+ name: values.name,
842
+ tag: values.tag,
843
+ image: values.image,
844
+ platform: values.platform,
845
+ template: values.template,
846
+ push: values.push ?? false
847
+ };
848
+ }
849
+ function printRunHelp() {
691
850
  console.log(`
692
- \u{1F916} SandAgent Runner CLI
851
+ \u{1F916} SandAgent Runner CLI \u2014 run
693
852
 
694
- Like gemini-cli or claude-code - runs locally in your terminal.
695
- Streams AI SDK UI messages directly to stdout.
853
+ Runs an agent locally in your terminal, streaming AI SDK UI messages to stdout.
696
854
 
697
855
  Usage:
698
856
  sandagent run [options] -- "<user input>"
699
857
 
700
858
  Options:
701
- -r, --runner <runner> Runner to use: claude, codex, copilot (default: claude)
702
- -m, --model <model> Model to use (default: claude-sonnet-4-20250514)
703
- -c, --cwd <path> Working directory (default: current directory)
859
+ -r, --runner <runner> Runner: claude, codex, copilot (default: claude)
860
+ -m, --model <model> Model (default: claude-sonnet-4-20250514)
861
+ -c, --cwd <path> Working directory (default: cwd)
704
862
  -s, --system-prompt <prompt> Custom system prompt
705
- -t, --max-turns <n> Maximum conversation turns
706
- -a, --allowed-tools <tools> Comma-separated list of allowed tools
707
- -r, --resume <session-id> Resume a previous session
708
- -o, --output-format <format> Output format (default: stream)
709
- Available: text, json(single result), stream-json(realtime streaming), stream(ai sdk ui sse format)
710
- -h, --help Show this help message
863
+ -t, --max-turns <n> Max conversation turns
864
+ -a, --allowed-tools <tools> Comma-separated allowed tools
865
+ --resume <session-id> Resume a previous session
866
+ -o, --output-format <fmt> text | json | stream-json | stream (default: stream)
867
+ -h, --help Show this help
868
+
869
+ Environment:
870
+ ANTHROPIC_API_KEY Anthropic API key (required)
871
+ SANDAGENT_WORKSPACE Default workspace path
872
+ `);
873
+ }
874
+ function printImageBuildHelp() {
875
+ console.log(`
876
+ \u{1F433} SandAgent Runner CLI \u2014 image build
877
+
878
+ Build (and optionally push) a SandAgent Docker image.
879
+ The image includes Claude Agent SDK + runner-cli pre-installed.
711
880
 
712
- Environment Variables:
713
- ANTHROPIC_API_KEY Anthropic API key (required)
714
- SANDAGENT_WORKSPACE Default workspace path
715
- SANDAGENT_LOG_LEVEL Logging level (debug, info, warn, error)
881
+ Usage:
882
+ sandagent image build [options]
883
+
884
+ Options:
885
+ --name <name> Image name, e.g. vikadata/sandagent-seo (default: sandagent)
886
+ --tag <tag> Image tag (default: latest)
887
+ --image <full> Full image name override (e.g. myorg/myimage:v1)
888
+ --platform <plat> Build platform (default: linux/amd64)
889
+ --template <path> Path to agent template directory to bake into the image
890
+ --push Push image to registry after build
891
+ -h, --help Show this help
716
892
 
717
893
  Examples:
718
- # Run with default settings
719
- sandagent run -- "Create a hello world script"
894
+ sandagent image build --name vikadata/sandagent-seo --tag 0.1.0
895
+ sandagent image build --name vikadata/sandagent-seo --tag 0.1.0 --template ./templates/seo-agent
896
+ sandagent image build --name vikadata/sandagent-seo --tag 0.1.0 --template ./templates/seo-agent --push
897
+ `);
898
+ }
899
+ function printImageHelp() {
900
+ console.log(`
901
+ \u{1F433} SandAgent Runner CLI \u2014 image
720
902
 
721
- # Run with custom system prompt
722
- sandagent run --system-prompt "You are a coding assistant" -- "Build a REST API with Express"
903
+ Manage SandAgent Docker images.
723
904
 
724
- # Specify working directory
725
- sandagent run --cwd ./my-project -- "Fix the bug in main.ts"
905
+ Usage:
906
+ sandagent image <subcommand> [options]
907
+
908
+ Subcommands:
909
+ build Build (and optionally push) a Docker image
910
+
911
+ Run "sandagent image build --help" for build options.
912
+ `);
913
+ }
914
+ function printGlobalHelp() {
915
+ console.log(`
916
+ \u{1F916} SandAgent Runner CLI
917
+
918
+ Usage:
919
+ sandagent <command> [options]
920
+
921
+ Commands:
922
+ run Run an agent locally (streams AI SDK UI messages to stdout)
923
+ image build Build a SandAgent Docker image (with optional --push)
924
+
925
+ Run "sandagent <command> --help" for command-specific options.
726
926
  `);
727
927
  }
728
928
  async function main() {
729
- const args = parseCliArgs();
730
- process.chdir(args.cwd);
731
- await runAgent({
732
- runner: args.runner,
733
- model: args.model,
734
- userInput: args.userInput,
735
- systemPrompt: args.systemPrompt,
736
- maxTurns: args.maxTurns,
737
- allowedTools: args.allowedTools,
738
- resume: args.resume,
739
- outputFormat: args.outputFormat
740
- });
929
+ const sub = getSubcommand();
930
+ if (!sub || sub === "--help" || sub === "-h") {
931
+ printGlobalHelp();
932
+ process.exit(0);
933
+ }
934
+ switch (sub) {
935
+ case "run": {
936
+ const args = parseRunArgs();
937
+ process.chdir(args.cwd);
938
+ await runAgent({
939
+ runner: args.runner,
940
+ model: args.model,
941
+ userInput: args.userInput,
942
+ systemPrompt: args.systemPrompt,
943
+ maxTurns: args.maxTurns,
944
+ allowedTools: args.allowedTools,
945
+ resume: args.resume,
946
+ outputFormat: args.outputFormat
947
+ });
948
+ break;
949
+ }
950
+ case "image": {
951
+ const subSub = getSubSubcommand();
952
+ if (!subSub || subSub === "--help" || subSub === "-h") {
953
+ printImageHelp();
954
+ process.exit(0);
955
+ }
956
+ if (subSub === "build") {
957
+ const args = parseImageBuildArgs();
958
+ await buildImage(args);
959
+ } else {
960
+ console.error(`Unknown image subcommand: ${subSub}`);
961
+ printImageHelp();
962
+ process.exit(1);
963
+ }
964
+ break;
965
+ }
966
+ default:
967
+ console.error(`Unknown command: ${sub}`);
968
+ printGlobalHelp();
969
+ process.exit(1);
970
+ }
741
971
  }
742
972
  main().catch((error) => {
743
973
  console.error("Fatal error:", error.message);
package/dist/cli.d.ts CHANGED
@@ -2,14 +2,9 @@
2
2
  /**
3
3
  * SandAgent Runner CLI
4
4
  *
5
- * Like gemini-cli or claude-code - runs locally in your terminal.
6
- * Streams AI SDK UI messages directly to stdout.
7
- *
8
- * Usage:
9
- * sandagent run [options] -- "<user input>"
10
- *
11
- * The CLI is designed to be executed in a specific working directory
12
- * and outputs AI SDK UI messages directly.
5
+ * Subcommands:
6
+ * sandagent run [options] -- "<user input>" Run an agent locally
7
+ * sandagent image build [options] Build (and optionally push) a Docker image
13
8
  */
14
9
  export {};
15
10
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;GAWG"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG"}