@tiens.nguyen/gu-cli 1.0.696 → 1.0.697
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/coding-choice-tags.mjs +33 -0
- package/coding-key-prompt.mjs +7 -5
- package/gu-repl.mjs +42 -11
- package/package.json +1 -1
package/coding-choice-tags.mjs
CHANGED
|
@@ -67,3 +67,36 @@ export function codingChoiceTags(choices) {
|
|
|
67
67
|
});
|
|
68
68
|
return { tags, show: new Set(tags).size > 1 };
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* WHERE AN OLLAMA CODER RUNS — "ollama.com", "127.0.0.1", "ollama1.gomarsic.cc" — or "" for any
|
|
73
|
+
* other kind.
|
|
74
|
+
*
|
|
75
|
+
* Reported: "/model ✓ coding model → kimi-k3 (ollama)". The tag was the API's provider label, the
|
|
76
|
+
* first word of the host, so https://ollama.com read "ollama" — the same word as the KIND — and a
|
|
77
|
+
* paid cloud model looked exactly like one on this machine. Whether it is local or cloud decides
|
|
78
|
+
* whether it costs money and needs a key, so it is shown as a prefix wherever the coder is named.
|
|
79
|
+
*
|
|
80
|
+
* The Ollama default port (11434) is left off; any other port is kept, because two local servers on
|
|
81
|
+
* different ports are different servers.
|
|
82
|
+
*/
|
|
83
|
+
export function ollamaWhere(kind, url) {
|
|
84
|
+
if (kind !== "ollama") return "";
|
|
85
|
+
let u;
|
|
86
|
+
try {
|
|
87
|
+
u = new URL(String(url ?? ""));
|
|
88
|
+
} catch {
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
const host = u.hostname.toLowerCase().replace(/\.$/, "").replace(/^\[|\]$/g, "");
|
|
92
|
+
if (host === "ollama.com" || host.endsWith(".ollama.com")) return "ollama.com";
|
|
93
|
+
const local = host === "localhost" || host === "127.0.0.1" || host === "::1";
|
|
94
|
+
const port = u.port && u.port !== "11434" ? `:${u.port}` : "";
|
|
95
|
+
return (local ? "127.0.0.1" : host) + port;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The coder's name as shown: "ollama.com/kimi-k3" for an Ollama coder, the bare name otherwise. */
|
|
99
|
+
export function coderDisplayName(name, where) {
|
|
100
|
+
const n = String(name ?? "").trim();
|
|
101
|
+
return where && n ? `${where}/${n}` : n;
|
|
102
|
+
}
|
package/coding-key-prompt.mjs
CHANGED
|
@@ -141,8 +141,10 @@ export async function promptCodingKeyIfMissing(payload, { askSecret, saveCodingK
|
|
|
141
141
|
*
|
|
142
142
|
* There is a coder either way; what differs is whether it can be USED yet. Name it, and say so.
|
|
143
143
|
*/
|
|
144
|
-
export function codingBannerLabel(payload, { dim = identity, override = "" } = {}) {
|
|
144
|
+
export function codingBannerLabel(payload, { dim = identity, override = "", where = "" } = {}) {
|
|
145
145
|
const p = payload ?? {};
|
|
146
|
+
// "ollama.com/kimi-k3" rather than "kimi-k3": local or cloud, visible in the header too.
|
|
147
|
+
const named = (n) => (where && n ? `${where}/${n}` : n);
|
|
146
148
|
// A `/model` PICK WINS OVER THE ACCOUNT DEFAULT, because it is what the next turn will
|
|
147
149
|
// actually use. The payload carries the account's coder; the override is this folder's,
|
|
148
150
|
// restored from its session file at startup. Reading only the payload put two different
|
|
@@ -152,15 +154,15 @@ export function codingBannerLabel(payload, { dim = identity, override = "" } = {
|
|
|
152
154
|
// Only possible since the pick started surviving an exit; before that the override was always
|
|
153
155
|
// empty at startup and the two could not disagree.
|
|
154
156
|
const pick = String(override ?? "").trim();
|
|
155
|
-
if (pick) return pick;
|
|
156
|
-
if (p.codingModelId) return String(p.codingModelId);
|
|
157
|
+
if (pick) return named(pick);
|
|
158
|
+
if (p.codingModelId) return named(String(p.codingModelId));
|
|
157
159
|
const name = String(p.codingKeyMissing ? p.codingKeyModel || "" : "").trim();
|
|
158
160
|
if (!name) return dim("—");
|
|
159
161
|
// A keyless host is never asked for anything — the prompt stores the placeholder itself and
|
|
160
162
|
// the coder is live moments later, so "(needs a key)" would be a lie with a shelf life of
|
|
161
163
|
// about one line.
|
|
162
|
-
if (isKeylessCodingHost(p.codingKeyUrl)) return name;
|
|
163
|
-
return name + dim(" (needs a key)");
|
|
164
|
+
if (isKeylessCodingHost(p.codingKeyUrl)) return named(name);
|
|
165
|
+
return named(name) + dim(" (needs a key)");
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
/**
|
package/gu-repl.mjs
CHANGED
|
@@ -113,7 +113,7 @@ import {
|
|
|
113
113
|
} from "./terminal-layout.mjs";
|
|
114
114
|
import { completedFlush } from "./completed-flush.mjs";
|
|
115
115
|
import { codingBannerLabel, SECRET_KEY_HINT } from "./coding-key-prompt.mjs";
|
|
116
|
-
import { codingChoiceTags } from "./coding-choice-tags.mjs";
|
|
116
|
+
import { codingChoiceTags, coderDisplayName, ollamaWhere } from "./coding-choice-tags.mjs";
|
|
117
117
|
import {
|
|
118
118
|
isDoubleClick,
|
|
119
119
|
clickRun,
|
|
@@ -1973,11 +1973,14 @@ let keyChoicePending = false;
|
|
|
1973
1973
|
const identityLineText = () =>
|
|
1974
1974
|
identityRow(
|
|
1975
1975
|
{
|
|
1976
|
-
coder:
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1976
|
+
coder: coderDisplayName(
|
|
1977
|
+
activeCoderName({
|
|
1978
|
+
sessionLabel: sessionCodingLabel,
|
|
1979
|
+
sessionModel: sessionCodingModel,
|
|
1980
|
+
accountDefault: defaultCodingModel,
|
|
1981
|
+
}),
|
|
1982
|
+
activeCoderWhere,
|
|
1983
|
+
),
|
|
1981
1984
|
folder: folderLabel(),
|
|
1982
1985
|
keyUnchosen: keyChoicePending,
|
|
1983
1986
|
},
|
|
@@ -3480,7 +3483,15 @@ if (process.stdin.isTTY) {
|
|
|
3480
3483
|
// a running turn owns the screen, and a masked prompt drawn over either corrupts both.
|
|
3481
3484
|
if (key && key.ctrl && key.name === "k" && !slashOverlayActive && !following
|
|
3482
3485
|
&& !listPickerActive && !approvalActive) {
|
|
3483
|
-
void
|
|
3486
|
+
// NOT a bare `void`: this runs outside the command loop that turns Esc into "(cancelled)",
|
|
3487
|
+
// so an Esc at the key's name or secret escaped as an unhandled rejection and ENDED THE
|
|
3488
|
+
// PROCESS (reported: "PromptCancelled: cancelled … Node.js v22"). Cancel is caught here; any
|
|
3489
|
+
// other failure is reported and the session goes on — a keypress must not kill the REPL.
|
|
3490
|
+
replaceCodingKey().catch((e) => {
|
|
3491
|
+
if (wasPromptCancelled(e)) console.log(dim(" (cancelled)\n"));
|
|
3492
|
+
else console.log(red(` ctrl+k failed: ${e?.message ?? e}\n`));
|
|
3493
|
+
paintBar();
|
|
3494
|
+
});
|
|
3484
3495
|
return;
|
|
3485
3496
|
}
|
|
3486
3497
|
if (key && key.ctrl && key.name === "t" && !slashOverlayActive) {
|
|
@@ -5780,8 +5791,11 @@ async function chooseModel() {
|
|
|
5780
5791
|
// OpenAI-compatible providers are both "OpenAI-compatible", so the kind names neither.
|
|
5781
5792
|
const { tags, show: showTag } = codingChoiceTags(choices);
|
|
5782
5793
|
const labels = choices.map((c, i) => {
|
|
5783
|
-
|
|
5784
|
-
|
|
5794
|
+
// An Ollama row says WHERE it runs in front of the name ("ollama.com/kimi-k3"), so the kind tag
|
|
5795
|
+
// beside it would only repeat "Ollama". Other rows keep the tag rule.
|
|
5796
|
+
const where = ollamaWhere(c.kind, c.url);
|
|
5797
|
+
const tag = showTag && !where ? ` ${dim(`· ${tags[i]}`)}` : "";
|
|
5798
|
+
return `${coderDisplayName(c.model, where)}${tag}${c.id === def ? " (default)" : ""}`;
|
|
5785
5799
|
});
|
|
5786
5800
|
const activeIdx = choices.findIndex((c) => c.id === active);
|
|
5787
5801
|
labels.push("change the Ollama server this machine uses…");
|
|
@@ -5827,9 +5841,12 @@ async function chooseModel() {
|
|
|
5827
5841
|
// WRITTEN NOW, not at the end of the next turn: `/model` then `/exit` is how the choice is
|
|
5828
5842
|
// made and then tested, and that path never reaches saveSession.
|
|
5829
5843
|
await saveSessionCoding(resolve(process.cwd()));
|
|
5830
|
-
|
|
5844
|
+
activeCoderWhere = ollamaWhere(chosen.kind, chosen.url);
|
|
5845
|
+
const where = showTag && !activeCoderWhere ? ` (${tags[idx]})` : "";
|
|
5831
5846
|
console.log(
|
|
5832
|
-
accent(
|
|
5847
|
+
accent(
|
|
5848
|
+
` ✓ coding model → ${coderDisplayName(chosen.model, activeCoderWhere)}${where}${chosen.id === def ? " (default)" : ""}\n`
|
|
5849
|
+
)
|
|
5833
5850
|
);
|
|
5834
5851
|
// DOES THIS ONE NEED AN API KEY? (task #181) Reported live on Windows 11 / Client Expert:
|
|
5835
5852
|
// picking kimi-k3 here and asking a question just span — the coder needs a key, none was
|
|
@@ -7669,6 +7686,8 @@ let defaultCodingModel = "";
|
|
|
7669
7686
|
// another backend must not keep showing the previous backend's total). Empty = let the API
|
|
7670
7687
|
// fall back to the account-default URL from settings (older behaviour).
|
|
7671
7688
|
let sessionCodingUrl = "";
|
|
7689
|
+
/** "ollama.com" / "127.0.0.1" / a host — where the ACTIVE coder runs, when it is an Ollama one. */
|
|
7690
|
+
let activeCoderWhere = "";
|
|
7672
7691
|
// The account-default backend's URL (from the startup probe's codingChoices), used when no
|
|
7673
7692
|
// /model override is active. Set once at startup alongside defaultCodingModel.
|
|
7674
7693
|
let defaultCodingUrl = "";
|
|
@@ -9329,6 +9348,17 @@ async function main() {
|
|
|
9329
9348
|
sessionCodingUrl = "";
|
|
9330
9349
|
}
|
|
9331
9350
|
}
|
|
9351
|
+
{
|
|
9352
|
+
// WHERE THE CODER RUNS, for the banner and the bar: the folder's pick, else the default.
|
|
9353
|
+
const choices = Array.isArray(probe?.codingChoices) ? probe.codingChoices : [];
|
|
9354
|
+
const active =
|
|
9355
|
+
(sessionCodingModel && choices.find((c) => c.id === sessionCodingModel || c.model === sessionCodingModel)) ||
|
|
9356
|
+
choices.find((c) => c.isDefault) ||
|
|
9357
|
+
null;
|
|
9358
|
+
// No choice list (an older API): no prefix, rather than guess from the ACCOUNT default's raw
|
|
9359
|
+
// payload fields, which key-choice-regression.test.mjs forbids for exactly that reason.
|
|
9360
|
+
activeCoderWhere = active ? ollamaWhere(active.kind, active.url) : "";
|
|
9361
|
+
}
|
|
9332
9362
|
// Task #133: no RAG storage question any more — the terminal ALWAYS keeps the index on
|
|
9333
9363
|
// this machine. `/rag-cloud` opts into additionally mirroring it to the user's S3 bucket.
|
|
9334
9364
|
// Startup banner (task #131): the gu logo on the left, compact status facts on the
|
|
@@ -9377,6 +9407,7 @@ async function main() {
|
|
|
9377
9407
|
sessionLabel: sessionCodingLabel,
|
|
9378
9408
|
sessionModel: sessionCodingModel,
|
|
9379
9409
|
}),
|
|
9410
|
+
where: activeCoderWhere,
|
|
9380
9411
|
}),
|
|
9381
9412
|
lbl("RAG") +
|
|
9382
9413
|
(p.ragEnabled
|
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.697",
|
|
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",
|