@tiens.nguyen/gu-cli 1.0.876 → 1.0.877
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/gu-repl.mjs +172 -60
- package/package.json +1 -1
package/gu-repl.mjs
CHANGED
|
@@ -547,6 +547,10 @@ const waitPaint = (n, s) => (REPL_COLOR ? color256(n, s) : white(s));
|
|
|
547
547
|
// cyan only with `gu --color`.
|
|
548
548
|
const thoughtPaint = (s) => (REPL_COLOR ? accent(s) : white(s));
|
|
549
549
|
const orange = (s) => color256(208, s); // peak-token label (task #84)
|
|
550
|
+
// A menu's ACTION rows (pick another model…, API keys…, add a provider…) in orange, apart from the
|
|
551
|
+
// models above them (#248). \x1b[22m first: an unselected picker row is wrapped in dim, which would
|
|
552
|
+
// turn the orange grey. Plain text when colour is off (NO_COLOR, TERM=dumb).
|
|
553
|
+
const actionRow = (s) => (dim("x") === "x" ? s : `\x1b[22m${orange(s)}`);
|
|
550
554
|
// Compact token count: 950 → "950", 12300 → "12.3k", 1500000 → "1.5M" (task #83).
|
|
551
555
|
const fmtTokens = (n) => {
|
|
552
556
|
if (n < 1000) return String(n);
|
|
@@ -6318,7 +6322,7 @@ async function manageCodingProviders({ back = false } = {}) {
|
|
|
6318
6322
|
`${p.model || dim("(no model set)")}${dim(` · ${p.host}`)}${p.enabled ? "" : dim(" · off")}`
|
|
6319
6323
|
);
|
|
6320
6324
|
const ADD = labels.length;
|
|
6321
|
-
labels.push("add another provider — Claude API, OpenAI, Kimi…");
|
|
6325
|
+
labels.push(actionRow("add another provider — Claude API, OpenAI, Kimi…"));
|
|
6322
6326
|
console.log(dim(` Providers — pick one to change it, or add another (${PICK_HINT}):`));
|
|
6323
6327
|
const idx = await pickFromList(labels, selAt, { back });
|
|
6324
6328
|
if (idx === PICK_BACK) return BACK;
|
|
@@ -6355,11 +6359,12 @@ async function manageCodingProviders({ back = false } = {}) {
|
|
|
6355
6359
|
}
|
|
6356
6360
|
}
|
|
6357
6361
|
|
|
6358
|
-
/**
|
|
6359
|
-
|
|
6360
|
-
|
|
6362
|
+
/**
|
|
6363
|
+
* What provider `p` offers, asked with ITS OWN key (#266) — its problems printed as it goes.
|
|
6364
|
+
* Shared by "change the model…" and /model's "pick another model on <host>…" (#248). → [ids]
|
|
6365
|
+
*/
|
|
6366
|
+
async function listProviderModels(ps, p) {
|
|
6361
6367
|
const { proxyFetch } = await import("./proxy-dispatcher.mjs");
|
|
6362
|
-
|
|
6363
6368
|
// LISTED WITH THIS PROVIDER'S OWN KEY (#266). This used to probe with NO key, on purpose: the
|
|
6364
6369
|
// only key the terminal could fetch was the ACCOUNT DEFAULT, which may belong to another provider.
|
|
6365
6370
|
// Since #245 a key belongs to its server, so the key used is the one bound to THIS row — the same
|
|
@@ -6407,6 +6412,13 @@ async function changeProviderModel(ps, rows, p) {
|
|
|
6407
6412
|
}
|
|
6408
6413
|
}
|
|
6409
6414
|
|
|
6415
|
+
return models;
|
|
6416
|
+
}
|
|
6417
|
+
|
|
6418
|
+
/** Point a provider at a different model. */
|
|
6419
|
+
async function changeProviderModel(ps, rows, p) {
|
|
6420
|
+
const md = await import("./model-doctor.mjs");
|
|
6421
|
+
const models = await listProviderModels(ps, p);
|
|
6410
6422
|
let model = "";
|
|
6411
6423
|
if (models.length) {
|
|
6412
6424
|
model = await chooseCodingModelFrom(ps, models, `Which model should ${p.host} use?`, p.model, { back: true });
|
|
@@ -7118,17 +7130,28 @@ async function chooseModel() {
|
|
|
7118
7130
|
// OpenAI-compatible providers are both "OpenAI-compatible", so the kind names neither.
|
|
7119
7131
|
const { labels, tags, showTag } = codingChoiceLabels(choices, def);
|
|
7120
7132
|
const activeIdx = choices.findIndex((c) => c.id === active);
|
|
7133
|
+
// THE CURRENT CODER'S KEYS, ON THIS LIST (#248) — the manager ctrl+k opens, for a provider that
|
|
7134
|
+
// takes a key (OpenAI-compatible, ollama.com). Before the add row, so no model row moves.
|
|
7135
|
+
const activeChoice = choices[activeIdx >= 0 ? activeIdx : choices.findIndex((c) => c.id === def)];
|
|
7136
|
+
const activeUrl = String(activeChoice?.url ?? "");
|
|
7137
|
+
const keyHost = activeChoice && ((activeChoice.kind ?? "") === "openai" || /(^|\.)ollama\.com$/i.test(hostOf(activeUrl))) ? hostOf(activeUrl) : "";
|
|
7138
|
+
// …and the rest of that server's models, the same row /agent-model has (#248). Not for local MLX.
|
|
7139
|
+
const modelsHost = activeChoice && ["openai", "ollama"].includes(activeChoice.kind ?? "") && activeUrl ? hostOf(activeUrl) : "";
|
|
7140
|
+
const MODELS = modelsHost ? labels.length : -1;
|
|
7141
|
+
if (modelsHost) labels.push(actionRow(`pick another model on ${modelsHost}…`));
|
|
7142
|
+
const KEYS = keyHost ? labels.length : -1;
|
|
7143
|
+
if (keyHost) labels.push(actionRow(`API keys for ${keyHost} — switch, add or remove…`));
|
|
7121
7144
|
// Task #223. Appended LAST, after the models: a new row must never shift an index someone
|
|
7122
7145
|
// already has muscle memory for.
|
|
7123
7146
|
//
|
|
7124
7147
|
// "add or change" rather than "add", because add-only was a one-way door: a model id typed
|
|
7125
7148
|
// wrong could not be corrected from anywhere, here or in web Settings. The entry leads to the
|
|
7126
7149
|
// add flow directly when there is nothing to change, so it costs a word and no extra step.
|
|
7127
|
-
labels.push(
|
|
7150
|
+
labels.push(actionRow(
|
|
7128
7151
|
choices.some((c) => (c.kind ?? "") === "openai")
|
|
7129
7152
|
? "add or change a provider — Claude API, OpenAI, Kimi…"
|
|
7130
7153
|
: "add a provider — Claude API, OpenAI, Kimi…"
|
|
7131
|
-
);
|
|
7154
|
+
));
|
|
7132
7155
|
// THREE MODELS ARE IN PLAY AND THIS COMMAND ONLY CHANGES ONE (task #169 req 5). "/model" has
|
|
7133
7156
|
// meant the CODING model since task #44, but "agent model" and "agent coding model" are close
|
|
7134
7157
|
// enough in name to have already been mixed up. The fix is to say which one THIS is — not to
|
|
@@ -7140,7 +7163,15 @@ async function chooseModel() {
|
|
|
7140
7163
|
console.log(dim(" (cancelled)\n"));
|
|
7141
7164
|
return;
|
|
7142
7165
|
}
|
|
7143
|
-
|
|
7166
|
+
if (idx === MODELS) {
|
|
7167
|
+
if ((await pickModelOnCoderServer(activeChoice, def)) === BACK) return chooseModel();
|
|
7168
|
+
return;
|
|
7169
|
+
}
|
|
7170
|
+
if (idx === KEYS) {
|
|
7171
|
+
if ((await replaceCodingKey({ back: true })) === BACK) return chooseModel();
|
|
7172
|
+
return;
|
|
7173
|
+
}
|
|
7174
|
+
// The add entry sits past the last model (and the keys row).
|
|
7144
7175
|
if (idx >= choices.length) {
|
|
7145
7176
|
if ((await manageCodingProviders({ back: true })) === BACK) return chooseModel();
|
|
7146
7177
|
return;
|
|
@@ -7148,6 +7179,62 @@ async function chooseModel() {
|
|
|
7148
7179
|
await applyChosenCoder(choices[idx], { def, tag: showTag ? tags[idx] : "" });
|
|
7149
7180
|
}
|
|
7150
7181
|
|
|
7182
|
+
/**
|
|
7183
|
+
* /model's "pick another model on <host>…" (#248): the current coder's server, asked for everything
|
|
7184
|
+
* it has (with its own key). The pick is ADDED to that provider's models — its main model stays —
|
|
7185
|
+
* and becomes the coder. `← back` returns BACK to /model.
|
|
7186
|
+
*/
|
|
7187
|
+
async function pickModelOnCoderServer(active, def) {
|
|
7188
|
+
const ps = await import("./coding-provider-setup.mjs");
|
|
7189
|
+
const settings = await withPendingStatus("reading your providers", fetchWorkerSettings);
|
|
7190
|
+
if (!settings) {
|
|
7191
|
+
console.log(red(" couldn't reach the API to read your providers.\n"));
|
|
7192
|
+
return;
|
|
7193
|
+
}
|
|
7194
|
+
const rows = ps.existingBackendRows(settings);
|
|
7195
|
+
const same = (u) => ps.ensureV1Root(String(u ?? "")).toLowerCase();
|
|
7196
|
+
const p = ps.manageableProviders(rows).find((x) => same(x.url) === same(active.url));
|
|
7197
|
+
// No saved row behind this coder (an account default from elsewhere): the provider manager.
|
|
7198
|
+
if (!p) return manageCodingProviders({ back: true });
|
|
7199
|
+
const models = await listProviderModels(ps, p);
|
|
7200
|
+
let model;
|
|
7201
|
+
if (models.length) {
|
|
7202
|
+
model = await chooseCodingModelFrom(ps, models, `Which model on ${p.host}?`, active.model, { back: true });
|
|
7203
|
+
if (model === BACK) return BACK;
|
|
7204
|
+
} else {
|
|
7205
|
+
// Not listed (no key, refused, down) — the lines above said so: the name is typed instead.
|
|
7206
|
+
model = (await ask(" model name (Enter to cancel): ")).trim() || null;
|
|
7207
|
+
}
|
|
7208
|
+
if (model === null) {
|
|
7209
|
+
console.log(dim(" (nothing changed)\n"));
|
|
7210
|
+
return;
|
|
7211
|
+
}
|
|
7212
|
+
const row = rows.find((b) => (b.id ?? b.kind) === p.id);
|
|
7213
|
+
const extras = Array.isArray(row?.models) ? row.models : [];
|
|
7214
|
+
if (model !== p.model && !extras.includes(model)) {
|
|
7215
|
+
const md = await import("./model-doctor.mjs");
|
|
7216
|
+
const saved = await withPendingStatus("saving", () =>
|
|
7217
|
+
md.saveWorkerSettings(apiBase, workerKey, {
|
|
7218
|
+
agentCodingBackends: rows.map((b) => ((b.id ?? b.kind) === p.id ? { ...b, models: [...extras, model] } : b)),
|
|
7219
|
+
})
|
|
7220
|
+
);
|
|
7221
|
+
if (!saved) {
|
|
7222
|
+
console.log(red(" couldn't save that — nothing changed.\n"));
|
|
7223
|
+
return;
|
|
7224
|
+
}
|
|
7225
|
+
}
|
|
7226
|
+
// The choice as the API now lists it — its id is the API's to make, not this side's.
|
|
7227
|
+
const probe = await withPendingStatus("loading models", () => fetchAgentPayload([{ role: "user", content: "ping" }])).catch(() => null);
|
|
7228
|
+
const chosen = (Array.isArray(probe?.codingChoices) ? probe.codingChoices : []).find(
|
|
7229
|
+
(c) => c.model === model && same(c.url) === same(p.url)
|
|
7230
|
+
);
|
|
7231
|
+
if (!chosen) {
|
|
7232
|
+
console.log(accent(` ✓ ${model} added to ${p.host}`) + dim(" · pick it in /model\n"));
|
|
7233
|
+
return;
|
|
7234
|
+
}
|
|
7235
|
+
await applyChosenCoder(chosen, { def: String(probe?.codingDefault ?? def ?? "").trim() });
|
|
7236
|
+
}
|
|
7237
|
+
|
|
7151
7238
|
/**
|
|
7152
7239
|
* Make `chosen` ({ id, model, kind, url }) the coding model: session state, saved choice, the key
|
|
7153
7240
|
* check and the lifetime token total. The tail of /model, extracted so /quick-models' ← / → (#233)
|
|
@@ -8746,25 +8833,6 @@ async function confirmEmbedSwitch(ec, current, chosen) {
|
|
|
8746
8833
|
return { changed: true, plan };
|
|
8747
8834
|
}
|
|
8748
8835
|
|
|
8749
|
-
// WHICH EMBEDDER, ASKED ONCE. The model sub-flow of /embed-model: the curated picker, then the
|
|
8750
|
-
// shared recognition step. Returns { changed, chosen, plan }.
|
|
8751
|
-
async function pickEmbedModel(ec, current, { back = false } = {}) {
|
|
8752
|
-
const rows = ec.embedChoiceRows(current);
|
|
8753
|
-
console.log(dim(` Choose the embedder (${PICK_HINT}):`));
|
|
8754
|
-
const labels = rows.map(
|
|
8755
|
-
(r) => `${r.model}${r.dims ? dim(` · ${r.dims}d`) : ""}${dim(` · ${r.note}`)}` + (r.current ? accent(" (current)") : "")
|
|
8756
|
-
);
|
|
8757
|
-
const idx = await pickFromList(labels, Math.max(0, rows.findIndex((r) => r.current)), { back });
|
|
8758
|
-
if (idx === PICK_BACK) return BACK;
|
|
8759
|
-
if (idx < 0) {
|
|
8760
|
-
console.log(dim(" (cancelled)\n"));
|
|
8761
|
-
return { changed: false, chosen: "", plan: null };
|
|
8762
|
-
}
|
|
8763
|
-
const chosen = rows[idx].model;
|
|
8764
|
-
const sw = await confirmEmbedSwitch(ec, current, chosen);
|
|
8765
|
-
return { changed: sw.changed, chosen: sw.changed ? chosen : "", plan: sw.plan };
|
|
8766
|
-
}
|
|
8767
|
-
|
|
8768
8836
|
// WHAT A SAVED CHANGE SOUNDS LIKE. Success lines used to live at the end of one long function;
|
|
8769
8837
|
// four exit paths now say them, so they are said once here: the embedder line, the rebuild note
|
|
8770
8838
|
// that only a REAL model change earns, and the pull hint — Ollama serves the embedder itself, so
|
|
@@ -8791,31 +8859,57 @@ async function chooseEmbedModel() {
|
|
|
8791
8859
|
const current = ec.currentEmbedModel(settings);
|
|
8792
8860
|
const currentUrl = String(settings.ragEmbedUrl || "").trim() || "http://127.0.0.1:11434";
|
|
8793
8861
|
const host = hostOf(currentUrl);
|
|
8794
|
-
// WHEN TO SHOW THE KEY ROW (
|
|
8862
|
+
// WHEN TO SHOW THE KEY ROW (keyRequired): a key is clearly the question when
|
|
8795
8863
|
// the server is ollama.com (it rejects anonymous calls) or already looks OpenAI-compatible (the
|
|
8796
8864
|
// /v1 shape), or when one is already bound — replacing the key people can no longer read is the
|
|
8797
8865
|
// very thing they come back for. A keyless localhost Ollama sees three rows instead.
|
|
8798
8866
|
const keyRequired = isOllamaCloudUrl(currentUrl) || /\/v1\/?$/i.test(currentUrl) ||
|
|
8799
8867
|
Boolean(String(settings.ragEmbedKeyId ?? "").trim());
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8868
|
+
// ONE LIST, THE SHAPE OF /model AND /agent-model (#248): the embedders, the current one marked,
|
|
8869
|
+
// then this server's own rows in orange — its other models, its keys — and add a provider. It was
|
|
8870
|
+
// a three-row menu first ("switch the embedder model…", "use another provider…", the key), one
|
|
8871
|
+
// step more than the other two commands.
|
|
8872
|
+
// "(current)" only for an embedder that was SET — nothing set means the default, which its own
|
|
8873
|
+
// note already says. Right after the name: at the end of the row an 80-column clip cut it off.
|
|
8874
|
+
const isSet = Boolean(String(settings.ragEmbedModel ?? "").trim());
|
|
8875
|
+
const rows = ec.embedChoiceRows(isSet ? current : "");
|
|
8876
|
+
const labels = rows.map(
|
|
8877
|
+
(r) => `${r.model}${r.current ? accent(" (current)") : ""}${r.dims ? dim(` · ${r.dims}d`) : ""}${dim(` · ${r.note}`)}`
|
|
8878
|
+
);
|
|
8879
|
+
const MODELS = labels.length;
|
|
8880
|
+
labels.push(actionRow(`pick another model on ${host || currentUrl}…`));
|
|
8881
|
+
const KEYS = keyRequired ? labels.length : -1;
|
|
8882
|
+
if (keyRequired) labels.push(actionRow(`API keys for ${host || currentUrl} — switch, add or remove…`));
|
|
8883
|
+
const ADD = labels.length;
|
|
8884
|
+
labels.push(actionRow("add a provider — OpenAI, Ollama…"));
|
|
8885
|
+
const head = ` Embedding model (now ${current} on ${host || currentUrl})`;
|
|
8886
|
+
const hint = `(${PICK_HINT}):`;
|
|
8887
|
+
const cols = Math.max(20, process.stdout.columns || 80);
|
|
8888
|
+
console.log(dim(head.length + 2 + hint.length < cols ? `${head} ${hint}` : `${head}\n ${hint}`));
|
|
8889
|
+
// The cursor starts on the one in use: the set one, else the default.
|
|
8890
|
+
const inUse = rows.findIndex((r) => ec.sameEmbedModel(r.model, current));
|
|
8891
|
+
const mi = await pickFromList(labels, Math.max(0, inUse));
|
|
8892
|
+
if (mi < 0) {
|
|
8805
8893
|
console.log(dim(" (cancelled)\n"));
|
|
8806
8894
|
return;
|
|
8807
8895
|
}
|
|
8896
|
+
const action = mi === MODELS ? "server" : mi === KEYS ? "key" : mi === ADD ? "provider" : "model";
|
|
8808
8897
|
|
|
8809
8898
|
// EVERY ARM IS THE ONE ADD-SERVER SCREEN (task #245) — the same URL → key → models → save as
|
|
8810
|
-
// /model and /agent-model.
|
|
8899
|
+
// /model and /agent-model. A listed embedder has been chosen already (and passed the
|
|
8811
8900
|
// recognition wall), so it only asks WHERE; the other two list what the server has.
|
|
8812
|
-
// "← back" in any arm returns to this
|
|
8901
|
+
// "← back" in any arm returns to this list (#242).
|
|
8813
8902
|
if (action === "model") {
|
|
8814
|
-
const
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
|
|
8903
|
+
const chosen = rows[mi].model;
|
|
8904
|
+
const sw = await confirmEmbedSwitch(ec, current, chosen);
|
|
8905
|
+
if (!sw.changed) return;
|
|
8906
|
+
await addServer({ role: "embed", dialect: "ollama", model: chosen, plan: sw.plan });
|
|
8907
|
+
return;
|
|
8908
|
+
}
|
|
8909
|
+
if (action === "server") {
|
|
8910
|
+
// That server's own list, with its key — the add-server screen with the URL known.
|
|
8911
|
+
const dialect = /\/v1\/?$/i.test(currentUrl) ? "openai" : "ollama";
|
|
8912
|
+
if ((await addServer({ role: "embed", dialect, url: currentUrl, back: true })) === BACK) return chooseEmbedModel();
|
|
8819
8913
|
return;
|
|
8820
8914
|
}
|
|
8821
8915
|
if (action === "provider") {
|
|
@@ -8970,12 +9064,18 @@ async function chooseAgentModel() {
|
|
|
8970
9064
|
if (!saved.length) return agentModelOnServer();
|
|
8971
9065
|
const current = am.currentAgentModel(settings);
|
|
8972
9066
|
const curHost = settings.agentModelUrl ? hostOf(String(settings.agentModelUrl)) : "";
|
|
9067
|
+
// THE CURRENT PROVIDER'S OWN ROWS, ON THIS LIST (#248). They were one step down, behind "other
|
|
9068
|
+
// models, providers and API keys…" — which read like the "add a provider" row under it.
|
|
9069
|
+
const source = am.agentListSource(settings);
|
|
9070
|
+
const needsKey = am.agentSourceNeedsKey(source);
|
|
8973
9071
|
for (;;) {
|
|
8974
9072
|
const labels = saved.map((c) => c.label + (c.current ? accent(" (current)") : ""));
|
|
8975
|
-
const
|
|
8976
|
-
labels.push(
|
|
9073
|
+
const MODELS = curHost ? labels.length : -1;
|
|
9074
|
+
if (curHost) labels.push(actionRow(`pick another model on ${curHost}…`));
|
|
9075
|
+
const KEYS = curHost && needsKey ? labels.length : -1;
|
|
9076
|
+
if (KEYS >= 0) labels.push(actionRow(`API keys for ${curHost} — switch, add or remove…`));
|
|
8977
9077
|
const ADD = labels.length;
|
|
8978
|
-
labels.push("add a provider — Claude API, OpenAI, Kimi, Ollama…");
|
|
9078
|
+
labels.push(actionRow("add a provider — Claude API, OpenAI, Kimi, Ollama…"));
|
|
8979
9079
|
// The hint drops to its own row when the header would wrap (80 columns over SSH).
|
|
8980
9080
|
const head = ` Agent model — the one that TALKS (now ${current || "unset"}${curHost ? ` on ${curHost}` : ""})`;
|
|
8981
9081
|
const hint = `(${PICK_HINT}):`;
|
|
@@ -8986,8 +9086,15 @@ async function chooseAgentModel() {
|
|
|
8986
9086
|
console.log(dim(" (cancelled)\n"));
|
|
8987
9087
|
return;
|
|
8988
9088
|
}
|
|
8989
|
-
if (idx ===
|
|
8990
|
-
|
|
9089
|
+
if (idx === MODELS) {
|
|
9090
|
+
// Straight to that server's full list; when it can't be listed, its recovery menu instead.
|
|
9091
|
+
if ((await agentModelOnServer({ back: true, start: "models" })) === BACK) continue;
|
|
9092
|
+
return;
|
|
9093
|
+
}
|
|
9094
|
+
if (idx === KEYS) {
|
|
9095
|
+
const rowId = String(source.row?.id ?? "").trim() || String(source.row?.kind ?? "").trim();
|
|
9096
|
+
if (!rowId) return enterAgentProviderKey(source.row, curHost);
|
|
9097
|
+
if ((await manageServerKeys({ role: "agent", backendId: rowId, host: curHost, model: current, back: true })) === BACK) continue;
|
|
8991
9098
|
return;
|
|
8992
9099
|
}
|
|
8993
9100
|
if (idx === ADD) {
|
|
@@ -9025,10 +9132,12 @@ async function useSavedAgentChoice(c) {
|
|
|
9025
9132
|
|
|
9026
9133
|
/**
|
|
9027
9134
|
* One server's view: its full model list, other providers, its keys — what /agent-model was before
|
|
9028
|
-
* #246
|
|
9029
|
-
* `
|
|
9135
|
+
* #246. With models already set up, /agent-model's list opens it as "pick another model on <host>…"
|
|
9136
|
+
* (#248): `start: "models"` skips the what-to-change menu and goes to the model list.
|
|
9137
|
+
* `back`: opened from that list, so its first screen offers "← back" and returns BACK.
|
|
9030
9138
|
*/
|
|
9031
|
-
async function agentModelOnServer({ back = false } = {}) {
|
|
9139
|
+
async function agentModelOnServer({ back = false, start = "menu" } = {}) {
|
|
9140
|
+
const again = () => agentModelOnServer({ back, start });
|
|
9032
9141
|
const am = await import("./agent-model-command.mjs");
|
|
9033
9142
|
const settings = await withPendingStatus("reading your agent model", fetchWorkerSettings);
|
|
9034
9143
|
if (!settings) {
|
|
@@ -9113,7 +9222,7 @@ async function agentModelOnServer({ back = false } = {}) {
|
|
|
9113
9222
|
if (action === "key") await enterAgentProviderKey(source.row, listHost);
|
|
9114
9223
|
else if (action === "provider") {
|
|
9115
9224
|
// "← back" on "Which provider?" returns to this menu (#247).
|
|
9116
|
-
if ((await addServer({ role: "agent", dialect: "openai", back: true })) === BACK) return
|
|
9225
|
+
if ((await addServer({ role: "agent", dialect: "openai", back: true })) === BACK) return again();
|
|
9117
9226
|
} else console.log("");
|
|
9118
9227
|
return;
|
|
9119
9228
|
}
|
|
@@ -9121,18 +9230,21 @@ async function agentModelOnServer({ back = false } = {}) {
|
|
|
9121
9230
|
// WHAT TO CHANGE, FIRST (#44). The ways to use an Ollama server or another provider used to sit
|
|
9122
9231
|
// after every model on this one — 130 rows down on api.openai.com — which read as "OpenAI models
|
|
9123
9232
|
// are all you can choose". A short menu of what to change comes before any list.
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9233
|
+
let firstAction = "models";
|
|
9234
|
+
if (start !== "models") {
|
|
9235
|
+
console.log(dim(` Agent model — the one that TALKS: ${current || "unset"} on ${listHost}`));
|
|
9236
|
+
const menu = am.agentMenuOptions({ host: listHost, modelCount: models.length, needsKey });
|
|
9237
|
+
const first = await pickFromList(menu.map((o) => o.label), 0, { back });
|
|
9238
|
+
if (first === PICK_BACK) return BACK;
|
|
9239
|
+
firstAction = first >= 0 ? menu[first].action : "cancel";
|
|
9240
|
+
}
|
|
9129
9241
|
if (firstAction === "cancel") {
|
|
9130
9242
|
console.log(dim(" (cancelled)\n"));
|
|
9131
9243
|
return;
|
|
9132
9244
|
}
|
|
9133
9245
|
// "← back" in any of these returns to this menu (#242) — read again, as on a first open.
|
|
9134
9246
|
if (firstAction === "provider") {
|
|
9135
|
-
if ((await chooseAgentProvider({ back: true })) === BACK) return
|
|
9247
|
+
if ((await chooseAgentProvider({ back: true })) === BACK) return again();
|
|
9136
9248
|
return;
|
|
9137
9249
|
}
|
|
9138
9250
|
if (firstAction === "key") {
|
|
@@ -9140,7 +9252,7 @@ async function agentModelOnServer({ back = false } = {}) {
|
|
|
9140
9252
|
const rowId = String(source.row?.id ?? "").trim() || String(source.row?.kind ?? "").trim();
|
|
9141
9253
|
if (!rowId) return enterAgentProviderKey(source.row, listHost);
|
|
9142
9254
|
const r = await manageServerKeys({ role: "agent", backendId: rowId, host: listHost, model: am.currentAgentModel(settings), back: true });
|
|
9143
|
-
if (r === BACK) return
|
|
9255
|
+
if (r === BACK) return again();
|
|
9144
9256
|
return;
|
|
9145
9257
|
}
|
|
9146
9258
|
|
|
@@ -9173,7 +9285,8 @@ async function agentModelOnServer({ back = false } = {}) {
|
|
|
9173
9285
|
// No other entries here any more (#44): the ways to change the provider are on the menu above,
|
|
9174
9286
|
// where they can be seen, rather than after every model.
|
|
9175
9287
|
const idx = await pickFromList(labels, Math.max(0, rows.findIndex((r) => r.current)), { back: true });
|
|
9176
|
-
|
|
9288
|
+
// Opened as "pick another model on <host>…", back is /agent-model's list; else this menu.
|
|
9289
|
+
if (idx === PICK_BACK) return start === "models" ? BACK : again();
|
|
9177
9290
|
if (idx < 0) {
|
|
9178
9291
|
console.log(dim(" (cancelled)\n"));
|
|
9179
9292
|
return;
|
|
@@ -10356,7 +10469,7 @@ async function promptNewCodingKey(backendId = "", provider = "") {
|
|
|
10356
10469
|
return id;
|
|
10357
10470
|
}
|
|
10358
10471
|
|
|
10359
|
-
async function replaceCodingKey() {
|
|
10472
|
+
async function replaceCodingKey({ back = false } = {}) {
|
|
10360
10473
|
const ks = await withPendingStatus("checking the coding model", () =>
|
|
10361
10474
|
coderKeyState(sessionCodingModel)
|
|
10362
10475
|
);
|
|
@@ -10391,8 +10504,7 @@ async function replaceCodingKey() {
|
|
|
10391
10504
|
const keys = allKeys ? ps.keysForBackend(allKeys, ks.backendId) : null;
|
|
10392
10505
|
if (keys && keys.length >= 1) {
|
|
10393
10506
|
// ONE SERVER'S KEYS (task #245): the same picker /agent-model and /embed-model open.
|
|
10394
|
-
|
|
10395
|
-
return;
|
|
10507
|
+
return manageServerKeys({ role: "coding", backendId: ks.backendId, host: ks.provider || ks.backendId || "this server", model: ks.model, back });
|
|
10396
10508
|
}
|
|
10397
10509
|
|
|
10398
10510
|
// NO KEYS YET, BUT AN API THAT UNDERSTANDS NAMES — so ask for one.
|
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.877",
|
|
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",
|