@tiens.nguyen/gonext-cli 1.0.371 → 1.0.373
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/gonext-repl.mjs +93 -25
- package/package.json +1 -1
package/gonext-repl.mjs
CHANGED
|
@@ -362,6 +362,7 @@ const COMMANDS = [
|
|
|
362
362
|
{ name: "/server", desc: "pick a deployment server (host/user) for this session" },
|
|
363
363
|
{ name: "/manual-edit", aliases: ["/revert"], usage: "/manual-edit [runId]", desc: "undo the agent's file edits so you can edit them yourself (latest run)" },
|
|
364
364
|
{ name: "/reset", aliases: ["/new"], desc: "forget this folder's saved conversation and start fresh" },
|
|
365
|
+
{ name: "/hover", desc: "toggle mouse hover/click on the thinking line (costs scrolling while it runs)" },
|
|
365
366
|
{ name: "/output-token", desc: "show the agent code-model OUTPUT-token total for this workspace" },
|
|
366
367
|
{ name: "/reset-output-token-global", desc: "reset the GLOBAL (you + coding server) output-token total to 0" },
|
|
367
368
|
{ name: "/help", desc: "list these commands" },
|
|
@@ -673,6 +674,18 @@ const tokenLineText = () => {
|
|
|
673
674
|
);
|
|
674
675
|
};
|
|
675
676
|
const FOOTER_ROWS = 3; // the rule row + the token row + the mode row
|
|
677
|
+
// The bar's rows as plain printable strings, one PHYSICAL terminal row each. Both text rows
|
|
678
|
+
// can exceed the width (the mode line is ~44 columns, the token row carries a model name) and
|
|
679
|
+
// would then WRAP onto an extra row — every consumer here counts rows with ESC[nA / ESC[nB, so
|
|
680
|
+
// a wrapped row corrupts the redraw (the #109 failure mode). Hence clipVisible on both.
|
|
681
|
+
const footerRows = () => {
|
|
682
|
+
const width = Math.max(20, process.stdout.columns || 80);
|
|
683
|
+
return [
|
|
684
|
+
dim("─".repeat(width)),
|
|
685
|
+
clipVisible(GUTTER + tokenLineText(), width - 1),
|
|
686
|
+
clipVisible(GUTTER + modeLineText(), width - 1),
|
|
687
|
+
];
|
|
688
|
+
};
|
|
676
689
|
// True only while the MAIN loop's ">> " prompt is waiting for input. The bar belongs to that
|
|
677
690
|
// prompt, so it must never appear under an ask()/askSecret() question or during a turn.
|
|
678
691
|
let footerActive = false;
|
|
@@ -690,18 +703,11 @@ const cursorPos = () =>
|
|
|
690
703
|
// matching up-move still lands on it.
|
|
691
704
|
function drawFooter() {
|
|
692
705
|
if (!footerActive || !process.stdout.isTTY || slashOverlayActive) return;
|
|
693
|
-
const width = Math.max(20, process.stdout.columns || 80);
|
|
694
706
|
const pos = cursorPos();
|
|
695
707
|
const down = Math.max(0, inputRows() - 1 - pos.rows); // caret row → last input row
|
|
696
708
|
process.stdout.write(
|
|
697
709
|
(down ? `\x1b[${down}B` : "") +
|
|
698
|
-
"\r\n\x1b[2K" +
|
|
699
|
-
// CLIPPED: both rows can exceed the width (the mode line is ~44 columns, the token row
|
|
700
|
-
// carries a model name) and would then WRAP onto an extra row — ESC[nA counts PHYSICAL
|
|
701
|
-
// rows, so the caret would come back short and the next keystroke would type over the
|
|
702
|
-
// bar (the #109 failure mode).
|
|
703
|
-
"\r\n\x1b[2K" + clipVisible(GUTTER + tokenLineText(), width - 1) +
|
|
704
|
-
"\r\n\x1b[2K" + clipVisible(GUTTER + modeLineText(), width - 1) +
|
|
710
|
+
footerRows().map((r) => "\r\n\x1b[2K" + r).join("") +
|
|
705
711
|
"\x1b[J" + // nothing stale below the bar
|
|
706
712
|
`\x1b[${FOOTER_ROWS + down}A\r` + // back to the caret's row
|
|
707
713
|
(pos.cols > 0 ? `\x1b[${pos.cols}C` : "")
|
|
@@ -757,6 +763,10 @@ if (process.stdin.isTTY) {
|
|
|
757
763
|
return;
|
|
758
764
|
}
|
|
759
765
|
if (following) {
|
|
766
|
+
// Ctrl+T expands/collapses the model's current Thought — the keyboard equivalent of the
|
|
767
|
+
// hover-click, so the fuller thought stays reachable with the mouse capture off (the
|
|
768
|
+
// capture is what would eat the scroll wheel; see hoverMouse).
|
|
769
|
+
if (key && key.ctrl && key.name === "t" && toggleActiveThought) toggleActiveThought();
|
|
760
770
|
rl.line = "";
|
|
761
771
|
rl.cursor = 0;
|
|
762
772
|
rl.historyIndex = -1;
|
|
@@ -805,18 +815,24 @@ rl.on("close", () => {
|
|
|
805
815
|
w(null);
|
|
806
816
|
}
|
|
807
817
|
});
|
|
808
|
-
// Mouse hover+click on the thinking line (task #96):
|
|
809
|
-
//
|
|
810
|
-
//
|
|
811
|
-
//
|
|
812
|
-
//
|
|
813
|
-
//
|
|
814
|
-
//
|
|
818
|
+
// Mouse hover+click on the thinking line (task #96): turns ON xterm ANY-MOTION tracking for
|
|
819
|
+
// the duration of a turn so we can HIGHLIGHT the line as the mouse hovers it AND expand it on
|
|
820
|
+
// click. ?1003 = report every button press/release AND all mouse MOTION (needed for hover with
|
|
821
|
+
// no button held); ?1006 = SGR extended coords (no 223-col cap). We disable ?1000/?1002 too on
|
|
822
|
+
// teardown in case a terminal had them latched.
|
|
823
|
+
//
|
|
824
|
+
// OFF BY DEFAULT, and it has to be: with ANY mouse reporting enabled the terminal hands us the
|
|
825
|
+
// WHEEL events (SGR button 64/65) instead of scrolling its own scrollback, so the user cannot
|
|
826
|
+
// scroll while a turn runs — and no tracking mode reports motion without also grabbing the
|
|
827
|
+
// wheel, so hover and scrolling are mutually exclusive. Scrolling wins by default (it also
|
|
828
|
+
// gives back native drag-to-select); `/hover` opts back into the mouse for a session, and
|
|
829
|
+
// Ctrl+T expands the thought from the keyboard either way.
|
|
830
|
+
let hoverMouse = false;
|
|
815
831
|
const MOUSE_ON = "\x1b[?1003h\x1b[?1006h";
|
|
816
832
|
const MOUSE_OFF = "\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?1006l";
|
|
817
833
|
let _mouseEnabled = false;
|
|
818
834
|
const enableMouse = () => {
|
|
819
|
-
if (_mouseEnabled || !process.stdin.isTTY) return;
|
|
835
|
+
if (!hoverMouse || _mouseEnabled || !process.stdin.isTTY) return;
|
|
820
836
|
process.stdout.write(MOUSE_ON);
|
|
821
837
|
_mouseEnabled = true;
|
|
822
838
|
};
|
|
@@ -1394,6 +1410,10 @@ let approvalResolve = null;
|
|
|
1394
1410
|
// first — otherwise the 120ms ticker keeps redrawing that block on top of the picker and
|
|
1395
1411
|
// its relative-cursor math lands on the wrong rows (duplicated/interleaved options).
|
|
1396
1412
|
let clearActiveStatus = null;
|
|
1413
|
+
// The active turn's thought expand/collapse (its local toggle), or null between turns — lets
|
|
1414
|
+
// Ctrl+T reveal the full Thought from the KEYBOARD, so the feature doesn't depend on the mouse
|
|
1415
|
+
// capture that would block scrolling (see hoverMouse).
|
|
1416
|
+
let toggleActiveThought = null;
|
|
1397
1417
|
function drawApprovalOptions(redraw) {
|
|
1398
1418
|
// Two option lines, redrawn in place. On redraw the cursor sits one row below "No";
|
|
1399
1419
|
// step up 2 rows, clear+rewrite both, landing back where we started.
|
|
@@ -1872,7 +1892,9 @@ async function runAgentTurn(history) {
|
|
|
1872
1892
|
let lastApprovalId = null; // the last command-approval request we've already answered
|
|
1873
1893
|
let carry = ""; // trailing partial content line held until its newline arrives
|
|
1874
1894
|
let statusShown = false; // a transient status line is currently on screen
|
|
1875
|
-
let statusLines = 2; //
|
|
1895
|
+
let statusLines = 2; // TOTAL rows the status block occupies, bottom bar included (clearStatus)
|
|
1896
|
+
// …of which these are the thought/word rows — the only ones hover + click hit-test against.
|
|
1897
|
+
let statusContentLines = 2;
|
|
1876
1898
|
let warnedPending = false;
|
|
1877
1899
|
// Transient poll failures (a network blip or a 5xx like the API's momentary "Worker
|
|
1878
1900
|
// key lookup failed." — a cold Mongo connection, NOT a bad key) must NOT kill a
|
|
@@ -1899,6 +1921,10 @@ async function runAgentTurn(history) {
|
|
|
1899
1921
|
// the lifetime per-workspace + global counters once the turn completes.
|
|
1900
1922
|
let latestOutputTokens = 0;
|
|
1901
1923
|
let outputTokensPosted = false; // guard: increment the lifetime counters once per turn
|
|
1924
|
+
// A new turn starts from zero on the bar's token row too — otherwise the previous turn's
|
|
1925
|
+
// in/out figures would sit there, looking live, until the first poll reports real numbers.
|
|
1926
|
+
lastTurnInputTokens = 0;
|
|
1927
|
+
lastTurnOutputTokens = 0;
|
|
1902
1928
|
// First few KB of the raw stream as consumed — a cheap fingerprint to verify the
|
|
1903
1929
|
// completed resultText really EXTENDS what we streamed (an old worker replaces it
|
|
1904
1930
|
// with the shorter bare answer, which must not be sliced by shownChars).
|
|
@@ -2020,16 +2046,23 @@ async function runAgentTurn(history) {
|
|
|
2020
2046
|
// the playful-word line.
|
|
2021
2047
|
const expLines = thoughtExpanded && rest ? wrapThought(rest, max, 4) : [];
|
|
2022
2048
|
const drawn = 2 + expLines.length;
|
|
2049
|
+
// The bottom bar rides along UNDER the live status while a turn runs, so it stays on screen
|
|
2050
|
+
// the whole time the agent is thinking instead of only at the prompt. It's part of the same
|
|
2051
|
+
// in-place block: the ticker redraws it every 120ms and clearStatus wipes it with the rest,
|
|
2052
|
+
// so content still lands on clean rows.
|
|
2053
|
+
const bar = process.stdout.isTTY ? footerRows() : [];
|
|
2023
2054
|
const rows = process.stdout.rows || 24;
|
|
2024
2055
|
// Hover-highlight only the THOUGHT line (line 1) — its exact viewport row comes from the
|
|
2025
2056
|
// \x1b[6n probe below (`thoughtRow`); fall back to the viewport-bottom assumption only when
|
|
2026
|
-
// the terminal never answered the probe.
|
|
2027
|
-
const tRow = thoughtRow >= 1 ? thoughtRow : rows - drawn + 1;
|
|
2057
|
+
// the terminal never answered the probe (the block's top = bottom minus ALL its rows).
|
|
2058
|
+
const tRow = thoughtRow >= 1 ? thoughtRow : rows - (drawn + bar.length) + 1;
|
|
2028
2059
|
const hovering = clickable && hoverRow === tRow;
|
|
2029
2060
|
clearStatus();
|
|
2030
2061
|
// Right after clearing, the cursor sits at the status block's TOP-LEFT = the thought line's
|
|
2031
2062
|
// row. Probe for it (throttled) so hover/click hit-testing is exact wherever the block is.
|
|
2032
|
-
|
|
2063
|
+
// Only worth probing when the mouse is captured — thoughtRow exists for hover/click
|
|
2064
|
+
// hit-testing, and with `/hover` off nothing reads it (and nothing would consume the reply).
|
|
2065
|
+
if (hoverMouse && process.stdin.isTTY && now - lastDsrAt > 400) { lastDsrAt = now; process.stdout.write("\x1b[6n"); }
|
|
2033
2066
|
// Line 1: green ● + green phase/seconds (matches the web's green streaming label), or the
|
|
2034
2067
|
// underlined bright-green hover style when the mouse is over a clickable thought. Then the
|
|
2035
2068
|
// GREY expanded lines (if any), then LAST the SPINNER + playful word + token labels
|
|
@@ -2042,8 +2075,10 @@ async function runAgentTurn(history) {
|
|
|
2042
2075
|
let out = `${markLine(green(BULLET))}${hovering ? hoverThought(label) : green(label)}`;
|
|
2043
2076
|
for (const wl of expLines) out += "\n" + GUTTER + dim(wl);
|
|
2044
2077
|
out += "\n" + `${markLine(color256(wc, glyph))}${color256(wc, `${thinkWord}…`)}${tokLabel}`;
|
|
2078
|
+
for (const br of bar) out += "\n" + br;
|
|
2045
2079
|
process.stdout.write(out);
|
|
2046
|
-
|
|
2080
|
+
statusContentLines = drawn;
|
|
2081
|
+
statusLines = drawn + bar.length;
|
|
2047
2082
|
statusShown = true;
|
|
2048
2083
|
};
|
|
2049
2084
|
// 120ms so the spinner animates smoothly (the frame math above uses the wall clock, so
|
|
@@ -2061,15 +2096,24 @@ async function runAgentTurn(history) {
|
|
|
2061
2096
|
if (!statusShown) return;
|
|
2062
2097
|
const rows = process.stdout.rows || 24;
|
|
2063
2098
|
const top = thoughtRow >= 1 ? thoughtRow : rows - statusLines + 1; // thought line row
|
|
2064
|
-
// Toggle only on the thought line + its grey expansion — NOT the
|
|
2065
|
-
//
|
|
2066
|
-
|
|
2099
|
+
// Toggle only on the thought line + its grey expansion — NOT the playful-word/token row,
|
|
2100
|
+
// and NOT the bottom bar below it (statusContentLines excludes the bar), so a click there
|
|
2101
|
+
// doesn't expand what it isn't part of.
|
|
2102
|
+
if (row < top || row > top + statusContentLines - 2) return;
|
|
2067
2103
|
// Only meaningful when there IS a fuller thought to reveal; otherwise ignore the click
|
|
2068
2104
|
// so we don't flicker an empty expand.
|
|
2105
|
+
if (!thoughtExpanded && !(fullLiveThought && !runningCmd)) return;
|
|
2106
|
+
toggleThought();
|
|
2107
|
+
};
|
|
2108
|
+
// Same expand/collapse, reachable without the mouse: Ctrl+T while a turn runs (see
|
|
2109
|
+
// toggleActiveThought). Ignored when there's nothing more to reveal, so it never flickers an
|
|
2110
|
+
// empty expansion.
|
|
2111
|
+
const toggleThought = () => {
|
|
2069
2112
|
if (!thoughtExpanded && !(fullLiveThought && !runningCmd)) return;
|
|
2070
2113
|
thoughtExpanded = !thoughtExpanded;
|
|
2071
2114
|
clearStatus(); // next tick (≤120ms) redraws at the new size; keeps cursor math correct
|
|
2072
2115
|
};
|
|
2116
|
+
toggleActiveThought = toggleThought;
|
|
2073
2117
|
const onMouseData = (chunk) => {
|
|
2074
2118
|
if (!following) return;
|
|
2075
2119
|
const s = chunk.toString("latin1");
|
|
@@ -2088,7 +2132,9 @@ async function runAgentTurn(history) {
|
|
|
2088
2132
|
if (isPress && (btn & 0b11) === 0 && (btn & 64) === 0) onStatusClick(row); // left click → expand
|
|
2089
2133
|
}
|
|
2090
2134
|
};
|
|
2091
|
-
|
|
2135
|
+
// Only listen for reports when we're actually capturing the mouse — with `/hover` off there
|
|
2136
|
+
// are none, and no \x1b[6n probe reply to consume either.
|
|
2137
|
+
if (hoverMouse && process.stdin.isTTY) process.stdin.on("data", onMouseData);
|
|
2092
2138
|
enableMouse();
|
|
2093
2139
|
|
|
2094
2140
|
// Called right before any real (bullet/edit-card/answer) content prints: drop the
|
|
@@ -2412,6 +2458,12 @@ async function runAgentTurn(history) {
|
|
|
2412
2458
|
if (Number.isFinite(job.agentOutputTokens) && job.agentOutputTokens > latestOutputTokens) {
|
|
2413
2459
|
latestOutputTokens = job.agentOutputTokens;
|
|
2414
2460
|
}
|
|
2461
|
+
// Mirror both into the bottom bar's token row so it counts UP live while the turn runs
|
|
2462
|
+
// (the bar rides under the status block now), instead of sitting on the last turn's
|
|
2463
|
+
// figures until this one finishes. The turn-end assignment writes the same numbers again.
|
|
2464
|
+
lastTurnInputTokens = latestCodeTokens;
|
|
2465
|
+
lastTurnOutputTokens = latestOutputTokens;
|
|
2466
|
+
lastTurnTotalOutput = globalOutputTokens + latestOutputTokens;
|
|
2415
2467
|
const text = typeof job.resultText === "string" ? job.resultText : "";
|
|
2416
2468
|
if (job.jobStatus === "completed") {
|
|
2417
2469
|
// Task #104: fold this turn's output-token total into the lifetime per-workspace +
|
|
@@ -2561,6 +2613,7 @@ async function runAgentTurn(history) {
|
|
|
2561
2613
|
clearInterval(ticker);
|
|
2562
2614
|
clearStatus();
|
|
2563
2615
|
clearActiveStatus = null; // no turn owns the status wiper between turns
|
|
2616
|
+
toggleActiveThought = null; // …nor the Ctrl+T thought toggle
|
|
2564
2617
|
// Task #96: stop mouse-reporting the moment the turn ends (also on cancel/error — this
|
|
2565
2618
|
// finally always runs), and detach the click parser, so the user's shell selection is
|
|
2566
2619
|
// never captured between turns.
|
|
@@ -2806,6 +2859,21 @@ async function main() {
|
|
|
2806
2859
|
printCommands();
|
|
2807
2860
|
continue;
|
|
2808
2861
|
}
|
|
2862
|
+
if (line === "/hover") {
|
|
2863
|
+
// Mouse hover/click on the thinking line (#96) vs. scrolling during a turn: the terminal
|
|
2864
|
+
// only reports wheel events to US when mouse tracking is on, so they're exclusive. Off by
|
|
2865
|
+
// default; this opts in for the session. Ctrl+T expands the thought either way.
|
|
2866
|
+
hoverMouse = !hoverMouse;
|
|
2867
|
+
console.log(
|
|
2868
|
+
(hoverMouse ? green("hover: ON") : dim("hover: OFF")) +
|
|
2869
|
+
dim(
|
|
2870
|
+
hoverMouse
|
|
2871
|
+
? " — mouse hover/click expands the thinking line; scrolling and drag-select are captured while a turn runs.\n"
|
|
2872
|
+
: " — scrolling and drag-select work during a turn; use Ctrl+T to expand the thinking line.\n"
|
|
2873
|
+
)
|
|
2874
|
+
);
|
|
2875
|
+
continue;
|
|
2876
|
+
}
|
|
2809
2877
|
if (line === "/model") {
|
|
2810
2878
|
await chooseModel();
|
|
2811
2879
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gonext-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.373",
|
|
4
4
|
"description": "GoNext CLI — the gonext terminal plus the local worker that runs agent / OCR / PDF / embedding jobs on your Mac (Ollama / MLX / OpenAI-compatible).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|