blun-king-cli 7.3.0 → 7.3.1

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 (3) hide show
  1. package/lib/chat.js +93 -157
  2. package/lib/ui.js +14 -4
  3. package/package.json +1 -1
package/lib/chat.js CHANGED
@@ -378,209 +378,136 @@ function startChat() {
378
378
  }
379
379
 
380
380
  // ── Raw-Mode Settings Panel (no inquirer needed) ──────
381
- function openRawSettings() {
382
- var settingsActive = true;
383
- var selectedField = 0;
384
- var fields = ui.SETTINGS_FIELDS;
381
+ // ── Generic dropdown below input panel ─────────────────
382
+ function openDropdownBelow(items, onSelect, onClose) {
383
+ var active = true;
384
+ var selected = 0;
385
385
 
386
- function drawSettings() {
386
+ function getMenuStart() {
387
387
  var rows = getRows();
388
- var cols = getCols();
389
- var t = ui.getTheme();
390
388
  var panelTop = rows - INPUT_PANEL_HEIGHT + 1;
389
+ return panelTop + 5; // right after the bottom frame of input panel
390
+ }
391
391
 
392
- // Draw settings over the input panel area
393
- process.stdout.write(saveCursor());
394
-
395
- // Clear panel area
396
- for (var r = panelTop; r <= panelTop + 4; r++) {
392
+ function clearMenu() {
393
+ var start = getMenuStart();
394
+ var rows = getRows();
395
+ for (var r = start; r <= rows; r++) {
397
396
  process.stdout.write(cursorTo(r, 1) + clearLine());
398
397
  }
398
+ }
399
399
 
400
- // Also use scroll area bottom for more space
401
- var settingsTop = panelTop - fields.length - 3;
402
- if (settingsTop < 2) settingsTop = 2;
400
+ function drawMenu() {
401
+ var cols = getCols();
402
+ var t = ui.getTheme();
403
+ var start = getMenuStart();
403
404
 
404
- // Header
405
- process.stdout.write(cursorTo(settingsTop, 1) + clearLine());
406
- process.stdout.write(t.frame(" ╭─ Settings ") + t.frame("─".repeat(Math.max(1, cols - 16))) + t.frame("╮"));
405
+ process.stdout.write(saveCursor());
407
406
 
408
- // Fields
409
- for (var i = 0; i < fields.length; i++) {
410
- var row = settingsTop + 1 + i;
407
+ for (var i = 0; i < items.length; i++) {
408
+ var row = start + i;
411
409
  process.stdout.write(cursorTo(row, 1) + clearLine());
412
- var marker = i === selectedField ? chalk.cyan(" ▸ ") : " ";
413
- var label = fields[i].label;
414
- var value = ui.formatSettingValue(fields[i], settings[fields[i].key]);
415
- var help = i === selectedField ? chalk.dim(" ← → change") : "";
416
- process.stdout.write(t.frame(" │") + marker + chalk.white(label) + " ".repeat(Math.max(1, 16 - label.length)) + value + help);
417
- process.stdout.write(cursorTo(row, cols) + t.frame("│"));
410
+ var marker = i === selected ? chalk.cyan(" ▸ ") : " ";
411
+ process.stdout.write(marker + items[i].display);
418
412
  }
419
413
 
420
- // Footer
421
- var footerRow = settingsTop + 1 + fields.length;
422
- process.stdout.write(cursorTo(footerRow, 1) + clearLine());
423
- process.stdout.write(t.frame(" ") + chalk.dim(" ↑↓ select ← change Enter/Esc close"));
424
- process.stdout.write(cursorTo(footerRow, cols) + t.frame("│"));
425
-
426
- process.stdout.write(cursorTo(footerRow + 1, 1) + clearLine());
427
- process.stdout.write(t.frame(" ╰") + t.frame("─".repeat(Math.max(1, cols - 4))) + t.frame("╯"));
414
+ // Help line
415
+ var helpRow = start + items.length;
416
+ process.stdout.write(cursorTo(helpRow, 1) + clearLine());
417
+ process.stdout.write(chalk.dim(" ↑↓ select " + (items[0].help || "Enter confirm Esc close")));
428
418
 
429
419
  process.stdout.write(restoreCursor());
430
420
  }
431
421
 
432
- // Replace data listener temporarily
433
422
  process.stdin.removeAllListeners("data");
434
423
 
435
- function onSettingsKey(key) {
436
- if (!settingsActive) return;
424
+ function onKey(key) {
425
+ if (!active) return;
437
426
 
438
- // Escape or Enter — close settings
439
- if (key === "\u001b" && key.length === 1 || key === "\r" || key === "\n") {
440
- settingsActive = false;
427
+ // Escape
428
+ if ((key === "\u001b" && key.length === 1) || key === "\u0003") {
429
+ active = false;
441
430
  process.stdin.removeAllListeners("data");
442
- // Redraw normal UI
443
- var rows = getRows();
444
- var settingsTop = rows - INPUT_PANEL_HEIGHT + 1 - fields.length - 3;
445
- if (settingsTop < 2) settingsTop = 2;
446
- for (var r = settingsTop; r <= rows; r++) {
447
- process.stdout.write(cursorTo(r, 1) + clearLine());
448
- }
449
- drawInputPanel();
431
+ clearMenu();
432
+ if (onClose) onClose();
450
433
  startRawInput();
451
434
  return;
452
435
  }
453
436
 
454
- // Up arrow
437
+ // Up
455
438
  if (key === "\u001b[A") {
456
- selectedField = (selectedField - 1 + fields.length) % fields.length;
457
- drawSettings();
439
+ selected = (selected - 1 + items.length) % items.length;
440
+ drawMenu();
458
441
  return;
459
442
  }
460
443
 
461
- // Down arrow
444
+ // Down
462
445
  if (key === "\u001b[B") {
463
- selectedField = (selectedField + 1) % fields.length;
464
- drawSettings();
446
+ selected = (selected + 1) % items.length;
447
+ drawMenu();
465
448
  return;
466
449
  }
467
450
 
468
- // Left/Right arrow cycle value
451
+ // Left/Right for settings cycling
469
452
  if (key === "\u001b[D" || key === "\u001b[C") {
470
- var field = fields[selectedField];
471
- var values = field.values;
472
- var curIdx = values.indexOf(settings[field.key]);
473
- if (curIdx < 0) curIdx = 0;
474
- var dir = key === "\u001b[C" ? 1 : -1;
475
- var newIdx = (curIdx + dir + values.length) % values.length;
476
- settings[field.key] = values[newIdx];
477
- if (field.key === "theme") ui.setTheme(settings.theme);
478
- auth.saveSettings(settings);
479
- drawSettings();
453
+ if (items[selected].onCycle) {
454
+ items[selected].onCycle(key === "\u001b[C" ? 1 : -1);
455
+ drawMenu();
456
+ }
480
457
  return;
481
458
  }
482
459
 
483
- // Ctrl+C
484
- if (key === "\u0003") {
485
- settingsActive = false;
460
+ // Enter
461
+ if (key === "\r" || key === "\n") {
462
+ active = false;
486
463
  process.stdin.removeAllListeners("data");
487
- drawInputPanel();
464
+ clearMenu();
465
+ if (onSelect) onSelect(items[selected]);
488
466
  startRawInput();
489
467
  return;
490
468
  }
491
469
  }
492
470
 
493
- process.stdin.on("data", onSettingsKey);
494
- drawSettings();
471
+ process.stdin.on("data", onKey);
472
+ drawMenu();
495
473
  }
496
474
 
497
- // ── Raw Command Picker ─────────────────────────────────
498
- function openRawCommandPicker() {
499
- var pickerActive = true;
500
- var selectedCmd = 0;
501
-
502
- function drawPicker() {
503
- var rows = getRows();
504
- var cols = getCols();
505
- var t = ui.getTheme();
506
- var panelTop = rows - INPUT_PANEL_HEIGHT + 1;
507
- var pickerTop = panelTop - SLASH_COMMANDS.length - 3;
508
- if (pickerTop < 2) pickerTop = 2;
509
-
510
- process.stdout.write(saveCursor());
511
-
512
- // Header
513
- process.stdout.write(cursorTo(pickerTop, 1) + clearLine());
514
- process.stdout.write(t.frame(" ╭─ Commands ") + t.frame("─".repeat(Math.max(1, cols - 16))) + t.frame("╮"));
515
-
516
- for (var i = 0; i < SLASH_COMMANDS.length; i++) {
517
- var row = pickerTop + 1 + i;
518
- process.stdout.write(cursorTo(row, 1) + clearLine());
519
- var marker = i === selectedCmd ? chalk.cyan(" ▸ ") : " ";
520
- process.stdout.write(t.frame(" │") + marker + chalk.green(SLASH_COMMANDS[i][0]) + " ".repeat(Math.max(1, 14 - SLASH_COMMANDS[i][0].length)) + chalk.gray(SLASH_COMMANDS[i][1]));
521
- process.stdout.write(cursorTo(row, cols) + t.frame("│"));
522
- }
523
-
524
- var footerRow = pickerTop + 1 + SLASH_COMMANDS.length;
525
- process.stdout.write(cursorTo(footerRow, 1) + clearLine());
526
- process.stdout.write(t.frame(" │") + chalk.dim(" ↑↓ select Enter run Esc close"));
527
- process.stdout.write(cursorTo(footerRow, cols) + t.frame("│"));
528
- process.stdout.write(cursorTo(footerRow + 1, 1) + clearLine());
529
- process.stdout.write(t.frame(" ╰") + t.frame("─".repeat(Math.max(1, cols - 4))) + t.frame("╯"));
530
-
531
- process.stdout.write(restoreCursor());
532
- }
533
-
534
- process.stdin.removeAllListeners("data");
535
-
536
- function onPickerKey(key) {
537
- if (!pickerActive) return;
538
-
539
- if (key === "\u001b" && key.length === 1 || key === "\u0003") {
540
- pickerActive = false;
541
- process.stdin.removeAllListeners("data");
542
- var rows = getRows();
543
- var pickerTop = rows - INPUT_PANEL_HEIGHT + 1 - SLASH_COMMANDS.length - 3;
544
- if (pickerTop < 2) pickerTop = 2;
545
- for (var r = pickerTop; r <= rows; r++) {
546
- process.stdout.write(cursorTo(r, 1) + clearLine());
547
- }
548
- drawInputPanel();
549
- startRawInput();
550
- return;
551
- }
552
-
553
- if (key === "\u001b[A") {
554
- selectedCmd = (selectedCmd - 1 + SLASH_COMMANDS.length) % SLASH_COMMANDS.length;
555
- drawPicker();
556
- return;
557
- }
558
-
559
- if (key === "\u001b[B") {
560
- selectedCmd = (selectedCmd + 1) % SLASH_COMMANDS.length;
561
- drawPicker();
562
- return;
563
- }
564
-
565
- if (key === "\r" || key === "\n") {
566
- pickerActive = false;
567
- process.stdin.removeAllListeners("data");
568
- var cmd = SLASH_COMMANDS[selectedCmd][0];
569
- var rows = getRows();
570
- var pickerTop = rows - INPUT_PANEL_HEIGHT + 1 - SLASH_COMMANDS.length - 3;
571
- if (pickerTop < 2) pickerTop = 2;
572
- for (var r = pickerTop; r <= rows; r++) {
573
- process.stdout.write(cursorTo(r, 1) + clearLine());
475
+ function openRawSettings() {
476
+ var fields = ui.SETTINGS_FIELDS;
477
+ var items = fields.map(function (f) {
478
+ return {
479
+ display: chalk.white(f.label) + " ".repeat(Math.max(1, 16 - f.label.length)) + ui.formatSettingValue(f, settings[f.key]),
480
+ field: f,
481
+ help: "← change Enter/Esc close",
482
+ onCycle: function (dir) {
483
+ var values = f.values;
484
+ var curIdx = values.indexOf(settings[f.key]);
485
+ if (curIdx < 0) curIdx = 0;
486
+ var newIdx = (curIdx + dir + values.length) % values.length;
487
+ settings[f.key] = values[newIdx];
488
+ if (f.key === "theme") ui.setTheme(settings.theme);
489
+ auth.saveSettings(settings);
490
+ // Update display text
491
+ this.display = chalk.white(f.label) + " ".repeat(Math.max(1, 16 - f.label.length)) + ui.formatSettingValue(f, settings[f.key]);
492
+ drawInputPanel(); // refresh status bar
574
493
  }
575
- drawInputPanel();
576
- startRawInput();
577
- processCommand(cmd);
578
- return;
579
- }
580
- }
494
+ };
495
+ });
496
+ openDropdownBelow(items, function () { drawInputPanel(); }, function () { drawInputPanel(); });
497
+ }
581
498
 
582
- process.stdin.on("data", onPickerKey);
583
- drawPicker();
499
+ function openRawCommandPicker() {
500
+ var items = SLASH_COMMANDS.map(function (c) {
501
+ return {
502
+ display: chalk.green(c[0]) + " ".repeat(Math.max(1, 14 - c[0].length)) + chalk.gray(c[1]),
503
+ cmd: c[0],
504
+ help: "Enter run Esc close"
505
+ };
506
+ });
507
+ openDropdownBelow(items, function (item) {
508
+ drawInputPanel();
509
+ processCommand(item.cmd);
510
+ }, function () { drawInputPanel(); });
584
511
  }
585
512
 
586
513
  // ── Settings Panel (inquirer) ─────────────────────────
@@ -775,6 +702,15 @@ function startChat() {
775
702
  inputBuffer = inputBuffer.slice(0, cursorPos) + key + inputBuffer.slice(cursorPos);
776
703
  cursorPos += key.length;
777
704
  drawInputPanel();
705
+
706
+ // Live "/" dropdown — show command picker as soon as "/" is typed
707
+ if (inputBuffer === "/") {
708
+ inputBuffer = "";
709
+ cursorPos = 0;
710
+ drawInputPanel();
711
+ openRawCommandPicker();
712
+ return;
713
+ }
778
714
  });
779
715
  }
780
716
 
package/lib/ui.js CHANGED
@@ -61,13 +61,23 @@ function setTheme(name) {
61
61
  // ── Settings Fields ───────────────────────────────────────
62
62
  var SETTINGS_FIELDS = [
63
63
  { section: "Core", key: "model", label: "Model", values: ["auto", "gemma4", "claude", "llama"], help: "Welches Modell standardmaessig antwortet." },
64
- { section: "Core", key: "language", label: "Language", values: ["de", "en"], help: "Primaere Antwortsprache." },
64
+ { section: "Core", key: "language", label: "Language", values: ["de", "en", "tr", "fr", "es"], help: "Primaere Antwortsprache." },
65
+ { section: "Core", key: "permissionMode", label: "Permission", values: ["safe", "normal", "god"], help: "Welche Aktionen BLUN ausfuehren darf." },
66
+ { section: "Core", key: "maxTokens", label: "Max Tokens", values: [1024, 2048, 4096, 8192, 16384], help: "Maximale Antwortlaenge." },
65
67
  { section: "Console", key: "theme", label: "Theme", values: ["neon", "midnight", "minimal"], help: "Farbstimmung der Konsole." },
66
- { section: "Console", key: "streamLevel", label: "Stream", values: ["off", "normal"], help: "Ob Antworten gestreamt werden." },
68
+ { section: "Console", key: "streamLevel", label: "Stream", values: ["off", "normal", "verbose"], help: "Wie Antworten angezeigt werden." },
69
+ { section: "Console", key: "markdownRendering", label: "Markdown", values: [true, false], help: "Markdown in Antworten rendern." },
70
+ { section: "Console", key: "codeHighlight", label: "Syntax", values: [true, false], help: "Code-Syntax-Highlighting." },
71
+ { section: "Console", key: "statuslineMode", label: "Statusline", values: ["compact", "full", "off"], help: "Wie detailliert die Statuszeile ist." },
67
72
  { section: "Runtime", key: "voice", label: "Voice", values: [true, false], help: "Voice-Features an/aus." },
68
- { section: "Runtime", key: "permissionMode", label: "Permission", values: ["safe", "normal", "god"], help: "Welche Aktionen BLUN ausfuehren darf." },
73
+ { section: "Runtime", key: "autoSave", label: "AutoSave", values: [true, false], help: "Chat-Verlauf automatisch speichern." },
74
+ { section: "Runtime", key: "contextMemory", label: "Memory", values: [true, false], help: "Kontext aus vorherigen Chats nutzen." },
75
+ { section: "Runtime", key: "fileAccess", label: "File Access", values: ["ask", "auto", "off"], help: "Dateizugriff: fragen/automatisch/aus." },
76
+ { section: "Runtime", key: "shellAccess", label: "Shell", values: ["ask", "auto", "off"], help: "Shell-Befehle: fragen/automatisch/aus." },
69
77
  { section: "Workflow", key: "costVisibility", label: "Cost", values: [true, false], help: "Token-/Kosten-Footer anzeigen." },
70
- { section: "Workflow", key: "statuslineMode", label: "Statusline", values: ["compact", "full"], help: "Wie detailliert die Statuszeile ist." },
78
+ { section: "Workflow", key: "autoCommit", label: "AutoCommit", values: [true, false], help: "Automatisch Git-Commits erstellen." },
79
+ { section: "Workflow", key: "codeReview", label: "Review", values: [true, false], help: "Code-Review vor Aenderungen." },
80
+ { section: "Workflow", key: "notifications", label: "Notify", values: [true, false], help: "Desktop-Benachrichtigungen." },
71
81
  ];
72
82
 
73
83
  var PERMISSION_MODES = ["safe", "normal", "god"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "7.3.0",
3
+ "version": "7.3.1",
4
4
  "description": "BLUN AI Assistant - Command Line Interface",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"