@tiens.nguyen/gonext-cli 1.0.354 → 1.0.356
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 +84 -36
- package/package.json +1 -1
package/gonext-repl.mjs
CHANGED
|
@@ -55,6 +55,18 @@ const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
|
55
55
|
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
56
56
|
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
57
57
|
const white = (s) => `\x1b[37m${s}\x1b[0m`;
|
|
58
|
+
// Left gutter so terminal output doesn't hug the edge (task #128, Phase 1). A terminal has
|
|
59
|
+
// no CSS padding — a "left margin" is just a space prefix on every printed line, applied
|
|
60
|
+
// consistently at the prompt, the ● bullets, plain replies, the banner and the live status
|
|
61
|
+
// so they share ONE clean left edge. Existing secondary indents already use 2 spaces, so a
|
|
62
|
+
// 2-space gutter converges everything to the same column. `gutterLines` insets a multi-line
|
|
63
|
+
// block (blank lines left bare — no trailing whitespace).
|
|
64
|
+
const GUTTER = " ";
|
|
65
|
+
const gutterLines = (s) =>
|
|
66
|
+
String(s)
|
|
67
|
+
.split("\n")
|
|
68
|
+
.map((l) => (l.length ? GUTTER + l : l))
|
|
69
|
+
.join("\n");
|
|
58
70
|
// ANSI 90 ("bright black") — a reliable neutral grey across terminal themes, unlike
|
|
59
71
|
// code 34 (blue) which many dark-theme palettes render as purple/indigo instead.
|
|
60
72
|
const codeColor = (s) => `\x1b[90m${s}\x1b[0m`;
|
|
@@ -278,7 +290,7 @@ const rl = createInterface({
|
|
|
278
290
|
terminal: IS_TTY,
|
|
279
291
|
completer: slashCompleter,
|
|
280
292
|
});
|
|
281
|
-
const PROMPT = cyan(">> ");
|
|
293
|
+
const PROMPT = GUTTER + cyan(">> ");
|
|
282
294
|
rl.setPrompt(PROMPT);
|
|
283
295
|
// Side effect of terminal:false above: readline no longer intercepts arrow keys for
|
|
284
296
|
// command-history recall, so pressing ↑/↓/→/← (or Home/End/Delete) sends the RAW escape
|
|
@@ -383,7 +395,9 @@ let secretInput = false;
|
|
|
383
395
|
|
|
384
396
|
// Visible width of PROMPT (">> ") — used to put the caret back on the input line by
|
|
385
397
|
// COLUMN, since the string itself carries color escapes that don't occupy cells.
|
|
386
|
-
|
|
398
|
+
// Visible width of PROMPT = the gutter + ">> " (task #128). Used by the slash-overlay
|
|
399
|
+
// cursor math to park the caret at the right column, so it must include the gutter.
|
|
400
|
+
const PROMPT_COLS = GUTTER.length + 3;
|
|
387
401
|
|
|
388
402
|
// Truncate to `max` VISIBLE columns, keeping the ANSI color escapes (which occupy no
|
|
389
403
|
// cells) intact. Essential for the overlay: every row it draws must fit on ONE physical
|
|
@@ -428,8 +442,12 @@ function clipVisible(s, max) {
|
|
|
428
442
|
function slashOverlaySeq(line, cursor, menu, cols) {
|
|
429
443
|
const width = Math.max(20, cols || 80);
|
|
430
444
|
const n = menu.length;
|
|
445
|
+
// The input line already insets via PROMPT (= GUTTER + ">> "). Inset the MENU rows too
|
|
446
|
+
// (task #128 Phase 2), clipping to width-1-GUTTER so gutter+row stays on ONE physical row
|
|
447
|
+
// — the #109 no-wrap invariant that keeps the \x1b[nA row math exact.
|
|
431
448
|
let out = "\r\x1b[2K" + clipVisible(PROMPT + line, width - 1);
|
|
432
|
-
for (const r of menu)
|
|
449
|
+
for (const r of menu)
|
|
450
|
+
out += "\n\x1b[2K" + GUTTER + clipVisible(r, width - 1 - GUTTER.length);
|
|
433
451
|
// Wipe anything left below (the filtered list can SHRINK as you type), then walk back
|
|
434
452
|
// up to the input line and park the caret at the typed-text offset.
|
|
435
453
|
out += "\x1b[J";
|
|
@@ -600,7 +618,10 @@ const ask = async (q) => {
|
|
|
600
618
|
// Route the question through readline's own prompt so its redraw logic stays
|
|
601
619
|
// consistent (a raw stdout write here would re-create the stale-cursor artifact
|
|
602
620
|
// that terminal:true mode is designed to avoid). Restored to ">> " afterwards.
|
|
603
|
-
|
|
621
|
+
// Inset the question by the gutter (task #128) so Y/n prompts share the left edge —
|
|
622
|
+
// gutterLines so a MULTI-line question ("…commands\n(npm test…)?") insets every line, not
|
|
623
|
+
// just the first. All ask() callers pass un-indented questions, so it never double-indents.
|
|
624
|
+
rl.setPrompt(gutterLines(q));
|
|
604
625
|
rl.prompt();
|
|
605
626
|
const a = await nextLine();
|
|
606
627
|
rl.setPrompt(PROMPT);
|
|
@@ -659,6 +680,7 @@ async function ensureWorkspace() {
|
|
|
659
680
|
}
|
|
660
681
|
wsSync = { enabled: hit.allowSync !== false, name: hit.name, path: hit.path };
|
|
661
682
|
console.log(
|
|
683
|
+
GUTTER +
|
|
662
684
|
dim(
|
|
663
685
|
`workspace: ${hit.name} → ${hit.path} (run ${hit.allowRun ? "enabled" : "disabled"}` +
|
|
664
686
|
`${hit.allowSync !== false ? ", cloud sync on" : ""})`
|
|
@@ -667,12 +689,12 @@ async function ensureWorkspace() {
|
|
|
667
689
|
return;
|
|
668
690
|
}
|
|
669
691
|
if (!existsSync(cwd) || !statSync(cwd).isDirectory()) return;
|
|
670
|
-
console.log(`This folder is not a registered workspace:\n ${cwd}`);
|
|
692
|
+
console.log(gutterLines(`This folder is not a registered workspace:\n ${cwd}`));
|
|
671
693
|
const register = await askYesNo(
|
|
672
694
|
"Register it so the agent can read/edit code here?", true /* default Yes */
|
|
673
695
|
);
|
|
674
696
|
if (!register) {
|
|
675
|
-
console.log(dim("Skipped — the agent will not be able to touch files here."));
|
|
697
|
+
console.log(GUTTER + dim("Skipped — the agent will not be able to touch files here."));
|
|
676
698
|
return;
|
|
677
699
|
}
|
|
678
700
|
const allowRun = await askYesNo(
|
|
@@ -700,7 +722,8 @@ async function ensureWorkspace() {
|
|
|
700
722
|
await writeFile(WS_FILE, JSON.stringify({ workspaces: next }, null, 2) + "\n");
|
|
701
723
|
wsSync = { enabled: allowSync, name: basename(cwd), path: cwd };
|
|
702
724
|
console.log(
|
|
703
|
-
|
|
725
|
+
GUTTER +
|
|
726
|
+
green(`✓ workspace registered: ${basename(cwd)}`) +
|
|
704
727
|
(allowRun ? green(" (run-commands enabled)") : dim(" (run-commands disabled)")) +
|
|
705
728
|
(allowSync ? green(" · cloud sync on") : dim(" · cloud sync off"))
|
|
706
729
|
);
|
|
@@ -1151,15 +1174,19 @@ let clearActiveStatus = null;
|
|
|
1151
1174
|
function drawApprovalOptions(redraw) {
|
|
1152
1175
|
// Two option lines, redrawn in place. On redraw the cursor sits one row below "No";
|
|
1153
1176
|
// step up 2 rows, clear+rewrite both, landing back where we started.
|
|
1177
|
+
// Gutter (task #128 Phase 2): inset each option after the line-clear. The ▸/spaces marker
|
|
1178
|
+
// is 2 cols so Yes/No align; the \x1b[2A row-up count is unchanged (still 2 rows).
|
|
1154
1179
|
const yes = approvalSel === 0 ? green("▸ Yes") : dim(" Yes");
|
|
1155
1180
|
const no = approvalSel === 1 ? green("▸ No") : dim(" No");
|
|
1156
|
-
process.stdout.write(
|
|
1181
|
+
process.stdout.write(
|
|
1182
|
+
(redraw ? "\x1b[2A" : "") + "\x1b[K" + GUTTER + yes + "\n\x1b[K" + GUTTER + no + "\n"
|
|
1183
|
+
);
|
|
1157
1184
|
}
|
|
1158
1185
|
function finishApproval(allow) {
|
|
1159
1186
|
if (!approvalActive) return;
|
|
1160
1187
|
approvalActive = false;
|
|
1161
1188
|
// Settle the picker: overwrite the two option lines with a single result line.
|
|
1162
|
-
process.stdout.write("\x1b[2A\x1b[J" + (allow ? green("
|
|
1189
|
+
process.stdout.write("\x1b[2A\x1b[J" + GUTTER + (allow ? green("▸ Yes — running it") : red("▸ No — skipped")) + "\n");
|
|
1163
1190
|
const r = approvalResolve;
|
|
1164
1191
|
approvalResolve = null;
|
|
1165
1192
|
if (r) r(!!allow);
|
|
@@ -1230,7 +1257,7 @@ async function approvalPrompt(command) {
|
|
|
1230
1257
|
}
|
|
1231
1258
|
const allow = await new Promise((resolve) => {
|
|
1232
1259
|
if (!process.stdin.isTTY) {
|
|
1233
|
-
process.stdout.write(dim(
|
|
1260
|
+
process.stdout.write("\n" + GUTTER + dim(`(approval needed for: ${command} — no interactive terminal, skipping)`) + "\n");
|
|
1234
1261
|
resolve(false);
|
|
1235
1262
|
return;
|
|
1236
1263
|
}
|
|
@@ -1260,8 +1287,10 @@ let listItems = [];
|
|
|
1260
1287
|
let listResolve = null;
|
|
1261
1288
|
function drawList(redraw) {
|
|
1262
1289
|
const n = listItems.length;
|
|
1290
|
+
// Gutter (task #128 Phase 2): inset each row after its line-clear. Items are short (model
|
|
1291
|
+
// / server names), so no clip is needed; the \x1b[nA row-up count is unchanged (n rows).
|
|
1263
1292
|
const body = listItems
|
|
1264
|
-
.map((it, i) => "\x1b[K" + (i === listSel ? green("▸ " + it) : dim(" " + it)))
|
|
1293
|
+
.map((it, i) => "\x1b[K" + GUTTER + (i === listSel ? green("▸ " + it) : dim(" " + it)))
|
|
1265
1294
|
.join("\n");
|
|
1266
1295
|
process.stdout.write((redraw ? `\x1b[${n}A` : "") + body + "\n");
|
|
1267
1296
|
}
|
|
@@ -1778,7 +1807,11 @@ async function runAgentTurn(history) {
|
|
|
1778
1807
|
// underlined bright-green hover style when the mouse is over a clickable thought. Then the
|
|
1779
1808
|
// GREY expanded lines (if any), then LAST the SPINNER + playful word + token labels
|
|
1780
1809
|
// (task #83/#84), e.g. " ⠙ Caffeinating… · 12.3k tok · 8.1k max".
|
|
1781
|
-
|
|
1810
|
+
// Gutter the live status block's first line (task #128) so the blinking ● lines up with
|
|
1811
|
+
// the finished ● bullets during a turn (no left-edge jump). The sub-lines below already
|
|
1812
|
+
// use a 2-space indent = the gutter. Safe for the redraw: clearStatus clears whole lines
|
|
1813
|
+
// and the hover/click hit-testing is ROW-based, so a horizontal offset changes nothing.
|
|
1814
|
+
let out = `${GUTTER}${green(BULLET)} ${hovering ? hoverThought(label) : green(label)}`;
|
|
1782
1815
|
for (const wl of expLines) out += "\n " + dim(wl);
|
|
1783
1816
|
out += "\n" + ` ${color256(wc, `${glyph} ${thinkWord}…`)}${tokLabel}`;
|
|
1784
1817
|
process.stdout.write(out);
|
|
@@ -1921,7 +1954,7 @@ async function runAgentTurn(history) {
|
|
|
1921
1954
|
if (isEditCardHeader(line)) {
|
|
1922
1955
|
onContent();
|
|
1923
1956
|
inEditCard = true;
|
|
1924
|
-
process.stdout.write(green("⏺") + bold(line.slice(1)) + "\n");
|
|
1957
|
+
process.stdout.write(GUTTER + green("⏺") + bold(line.slice(1)) + "\n");
|
|
1925
1958
|
lastWasBlank = false;
|
|
1926
1959
|
lastContentAt = Date.now();
|
|
1927
1960
|
continue;
|
|
@@ -1933,9 +1966,9 @@ async function runAgentTurn(history) {
|
|
|
1933
1966
|
if (m) {
|
|
1934
1967
|
// " 19 - old text" → dim line number, red/green signed content.
|
|
1935
1968
|
const paintDiff = m[3] === "+" ? green : red;
|
|
1936
|
-
process.stdout.write(`${m[1]}${dim(m[2])} ${paintDiff(`${m[3]} ${m[4]}`)}\n`);
|
|
1969
|
+
process.stdout.write(`${GUTTER}${m[1]}${dim(m[2])} ${paintDiff(`${m[3]} ${m[4]}`)}\n`);
|
|
1937
1970
|
} else {
|
|
1938
|
-
process.stdout.write(dim(line) + "\n"); // "└ summary" / "… (+N more)"
|
|
1971
|
+
process.stdout.write(GUTTER + dim(line) + "\n"); // "└ summary" / "… (+N more)"
|
|
1939
1972
|
}
|
|
1940
1973
|
lastWasBlank = false;
|
|
1941
1974
|
lastContentAt = Date.now();
|
|
@@ -1959,7 +1992,7 @@ async function runAgentTurn(history) {
|
|
|
1959
1992
|
}
|
|
1960
1993
|
if (rest) {
|
|
1961
1994
|
onContent();
|
|
1962
|
-
process.stdout.write(codeColor(rest) + "\n");
|
|
1995
|
+
process.stdout.write(GUTTER + codeColor(rest) + "\n");
|
|
1963
1996
|
lastWasBlank = false;
|
|
1964
1997
|
}
|
|
1965
1998
|
lastContentAt = Date.now();
|
|
@@ -1978,7 +2011,7 @@ async function runAgentTurn(history) {
|
|
|
1978
2011
|
const before = t.replace(CODE_CLOSE_RE, "").trim();
|
|
1979
2012
|
if (before) {
|
|
1980
2013
|
onContent();
|
|
1981
|
-
process.stdout.write(codeColor(before) + "\n");
|
|
2014
|
+
process.stdout.write(GUTTER + codeColor(before) + "\n");
|
|
1982
2015
|
lastWasBlank = false;
|
|
1983
2016
|
}
|
|
1984
2017
|
inCode = false;
|
|
@@ -1987,7 +2020,7 @@ async function runAgentTurn(history) {
|
|
|
1987
2020
|
}
|
|
1988
2021
|
if (t === "") { lastContentAt = Date.now(); continue; } // skip blank lines in code
|
|
1989
2022
|
onContent();
|
|
1990
|
-
process.stdout.write(codeColor(line) + "\n");
|
|
2023
|
+
process.stdout.write(GUTTER + codeColor(line) + "\n");
|
|
1991
2024
|
lastWasBlank = false;
|
|
1992
2025
|
continue;
|
|
1993
2026
|
}
|
|
@@ -2024,14 +2057,18 @@ async function runAgentTurn(history) {
|
|
|
2024
2057
|
if (plainReplyFlow) {
|
|
2025
2058
|
// Plain-reply content IS the answer — print it in the default color and remember
|
|
2026
2059
|
// it's visible so the caller doesn't reprint it once the turn completes.
|
|
2027
|
-
|
|
2060
|
+
// Gutter (task #128) ONLY at line-start: when alreadyFlushed>0 this is a re-flush of
|
|
2061
|
+
// a growing SAME physical line, so a gutter here would land mid-line.
|
|
2062
|
+
process.stdout.write(
|
|
2063
|
+
(alreadyFlushed === 0 ? GUTTER : "") + line.slice(alreadyFlushed) + "\n"
|
|
2064
|
+
);
|
|
2028
2065
|
liveAnswer += line.slice(alreadyFlushed) + "\n";
|
|
2029
2066
|
answerShownLive = true;
|
|
2030
2067
|
} else {
|
|
2031
2068
|
// The remaining out-of-fence ACTION events ("Command passed → …", "Searching code
|
|
2032
2069
|
// → …", "Server up → …", "Composing answer…") each print as ONE solid bullet —
|
|
2033
2070
|
// the clean, summarized action list (task #41).
|
|
2034
|
-
process.stdout.write(white(BULLET) + " " + line.trim() + "\n");
|
|
2071
|
+
process.stdout.write(GUTTER + white(BULLET) + " " + line.trim() + "\n");
|
|
2035
2072
|
}
|
|
2036
2073
|
lastWasBlank = false;
|
|
2037
2074
|
}
|
|
@@ -2041,7 +2078,10 @@ async function runAgentTurn(history) {
|
|
|
2041
2078
|
// this was meant to fix. Only safe in the plain-reply flow (see plainReplyFlow above).
|
|
2042
2079
|
if (plainReplyFlow && !inCode && carry.length > carryFlushedLen) {
|
|
2043
2080
|
if (carryFlushedLen === 0) onContent();
|
|
2044
|
-
|
|
2081
|
+
// Gutter (task #128) only at the pending line's START; a continuation appends bare.
|
|
2082
|
+
process.stdout.write(
|
|
2083
|
+
(carryFlushedLen === 0 ? GUTTER : "") + carry.slice(carryFlushedLen)
|
|
2084
|
+
); // default color — this IS the answer
|
|
2045
2085
|
liveAnswer += carry.slice(carryFlushedLen);
|
|
2046
2086
|
carryFlushedLen = carry.length;
|
|
2047
2087
|
answerShownLive = true;
|
|
@@ -2161,7 +2201,14 @@ async function runAgentTurn(history) {
|
|
|
2161
2201
|
}
|
|
2162
2202
|
if (rest) {
|
|
2163
2203
|
onContent();
|
|
2164
|
-
|
|
2204
|
+
// Gutter (task #128 Phase 2): this tail CONTINUES the already-streamed answer, so
|
|
2205
|
+
// inset only NEW lines within it (\n followed by content), and prepend the gutter
|
|
2206
|
+
// only when the cursor is already at a line start (liveAnswer empty or ended with
|
|
2207
|
+
// a newline). Rare path — an old worker that replaced the streamed text.
|
|
2208
|
+
const atLineStart = liveAnswer === "" || /\n$/.test(liveAnswer);
|
|
2209
|
+
process.stdout.write(
|
|
2210
|
+
(atLineStart ? GUTTER : "") + rest.replace(/\n(?=[^\n])/g, "\n" + GUTTER)
|
|
2211
|
+
);
|
|
2165
2212
|
lastWasBlank = false;
|
|
2166
2213
|
}
|
|
2167
2214
|
}
|
|
@@ -2232,7 +2279,7 @@ async function runAgentTurn(history) {
|
|
|
2232
2279
|
// killed the Python agent. Print a quiet note and return an empty, non-persisted
|
|
2233
2280
|
// turn (main() drops it from history).
|
|
2234
2281
|
clearStatus();
|
|
2235
|
-
process.stdout.write(
|
|
2282
|
+
process.stdout.write("\n" + GUTTER + dim("(cancelled — the agent was stopped)") + "\n");
|
|
2236
2283
|
return { text: "", shown: false, cancelled: true };
|
|
2237
2284
|
}
|
|
2238
2285
|
if (job.jobStatus === "failed") {
|
|
@@ -2272,7 +2319,7 @@ async function runAgentTurn(history) {
|
|
|
2272
2319
|
|
|
2273
2320
|
// ---------- REPL ----------
|
|
2274
2321
|
async function main() {
|
|
2275
|
-
console.log(cyan("GoNext agent REPL") + dim(` v${REPL_VERSION} · API ${apiBase} · key ${workerKey.slice(0, 8)}…`));
|
|
2322
|
+
console.log(GUTTER + cyan("GoNext agent REPL") + dim(` v${REPL_VERSION} · API ${apiBase} · key ${workerKey.slice(0, 8)}…`));
|
|
2276
2323
|
// Task #127, Phase 2: the REPL enqueues jobs that the polling DAEMON executes, so ensure
|
|
2277
2324
|
// one is running — no GoTerminal needed. Idempotent + BC4-safe: startDaemon() no-ops when
|
|
2278
2325
|
// a daemon (ours, GoTerminal's, or a manual one) is already alive. Best-effort; a failure
|
|
@@ -2319,6 +2366,7 @@ async function main() {
|
|
|
2319
2366
|
await saveSessionRagMode(resolve(process.cwd()), sessionRagMode);
|
|
2320
2367
|
}
|
|
2321
2368
|
console.log(
|
|
2369
|
+
GUTTER +
|
|
2322
2370
|
dim(
|
|
2323
2371
|
`agent model: ${p.agentModelId || probe?.modelKey || "?"}` +
|
|
2324
2372
|
(p.codingModelId ? ` · coder: ${p.codingModelId}` : "") +
|
|
@@ -2385,14 +2433,14 @@ async function main() {
|
|
|
2385
2433
|
}
|
|
2386
2434
|
process.exit(1);
|
|
2387
2435
|
}
|
|
2388
|
-
console.log(dim("Ask about this repo. Type / to see commands (Tab completes). Ctrl-C aborts a running turn.\n"));
|
|
2436
|
+
console.log(GUTTER + dim("Ask about this repo. Type / to see commands (Tab completes). Ctrl-C aborts a running turn.\n"));
|
|
2389
2437
|
|
|
2390
2438
|
const cwd = resolve(process.cwd());
|
|
2391
2439
|
const history = await loadSession(cwd);
|
|
2392
2440
|
if (history.length > 0) {
|
|
2393
2441
|
const turns = history.filter((m) => m.role === "user").length;
|
|
2394
2442
|
console.log(
|
|
2395
|
-
dim(`Resumed session: ${turns} prior turn(s) from last time in this folder — /reset to start fresh.\n`)
|
|
2443
|
+
GUTTER + dim(`Resumed session: ${turns} prior turn(s) from last time in this folder — /reset to start fresh.\n`)
|
|
2396
2444
|
);
|
|
2397
2445
|
}
|
|
2398
2446
|
// Ctrl-C arrives on ONE of two paths depending on the readline mode: with
|
|
@@ -2413,7 +2461,7 @@ async function main() {
|
|
|
2413
2461
|
// agent; the poll loop then returns cleanly with the "(cancelled)" note.
|
|
2414
2462
|
cancelRequested = true;
|
|
2415
2463
|
const jobId = currentJobId;
|
|
2416
|
-
process.stdout.write(red("
|
|
2464
|
+
process.stdout.write("\n" + GUTTER + red("^C ") + dim("stopping the agent…\n"));
|
|
2417
2465
|
fetch(`${apiBase}/api/worker/jobs/cancel`, {
|
|
2418
2466
|
method: "POST",
|
|
2419
2467
|
headers: { "Content-Type": "application/json", "X-Worker-Key": workerKey },
|
|
@@ -2425,12 +2473,12 @@ async function main() {
|
|
|
2425
2473
|
// Second Ctrl+C (or no jobId yet): give up waiting locally. The worker keeps
|
|
2426
2474
|
// trying to stop, but we stop following.
|
|
2427
2475
|
followAborted = true;
|
|
2428
|
-
process.stdout.write(red("
|
|
2476
|
+
process.stdout.write("\n" + GUTTER + red("^C "));
|
|
2429
2477
|
}
|
|
2430
2478
|
} else {
|
|
2431
2479
|
// Ctrl+C at a prompt with the slash overlay open = dismiss the overlay first (#109).
|
|
2432
2480
|
if (slashOverlayActive) closeSlashOverlay();
|
|
2433
|
-
process.stdout.write(
|
|
2481
|
+
process.stdout.write("\n" + GUTTER + dim("(/exit to quit)") + "\n");
|
|
2434
2482
|
// Discard anything half-typed (Ctrl-C at a prompt means "never mind") and
|
|
2435
2483
|
// redraw through readline so its cursor tracking stays right.
|
|
2436
2484
|
rl.line = "";
|
|
@@ -2503,7 +2551,7 @@ async function main() {
|
|
|
2503
2551
|
if (line === "/reset" || line === "/new") {
|
|
2504
2552
|
history.length = 0;
|
|
2505
2553
|
await clearSession(cwd);
|
|
2506
|
-
console.log(dim("Session cleared — starting fresh.\n"));
|
|
2554
|
+
console.log(GUTTER + dim("Session cleared — starting fresh.\n"));
|
|
2507
2555
|
continue;
|
|
2508
2556
|
}
|
|
2509
2557
|
if (line === "/output-token") {
|
|
@@ -2531,15 +2579,15 @@ async function main() {
|
|
|
2531
2579
|
false
|
|
2532
2580
|
);
|
|
2533
2581
|
if (!ok) {
|
|
2534
|
-
console.log(dim("Cancelled — output-token totals unchanged.\n"));
|
|
2582
|
+
console.log(GUTTER + dim("Cancelled — output-token totals unchanged.\n"));
|
|
2535
2583
|
continue;
|
|
2536
2584
|
}
|
|
2537
2585
|
const done = await resetGlobalOutputTokens();
|
|
2538
2586
|
if (done) {
|
|
2539
2587
|
globalOutputTokens = 0;
|
|
2540
|
-
console.log(green("✓ global + workspace output-token totals reset to 0.\n"));
|
|
2588
|
+
console.log(GUTTER + green("✓ global + workspace output-token totals reset to 0.\n"));
|
|
2541
2589
|
} else {
|
|
2542
|
-
console.log(dim("Could not reset the output-token totals (API error).\n"));
|
|
2590
|
+
console.log(GUTTER + dim("Could not reset the output-token totals (API error).\n"));
|
|
2543
2591
|
}
|
|
2544
2592
|
continue;
|
|
2545
2593
|
}
|
|
@@ -2560,7 +2608,7 @@ async function main() {
|
|
|
2560
2608
|
// question; point the user at /help (all real commands were handled above).
|
|
2561
2609
|
if (line.startsWith("/")) {
|
|
2562
2610
|
const cmd = line.split(/\s+/)[0];
|
|
2563
|
-
console.log(dim(`unknown command: ${cmd} — type /help (or / then Tab) to list commands\n`));
|
|
2611
|
+
console.log(GUTTER + dim(`unknown command: ${cmd} — type /help (or / then Tab) to list commands\n`));
|
|
2564
2612
|
continue;
|
|
2565
2613
|
}
|
|
2566
2614
|
// Resume a cancelled task: a BARE "continue" right after a Ctrl+C re-runs the ORIGINAL
|
|
@@ -2576,7 +2624,7 @@ async function main() {
|
|
|
2576
2624
|
) {
|
|
2577
2625
|
taskLine = pendingResume;
|
|
2578
2626
|
const shown = pendingResume.length > 80 ? pendingResume.slice(0, 80) + "…" : pendingResume;
|
|
2579
|
-
console.log(dim(`↻ resuming the cancelled task: ${shown}\n`));
|
|
2627
|
+
console.log(GUTTER + dim(`↻ resuming the cancelled task: ${shown}\n`));
|
|
2580
2628
|
}
|
|
2581
2629
|
history.push({ role: "user", content: taskLine });
|
|
2582
2630
|
try {
|
|
@@ -2590,7 +2638,7 @@ async function main() {
|
|
|
2590
2638
|
await saveSession(cwd, history);
|
|
2591
2639
|
// A plain-reply answer already streamed live in the loop above — printing it
|
|
2592
2640
|
// again here would just duplicate it; a blank line is enough for spacing.
|
|
2593
|
-
console.log(shown ? "" : `\n\n${answer}\n`);
|
|
2641
|
+
console.log(shown ? "" : `\n\n${gutterLines(answer)}\n`);
|
|
2594
2642
|
// Task #104: persistent token footer at the bottom of the screen — this turn's
|
|
2595
2643
|
// code-model INPUT + this turn's OUTPUT (↓ out), then the model's GLOBAL lifetime
|
|
2596
2644
|
// output total (all turns for this user + coding server), labelled with the model
|
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.356",
|
|
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",
|