omegon 0.7.2 → 0.7.4
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/extensions/00-splash/index.ts +142 -0
- package/extensions/bootstrap/index.ts +16 -18
- package/package.json +1 -1
|
@@ -279,6 +279,112 @@ class BrandedHeader implements Component {
|
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
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
|
+
|
|
282
388
|
// ---------------------------------------------------------------------------
|
|
283
389
|
// Extension entry point
|
|
284
390
|
// ---------------------------------------------------------------------------
|
|
@@ -286,6 +392,42 @@ export default function splashExtension(pi: ExtensionAPI): void {
|
|
|
286
392
|
// Initialise shared state immediately so other extensions can write to it
|
|
287
393
|
getSharedState();
|
|
288
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
|
+
|
|
289
431
|
let version = "0.0.0";
|
|
290
432
|
|
|
291
433
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -470,11 +470,18 @@ function restartOmegon(): never {
|
|
|
470
470
|
' [ "$_w" -ge 50 ] && break',
|
|
471
471
|
"done",
|
|
472
472
|
// Extra grace period for fd/terminal release
|
|
473
|
-
"sleep 0.
|
|
474
|
-
//
|
|
475
|
-
//
|
|
476
|
-
|
|
473
|
+
"sleep 0.3",
|
|
474
|
+
// Hard terminal reset: RIS (Reset to Initial State) clears ALL protocol
|
|
475
|
+
// state — kitty keyboard protocol, bracketed paste, mouse tracking,
|
|
476
|
+
// modifyOtherKeys, SGR, scroll regions, alternate screen, everything.
|
|
477
|
+
// This is what `reset` does internally and has worked since the VT100.
|
|
478
|
+
"printf '\\033c' 2>/dev/null",
|
|
477
479
|
"stty sane 2>/dev/null",
|
|
480
|
+
// `reset` as belt-and-suspenders — reinitializes terminfo state.
|
|
481
|
+
// Some terminals (Kitty) maintain protocol state that RIS alone
|
|
482
|
+
// doesn't fully clear; reset queries terminfo and sends the full
|
|
483
|
+
// initialization sequence for the current TERM.
|
|
484
|
+
"reset 2>/dev/null",
|
|
478
485
|
// Clean up this script
|
|
479
486
|
`rm -f "${script}"`,
|
|
480
487
|
// Replace this shell with new omegon
|
|
@@ -484,26 +491,17 @@ function restartOmegon(): never {
|
|
|
484
491
|
// Reset terminal to cooked mode BEFORE exiting so the restart script
|
|
485
492
|
// (and the user) aren't stuck with raw-mode terminal if something goes wrong.
|
|
486
493
|
try {
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
process.stdout.write(
|
|
491
|
-
"\x1b[<u" + // Pop kitty keyboard protocol flags
|
|
492
|
-
"\x1b[>4;0m" + // Disable modifyOtherKeys
|
|
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
|
|
497
|
-
);
|
|
494
|
+
// RIS (Reset to Initial State) — the only reliable way to ensure ALL
|
|
495
|
+
// terminal protocol state is cleared. Selective escape sequences are
|
|
496
|
+
// fragile and miss features we don't know about.
|
|
497
|
+
process.stdout.write("\x1bc");
|
|
498
498
|
// Pause stdin to prevent buffered input from being re-interpreted
|
|
499
499
|
// after raw mode is disabled (prevents Ctrl+D from closing parent shell).
|
|
500
500
|
process.stdin.pause();
|
|
501
501
|
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
502
502
|
process.stdin.setRawMode(false);
|
|
503
503
|
}
|
|
504
|
-
//
|
|
505
|
-
// Use /dev/null for stdout/stderr to prevent any stray output (including
|
|
506
|
-
// terminal bells) from reaching the user's terminal during the transition.
|
|
504
|
+
// stty sane resets line discipline to known-good state.
|
|
507
505
|
spawnSync("stty", ["sane"], { stdio: ["inherit", "ignore", "ignore"], timeout: 2000 });
|
|
508
506
|
} catch { /* best-effort */ }
|
|
509
507
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omegon",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
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",
|