@tiens.nguyen/gu-cli 1.0.898 → 1.0.900

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.
Files changed (2) hide show
  1. package/gu-repl.mjs +27 -89
  2. package/package.json +1 -1
package/gu-repl.mjs CHANGED
@@ -1053,7 +1053,6 @@ const COMMANDS = [
1053
1053
  // already has for exactly this — /quit, /rag-local.
1054
1054
  // /host-mode is gone (owner, 11-07): modes are chosen at setup; `gu clear` sets up again.
1055
1055
  // /undo is HIDDEN_COMMANDS now — still works when typed.
1056
- { name: "/server", desc: "pick a deployment server (host/user) for this session" },
1057
1056
  { name: "/proxy", usage: "/proxy [show|set|on|off|clear|test]", desc: "route MODEL calls through an HTTP proxy (local calls always go direct)" },
1058
1057
  { name: "/s3", usage: "/s3 [test]", desc: "set up YOUR S3 bucket (RAG index, and the PDFs/downloads gu makes) and check the AWS keys work — without it, files are saved on this computer" },
1059
1058
  { name: "/context-window", usage: "/context-window [tokens|auto]", desc: "tell gu how big the coding model's context is — hosted models cannot be probed" },
@@ -7171,7 +7170,6 @@ async function useCoderInThisFolder({ ps, rows, beforeChoices, url, host, locali
7171
7170
  console.log(GUTTER + dim("restart it with `gu api restart`, then add that provider again.\n"));
7172
7171
  }
7173
7172
  sessionCodingModel = offered.id;
7174
- setModelFallback(null); // #343
7175
7173
  sessionCodingLabel = model;
7176
7174
  sessionCodingUrl = url;
7177
7175
  // …and the host the bar puts in front of it (#263): left alone, the PREVIOUS coder's
@@ -7180,6 +7178,7 @@ async function useCoderInThisFolder({ ps, rows, beforeChoices, url, host, locali
7180
7178
  // The folder follows the SERVER's key (task #245) — its default, which a first key already is.
7181
7179
  sessionCodingKeyId = "";
7182
7180
  sessionApiKeyTail = key ? key.slice(-4) : "";
7181
+ setModelFallback(null); // #343 — AFTER the name, server and tail: it repaints the bar (GH #397)
7183
7182
  keyChoicePending = false;
7184
7183
  await saveSessionCoding(cwd);
7185
7184
  await saveSessionCodingKeyId(cwd);
@@ -7202,7 +7201,12 @@ async function useCoderInThisFolder({ ps, rows, beforeChoices, url, host, locali
7202
7201
  // The bar still said "coding model not set · /model" under the success line of the FIRST model
7203
7202
  // added on a fresh install, until the next turn repainted it (GH #397).
7204
7203
  noModelAtAll = after?.payload ? nothingToAnswerWith(after.payload) : false;
7205
- if (after?.payload) agentMissing = after.payload.agentMissing === true;
7204
+ if (after?.payload) {
7205
+ agentMissing = after.payload.agentMissing === true;
7206
+ // The first model added IS the account default now. Startup read the default while there was
7207
+ // none, so picking "(default)" later left the bar with no model name at all (GH #397).
7208
+ seedDefaultCoder(after.payload);
7209
+ }
7206
7210
  if (barPinned()) paintBar();
7207
7211
  }
7208
7212
 
@@ -7462,7 +7466,6 @@ async function applyChosenCoder(chosen, { def = "", tag = "", quick = false } =
7462
7466
  tail: sessionApiKeyTail,
7463
7467
  };
7464
7468
  sessionCodingModel = chosen.id === def ? "" : chosen.id;
7465
- setModelFallback(null); // #343: the warning named the coder that was just replaced
7466
7469
  // Remember the friendly name for the token footer, which should never show a raw
7467
7470
  // "<kind>::<model>" wire id.
7468
7471
  sessionCodingLabel = chosen.id === def ? "" : chosen.model;
@@ -7472,6 +7475,12 @@ async function applyChosenCoder(chosen, { def = "", tag = "", quick = false } =
7472
7475
  // The server goes WITH the model, before any await (#263): set after the save below, the bar
7473
7476
  // could repaint in between and show the NEW model under the OLD server for a moment.
7474
7477
  activeCoderWhere = coderWhere(chosen.kind, chosen.url);
7478
+ // The old provider's key tail must not sit beside the new model while the key is looked up (it
7479
+ // was shown for a moment: Kimi with Qwen's tail). Blank until coderKeyState answers below.
7480
+ sessionApiKeyTail = "";
7481
+ // AFTER the fields above: this repaints the bar, and between the id and its name it showed the
7482
+ // raw "<row id>::<model>" wire id for a frame (GH #397). #343: the warning named the old coder.
7483
+ setModelFallback(null);
7475
7484
  // WRITTEN NOW, not at the end of the next turn: `/model` then `/exit` is how the choice is
7476
7485
  // made and then tested, and that path never reaches saveSession.
7477
7486
  await saveSessionCoding(resolve(process.cwd()));
@@ -9827,39 +9836,6 @@ async function agentModelOnServer({ back = false, start = "menu" } = {}) {
9827
9836
  console.log(dim(" takes effect on your next question.\n"));
9828
9837
  }
9829
9838
 
9830
- // Probe whether SSH KEY auth already works for a server (so we can tell the user to run
9831
- // ssh-copy-id if not). BatchMode=yes = never prompt for a password → exit!=0 if no key.
9832
- async function checkServerKeyAuth(s) {
9833
- try {
9834
- const ok = await new Promise((resolve) => {
9835
- const p = spawn(
9836
- "ssh",
9837
- [
9838
- "-o", "BatchMode=yes",
9839
- "-o", "ConnectTimeout=6",
9840
- "-o", "StrictHostKeyChecking=accept-new",
9841
- `${s.user}@${s.host}`,
9842
- "true",
9843
- ],
9844
- { stdio: "ignore" }
9845
- );
9846
- p.on("exit", (code) => resolve(code === 0));
9847
- p.on("error", () => resolve(false));
9848
- });
9849
- if (ok) console.log(dim(" ✓ key auth works — deploys will connect without a password."));
9850
- else
9851
- console.log(
9852
- yellow(" key auth not set up yet.") +
9853
- dim(` Run once in your terminal: `) +
9854
- `ssh-copy-id ${s.user}@${s.host}`
9855
- );
9856
- } catch {
9857
- /* best-effort check — never block the picker */
9858
- }
9859
- }
9860
-
9861
- // /server — pick a deployment target (from the web app's Servers registry) for this
9862
- // session. Host/user only, never a secret; the agent deploys here with KEY auth (#69).
9863
9839
  /**
9864
9840
  * `/proxy` — configure the outbound proxy for MODEL calls (task #160).
9865
9841
  *
@@ -10248,53 +10224,6 @@ async function runProxyCommand(line) {
10248
10224
  }
10249
10225
  }
10250
10226
 
10251
- async function chooseServer() {
10252
- let servers;
10253
- try {
10254
- const res = await withPendingStatus("loading servers", () =>
10255
- fetch(`${apiBase}/api/worker/deploy-servers`, {
10256
- headers: { "X-Worker-Key": workerKey },
10257
- signal: AbortSignal.timeout(API_CALL_TIMEOUT_MS),
10258
- })
10259
- );
10260
- const data = await res.json().catch(() => ({}));
10261
- if (!res.ok) {
10262
- console.log(red(` couldn't load servers: ${data?.error || `HTTP ${res.status}`}\n`));
10263
- return;
10264
- }
10265
- servers = Array.isArray(data?.servers) ? data.servers : [];
10266
- } catch (err) {
10267
- console.log(red(` ${describeApiFailure(err, "couldn't load servers")}\n`));
10268
- return;
10269
- }
10270
- if (servers.length === 0) {
10271
- console.log(dim(" No deployment servers yet — add them in the web app → Servers.\n"));
10272
- return;
10273
- }
10274
- // Arrow-key picker: ↑/↓ to move, Enter to choose (no number typing). The list ends with
10275
- // a "none" row to clear the selection. Pre-highlight the current pick if there is one.
10276
- const labels = servers.map((s) => `${s.name} (${s.user}@${s.host})`);
10277
- labels.push("none (clear selection)");
10278
- const activeIdx = servers.findIndex((s) => s.host === selectedServer?.host);
10279
- console.log(dim(` Choose a deployment server (${PICK_HINT}):`));
10280
- const idx = await pickFromList(labels, activeIdx >= 0 ? activeIdx : 0);
10281
- if (idx < 0) {
10282
- console.log(dim(" (cancelled)\n"));
10283
- return;
10284
- }
10285
- if (idx === servers.length) {
10286
- // the trailing "none" row
10287
- selectedServer = null;
10288
- console.log(dim(" ✓ deployment server cleared.\n"));
10289
- return;
10290
- }
10291
- const s = servers[idx];
10292
- selectedServer = { name: s.name, host: s.host, user: s.user };
10293
- console.log(accent(` ✓ deploy target → ${s.name} (${s.user}@${s.host})`));
10294
- await checkServerKeyAuth(selectedServer);
10295
- console.log("");
10296
- }
10297
-
10298
10227
  /**
10299
10228
  * Ask for the coding model's API key if the resolved coder needs one and none is stored.
10300
10229
  *
@@ -14543,6 +14472,20 @@ async function main() {
14543
14472
  // payload fields, which key-choice-regression.test.mjs forbids for exactly that reason.
14544
14473
  activeCoderWhere = whereOfActiveCoder(probe?.codingChoices, sessionCodingModel);
14545
14474
  }
14475
+ // THE KEY TAIL BESIDE THE MODEL, at startup too (GH #397). It was read only when the folder had
14476
+ // its own key choice, so the normal case — the server's default key — showed a model with no
14477
+ // key until the first /model switch.
14478
+ if (!sessionApiKeyTail) {
14479
+ try {
14480
+ const choices = Array.isArray(probe?.codingChoices) ? probe.codingChoices : [];
14481
+ const active = choices.find((c) => c.id === sessionCodingModel) ?? choices.find((c) => c.isDefault);
14482
+ const { codingNeedsKey } = await import("./coding-key-prompt.mjs");
14483
+ if (active && codingNeedsKey(active.kind, active.url)) {
14484
+ const kd = await fetchCodingKey(active.backendId || "");
14485
+ sessionApiKeyTail = kd?.key ? kd.key.slice(-4) : "";
14486
+ }
14487
+ } catch { /* the bar works without the tail */ }
14488
+ }
14546
14489
  // Task #133: no RAG storage question any more — the terminal ALWAYS keeps the index on
14547
14490
  // this machine. `/rag-cloud` opts into additionally mirroring it to the user's S3 bucket.
14548
14491
  // Startup banner (task #131): the gu logo on the left, compact status facts on the
@@ -15479,11 +15422,6 @@ async function main() {
15479
15422
  await syncEmbedWarning({ repair: false });
15480
15423
  continue;
15481
15424
  }
15482
- if (line === "/server") {
15483
- await chooseServer();
15484
- await saveSession(cwd, history); // remember the pick for this folder
15485
- continue;
15486
- }
15487
15425
  if (line === "/full-log" || line.startsWith("/full-log ")) {
15488
15426
  await runFullLogCommand(line);
15489
15427
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gu-cli",
3
- "version": "1.0.898",
3
+ "version": "1.0.900",
4
4
  "description": "gu: the coding agent in your terminal. It reads your repo, makes the change and runs the tests, with your own models (Ollama or any OpenAI-compatible provider). Free installs keep the agent, its API and its database on your machine. macOS, Linux and Windows.",
5
5
  "keywords": [
6
6
  "ai",