@tiens.nguyen/gu-cli 1.0.877 → 1.0.878

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 +16 -7
  2. package/package.json +1 -1
package/gu-repl.mjs CHANGED
@@ -568,6 +568,10 @@ const fmtTokens = (n) => {
568
568
  * naming it here would imply the picker is something you have to break out of.
569
569
  */
570
570
  const PICK_HINT = "↑/↓ then Enter · Esc to cancel";
571
+ // #249: a list whose last rows are orange actions says Esc UNDER them (pickFromList's footer);
572
+ // its header keeps only the arrows, so the instruction is not given twice.
573
+ const PICK_ARROWS = "↑/↓ then Enter";
574
+ const ESC_FOOTER = "Esc to cancel";
571
575
  const PICK_MANY_HINT = "↑/↓ · space toggles · ctrl+a all · Enter saves · Esc cancels";
572
576
  // How many EXTRA models one server offers alongside its default. Task #245 made it a rule, not a
573
577
  // readability cap: a server URL offers AT MOST 3 models, default included (MAX_MODELS_PER_SERVER
@@ -7157,8 +7161,8 @@ async function chooseModel() {
7157
7161
  // enough in name to have already been mixed up. The fix is to say which one THIS is — not to
7158
7162
  // print the other two here: the agent model and the embedder are independent settings with
7159
7163
  // their own commands, and listing them together implies a relationship that does not exist.
7160
- console.log(dim(` Choose the CODING model — the one that writes code (${PICK_HINT}):`));
7161
- const idx = await pickFromList(labels, activeIdx >= 0 ? activeIdx : 0);
7164
+ console.log(dim(` Choose the CODING model — the one that writes code (${PICK_ARROWS}):`));
7165
+ const idx = await pickFromList(labels, activeIdx >= 0 ? activeIdx : 0, { footer: ESC_FOOTER });
7162
7166
  if (idx < 0) {
7163
7167
  console.log(dim(" (cancelled)\n"));
7164
7168
  return;
@@ -8883,12 +8887,12 @@ async function chooseEmbedModel() {
8883
8887
  const ADD = labels.length;
8884
8888
  labels.push(actionRow("add a provider — OpenAI, Ollama…"));
8885
8889
  const head = ` Embedding model (now ${current} on ${host || currentUrl})`;
8886
- const hint = `(${PICK_HINT}):`;
8890
+ const hint = `(${PICK_ARROWS}):`;
8887
8891
  const cols = Math.max(20, process.stdout.columns || 80);
8888
8892
  console.log(dim(head.length + 2 + hint.length < cols ? `${head} ${hint}` : `${head}\n ${hint}`));
8889
8893
  // The cursor starts on the one in use: the set one, else the default.
8890
8894
  const inUse = rows.findIndex((r) => ec.sameEmbedModel(r.model, current));
8891
- const mi = await pickFromList(labels, Math.max(0, inUse));
8895
+ const mi = await pickFromList(labels, Math.max(0, inUse), { footer: ESC_FOOTER });
8892
8896
  if (mi < 0) {
8893
8897
  console.log(dim(" (cancelled)\n"));
8894
8898
  return;
@@ -9078,10 +9082,10 @@ async function chooseAgentModel() {
9078
9082
  labels.push(actionRow("add a provider — Claude API, OpenAI, Kimi, Ollama…"));
9079
9083
  // The hint drops to its own row when the header would wrap (80 columns over SSH).
9080
9084
  const head = ` Agent model — the one that TALKS (now ${current || "unset"}${curHost ? ` on ${curHost}` : ""})`;
9081
- const hint = `(${PICK_HINT}):`;
9085
+ const hint = `(${PICK_ARROWS}):`;
9082
9086
  const cols = Math.max(20, process.stdout.columns || 80);
9083
9087
  console.log(dim(head.length + 2 + hint.length < cols ? `${head} ${hint}` : `${head}\n ${hint}`));
9084
- const idx = await pickFromList(labels, Math.max(0, saved.findIndex((c) => c.current)));
9088
+ const idx = await pickFromList(labels, Math.max(0, saved.findIndex((c) => c.current)), { footer: ESC_FOOTER });
9085
9089
  if (idx < 0) {
9086
9090
  console.log(dim(" (cancelled)\n"));
9087
9091
  return;
@@ -11245,6 +11249,8 @@ let listChecked = new Set();
11245
11249
  // arrive after the choosing is over.
11246
11250
  let listMax = 0;
11247
11251
  let listNote = "";
11252
+ /** #249: a dim line under the rows (pickFromList's `footer`), drawn and erased with the block. */
11253
+ let listFooter = "";
11248
11254
  // When true, ctrl+d in a single-select picker finishes with a DELETE request for the
11249
11255
  // highlighted row instead of choosing it (task #222). Off unless a caller asks for it, so no
11250
11256
  // existing picker gains a destructive key by accident.
@@ -11274,7 +11280,7 @@ function drawList(redraw) {
11274
11280
  // provider's /v1/models was taller than the terminal: the screen scrolled, the highlighted row
11275
11281
  // went off the top, and the ESC[nA walk below then counted rows that had moved — "cannot scroll
11276
11282
  // up or down, stuck here". Two rows of margin keep the block clear of the line above it.
11277
- const maxBlock = Math.max(5, viewportHeight() - 2) - (listNote ? 1 : 0) - (listHeader ? listHeaderRows : 0);
11283
+ const maxBlock = Math.max(5, viewportHeight() - 2) - (listNote ? 1 : 0) - (listFooter ? 1 : 0) - (listHeader ? listHeaderRows : 0);
11278
11284
  const win = listWindow(listItems.length, listSel, listTop, maxBlock);
11279
11285
  listTop = win.top;
11280
11286
  const row = (i) => {
@@ -11302,6 +11308,7 @@ function drawList(redraw) {
11302
11308
  // The cap's message rides UNDER the list, in the same block, so it is erased and redrawn with
11303
11309
  // it — a note printed into the scrolling output would stay on screen after the list is gone.
11304
11310
  if (listNote) rows.push(clipVisible(GUTTER + yellow(listNote), width - 1));
11311
+ if (listFooter) rows.push(clipVisible(GUTTER + dim(listFooter), width - 1));
11305
11312
  // On a redraw the caret sits one row BELOW the block (each draw ends with "\n").
11306
11313
  const up = redraw ? physicalRows(listRowsDrawn, width) : 0;
11307
11314
  // #321: what the screen held under the picker when it opened, and how far it reaches — so the
@@ -11346,6 +11353,7 @@ function finishList(index) {
11346
11353
  listChecked = new Set();
11347
11354
  listMax = 0;
11348
11355
  listNote = "";
11356
+ listFooter = "";
11349
11357
  const allowDelete = listAllowDelete;
11350
11358
  const deleting = listDeleteRequested;
11351
11359
  listAllowDelete = false;
@@ -11486,6 +11494,7 @@ function pickFromList(items, initialSel = 0, opts = {}) {
11486
11494
  return;
11487
11495
  }
11488
11496
  listMulti = false; // explicit: a cancelled multi-select must not leak its mode into this one
11497
+ listFooter = typeof opts.footer === "string" ? opts.footer : "";
11489
11498
  listAllowDelete = opts.allowDelete === true;
11490
11499
  listDeleteRequested = false;
11491
11500
  listKeys = opts.keys && typeof opts.keys === "object" ? opts.keys : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gu-cli",
3
- "version": "1.0.877",
3
+ "version": "1.0.878",
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",