@turri-dev/cli 0.1.6 → 0.1.7-next.ccadaa0

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 (226) hide show
  1. package/assets/jellyfish/dark-00.png +0 -0
  2. package/assets/jellyfish/dark-01.png +0 -0
  3. package/assets/jellyfish/dark-02.png +0 -0
  4. package/assets/jellyfish/dark-03.png +0 -0
  5. package/assets/jellyfish/dark-04.png +0 -0
  6. package/assets/jellyfish/dark-05.png +0 -0
  7. package/assets/jellyfish/dark-06.png +0 -0
  8. package/assets/jellyfish/dark-07.png +0 -0
  9. package/assets/jellyfish/dark-08.png +0 -0
  10. package/assets/jellyfish/dark-09.png +0 -0
  11. package/assets/jellyfish/dark-10.png +0 -0
  12. package/assets/jellyfish/dark-11.png +0 -0
  13. package/assets/jellyfish/dark-12.png +0 -0
  14. package/assets/jellyfish/dark-13.png +0 -0
  15. package/assets/jellyfish/dark-14.png +0 -0
  16. package/assets/jellyfish/dark-15.png +0 -0
  17. package/assets/jellyfish/dark-16.png +0 -0
  18. package/assets/jellyfish/dark-17.png +0 -0
  19. package/assets/jellyfish/dark-18.png +0 -0
  20. package/assets/jellyfish/dark-19.png +0 -0
  21. package/assets/jellyfish/dark-20.png +0 -0
  22. package/assets/jellyfish/dark-21.png +0 -0
  23. package/assets/jellyfish/dark-22.png +0 -0
  24. package/assets/jellyfish/dark-23.png +0 -0
  25. package/assets/jellyfish/dark-24.png +0 -0
  26. package/assets/jellyfish/dark-25.png +0 -0
  27. package/assets/jellyfish/dark-26.png +0 -0
  28. package/assets/jellyfish/dark-27.png +0 -0
  29. package/assets/jellyfish/dark-28.png +0 -0
  30. package/assets/jellyfish/dark-29.png +0 -0
  31. package/assets/jellyfish/dark-30.png +0 -0
  32. package/assets/jellyfish/dark-31.png +0 -0
  33. package/assets/jellyfish/dark-32.png +0 -0
  34. package/assets/jellyfish/dark-33.png +0 -0
  35. package/assets/jellyfish/dark-34.png +0 -0
  36. package/assets/jellyfish/dark-35.png +0 -0
  37. package/assets/jellyfish/dark-36.png +0 -0
  38. package/assets/jellyfish/dark-37.png +0 -0
  39. package/assets/jellyfish/dark-38.png +0 -0
  40. package/assets/jellyfish/dark-39.png +0 -0
  41. package/assets/jellyfish/dark-40.png +0 -0
  42. package/assets/jellyfish/light-00.png +0 -0
  43. package/assets/jellyfish/light-01.png +0 -0
  44. package/assets/jellyfish/light-02.png +0 -0
  45. package/assets/jellyfish/light-03.png +0 -0
  46. package/assets/jellyfish/light-04.png +0 -0
  47. package/assets/jellyfish/light-05.png +0 -0
  48. package/assets/jellyfish/light-06.png +0 -0
  49. package/assets/jellyfish/light-07.png +0 -0
  50. package/assets/jellyfish/light-08.png +0 -0
  51. package/assets/jellyfish/light-09.png +0 -0
  52. package/assets/jellyfish/light-10.png +0 -0
  53. package/assets/jellyfish/light-11.png +0 -0
  54. package/assets/jellyfish/light-12.png +0 -0
  55. package/assets/jellyfish/light-13.png +0 -0
  56. package/assets/jellyfish/light-14.png +0 -0
  57. package/assets/jellyfish/light-15.png +0 -0
  58. package/assets/jellyfish/light-16.png +0 -0
  59. package/assets/jellyfish/light-17.png +0 -0
  60. package/assets/jellyfish/light-18.png +0 -0
  61. package/assets/jellyfish/light-19.png +0 -0
  62. package/assets/jellyfish/light-20.png +0 -0
  63. package/assets/jellyfish/light-21.png +0 -0
  64. package/assets/jellyfish/light-22.png +0 -0
  65. package/assets/jellyfish/light-23.png +0 -0
  66. package/assets/jellyfish/light-24.png +0 -0
  67. package/assets/jellyfish/light-25.png +0 -0
  68. package/assets/jellyfish/light-26.png +0 -0
  69. package/assets/jellyfish/light-27.png +0 -0
  70. package/assets/jellyfish/light-28.png +0 -0
  71. package/assets/jellyfish/light-29.png +0 -0
  72. package/assets/jellyfish/light-30.png +0 -0
  73. package/assets/jellyfish/light-31.png +0 -0
  74. package/assets/jellyfish/light-32.png +0 -0
  75. package/assets/jellyfish/light-33.png +0 -0
  76. package/assets/jellyfish/light-34.png +0 -0
  77. package/assets/jellyfish/light-35.png +0 -0
  78. package/assets/jellyfish/light-36.png +0 -0
  79. package/assets/jellyfish/light-37.png +0 -0
  80. package/assets/jellyfish/light-38.png +0 -0
  81. package/assets/jellyfish/light-39.png +0 -0
  82. package/assets/jellyfish/light-40.png +0 -0
  83. package/dist/ask.d.ts +83 -0
  84. package/dist/brand.d.ts +13 -0
  85. package/dist/btw.d.ts +67 -0
  86. package/dist/chat-models.d.ts +22 -0
  87. package/dist/chat-skills.d.ts +17 -0
  88. package/dist/cli-options.d.ts +25 -0
  89. package/dist/cli.d.ts +9 -0
  90. package/dist/config-command.d.ts +26 -0
  91. package/dist/config-edit.d.ts +69 -0
  92. package/dist/config.d.ts +321 -0
  93. package/dist/conform-cli.d.ts +1 -0
  94. package/dist/cultivate-cli.d.ts +1 -0
  95. package/dist/drift-cli.d.ts +1 -0
  96. package/dist/environment.d.ts +39 -0
  97. package/dist/extension.d.ts +147 -0
  98. package/dist/extension.js +49 -18
  99. package/dist/genome.d.ts +62 -0
  100. package/dist/graph.d.ts +52 -0
  101. package/dist/host.d.ts +21 -0
  102. package/dist/host.js +25 -0
  103. package/dist/identity.d.ts +2 -0
  104. package/dist/init-config.d.ts +106 -0
  105. package/dist/jellyfish.d.ts +35 -0
  106. package/dist/jellyfish.js +230 -97
  107. package/dist/judge.d.ts +25 -0
  108. package/dist/lineage-cli.d.ts +1 -0
  109. package/dist/loop-cli.d.ts +34 -0
  110. package/dist/loop-tools.d.ts +80 -0
  111. package/dist/mature-tools.d.ts +53 -0
  112. package/dist/model-check.d.ts +42 -0
  113. package/dist/module-report.d.ts +9 -0
  114. package/dist/pi-launch.d.ts +1 -0
  115. package/dist/pi.d.ts +246 -0
  116. package/dist/picture-row.d.ts +10 -0
  117. package/dist/plain/check.d.ts +23 -0
  118. package/dist/plain/concepts.d.ts +11 -0
  119. package/dist/plain/errors.d.ts +22 -0
  120. package/dist/plain/files.d.ts +12 -0
  121. package/dist/plain/highlight.d.ts +42 -0
  122. package/dist/plain/index.d.ts +15 -0
  123. package/dist/plain/liquid.d.ts +22 -0
  124. package/dist/plain/markdown.d.ts +74 -0
  125. package/dist/plain/parser.d.ts +87 -0
  126. package/dist/plain/spec.d.ts +90 -0
  127. package/dist/plain/stdlib.d.ts +14 -0
  128. package/dist/question-dialog.d.ts +51 -0
  129. package/dist/questioning.d.ts +55 -0
  130. package/dist/quiet.d.ts +34 -0
  131. package/dist/regenerate/activity.d.ts +207 -0
  132. package/dist/regenerate/agent-env.d.ts +44 -0
  133. package/dist/regenerate/agent-shell.d.ts +49 -0
  134. package/dist/regenerate/arbiter.d.ts +68 -0
  135. package/dist/regenerate/audit.d.ts +69 -0
  136. package/dist/regenerate/cases.d.ts +9 -0
  137. package/dist/regenerate/change-view.d.ts +18 -0
  138. package/dist/regenerate/choose-conformance.d.ts +32 -0
  139. package/dist/regenerate/chunk-plan.d.ts +164 -0
  140. package/dist/regenerate/codebase.d.ts +21 -0
  141. package/dist/regenerate/command-check.d.ts +40 -0
  142. package/dist/regenerate/compaction.d.ts +13 -0
  143. package/dist/regenerate/confine.d.ts +62 -0
  144. package/dist/regenerate/conform.d.ts +63 -0
  145. package/dist/regenerate/conformance-agents.d.ts +405 -0
  146. package/dist/regenerate/conformance.d.ts +189 -0
  147. package/dist/regenerate/conversation-writes.d.ts +10 -0
  148. package/dist/regenerate/credentials.d.ts +18 -0
  149. package/dist/regenerate/debug-log.d.ts +54 -0
  150. package/dist/regenerate/debugger.d.ts +31 -0
  151. package/dist/regenerate/delivery.d.ts +6 -0
  152. package/dist/regenerate/diagnose-job.d.ts +2 -0
  153. package/dist/regenerate/diagnose.d.ts +46 -0
  154. package/dist/regenerate/diff-view.d.ts +61 -0
  155. package/dist/regenerate/drift.d.ts +84 -0
  156. package/dist/regenerate/engine.d.ts +52 -0
  157. package/dist/regenerate/environment-error.d.ts +136 -0
  158. package/dist/regenerate/job-log.d.ts +28 -0
  159. package/dist/regenerate/job-panel.d.ts +122 -0
  160. package/dist/regenerate/jobs-tree.d.ts +71 -0
  161. package/dist/regenerate/jobs.d.ts +186 -0
  162. package/dist/regenerate/lanes.d.ts +16 -0
  163. package/dist/regenerate/lineage.d.ts +95 -0
  164. package/dist/regenerate/list-arguments.d.ts +21 -0
  165. package/dist/regenerate/loop.d.ts +392 -0
  166. package/dist/regenerate/masking.d.ts +19 -0
  167. package/dist/regenerate/notes.d.ts +159 -0
  168. package/dist/regenerate/os-sandbox.d.ts +89 -0
  169. package/dist/regenerate/panel-dock.d.ts +23 -0
  170. package/dist/regenerate/paths.d.ts +92 -0
  171. package/dist/regenerate/plain-palette.d.ts +34 -0
  172. package/dist/regenerate/prompt.d.ts +192 -0
  173. package/dist/regenerate/records.d.ts +88 -0
  174. package/dist/regenerate/regenerate.d.ts +108 -0
  175. package/dist/regenerate/regression.d.ts +58 -0
  176. package/dist/regenerate/release.d.ts +123 -0
  177. package/dist/regenerate/requires.d.ts +96 -0
  178. package/dist/regenerate/resources.d.ts +53 -0
  179. package/dist/regenerate/self-check.d.ts +29 -0
  180. package/dist/regenerate/services.d.ts +28 -0
  181. package/dist/regenerate/shell.d.ts +91 -0
  182. package/dist/regenerate/side-panel.d.ts +57 -0
  183. package/dist/regenerate/spec-map-view.d.ts +139 -0
  184. package/dist/regenerate/spec-map.d.ts +138 -0
  185. package/dist/regenerate/spec-steps.d.ts +137 -0
  186. package/dist/regenerate/state.d.ts +20 -0
  187. package/dist/regenerate/steer.d.ts +34 -0
  188. package/dist/regenerate/steering.d.ts +56 -0
  189. package/dist/regenerate/stop-report.d.ts +43 -0
  190. package/dist/regenerate/submit-work.d.ts +42 -0
  191. package/dist/regenerate/test-kinds.d.ts +42 -0
  192. package/dist/regenerate/test-scripts.d.ts +221 -0
  193. package/dist/regenerate/testing.d.ts +141 -0
  194. package/dist/regenerate/transcript-view.d.ts +21 -0
  195. package/dist/regenerate/unit-gate.d.ts +27 -0
  196. package/dist/regenerate/unit-review.d.ts +152 -0
  197. package/dist/regenerate/web.d.ts +61 -0
  198. package/dist/regenerate/workspace.d.ts +12 -0
  199. package/dist/regenerate-cli.d.ts +1 -0
  200. package/dist/release-agent.d.ts +26 -0
  201. package/dist/release-cli.d.ts +1 -0
  202. package/dist/release-tools.d.ts +38 -0
  203. package/dist/resume-line.d.ts +2 -0
  204. package/dist/review/live.d.ts +72 -0
  205. package/dist/rules.d.ts +20 -0
  206. package/dist/run-report.d.ts +88 -0
  207. package/dist/screen-padding.d.ts +28 -0
  208. package/dist/self-invocation.d.ts +25 -0
  209. package/dist/setup.d.ts +43 -0
  210. package/dist/skill-progress.d.ts +46 -0
  211. package/dist/skill.d.ts +98 -0
  212. package/dist/skills.d.ts +30 -0
  213. package/dist/spec-diff-cli.d.ts +1 -0
  214. package/dist/spec-tools.d.ts +36 -0
  215. package/dist/stack.d.ts +58 -0
  216. package/dist/stacks.d.ts +12 -0
  217. package/dist/stdlib.d.ts +27 -0
  218. package/dist/terminal-images.d.ts +2 -0
  219. package/dist/tools.d.ts +101 -0
  220. package/dist/validate-cli.d.ts +1 -0
  221. package/docs/README.md +2 -0
  222. package/docs/hosting.md +52 -0
  223. package/docs/images/turri-banner.png +0 -0
  224. package/docs/theming.md +6 -4
  225. package/docs/using.md +1 -1
  226. package/package.json +8 -1
package/dist/extension.js CHANGED
@@ -30,7 +30,7 @@ import { checkPlainFile, checkPlainTargets, formatCheckResults } from "./plain/c
30
30
  import { whyFailed } from "./regenerate/diagnose-job.js";
31
31
  import { createWorkLoop, describeModules, isUpToDate, previewJobs } from "./regenerate/loop.js";
32
32
  import { brandNeon, brandPink, plainPaint } from "./regenerate/plain-palette.js";
33
- import { halfBlocks, JELLYFISH_FRAME_MS, JELLYFISH_TEXT_FRAMES } from "./jellyfish.js";
33
+ import { halfBlocks, JELLYFISH_FRAME_MS, JELLYFISH_SEQUENCE, JELLYFISH_TEXT_FRAMES } from "./jellyfish.js";
34
34
  import { createJobPanel, didNotPass, keyPressed, PANEL_KEY, spinner } from "./regenerate/job-panel.js";
35
35
  import { clickToFocus, dockedWidth, narrowChat, shade, shadeChat } from "./regenerate/panel-dock.js";
36
36
  import { overPictureRow, PICTURE_AT } from "./picture-row.js";
@@ -80,8 +80,17 @@ const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
80
80
  */
81
81
  export function askUserVia(ctx, onExchange,
82
82
  /** Given, "/btw ..." typed as an answer is a side question: answered with this, and the question asked again. */
83
- aside) {
83
+ aside,
84
+ /** A host's own dialogs (TurriHost.dialogs), the question put in them instead of the terminal's. */
85
+ dialogs) {
84
86
  return async (request) => {
87
+ if (dialogs !== undefined) {
88
+ const asking = (asked) => runAsk(asked, dialogs);
89
+ const bare = { ...request, said: undefined };
90
+ const answer = aside !== undefined ? await askWithAsides(bare, asking, aside) : await asking(bare);
91
+ onExchange(request.question, answer, [request.context, request.said].map((part) => part?.trim() ?? "").filter((part) => part !== "").join("\n\n") || undefined);
92
+ return answer;
93
+ }
85
94
  if (!ctx.hasUI) {
86
95
  return "No user is available to answer. Decide yourself, and say in your report what you assumed.";
87
96
  }
@@ -156,9 +165,9 @@ function hookPeekKey(ctx) {
156
165
  * pick from, several at once, "Something else…" to type instead, "Chat about this" to talk it over in
157
166
  * the chat first - so a question with a handful of answers is picked, not typed.
158
167
  */
159
- export function chatAskUserTool() {
168
+ export function chatAskUserTool(dialogs) {
160
169
  let conversation;
161
- const tool = createAskUserTool((request) => (conversation !== undefined ? askUserVia(conversation, () => { })(request) : Promise.resolve("No user is available to answer.")), undefined, undefined, undefined, "reply");
170
+ const tool = createAskUserTool((request) => (conversation !== undefined ? askUserVia(conversation, () => { }, undefined, dialogs)(request) : Promise.resolve("No user is available to answer.")), undefined, undefined, undefined, "reply");
162
171
  return {
163
172
  ...tool,
164
173
  promptGuidelines: [
@@ -171,7 +180,14 @@ export function chatAskUserTool() {
171
180
  },
172
181
  };
173
182
  }
174
- export default function (pi) {
183
+ /** The host each conversation's jobs report to, for the work loop made for them later. */
184
+ const hosts = new WeakMap();
185
+ /** turri's conversation as an extension of pi's, for a host that runs it itself: see TurriHost. */
186
+ export function createTurriExtension(host = {}) {
187
+ return (pi) => turriExtension(pi, host);
188
+ }
189
+ export default createTurriExtension();
190
+ function turriExtension(pi, host) {
175
191
  dropNoise();
176
192
  const cwd = process.cwd();
177
193
  // turri's own skills, and the project's - .turri/skills, .agents/skills (skills.ts).
@@ -189,7 +205,10 @@ export default function (pi) {
189
205
  const deployState = createDeployState();
190
206
  const jobs = createJobRegistry({
191
207
  // The loop's plan changed - a module being planned, or planned: the live panel shows it at once.
192
- onNotify: () => panel.refresh(),
208
+ onNotify: () => {
209
+ panel.refresh();
210
+ host.onPlanChange?.();
211
+ },
193
212
  // A job that fails is run once more; failing again, it and every job behind it fail, and the
194
213
  // harness - which can read the skills, the rules, the code, the tests and turri itself -
195
214
  // works out why, and what has to be done.
@@ -208,9 +227,13 @@ export default function (pi) {
208
227
  });
209
228
  return diagnosis !== undefined ? describeDiagnosis(diagnosis) : undefined;
210
229
  },
211
- onChange: () => panel.refresh(),
230
+ onChange: (job) => {
231
+ panel.refresh();
232
+ host.onJobChange?.(job);
233
+ },
212
234
  onFinish: (job) => {
213
235
  panel.refresh();
236
+ host.onJobChange?.(job);
214
237
  // Queued again as another job: that one says how it went.
215
238
  if (job.replacedBy !== undefined)
216
239
  return;
@@ -236,6 +259,8 @@ export default function (pi) {
236
259
  }, { triggerTurn: true, deliverAs: "followUp" });
237
260
  },
238
261
  });
262
+ hosts.set(jobs, host);
263
+ host.onJobs?.(jobs);
239
264
  // The ***plain rules go into the system prompt in full, every turn. The block is
240
265
  // identical each time, so it sits inside the cached prefix rather than invalidating it.
241
266
  // With them, the instruction to do turri's work with its tools rather than its command
@@ -375,7 +400,7 @@ export default function (pi) {
375
400
  registerBranding(pi, discoveredSkills);
376
401
  registerInterviewLog(pi);
377
402
  registerJobTools(pi, jobs, cwd);
378
- registerCheckPlain(pi, cwd);
403
+ registerCheckPlain(pi, cwd, host.dialogs);
379
404
  registerRegenerate(pi, cwd, jobs);
380
405
  registerDrift(pi, cwd, jobs);
381
406
  registerGlimpse(pi, cwd, jobs);
@@ -549,7 +574,7 @@ export default function (pi) {
549
574
  push(` Q: ${question}`);
550
575
  push(` A: ${answer}`);
551
576
  recordExchange(pi, { skill: skill.name, question, answer, ...(said !== undefined && said.trim() !== "" ? { said: said.trim() } : {}) });
552
- }, (question) => askOnTheSide(pi, ctx, cwd, jobs, question)),
577
+ }, (question) => askOnTheSide(pi, ctx, cwd, jobs, question), host.dialogs),
553
578
  model: conversationModel(ctx),
554
579
  // Renders queued by a step land in the same registry as everything else, so
555
580
  // pulses sees them without knowing a skill started them.
@@ -1326,6 +1351,7 @@ function workLoopFor(jobs, cwd, ctx) {
1326
1351
  });
1327
1352
  shared = { loop, from };
1328
1353
  workLoops.set(jobs, shared);
1354
+ hosts.get(jobs)?.onWorkLoop?.(loop);
1329
1355
  }
1330
1356
  shared.from.ctx = ctx;
1331
1357
  return shared.loop;
@@ -1932,9 +1958,9 @@ function jobLines(all) {
1932
1958
  * so it needs no external tool, no API key and no credits - and it is the only honest way
1933
1959
  * to answer "is this spec valid?" without generating anything.
1934
1960
  */
1935
- function registerCheckPlain(pi, cwd) {
1961
+ function registerCheckPlain(pi, cwd, dialogs) {
1936
1962
  pi.registerTool(checkPlainTool(cwd));
1937
- pi.registerTool(chatAskUserTool());
1963
+ pi.registerTool(chatAskUserTool(dialogs));
1938
1964
  // The web, for the conversation too - a library's documentation, an API, an error; curl is its
1939
1965
  // bash. Run here, like the rendering agents' (see render/web.ts).
1940
1966
  for (const tool of createWebTools())
@@ -2327,8 +2353,8 @@ function registerBranding(pi, skills) {
2327
2353
  ...(data.pending > 0 ? [theme.fg("warning", `${data.pending} skill${data.pending === 1 ? "" : "s"} failed to load`)] : []),
2328
2354
  ...(data.sandbox !== undefined && data.sandbox.level !== "ok" ? [theme.fg(data.sandbox.level === "limited" ? "warning" : "error", data.sandbox.text)] : []),
2329
2355
  ];
2330
- // The jellyfish turri is named after, in ink for the theme's background: a picture where the
2331
- // terminal shows images, drawn in text where it does not.
2356
+ // The jellyfish turri is named after, regenerating, in ink for the theme's background: a picture
2357
+ // where the terminal shows images, drawn in text where it does not.
2332
2358
  return new Banner(lines, (rule) => theme.fg("borderMuted", rule), jellyfishArt(theme.appearance, (theme.getColorMode?.() ?? "truecolor") === "truecolor"));
2333
2359
  });
2334
2360
  pi.on("session_start", (_event, ctx) => {
@@ -2351,10 +2377,15 @@ function registerBranding(pi, skills) {
2351
2377
  }
2352
2378
  });
2353
2379
  }
2354
- /** How many rows the banner's jellyfish takes at most: as tall as the one drawn in text. */
2355
- const JELLYFISH_ROWS = 10;
2356
- /** Which frame of the jellyfish every banner shows, moved on by `animateJellyfish`. */
2380
+ /**
2381
+ * How many rows the banner's picture takes at most: its frame reaches down to the sea floor the polyp stands on, so it
2382
+ * is given more than the jellyfish drawn in text, ten.
2383
+ */
2384
+ const JELLYFISH_ROWS = 14;
2385
+ /** How far along its sequence the jellyfish every banner shows is, moved on by `animateJellyfish`. */
2357
2386
  let jellyfishFrame = 0;
2387
+ /** The frame the jellyfish is on: the one its sequence has at this point. */
2388
+ const shownFrame = () => JELLYFISH_SEQUENCE[jellyfishFrame % JELLYFISH_SEQUENCE.length];
2358
2389
  let jellyfishTimer;
2359
2390
  /**
2360
2391
  * Moves the jellyfish on a frame every JELLYFISH_FRAME_MS while the banner is on the screen. An entry's
@@ -2455,7 +2486,7 @@ class Banner {
2455
2486
  const text = this.lines.flatMap((line) => wrapTextWithAnsi(line, Math.max(1, width - 1 - left)));
2456
2487
  const height = Math.max(text.length, placed?.rows ?? 0);
2457
2488
  const above = Math.floor((height - text.length) / 2);
2458
- const drawn = placed?.kind === "text" ? placed.frames[jellyfishFrame % placed.frames.length] : undefined;
2489
+ const drawn = placed?.kind === "text" ? placed.frames[shownFrame()] : undefined;
2459
2490
  const rows = Array.from({ length: height }, (_, row) => {
2460
2491
  const line = text[row - above];
2461
2492
  const jellyfish = drawn !== undefined ? (drawn[row] ?? " ".repeat(placed.columns)) : " ".repeat(placed?.columns ?? 0);
@@ -2476,7 +2507,7 @@ class Banner {
2476
2507
  const kitty = getCapabilities().images === "kitty";
2477
2508
  if (kitty && this.kittyImageId === undefined)
2478
2509
  this.kittyImageId = allocateImageId();
2479
- const result = renderImage(art.frames[jellyfishFrame % art.frames.length], art.dimensions, {
2510
+ const result = renderImage(art.frames[shownFrame()], art.dimensions, {
2480
2511
  maxWidthCells: room,
2481
2512
  maxHeightCells: rows,
2482
2513
  imageId: this.kittyImageId,
@@ -0,0 +1,62 @@
1
+ import { type Reporter } from "./graph.ts";
2
+ import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
3
+ import { type Skill } from "./skill.ts";
4
+ import { type AskUser } from "./tools.ts";
5
+ import type { JobRegistry } from "./regenerate/jobs.ts";
6
+ import type { WorkLoop } from "./regenerate/loop.ts";
7
+ export interface EscalationRequest {
8
+ step: string;
9
+ artifact: string;
10
+ critique: string;
11
+ question: string;
12
+ }
13
+ export interface RunSkillResult {
14
+ skill: Skill;
15
+ ok: boolean;
16
+ artifacts: Record<string, string>;
17
+ /** The last step's artifact - what the caller usually wants. */
18
+ output: string;
19
+ }
20
+ /**
21
+ * Run one skill to completion. The only entry point: the CLI and the Pi extension
22
+ * both go through here, so a skill behaves identically whether a human launched it
23
+ * or a conversational agent decided to.
24
+ */
25
+ export declare function runSkill(opts: {
26
+ skillDir: string;
27
+ cwd: string;
28
+ runDir: string;
29
+ report: Reporter;
30
+ /** Called when a gate cannot be satisfied. Return true to accept the artifact anyway. */
31
+ escalate: (request: EscalationRequest) => Promise<boolean>;
32
+ /** Lets a step interview the user. Omitted: the skill runs with nobody to ask. */
33
+ askUser?: AskUser;
34
+ /** "provider/model-id" for the executor and the judges. Omitted: pi's default. */
35
+ model?: string;
36
+ /** Lets steps build functionalities in the background while the interview continues. */
37
+ renderQueue?: WorkLoop;
38
+ /** What the user has already said, so the skill does not re-ask it. */
39
+ context?: string;
40
+ /** Pick up the run of this skill that was interrupted here, rather than start a new one. */
41
+ resume?: boolean;
42
+ /**
43
+ * Tools the conversation's agent has that the skill's agent needs as well, because the person
44
+ * talks to the skill's agent while it runs - choosing the render model, for one.
45
+ */
46
+ sharedTools?: ToolDefinition[];
47
+ /**
48
+ * The conversation's tools that act on the work loop (loop-tools.ts): drift, for every skill with the loop - how its
49
+ * builds stand - and regenerate and halt for a skill that names them in its allowed-tools.
50
+ */
51
+ loopTools?: ToolDefinition[];
52
+ /** The jobs the render queue runs, for a skill that waits for its builds and reads what they recorded. */
53
+ jobs?: JobRegistry;
54
+ }): Promise<RunSkillResult>;
55
+ /** Whether there is a run of this skill here that was interrupted, and could be resumed. */
56
+ export declare function interruptedRun(runDir: string, skillName: string): {
57
+ step?: string;
58
+ updatedAt: string;
59
+ } | undefined;
60
+ /** Human-readable one-liner per step, for logs and tool output. */
61
+ export declare function describeGates(skill: Skill): string[];
62
+ export type { Reporter };
@@ -0,0 +1,52 @@
1
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
2
+ import { type LiveReviewer } from "./review/live.ts";
3
+ import { type Skill } from "./skill.ts";
4
+ export type Decision = "advance" | "retry" | "escalate" | "abort";
5
+ export declare const GenomeState: import("@langchain/langgraph").AnnotationRoot<{
6
+ artifacts: import("@langchain/langgraph").BaseChannel<Record<string, string>, Record<string, string> | import("@langchain/langgraph").OverwriteValue<Record<string, string>>, unknown>;
7
+ attempts: import("@langchain/langgraph").BaseChannel<Record<string, number>, Record<string, number> | import("@langchain/langgraph").OverwriteValue<Record<string, number>>, unknown>;
8
+ /** Judge feedback for the step currently being retried. */
9
+ critique: import("@langchain/langgraph").BaseChannel<string, string | import("@langchain/langgraph").OverwriteValue<string>, unknown>;
10
+ decision: import("@langchain/langgraph").BaseChannel<Decision, Decision | import("@langchain/langgraph").OverwriteValue<Decision>, unknown>;
11
+ }>;
12
+ export type GenomeStateType = typeof GenomeState.State;
13
+ export interface Reporter {
14
+ step(name: string, attempt: number): void;
15
+ artifact(path: string, body: string): void;
16
+ gate(name: string, judges: number): void;
17
+ /** One continuous-review pass finished while the step was still running. */
18
+ livePass?(step: string, scope: string, findings: number): void;
19
+ /** The continuous reviewer's closing word, read at the gate. */
20
+ liveReview?(name: string, passes: number, findings: number): void;
21
+ judge(index: number, model: string, verdict: string, violations: number): void;
22
+ outcome(name: string, decision: Decision, critique: string): void;
23
+ /** One thing the skill's agent just did - a tool call, as one short line - for a view of it while it works. */
24
+ activity?(line: string): void;
25
+ /** The skill's agent's words as they stream, from the start of each reply: `fresh` when a new one begins. */
26
+ said?(delta: string, fresh: boolean): void;
27
+ }
28
+ /** How many times a step whose turn ends on a question in its reply is sent back to carry on. */
29
+ export declare const QUESTION_NUDGES = 2;
30
+ /** Whether a reply ends on a question: its last line, its markdown left aside, ends with a question mark. */
31
+ export declare function endsOnQuestion(reply: string): boolean;
32
+ export declare function compileSkill(opts: {
33
+ skill: Skill;
34
+ cwd: string;
35
+ runDir: string;
36
+ executor: AgentSession;
37
+ report: Reporter;
38
+ /** Default model for the judges, when a gate does not name one of its own. */
39
+ model?: string;
40
+ /** What the user has already said, carried in from the conversation that started this. */
41
+ context?: string;
42
+ /** Reviewers already reading along, by step name. Their findings gate the step too. */
43
+ liveReviewers?: Map<string, LiveReviewer>;
44
+ /** Start at this step instead of the first: a resumed run, picking up where it stopped. */
45
+ startAt?: string;
46
+ /** Called as each step starts, with the state it starts from - what a resumed run restores. */
47
+ onStepStart?: (step: string, state: GenomeStateType) => void;
48
+ /** Called once a step is reported started, before its agent is prompted: the harness's own question goes here. */
49
+ beforeStep?: (step: string) => Promise<void>;
50
+ /** What the harness tells the agent as a step starts, beside its instructions - the questioning level, say. */
51
+ stepNote?: (step: string) => string | undefined;
52
+ }): any;
package/dist/host.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * turri for a host that runs it itself - a desktop app, an editor - imported as `@turri-dev/cli/host`: the
3
+ * conversation as pi's extension, with the host told of its jobs and asking its questions (createTurriExtension), and
4
+ * what a host shows beside it - a spec parsed, checked and coloured, how far each functional spec is built, the work a
5
+ * spec change would plan. It is turri's own code, as the terminal runs it; a 0.x release may change any of it.
6
+ */
7
+ export { createTurriExtension, type TurriHost } from "./extension.ts";
8
+ export type { AskUserIO } from "./ask.ts";
9
+ /** pi itself, as turri's extension is built against it: a host makes its session with this one, not another copy. */
10
+ export * as pi from "@earendil-works/pi-coding-agent";
11
+ export { configForSpec, hasProjectConfig, modelSpec, projectModels, readProjectConfig, stacksOf, type ProjectConfig } from "./config.ts";
12
+ export { MODULES_DIR, projectDirOf, resolveTemplateDirs } from "./regenerate/paths.ts";
13
+ export { BEDROCK_DEFAULT, BEDROCK_REGIONS, bedrockModels, initConfig, VENDORS, type BedrockChoice, type Vendor } from "./init-config.ts";
14
+ export { checkPlainFile, type CheckResult } from "./plain/check.ts";
15
+ export { plainFileParser } from "./plain/parser.ts";
16
+ export { FUNCTIONAL_REQUIREMENTS, iterateFunctionalSpecs } from "./plain/spec.ts";
17
+ export { highlightLines, roleOf, type PlainRole } from "./plain/highlight.ts";
18
+ export { createWorkLoop, describeModules, functionalityCompletion, promptOf, type DriftReport, type ModuleStatus, type WorkLoop } from "./regenerate/loop.ts";
19
+ export { createJobRegistry, defaultParallelJobs, type Job, type JobRegistry } from "./regenerate/jobs.ts";
20
+ export { describeTurnIncident, onModelEvent, type TranscriptEntry } from "./regenerate/activity.ts";
21
+ export { sweepOrphanedGroups } from "./regenerate/shell.ts";
package/dist/host.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * turri for a host that runs it itself - a desktop app, an editor - imported as `@turri-dev/cli/host`: the
3
+ * conversation as pi's extension, with the host told of its jobs and asking its questions (createTurriExtension), and
4
+ * what a host shows beside it - a spec parsed, checked and coloured, how far each functional spec is built, the work a
5
+ * spec change would plan. It is turri's own code, as the terminal runs it; a 0.x release may change any of it.
6
+ */
7
+ // The conversation.
8
+ export { createTurriExtension } from "./extension.js";
9
+ /** pi itself, as turri's extension is built against it: a host makes its session with this one, not another copy. */
10
+ export * as pi from "@earendil-works/pi-coding-agent";
11
+ // The project and its settings.
12
+ export { configForSpec, hasProjectConfig, modelSpec, projectModels, readProjectConfig, stacksOf } from "./config.js";
13
+ export { MODULES_DIR, projectDirOf, resolveTemplateDirs } from "./regenerate/paths.js";
14
+ // A project's first config.turri.yaml, as `turri init-config` writes it - the vendor whose models it takes chosen by the host.
15
+ export { BEDROCK_DEFAULT, BEDROCK_REGIONS, bedrockModels, initConfig, VENDORS } from "./init-config.js";
16
+ // A spec: parsed, checked, coloured.
17
+ export { checkPlainFile } from "./plain/check.js";
18
+ export { plainFileParser } from "./plain/parser.js";
19
+ export { FUNCTIONAL_REQUIREMENTS, iterateFunctionalSpecs } from "./plain/spec.js";
20
+ export { highlightLines, roleOf } from "./plain/highlight.js";
21
+ // The work: what is built, what is left, and the jobs that build it.
22
+ export { createWorkLoop, describeModules, functionalityCompletion, promptOf } from "./regenerate/loop.js";
23
+ export { createJobRegistry, defaultParallelJobs } from "./regenerate/jobs.js";
24
+ export { describeTurnIncident, onModelEvent } from "./regenerate/activity.js";
25
+ export { sweepOrphanedGroups } from "./regenerate/shell.js";
@@ -0,0 +1,2 @@
1
+ /** Who the conversation's agent is: turri, and what it does - first, so it speaks as turri, in the first person. */
2
+ export declare const TURRI_IDENTITY: string;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * `turri init-config`: a project's first config.turri.yaml - the two models, a strong one to talk to and a smaller one to
3
+ * write the code - so a new project starts with a choice that works rather than an empty file. It asks which vendor's
4
+ * models you use and fills in theirs. Every other setting is written out with the value turri uses for it (DEFAULTS),
5
+ * so the file says how the project is built; the optional ones, which have no value of their own - the testing
6
+ * scripts, `module`, `judge-model`, `models` - are there commented out, with an example. docs/config.md has them all.
7
+ *
8
+ * Then it says what to do next: where the file is - turri reads it from that folder and the folders below it, nowhere
9
+ * else - and how to sign in to the vendor so pi, which turri runs on, can reach the models.
10
+ *
11
+ * Amazon Bedrock serves Anthropic's and OpenAI's models in a geography each - an inference profile, `eu.`, `us.`,
12
+ * `global.` - so it asks which of the two, and where. A config.turri.yaml already there keeps everything but the two models; a new one takes
13
+ * from a config.yaml beside it the keys turri reads too.
14
+ */
15
+ /** A vendor whose models the quick start config is filled in with. */
16
+ export type Vendor = "anthropic" | "openai" | "google" | "amazon-bedrock" | "other";
17
+ /** Whose models Amazon Bedrock serves here. */
18
+ export type BedrockMaker = "anthropic" | "openai";
19
+ /** Amazon Bedrock's choice: whose models, and the geography of the inference profile they run in. */
20
+ export interface BedrockChoice {
21
+ maker: BedrockMaker;
22
+ region: string;
23
+ }
24
+ /** The geographies Bedrock serves each maker's two models in, as pi knows them - the first the default. */
25
+ export declare const BEDROCK_REGIONS: Record<BedrockMaker, string[]>;
26
+ /** Bedrock's models for a maker in a geography: Opus 5.5 and Haiku 5.5, or GPT-6 Astra and Luna. */
27
+ export declare function bedrockModels(choice: BedrockChoice): {
28
+ harness: string;
29
+ coding: string;
30
+ strong: string;
31
+ };
32
+ /** Bedrock's default: Anthropic's models, anywhere. */
33
+ export declare const BEDROCK_DEFAULT: BedrockChoice;
34
+ interface VendorPreset {
35
+ label: string;
36
+ /** The model you talk to: it writes the specs and plans the work. */
37
+ harness: string;
38
+ /** The smaller, cheaper one that writes the code. */
39
+ coding: string;
40
+ /** A strong model for the commented-out judge, reviewer and debugger examples. */
41
+ strong: string;
42
+ /** The environment variables pi reads the vendor's keys from - pi's own names (pi.dev, providers). */
43
+ keys: Array<{
44
+ vendor: string;
45
+ variable: string;
46
+ }>;
47
+ }
48
+ export declare const VENDORS: Record<Vendor, VendorPreset>;
49
+ /**
50
+ * A config.turri.yaml with its two models set to these lines, and nothing else changed: each key's line - and the lines
51
+ * indented under it, when it is written as a block - replaced where it is, or put at the top when it is not there.
52
+ */
53
+ export declare function withModels(text: string, lines: [string, string]): string;
54
+ /**
55
+ * What turri does with each setting the config leaves out, written into the quick start config so it says so. They
56
+ * must stay what the code falls back to - tests/init-config.test.ts holds them to the constants they mirror.
57
+ */
58
+ export declare const DEFAULTS: {
59
+ readonly conformanceTests: false;
60
+ readonly review: false;
61
+ readonly mutationTesting: true;
62
+ readonly allowToStopJobs: true;
63
+ readonly releaseAfterPulse: false;
64
+ readonly testingScriptsRunOn: "in-place";
65
+ readonly turnLimit: 120;
66
+ readonly compactAt: 100000;
67
+ readonly maxSpecsPerPulse: 3;
68
+ readonly maxLinesPerPulse: 120;
69
+ };
70
+ /** How many pulses run at once without a setting: half this machine's cores, one to four - as regenerate/jobs.ts has it. */
71
+ export declare function defaultParallel(cores?: number): number;
72
+ /** The quick start config.turri.yaml for a vendor's models - for Bedrock, the maker and geography chosen. */
73
+ export declare function quickStartConfig(vendor?: Vendor, bedrock?: BedrockChoice): string;
74
+ /** The quick start config for the mix of vendors: what turri wrote before it asked. */
75
+ export declare const QUICK_START_CONFIG: string;
76
+ /** A vendor from what was typed: its number in the list, or its name. */
77
+ export declare function vendorOf(answer: string): Vendor | undefined;
78
+ /** Bedrock's maker from what was typed: its number - 1 Anthropic, 2 OpenAI - or its name. */
79
+ export declare function bedrockMakerOf(answer: string): BedrockMaker | undefined;
80
+ /** A geography the maker's models are served in, from what was typed: its number in the list, or its name. */
81
+ export declare function bedrockRegionOf(maker: BedrockMaker, answer: string): string | undefined;
82
+ /**
83
+ * What a config.yaml in `folder` says that turri reads too - another tool's testing scripts, say - by turri's names
84
+ * (`template_dir` read as `template-dir`), in the file's order; and the keys it leaves, turri reading none of them. The
85
+ * two models are the vendor's, so they are not taken.
86
+ */
87
+ export declare function sharedWithLegacy(folder: string): {
88
+ carried: Array<[string, unknown]>;
89
+ left: string[];
90
+ };
91
+ /**
92
+ * Write the quick start config.turri.yaml in `dir`, with what a config.yaml beside it says that turri reads too. One already
93
+ * there keeps everything but its two models, which are set to the vendor's - `force` writes the whole of it again. Says
94
+ * what it did.
95
+ */
96
+ export declare function initConfig(dir: string, options?: {
97
+ force?: boolean;
98
+ vendor?: Vendor;
99
+ bedrock?: BedrockChoice;
100
+ }): {
101
+ written: boolean;
102
+ message: string;
103
+ };
104
+ /** `turri init-config [--vendor <vendor>] [--maker <maker>] [--region <geography>] [--force] [folder]`. */
105
+ export declare function main(args: string[]): Promise<number>;
106
+ export {};
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The jellyfish turri is named after, in pixels, regenerating: a polyp on the sea floor grows into the jellyfish, it
3
+ * swims a while, then sinks back to the floor and the polyp grows again - the design system's hero and its lifecycle
4
+ * (its pixel kit's `sprite("hero")` and `cycleFrames()`, ported here pixel for pixel). The banner's pictures are
5
+ * rendered from these frames (scripts/jellyfish-frames.mjs writes assets/jellyfish/); where a terminal shows no
6
+ * images the same frames are drawn here in half blocks, at a third of the size.
7
+ */
8
+ /**
9
+ * What a pixel is: the bell's pink, its midtone and shadow, its rim, its highlight, the gut, and a tentacle - whole,
10
+ * half seen, or a polyp's crown, a little more than half.
11
+ */
12
+ type Pixel = "p" | "m" | "i" | "e" | "h" | "o" | "N" | "n" | "c";
13
+ /** The pixels' colours on a dark background and a light one - the tentacles blue whale on light, where neon does not show. */
14
+ export declare const JELLYFISH_COLOURS: Record<"dark" | "light", Record<Pixel, string>>;
15
+ /** How much of a pixel shows, for a picture's alpha. */
16
+ export declare function jellyfishAlpha(pixel: string): number;
17
+ /** The jellyfish's frames, each as rows of pixels: what the pictures are rendered from. */
18
+ export declare const JELLYFISH_FRAMES: string[][];
19
+ export declare const JELLYFISH_WIDTH: number;
20
+ export declare const JELLYFISH_HEIGHT: number;
21
+ /**
22
+ * The order the frames play in, round and round: the polyp a moment, growing into the jellyfish, swimming four
23
+ * seconds - a stroke every five frames - then sinking, and the polyp growing back.
24
+ */
25
+ export declare const JELLYFISH_SEQUENCE: number[];
26
+ /** How long each frame shows. */
27
+ export declare const JELLYFISH_FRAME_MS = 150;
28
+ /** The frames as a terminal without images draws them, in half blocks: the same order, a third of the size. */
29
+ export declare const JELLYFISH_TEXT_FRAMES: string[][];
30
+ /**
31
+ * A frame drawn in half blocks for a terminal that shows no images: two pixels a cell, the upper one the text's colour
32
+ * and the lower one the background's, in as many colours as the terminal has: a column a pixel, a row every two.
33
+ */
34
+ export declare function halfBlocks(pixels: string[], appearance: "dark" | "light", truecolor: boolean): string[];
35
+ export {};