blun-king-cli 7.0.5 → 7.1.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 (3) hide show
  1. package/lib/chat.js +209 -209
  2. package/lib/ui.js +283 -287
  3. package/package.json +1 -1
package/lib/chat.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
- * BLUN King CLI v7.0.0 - Chat Module
3
- * Claude Code-style interactive chat with framed input, streaming, permission modes
2
+ * BLUN King CLI v7.1.0 - Chat Module
3
+ * Full-screen REPL with Settings Panel, Slash Suggestions, Theme support
4
+ * Adapted from Codex 1 patterns for blun.ai remote API
4
5
  * CommonJS
5
6
  */
6
7
 
@@ -11,27 +12,26 @@ var auth = require("./auth");
11
12
  var client = require("./client");
12
13
 
13
14
  var localMode = false;
14
- var permissionMode = "normal"; // safe, normal, god
15
15
 
16
- function showPrompt(rl, username, model) {
17
- ui.setCurrentPerm(permissionMode);
18
- ui.renderInputBox(username, model, permissionMode);
19
- rl.prompt();
20
- }
21
-
22
- function cyclePermission() {
23
- var modes = ui.PERMISSION_MODES;
24
- var idx = modes.indexOf(permissionMode);
25
- permissionMode = modes[(idx + 1) % modes.length];
26
- ui.setCurrentPerm(permissionMode);
27
- return permissionMode;
28
- }
16
+ // ── Slash Commands ────────────────────────────────────────
17
+ var SLASH_COMMANDS = [
18
+ ["/help", "Show all commands"],
19
+ ["/settings", "Open interactive settings panel"],
20
+ ["/model", "Switch AI model"],
21
+ ["/theme", "Switch terminal theme (neon/midnight/minimal)"],
22
+ ["/perm", "Cycle permission mode (safe/normal/god)"],
23
+ ["/status", "Token usage & uptime"],
24
+ ["/whoami", "Current user info"],
25
+ ["/keys", "List API keys"],
26
+ ["/login", "Login from chat"],
27
+ ["/clear", "Clear screen"],
28
+ ["/exit", "Exit chat"],
29
+ ];
30
+ var slashNames = SLASH_COMMANDS.map(function (c) { return c[0]; });
29
31
 
30
32
  function startChat() {
31
- // Show banner
32
33
  ui.renderBanner();
33
34
 
34
- // Check auth
35
35
  var authPromise;
36
36
  if (!auth.isAuthenticated()) {
37
37
  authPromise = auth.showWelcomeMenu().then(function (result) {
@@ -54,10 +54,7 @@ function startChat() {
54
54
  var creds = auth.getStoredCredentials();
55
55
  if (creds) {
56
56
  ui.renderResponse(
57
- "Connected as " +
58
- (creds.name || creds.email || "user") +
59
- " | Plan: " +
60
- (creds.plan || "free"),
57
+ "Connected as " + (creds.name || creds.email || "user") + " | Plan: " + (creds.plan || "free"),
61
58
  "system"
62
59
  );
63
60
  } else if (localMode) {
@@ -65,25 +62,17 @@ function startChat() {
65
62
  }
66
63
 
67
64
  var settings = auth.getSettings();
68
- permissionMode = settings.permissionMode || "normal";
69
- var username = (creds ? creds.name : null) || "local";
70
- var model = settings.model || "auto";
65
+ // Ensure all settings fields exist
66
+ if (!settings.theme) settings.theme = "neon";
67
+ if (!settings.streamLevel) settings.streamLevel = "normal";
68
+ if (!settings.permissionMode) settings.permissionMode = "normal";
69
+ if (typeof settings.costVisibility === "undefined") settings.costVisibility = true;
70
+ if (!settings.statuslineMode) settings.statuslineMode = "full";
71
71
 
72
- console.log();
72
+ ui.setTheme(settings.theme || "neon");
73
73
 
74
- var SLASH_COMMANDS = [
75
- ["/help", "Show all commands"],
76
- ["/model", "Switch AI model"],
77
- ["/settings", "Show/change settings"],
78
- ["/perm", "Cycle permission mode (safe/normal/god)"],
79
- ["/status", "Token usage & uptime"],
80
- ["/whoami", "Current user info"],
81
- ["/keys", "List API keys"],
82
- ["/login", "Login from chat"],
83
- ["/clear", "Clear screen"],
84
- ["/exit", "Exit chat"],
85
- ];
86
- var slashNames = SLASH_COMMANDS.map(function (c) { return c[0]; });
74
+ var username = (creds ? creds.name : null) || "local";
75
+ var model = settings.model || "auto";
87
76
 
88
77
  var rl = readline.createInterface({
89
78
  input: process.stdin,
@@ -92,27 +81,89 @@ function startChat() {
92
81
  terminal: true,
93
82
  completer: function (line) {
94
83
  if (line.startsWith("/")) {
95
- var hits = slashNames.filter(function (c) { return c.startsWith(line); });
84
+ var hits = slashNames.filter(function (c) { return c.indexOf(line) === 0; });
96
85
  return [hits.length ? hits : slashNames, line];
97
86
  }
98
87
  return [[], line];
99
88
  },
100
89
  });
101
90
 
102
- showPrompt(rl, username, model);
91
+ function showPrompt() {
92
+ ui.renderInputPanel(settings);
93
+ rl.setPrompt(ui.renderPrompt());
94
+ rl.prompt();
95
+ }
96
+
97
+ // ── Interactive Settings Panel ──────────────────────
98
+ function openSettingsPanel() {
99
+ var selection = 0;
100
+
101
+ function render() {
102
+ // Clear and redraw
103
+ readline.cursorTo(process.stdout, 0);
104
+ readline.clearScreenDown(process.stdout);
105
+ var lines = ui.buildSettingsPanel(settings, selection);
106
+ console.log(lines.join("\n"));
107
+ }
108
+
109
+ function cleanup() {
110
+ process.stdin.removeListener("keypress", onKeypress);
111
+ if (process.stdin.isTTY && process.stdin.setRawMode) process.stdin.setRawMode(false);
112
+ process.stdin.resume();
113
+ console.log();
114
+ showPrompt();
115
+ }
116
+
117
+ function onKeypress(_, key) {
118
+ key = key || {};
119
+ if (key.name === "up") {
120
+ selection = (selection + ui.SETTINGS_FIELDS.length - 1) % ui.SETTINGS_FIELDS.length;
121
+ render();
122
+ } else if (key.name === "down") {
123
+ selection = (selection + 1) % ui.SETTINGS_FIELDS.length;
124
+ render();
125
+ } else if (key.name === "right") {
126
+ var field = ui.SETTINGS_FIELDS[selection];
127
+ settings = ui.cycleSetting(settings, field.key, 1);
128
+ if (field.key === "theme") ui.setTheme(settings.theme);
129
+ auth.saveSettings(settings);
130
+ render();
131
+ } else if (key.name === "left") {
132
+ var field = ui.SETTINGS_FIELDS[selection];
133
+ settings = ui.cycleSetting(settings, field.key, -1);
134
+ if (field.key === "theme") ui.setTheme(settings.theme);
135
+ auth.saveSettings(settings);
136
+ render();
137
+ } else if (key.name === "return" || key.name === "escape") {
138
+ cleanup();
139
+ } else if (key.ctrl && key.name === "c") {
140
+ cleanup();
141
+ }
142
+ }
143
+
144
+ // Pause readline, take raw input
145
+ rl.pause();
146
+ readline.emitKeypressEvents(process.stdin);
147
+ if (process.stdin.isTTY && process.stdin.setRawMode) process.stdin.setRawMode(true);
148
+ process.stdin.on("keypress", onKeypress);
149
+ render();
150
+ }
151
+
152
+ console.log();
153
+ showPrompt();
103
154
 
104
155
  return new Promise(function (resolveChat) {
105
156
  rl.on("line", function (line) {
106
157
  var input = line.trim();
107
158
 
108
159
  if (!input) {
109
- showPrompt(rl, username, model);
160
+ showPrompt();
110
161
  return;
111
162
  }
112
163
 
113
- ui.renderInputBoxClose();
164
+ ui.renderInputPanelClose(settings);
114
165
 
115
- // Commands
166
+ // ── Commands ────────────────────────────────
116
167
  if (input === "/exit" || input === "/quit") {
117
168
  ui.renderResponse("Goodbye!", "system");
118
169
  rl.close();
@@ -122,245 +173,194 @@ function startChat() {
122
173
  if (input === "/clear") {
123
174
  console.clear();
124
175
  ui.renderBanner();
125
- showPrompt(rl, username, model);
176
+ showPrompt();
177
+ return;
178
+ }
179
+
180
+ if (input === "/settings") {
181
+ openSettingsPanel();
126
182
  return;
127
183
  }
128
184
 
129
185
  if (input === "/status") {
130
- ui.renderStatusBar(ui.getStats());
131
- showPrompt(rl, username, model);
186
+ var stats = ui.getStats();
187
+ var t = ui.getTheme();
188
+ console.log();
189
+ console.log(t.header(" Status"));
190
+ console.log(t.muted(" " + "\u2500".repeat(40)));
191
+ console.log(" Model: " + chalk.white(settings.model || "auto"));
192
+ console.log(" Permission: " + ui.formatSettingValue({ key: "permissionMode" }, settings.permissionMode));
193
+ console.log(" Theme: " + chalk.white(settings.theme || "neon"));
194
+ console.log(" Tokens: " + chalk.white(ui.formatNumber(stats.tokens)));
195
+ console.log(" Cost: " + chalk.white(ui.estimateCost(stats.tokens, settings.model)));
196
+ console.log(" Uptime: " + chalk.white(ui.formatDuration(stats.uptime)));
197
+ console.log();
198
+ showPrompt();
132
199
  return;
133
200
  }
134
201
 
135
202
  if (input === "/perm" || input === "/permission") {
136
- var newMode = cyclePermission();
137
- settings.permissionMode = newMode;
203
+ var modes = ui.PERMISSION_MODES;
204
+ var idx = modes.indexOf(settings.permissionMode);
205
+ settings.permissionMode = modes[(idx + 1) % modes.length];
138
206
  auth.saveSettings(settings);
139
- ui.renderPermissionLine(newMode);
140
- console.log();
141
- showPrompt(rl, username, model);
207
+ ui.renderResponse("Permission: " + settings.permissionMode, "system");
208
+ showPrompt();
142
209
  return;
143
210
  }
144
211
 
145
- if (input === "/login") {
146
- auth
147
- .login()
148
- .then(function () {
149
- localMode = false;
150
- creds = auth.getStoredCredentials();
151
- username = (creds ? creds.name : null) || "local";
152
- ui.renderResponse("Logged in!", "system");
153
- })
154
- .catch(function (e) {
155
- ui.renderResponse(e.message, "error");
156
- })
157
- .then(function () {
158
- showPrompt(rl, username, model);
159
- });
212
+ if (input.indexOf("/theme") === 0) {
213
+ var parts = input.split(/\s+/);
214
+ if (parts[1] && ui.THEMES[parts[1]]) {
215
+ settings.theme = parts[1];
216
+ ui.setTheme(parts[1]);
217
+ auth.saveSettings(settings);
218
+ ui.renderResponse("Theme: " + parts[1], "system");
219
+ } else {
220
+ var modes = Object.keys(ui.THEMES);
221
+ var idx = modes.indexOf(settings.theme || "neon");
222
+ settings.theme = modes[(idx + 1) % modes.length];
223
+ ui.setTheme(settings.theme);
224
+ auth.saveSettings(settings);
225
+ ui.renderResponse("Theme: " + settings.theme, "system");
226
+ }
227
+ showPrompt();
160
228
  return;
161
229
  }
162
230
 
163
- if (input === "/whoami") {
164
- auth
165
- .whoami()
166
- .catch(function (e) {
167
- ui.renderResponse(e.message, "error");
168
- })
169
- .then(function () {
170
- showPrompt(rl, username, model);
231
+ if (input.indexOf("/model") === 0) {
232
+ var parts = input.split(/\s+/);
233
+ if (parts[1]) {
234
+ settings.model = parts[1];
235
+ model = parts[1];
236
+ auth.saveSettings(settings);
237
+ ui.renderResponse("Model: " + parts[1], "system");
238
+ showPrompt();
239
+ } else {
240
+ var inquirer = require("inquirer");
241
+ inquirer.prompt([{
242
+ type: "list",
243
+ name: "model",
244
+ message: chalk.cyan("Select Model"),
245
+ choices: [
246
+ { name: "Auto (recommended)", value: "auto" },
247
+ { name: "Gemma 4", value: "gemma4" },
248
+ { name: "Claude", value: "claude" },
249
+ { name: "Llama", value: "llama" },
250
+ ],
251
+ }]).then(function (ans) {
252
+ if (ans.model) {
253
+ model = ans.model;
254
+ settings.model = ans.model;
255
+ auth.saveSettings(settings);
256
+ ui.renderResponse("Model: " + ans.model, "system");
257
+ }
258
+ showPrompt();
171
259
  });
260
+ }
172
261
  return;
173
262
  }
174
263
 
175
- if (input === "/keys") {
176
- auth
177
- .listApiKeys()
178
- .catch(function (e) {
179
- ui.renderResponse(e.message, "error");
180
- })
181
- .then(function () {
182
- showPrompt(rl, username, model);
183
- });
264
+ if (input === "/login") {
265
+ auth.login().then(function () {
266
+ localMode = false;
267
+ creds = auth.getStoredCredentials();
268
+ username = (creds ? creds.name : null) || "local";
269
+ ui.renderResponse("Logged in!", "system");
270
+ }).catch(function (e) {
271
+ ui.renderResponse(e.message, "error");
272
+ }).then(function () { showPrompt(); });
184
273
  return;
185
274
  }
186
275
 
187
- if (input === "/model") {
188
- auth
189
- .promptMenu("Select Model", [
190
- { label: "Auto (recommended)", value: "auto" },
191
- { label: "Gemma 4", value: "gemma4" },
192
- { label: "Claude", value: "claude" },
193
- { label: "Llama", value: "llama" },
194
- ])
195
- .then(function (choice) {
196
- if (choice) {
197
- model = choice;
198
- settings.model = choice;
199
- auth.saveSettings(settings);
200
- ui.renderResponse("Model set to: " + choice, "system");
201
- }
202
- showPrompt(rl, username, model);
203
- });
276
+ if (input === "/whoami") {
277
+ auth.whoami().catch(function (e) {
278
+ ui.renderResponse(e.message, "error");
279
+ }).then(function () { showPrompt(); });
204
280
  return;
205
281
  }
206
282
 
207
- if (input === "/settings") {
208
- ui.renderBox(
209
- "Settings",
210
- [
211
- "Model: " + (settings.model || "auto"),
212
- "Language: " + (settings.language || "de"),
213
- "Voice: " + (settings.voice ? "on" : "off"),
214
- "Permission: " + (permissionMode || "normal"),
215
- ].join("\n"),
216
- "cyan"
217
- );
218
- auth
219
- .promptMenu("Change Setting", [
220
- { label: "Model", value: "model" },
221
- { label: "Language (de/en)", value: "language" },
222
- { label: "Voice on/off", value: "voice" },
223
- { label: "Back", value: "back" },
224
- ])
225
- .then(function (action) {
226
- if (action === "model") {
227
- return auth
228
- .promptMenu("Select Model", [
229
- { label: "Auto", value: "auto" },
230
- { label: "Gemma 4", value: "gemma4" },
231
- { label: "Claude", value: "claude" },
232
- { label: "Llama", value: "llama" },
233
- ])
234
- .then(function (m) {
235
- if (m) {
236
- model = m;
237
- settings.model = m;
238
- auth.saveSettings(settings);
239
- ui.renderResponse("Model: " + m, "system");
240
- }
241
- });
242
- } else if (action === "language") {
243
- return auth.prompt(chalk.green(" Language (de/en): ")).then(function (l) {
244
- if (l) {
245
- settings.language = l;
246
- auth.saveSettings(settings);
247
- ui.renderResponse("Language: " + l, "system");
248
- }
249
- });
250
- } else if (action === "voice") {
251
- settings.voice = !settings.voice;
252
- auth.saveSettings(settings);
253
- ui.renderResponse(
254
- "Voice: " + (settings.voice ? "on" : "off"),
255
- "system"
256
- );
257
- }
258
- })
259
- .then(function () {
260
- showPrompt(rl, username, model);
261
- });
283
+ if (input === "/keys") {
284
+ auth.listApiKeys().catch(function (e) {
285
+ ui.renderResponse(e.message, "error");
286
+ }).then(function () { showPrompt(); });
262
287
  return;
263
288
  }
264
289
 
265
290
  if (input === "/help") {
266
- console.log(chalk.bold.cyan("\n Commands:\n"));
267
- var helpLines = [
268
- ["/exit", "Exit chat"],
269
- ["/clear", "Clear screen"],
270
- ["/status", "Show token usage & uptime"],
271
- ["/login", "Login from chat"],
272
- ["/whoami", "Show current user info"],
273
- ["/keys", "List API keys"],
274
- ["/model", "Switch AI model"],
275
- ["/settings", "Show/change settings"],
276
- ["/perm", "Cycle permission mode (safe/normal/god)"],
277
- ["/help", "Show this help"],
278
- ];
279
- helpLines.forEach(function (h) {
280
- console.log(" " + chalk.green(h[0]) + " ".repeat(14 - h[0].length) + chalk.dim(h[1]));
291
+ var t = ui.getTheme();
292
+ console.log();
293
+ console.log(t.header(" Commands"));
294
+ console.log(t.muted(" " + "\u2500".repeat(40)));
295
+ SLASH_COMMANDS.forEach(function (c) {
296
+ console.log(" " + chalk.green(c[0]) + " ".repeat(Math.max(1, 16 - c[0].length)) + chalk.gray(c[1]));
281
297
  });
282
298
  console.log();
283
- showPrompt(rl, username, model);
299
+ console.log(t.muted(" Tip: Type / and press Tab for autocomplete"));
300
+ console.log();
301
+ showPrompt();
284
302
  return;
285
303
  }
286
304
 
287
- // Slash command picker - type just "/" to open interactive menu
305
+ // ── Slash command picker (just "/") ──────────
288
306
  if (input === "/") {
289
307
  var inquirer = require("inquirer");
290
308
  inquirer.prompt([{
291
309
  type: "list",
292
310
  name: "cmd",
293
311
  message: chalk.cyan("Select command"),
294
- choices: [
295
- { name: chalk.green("/model") + chalk.dim(" Switch AI model"), value: "/model" },
296
- { name: chalk.green("/settings") + chalk.dim(" Show/change settings"), value: "/settings" },
297
- { name: chalk.green("/perm") + chalk.dim(" Cycle permission mode"), value: "/perm" },
298
- { name: chalk.green("/status") + chalk.dim(" Token usage & uptime"), value: "/status" },
299
- { name: chalk.green("/whoami") + chalk.dim(" Current user info"), value: "/whoami" },
300
- { name: chalk.green("/keys") + chalk.dim(" List API keys"), value: "/keys" },
301
- { name: chalk.green("/login") + chalk.dim(" Login from chat"), value: "/login" },
302
- { name: chalk.green("/clear") + chalk.dim(" Clear screen"), value: "/clear" },
303
- { name: chalk.green("/help") + chalk.dim(" Show all commands"), value: "/help" },
312
+ choices: SLASH_COMMANDS.map(function (c) {
313
+ return { name: chalk.green(c[0]) + chalk.gray(" " + c[1]), value: c[0] };
314
+ }).concat([
304
315
  new inquirer.Separator(),
305
- { name: chalk.red("/exit") + chalk.dim(" Exit chat"), value: "/exit" },
306
316
  { name: chalk.dim("Cancel"), value: "" },
307
- ],
317
+ ]),
308
318
  pageSize: 12,
309
319
  }]).then(function (ans) {
310
320
  if (ans.cmd) {
311
321
  rl.emit("line", ans.cmd);
312
322
  } else {
313
- showPrompt(rl, username, model);
323
+ showPrompt();
314
324
  }
315
325
  });
316
326
  return;
317
327
  }
318
328
 
319
- // Send message
329
+ // ── Send message ────────────────────────────
320
330
  if (localMode) {
321
- ui.renderResponse(
322
- "Local mode - cannot send messages. Use /login to authenticate.",
323
- "error"
324
- );
325
- showPrompt(rl, username, model);
331
+ ui.renderResponse("Local mode - cannot send messages. Use /login to authenticate.", "error");
332
+ showPrompt();
326
333
  return;
327
334
  }
328
335
 
329
- // Send message
330
336
  var spinner = ui.renderSpinner("Thinking...");
331
- client
332
- .sendMessage(input, { model: settings.model || "auto" })
337
+ client.sendMessage(input, { model: settings.model || "auto" })
333
338
  .then(function (res) {
334
339
  ui.stopSpinner(true, "Response received");
335
340
  if (res.status === 200 && res.data) {
336
- var text =
337
- res.data.answer ||
338
- res.data.response ||
339
- res.data.text ||
340
- res.data.message ||
341
- JSON.stringify(res.data);
342
- ui.renderResponse(text, "agent");
341
+ var text = res.data.answer || res.data.response || res.data.text || res.data.message || JSON.stringify(res.data);
343
342
  var tok = res.data.tokens || res.data.usage || {};
344
343
  var tokTotal = tok.total || tok.total_tokens || 0;
345
344
  if (!tokTotal && text) {
346
345
  tokTotal = Math.ceil(text.length / 4) + Math.ceil(input.length / 4);
347
346
  }
348
347
  if (tokTotal) ui.addTokens(tokTotal);
349
- ui.renderMiniStatus(settings.model || "chat", tokTotal);
348
+ ui.renderResponse(text, "agent", {
349
+ model: settings.model || "auto",
350
+ tokens: tokTotal,
351
+ cost: settings.costVisibility,
352
+ });
350
353
  } else if (res.status === 401 || res.status === 403) {
351
354
  ui.renderResponse("Auth error. Use /login to re-authenticate.", "error");
352
355
  } else {
353
356
  ui.renderResponse("Error: " + JSON.stringify(res.data), "error");
354
357
  }
355
- showPrompt(rl, username, model);
358
+ showPrompt();
356
359
  })
357
360
  .catch(function (e) {
358
361
  ui.stopSpinner(false, e.message);
359
- ui.renderResponse(
360
- "Request failed: " + e.message + "\n Try /login if auth expired.",
361
- "error"
362
- );
363
- showPrompt(rl, username, model);
362
+ ui.renderResponse("Request failed: " + e.message + "\n Try /login if auth expired.", "error");
363
+ showPrompt();
364
364
  });
365
365
  });
366
366
 
package/lib/ui.js CHANGED
@@ -1,13 +1,11 @@
1
1
  /**
2
- * BLUN King CLI v7.0.0 - Rich Terminal UI Module
3
- * Claude Code-style terminal experience
4
- * CommonJS - chalk@4, ora@5, boxen@5
2
+ * BLUN King CLI v7.1.0 - Rich Terminal UI Module
3
+ * Adapted from Codex 1 patterns + Claude Code style
4
+ * CommonJS - chalk@4, ora@5
5
5
  */
6
6
 
7
7
  var chalk = require("chalk");
8
8
  var ora = require("ora");
9
- var Table = require("cli-table3");
10
- var boxen = require("boxen");
11
9
  var gradient = require("gradient-string");
12
10
  var figlet = require("figlet");
13
11
 
@@ -16,25 +14,104 @@ var startTime = Date.now();
16
14
  var totalTokens = 0;
17
15
  var totalCost = 0;
18
16
 
19
- var colors = {
20
- user: chalk.cyan,
21
- agent: chalk.green,
22
- system: chalk.yellow,
23
- error: chalk.red,
24
- dim: chalk.gray,
25
- accent: chalk.magenta,
26
- bold: chalk.bold,
27
- code: chalk.bgGray.white,
17
+ // ── Theme System ──────────────────────────────────────────
18
+ var THEMES = {
19
+ neon: {
20
+ frame: chalk.cyan,
21
+ input: chalk.blue,
22
+ response: chalk.green,
23
+ error: chalk.red,
24
+ header: chalk.cyan,
25
+ accent: chalk.yellow,
26
+ muted: chalk.gray,
27
+ status: chalk.dim,
28
+ },
29
+ midnight: {
30
+ frame: chalk.magenta,
31
+ input: chalk.gray,
32
+ response: chalk.green,
33
+ error: chalk.red,
34
+ header: chalk.blue,
35
+ accent: chalk.magenta,
36
+ muted: chalk.gray,
37
+ status: chalk.dim,
38
+ },
39
+ minimal: {
40
+ frame: chalk.gray,
41
+ input: chalk.gray,
42
+ response: chalk.blue,
43
+ error: chalk.red,
44
+ header: chalk.gray,
45
+ accent: chalk.white,
46
+ muted: chalk.gray,
47
+ status: chalk.dim,
48
+ },
28
49
  };
29
50
 
30
- // Permission modes
51
+ var currentTheme = "neon";
52
+
53
+ function getTheme() {
54
+ return THEMES[currentTheme] || THEMES.neon;
55
+ }
56
+
57
+ function setTheme(name) {
58
+ if (THEMES[name]) currentTheme = name;
59
+ }
60
+
61
+ // ── Settings Fields ───────────────────────────────────────
62
+ var SETTINGS_FIELDS = [
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." },
65
+ { 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." },
67
+ { 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." },
69
+ { 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." },
71
+ ];
72
+
31
73
  var PERMISSION_MODES = ["safe", "normal", "god"];
32
- var permissionLabels = {
33
- safe: chalk.green("safe"),
34
- normal: chalk.yellow("normal"),
35
- god: chalk.red("god mode"),
36
- };
37
74
 
75
+ // ── Helpers ───────────────────────────────────────────────
76
+ function getWidth() {
77
+ return Math.min((process.stdout.columns || 80) - 4, 96);
78
+ }
79
+
80
+ function fitPlain(text, width) {
81
+ var raw = String(text || "");
82
+ if (raw.length <= width) return raw + " ".repeat(width - raw.length);
83
+ if (width <= 3) return "...";
84
+ return raw.slice(0, width - 3) + "...";
85
+ }
86
+
87
+ function formatNumber(v) {
88
+ return Number(v || 0).toLocaleString("de-DE");
89
+ }
90
+
91
+ function formatDuration(ms) {
92
+ var s = Math.floor((ms || 0) / 1000);
93
+ var m = Math.floor(s / 60);
94
+ s = s % 60;
95
+ return m + "m " + (s < 10 ? "0" : "") + s + "s";
96
+ }
97
+
98
+ function estimateCost(tokens, model) {
99
+ var rates = { auto: 0.004, gemma4: 0.0015, claude: 0.015, llama: 0.002 };
100
+ var rate = rates[String(model || "auto").toLowerCase()] || rates.auto;
101
+ return ((Number(tokens || 0) / 1000) * rate).toFixed(4) + " EUR";
102
+ }
103
+
104
+ function formatSettingValue(field, value) {
105
+ if (field.key === "language") return value === "en" ? "English" : "Deutsch";
106
+ if (field.key === "permissionMode") {
107
+ var colors = { safe: chalk.green, normal: chalk.yellow, god: chalk.red };
108
+ return (colors[value] || chalk.white)(String(value));
109
+ }
110
+ if (typeof value === "boolean") return value ? chalk.green("On") : chalk.gray("Off");
111
+ return chalk.white(String(value || "-"));
112
+ }
113
+
114
+ // ── Banner ────────────────────────────────────────────────
38
115
  function renderBanner() {
39
116
  var art;
40
117
  try {
@@ -43,179 +120,184 @@ function renderBanner() {
43
120
  art = figlet.textSync("BLUN King", { font: "Standard" });
44
121
  }
45
122
  console.log(gradient.pastel.multiline(art));
46
- console.log(chalk.dim(" " + "\u2500".repeat(53)));
123
+ var t = getTheme();
124
+ console.log(t.muted(" " + "\u2500".repeat(53)));
47
125
  console.log(
48
- chalk.dim(" ") +
49
- chalk.bold.white("BLUN King CLI") +
50
- chalk.dim(" \u2502 ") +
51
- chalk.green("v" + require("../package.json").version) +
52
- chalk.dim(" \u2502 ") +
53
- chalk.yellow("Multi-KI Engine")
126
+ " " + chalk.bold.white("BLUN King CLI") +
127
+ t.muted(" \u2502 ") +
128
+ chalk.green("v" + require("../package.json").version) +
129
+ t.muted(" \u2502 ") +
130
+ t.accent("Multi-KI Engine")
54
131
  );
55
- console.log(chalk.dim(" " + "\u2500".repeat(53) + "\n"));
132
+ console.log(t.muted(" " + "\u2500".repeat(53) + "\n"));
56
133
  }
57
134
 
58
- function getInputBoxWidth() {
59
- return Math.min((process.stdout.columns || 80) - 2, 76);
60
- }
135
+ // ── Input Panel (framed) ──────────────────────────────────
136
+ function renderInputPanel(settings) {
137
+ var t = getTheme();
138
+ var w = getWidth();
139
+ var inner = w - 2;
140
+ var model = (settings && settings.model) || "auto";
141
+ var perm = (settings && settings.permissionMode) || "normal";
61
142
 
62
- function renderInputBox(username, model, permMode) {
63
- var w = getInputBoxWidth();
64
- var pm = permMode || currentPermMode || "normal";
65
- var label = permissionLabels[pm] || permissionLabels.normal;
66
- var modelTag = chalk.bgCyan.black(" " + (model || "auto") + " ");
67
- var info = modelTag + chalk.dim(" | ") + label;
68
- console.log(chalk.green("\u256d\u2500") + chalk.green.bold(" BLUN ") + chalk.green("\u2500".repeat(Math.max(2, w - 10))) + chalk.green("\u256e"));
143
+ // Top frame
144
+ console.log(t.frame("\u256D\u2500") + t.frame.bold(" BLUN ") + t.frame("\u2500".repeat(Math.max(2, inner - 8))) + t.frame("\u256E"));
69
145
  }
70
146
 
71
- var currentPermMode = "normal";
72
- function setCurrentPerm(pm) { currentPermMode = pm; }
73
-
74
- function renderInputBoxClose() {
75
- var w = getInputBoxWidth();
76
- var pm = currentPermMode || "normal";
77
- var label = permissionLabels[pm] || permissionLabels.normal;
78
- var stats = getStats();
79
- var elapsed = Math.floor(stats.uptime / 1000);
80
- var mins = Math.floor(elapsed / 60);
81
- var secs = elapsed % 60;
82
- var infoLine = label + chalk.dim(" | Tokens: " + totalTokens.toLocaleString() + " | " + mins + "m " + secs + "s");
83
- console.log(chalk.green("\u2570") + chalk.green("\u2500") + chalk.dim(" ") + infoLine + chalk.dim(" ") + chalk.green("\u2500".repeat(Math.max(2, w - 40))) + chalk.green("\u256f"));
84
- }
147
+ function renderInputPanelClose(settings) {
148
+ var t = getTheme();
149
+ var w = getWidth();
150
+ var inner = w - 2;
151
+ var model = (settings && settings.model) || "auto";
152
+ var perm = (settings && settings.permissionMode) || "normal";
153
+ var permColor = { safe: chalk.green, normal: chalk.yellow, god: chalk.red };
154
+ var permLabel = (permColor[perm] || chalk.white)(perm);
85
155
 
86
- function renderPrompt() {
87
- return chalk.green("\u2502 ") + chalk.bold.green("> ");
88
- }
156
+ var elapsed = formatDuration(Date.now() - startTime);
157
+ var tokens = formatNumber(totalTokens);
158
+ var cost = estimateCost(totalTokens, model);
89
159
 
90
- function renderPermissionLine(permMode) {
91
- var pm = permMode || "normal";
92
- var label = permissionLabels[pm] || permissionLabels.normal;
93
- console.log(
94
- chalk.dim(" bypass permissions ") +
95
- label +
96
- chalk.dim(" (shift+tab to cycle)")
97
- );
160
+ var statusParts = [];
161
+ statusParts.push(permLabel);
162
+ statusParts.push(t.muted("Tokens: " + tokens));
163
+ statusParts.push(t.muted(elapsed));
164
+ if (settings && settings.costVisibility !== false) {
165
+ statusParts.push(t.muted(cost));
166
+ }
167
+ var statusLine = statusParts.join(t.muted(" \u2502 "));
168
+
169
+ console.log(t.frame("\u2570\u2500 ") + statusLine + " " + t.frame("\u2500".repeat(Math.max(1, inner - 50))) + t.frame("\u256F"));
98
170
  }
99
171
 
100
- function renderWelcomeInfo() {
101
- var w = getInputBoxWidth();
102
- console.log(chalk.dim("\u2502"));
103
- console.log(
104
- chalk.dim("\u2502") +
105
- chalk.dim(" Tip: Type a message and press Enter. Use /help for all commands.")
106
- );
107
- console.log(chalk.dim("\u2570" + "\u2500".repeat(w) + "\u256f"));
172
+ // ── Prompt ────────────────────────────────────────────────
173
+ function renderPrompt() {
174
+ var t = getTheme();
175
+ return t.frame("\u2502 ") + chalk.bold("> ");
108
176
  }
109
177
 
110
- function renderResponse(text, type) {
111
- var colorFn = colors[type] || colors.agent;
112
- var labels = { user: "YOU", agent: "BLUN", system: "SYS", error: "ERR" };
113
- var label = labels[type] || "BLUN";
178
+ // ── Response Panel ────────────────────────────────────────
179
+ function renderResponse(text, type, meta) {
180
+ var t = getTheme();
114
181
  var rendered = renderMarkdown(text);
115
182
 
116
183
  if (type === "agent") {
117
- // Claude Code style - plain white text, no box, no color
118
184
  var lines = rendered.split("\n");
119
185
  lines.forEach(function (line) {
120
186
  console.log(" " + line);
121
187
  });
188
+ if (meta) {
189
+ var metaParts = [];
190
+ if (meta.model) metaParts.push(chalk.bgCyan.black(" " + meta.model + " "));
191
+ if (meta.tokens) metaParts.push(t.muted("Tokens: " + formatNumber(meta.tokens)));
192
+ metaParts.push(t.muted("Total: " + formatNumber(totalTokens)));
193
+ metaParts.push(t.muted(formatDuration(Date.now() - startTime)));
194
+ if (meta.cost !== false) metaParts.push(t.muted(estimateCost(totalTokens, meta.model)));
195
+ console.log(" " + metaParts.join(t.muted(" \u2502 ")));
196
+ }
122
197
  console.log();
123
198
  } else if (type === "error") {
124
- console.log(chalk.red(" \u2718 " + rendered));
199
+ console.log(t.error(" \u2718 " + rendered));
125
200
  console.log();
126
201
  } else if (type === "system") {
127
- console.log(chalk.yellow.bold(" [SYS] ") + chalk.yellow(rendered));
128
- console.log();
129
- } else {
130
- var prefix = colorFn.bold(" [" + label + "] ");
131
- var lines = rendered.split("\n");
132
- lines.forEach(function (line, i) {
133
- if (i === 0) {
134
- console.log(prefix + colorFn(line));
135
- } else {
136
- console.log(" " + colorFn(line));
137
- }
138
- });
202
+ console.log(t.accent(" [SYS] ") + t.accent(rendered));
139
203
  console.log();
140
204
  }
141
205
  }
142
206
 
143
- function renderStreamStart() {
144
- // Print box top for streaming response
145
- var w = getInputBoxWidth();
146
- console.log(
147
- chalk.dim(" ") +
148
- chalk.green("\u256d") +
149
- chalk.green.bold(" \u2728 BLUN ") +
150
- chalk.green("\u2500".repeat(Math.max(2, w - 12))) +
151
- chalk.green("\u256e")
152
- );
153
- process.stdout.write(chalk.green(" \u2502 "));
207
+ // ── Settings Panel (arrow-key navigated) ──────────────────
208
+ function buildSettingsPanel(settings, selection) {
209
+ var t = getTheme();
210
+ var w = getWidth();
211
+ var inner = w - 6;
212
+ var lines = [];
213
+ var currentSection = "";
214
+ var sel = selection || 0;
215
+
216
+ for (var i = 0; i < SETTINGS_FIELDS.length; i++) {
217
+ var field = SETTINGS_FIELDS[i];
218
+ if (field.section !== currentSection) {
219
+ currentSection = field.section;
220
+ if (lines.length) lines.push("");
221
+ lines.push(t.header(" " + currentSection));
222
+ }
223
+ var marker = i === sel ? chalk.cyan("> ") : " ";
224
+ var label = field.label;
225
+ var value = settings[field.key];
226
+ lines.push(marker + chalk.white(fitPlain(label, 14)) + formatSettingValue(field, value));
227
+ }
228
+
229
+ var selectedField = SETTINGS_FIELDS[Math.max(0, Math.min(sel, SETTINGS_FIELDS.length - 1))];
230
+ lines.push("");
231
+ lines.push(t.accent(" Active: " + selectedField.label + " " + formatSettingValue(selectedField, settings[selectedField.key])));
232
+ lines.push(t.muted(" " + (selectedField.help || "")));
233
+ lines.push("");
234
+ lines.push(t.muted(" Up/Down = select Left/Right = change Enter/Esc = close"));
235
+
236
+ // Frame it
237
+ var framed = [];
238
+ framed.push(t.frame("\u256D\u2500 Settings \u2500 CLI Preferences " + "\u2500".repeat(Math.max(2, inner - 30)) + "\u256E"));
239
+ lines.forEach(function (line) {
240
+ framed.push(t.frame("\u2502") + " " + line);
241
+ });
242
+ framed.push(t.frame("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
243
+ return framed;
154
244
  }
155
245
 
156
- function renderStreamChunk(text) {
157
- process.stdout.write(text);
246
+ function cycleSetting(settings, key, direction) {
247
+ var field = null;
248
+ for (var i = 0; i < SETTINGS_FIELDS.length; i++) {
249
+ if (SETTINGS_FIELDS[i].key === key) { field = SETTINGS_FIELDS[i]; break; }
250
+ }
251
+ if (!field) return settings;
252
+ var values = field.values || [];
253
+ var currentIndex = -1;
254
+ for (var j = 0; j < values.length; j++) {
255
+ if (values[j] === settings[key]) { currentIndex = j; break; }
256
+ }
257
+ if (currentIndex < 0) currentIndex = 0;
258
+ var nextIndex = (currentIndex + (direction || 1) + values.length) % values.length;
259
+ var result = {};
260
+ for (var k in settings) result[k] = settings[k];
261
+ result[key] = values[nextIndex];
262
+ return result;
158
263
  }
159
264
 
160
- function renderStreamEnd() {
161
- var w = getInputBoxWidth();
162
- console.log();
163
- console.log(
164
- chalk.dim(" ") +
165
- chalk.green("\u2570") +
166
- chalk.green("\u2500".repeat(w - 2)) +
167
- chalk.green("\u256f")
168
- );
265
+ // ── Slash Command Suggestions ─────────────────────────────
266
+ function getSlashSuggestions(input, commands, limit) {
267
+ var query = String(input || "").trim().toLowerCase();
268
+ if (!query.startsWith("/")) return [];
269
+ limit = limit || 8;
270
+
271
+ var scored = [];
272
+ for (var i = 0; i < commands.length; i++) {
273
+ var cmd = commands[i][0].toLowerCase();
274
+ var score = -1;
275
+ if (cmd === query) score = 1000;
276
+ else if (cmd.indexOf(query) === 0) score = 900 - (cmd.length - query.length);
277
+ else if (cmd.indexOf(query.slice(1)) >= 0) score = 600;
278
+ if (score >= 0) scored.push({ cmd: commands[i][0], desc: commands[i][1], score: score });
279
+ }
280
+
281
+ scored.sort(function (a, b) { return b.score - a.score; });
282
+ return scored.slice(0, limit);
169
283
  }
170
284
 
171
- function renderMarkdown(text) {
172
- var out = text;
173
- // Code blocks
174
- out = out.replace(/```(\w*)\n([\s\S]*?)```/g, function (_, lang, code) {
175
- var header = lang ? chalk.dim.italic(" " + lang + " ") : "";
176
- var lines = code.split("\n").map(function (l) {
177
- return chalk.dim(" \u2502 ") + chalk.white(l);
178
- });
179
- return (
180
- chalk.dim(" \u250c\u2500\u2500") +
181
- header +
182
- chalk.dim("\u2500".repeat(Math.max(1, 48 - (lang || "").length))) +
183
- "\n" +
184
- lines.join("\n") +
185
- "\n" +
186
- chalk.dim(" \u2514" + "\u2500".repeat(52))
187
- );
188
- });
189
- out = out.replace(/^### (.+)$/gm, function (_, h) {
190
- return chalk.bold.underline(h);
191
- });
192
- out = out.replace(/^## (.+)$/gm, function (_, h) {
193
- return chalk.bold.yellow(h);
194
- });
195
- out = out.replace(/^# (.+)$/gm, function (_, h) {
196
- return chalk.bold.magenta.underline(h);
197
- });
198
- out = out.replace(/\*\*\*(.+?)\*\*\*/g, function (_, t) {
199
- return chalk.bold.italic(t);
200
- });
201
- out = out.replace(/\*\*(.+?)\*\*/g, function (_, t) {
202
- return chalk.bold(t);
203
- });
204
- out = out.replace(/\*(.+?)\*/g, function (_, t) {
205
- return chalk.italic(t);
206
- });
207
- out = out.replace(/`([^`]+)`/g, function (_, c) {
208
- return chalk.bgGray.white(" " + c + " ");
209
- });
210
- out = out.replace(/^(\s*)[-*] (.+)$/gm, function (_, sp, t) {
211
- return sp + chalk.green("\u25cf") + " " + t;
212
- });
213
- out = out.replace(/^(\s*)\d+\. (.+)$/gm, function (_, sp, t) {
214
- return sp + chalk.yellow("\u25b8") + " " + t;
215
- });
216
- return out;
285
+ function renderSuggestions(suggestions, selectedIndex) {
286
+ var t = getTheme();
287
+ var w = getWidth();
288
+ if (!suggestions || !suggestions.length) return;
289
+ var inner = w - 6;
290
+
291
+ console.log(t.frame(" \u256D" + "\u2500".repeat(inner) + "\u256E"));
292
+ for (var i = 0; i < suggestions.length; i++) {
293
+ var prefix = i === (selectedIndex || 0) ? chalk.cyan("> ") : " ";
294
+ var line = prefix + chalk.green(fitPlain(suggestions[i].cmd, 16)) + " " + chalk.gray(suggestions[i].desc);
295
+ console.log(t.frame(" \u2502") + " " + fitPlain(line, inner - 2) + " " + t.frame("\u2502"));
296
+ }
297
+ console.log(t.frame(" \u2570" + "\u2500".repeat(inner) + "\u256F"));
217
298
  }
218
299
 
300
+ // ── Spinner ───────────────────────────────────────────────
219
301
  function renderSpinner(message) {
220
302
  if (activeSpinner) activeSpinner.stop();
221
303
  activeSpinner = ora({
@@ -236,125 +318,34 @@ function stopSpinner(success, message) {
236
318
  activeSpinner = null;
237
319
  }
238
320
 
239
- function renderTable(headers, rows) {
240
- var table = new Table({
241
- head: headers.map(function (h) {
242
- return chalk.cyan.bold(h);
243
- }),
244
- chars: {
245
- top: "\u2500", "top-mid": "\u252c", "top-left": "\u250c", "top-right": "\u2510",
246
- bottom: "\u2500", "bottom-mid": "\u2534", "bottom-left": "\u2514", "bottom-right": "\u2518",
247
- left: "\u2502", "left-mid": "\u251c", mid: "\u2500", "mid-mid": "\u253c",
248
- right: "\u2502", "right-mid": "\u2524", middle: "\u2502",
249
- },
250
- style: { "padding-left": 1, "padding-right": 1 },
251
- });
252
- rows.forEach(function (r) {
253
- table.push(r);
254
- });
255
- console.log(table.toString());
256
- console.log();
257
- }
258
-
259
- function renderBox(title, content, color) {
260
- var borderColor = color || "green";
261
- var rendered = renderMarkdown(content);
262
- console.log(
263
- boxen(rendered, {
264
- title: title ? chalk.bold(title) : undefined,
265
- titleAlignment: "center",
266
- padding: 1,
267
- margin: { top: 0, bottom: 1, left: 2, right: 2 },
268
- borderStyle: "round",
269
- borderColor: borderColor,
270
- })
271
- );
272
- }
273
-
274
- function renderCodeBlock(code, language) {
275
- var lang = language ? chalk.dim.italic(" " + language + " ") : "";
276
- var lineLen = Math.max(1, 50 - (language || "").length);
277
- console.log(chalk.dim(" \u250c\u2500\u2500") + lang + chalk.dim("\u2500".repeat(lineLen)));
278
- code.split("\n").forEach(function (line) {
279
- var highlighted = line;
280
- highlighted = highlighted.replace(/"[^"]*"/g, function (m) {
281
- return chalk.yellow(m);
282
- });
283
- highlighted = highlighted.replace(/'[^']*'/g, function (m) {
284
- return chalk.yellow(m);
285
- });
286
- var kws = [
287
- "const", "let", "var", "function", "return", "if", "else", "for",
288
- "while", "class", "import", "export", "from", "require", "async",
289
- "await", "try", "catch", "throw", "new",
290
- ];
291
- var kwRe = new RegExp("\\b(" + kws.join("|") + ")\\b", "g");
292
- highlighted = highlighted.replace(kwRe, function (m) {
293
- return chalk.magenta(m);
294
- });
295
- highlighted = highlighted.replace(/\b(\d+\.?\d*)\b/g, function (m) {
296
- return chalk.cyan(m);
297
- });
298
- highlighted = highlighted.replace(/(\/\/.*)$/g, function (m) {
299
- return chalk.dim(m);
321
+ // ── Markdown Rendering ────────────────────────────────────
322
+ function renderMarkdown(text) {
323
+ var out = text;
324
+ out = out.replace(/```(\w*)\n([\s\S]*?)```/g, function (_, lang, code) {
325
+ var header = lang ? chalk.dim.italic(" " + lang + " ") : "";
326
+ var lines = code.split("\n").map(function (l) {
327
+ return chalk.dim(" \u2502 ") + chalk.white(l);
300
328
  });
301
- console.log(chalk.dim(" \u2502 ") + highlighted);
329
+ return (
330
+ chalk.dim(" \u250c\u2500\u2500") + header +
331
+ chalk.dim("\u2500".repeat(Math.max(1, 48 - (lang || "").length))) +
332
+ "\n" + lines.join("\n") + "\n" +
333
+ chalk.dim(" \u2514" + "\u2500".repeat(52))
334
+ );
302
335
  });
303
- console.log(chalk.dim(" \u2514" + "\u2500".repeat(54)));
304
- console.log();
305
- }
306
-
307
- function renderMiniStatus(model, tokens) {
308
- var elapsed = Math.floor((Date.now() - startTime) / 1000);
309
- var mins = Math.floor(elapsed / 60);
310
- var secs = elapsed % 60;
311
- var costEst = totalCost > 0 ? "$" + totalCost.toFixed(4) : "~$" + (totalTokens * 0.000003).toFixed(4);
312
- var parts = [];
313
- parts.push(chalk.bgCyan.black(" " + (model || "chat") + " "));
314
- if (tokens) parts.push(chalk.dim("Tokens: " + tokens.toLocaleString()));
315
- parts.push(chalk.dim("Total: " + totalTokens.toLocaleString()));
316
- parts.push(chalk.dim(mins + "m " + (secs < 10 ? "0" : "") + secs + "s"));
317
- parts.push(chalk.dim(costEst));
318
- console.log(" " + parts.join(chalk.dim(" \u2502 ")));
319
- console.log();
320
- }
321
-
322
- function renderStatusBar(stats) {
323
- var s = stats || {};
324
- var model = s.model || "auto";
325
- var tokens = s.tokens || totalTokens;
326
- var cost = s.cost || totalCost;
327
- var elapsed = Math.floor((Date.now() - startTime) / 1000);
328
- var mins = Math.floor(elapsed / 60);
329
- var secs = elapsed % 60;
330
- var uptime = mins + "m " + secs + "s";
331
- var parts = [
332
- chalk.bgCyan.black(" " + model + " "),
333
- chalk.bgGreen.black(" Tokens: " + tokens.toLocaleString() + " "),
334
- chalk.bgYellow.black(" Cost: $" + cost.toFixed(4) + " "),
335
- chalk.bgMagenta.black(" Uptime: " + uptime + " "),
336
- ];
337
- var bar = parts.join(chalk.dim(" \u2502 "));
338
- var width = process.stdout.columns || 80;
339
- console.log(chalk.dim("\u2500".repeat(width)));
340
- console.log(bar);
341
- console.log(chalk.dim("\u2500".repeat(width)));
342
- }
343
-
344
- function renderProgress(current, total, label) {
345
- var width = 40;
346
- var pct = Math.min(1, current / total);
347
- var filled = Math.round(width * pct);
348
- var empty = width - filled;
349
- var bar = chalk.green("\u2588".repeat(filled)) + chalk.dim("\u2591".repeat(empty));
350
- var pctStr = chalk.bold(Math.round(pct * 100) + "%");
351
- var lbl = label ? chalk.dim(" " + label) : "";
352
- process.stdout.write(
353
- "\r " + bar + " " + pctStr + lbl + " (" + current + "/" + total + ")"
354
- );
355
- if (current >= total) console.log();
336
+ out = out.replace(/^### (.+)$/gm, function (_, h) { return chalk.bold.underline(h); });
337
+ out = out.replace(/^## (.+)$/gm, function (_, h) { return chalk.bold.yellow(h); });
338
+ out = out.replace(/^# (.+)$/gm, function (_, h) { return chalk.bold.magenta.underline(h); });
339
+ out = out.replace(/\*\*\*(.+?)\*\*\*/g, function (_, t) { return chalk.bold.italic(t); });
340
+ out = out.replace(/\*\*(.+?)\*\*/g, function (_, t) { return chalk.bold(t); });
341
+ out = out.replace(/\*(.+?)\*/g, function (_, t) { return chalk.italic(t); });
342
+ out = out.replace(/`([^`]+)`/g, function (_, c) { return chalk.bgGray.white(" " + c + " "); });
343
+ out = out.replace(/^(\s*)[-*] (.+)$/gm, function (_, sp, t) { return sp + chalk.green("\u25cf") + " " + t; });
344
+ out = out.replace(/^(\s*)\d+\. (.+)$/gm, function (_, sp, t) { return sp + chalk.yellow("\u25b8") + " " + t; });
345
+ return out;
356
346
  }
357
347
 
348
+ // ── Token Tracking ────────────────────────────────────────
358
349
  function addTokens(count, costPerToken) {
359
350
  totalTokens += count;
360
351
  totalCost += count * (costPerToken || 0.000003);
@@ -365,28 +356,33 @@ function getStats() {
365
356
  }
366
357
 
367
358
  module.exports = {
359
+ // Core
368
360
  renderBanner: renderBanner,
369
361
  renderPrompt: renderPrompt,
370
- renderInputBox: renderInputBox,
371
- renderInputBoxClose: renderInputBoxClose,
372
- setCurrentPerm: setCurrentPerm,
373
- renderWelcomeInfo: renderWelcomeInfo,
374
- renderPermissionLine: renderPermissionLine,
375
- renderMiniStatus: renderMiniStatus,
376
362
  renderResponse: renderResponse,
377
- renderStreamStart: renderStreamStart,
378
- renderStreamChunk: renderStreamChunk,
379
- renderStreamEnd: renderStreamEnd,
380
363
  renderSpinner: renderSpinner,
381
364
  stopSpinner: stopSpinner,
382
- renderTable: renderTable,
383
- renderBox: renderBox,
384
- renderCodeBlock: renderCodeBlock,
385
- renderStatusBar: renderStatusBar,
386
- renderProgress: renderProgress,
387
365
  renderMarkdown: renderMarkdown,
366
+ // Input Panel
367
+ renderInputPanel: renderInputPanel,
368
+ renderInputPanelClose: renderInputPanelClose,
369
+ // Settings
370
+ buildSettingsPanel: buildSettingsPanel,
371
+ cycleSetting: cycleSetting,
372
+ SETTINGS_FIELDS: SETTINGS_FIELDS,
373
+ // Suggestions
374
+ getSlashSuggestions: getSlashSuggestions,
375
+ renderSuggestions: renderSuggestions,
376
+ // Theme
377
+ setTheme: setTheme,
378
+ getTheme: getTheme,
379
+ // Helpers
380
+ formatSettingValue: formatSettingValue,
388
381
  addTokens: addTokens,
389
382
  getStats: getStats,
390
- colors: colors,
383
+ estimateCost: estimateCost,
384
+ formatNumber: formatNumber,
385
+ formatDuration: formatDuration,
391
386
  PERMISSION_MODES: PERMISSION_MODES,
387
+ THEMES: THEMES,
392
388
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "7.0.5",
3
+ "version": "7.1.0",
4
4
  "description": "BLUN AI Assistant - Command Line Interface",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"