@tiens.nguyen/gu-cli 1.0.741 → 1.0.743
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-key-prompt.mjs +21 -6
- package/gu-repl.mjs +8 -3
- package/package.json +1 -1
- package/terminal-layout.mjs +25 -0
package/coding-key-prompt.mjs
CHANGED
|
@@ -24,6 +24,8 @@ const identity = (s) => s;
|
|
|
24
24
|
* and on Ctrl+K to replace one — and they were two hand-written copies of the same idea. A hint
|
|
25
25
|
* added to one of them would have left the other exactly as confusing.
|
|
26
26
|
*/
|
|
27
|
+
import { keyInUse } from "./server-keys.mjs";
|
|
28
|
+
|
|
27
29
|
export const SECRET_KEY_HINT = "type or paste the key, then press Enter — it stays masked";
|
|
28
30
|
|
|
29
31
|
/**
|
|
@@ -217,13 +219,26 @@ export function codingKeyFallbackLine(kj, jobId = "") {
|
|
|
217
219
|
* when a choice HAS been made, and when only one key exists — with one there is nothing to
|
|
218
220
|
* choose, and a warning on a correct setup is how warnings stop being read.
|
|
219
221
|
*/
|
|
220
|
-
export function unchosenKeyWarning({ codingKind = "", codingUrl = "", selectedKeyId = "", keys = null } = {}) {
|
|
222
|
+
export function unchosenKeyWarning({ codingKind = "", codingUrl = "", selectedKeyId = "", keys = null, backendId = "" } = {}) {
|
|
221
223
|
const quiet = { warn: false, using: null, why: "" };
|
|
222
224
|
// Local Ollama and MLX need no credential; the Ollama cloud does.
|
|
223
225
|
if (!codingNeedsKey(codingKind, codingUrl)) return quiet;
|
|
224
|
-
if (!Array.isArray(keys)
|
|
226
|
+
if (!Array.isArray(keys)) return quiet;
|
|
225
227
|
if (String(selectedKeyId ?? "").trim()) return quiet;
|
|
226
|
-
|
|
228
|
+
// KEYS BELONG TO A SERVER (#245). Reported: the bar said "key not chosen" beside ollama.com while
|
|
229
|
+
// the key picker showed its one key, hsbc_ollama, "(in use)" — because this counted all seven keys
|
|
230
|
+
// on the ACCOUNT (Anthropic, OpenAI, …) where the API only ever chooses among THIS server's keys.
|
|
231
|
+
// With the server known: its keys, else the keys tied to no server; and a server default IS a
|
|
232
|
+
// choice. Without it (an older caller) the old account-wide count stands.
|
|
233
|
+
if (backendId) {
|
|
234
|
+
const own = keys.filter((k) => k && k.backendId === backendId);
|
|
235
|
+
const pool = own.length ? own : keys.filter((k) => k && !k.backendId);
|
|
236
|
+
if (own.some((k) => k.default)) return quiet;
|
|
237
|
+
if (pool.length < 2) return quiet;
|
|
238
|
+
return { warn: true, using: keyInUse(keys, backendId) ?? pool[0], why: "none-chosen", count: pool.length };
|
|
239
|
+
}
|
|
240
|
+
if (keys.length < 2) return quiet;
|
|
241
|
+
return { warn: true, using: keys[0], why: "none-chosen", count: keys.length };
|
|
227
242
|
}
|
|
228
243
|
|
|
229
244
|
/**
|
|
@@ -245,14 +260,14 @@ export function unchosenKeyWarning({ codingKind = "", codingUrl = "", selectedKe
|
|
|
245
260
|
* qualified ("<kind>::<model>", task #123) stores the bare form and is still a valid pick.
|
|
246
261
|
*/
|
|
247
262
|
/** The folder's coder — its kind AND its URL, which together decide whether it needs a key. */
|
|
248
|
-
export function effectiveCoder({ sessionCodingModel = "", choices = null, defaultKind = "", defaultUrl = "" } = {}) {
|
|
263
|
+
export function effectiveCoder({ sessionCodingModel = "", choices = null, defaultKind = "", defaultUrl = "", defaultBackendId = "" } = {}) {
|
|
249
264
|
const want = String(sessionCodingModel ?? "").trim();
|
|
250
265
|
const list = Array.isArray(choices) ? choices : [];
|
|
251
266
|
if (want) {
|
|
252
267
|
const hit = list.find((c) => c?.id === want || c?.model === want);
|
|
253
|
-
if (hit) return { kind: String(hit.kind ?? ""), url: String(hit.url ?? "") };
|
|
268
|
+
if (hit) return { kind: String(hit.kind ?? ""), url: String(hit.url ?? ""), backendId: String(hit.backendId ?? "") };
|
|
254
269
|
}
|
|
255
|
-
return { kind: String(defaultKind ?? ""), url: String(defaultUrl ?? "") };
|
|
270
|
+
return { kind: String(defaultKind ?? ""), url: String(defaultUrl ?? ""), backendId: String(defaultBackendId ?? "") };
|
|
256
271
|
}
|
|
257
272
|
|
|
258
273
|
export function effectiveCoderKind({
|
package/gu-repl.mjs
CHANGED
|
@@ -6251,6 +6251,8 @@ async function chooseModel() {
|
|
|
6251
6251
|
codingKind: "openai",
|
|
6252
6252
|
selectedKeyId: sessionCodingKeyId,
|
|
6253
6253
|
keys: keysNow,
|
|
6254
|
+
// Only THIS server's keys are a choice to make (#245) — not every key on the account.
|
|
6255
|
+
backendId: ks.backendId,
|
|
6254
6256
|
}).warn;
|
|
6255
6257
|
const line = codingKeyLine({
|
|
6256
6258
|
keys: keysNow,
|
|
@@ -6272,7 +6274,8 @@ async function chooseModel() {
|
|
|
6272
6274
|
// nothing else, so the screen that follows a pick answered the less common question and left
|
|
6273
6275
|
// the more common one — "how do I change the model" — with no visible answer at all. Reported
|
|
6274
6276
|
// three times from exactly this view, twice after being told where the picker entry was.
|
|
6275
|
-
|
|
6277
|
+
// /provider is hidden (see HIDDEN_COMMANDS): point at the row of /model that does the same.
|
|
6278
|
+
console.log(dim(" to change this provider's model or URL, or remove it: /model → add or change a provider\n"));
|
|
6276
6279
|
}
|
|
6277
6280
|
// Task #125: the GLOBAL lifetime total is per-backend, so re-fetch it for the newly
|
|
6278
6281
|
// selected backend NOW — otherwise the footer would keep showing the previous backend's
|
|
@@ -10976,11 +10979,12 @@ async function main() {
|
|
|
10976
10979
|
// machine an Ollama coder, which needs no key — while the folder was on gpt-5. Reading
|
|
10977
10980
|
// it directly made this check answer about a coder nobody was using, and the mark only
|
|
10978
10981
|
// appeared after /model, which re-probes WITH the override.
|
|
10979
|
-
const { kind, url } = effectiveCoder({
|
|
10982
|
+
const { kind, url, backendId } = effectiveCoder({
|
|
10980
10983
|
sessionCodingModel,
|
|
10981
10984
|
choices: probe?.codingChoices,
|
|
10982
10985
|
defaultKind: p?.codingKind,
|
|
10983
10986
|
defaultUrl: probe?.codingChoices?.find?.((c) => c?.isDefault)?.url ?? "",
|
|
10987
|
+
defaultBackendId: probe?.codingChoices?.find?.((c) => c?.isDefault)?.backendId ?? "",
|
|
10984
10988
|
});
|
|
10985
10989
|
const allKeys =
|
|
10986
10990
|
codingNeedsKey(kind, url) && !sessionCodingKeyId ? await fetchCodingKeys() : null;
|
|
@@ -10989,6 +10993,7 @@ async function main() {
|
|
|
10989
10993
|
codingUrl: url,
|
|
10990
10994
|
selectedKeyId: sessionCodingKeyId,
|
|
10991
10995
|
keys: allKeys,
|
|
10996
|
+
backendId,
|
|
10992
10997
|
});
|
|
10993
10998
|
// THE BAR CARRIES IT TOO, from the same decision — a line printed at startup scrolls
|
|
10994
10999
|
// away within a few turns, and the report was precisely that the warning has to be
|
|
@@ -10996,7 +11001,7 @@ async function main() {
|
|
|
10996
11001
|
keyChoicePending = w.warn;
|
|
10997
11002
|
if (w.warn) {
|
|
10998
11003
|
console.log(
|
|
10999
|
-
GUTTER + orange(` ${
|
|
11004
|
+
GUTTER + orange(` ${w.count} API keys for this server and this folder has not chosen one`) +
|
|
11000
11005
|
dim(` — using ${w.using.name} ····${w.using.tail}.`)
|
|
11001
11006
|
);
|
|
11002
11007
|
console.log(GUTTER + dim(" ctrl+k picks the right one; a wrong key fails mid-task.\n"));
|
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.743",
|
|
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",
|
package/terminal-layout.mjs
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
const WIDE_RANGES =
|
|
30
30
|
/[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-ꥠ-가-힣豈-︐-︙︰--⦆¢-₩]/u;
|
|
31
31
|
const ZERO_WIDTH = /[\p{Mn}\p{Me}-]/u;
|
|
32
|
+
import { keyInUse } from "./server-keys.mjs";
|
|
33
|
+
|
|
32
34
|
export function charWidth(ch) {
|
|
33
35
|
if (!ch) return 0;
|
|
34
36
|
if (ZERO_WIDTH.test(ch)) return 0;
|
|
@@ -237,6 +239,29 @@ export function codingKeyLine({
|
|
|
237
239
|
if (keys.some((k) => k.backendId)) return `not tied to ${where}`;
|
|
238
240
|
return "";
|
|
239
241
|
};
|
|
242
|
+
if (!selectedId && backendId) {
|
|
243
|
+
// KEYS BELONG TO A SERVER (#245). Reported: /model said "test1 ····AqsA · not chosen for this
|
|
244
|
+
// folder — using the first key · that key is for another provider" while the picker under it
|
|
245
|
+
// showed claude2 "(in use · default · first key)" and the bar 6QAA. test1 was the first key on
|
|
246
|
+
// the ACCOUNT; the API hands a coder its SERVER's key — the one keyInUse names, the same answer
|
|
247
|
+
// the picker gives. "Not chosen" only when this server has several keys and no default.
|
|
248
|
+
const used = keyInUse(keys, backendId);
|
|
249
|
+
if (used) {
|
|
250
|
+
const own = keys.filter((k) => k && k.backendId === backendId);
|
|
251
|
+
const pool = own.length ? own : keys.filter((k) => k && !k.backendId);
|
|
252
|
+
const notes = [];
|
|
253
|
+
if (pool.length > 1 && !own.some((k) => k.default)) notes.push("not chosen for this folder — using the first key");
|
|
254
|
+
else if (used.default && own.length > 1) notes.push("this server's default");
|
|
255
|
+
const provider = noteFor(used);
|
|
256
|
+
if (provider) notes.push(provider);
|
|
257
|
+
return {
|
|
258
|
+
text: `${used.name} ····${used.tail}`,
|
|
259
|
+
verb: pool.length > 1 ? "change" : "replace",
|
|
260
|
+
isDefault: true,
|
|
261
|
+
...(notes.length ? { note: notes.join(" · ") } : {}),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
240
265
|
if (!selectedId) {
|
|
241
266
|
// NEVER PICKED — AND THAT IS WORTH SAYING OUT LOUD (reported). This folder rides the account
|
|
242
267
|
// default, which is simply the FIRST key in the list. With several stored, the one being used
|