@dendelion/paper-camp 0.2.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.
@@ -0,0 +1,1250 @@
1
+ #!/usr/bin/env node
2
+ import { join as O, dirname as ct, extname as dt, basename as pt, resolve as Y } from "node:path";
3
+ import { Command as ut } from "commander";
4
+ import { t as nt, r as z, m as Q, s as k, q as M, j as Z, c as at, i as it, w as B, a as ot, n as V, b as lt, f as ft, P as gt, l as mt, A as yt } from "../chunks/serializer.D1Ts_p8h.js";
5
+ import { DEFAULT_AGENTS as X, PLAN_KINDS as F, AGENT_IDS as U } from "../types/index.js";
6
+ import { readFile as $, writeFile as I, mkdir as ht, stat as st } from "node:fs/promises";
7
+ import { createServer as Ct } from "node:http";
8
+ import { fileURLToPath as wt } from "node:url";
9
+ import { watch as G, readFileSync as St } from "node:fs";
10
+ import { spawn as K, spawnSync as L } from "node:child_process";
11
+ import { createInterface as Tt } from "node:readline";
12
+ const E = (n, c) => O(n, "papercamp", c), _ = (n) => $(n, "utf-8").catch(() => "");
13
+ async function q(n) {
14
+ const [c, o, s, u] = await Promise.all([
15
+ _(E(n, "plans.md")),
16
+ _(E(n, "decisions.md")),
17
+ _(E(n, "open-questions.md")),
18
+ _(E(n, "progress.md"))
19
+ ]);
20
+ return {
21
+ plans: k(c).entries,
22
+ decisions: Q(o).entries,
23
+ openQuestions: z(s).entries,
24
+ progress: nt(u)
25
+ };
26
+ }
27
+ function Nt(n, c) {
28
+ const o = [], s = (/* @__PURE__ */ new Date()).toISOString(), u = new Map(n.plans.map((e) => [e.title, e])), y = new Map(c.plans.map((e) => [e.title, e]));
29
+ for (const [e, d] of y) {
30
+ const f = u.get(e);
31
+ if (!f)
32
+ o.push({ message: `New plan added: ${e}`, timestamp: s });
33
+ else if (f.status !== d.status)
34
+ o.push({ message: `Plan "${e}" marked ${d.status}`, timestamp: s });
35
+ else
36
+ for (let p = 0; p < d.phases.length; p++) {
37
+ const l = f.phases[p], m = d.phases[p];
38
+ l && l.done !== m.done && o.push({
39
+ message: m.done ? `Phase ${p + 1}/${d.phases.length} checked off in "${e}"` : `Phase ${p + 1}/${d.phases.length} unchecked in "${e}"`,
40
+ timestamp: s
41
+ });
42
+ }
43
+ }
44
+ for (const [e] of u)
45
+ y.has(e) || o.push({ message: `Plan removed: ${e}`, timestamp: s });
46
+ const r = new Map(n.decisions.map((e) => [e.title, e])), t = new Map(c.decisions.map((e) => [e.title, e]));
47
+ for (const [e] of t)
48
+ r.has(e) || o.push({ message: `New decision: ${e}`, timestamp: s });
49
+ const b = new Map(n.openQuestions.map((e) => [e.title, e])), C = new Map(c.openQuestions.map((e) => [e.title, e]));
50
+ for (const [e] of C)
51
+ b.has(e) || o.push({ message: `New open question: ${e}`, timestamp: s });
52
+ const S = new Map(n.progress.map((e) => [e.date, e])), i = new Map(c.progress.map((e) => [e.date, e]));
53
+ for (const [e] of i)
54
+ S.has(e) || o.push({ message: `Progress logged: ${e}`, timestamp: s });
55
+ return o;
56
+ }
57
+ function Ot(n) {
58
+ const c = /* @__PURE__ */ new Set();
59
+ let o = null, s = null;
60
+ const u = ["plans.md", "decisions.md", "open-questions.md", "progress.md"];
61
+ async function y() {
62
+ try {
63
+ const r = await q(n);
64
+ if (o) {
65
+ const t = Nt(o, r);
66
+ for (const b of t) {
67
+ const C = `data: ${JSON.stringify(b)}
68
+
69
+ `;
70
+ for (const S of c)
71
+ try {
72
+ S.write(C);
73
+ } catch {
74
+ c.delete(S);
75
+ }
76
+ }
77
+ }
78
+ o = r;
79
+ } catch {
80
+ }
81
+ }
82
+ for (const r of u) {
83
+ const t = E(n, r);
84
+ try {
85
+ G(t, () => {
86
+ s && clearTimeout(s), s = setTimeout(y, 300);
87
+ });
88
+ } catch {
89
+ }
90
+ }
91
+ return q(n).then((r) => {
92
+ o = r;
93
+ }), {
94
+ subscribe(r) {
95
+ c.add(r);
96
+ const t = JSON.stringify({
97
+ message: "Watching for changes…",
98
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
99
+ });
100
+ r.write(`data: ${t}
101
+
102
+ `), r.on("close", () => c.delete(r));
103
+ }
104
+ };
105
+ }
106
+ function jt(n) {
107
+ return ["-p", n, "--output-format", "stream-json", "--verbose", "--permission-mode", "auto"];
108
+ }
109
+ function bt(n) {
110
+ let c;
111
+ try {
112
+ c = JSON.parse(n);
113
+ } catch {
114
+ return null;
115
+ }
116
+ switch (c.type) {
117
+ case "system":
118
+ return c.subtype === "init" ? { text: "Agent session started" } : c.subtype === "post_turn_summary" && typeof c.status_detail == "string" ? { text: c.status_detail } : null;
119
+ case "rate_limit_event":
120
+ return null;
121
+ case "assistant": {
122
+ const o = c.message, s = (o == null ? void 0 : o.content) ?? [];
123
+ for (const u of s) {
124
+ const y = u;
125
+ if (y.type === "tool_use")
126
+ return { text: `Running ${y.name ?? "a tool"}…` };
127
+ if (y.type === "text" && typeof y.text == "string" && y.text.trim())
128
+ return { text: y.text.trim() };
129
+ }
130
+ return null;
131
+ }
132
+ case "user": {
133
+ const o = c.message, s = o == null ? void 0 : o.content, u = Array.isArray(s) ? s[0] : void 0;
134
+ return u != null && u.is_error ? { text: `Error: ${typeof u.content == "string" ? u.content : "Tool call failed"}`, error: !0 } : { text: "Tool finished" };
135
+ }
136
+ case "result": {
137
+ const o = !!c.is_error;
138
+ return { text: (typeof c.result == "string" ? c.result.trim() : "") || (o ? "Agent run failed" : "Agent run finished"), done: !0, error: o };
139
+ }
140
+ default:
141
+ return { text: "Agent is working…" };
142
+ }
143
+ }
144
+ function Jt(n) {
145
+ return ["run", n, "--format", "json"];
146
+ }
147
+ const vt = {
148
+ bash: "Running command",
149
+ read: "Reading file",
150
+ edit: "Editing file",
151
+ write: "Writing file",
152
+ glob: "Searching files",
153
+ grep: "Searching code",
154
+ websearch: "Searching web",
155
+ webfetch: "Fetching URL",
156
+ question: "Asking for input"
157
+ };
158
+ function Ht(n) {
159
+ let c;
160
+ try {
161
+ c = JSON.parse(n);
162
+ } catch {
163
+ return null;
164
+ }
165
+ const o = c.type, s = c.part;
166
+ if (!o || !s) return null;
167
+ switch (o) {
168
+ case "step_start":
169
+ return null;
170
+ case "text": {
171
+ const u = s.text;
172
+ return u != null && u.trim() ? { text: u.trim() } : null;
173
+ }
174
+ case "tool_use": {
175
+ const u = s.tool, y = s.state ? s.state.input : void 0, r = y ? y.description : void 0, t = u ? vt[u] : "Running tool", b = typeof r == "string" && r.trim() ? `: ${r.trim()}` : "";
176
+ return u ? { text: `${t}${b}…` } : null;
177
+ }
178
+ case "step_finish": {
179
+ const u = s.reason, y = u === "tool-calls" ? null : u === "stop" ? "Done" : "Step finished";
180
+ return y ? { text: y } : null;
181
+ }
182
+ default:
183
+ return null;
184
+ }
185
+ }
186
+ const tt = "claude-code", P = {
187
+ "claude-code": {
188
+ command: "claude",
189
+ buildArgs: jt,
190
+ parseLine: bt
191
+ },
192
+ opencode: {
193
+ command: "opencode",
194
+ buildArgs: Jt,
195
+ parseLine: Ht
196
+ }
197
+ }, xt = {
198
+ phase: "phase",
199
+ audit: "phase",
200
+ draft: "planDraft",
201
+ extend: "ideaExtend"
202
+ };
203
+ function kt(n) {
204
+ const { agentId: c, defaultAgents: o, taskKind: s } = n;
205
+ if (c && c in P) return { id: c, adapter: P[c] };
206
+ if (s && o) {
207
+ const u = xt[s], y = o[u];
208
+ if (y && y in P) return { id: y, adapter: P[y] };
209
+ }
210
+ return { id: tt, adapter: P[tt] };
211
+ }
212
+ const $t = 50;
213
+ function It(n) {
214
+ try {
215
+ const c = St(O(n, ".paper-camp", "config.json"), "utf-8"), o = JSON.parse(c);
216
+ return o.defaultAgents ? o.defaultAgents : o.defaultAgent ? {
217
+ phase: o.defaultAgent,
218
+ planDraft: o.defaultAgent,
219
+ ideaExtend: o.defaultAgent
220
+ } : X;
221
+ } catch {
222
+ return X;
223
+ }
224
+ }
225
+ function Pt(n, c, o) {
226
+ return `You're working on phase ${o + 1} ("${c.text}") of the plan "${n.title}" (${n.id ?? "no id"}) in papercamp/plans.md.
227
+
228
+ ${c.description ?? ""}
229
+
230
+ Plan context: ${n.body}
231
+
232
+ Do only this phase. When done, check it off in plans.md (- [ ] -> - [x]) and append what you did to progress.md. If this was the last unchecked phase, set the plan's Status to \`review\`, not \`done\`, per this repo's AGENTS.md.`;
233
+ }
234
+ function At(n, c = () => {
235
+ }) {
236
+ const o = /* @__PURE__ */ new Set();
237
+ let s = null;
238
+ function u(a) {
239
+ const g = `data: ${JSON.stringify({ message: a, timestamp: (/* @__PURE__ */ new Date()).toISOString(), type: "agent" })}
240
+
241
+ `;
242
+ for (const w of o)
243
+ try {
244
+ w.write(g);
245
+ } catch {
246
+ o.delete(w);
247
+ }
248
+ }
249
+ function y(a, g) {
250
+ a.lines.push(g), a.lines.length > $t && a.lines.shift(), u(g);
251
+ }
252
+ function r(a, g) {
253
+ a.status = g, u(`agent: ${g}`);
254
+ }
255
+ async function t(a) {
256
+ var g, w;
257
+ try {
258
+ if (a.taskKind === "extend") {
259
+ const x = await $(O(n, "papercamp", "ideas.md"), "utf-8"), W = M(x).find((rt) => rt.id === a.ideaId);
260
+ return !W || a.ideaBodyBaseline === void 0 ? null : W.body !== a.ideaBodyBaseline;
261
+ }
262
+ const T = await $(O(n, "papercamp", "plans.md"), "utf-8"), { entries: j } = k(T);
263
+ if (a.ideaId !== void 0)
264
+ return j.some((x) => x.idea === a.ideaId);
265
+ const v = j.find((x) => x.id === a.planId) ?? j.find((x) => x.title === a.planTitle);
266
+ return v ? a.phaseIndex !== void 0 ? ((g = v.phases[a.phaseIndex]) == null ? void 0 : g.done) ?? null : a.planBaseline ? v.phases.length > a.planBaseline.phases || (((w = v.log) == null ? void 0 : w.length) ?? 0) > a.planBaseline.log : null : null;
267
+ } catch {
268
+ return null;
269
+ }
270
+ }
271
+ function b(a, g) {
272
+ r(a, g ? "error" : "done"), !g && t(a).then((w) => {
273
+ if (s === a && w === !1) {
274
+ const T = a.taskKind === "extend" ? `Warning: agent finished but the idea body for ${a.ideaId} did not change — verify manually` : a.ideaId !== void 0 ? `Warning: agent finished but no plan linking idea: ${a.ideaId} appeared in plans.md — verify manually` : a.phaseIndex !== void 0 ? "Warning: agent finished but did not check off this phase in plans.md — verify manually" : "Warning: agent finished but appended nothing to Phases or Log — verify manually";
275
+ y(a, T);
276
+ }
277
+ });
278
+ }
279
+ function C(a) {
280
+ if (!a.proc.stdout) return;
281
+ Tt({ input: a.proc.stdout }).on("line", (w) => {
282
+ if (s !== a || !w.trim()) return;
283
+ const T = a.adapter.parseLine(w);
284
+ T && (y(a, T.text), T.done && b(a, !!T.error));
285
+ }), a.proc.on("close", (w) => {
286
+ s === a && (a.status === "starting" || a.status === "running" ? b(a, w !== 0) : a.status === "stopping" && r(a, "done"));
287
+ }), a.proc.on("error", (w) => {
288
+ s === a && (y(a, `Failed to spawn agent: ${w.message}`), r(a, "error"));
289
+ });
290
+ }
291
+ function S(a, g) {
292
+ return K(a.command, g, {
293
+ cwd: n,
294
+ stdio: ["ignore", "pipe", "pipe"]
295
+ });
296
+ }
297
+ function i() {
298
+ return s !== null && s.status !== "done" && s.status !== "error";
299
+ }
300
+ function e(a, g, w) {
301
+ if (i())
302
+ return { ok: !1, error: "An agent task is already running" };
303
+ const T = It(n), { id: j, adapter: v } = kt({
304
+ agentId: a.agentOverride,
305
+ defaultAgents: T,
306
+ taskKind: w.taskKind
307
+ }), x = S(v, v.buildArgs(g)), R = {
308
+ planTitle: a.planTitle,
309
+ planId: a.planId,
310
+ status: "starting",
311
+ agentId: j,
312
+ adapter: v,
313
+ proc: x,
314
+ lines: [],
315
+ ...w
316
+ };
317
+ return s = R, C(R), r(R, "running"), { ok: !0 };
318
+ }
319
+ function d(a, g) {
320
+ if (i())
321
+ return { ok: !1, error: "An agent task is already running" };
322
+ const w = a.phases[g];
323
+ if (!w)
324
+ return { ok: !1, error: "Phase not found" };
325
+ c(a);
326
+ const T = Pt(a, w, g);
327
+ return e({ planTitle: a.title, planId: a.id, agentOverride: a.agent }, T, {
328
+ taskKind: "phase",
329
+ phaseIndex: g
330
+ });
331
+ }
332
+ function f(a, g) {
333
+ var w;
334
+ return e({ planTitle: a.title, planId: a.id, agentOverride: a.agent }, g, {
335
+ taskKind: "audit",
336
+ planBaseline: { phases: a.phases.length, log: ((w = a.log) == null ? void 0 : w.length) ?? 0 }
337
+ });
338
+ }
339
+ function p(a, g) {
340
+ return a.id ? e({ planTitle: `Draft plan for ${a.id}` }, g, {
341
+ taskKind: "draft",
342
+ ideaId: a.id
343
+ }) : { ok: !1, error: "Idea has no id to link a drafted plan back to" };
344
+ }
345
+ function l(a, g) {
346
+ return a.id ? e({ planTitle: `Extend ${a.id}` }, g, {
347
+ taskKind: "extend",
348
+ ideaId: a.id,
349
+ ideaBodyBaseline: a.body
350
+ }) : { ok: !1, error: "Idea has no id to extend" };
351
+ }
352
+ function m() {
353
+ if (!s)
354
+ return { ok: !1, error: "No agent task running" };
355
+ const a = s;
356
+ return r(a, "stopping"), a.proc.killed || a.proc.kill("SIGTERM"), setTimeout(() => {
357
+ s === a && a.status === "stopping" && a.proc.kill("SIGKILL");
358
+ }, 5e3), { ok: !0 };
359
+ }
360
+ function h() {
361
+ return s ? {
362
+ status: s.status,
363
+ taskKind: s.taskKind,
364
+ planTitle: s.planTitle,
365
+ planId: s.planId,
366
+ phaseIndex: s.phaseIndex,
367
+ ideaId: s.ideaId,
368
+ agentId: s.agentId,
369
+ lines: [...s.lines]
370
+ } : null;
371
+ }
372
+ return {
373
+ start: d,
374
+ startForPlan: f,
375
+ startForIdea: p,
376
+ startForIdeaExtend: l,
377
+ stop: m,
378
+ getStatus: h,
379
+ subscribe(a) {
380
+ o.add(a), a.on("close", () => o.delete(a));
381
+ },
382
+ killCurrent() {
383
+ s != null && s.proc && !s.proc.killed && s.proc.kill();
384
+ }
385
+ };
386
+ }
387
+ function Et(n) {
388
+ const c = /* @__PURE__ */ new Set();
389
+ function o(p) {
390
+ const l = `data: ${JSON.stringify(p)}
391
+
392
+ `;
393
+ for (const m of c)
394
+ try {
395
+ m.write(l);
396
+ } catch {
397
+ c.delete(m);
398
+ }
399
+ }
400
+ function s(p) {
401
+ const l = [];
402
+ for (const m of p.split(`
403
+ `)) {
404
+ if (!m.trim()) continue;
405
+ const h = m[0] ?? " ", a = m[1] ?? " ", g = m.slice(3), w = g.split(" -> ").pop() ?? g;
406
+ l.push({
407
+ path: w,
408
+ status: `${h}${a}`,
409
+ staged: h !== " " && h !== "?"
410
+ });
411
+ }
412
+ return l;
413
+ }
414
+ function u(p) {
415
+ return new Promise((l, m) => {
416
+ var w, T;
417
+ const h = K("git", p, {
418
+ cwd: n,
419
+ stdio: ["ignore", "pipe", "pipe"]
420
+ });
421
+ let a = "", g = "";
422
+ (w = h.stdout) == null || w.on("data", (j) => {
423
+ a += j.toString();
424
+ }), (T = h.stderr) == null || T.on("data", (j) => {
425
+ g += j.toString();
426
+ }), h.on("close", (j) => {
427
+ j === 0 ? l(a) : m(new Error(g || `git ${p[0]} exited with code ${j}`));
428
+ }), h.on("error", m);
429
+ });
430
+ }
431
+ function y() {
432
+ return u(["status", "--porcelain=v1"]).then(s);
433
+ }
434
+ async function r(p, l, m) {
435
+ p.length > 0 && await u(["add", "--", ...p]);
436
+ const h = ["commit", "-m", l];
437
+ m && h.push("-m", m), await u(h);
438
+ }
439
+ function t(p) {
440
+ if (!p.kind || !p.id) return;
441
+ const l = p.kind.toLowerCase(), m = p.id.toLowerCase(), h = p.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""), a = `${l}/${m}-${h}`, g = L("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: n });
442
+ if (g.status !== 0)
443
+ throw new Error(
444
+ g.stderr.toString().trim() || "Unable to read current git branch"
445
+ );
446
+ if (g.stdout.toString().trim() === a) return;
447
+ const T = L("git", ["checkout", "-b", a, "main"], { cwd: n });
448
+ if (T.status !== 0) {
449
+ const j = L("git", ["checkout", a], { cwd: n });
450
+ if (j.status !== 0)
451
+ throw new Error(
452
+ j.stderr.toString().trim() || T.stderr.toString().trim() || `Unable to check out ${a}`
453
+ );
454
+ }
455
+ }
456
+ async function b() {
457
+ try {
458
+ await y(), o({
459
+ message: "Working tree status updated",
460
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
461
+ });
462
+ } catch {
463
+ }
464
+ }
465
+ const C = O(n, ".git");
466
+ let S = null;
467
+ try {
468
+ G(C, { recursive: !0 }, (p, l) => {
469
+ l === "index" && (S && clearTimeout(S), S = setTimeout(b, 500));
470
+ });
471
+ } catch {
472
+ }
473
+ const i = O(n, "src");
474
+ let e = null;
475
+ try {
476
+ G(i, { recursive: !0 }, () => {
477
+ e && clearTimeout(e), e = setTimeout(b, 500);
478
+ });
479
+ } catch {
480
+ }
481
+ function d() {
482
+ return L("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: n }).stdout.toString().trim();
483
+ }
484
+ function f() {
485
+ const l = d().match(/^[a-z]+\/([a-z]+-\d+)-/);
486
+ return l ? l[1].toUpperCase() : null;
487
+ }
488
+ return {
489
+ async getStatus() {
490
+ return y();
491
+ },
492
+ getCurrentBranch: d,
493
+ commit: r,
494
+ ensureBranch: t,
495
+ getFeatureBranchPlanId: f,
496
+ subscribe(p) {
497
+ c.add(p), p.on("close", () => c.delete(p));
498
+ }
499
+ };
500
+ }
501
+ const Dt = {
502
+ lint: "npx biome lint .",
503
+ format: "npx biome format .",
504
+ test: "npx vitest run"
505
+ };
506
+ function Rt(n) {
507
+ const c = /* @__PURE__ */ new Set(), o = {
508
+ lint: { status: "stale", lastRun: null, output: "" },
509
+ format: { status: "stale", lastRun: null, output: "" },
510
+ test: { status: "stale", lastRun: null, output: "" }
511
+ }, s = /* @__PURE__ */ new Set(), u = /* @__PURE__ */ new Set();
512
+ function y(i) {
513
+ const e = `data: ${JSON.stringify(i)}
514
+
515
+ `;
516
+ for (const d of c)
517
+ try {
518
+ d.write(e);
519
+ } catch {
520
+ c.delete(d);
521
+ }
522
+ }
523
+ function r(i, e, d) {
524
+ o[i] = { status: e, lastRun: (/* @__PURE__ */ new Date()).toISOString(), output: d }, y({
525
+ message: `${i}: ${e}`,
526
+ timestamp: o[i].lastRun
527
+ }), e !== "running" && u.has(i) && (u.delete(i), t(i));
528
+ }
529
+ function t(i) {
530
+ var l, m;
531
+ if (s.has(i)) {
532
+ u.add(i);
533
+ return;
534
+ }
535
+ s.add(i), r(i, "running", "");
536
+ const e = Dt[i], d = K(e, {
537
+ cwd: n,
538
+ stdio: ["ignore", "pipe", "pipe"],
539
+ shell: !0
540
+ });
541
+ let f = "", p = "";
542
+ (l = d.stdout) == null || l.on("data", (h) => {
543
+ f += h.toString();
544
+ }), (m = d.stderr) == null || m.on("data", (h) => {
545
+ p += h.toString();
546
+ }), d.on("close", (h) => {
547
+ s.delete(i);
548
+ const a = f + p;
549
+ h === 0 ? r(i, "pass", a) : r(i, "fail", a);
550
+ }), d.on("error", (h) => {
551
+ s.delete(i), r(i, "fail", `Failed to spawn process: ${h.message}`);
552
+ });
553
+ }
554
+ function b() {
555
+ if (s.has("lint") || s.has("format")) return;
556
+ r("lint", "running", "Applying automatic fixes…"), r("format", "running", "Applying automatic fixes…");
557
+ const i = K("npx biome check . --write", {
558
+ cwd: n,
559
+ stdio: ["ignore", "pipe", "pipe"],
560
+ shell: !0
561
+ });
562
+ i.on("close", () => {
563
+ t("lint"), t("format");
564
+ }), i.on("error", (e) => {
565
+ const d = `Failed to spawn fix process: ${e.message}`;
566
+ r("lint", "fail", d), r("format", "fail", d);
567
+ });
568
+ }
569
+ const C = O(n, "src");
570
+ let S = null;
571
+ try {
572
+ G(C, { recursive: !0 }, () => {
573
+ S && clearTimeout(S), S = setTimeout(() => {
574
+ t("lint"), t("format");
575
+ }, 1e3);
576
+ });
577
+ } catch {
578
+ }
579
+ return {
580
+ getStatus() {
581
+ return {
582
+ lint: { ...o.lint },
583
+ format: { ...o.format },
584
+ test: { ...o.test }
585
+ };
586
+ },
587
+ runCheck: t,
588
+ runQualityFix: b,
589
+ subscribe(i) {
590
+ c.add(i);
591
+ for (const e of ["lint", "format", "test"]) {
592
+ const d = o[e];
593
+ d.status !== "stale" && i.write(
594
+ `data: ${JSON.stringify({ message: `${e}: ${d.status}`, timestamp: d.lastRun })}
595
+
596
+ `
597
+ );
598
+ }
599
+ i.on("close", () => c.delete(i));
600
+ }
601
+ };
602
+ }
603
+ async function N(n) {
604
+ try {
605
+ return await $(n, "utf-8");
606
+ } catch (c) {
607
+ if (c.code === "ENOENT") return "";
608
+ throw c;
609
+ }
610
+ }
611
+ async function et(n) {
612
+ try {
613
+ return await st(n), !0;
614
+ } catch {
615
+ return !1;
616
+ }
617
+ }
618
+ async function H(n) {
619
+ return new Promise((c, o) => {
620
+ let s = "";
621
+ n.on("data", (u) => {
622
+ s += u;
623
+ }), n.on("end", () => c(s)), n.on("error", o);
624
+ });
625
+ }
626
+ const J = (n, c) => O(n, "papercamp", c), Lt = [
627
+ "biome.json",
628
+ "tsconfig.json",
629
+ "tailwind.config.ts",
630
+ "vite.config.ts",
631
+ "vite.app.config.ts",
632
+ "postcss.config.js",
633
+ "package.json"
634
+ ];
635
+ async function A(n, c, o) {
636
+ const s = c.getFeatureBranchPlanId();
637
+ if (!s || o && s === o) return null;
638
+ const u = await N(J(n, "plans.md"));
639
+ if (!u) return null;
640
+ const { entries: y } = k(u), r = y.find((t) => t.id === s);
641
+ return !r || r.status === "done" || r.status === "dropped" ? null : `Finish \`${s}\` — ${r.title} — before starting another plan`;
642
+ }
643
+ const _t = [
644
+ {
645
+ path: "/api/package-name",
646
+ handler: async (n) => {
647
+ const c = await N(O(n, "package.json"));
648
+ if (!c) return null;
649
+ try {
650
+ return JSON.parse(c).name ?? null;
651
+ } catch {
652
+ return null;
653
+ }
654
+ }
655
+ },
656
+ {
657
+ path: "/api/plans",
658
+ handler: async (n) => k(await N(J(n, "plans.md")))
659
+ },
660
+ {
661
+ path: "/api/progress",
662
+ handler: async (n) => ({
663
+ entries: nt(await N(J(n, "progress.md")))
664
+ })
665
+ },
666
+ {
667
+ path: "/api/decisions",
668
+ handler: async (n) => Q(await N(J(n, "decisions.md")))
669
+ },
670
+ {
671
+ path: "/api/open-questions",
672
+ handler: async (n) => z(await N(J(n, "open-questions.md")))
673
+ },
674
+ {
675
+ path: "/api/ideas",
676
+ handler: async (n) => ({
677
+ content: await N(J(n, "ideas.md"))
678
+ })
679
+ },
680
+ {
681
+ path: "/api/consistency",
682
+ handler: async (n) => {
683
+ const [c, o, s] = await Promise.all([
684
+ N(J(n, "decisions.md")),
685
+ N(J(n, "open-questions.md")),
686
+ N(J(n, "plans.md"))
687
+ ]), u = Q(c), y = z(o), r = k(s);
688
+ return ft(u.entries, y.entries, r.entries);
689
+ }
690
+ },
691
+ {
692
+ path: "/api/config",
693
+ handler: async (n) => {
694
+ const c = await N(O(n, ".paper-camp", "config.json"));
695
+ return c ? JSON.parse(c) : null;
696
+ }
697
+ },
698
+ {
699
+ path: "/api/docs",
700
+ handler: async (n) => {
701
+ const c = ["MAIN.md", "README.md", "CHANGELOG.md", "LICENSE"], o = [];
702
+ for (const s of c) {
703
+ const u = await N(O(n, s));
704
+ u && o.push({ name: s, content: u });
705
+ }
706
+ return { files: o };
707
+ }
708
+ },
709
+ {
710
+ path: "/api/configs",
711
+ handler: async (n) => {
712
+ const c = [
713
+ "biome.json",
714
+ "tsconfig.json",
715
+ "tailwind.config.ts",
716
+ "vite.config.ts",
717
+ "vite.app.config.ts",
718
+ "postcss.config.js",
719
+ "package.json"
720
+ ], o = [];
721
+ for (const s of c)
722
+ await N(O(n, s)) && o.push(s);
723
+ return { files: o };
724
+ }
725
+ }
726
+ ];
727
+ function Mt(n) {
728
+ const c = Ot(n), o = Et(n), s = Rt(n), u = At(n, (r) => o.ensureBranch(r)), y = async (r, t, b) => {
729
+ const C = (r.url ?? "").split("?")[0];
730
+ if (r.method === "DELETE" && C === "/api/plans") {
731
+ try {
732
+ const e = new URL(r.url ?? "", `http://${r.headers.host ?? "localhost"}`).searchParams.get("title");
733
+ if (!(e != null && e.trim())) {
734
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
735
+ return;
736
+ }
737
+ const d = J(n, "plans.md"), f = k(await N(d)), p = e.trim(), l = f.entries.filter((m) => m.title !== p);
738
+ if (l.length === f.entries.length) {
739
+ t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan not found" }));
740
+ return;
741
+ }
742
+ await I(d, Z(l)), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
743
+ } catch (i) {
744
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
745
+ }
746
+ return;
747
+ }
748
+ if (r.method === "POST" && C === "/api/plans") {
749
+ try {
750
+ const i = await H(r), { title: e, content: d, kind: f } = JSON.parse(i);
751
+ if (!(e != null && e.trim())) {
752
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
753
+ return;
754
+ }
755
+ const p = f && F.includes(f) ? f : "feat", l = O(n, ".paper-camp", "config.json"), m = await at(l, p), h = it({
756
+ title: e.trim(),
757
+ status: "idea",
758
+ kind: p,
759
+ id: m,
760
+ created: B(),
761
+ body: d == null ? void 0 : d.trim()
762
+ });
763
+ await ot(J(n, "plans.md"), h), t.statusCode = 201, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0, id: m }));
764
+ } catch (i) {
765
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
766
+ }
767
+ return;
768
+ }
769
+ if (r.method === "PATCH" && C === "/api/plans") {
770
+ try {
771
+ const e = new URL(r.url ?? "", `http://${r.headers.host ?? "localhost"}`).searchParams.get("title");
772
+ if (!(e != null && e.trim())) {
773
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
774
+ return;
775
+ }
776
+ const d = await H(r), f = JSON.parse(d);
777
+ if (f.agent && !U.includes(f.agent)) {
778
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "agent must be a known agent id" }));
779
+ return;
780
+ }
781
+ const p = J(n, "plans.md"), l = k(await N(p)), m = e.trim();
782
+ let h = !1;
783
+ const a = l.entries.map((g) => g.title === m ? (h = !0, {
784
+ ...g,
785
+ ...f.status !== void 0 && { status: f.status },
786
+ ...f.phases !== void 0 && { phases: f.phases },
787
+ ...f.log !== void 0 && { log: f.log },
788
+ ...f.agent !== void 0 && { agent: f.agent ?? void 0 },
789
+ updated: B()
790
+ }) : f.status === "in-progress" && g.status === "in-progress" ? { ...g, status: "planned", updated: B() } : g);
791
+ if (!h) {
792
+ t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan not found" }));
793
+ return;
794
+ }
795
+ if (f.status === "done") {
796
+ const g = l.entries.find((T) => T.title === m), w = await A(n, o, g == null ? void 0 : g.id);
797
+ if (w) {
798
+ t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: w }));
799
+ return;
800
+ }
801
+ }
802
+ if (await I(p, Z(a)), f.status === "done") {
803
+ const g = a.find((w) => w.title === m);
804
+ if (g)
805
+ try {
806
+ o.ensureBranch(g);
807
+ } catch {
808
+ }
809
+ }
810
+ t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
811
+ } catch (i) {
812
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
813
+ }
814
+ return;
815
+ }
816
+ if (r.method === "POST" && C === "/api/ideas") {
817
+ try {
818
+ const i = await H(r), { title: e, content: d } = JSON.parse(i);
819
+ if (!(e != null && e.trim())) {
820
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
821
+ return;
822
+ }
823
+ const f = J(n, "ideas.md"), p = await N(f), h = `IDEA-${M(p).reduce((T, j) => {
824
+ if (j.id) {
825
+ const v = Number.parseInt(j.id.replace("IDEA-", ""), 10);
826
+ return Number.isNaN(v) ? T : Math.max(T, v);
827
+ }
828
+ return T;
829
+ }, 0) + 1}`, a = `### ${h}: ${e.trim()}
830
+
831
+ ${(d == null ? void 0 : d.trim()) ?? ""}`, g = p.trimEnd(), w = g.length === 0 ? "" : `
832
+
833
+ ---
834
+
835
+ `;
836
+ await I(f, `${g}${w}${a}
837
+ `), t.statusCode = 201, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0, id: h }));
838
+ } catch (i) {
839
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
840
+ }
841
+ return;
842
+ }
843
+ if (r.method === "GET" && C === "/api/icon") {
844
+ const i = O(n, ".paper-camp", "assets"), e = {
845
+ svg: "image/svg+xml",
846
+ png: "image/png",
847
+ jpg: "image/jpeg",
848
+ jpeg: "image/jpeg",
849
+ gif: "image/gif",
850
+ webp: "image/webp"
851
+ };
852
+ for (const [d, f] of Object.entries(e))
853
+ try {
854
+ const p = await $(O(i, `icon.${d}`));
855
+ t.statusCode = 200, t.setHeader("Content-Type", f), t.setHeader("Cache-Control", "no-cache"), t.end(p);
856
+ return;
857
+ } catch {
858
+ }
859
+ t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "no icon uploaded" }));
860
+ return;
861
+ }
862
+ if (r.method === "POST" && C === "/api/icon") {
863
+ try {
864
+ const i = await H(r), { dataUri: e } = JSON.parse(i);
865
+ if (!e) {
866
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "dataUri is required" }));
867
+ return;
868
+ }
869
+ const d = e.match(/^data:(image\/[a-z0-9+.-]+);base64,(.+)$/);
870
+ if (!d) {
871
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "invalid data URI" }));
872
+ return;
873
+ }
874
+ const f = d[1], p = f === "image/svg+xml" ? "svg" : f.split("/")[1], l = Buffer.from(d[2], "base64"), m = O(n, ".paper-camp", "assets");
875
+ await ht(m, { recursive: !0 }), await I(O(m, `icon.${p}`), l), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
876
+ } catch (i) {
877
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
878
+ }
879
+ return;
880
+ }
881
+ if (r.method === "GET" && C === "/api/git/status") {
882
+ try {
883
+ const i = await o.getStatus(), e = o.getCurrentBranch();
884
+ t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ branch: e, entries: i }));
885
+ } catch (i) {
886
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
887
+ }
888
+ return;
889
+ }
890
+ if (r.method === "POST" && C === "/api/git/commit") {
891
+ try {
892
+ const i = await H(r), { files: e, title: d, message: f } = JSON.parse(i);
893
+ if (!(d != null && d.trim())) {
894
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
895
+ return;
896
+ }
897
+ await o.commit(e ?? [], d.trim(), f == null ? void 0 : f.trim()), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
898
+ } catch (i) {
899
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
900
+ }
901
+ return;
902
+ }
903
+ if (r.method === "GET" && C === "/api/status") {
904
+ t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify(s.getStatus()));
905
+ return;
906
+ }
907
+ if (r.method === "POST" && C === "/api/status/check") {
908
+ const e = new URL(r.url ?? "", `http://${r.headers.host ?? "localhost"}`).searchParams.get("name");
909
+ if (e !== "lint" && e !== "format" && e !== "test") {
910
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "name must be lint, format, or test" }));
911
+ return;
912
+ }
913
+ s.runCheck(e), t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
914
+ return;
915
+ }
916
+ if (r.method === "POST" && C === "/api/status/fix") {
917
+ s.runQualityFix(), t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
918
+ return;
919
+ }
920
+ if (r.method === "GET" && C === "/api/activity/stream") {
921
+ t.statusCode = 200, t.setHeader("Content-Type", "text/event-stream"), t.setHeader("Cache-Control", "no-cache"), t.setHeader("Connection", "keep-alive"), t.flushHeaders(), c.subscribe(t), o.subscribe(t), s.subscribe(t), u.subscribe(t);
922
+ return;
923
+ }
924
+ if (r.method === "GET" && C === "/api/agent/status") {
925
+ t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify(u.getStatus()));
926
+ return;
927
+ }
928
+ if (r.method === "POST" && C === "/api/agent/launch") {
929
+ try {
930
+ const i = await H(r), { planId: e, phaseIndex: d } = JSON.parse(i);
931
+ if (!e || typeof d != "number") {
932
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "planId and phaseIndex are required" }));
933
+ return;
934
+ }
935
+ const p = k(await N(J(n, "plans.md"))).entries.find((h) => h.id === e);
936
+ if (!p) {
937
+ t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan not found" }));
938
+ return;
939
+ }
940
+ const l = await A(n, o, p.id);
941
+ if (l) {
942
+ t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: l }));
943
+ return;
944
+ }
945
+ const m = await u.start(p, d);
946
+ if (!m.ok) {
947
+ t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: m.error }));
948
+ return;
949
+ }
950
+ t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
951
+ } catch (i) {
952
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
953
+ }
954
+ return;
955
+ }
956
+ if (r.method === "POST" && C === "/api/agent/launch-audit") {
957
+ try {
958
+ const i = await H(r), { planId: e, prompt: d } = JSON.parse(i);
959
+ if (!e || !d) {
960
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "planId and prompt are required" }));
961
+ return;
962
+ }
963
+ const p = k(await N(J(n, "plans.md"))).entries.find((h) => h.id === e);
964
+ if (!p) {
965
+ t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan not found" }));
966
+ return;
967
+ }
968
+ const l = await A(n, o, p.id);
969
+ if (l) {
970
+ t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: l }));
971
+ return;
972
+ }
973
+ const m = u.startForPlan(p, d);
974
+ if (!m.ok) {
975
+ t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: m.error }));
976
+ return;
977
+ }
978
+ t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
979
+ } catch (i) {
980
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
981
+ }
982
+ return;
983
+ }
984
+ if (r.method === "POST" && C === "/api/agent/launch-draft") {
985
+ try {
986
+ const i = await H(r), { ideaId: e, prompt: d } = JSON.parse(i);
987
+ if (!e || !d) {
988
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "ideaId and prompt are required" }));
989
+ return;
990
+ }
991
+ const p = M(await N(J(n, "ideas.md"))).find((h) => h.id === e);
992
+ if (!p) {
993
+ t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "idea not found" }));
994
+ return;
995
+ }
996
+ const l = await A(n, o);
997
+ if (l) {
998
+ t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: l }));
999
+ return;
1000
+ }
1001
+ const m = u.startForIdea(p, d);
1002
+ if (!m.ok) {
1003
+ t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: m.error }));
1004
+ return;
1005
+ }
1006
+ t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1007
+ } catch (i) {
1008
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
1009
+ }
1010
+ return;
1011
+ }
1012
+ if (r.method === "POST" && C === "/api/agent/launch-extend") {
1013
+ try {
1014
+ const i = await H(r), { ideaId: e, prompt: d } = JSON.parse(i);
1015
+ if (!e || !d) {
1016
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "ideaId and prompt are required" }));
1017
+ return;
1018
+ }
1019
+ const p = M(await N(J(n, "ideas.md"))).find((h) => h.id === e);
1020
+ if (!p) {
1021
+ t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "idea not found" }));
1022
+ return;
1023
+ }
1024
+ const l = await A(n, o);
1025
+ if (l) {
1026
+ t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: l }));
1027
+ return;
1028
+ }
1029
+ const m = u.startForIdeaExtend(p, d);
1030
+ if (!m.ok) {
1031
+ t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: m.error }));
1032
+ return;
1033
+ }
1034
+ t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1035
+ } catch (i) {
1036
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
1037
+ }
1038
+ return;
1039
+ }
1040
+ if (r.method === "POST" && C === "/api/agent/stop") {
1041
+ const i = u.stop();
1042
+ if (!i.ok) {
1043
+ t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.error }));
1044
+ return;
1045
+ }
1046
+ t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1047
+ return;
1048
+ }
1049
+ if (r.method === "POST" && C === "/api/config") {
1050
+ try {
1051
+ const i = O(n, ".paper-camp", "config.json"), e = await N(i);
1052
+ if (!e) {
1053
+ t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "config not found" }));
1054
+ return;
1055
+ }
1056
+ const d = await H(r), { port: f, projectName: p, defaultAgent: l, defaultAgents: m } = JSON.parse(d);
1057
+ if (f !== void 0 && (!Number.isInteger(f) || f <= 0)) {
1058
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "port must be a positive integer" }));
1059
+ return;
1060
+ }
1061
+ if (p !== void 0 && p.trim().length === 0) {
1062
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "projectName must not be empty" }));
1063
+ return;
1064
+ }
1065
+ if (l !== void 0 && !U.includes(l)) {
1066
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "defaultAgent must be a known agent id" }));
1067
+ return;
1068
+ }
1069
+ if (m !== void 0) {
1070
+ for (const v of ["phase", "planDraft", "ideaExtend"])
1071
+ if (!U.includes(m[v])) {
1072
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: `defaultAgents.${v} must be a known agent id` }));
1073
+ return;
1074
+ }
1075
+ }
1076
+ const h = JSON.parse(e), a = m ?? (l !== void 0 ? { phase: l, planDraft: l, ideaExtend: l } : void 0), g = h, { defaultAgent: w, ...T } = g, j = {
1077
+ ...T,
1078
+ ...f !== void 0 && { port: f },
1079
+ ...p !== void 0 && { projectName: p.trim() },
1080
+ ...a && { defaultAgents: a }
1081
+ };
1082
+ await I(i, JSON.stringify(j, null, 2)), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1083
+ } catch (i) {
1084
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
1085
+ }
1086
+ return;
1087
+ }
1088
+ if (r.method === "GET" && C === "/api/env") {
1089
+ try {
1090
+ const i = O(n, ".env"), e = O(n, ".env.example"), [d, f] = await Promise.all([
1091
+ et(i),
1092
+ et(e)
1093
+ ]), p = d ? V(await N(i)) : [], l = f ? V(await N(e)).map((a) => a.key) : [], m = new Set(p.map((a) => a.key)), h = l.filter((a) => !m.has(a));
1094
+ t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ exists: d, exampleExists: f, entries: p, missingKeys: h }));
1095
+ } catch (i) {
1096
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
1097
+ }
1098
+ return;
1099
+ }
1100
+ if (r.method === "POST" && C === "/api/env") {
1101
+ try {
1102
+ const i = await H(r), { entries: e } = JSON.parse(i);
1103
+ if (!Array.isArray(e)) {
1104
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "entries is required" }));
1105
+ return;
1106
+ }
1107
+ const d = /* @__PURE__ */ new Set();
1108
+ for (const l of e) {
1109
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(l.key)) {
1110
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: `invalid key: ${l.key}` }));
1111
+ return;
1112
+ }
1113
+ if (d.has(l.key)) {
1114
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: `duplicate key: ${l.key}` }));
1115
+ return;
1116
+ }
1117
+ d.add(l.key);
1118
+ }
1119
+ const f = O(n, ".env"), p = await N(f);
1120
+ await I(f, lt(p, e)), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1121
+ } catch (i) {
1122
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
1123
+ }
1124
+ return;
1125
+ }
1126
+ if (r.method === "GET" && C === "/api/configs") {
1127
+ const e = new URL(r.url ?? "", `http://${r.headers.host ?? "localhost"}`).searchParams.get("name");
1128
+ if (e)
1129
+ try {
1130
+ if (!Lt.includes(e)) {
1131
+ t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "invalid config file name" }));
1132
+ return;
1133
+ }
1134
+ const d = await N(O(n, e));
1135
+ if (!d) {
1136
+ t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "config file not found" }));
1137
+ return;
1138
+ }
1139
+ t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ name: e, content: d }));
1140
+ return;
1141
+ } catch (d) {
1142
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: d.message }));
1143
+ return;
1144
+ }
1145
+ }
1146
+ const S = _t.find((i) => i.path === C);
1147
+ if (!S) {
1148
+ b();
1149
+ return;
1150
+ }
1151
+ try {
1152
+ const i = await S.handler(n);
1153
+ t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify(i));
1154
+ } catch (i) {
1155
+ t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
1156
+ }
1157
+ };
1158
+ return y.agent = u, y;
1159
+ }
1160
+ const Bt = {
1161
+ ".html": "text/html; charset=utf-8",
1162
+ ".js": "text/javascript; charset=utf-8",
1163
+ ".mjs": "text/javascript; charset=utf-8",
1164
+ ".css": "text/css; charset=utf-8",
1165
+ ".json": "application/json; charset=utf-8",
1166
+ ".svg": "image/svg+xml",
1167
+ ".png": "image/png",
1168
+ ".jpg": "image/jpeg",
1169
+ ".ico": "image/x-icon",
1170
+ ".woff": "font/woff",
1171
+ ".woff2": "font/woff2"
1172
+ };
1173
+ function Ft() {
1174
+ return O(ct(wt(import.meta.url)), "..", "app");
1175
+ }
1176
+ async function Gt({ root: n, port: c }) {
1177
+ const o = Ft(), s = O(o, "index.html"), u = await $(s, "utf-8").catch(() => null);
1178
+ if (u === null)
1179
+ throw new Error(
1180
+ `Dashboard assets not found at ${o}. Run \`pnpm build\` (or reinstall the package) so dist/app exists.`
1181
+ );
1182
+ const y = Mt(n);
1183
+ async function r(C, S) {
1184
+ const i = decodeURIComponent((C.url ?? "/").split("?")[0]), e = O(o, i === "/" ? "index.html" : i);
1185
+ try {
1186
+ if ((await st(e)).isFile()) {
1187
+ S.statusCode = 200, S.setHeader("Content-Type", Bt[dt(e)] ?? "application/octet-stream"), S.end(await $(e));
1188
+ return;
1189
+ }
1190
+ } catch {
1191
+ }
1192
+ S.statusCode = 200, S.setHeader("Content-Type", "text/html; charset=utf-8"), S.end(u);
1193
+ }
1194
+ const t = Ct((C, S) => {
1195
+ y(C, S, () => {
1196
+ r(C, S).catch((i) => {
1197
+ S.statusCode = 500, S.end(String(i));
1198
+ });
1199
+ });
1200
+ }), b = () => {
1201
+ y.agent.killCurrent(), process.exit(0);
1202
+ };
1203
+ process.on("SIGINT", b), process.on("SIGTERM", b), await new Promise((C) => t.listen(c, C));
1204
+ }
1205
+ const D = new ut();
1206
+ D.name("paper-camp").description("Local-first, AI-native project companion.").version(gt);
1207
+ D.command("init [project-name]").description("Initialize Paper Camp in the current directory").option("-i, --intent <text>", "one-line description of what you are building").action(async (n, c) => {
1208
+ const o = process.cwd(), s = n ?? pt(o);
1209
+ try {
1210
+ await mt(o, { projectName: s, intent: c.intent }), console.log(`Initialized Paper Camp in ${o}`), console.log(" .paper-camp/config.json"), console.log(" papercamp/ideas.md, plans.md, progress.md, decisions.md, open-questions.md");
1211
+ } catch (u) {
1212
+ if (u instanceof yt) {
1213
+ console.error(u.message), process.exitCode = 1;
1214
+ return;
1215
+ }
1216
+ throw u;
1217
+ }
1218
+ });
1219
+ D.command("dev").description("Start the local dashboard").option("-p, --port <number>", "port to listen on", "3333").action(async (n) => {
1220
+ const c = Number(n.port), o = process.cwd();
1221
+ try {
1222
+ await Gt({ root: o, port: c }), console.log(`Paper Camp dashboard running at http://localhost:${c}`);
1223
+ } catch (s) {
1224
+ console.error(s.message), process.exitCode = 1;
1225
+ }
1226
+ });
1227
+ D.command("add <type> [name]").description("Add a new entry (currently supports: plan)").option("-k, --kind <kind>", `plan kind (${F.join("|")})`, "feat").action(async (n, c, o) => {
1228
+ if (n !== "plan") {
1229
+ console.error(`Unknown type "${n}". Supported types: plan`), process.exitCode = 1;
1230
+ return;
1231
+ }
1232
+ if (!c) {
1233
+ console.error("Usage: paper-camp add plan <name> [--kind feat|fix|chore|docs|refactor]"), process.exitCode = 1;
1234
+ return;
1235
+ }
1236
+ if (!F.includes(o.kind)) {
1237
+ console.error(`Unknown kind "${o.kind}". Supported kinds: ${F.join(", ")}`), process.exitCode = 1;
1238
+ return;
1239
+ }
1240
+ const s = o.kind, u = Y(process.cwd(), ".paper-camp", "config.json"), y = await at(u, s), r = Y(process.cwd(), "papercamp", "plans.md"), t = it({
1241
+ title: c,
1242
+ status: "idea",
1243
+ kind: s,
1244
+ id: y,
1245
+ created: B()
1246
+ });
1247
+ await ot(r, t), console.log(`Added plan "${c}"${y ? ` (${y})` : ""} to papercamp/plans.md`);
1248
+ });
1249
+ D.parseAsync(process.argv);
1250
+ //# sourceMappingURL=index.js.map