agentic-pi 0.1.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.
Files changed (53) hide show
  1. package/README.md +418 -0
  2. package/dist/args.d.ts +31 -0
  3. package/dist/args.js +109 -0
  4. package/dist/args.js.map +1 -0
  5. package/dist/cli.d.ts +9 -0
  6. package/dist/cli.js +47 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/emitter.d.ts +49 -0
  9. package/dist/emitter.js +67 -0
  10. package/dist/emitter.js.map +1 -0
  11. package/dist/extensions/github/auth.d.ts +54 -0
  12. package/dist/extensions/github/auth.js +116 -0
  13. package/dist/extensions/github/auth.js.map +1 -0
  14. package/dist/extensions/github/client.d.ts +6387 -0
  15. package/dist/extensions/github/client.js +358 -0
  16. package/dist/extensions/github/client.js.map +1 -0
  17. package/dist/extensions/github/credentials.d.ts +24 -0
  18. package/dist/extensions/github/credentials.js +44 -0
  19. package/dist/extensions/github/credentials.js.map +1 -0
  20. package/dist/extensions/github/index.d.ts +46 -0
  21. package/dist/extensions/github/index.js +67 -0
  22. package/dist/extensions/github/index.js.map +1 -0
  23. package/dist/extensions/github/profiles.d.ts +17 -0
  24. package/dist/extensions/github/profiles.js +71 -0
  25. package/dist/extensions/github/profiles.js.map +1 -0
  26. package/dist/extensions/github/tools.d.ts +18 -0
  27. package/dist/extensions/github/tools.js +289 -0
  28. package/dist/extensions/github/tools.js.map +1 -0
  29. package/dist/index.d.ts +38 -0
  30. package/dist/index.js +34 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/models.d.ts +13 -0
  33. package/dist/models.js +31 -0
  34. package/dist/models.js.map +1 -0
  35. package/dist/run.d.ts +139 -0
  36. package/dist/run.js +131 -0
  37. package/dist/run.js.map +1 -0
  38. package/dist/runner.d.ts +22 -0
  39. package/dist/runner.js +143 -0
  40. package/dist/runner.js.map +1 -0
  41. package/dist/sandbox/gondolin.d.ts +39 -0
  42. package/dist/sandbox/gondolin.js +210 -0
  43. package/dist/sandbox/gondolin.js.map +1 -0
  44. package/dist/sandbox/index.d.ts +37 -0
  45. package/dist/sandbox/index.js +55 -0
  46. package/dist/sandbox/index.js.map +1 -0
  47. package/dist/sandbox/preflight.d.ts +24 -0
  48. package/dist/sandbox/preflight.js +93 -0
  49. package/dist/sandbox/preflight.js.map +1 -0
  50. package/dist/stdin.d.ts +1 -0
  51. package/dist/stdin.js +11 -0
  52. package/dist/stdin.js.map +1 -0
  53. package/package.json +44 -0
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Gondolin sandbox backend for agentic-pi.
3
+ *
4
+ * Routes Pi's built-in `read`/`write`/`edit`/`bash` tools through a Gondolin
5
+ * micro-VM. The agent's tool calls execute against `/workspace` inside the
6
+ * VM, where the host's current working directory has been mounted RW. Files
7
+ * the agent writes appear on the host because RealFSProvider is a passthrough.
8
+ *
9
+ * Pattern adapted from gondolin/host/examples/pi-gondolin.ts but adjusted
10
+ * for SDK mode (no Pi extension hooks; we drive `createAgentSession`
11
+ * directly).
12
+ *
13
+ * Note: `grep`/`find`/`ls` are NOT routed through the VM in this version —
14
+ * they run on the host but they read the *same* files (because the mount
15
+ * is a passthrough), so the agent sees consistent state. This matches the
16
+ * upstream example. If full FS isolation is needed later, route those too.
17
+ */
18
+ import path from "node:path";
19
+ import { createBashTool, createEditTool, createReadTool, createWriteTool, } from "@earendil-works/pi-coding-agent";
20
+ import { RealFSProvider, VM } from "@earendil-works/gondolin";
21
+ const GUEST_WORKSPACE = "/workspace";
22
+ function shQuote(value) {
23
+ // POSIX shell quoting: wrap in single quotes; escape any internal quote.
24
+ return "'" + value.replace(/'/g, "'\\''") + "'";
25
+ }
26
+ function toGuestPath(localCwd, localPath) {
27
+ const rel = path.relative(localCwd, localPath);
28
+ if (rel === "")
29
+ return GUEST_WORKSPACE;
30
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
31
+ throw new Error(`path escapes workspace: ${localPath}`);
32
+ }
33
+ const posixRel = rel.split(path.sep).join(path.posix.sep);
34
+ return path.posix.join(GUEST_WORKSPACE, posixRel);
35
+ }
36
+ function sanitizeEnv(env) {
37
+ if (!env)
38
+ return undefined;
39
+ const out = {};
40
+ for (const [k, v] of Object.entries(env)) {
41
+ if (typeof v === "string")
42
+ out[k] = v;
43
+ }
44
+ return out;
45
+ }
46
+ function createGondolinReadOps(vm, localCwd) {
47
+ return {
48
+ readFile: async (p) => {
49
+ const guestPath = toGuestPath(localCwd, p);
50
+ const r = await vm.exec(["/bin/cat", guestPath]);
51
+ if (!r.ok)
52
+ throw new Error(`cat failed (${r.exitCode}): ${r.stderr}`);
53
+ return r.stdoutBuffer;
54
+ },
55
+ access: async (p) => {
56
+ const guestPath = toGuestPath(localCwd, p);
57
+ const r = await vm.exec(["/bin/sh", "-lc", `test -r ${shQuote(guestPath)}`]);
58
+ if (!r.ok)
59
+ throw new Error(`not readable: ${p}`);
60
+ },
61
+ detectImageMimeType: async (p) => {
62
+ const guestPath = toGuestPath(localCwd, p);
63
+ try {
64
+ const r = await vm.exec([
65
+ "/bin/sh",
66
+ "-lc",
67
+ `file --mime-type -b ${shQuote(guestPath)}`,
68
+ ]);
69
+ if (!r.ok)
70
+ return null;
71
+ const m = r.stdout.trim();
72
+ return ["image/jpeg", "image/png", "image/gif", "image/webp"].includes(m) ? m : null;
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ },
78
+ };
79
+ }
80
+ function createGondolinWriteOps(vm, localCwd) {
81
+ return {
82
+ writeFile: async (p, content) => {
83
+ const guestPath = toGuestPath(localCwd, p);
84
+ const dir = path.posix.dirname(guestPath);
85
+ const b64 = Buffer.from(content, "utf8").toString("base64");
86
+ const script = [
87
+ `set -eu`,
88
+ `mkdir -p ${shQuote(dir)}`,
89
+ `echo ${shQuote(b64)} | base64 -d > ${shQuote(guestPath)}`,
90
+ ].join("\n");
91
+ const r = await vm.exec(["/bin/sh", "-lc", script]);
92
+ if (!r.ok)
93
+ throw new Error(`write failed (${r.exitCode}): ${r.stderr}`);
94
+ },
95
+ mkdir: async (dir) => {
96
+ const guestDir = toGuestPath(localCwd, dir);
97
+ const r = await vm.exec(["/bin/mkdir", "-p", guestDir]);
98
+ if (!r.ok)
99
+ throw new Error(`mkdir failed (${r.exitCode}): ${r.stderr}`);
100
+ },
101
+ };
102
+ }
103
+ function createGondolinEditOps(vm, localCwd) {
104
+ const r = createGondolinReadOps(vm, localCwd);
105
+ const w = createGondolinWriteOps(vm, localCwd);
106
+ return { readFile: r.readFile, access: r.access, writeFile: w.writeFile };
107
+ }
108
+ function createGondolinBashOps(vm, localCwd) {
109
+ return {
110
+ exec: async (command, cwd, { onData, signal, timeout, env }) => {
111
+ const guestCwd = toGuestPath(localCwd, cwd);
112
+ const ac = new AbortController();
113
+ const onAbort = () => ac.abort();
114
+ signal?.addEventListener("abort", onAbort, { once: true });
115
+ let timedOut = false;
116
+ const timer = timeout && timeout > 0
117
+ ? setTimeout(() => {
118
+ timedOut = true;
119
+ ac.abort();
120
+ }, timeout * 1000)
121
+ : undefined;
122
+ try {
123
+ const proc = vm.exec(["/bin/sh", "-lc", command], {
124
+ cwd: guestCwd,
125
+ signal: ac.signal,
126
+ env: sanitizeEnv(env),
127
+ stdout: "pipe",
128
+ stderr: "pipe",
129
+ });
130
+ for await (const chunk of proc.output()) {
131
+ onData(chunk.data);
132
+ }
133
+ const r = await proc;
134
+ return { exitCode: r.exitCode };
135
+ }
136
+ catch (err) {
137
+ if (signal?.aborted)
138
+ throw new Error("aborted");
139
+ if (timedOut)
140
+ throw new Error(`timeout:${timeout}`);
141
+ throw err;
142
+ }
143
+ finally {
144
+ if (timer)
145
+ clearTimeout(timer);
146
+ signal?.removeEventListener("abort", onAbort);
147
+ }
148
+ },
149
+ };
150
+ }
151
+ /**
152
+ * Boot a Gondolin VM mounting `cwd` at /workspace, and build the four
153
+ * Pi tool overrides (read, write, edit, bash) that route through it.
154
+ *
155
+ * Throws if VM.create rejects. The preflight check is the caller's
156
+ * responsibility — call `preflightGondolin()` first.
157
+ */
158
+ export async function buildGondolinSandbox(cwd) {
159
+ const t0 = Date.now();
160
+ const vm = await VM.create({
161
+ vfs: {
162
+ mounts: {
163
+ [GUEST_WORKSPACE]: new RealFSProvider(cwd),
164
+ },
165
+ },
166
+ });
167
+ const createMs = Date.now() - t0;
168
+ // Confirm the VM is actually executable before returning. Without this
169
+ // probe, a hung VM (see SPIKE-gondolin.md / upstream issue #51) would
170
+ // slip through and the orchestrator would inherit the hang at first
171
+ // tool call. Two-second budget is generous — a working guest responds in
172
+ // a few hundred ms.
173
+ const probe = await Promise.race([
174
+ vm.exec(["/bin/true"]),
175
+ new Promise((resolve) => setTimeout(() => resolve({ ok: false, exitCode: -1, stderr: "vm-exec-probe-timeout" }), 5_000)),
176
+ ]);
177
+ if (!probe.ok) {
178
+ await vm.close().catch(() => undefined);
179
+ throw new Error(`Gondolin VM started but is not executing commands ` +
180
+ `(probe: ${probe.stderr || "no stderr"}). ` +
181
+ `This matches upstream issue #51 — likely no working accelerator. ` +
182
+ `See SPIKE-gondolin.md.`);
183
+ }
184
+ // Names match Pi's built-ins (read/write/edit/bash). Pi's host
185
+ // versions are suppressed by the runner via noTools:"builtin" so these
186
+ // do not collide.
187
+ const customTools = [
188
+ createReadTool(cwd, { operations: createGondolinReadOps(vm, cwd) }),
189
+ createWriteTool(cwd, { operations: createGondolinWriteOps(vm, cwd) }),
190
+ createEditTool(cwd, { operations: createGondolinEditOps(vm, cwd) }),
191
+ createBashTool(cwd, { operations: createGondolinBashOps(vm, cwd) }),
192
+ ];
193
+ let closed = false;
194
+ return {
195
+ customTools,
196
+ status: {
197
+ backend: "gondolin",
198
+ cwd,
199
+ guestPath: GUEST_WORKSPACE,
200
+ createMs,
201
+ },
202
+ close: async () => {
203
+ if (closed)
204
+ return;
205
+ closed = true;
206
+ await vm.close().catch(() => undefined);
207
+ },
208
+ };
209
+ }
210
+ //# sourceMappingURL=gondolin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gondolin.js","sourceRoot":"","sources":["../../src/sandbox/gondolin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Sandbox backend dispatcher.
3
+ *
4
+ * Currently supports two backends: `none` (the default — Pi's built-in
5
+ * tools run on the host) and `gondolin` (tool ops routed through a
6
+ * micro-VM). See SPIKE-gondolin.md for the design context.
7
+ */
8
+ import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
9
+ import { type GondolinSandbox } from "./gondolin.js";
10
+ export type SandboxBackend = "none" | "gondolin";
11
+ export interface SandboxNone {
12
+ backend: "none";
13
+ customTools: ToolDefinition<any>[];
14
+ /** Whether Pi's built-in read/write/edit/bash should be suppressed. */
15
+ suppressBuiltins: boolean;
16
+ close: () => Promise<void>;
17
+ status: Record<string, unknown>;
18
+ }
19
+ export type SandboxResult = SandboxNone | (Omit<GondolinSandbox, "status"> & {
20
+ backend: "gondolin";
21
+ suppressBuiltins: true;
22
+ status: GondolinSandbox["status"];
23
+ });
24
+ export interface BuildSandboxOptions {
25
+ backend: SandboxBackend;
26
+ cwd: string;
27
+ }
28
+ export type BuildSandboxOutcome = {
29
+ ok: true;
30
+ sandbox: SandboxResult;
31
+ } | {
32
+ ok: false;
33
+ backend: SandboxBackend;
34
+ reason: string;
35
+ hint: string;
36
+ };
37
+ export declare function buildSandbox(opts: BuildSandboxOptions): Promise<BuildSandboxOutcome>;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Sandbox backend dispatcher.
3
+ *
4
+ * Currently supports two backends: `none` (the default — Pi's built-in
5
+ * tools run on the host) and `gondolin` (tool ops routed through a
6
+ * micro-VM). See SPIKE-gondolin.md for the design context.
7
+ */
8
+ import { buildGondolinSandbox } from "./gondolin.js";
9
+ import { preflightGondolin } from "./preflight.js";
10
+ export async function buildSandbox(opts) {
11
+ if (opts.backend === "none") {
12
+ return {
13
+ ok: true,
14
+ sandbox: {
15
+ backend: "none",
16
+ customTools: [],
17
+ suppressBuiltins: false,
18
+ close: async () => undefined,
19
+ status: { backend: "none" },
20
+ },
21
+ };
22
+ }
23
+ // backend === "gondolin"
24
+ const preflight = preflightGondolin();
25
+ if (!preflight.ok) {
26
+ return {
27
+ ok: false,
28
+ backend: "gondolin",
29
+ reason: preflight.reason,
30
+ hint: preflight.hint,
31
+ };
32
+ }
33
+ try {
34
+ const sandbox = await buildGondolinSandbox(opts.cwd);
35
+ return {
36
+ ok: true,
37
+ sandbox: {
38
+ backend: "gondolin",
39
+ customTools: sandbox.customTools,
40
+ suppressBuiltins: true,
41
+ close: sandbox.close,
42
+ status: sandbox.status,
43
+ },
44
+ };
45
+ }
46
+ catch (err) {
47
+ return {
48
+ ok: false,
49
+ backend: "gondolin",
50
+ reason: "vm-create-failed",
51
+ hint: err.message,
52
+ };
53
+ }
54
+ }
55
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sandbox/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,oBAAoB,EAAwB,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAwB,MAAM,gBAAgB,CAAC;AAkCzE,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAyB;IAC1D,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;QAC5B,OAAO;YACL,EAAE,EAAE,IAAI;YACR,OAAO,EAAE;gBACP,OAAO,EAAE,MAAM;gBACf,WAAW,EAAE,EAAE;gBACf,gBAAgB,EAAE,KAAK;gBACvB,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS;gBAC5B,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;aAC5B;SACF,CAAC;IACJ,CAAC;IAED,yBAAyB;IACzB,MAAM,SAAS,GAAoB,iBAAiB,EAAE,CAAC;IACvD,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;QAClB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,UAAU;YACnB,MAAM,EAAE,SAAS,CAAC,MAAM;YACxB,IAAI,EAAE,SAAS,CAAC,IAAI;SACrB,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrD,OAAO;YACL,EAAE,EAAE,IAAI;YACR,OAAO,EAAE;gBACP,OAAO,EAAE,UAAU;gBACnB,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,gBAAgB,EAAE,IAAI;gBACtB,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,UAAU;YACnB,MAAM,EAAE,kBAAkB;YAC1B,IAAI,EAAG,GAAa,CAAC,OAAO;SAC7B,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Pre-flight checks for the Gondolin sandbox backend.
3
+ *
4
+ * The reason this exists at all: per upstream issue #51, Gondolin's
5
+ * `VM.create()` returns a "VM ready" Promise even when the underlying QEMU
6
+ * guest never actually boots (silently hangs at ~95% CPU). That happens any
7
+ * time QEMU lacks a hypervisor accelerator — most commonly when running
8
+ * inside a container with no `/dev/kvm`. We refuse to start in those
9
+ * environments rather than let the orchestrator inherit a hung process.
10
+ *
11
+ * Empirically verified: works on macOS host (HVF auto-used by QEMU 11);
12
+ * fails inside Colima / Docker Desktop (no /dev/kvm exposed). See
13
+ * SPIKE-gondolin.md for the full evidence.
14
+ */
15
+ export type PreflightStatus = {
16
+ ok: true;
17
+ detail: string;
18
+ } | {
19
+ ok: false;
20
+ reason: PreflightFailureReason;
21
+ hint: string;
22
+ };
23
+ export type PreflightFailureReason = "qemu-not-installed" | "qemu-img-not-installed" | "linux-no-kvm" | "in-container-no-accel";
24
+ export declare function preflightGondolin(): PreflightStatus;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Pre-flight checks for the Gondolin sandbox backend.
3
+ *
4
+ * The reason this exists at all: per upstream issue #51, Gondolin's
5
+ * `VM.create()` returns a "VM ready" Promise even when the underlying QEMU
6
+ * guest never actually boots (silently hangs at ~95% CPU). That happens any
7
+ * time QEMU lacks a hypervisor accelerator — most commonly when running
8
+ * inside a container with no `/dev/kvm`. We refuse to start in those
9
+ * environments rather than let the orchestrator inherit a hung process.
10
+ *
11
+ * Empirically verified: works on macOS host (HVF auto-used by QEMU 11);
12
+ * fails inside Colima / Docker Desktop (no /dev/kvm exposed). See
13
+ * SPIKE-gondolin.md for the full evidence.
14
+ */
15
+ import { existsSync, accessSync, constants } from "node:fs";
16
+ import { execFileSync } from "node:child_process";
17
+ function which(bin) {
18
+ try {
19
+ const out = execFileSync("/usr/bin/env", ["which", bin], {
20
+ encoding: "utf8",
21
+ stdio: ["ignore", "pipe", "ignore"],
22
+ }).trim();
23
+ return out || null;
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ function isReadable(path) {
30
+ try {
31
+ accessSync(path, constants.R_OK);
32
+ return true;
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ /**
39
+ * Best-effort container detection. We're conservative — false negatives are
40
+ * tolerable (the only consequence is a slower failure inside QEMU itself).
41
+ * False positives would block valid native runs, so we only flag the
42
+ * obvious cases.
43
+ */
44
+ function looksLikeContainer() {
45
+ if (existsSync("/.dockerenv"))
46
+ return true;
47
+ if (process.env.container)
48
+ return true;
49
+ return false;
50
+ }
51
+ export function preflightGondolin() {
52
+ const qemuX86 = which("qemu-system-x86_64");
53
+ const qemuArm = which("qemu-system-aarch64");
54
+ const qemu = qemuX86 || qemuArm;
55
+ if (!qemu) {
56
+ return {
57
+ ok: false,
58
+ reason: "qemu-not-installed",
59
+ hint: "Install QEMU on the host: 'brew install qemu' (macOS) or " +
60
+ "'apt install qemu-system-x86 qemu-system-arm qemu-utils' (Debian/Ubuntu). " +
61
+ "See SPIKE-gondolin.md for context.",
62
+ };
63
+ }
64
+ const qemuImg = which("qemu-img");
65
+ if (!qemuImg) {
66
+ return {
67
+ ok: false,
68
+ reason: "qemu-img-not-installed",
69
+ hint: "qemu-img missing. On Debian/Ubuntu install 'qemu-utils'; on macOS " +
70
+ "the 'qemu' brew formula already includes it — re-check $PATH.",
71
+ };
72
+ }
73
+ // On Linux, the only practical accelerator is KVM. Without /dev/kvm,
74
+ // Gondolin falls into the upstream-#51 silent-hang bug. Refuse early.
75
+ if (process.platform === "linux" && !isReadable("/dev/kvm")) {
76
+ const inContainer = looksLikeContainer();
77
+ return {
78
+ ok: false,
79
+ reason: inContainer ? "in-container-no-accel" : "linux-no-kvm",
80
+ hint: inContainer
81
+ ? "Detected container environment with no readable /dev/kvm. " +
82
+ "Gondolin requires KVM and Docker-in-Docker nested virt is not viable. " +
83
+ "Run agentic-pi natively, or expose /dev/kvm with 'docker run --device /dev/kvm'."
84
+ : "/dev/kvm not readable. Add the running user to the 'kvm' group, " +
85
+ "or run as a user with read access. Without it Gondolin hangs (upstream issue #51).",
86
+ };
87
+ }
88
+ // macOS: QEMU 11 auto-uses Hypervisor.framework (HVF). No /dev/kvm
89
+ // needed. Empirically verified in SPIKE-gondolin.md.
90
+ const accel = process.platform === "darwin" ? "HVF (auto)" : "KVM (/dev/kvm)";
91
+ return { ok: true, detail: `qemu=${qemu} qemu-img=${qemuImg} accel=${accel}` };
92
+ }
93
+ //# sourceMappingURL=preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/sandbox/preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAYlD,SAAS,KAAK,CAAC,GAAW;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,cAAc,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE;YACvD,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;SACpC,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,GAAG,IAAI,IAAI,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI,CAAC;QACH,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,kBAAkB;IACzB,IAAI,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,MAAM,OAAO,GAAG,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,OAAO,IAAI,OAAO,CAAC;IAEhC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,oBAAoB;YAC5B,IAAI,EACF,2DAA2D;gBAC3D,4EAA4E;gBAC5E,oCAAoC;SACvC,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;IAClC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,wBAAwB;YAChC,IAAI,EACF,oEAAoE;gBACpE,+DAA+D;SAClE,CAAC;IACJ,CAAC;IAED,qEAAqE;IACrE,sEAAsE;IACtE,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5D,MAAM,WAAW,GAAG,kBAAkB,EAAE,CAAC;QACzC,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,cAAc;YAC9D,IAAI,EAAE,WAAW;gBACf,CAAC,CAAC,4DAA4D;oBAC5D,wEAAwE;oBACxE,kFAAkF;gBACpF,CAAC,CAAC,kEAAkE;oBAClE,oFAAoF;SACzF,CAAC;IACJ,CAAC;IAED,mEAAmE;IACnE,qDAAqD;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,gBAAgB,CAAC;IAC9E,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,IAAI,aAAa,OAAO,UAAU,KAAK,EAAE,EAAE,CAAC;AACjF,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function readStdin(): Promise<string>;
package/dist/stdin.js ADDED
@@ -0,0 +1,11 @@
1
+ export async function readStdin() {
2
+ if (process.stdin.isTTY)
3
+ return "";
4
+ const chunks = [];
5
+ return await new Promise((resolve, reject) => {
6
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
7
+ process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
8
+ process.stdin.on("error", reject);
9
+ });
10
+ }
11
+ //# sourceMappingURL=stdin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdin.js","sourceRoot":"","sources":["../src/stdin.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACnC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,OAAO,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAChE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC/E,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "agentic-pi",
3
+ "version": "0.1.0",
4
+ "description": "A drop-in coding-agent harness built on earendil-works/pi, designed to replace opencode in workflow-driven systems like lastlight.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "agentic-pi": "./dist/cli.js"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "files": [
13
+ "dist",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "watch": "tsc --watch",
19
+ "start": "node dist/cli.js",
20
+ "test": "node scripts/run-tests.mjs",
21
+ "test:unit": "node scripts/run-tests.mjs --unit",
22
+ "test:integration": "node scripts/run-tests.mjs --integration",
23
+ "check": "tsc --noEmit",
24
+ "clean": "rm -rf dist"
25
+ },
26
+ "dependencies": {
27
+ "@earendil-works/gondolin": "^0.12.0",
28
+ "@earendil-works/pi-ai": "^0.75.4",
29
+ "@earendil-works/pi-coding-agent": "^0.75.4",
30
+ "@octokit/auth-app": "^7.1.4",
31
+ "@octokit/rest": "^21.0.2",
32
+ "@sinclair/typebox": "^0.34.0",
33
+ "@types/jsonwebtoken": "^9.0.10",
34
+ "jsonwebtoken": "^9.0.3"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.10.0",
38
+ "tsx": "^4.19.2",
39
+ "typescript": "^5.7.2"
40
+ },
41
+ "engines": {
42
+ "node": ">=20"
43
+ }
44
+ }