@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
83 lines (82 loc) • 3.18 kB
JavaScript
/**
* The phren splash: mascot + wordmark text effect, shared by every terminal
* host (the interactive shell, phren-agent, anything embedding phren).
*
* The shell drives it from its own intro policy (once per version, always,
* off); other hosts call `playSplash` directly. Everything is injectable so
* the sequence can be tested without a terminal or real time.
*/
import { createPhrenAnimator } from "../phren-art.js";
import { LOGO_REVEAL_FRAMES, LOGO_REVEAL_FRAME_MS, LOGO_SHIMMER_FRAME_MS, logoRevealFrame, logoShimmerFrame, } from "./logo-fx.js";
import { composeStartupFrame, enterFullscreen, exitFullscreen, fitFrame, paintFrame, shellStartupFrames, style, } from "./render.js";
const NARROW_STAGE_MS = 160;
function realSleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Play the splash and return when it is dismissed. Does nothing when stdout
* is not a terminal.
*/
export async function playSplash(opts) {
const isTTY = opts.isTTY ?? Boolean(process.stdout.isTTY);
if (!isTTY)
return;
const paint = opts.paint ?? paintFrame;
const sleep = opts.sleep ?? realSleep;
const screen = opts.screen ?? { enter: enterFullscreen, exit: exitFullscreen };
const hint = opts.hint ? style.dim(opts.hint) : undefined;
const dwellMs = opts.dwellMs ?? 0;
if (opts.fullscreen)
screen.enter();
const animator = createPhrenAnimator({ facing: "right" });
animator.start();
let shimmerTick = 0;
const frame = (logo, withHint = true) => {
paint(fitFrame(composeStartupFrame(animator.getFrame(), opts.version, withHint ? hint : undefined, logo ?? logoShimmerFrame(shimmerTick++), opts.tagline)));
};
try {
if (opts.reveal) {
// Terminals too narrow for the wordmark get the staged text reveal instead.
const stages = shellStartupFrames(opts.version, opts.tagline);
if (stages.length > 1) {
for (const stage of stages.slice(0, -1)) {
paint(stage);
await sleep(NARROW_STAGE_MS);
}
}
else {
for (let i = 0; i < LOGO_REVEAL_FRAMES; i++) {
frame(logoRevealFrame(i / (LOGO_REVEAL_FRAMES - 1)), false);
await sleep(LOGO_REVEAL_FRAME_MS);
}
}
}
if (opts.waitForKeypress) {
const interval = setInterval(() => frame(), LOGO_SHIMMER_FRAME_MS);
frame();
try {
await opts.waitForKeypress();
}
finally {
clearInterval(interval);
}
}
else if (dwellMs > 0) {
const start = Date.now();
let elapsed = 0;
do {
frame();
await sleep(LOGO_SHIMMER_FRAME_MS);
elapsed = opts.sleep ? elapsed + LOGO_SHIMMER_FRAME_MS : Date.now() - start;
} while (elapsed < dwellMs);
}
else {
frame();
}
}
finally {
animator.stop();
if (opts.fullscreen)
screen.exit();
}
}