@modelstatus/cli 0.1.47 → 0.1.49
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/package.json +1 -1
- package/src/tui/app.js +37 -5
- package/src/tui/game/launch.js +5 -6
- package/src/tui/game/loop.js +2 -2
- package/src/tui/game/term.js +13 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelstatus/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.49",
|
|
4
4
|
"description": "Track which AI models you use, where, and never get surprised by a retirement. Free offline model-health for any repo (mm status), browser sign-in for cloud inventory + alerts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm",
|
package/src/tui/app.js
CHANGED
|
@@ -69,11 +69,17 @@ function useTermDims() {
|
|
|
69
69
|
const cols = Number.isFinite(fw) && fw > 0 ? fw : dims.cols;
|
|
70
70
|
const rows = Number.isFinite(fh) && fh > 0 ? fh : dims.rows;
|
|
71
71
|
// Never exceed the real terminal width (a wider frame soft-wraps every chrome
|
|
72
|
-
// line and shatters the window).
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
|
|
72
|
+
// line and shatters the window).
|
|
73
|
+
//
|
|
74
|
+
// Height: RESERVE THE LAST ROW (rows - 1). A frame that fills the full height
|
|
75
|
+
// makes ink write+newline the bottom line, which scrolls the buffer up by 1
|
|
76
|
+
// EVERY render; across the handful of renders during load/settle that scroll
|
|
77
|
+
// accumulates and creeps the window's top off-screen (the "not full-screen after
|
|
78
|
+
// the game / in Warp" clip). One row short keeps the last line blank so nothing
|
|
79
|
+
// ever scrolls. This is safe now that the TUI lives in the ALT SCREEN: ink's
|
|
80
|
+
// cursor-up diffing (which it uses when the frame is shorter than the terminal)
|
|
81
|
+
// stays inside the fixed alt buffer and can't drift the way it did inline.
|
|
82
|
+
return { outer: Math.min(cols || 80, 220), termRows: Math.max(8, Math.min((rows || 24) - 1, 200)) };
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
function PromptRow({ prompt }) {
|
|
@@ -269,12 +275,37 @@ export const appController = {
|
|
|
269
275
|
remount(next = {}) {
|
|
270
276
|
const opts = { ...(this._opts || {}), ...next };
|
|
271
277
|
this._opts = opts;
|
|
278
|
+
// We're still in the alt screen (entered by runApp; the game kept it). Clear +
|
|
279
|
+
// home so the fresh Ink tree paints the whole screen from the top with no
|
|
280
|
+
// leftover game frame and no inline-cursor drift.
|
|
281
|
+
try { process.stdout.write("\x1b[2J\x1b[H"); } catch { /* ignore */ }
|
|
272
282
|
this._instance = render(h(Bootstrap, opts));
|
|
273
283
|
return this._instance;
|
|
274
284
|
},
|
|
275
285
|
};
|
|
276
286
|
|
|
277
287
|
export function runApp(opts) {
|
|
288
|
+
// Run the TUI in the ALTERNATE SCREEN BUFFER (a clean full-screen surface, like
|
|
289
|
+
// the game). Ink's default INLINE rendering drifts after the game's alt-screen
|
|
290
|
+
// round-trip in block-model terminals (Warp) — the window scrolls off the top
|
|
291
|
+
// and Ctrl-L can't recover it. Owning the alt screen ourselves makes every
|
|
292
|
+
// (re)mount paint a clean full screen, and leaving it on quit restores the
|
|
293
|
+
// host's scrollback. The in-TUI game keeps this alt screen (keepAlt) instead of
|
|
294
|
+
// toggling its own, so there's no unreliable nested-alt-screen handling.
|
|
295
|
+
const out = process.stdout;
|
|
296
|
+
const leaveAlt = () => {
|
|
297
|
+
if (!appController._inAlt) return;
|
|
298
|
+
try { out.write("\x1b[?1049l\x1b[?25h"); } catch { /* ignore */ }
|
|
299
|
+
appController._inAlt = false;
|
|
300
|
+
};
|
|
301
|
+
appController._leaveAlt = leaveAlt;
|
|
302
|
+
// Never strand the terminal in the alt buffer on a crash/signal.
|
|
303
|
+
process.once("exit", leaveAlt);
|
|
304
|
+
process.once("SIGINT", () => { leaveAlt(); process.exit(130); });
|
|
305
|
+
process.once("SIGTERM", () => { leaveAlt(); process.exit(143); });
|
|
306
|
+
try { out.write("\x1b[?1049h\x1b[2J\x1b[H"); } catch { /* ignore */ }
|
|
307
|
+
appController._inAlt = true;
|
|
308
|
+
|
|
278
309
|
appController._opts = opts;
|
|
279
310
|
const app = render(h(Bootstrap, opts));
|
|
280
311
|
appController._instance = app;
|
|
@@ -289,6 +320,7 @@ export function runApp(opts) {
|
|
|
289
320
|
if (appController._instance && appController._instance !== inst) {
|
|
290
321
|
arm(appController._instance);
|
|
291
322
|
} else {
|
|
323
|
+
leaveAlt(); // real quit → restore the host screen + scrollback
|
|
292
324
|
resolve();
|
|
293
325
|
}
|
|
294
326
|
});
|
package/src/tui/game/launch.js
CHANGED
|
@@ -67,6 +67,7 @@ export async function playGameInTui({ dir, width, height, initialView = "scan",
|
|
|
67
67
|
height: process.stdout.rows || height,
|
|
68
68
|
level: 1,
|
|
69
69
|
scanStore: handle,
|
|
70
|
+
inTui: true, // render in the TUI's alt screen; don't toggle a nested one
|
|
70
71
|
});
|
|
71
72
|
} finally {
|
|
72
73
|
try { handle && handle.abort(); } catch { /* ignore */ }
|
|
@@ -80,12 +81,10 @@ export async function playGameInTui({ dir, width, height, initialView = "scan",
|
|
|
80
81
|
// TUI comes back shifted down / not full-height until the next resize. Reset
|
|
81
82
|
// the scroll region (\x1b[r), clear the screen, and home the cursor so the
|
|
82
83
|
// remounted tree fills the whole terminal from the top (same as Ctrl-L).
|
|
83
|
-
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
// few beats post-mount as a backstop.
|
|
88
|
-
await new Promise((r) => setTimeout(r, 80));
|
|
84
|
+
// The game kept the TUI's alt screen intact (inTui → keepAlt), so we never
|
|
85
|
+
// left it — just let the game's restore() flush, then remount. remount() clears
|
|
86
|
+
// + homes inside the alt screen so the fresh tree paints a clean full screen.
|
|
87
|
+
await new Promise((r) => setImmediate(r));
|
|
89
88
|
appController.remount({ initialView, fresh: false });
|
|
90
89
|
} catch (e) {
|
|
91
90
|
if (onError) onError(e); else throw e;
|
package/src/tui/game/loop.js
CHANGED
|
@@ -74,7 +74,7 @@ const fmtNum = (n) => (n == null ? "0" : Number(n).toLocaleString("en-US"));
|
|
|
74
74
|
* @param {object} [o._inject] test seams: { out, inp, proc, now, schedule, autoExitAfterMs }
|
|
75
75
|
* @returns {Promise<{score:number, level:number, best:number, status:string}>}
|
|
76
76
|
*/
|
|
77
|
-
export function runGame({ width, height, level = 1, scanStore = null, onExit, _inject = {} } = {}) {
|
|
77
|
+
export function runGame({ width, height, level = 1, scanStore = null, onExit, inTui = false, _inject = {} } = {}) {
|
|
78
78
|
return new Promise((resolve) => {
|
|
79
79
|
const proc = _inject.proc || process;
|
|
80
80
|
const out = _inject.out || proc.stdout || process.stdout;
|
|
@@ -89,7 +89,7 @@ export function runGame({ width, height, level = 1, scanStore = null, onExit, _i
|
|
|
89
89
|
try { best = Number(loadConfig().dkHighScore) || 0; } catch { best = 0; }
|
|
90
90
|
|
|
91
91
|
const dims = { width, height };
|
|
92
|
-
const term = new Term({ out, inp, proc, color: COLOR_ON });
|
|
92
|
+
const term = new Term({ out, inp, proc, color: COLOR_ON, keepAlt: inTui });
|
|
93
93
|
const input = new InputState({ now });
|
|
94
94
|
|
|
95
95
|
// Allocate the buffer for the WHOLE frame: HUD rows + board + key row, BOARD_W
|
package/src/tui/game/term.js
CHANGED
|
@@ -216,11 +216,17 @@ export class Term {
|
|
|
216
216
|
inp = process.stdin,
|
|
217
217
|
proc = process,
|
|
218
218
|
color = COLOR_ON,
|
|
219
|
+
// When launched from inside the TUI (which now OWNS the alternate screen),
|
|
220
|
+
// don't toggle the alt buffer ourselves — render into the TUI's alt screen and
|
|
221
|
+
// leave it intact on exit so the remounted TUI stays on a clean full screen
|
|
222
|
+
// (toggling a nested alt screen is unreliable, esp. in Warp's block model).
|
|
223
|
+
keepAlt = false,
|
|
219
224
|
} = {}) {
|
|
220
225
|
this.out = out;
|
|
221
226
|
this.inp = inp;
|
|
222
227
|
this.proc = proc;
|
|
223
228
|
this.color = color;
|
|
229
|
+
this.keepAlt = keepAlt;
|
|
224
230
|
this.started = false;
|
|
225
231
|
this.restored = false;
|
|
226
232
|
this._wasRaw = false;
|
|
@@ -241,8 +247,9 @@ export class Term {
|
|
|
241
247
|
|
|
242
248
|
// Enter alt screen + hide cursor ONCE (never per frame — that's the anti-
|
|
243
249
|
// flicker invariant). Clear the alt buffer so a stale prior frame can't show
|
|
244
|
-
// through before the first diff paints.
|
|
245
|
-
|
|
250
|
+
// through before the first diff paints. keepAlt → the TUI already owns the alt
|
|
251
|
+
// screen, so only hide-cursor + clear (don't re-enter / nest the alt buffer).
|
|
252
|
+
this.write((this.keepAlt ? "" : ALT_ENTER) + CURSOR_HIDE + CLEAR_SCREEN);
|
|
246
253
|
|
|
247
254
|
// Raw mode so we get bytes immediately (no line buffering / no echo). Save
|
|
248
255
|
// the prior state so restore() returns the terminal exactly as it was.
|
|
@@ -300,8 +307,10 @@ export class Term {
|
|
|
300
307
|
this.restored = true;
|
|
301
308
|
|
|
302
309
|
// Order matters: reset SGR + show cursor + leave alt LAST so the chrome
|
|
303
|
-
// lands back on the normal buffer with a visible cooked cursor.
|
|
304
|
-
|
|
310
|
+
// lands back on the normal buffer with a visible cooked cursor. keepAlt → the
|
|
311
|
+
// TUI owns the alt screen and will remount into it, so only reset SGR (don't
|
|
312
|
+
// show the cursor or leave the alt buffer).
|
|
313
|
+
try { this.write(this.keepAlt ? SGR_RESET : SGR_RESET + CURSOR_SHOW + ALT_LEAVE); } catch {}
|
|
305
314
|
|
|
306
315
|
const inp = this.inp;
|
|
307
316
|
try {
|