blun-king-cli 7.0.4 → 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 +210 -211
  2. package/lib/ui.js +284 -288
  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,27 +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
- // Show permission line
73
- ui.renderPermissionLine(permissionMode);
74
- console.log();
72
+ ui.setTheme(settings.theme || "neon");
75
73
 
76
- var SLASH_COMMANDS = [
77
- ["/help", "Show all commands"],
78
- ["/model", "Switch AI model"],
79
- ["/settings", "Show/change settings"],
80
- ["/perm", "Cycle permission mode (safe/normal/god)"],
81
- ["/status", "Token usage & uptime"],
82
- ["/whoami", "Current user info"],
83
- ["/keys", "List API keys"],
84
- ["/login", "Login from chat"],
85
- ["/clear", "Clear screen"],
86
- ["/exit", "Exit chat"],
87
- ];
88
- var slashNames = SLASH_COMMANDS.map(function (c) { return c[0]; });
74
+ var username = (creds ? creds.name : null) || "local";
75
+ var model = settings.model || "auto";
89
76
 
90
77
  var rl = readline.createInterface({
91
78
  input: process.stdin,
@@ -94,26 +81,89 @@ function startChat() {
94
81
  terminal: true,
95
82
  completer: function (line) {
96
83
  if (line.startsWith("/")) {
97
- var hits = slashNames.filter(function (c) { return c.startsWith(line); });
84
+ var hits = slashNames.filter(function (c) { return c.indexOf(line) === 0; });
98
85
  return [hits.length ? hits : slashNames, line];
99
86
  }
100
87
  return [[], line];
101
88
  },
102
89
  });
103
90
 
104
- 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();
105
154
 
106
155
  return new Promise(function (resolveChat) {
107
156
  rl.on("line", function (line) {
108
157
  var input = line.trim();
109
- ui.renderInputBoxClose();
110
158
 
111
159
  if (!input) {
112
- showPrompt(rl, username, model);
160
+ showPrompt();
113
161
  return;
114
162
  }
115
163
 
116
- // Commands
164
+ ui.renderInputPanelClose(settings);
165
+
166
+ // ── Commands ────────────────────────────────
117
167
  if (input === "/exit" || input === "/quit") {
118
168
  ui.renderResponse("Goodbye!", "system");
119
169
  rl.close();
@@ -123,245 +173,194 @@ function startChat() {
123
173
  if (input === "/clear") {
124
174
  console.clear();
125
175
  ui.renderBanner();
126
- showPrompt(rl, username, model);
176
+ showPrompt();
177
+ return;
178
+ }
179
+
180
+ if (input === "/settings") {
181
+ openSettingsPanel();
127
182
  return;
128
183
  }
129
184
 
130
185
  if (input === "/status") {
131
- ui.renderStatusBar(ui.getStats());
132
- 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();
133
199
  return;
134
200
  }
135
201
 
136
202
  if (input === "/perm" || input === "/permission") {
137
- var newMode = cyclePermission();
138
- settings.permissionMode = newMode;
203
+ var modes = ui.PERMISSION_MODES;
204
+ var idx = modes.indexOf(settings.permissionMode);
205
+ settings.permissionMode = modes[(idx + 1) % modes.length];
139
206
  auth.saveSettings(settings);
140
- ui.renderPermissionLine(newMode);
141
- console.log();
142
- showPrompt(rl, username, model);
207
+ ui.renderResponse("Permission: " + settings.permissionMode, "system");
208
+ showPrompt();
143
209
  return;
144
210
  }
145
211
 
146
- if (input === "/login") {
147
- auth
148
- .login()
149
- .then(function () {
150
- localMode = false;
151
- creds = auth.getStoredCredentials();
152
- username = (creds ? creds.name : null) || "local";
153
- ui.renderResponse("Logged in!", "system");
154
- })
155
- .catch(function (e) {
156
- ui.renderResponse(e.message, "error");
157
- })
158
- .then(function () {
159
- showPrompt(rl, username, model);
160
- });
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();
161
228
  return;
162
229
  }
163
230
 
164
- if (input === "/whoami") {
165
- auth
166
- .whoami()
167
- .catch(function (e) {
168
- ui.renderResponse(e.message, "error");
169
- })
170
- .then(function () {
171
- 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();
172
259
  });
260
+ }
173
261
  return;
174
262
  }
175
263
 
176
- if (input === "/keys") {
177
- auth
178
- .listApiKeys()
179
- .catch(function (e) {
180
- ui.renderResponse(e.message, "error");
181
- })
182
- .then(function () {
183
- showPrompt(rl, username, model);
184
- });
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(); });
185
273
  return;
186
274
  }
187
275
 
188
- if (input === "/model") {
189
- auth
190
- .promptMenu("Select Model", [
191
- { label: "Auto (recommended)", value: "auto" },
192
- { label: "Gemma 4", value: "gemma4" },
193
- { label: "Claude", value: "claude" },
194
- { label: "Llama", value: "llama" },
195
- ])
196
- .then(function (choice) {
197
- if (choice) {
198
- model = choice;
199
- settings.model = choice;
200
- auth.saveSettings(settings);
201
- ui.renderResponse("Model set to: " + choice, "system");
202
- }
203
- showPrompt(rl, username, model);
204
- });
276
+ if (input === "/whoami") {
277
+ auth.whoami().catch(function (e) {
278
+ ui.renderResponse(e.message, "error");
279
+ }).then(function () { showPrompt(); });
205
280
  return;
206
281
  }
207
282
 
208
- if (input === "/settings") {
209
- ui.renderBox(
210
- "Settings",
211
- [
212
- "Model: " + (settings.model || "auto"),
213
- "Language: " + (settings.language || "de"),
214
- "Voice: " + (settings.voice ? "on" : "off"),
215
- "Permission: " + (permissionMode || "normal"),
216
- ].join("\n"),
217
- "cyan"
218
- );
219
- auth
220
- .promptMenu("Change Setting", [
221
- { label: "Model", value: "model" },
222
- { label: "Language (de/en)", value: "language" },
223
- { label: "Voice on/off", value: "voice" },
224
- { label: "Back", value: "back" },
225
- ])
226
- .then(function (action) {
227
- if (action === "model") {
228
- return auth
229
- .promptMenu("Select Model", [
230
- { label: "Auto", value: "auto" },
231
- { label: "Gemma 4", value: "gemma4" },
232
- { label: "Claude", value: "claude" },
233
- { label: "Llama", value: "llama" },
234
- ])
235
- .then(function (m) {
236
- if (m) {
237
- model = m;
238
- settings.model = m;
239
- auth.saveSettings(settings);
240
- ui.renderResponse("Model: " + m, "system");
241
- }
242
- });
243
- } else if (action === "language") {
244
- return auth.prompt(chalk.green(" Language (de/en): ")).then(function (l) {
245
- if (l) {
246
- settings.language = l;
247
- auth.saveSettings(settings);
248
- ui.renderResponse("Language: " + l, "system");
249
- }
250
- });
251
- } else if (action === "voice") {
252
- settings.voice = !settings.voice;
253
- auth.saveSettings(settings);
254
- ui.renderResponse(
255
- "Voice: " + (settings.voice ? "on" : "off"),
256
- "system"
257
- );
258
- }
259
- })
260
- .then(function () {
261
- showPrompt(rl, username, model);
262
- });
283
+ if (input === "/keys") {
284
+ auth.listApiKeys().catch(function (e) {
285
+ ui.renderResponse(e.message, "error");
286
+ }).then(function () { showPrompt(); });
263
287
  return;
264
288
  }
265
289
 
266
290
  if (input === "/help") {
267
- console.log(chalk.bold.cyan("\n Commands:\n"));
268
- var helpLines = [
269
- ["/exit", "Exit chat"],
270
- ["/clear", "Clear screen"],
271
- ["/status", "Show token usage & uptime"],
272
- ["/login", "Login from chat"],
273
- ["/whoami", "Show current user info"],
274
- ["/keys", "List API keys"],
275
- ["/model", "Switch AI model"],
276
- ["/settings", "Show/change settings"],
277
- ["/perm", "Cycle permission mode (safe/normal/god)"],
278
- ["/help", "Show this help"],
279
- ];
280
- helpLines.forEach(function (h) {
281
- 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]));
282
297
  });
283
298
  console.log();
284
- showPrompt(rl, username, model);
299
+ console.log(t.muted(" Tip: Type / and press Tab for autocomplete"));
300
+ console.log();
301
+ showPrompt();
285
302
  return;
286
303
  }
287
304
 
288
- // Slash command picker - type just "/" to open interactive menu
305
+ // ── Slash command picker (just "/") ──────────
289
306
  if (input === "/") {
290
307
  var inquirer = require("inquirer");
291
308
  inquirer.prompt([{
292
309
  type: "list",
293
310
  name: "cmd",
294
311
  message: chalk.cyan("Select command"),
295
- choices: [
296
- { name: chalk.green("/model") + chalk.dim(" Switch AI model"), value: "/model" },
297
- { name: chalk.green("/settings") + chalk.dim(" Show/change settings"), value: "/settings" },
298
- { name: chalk.green("/perm") + chalk.dim(" Cycle permission mode"), value: "/perm" },
299
- { name: chalk.green("/status") + chalk.dim(" Token usage & uptime"), value: "/status" },
300
- { name: chalk.green("/whoami") + chalk.dim(" Current user info"), value: "/whoami" },
301
- { name: chalk.green("/keys") + chalk.dim(" List API keys"), value: "/keys" },
302
- { name: chalk.green("/login") + chalk.dim(" Login from chat"), value: "/login" },
303
- { name: chalk.green("/clear") + chalk.dim(" Clear screen"), value: "/clear" },
304
- { 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([
305
315
  new inquirer.Separator(),
306
- { name: chalk.red("/exit") + chalk.dim(" Exit chat"), value: "/exit" },
307
316
  { name: chalk.dim("Cancel"), value: "" },
308
- ],
317
+ ]),
309
318
  pageSize: 12,
310
319
  }]).then(function (ans) {
311
320
  if (ans.cmd) {
312
321
  rl.emit("line", ans.cmd);
313
322
  } else {
314
- showPrompt(rl, username, model);
323
+ showPrompt();
315
324
  }
316
325
  });
317
326
  return;
318
327
  }
319
328
 
320
- // Send message
329
+ // ── Send message ────────────────────────────
321
330
  if (localMode) {
322
- ui.renderResponse(
323
- "Local mode - cannot send messages. Use /login to authenticate.",
324
- "error"
325
- );
326
- showPrompt(rl, username, model);
331
+ ui.renderResponse("Local mode - cannot send messages. Use /login to authenticate.", "error");
332
+ showPrompt();
327
333
  return;
328
334
  }
329
335
 
330
- // Send message
331
336
  var spinner = ui.renderSpinner("Thinking...");
332
- client
333
- .sendMessage(input, { model: settings.model || "auto" })
337
+ client.sendMessage(input, { model: settings.model || "auto" })
334
338
  .then(function (res) {
335
339
  ui.stopSpinner(true, "Response received");
336
340
  if (res.status === 200 && res.data) {
337
- var text =
338
- res.data.answer ||
339
- res.data.response ||
340
- res.data.text ||
341
- res.data.message ||
342
- JSON.stringify(res.data);
343
- ui.renderResponse(text, "agent");
341
+ var text = res.data.answer || res.data.response || res.data.text || res.data.message || JSON.stringify(res.data);
344
342
  var tok = res.data.tokens || res.data.usage || {};
345
343
  var tokTotal = tok.total || tok.total_tokens || 0;
346
344
  if (!tokTotal && text) {
347
345
  tokTotal = Math.ceil(text.length / 4) + Math.ceil(input.length / 4);
348
346
  }
349
347
  if (tokTotal) ui.addTokens(tokTotal);
350
- ui.renderMiniStatus(settings.model || "chat", tokTotal);
348
+ ui.renderResponse(text, "agent", {
349
+ model: settings.model || "auto",
350
+ tokens: tokTotal,
351
+ cost: settings.costVisibility,
352
+ });
351
353
  } else if (res.status === 401 || res.status === 403) {
352
354
  ui.renderResponse("Auth error. Use /login to re-authenticate.", "error");
353
355
  } else {
354
356
  ui.renderResponse("Error: " + JSON.stringify(res.data), "error");
355
357
  }
356
- showPrompt(rl, username, model);
358
+ showPrompt();
357
359
  })
358
360
  .catch(function (e) {
359
361
  ui.stopSpinner(false, e.message);
360
- ui.renderResponse(
361
- "Request failed: " + e.message + "\n Try /login if auth expired.",
362
- "error"
363
- );
364
- showPrompt(rl, username, model);
362
+ ui.renderResponse("Request failed: " + e.message + "\n Try /login if auth expired.", "error");
363
+ showPrompt();
365
364
  });
366
365
  });
367
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 text with green prefix, no box
118
184
  var lines = rendered.split("\n");
119
185
  lines.forEach(function (line) {
120
- console.log(" " + chalk.green(line));
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.4",
3
+ "version": "7.1.0",
4
4
  "description": "BLUN AI Assistant - Command Line Interface",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"