@tiens.nguyen/gonext-cli 1.0.360 → 1.0.362
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 +142 -63
- 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,18 +112,33 @@ 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))
|
|
118
121
|
.join("\n");
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
|
|
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))
|
|
129
|
+
.join("\n");
|
|
130
|
+
// The blank SEAM between the dim action log and the bright final answer (grouping, senior-UI
|
|
131
|
+
// advice): the completed-action bullets are now a TIGHT group with no blanks between them, so
|
|
132
|
+
// this one gap is what separates the "what I did" scaffolding from the deliverable. A knob:
|
|
133
|
+
// "" = answer butts against the log, "\n" = one blank line. (Task #129 originally used this
|
|
134
|
+
// BETWEEN every bullet; that read too loose once the bullets went dim — see the bullet write.)
|
|
124
135
|
const LINE_SPACING = "\n";
|
|
136
|
+
// Plain (chat) reply rendering. false = BUFFER the reply and print it WRAPPED at completion
|
|
137
|
+
// (via renderAnswer, from the authoritative resultText) so long lines keep the left margin —
|
|
138
|
+
// the thinking ticker animates during generation, then the wrapped answer appears. true = the
|
|
139
|
+
// old live char-by-char stream, which reads nicely but SOFT-WRAPS a long line back to column 0.
|
|
140
|
+
// This is the one knob to flip back to streaming if the buffered path ever misbehaves live.
|
|
141
|
+
const STREAM_PLAIN_REPLY = false;
|
|
125
142
|
// ANSI 90 ("bright black") — a reliable neutral grey across terminal themes, unlike
|
|
126
143
|
// code 34 (blue) which many dark-theme palettes render as purple/indigo instead.
|
|
127
144
|
const codeColor = (s) => `\x1b[90m${s}\x1b[0m`;
|
|
@@ -211,7 +228,23 @@ const fmtTokens = (n) => {
|
|
|
211
228
|
if (n < 1_000_000) return `${(n / 1000).toFixed(n < 100_000 ? 1 : 0)}k`;
|
|
212
229
|
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
213
230
|
};
|
|
214
|
-
const BULLET = "●"; // a completed action
|
|
231
|
+
const BULLET = "●"; // a neutral completed action (informational step)
|
|
232
|
+
const CHECK = "✓"; // a step that succeeded
|
|
233
|
+
const CROSS = "✗"; // a step that FAILED
|
|
234
|
+
const brightText = (s) => s; // default terminal colour (un-dimmed) — the "pops" text weight
|
|
235
|
+
// Classify a finished action bullet for the "spotlight the answer" hierarchy: successes and
|
|
236
|
+
// neutral steps RECEDE (dim), a FAILURE POPS (red ✗ + full-bright text) — it's the one thing
|
|
237
|
+
// the user must not miss while everything else is quiet. Keyed off the worker's step strings
|
|
238
|
+
// ("Command finished/failed → …", "Server up → …", "App launched → …", "HTTP … → HTTP 2xx/4xx/
|
|
239
|
+
// 5xx", "Searching/Downloading/Composing …"). Failure is checked FIRST so a failing HTTP or a
|
|
240
|
+
// "failed" command never reads as success.
|
|
241
|
+
const classifyBullet = (text) => {
|
|
242
|
+
if (/\bfail(ed|s|ure)?\b|\berror(ed)?\b|→\s*(?:HTTP\s*)?[45]\d\d\b/i.test(text))
|
|
243
|
+
return { mark: CROSS, markColor: red, textColor: brightText };
|
|
244
|
+
if (/^(?:Command finished|Server up|App launched)\b|→\s*(?:HTTP\s*)?2\d\d\b|\b(?:passed|succeeded|ready)\b/i.test(text))
|
|
245
|
+
return { mark: CHECK, markColor: green, textColor: dim };
|
|
246
|
+
return { mark: BULLET, markColor: dim, textColor: dim };
|
|
247
|
+
};
|
|
215
248
|
|
|
216
249
|
// ---------- flags ----------
|
|
217
250
|
const argv = process.argv.slice(2);
|
|
@@ -345,7 +378,11 @@ const rl = createInterface({
|
|
|
345
378
|
terminal: IS_TTY,
|
|
346
379
|
completer: slashCompleter,
|
|
347
380
|
});
|
|
348
|
-
|
|
381
|
+
// ">>" is a MARGIN MARKER like the ● bullets — it hangs in the gutter (no leading gutter) so
|
|
382
|
+
// the user's typed input lands on the SAME text column (GUTTER+1) as the agent's bullets and
|
|
383
|
+
// answer. ">> " is exactly GUTTER-wide, so input aligns; the ">>" right-edge sits at the same
|
|
384
|
+
// column the ● / ✓ / ✗ markers hang at (markers right-aligned in the margin, text in one column).
|
|
385
|
+
const PROMPT = cyan(">> ");
|
|
349
386
|
rl.setPrompt(PROMPT);
|
|
350
387
|
// Side effect of terminal:false above: readline no longer intercepts arrow keys for
|
|
351
388
|
// command-history recall, so pressing ↑/↓/→/← (or Home/End/Delete) sends the RAW escape
|
|
@@ -450,9 +487,9 @@ let secretInput = false;
|
|
|
450
487
|
|
|
451
488
|
// Visible width of PROMPT (">> ") — used to put the caret back on the input line by
|
|
452
489
|
// COLUMN, since the string itself carries color escapes that don't occupy cells.
|
|
453
|
-
// Visible width of PROMPT =
|
|
454
|
-
// cursor math to park the caret at the
|
|
455
|
-
const PROMPT_COLS =
|
|
490
|
+
// Visible width of PROMPT = ">> " (3), since it now hangs with no leading gutter. Used by the
|
|
491
|
+
// slash-overlay cursor math to park the caret at the input's text column (GUTTER+1 = col 4).
|
|
492
|
+
const PROMPT_COLS = 3;
|
|
456
493
|
|
|
457
494
|
// Truncate to `max` VISIBLE columns, keeping the ANSI color escapes (which occupy no
|
|
458
495
|
// cells) intact. Essential for the overlay: every row it draws must fit on ONE physical
|
|
@@ -735,16 +772,16 @@ async function ensureWorkspace() {
|
|
|
735
772
|
}
|
|
736
773
|
wsSync = { enabled: hit.allowSync !== false, name: hit.name, path: hit.path };
|
|
737
774
|
console.log(
|
|
738
|
-
|
|
739
|
-
dim(
|
|
775
|
+
wrapGutter(
|
|
740
776
|
`workspace: ${hit.name} → ${hit.path} (run ${hit.allowRun ? "enabled" : "disabled"}` +
|
|
741
|
-
`${hit.allowSync !== false ? ", cloud sync on" : ""})
|
|
777
|
+
`${hit.allowSync !== false ? ", cloud sync on" : ""})`,
|
|
778
|
+
dim
|
|
742
779
|
)
|
|
743
780
|
);
|
|
744
781
|
return;
|
|
745
782
|
}
|
|
746
783
|
if (!existsSync(cwd) || !statSync(cwd).isDirectory()) return;
|
|
747
|
-
console.log(
|
|
784
|
+
console.log(wrapGutter(`This folder is not a registered workspace:\n ${cwd}`));
|
|
748
785
|
const register = await askYesNo(
|
|
749
786
|
"Register it so the agent can read/edit code here?", true /* default Yes */
|
|
750
787
|
);
|
|
@@ -1862,14 +1899,14 @@ async function runAgentTurn(history) {
|
|
|
1862
1899
|
// underlined bright-green hover style when the mouse is over a clickable thought. Then the
|
|
1863
1900
|
// GREY expanded lines (if any), then LAST the SPINNER + playful word + token labels
|
|
1864
1901
|
// (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 = `${
|
|
1902
|
+
// The blinking ● and the spinner are hanging margin markers (markLine): the label/word land
|
|
1903
|
+
// on the gutter text column, matching the finished ● bullets (no left-edge jump when a live
|
|
1904
|
+
// action becomes a bullet). The expanded grey sub-lines are plain text at the gutter. Safe
|
|
1905
|
+
// for the redraw: clearStatus clears whole lines and hover/click hit-testing is ROW-based,
|
|
1906
|
+
// so a horizontal offset changes nothing.
|
|
1907
|
+
let out = `${markLine(green(BULLET))}${hovering ? hoverThought(label) : green(label)}`;
|
|
1871
1908
|
for (const wl of expLines) out += "\n" + GUTTER + dim(wl);
|
|
1872
|
-
out += "\n" + `${
|
|
1909
|
+
out += "\n" + `${markLine(color256(wc, glyph))}${color256(wc, `${thinkWord}…`)}${tokLabel}`;
|
|
1873
1910
|
process.stdout.write(out);
|
|
1874
1911
|
statusLines = drawn;
|
|
1875
1912
|
statusShown = true;
|
|
@@ -2010,7 +2047,7 @@ async function runAgentTurn(history) {
|
|
|
2010
2047
|
if (isEditCardHeader(line)) {
|
|
2011
2048
|
onContent();
|
|
2012
2049
|
inEditCard = true;
|
|
2013
|
-
process.stdout.write(
|
|
2050
|
+
process.stdout.write(markLine(green("⏺")) + bold(line.slice(1).replace(/^\s+/, "")) + "\n");
|
|
2014
2051
|
lastWasBlank = false;
|
|
2015
2052
|
lastContentAt = Date.now();
|
|
2016
2053
|
continue;
|
|
@@ -2094,7 +2131,9 @@ async function runAgentTurn(history) {
|
|
|
2094
2131
|
// • PLAIN-REPLY answer → this text IS the answer, so its blanks are real
|
|
2095
2132
|
// paragraph breaks. Keep them (collapsing runs) and clear the status block
|
|
2096
2133
|
// first, which is what the old `!statusShown` guard was really protecting.
|
|
2097
|
-
|
|
2134
|
+
// When buffering the plain reply, blanks live in resultText and are re-emitted by the
|
|
2135
|
+
// caller's wrapped reprint — swallow them here (and keep the ticker up: no onContent).
|
|
2136
|
+
if (plainReplyFlow && STREAM_PLAIN_REPLY && !lastWasBlank) {
|
|
2098
2137
|
onContent();
|
|
2099
2138
|
process.stdout.write("\n");
|
|
2100
2139
|
lastWasBlank = true;
|
|
@@ -2109,27 +2148,44 @@ async function runAgentTurn(history) {
|
|
|
2109
2148
|
const mRun = /^Running → (.+)$/.exec(line.trim());
|
|
2110
2149
|
if (mRun) { runningCmd = mRun[1]; thinking = false; lastContentAt = Date.now(); continue; }
|
|
2111
2150
|
}
|
|
2112
|
-
|
|
2151
|
+
// Skip the status-clear for a BUFFERED plain reply (STREAM_PLAIN_REPLY=false): nothing
|
|
2152
|
+
// prints yet, so the thinking ticker should keep animating until completion.
|
|
2153
|
+
if (alreadyFlushed === 0 && !(plainReplyFlow && !STREAM_PLAIN_REPLY)) onContent();
|
|
2113
2154
|
if (plainReplyFlow) {
|
|
2114
|
-
// Plain-reply content IS the answer
|
|
2115
|
-
// it
|
|
2116
|
-
//
|
|
2117
|
-
//
|
|
2118
|
-
process.stdout.write(
|
|
2119
|
-
(alreadyFlushed === 0 ? GUTTER : "") + line.slice(alreadyFlushed) + "\n"
|
|
2120
|
-
);
|
|
2155
|
+
// Plain-reply content IS the answer. When STREAMING, print it live (default colour) and
|
|
2156
|
+
// mark it shown so the caller doesn't reprint. When BUFFERING, only accumulate — the
|
|
2157
|
+
// caller reprints the whole thing WRAPPED from resultText (shown stays false), so a long
|
|
2158
|
+
// line keeps its margin instead of soft-wrapping. Gutter only at a line start.
|
|
2121
2159
|
liveAnswer += line.slice(alreadyFlushed) + "\n";
|
|
2122
|
-
|
|
2160
|
+
if (STREAM_PLAIN_REPLY) {
|
|
2161
|
+
process.stdout.write(
|
|
2162
|
+
(alreadyFlushed === 0 ? GUTTER : "") + line.slice(alreadyFlushed) + "\n"
|
|
2163
|
+
);
|
|
2164
|
+
answerShownLive = true;
|
|
2165
|
+
}
|
|
2123
2166
|
} else {
|
|
2124
|
-
// The
|
|
2125
|
-
//
|
|
2126
|
-
//
|
|
2127
|
-
//
|
|
2128
|
-
//
|
|
2129
|
-
//
|
|
2130
|
-
//
|
|
2131
|
-
|
|
2132
|
-
|
|
2167
|
+
// The out-of-fence ACTION events ("Command finished/failed → …", "Server up → …",
|
|
2168
|
+
// "Searching code → …", "Composing answer…") print as ONE solid bullet each — a clean,
|
|
2169
|
+
// summarized action list (task #41). They form a TIGHT group now (no blank between them,
|
|
2170
|
+
// superseding #129's uniform spacing): completed actions are dim scaffolding, so a
|
|
2171
|
+
// compact grey log reads better than a sparse one, and the ONE blank that matters is
|
|
2172
|
+
// before the bright answer (the caller's LINE_SPACING seam), separating the log from the
|
|
2173
|
+
// deliverable. #115's status-race stays gone — there are simply no per-bullet blanks.
|
|
2174
|
+
//
|
|
2175
|
+
// Glyph vocabulary (senior-UI advice): a step that SUCCEEDED gets a green ✓ (with dim
|
|
2176
|
+
// text — a receded success), a FAILURE gets a red ✗ + full-bright text so it POPS (the
|
|
2177
|
+
// one thing not to miss), and a neutral/informational step keeps the dim ● . The marker
|
|
2178
|
+
// hangs in the gutter (markLine); a long bullet wraps with every continuation row
|
|
2179
|
+
// re-indented to the SAME gutter, so it shares one text column with the prompt/answer.
|
|
2180
|
+
const st = classifyBullet(line.trim());
|
|
2181
|
+
const bRows = wrapBlock(line.trim(), GUTTER, process.stdout.columns || 80);
|
|
2182
|
+
process.stdout.write(
|
|
2183
|
+
bRows
|
|
2184
|
+
.map((r, i) =>
|
|
2185
|
+
i === 0 ? markLine(st.markColor(st.mark)) + st.textColor(r) : GUTTER + st.textColor(r)
|
|
2186
|
+
)
|
|
2187
|
+
.join("\n") + "\n"
|
|
2188
|
+
);
|
|
2133
2189
|
}
|
|
2134
2190
|
lastWasBlank = false;
|
|
2135
2191
|
}
|
|
@@ -2137,7 +2193,8 @@ async function runAgentTurn(history) {
|
|
|
2137
2193
|
// reply) and can take many seconds to generate — without this, nothing would print
|
|
2138
2194
|
// until the whole thing finally lands, right back to the "wait then dump" problem
|
|
2139
2195
|
// this was meant to fix. Only safe in the plain-reply flow (see plainReplyFlow above).
|
|
2140
|
-
|
|
2196
|
+
// Buffered mode skips this entirely — the reply is reprinted wrapped at completion.
|
|
2197
|
+
if (STREAM_PLAIN_REPLY && plainReplyFlow && !inCode && carry.length > carryFlushedLen) {
|
|
2141
2198
|
if (carryFlushedLen === 0) onContent();
|
|
2142
2199
|
// Gutter (task #128) only at the pending line's START; a continuation appends bare.
|
|
2143
2200
|
process.stdout.write(
|
|
@@ -2158,8 +2215,10 @@ async function runAgentTurn(history) {
|
|
|
2158
2215
|
fullLiveThought = thoughtFull(fenceThought + carry); // #96: fuller thought for expand
|
|
2159
2216
|
}
|
|
2160
2217
|
// A non-empty remainder is a partial CONTENT line (heartbeats always arrive whole with
|
|
2161
|
-
// a newline), so tokens are actively flowing — keep the ticker asleep.
|
|
2162
|
-
|
|
2218
|
+
// a newline), so tokens are actively flowing — keep the ticker asleep. EXCEPT a buffered
|
|
2219
|
+
// plain reply: nothing prints during generation, so let the ticker keep animating
|
|
2220
|
+
// "Thinking…" (don't mark content as flowing) until the wrapped answer lands at completion.
|
|
2221
|
+
if (carry.trim() !== "" && !(plainReplyFlow && !STREAM_PLAIN_REPLY)) lastContentAt = Date.now();
|
|
2163
2222
|
};
|
|
2164
2223
|
|
|
2165
2224
|
try {
|
|
@@ -2248,11 +2307,14 @@ async function runAgentTurn(history) {
|
|
|
2248
2307
|
// nothing (the reported "Hello! How" cut) or a garbage tail. Verify with the
|
|
2249
2308
|
// seenRaw fingerprint; on a mismatch, recover by answer-prefix instead: print
|
|
2250
2309
|
// whatever of the final answer wasn't already shown live.
|
|
2310
|
+
// Buffered plain reply: nothing streamed live (answerShownLive stays false), so skip
|
|
2311
|
+
// the suffix reconciliation entirely — the caller reprints the whole reply WRAPPED from
|
|
2312
|
+
// resultText (shown=false), which is also more truncation-proof than the live-stream math.
|
|
2251
2313
|
const extendsStream = text.length >= shownChars && text.startsWith(seenRaw);
|
|
2252
|
-
if (plainReplyFlow && extendsStream && text.length > shownChars) {
|
|
2314
|
+
if (STREAM_PLAIN_REPLY && plainReplyFlow && extendsStream && text.length > shownChars) {
|
|
2253
2315
|
consume(text.slice(shownChars));
|
|
2254
2316
|
shownChars = text.length;
|
|
2255
|
-
} else if (plainReplyFlow && !extendsStream && answerShownLive) {
|
|
2317
|
+
} else if (STREAM_PLAIN_REPLY && plainReplyFlow && !extendsStream && answerShownLive) {
|
|
2256
2318
|
const ans = answerFrom(text);
|
|
2257
2319
|
let rest = ans.startsWith(liveAnswer) ? ans.slice(liveAnswer.length) : null;
|
|
2258
2320
|
if (rest === null) {
|
|
@@ -2380,7 +2442,23 @@ async function runAgentTurn(history) {
|
|
|
2380
2442
|
|
|
2381
2443
|
// ---------- REPL ----------
|
|
2382
2444
|
async function main() {
|
|
2383
|
-
|
|
2445
|
+
// Wrap the header so a narrow window doesn't soft-wrap "key wk_…" to column 0; keep the
|
|
2446
|
+
// "GoNext agent REPL" label cyan on row 0, dim the rest (and any wrapped continuation rows).
|
|
2447
|
+
{
|
|
2448
|
+
const LABEL = "GoNext agent REPL";
|
|
2449
|
+
const header = `${LABEL} v${REPL_VERSION} · API ${apiBase} · key ${workerKey.slice(0, 8)}…`;
|
|
2450
|
+
console.log(
|
|
2451
|
+
wrapBlock(header, GUTTER, process.stdout.columns || 80)
|
|
2452
|
+
.map((l, i) =>
|
|
2453
|
+
!l.length
|
|
2454
|
+
? l
|
|
2455
|
+
: i === 0 && l.startsWith(LABEL)
|
|
2456
|
+
? GUTTER + cyan(LABEL) + dim(l.slice(LABEL.length))
|
|
2457
|
+
: GUTTER + dim(l)
|
|
2458
|
+
)
|
|
2459
|
+
.join("\n")
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2384
2462
|
// Task #127, Phase 2: the REPL enqueues jobs that the polling DAEMON executes, so ensure
|
|
2385
2463
|
// one is running — no GoTerminal needed. Idempotent + BC4-safe: startDaemon() no-ops when
|
|
2386
2464
|
// a daemon (ours, GoTerminal's, or a manual one) is already alive. Best-effort; a failure
|
|
@@ -2427,14 +2505,15 @@ async function main() {
|
|
|
2427
2505
|
await saveSessionRagMode(resolve(process.cwd()), sessionRagMode);
|
|
2428
2506
|
}
|
|
2429
2507
|
console.log(
|
|
2430
|
-
|
|
2431
|
-
dim(
|
|
2508
|
+
wrapGutter(
|
|
2432
2509
|
`agent model: ${p.agentModelId || probe?.modelKey || "?"}` +
|
|
2433
2510
|
(p.codingModelId ? ` · coder: ${p.codingModelId}` : "") +
|
|
2434
2511
|
(p.ragEnabled ? ` · RAG ${sessionRagMode === "cloud" ? "cloud" : "local"}` : "") +
|
|
2435
2512
|
` · auto-test: ${sessionTestAuto ? "on" : "off"}` +
|
|
2436
|
-
(selectedServer ? ` · deploy: ${selectedServer.name} (${selectedServer.user}@${selectedServer.host})` : "")
|
|
2437
|
-
|
|
2513
|
+
(selectedServer ? ` · deploy: ${selectedServer.name} (${selectedServer.user}@${selectedServer.host})` : "") +
|
|
2514
|
+
(sessionTestAuto ? "" : " (/test-auto to enable)"),
|
|
2515
|
+
dim
|
|
2516
|
+
)
|
|
2438
2517
|
);
|
|
2439
2518
|
// Task #127, Phase 3: if the base chat model server isn't answering, offer to set it up
|
|
2440
2519
|
// (download + start mlx_lm.server). BC3: only runs when NOTHING answers — an already-up
|
|
@@ -2494,14 +2573,14 @@ async function main() {
|
|
|
2494
2573
|
}
|
|
2495
2574
|
process.exit(1);
|
|
2496
2575
|
}
|
|
2497
|
-
console.log(
|
|
2576
|
+
console.log(wrapGutter("Ask about this repo. Type / to see commands (Tab completes). Ctrl-C aborts a running turn.", dim) + "\n");
|
|
2498
2577
|
|
|
2499
2578
|
const cwd = resolve(process.cwd());
|
|
2500
2579
|
const history = await loadSession(cwd);
|
|
2501
2580
|
if (history.length > 0) {
|
|
2502
2581
|
const turns = history.filter((m) => m.role === "user").length;
|
|
2503
2582
|
console.log(
|
|
2504
|
-
|
|
2583
|
+
wrapGutter(`Resumed session: ${turns} prior turn(s) from last time in this folder — /reset to start fresh.`, dim) + "\n"
|
|
2505
2584
|
);
|
|
2506
2585
|
}
|
|
2507
2586
|
// 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.362",
|
|
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",
|