From 3b02af47c948008d7846912e2bb234ca6d4d1b57 Mon Sep 17 00:00:00 2001 From: Darknode-Official Date: Sun, 1 Nov 2026 18:10:02 -0800 Subject: [PATCH] TUI polish: wrap the welcome tip, tidy /settings and colour its on/off values - welcome card: the tip word-wraps under its label (up to 3 lines) instead of being cut off with an ellipsis at the card's fixed width. - /settings: wider label column so "Redact secrets to cloud" no longer runs into its value; on/off values are green/red like the slash menu; the Limits section uses the same value column, lowercase on/off, a one-line header, and a shorter env note so rows stop wrapping at 110 columns. --- darknode.js | 12 +++++++----- lib/cli/tui-format.js | 12 +++++++++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/darknode.js b/darknode.js index 5ee1e75..9cf9056 100755 --- a/darknode.js +++ b/darknode.js @@ -5696,18 +5696,20 @@ function nexusTui(engine, cwd, nexusMd, autoResume, launch) { render(); return; } const v = { engine, model: (sess.model && sess.model !== engine ? sess.model : ""), effort, fallback, style, lean, cowork, costCap, cost: sess.cost, mode: (MODES[mode] && MODES[mode].k) || "auto-accept", guard, policyOrg: policy.org, audit: policy.audit, redact, offline, notify, ctxPct: sess.ctxWindow ? Math.min(100, Math.round((sess.ctxUsed / sess.ctxWindow) * 100)) : null, pins: pinned.size, bgRunning: bgJobs.running() }; + // on/off values read green/red at a glance, matching the slash-menu toggle colours + const onOff = (x) => /^on\b/i.test(x) ? green(x) : /^off$/i.test(x) ? red(x) : x; let text = bold("Settings") + gray(" change any option with the command shown"); - for (const s of describeSettings(v)) { text += "\n\n" + bold(cyan(s.group)); for (const r of s.rows) { const gap = " ".repeat(Math.max(2, 20 - r.value.length)); text += "\n " + r.label.padEnd(24) + bold(r.value) + gap + gray(r.cmd); } } + for (const s of describeSettings(v)) { text += "\n\n" + bold(cyan(s.group)); for (const r of s.rows) { const gap = " ".repeat(Math.max(2, 20 - r.value.length)); text += "\n " + r.label.padEnd(26) + bold(onOff(r.value)) + gap + gray(r.cmd); } } // Governed limits — all OFF by default; each shows its live value and where it came from. const liveGov = { budgetCap: costCap, autoCompact, minimizeCalls, stepCap, trimOutput, cache: cacheOn, map: repoMapOn, lean }; - text += "\n\n" + bold(cyan("Limits")) + gray(" (off by default — opt in with /settings on; persisted to ~/.nexus/settings.json. The env var shown on each row overrides it for one launch.)"); + text += "\n\n" + bold(cyan("Limits")) + gray(" off by default · saved to ~/.nexus/settings.json · an env var overrides one launch"); for (const row of settingsStore.describe()) { if (!(row.key in liveGov)) continue; const val = liveGov[row.key]; - const shown = row.key === "budgetCap" ? (val ? "$" + Number(val).toFixed(2) : "off") : (val ? "ON" : "off"); - const src = Ssrc[row.key] === "env" ? gray(" [env]") : Ssrc[row.key] === "saved" ? gray(" [saved]") : gray(" [default]"); + const shown = row.key === "budgetCap" ? (val ? "$" + Number(val).toFixed(2) : "off") : (val ? "on" : "off"); + const srcTxt = Ssrc[row.key] === "env" ? " env" : Ssrc[row.key] === "saved" ? " saved" : " default", src = gray(srcTxt) + " ".repeat(Math.max(2, 20 - shown.length - srcTxt.length)); const envName = nexusSettings.ENV_NAME[row.key]; - text += "\n " + row.key.padEnd(24) + bold(shown) + src + gray(" /settings " + row.key + (row.key === "budgetCap" ? " |off" : " on|off") + (envName ? " · env " + envName : "")); + text += "\n " + row.key.padEnd(26) + bold(onOff(shown)) + src + gray("/settings " + row.key + (row.key === "budgetCap" ? " |off" : " on|off") + (envName ? " " + envName : "")); } text += "\n " + gray("colour: NO_COLOR=1 disables, FORCE_COLOR=1 forces (NO_COLOR wins)."); transcript.push({ role: "system", text }); diff --git a/lib/cli/tui-format.js b/lib/cli/tui-format.js index 294073c..193b653 100644 --- a/lib/cli/tui-format.js +++ b/lib/cli/tui-format.js @@ -303,7 +303,17 @@ function welcomeCard(opts) { } } // ---- tip line ---- - if (opts.tip) { lines.push(rule()); lines.push(row(label(padRight("Tip", 10)) + faint(opts.tip))); } + if (opts.tip) { + // word-wrap the tip under its label instead of truncating it with "…" + lines.push(rule()); + const tw = Math.max(10, inner - 10), out = []; + let cur = ""; + for (const w of String(opts.tip).split(/\s+/).filter(Boolean)) { + if (cur && visLen(cur) + 1 + visLen(w) > tw) { out.push(cur); cur = w; } else cur = cur ? cur + " " + w : w; + } + if (cur) out.push(cur); + out.slice(0, 3).forEach((t, i) => lines.push(row((i ? " ".repeat(10) : label(padRight("Tip", 10))) + faint(t)))); + } lines.push(border("╰" + "─".repeat(W - 2) + "╯")); lines.push(""); return lines;