blun-king-cli 2.4.0 → 2.5.0

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/bin/blun.js +93 -51
  2. package/package.json +1 -1
package/bin/blun.js CHANGED
@@ -106,16 +106,25 @@ var chatHistory = [];
106
106
  // ── Print Helpers ──
107
107
  function printHeader() {
108
108
  var w = Math.min(process.stdout.columns || 60, 60);
109
- var line = BOX.h.repeat(w - 4);
109
+ var inner = w - 4; // inner width between │ and │
110
+ var line = BOX.h.repeat(inner);
111
+ // Helper: pad text to exact inner width
112
+ function row(text, visLen) { return C.brightBlue + " " + BOX.v + C.reset + text + " ".repeat(Math.max(0, inner - visLen)) + C.brightBlue + BOX.v + C.reset; }
113
+ var modelStr = typeof config.model === "string" ? config.model : (config.model && config.model.name ? config.model.name : "default");
114
+ var wdShort = config.workdir.length > inner - 10 ? "..." + config.workdir.slice(-(inner - 13)) : config.workdir;
115
+ var titleText = " " + BOX.bot + " BLUN KING CLI v" + PKG_VERSION;
116
+ var subText = " Premium KI \u2014 Local First \u2014 Autonom";
110
117
  console.log("");
111
118
  console.log(C.brightBlue + " " + BOX.tl + line + BOX.tr + C.reset);
112
- console.log(C.brightBlue + " " + BOX.v + C.reset + C.bold + C.brightWhite + " " + BOX.bot + " BLUN KING CLI" + C.reset + C.dim + " v" + PKG_VERSION + C.reset + " ".repeat(Math.max(0, w - 24)) + C.brightBlue + BOX.v + C.reset);
113
- console.log(C.brightBlue + " " + BOX.v + C.reset + C.gray + " Premium KI — Local First — Autonom" + C.reset + " ".repeat(Math.max(0, w - 41)) + C.brightBlue + BOX.v + C.reset);
119
+ console.log(row(C.bold + C.brightWhite + titleText + C.reset, titleText.length));
120
+ console.log(row(C.gray + subText + C.reset, subText.length));
114
121
  console.log(C.brightBlue + " " + BOX.v + line + BOX.v + C.reset);
115
- console.log(C.brightBlue + " " + BOX.v + C.reset + C.gray + " API " + C.brightCyan + config.api.base_url + C.reset + " ".repeat(Math.max(0, w - 14 - config.api.base_url.length)) + C.brightBlue + BOX.v + C.reset);
116
- console.log(C.brightBlue + " " + BOX.v + C.reset + C.gray + " Model " + C.brightCyan + config.model + C.reset + " ".repeat(Math.max(0, w - 14 - config.model.length)) + C.brightBlue + BOX.v + C.reset);
117
- var wdShort = config.workdir.length > w - 16 ? "..." + config.workdir.slice(-(w - 19)) : config.workdir;
118
- console.log(C.brightBlue + " " + BOX.v + C.reset + C.gray + " Dir " + C.brightCyan + wdShort + C.reset + " ".repeat(Math.max(0, w - 14 - wdShort.length)) + C.brightBlue + BOX.v + C.reset);
122
+ var apiText = " API " + config.api.base_url;
123
+ console.log(row(C.gray + " API " + C.brightCyan + config.api.base_url + C.reset, apiText.length));
124
+ var modelText = " Model " + modelStr;
125
+ console.log(row(C.gray + " Model " + C.brightCyan + modelStr + C.reset, modelText.length));
126
+ var dirText = " Dir " + wdShort;
127
+ console.log(row(C.gray + " Dir " + C.brightCyan + wdShort + C.reset, dirText.length));
119
128
  console.log(C.brightBlue + " " + BOX.bl + line + BOX.br + C.reset);
120
129
  console.log("");
121
130
  console.log(C.gray + " /help " + C.dim + "for commands " + C.gray + BOX.dot + " just type to chat" + C.reset);
@@ -2325,59 +2334,50 @@ async function main() {
2325
2334
  var menuSelected = 0;
2326
2335
  var cursorPos = 0;
2327
2336
  var processing = false;
2328
- var drawnLines = 0; // total lines drawn by the UI (box + menu)
2337
+ var uiStartRow = -1; // row where UI starts on screen
2329
2338
 
2330
2339
  function getTermWidth() { return process.stdout.columns || 80; }
2331
2340
 
2332
- // Erase everything we drew and move cursor back to start
2341
+ // Get current cursor row via sync trick
2342
+ function getCursorRow() {
2343
+ // Fallback: track manually
2344
+ return -1;
2345
+ }
2346
+
2347
+ // Erase UI using readline module (Windows-safe)
2333
2348
  function eraseUI() {
2334
- if (drawnLines <= 0) return;
2335
- // Move cursor up to where we started drawing
2336
- process.stdout.write("\x1b[?25l");
2337
- // Go up drawnLines from current cursor position
2338
- // Cursor is on content line (line 2), so we need to go up 1 to reach top
2339
- process.stdout.write("\x1b[" + (drawnLines) + "A\r");
2340
- // Clear all lines we drew + extra buffer
2341
- for (var i = 0; i < drawnLines + 3; i++) {
2342
- process.stdout.write("\x1b[2K");
2343
- if (i < drawnLines + 2) process.stdout.write("\n");
2344
- }
2345
- // Go back up
2346
- process.stdout.write("\x1b[" + (drawnLines + 3) + "A\r");
2347
- process.stdout.write("\x1b[?25h");
2348
- drawnLines = 0;
2349
+ if (uiStartRow < 0) return;
2350
+ // Move cursor to where UI started
2351
+ readline.cursorTo(process.stdout, 0, uiStartRow);
2352
+ // Clear everything from here down
2353
+ readline.clearScreenDown(process.stdout);
2354
+ uiStartRow = -1;
2349
2355
  }
2350
2356
 
2351
- // Draw the complete UI (box + menu) from scratch
2352
- function drawUI() {
2353
- process.stdout.write("\x1b[?25l"); // hide cursor
2357
+ // Build UI lines as array, then write all at once
2358
+ function buildUILines() {
2354
2359
  var w = Math.min(getTermWidth() - 4, 76);
2355
2360
  var displayText = inputBuffer;
2356
2361
  if (displayText.length > w - 4) displayText = displayText.slice(-(w - 4));
2357
- var pad = Math.max(0, w - 3 - displayText.length);
2358
- var lines = 0;
2362
+ var inner = w - 2;
2363
+ var textPad = Math.max(0, inner - 2 - displayText.length);
2364
+ var lines = [];
2359
2365
 
2360
- // Box top
2361
- process.stdout.write("\x1b[2K" + C.brightBlue + " \u256D" + "\u2500".repeat(w - 2) + "\u256E" + C.reset + "\n"); lines++;
2362
- // Box content
2363
- process.stdout.write("\x1b[2K" + C.brightBlue + " \u2502 " + C.reset + C.brightWhite + displayText + C.reset + " ".repeat(pad) + C.brightBlue + "\u2502" + C.reset + "\n"); lines++;
2364
- // Box bottom
2365
- process.stdout.write("\x1b[2K" + C.brightBlue + " \u2570" + "\u2500".repeat(w - 2) + "\u256F" + C.reset + "\n"); lines++;
2366
+ // Box
2367
+ lines.push(C.brightBlue + " \u256D" + "\u2500".repeat(inner) + "\u256E" + C.reset);
2368
+ lines.push(C.brightBlue + " \u2502 " + C.reset + C.brightWhite + displayText + C.reset + " ".repeat(textPad) + " " + C.brightBlue + "\u2502" + C.reset);
2369
+ lines.push(C.brightBlue + " \u2570" + "\u2500".repeat(inner) + "\u256F" + C.reset);
2366
2370
 
2367
- // Status bar (permission mode + model + workdir)
2371
+ // Status bar
2368
2372
  var rawPerm = getSetting("permissions.defaultMode");
2369
2373
  var permMode = typeof rawPerm === "string" ? rawPerm : "ask";
2370
- // Check if --dangerously-skip-permissions or godmode
2371
2374
  var isDangerous = process.argv.includes("--dangerously-skip-permissions") || permMode === "allow";
2372
2375
  var permLabel = isDangerous ? "GOD MODE" : permMode === "deny" ? "LOCKED" : "ask permission";
2373
2376
  var permIcon = isDangerous ? "\u26A1" : permMode === "deny" ? "\u2718" : "\u2753";
2374
2377
  var permColor = isDangerous ? C.red + C.bold : permMode === "deny" ? C.red : C.yellow;
2375
2378
  var modelName = typeof config.model === "string" ? config.model : (config.model && config.model.name ? config.model.name : "default");
2376
2379
  var wdShort = config.workdir ? path.basename(config.workdir) : "~";
2377
- var statusLine = permColor + " " + permIcon + " " + permLabel + C.reset + C.dim + " \u2502 " + C.reset +
2378
- C.cyan + modelName + C.reset + C.dim + " \u2502 " + C.reset +
2379
- C.dim + wdShort + C.reset;
2380
- process.stdout.write("\x1b[2K" + statusLine + "\n"); lines++;
2380
+ lines.push(permColor + " " + permIcon + " " + permLabel + C.reset + C.dim + " \u2502 " + C.reset + C.cyan + modelName + C.reset + C.dim + " \u2502 " + C.reset + C.dim + wdShort + C.reset);
2381
2381
 
2382
2382
  // Menu
2383
2383
  if (menuVisible && menuItems.length > 0) {
@@ -2388,25 +2388,67 @@ async function main() {
2388
2388
  var descStr = item.desc.slice(0, getTermWidth() - 28);
2389
2389
  var cmdColor = i === menuSelected ? C.green + C.bold : C.green;
2390
2390
  var descColor = i === menuSelected ? C.brightWhite : C.white;
2391
- process.stdout.write("\x1b[2K" + prefix + cmdColor + cmdStr + descColor + descStr + C.reset + "\n");
2392
- lines++;
2391
+ lines.push(prefix + cmdColor + cmdStr + descColor + descStr + C.reset);
2393
2392
  });
2394
2393
  if (menuItems.length > 8) {
2395
- process.stdout.write("\x1b[2K" + C.dim + " ... " + (menuItems.length - 8) + " more" + C.reset + "\n");
2396
- lines++;
2394
+ lines.push(C.dim + " ... " + (menuItems.length - 8) + " more" + C.reset);
2397
2395
  }
2398
2396
  }
2399
2397
 
2400
- drawnLines = lines;
2398
+ return { lines: lines, cursorLine: 1, cursorCol: 4 + displayText.length };
2399
+ }
2400
+
2401
+ function drawUI() {
2402
+ process.stdout.write("\x1b[?25l"); // hide cursor
2403
+ // Remember where we start drawing
2404
+ uiStartRow = (process.stdout.rows || 24) - 1; // approximate
2405
+ // Use getCursorPosition trick: write lines, then position cursor
2406
+ var ui = buildUILines();
2407
+
2408
+ // Save absolute position before drawing
2409
+ // Write a marker to know our row
2410
+ process.stdout.write("\x1b[6n"); // request cursor position (async, but we don't wait)
2411
+
2412
+ // Actually just track rows manually
2413
+ uiStartRow = -1; // will be set below
2414
+
2415
+ // Clear any previous content and write fresh
2416
+ var output = ui.lines.join("\n") + "\n";
2417
+ process.stdout.write(output);
2418
+
2419
+ // Now cursor is at bottom. Calculate how many lines we wrote.
2420
+ var totalLines = ui.lines.length;
2421
+
2422
+ // Move cursor back to input position (line index 1 = content line)
2423
+ // We're at totalLines below start, need to go up (totalLines - 1 - cursorLine) from current minus 1
2424
+ var upMoves = totalLines - ui.cursorLine;
2425
+ process.stdout.write("\x1b[" + upMoves + "A");
2426
+ process.stdout.write("\r\x1b[" + ui.cursorCol + "C");
2427
+
2428
+ // Track for eraseUI: store how far up the start is from cursor
2429
+ uiStartRow = totalLines; // repurpose as "total lines drawn"
2401
2430
 
2402
- // Position cursor inside the box (line 2, after text)
2403
- process.stdout.write("\x1b[" + (lines - 1) + "A"); // back to content line
2404
- process.stdout.write("\r\x1b[" + (4 + displayText.length) + "C"); // right to cursor pos
2405
2431
  process.stdout.write("\x1b[?25h"); // show cursor
2406
2432
  }
2407
2433
 
2434
+ // Override eraseUI to use line count
2435
+ eraseUI = function() {
2436
+ if (uiStartRow <= 0) return;
2437
+ process.stdout.write("\x1b[?25l");
2438
+ // Cursor is on content line (line 1). Move up 1 to reach top.
2439
+ process.stdout.write("\x1b[1A\r");
2440
+ // Clear all lines
2441
+ for (var i = 0; i < uiStartRow + 2; i++) {
2442
+ process.stdout.write("\x1b[2K\x1b[1B");
2443
+ }
2444
+ // Move back up
2445
+ process.stdout.write("\x1b[" + (uiStartRow + 2) + "A\r");
2446
+ process.stdout.write("\x1b[?25h");
2447
+ uiStartRow = -1;
2448
+ };
2449
+
2408
2450
  function refreshUI() {
2409
- eraseUI();
2451
+ if (uiStartRow > 0) eraseUI();
2410
2452
  // Compute menu items
2411
2453
  if (inputBuffer.startsWith("/")) {
2412
2454
  var filter = inputBuffer.toLowerCase();
@@ -2425,7 +2467,7 @@ async function main() {
2425
2467
  function drawPrompt() {
2426
2468
  if (processing) return;
2427
2469
  console.log(""); // spacing
2428
- drawnLines = 0;
2470
+ uiStartRow = -1;
2429
2471
  refreshUI();
2430
2472
  }
2431
2473
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "BLUN King CLI — Premium KI Console",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"