@tiens.nguyen/gu-cli 1.0.873 → 1.0.874
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/agent-model-command.mjs +8 -6
- package/embed-model-command.mjs +2 -2
- package/gu-repl.mjs +26 -17
- package/package.json +1 -1
package/agent-model-command.mjs
CHANGED
|
@@ -350,8 +350,8 @@ export function agentRecoveryOptions({ keyProblem = false, host = "" } = {}) {
|
|
|
350
350
|
return [
|
|
351
351
|
{ action: "leave", label: "leave it" },
|
|
352
352
|
...(keyProblem ? [{ action: "key", label: `enter the API key for ${host || "this provider"}…` }] : []),
|
|
353
|
-
|
|
354
|
-
{ action: "provider", label: "add a provider for the agent — Claude API, OpenAI, Kimi…" },
|
|
353
|
+
// #244: no separate Ollama row — Ollama is one of the providers on the add screen.
|
|
354
|
+
{ action: "provider", label: "add a provider for the agent — Claude API, OpenAI, Kimi, Ollama…" },
|
|
355
355
|
];
|
|
356
356
|
}
|
|
357
357
|
|
|
@@ -375,8 +375,9 @@ export function agentMenuOptions({ host = "", modelCount = 0, needsKey = false }
|
|
|
375
375
|
const where = host || "this server";
|
|
376
376
|
return [
|
|
377
377
|
{ action: "models", label: `pick another model on ${where}${modelCount ? ` (${modelCount})` : ""}` },
|
|
378
|
-
|
|
379
|
-
|
|
378
|
+
// #244: "use an Ollama server for the agent…" was a second way to the same add screen, left from
|
|
379
|
+
// when Ollama had its own setup (#41). Ollama, ollama.com included, is one provider among the rest.
|
|
380
|
+
{ action: "provider", label: "use another provider — Claude API, OpenAI, Kimi, Ollama…" },
|
|
380
381
|
// Task #245: the SAME key manager /model's ctrl+k and /embed-model open — switch, add, make
|
|
381
382
|
// default, delete — for this server's keys only.
|
|
382
383
|
...(needsKey ? [{ action: "key", label: `API keys for ${where} — switch, add or remove…` }] : []),
|
|
@@ -393,7 +394,8 @@ export function agentProviderChoices(settings) {
|
|
|
393
394
|
const current = ensureV1Root(String(settings?.agentModelUrl ?? "")).toLowerCase();
|
|
394
395
|
const out = [];
|
|
395
396
|
for (const row of rows) {
|
|
396
|
-
|
|
397
|
+
// Ollama rows too (#244): ollama.com or a box saved earlier is a provider like any other.
|
|
398
|
+
if (!row || (row.kind !== "openai" && row.kind !== "ollama") || !row.url) continue;
|
|
397
399
|
let host = String(row.url);
|
|
398
400
|
try {
|
|
399
401
|
host = new URL(String(row.url)).host;
|
|
@@ -403,7 +405,7 @@ export function agentProviderChoices(settings) {
|
|
|
403
405
|
const isCurrent = ensureV1Root(String(row.url)).toLowerCase() === current;
|
|
404
406
|
out.push({ action: "row", row, label: `${host}${isCurrent ? " (current)" : ""}` });
|
|
405
407
|
}
|
|
406
|
-
out.push({ action: "add", label: "add a new provider — Claude API, OpenAI, Kimi…" });
|
|
408
|
+
out.push({ action: "add", label: "add a new provider — Claude API, OpenAI, Kimi, Ollama…" });
|
|
407
409
|
return out;
|
|
408
410
|
}
|
|
409
411
|
|
package/embed-model-command.mjs
CHANGED
|
@@ -106,8 +106,8 @@ export function embedMenuOptions({ current = DEFAULT_EMBED_MODEL, host = "", key
|
|
|
106
106
|
const where = host || "this server";
|
|
107
107
|
return [
|
|
108
108
|
{ action: "model", label: `switch the embedder model (currently ${current})` },
|
|
109
|
-
|
|
110
|
-
{ action: "provider", label: "use another OpenAI
|
|
109
|
+
// #244: one row for every server — Ollama (this machine, the network) is a choice on the add screen.
|
|
110
|
+
{ action: "provider", label: "use another provider — OpenAI, Ollama…" },
|
|
111
111
|
...(keyRequired ? [{ action: "key", label: `enter a new API key for ${where}…` }] : []),
|
|
112
112
|
];
|
|
113
113
|
}
|
package/gu-repl.mjs
CHANGED
|
@@ -543,6 +543,9 @@ const color256 = (n, s) => `\x1b[38;5;${n}m${s}\x1b[0m`;
|
|
|
543
543
|
// WAIT_COLORS_256 colour only when this run was started with `gu --color`. White goes through
|
|
544
544
|
// paint(), so NO_COLOR / TERM=dumb still turn it off.
|
|
545
545
|
const waitPaint = (n, s) => (REPL_COLOR ? color256(n, s) : white(s));
|
|
546
|
+
// The live thought line above it (#245) follows the same switch: white by default, the accent
|
|
547
|
+
// cyan only with `gu --color`.
|
|
548
|
+
const thoughtPaint = (s) => (REPL_COLOR ? accent(s) : white(s));
|
|
546
549
|
const orange = (s) => color256(208, s); // peak-token label (task #84)
|
|
547
550
|
// Compact token count: 950 → "950", 12300 → "12.3k", 1500000 → "1.5M" (task #83).
|
|
548
551
|
const fmtTokens = (n) => {
|
|
@@ -636,7 +639,7 @@ if (cliVerdict.kind === "run") {
|
|
|
636
639
|
" -v, --verbose show RAG index-file activity in the bottom bar (each shard written)\n" +
|
|
637
640
|
" --log log this run in detail (~/.gonext/worker.log). Without it only starts,\n" +
|
|
638
641
|
" stops and errors are logged. Ends when this gu closes.\n" +
|
|
639
|
-
" --color colour the thinking spinner and its playful word
|
|
642
|
+
" --color colour the live thought, the thinking spinner and its playful word.\n" +
|
|
640
643
|
" Without it they are plain white.\n" +
|
|
641
644
|
" --name <text> show <text> instead of the gu logo in the startup banner\n" +
|
|
642
645
|
" (this run only; letters, digits, space - _ are drawn as art)\n" +
|
|
@@ -6642,6 +6645,8 @@ async function addServer({ role = "coding", dialect = "openai", url: presetUrl =
|
|
|
6642
6645
|
for (;;) {
|
|
6643
6646
|
// --- 1. the server --------------------------------------------------------------------
|
|
6644
6647
|
target = null;
|
|
6648
|
+
// The dialect the URL prompt reads the address in: the caller's, or Ollama's when that row was picked.
|
|
6649
|
+
let askDialect = dialect;
|
|
6645
6650
|
if (presetUrl) {
|
|
6646
6651
|
target = as.serverTarget(dialect, presetUrl);
|
|
6647
6652
|
if (!target.ok) {
|
|
@@ -6653,7 +6658,14 @@ async function addServer({ role = "coding", dialect = "openai", url: presetUrl =
|
|
|
6653
6658
|
// the one add flow, so every role that adds a provider offers the same list (the embedder
|
|
6654
6659
|
// without Claude, which serves no embeddings).
|
|
6655
6660
|
const presets = ps.providerPresets(role);
|
|
6656
|
-
|
|
6661
|
+
// #244: an Ollama on this machine or the network is a row here, like the hosted ones — the
|
|
6662
|
+
// menus no longer have a separate "use an Ollama server…" entrance. It asks for the address.
|
|
6663
|
+
const OLLAMA_HERE = presets.length;
|
|
6664
|
+
const rowsShown = [
|
|
6665
|
+
...presets.map((p) => `${p.name}${dim(` · ${p.url}`)}`),
|
|
6666
|
+
`Ollama — this machine or your network…${dim(" · e.g. 127.0.0.1:11434")}`,
|
|
6667
|
+
"another URL…",
|
|
6668
|
+
];
|
|
6657
6669
|
// A preset names its own dialect (Ollama cloud is "ollama"); the rest are OpenAI-compatible.
|
|
6658
6670
|
console.log(dim(` Which provider? (${PICK_HINT})`));
|
|
6659
6671
|
const pi = await pickFromList(rowsShown, 0, { back });
|
|
@@ -6663,11 +6675,12 @@ async function addServer({ role = "coding", dialect = "openai", url: presetUrl =
|
|
|
6663
6675
|
return;
|
|
6664
6676
|
}
|
|
6665
6677
|
if (pi < presets.length) target = as.serverTarget(presets[pi].dialect ?? "openai", presets[pi].url);
|
|
6678
|
+
else if (pi === OLLAMA_HERE) askDialect = "ollama";
|
|
6666
6679
|
}
|
|
6667
6680
|
if (!target && !presetUrl) {
|
|
6668
|
-
const dflt = as.defaultServerUrl(role,
|
|
6669
|
-
console.log(dim(
|
|
6670
|
-
? " An Ollama server: this machine, a box on your network, or
|
|
6681
|
+
const dflt = as.defaultServerUrl(role, askDialect, settings, existing);
|
|
6682
|
+
console.log(dim(askDialect === "ollama"
|
|
6683
|
+
? " An Ollama server: this machine, a box on your network, or ollama.com (with a key)."
|
|
6671
6684
|
: " Any OpenAI-compatible endpoint: OpenRouter, Together, vLLM, LM Studio, …"));
|
|
6672
6685
|
// The address goes ABOVE, in full, and Enter's meaning is said out loud (46d1524e).
|
|
6673
6686
|
console.log(dim(dflt ? ` Enter uses ${dflt} · Esc cancels\n` : " Esc cancels.\n"));
|
|
@@ -6676,9 +6689,9 @@ async function addServer({ role = "coding", dialect = "openai", url: presetUrl =
|
|
|
6676
6689
|
if (typed === null || as.isCancelWord(typed)) break;
|
|
6677
6690
|
const raw = typed.trim() || dflt;
|
|
6678
6691
|
if (!raw) break;
|
|
6679
|
-
const t = as.serverTarget(
|
|
6692
|
+
const t = as.serverTarget(askDialect, raw);
|
|
6680
6693
|
if (!t.ok) {
|
|
6681
|
-
if (
|
|
6694
|
+
if (askDialect === "openai") for (const l of ps.describeUrlProblem(t.why, raw)) console.log(GUTTER + dim(l));
|
|
6682
6695
|
else console.log(GUTTER + dim(`"${raw}" is ${t.why} — try host:port, e.g. 127.0.0.1:11434`));
|
|
6683
6696
|
continue;
|
|
6684
6697
|
}
|
|
@@ -8805,8 +8818,8 @@ async function chooseEmbedModel() {
|
|
|
8805
8818
|
await addServer({ role: "embed", dialect: "ollama", model: chosen, plan });
|
|
8806
8819
|
return;
|
|
8807
8820
|
}
|
|
8808
|
-
if (action === "
|
|
8809
|
-
if ((await addServer({ role: "embed", dialect:
|
|
8821
|
+
if (action === "provider") {
|
|
8822
|
+
if ((await addServer({ role: "embed", dialect: "openai", back: true })) === BACK) return chooseEmbedModel();
|
|
8810
8823
|
return;
|
|
8811
8824
|
}
|
|
8812
8825
|
|
|
@@ -9024,7 +9037,6 @@ async function chooseAgentModel() {
|
|
|
9024
9037
|
);
|
|
9025
9038
|
const action = go >= 0 ? options[go].action : "leave";
|
|
9026
9039
|
if (action === "key") await enterAgentProviderKey(source.row, listHost);
|
|
9027
|
-
else if (action === "ollama") await addServer({ role: "agent", dialect: "ollama" });
|
|
9028
9040
|
else if (action === "provider") await addServer({ role: "agent", dialect: "openai" });
|
|
9029
9041
|
else console.log("");
|
|
9030
9042
|
return;
|
|
@@ -9042,10 +9054,6 @@ async function chooseAgentModel() {
|
|
|
9042
9054
|
return;
|
|
9043
9055
|
}
|
|
9044
9056
|
// "← back" in any of these returns to this menu (#242) — read again, as on a first open.
|
|
9045
|
-
if (firstAction === "ollama") {
|
|
9046
|
-
if ((await addServer({ role: "agent", dialect: "ollama", back: true })) === BACK) return chooseAgentModel();
|
|
9047
|
-
return;
|
|
9048
|
-
}
|
|
9049
9057
|
if (firstAction === "provider") {
|
|
9050
9058
|
if ((await chooseAgentProvider({ back: true })) === BACK) return chooseAgentModel();
|
|
9051
9059
|
return;
|
|
@@ -12493,7 +12501,8 @@ async function runAgentTurn(history, { attachJobId = "", ownJob = false } = {})
|
|
|
12493
12501
|
// Only worth probing when the mouse is captured — thoughtRow exists for hover/click
|
|
12494
12502
|
// hit-testing, and with `/hover` off nothing reads it (and nothing would consume the reply).
|
|
12495
12503
|
if (hoverMouse && process.stdin.isTTY && now - lastDsrAt > 400) { lastDsrAt = now; ui("\x1b[6n"); }
|
|
12496
|
-
// Line 1:
|
|
12504
|
+
// Line 1 (#245: white by default; the cyan below only with `gu --color`, like the spinner row):
|
|
12505
|
+
// cyan ● + cyan phase/seconds — the logo's colour, which is what this REPL uses for
|
|
12497
12506
|
// WHAT IS LIVE RIGHT NOW: the ">> " prompt is cyan while you are typing and goes dim once
|
|
12498
12507
|
// submitted, and a selection is drawn on the same cyan. Green is left to mean SUCCEEDED (the
|
|
12499
12508
|
// ✓ bullets), which is what it means everywhere else here — a running step is not a finished
|
|
@@ -12512,7 +12521,7 @@ async function runAgentTurn(history, { attachJobId = "", ownJob = false } = {})
|
|
|
12512
12521
|
const clipRow = (r) => clipVisible(r, Math.max(20, process.stdout.columns || 80) - 1);
|
|
12513
12522
|
const contentRows = [];
|
|
12514
12523
|
for (let i = 0; i < LEAD_ROWS; i++) contentRows.push(""); // the separator — see LEAD_ROWS
|
|
12515
|
-
contentRows.push(clipRow(`${markLine(
|
|
12524
|
+
contentRows.push(clipRow(`${markLine(thoughtPaint(BULLET))}${hovering ? hoverThought(label) : thoughtPaint(label)}`));
|
|
12516
12525
|
for (const dr of detailRows) contentRows.push(clipRow(dim(dr)));
|
|
12517
12526
|
for (const wl of expLines) contentRows.push(clipRow(GUTTER + dim(wl)));
|
|
12518
12527
|
for (let i = 0; i < WORD_GAP; i++) contentRows.push(""); // see WORD_GAP
|
|
@@ -14182,7 +14191,7 @@ async function main() {
|
|
|
14182
14191
|
red("gu: this machine has no models set up yet.") +
|
|
14183
14192
|
dim(
|
|
14184
14193
|
"\n Nothing can run until it knows which Ollama server to use." +
|
|
14185
|
-
"\n Start `gu`, then `/agent-model` → use
|
|
14194
|
+
"\n Start `gu`, then `/agent-model` → use another provider → Ollama, `/embed-model` for RAG," +
|
|
14186
14195
|
"\n and `/model` → add a provider → another URL… for the coding model." +
|
|
14187
14196
|
`\n The agent server needs a ${REQUIRED_AGENT_PREFIX} model (e.g. ${REQUIRED_AGENT_MODEL})` +
|
|
14188
14197
|
`\n and any ONE of these embedders: ${ACCEPTED_EMBED_MODELS.map((m) => m.replace(/:latest$/, "")).join(", ")};` +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gu-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.874",
|
|
4
4
|
"description": "Gu CLI — the gu 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",
|