@tiens.nguyen/gu-cli 1.0.873 → 1.0.875
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 +56 -6
- package/embed-model-command.mjs +2 -2
- package/gu-repl.mjs +106 -22
- 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…` }] : []),
|
|
@@ -388,12 +389,61 @@ export function agentMenuOptions({ host = "", modelCount = 0, needsKey = false }
|
|
|
388
389
|
* rows, current one marked — then "add a new one". Switching to a provider you already set up must
|
|
389
390
|
* not mean typing its URL again. Returns [{ action: "row" | "add", row?, label }].
|
|
390
391
|
*/
|
|
392
|
+
/**
|
|
393
|
+
* EVERY MODEL ALREADY SET UP ON THIS MACHINE, for the agent (#246) — what /model offers the coder.
|
|
394
|
+
*
|
|
395
|
+
* Reported: /model listed several providers and models, and /agent-model showed none of them —
|
|
396
|
+
* it asked to add a provider or enter a key. They are the same servers with the same keys: a key
|
|
397
|
+
* belongs to its server, and the API finds the agent's key by matching agentModelUrl to a saved row
|
|
398
|
+
* (resolveKeyForRole). So a pick here saves that row's URL and the model, and its key comes along.
|
|
399
|
+
*
|
|
400
|
+
* Rows: OpenAI-compatible and Ollama, enabled, with a URL; their default model, then the extra
|
|
401
|
+
* models offered in /model. Embedding models are left out — as the agent they answer nonsense.
|
|
402
|
+
* → [{ rowId, kind, url, host, model, label, current }]
|
|
403
|
+
*/
|
|
404
|
+
export function savedAgentChoices(settings) {
|
|
405
|
+
const rows = Array.isArray(settings?.agentCodingBackends) ? settings.agentCodingBackends : [];
|
|
406
|
+
const currentUrl = ensureV1Root(String(settings?.agentModelUrl ?? "")).toLowerCase();
|
|
407
|
+
const currentModel = currentAgentModel(settings);
|
|
408
|
+
const out = [];
|
|
409
|
+
const seen = new Set();
|
|
410
|
+
const takenKinds = new Set();
|
|
411
|
+
for (const row of rows) {
|
|
412
|
+
if (!row || typeof row !== "object" || row.enabled === false) continue;
|
|
413
|
+
const kind = String(row.kind ?? "");
|
|
414
|
+
if (kind !== "openai" && kind !== "ollama") continue;
|
|
415
|
+
const url = ensureV1Root(String(row.url ?? "").trim());
|
|
416
|
+
if (!url) continue;
|
|
417
|
+
// The API's identity rule: an id-less row is known by its kind, the first of each only.
|
|
418
|
+
const rowId = String(row.id ?? "").trim() || (takenKinds.has(kind) ? "" : kind);
|
|
419
|
+
if (!rowId) continue;
|
|
420
|
+
takenKinds.add(kind);
|
|
421
|
+
let host = url;
|
|
422
|
+
try {
|
|
423
|
+
host = new URL(url).host;
|
|
424
|
+
} catch {
|
|
425
|
+
/* keep the url */
|
|
426
|
+
}
|
|
427
|
+
const models = [String(row.defaultModel ?? "").trim(), ...(Array.isArray(row.models) ? row.models.map((m) => String(m ?? "").trim()) : [])];
|
|
428
|
+
for (const model of models) {
|
|
429
|
+
if (!model || isLikelyEmbedder({ model })) continue;
|
|
430
|
+
const key = `${url.toLowerCase()}\n${model}`;
|
|
431
|
+
if (seen.has(key)) continue;
|
|
432
|
+
seen.add(key);
|
|
433
|
+
const current = url.toLowerCase() === currentUrl && model === currentModel;
|
|
434
|
+
out.push({ rowId, kind, url, host, model, label: `${host}/${model}`, current });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return out;
|
|
438
|
+
}
|
|
439
|
+
|
|
391
440
|
export function agentProviderChoices(settings) {
|
|
392
441
|
const rows = Array.isArray(settings?.agentCodingBackends) ? settings.agentCodingBackends : [];
|
|
393
442
|
const current = ensureV1Root(String(settings?.agentModelUrl ?? "")).toLowerCase();
|
|
394
443
|
const out = [];
|
|
395
444
|
for (const row of rows) {
|
|
396
|
-
|
|
445
|
+
// Ollama rows too (#244): ollama.com or a box saved earlier is a provider like any other.
|
|
446
|
+
if (!row || (row.kind !== "openai" && row.kind !== "ollama") || !row.url) continue;
|
|
397
447
|
let host = String(row.url);
|
|
398
448
|
try {
|
|
399
449
|
host = new URL(String(row.url)).host;
|
|
@@ -403,7 +453,7 @@ export function agentProviderChoices(settings) {
|
|
|
403
453
|
const isCurrent = ensureV1Root(String(row.url)).toLowerCase() === current;
|
|
404
454
|
out.push({ action: "row", row, label: `${host}${isCurrent ? " (current)" : ""}` });
|
|
405
455
|
}
|
|
406
|
-
out.push({ action: "add", label: "add a new provider — Claude API, OpenAI, Kimi…" });
|
|
456
|
+
out.push({ action: "add", label: "add a new provider — Claude API, OpenAI, Kimi, Ollama…" });
|
|
407
457
|
return out;
|
|
408
458
|
}
|
|
409
459
|
|
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
|
|
|
@@ -8944,6 +8957,78 @@ async function chooseAgentProvider({ back = false } = {}) {
|
|
|
8944
8957
|
* The decisions live in agent-model-command.mjs; this is the terminal half only.
|
|
8945
8958
|
*/
|
|
8946
8959
|
async function chooseAgentModel() {
|
|
8960
|
+
const am = await import("./agent-model-command.mjs");
|
|
8961
|
+
const settings = await withPendingStatus("reading your agent model", fetchWorkerSettings);
|
|
8962
|
+
if (!settings) {
|
|
8963
|
+
console.log(red(" couldn't reach the API to read your current agent model.\n"));
|
|
8964
|
+
return;
|
|
8965
|
+
}
|
|
8966
|
+
// WHAT /model ALREADY HAS, FIRST (#246). The providers and models added for the coder are the
|
|
8967
|
+
// same servers with the same keys; listing them here means switching the agent to one of them is
|
|
8968
|
+
// one pick, not "add a provider" or "enter the API key" for something already set up.
|
|
8969
|
+
const saved = am.savedAgentChoices(settings);
|
|
8970
|
+
if (!saved.length) return agentModelOnServer();
|
|
8971
|
+
const current = am.currentAgentModel(settings);
|
|
8972
|
+
const curHost = settings.agentModelUrl ? hostOf(String(settings.agentModelUrl)) : "";
|
|
8973
|
+
for (;;) {
|
|
8974
|
+
const labels = saved.map((c) => c.label + (c.current ? accent(" (current)") : ""));
|
|
8975
|
+
const MORE = labels.length;
|
|
8976
|
+
labels.push("other models, providers and API keys…");
|
|
8977
|
+
const ADD = labels.length;
|
|
8978
|
+
labels.push("add a provider — Claude API, OpenAI, Kimi, Ollama…");
|
|
8979
|
+
// The hint drops to its own row when the header would wrap (80 columns over SSH).
|
|
8980
|
+
const head = ` Agent model — the one that TALKS (now ${current || "unset"}${curHost ? ` on ${curHost}` : ""})`;
|
|
8981
|
+
const hint = `(${PICK_HINT}):`;
|
|
8982
|
+
const cols = Math.max(20, process.stdout.columns || 80);
|
|
8983
|
+
console.log(dim(head.length + 2 + hint.length < cols ? `${head} ${hint}` : `${head}\n ${hint}`));
|
|
8984
|
+
const idx = await pickFromList(labels, Math.max(0, saved.findIndex((c) => c.current)));
|
|
8985
|
+
if (idx < 0) {
|
|
8986
|
+
console.log(dim(" (cancelled)\n"));
|
|
8987
|
+
return;
|
|
8988
|
+
}
|
|
8989
|
+
if (idx === MORE) {
|
|
8990
|
+
if ((await agentModelOnServer({ back: true })) === BACK) continue;
|
|
8991
|
+
return;
|
|
8992
|
+
}
|
|
8993
|
+
if (idx === ADD) {
|
|
8994
|
+
if ((await addServer({ role: "agent", dialect: "openai", back: true })) === BACK) continue;
|
|
8995
|
+
return;
|
|
8996
|
+
}
|
|
8997
|
+
await useSavedAgentChoice(saved[idx]);
|
|
8998
|
+
return;
|
|
8999
|
+
}
|
|
9000
|
+
}
|
|
9001
|
+
|
|
9002
|
+
/**
|
|
9003
|
+
* Point the agent at a model already set up for /model (#246): that row's URL and the model. The
|
|
9004
|
+
* API finds the key by the URL (resolveKeyForRole), so nothing is re-entered — unless the server
|
|
9005
|
+
* needs a key and has none, which is asked here rather than failing on the next question.
|
|
9006
|
+
*/
|
|
9007
|
+
async function useSavedAgentChoice(c) {
|
|
9008
|
+
if (c.current) {
|
|
9009
|
+
console.log(dim(` already using ${c.model} on ${c.host} — nothing changed.\n`));
|
|
9010
|
+
return;
|
|
9011
|
+
}
|
|
9012
|
+
const as = await import("./add-server.mjs");
|
|
9013
|
+
const target = as.serverTarget(c.kind === "ollama" ? "ollama" : "openai", c.url);
|
|
9014
|
+
const keys = (await withPendingStatus(`reading the keys for ${c.host}`, () => fetchServerKeys(c.rowId))) ?? [];
|
|
9015
|
+
if (target.ok && as.keyRequirement(target) === "required" && !keys.length) {
|
|
9016
|
+
console.log(GUTTER + dim(`${c.host} needs an API key and none is saved for it — enter one.`));
|
|
9017
|
+
await addServer({ role: "agent", dialect: target.dialect, url: c.url, newKey: true });
|
|
9018
|
+
return;
|
|
9019
|
+
}
|
|
9020
|
+
// A folder pick of ANOTHER server's key would be sent to this one: this folder follows the
|
|
9021
|
+
// server's own key from now on.
|
|
9022
|
+
if (sessionAgentKeyId && !keys.some((k) => k.id === sessionAgentKeyId)) await saveFolderKeyPick("agent", "");
|
|
9023
|
+
await saveAgentModel(c.url, c.model, c.host);
|
|
9024
|
+
}
|
|
9025
|
+
|
|
9026
|
+
/**
|
|
9027
|
+
* One server's view: its full model list, other providers, its keys — what /agent-model was before
|
|
9028
|
+
* #246, now behind "other models, providers and API keys…" when something is already set up.
|
|
9029
|
+
* `back`: opened from that list, so its first menu offers "← back".
|
|
9030
|
+
*/
|
|
9031
|
+
async function agentModelOnServer({ back = false } = {}) {
|
|
8947
9032
|
const am = await import("./agent-model-command.mjs");
|
|
8948
9033
|
const settings = await withPendingStatus("reading your agent model", fetchWorkerSettings);
|
|
8949
9034
|
if (!settings) {
|
|
@@ -9020,11 +9105,12 @@ async function chooseAgentModel() {
|
|
|
9020
9105
|
const options = am.agentRecoveryOptions({ keyProblem, host: listHost });
|
|
9021
9106
|
const go = await pickFromList(
|
|
9022
9107
|
options.map((o, i) => (i === 0 ? dim(o.label) : o.label)),
|
|
9023
|
-
keyProblem ? 1 : 0
|
|
9108
|
+
keyProblem ? 1 : 0,
|
|
9109
|
+
{ back }
|
|
9024
9110
|
);
|
|
9111
|
+
if (go === PICK_BACK) return BACK;
|
|
9025
9112
|
const action = go >= 0 ? options[go].action : "leave";
|
|
9026
9113
|
if (action === "key") await enterAgentProviderKey(source.row, listHost);
|
|
9027
|
-
else if (action === "ollama") await addServer({ role: "agent", dialect: "ollama" });
|
|
9028
9114
|
else if (action === "provider") await addServer({ role: "agent", dialect: "openai" });
|
|
9029
9115
|
else console.log("");
|
|
9030
9116
|
return;
|
|
@@ -9035,19 +9121,16 @@ async function chooseAgentModel() {
|
|
|
9035
9121
|
// are all you can choose". A short menu of what to change comes before any list.
|
|
9036
9122
|
console.log(dim(` Agent model — the one that TALKS: ${current || "unset"} on ${listHost}`));
|
|
9037
9123
|
const menu = am.agentMenuOptions({ host: listHost, modelCount: models.length, needsKey });
|
|
9038
|
-
const first = await pickFromList(menu.map((o) => o.label), 0);
|
|
9124
|
+
const first = await pickFromList(menu.map((o) => o.label), 0, { back });
|
|
9125
|
+
if (first === PICK_BACK) return BACK;
|
|
9039
9126
|
const firstAction = first >= 0 ? menu[first].action : "cancel";
|
|
9040
9127
|
if (firstAction === "cancel") {
|
|
9041
9128
|
console.log(dim(" (cancelled)\n"));
|
|
9042
9129
|
return;
|
|
9043
9130
|
}
|
|
9044
9131
|
// "← 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
9132
|
if (firstAction === "provider") {
|
|
9050
|
-
if ((await chooseAgentProvider({ back: true })) === BACK) return
|
|
9133
|
+
if ((await chooseAgentProvider({ back: true })) === BACK) return agentModelOnServer({ back });
|
|
9051
9134
|
return;
|
|
9052
9135
|
}
|
|
9053
9136
|
if (firstAction === "key") {
|
|
@@ -9055,7 +9138,7 @@ async function chooseAgentModel() {
|
|
|
9055
9138
|
const rowId = String(source.row?.id ?? "").trim() || String(source.row?.kind ?? "").trim();
|
|
9056
9139
|
if (!rowId) return enterAgentProviderKey(source.row, listHost);
|
|
9057
9140
|
const r = await manageServerKeys({ role: "agent", backendId: rowId, host: listHost, model: am.currentAgentModel(settings), back: true });
|
|
9058
|
-
if (r === BACK) return
|
|
9141
|
+
if (r === BACK) return agentModelOnServer({ back });
|
|
9059
9142
|
return;
|
|
9060
9143
|
}
|
|
9061
9144
|
|
|
@@ -9088,7 +9171,7 @@ async function chooseAgentModel() {
|
|
|
9088
9171
|
// No other entries here any more (#44): the ways to change the provider are on the menu above,
|
|
9089
9172
|
// where they can be seen, rather than after every model.
|
|
9090
9173
|
const idx = await pickFromList(labels, Math.max(0, rows.findIndex((r) => r.current)), { back: true });
|
|
9091
|
-
if (idx === PICK_BACK) return
|
|
9174
|
+
if (idx === PICK_BACK) return agentModelOnServer({ back });
|
|
9092
9175
|
if (idx < 0) {
|
|
9093
9176
|
console.log(dim(" (cancelled)\n"));
|
|
9094
9177
|
return;
|
|
@@ -12493,7 +12576,8 @@ async function runAgentTurn(history, { attachJobId = "", ownJob = false } = {})
|
|
|
12493
12576
|
// Only worth probing when the mouse is captured — thoughtRow exists for hover/click
|
|
12494
12577
|
// hit-testing, and with `/hover` off nothing reads it (and nothing would consume the reply).
|
|
12495
12578
|
if (hoverMouse && process.stdin.isTTY && now - lastDsrAt > 400) { lastDsrAt = now; ui("\x1b[6n"); }
|
|
12496
|
-
// Line 1:
|
|
12579
|
+
// Line 1 (#245: white by default; the cyan below only with `gu --color`, like the spinner row):
|
|
12580
|
+
// cyan ● + cyan phase/seconds — the logo's colour, which is what this REPL uses for
|
|
12497
12581
|
// WHAT IS LIVE RIGHT NOW: the ">> " prompt is cyan while you are typing and goes dim once
|
|
12498
12582
|
// submitted, and a selection is drawn on the same cyan. Green is left to mean SUCCEEDED (the
|
|
12499
12583
|
// ✓ bullets), which is what it means everywhere else here — a running step is not a finished
|
|
@@ -12512,7 +12596,7 @@ async function runAgentTurn(history, { attachJobId = "", ownJob = false } = {})
|
|
|
12512
12596
|
const clipRow = (r) => clipVisible(r, Math.max(20, process.stdout.columns || 80) - 1);
|
|
12513
12597
|
const contentRows = [];
|
|
12514
12598
|
for (let i = 0; i < LEAD_ROWS; i++) contentRows.push(""); // the separator — see LEAD_ROWS
|
|
12515
|
-
contentRows.push(clipRow(`${markLine(
|
|
12599
|
+
contentRows.push(clipRow(`${markLine(thoughtPaint(BULLET))}${hovering ? hoverThought(label) : thoughtPaint(label)}`));
|
|
12516
12600
|
for (const dr of detailRows) contentRows.push(clipRow(dim(dr)));
|
|
12517
12601
|
for (const wl of expLines) contentRows.push(clipRow(GUTTER + dim(wl)));
|
|
12518
12602
|
for (let i = 0; i < WORD_GAP; i++) contentRows.push(""); // see WORD_GAP
|
|
@@ -14182,7 +14266,7 @@ async function main() {
|
|
|
14182
14266
|
red("gu: this machine has no models set up yet.") +
|
|
14183
14267
|
dim(
|
|
14184
14268
|
"\n Nothing can run until it knows which Ollama server to use." +
|
|
14185
|
-
"\n Start `gu`, then `/agent-model` → use
|
|
14269
|
+
"\n Start `gu`, then `/agent-model` → use another provider → Ollama, `/embed-model` for RAG," +
|
|
14186
14270
|
"\n and `/model` → add a provider → another URL… for the coding model." +
|
|
14187
14271
|
`\n The agent server needs a ${REQUIRED_AGENT_PREFIX} model (e.g. ${REQUIRED_AGENT_MODEL})` +
|
|
14188
14272
|
`\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.875",
|
|
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",
|