@tiens.nguyen/gonext-cli 1.0.360 → 1.0.361
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 +71 -30
- package/package.json +1 -1
package/gonext-repl.mjs
CHANGED
|
@@ -67,12 +67,14 @@ const gutterLines = (s) =>
|
|
|
67
67
|
.split("\n")
|
|
68
68
|
.map((l) => (l.length ? GUTTER + l : l))
|
|
69
69
|
.join("\n");
|
|
70
|
-
// The ●
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
|
|
70
|
+
// The ● (and ⏺, the spinner glyph) are MARGIN MARKERS, not text — they HANG in the gutter so a
|
|
71
|
+
// bullet's text lands on the SAME column as every other line (prompt, banner, plain reply, the
|
|
72
|
+
// final answer). There is ONE text column (GUTTER) and the marker sits to its left, instead of
|
|
73
|
+
// pushing bullet text 2 cols right of everything else. `markLine(glyph)` builds a GUTTER-wide
|
|
74
|
+
// prefix: a 1-col glyph right-aligned before a single trailing space, so text starts at the
|
|
75
|
+
// gutter column exactly like an un-marked line.
|
|
76
|
+
const MARK_PAD = " ".repeat(Math.max(0, GUTTER.length - 2));
|
|
77
|
+
const markLine = (glyph) => MARK_PAD + glyph + " ";
|
|
76
78
|
// Render the model's inline **bold** as ANSI bold (markdown polish). The terminal has no
|
|
77
79
|
// markdown renderer, so a heading like `**What I did:**` was printing its literal asterisks.
|
|
78
80
|
// Only paired ** with non-space content between them is consumed (so a stray `**` or `a ** b`
|
|
@@ -110,11 +112,20 @@ const wrapBlock = (text, indent, width) => {
|
|
|
110
112
|
return rows;
|
|
111
113
|
};
|
|
112
114
|
// The completed agent answer, ready to print: wrapped to the current terminal width, every row
|
|
113
|
-
//
|
|
114
|
-
// inline **bold** rendered per row (after wrapping, so the width math
|
|
115
|
+
// indented to the shared text column (GUTTER) so it lines up with the bullets' text and the
|
|
116
|
+
// rest of the output, with inline **bold** rendered per row (after wrapping, so the width math
|
|
117
|
+
// is on visible text).
|
|
115
118
|
const renderAnswer = (answer) =>
|
|
116
|
-
wrapBlock(answer,
|
|
117
|
-
.map((l) => (l.length ?
|
|
119
|
+
wrapBlock(answer, GUTTER, process.stdout.columns || 80)
|
|
120
|
+
.map((l) => (l.length ? GUTTER + renderMdBold(l) : l))
|
|
121
|
+
.join("\n");
|
|
122
|
+
// Wrap a PLAIN (uncoloured) banner/status string to the terminal width and re-indent every row
|
|
123
|
+
// to GUTTER, colouring each row with `colorFn` — so a long startup line keeps its left margin
|
|
124
|
+
// on wrap (narrow windows) instead of soft-wrapping back to column 0. Pass plain text: the
|
|
125
|
+
// colour is applied per row, AFTER the width math (ANSI codes would otherwise skew the wrap).
|
|
126
|
+
const wrapGutter = (plain, colorFn = (s) => s) =>
|
|
127
|
+
wrapBlock(plain, GUTTER, process.stdout.columns || 80)
|
|
128
|
+
.map((l) => (l.length ? GUTTER + colorFn(l) : l))
|
|
118
129
|
.join("\n");
|
|
119
130
|
// Vertical rhythm between ● action/answer bullets (task #129). One blank line so the list
|
|
120
131
|
// breathes instead of being cramped. A knob: "" = the old tight list, "\n" = one blank.
|
|
@@ -735,16 +746,16 @@ async function ensureWorkspace() {
|
|
|
735
746
|
}
|
|
736
747
|
wsSync = { enabled: hit.allowSync !== false, name: hit.name, path: hit.path };
|
|
737
748
|
console.log(
|
|
738
|
-
|
|
739
|
-
dim(
|
|
749
|
+
wrapGutter(
|
|
740
750
|
`workspace: ${hit.name} → ${hit.path} (run ${hit.allowRun ? "enabled" : "disabled"}` +
|
|
741
|
-
`${hit.allowSync !== false ? ", cloud sync on" : ""})
|
|
751
|
+
`${hit.allowSync !== false ? ", cloud sync on" : ""})`,
|
|
752
|
+
dim
|
|
742
753
|
)
|
|
743
754
|
);
|
|
744
755
|
return;
|
|
745
756
|
}
|
|
746
757
|
if (!existsSync(cwd) || !statSync(cwd).isDirectory()) return;
|
|
747
|
-
console.log(
|
|
758
|
+
console.log(wrapGutter(`This folder is not a registered workspace:\n ${cwd}`));
|
|
748
759
|
const register = await askYesNo(
|
|
749
760
|
"Register it so the agent can read/edit code here?", true /* default Yes */
|
|
750
761
|
);
|
|
@@ -1862,14 +1873,14 @@ async function runAgentTurn(history) {
|
|
|
1862
1873
|
// underlined bright-green hover style when the mouse is over a clickable thought. Then the
|
|
1863
1874
|
// GREY expanded lines (if any), then LAST the SPINNER + playful word + token labels
|
|
1864
1875
|
// (task #83/#84), e.g. " ⠙ Caffeinating… · 12.3k tok · 8.1k max".
|
|
1865
|
-
//
|
|
1866
|
-
// the finished ● bullets
|
|
1867
|
-
//
|
|
1868
|
-
// clears whole lines and
|
|
1869
|
-
// changes nothing.
|
|
1870
|
-
let out = `${
|
|
1876
|
+
// The blinking ● and the spinner are hanging margin markers (markLine): the label/word land
|
|
1877
|
+
// on the gutter text column, matching the finished ● bullets (no left-edge jump when a live
|
|
1878
|
+
// action becomes a bullet). The expanded grey sub-lines are plain text at the gutter. Safe
|
|
1879
|
+
// for the redraw: clearStatus clears whole lines and hover/click hit-testing is ROW-based,
|
|
1880
|
+
// so a horizontal offset changes nothing.
|
|
1881
|
+
let out = `${markLine(green(BULLET))}${hovering ? hoverThought(label) : green(label)}`;
|
|
1871
1882
|
for (const wl of expLines) out += "\n" + GUTTER + dim(wl);
|
|
1872
|
-
out += "\n" + `${
|
|
1883
|
+
out += "\n" + `${markLine(color256(wc, glyph))}${color256(wc, `${thinkWord}…`)}${tokLabel}`;
|
|
1873
1884
|
process.stdout.write(out);
|
|
1874
1885
|
statusLines = drawn;
|
|
1875
1886
|
statusShown = true;
|
|
@@ -2010,7 +2021,7 @@ async function runAgentTurn(history) {
|
|
|
2010
2021
|
if (isEditCardHeader(line)) {
|
|
2011
2022
|
onContent();
|
|
2012
2023
|
inEditCard = true;
|
|
2013
|
-
process.stdout.write(
|
|
2024
|
+
process.stdout.write(markLine(green("⏺")) + bold(line.slice(1).replace(/^\s+/, "")) + "\n");
|
|
2014
2025
|
lastWasBlank = false;
|
|
2015
2026
|
lastContentAt = Date.now();
|
|
2016
2027
|
continue;
|
|
@@ -2129,7 +2140,20 @@ async function runAgentTurn(history) {
|
|
|
2129
2140
|
// first (which follows the prompt) and none trailing. Deterministic (keyed off
|
|
2130
2141
|
// lastWasBlank, not the ticker) so #115's status-race stays fixed.
|
|
2131
2142
|
if (LINE_SPACING && !lastWasBlank) process.stdout.write(LINE_SPACING);
|
|
2132
|
-
|
|
2143
|
+
// Completed actions are SCAFFOLDING, not the deliverable — commit them DIM the instant
|
|
2144
|
+
// they print (they're history the moment they land), so the eye lands on the full-bright
|
|
2145
|
+
// final answer below (the "spotlight the answer" design). The loud "happening now"
|
|
2146
|
+
// element stays the green live ticker, not this settled history. Dimming at print time
|
|
2147
|
+
// (not re-colouring afterwards) avoids cursor-walking back over wrapped/scrolled rows.
|
|
2148
|
+
// The ● is a hanging margin marker (markLine): text lands on the gutter column, and a
|
|
2149
|
+
// long bullet wraps so every continuation row re-indents to that SAME gutter — the whole
|
|
2150
|
+
// bullet shares one text column with the prompt/banner instead of soft-wrapping to col 0.
|
|
2151
|
+
const bRows = wrapBlock(line.trim(), GUTTER, process.stdout.columns || 80);
|
|
2152
|
+
process.stdout.write(
|
|
2153
|
+
bRows
|
|
2154
|
+
.map((r, i) => (i === 0 ? markLine(dim(BULLET)) + dim(r) : GUTTER + dim(r)))
|
|
2155
|
+
.join("\n") + "\n"
|
|
2156
|
+
);
|
|
2133
2157
|
}
|
|
2134
2158
|
lastWasBlank = false;
|
|
2135
2159
|
}
|
|
@@ -2380,7 +2404,23 @@ async function runAgentTurn(history) {
|
|
|
2380
2404
|
|
|
2381
2405
|
// ---------- REPL ----------
|
|
2382
2406
|
async function main() {
|
|
2383
|
-
|
|
2407
|
+
// Wrap the header so a narrow window doesn't soft-wrap "key wk_…" to column 0; keep the
|
|
2408
|
+
// "GoNext agent REPL" label cyan on row 0, dim the rest (and any wrapped continuation rows).
|
|
2409
|
+
{
|
|
2410
|
+
const LABEL = "GoNext agent REPL";
|
|
2411
|
+
const header = `${LABEL} v${REPL_VERSION} · API ${apiBase} · key ${workerKey.slice(0, 8)}…`;
|
|
2412
|
+
console.log(
|
|
2413
|
+
wrapBlock(header, GUTTER, process.stdout.columns || 80)
|
|
2414
|
+
.map((l, i) =>
|
|
2415
|
+
!l.length
|
|
2416
|
+
? l
|
|
2417
|
+
: i === 0 && l.startsWith(LABEL)
|
|
2418
|
+
? GUTTER + cyan(LABEL) + dim(l.slice(LABEL.length))
|
|
2419
|
+
: GUTTER + dim(l)
|
|
2420
|
+
)
|
|
2421
|
+
.join("\n")
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2384
2424
|
// Task #127, Phase 2: the REPL enqueues jobs that the polling DAEMON executes, so ensure
|
|
2385
2425
|
// one is running — no GoTerminal needed. Idempotent + BC4-safe: startDaemon() no-ops when
|
|
2386
2426
|
// a daemon (ours, GoTerminal's, or a manual one) is already alive. Best-effort; a failure
|
|
@@ -2427,14 +2467,15 @@ async function main() {
|
|
|
2427
2467
|
await saveSessionRagMode(resolve(process.cwd()), sessionRagMode);
|
|
2428
2468
|
}
|
|
2429
2469
|
console.log(
|
|
2430
|
-
|
|
2431
|
-
dim(
|
|
2470
|
+
wrapGutter(
|
|
2432
2471
|
`agent model: ${p.agentModelId || probe?.modelKey || "?"}` +
|
|
2433
2472
|
(p.codingModelId ? ` · coder: ${p.codingModelId}` : "") +
|
|
2434
2473
|
(p.ragEnabled ? ` · RAG ${sessionRagMode === "cloud" ? "cloud" : "local"}` : "") +
|
|
2435
2474
|
` · auto-test: ${sessionTestAuto ? "on" : "off"}` +
|
|
2436
|
-
(selectedServer ? ` · deploy: ${selectedServer.name} (${selectedServer.user}@${selectedServer.host})` : "")
|
|
2437
|
-
|
|
2475
|
+
(selectedServer ? ` · deploy: ${selectedServer.name} (${selectedServer.user}@${selectedServer.host})` : "") +
|
|
2476
|
+
(sessionTestAuto ? "" : " (/test-auto to enable)"),
|
|
2477
|
+
dim
|
|
2478
|
+
)
|
|
2438
2479
|
);
|
|
2439
2480
|
// Task #127, Phase 3: if the base chat model server isn't answering, offer to set it up
|
|
2440
2481
|
// (download + start mlx_lm.server). BC3: only runs when NOTHING answers — an already-up
|
|
@@ -2494,14 +2535,14 @@ async function main() {
|
|
|
2494
2535
|
}
|
|
2495
2536
|
process.exit(1);
|
|
2496
2537
|
}
|
|
2497
|
-
console.log(
|
|
2538
|
+
console.log(wrapGutter("Ask about this repo. Type / to see commands (Tab completes). Ctrl-C aborts a running turn.", dim) + "\n");
|
|
2498
2539
|
|
|
2499
2540
|
const cwd = resolve(process.cwd());
|
|
2500
2541
|
const history = await loadSession(cwd);
|
|
2501
2542
|
if (history.length > 0) {
|
|
2502
2543
|
const turns = history.filter((m) => m.role === "user").length;
|
|
2503
2544
|
console.log(
|
|
2504
|
-
|
|
2545
|
+
wrapGutter(`Resumed session: ${turns} prior turn(s) from last time in this folder — /reset to start fresh.`, dim) + "\n"
|
|
2505
2546
|
);
|
|
2506
2547
|
}
|
|
2507
2548
|
// Ctrl-C arrives on ONE of two paths depending on the readline mode: with
|
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.361",
|
|
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",
|