@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.
@@ -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
- { action: "ollama", label: "use an Ollama server for the agent…" },
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
- { action: "ollama", label: "use an Ollama server for the agent…" },
379
- { action: "provider", label: "use another provider — Claude API, OpenAI, Kimi…" },
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
- if (!row || row.kind !== "openai" || !row.url) continue;
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
 
@@ -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
- { action: "ollama", label: "use an Ollama server for embedding…" },
110
- { action: "provider", label: "use another OpenAI-compatible provider…" },
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 (cycling colours).\n" +
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
- const rowsShown = [...presets.map((p) => `${p.name}${dim(` · ${p.url}`)}`), "another URL…"];
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, dialect, settings, existing);
6669
- console.log(dim(dialect === "ollama"
6670
- ? " An Ollama server: this machine, a box on your network, or https://ollama.com (needs a key)."
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(dialect, raw);
6692
+ const t = as.serverTarget(askDialect, raw);
6680
6693
  if (!t.ok) {
6681
- if (dialect === "openai") for (const l of ps.describeUrlProblem(t.why, raw)) console.log(GUTTER + dim(l));
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 === "ollama" || action === "provider") {
8809
- if ((await addServer({ role: "embed", dialect: action === "ollama" ? "ollama" : "openai", back: true })) === BACK) return chooseEmbedModel();
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: cyan ● + cyan phase/seconds — the logo's colour, which is what this REPL uses for
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(accent(BULLET))}${hovering ? hoverThought(label) : accent(label)}`));
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 an Ollama server, `/embed-model` for RAG," +
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.873",
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",