@ai-hero/sandcastle 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +284 -0
  2. package/dist/AgentProvider.d.ts +8 -0
  3. package/dist/AgentProvider.d.ts.map +1 -0
  4. package/dist/AgentProvider.js +60 -0
  5. package/dist/AgentProvider.js.map +1 -0
  6. package/dist/Config.d.ts +35 -0
  7. package/dist/Config.d.ts.map +1 -0
  8. package/dist/Config.js +56 -0
  9. package/dist/Config.js.map +1 -0
  10. package/dist/CopyToSandbox.d.ts +8 -0
  11. package/dist/CopyToSandbox.d.ts.map +1 -0
  12. package/dist/CopyToSandbox.js +32 -0
  13. package/dist/CopyToSandbox.js.map +1 -0
  14. package/dist/Display.d.ts +52 -0
  15. package/dist/Display.d.ts.map +1 -0
  16. package/dist/Display.js +132 -0
  17. package/dist/Display.js.map +1 -0
  18. package/dist/DockerLifecycle.d.ts +37 -0
  19. package/dist/DockerLifecycle.d.ts.map +1 -0
  20. package/dist/DockerLifecycle.js +109 -0
  21. package/dist/DockerLifecycle.js.map +1 -0
  22. package/dist/DockerSandbox.d.ts +6 -0
  23. package/dist/DockerSandbox.d.ts.map +1 -0
  24. package/dist/DockerSandbox.js +122 -0
  25. package/dist/DockerSandbox.js.map +1 -0
  26. package/dist/EnvResolver.d.ts +11 -0
  27. package/dist/EnvResolver.d.ts.map +1 -0
  28. package/dist/EnvResolver.js +43 -0
  29. package/dist/EnvResolver.js.map +1 -0
  30. package/dist/ErrorHandler.d.ts +15 -0
  31. package/dist/ErrorHandler.d.ts.map +1 -0
  32. package/dist/ErrorHandler.js +58 -0
  33. package/dist/ErrorHandler.js.map +1 -0
  34. package/dist/FilesystemSandbox.d.ts +6 -0
  35. package/dist/FilesystemSandbox.d.ts.map +1 -0
  36. package/dist/FilesystemSandbox.js +83 -0
  37. package/dist/FilesystemSandbox.js.map +1 -0
  38. package/dist/InitService.d.ts +11 -0
  39. package/dist/InitService.d.ts.map +1 -0
  40. package/dist/InitService.js +111 -0
  41. package/dist/InitService.js.map +1 -0
  42. package/dist/Orchestrator.d.ts +49 -0
  43. package/dist/Orchestrator.d.ts.map +1 -0
  44. package/dist/Orchestrator.js +155 -0
  45. package/dist/Orchestrator.js.map +1 -0
  46. package/dist/PromptArgumentSubstitution.d.ts +6 -0
  47. package/dist/PromptArgumentSubstitution.d.ts.map +1 -0
  48. package/dist/PromptArgumentSubstitution.js +33 -0
  49. package/dist/PromptArgumentSubstitution.js.map +1 -0
  50. package/dist/PromptPreprocessor.d.ts +7 -0
  51. package/dist/PromptPreprocessor.d.ts.map +1 -0
  52. package/dist/PromptPreprocessor.js +34 -0
  53. package/dist/PromptPreprocessor.js.map +1 -0
  54. package/dist/PromptResolver.d.ts +9 -0
  55. package/dist/PromptResolver.d.ts.map +1 -0
  56. package/dist/PromptResolver.js +26 -0
  57. package/dist/PromptResolver.js.map +1 -0
  58. package/dist/RecoveryMessage.d.ts +15 -0
  59. package/dist/RecoveryMessage.d.ts.map +1 -0
  60. package/dist/RecoveryMessage.js +81 -0
  61. package/dist/RecoveryMessage.js.map +1 -0
  62. package/dist/Sandbox.d.ts +23 -0
  63. package/dist/Sandbox.d.ts.map +1 -0
  64. package/dist/Sandbox.js +5 -0
  65. package/dist/Sandbox.js.map +1 -0
  66. package/dist/SandboxFactory.d.ts +56 -0
  67. package/dist/SandboxFactory.d.ts.map +1 -0
  68. package/dist/SandboxFactory.js +219 -0
  69. package/dist/SandboxFactory.js.map +1 -0
  70. package/dist/SandboxLifecycle.d.ts +32 -0
  71. package/dist/SandboxLifecycle.d.ts.map +1 -0
  72. package/dist/SandboxLifecycle.js +152 -0
  73. package/dist/SandboxLifecycle.js.map +1 -0
  74. package/dist/SyncService.d.ts +20 -0
  75. package/dist/SyncService.d.ts.map +1 -0
  76. package/dist/SyncService.js +504 -0
  77. package/dist/SyncService.js.map +1 -0
  78. package/dist/TokenResolver.d.ts +6 -0
  79. package/dist/TokenResolver.d.ts.map +1 -0
  80. package/dist/TokenResolver.js +43 -0
  81. package/dist/TokenResolver.js.map +1 -0
  82. package/dist/WorktreeManager.d.ts +42 -0
  83. package/dist/WorktreeManager.d.ts.map +1 -0
  84. package/dist/WorktreeManager.js +170 -0
  85. package/dist/WorktreeManager.js.map +1 -0
  86. package/dist/cli.d.ts +22 -0
  87. package/dist/cli.d.ts.map +1 -0
  88. package/dist/cli.js +217 -0
  89. package/dist/cli.js.map +1 -0
  90. package/dist/errors.d.ts +95 -0
  91. package/dist/errors.d.ts.map +1 -0
  92. package/dist/errors.js +35 -0
  93. package/dist/errors.js.map +1 -0
  94. package/dist/index.d.ts +4 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +2 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/main.d.ts +3 -0
  99. package/dist/main.d.ts.map +1 -0
  100. package/dist/main.js +16 -0
  101. package/dist/main.js.map +1 -0
  102. package/dist/run.d.ts +91 -0
  103. package/dist/run.d.ts.map +1 -0
  104. package/dist/run.js +155 -0
  105. package/dist/run.js.map +1 -0
  106. package/dist/templates/blank/main.ts +9 -0
  107. package/dist/templates/blank/prompt.md +12 -0
  108. package/dist/templates/blank/template.json +4 -0
  109. package/dist/templates/parallel-planner/implement-prompt.md +62 -0
  110. package/dist/templates/parallel-planner/main.ts +200 -0
  111. package/dist/templates/parallel-planner/merge-prompt.md +22 -0
  112. package/dist/templates/parallel-planner/plan-prompt.md +33 -0
  113. package/dist/templates/parallel-planner/template.json +4 -0
  114. package/dist/templates/sequential-reviewer/implement-prompt.md +62 -0
  115. package/dist/templates/sequential-reviewer/main.ts +102 -0
  116. package/dist/templates/sequential-reviewer/review-prompt.md +43 -0
  117. package/dist/templates/sequential-reviewer/template.json +4 -0
  118. package/dist/templates/simple-loop/main.ts +37 -0
  119. package/dist/templates/simple-loop/prompt.md +51 -0
  120. package/dist/templates/simple-loop/template.json +4 -0
  121. package/dist/templates.d.ts +2 -0
  122. package/dist/templates.d.ts.map +1 -0
  123. package/dist/templates.js +26 -0
  124. package/dist/templates.js.map +1 -0
  125. package/dist/terminalCleanup.d.ts +30 -0
  126. package/dist/terminalCleanup.d.ts.map +1 -0
  127. package/dist/terminalCleanup.js +37 -0
  128. package/dist/terminalCleanup.js.map +1 -0
  129. package/dist/testSandbox.d.ts +8 -0
  130. package/dist/testSandbox.d.ts.map +1 -0
  131. package/dist/testSandbox.js +101 -0
  132. package/dist/testSandbox.js.map +1 -0
  133. package/package.json +62 -0
package/README.md ADDED
@@ -0,0 +1,284 @@
1
+ # Sandcastle
2
+
3
+ A TypeScript CLI for orchestrating AI coding agents in isolated Docker containers. Sandcastle handles the hard parts — building worktrees, invoking the agent, and merging commits back — so you can run AFK agents with a single `run()`.
4
+
5
+ ## Prerequisites
6
+
7
+ - [Docker Desktop](https://www.docker.com/)
8
+ - [Git](https://git-scm.com/)
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ pnpm add @ai-hero/sandcastle
14
+ ```
15
+
16
+ ## Quick start
17
+
18
+ ```bash
19
+ # 1. Initialize — scaffolds .sandcastle/ config directory and builds the Docker image
20
+ cd /path/to/your/repo
21
+ npx sandcastle init
22
+
23
+ # 2. Set up environment variables in .sandcastle/.env
24
+ cp .sandcastle/.env.example .sandcastle/.env
25
+ # Edit .sandcastle/.env and fill in your values
26
+ ```
27
+
28
+ ```typescript
29
+ // 3. Run the agent via the JS API
30
+ import { run } from "@ai-hero/sandcastle";
31
+
32
+ await run({
33
+ promptFile: ".sandcastle/prompt.md",
34
+ });
35
+ ```
36
+
37
+ ```bash
38
+ npx tsx main.ts
39
+ ```
40
+
41
+ ## CLI commands
42
+
43
+ ### `sandcastle init`
44
+
45
+ Scaffolds the `.sandcastle/` config directory and builds the Docker image. This is the first command you run in a new repo.
46
+
47
+ | Option | Required | Default | Description |
48
+ | -------------- | -------- | ------------------ | ------------------------------------------ |
49
+ | `--image-name` | No | `sandcastle:local` | Docker image name |
50
+ | `--agent` | No | `claude-code` | Agent provider to use (e.g. `claude-code`) |
51
+
52
+ Creates the following files:
53
+
54
+ ```
55
+ .sandcastle/
56
+ ├── Dockerfile # Sandbox environment (customize as needed)
57
+ ├── prompt.md # Agent instructions
58
+ ├── .env.example # Token placeholders
59
+ └── .gitignore # Ignores .env, patches/, logs/
60
+ ```
61
+
62
+ Errors if `.sandcastle/` already exists to prevent overwriting customizations.
63
+
64
+ ### `sandcastle build-image`
65
+
66
+ Rebuilds the Docker image from an existing `.sandcastle/` directory. Use this after modifying the Dockerfile.
67
+
68
+ | Option | Required | Default | Description |
69
+ | -------------- | -------- | ------------------ | --------------------------------------------------------------------------------- |
70
+ | `--image-name` | No | `sandcastle:local` | Docker image name |
71
+ | `--dockerfile` | No | — | Path to a custom Dockerfile (build context will be the current working directory) |
72
+
73
+ ### `sandcastle interactive`
74
+
75
+ Opens an interactive Claude Code session inside the sandbox. Syncs your repo in, launches Claude with TTY passthrough, and syncs changes back when you exit.
76
+
77
+ | Option | Required | Default | Description |
78
+ | -------------- | -------- | ------------------ | -------------------------- |
79
+ | `--image-name` | No | `sandcastle:local` | Docker image name |
80
+ | `--model` | No | `claude-opus-4-6` | Model to use for the agent |
81
+ | `--agent` | No | `claude-code` | Agent provider to use |
82
+
83
+ ### `sandcastle remove-image`
84
+
85
+ Removes the Docker image.
86
+
87
+ | Option | Required | Default | Description |
88
+ | -------------- | -------- | ------------------ | ----------------- |
89
+ | `--image-name` | No | `sandcastle:local` | Docker image name |
90
+
91
+ ## Prompts
92
+
93
+ Sandcastle uses a flexible prompt system. You write the prompt, and the engine executes it — no opinions about workflow, task management, or context sources are imposed.
94
+
95
+ ### Prompt resolution
96
+
97
+ The prompt is resolved from one of three sources (in order of precedence):
98
+
99
+ 1. `prompt: "inline string"` — pass an inline prompt directly via `RunOptions`
100
+ 2. `promptFile: "./path/to/prompt.md"` — point to a specific file via `RunOptions`
101
+ 3. `.sandcastle/prompt.md` — default location (created by `sandcastle init`)
102
+
103
+ `prompt` and `promptFile` are mutually exclusive — providing both is an error.
104
+
105
+ ### Dynamic context with `` !`command` ``
106
+
107
+ Use `` !`command` `` expressions in your prompt to pull in dynamic context. Each expression is replaced with the command's stdout before the prompt is sent to the agent.
108
+
109
+ Commands run **inside the sandbox** after sync-in and `onSandboxReady` hooks, so they see the same repo state the agent sees (including installed dependencies).
110
+
111
+ ```markdown
112
+ # Open issues
113
+
114
+ !`gh issue list --state open --json number,title,body,comments,labels --limit 20`
115
+
116
+ # Recent commits
117
+
118
+ !`git log --oneline -10`
119
+ ```
120
+
121
+ If any command exits with a non-zero code, the run fails immediately with an error.
122
+
123
+ ### Prompt arguments with `{{KEY}}`
124
+
125
+ Use `{{KEY}}` placeholders in your prompt to inject values from the `promptArgs` option. This is useful for reusing the same prompt file across multiple runs with different parameters.
126
+
127
+ ```typescript
128
+ import { run } from "@ai-hero/sandcastle";
129
+
130
+ await run({
131
+ promptFile: "./my-prompt.md",
132
+ promptArgs: { ISSUE_NUMBER: 42, PRIORITY: "high" },
133
+ });
134
+ ```
135
+
136
+ In the prompt file:
137
+
138
+ ```markdown
139
+ Work on issue #{{ISSUE_NUMBER}} (priority: {{PRIORITY}}).
140
+ ```
141
+
142
+ Prompt argument substitution runs on the host before shell expression expansion, so `{{KEY}}` placeholders inside `` !`command` `` expressions are replaced first:
143
+
144
+ ```markdown
145
+ !`gh issue view {{ISSUE_NUMBER}} --json body -q .body`
146
+ ```
147
+
148
+ A `{{KEY}}` placeholder with no matching prompt argument is an error. Unused prompt arguments produce a warning.
149
+
150
+ ### Early termination with `<promise>COMPLETE</promise>`
151
+
152
+ When the agent outputs `<promise>COMPLETE</promise>`, the orchestrator stops the iteration loop early. This is a convention you document in your prompt for the agent to follow — the engine never injects it.
153
+
154
+ This is useful for task-based workflows where the agent should stop once it has finished, rather than running all remaining iterations.
155
+
156
+ ### Templates
157
+
158
+ `sandcastle init` prompts you to choose a template, which scaffolds a ready-to-use prompt and `main.ts` suited to a specific workflow. Four templates are available:
159
+
160
+ | Template | Description |
161
+ | --------------------- | ----------------------------------------------------------------------- |
162
+ | `blank` | Bare scaffold — write your own prompt and orchestration |
163
+ | `simple-loop` | Picks GitHub issues one by one and closes them |
164
+ | `sequential-reviewer` | Implements issues one by one, with a code review step after each |
165
+ | `parallel-planner` | Plans parallelizable issues, executes on separate branches, then merges |
166
+
167
+ Select a template during `sandcastle init` when prompted, or re-run init in a fresh repo to try a different one.
168
+
169
+ ## Node API
170
+
171
+ Sandcastle exports a programmatic `run()` function for use in Node.js scripts, CI pipelines, or custom tooling.
172
+
173
+ ```typescript
174
+ import { run } from "@ai-hero/sandcastle";
175
+
176
+ const result = await run({
177
+ promptFile: "./my-prompt.md",
178
+ maxIterations: 3,
179
+ branch: "agent/fix-123",
180
+ });
181
+
182
+ console.log(result.iterationsRun); // number of iterations executed
183
+ console.log(result.wasCompletionSignalDetected); // true if agent emitted <promise>COMPLETE</promise>
184
+ console.log(result.commits); // array of { sha } for commits created
185
+ console.log(result.branch); // target branch name
186
+ ```
187
+
188
+ ### `RunOptions`
189
+
190
+ | Option | Type | Default | Description |
191
+ | ------------------ | ---------- | ----------------------------- | -------------------------------------------------------------- |
192
+ | `prompt` | string | — | Inline prompt (mutually exclusive with `promptFile`) |
193
+ | `promptFile` | string | `.sandcastle/prompt.md` | Path to prompt file (mutually exclusive with `prompt`) |
194
+ | `maxIterations` | number | `5` | Maximum iterations to run |
195
+ | `hooks` | object | — | Lifecycle hooks (`onSandboxReady`) |
196
+ | `branch` | string | — | Target branch for sandbox work |
197
+ | `model` | string | `claude-opus-4-6` | Model to use for the agent |
198
+ | `agent` | string | `claude-code` | Agent provider name |
199
+ | `imageName` | string | `sandcastle:local` | Docker image name for the sandbox |
200
+ | `promptArgs` | PromptArgs | — | Key-value map for `{{KEY}}` placeholder substitution |
201
+ | `logging` | object | file (auto-generated) | `{ type: 'file', path }` or `{ type: 'stdout' }` |
202
+ | `completionSignal` | string | `<promise>COMPLETE</promise>` | Custom string the agent emits to stop the iteration loop early |
203
+ | `timeoutSeconds` | number | `900` | Timeout for the entire run in seconds |
204
+
205
+ ### `RunResult`
206
+
207
+ | Field | Type | Description |
208
+ | ----------------------------- | ----------- | -------------------------------------------------- |
209
+ | `iterationsRun` | number | Number of iterations that were executed |
210
+ | `wasCompletionSignalDetected` | boolean | Whether the agent signaled completion |
211
+ | `stdout` | string | Agent output |
212
+ | `commits` | `{ sha }[]` | Commits created during the run |
213
+ | `branch` | string | Target branch name |
214
+ | `logFilePath` | string? | Path to the log file (only when logging to a file) |
215
+
216
+ Environment variables are resolved automatically from `.sandcastle/.env` and `process.env` — no need to pass them to the API. The required variables depend on the **agent provider** (see `sandcastle init` output for details).
217
+
218
+ ## Configuration
219
+
220
+ ### Config directory (`.sandcastle/`)
221
+
222
+ All per-repo sandbox configuration lives in `.sandcastle/`. Run `sandcastle init` to create it.
223
+
224
+ ### Custom Dockerfile
225
+
226
+ The `.sandcastle/Dockerfile` controls the sandbox environment. The default template installs:
227
+
228
+ - **Node.js 22** (base image)
229
+ - **git**, **curl**, **jq** (system dependencies)
230
+ - **GitHub CLI** (`gh`)
231
+ - **Claude Code CLI**
232
+ - A non-root `agent` user (required — Claude runs as this user)
233
+
234
+ When customizing the Dockerfile, ensure you keep:
235
+
236
+ - A non-root user (the default `agent` user) for Claude to run as
237
+ - `git` (required for sync-in/sync-out)
238
+ - `gh` (required for issue fetching)
239
+ - Claude Code CLI installed and on PATH
240
+
241
+ Add your project-specific dependencies (e.g., language runtimes, build tools) to the Dockerfile as needed.
242
+
243
+ ### Hooks
244
+
245
+ Hooks are arrays of `{ "command": "..." }` objects executed sequentially inside the sandbox. If any command exits with a non-zero code, execution stops immediately with an error.
246
+
247
+ | Hook | When it runs | Working directory |
248
+ | ---------------- | ----------------------- | ---------------------- |
249
+ | `onSandboxReady` | After sync-in completes | Sandbox repo directory |
250
+
251
+ **`onSandboxReady`** runs after the repo is synced in. Use it for dependency installation or build steps (e.g., `npm install`).
252
+
253
+ Pass hooks programmatically via `run()`:
254
+
255
+ ```ts
256
+ await run({
257
+ hooks: {
258
+ onSandboxReady: [{ command: "npm install" }],
259
+ },
260
+ // ...
261
+ });
262
+ ```
263
+
264
+ ## How it works
265
+
266
+ Sandcastle uses git primitives for reliable repo synchronization:
267
+
268
+ - **Sync-in**: Creates a `git bundle` on your host capturing all refs (including unpushed commits), copies it into the sandbox, and unpacks it. The sandbox always matches your host's committed state.
269
+ - **Sync-out**: Runs `git format-patch` inside the sandbox to extract new commits, copies the patches to your host, and applies them with `git am --3way`. Uncommitted changes (staged, unstaged, and untracked files) are also captured.
270
+
271
+ This approach avoids GitHub round-trips and produces clean, replayable commit history.
272
+
273
+ ## Development
274
+
275
+ ```bash
276
+ npm install
277
+ npm run build # Build with tsgo
278
+ npm test # Run tests with vitest
279
+ npm run typecheck # Type-check
280
+ ```
281
+
282
+ ## License
283
+
284
+ MIT
@@ -0,0 +1,8 @@
1
+ export interface AgentProvider {
2
+ readonly name: string;
3
+ readonly envManifest: Record<string, string>;
4
+ readonly dockerfileTemplate: string;
5
+ }
6
+ export declare const claudeCodeProvider: AgentProvider;
7
+ export declare const getAgentProvider: (name: string) => AgentProvider;
8
+ //# sourceMappingURL=AgentProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentProvider.d.ts","sourceRoot":"","sources":["../src/AgentProvider.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AA2CD,eAAO,MAAM,kBAAkB,EAAE,aAShC,CAAC;AAMF,eAAO,MAAM,gBAAgB,iCAQ5B,CAAC"}
@@ -0,0 +1,60 @@
1
+ import { SANDBOX_WORKSPACE_DIR } from "./SandboxFactory.js";
2
+ const CLAUDE_CODE_DOCKERFILE = `FROM node:22-bookworm
3
+
4
+ # Install system dependencies
5
+ RUN apt-get update && apt-get install -y \\
6
+ git \\
7
+ curl \\
8
+ jq \\
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Enable corepack (pnpm, yarn)
12
+ RUN corepack enable
13
+
14
+ # Install GitHub CLI
15
+ RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \\
16
+ | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \\
17
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \\
18
+ | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \\
19
+ && apt-get update && apt-get install -y gh \\
20
+ && rm -rf /var/lib/apt/lists/*
21
+
22
+ # Create a non-root user for Claude to run as
23
+ RUN useradd -m -s /bin/bash agent
24
+ USER agent
25
+
26
+ # Install Claude Code CLI
27
+ RUN curl -fsSL https://claude.ai/install.sh | bash
28
+
29
+ # Add Claude to PATH
30
+ ENV PATH="/home/agent/.local/bin:$PATH"
31
+
32
+ # Create repos directory
33
+ RUN mkdir -p /home/agent/repos
34
+
35
+ WORKDIR /home/agent
36
+
37
+ # In worktree sandbox mode, Sandcastle bind-mounts the git worktree at ${SANDBOX_WORKSPACE_DIR}
38
+ # and overrides the working directory to ${SANDBOX_WORKSPACE_DIR} at container start.
39
+ # Structure your Dockerfile so that ${SANDBOX_WORKSPACE_DIR} can serve as the project root.
40
+ ENTRYPOINT ["sleep", "infinity"]
41
+ `;
42
+ export const claudeCodeProvider = {
43
+ name: "claude-code",
44
+ envManifest: {
45
+ ANTHROPIC_API_KEY: "Anthropic API key",
46
+ GH_TOKEN: "GitHub personal access token",
47
+ },
48
+ dockerfileTemplate: CLAUDE_CODE_DOCKERFILE,
49
+ };
50
+ const AGENT_REGISTRY = {
51
+ "claude-code": claudeCodeProvider,
52
+ };
53
+ export const getAgentProvider = (name) => {
54
+ const provider = AGENT_REGISTRY[name];
55
+ if (!provider) {
56
+ throw new Error(`Unknown agent provider: "${name}". Available providers: ${Object.keys(AGENT_REGISTRY).join(", ")}`);
57
+ }
58
+ return provider;
59
+ };
60
+ //# sourceMappingURL=AgentProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentProvider.js","sourceRoot":"","sources":["../src/AgentProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAQ5D,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yEAmC0C,qBAAqB;2CACnD,qBAAqB;sCAC1B,qBAAqB;;CAE1D,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAkB;IAC/C,IAAI,EAAE,aAAa;IAEnB,WAAW,EAAE;QACX,iBAAiB,EAAE,mBAAmB;QACtC,QAAQ,EAAE,8BAA8B;KACzC;IAED,kBAAkB,EAAE,sBAAsB;CAC3C,CAAC;AAEF,MAAM,cAAc,GAAkC;IACpD,aAAa,EAAE,kBAAkB;CAClC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAY,EAAiB,EAAE;IAC9D,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,4BAA4B,IAAI,2BAA2B,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACpG,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { FileSystem } from "@effect/platform";
2
+ import { Effect, Schema } from "effect";
3
+ declare const HookDefinition: Schema.Struct<{
4
+ command: typeof Schema.String;
5
+ }>;
6
+ declare const SandcastleConfigSchema: Schema.Struct<{
7
+ hooks: Schema.optional<Schema.Struct<{
8
+ onSandboxReady: Schema.optional<Schema.Array$<Schema.Struct<{
9
+ command: typeof Schema.String;
10
+ }>>>;
11
+ }>>;
12
+ defaultMaxIterations: Schema.optional<typeof Schema.Number>;
13
+ model: Schema.optional<typeof Schema.String>;
14
+ agent: Schema.optional<typeof Schema.String>;
15
+ imageName: Schema.optional<typeof Schema.String>;
16
+ }>;
17
+ export type HookDefinition = typeof HookDefinition.Type;
18
+ export type SandcastleConfig = typeof SandcastleConfigSchema.Type;
19
+ export declare class ConfigError extends Error {
20
+ readonly _tag = "ConfigError";
21
+ constructor(message: string);
22
+ }
23
+ export declare const readConfig: (repoDir: string) => Effect.Effect<{
24
+ readonly hooks?: {
25
+ readonly onSandboxReady?: readonly {
26
+ readonly command: string;
27
+ }[] | undefined;
28
+ } | undefined;
29
+ readonly defaultMaxIterations?: number | undefined;
30
+ readonly model?: string | undefined;
31
+ readonly agent?: string | undefined;
32
+ readonly imageName?: string | undefined;
33
+ }, ConfigError, FileSystem.FileSystem>;
34
+ export {};
35
+ //# sourceMappingURL=Config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../src/Config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGxC,QAAA,MAAM,cAAc;;EAEuB,CAAC;AAE5C,QAAA,MAAM,sBAAsB;;;;;;;;;;EAUiB,CAAC;AAE9C,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC;AACxD,MAAM,MAAM,gBAAgB,GAAG,OAAO,sBAAsB,CAAC,IAAI,CAAC;AAElE,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,iBAAiB;IAC9B,YAAY,OAAO,EAAE,MAAM,EAE1B;CACF;AAUD,eAAO,MAAM,UAAU;;;;;;;;;;sCAoCnB,CAAC"}
package/dist/Config.js ADDED
@@ -0,0 +1,56 @@
1
+ import { FileSystem } from "@effect/platform";
2
+ import { Effect, Schema } from "effect";
3
+ import { join } from "node:path";
4
+ const HookDefinition = Schema.Struct({
5
+ command: Schema.String,
6
+ }).annotations({ title: "HookDefinition" });
7
+ const SandcastleConfigSchema = Schema.Struct({
8
+ hooks: Schema.optional(Schema.Struct({
9
+ onSandboxReady: Schema.optional(Schema.Array(HookDefinition)),
10
+ })),
11
+ defaultMaxIterations: Schema.optional(Schema.Number),
12
+ model: Schema.optional(Schema.String),
13
+ agent: Schema.optional(Schema.String),
14
+ imageName: Schema.optional(Schema.String),
15
+ }).annotations({ title: "SandcastleConfig" });
16
+ export class ConfigError extends Error {
17
+ _tag = "ConfigError";
18
+ constructor(message) {
19
+ super(message);
20
+ }
21
+ }
22
+ const RENAMED_KEYS = {
23
+ defaultIterations: "defaultMaxIterations",
24
+ };
25
+ const decodeConfig = Schema.decodeUnknownEither(SandcastleConfigSchema, {
26
+ onExcessProperty: "error",
27
+ });
28
+ export const readConfig = (repoDir) => Effect.gen(function* () {
29
+ const fs = yield* FileSystem.FileSystem;
30
+ const content = yield* fs
31
+ .readFileString(join(repoDir, ".sandcastle", "config.json"))
32
+ .pipe(Effect.catchAll(() => Effect.succeed(null)));
33
+ if (content === null)
34
+ return {};
35
+ let raw;
36
+ try {
37
+ raw = JSON.parse(content);
38
+ }
39
+ catch {
40
+ return yield* Effect.fail(new ConfigError("Invalid JSON in config.json"));
41
+ }
42
+ // Check for renamed keys and produce clear errors
43
+ if (typeof raw === "object" && raw !== null) {
44
+ for (const [oldKey, newKey] of Object.entries(RENAMED_KEYS)) {
45
+ if (oldKey in raw) {
46
+ return yield* Effect.fail(new ConfigError(`"${oldKey}" has been renamed to "${newKey}" in config.json. Please update your config.`));
47
+ }
48
+ }
49
+ }
50
+ const result = decodeConfig(raw);
51
+ if (result._tag === "Left") {
52
+ return yield* Effect.fail(new ConfigError(result.left.message));
53
+ }
54
+ return result.right;
55
+ });
56
+ //# sourceMappingURL=Config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Config.js","sourceRoot":"","sources":["../src/Config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { Effect } from "effect";
2
+ /**
3
+ * Copy files and directories from the host repo root to the worktree root,
4
+ * using `cp -R --reflink=auto` for copy-on-write when the filesystem supports it.
5
+ * Missing paths are silently skipped.
6
+ */
7
+ export declare const copyToSandbox: (paths: string[], hostRepoDir: string, worktreePath: string) => Effect.Effect<void, never, never>;
8
+ //# sourceMappingURL=CopyToSandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CopyToSandbox.d.ts","sourceRoot":"","sources":["../src/CopyToSandbox.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC;;;;GAIG;AACH,eAAO,MAAM,aAAa,mGAyBtB,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { Effect } from "effect";
5
+ /**
6
+ * Copy files and directories from the host repo root to the worktree root,
7
+ * using `cp -R --reflink=auto` for copy-on-write when the filesystem supports it.
8
+ * Missing paths are silently skipped.
9
+ */
10
+ export const copyToSandbox = (paths, hostRepoDir, worktreePath) => Effect.gen(function* () {
11
+ for (const relativePath of paths) {
12
+ const src = join(hostRepoDir, relativePath);
13
+ if (!existsSync(src)) {
14
+ continue;
15
+ }
16
+ const dest = join(worktreePath, relativePath);
17
+ yield* Effect.async((resume) => {
18
+ execFile("cp", ["-R", "--reflink=auto", src, dest], (error) => {
19
+ if (error) {
20
+ // Fall back to a regular copy if reflink is not supported
21
+ execFile("cp", ["-R", src, dest], () => {
22
+ resume(Effect.succeed(undefined));
23
+ });
24
+ }
25
+ else {
26
+ resume(Effect.succeed(undefined));
27
+ }
28
+ });
29
+ });
30
+ }
31
+ });
32
+ //# sourceMappingURL=CopyToSandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CopyToSandbox.js","sourceRoot":"","sources":["../src/CopyToSandbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,KAAe,EACf,WAAmB,EACnB,YAAoB,EACQ,EAAE,CAC9B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,KAAK,MAAM,YAAY,IAAI,KAAK,EAAE,CAAC;QACjC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;QAC5C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;QAC9C,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAc,CAAC,MAAM,EAAE,EAAE;YAC1C,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE;gBAC5D,IAAI,KAAK,EAAE,CAAC;oBACV,0DAA0D;oBAC1D,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE;wBACrC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;oBACpC,CAAC,CAAC,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;gBACpC,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { FileSystem } from "@effect/platform";
2
+ import { Context, Effect, Layer, Ref } from "effect";
3
+ export type Severity = "info" | "success" | "warn" | "error";
4
+ export type DisplayEntry = {
5
+ readonly _tag: "intro";
6
+ readonly title: string;
7
+ } | {
8
+ readonly _tag: "status";
9
+ readonly message: string;
10
+ readonly severity: Severity;
11
+ } | {
12
+ readonly _tag: "spinner";
13
+ readonly message: string;
14
+ } | {
15
+ readonly _tag: "summary";
16
+ readonly title: string;
17
+ readonly rows: Record<string, string>;
18
+ } | {
19
+ readonly _tag: "taskLog";
20
+ readonly title: string;
21
+ readonly messages: ReadonlyArray<string>;
22
+ } | {
23
+ readonly _tag: "text";
24
+ readonly message: string;
25
+ };
26
+ export interface DisplayService {
27
+ readonly intro: (title: string) => Effect.Effect<void>;
28
+ readonly status: (message: string, severity: Severity) => Effect.Effect<void>;
29
+ readonly spinner: <A, E, R>(message: string, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
30
+ readonly summary: (title: string, rows: Record<string, string>) => Effect.Effect<void>;
31
+ readonly taskLog: <A, E, R>(title: string, effect: (message: (msg: string) => void) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
32
+ readonly text: (message: string) => Effect.Effect<void>;
33
+ }
34
+ declare const Display_base: Context.TagClass<Display, "Display", DisplayService>;
35
+ export declare class Display extends Display_base {
36
+ }
37
+ export declare const SilentDisplay: {
38
+ layer: (ref: Ref.Ref<readonly DisplayEntry[]>) => Layer.Layer<Display, never, never>;
39
+ };
40
+ export declare const FileDisplay: {
41
+ layer: (filePath: string) => Layer.Layer<Display, never, FileSystem.FileSystem>;
42
+ };
43
+ export declare const terminalStyle: {
44
+ status: (message: string) => string;
45
+ summaryTitle: (title: string) => string;
46
+ summaryRow: (key: string, value: string) => string;
47
+ };
48
+ export declare const ClackDisplay: {
49
+ layer: Layer.Layer<Display, never, never>;
50
+ };
51
+ export {};
52
+ //# sourceMappingURL=Display.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Display.d.ts","sourceRoot":"","sources":["../src/Display.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAGrD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AAE7D,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAClD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACtD;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC1C,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAEvD,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAE9E,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EACxB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAE5B,QAAQ,CAAC,OAAO,EAAE,CAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACzB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAEzB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EACxB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC/D,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAE5B,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CACzD;;AAED,qBAAa,OAAQ,SAAQ,YAG1B;CAAG;AAEN,eAAO,MAAM,aAAa;;CAuDzB,CAAC;AAEF,eAAO,MAAM,WAAW;;CA6DvB,CAAC;AASF,eAAO,MAAM,aAAa;;;;CAKzB,CAAC;AAEF,eAAO,MAAM,YAAY;;CAoDxB,CAAC"}