slopie 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,147 @@
1
+ // Instant first frame for `slopie audit`.
2
+ //
3
+ // Loading the CLI bundle takes ~300 ms. For the interactive audit we paint the
4
+ // loading story's first frame (Slopie, the brand, the privacy promise) before
5
+ // the bundle loads, so the screen responds at once; the real loading story
6
+ // takes over the alternate screen in exactly the same place and calls
7
+ // `globalThis.__slopieSplash.handOff()`.
8
+ //
9
+ // The pixel art and geometry mirror packages/cli/src/audit-ui/{mascot,loading}.ts;
10
+ // tests/audit-ui/splash.test.ts keeps them identical.
11
+ //
12
+ // If anything else ends up writing (an error, --help), the alternate screen is
13
+ // left first so the message stays visible, and the terminal is always restored
14
+ // on exit.
15
+
16
+ const PLAIN_FLAGS = new Set(['--json', '--plain', '--export', '--legacy', '--sweep', '--verify', '--help', '-h', '--version', '-V']);
17
+
18
+ /** Slopie, idle. Rows of palette letters; '.' is transparent. First row is pixel row -2 (the tip). */
19
+ export const SPLASH_ART = {
20
+ big: {
21
+ top: -2,
22
+ palette: ['#ff7a1a', '#e16011', '#c94a0a', '#ffb070', '#fff4e6', '#ffffff', '#1c0f08', '#8a2e05', '#ff7027'],
23
+ rows: ['........ab....', '.......aa.....', '.....aaaa.....', '...aaddaaaa...', '..adedaaaaaa..', '.aadaaaaaaaab.', '.adaaaaaaaabb.', 'aaaaaaaaaaaabb', 'aaafgaaaafgabb', 'aaaggaaaaggabb', 'aaaaaaaaaaaabb', 'aiiaahaahaaiib', 'caaaaahhaaaabc', 'ccaaaaaaaaaacc', '.cccccccccccc.', '..cccccccccc..', '...c.......c..', '...........b..'],
24
+ },
25
+ compact: {
26
+ top: 0,
27
+ palette: ['#ff7a1a', '#e16011', '#c94a0a', '#ffb070', '#fff4e6', '#1c0f08', '#8a2e05'],
28
+ rows: ['..aaaa..', '.adaaaa.', 'aeaaaaab', 'aafaafab', 'aafaafab', 'aagaagab', 'caaggaac', '.cccccc.'],
29
+ },
30
+ };
31
+
32
+ /** Mirrors loadingGeometry() in audit-ui/loading.ts. */
33
+ export function splashGeometry(w, h) {
34
+ const L = w >= 100 && h >= 30;
35
+ const S = w < 76 || h < 23;
36
+ const mx = L ? 4 : S ? 2 : 3;
37
+ const scale = L ? 2 : 1;
38
+ const mascotW = S ? 10 : 18 * scale;
39
+ const mascotH = S ? 4 : 8 * scale;
40
+ const panelX = S ? mx : Math.max(mx + mascotW + 6, Math.floor(w * (L ? 0.46 : 0.42)));
41
+ const sx = S ? Math.floor((w - 10) / 2) : Math.max(mx, Math.floor((panelX - mascotW) / 2) - 1);
42
+ const sy = S ? 2 : Math.max(2, Math.floor((h - mascotH) / 2) - 2);
43
+ const rowsY = S ? sy + mascotH + 2 : Math.max(3, Math.floor(h / 2) - (L ? 6 : 5));
44
+ return { L, S, mx, scale, mascotW, mascotH, panelX, sx, sy, rowsY };
45
+ }
46
+
47
+ const INK = [13, 11, 10];
48
+ const TUNED_256 = { '#ff7a1a': 208, '#ffb070': 215, '#fff4e6': 230, '#ffffff': 231, '#c94a0a': 166, '#8a2e05': 94, '#1c0f08': 232 };
49
+ const hexRgb = hex => [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16));
50
+ function to256(hex) {
51
+ if (TUNED_256[hex]) return TUNED_256[hex];
52
+ const cube = [0, 95, 135, 175, 215, 255];
53
+ const near = v => cube.reduce((best, c, i) => (Math.abs(c - v) < Math.abs(cube[best] - v) ? i : best), 0);
54
+ const [r, g, b] = hexRgb(hex);
55
+ return 16 + 36 * near(r) + 6 * near(g) + near(b);
56
+ }
57
+
58
+ /** Cells for the art: Map "x,y" → [topHex|null, bottomHex|null], in cell coordinates. */
59
+ export function splashCells(size, x, y, scale) {
60
+ const art = SPLASH_ART[size];
61
+ const offset = size === 'big' ? 2 : 0;
62
+ const top = size === 'big' ? 2 : 0;
63
+ const cells = new Map();
64
+ art.rows.forEach((row, ry) => {
65
+ for (let rx = 0; rx < row.length; rx++) {
66
+ if (row[rx] === '.') continue;
67
+ const color = art.palette['abcdefghij'.indexOf(row[rx])];
68
+ const px = rx, py = ry + art.top;
69
+ for (let sy = 0; sy < scale; sy++) for (let sx = 0; sx < scale; sx++) {
70
+ const cx = x + (px + offset) * scale + sx;
71
+ const pyy = y * 2 + (py + top) * scale + sy;
72
+ const key = `${cx},${pyy >> 1}`;
73
+ const cell = cells.get(key) ?? [null, null];
74
+ cell[pyy & 1] = color;
75
+ cells.set(key, cell);
76
+ }
77
+ }
78
+ });
79
+ return cells;
80
+ }
81
+
82
+ export function auditSplash(argv = process.argv.slice(2)) {
83
+ const { stdin, stdout, stderr } = process;
84
+ if (argv[0] !== 'audit' || argv.some(arg => PLAIN_FLAGS.has(arg.split('=')[0]))) return;
85
+ if (!stdin.isTTY || !stdout.isTTY || process.env.SLOPIE_NO_SPLASH === '1') return;
86
+ const w = stdout.columns || 80;
87
+ const h = stdout.rows || 24;
88
+ if (w < 60 || h < 20) return;
89
+
90
+ const color = !('NO_COLOR' in process.env) && !argv.includes('--no-color') && process.env.TERM !== 'dumb';
91
+ const truecolor = /^(truecolor|24bit)$/i.test(process.env.COLORTERM ?? '') || /^(iTerm\.app|WezTerm|ghostty|vscode)$/i.test(process.env.TERM_PROGRAM ?? '');
92
+ const deep = color && (truecolor || /256/.test(process.env.TERM ?? '') || process.env.TERM_PROGRAM === 'Apple_Terminal');
93
+ const fg = hex => (!color ? '' : truecolor ? `\x1b[38;2;${hexRgb(hex).join(';')}m` : `\x1b[38;5;${to256(hex)}m`);
94
+ const bgOf = hex => (!deep ? '' : truecolor ? `\x1b[48;2;${hexRgb(hex).join(';')}m` : `\x1b[48;5;${to256(hex)}m`);
95
+ const ink = !deep ? '' : truecolor ? `\x1b[48;2;${INK.join(';')}m` : '\x1b[48;5;233m';
96
+ const reset = `\x1b[0m${ink}`;
97
+
98
+ const g = splashGeometry(w, h);
99
+ let out = `\x1b[?1049h\x1b[?25l${ink}\x1b[H\x1b[2J`;
100
+ const at = (x, y, text) => { out += `\x1b[${y + 1};${x + 1}H${text}${reset}`; };
101
+ at(g.mx, g.L ? 1 : 0, `\x1b[1m${fg('#ff7a1a')}slopie${reset} ${fg('#5c5c5c')}audit`);
102
+ if (deep) {
103
+ const cells = splashCells(g.S ? 'compact' : 'big', g.sx, g.sy, g.S ? 1 : g.scale);
104
+ for (const [key, [top, bottom]] of cells) {
105
+ const [x, y] = key.split(',').map(Number);
106
+ if (top && bottom) at(x, y, top === bottom ? `${fg(top)}█` : `${fg(top)}${bgOf(bottom)}▀`);
107
+ else if (top) at(x, y, `${fg(top)}▀`);
108
+ else if (bottom) at(x, y, `${fg(bottom)}▄`);
109
+ }
110
+ } else {
111
+ at(g.sx + 4, g.sy + Math.floor(g.mascotH / 2), `${fg('#ff7a1a')}(•ᴗ•)`);
112
+ }
113
+ const privacy = 'all local · nothing leaves this machine';
114
+ const py = h - (g.L ? 2 : 1);
115
+ const px = Math.round(w / 2 - (privacy.length + 2) / 2);
116
+ at(px, py, `${fg('#ff7a1a')}●${reset} ${fg('#5c5c5c')}${privacy}`);
117
+ const cy = g.rowsY + (g.L ? 13 : 9);
118
+ at(g.panelX, cy, `${fg('#ffb070')}finding your agents`);
119
+ stdout.write(out + '\x1b[0m');
120
+ try { stdin.setRawMode(true); } catch { /* not a raw-capable tty */ }
121
+
122
+ let active = true;
123
+ const originalOut = stdout.write.bind(stdout);
124
+ const originalErr = stderr.write.bind(stderr);
125
+ const leave = () => {
126
+ if (!active) return;
127
+ active = false;
128
+ stdout.write = originalOut;
129
+ stderr.write = originalErr;
130
+ try { stdin.setRawMode(false); } catch { /* ignore */ }
131
+ originalOut('\x1b[0m\x1b[?25h\x1b[?1049l');
132
+ };
133
+ // Anything other than the story writing first means we must get out of the way.
134
+ stdout.write = (...args) => { leave(); return stdout.write(...args); };
135
+ stderr.write = (...args) => { leave(); return stderr.write(...args); };
136
+ process.once('exit', leave);
137
+
138
+ globalThis.__slopieSplash = {
139
+ handOff() {
140
+ if (!active) return;
141
+ active = false;
142
+ stdout.write = originalOut;
143
+ stderr.write = originalErr;
144
+ process.off('exit', leave);
145
+ },
146
+ };
147
+ }
package/bin/potsherd.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  // The published entry point. `dist/potsherd.js` is a single bundled file so
3
3
  // that `npx potsherd audit` needs no workspace resolution and no build step.
4
+ import {auditSplash} from './audit-splash.js';
5
+ auditSplash();
4
6
  import('../dist/potsherd.js').catch((err) => {
5
7
  console.error('potsherd failed to start:', err?.message ?? err);
6
8
  // NOT `npm i -g potsherd`: the package is unpublished, so that is a 404
package/bin/slopie.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  // Public Slopie launcher; storage and the potsherd launcher remain compatible.
3
3
  process.env['POTSHERD_CLI_PUBLIC_NAME']='slopie';
4
+ import {auditSplash} from './audit-splash.js';
5
+ auditSplash();
4
6
  import('../dist/potsherd.js').catch((err)=>{
5
7
  console.error('slopie failed to start:',err?.message??err);
6
8
  console.error('this usually means the package did not build. try: pnpm install && pnpm build');