@turri-dev/cli 0.1.7-next.eb57c34 → 0.1.7
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.
- package/README.md +1 -3
- package/assets/jellyfish/dark-00.png +0 -0
- package/assets/jellyfish/dark-01.png +0 -0
- package/assets/jellyfish/dark-02.png +0 -0
- package/assets/jellyfish/dark-03.png +0 -0
- package/assets/jellyfish/dark-04.png +0 -0
- package/assets/jellyfish/dark-05.png +0 -0
- package/assets/jellyfish/dark-06.png +0 -0
- package/assets/jellyfish/dark-07.png +0 -0
- package/assets/jellyfish/dark-08.png +0 -0
- package/assets/jellyfish/dark-09.png +0 -0
- package/assets/jellyfish/dark-10.png +0 -0
- package/assets/jellyfish/dark-11.png +0 -0
- package/assets/jellyfish/dark-12.png +0 -0
- package/assets/jellyfish/dark-13.png +0 -0
- package/assets/jellyfish/dark-14.png +0 -0
- package/assets/jellyfish/dark-15.png +0 -0
- package/assets/jellyfish/dark-16.png +0 -0
- package/assets/jellyfish/dark-17.png +0 -0
- package/assets/jellyfish/dark-18.png +0 -0
- package/assets/jellyfish/dark-19.png +0 -0
- package/assets/jellyfish/dark-20.png +0 -0
- package/assets/jellyfish/dark-21.png +0 -0
- package/assets/jellyfish/dark-22.png +0 -0
- package/assets/jellyfish/dark-23.png +0 -0
- package/assets/jellyfish/dark-24.png +0 -0
- package/assets/jellyfish/dark-25.png +0 -0
- package/assets/jellyfish/dark-26.png +0 -0
- package/assets/jellyfish/dark-27.png +0 -0
- package/assets/jellyfish/dark-28.png +0 -0
- package/assets/jellyfish/dark-29.png +0 -0
- package/assets/jellyfish/dark-30.png +0 -0
- package/assets/jellyfish/dark-31.png +0 -0
- package/assets/jellyfish/dark-32.png +0 -0
- package/assets/jellyfish/dark-33.png +0 -0
- package/assets/jellyfish/dark-34.png +0 -0
- package/assets/jellyfish/dark-35.png +0 -0
- package/assets/jellyfish/dark-36.png +0 -0
- package/assets/jellyfish/dark-37.png +0 -0
- package/assets/jellyfish/dark-38.png +0 -0
- package/assets/jellyfish/dark-39.png +0 -0
- package/assets/jellyfish/dark-40.png +0 -0
- package/assets/jellyfish/light-00.png +0 -0
- package/assets/jellyfish/light-01.png +0 -0
- package/assets/jellyfish/light-02.png +0 -0
- package/assets/jellyfish/light-03.png +0 -0
- package/assets/jellyfish/light-04.png +0 -0
- package/assets/jellyfish/light-05.png +0 -0
- package/assets/jellyfish/light-06.png +0 -0
- package/assets/jellyfish/light-07.png +0 -0
- package/assets/jellyfish/light-08.png +0 -0
- package/assets/jellyfish/light-09.png +0 -0
- package/assets/jellyfish/light-10.png +0 -0
- package/assets/jellyfish/light-11.png +0 -0
- package/assets/jellyfish/light-12.png +0 -0
- package/assets/jellyfish/light-13.png +0 -0
- package/assets/jellyfish/light-14.png +0 -0
- package/assets/jellyfish/light-15.png +0 -0
- package/assets/jellyfish/light-16.png +0 -0
- package/assets/jellyfish/light-17.png +0 -0
- package/assets/jellyfish/light-18.png +0 -0
- package/assets/jellyfish/light-19.png +0 -0
- package/assets/jellyfish/light-20.png +0 -0
- package/assets/jellyfish/light-21.png +0 -0
- package/assets/jellyfish/light-22.png +0 -0
- package/assets/jellyfish/light-23.png +0 -0
- package/assets/jellyfish/light-24.png +0 -0
- package/assets/jellyfish/light-25.png +0 -0
- package/assets/jellyfish/light-26.png +0 -0
- package/assets/jellyfish/light-27.png +0 -0
- package/assets/jellyfish/light-28.png +0 -0
- package/assets/jellyfish/light-29.png +0 -0
- package/assets/jellyfish/light-30.png +0 -0
- package/assets/jellyfish/light-31.png +0 -0
- package/assets/jellyfish/light-32.png +0 -0
- package/assets/jellyfish/light-33.png +0 -0
- package/assets/jellyfish/light-34.png +0 -0
- package/assets/jellyfish/light-35.png +0 -0
- package/assets/jellyfish/light-36.png +0 -0
- package/assets/jellyfish/light-37.png +0 -0
- package/assets/jellyfish/light-38.png +0 -0
- package/assets/jellyfish/light-39.png +0 -0
- package/assets/jellyfish/light-40.png +0 -0
- package/dist/extension.js +13 -8
- package/dist/jellyfish.d.ts +23 -13
- package/dist/jellyfish.js +230 -97
- package/dist/regenerate/loop.d.ts +2 -0
- package/dist/regenerate/loop.js +3 -2
- package/docs/images/turri-banner.gif +0 -0
- package/docs/theming.md +6 -4
- package/docs/using.md +1 -1
- package/package.json +1 -1
- package/docs/images/turri-banner.png +0 -0
- package/docs/images/turri-screenshot.png +0 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<p align="center"><img src="docs/images/turri-banner.
|
|
1
|
+
<p align="center"><img src="docs/images/turri-banner.gif" alt="turri, beside its mark - the first coding agent with the power of regenerative software: npm install -g @turri-dev/cli - and a pixel jellyfish regenerating, from a polyp on the sea floor"></p>
|
|
2
2
|
|
|
3
3
|
<h1 align="center">turri<br>the first coding agent with the power of<br>regenerative software<br> </h1>
|
|
4
4
|
|
|
@@ -101,8 +101,6 @@ 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
|
-
|
|
106
104
|
**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.
|
|
107
105
|
|
|
108
106
|
1. **Define.** You say what the software should do; **turri** writes it down as \*\*\*plain.
|
|
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
|
|
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
|
|
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
|
|
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
|
@@ -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";
|
|
@@ -2353,8 +2353,8 @@ function registerBranding(pi, skills) {
|
|
|
2353
2353
|
...(data.pending > 0 ? [theme.fg("warning", `${data.pending} skill${data.pending === 1 ? "" : "s"} failed to load`)] : []),
|
|
2354
2354
|
...(data.sandbox !== undefined && data.sandbox.level !== "ok" ? [theme.fg(data.sandbox.level === "limited" ? "warning" : "error", data.sandbox.text)] : []),
|
|
2355
2355
|
];
|
|
2356
|
-
// The jellyfish turri is named after, in ink for the theme's background: a picture
|
|
2357
|
-
// 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.
|
|
2358
2358
|
return new Banner(lines, (rule) => theme.fg("borderMuted", rule), jellyfishArt(theme.appearance, (theme.getColorMode?.() ?? "truecolor") === "truecolor"));
|
|
2359
2359
|
});
|
|
2360
2360
|
pi.on("session_start", (_event, ctx) => {
|
|
@@ -2377,10 +2377,15 @@ function registerBranding(pi, skills) {
|
|
|
2377
2377
|
}
|
|
2378
2378
|
});
|
|
2379
2379
|
}
|
|
2380
|
-
/**
|
|
2381
|
-
|
|
2382
|
-
|
|
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`. */
|
|
2383
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];
|
|
2384
2389
|
let jellyfishTimer;
|
|
2385
2390
|
/**
|
|
2386
2391
|
* Moves the jellyfish on a frame every JELLYFISH_FRAME_MS while the banner is on the screen. An entry's
|
|
@@ -2481,7 +2486,7 @@ class Banner {
|
|
|
2481
2486
|
const text = this.lines.flatMap((line) => wrapTextWithAnsi(line, Math.max(1, width - 1 - left)));
|
|
2482
2487
|
const height = Math.max(text.length, placed?.rows ?? 0);
|
|
2483
2488
|
const above = Math.floor((height - text.length) / 2);
|
|
2484
|
-
const drawn = placed?.kind === "text" ? placed.frames[
|
|
2489
|
+
const drawn = placed?.kind === "text" ? placed.frames[shownFrame()] : undefined;
|
|
2485
2490
|
const rows = Array.from({ length: height }, (_, row) => {
|
|
2486
2491
|
const line = text[row - above];
|
|
2487
2492
|
const jellyfish = drawn !== undefined ? (drawn[row] ?? " ".repeat(placed.columns)) : " ".repeat(placed?.columns ?? 0);
|
|
@@ -2502,7 +2507,7 @@ class Banner {
|
|
|
2502
2507
|
const kitty = getCapabilities().images === "kitty";
|
|
2503
2508
|
if (kitty && this.kittyImageId === undefined)
|
|
2504
2509
|
this.kittyImageId = allocateImageId();
|
|
2505
|
-
const result = renderImage(art.frames[
|
|
2510
|
+
const result = renderImage(art.frames[shownFrame()], art.dimensions, {
|
|
2506
2511
|
maxWidthCells: room,
|
|
2507
2512
|
maxHeightCells: rows,
|
|
2508
2513
|
imageId: this.kittyImageId,
|
package/dist/jellyfish.d.ts
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The jellyfish turri is named after, in pixels: a
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
7
|
*/
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
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. */
|
|
11
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[][];
|
|
12
19
|
export declare const JELLYFISH_WIDTH: number;
|
|
13
20
|
export declare const JELLYFISH_HEIGHT: number;
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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[];
|
|
18
26
|
/** How long each frame shows. */
|
|
19
|
-
export declare const JELLYFISH_FRAME_MS =
|
|
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[][];
|
|
20
30
|
/**
|
|
21
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
|
|
22
32
|
* and the lower one the background's, in as many colours as the terminal has: a column a pixel, a row every two.
|
package/dist/jellyfish.js
CHANGED
|
@@ -1,114 +1,247 @@
|
|
|
1
1
|
import { sgr } from "./regenerate/plain-palette.js";
|
|
2
|
-
/**
|
|
2
|
+
/** How much of a pixel shows. */
|
|
3
|
+
const ALPHA = { p: 1, m: 1, i: 1, e: 1, h: 1, o: 1, N: 1, n: 0.5, c: 0.6 };
|
|
4
|
+
/** The pixels' colours on a dark background and a light one - the tentacles blue whale on light, where neon does not show. */
|
|
3
5
|
export const JELLYFISH_COLOURS = {
|
|
4
|
-
dark: { p: "#ff5c8d",
|
|
5
|
-
light: { p: "#ff5c8d",
|
|
6
|
+
dark: { p: "#ff5c8d", m: "#a9479a", i: "#5b57d9", e: "#732553", h: "#ff7fa6", o: "#ff7a3d", N: "#5ce1e6", n: "#5ce1e6", c: "#5ce1e6" },
|
|
7
|
+
light: { p: "#ff5c8d", m: "#a9479a", i: "#5b57d9", e: "#732553", h: "#ff7fa6", o: "#ff7a3d", N: "#1e3442", n: "#1e3442", c: "#1e3442" },
|
|
6
8
|
};
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
],
|
|
9
|
+
/** What a pixel short of whole is laid over, to draw it in half blocks: abyss, or white. */
|
|
10
|
+
const BACKGROUND = { dark: "#0a0f1a", light: "#f4f7f9" };
|
|
11
|
+
/** How much of a pixel shows, for a picture's alpha. */
|
|
12
|
+
export function jellyfishAlpha(pixel) {
|
|
13
|
+
return ALPHA[pixel] ?? 0;
|
|
14
|
+
}
|
|
15
|
+
/** The lifecycle's grid, and the sea floor the polyp stands on. */
|
|
16
|
+
const WIDTH = 40;
|
|
17
|
+
const HEIGHT = 60;
|
|
18
|
+
const FLOOR = 59;
|
|
19
|
+
const put = (grid, x, y, pixel, bounded = true) => {
|
|
20
|
+
x = Math.round(x);
|
|
21
|
+
y = Math.round(y);
|
|
22
|
+
if (bounded && (x < 0 || x > WIDTH - 1 || y < 0 || y > FLOOR))
|
|
23
|
+
return;
|
|
24
|
+
grid.set(`${x},${y}`, pixel);
|
|
47
25
|
};
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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)
|
|
26
|
+
/** The bell's colour down its height: pink, through its midtone, to its shadow - dithered where one meets the next. */
|
|
27
|
+
const band = (t, x, y) => t < 0.3 ? "p" : t < 0.42 ? ((x + y) % 2 ? "p" : "m") : t < 0.62 ? "m" : t < 0.74 ? ((x + y) % 2 ? "m" : "i") : "i";
|
|
28
|
+
/** An ellipse filled with the bell's colours, or its top half - a dome. */
|
|
29
|
+
function shape(grid, cx, cy, rx, ry, dome, bounded = true) {
|
|
30
|
+
for (let y = Math.floor(cy - ry); y <= Math.ceil(cy + ry); y++) {
|
|
31
|
+
if (dome && y > cy)
|
|
32
|
+
continue;
|
|
33
|
+
for (let x = Math.floor(cx - rx); x <= Math.ceil(cx + rx); x++) {
|
|
34
|
+
const dx = (x + 0.5 - cx) / rx;
|
|
35
|
+
const dy = (y + 0.5 - cy) / ry;
|
|
36
|
+
if (dx * dx + dy * dy > 1)
|
|
88
37
|
continue;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (strand.ink === "n" && left > 5 && (y + t) % 3 === 0)
|
|
92
|
-
set(rim + 1 + y, x + (strand.at < 0 ? -1 : 1), "n", false);
|
|
38
|
+
const t = dome ? (y - (cy - ry)) / ry : (y - (cy - ry)) / (2 * ry);
|
|
39
|
+
put(grid, x, y, band(Math.max(0, Math.min(1, t)), x, y), bounded);
|
|
93
40
|
}
|
|
94
|
-
}
|
|
95
|
-
return grid.map((row) => row.join(""));
|
|
41
|
+
}
|
|
96
42
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
43
|
+
/** The bell's rim: every fourth pixel from the middle left out, the rest eggplant. */
|
|
44
|
+
function rim(grid, y) {
|
|
45
|
+
for (let x = 0; x < WIDTH + 1; x++) {
|
|
46
|
+
const key = `${x},${y}`;
|
|
47
|
+
if (!grid.has(key))
|
|
48
|
+
continue;
|
|
49
|
+
if (Math.abs(x - 20) % 4 === 2)
|
|
50
|
+
grid.delete(key);
|
|
51
|
+
else
|
|
52
|
+
grid.set(key, "e");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** The glint on the bell, up on the left. */
|
|
56
|
+
const GLINT = [[11, 6], [12, 5], [13, 5], [10, 7], [11, 7], [14, 4]];
|
|
57
|
+
/** Where each of the thirteen tentacles hangs, how long it is, and its place in the sway. */
|
|
58
|
+
const TENTACLES = [8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32].map((x, i) => ({ x, length: 16 + ((i * 7) % 12), i }));
|
|
59
|
+
/** The jellyfish at a scale `k`, its rim on row `cy`, its tentacles grown `grown` of the way. */
|
|
60
|
+
function medusa(grid, k, cy, grown) {
|
|
61
|
+
shape(grid, 20.5, cy, 14.5 * k, 16 * k, true);
|
|
62
|
+
if (k > 0.45)
|
|
63
|
+
rim(grid, Math.floor(cy));
|
|
64
|
+
if (k > 0.8) {
|
|
65
|
+
for (const [gx, gy] of GLINT) {
|
|
66
|
+
const x = Math.round(20 + (gx - 20) * k);
|
|
67
|
+
const y = Math.round(cy + (gy - 18) * k);
|
|
68
|
+
if (grid.has(`${x},${y}`))
|
|
69
|
+
put(grid, x, y, "h");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const gutY = cy - 7 * k;
|
|
73
|
+
const halfWidth = Math.max(1.5, 2.5 * k);
|
|
74
|
+
const halfHeight = Math.max(0.6, 1.5 * k);
|
|
75
|
+
for (let y = Math.round(gutY - halfHeight); y < gutY + halfHeight; y++)
|
|
76
|
+
for (let x = Math.round(20 - halfWidth); x < 20 + halfWidth; x++)
|
|
77
|
+
put(grid, x, y, "o");
|
|
78
|
+
if (k > 0.7) {
|
|
79
|
+
for (let x = 19; x <= 21; x++) {
|
|
80
|
+
put(grid, x, gutY - 2, "o");
|
|
81
|
+
put(grid, x, gutY + 2, "o");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const { x, length, i } of TENTACLES) {
|
|
85
|
+
const shown = Math.round(length * Math.pow(grown, 1.2));
|
|
86
|
+
const root = 20 + (x - 20) * k;
|
|
87
|
+
for (let j = 0; j < shown; j++)
|
|
88
|
+
put(grid, root + Math.round(Math.sin(j * 0.5 + i) * 1.6 * (j / shown)), cy + 1 + j, j % 2 ? "n" : "N");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** The polyp on the floor, grown `u` of the way: a stalk, a head with its gut, a crown of tentacles. */
|
|
92
|
+
function polyp(grid, u) {
|
|
93
|
+
shape(grid, 20, FLOOR, 3, 1.4, true);
|
|
94
|
+
const height = Math.round(2 + 12 * u);
|
|
95
|
+
const radius = 1.4 + 2.2 * u;
|
|
96
|
+
for (let y = FLOOR - height; y <= FLOOR; y++) {
|
|
97
|
+
for (let x = 19; x <= 20; x++)
|
|
98
|
+
put(grid, x, y, band(0.45 + (0.55 * (y - (FLOOR - height))) / Math.max(1, height), x, y));
|
|
99
|
+
}
|
|
100
|
+
const head = FLOOR - height - radius + 1;
|
|
101
|
+
shape(grid, 20, head, radius, radius, false);
|
|
102
|
+
put(grid, 19, head, "o");
|
|
103
|
+
put(grid, 20, head, "o");
|
|
104
|
+
if (radius > 2.5) {
|
|
105
|
+
put(grid, 19, head - 1, "o");
|
|
106
|
+
put(grid, 20, head - 1, "o");
|
|
107
|
+
}
|
|
108
|
+
const crown = [[-4, -3], [3, -3], [-1, -5], [1, -5], [-5, -4], [4, -4], [-1, -6], [1, -6], [-6, -5], [5, -5]];
|
|
109
|
+
const scale = radius / 3.6;
|
|
110
|
+
for (let i = 0; i < Math.floor(u * crown.length); i++) {
|
|
111
|
+
const [dx, dy] = crown[i];
|
|
112
|
+
put(grid, 20 + dx * scale + (dx < 0 ? 0 : 0.5), head + dy * scale, i % 2 ? "c" : "N");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const ease = (t) => (t < 0 ? 0 : t > 1 ? 1 : t * t * (3 - 2 * t));
|
|
116
|
+
/** The lifecycle: twenty frames of the polyp growing into the jellyfish, nineteen of it sinking and the polyp growing back. */
|
|
117
|
+
function lifecycle() {
|
|
118
|
+
const frames = [];
|
|
119
|
+
for (let f = 0; f < 20; f++) {
|
|
120
|
+
const grid = new Map();
|
|
121
|
+
const g = f / 19;
|
|
122
|
+
const p = ease((g - 0.2) / 0.65);
|
|
123
|
+
if (1 - p > 0.06)
|
|
124
|
+
polyp(grid, 1 - p);
|
|
125
|
+
else if (p < 1)
|
|
126
|
+
shape(grid, 20, FLOOR, 2.2, 1, true);
|
|
127
|
+
const e = ease((g - 0.08) / 0.92);
|
|
128
|
+
if (e > 0)
|
|
129
|
+
medusa(grid, 0.18 + 0.82 * e, 40 - 22 * e, Math.pow(e, 1.5));
|
|
130
|
+
frames.push(grid);
|
|
131
|
+
}
|
|
132
|
+
// It sinks to the floor first, and the polyp grows up from there.
|
|
133
|
+
for (let f = 0; f <= 18; f++) {
|
|
134
|
+
const grid = new Map();
|
|
135
|
+
if (f <= 8) {
|
|
136
|
+
const s = f / 8;
|
|
137
|
+
medusa(grid, 1 - 0.72 * s, 18 + 40 * s, 1 - s);
|
|
138
|
+
}
|
|
139
|
+
else if (f <= 11) {
|
|
140
|
+
const w = (f - 9) / 2;
|
|
141
|
+
shape(grid, 20, FLOOR, 4.2 - 1.2 * w, 3.2 - 0.8 * w, true);
|
|
142
|
+
put(grid, 19, FLOOR - 1, "o");
|
|
143
|
+
put(grid, 20, FLOOR - 1, "o");
|
|
144
|
+
}
|
|
145
|
+
else
|
|
146
|
+
polyp(grid, (f - 11) / 7);
|
|
147
|
+
frames.push(grid);
|
|
148
|
+
}
|
|
149
|
+
return frames;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The jellyfish swimming, grown: its bell still, its tentacles in one of two strokes - the second drawn a pixel higher,
|
|
153
|
+
* as it bobs.
|
|
154
|
+
*/
|
|
155
|
+
function hero(stroke) {
|
|
156
|
+
const grid = new Map();
|
|
157
|
+
shape(grid, 20.5, 18, 14.5, 16, true, false);
|
|
158
|
+
rim(grid, 18);
|
|
159
|
+
for (const [x, y] of GLINT)
|
|
160
|
+
if (grid.has(`${x},${y}`))
|
|
161
|
+
put(grid, x, y, "h", false);
|
|
162
|
+
for (let y = 10; y <= 12; y++)
|
|
163
|
+
for (let x = 18; x <= 22; x++)
|
|
164
|
+
put(grid, x, y, "o", false);
|
|
165
|
+
for (let x = 19; x <= 21; x++) {
|
|
166
|
+
put(grid, x, 9, "o", false);
|
|
167
|
+
put(grid, x, 13, "o", false);
|
|
168
|
+
}
|
|
169
|
+
for (const { x, length, i } of TENTACLES) {
|
|
170
|
+
for (let k = 0; k < length; k++) {
|
|
171
|
+
const sway = Math.round(Math.sin(k * 0.5 + i + (stroke === 1 ? 2.2 : 0)) * 1.6 * (k / length));
|
|
172
|
+
put(grid, x + sway, 19 + k, k % 2 ? "n" : "N", false);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (stroke === 0)
|
|
176
|
+
return grid;
|
|
177
|
+
const raised = new Map();
|
|
178
|
+
for (const [key, pixel] of grid) {
|
|
179
|
+
const [x, y] = key.split(",").map(Number);
|
|
180
|
+
raised.set(`${x},${y - 1}`, pixel);
|
|
181
|
+
}
|
|
182
|
+
return raised;
|
|
183
|
+
}
|
|
184
|
+
/** Every frame on one grid, cut to what any of them draws on: rows of pixels, "." where there is none. */
|
|
185
|
+
function crop(grids) {
|
|
186
|
+
const cells = grids.flatMap((grid) => [...grid.keys()].map((key) => key.split(",").map(Number)));
|
|
187
|
+
const left = Math.min(...cells.map(([x]) => x));
|
|
188
|
+
const right = Math.max(...cells.map(([x]) => x));
|
|
189
|
+
const top = Math.min(...cells.map(([, y]) => y));
|
|
190
|
+
const bottom = Math.max(...cells.map(([, y]) => y));
|
|
191
|
+
return grids.map((grid) => Array.from({ length: bottom - top + 1 }, (_, y) => Array.from({ length: right - left + 1 }, (_, x) => grid.get(`${x + left},${y + top}`) ?? ".").join("")));
|
|
192
|
+
}
|
|
193
|
+
/** The lifecycle's frames, then the jellyfish's two strokes, swimming. */
|
|
194
|
+
const LIFECYCLE = lifecycle();
|
|
195
|
+
const SWIM = LIFECYCLE.length;
|
|
196
|
+
/** The jellyfish's frames, each as rows of pixels: what the pictures are rendered from. */
|
|
197
|
+
export const JELLYFISH_FRAMES = crop([...LIFECYCLE, hero(0), hero(1)]);
|
|
198
|
+
export const JELLYFISH_WIDTH = JELLYFISH_FRAMES[0][0].length;
|
|
199
|
+
export const JELLYFISH_HEIGHT = JELLYFISH_FRAMES[0].length;
|
|
200
|
+
/**
|
|
201
|
+
* The order the frames play in, round and round: the polyp a moment, growing into the jellyfish, swimming four
|
|
202
|
+
* seconds - a stroke every five frames - then sinking, and the polyp growing back.
|
|
203
|
+
*/
|
|
204
|
+
export const JELLYFISH_SEQUENCE = [
|
|
205
|
+
...[0, 0, 0],
|
|
206
|
+
...Array.from({ length: 20 }, (_, f) => f),
|
|
207
|
+
...Array.from({ length: 27 }, (_, f) => SWIM + (Math.floor(f / 5) % 2)),
|
|
208
|
+
...Array.from({ length: 19 }, (_, f) => 20 + f),
|
|
209
|
+
];
|
|
103
210
|
/** How long each frame shows. */
|
|
104
|
-
export const JELLYFISH_FRAME_MS =
|
|
211
|
+
export const JELLYFISH_FRAME_MS = 150;
|
|
212
|
+
/** How much smaller the frames are drawn in half blocks: a cell is a block of SHRINK pixels square. */
|
|
213
|
+
const SHRINK = 3;
|
|
214
|
+
/**
|
|
215
|
+
* A frame a third of the size: each block drawn as the pixel at its middle. What most of a block is would run the
|
|
216
|
+
* tentacles, two pixels apart, together into one fringe; its middle keeps strands apart.
|
|
217
|
+
*/
|
|
218
|
+
function shrink(pixels) {
|
|
219
|
+
const middle = Math.floor(SHRINK / 2);
|
|
220
|
+
const rows = [];
|
|
221
|
+
for (let y = middle; y < pixels.length; y += SHRINK) {
|
|
222
|
+
let row = "";
|
|
223
|
+
for (let x = middle; x < pixels[0].length; x += SHRINK)
|
|
224
|
+
row += pixels[y][x];
|
|
225
|
+
rows.push(row);
|
|
226
|
+
}
|
|
227
|
+
return rows;
|
|
228
|
+
}
|
|
229
|
+
/** The frames as a terminal without images draws them, in half blocks: the same order, a third of the size. */
|
|
230
|
+
export const JELLYFISH_TEXT_FRAMES = JELLYFISH_FRAMES.map(shrink);
|
|
231
|
+
/** A colour laid over another, `alpha` of it showing. */
|
|
232
|
+
function over(hex, under, alpha) {
|
|
233
|
+
if (alpha >= 1)
|
|
234
|
+
return hex;
|
|
235
|
+
const channel = (colour, at) => Number.parseInt(colour.slice(at, at + 2), 16);
|
|
236
|
+
return `#${[1, 3, 5].map((at) => Math.round(channel(hex, at) * alpha + channel(under, at) * (1 - alpha)).toString(16).padStart(2, "0")).join("")}`;
|
|
237
|
+
}
|
|
105
238
|
/**
|
|
106
239
|
* 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
240
|
* and the lower one the background's, in as many colours as the terminal has: a column a pixel, a row every two.
|
|
108
241
|
*/
|
|
109
242
|
export function halfBlocks(pixels, appearance, truecolor) {
|
|
110
243
|
const colours = JELLYFISH_COLOURS[appearance];
|
|
111
|
-
const colour = (pixel) =>
|
|
244
|
+
const colour = (pixel) => pixel === undefined || pixel === "." ? undefined : over(colours[pixel], BACKGROUND[appearance], ALPHA[pixel]);
|
|
112
245
|
const rows = [];
|
|
113
246
|
for (let y = 0; y < pixels.length; y += 2) {
|
|
114
247
|
let row = "";
|
|
@@ -281,6 +281,8 @@ export interface ModuleCompletion {
|
|
|
281
281
|
completed: number;
|
|
282
282
|
/** Of the completed ones, those its conformance tests are recorded as written for, as the spec has them now. */
|
|
283
283
|
conformanceTested: number;
|
|
284
|
+
/** Those conformance-tested ones, by number: for a host that shows each functional spec's state. */
|
|
285
|
+
tested: string[];
|
|
284
286
|
/** Its functional specs not completed, by number. */
|
|
285
287
|
incomplete: string[];
|
|
286
288
|
}
|
package/dist/regenerate/loop.js
CHANGED
|
@@ -121,7 +121,7 @@ export function functionalityCompletion(plainFile) {
|
|
|
121
121
|
if (readRecord(entry.buildDir) !== null)
|
|
122
122
|
delivered = index;
|
|
123
123
|
});
|
|
124
|
-
const counts = chain.map((entry) => ({ module: entry.moduleName, specified: entry.frids.length, completed: 0, conformanceTested: 0, incomplete: [...entry.frids] }));
|
|
124
|
+
const counts = chain.map((entry) => ({ module: entry.moduleName, specified: entry.frids.length, completed: 0, conformanceTested: 0, tested: [], incomplete: [...entry.frids] }));
|
|
125
125
|
if (delivered === -1)
|
|
126
126
|
return { codebase: tipOf(chain).buildDir, modules: counts };
|
|
127
127
|
const codebase = chain.slice(0, delivered + 1);
|
|
@@ -155,7 +155,8 @@ export function functionalityCompletion(plainFile) {
|
|
|
155
155
|
const completed = entry.frids.filter((frid) => !touched.has(`${entry.moduleName}:${frid}`));
|
|
156
156
|
const tested = (readRecord(conformanceDirFor(entry.moduleName, modulesRoot(projectDirOf(entry.plainFile))))?.[FUNCTIONAL_REQUIREMENTS] ?? []).filter((item) => item.frid !== undefined && item.markdown.trim() === specText(entry.moduleName, item.frid));
|
|
157
157
|
count.completed = completed.length;
|
|
158
|
-
count.
|
|
158
|
+
count.tested = completed.filter((frid) => tested.some((item) => item.frid === frid));
|
|
159
|
+
count.conformanceTested = count.tested.length;
|
|
159
160
|
count.incomplete = entry.frids.filter((frid) => !completed.includes(frid));
|
|
160
161
|
});
|
|
161
162
|
return { codebase: tip.buildDir, modules: counts };
|
|
Binary file
|
package/docs/theming.md
CHANGED
|
@@ -14,7 +14,7 @@ Bad input fails quietly - an unknown `--use-theme` name and a malformed theme fi
|
|
|
14
14
|
|
|
15
15
|
## The palette
|
|
16
16
|
|
|
17
|
-
Both themes, the command line's colours, the spec diff's, the banner's jellyfish and the docs' diagram are drawn from turri's design system and nothing else: dark, quiet surfaces, one pink thing to press, neon for anything you can follow. These are all the colours turri has - none is mixed, tinted or added.
|
|
17
|
+
Both themes, the command line's colours, the spec diff's, the banner's jellyfish and the docs' diagram are drawn from turri's design system and nothing else: dark, quiet surfaces, one pink thing to press, neon for anything you can follow. These are all the colours turri has - none is mixed, tinted or added, but for the jellyfish's tentacles, every other pixel of which the design draws half seen.
|
|
18
18
|
|
|
19
19
|
| name | value | in `turri-dark` |
|
|
20
20
|
|---|---|---|
|
|
@@ -25,13 +25,15 @@ 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
|
|
28
|
+
| white | `#f4f7f9` | headings, tool titles, what you typed |
|
|
29
29
|
| siesta tan | `#e9d8c8` | strings in code, \*\*\*plain's text in quotes or backticks |
|
|
30
|
-
| eggplant | `#732553` | a removed line's fill in a spec diff |
|
|
30
|
+
| eggplant | `#732553` | a removed line's fill in a spec diff, the jellyfish's rim |
|
|
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` | inline code - siesta tan beside white text was too close to tell apart, and tiring to read - types in code, an added line's fill in a light theme's spec diff: `mdCode` |
|
|
33
33
|
| pink | `#ff5c8d` | the wordmark, the jellyfish's bell, and what is done - its `✓`, the spec map's `◇` and `◆`: `success` |
|
|
34
|
-
| pink hover | `#ff7fa6` | \*\*\*plain's section headers, a removed line's `-`, keywords in code |
|
|
34
|
+
| pink hover | `#ff7fa6` | the glint on the jellyfish's bell, \*\*\*plain's section headers, a removed line's `-`, keywords in code |
|
|
35
|
+
| orchid | `#a9479a` | the jellyfish's bell, between its pink and its indigo - nowhere else |
|
|
36
|
+
| indigo | `#5b57d9` | the jellyfish's bell, at its rim - nowhere else |
|
|
35
37
|
| 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
38
|
|
|
37
39
|
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.
|
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,
|
|
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, regenerating until the conversation fills the screen: a pixel polyp on the sea floor grows into the jellyfish - a bell from pink down to indigo, an orange gut, neon tentacles - which swims a while, sinks back to the floor, and the polyp grows again, round and round: a picture in a terminal that shows images (Kitty, Ghostty, WezTerm, Warp, iTerm2), and drawn at a third of the size in half blocks in any other, tmux included (`src/jellyfish.ts` draws it, ported from the design system's pixel kit, `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
|
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|