@turri-dev/cli 0.1.4-next.4342332 → 0.1.4-next.5255d82

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 (44) hide show
  1. package/README.md +4 -2
  2. package/assets/jellyfish/dark-00.png +0 -0
  3. package/assets/jellyfish/dark-01.png +0 -0
  4. package/assets/jellyfish/dark-02.png +0 -0
  5. package/assets/jellyfish/dark-03.png +0 -0
  6. package/assets/jellyfish/dark-04.png +0 -0
  7. package/assets/jellyfish/dark-05.png +0 -0
  8. package/assets/jellyfish/dark-06.png +0 -0
  9. package/assets/jellyfish/dark-07.png +0 -0
  10. package/assets/jellyfish/dark-08.png +0 -0
  11. package/assets/jellyfish/dark-09.png +0 -0
  12. package/assets/jellyfish/dark-10.png +0 -0
  13. package/assets/jellyfish/dark-11.png +0 -0
  14. package/assets/jellyfish/light-00.png +0 -0
  15. package/assets/jellyfish/light-01.png +0 -0
  16. package/assets/jellyfish/light-02.png +0 -0
  17. package/assets/jellyfish/light-03.png +0 -0
  18. package/assets/jellyfish/light-04.png +0 -0
  19. package/assets/jellyfish/light-05.png +0 -0
  20. package/assets/jellyfish/light-06.png +0 -0
  21. package/assets/jellyfish/light-07.png +0 -0
  22. package/assets/jellyfish/light-08.png +0 -0
  23. package/assets/jellyfish/light-09.png +0 -0
  24. package/assets/jellyfish/light-10.png +0 -0
  25. package/assets/jellyfish/light-11.png +0 -0
  26. package/dist/extension.js +48 -46
  27. package/dist/jellyfish.js +130 -0
  28. package/dist/picture-row.js +3 -2
  29. package/dist/regenerate/panel-dock.js +7 -5
  30. package/dist/regenerate/plain-palette.js +1 -1
  31. package/dist/screen-padding.js +72 -0
  32. package/docs/development.md +1 -0
  33. package/docs/images/turri-banner.png +0 -0
  34. package/docs/images/turri-screenshot.png +0 -0
  35. package/docs/theming.md +3 -3
  36. package/docs/using.md +2 -2
  37. package/package.json +1 -1
  38. package/assets/jellyfish/dark-12.png +0 -0
  39. package/assets/jellyfish/dark-13.png +0 -0
  40. package/assets/jellyfish/dark-14.png +0 -0
  41. package/assets/jellyfish/fallback.txt +0 -252
  42. package/assets/jellyfish/light-12.png +0 -0
  43. package/assets/jellyfish/light-13.png +0 -0
  44. package/assets/jellyfish/light-14.png +0 -0
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- <p align="center"><img src="docs/images/turri-banner.png" alt="turri - coding agent with the power of regenerative software: regenerate, drift, lineage"></p>
1
+ <p align="center"><img src="docs/images/turri-banner.png" alt="turri - the first coding agent with the power of regenerative software, beside a pixel jellyfish: npm install -g @turri-dev/cli"></p>
2
2
 
3
3
  <h1 align="center">turri<br>the first coding agent with the power of<br>regenerative software<br>&nbsp;</h1>
4
4
 
@@ -101,9 +101,11 @@ With **turri** you work on the durable intent. You say what the software must do
101
101
 
102
102
  ## How it works
103
103
 
104
+ <p align="center"><img src="docs/images/turri-screenshot.png" width="900" alt="turri in the terminal: on the left the conversation, where /regenerate plans a project's modules and a coding agent works through one of them; on the right the spec map of that module's ***plain, each functionality marked as regenerated or being worked on"></p>
105
+
104
106
  **turri** captures the durable intent in [\*\*\*plain](https://plainlang.org/), a language for saying what software should do. \*\*\*plain breaks the intent down into functionalities - small units, each a single thing the software does - and **turri** regenerates the code from them piece by piece.
105
107
 
106
- 1. **Define.** Write \*\*\*plain with **turri**
108
+ 1. **Define.** You say what the software should do; **turri** writes it down as \*\*\*plain.
107
109
  2. **Review and refine.** You review \*\*\*plain and change it with **turri** until it says what you mean.
108
110
  3. **Regenerate.** Agents write the code; **turri** decides what they write and when, step by step, the same way every time.
109
111
  4. **Validate.** Agents write conformance tests from \*\*\*plain; **turri** runs them, and keeps the code only when it does what \*\*\*plain says.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/dist/extension.js CHANGED
@@ -5,7 +5,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
5
5
  import { basename, dirname, join, relative, resolve } from "node:path";
6
6
  import { conversationCommand, conversationSettled, conversationWriting } from "./regenerate/conversation-writes.js";
7
7
  import { fileURLToPath } from "node:url";
8
- import { allocateImageId, Box, getCapabilities, getPngDimensions, isKeyRelease, isKeyRepeat, matchesKey, renderImage, Text, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
8
+ import { allocateImageId, Box, getCapabilities, getPngDimensions, isKeyRelease, isKeyRepeat, matchesKey, renderImage, Text, wrapTextWithAnsi } from "@earendil-works/pi-tui";
9
9
  import { defineTool } from "@earendil-works/pi-coding-agent";
10
10
  import { Type } from "typebox";
11
11
  import { CODING_MODEL_ENV } from "./chat-models.js";
@@ -30,9 +30,11 @@ 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
34
  import { createJobPanel, didNotPass, keyPressed, PANEL_KEY, spinner } from "./regenerate/job-panel.js";
34
35
  import { clickToFocus, dockedWidth, narrowChat, shade, shadeChat } from "./regenerate/panel-dock.js";
35
36
  import { overPictureRow, PICTURE_AT } from "./picture-row.js";
37
+ import { padScreen, paddedColumns, paddedRows, SCREEN_PADDING } from "./screen-padding.js";
36
38
  import { skillAboutLine, skillProgressLine } from "./skill-progress.js";
37
39
  import { createSidePanel } from "./regenerate/side-panel.js";
38
40
  import { createSpecMapSource, defaultMapFiles } from "./regenerate/spec-map.js";
@@ -47,7 +49,7 @@ import { inheritProviders, inheritThinkingLevel } from "./pi.js";
47
49
  import { beginCapture, captureConsole, describeCaptured, dropNoise } from "./quiet.js";
48
50
  import { loadRules, renderRulesBlock } from "./rules.js";
49
51
  import { registerTurriConfig } from "./config-command.js";
50
- import { describeStdlib, stdlibStackCount } from "./stdlib.js";
52
+ import { describeStdlib } from "./stdlib.js";
51
53
  import { peekKeyListener, runAsk } from "./ask.js";
52
54
  import { QuestionDialog } from "./question-dialog.js";
53
55
  import { registerStacks } from "./stacks.js";
@@ -96,7 +98,7 @@ aside) {
96
98
  ...(context !== undefined ? { context } : {}),
97
99
  ...(options !== undefined ? { options } : {}),
98
100
  ...(selected !== undefined ? { selected } : {}),
99
- rows: () => tui.terminal.rows,
101
+ rows: () => paddedRows(tui.terminal.rows),
100
102
  style: { accent: (text) => theme.fg("accent", text), muted: (text) => theme.fg("muted", text), bold: (text) => theme.bold(text) },
101
103
  done: (answer) => {
102
104
  if (questionShown === shown)
@@ -370,7 +372,7 @@ export default function (pi) {
370
372
  conversationModels = ctx.modelRegistry;
371
373
  });
372
374
  inheritProviders(() => conversationModels);
373
- registerBranding(pi, rules.length, discoveredSkills);
375
+ registerBranding(pi, discoveredSkills);
374
376
  registerInterviewLog(pi);
375
377
  registerJobTools(pi, jobs, cwd);
376
378
  registerCheckPlain(pi, cwd);
@@ -1526,7 +1528,7 @@ function registerJobTools(pi, jobs, cwd) {
1526
1528
  // The terminal the panel is drawn on, for its width: set as it opens.
1527
1529
  let panelTerminal;
1528
1530
  /** The columns the panel takes beside the chat - 0 over the whole screen, or when the terminal is too narrow to share. */
1529
- const panelBeside = () => (panelDocked && panelTerminal !== undefined ? dockedWidth(panelTerminal.columns) : 0);
1531
+ const panelBeside = () => (panelDocked && panelTerminal !== undefined ? dockedWidth(paddedColumns(panelTerminal.columns)) : 0);
1530
1532
  // What is open in the pulse list and the map, and where their cursors are: the conversation's, as left each time it opens.
1531
1533
  const jobsTree = createJobsTreeState();
1532
1534
  const mapState = createSpecMapState();
@@ -1584,7 +1586,7 @@ function registerJobTools(pi, jobs, cwd) {
1584
1586
  const layOut = () => {
1585
1587
  if (panelDocked && undock === undefined) {
1586
1588
  const widen = narrowChat(tui, panelBeside);
1587
- const unwatch = clickToFocus(tui, () => tui.terminal.columns - panelBeside(), (side) => {
1589
+ const unwatch = clickToFocus(tui, () => tui.terminal.columns - SCREEN_PADDING.right - panelBeside(), (side) => {
1588
1590
  if (panelBeside() === 0)
1589
1591
  return;
1590
1592
  if (side === "chat")
@@ -1625,7 +1627,7 @@ function registerJobTools(pi, jobs, cwd) {
1625
1627
  tree: jobsTree,
1626
1628
  map: () => specMap.read(),
1627
1629
  mapState,
1628
- rows: () => tui.terminal.rows,
1630
+ rows: () => paddedRows(tui.terminal.rows),
1629
1631
  key: PANEL_KEY,
1630
1632
  dock: {
1631
1633
  docked: () => panelBeside() > 0,
@@ -1756,14 +1758,15 @@ function registerJobTools(pi, jobs, cwd) {
1756
1758
  return component;
1757
1759
  }, {
1758
1760
  overlay: true,
1759
- // The whole screen, or the columns on the right beside the chat - read at each frame, so it follows the terminal.
1761
+ // The whole screen, or the columns on the right beside the chat - read at each frame, so it follows the terminal -
1762
+ // inside the screen's padding.
1760
1763
  overlayOptions: {
1761
1764
  get width() {
1762
1765
  return panelBeside() > 0 ? panelBeside() : "100%";
1763
1766
  },
1764
1767
  maxHeight: "100%",
1765
1768
  anchor: "top-right",
1766
- margin: 0,
1769
+ margin: { ...SCREEN_PADDING },
1767
1770
  },
1768
1771
  onHandle: (handle) => {
1769
1772
  if (panel !== undefined)
@@ -2295,7 +2298,7 @@ const BANNER_ENTRY = "turri_banner";
2295
2298
  export function chatRulesBlock(rules = loadRules(join(PACKAGE_ROOT, "genome", "rules"))) {
2296
2299
  return renderRulesBlock(rules);
2297
2300
  }
2298
- function registerBranding(pi, ruleCount, skills) {
2301
+ function registerBranding(pi, skills) {
2299
2302
  pi.registerCommand("stdlib", {
2300
2303
  description: "List the ***plain standard library: the stacks a spec can import instead of writing its implementation reqs.",
2301
2304
  handler: async (_args, ctx) => {
@@ -2310,38 +2313,29 @@ function registerBranding(pi, ruleCount, skills) {
2310
2313
  const lines = [
2311
2314
  `${brandPink(theme)("turri")} ${theme.fg("muted", `v${data.version}`)}` +
2312
2315
  (data.commit !== undefined ? theme.fg("dim", ` from a clone, ${data.commit}${data.changed ? " with changes" : ""}`) : ""),
2313
- theme.fg("text", "regenerative software - the code is regenerated from the spec"),
2314
- theme.fg("muted", `${data.rules} rules · ${data.skills} skill${data.skills === 1 ? "" : "s"}` +
2315
- (data.projectSkills !== undefined && data.projectSkills > 0 ? ` (${data.projectSkills} the project's)` : "") +
2316
- (data.stdlib !== undefined && data.stdlib > 0 ? ` · ${data.stdlib} stdlib stacks (/stdlib)` : "") +
2317
- (data.pending > 0 ? theme.fg("warning", ` (${data.pending} failed to load)`) : "")),
2316
+ theme.fg("text", "the first coding agent with the power of regenerative software"),
2318
2317
  // Where the settings are changed, without leaving the chat.
2319
2318
  theme.fg("muted", "configure with /turri-config"),
2320
- theme.fg("dim", data.cwd),
2321
- // Where every command an agent runs will run - or that none can.
2322
- ...(data.sandbox
2323
- ? [theme.fg(data.sandbox.level === "ok" ? "dim" : data.sandbox.level === "limited" ? "warning" : "error", data.sandbox.text)]
2324
- : []),
2319
+ // Only what is wrong: skills that did not load, and agents' commands less fenced than they should be.
2320
+ ...(data.pending > 0 ? [theme.fg("warning", `${data.pending} skill${data.pending === 1 ? "" : "s"} failed to load`)] : []),
2321
+ ...(data.sandbox !== undefined && data.sandbox.level !== "ok" ? [theme.fg(data.sandbox.level === "limited" ? "warning" : "error", data.sandbox.text)] : []),
2325
2322
  ];
2326
2323
  // The jellyfish turri is named after, in ink for the theme's background: a picture where the
2327
2324
  // terminal shows images, drawn in text where it does not.
2328
- return new Banner(lines, (rule) => theme.fg("borderMuted", rule), jellyfishArt(theme.appearance, (art) => theme.fg("text", art)));
2325
+ return new Banner(lines, (rule) => theme.fg("borderMuted", rule), jellyfishArt(theme.appearance, (theme.getColorMode?.() ?? "truecolor") === "truecolor"));
2329
2326
  });
2330
2327
  pi.on("session_start", (_event, ctx) => {
2331
- if (ctx.hasUI)
2328
+ if (ctx.hasUI) {
2329
+ padTheScreen(ctx);
2332
2330
  animateJellyfish(ctx);
2331
+ }
2333
2332
  try {
2334
2333
  const identity = turriIdentity();
2335
2334
  pi.appendEntry(BANNER_ENTRY, {
2336
2335
  version: identity.version ?? "0.0.0",
2337
2336
  commit: identity.git_commit?.slice(0, 7),
2338
2337
  changed: identity.git_dirty === true,
2339
- rules: ruleCount,
2340
- skills: skills.skills.length,
2341
- projectSkills: skills.skills.filter((skill) => skill.origin !== "turri").length,
2342
2338
  pending: skills.pending.length,
2343
- stdlib: stdlibStackCount(),
2344
- cwd: ctx.cwd,
2345
2339
  sandbox: sandboxSummary(),
2346
2340
  });
2347
2341
  }
@@ -2350,10 +2344,8 @@ function registerBranding(pi, ruleCount, skills) {
2350
2344
  }
2351
2345
  });
2352
2346
  }
2353
- /** How many rows the banner's jellyfish takes: taller than the lines beside it. */
2354
- const JELLYFISH_ROWS = 13;
2355
- /** How long each frame of the jellyfish shows. */
2356
- const JELLYFISH_FRAME_MS = 250;
2347
+ /** How many rows the banner's jellyfish takes at most: as tall as the one drawn in text. */
2348
+ const JELLYFISH_ROWS = 10;
2357
2349
  /** Which frame of the jellyfish every banner shows, moved on by `animateJellyfish`. */
2358
2350
  let jellyfishFrame = 0;
2359
2351
  let jellyfishTimer;
@@ -2388,6 +2380,24 @@ function animateJellyfish(ctx) {
2388
2380
  }, { placement: "belowEditor" });
2389
2381
  }
2390
2382
  const JELLYFISH_CLOCK_KEY = "turri-jellyfish";
2383
+ /**
2384
+ * The whole screen inside its padding (`screen-padding.ts`). An extension is given pi's screen only through what it
2385
+ * draws, so an empty widget below the editor hands it over - at each frame, as pi starts a new screen when it changes
2386
+ * how it draws.
2387
+ */
2388
+ function padTheScreen(ctx) {
2389
+ ctx.ui.setWidget(SCREEN_PADDING_KEY, (tui) => {
2390
+ padScreen(tui);
2391
+ return {
2392
+ render: () => {
2393
+ padScreen(tui);
2394
+ return [];
2395
+ },
2396
+ invalidate: () => { },
2397
+ };
2398
+ }, { placement: "belowEditor" });
2399
+ }
2400
+ const SCREEN_PADDING_KEY = "turri-padding";
2391
2401
  /** The columns the banner's picture takes from the left edge, while it shows one; 0 when it does not. */
2392
2402
  let pictureRight = 0;
2393
2403
  /**
@@ -2446,7 +2456,7 @@ class Banner {
2446
2456
  return line !== undefined ? `${before}${line}` : placed?.kind === "text" ? ` ${jellyfish}` : "";
2447
2457
  });
2448
2458
  // The side panel is drawn beside the picture (drawOverlaysBesidePicture), which goes on showing.
2449
- pictureRight = placed?.kind === "image" ? 1 + placed.columns : 0;
2459
+ pictureRight = placed?.kind === "image" ? SCREEN_PADDING.left + 1 + placed.columns : 0;
2450
2460
  if (placed?.kind === "image") {
2451
2461
  const at = placed.onLastRow ? placed.rows - 1 : 0;
2452
2462
  rows[at] = `${rows[at]}${PICTURE_AT}${placed.sequence}`;
@@ -2473,24 +2483,16 @@ class Banner {
2473
2483
  }
2474
2484
  const jellyfishFrameCache = new Map();
2475
2485
  /**
2476
- * The banner's jellyfish for this terminal: where it shows images, the picture's frames spread evenly
2477
- * over the video, light ink for a dark background and dark ink for a light one; where it does not, the
2478
- * jellyfish drawn in text, in `ink`. None when they cannot be read.
2486
+ * The banner's jellyfish for this terminal: where it shows images, the picture's frames
2487
+ * (scripts/jellyfish-frames.mjs writes them), in the colours for the theme's background; where it does not, the
2488
+ * small one in half blocks. None when the pictures cannot be read.
2479
2489
  */
2480
- function jellyfishArt(appearance, ink) {
2490
+ function jellyfishArt(appearance, truecolor) {
2481
2491
  const dir = join(PACKAGE_ROOT, "assets", "jellyfish");
2482
2492
  try {
2483
2493
  if (getCapabilities().images === null) {
2484
- // Its frames one after another, each ended by a line of "---" (scripts/jellyfish-frames.mjs writes them).
2485
- const drawn = readFileSync(join(dir, "fallback.txt"), "utf8")
2486
- .split(/^---$/m)
2487
- .map((frame) => frame.replace(/^\n+|\n+$/g, "").split("\n"))
2488
- .filter((frame) => frame.some((line) => line.trim() !== ""));
2489
- if (drawn.length === 0)
2490
- return undefined;
2491
- const columns = Math.max(...drawn.flat().map((line) => visibleWidth(line)));
2492
- const rows = Math.max(...drawn.map((frame) => frame.length));
2493
- return { kind: "text", frames: drawn.map((frame) => frame.map((line) => ink(line.padEnd(columns)))), columns, rows };
2494
+ const frames = JELLYFISH_TEXT_FRAMES.map((pixels) => halfBlocks(pixels, appearance, truecolor));
2495
+ return { kind: "text", frames, columns: JELLYFISH_TEXT_FRAMES[0][0].length, rows: frames[0].length };
2494
2496
  }
2495
2497
  let frames = jellyfishFrameCache.get(appearance);
2496
2498
  if (frames === undefined) {
@@ -0,0 +1,130 @@
1
+ import { sgr } from "./regenerate/plain-palette.js";
2
+ /** The pixels' colours on a dark background and a light one - the tentacles darker on light, where neon does not show. */
3
+ export const JELLYFISH_COLOURS = {
4
+ dark: { p: "#ff5c8d", h: "#ff7fa6", w: "#f4f7f9", o: "#ff7a3d", e: "#732553", N: "#8eebee", n: "#5ce1e6", l: "#85a3b2", g: "#3e5a6b" },
5
+ light: { p: "#ff5c8d", h: "#ff7fa6", w: "#f4f7f9", o: "#ff7a3d", e: "#732553", N: "#3e5a6b", n: "#1e3442", l: "#85a3b2", g: "#85a3b2" },
6
+ };
7
+ /**
8
+ * How shut the bell is, frame by frame - a quarter second each: shut quickly, held a moment, opened slowly, at rest
9
+ * until the next stroke.
10
+ */
11
+ const SHUT = [0.1, 0.55, 0.9, 1, 0.85, 0.7, 0.56, 0.44, 0.33, 0.23, 0.14, 0.06];
12
+ const LARGE = {
13
+ width: 24,
14
+ height: 26,
15
+ open: [6, 12, 16, 18, 20, 22, 22, 24, 24],
16
+ shut: [6, 10, 14, 16, 18, 18, 18, 20, 20],
17
+ glint: [[-3, 2], [-4, 2], [-5, 3], [-6, 4], [-6, 5]],
18
+ shine: [-2, 2],
19
+ gut: [".oo..oo.", "oooooooo", "oooooooo", ".oo..oo."],
20
+ gutRow: 4,
21
+ strands: [
22
+ { at: -8, length: 10, sway: 1.1, ink: "N" },
23
+ { at: -4.5, length: 13, sway: 1.3, ink: "N" },
24
+ { at: -1, length: 14, sway: 0.8, ink: "n" },
25
+ { at: 2, length: 13, sway: 0.8, ink: "n" },
26
+ { at: 5.5, length: 13, sway: 1.3, ink: "N" },
27
+ { at: 9, length: 10, sway: 1.1, ink: "N" },
28
+ ],
29
+ };
30
+ const SMALL = {
31
+ width: 18,
32
+ height: 20,
33
+ open: [4, 10, 12, 14, 16, 18, 18],
34
+ shut: [4, 8, 10, 12, 14, 14, 14],
35
+ glint: [[-2, 1], [-3, 2], [-4, 3]],
36
+ shine: [-1, 1],
37
+ gut: ["oo..oo", "oooooo", "oo..oo"],
38
+ gutRow: 3,
39
+ strands: [
40
+ { at: -6, length: 8, sway: 0.9, ink: "N" },
41
+ { at: -3, length: 10, sway: 1, ink: "N" },
42
+ { at: -0.5, length: 11, sway: 0.7, ink: "n" },
43
+ { at: 1.5, length: 10, sway: 0.7, ink: "n" },
44
+ { at: 4, length: 10, sway: 1, ink: "N" },
45
+ { at: 7, length: 8, sway: 0.9, ink: "N" },
46
+ ],
47
+ };
48
+ /** One frame of a design: its height in rows of its width in pixels, "." where there is none. */
49
+ function frame(design, t) {
50
+ const grid = Array.from({ length: design.height }, () => Array(design.width).fill("."));
51
+ const centre = design.width / 2;
52
+ const set = (y, x, pixel, over = true) => {
53
+ if (y < 0 || y >= design.height || x < 0 || x >= design.width)
54
+ return;
55
+ if (over || grid[y][x] === ".")
56
+ grid[y][x] = pixel;
57
+ };
58
+ const shut = SHUT[t % SHUT.length];
59
+ // Shut, it has risen a pixel.
60
+ const top = shut > 0.6 ? 0 : 1;
61
+ const widths = design.open.map((open, row) => 2 * Math.round((open - (open - design.shut[row]) * shut) / 2));
62
+ widths.forEach((width, row) => {
63
+ for (let x = centre - width / 2; x < centre + width / 2; x++)
64
+ set(top + row, x, "p");
65
+ });
66
+ const rimWidth = widths.at(-1);
67
+ const rim = top + widths.length;
68
+ // The underside along the bottom row, the rim scalloped under it.
69
+ for (let x = centre - rimWidth / 2 + 2; x < centre + rimWidth / 2 - 2; x++)
70
+ set(rim - 1, x, "e");
71
+ for (let x = centre - rimWidth / 2; x < centre + rimWidth / 2; x += 2)
72
+ set(rim, x + 1, "p");
73
+ // A glint up on the left, curving with the dome, and a pixel of shine.
74
+ for (const [dx, dy] of design.glint)
75
+ set(top + dy, centre + dx, "h");
76
+ set(top + design.shine[1], centre + design.shine[0], "w");
77
+ const gutLeft = centre - Math.floor(design.gut[0].length / 2);
78
+ design.gut.forEach((line, dy) => [...line].forEach((pixel, dx) => pixel === "o" && set(top + design.gutRow + dy, gutLeft + dx, "o")));
79
+ // The tentacles: rooted under the bell, drawn in as it shuts, swaying - the further down, the later.
80
+ const phase = (2 * Math.PI * t) / SHUT.length;
81
+ design.strands.forEach((strand, i) => {
82
+ const root = centre - 0.5 + strand.at * (rimWidth / design.open.at(-1));
83
+ for (let y = 0; y < strand.length; y++) {
84
+ const x = Math.round(root + strand.sway * Math.sin(phase - y * 0.55 + i * 0.8) * Math.min(1, y / 3));
85
+ const left = strand.length - y;
86
+ // The tips come apart, every other pixel, fading - into code.
87
+ if (left <= 3 && y % 2 === 1)
88
+ continue;
89
+ set(rim + 1 + y, x, left > 5 ? strand.ink : left > 2 ? "l" : "g", false);
90
+ // The oral arms are frilled.
91
+ if (strand.ink === "n" && left > 5 && (y + t) % 3 === 0)
92
+ set(rim + 1 + y, x + (strand.at < 0 ? -1 : 1), "n", false);
93
+ }
94
+ });
95
+ return grid.map((row) => row.join(""));
96
+ }
97
+ export const JELLYFISH_WIDTH = LARGE.width;
98
+ export const JELLYFISH_HEIGHT = LARGE.height;
99
+ /** The large jellyfish's frames, a stroke: what the pictures are rendered from. */
100
+ export const JELLYFISH_FRAMES = SHUT.map((_, t) => frame(LARGE, t));
101
+ /** The small jellyfish's frames, a stroke: what a terminal without images draws, in half blocks. */
102
+ export const JELLYFISH_TEXT_FRAMES = SHUT.map((_, t) => frame(SMALL, t));
103
+ /** How long each frame shows. */
104
+ export const JELLYFISH_FRAME_MS = 250;
105
+ /**
106
+ * A frame drawn in half blocks for a terminal that shows no images: two pixels a cell, the upper one the text's colour
107
+ * and the lower one the background's, in as many colours as the terminal has: a column a pixel, a row every two.
108
+ */
109
+ export function halfBlocks(pixels, appearance, truecolor) {
110
+ const colours = JELLYFISH_COLOURS[appearance];
111
+ const colour = (pixel) => (pixel === undefined || pixel === "." ? undefined : colours[pixel]);
112
+ const rows = [];
113
+ for (let y = 0; y < pixels.length; y += 2) {
114
+ let row = "";
115
+ for (let x = 0; x < (pixels[0]?.length ?? 0); x++) {
116
+ const upper = colour(pixels[y]?.[x]);
117
+ const lower = colour(pixels[y + 1]?.[x]);
118
+ if (upper === undefined && lower === undefined)
119
+ row += " ";
120
+ else if (upper === undefined)
121
+ row += `\x1b[${sgr(lower, false, truecolor)}m▄\x1b[39m`;
122
+ else if (lower === undefined)
123
+ row += `\x1b[${sgr(upper, false, truecolor)}m▀\x1b[39m`;
124
+ else
125
+ row += `\x1b[${sgr(upper, false, truecolor)};${sgr(lower, true, truecolor)}m▀\x1b[39;49m`;
126
+ }
127
+ rows.push(row);
128
+ }
129
+ return rows;
130
+ }
@@ -1,6 +1,7 @@
1
1
  import { compositeTuiLine } from "@earendil-works/pi-tui";
2
- /** What the banner puts between a row's text and the picture after it: back to the jellyfish's column. */
3
- export const PICTURE_AT = "\x1b[2G";
2
+ import { SCREEN_PADDING } from "./screen-padding.js";
3
+ /** What the banner puts between a row's text and the picture after it: back to the jellyfish's column, a column in from the screen's padding. */
4
+ export const PICTURE_AT = `\x1b[${SCREEN_PADDING.left + 2}G`;
4
5
  /** The picture's escape sequence, as the banner writes it after PICTURE_AT: Kitty's, in chunks, or iTerm2's - climbing first on iTerm2. */
5
6
  const PICTURE = /^(?:\x1b\[\d+A)?(?:(?:\x1b_G[^\x1b]*\x1b\\)+|\x1b\]1337;File=[^\x07]*\x07)/;
6
7
  /**
@@ -1,15 +1,16 @@
1
1
  import { HStack } from "@earendil-works/pi-tui";
2
+ import { unpadded } from "../screen-padding.js";
2
3
  /**
3
4
  * The side panel beside the chat rather than over it: the chat drawn on the left in what the panel leaves it, the panel
4
5
  * laid over the columns on the right.
5
6
  *
6
7
  * pi draws its screen one of two ways, and the chat is narrowed in each as it is drawn: full screen (pi's default), a
7
8
  * layout - its root put in a row beside a filler as wide as the panel; the regular screen, the lines its render gives -
8
- * asked for in fewer columns.
9
+ * asked for in fewer columns. Either way inside the screen's padding (`screen-padding.ts`), when it has one.
9
10
  */
10
11
  /** Narrower than this, the chat and the panel have no room side by side: the panel takes the whole screen. */
11
12
  const SHARED_FROM = 100;
12
- /** The columns the panel takes beside the chat: half the screen, the rule between them in the middle - 0 when the terminal is too narrow to share. */
13
+ /** The columns the panel takes beside the chat: half of what they share, the rule between them in the middle - 0 when that is too narrow to share. */
13
14
  export function dockedWidth(columns) {
14
15
  return columns < SHARED_FROM ? 0 : Math.floor(columns / 2);
15
16
  }
@@ -20,14 +21,15 @@ export function dockedWidth(columns) {
20
21
  export function narrowChat(tui, panelWidth) {
21
22
  const screen = tui;
22
23
  if (typeof screen.setLayoutRoot === "function" && screen.layoutRoot !== undefined) {
23
- const root = screen.layoutRoot;
24
+ // What the padding pads: the row goes inside it, and is padded as it is set.
25
+ const root = unpadded(screen.layoutRoot);
24
26
  // A line as wide as the panel: the row gives the filler what it measures, the chat the rest.
25
27
  const filler = { render: () => [" ".repeat(panelWidth())], invalidate() { } };
26
28
  screen.setLayoutRoot(new HStack([{ component: root, basis: 0, grow: 1 }, { component: filler }]));
27
29
  return () => screen.setLayoutRoot(root);
28
30
  }
29
- // Read from the prototype: the screen's own render, not one laid over it.
30
- const render = Object.getPrototypeOf(tui).render;
31
+ // The render the screen has now - padded, when it is.
32
+ const render = screen.render;
31
33
  screen.render = function (width) {
32
34
  return render.call(this, Math.max(1, width - panelWidth()));
33
35
  };
@@ -47,7 +47,7 @@ const rgb = (hex) => {
47
47
  return [(value >> 16) & 255, (value >> 8) & 255, value & 255];
48
48
  };
49
49
  /** A colour's SGR parameters: 24-bit, or the nearest of the 256 where the terminal has no more. */
50
- function sgr(hex, background, truecolor) {
50
+ export function sgr(hex, background, truecolor) {
51
51
  const [r, g, b] = rgb(hex);
52
52
  if (truecolor)
53
53
  return `${background ? 48 : 38};2;${r};${g};${b}`;
@@ -0,0 +1,72 @@
1
+ import { HStack, Spacer, VStack } from "@earendil-works/pi-tui";
2
+ /**
3
+ * Room around the whole of turri's screen: a row above and below, two columns either side - a cell is about twice as
4
+ * tall as it is wide.
5
+ *
6
+ * pi draws its screen one of two ways, and each is padded as it is drawn: full screen (pi's default), a layout - its
7
+ * root put in the middle of blank rows and columns; the regular screen, the lines its render gives - asked for in fewer
8
+ * columns, indented, with blank lines above and below. Overlays are laid over the whole terminal, so one that should
9
+ * keep to the padding is given it as its margin.
10
+ */
11
+ export const SCREEN_PADDING = { top: 1, right: 2, bottom: 1, left: 2 };
12
+ /** The columns inside the padding. */
13
+ export function paddedColumns(columns) {
14
+ return Math.max(1, columns - SCREEN_PADDING.left - SCREEN_PADDING.right);
15
+ }
16
+ /** The rows inside the padding. */
17
+ export function paddedRows(rows) {
18
+ return Math.max(1, rows - SCREEN_PADDING.top - SCREEN_PADDING.bottom);
19
+ }
20
+ /** Each padded layout root, to what it pads. */
21
+ const content = new WeakMap();
22
+ /** What a layout root pads - the root itself when it is not padded. */
23
+ export function unpadded(root) {
24
+ return content.get(root) ?? root;
25
+ }
26
+ /** A blank column as wide as the padding on its side: the row gives it what it measures. */
27
+ const blankColumns = (columns) => ({ render: () => [" ".repeat(columns)], invalidate() { } });
28
+ function padded(root) {
29
+ const row = new HStack([
30
+ { component: blankColumns(SCREEN_PADDING.left), basis: SCREEN_PADDING.left },
31
+ { component: root, basis: 0, grow: 1 },
32
+ { component: blankColumns(SCREEN_PADDING.right), basis: SCREEN_PADDING.right },
33
+ ]);
34
+ const whole = new VStack([
35
+ { component: new Spacer(SCREEN_PADDING.top), basis: SCREEN_PADDING.top },
36
+ // As pi lays out its own transcript: the rows the padding leaves, whatever it measures.
37
+ { component: row, basis: 0, grow: 1, minSize: 1 },
38
+ { component: new Spacer(SCREEN_PADDING.bottom), basis: SCREEN_PADDING.bottom },
39
+ ]);
40
+ content.set(whole, root);
41
+ return whole;
42
+ }
43
+ /**
44
+ * pi's screen drawn inside the padding, from its next frame - and every layout root it is given after, so the chat
45
+ * narrowed beside the docked panel (`narrowChat`) is padded too. Once a screen: pi starts a new one when it changes how
46
+ * it draws, which is padded when it is passed in.
47
+ */
48
+ export function padScreen(tui) {
49
+ const screen = tui;
50
+ if (screen.turriPadded === true)
51
+ return;
52
+ if (typeof screen.setLayoutRoot === "function") {
53
+ // Full screen before pi has given it its layout: padded at a later frame.
54
+ if (screen.layoutRoot === undefined)
55
+ return;
56
+ screen.turriPadded = true;
57
+ const set = screen.setLayoutRoot;
58
+ screen.setLayoutRoot = function (root) {
59
+ set.call(this, root === undefined ? undefined : padded(unpadded(root)));
60
+ };
61
+ screen.setLayoutRoot(screen.layoutRoot);
62
+ return;
63
+ }
64
+ screen.turriPadded = true;
65
+ // Read from the prototype: the screen's own render, not one laid over it.
66
+ const render = Object.getPrototypeOf(tui).render;
67
+ const left = " ".repeat(SCREEN_PADDING.left);
68
+ screen.render = function (width) {
69
+ const lines = render.call(this, paddedColumns(width)).map((line) => `${left}${line}`);
70
+ return [...new Array(SCREEN_PADDING.top).fill(""), ...lines, ...new Array(SCREEN_PADDING.bottom).fill("")];
71
+ };
72
+ }
@@ -54,6 +54,7 @@ The workflow signs in to npm with npm's trusted publishing (OIDC), not a token:
54
54
  | `src/regenerate/engine.ts` | choosing and resolving render models |
55
55
  | `src/regenerate/job-panel.ts`, `src/regenerate/transcript-view.ts`, `src/regenerate/diff-view.ts` | the job panel, the side panel's colours, and a spec diff as they show it - highlighted and banded |
56
56
  | `src/regenerate/side-panel.ts`, `src/regenerate/panel-dock.ts` | the side panel, and the chat narrowed beside it when it is docked |
57
+ | `src/screen-padding.ts` | the blank row above and below the whole screen and the two columns either side, in both of pi's ways of drawing it |
57
58
  | `src/regenerate/spec-map.ts`, `src/regenerate/spec-map-view.ts` | regen: each item's mark, from the records, the `.plain` files and the work loop's plan (`mapPlan`), and the map drawn |
58
59
  | `src/regenerate/debug-log.ts` | each render agent's whole conversation, always streamed to `.turri/debug/` |
59
60
  | `genome/` | what turri knows and can do: `genome/skills/`, the skill library, and `genome/rules/`, the \*\*\*plain authoring rules |
Binary file
Binary file
package/docs/theming.md CHANGED
@@ -25,14 +25,14 @@ Both themes, the command line's colours, the spec diff's, the banner's jellyfish
25
25
  | lines | `#22344a` | rules, dividers, frames: `borderMuted`, `mdHr` |
26
26
  | path | `#3e5a6b` | the editor's border, what is dim: `dim`, line numbers |
27
27
  | grauzone | `#85a3b2` | body text, and what can be skimmed: `text`, `muted` |
28
- | white | `#f4f7f9` | headings, tool titles, what you typed, the jellyfish |
28
+ | white | `#f4f7f9` | headings, tool titles, what you typed, the jellyfish's shine |
29
29
  | siesta tan | `#e9d8c8` | inline code, \*\*\*plain's text in quotes or backticks |
30
30
  | eggplant | `#732553` | a removed line's fill in a spec diff |
31
31
  | neon | `#5ce1e6` | links, focus, the selection, section labels, \*\*\*plain's concepts in a spec diff: `accent`, `mdLink`, regen's `◆` |
32
32
  | neon hover | `#8eebee` | types in code, an added line's fill in a light theme's spec diff |
33
- | pink | `#ff5c8d` | the wordmark, and what is done - its `✓`, the spec map's `◇` and `◆`: `success` |
33
+ | pink | `#ff5c8d` | the wordmark, the jellyfish's bell, and what is done - its `✓`, the spec map's `◇` and `◆`: `success` |
34
34
  | pink hover | `#ff7fa6` | \*\*\*plain's section headers, a removed line's `-`, keywords in code |
35
- | orange | `#ff7a3d` | the `$` of bash mode, and everything that asks for you - what failed (`✗`), what works with limits, what needs you (the spec map's `!`): `error`, `warning` |
35
+ | orange | `#ff7a3d` | the jellyfish's gut, the `$` of bash mode, and everything that asks for you - what failed (`✗`), what works with limits, what needs you (the spec map's `!`): `error`, `warning` |
36
36
 
37
37
  The design system keeps pink for the one thing to press and orange for the `$` prompt alone. A terminal has no buttons, but it needs status colours, so pink carries what is done and orange everything that asks for you - what failed, what works with limits, what waits on you - the mark (`✗`, `!`, `■`) telling them apart. The palette has no red.
38
38
 
package/docs/using.md CHANGED
@@ -7,7 +7,7 @@ cd ~/my-project
7
7
  turri
8
8
  ```
9
9
 
10
- That is pi, with turri's tools, skills and themes loaded - `turri` alone, or with pi's options (`turri -c`, `turri --harness-model <m>`). It opens with turri's own banner - the jellyfish it is named after, swimming until the conversation fills the screen: a picture in a terminal that shows images (Kitty, Ghostty, WezTerm, Warp, iTerm2), and drawn in text in any other, tmux included - swimming a stroke a loop, its tentacles following its bell, frames `scripts/jellyfish-frames.mjs` writes - turri's version (and, run from a clone, the commit it is on, and whether the clone has changes), where commands will run, the rules and skills loaded - rather than pi's startup header and resource list; `turri --verbose` shows those too. pi's "Update Available" notice is left out - turri's pi is a pinned dependency `pi update` never touches - and the terminal's tab reads `🪼 turri - <folder>`, not pi's `π`. Talk to it: ask it to start a project, add a feature, render a module, fix a spec. It decides which skill or tool applies - but not *how* a skill runs: `cultivate` hands the work to the gated runner, which owns the steps, the gates and the judges (see [skills and gates](skills-and-gates.md)).
10
+ That is pi, with turri's tools, skills and themes loaded - `turri` alone, or with pi's options (`turri -c`, `turri --harness-model <m>`). It opens with turri's own banner - the jellyfish it is named after, swimming until the conversation fills the screen: a pixel jellyfish - a pink bell, an orange gut, neon tentacles coming apart into code at their tips - swimming a stroke a loop, its bell shutting and rising, its tentacles following: a picture in a terminal that shows images (Kitty, Ghostty, WezTerm, Warp, iTerm2), and drawn smaller in half blocks in any other, tmux included (`src/jellyfish.ts` draws it, `scripts/jellyfish-frames.mjs` writes the pictures) - turri's version (and, run from a clone, the commit it is on, and whether the clone has changes) and where its settings are changed - and only when something is wrong, skills that failed to load or agents' commands less fenced than they should be - rather than pi's startup header and resource list; `turri --verbose` shows those too. pi's "Update Available" notice is left out - turri's pi is a pinned dependency `pi update` never touches - and the terminal's tab reads `🪼 turri - <folder>`, not pi's `π`. Talk to it: ask it to start a project, add a feature, render a module, fix a spec. It decides which skill or tool applies - but not *how* a skill runs: `cultivate` hands the work to the gated runner, which owns the steps, the gates and the judges (see [skills and gates](skills-and-gates.md)).
11
11
 
12
12
  The skills that build from scratch:
13
13
 
@@ -58,7 +58,7 @@ A render takes minutes, so in the TUI every render tool starts the work as a pul
58
58
  - **The panel** above the input, while something runs, shows on top what finished - a line a module, its pulses oldest first, each with its mark (green, red or amber, as in the side panel), number and kind - what it regenerated is in the side panel - `cognio … 2 earlier · ✓ 3 regenerate · ✓ 4 conformance`, the newest that fit the width - then a rule, and under it each running pulse - `cognio · regenerate pulse 5`, `cognio · conformance pulse 6` - its time and cost, and what its agents are doing. A module whose work is being planned into pulses shows at once, before any pulse runs - `shorty · planning the pulses · 12s` - the planner is an agent, and takes a while; a `/regenerate` says it began the moment it is sent. Pulses are numbered within their module, as regen numbers them. `ctrl+o` expands the agents' trace alone; what finished stays a line a module. The line of keys under it - `ctrl+o to expand the trace · ctrl+h to peek at regen` - has each key breathing, fully there to a little faint and back, until it is first pressed: once `ctrl+o` has opened the trace, or the side panel has been opened, that key is still for the rest of the session and the other breathes on. What waits is not in it, and it shows no spec diff - the side panel has both. With nothing running it is gone.
59
59
  - **A pulse's change** is what its agent works from, not a text diff of the module's file: the items `<spec change>` gives the agent - only what the pulse's functional specs use, an import's items as well as the module's own, a code pulse's without the test reqs - each in the file it is written in, under its section, in the file's order (`change-view.ts`).
60
60
  - **A spec diff**, in the side panel, reads as an editor shows a diff: its \*\*\*plain highlighted as [plyn](https://github.com/plainlang/plyn) highlights it - section headers, concepts, names in backticks, text in quotes, links, the front matter - each added line filled green and each removed one red, the text on top, the fill the width of the panel ([colours](theming.md#spec-diffs)). It is shown file by file, each file's lines together under its name, with what it adds and takes out - the module being rendered first, the rest by name, and none the diff does not change. Each line has its number in that file: a spec diff is of the spec as read, with what it imports laid in, so a line is placed in the file it is written in - its links read as the parser renders them - and a removed line, in no file now, goes with the change it is part of and has no number. A line is never put in a file on a guess: one that cannot be placed is listed last, under "not found in the spec's files". Where lines of a file are not in the diff, `↕ 28 lines` says how many, as GitHub does - `↑` above the first - and names the section the lines under it are in when it is another than the one above, `↕ 28 lines · ***functional specs***`, as GitHub names a hunk's function: each section is named once.
61
- - **The side panel** (`ctrl+h`, as it was left - but on the pulse that last failed, stopped or did not pass, when one has since it was last opened: the live panel that showed it is gone with the last pulse, and its announcement in the chat says `ctrl+h shows this pulse`; **`/pulses`** on the pulses; not `ctrl+j`, which a terminal without the Kitty keyboard protocol sends as a line feed, the same as `shift+enter`, and which a dialog takes for `enter`; both stay pi's new-line keys. `ctrl+h` is the byte old terminals sent for backspace; a terminal set to send `^H` for its Backspace key would open the panel on it) takes the whole screen - the panel or the chat, one at a time - or, `s`, sits beside the chat: the chat on the left, the panel in the right half of the screen, and `s` again over the whole screen. It opens as it was last left, for the conversation; a terminal under 100 columns has no room for both and shows the panel over the whole screen. It opens on **regen**: the spec itself, in full, with how much of it the code has been regenerated from drawn onto it.
61
+ - **The side panel** (`ctrl+h`, as it was left - but on the pulse that last failed, stopped or did not pass, when one has since it was last opened: the live panel that showed it is gone with the last pulse, and its announcement in the chat says `ctrl+h shows this pulse`; **`/pulses`** on the pulses; not `ctrl+j`, which a terminal without the Kitty keyboard protocol sends as a line feed, the same as `shift+enter`, and which a dialog takes for `enter`; both stay pi's new-line keys. `ctrl+h` is the byte old terminals sent for backspace; a terminal set to send `^H` for its Backspace key would open the panel on it) takes the whole screen - the panel or the chat, one at a time - or, `s`, sits beside the chat: the chat on the left, the panel in the right half of the screen, and `s` again over the whole screen. It opens as it was last left, for the conversation; a terminal under 104 columns - 100 inside the screen's padding, a blank row above and below everything and two columns either side - has no room for both and shows the panel over the whole screen. It opens on **regen**: the spec itself, in full, with how much of it the code has been regenerated from drawn onto it.
62
62
 
63
63
  Every item of the spec - a definition, a req, a functional spec with its sub-bullets and acceptance tests, what turri plans and diffs by - is a row in the file's order, its \*\*\*plain highlighted as plyn highlights it (as a spec diff is) and wrapped to the width, never cut short. The map has two columns, each headed on the line over it with a line of space under it: **status**, a mark a row - its rung on a ladder every item climbs - and the module's file, its spec. The top of the panel is where it is seen first: the top line, the module tabs, and the keys - each view's, `d talk it through in the chat` first when the module needs you - wrapped, never cut short, a line of space between the top line and the keys; then straight under them a rule, a line of space, and the columns' headings. The pulses view has its keys at the top too, and a line of space under its rule. The legend is a box of its own in the upper right corner, over what is under it, a line a mark, up the ladder. It names every rung, whatever the page shows - to take out only where something is - and a panel under 60 columns wide has none.
64
64
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turri-dev/cli",
3
- "version": "0.1.4-next.4342332",
3
+ "version": "0.1.4-next.5255d82",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
Binary file
Binary file
Binary file
@@ -1,252 +0,0 @@
1
-
2
- ▄▄▆▆██▆▆▄▄
3
- ▄▆█▓▒░░░░░░▒▓█▆▄
4
- ▄█▓▒░ ░▒▒▒▒░ ░▒▓█▄
5
- ▐█▒░ ░▒▓████▓▒░ ░▒█▌
6
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
7
- ) ) ) ║║ ╱ ) )
8
- ( ╱ ╱ ╱╱ ( ( )
9
- ╱ ╱ ) )) ) ) )
10
- ╱ ( ( ) ⁏ ) ) (
11
- ) ⁏ ) ⁏ @ ) ⁏ )
12
- ⁏ $ ⁏ ; ⁏ * ⁏
13
- # % & .
14
- ---
15
-
16
- ▄▆▆██▆▆▄
17
- ▄▆█▓▒░░░░▒▓█▆▄
18
- ▄█▓▒░ ░▒▒▒▒░ ░▒▓█▄
19
- ▐█▒░ ░▒▓████▓▒░ ░▒█▌
20
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
21
- ) ) ) ║║ ) ) )
22
- ( ( ( (╱ ( ( (
23
- ╱ ╱ ╱ ╱ ) ) )
24
- ╱ ( ) ) ⁏ ) ) )
25
- ) ⁏ ) ⁏ @ ) ⁏ ╱
26
- ⁏ $ ⁏ ; ⁏ * ⁏
27
- # % & .
28
- ---
29
- ▄▆██▆▄
30
- ▄▆█▓▒░░▒▓█▆▄
31
- ▄█▓▒░░▒▓▓▒░░▒▓█▄
32
- ▐█▒░░▒▓████▓▒░░▒█▌
33
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
34
- ) ) ) ║║ ) ) )
35
- ( ( ( (( ( ( )
36
- ) ) ) )) ) ) )
37
- ╱ ╱ ( (⁏ ) ) )
38
- ) ⁏ ) ⁏ @ ) ⁏ ╱
39
- ⁏ $ ⁏ ; ⁏ *⁏
40
- # % & .
41
-
42
- ---
43
- ▄▆██▆▄
44
- ▄▆█▓▒░░▒▓█▆▄
45
- ▄█▓▒░░▒▓▓▒░░▒▓█▄
46
- ▐█▒░░▒▓████▓▒░░▒█▌
47
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
48
- ) ) ) ║║ ) ) )
49
- ( ( ) (( ( ) (
50
- ) ) ))) ) ) )
51
- ╱ ╱ ( (⁏ ( ( )
52
- ) ⁏ ) ⁏@ ) ⁏ )
53
- ⁏ $ ⁏ ; ⁏ * ⁏
54
- # % & .
55
-
56
- ---
57
- ▄▆██▆▄
58
- ▄▆█▓▒░░▒▓█▆▄
59
- ▄█▓▒░░▒▓▓▒░░▒▓█▄
60
- ▐█▒░░▒▓████▓▒░░▒█▌
61
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
62
- ) ) ) ║║ ) ) )
63
- ( ) ) )) ) ) (
64
- ) ) ) )) ) ) )
65
- ( ( ( (⁏ ( ( )
66
- ) ⁏ ) ⁏@ ) ⁏ )
67
- ⁏ $ ⁏ ; ⁏ * ⁏
68
- # % & .
69
-
70
- ---
71
- ▄▆██▆▄
72
- ▄▆█▓▒░░▒▓█▆▄
73
- ▄█▓▒░░▒▓▓▒░░▒▓█▄
74
- ▐█▒░░▒▓████▓▒░░▒█▌
75
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
76
- ) ) ) ║║ ) ) )
77
- ( ( ) )) ( ) )
78
- ) ) ) ))) ) )
79
- ( ( ( (⁏ ( ( )
80
- ) ⁏ ) ⁏@ ) ⁏ )
81
- ⁏ $ ⁏ ; ⁏ * ⁏
82
- # % & .
83
-
84
- ---
85
- ▄▆██▆▄
86
- ▄▆█▓▒░░▒▓█▆▄
87
- ▄█▓▒░░▒▓▓▒░░▒▓█▄
88
- ▐█▒░░▒▓████▓▒░░▒█▌
89
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
90
- ) ) ) ║║ ) ) )
91
- ( ( ((( ( ) )
92
- ) ) ))) ) ) )
93
- ) ( ( (⁏ ( ( )
94
- ) ⁏ ) ⁏@ ) ⁏ )
95
- ⁏ $ ⁏ ; ⁏ * ⁏
96
- # % & .
97
-
98
- ---
99
-
100
- ▄▆▆██▆▆▄
101
- ▄▆█▓▒░░░░▒▓█▆▄
102
- ▄█▓▒░ ░▒▒▒▒░ ░▒▓█▄
103
- ▐█▒░ ░▒▓████▓▒░ ░▒█▌
104
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
105
- ) ) ) ║║ ) ) )
106
- ( ( ( (( ( ( )
107
- ) ) ) )) ) ) )
108
- ( ( ) ) ⁏ ( ( (
109
- ) ⁏ ) ⁏@ ) ⁏ )
110
- ⁏ $ ⁏ ; ⁏ * ⁏
111
- # % & .
112
- ---
113
-
114
- ▄▆▆██▆▆▄
115
- ▄▆█▓▒░░░░▒▓█▆▄
116
- ▄█▓▒░ ░▒▒▒▒░ ░▒▓█▄
117
- ▐█▒░ ░▒▓████▓▒░ ░▒█▌
118
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
119
- ) ) ) ║║ ) ) )
120
- ( ( ( (( ( ) )
121
- ╱ ) ) )) ) ) )
122
- ) ( ) )⁏ ( ( )
123
- ) ⁏ ) ⁏@ ) ⁏ )
124
- ⁏ $ ⁏ ; ⁏ * ⁏
125
- # % & .
126
- ---
127
-
128
- ▄▆▆██▆▆▄
129
- ▄▆█▓▒░░░░▒▓█▆▄
130
- ▄█▓▒░ ░▒▒▒▒░ ░▒▓█▄
131
- ▐█▒░ ░▒▓████▓▒░ ░▒█▌
132
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
133
- ) ) ) ║║ ) ) )
134
- ( ( ( (( ( ) (
135
- ╱ ) ) )) ) ) )
136
- ( ( ( )⁏ ( ) )
137
- ) ⁏ ) ⁏@ ) ⁏ )
138
- ⁏ $ ⁏ ; ⁏ * ⁏
139
- # % & .
140
- ---
141
-
142
- ▄▆▆██▆▆▄
143
- ▄▆█▓▒░░░░▒▓█▆▄
144
- ▄█▓▒░ ░▒▒▒▒░ ░▒▓█▄
145
- ▐█▒░ ░▒▓████▓▒░ ░▒█▌
146
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
147
- ) ) ) ║ ) ) )
148
- ( ( ( (( ( ) (
149
- ╱ ╱ ) )) ) ) )
150
- ( ) ( )⁏ ( ) )
151
- ) ⁏ ) ⁏@ ) ⁏ )
152
- ⁏ $ ⁏ ; ⁏ * ⁏
153
- # % & .
154
- ---
155
-
156
- ▄▄▆▆██▆▆▄▄
157
- ▄▆█▓▒░░░░░░▒▓█▆▄
158
- ▄█▓▒░ ░▒▒▒▒░ ░▒▓█▄
159
- ▐█▒░ ░▒▓████▓▒░ ░▒█▌
160
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
161
- ) ) ) ║ ╱ ) )
162
- ( ╱ ( (( ( ( )
163
- ╱ ) ╱ )) ) ) )
164
- ( ( ) (⁏ ) ) (
165
- ) ⁏ ) ⁏ @ ) ⁏ )
166
- ⁏ $ ⁏ ; ⁏ * ⁏
167
- # % & .
168
- ---
169
-
170
- ▄▄▆▆██▆▆▄▄
171
- ▄▆█▓▒░░░░░░▒▓█▆▄
172
- ▄█▓▒░ ░▒▒▒▒░ ░▒▓█▄
173
- ▐█▒░ ░▒▓████▓▒░ ░▒█▌
174
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
175
- ) ) ) ║║ ╱ ) )
176
- ( ╱ ( (( ( ( )
177
- ╱ ) ╱ )) ) ) )
178
- ( ( ) (⁏ ) ) (
179
- ) ⁏ ) ⁏ @ ) ⁏ )
180
- ⁏ $ ⁏ ; ⁏ * ⁏
181
- # % & .
182
- ---
183
-
184
- ▄▄▆▆██▆▆▄▄
185
- ▄▆█▓▒░░░░░░▒▓█▆▄
186
- ▄█▓▒░ ░▒▒▒▒░ ░▒▓█▄
187
- ▐█▒░ ░▒▓████▓▒░ ░▒█▌
188
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
189
- ) ) ) ║║ ╱ ) )
190
- ( ╱ ( (( ( ( )
191
- ╱ ) ╱ )) ) ) )
192
- ( ( ) (⁏ ) ) (
193
- ) ⁏ ) ⁏ @ ) ⁏ )
194
- ⁏ $ ⁏ ; ⁏ * ⁏
195
- # % & .
196
- ---
197
-
198
- ▄▄▆▆██▆▆▄▄
199
- ▄▆█▓▒░░░░░░▒▓█▆▄
200
- ▄█▓▒░ ░▒▒▒▒░ ░▒▓█▄
201
- ▐█▒░ ░▒▓████▓▒░ ░▒█▌
202
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
203
- ) ) ) ║║ ╱ ╱ )
204
- ( ╱ ╱ ╱╱ ( ) )
205
- ╱ ╱ ) )) ) ) )
206
- ( ( ( ) ⁏ ) ) (
207
- ) ⁏ ) ⁏ @ ) ⁏ )
208
- ⁏ $ ⁏ ; ⁏ * ⁏
209
- # % & .
210
- ---
211
-
212
- ▄▄▆▆██▆▆▄▄
213
- ▄▆█▓▒░░░░░░▒▓█▆▄
214
- ▄█▓▒░ ░▒▒▒▒░ ░▒▓█▄
215
- ▐█▒░ ░▒▓████▓▒░ ░▒█▌
216
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
217
- ) ) ╱ ║║ ╱ ╱ )
218
- ( ╱ ╱ ╱╱ ( ) )
219
- ╱ ╱ ) )) ) ) )
220
- ( ( ( ) ⁏ ) ) (
221
- ) ⁏ ) ⁏ @ ) ⁏ )
222
- ⁏ $ ⁏ ; ⁏ * ⁏
223
- # % & .
224
- ---
225
-
226
- ▄▄▆▆██▆▆▄▄
227
- ▄▆█▓▒░░░░░░▒▓█▆▄
228
- ▄█▓▒░ ░▒▒▒▒░ ░▒▓█▄
229
- ▐█▒░ ░▒▓████▓▒░ ░▒█▌
230
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
231
- ) ) ╱ ║║ ╱ ╱ )
232
- ( ╱ ╱ ╱╱ ( ) )
233
- ╱ ╱ ) )) ) ) )
234
- ( ( ( ) ⁏ ) ) (
235
- ) ⁏ ) ⁏ @ ) ⁏ )
236
- ⁏ $ ⁏ ; ⁏ * ⁏
237
- # % & .
238
- ---
239
-
240
- ▄▄▆▆██▆▆▄▄
241
- ▄▆█▓▒░░░░░░▒▓█▆▄
242
- ▄█▓▒░ ░▒▒▒▒░ ░▒▓█▄
243
- ▐█▒░ ░▒▓████▓▒░ ░▒█▌
244
- ▝█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█▘
245
- ) ) ) ║║ ╱ ╱ )
246
- ( ╱ ╱ ╱╱ ( ) )
247
- ╱ ╱ ) )) ) ) )
248
- ( ( ( ) ⁏ ) ) (
249
- ) ⁏ ) ⁏ @ ) ⁏ )
250
- ⁏ $ ⁏ ; ⁏ * ⁏
251
- # % & .
252
- ---
Binary file
Binary file
Binary file