2026-07-17 15:45:25 +08:00
#!/usr/bin/env node
import {
pickBestSession ,
discoverSessions ,
defaultGrokHome ,
findSessionDirById ,
loadActiveSessions ,
loadSnapshotFromDir ,
2026-07-17 16:27:35 +08:00
pickFromActiveSessions ,
sortSessionsByRecency ,
2026-07-17 15:45:25 +08:00
} from "./session.js" ;
import { getCreditUsage , unavailableUsage } from "./billing.js" ;
import { renderHud , renderJson } from "./render.js" ;
import { loadConfig } from "./config.js" ;
import { writeStatusFiles } from "./status.js" ;
import {
installGlobalHooks ,
uninstallGlobalHooks ,
packageRoot ,
} from "./install.js" ;
import { installDashboard , uninstallDashboard } from "./install-dashboard.js" ;
import {
runDashboardLoop ,
ensureDashboardDaemon ,
stopDashboard ,
isDashboardRunning ,
refreshDashboard ,
} from "./dashboard.js" ;
import {
execGrokInSameWindowTmux ,
applyTmuxStatusBar ,
installSameWindowLauncher ,
isInsideTmux ,
writeTmuxConfFile ,
} from "./tmux-hud.js" ;
import {
persistTheme ,
resolveTheme ,
resolveThemeMode ,
readGrokUiConfig ,
clearAppearanceCache ,
2026-07-17 16:27:35 +08:00
ensureFollowMode ,
themeFingerprint ,
2026-07-17 15:45:25 +08:00
} from "./theme.js" ;
import {
applyPreset ,
saveHudConfig ,
loadHudConfig ,
2026-07-17 16:27:35 +08:00
ensureDefaultConfig ,
2026-07-17 15:45:25 +08:00
type HudPreset ,
} from "./hud-config.js" ;
import type { SessionSnapshot , UsageSnapshot } from "./types.js" ;
import path from "node:path" ;
2026-07-17 16:27:35 +08:00
import fs from "node:fs" ;
2026-07-17 15:45:25 +08:00
import { fileURLToPath } from "node:url" ;
export { contextPercentFromSignals , renderBar , formatTokenCount } from "./bar.js" ;
export { parseUpdatesLines , formatToolLine } from "./activity.js" ;
export { normalizeBillingPayload } from "./billing.js" ;
export {
loadSnapshotFromDir ,
pickBestSession ,
2026-07-17 16:27:35 +08:00
pickFromActiveSessions ,
2026-07-17 15:45:25 +08:00
discoverSessions ,
findSessionDirById ,
2026-07-17 16:27:35 +08:00
sortSessionsByRecency ,
sessionRecencyMs ,
2026-07-17 15:45:25 +08:00
} from "./session.js" ;
export { renderHud , renderJson } from "./render.js" ;
export { runHookTick } from "./hook.js" ;
export { writeStatusFiles , formatCompactLine } from "./status.js" ;
export { installGlobalHooks , uninstallGlobalHooks } from "./install.js" ;
export { installDashboard , uninstallDashboard } from "./install-dashboard.js" ;
export interface CliOptions {
watch : boolean ;
once : boolean ;
tmux : boolean ;
json : boolean ;
all : boolean ;
noColor : boolean ;
cwd ?: string ;
session ?: string ;
sessionDir ?: string ;
grokHome ?: string ;
refreshMs ?: number ;
maxIterations ?: number ;
help : boolean ;
version : boolean ;
noUsage : boolean ;
installHooks : boolean ;
uninstallHooks : boolean ;
tick : boolean ;
followActive : boolean ;
installDashboard : boolean ;
uninstallDashboard : boolean ;
dashboard : boolean ;
dashboardStart : boolean ;
dashboardStop : boolean ;
dashboardWindow : boolean ;
runInTerminal : boolean ;
theme ?: string ;
preset ?: string ;
2026-07-17 16:27:35 +08:00
settings : boolean ;
language ?: string ;
2026-07-17 15:45:25 +08:00
}
export function parseArgs ( argv : string []) : CliOptions {
const opts : CliOptions = {
watch : false ,
once : true ,
tmux : false ,
json : false ,
all : false ,
noColor : false ,
help : false ,
version : false ,
noUsage : false ,
installHooks : false ,
uninstallHooks : false ,
tick : false ,
followActive : false ,
installDashboard : false ,
uninstallDashboard : false ,
dashboard : false ,
dashboardStart : false ,
dashboardStop : false ,
dashboardWindow : false ,
runInTerminal : false ,
2026-07-17 16:27:35 +08:00
settings : false ,
2026-07-17 15:45:25 +08:00
};
for ( let i = 0 ; i < argv . length ; i ++ ) {
const arg = argv [ i ] ! ;
switch ( arg ) {
case "-w" :
case "--watch" :
opts . watch = true ;
opts . once = false ;
break ;
case "--once" :
opts . once = true ;
opts . watch = false ;
break ;
case "--tmux" :
opts . tmux = true ;
break ;
case "--json" :
opts . json = true ;
break ;
case "--all" :
opts . all = true ;
break ;
case "--no-color" :
case "--plain" :
opts . noColor = true ;
break ;
case "--no-usage" :
opts . noUsage = true ;
break ;
case "--cwd" :
opts . cwd = argv [ ++ i ];
break ;
case "--session" :
opts . session = argv [ ++ i ];
break ;
case "--session-dir" :
opts . sessionDir = argv [ ++ i ];
break ;
case "--grok-home" :
opts . grokHome = argv [ ++ i ];
break ;
case "--interval" :
case "--refresh-ms" :
opts . refreshMs = Number ( argv [ ++ i ]);
break ;
case "--max-iterations" :
opts . maxIterations = Number ( argv [ ++ i ]);
break ;
case "--install-hooks" :
case "--install" :
opts . installHooks = true ;
break ;
case "--uninstall-hooks" :
case "--uninstall" :
opts . uninstallHooks = true ;
break ;
case "--tick" :
opts . tick = true ;
opts . noUsage = true ;
break ;
case "--follow-active" :
opts . followActive = true ;
break ;
case "--install-dashboard" :
opts . installDashboard = true ;
break ;
case "--uninstall-dashboard" :
opts . uninstallDashboard = true ;
break ;
case "--dashboard" :
opts . dashboard = true ;
opts . once = false ;
break ;
case "--dashboard-start" :
opts . dashboardStart = true ;
break ;
case "--dashboard-stop" :
opts . dashboardStop = true ;
break ;
case "--dashboard-window" :
case "--open-window" :
// deprecated / rejected by user — ignore, prefer same-window tmux
opts . dashboardWindow = false ;
break ;
case "--run-in-terminal" :
case "--run" :
opts . runInTerminal = true ;
break ;
case "--theme" :
opts . theme = argv [ ++ i ];
break ;
case "--preset" :
opts . preset = argv [ ++ i ];
break ;
2026-07-17 16:27:35 +08:00
case "--settings" :
case "--config-ui" :
case "settings" :
opts . settings = true ;
break ;
case "--lang" :
case "--language" :
opts . language = argv [ ++ i ];
opts . settings = true ;
break ;
2026-07-17 15:45:25 +08:00
case "-h" :
case "--help" :
opts . help = true ;
break ;
case "-V" :
case "--version" :
opts . version = true ;
break ;
default :
if ( arg . startsWith ( "-" )) {
// unknown flag: ignore softly
}
break ;
}
}
return opts ;
}
export function helpText () : string {
return `grok-build-hud — live context + quota in the SAME Terminal window
2026-07-17 16:27:35 +08:00
After install:
1. Open Terminal (shell hook ensures HUD daemon)
2. Type: grok
→ Grok + bottom HUD (IN/OUT/CACHE exact token counts)
bare CLI: GROK_NO_HUD=1 grok -p "hi"
2026-07-17 15:45:25 +08:00
Install once:
grok-build-hud --install-dashboard
2026-07-17 16:27:35 +08:00
Also:
grok-hud status # full dual bars + token breakdown
2026-07-17 15:45:25 +08:00
grok-hud stop # stop background updater
2026-07-17 16:27:35 +08:00
grok-build-hud --theme auto # follow Grok /theme (default, not locked)
grok-build-hud --preset full|essential|minimal
grok-hud settings # 设定界面(语言 中/英、预设、行数)
2026-07-17 16:55:18 +08:00
grok-hud lang en # English (default)
grok-hud lang zh # 简体中文
grok-hud lang tw # 繁體中文
2026-07-17 16:27:35 +08:00
Theme: always tracks Grok [ui].theme (auto → OS light/dark maps).
Change inside Grok with /theme — HUD re-paints in ~1s.
2026-07-17 16:55:18 +08:00
Language: default English; switch in settings or: grok-hud lang zh
2026-07-17 15:45:25 +08:00
2026-07-17 16:27:35 +08:00
Lifecycle: Terminal open → ready (not machine boot).
2026-07-17 16:32:20 +08:00
文档: README.md(中文)· README.en.md( English)
2026-07-17 15:45:25 +08:00
` ;
}
async function resolveUsage (
grokHome : string ,
opts : CliOptions ,
cfg : ReturnType < typeof loadConfig > ,
) : Promise < UsageSnapshot > {
if ( opts . noUsage || ! cfg . showUsage ) {
return unavailableUsage ( "usage disabled" );
}
try {
return await getCreditUsage ( grokHome , {
cacheTtlMs : cfg . usageCacheTtlMs ,
enabled : true ,
});
} catch {
return unavailableUsage ( "usage unavailable" );
}
}
function printFrame (
sessions : SessionSnapshot [],
usage : UsageSnapshot ,
opts : CliOptions ,
cfg : ReturnType < typeof loadConfig > ,
out : ( s : string ) => void ,
) : void {
if ( ! sessions . length ) {
out ( "No Grok Build sessions found under grok home." );
return ;
}
const list = opts . all ? sessions : [ sessions [ 0 ] ! ];
const chunks : string [] = [];
for ( const s of list ) {
if ( opts . json ) {
chunks . push ( renderJson ( s , usage ));
} else {
chunks . push (
renderHud ( s , usage , {
color : ! opts . noColor && ! opts . tmux ,
tmux : opts . tmux ,
pathLevels : cfg . pathLevels ,
warningThreshold : cfg . warningThreshold ,
criticalThreshold : cfg . criticalThreshold ,
}),
);
}
}
out ( chunks . join ( opts . json ? "\n" : "\n\n" ));
}
export async function runCli (
argv : string [],
io : {
stdout ?: ( s : string ) => void ;
stderr ?: ( s : string ) => void ;
sleep ?: ( ms : number ) => Promise < void > ;
} = {},
) : Promise < number > {
const out = io . stdout ?? (( s : string ) => process . stdout . write ( s + ( s . endsWith ( "\n" ) ? "" : "\n" )));
const err = io . stderr ?? (( s : string ) => process . stderr . write ( s + "\n" ));
const sleep =
io . sleep ??
(( ms : number ) => new Promise (( r ) => setTimeout ( r , ms )));
const opts = parseArgs ( argv );
const cfg = loadConfig ();
if ( opts . help ) {
out ( helpText ());
return 0 ;
}
if ( opts . version ) {
out ( "0.2.0" );
return 0 ;
}
2026-07-17 16:27:35 +08:00
if ( opts . settings ) {
const { runSettingsUi } = await import ( "./settings-ui.js" );
const grokHome = opts . grokHome ?? defaultGrokHome ();
const saved = await runSettingsUi ({
grokHome ,
language : opts . language ,
});
if ( saved ) {
writeTmuxConfFile ( grokHome );
applyTmuxStatusBar ({ grokHome });
await refreshDashboard ({ grokHome });
// restart daemon so labels pick up language
stopDashboard ( grokHome );
ensureDashboardDaemon ({
grokHome ,
entryJs : path . join ( packageRoot (), "dist" , "src" , "index.js" ),
intervalMs : 500 ,
});
}
return 0 ;
}
2026-07-17 15:45:25 +08:00
if ( opts . preset ) {
const p = opts . preset . toLowerCase ();
if ( p !== "full" && p !== "essential" && p !== "minimal" ) {
err ( "Preset must be: full | essential | minimal" );
return 1 ;
}
2026-07-17 16:27:35 +08:00
const grokHome = opts . grokHome ?? defaultGrokHome ();
const prev = loadHudConfig ( grokHome );
2026-07-17 15:45:25 +08:00
const cfg = applyPreset ( p as HudPreset );
2026-07-17 16:27:35 +08:00
// keep readability defaults + user language
2026-07-17 15:45:25 +08:00
cfg . bold = true ;
cfg . barWidth = Math . max ( cfg . barWidth ?? 12 , 12 );
2026-07-17 16:55:18 +08:00
cfg . language = prev . language ?? "en" ;
2026-07-17 16:27:35 +08:00
const saved = saveHudConfig ( cfg , grokHome );
2026-07-17 15:45:25 +08:00
writeTmuxConfFile ( opts . grokHome );
applyTmuxStatusBar ({ grokHome : opts . grokHome });
await refreshDashboard ({ grokHome : opts . grokHome });
out (
`HUD preset → ${ p } ( ${ cfg . statusLines } status row(s), bold, barWidth= ${ cfg . barWidth } )\n config: ${ saved } \n` +
2026-07-17 16:27:35 +08:00
`language: ${ cfg . language } \n` +
`presets: full | essential | minimal` ,
2026-07-17 15:45:25 +08:00
);
return 0 ;
}
if ( opts . theme ) {
const mode = resolveThemeMode ( opts . theme );
clearAppearanceCache ();
const grokHome = opts . grokHome ?? defaultGrokHome ();
2026-07-17 16:27:35 +08:00
// Default path: always follow Grok. --theme auto restores follow mode.
// --theme tokyonight only previews once unless GROK_HUD_LOCK=1.
if ( mode === "auto" || mode === "system" ) {
ensureFollowMode ( grokHome );
persistTheme ( "auto" );
delete process . env . GROK_HUD_THEME ;
delete process . env . GROK_HUD_LOCK ;
} else if ( process . env . GROK_HUD_LOCK === "1" ) {
persistTheme ( mode );
process . env . GROK_HUD_THEME = mode ;
} else {
// One-shot preview without locking
persistTheme ( "auto" );
}
2026-07-17 15:45:25 +08:00
const ui = readGrokUiConfig ( grokHome );
2026-07-17 16:27:35 +08:00
const t =
mode === "auto" || mode === "system"
? resolveTheme ( "auto" , process . env , { grokHome })
: resolveTheme ( mode , process . env , { grokHome });
2026-07-17 15:45:25 +08:00
writeTmuxConfFile ( grokHome );
applyTmuxStatusBar ({ grokHome });
2026-07-17 16:27:35 +08:00
// force fingerprint rewrite next tick
try {
fs . writeFileSync (
path . join ( grokHome , "hud" , ".last-theme" ),
themeFingerprint ( t , ui , process . env ) + "\n" ,
);
} catch {
/* ignore */
}
2026-07-17 15:45:25 +08:00
await refreshDashboard ({ grokHome });
stopDashboard ( grokHome );
ensureDashboardDaemon ({
grokHome ,
entryJs : path . join ( packageRoot (), "dist" , "src" , "index.js" ),
intervalMs : 500 ,
});
2026-07-17 16:27:35 +08:00
if ( mode === "auto" || mode === "system" ) {
out (
`HUD follows Grok [ui].theme (not locked).\n` +
` config: theme=" ${ ui . theme } " → palette " ${ t . name } "\n` +
` auto maps: dark→ ${ ui . autoDarkTheme } light→ ${ ui . autoLightTheme } \n` +
`Change with /theme inside Grok; HUD re-paints within ~1s.\n` +
`Optional lock: GROK_HUD_LOCK=1 grok-build-hud --theme tokyonight` ,
);
} else if ( process . env . GROK_HUD_LOCK === "1" ) {
2026-07-17 15:45:25 +08:00
out (
2026-07-17 16:27:35 +08:00
`Theme locked to " ${ t . name } " (GROK_HUD_LOCK=1).\n` +
`Unlock: grok-build-hud --theme auto` ,
2026-07-17 15:45:25 +08:00
);
} else {
out (
2026-07-17 16:27:35 +08:00
`Preview palette " ${ t . name } " once; still following Grok after this.\n` +
`To lock: GROK_HUD_LOCK=1 grok-build-hud --theme ${ mode } \n` +
`Follow mode: grok-build-hud --theme auto` ,
2026-07-17 15:45:25 +08:00
);
}
return 0 ;
}
if ( opts . installDashboard ) {
try {
const r = installDashboard ({
grokHome : opts . grokHome ,
startDaemon : true ,
});
2026-07-17 16:27:35 +08:00
// Always follow Grok [ui].theme — clear any legacy locked palette
const home = opts . grokHome ?? defaultGrokHome ();
ensureFollowMode ( home );
const hudCfg = ensureDefaultConfig ( home );
// wrapGrokCommand already done inside installDashboard; still refresh run shims
const launchers = installSameWindowLauncher ({ wrapGrokCommand : true });
out ( "Installed Grok + HUD (same window, no second window):" );
out ( ` ★ start: grok` );
out ( ` (plain command — auto HUD; no extra flags)` );
out ( ` theme: follows Grok /theme (not locked)` );
out (
2026-07-17 16:55:18 +08:00
` language: ${ hudCfg . language } (settings: grok-hud settings · 中文: grok-hud lang zh)` ,
2026-07-17 16:27:35 +08:00
);
if ( r . grokWrap ) {
out ( ` wrap: ${ r . grokWrap . wrapperPath } ` );
out ( ` real bin: ${ r . grokWrap . realPath } ` );
out ( ` bare escape: GROK_NO_HUD=1 grok …` );
}
out ( ` alias: ${ launchers . runPath } (same as grok)` );
out ( ` hooks: ${ r . hooksPath } ` );
out ( ` tmux cfg: ${ r . tmuxConf } ` );
if ( r . terminalHook ) {
out ( ` terminal: ${ r . terminalHook } ` );
out ( ` (opens Terminal → auto ensure HUD daemon)` );
}
if ( r . launchAgent ) {
out (
` agent: ${ r . launchAgent } (optional, not login-boot; Terminal-driven)` ,
);
}
2026-07-17 15:45:25 +08:00
out (
2026-07-17 16:27:35 +08:00
` daemon: ${ r . daemon . alreadyRunning ? "already running" : r . daemon . started ? `started pid ${ r . daemon . pid } ` : "not started" } ` ,
2026-07-17 15:45:25 +08:00
);
await refreshDashboard ({ grokHome : opts . grokHome });
if ( isInsideTmux ()) {
applyTmuxStatusBar ({ grokHome : opts . grokHome });
out ( "\nAlready inside tmux — status bar applied to THIS window." );
} else {
out (
2026-07-17 16:27:35 +08:00
"\n★ 打开 Terminal 后输入: grok\n 同标签页启动 Grok + 底部 HUD(含 IN/OUT/CACHE 精确数字)。\n 开终端会自动拉起 status 更新器;不是开机自启。" ,
2026-07-17 15:45:25 +08:00
);
}
return 0 ;
} catch ( e ) {
err ( e instanceof Error ? e . message : String ( e ));
return 1 ;
}
}
if ( opts . runInTerminal ) {
ensureDashboardDaemon ({
grokHome : opts . grokHome ,
entryJs : path . join ( packageRoot (), "dist" , "src" , "index.js" ),
intervalMs : 500 ,
});
await refreshDashboard ({ grokHome : opts . grokHome });
// never returns
execGrokInSameWindowTmux ({
grokHome : opts . grokHome ,
extraGrokArgs : [],
});
return 0 ;
}
if ( opts . uninstallDashboard ) {
uninstallDashboard ({ grokHome : opts . grokHome });
uninstallGlobalHooks ( opts . grokHome );
out ( "Dashboard uninstalled (daemon stopped, LaunchAgent removed)." );
return 0 ;
}
if ( opts . dashboardStop ) {
const ok = stopDashboard ( opts . grokHome );
out ( ok ? "Dashboard daemon stopped." : "No dashboard daemon was running." );
return 0 ;
}
if ( opts . dashboardStart ) {
const r = ensureDashboardDaemon ({
grokHome : opts . grokHome ,
entryJs : path . join ( packageRoot (), "dist" , "src" , "index.js" ),
intervalMs : opts . refreshMs ?? 2000 ,
});
if ( r . alreadyRunning ) out ( "Dashboard already running." );
else if ( r . started ) out ( `Dashboard started (pid ${ r . pid } ).` );
else out ( "Failed to start dashboard." );
await refreshDashboard ({ grokHome : opts . grokHome });
return r . started || r . alreadyRunning ? 0 : 1 ;
}
if ( opts . dashboard ) {
return runDashboardLoop ({
grokHome : opts . grokHome ,
intervalMs : opts . refreshMs ?? 2000 ,
maxIterations : opts . maxIterations ,
noUsage : opts . noUsage ,
writePid : true ,
});
}
if ( opts . installHooks ) {
try {
const { hooksPath , hookJs } = installGlobalHooks ({
grokHome : opts . grokHome ,
root : packageRoot (),
});
// also ensure dashboard daemon for title bar
ensureDashboardDaemon ({
grokHome : opts . grokHome ,
entryJs : path . join ( packageRoot (), "dist" , "src" , "index.js" ),
});
out ( `Installed live HUD hooks:\n ${ hooksPath } \n entry: ${ hookJs } ` );
out (
"\nNext: restart this Grok session (or open /hooks and press r to reload).\nThen keep working — after each turn you should see [hud] annotations in scrollback." ,
);
return 0 ;
} catch ( e ) {
err ( e instanceof Error ? e . message : String ( e ));
return 1 ;
}
}
if ( opts . uninstallHooks ) {
const { removed , hooksPath } = uninstallGlobalHooks ( opts . grokHome );
out ( removed ? `Removed ${ hooksPath } ` : `No hooks file at ${ hooksPath } ` );
return 0 ;
}
const grokHome = opts . grokHome ?? defaultGrokHome ();
const discoverOpts = {
grokHome ,
sessionId : opts . session ,
cwd : opts . cwd ,
sessionDir : opts . sessionDir ,
};
const resolveSessions = () : SessionSnapshot [] => {
if ( opts . sessionDir ) {
const one = pickBestSession ( discoverOpts );
return one ? [ one ] : [];
}
if ( opts . followActive || ( ! opts . cwd && ! opts . session )) {
2026-07-17 16:27:35 +08:00
// Prefer newest live active session (not active_sessions insertion order)
2026-07-17 15:45:25 +08:00
const active = loadActiveSessions ( grokHome );
const liveSnaps : SessionSnapshot [] = [];
for ( const a of active ) {
const dir = findSessionDirById ( grokHome , a . session_id );
if ( ! dir ) continue ;
const snap = loadSnapshotFromDir ( dir , { active });
if ( snap ) liveSnaps . push ( snap );
}
if ( liveSnaps . length ) {
2026-07-17 16:27:35 +08:00
const ranked = sortSessionsByRecency ( liveSnaps , active );
2026-07-17 15:45:25 +08:00
if ( opts . cwd ) {
2026-07-17 16:27:35 +08:00
const matched = ranked . filter (( s ) =>
2026-07-17 15:45:25 +08:00
s . cwd ? s . cwd === opts . cwd || s . cwd . includes ( opts . cwd ! ) : false ,
);
// use path equality via discover as fallback
const viaCwd = discoverSessions ({ ... discoverOpts , cwd : opts . cwd });
if ( viaCwd . length ) return viaCwd ;
if ( matched . length ) return matched ;
}
if ( opts . session ) {
2026-07-17 16:27:35 +08:00
const m = ranked . filter (( s ) => s . sessionId === opts . session );
2026-07-17 15:45:25 +08:00
if ( m . length ) return m ;
}
2026-07-17 16:27:35 +08:00
if ( ! opts . cwd && ! opts . session ) return ranked ;
2026-07-17 15:45:25 +08:00
}
2026-07-17 16:27:35 +08:00
// Single-shot helper for tests / callers
const one = pickFromActiveSessions ( grokHome );
if ( one && ! opts . cwd && ! opts . session ) return [ one ];
2026-07-17 15:45:25 +08:00
}
return discoverSessions ( discoverOpts );
};
const renderOnce = async () : Promise < number > => {
const sessions = resolveSessions ();
const usage = await resolveUsage ( grokHome , opts , cfg );
if ( ! sessions . length ) {
err ( "grok-build-hud: no sessions found" );
printFrame ([], usage , opts , cfg , out );
return 1 ;
}
// Always refresh status files so external readers stay current
try {
writeStatusFiles ( sessions [ 0 ] ! , opts . noUsage ? null : usage , grokHome );
} catch {
/* ignore */
}
if ( opts . tick ) {
out ( writeStatusFiles ( sessions [ 0 ] ! , null , grokHome ). compact );
return 0 ;
}
printFrame ( sessions , usage , opts , cfg , out );
return 0 ;
};
if ( opts . watch ) {
const interval = opts . refreshMs && opts . refreshMs > 0 ? opts . refreshMs : cfg . refreshMs ;
let i = 0 ;
// eslint-disable-next-line no-constant-condition
while ( true ) {
i += 1 ;
if ( ! opts . tmux && ! opts . json && process . stdout . isTTY ) {
// clear screen lightly for watch
process . stdout . write ( "\x1b[2J\x1b[H" );
}
await renderOnce ();
if ( opts . maxIterations && i >= opts . maxIterations ) {
return 0 ;
}
await sleep ( interval );
}
}
return renderOnce ();
}
// Direct `node dist/src/index.js ...` entry. When imported by tests, argv[1]
// is the test file path, so this block does not run.
function isDirectCliRun () : boolean {
const entry = process . argv [ 1 ];
if ( ! entry ) return false ;
try {
return path . resolve ( entry ) === fileURLToPath ( import . meta . url );
} catch {
return false ;
}
}
if ( isDirectCliRun ()) {
runCli ( process . argv . slice ( 2 ))
. then (( code ) => {
process . exit ( typeof code === "number" ? code : 0 );
})
. catch (( e ) => {
console . error ( e );
process . exit ( 1 );
});
}