projectinator 0.3.1 → 0.4.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 (171) hide show
  1. package/README.md +7 -6
  2. package/bin/projectinator.mjs +22 -15
  3. package/dist/bakeoff.js +163 -0
  4. package/dist/bakeoff.js.map +1 -0
  5. package/dist/build-state.js +26 -0
  6. package/dist/build-state.js.map +1 -0
  7. package/dist/burndown.js +19 -0
  8. package/dist/burndown.js.map +1 -0
  9. package/dist/calibration.js +86 -0
  10. package/dist/calibration.js.map +1 -0
  11. package/dist/cli.js +289 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/cost.js +35 -0
  14. package/dist/cost.js.map +1 -0
  15. package/dist/council.js +147 -0
  16. package/dist/council.js.map +1 -0
  17. package/dist/demo.js +93 -0
  18. package/dist/demo.js.map +1 -0
  19. package/dist/estimate.js +98 -0
  20. package/dist/estimate.js.map +1 -0
  21. package/dist/executor.js +118 -0
  22. package/dist/executor.js.map +1 -0
  23. package/dist/git.js +82 -0
  24. package/dist/git.js.map +1 -0
  25. package/dist/intake.js +98 -0
  26. package/dist/intake.js.map +1 -0
  27. package/dist/models.js +171 -0
  28. package/dist/models.js.map +1 -0
  29. package/dist/narrate.js +76 -0
  30. package/dist/narrate.js.map +1 -0
  31. package/dist/openrouter.js +112 -0
  32. package/dist/openrouter.js.map +1 -0
  33. package/dist/orchestrator.js +273 -0
  34. package/dist/orchestrator.js.map +1 -0
  35. package/dist/pm.js +245 -0
  36. package/dist/pm.js.map +1 -0
  37. package/dist/preview.js +194 -0
  38. package/dist/preview.js.map +1 -0
  39. package/dist/registry-store.js +38 -0
  40. package/dist/registry-store.js.map +1 -0
  41. package/dist/registry.js +115 -0
  42. package/dist/registry.js.map +1 -0
  43. package/dist/research.js +98 -0
  44. package/dist/research.js.map +1 -0
  45. package/dist/retro.js +83 -0
  46. package/dist/retro.js.map +1 -0
  47. package/dist/roles.js +357 -0
  48. package/dist/roles.js.map +1 -0
  49. package/dist/router.js +95 -0
  50. package/dist/router.js.map +1 -0
  51. package/dist/run-bakeoff.js +68 -0
  52. package/dist/run-bakeoff.js.map +1 -0
  53. package/dist/run-build.js +193 -0
  54. package/dist/run-build.js.map +1 -0
  55. package/{src/run-dev.ts → dist/run-dev.js} +23 -39
  56. package/dist/run-dev.js.map +1 -0
  57. package/{src/run-pm.ts → dist/run-pm.js} +34 -46
  58. package/dist/run-pm.js.map +1 -0
  59. package/{src/run-research.ts → dist/run-research.js} +19 -30
  60. package/dist/run-research.js.map +1 -0
  61. package/{src/run-scout.ts → dist/run-scout.js} +17 -27
  62. package/dist/run-scout.js.map +1 -0
  63. package/dist/run-web.js +93 -0
  64. package/dist/run-web.js.map +1 -0
  65. package/dist/scout.js +81 -0
  66. package/dist/scout.js.map +1 -0
  67. package/dist/session-cost.js +15 -0
  68. package/dist/session-cost.js.map +1 -0
  69. package/dist/stack.js +34 -0
  70. package/dist/stack.js.map +1 -0
  71. package/dist/stuck.js +21 -0
  72. package/dist/stuck.js.map +1 -0
  73. package/dist/tui/App.js +1269 -0
  74. package/dist/tui/App.js.map +1 -0
  75. package/dist/tui/BakeOff.js +95 -0
  76. package/dist/tui/BakeOff.js.map +1 -0
  77. package/dist/tui/BoardEditor.js +183 -0
  78. package/dist/tui/BoardEditor.js.map +1 -0
  79. package/dist/tui/EditableBoard.js +140 -0
  80. package/dist/tui/EditableBoard.js.map +1 -0
  81. package/dist/tui/Frame.js +78 -0
  82. package/dist/tui/Frame.js.map +1 -0
  83. package/dist/tui/Intake.js +59 -0
  84. package/dist/tui/Intake.js.map +1 -0
  85. package/dist/tui/Kanban.js +75 -0
  86. package/dist/tui/Kanban.js.map +1 -0
  87. package/dist/tui/Settings.js +299 -0
  88. package/dist/tui/Settings.js.map +1 -0
  89. package/dist/tui/StackPick.js +44 -0
  90. package/dist/tui/StackPick.js.map +1 -0
  91. package/dist/tui/WebAccounts.js +131 -0
  92. package/dist/tui/WebAccounts.js.map +1 -0
  93. package/dist/tui/components.js +178 -0
  94. package/dist/tui/components.js.map +1 -0
  95. package/dist/tui/config.js +129 -0
  96. package/dist/tui/config.js.map +1 -0
  97. package/dist/tui/deploy.js +114 -0
  98. package/dist/tui/deploy.js.map +1 -0
  99. package/dist/tui/engine.js +710 -0
  100. package/dist/tui/engine.js.map +1 -0
  101. package/dist/tui/notify.js +47 -0
  102. package/dist/tui/notify.js.map +1 -0
  103. package/dist/tui/panels.js +42 -0
  104. package/dist/tui/panels.js.map +1 -0
  105. package/dist/tui/templates.js +106 -0
  106. package/dist/tui/templates.js.map +1 -0
  107. package/dist/tui/theme.js +42 -0
  108. package/dist/tui/theme.js.map +1 -0
  109. package/dist/tui/validate.js +51 -0
  110. package/dist/tui/validate.js.map +1 -0
  111. package/{src/tui.tsx → dist/tui.js} +23 -33
  112. package/dist/tui.js.map +1 -0
  113. package/dist/types.js +19 -0
  114. package/dist/types.js.map +1 -0
  115. package/dist/web/oauth-anthropic.js +179 -0
  116. package/dist/web/oauth-anthropic.js.map +1 -0
  117. package/dist/web/session.js +274 -0
  118. package/dist/web/session.js.map +1 -0
  119. package/package.json +5 -4
  120. package/src/bakeoff.ts +0 -214
  121. package/src/build-state.ts +0 -47
  122. package/src/burndown.ts +0 -35
  123. package/src/calibration.ts +0 -88
  124. package/src/cli.ts +0 -263
  125. package/src/cost.ts +0 -43
  126. package/src/council.ts +0 -175
  127. package/src/demo.ts +0 -106
  128. package/src/estimate.ts +0 -111
  129. package/src/executor.ts +0 -169
  130. package/src/git.ts +0 -72
  131. package/src/intake.ts +0 -125
  132. package/src/models.ts +0 -175
  133. package/src/narrate.ts +0 -87
  134. package/src/openrouter.ts +0 -119
  135. package/src/orchestrator.ts +0 -310
  136. package/src/pm.ts +0 -302
  137. package/src/preview.ts +0 -206
  138. package/src/registry-store.ts +0 -41
  139. package/src/registry.ts +0 -132
  140. package/src/research.ts +0 -123
  141. package/src/retro.ts +0 -99
  142. package/src/roles.ts +0 -401
  143. package/src/router.ts +0 -120
  144. package/src/run-bakeoff.ts +0 -77
  145. package/src/run-build.ts +0 -203
  146. package/src/run-web.ts +0 -87
  147. package/src/scout.ts +0 -121
  148. package/src/session-cost.ts +0 -17
  149. package/src/stack.ts +0 -46
  150. package/src/tui/App.tsx +0 -1760
  151. package/src/tui/BakeOff.tsx +0 -190
  152. package/src/tui/BoardEditor.tsx +0 -260
  153. package/src/tui/EditableBoard.tsx +0 -179
  154. package/src/tui/Frame.tsx +0 -142
  155. package/src/tui/Intake.tsx +0 -111
  156. package/src/tui/Kanban.tsx +0 -158
  157. package/src/tui/Settings.tsx +0 -513
  158. package/src/tui/StackPick.tsx +0 -79
  159. package/src/tui/WebAccounts.tsx +0 -197
  160. package/src/tui/components.tsx +0 -340
  161. package/src/tui/config.ts +0 -150
  162. package/src/tui/deploy.ts +0 -137
  163. package/src/tui/engine.ts +0 -749
  164. package/src/tui/notify.ts +0 -21
  165. package/src/tui/panels.tsx +0 -89
  166. package/src/tui/templates.ts +0 -119
  167. package/src/tui/theme.ts +0 -44
  168. package/src/tui/validate.ts +0 -53
  169. package/src/types.ts +0 -208
  170. package/src/web/oauth-anthropic.ts +0 -206
  171. package/src/web/session.ts +0 -299
package/src/run-build.ts DELETED
@@ -1,203 +0,0 @@
1
- // Phase 4 entry — run a whole build end to end.
2
- //
3
- // npm run build -- "an idea" dry: decompose + toposort + routing plan, NO spend
4
- // npm run build -- --mini dry: tiny fixed 3-task backlog (design->code->test)
5
- // npm run build -- --live --mini LIVE: run the mini backlog (cheap end-to-end)
6
- // npm run build -- --live --lock anthropic "an idea" LIVE: full pipeline on one provider
7
- //
8
- // --lock <provider> routes every role to one provider (use the one you hold a key for).
9
- // Default lock is anthropic.
10
-
11
- import { mkdirSync } from "node:fs";
12
- import { fileURLToPath } from "node:url";
13
- import { dirname, join } from "node:path";
14
- import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
15
- import type { Provider, Task } from "./types.js";
16
- import { DEFAULT_POLICY } from "./router.js";
17
- import { toposort, runBacklog, type OrchestratorEvent } from "./orchestrator.js";
18
- import { lockRegistryToProvider, makePiExecutor } from "./roles.js";
19
- import { estimateTokens } from "./estimate.js";
20
- import { route } from "./router.js";
21
- import { decomposeIdea } from "./pm.js";
22
- import { newBuildState, saveState, loadState, completedIds, type BuildState } from "./build-state.js";
23
- import { initRepo, commitTask } from "./git.js";
24
-
25
- const args = process.argv.slice(2);
26
- const live = args.includes("--live");
27
- const mini = args.includes("--mini");
28
- const fan = args.includes("--fan");
29
- const resume = args.includes("--resume");
30
- const lockIdx = args.indexOf("--lock");
31
- const lockProvider = (lockIdx >= 0 ? args[lockIdx + 1] : "anthropic") as Provider;
32
- const concIdx = args.indexOf("--concurrency");
33
- const concurrency = Math.max(1, parseInt((concIdx >= 0 ? args[concIdx + 1] : "1") ?? "1", 10) || 1);
34
- const capIdx = args.indexOf("--task-cap");
35
- const tmoIdx = args.indexOf("--task-timeout");
36
- const consumed = new Set(
37
- ["--live", "--mini", "--fan", "--resume", "--lock", lockIdx >= 0 ? args[lockIdx + 1] : "",
38
- "--concurrency", concIdx >= 0 ? args[concIdx + 1] : "",
39
- "--task-cap", capIdx >= 0 ? args[capIdx + 1] : "",
40
- "--task-timeout", tmoIdx >= 0 ? args[tmoIdx + 1] : ""].filter(Boolean),
41
- );
42
- const idea = args.filter((a) => !consumed.has(a)).join(" ").trim() ||
43
- "A one-page site with a headline and a contact form.";
44
-
45
- const money = (n: number) => `$${n.toFixed(2)}`;
46
- const registry = lockRegistryToProvider(lockProvider);
47
- // --task-cap USD / --task-timeout MIN override the per-task limits (0 = unlimited).
48
- const taskLimits = {
49
- costCapUSD: capIdx >= 0 ? parseFloat(args[capIdx + 1] ?? "") : DEFAULT_POLICY.taskLimits.costCapUSD,
50
- timeoutMs: tmoIdx >= 0 ? parseFloat(args[tmoIdx + 1] ?? "") * 60_000 : DEFAULT_POLICY.taskLimits.timeoutMs,
51
- };
52
- const policy = { ...DEFAULT_POLICY, backendMode: "api" as const, budgetCapUSD: mini ? 10 : 25, taskLimits };
53
-
54
- // --- a tiny fixed backlog for cheap end-to-end proof ---
55
- function miniBacklog(): Task[] {
56
- const mk = (id: string, cap: Task["capability"], diff: Task["difficulty"], title: string, deps: string[] = []): Task => ({
57
- id, title, capability: cap, difficulty: diff, dependsOn: deps, estTokens: estimateTokens(cap, diff),
58
- });
59
- return [
60
- mk("D-1", "design", "low", "Write a short design spec for a centered card that says 'Hello from Projectinator' on a soft gradient background."),
61
- mk("C-1", "code", "low", "Create index.html implementing the design spec exactly. Single self-contained file with embedded CSS.", ["D-1"]),
62
- mk("R-1", "review", "trivial", "Review index.html against the design spec: file present, no unresolved references, runs on double-click.", ["C-1"]),
63
- mk("T-1", "test", "trivial", "Open/read index.html and verify it is valid HTML and matches the design spec (centered card, the headline text, a gradient).", ["R-1"]),
64
- ];
65
- }
66
-
67
- // A fan-out backlog with independent branches, to show parallel execution:
68
- // two design tasks (independent) -> two code tasks -> one test that joins them.
69
- function fanBacklog(): Task[] {
70
- const mk = (id: string, cap: Task["capability"], diff: Task["difficulty"], title: string, deps: string[] = []): Task => ({
71
- id, title, capability: cap, difficulty: diff, dependsOn: deps, estTokens: estimateTokens(cap, diff),
72
- });
73
- return [
74
- mk("DA", "design", "low", "Design spec for a 'Newsletter signup' card (email input + button)."),
75
- mk("DB", "design", "low", "Design spec for an 'FAQ' accordion (3 questions)."),
76
- mk("CA", "code", "low", "Create newsletter.html from the newsletter design spec.", ["DA"]),
77
- mk("CB", "code", "low", "Create faq.html from the FAQ design spec.", ["DB"]),
78
- mk("RA", "review", "trivial", "Review newsletter.html against its spec.", ["CA"]),
79
- mk("RB", "review", "trivial", "Review faq.html against its spec.", ["CB"]),
80
- mk("TJ", "test", "trivial", "Verify newsletter.html and faq.html are valid and match their specs.", ["RA", "RB"]),
81
- ];
82
- }
83
-
84
- async function getTasks(): Promise<Task[]> {
85
- if (fan) return fanBacklog();
86
- if (mini) return miniBacklog();
87
- if (!live) {
88
- // Dry + full idea: we can't call the PM without spending, so show the mini path.
89
- console.log(" (Full-idea decomposition needs the PM model. Use --mini for a dry preview, or add --live.)\n");
90
- return miniBacklog();
91
- }
92
- console.log(" Decomposing idea with PM...\n");
93
- const res = await decomposeIdea(idea, {
94
- backend: "api",
95
- modelOverride: { provider: lockProvider, model: lockRegistryToProvider(lockProvider).find((e) => e.capability === "plan")!.byBackend.api.model },
96
- });
97
- console.log(` PM produced ${res.tasks.length} tasks.\n`);
98
- return res.tasks;
99
- }
100
-
101
- const variant = fan ? " / fan" : mini ? " / mini" : "";
102
- console.log(`\n Projectinator — Phase 4 full build [${live ? "LIVE" : "DRY"}${variant}] lock=${lockProvider} concurrency=${concurrency}\n`);
103
- if (!mini && !fan) console.log(` Idea: ${idea}\n`);
104
-
105
- let tasks = await getTasks();
106
-
107
- // Dry: show plan only.
108
- if (!live) {
109
- const ordered = toposort(tasks);
110
- console.log(" Execution order (toposorted):");
111
- let est = 0;
112
- for (const t of ordered) {
113
- const d = route(t, { policy, registry, runningTotalBefore: est });
114
- est = d.runningTotal;
115
- const dep = t.dependsOn?.length ? ` <- ${t.dependsOn.join(",")}` : "";
116
- console.log(` ${t.id.padEnd(6)} [${t.capability}/${t.difficulty}] -> ${d.model.name.padEnd(20)} ${money(d.cost)}${dep}`);
117
- }
118
- console.log(`\n Estimated total: ${money(est)} (cap ${money(policy.budgetCapUSD)})`);
119
- console.log(" Dry run. Add --live to actually build.\n");
120
- process.exit(0);
121
- }
122
-
123
- // Live: key check.
124
- const envKey: Record<Provider, string[]> = {
125
- anthropic: ["ANTHROPIC_API_KEY"],
126
- openai: ["OPENAI_API_KEY"],
127
- google: ["GEMINI_API_KEY", "GOOGLE_API_KEY", "GOOGLE_GENERATIVE_AI_API_KEY"],
128
- openrouter: ["OPENROUTER_API_KEY"],
129
- };
130
- if (!(envKey[lockProvider] ?? []).some((k) => process.env[k])) {
131
- console.error(` No API key for ${lockProvider}. Set: ${(envKey[lockProvider] ?? []).join(", ")}\n`);
132
- process.exit(1);
133
- }
134
-
135
- const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)));
136
- const workspace = join(projectRoot, ".workspace", fan ? "fan-build" : mini ? "mini-build" : "build");
137
- mkdirSync(workspace, { recursive: true });
138
- const statePath = join(workspace, "build-state.json");
139
-
140
- // Resume: load prior state, reuse its saved backlog, seed finished outcomes.
141
- let state: BuildState;
142
- let seedOutcomes = undefined;
143
- const prior = resume ? loadState(statePath) : undefined;
144
- if (prior) {
145
- state = prior;
146
- state.status = "running";
147
- tasks = prior.tasks; // authoritative backlog from the interrupted run
148
- seedOutcomes = prior.outcomes;
149
- const doneCount = completedIds(prior).size;
150
- console.log(` Resuming: ${doneCount} task(s) already done, restored ${money(prior.totalCost)}.`);
151
- } else {
152
- if (resume) console.log(" (No prior state found — starting fresh.)");
153
- state = newBuildState(fan ? "fan-build" : mini ? "mini-build" : "build", tasks);
154
- }
155
- console.log(` Workspace: ${workspace}\n Building...\n`);
156
-
157
- // Version the workspace + commit after each task.
158
- initRepo(workspace);
159
- const titleById = new Map(tasks.map((t) => [t.id, t.title]));
160
-
161
- const onEvent = (_e: AgentSessionEvent) => {};
162
- const executor = makePiExecutor({
163
- workspace,
164
- backend: "api",
165
- onEvent,
166
- onFallback: (info) => console.log(` ↪ ${info.taskId}: ${info.from} failed — fell back to ${info.to}/${info.model}`),
167
- });
168
-
169
- const onProgress = (e: OrchestratorEvent) => {
170
- if (e.type === "task_start") console.log(` ▶ ${e.task.id} [${e.task.capability}] -> ${e.provider}/${e.modelId} (round ${e.round})`);
171
- else if (e.type === "task_done") {
172
- const hash = commitTask(workspace, e.outcome.taskId, titleById.get(e.outcome.taskId) ?? e.outcome.taskId);
173
- console.log(` ✓ ${e.outcome.taskId} ${money(e.outcome.cost)} running ${money(e.runningTotal)}${e.outcome.verdict ? ` verdict=${e.outcome.verdict.passed ? (e.outcome.verdict.runtimeChecked ? "PASS" : "PASS* (app not executed — no Chromium)") : "FAIL"}` : ""}${hash ? ` [${hash}]` : ""}`);
174
- }
175
- else if (e.type === "task_failed") console.log(` ⛔ ${e.outcome.taskId} ABORTED (${e.outcome.error}) — billed ${money(e.outcome.cost)}, halting`);
176
- else if (e.type === "task_skipped") console.log(` · ${e.taskId} skipped (already done)`);
177
- else if (e.type === "test_failed") console.log(` ✗ ${e.taskId} FAILED (${e.bugs} bugs) — round ${e.round}`);
178
- else if (e.type === "retry_dev") console.log(` ↻ re-running ${e.taskId} to fix ${e.forTest}`);
179
- else if (e.type === "budget_halt") console.log(` ⚠ BUDGET HALT at ${money(e.runningTotal)} (cap ${money(e.cap)})`);
180
- };
181
-
182
- // Checkpoint after every task so a crash/cancel loses at most one task.
183
- const onCheckpoint = (outcomes: typeof state.outcomes, totalCost: number) => {
184
- state.outcomes = outcomes;
185
- state.totalCost = totalCost;
186
- saveState(state, statePath);
187
- };
188
-
189
- const result = await runBacklog(tasks, { policy, execute: executor, registry, onProgress, seedOutcomes, onCheckpoint, concurrency });
190
-
191
- state.outcomes = result.outcomes;
192
- state.totalCost = result.totalCost;
193
- state.status = result.halted ? "halted" : "complete";
194
- state.haltReason = result.haltReason;
195
- saveState(state, statePath);
196
-
197
- console.log(`\n --- BUILD ${result.halted ? "HALTED" : "COMPLETE"} ---`);
198
- console.log(` Steps run: ${result.outcomes.length}`);
199
- console.log(` Total cost: ${money(result.totalCost)}`);
200
- if (result.haltReason) console.log(` Halt reason: ${result.haltReason} (re-run with --resume to continue)`);
201
- const finalFiles = result.outcomes.at(-1)?.files ?? [];
202
- console.log(` Files in workspace: ${finalFiles.join(", ") || "(none)"}`);
203
- console.log(` State saved: ${statePath}\n`);
package/src/run-web.ts DELETED
@@ -1,87 +0,0 @@
1
- // EXPERIMENTAL web-login CLI.
2
- //
3
- // npm run web -- login claude open a browser, log in once (session saved)
4
- // npm run web -- ask claude "2+2? one word" send a prompt via your web session
5
- //
6
- // This is the standalone proof-of-concept. Wiring it into the full build pipeline
7
- // (parsing tasks/verdicts/code from text) is a separate step.
8
-
9
- import { spawn } from "node:child_process";
10
- import { createInterface } from "node:readline";
11
- import { webLogin, webComplete, webDump, closeWebSessions, isWebProvider, PROVIDERS } from "./web/session.js";
12
- import { buildAuthorizeUrl, exchangeCode, oauthTest } from "./web/oauth-anthropic.js";
13
-
14
- const [cmd, provider, ...rest] = process.argv.slice(2);
15
-
16
- function usage(): never {
17
- console.log("\n Usage:");
18
- console.log(" npm run web -- login <provider>");
19
- console.log(' npm run web -- ask <provider> "your prompt"');
20
- console.log(" npm run web -- oauth # Claude: OAuth connect (subscription)");
21
- console.log(" npm run web -- oauth-test # Claude: probe if the token still works");
22
- console.log(`\n Providers: ${Object.keys(PROVIDERS).join(", ")}\n`);
23
- process.exit(1);
24
- }
25
-
26
- function ask(q: string): Promise<string> {
27
- const rl = createInterface({ input: process.stdin, output: process.stdout });
28
- return new Promise((resolve) => rl.question(q, (a) => { rl.close(); resolve(a); }));
29
- }
30
-
31
- // ---- Claude OAuth (subscription) — the enforcement experiment ----
32
- if (cmd === "oauth") {
33
- const pending = buildAuthorizeUrl();
34
- console.log("\n Opening your browser to approve Claude access…");
35
- console.log(" If it doesn't open, paste this URL manually:\n");
36
- console.log(" " + pending.url + "\n");
37
- try { spawn("open", [pending.url], { stdio: "ignore", detached: true }).unref(); } catch { /* print-only */ }
38
- const code = await ask(" After approving, copy the code shown on the page and paste it here:\n code> ");
39
- try {
40
- await exchangeCode(code, pending);
41
- console.log("\n Token stored. Testing whether it survives enforcement…\n");
42
- const reply = await oauthTest();
43
- console.log(` ✅ Works. Reply: ${reply}\n`);
44
- } catch (e) {
45
- console.error(`\n ❌ ${e instanceof Error ? e.message : e}\n`);
46
- process.exit(1);
47
- }
48
- process.exit(0);
49
- } else if (cmd === "oauth-test") {
50
- try {
51
- const reply = await oauthTest();
52
- console.log(`\n ✅ Works. Reply: ${reply}\n`);
53
- } catch (e) {
54
- console.error(`\n ❌ ${e instanceof Error ? e.message : e}\n`);
55
- process.exit(1);
56
- }
57
- process.exit(0);
58
- }
59
-
60
- if (!cmd || !provider || !isWebProvider(provider)) usage();
61
-
62
- if (cmd === "login") {
63
- await webLogin(provider);
64
- console.log(` Logged in to ${PROVIDERS[provider].label}. Try: npm run web -- ask ${provider} "hello"\n`);
65
- } else if (cmd === "ask") {
66
- const prompt = rest.join(" ").trim();
67
- if (!prompt) usage();
68
- console.log(` Asking ${PROVIDERS[provider].label} via your web session…\n`);
69
- try {
70
- const reply = await webComplete(provider, prompt);
71
- console.log(" --- reply ---");
72
- console.log(reply.split("\n").map((l) => " " + l).join("\n"));
73
- console.log("");
74
- } catch (e) {
75
- console.error(` Failed: ${e instanceof Error ? e.message : e}`);
76
- console.error(` If selectors are stale, edit src/web/session.ts. If not logged in, run: npm run web -- login ${provider}\n`);
77
- await closeWebSessions();
78
- process.exit(1);
79
- }
80
- await closeWebSessions(); // one-shot CLI: shut the background browser down
81
- } else if (cmd === "dump") {
82
- const prompt = rest.join(" ").trim() || "say hello in one word";
83
- console.log(` Diagnosing ${PROVIDERS[provider].label} DOM…\n`);
84
- await webDump(provider, prompt);
85
- } else {
86
- usage();
87
- }
package/src/scout.ts DELETED
@@ -1,121 +0,0 @@
1
- // The Scout — keeps the registry current.
2
- //
3
- // Input: findings (best model per capability+tier, from a research/benchmark pass —
4
- // the same kind of verified output the deep-research harness produces).
5
- // Output: a proposed registry + a human-readable diff. SEMI-AUTO by design: the Scout
6
- // proposes, you approve, then apply. Nothing changes routing without a look.
7
- //
8
- // Pure + testable: no network. Feed it findings; it computes the delta.
9
-
10
- import type { Backend, Capability, Provider, RegistryEntry, Tier } from "./types.js";
11
- import { MODELS } from "./models.js";
12
-
13
- export interface Finding {
14
- capability: Capability;
15
- tier: Tier;
16
- /** Which backend slot this evidence informs. Benchmarks mostly inform "api". */
17
- backend: Backend;
18
- provider: Provider;
19
- model: string;
20
- evidence: string;
21
- date?: string;
22
- }
23
-
24
- export type ChangeKind = "add" | "update" | "noop" | "unknown-model";
25
-
26
- export interface RegistryChange {
27
- kind: ChangeKind;
28
- capability: Capability;
29
- tier: Tier;
30
- backend: Backend;
31
- from?: string;
32
- to: string;
33
- evidence: string;
34
- }
35
-
36
- export interface Proposal {
37
- updated: RegistryEntry[];
38
- changes: RegistryChange[];
39
- }
40
-
41
- const keyOf = (c: Capability, t: Tier) => `${c}/${t}`;
42
-
43
- /** Compute a proposed registry from the current one + findings. Pure. */
44
- export function proposeUpdate(current: RegistryEntry[], findings: Finding[]): Proposal {
45
- // Clone entries so we don't mutate the input.
46
- const map = new Map<string, RegistryEntry>(
47
- current.map((e) => [keyOf(e.capability, e.tier), cloneEntry(e)]),
48
- );
49
- const changes: RegistryChange[] = [];
50
-
51
- for (const f of findings) {
52
- const k = keyOf(f.capability, f.tier);
53
- const known = !!MODELS[f.model];
54
- const existing = map.get(k);
55
-
56
- if (!existing) {
57
- // New capability/tier slot — seed both backends with the finding, mark others later.
58
- map.set(k, {
59
- capability: f.capability,
60
- tier: f.tier,
61
- byBackend: {
62
- web: { provider: f.provider, model: f.model },
63
- api: { provider: f.provider, model: f.model },
64
- },
65
- evidence: f.evidence,
66
- updated: f.date ?? "scout",
67
- });
68
- changes.push({
69
- kind: known ? "add" : "unknown-model",
70
- capability: f.capability, tier: f.tier, backend: f.backend,
71
- to: f.model, evidence: f.evidence,
72
- });
73
- continue;
74
- }
75
-
76
- const cur = existing.byBackend[f.backend];
77
- if (cur.model === f.model && cur.provider === f.provider) {
78
- changes.push({
79
- kind: "noop", capability: f.capability, tier: f.tier, backend: f.backend,
80
- from: cur.model, to: f.model, evidence: f.evidence,
81
- });
82
- continue;
83
- }
84
-
85
- existing.byBackend[f.backend] = { provider: f.provider, model: f.model };
86
- existing.evidence = f.evidence;
87
- existing.updated = f.date ?? "scout";
88
- changes.push({
89
- kind: known ? "update" : "unknown-model",
90
- capability: f.capability, tier: f.tier, backend: f.backend,
91
- from: cur.model, to: f.model, evidence: f.evidence,
92
- });
93
- }
94
-
95
- return { updated: [...map.values()], changes };
96
- }
97
-
98
- /** Only the changes that actually alter routing (drops noops). */
99
- export function meaningfulChanges(changes: RegistryChange[]): RegistryChange[] {
100
- return changes.filter((c) => c.kind !== "noop");
101
- }
102
-
103
- export function formatProposal(changes: RegistryChange[]): string {
104
- const real = meaningfulChanges(changes);
105
- if (!real.length) return " No changes — registry already matches the findings.";
106
- const icon: Record<ChangeKind, string> = { add: "+", update: "~", noop: " ", "unknown-model": "!" };
107
- return real
108
- .map((c) => {
109
- const arrow = c.from ? `${c.from} -> ${c.to}` : c.to;
110
- const warn = c.kind === "unknown-model" ? " [!] model not in models.ts — add it before applying" : "";
111
- return ` ${icon[c.kind]} ${c.capability}/${c.tier} [${c.backend}] ${arrow}\n ${c.evidence}${warn}`;
112
- })
113
- .join("\n");
114
- }
115
-
116
- function cloneEntry(e: RegistryEntry): RegistryEntry {
117
- return {
118
- ...e,
119
- byBackend: { web: { ...e.byBackend.web }, api: { ...e.byBackend.api } },
120
- };
121
- }
@@ -1,17 +0,0 @@
1
- // Running total of money spent in THIS process (session). Every Pi session adds its
2
- // own cost here after it finishes, so it captures builds, planning, breakdowns — all of it.
3
- // Resets on process restart (module-level state).
4
-
5
- let total = 0;
6
-
7
- export function addSessionCost(n: number): void {
8
- if (Number.isFinite(n) && n > 0) total += n;
9
- }
10
-
11
- export function sessionCost(): number {
12
- return Math.round(total * 100) / 100;
13
- }
14
-
15
- export function resetSessionCost(): void {
16
- total = 0;
17
- }
package/src/stack.ts DELETED
@@ -1,46 +0,0 @@
1
- // Target stack — the platform + framework a build should target. The choice is
2
- // turned into a short instruction appended to the brief, so the PM and dev build
3
- // the right thing. Kept build-free on purpose (CDN React, no Vite/npm) so the
4
- // existing static test/preview/deploy pipeline works unchanged.
5
-
6
- export type Platform = "web" | "mobile" | "desktop";
7
-
8
- /** Web framework ids. "ai" = let the PM decide; any other string = custom. */
9
- export type Framework = "vanilla" | "react" | "ai" | (string & {});
10
-
11
- export interface StackChoice {
12
- platform: Platform;
13
- framework: Framework;
14
- }
15
-
16
- export const WEB_FRAMEWORKS: { id: Framework; label: string }[] = [
17
- { id: "vanilla", label: "Vanilla HTML / CSS / JS (no framework)" },
18
- { id: "react", label: "React (via CDN, no build step)" },
19
- { id: "ai", label: "Let the AI decide" },
20
- ];
21
-
22
- const WEB_DESC: Record<string, string> = {
23
- vanilla: "vanilla HTML, CSS, and JavaScript — no framework, no build step",
24
- react:
25
- "React loaded from a CDN (no build step): a single index.html that imports React and ReactDOM " +
26
- "from https://esm.sh/react and https://esm.sh/react-dom/client as ES modules, uses function " +
27
- "components and hooks, and mounts into a <div id=\"root\">. Put styles in styles.css. Do NOT use " +
28
- "Vite, JSX files, npm, or any build tooling — everything must run by opening index.html.",
29
- };
30
-
31
- /** The instruction appended to the brief for the chosen stack (empty = AI decides). */
32
- export function stackInstruction(choice: StackChoice): string {
33
- const { platform, framework } = choice;
34
- if (platform !== "web") {
35
- return `\n\nTarget platform: ${platform}. Native ${platform} toolchains aren't wired up yet — build a responsive web app (single index.html) styled to feel like a ${platform} app.`;
36
- }
37
- if (!framework || framework === "ai") return ""; // let the PM pick a web approach
38
- const desc = WEB_DESC[framework] ?? `the ${framework} stack (no build step; must run by opening index.html)`;
39
- return `\n\nTarget stack: ${desc}.`;
40
- }
41
-
42
- /** Human label for a stored choice, for display. */
43
- export function stackLabel(choice: StackChoice): string {
44
- if (choice.platform !== "web") return `${choice.platform} (as web)`;
45
- return WEB_FRAMEWORKS.find((f) => f.id === choice.framework)?.label ?? String(choice.framework);
46
- }