omegon 0.7.1 → 0.7.3
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.
|
@@ -14,7 +14,11 @@ import type { Component, TUI } from "@styrene-lab/pi-tui";
|
|
|
14
14
|
import { truncateToWidth } from "@styrene-lab/pi-tui";
|
|
15
15
|
import {
|
|
16
16
|
LOGO_LINES,
|
|
17
|
+
WORDMARK_LINES,
|
|
17
18
|
LINE_WIDTH,
|
|
19
|
+
COMPACT_LOGO_LINES,
|
|
20
|
+
COMPACT_LINE_WIDTH,
|
|
21
|
+
COMPACT_MARK_ROWS,
|
|
18
22
|
FRAME_INTERVAL_MS,
|
|
19
23
|
TOTAL_FRAMES,
|
|
20
24
|
HOLD_FRAMES,
|
|
@@ -95,8 +99,9 @@ const PENDING_GLYPH = "· ";
|
|
|
95
99
|
// ---------------------------------------------------------------------------
|
|
96
100
|
class SplashHeader implements Component {
|
|
97
101
|
private tui: TUI;
|
|
102
|
+
private lines: string[];
|
|
98
103
|
private frame = 0;
|
|
99
|
-
private frameMap
|
|
104
|
+
private frameMap: ReturnType<typeof assignUnlockFrames>;
|
|
100
105
|
private noiseSeed = (Date.now() * 7) & 0x7fffffff;
|
|
101
106
|
private timer: ReturnType<typeof setInterval> | null = null;
|
|
102
107
|
private scanFrame = 0;
|
|
@@ -107,9 +112,16 @@ class SplashHeader implements Component {
|
|
|
107
112
|
private cachedLines: string[] | undefined;
|
|
108
113
|
private cachedWidth: number | undefined;
|
|
109
114
|
|
|
110
|
-
|
|
115
|
+
private markRows: number;
|
|
116
|
+
private logoWidth: number;
|
|
117
|
+
|
|
118
|
+
constructor(tui: TUI, onTransition: () => void, lines: string[], markRows: number, logoWidth: number) {
|
|
111
119
|
this.tui = tui;
|
|
112
120
|
this.onTransition = onTransition;
|
|
121
|
+
this.lines = lines;
|
|
122
|
+
this.markRows = markRows;
|
|
123
|
+
this.logoWidth = logoWidth;
|
|
124
|
+
this.frameMap = assignUnlockFrames(lines, TOTAL_FRAMES, Date.now() & 0xffff);
|
|
113
125
|
}
|
|
114
126
|
|
|
115
127
|
start(): void {
|
|
@@ -147,14 +159,14 @@ class SplashHeader implements Component {
|
|
|
147
159
|
const lines: string[] = [];
|
|
148
160
|
|
|
149
161
|
// Centre the logo horizontally
|
|
150
|
-
const logoW =
|
|
162
|
+
const logoW = this.logoWidth;
|
|
151
163
|
const pad = Math.max(0, Math.floor((width - logoW) / 2));
|
|
152
164
|
const padStr = " ".repeat(pad);
|
|
153
165
|
|
|
154
166
|
// Render logo frame
|
|
155
167
|
const logoFrame = this.transitioned
|
|
156
|
-
? renderFrame(TOTAL_FRAMES + 1,
|
|
157
|
-
: renderFrame(Math.min(this.frame, TOTAL_FRAMES),
|
|
168
|
+
? renderFrame(TOTAL_FRAMES + 1, this.lines, this.frameMap, this.noiseSeed, this.markRows)
|
|
169
|
+
: renderFrame(Math.min(this.frame, TOTAL_FRAMES), this.lines, this.frameMap, this.noiseSeed, this.markRows);
|
|
158
170
|
|
|
159
171
|
lines.push(""); // top spacer
|
|
160
172
|
for (const row of logoFrame) {
|
|
@@ -267,6 +279,112 @@ class BrandedHeader implements Component {
|
|
|
267
279
|
}
|
|
268
280
|
}
|
|
269
281
|
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Extension entry point
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
// Fullscreen splash replay component (easter egg)
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
class SplashReplay implements Component {
|
|
289
|
+
private tui: TUI;
|
|
290
|
+
private lines: string[];
|
|
291
|
+
private frame = 0;
|
|
292
|
+
private frameMap: ReturnType<typeof assignUnlockFrames>;
|
|
293
|
+
private noiseSeed = (Date.now() * 7) & 0x7fffffff;
|
|
294
|
+
private timer: ReturnType<typeof setInterval> | null = null;
|
|
295
|
+
private holdCount = 0;
|
|
296
|
+
private done: () => void;
|
|
297
|
+
private markRows: number;
|
|
298
|
+
private logoWidth: number;
|
|
299
|
+
private cachedLines: string[] | undefined;
|
|
300
|
+
private cachedWidth: number | undefined;
|
|
301
|
+
|
|
302
|
+
constructor(tui: TUI, done: () => void, lines: string[], markRows: number, logoWidth: number) {
|
|
303
|
+
this.tui = tui;
|
|
304
|
+
this.done = done;
|
|
305
|
+
this.lines = lines;
|
|
306
|
+
this.markRows = markRows;
|
|
307
|
+
this.logoWidth = logoWidth;
|
|
308
|
+
this.frameMap = assignUnlockFrames(lines, TOTAL_FRAMES, Date.now() & 0xffff);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
start(): void {
|
|
312
|
+
this.timer = setInterval(() => this.tick(), FRAME_INTERVAL_MS);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private tick(): void {
|
|
316
|
+
this.frame++;
|
|
317
|
+
this.cachedLines = undefined;
|
|
318
|
+
|
|
319
|
+
if (this.frame >= TOTAL_FRAMES) {
|
|
320
|
+
this.holdCount++;
|
|
321
|
+
if (this.holdCount >= HOLD_FRAMES + 12) {
|
|
322
|
+
this.dispose();
|
|
323
|
+
this.done();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
this.tui.requestRender();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
render(width: number): string[] {
|
|
332
|
+
if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
|
|
333
|
+
|
|
334
|
+
const height = process.stdout.rows ?? 24;
|
|
335
|
+
const lines: string[] = [];
|
|
336
|
+
|
|
337
|
+
const logoFrame = renderFrame(
|
|
338
|
+
Math.min(this.frame, TOTAL_FRAMES),
|
|
339
|
+
this.lines,
|
|
340
|
+
this.frameMap,
|
|
341
|
+
this.noiseSeed,
|
|
342
|
+
this.markRows,
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// Vertically centre
|
|
346
|
+
const topPad = Math.max(0, Math.floor((height - logoFrame.length) / 2));
|
|
347
|
+
for (let i = 0; i < topPad; i++) lines.push("");
|
|
348
|
+
|
|
349
|
+
// Horizontally centre
|
|
350
|
+
const pad = Math.max(0, Math.floor((width - this.logoWidth) / 2));
|
|
351
|
+
const padStr = " ".repeat(pad);
|
|
352
|
+
for (const row of logoFrame) {
|
|
353
|
+
lines.push(truncateToWidth(padStr + row, width));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Fill remaining
|
|
357
|
+
const remaining = height - lines.length;
|
|
358
|
+
for (let i = 0; i < remaining; i++) lines.push("");
|
|
359
|
+
|
|
360
|
+
this.cachedLines = lines;
|
|
361
|
+
this.cachedWidth = width;
|
|
362
|
+
return lines;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
handleInput(input: string): boolean {
|
|
366
|
+
// Any key dismisses early
|
|
367
|
+
if (input) {
|
|
368
|
+
this.dispose();
|
|
369
|
+
this.done();
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
invalidate(): void {
|
|
376
|
+
this.cachedLines = undefined;
|
|
377
|
+
this.cachedWidth = undefined;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
dispose(): void {
|
|
381
|
+
if (this.timer) {
|
|
382
|
+
clearInterval(this.timer);
|
|
383
|
+
this.timer = null;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
270
388
|
// ---------------------------------------------------------------------------
|
|
271
389
|
// Extension entry point
|
|
272
390
|
// ---------------------------------------------------------------------------
|
|
@@ -274,6 +392,42 @@ export default function splashExtension(pi: ExtensionAPI): void {
|
|
|
274
392
|
// Initialise shared state immediately so other extensions can write to it
|
|
275
393
|
getSharedState();
|
|
276
394
|
|
|
395
|
+
// Easter egg: /splash replays the animation fullscreen
|
|
396
|
+
pi.registerCommand("splash", {
|
|
397
|
+
description: "Replay the Omegon splash animation",
|
|
398
|
+
handler: async (_args, ctx) => {
|
|
399
|
+
if (!ctx.hasUI) return;
|
|
400
|
+
const termWidth = process.stdout.columns ?? 80;
|
|
401
|
+
const termRows = process.stdout.rows ?? 24;
|
|
402
|
+
|
|
403
|
+
// Pick the best art that fits
|
|
404
|
+
let artLines: string[];
|
|
405
|
+
let markRows: number;
|
|
406
|
+
let logoWidth: number;
|
|
407
|
+
const canFitFull = termWidth >= LINE_WIDTH + 4 && termRows >= LOGO_LINES.length + 4;
|
|
408
|
+
const canFitCompact = termWidth >= COMPACT_LINE_WIDTH + 4 && termRows >= COMPACT_LOGO_LINES.length + 4;
|
|
409
|
+
if (canFitFull) {
|
|
410
|
+
artLines = LOGO_LINES;
|
|
411
|
+
markRows = 31;
|
|
412
|
+
logoWidth = LINE_WIDTH;
|
|
413
|
+
} else if (canFitCompact) {
|
|
414
|
+
artLines = COMPACT_LOGO_LINES;
|
|
415
|
+
markRows = COMPACT_MARK_ROWS;
|
|
416
|
+
logoWidth = COMPACT_LINE_WIDTH;
|
|
417
|
+
} else {
|
|
418
|
+
artLines = WORDMARK_LINES;
|
|
419
|
+
markRows = 0;
|
|
420
|
+
logoWidth = LINE_WIDTH;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
await ctx.ui.custom<void>((tui, _theme, _kb, done) => {
|
|
424
|
+
const replay = new SplashReplay(tui, () => done(undefined), artLines, markRows, logoWidth);
|
|
425
|
+
replay.start();
|
|
426
|
+
return replay;
|
|
427
|
+
});
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
|
|
277
431
|
let version = "0.0.0";
|
|
278
432
|
|
|
279
433
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -295,15 +449,42 @@ export default function splashExtension(pi: ExtensionAPI): void {
|
|
|
295
449
|
// after a version update), it renders as a one-liner below the splash.
|
|
296
450
|
// This is acceptable — it only appears once per update.
|
|
297
451
|
const termWidth = process.stdout.columns ?? 80;
|
|
298
|
-
|
|
299
|
-
|
|
452
|
+
const termRows = process.stdout.rows ?? 24;
|
|
453
|
+
|
|
454
|
+
// Four tiers based on terminal size:
|
|
455
|
+
// Full (sigil + wordmark): needs ~46 rows and LINE_WIDTH+4 cols (~84 cols)
|
|
456
|
+
// Compact (smaller sigil + wordmark): needs ~34 rows and COMPACT_LINE_WIDTH+4 cols (~58 cols)
|
|
457
|
+
// Wordmark only: needs ~14 rows and LINE_WIDTH+4 cols
|
|
458
|
+
// Minimal (no animation): everything else
|
|
459
|
+
const canFitFull = termWidth >= LINE_WIDTH + 4 && termRows >= LOGO_LINES.length + 6;
|
|
460
|
+
const canFitCompact = termWidth >= COMPACT_LINE_WIDTH + 4 && termRows >= COMPACT_LOGO_LINES.length + 6;
|
|
461
|
+
const canFitWordmark = termWidth >= LINE_WIDTH + 4 && termRows >= WORDMARK_LINES.length + 6;
|
|
462
|
+
|
|
463
|
+
if (!canFitCompact && !canFitWordmark) {
|
|
464
|
+
// Too small for any animation — minimal branded header
|
|
300
465
|
ctx.ui.setHeader(() => new BrandedHeader(version));
|
|
301
466
|
} else {
|
|
467
|
+
let artLines: string[];
|
|
468
|
+
let markRows: number;
|
|
469
|
+
let logoWidth: number;
|
|
470
|
+
if (canFitFull) {
|
|
471
|
+
artLines = LOGO_LINES;
|
|
472
|
+
markRows = 31; // MARK_ROWS
|
|
473
|
+
logoWidth = LINE_WIDTH;
|
|
474
|
+
} else if (canFitCompact) {
|
|
475
|
+
artLines = COMPACT_LOGO_LINES;
|
|
476
|
+
markRows = COMPACT_MARK_ROWS;
|
|
477
|
+
logoWidth = COMPACT_LINE_WIDTH;
|
|
478
|
+
} else {
|
|
479
|
+
artLines = WORDMARK_LINES;
|
|
480
|
+
markRows = 0; // all wordmark
|
|
481
|
+
logoWidth = LINE_WIDTH;
|
|
482
|
+
}
|
|
302
483
|
ctx.ui.setHeader((tui, _theme) => {
|
|
303
484
|
const splash = new SplashHeader(tui, () => {
|
|
304
485
|
// Transition to minimal branded header
|
|
305
486
|
ctx.ui.setHeader((_, _t) => new BrandedHeader(version));
|
|
306
|
-
});
|
|
487
|
+
}, artLines, markRows, logoWidth);
|
|
307
488
|
splash.start();
|
|
308
489
|
return splash;
|
|
309
490
|
});
|
|
@@ -139,6 +139,7 @@ export function renderFrame(
|
|
|
139
139
|
lines: string[],
|
|
140
140
|
frameMap: FrameMap,
|
|
141
141
|
noiseSeed: number,
|
|
142
|
+
markRows: number = MARK_ROWS,
|
|
142
143
|
): string[] {
|
|
143
144
|
const rng = new SimpleRNG(noiseSeed + frame * 997);
|
|
144
145
|
const output: string[] = [];
|
|
@@ -162,7 +163,7 @@ export function renderFrame(
|
|
|
162
163
|
buf += " ";
|
|
163
164
|
} else if (frame >= unlock) {
|
|
164
165
|
// Resolved — final glyph
|
|
165
|
-
const color = y >=
|
|
166
|
+
const color = y >= markRows + 1 ? `${BOLD}${BRIGHT}` : PRIMARY;
|
|
166
167
|
if (color !== lastColor) { buf += color; lastColor = color; }
|
|
167
168
|
buf += ch;
|
|
168
169
|
} else {
|
|
@@ -190,5 +191,56 @@ export function renderFrame(
|
|
|
190
191
|
// ---------------------------------------------------------------------------
|
|
191
192
|
// Pre-computed data for the default logo
|
|
192
193
|
// ---------------------------------------------------------------------------
|
|
193
|
-
|
|
194
|
+
/** Wordmark-only lines (spacer + 7 wordmark rows) for compact terminals. */
|
|
195
|
+
const WORDMARK_LINES: string[] = LOGO_LINES.slice(MARK_ROWS + 1); // skip sigil + first spacer, keep second spacer + wordmark
|
|
196
|
+
// Pad to same width
|
|
197
|
+
for (let i = 0; i < WORDMARK_LINES.length; i++) {
|
|
198
|
+
WORDMARK_LINES[i] = WORDMARK_LINES[i].padEnd(LINE_WIDTH);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Compact logo — sigil + wordmark for mid-size terminals (~56 cols)
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
const COMPACT_MARK_ROWS = 23;
|
|
205
|
+
|
|
206
|
+
const COMPACT_LOGO_LINES: string[] = [
|
|
207
|
+
" * ``` #` ",
|
|
208
|
+
" ` ```##` ``````##` .#` ",
|
|
209
|
+
"````##`######### `############`##` ",
|
|
210
|
+
"*`*############## `##################` ",
|
|
211
|
+
"##:````*` `####` `#########` *#######:## ",
|
|
212
|
+
"` ##### ``####### ######`#` ",
|
|
213
|
+
" `##### #######. #########` ",
|
|
214
|
+
" ####`` ``*@@@@@@@@@@`* `## #### ",
|
|
215
|
+
" ##### `@@@@@@@@@@@@@@@@@@@ `#@ :` ",
|
|
216
|
+
" #####`@@@@@@@@@@@@@@@@@@@@@@@@` `#` ",
|
|
217
|
+
" ##*@@@@@@@@@@@@@@@@@@@@@@@@@@@` ",
|
|
218
|
+
" :@@@@@@@@@@@``##```@@@@@@@@@@@`` ",
|
|
219
|
+
" @@@@@@@@*#:` `#######`@@@@@@@@` ` ` ",
|
|
220
|
+
" @@@@@@@#####` `########`@@@@@@@`####`#` ",
|
|
221
|
+
" @@@@@@ ###### `#`#####`@@@@@@########` ",
|
|
222
|
+
" @@@@@ ###### `::``#*@@@@@`##` #### ",
|
|
223
|
+
" `@@@@@####### `@@@@@`###` `*## ",
|
|
224
|
+
" ``#` .@@@@`##### `@@@@@` ``###` `**",
|
|
225
|
+
" ``:######```@@@@@#` `.@@@@. `#.##` ",
|
|
226
|
+
" ######`####`* `@@@@@@ ``@@@@@` ``#####` ",
|
|
227
|
+
" #* .@@@@@@@@@@@@@@ :@@@@@@@@@@@@@@## ",
|
|
228
|
+
" ` .@@@@@@@@@@@@@@ :@@@@@@@@@@@@@@` ",
|
|
229
|
+
" .@ ` ` ",
|
|
230
|
+
// spacer
|
|
231
|
+
" ",
|
|
232
|
+
// wordmark (4 rows)
|
|
233
|
+
" @@@@@@@ @@@` `@@@ @@@@@@``@@@@@@ `@@@@@@@`@@@` @@ ",
|
|
234
|
+
" @@ @@ @@@@`@@@@ @@```` `@@` `@@ @@ @@@@ @@ ",
|
|
235
|
+
" @@ @@ @@ @*@`@@ @@@@` `@@`@@@ `@@ @@ @@ *@@@ ",
|
|
236
|
+
" @@@@@@@ @@ `@``@@ @@@@@@``@@@@@@ `@@@@@@@`@@ `@@ ",
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
const COMPACT_LINE_WIDTH = Math.max(...COMPACT_LOGO_LINES.map(l => l.length));
|
|
240
|
+
for (let i = 0; i < COMPACT_LOGO_LINES.length; i++) {
|
|
241
|
+
COMPACT_LOGO_LINES[i] = COMPACT_LOGO_LINES[i].padEnd(COMPACT_LINE_WIDTH);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export { LOGO_LINES, WORDMARK_LINES, LINE_WIDTH, MARK_ROWS };
|
|
245
|
+
export { COMPACT_LOGO_LINES, COMPACT_LINE_WIDTH, COMPACT_MARK_ROWS };
|
|
194
246
|
export { PRIMARY, PRIMARY_DIM, DIM, BRIGHT, SUCCESS, ERROR_CLR, RESET, BOLD };
|
|
@@ -471,9 +471,9 @@ function restartOmegon(): never {
|
|
|
471
471
|
"done",
|
|
472
472
|
// Extra grace period for fd/terminal release
|
|
473
473
|
"sleep 0.2",
|
|
474
|
-
//
|
|
475
|
-
//
|
|
476
|
-
"printf '\\033[<u\\033[>4;0m\\033[?2004l' 2>/dev/null",
|
|
474
|
+
// Full terminal protocol reset — stty sane only resets line discipline,
|
|
475
|
+
// not terminal protocol state (kitty keyboard, bracketed paste, cursor, SGR)
|
|
476
|
+
"printf '\\033[<u\\033[>4;0m\\033[?2004l\\033[?25h\\033[0m\\033[r' 2>/dev/null",
|
|
477
477
|
"stty sane 2>/dev/null",
|
|
478
478
|
// Clean up this script
|
|
479
479
|
`rm -f "${script}"`,
|
|
@@ -484,15 +484,20 @@ function restartOmegon(): never {
|
|
|
484
484
|
// Reset terminal to cooked mode BEFORE exiting so the restart script
|
|
485
485
|
// (and the user) aren't stuck with raw-mode terminal if something goes wrong.
|
|
486
486
|
try {
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
// script (and any keystrokes during the wait) dump raw kitty sequences.
|
|
487
|
+
// Full terminal protocol teardown: pop kitty keyboard protocol,
|
|
488
|
+
// disable modifyOtherKeys, disable bracketed paste, show cursor,
|
|
489
|
+
// reset SGR attributes, and clear any pending scroll region.
|
|
491
490
|
process.stdout.write(
|
|
492
491
|
"\x1b[<u" + // Pop kitty keyboard protocol flags
|
|
493
492
|
"\x1b[>4;0m" + // Disable modifyOtherKeys
|
|
494
|
-
"\x1b[?2004l"
|
|
493
|
+
"\x1b[?2004l" + // Disable bracketed paste
|
|
494
|
+
"\x1b[?25h" + // Show cursor
|
|
495
|
+
"\x1b[0m" + // Reset all SGR attributes
|
|
496
|
+
"\x1b[r" // Reset scroll region to full screen
|
|
495
497
|
);
|
|
498
|
+
// Pause stdin to prevent buffered input from being re-interpreted
|
|
499
|
+
// after raw mode is disabled (prevents Ctrl+D from closing parent shell).
|
|
500
|
+
process.stdin.pause();
|
|
496
501
|
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
497
502
|
process.stdin.setRawMode(false);
|
|
498
503
|
}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omegon",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "Omegon — an opinionated distribution of pi (by Mario Zechner) with extensions for lifecycle management, memory, orchestration, and visualization",
|
|
5
5
|
"bin": {
|
|
6
6
|
"omegon": "bin/omegon.mjs",
|