ghostrail 0.0.0
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/NOTICE +16 -0
- package/README.md +56 -0
- package/dist/agent/claude-code.d.ts +37 -0
- package/dist/agent/claude-code.d.ts.map +1 -0
- package/dist/agent/claude-code.js +74 -0
- package/dist/agent/claude-code.js.map +1 -0
- package/dist/agent/index.d.ts +4 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +4 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/prompt.d.ts +9 -0
- package/dist/agent/prompt.d.ts.map +1 -0
- package/dist/agent/prompt.js +15 -0
- package/dist/agent/prompt.js.map +1 -0
- package/dist/agent/result.d.ts +16 -0
- package/dist/agent/result.d.ts.map +1 -0
- package/dist/agent/result.js +56 -0
- package/dist/agent/result.js.map +1 -0
- package/dist/args.d.ts +27 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +25 -0
- package/dist/args.js.map +1 -0
- package/dist/backend/container.d.ts +18 -0
- package/dist/backend/container.d.ts.map +1 -0
- package/dist/backend/container.js +70 -0
- package/dist/backend/container.js.map +1 -0
- package/dist/backend/docker.d.ts +22 -0
- package/dist/backend/docker.d.ts.map +1 -0
- package/dist/backend/docker.js +38 -0
- package/dist/backend/docker.js.map +1 -0
- package/dist/backend/fs.d.ts +9 -0
- package/dist/backend/fs.d.ts.map +1 -0
- package/dist/backend/fs.js +21 -0
- package/dist/backend/fs.js.map +1 -0
- package/dist/backend/index.d.ts +7 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +6 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/local-worktree.d.ts +17 -0
- package/dist/backend/local-worktree.d.ts.map +1 -0
- package/dist/backend/local-worktree.js +51 -0
- package/dist/backend/local-worktree.js.map +1 -0
- package/dist/backend/proc.d.ts +24 -0
- package/dist/backend/proc.d.ts.map +1 -0
- package/dist/backend/proc.js +103 -0
- package/dist/backend/proc.js.map +1 -0
- package/dist/backend/types.d.ts +75 -0
- package/dist/backend/types.d.ts.map +1 -0
- package/dist/backend/types.js +12 -0
- package/dist/backend/types.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +10 -0
- package/dist/bin.js.map +1 -0
- package/dist/board/index.d.ts +4 -0
- package/dist/board/index.d.ts.map +1 -0
- package/dist/board/index.js +4 -0
- package/dist/board/index.js.map +1 -0
- package/dist/board/page.d.ts +3 -0
- package/dist/board/page.d.ts.map +1 -0
- package/dist/board/page.js +55 -0
- package/dist/board/page.js.map +1 -0
- package/dist/board/server.d.ts +36 -0
- package/dist/board/server.d.ts.map +1 -0
- package/dist/board/server.js +110 -0
- package/dist/board/server.js.map +1 -0
- package/dist/board/stats.d.ts +26 -0
- package/dist/board/stats.d.ts.map +1 -0
- package/dist/board/stats.js +57 -0
- package/dist/board/stats.js.map +1 -0
- package/dist/cli/commands.d.ts +39 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +224 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +45 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/help.d.ts +6 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +29 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/config/duration.d.ts +14 -0
- package/dist/config/duration.d.ts.map +1 -0
- package/dist/config/duration.js +25 -0
- package/dist/config/duration.js.map +1 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +4 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/load.d.ts +26 -0
- package/dist/config/load.d.ts.map +1 -0
- package/dist/config/load.js +306 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/result.d.ts +18 -0
- package/dist/config/result.d.ts.map +1 -0
- package/dist/config/result.js +7 -0
- package/dist/config/result.js.map +1 -0
- package/dist/config/schema.d.ts +77 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +35 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/factory/build.d.ts +39 -0
- package/dist/factory/build.d.ts.map +1 -0
- package/dist/factory/build.js +85 -0
- package/dist/factory/build.js.map +1 -0
- package/dist/gate/gate.d.ts +12 -0
- package/dist/gate/gate.d.ts.map +1 -0
- package/dist/gate/gate.js +22 -0
- package/dist/gate/gate.js.map +1 -0
- package/dist/gate/index.d.ts +2 -0
- package/dist/gate/index.d.ts.map +1 -0
- package/dist/gate/index.js +2 -0
- package/dist/gate/index.js.map +1 -0
- package/dist/global/index.d.ts +3 -0
- package/dist/global/index.d.ts.map +1 -0
- package/dist/global/index.js +3 -0
- package/dist/global/index.js.map +1 -0
- package/dist/global/stage.d.ts +13 -0
- package/dist/global/stage.d.ts.map +1 -0
- package/dist/global/stage.js +24 -0
- package/dist/global/stage.js.map +1 -0
- package/dist/global/store.d.ts +30 -0
- package/dist/global/store.d.ts.map +1 -0
- package/dist/global/store.js +65 -0
- package/dist/global/store.js.map +1 -0
- package/dist/lock/file-lock.d.ts +16 -0
- package/dist/lock/file-lock.d.ts.map +1 -0
- package/dist/lock/file-lock.js +51 -0
- package/dist/lock/file-lock.js.map +1 -0
- package/dist/loop/branch.d.ts +14 -0
- package/dist/loop/branch.d.ts.map +1 -0
- package/dist/loop/branch.js +24 -0
- package/dist/loop/branch.js.map +1 -0
- package/dist/loop/engine.d.ts +37 -0
- package/dist/loop/engine.d.ts.map +1 -0
- package/dist/loop/engine.js +204 -0
- package/dist/loop/engine.js.map +1 -0
- package/dist/loop/index.d.ts +6 -0
- package/dist/loop/index.d.ts.map +1 -0
- package/dist/loop/index.js +3 -0
- package/dist/loop/index.js.map +1 -0
- package/dist/loop/ports.d.ts +97 -0
- package/dist/loop/ports.d.ts.map +1 -0
- package/dist/loop/ports.js +2 -0
- package/dist/loop/ports.js.map +1 -0
- package/dist/loop/record.d.ts +26 -0
- package/dist/loop/record.d.ts.map +1 -0
- package/dist/loop/record.js +2 -0
- package/dist/loop/record.js.map +1 -0
- package/dist/publish/gh.d.ts +24 -0
- package/dist/publish/gh.d.ts.map +1 -0
- package/dist/publish/gh.js +109 -0
- package/dist/publish/gh.js.map +1 -0
- package/dist/publish/githost.d.ts +54 -0
- package/dist/publish/githost.d.ts.map +1 -0
- package/dist/publish/githost.js +33 -0
- package/dist/publish/githost.js.map +1 -0
- package/dist/publish/github-publisher.d.ts +30 -0
- package/dist/publish/github-publisher.d.ts.map +1 -0
- package/dist/publish/github-publisher.js +51 -0
- package/dist/publish/github-publisher.js.map +1 -0
- package/dist/publish/index.d.ts +4 -0
- package/dist/publish/index.d.ts.map +1 -0
- package/dist/publish/index.js +4 -0
- package/dist/publish/index.js.map +1 -0
- package/dist/respond/index.d.ts +4 -0
- package/dist/respond/index.d.ts.map +1 -0
- package/dist/respond/index.js +4 -0
- package/dist/respond/index.js.map +1 -0
- package/dist/respond/respond.d.ts +42 -0
- package/dist/respond/respond.d.ts.map +1 -0
- package/dist/respond/respond.js +128 -0
- package/dist/respond/respond.js.map +1 -0
- package/dist/respond/select.d.ts +22 -0
- package/dist/respond/select.d.ts.map +1 -0
- package/dist/respond/select.js +31 -0
- package/dist/respond/select.js.map +1 -0
- package/dist/respond/store.d.ts +25 -0
- package/dist/respond/store.d.ts.map +1 -0
- package/dist/respond/store.js +49 -0
- package/dist/respond/store.js.map +1 -0
- package/dist/secrets/redact.d.ts +11 -0
- package/dist/secrets/redact.d.ts.map +1 -0
- package/dist/secrets/redact.js +21 -0
- package/dist/secrets/redact.js.map +1 -0
- package/dist/source/index.d.ts +2 -0
- package/dist/source/index.d.ts.map +1 -0
- package/dist/source/index.js +2 -0
- package/dist/source/index.js.map +1 -0
- package/dist/source/linear.d.ts +61 -0
- package/dist/source/linear.d.ts.map +1 -0
- package/dist/source/linear.js +166 -0
- package/dist/source/linear.js.map +1 -0
- package/dist/store/events.d.ts +105 -0
- package/dist/store/events.d.ts.map +1 -0
- package/dist/store/events.js +64 -0
- package/dist/store/events.js.map +1 -0
- package/dist/store/file-store.d.ts +49 -0
- package/dist/store/file-store.d.ts.map +1 -0
- package/dist/store/file-store.js +188 -0
- package/dist/store/file-store.js.map +1 -0
- package/dist/store/index.d.ts +3 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +3 -0
- package/dist/store/index.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +21 -0
- package/dist/version.js.map +1 -0
- package/package.json +52 -0
- package/templates/content-loop/README.md +23 -0
- package/templates/content-loop/ghostrail.toml +36 -0
- package/templates/content-loop/prompts/draft.md +29 -0
- package/templates/fix-bugs-local/README.md +23 -0
- package/templates/fix-bugs-local/ghostrail.toml +37 -0
- package/templates/fix-bugs-local/prompts/fix-bug.md +27 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { DEFAULT_CONTAINER_IMAGE, dockerCpArgs, dockerExecArgs, dockerRmArgs, dockerRunArgs, } from "./docker.js";
|
|
6
|
+
import { spawnCollect } from "./proc.js";
|
|
7
|
+
const KIND = "container";
|
|
8
|
+
async function docker(args, timeoutMs) {
|
|
9
|
+
return spawnCollect(["docker", ...args], timeoutMs === undefined ? {} : { timeoutMs });
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The recommended default for a shared tool: a fresh, disposable container per
|
|
13
|
+
* item, on its own branch, with a clean filesystem and nothing bleeding between
|
|
14
|
+
* runs. The repo is cloned on the host onto the new branch and copied into the
|
|
15
|
+
* container, so the image only needs a shell (not git); commands run via
|
|
16
|
+
* `docker exec`. Teardown force-removes the container.
|
|
17
|
+
*/
|
|
18
|
+
export class ContainerBackend {
|
|
19
|
+
kind = KIND;
|
|
20
|
+
/** True if the Docker CLI and daemon are reachable. */
|
|
21
|
+
static async isAvailable() {
|
|
22
|
+
const result = await docker(["version", "--format", "{{.Server.Version}}"], 10_000);
|
|
23
|
+
return result.exitCode === 0;
|
|
24
|
+
}
|
|
25
|
+
async provision(spec) {
|
|
26
|
+
const id = randomUUID();
|
|
27
|
+
const name = `ghostrail-${id}`;
|
|
28
|
+
const image = spec.image ?? DEFAULT_CONTAINER_IMAGE;
|
|
29
|
+
const hostClone = join(tmpdir(), "ghostrail-cc", id);
|
|
30
|
+
await mkdir(join(tmpdir(), "ghostrail-cc"), { recursive: true });
|
|
31
|
+
try {
|
|
32
|
+
const clone = await spawnCollect(["git", "clone", "--quiet", spec.repoPath, hostClone]);
|
|
33
|
+
if (clone.exitCode !== 0)
|
|
34
|
+
throw new Error(`git clone failed: ${clone.stderr.trim()}`);
|
|
35
|
+
// "create" makes a new branch off the base; "attach" checks out an existing branch.
|
|
36
|
+
const checkoutArgs = spec.mode === "attach"
|
|
37
|
+
? ["checkout", spec.branch]
|
|
38
|
+
: ["checkout", "-B", spec.branch, spec.baseBranch];
|
|
39
|
+
const branch = await spawnCollect(["git", "-C", hostClone, ...checkoutArgs]);
|
|
40
|
+
if (branch.exitCode !== 0)
|
|
41
|
+
throw new Error(`git checkout failed: ${branch.stderr.trim()}`);
|
|
42
|
+
const run = await docker(dockerRunArgs(name, image));
|
|
43
|
+
if (run.exitCode !== 0)
|
|
44
|
+
throw new Error(`docker run failed: ${run.stderr.trim()}`);
|
|
45
|
+
const cp = await docker(dockerCpArgs(`${hostClone}/.`, name));
|
|
46
|
+
if (cp.exitCode !== 0) {
|
|
47
|
+
await docker(dockerRmArgs(name));
|
|
48
|
+
throw new Error(`docker cp failed: ${cp.stderr.trim()}`);
|
|
49
|
+
}
|
|
50
|
+
return { id, backend: KIND, ref: name, branch: spec.branch };
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
await rm(hostClone, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exec(workspace, command, options = {}) {
|
|
57
|
+
return docker(dockerExecArgs(workspace.ref, command, options), options.timeoutMs);
|
|
58
|
+
}
|
|
59
|
+
stream(workspace, command, onLog, options = {}) {
|
|
60
|
+
const args = dockerExecArgs(workspace.ref, command, options);
|
|
61
|
+
return spawnCollect(["docker", ...args], {
|
|
62
|
+
onLog,
|
|
63
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async teardown(workspace) {
|
|
67
|
+
await docker(dockerRmArgs(workspace.ref));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=container.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/backend/container.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ExecOptions } from "./types.js";
|
|
2
|
+
/** The image used when a container backend config gives none. */
|
|
3
|
+
export declare const DEFAULT_CONTAINER_IMAGE = "alpine:3";
|
|
4
|
+
/** Working directory inside every container. */
|
|
5
|
+
export declare const CONTAINER_WORKDIR = "/work";
|
|
6
|
+
/**
|
|
7
|
+
* Build `docker run` args for a long-lived, detached container that idles until
|
|
8
|
+
* torn down. The container is named `name`, works out of {@link CONTAINER_WORKDIR},
|
|
9
|
+
* and runs `sleep infinity` so commands can be `docker exec`'d into it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function dockerRunArgs(name: string, image: string): string[];
|
|
12
|
+
/**
|
|
13
|
+
* Build `docker exec` args to run `command` inside container `name`.
|
|
14
|
+
* A relative `options.cwd` is resolved under {@link CONTAINER_WORKDIR}; each
|
|
15
|
+
* `options.env` entry becomes a `-e KEY=VALUE` flag, in insertion order.
|
|
16
|
+
*/
|
|
17
|
+
export declare function dockerExecArgs(name: string, command: readonly string[], options?: ExecOptions): string[];
|
|
18
|
+
/** Build `docker cp` args to copy host `source` into `name`'s work dir. */
|
|
19
|
+
export declare function dockerCpArgs(source: string, name: string): string[];
|
|
20
|
+
/** Build `docker rm -f` args to force-remove container `name`. */
|
|
21
|
+
export declare function dockerRmArgs(name: string): string[];
|
|
22
|
+
//# sourceMappingURL=docker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../src/backend/docker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,iEAAiE;AACjE,eAAO,MAAM,uBAAuB,aAAa,CAAC;AAElD,gDAAgD;AAChD,eAAO,MAAM,iBAAiB,UAAU,CAAC;AAEzC;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAEnE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,GAAE,WAAgB,GACxB,MAAM,EAAE,CAYV;AAED,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEnE;AAED,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEnD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** The image used when a container backend config gives none. */
|
|
2
|
+
export const DEFAULT_CONTAINER_IMAGE = "alpine:3";
|
|
3
|
+
/** Working directory inside every container. */
|
|
4
|
+
export const CONTAINER_WORKDIR = "/work";
|
|
5
|
+
/**
|
|
6
|
+
* Build `docker run` args for a long-lived, detached container that idles until
|
|
7
|
+
* torn down. The container is named `name`, works out of {@link CONTAINER_WORKDIR},
|
|
8
|
+
* and runs `sleep infinity` so commands can be `docker exec`'d into it.
|
|
9
|
+
*/
|
|
10
|
+
export function dockerRunArgs(name, image) {
|
|
11
|
+
return ["run", "-d", "--name", name, "-w", CONTAINER_WORKDIR, image, "sleep", "infinity"];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Build `docker exec` args to run `command` inside container `name`.
|
|
15
|
+
* A relative `options.cwd` is resolved under {@link CONTAINER_WORKDIR}; each
|
|
16
|
+
* `options.env` entry becomes a `-e KEY=VALUE` flag, in insertion order.
|
|
17
|
+
*/
|
|
18
|
+
export function dockerExecArgs(name, command, options = {}) {
|
|
19
|
+
const args = ["exec"];
|
|
20
|
+
const workdir = options.cwd !== undefined ? `${CONTAINER_WORKDIR}/${options.cwd}` : CONTAINER_WORKDIR;
|
|
21
|
+
args.push("-w", workdir);
|
|
22
|
+
if (options.env) {
|
|
23
|
+
for (const [key, value] of Object.entries(options.env)) {
|
|
24
|
+
args.push("-e", `${key}=${value}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
args.push(name, ...command);
|
|
28
|
+
return args;
|
|
29
|
+
}
|
|
30
|
+
/** Build `docker cp` args to copy host `source` into `name`'s work dir. */
|
|
31
|
+
export function dockerCpArgs(source, name) {
|
|
32
|
+
return ["cp", source, `${name}:${CONTAINER_WORKDIR}`];
|
|
33
|
+
}
|
|
34
|
+
/** Build `docker rm -f` args to force-remove container `name`. */
|
|
35
|
+
export function dockerRmArgs(name) {
|
|
36
|
+
return ["rm", "-f", name];
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=docker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker.js","sourceRoot":"","sources":["../../src/backend/docker.ts"],"names":[],"mappings":"AAEA,iEAAiE;AACjE,MAAM,CAAC,MAAM,uBAAuB,GAAG,UAAU,CAAC;AAElD,gDAAgD;AAChD,MAAM,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAEzC;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,KAAa;IACvD,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AAC5F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,OAA0B,EAC1B,UAAuB,EAAE;IAEzB,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB,MAAM,OAAO,GACX,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,iBAAiB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACxF,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACzB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAChB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACvD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,CAAC;IAC5B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,IAAY;IACvD,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,IAAI,iBAAiB,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Backend, Workspace } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Write `content` to `path` (relative to the workspace root) through the backend,
|
|
4
|
+
* creating parent directories. Content is base64-encoded and decoded in the
|
|
5
|
+
* workspace, so it is shell-safe for any bytes and needs no stdin (which
|
|
6
|
+
* {@link Backend.exec} does not provide). Works identically on every backend.
|
|
7
|
+
*/
|
|
8
|
+
export declare function writeWorkspaceFile(backend: Backend, workspace: Workspace, path: string, content: string): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/backend/fs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAOrD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAUf"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Single-quote a string for safe use in a `sh -c` script. */
|
|
2
|
+
function shellQuote(value) {
|
|
3
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Write `content` to `path` (relative to the workspace root) through the backend,
|
|
7
|
+
* creating parent directories. Content is base64-encoded and decoded in the
|
|
8
|
+
* workspace, so it is shell-safe for any bytes and needs no stdin (which
|
|
9
|
+
* {@link Backend.exec} does not provide). Works identically on every backend.
|
|
10
|
+
*/
|
|
11
|
+
export async function writeWorkspaceFile(backend, workspace, path, content) {
|
|
12
|
+
const encoded = Buffer.from(content, "utf8").toString("base64");
|
|
13
|
+
const quotedPath = shellQuote(path);
|
|
14
|
+
const script = `mkdir -p "$(dirname ${quotedPath})" && ` +
|
|
15
|
+
`printf %s ${shellQuote(encoded)} | base64 -d > ${quotedPath}`;
|
|
16
|
+
const result = await backend.exec(workspace, ["sh", "-c", script]);
|
|
17
|
+
if (result.exitCode !== 0) {
|
|
18
|
+
throw new Error(`writeWorkspaceFile(${path}) failed: ${result.stderr.trim()}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/backend/fs.ts"],"names":[],"mappings":"AAEA,8DAA8D;AAC9D,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AAC7C,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAgB,EAChB,SAAoB,EACpB,IAAY,EACZ,OAAe;IAEf,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChE,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,MAAM,GACV,uBAAuB,UAAU,QAAQ;QACzC,aAAa,UAAU,CAAC,OAAO,CAAC,kBAAkB,UAAU,EAAE,CAAC;IACjE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACnE,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,aAAa,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACjF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ContainerBackend } from "./container.js";
|
|
2
|
+
export { writeWorkspaceFile } from "./fs.js";
|
|
3
|
+
export { CONTAINER_WORKDIR, DEFAULT_CONTAINER_IMAGE, dockerCpArgs, dockerExecArgs, dockerRmArgs, dockerRunArgs, } from "./docker.js";
|
|
4
|
+
export { LocalWorktreeBackend } from "./local-worktree.js";
|
|
5
|
+
export { splitStream } from "./proc.js";
|
|
6
|
+
export type { Backend, ExecOptions, ExecResult, LogLine, ProvisionSpec, Workspace, } from "./types.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,YAAY,EACV,OAAO,EACP,WAAW,EACX,UAAU,EACV,OAAO,EACP,aAAa,EACb,SAAS,GACV,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ContainerBackend } from "./container.js";
|
|
2
|
+
export { writeWorkspaceFile } from "./fs.js";
|
|
3
|
+
export { CONTAINER_WORKDIR, DEFAULT_CONTAINER_IMAGE, dockerCpArgs, dockerExecArgs, dockerRmArgs, dockerRunArgs, } from "./docker.js";
|
|
4
|
+
export { LocalWorktreeBackend } from "./local-worktree.js";
|
|
5
|
+
export { splitStream } from "./proc.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Backend, ExecOptions, ExecResult, LogLine, ProvisionSpec, Workspace } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The zero-infrastructure default backend: a git worktree in the current
|
|
4
|
+
* environment. Fastest and least isolated, best for a trusted solo repo. Each
|
|
5
|
+
* provision adds a worktree on a fresh branch off the base; teardown removes it.
|
|
6
|
+
*/
|
|
7
|
+
export declare class LocalWorktreeBackend implements Backend {
|
|
8
|
+
readonly kind = "local-worktree";
|
|
9
|
+
/** repoPath per workspace id, needed to remove the worktree at teardown. */
|
|
10
|
+
private readonly repoByWorkspace;
|
|
11
|
+
provision(spec: ProvisionSpec): Promise<Workspace>;
|
|
12
|
+
exec(workspace: Workspace, command: readonly string[], options?: ExecOptions): Promise<ExecResult>;
|
|
13
|
+
stream(workspace: Workspace, command: readonly string[], onLog: (line: LogLine) => void, options?: ExecOptions): Promise<ExecResult>;
|
|
14
|
+
teardown(workspace: Workspace): Promise<void>;
|
|
15
|
+
private execOptions;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=local-worktree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-worktree.d.ts","sourceRoot":"","sources":["../../src/backend/local-worktree.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,OAAO,EACP,WAAW,EACX,UAAU,EACV,OAAO,EACP,aAAa,EACb,SAAS,EACV,MAAM,YAAY,CAAC;AAIpB;;;;GAIG;AACH,qBAAa,oBAAqB,YAAW,OAAO;IAClD,QAAQ,CAAC,IAAI,oBAAQ;IACrB,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAEvD,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,SAAS,CAAC;IAqBxD,IAAI,CACF,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,UAAU,CAAC;IAItB,MAAM,CACJ,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,EAC9B,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,UAAU,CAAC;IAIhB,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAUnD,OAAO,CAAC,WAAW;CAIpB"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { spawnCollect } from "./proc.js";
|
|
6
|
+
const KIND = "local-worktree";
|
|
7
|
+
/**
|
|
8
|
+
* The zero-infrastructure default backend: a git worktree in the current
|
|
9
|
+
* environment. Fastest and least isolated, best for a trusted solo repo. Each
|
|
10
|
+
* provision adds a worktree on a fresh branch off the base; teardown removes it.
|
|
11
|
+
*/
|
|
12
|
+
export class LocalWorktreeBackend {
|
|
13
|
+
kind = KIND;
|
|
14
|
+
/** repoPath per workspace id, needed to remove the worktree at teardown. */
|
|
15
|
+
repoByWorkspace = new Map();
|
|
16
|
+
async provision(spec) {
|
|
17
|
+
const id = randomUUID();
|
|
18
|
+
const dir = join(tmpdir(), "ghostrail-wt", id);
|
|
19
|
+
await mkdir(join(tmpdir(), "ghostrail-wt"), { recursive: true });
|
|
20
|
+
// "create" makes a new branch off the base; "attach" checks out an existing branch.
|
|
21
|
+
const addArgs = spec.mode === "attach"
|
|
22
|
+
? ["worktree", "add", dir, spec.branch]
|
|
23
|
+
: ["worktree", "add", "-b", spec.branch, dir, spec.baseBranch];
|
|
24
|
+
const result = await spawnCollect(["git", "-C", spec.repoPath, ...addArgs]);
|
|
25
|
+
if (result.exitCode !== 0) {
|
|
26
|
+
throw new Error(`local-worktree provision failed: ${result.stderr.trim() || result.stdout.trim()}`);
|
|
27
|
+
}
|
|
28
|
+
this.repoByWorkspace.set(id, spec.repoPath);
|
|
29
|
+
return { id, backend: KIND, ref: dir, branch: spec.branch };
|
|
30
|
+
}
|
|
31
|
+
exec(workspace, command, options = {}) {
|
|
32
|
+
return spawnCollect(command, this.execOptions(workspace, options));
|
|
33
|
+
}
|
|
34
|
+
stream(workspace, command, onLog, options = {}) {
|
|
35
|
+
return spawnCollect(command, { ...this.execOptions(workspace, options), onLog });
|
|
36
|
+
}
|
|
37
|
+
async teardown(workspace) {
|
|
38
|
+
const repoPath = this.repoByWorkspace.get(workspace.id);
|
|
39
|
+
if (repoPath !== undefined) {
|
|
40
|
+
await spawnCollect(["git", "-C", repoPath, "worktree", "remove", "--force", workspace.ref]);
|
|
41
|
+
await spawnCollect(["git", "-C", repoPath, "worktree", "prune"]);
|
|
42
|
+
this.repoByWorkspace.delete(workspace.id);
|
|
43
|
+
}
|
|
44
|
+
await rm(workspace.ref, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
execOptions(workspace, options) {
|
|
47
|
+
const cwd = options.cwd !== undefined ? join(workspace.ref, options.cwd) : workspace.ref;
|
|
48
|
+
return { ...options, cwd };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=local-worktree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-worktree.js","sourceRoot":"","sources":["../../src/backend/local-worktree.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ExecOptions, ExecResult, LogLine } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Split a running text stream into complete lines.
|
|
4
|
+
*
|
|
5
|
+
* Given the leftover `pending` text from the previous chunk and a new `chunk`,
|
|
6
|
+
* return the complete lines finished by this chunk (without their trailing
|
|
7
|
+
* newline) and the new `pending` remainder (text after the last newline, which
|
|
8
|
+
* may be empty). Handles `\n` and `\r\n`. A chunk with no newline yields no
|
|
9
|
+
* lines and grows `pending`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function splitStream(pending: string, chunk: string): {
|
|
12
|
+
lines: string[];
|
|
13
|
+
pending: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Spawn a command, collect its output, and resolve an {@link ExecResult}.
|
|
17
|
+
* Never rejects: a spawn error resolves with a non-zero exit code and the error
|
|
18
|
+
* text on stderr. If `onLog` is given, each complete output line is delivered as
|
|
19
|
+
* it arrives. A `timeoutMs` kills the process and sets `timedOut`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function spawnCollect(command: readonly string[], options?: ExecOptions & {
|
|
22
|
+
onLog?: (line: LogLine) => void;
|
|
23
|
+
}): Promise<ExecResult>;
|
|
24
|
+
//# sourceMappingURL=proc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proc.d.ts","sourceRoot":"","sources":["../../src/backend/proc.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEnE;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAMhG;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,GAAE,WAAW,GAAG;IAAE,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;CAAO,GAC9D,OAAO,CAAC,UAAU,CAAC,CA+ErB"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
/**
|
|
4
|
+
* Split a running text stream into complete lines.
|
|
5
|
+
*
|
|
6
|
+
* Given the leftover `pending` text from the previous chunk and a new `chunk`,
|
|
7
|
+
* return the complete lines finished by this chunk (without their trailing
|
|
8
|
+
* newline) and the new `pending` remainder (text after the last newline, which
|
|
9
|
+
* may be empty). Handles `\n` and `\r\n`. A chunk with no newline yields no
|
|
10
|
+
* lines and grows `pending`.
|
|
11
|
+
*/
|
|
12
|
+
export function splitStream(pending, chunk) {
|
|
13
|
+
const combined = pending + chunk;
|
|
14
|
+
const parts = combined.split("\n");
|
|
15
|
+
const rest = parts.pop() ?? "";
|
|
16
|
+
const lines = parts.map((line) => (line.endsWith("\r") ? line.slice(0, -1) : line));
|
|
17
|
+
return { lines, pending: rest };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Spawn a command, collect its output, and resolve an {@link ExecResult}.
|
|
21
|
+
* Never rejects: a spawn error resolves with a non-zero exit code and the error
|
|
22
|
+
* text on stderr. If `onLog` is given, each complete output line is delivered as
|
|
23
|
+
* it arrives. A `timeoutMs` kills the process and sets `timedOut`.
|
|
24
|
+
*/
|
|
25
|
+
export function spawnCollect(command, options = {}) {
|
|
26
|
+
const [file, ...args] = command;
|
|
27
|
+
const start = Date.now();
|
|
28
|
+
if (file === undefined) {
|
|
29
|
+
return Promise.resolve({
|
|
30
|
+
exitCode: 1,
|
|
31
|
+
stdout: "",
|
|
32
|
+
stderr: "spawnCollect: empty command",
|
|
33
|
+
durationMs: 0,
|
|
34
|
+
timedOut: false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
// detached: the child leads its own process group, so a timeout can kill the
|
|
39
|
+
// whole tree (shell + its children), not just the shell — otherwise a killed
|
|
40
|
+
// shell's grandchildren keep the pipes open and the call hangs.
|
|
41
|
+
const child = spawn(file, args, {
|
|
42
|
+
cwd: options.cwd,
|
|
43
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
44
|
+
detached: true,
|
|
45
|
+
});
|
|
46
|
+
const killTree = () => {
|
|
47
|
+
if (child.pid === undefined)
|
|
48
|
+
return;
|
|
49
|
+
try {
|
|
50
|
+
process.kill(-child.pid, "SIGKILL");
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
child.kill("SIGKILL");
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
let stdout = "";
|
|
57
|
+
let stderr = "";
|
|
58
|
+
let timedOut = false;
|
|
59
|
+
let settled = false;
|
|
60
|
+
const pending = { stdout: "", stderr: "" };
|
|
61
|
+
const onData = (which) => (buf) => {
|
|
62
|
+
const text = buf.toString("utf8");
|
|
63
|
+
if (which === "stdout")
|
|
64
|
+
stdout += text;
|
|
65
|
+
else
|
|
66
|
+
stderr += text;
|
|
67
|
+
if (options.onLog) {
|
|
68
|
+
const split = splitStream(pending[which], text);
|
|
69
|
+
pending[which] = split.pending;
|
|
70
|
+
for (const line of split.lines)
|
|
71
|
+
options.onLog({ stream: which, text: line });
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
child.stdout?.on("data", onData("stdout"));
|
|
75
|
+
child.stderr?.on("data", onData("stderr"));
|
|
76
|
+
const timer = options.timeoutMs !== undefined
|
|
77
|
+
? setTimeout(() => {
|
|
78
|
+
timedOut = true;
|
|
79
|
+
killTree();
|
|
80
|
+
}, options.timeoutMs)
|
|
81
|
+
: undefined;
|
|
82
|
+
const finish = (exitCode) => {
|
|
83
|
+
if (settled)
|
|
84
|
+
return;
|
|
85
|
+
settled = true;
|
|
86
|
+
if (timer)
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
if (options.onLog) {
|
|
89
|
+
for (const which of ["stdout", "stderr"]) {
|
|
90
|
+
if (pending[which].length > 0)
|
|
91
|
+
options.onLog({ stream: which, text: pending[which] });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
resolve({ exitCode, stdout, stderr, durationMs: Date.now() - start, timedOut });
|
|
95
|
+
};
|
|
96
|
+
child.on("error", (error) => {
|
|
97
|
+
stderr += `${error instanceof Error ? error.message : String(error)}\n`;
|
|
98
|
+
finish(127);
|
|
99
|
+
});
|
|
100
|
+
child.on("close", (code) => finish(timedOut ? 124 : (code ?? 0)));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=proc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proc.js","sourceRoot":"","sources":["../../src/backend/proc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The backend adapter contract: the isolation seam and the heart of
|
|
3
|
+
* "optionality for back ends". Every backend (local-worktree, container,
|
|
4
|
+
* and later Firecracker/E2B/remote) implements the same five verbs so the loop,
|
|
5
|
+
* the agent, and the gate never care where a run physically executes.
|
|
6
|
+
*
|
|
7
|
+
* The five verbs from the research spec map onto this interface as:
|
|
8
|
+
* provision -> provision(), exec -> exec(), stream -> stream(),
|
|
9
|
+
* result -> the resolved ExecResult, teardown -> teardown().
|
|
10
|
+
*/
|
|
11
|
+
/** How to create an isolated workspace for one item. */
|
|
12
|
+
export interface ProvisionSpec {
|
|
13
|
+
/** Absolute path to the source git repository on the host. */
|
|
14
|
+
repoPath: string;
|
|
15
|
+
/** Existing ref to branch from (e.g. "main"). Ignored when `mode` is "attach". */
|
|
16
|
+
baseBranch: string;
|
|
17
|
+
/** The branch to work on: created when `mode` is "create", checked out when "attach". */
|
|
18
|
+
branch: string;
|
|
19
|
+
/**
|
|
20
|
+
* "create" (default) makes a new `branch` off `baseBranch`; "attach" checks out
|
|
21
|
+
* an existing `branch` (e.g. a PR's head branch, for the respond pass).
|
|
22
|
+
*/
|
|
23
|
+
mode?: "create" | "attach";
|
|
24
|
+
/** Container image, for backends that need one. Ignored by local-worktree. */
|
|
25
|
+
image?: string;
|
|
26
|
+
}
|
|
27
|
+
/** A provisioned, isolated working copy. Opaque handle addressed by `ref`. */
|
|
28
|
+
export interface Workspace {
|
|
29
|
+
/** Unique id for this provisioning. */
|
|
30
|
+
id: string;
|
|
31
|
+
/** The backend kind that produced it. */
|
|
32
|
+
backend: string;
|
|
33
|
+
/**
|
|
34
|
+
* How the backend addresses the workspace: a host directory for
|
|
35
|
+
* local-worktree, a container name for container. Not assumed host-readable.
|
|
36
|
+
*/
|
|
37
|
+
ref: string;
|
|
38
|
+
/** The branch the work happens on. */
|
|
39
|
+
branch: string;
|
|
40
|
+
}
|
|
41
|
+
export interface ExecOptions {
|
|
42
|
+
/** Directory to run in, relative to the workspace root. */
|
|
43
|
+
cwd?: string;
|
|
44
|
+
/** Extra environment variables for the command. */
|
|
45
|
+
env?: Record<string, string>;
|
|
46
|
+
/** Kill the command and set `timedOut` after this many milliseconds. */
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
}
|
|
49
|
+
/** The outcome of running a command in a workspace (the "result" verb). */
|
|
50
|
+
export interface ExecResult {
|
|
51
|
+
exitCode: number;
|
|
52
|
+
stdout: string;
|
|
53
|
+
stderr: string;
|
|
54
|
+
durationMs: number;
|
|
55
|
+
/** True when the command was killed for exceeding `timeoutMs`. */
|
|
56
|
+
timedOut: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** One line of streamed command output. */
|
|
59
|
+
export interface LogLine {
|
|
60
|
+
stream: "stdout" | "stderr";
|
|
61
|
+
text: string;
|
|
62
|
+
}
|
|
63
|
+
/** A pluggable isolation backend. */
|
|
64
|
+
export interface Backend {
|
|
65
|
+
readonly kind: string;
|
|
66
|
+
/** Create an isolated workspace on `spec.branch`, based on `spec.baseBranch`. */
|
|
67
|
+
provision(spec: ProvisionSpec): Promise<Workspace>;
|
|
68
|
+
/** Run a command to completion in the workspace and return its result. */
|
|
69
|
+
exec(workspace: Workspace, command: readonly string[], options?: ExecOptions): Promise<ExecResult>;
|
|
70
|
+
/** Like {@link Backend.exec}, but delivers each output line to `onLog` as it arrives. */
|
|
71
|
+
stream(workspace: Workspace, command: readonly string[], onLog: (line: LogLine) => void, options?: ExecOptions): Promise<ExecResult>;
|
|
72
|
+
/** Destroy the workspace and its resources. Idempotent: a second call is a no-op. */
|
|
73
|
+
teardown(workspace: Workspace): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/backend/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;IACnB,yFAAyF;IACzF,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC3B,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACxB,uCAAuC;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,2CAA2C;AAC3C,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qCAAqC;AACrC,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACnD,0EAA0E;IAC1E,IAAI,CACF,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,UAAU,CAAC,CAAC;IACvB,yFAAyF;IACzF,MAAM,CACJ,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,EAC9B,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,UAAU,CAAC,CAAC;IACvB,qFAAqF;IACrF,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The backend adapter contract: the isolation seam and the heart of
|
|
3
|
+
* "optionality for back ends". Every backend (local-worktree, container,
|
|
4
|
+
* and later Firecracker/E2B/remote) implements the same five verbs so the loop,
|
|
5
|
+
* the agent, and the gate never care where a run physically executes.
|
|
6
|
+
*
|
|
7
|
+
* The five verbs from the research spec map onto this interface as:
|
|
8
|
+
* provision -> provision(), exec -> exec(), stream -> stream(),
|
|
9
|
+
* result -> the resolved ExecResult, teardown -> teardown().
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/backend/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { run } from "./cli.js";
|
|
4
|
+
run(process.argv.slice(2))
|
|
5
|
+
.then((code) => process.exit(code))
|
|
6
|
+
.catch((error) => {
|
|
7
|
+
process.stderr.write(`ghostrail: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KACvB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KAClC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/board/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/board/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,iBAAiB,EAAE,UAAU,EAAc,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAc,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** The self-contained board page: polls the read API and renders runs + stats. */
|
|
2
|
+
export declare const PAGE_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>ghostrail board</title>\n<style>\n :root { color-scheme: light dark; }\n body { font: 14px/1.5 system-ui, sans-serif; margin: 0; padding: 1.5rem; }\n h1 { font-size: 1.1rem; margin: 0 0 1rem; }\n .stats { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; }\n .stat { border: 1px solid #8884; border-radius: 8px; padding: .5rem .75rem; }\n .stat b { display: block; font-size: 1.3rem; }\n table { border-collapse: collapse; width: 100%; }\n th, td { text-align: left; padding: .4rem .6rem; border-bottom: 1px solid #8883; }\n .state { font-weight: 600; }\n .done { color: #2a9d3a; } .failed { color: #d33; } .blocked { color: #d90; }\n .noop, .skipped { opacity: .6; } .muted { opacity: .6; }\n a { color: inherit; }\n</style>\n</head>\n<body>\n<h1>ghostrail board</h1>\n<div class=\"stats\" id=\"stats\"></div>\n<table><thead><tr><th>Run</th><th>Item</th><th>State</th><th>Backend</th><th>Gate</th><th>PR</th><th>When</th></tr></thead>\n<tbody id=\"rows\"></tbody></table>\n<script>\nconst esc = (s) => String(s ?? \"\").replace(/[&<>]/g, (c) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\" }[c]));\nasync function refresh() {\n const [runs, stats] = await Promise.all([\n fetch(\"api/runs\").then((r) => r.json()),\n fetch(\"api/stats\").then((r) => r.json()),\n ]);\n document.getElementById(\"stats\").innerHTML = [\n [\"runs\", stats.total],\n [\"success\", (stats.successRate * 100).toFixed(0) + \"%\"],\n [\"PRs\", stats.prCount],\n ].map(([k, v]) => '<div class=\"stat\"><b>' + esc(v) + \"</b>\" + esc(k) + \"</div>\").join(\"\");\n document.getElementById(\"rows\").innerHTML = runs.map((r) => {\n const gate = (r.gate || []).map((s) => (s.exitCode === 0 ? \"\u2713\" : \"\u2717\")).join(\"\");\n const pr = r.prUrl ? '<a href=\"' + esc(r.prUrl) + '\">#' + esc(r.prNumber) + \"</a>\" : \"\";\n const when = r.startedAt ? new Date(r.startedAt).toLocaleString() : \"\";\n const state = esc(r.state || \"in-progress\");\n return \"<tr><td class=muted>\" + esc(r.runId.slice(0, 8)) + \"</td><td>\" + esc(r.title || r.itemId || \"\") +\n '</td><td class=\"state ' + state + '\">' + state + \"</td><td>\" + esc(r.backend || \"\") +\n \"</td><td>\" + gate + \"</td><td>\" + pr + \"</td><td class=muted>\" + esc(when) + \"</td></tr>\";\n }).join(\"\");\n}\nrefresh();\nsetInterval(refresh, 5000);\n</script>\n</body>\n</html>";
|
|
3
|
+
//# sourceMappingURL=page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/board/page.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,eAAO,MAAM,SAAS,ugFAoDd,CAAC"}
|