@tiens.nguyen/gu-cli 1.0.877 → 1.0.879
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 +23 -8
- package/package.json +1 -1
package/gu-repl.mjs
CHANGED
|
@@ -550,6 +550,7 @@ const orange = (s) => color256(208, s); // peak-token label (task #84)
|
|
|
550
550
|
// A menu's ACTION rows (pick another model…, API keys…, add a provider…) in orange, apart from the
|
|
551
551
|
// models above them (#248). \x1b[22m first: an unselected picker row is wrapped in dim, which would
|
|
552
552
|
// turn the orange grey. Plain text when colour is off (NO_COLOR, TERM=dumb).
|
|
553
|
+
const ACTION_ROW_START = "\x1b[22m\x1b[38;5;208m";
|
|
553
554
|
const actionRow = (s) => (dim("x") === "x" ? s : `\x1b[22m${orange(s)}`);
|
|
554
555
|
// Compact token count: 950 → "950", 12300 → "12.3k", 1500000 → "1.5M" (task #83).
|
|
555
556
|
const fmtTokens = (n) => {
|
|
@@ -568,6 +569,10 @@ const fmtTokens = (n) => {
|
|
|
568
569
|
* naming it here would imply the picker is something you have to break out of.
|
|
569
570
|
*/
|
|
570
571
|
const PICK_HINT = "↑/↓ then Enter · Esc to cancel";
|
|
572
|
+
// #249: a list whose last rows are orange actions says Esc UNDER them (pickFromList's footer);
|
|
573
|
+
// its header keeps only the arrows, so the instruction is not given twice.
|
|
574
|
+
const PICK_ARROWS = "↑/↓ then Enter";
|
|
575
|
+
const ESC_FOOTER = "Esc to cancel";
|
|
571
576
|
const PICK_MANY_HINT = "↑/↓ · space toggles · ctrl+a all · Enter saves · Esc cancels";
|
|
572
577
|
// How many EXTRA models one server offers alongside its default. Task #245 made it a rule, not a
|
|
573
578
|
// readability cap: a server URL offers AT MOST 3 models, default included (MAX_MODELS_PER_SERVER
|
|
@@ -7157,8 +7162,8 @@ async function chooseModel() {
|
|
|
7157
7162
|
// enough in name to have already been mixed up. The fix is to say which one THIS is — not to
|
|
7158
7163
|
// print the other two here: the agent model and the embedder are independent settings with
|
|
7159
7164
|
// 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 (${
|
|
7161
|
-
const idx = await pickFromList(labels, activeIdx >= 0 ? activeIdx : 0);
|
|
7165
|
+
console.log(dim(` Choose the CODING model — the one that writes code (${PICK_ARROWS}):`));
|
|
7166
|
+
const idx = await pickFromList(labels, activeIdx >= 0 ? activeIdx : 0, { footer: ESC_FOOTER });
|
|
7162
7167
|
if (idx < 0) {
|
|
7163
7168
|
console.log(dim(" (cancelled)\n"));
|
|
7164
7169
|
return;
|
|
@@ -8883,12 +8888,12 @@ async function chooseEmbedModel() {
|
|
|
8883
8888
|
const ADD = labels.length;
|
|
8884
8889
|
labels.push(actionRow("add a provider — OpenAI, Ollama…"));
|
|
8885
8890
|
const head = ` Embedding model (now ${current} on ${host || currentUrl})`;
|
|
8886
|
-
const hint = `(${
|
|
8891
|
+
const hint = `(${PICK_ARROWS}):`;
|
|
8887
8892
|
const cols = Math.max(20, process.stdout.columns || 80);
|
|
8888
8893
|
console.log(dim(head.length + 2 + hint.length < cols ? `${head} ${hint}` : `${head}\n ${hint}`));
|
|
8889
8894
|
// The cursor starts on the one in use: the set one, else the default.
|
|
8890
8895
|
const inUse = rows.findIndex((r) => ec.sameEmbedModel(r.model, current));
|
|
8891
|
-
const mi = await pickFromList(labels, Math.max(0, inUse));
|
|
8896
|
+
const mi = await pickFromList(labels, Math.max(0, inUse), { footer: ESC_FOOTER });
|
|
8892
8897
|
if (mi < 0) {
|
|
8893
8898
|
console.log(dim(" (cancelled)\n"));
|
|
8894
8899
|
return;
|
|
@@ -9078,10 +9083,10 @@ async function chooseAgentModel() {
|
|
|
9078
9083
|
labels.push(actionRow("add a provider — Claude API, OpenAI, Kimi, Ollama…"));
|
|
9079
9084
|
// The hint drops to its own row when the header would wrap (80 columns over SSH).
|
|
9080
9085
|
const head = ` Agent model — the one that TALKS (now ${current || "unset"}${curHost ? ` on ${curHost}` : ""})`;
|
|
9081
|
-
const hint = `(${
|
|
9086
|
+
const hint = `(${PICK_ARROWS}):`;
|
|
9082
9087
|
const cols = Math.max(20, process.stdout.columns || 80);
|
|
9083
9088
|
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)));
|
|
9089
|
+
const idx = await pickFromList(labels, Math.max(0, saved.findIndex((c) => c.current)), { footer: ESC_FOOTER });
|
|
9085
9090
|
if (idx < 0) {
|
|
9086
9091
|
console.log(dim(" (cancelled)\n"));
|
|
9087
9092
|
return;
|
|
@@ -11245,6 +11250,8 @@ let listChecked = new Set();
|
|
|
11245
11250
|
// arrive after the choosing is over.
|
|
11246
11251
|
let listMax = 0;
|
|
11247
11252
|
let listNote = "";
|
|
11253
|
+
/** #249: a dim line under the rows (pickFromList's `footer`), drawn and erased with the block. */
|
|
11254
|
+
let listFooter = "";
|
|
11248
11255
|
// When true, ctrl+d in a single-select picker finishes with a DELETE request for the
|
|
11249
11256
|
// highlighted row instead of choosing it (task #222). Off unless a caller asks for it, so no
|
|
11250
11257
|
// existing picker gains a destructive key by accident.
|
|
@@ -11274,7 +11281,7 @@ function drawList(redraw) {
|
|
|
11274
11281
|
// provider's /v1/models was taller than the terminal: the screen scrolled, the highlighted row
|
|
11275
11282
|
// went off the top, and the ESC[nA walk below then counted rows that had moved — "cannot scroll
|
|
11276
11283
|
// 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);
|
|
11284
|
+
const maxBlock = Math.max(5, viewportHeight() - 2) - (listNote ? 1 : 0) - (listFooter ? 1 : 0) - (listHeader ? listHeaderRows : 0);
|
|
11278
11285
|
const win = listWindow(listItems.length, listSel, listTop, maxBlock);
|
|
11279
11286
|
listTop = win.top;
|
|
11280
11287
|
const row = (i) => {
|
|
@@ -11284,7 +11291,11 @@ function drawList(redraw) {
|
|
|
11284
11291
|
const box = listMulti ? (listChecked.has(i) ? "[x] " : "[ ] ") : "";
|
|
11285
11292
|
const left = listDeadline && i === listTimeoutIndex ? " (auto in " + countdownLabel(listDeadline - Date.now()) + ")" : "";
|
|
11286
11293
|
const body = box + listItems[i] + left;
|
|
11287
|
-
|
|
11294
|
+
// An orange action row (#249) gets an orange ▸ too — the cursor in the row's own colour.
|
|
11295
|
+
const cursor = i === listSel
|
|
11296
|
+
? (String(listItems[i]).startsWith(ACTION_ROW_START) ? orange("▸ ") + body : accent("▸ " + body))
|
|
11297
|
+
: dim(" " + body);
|
|
11298
|
+
return clipVisible(GUTTER + cursor, width - 1);
|
|
11288
11299
|
};
|
|
11289
11300
|
const marker = (text) => clipVisible(GUTTER + dim(text), width - 1);
|
|
11290
11301
|
const rows = [];
|
|
@@ -11302,6 +11313,8 @@ function drawList(redraw) {
|
|
|
11302
11313
|
// The cap's message rides UNDER the list, in the same block, so it is erased and redrawn with
|
|
11303
11314
|
// it — a note printed into the scrolling output would stay on screen after the list is gone.
|
|
11304
11315
|
if (listNote) rows.push(clipVisible(GUTTER + yellow(listNote), width - 1));
|
|
11316
|
+
// Indented like the rows' text (past the "▸ " column), not like the ▸ itself.
|
|
11317
|
+
if (listFooter) rows.push(clipVisible(GUTTER + dim(" " + listFooter), width - 1));
|
|
11305
11318
|
// On a redraw the caret sits one row BELOW the block (each draw ends with "\n").
|
|
11306
11319
|
const up = redraw ? physicalRows(listRowsDrawn, width) : 0;
|
|
11307
11320
|
// #321: what the screen held under the picker when it opened, and how far it reaches — so the
|
|
@@ -11346,6 +11359,7 @@ function finishList(index) {
|
|
|
11346
11359
|
listChecked = new Set();
|
|
11347
11360
|
listMax = 0;
|
|
11348
11361
|
listNote = "";
|
|
11362
|
+
listFooter = "";
|
|
11349
11363
|
const allowDelete = listAllowDelete;
|
|
11350
11364
|
const deleting = listDeleteRequested;
|
|
11351
11365
|
listAllowDelete = false;
|
|
@@ -11486,6 +11500,7 @@ function pickFromList(items, initialSel = 0, opts = {}) {
|
|
|
11486
11500
|
return;
|
|
11487
11501
|
}
|
|
11488
11502
|
listMulti = false; // explicit: a cancelled multi-select must not leak its mode into this one
|
|
11503
|
+
listFooter = typeof opts.footer === "string" ? opts.footer : "";
|
|
11489
11504
|
listAllowDelete = opts.allowDelete === true;
|
|
11490
11505
|
listDeleteRequested = false;
|
|
11491
11506
|
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.
|
|
3
|
+
"version": "1.0.879",
|
|
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",
|