blun-king-cli 7.0.5 → 7.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/lib/chat.js +206 -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,86 @@ 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
+ rl.setPrompt(chalk.bold.green("> "));
93
+ rl.prompt();
94
+ }
95
+
96
+ // ── Settings via inquirer (Windows-compatible) ──────
97
+ function openSettingsPanel() {
98
+ var inquirer = require("inquirer");
99
+ var t = ui.getTheme();
100
+
101
+ // Show current settings
102
+ console.log();
103
+ console.log(t.header(" Current Settings"));
104
+ console.log(t.muted(" " + "\u2500".repeat(40)));
105
+ ui.SETTINGS_FIELDS.forEach(function (f) {
106
+ console.log(" " + chalk.white(f.label + ":") + " ".repeat(Math.max(1, 14 - f.label.length)) + ui.formatSettingValue(f, settings[f.key]));
107
+ });
108
+ console.log();
109
+
110
+ inquirer.prompt([{
111
+ type: "list",
112
+ name: "field",
113
+ message: "Change setting",
114
+ choices: ui.SETTINGS_FIELDS.map(function (f) {
115
+ return { name: f.label + " — " + ui.formatSettingValue(f, settings[f.key]), value: f.key };
116
+ }).concat([{ name: chalk.dim("Back"), value: "back" }]),
117
+ pageSize: 12,
118
+ }]).then(function (ans) {
119
+ if (ans.field === "back") {
120
+ showPrompt();
121
+ return;
122
+ }
123
+ var field = null;
124
+ for (var i = 0; i < ui.SETTINGS_FIELDS.length; i++) {
125
+ if (ui.SETTINGS_FIELDS[i].key === ans.field) { field = ui.SETTINGS_FIELDS[i]; break; }
126
+ }
127
+ if (!field) { showPrompt(); return; }
128
+
129
+ return inquirer.prompt([{
130
+ type: "list",
131
+ name: "value",
132
+ message: field.label + " (" + (field.help || "") + ")",
133
+ choices: field.values.map(function (v) {
134
+ var isCurrent = settings[field.key] === v;
135
+ var label = String(v);
136
+ if (typeof v === "boolean") label = v ? "On" : "Off";
137
+ return { name: label + (isCurrent ? chalk.green(" (current)") : ""), value: v };
138
+ }),
139
+ }]).then(function (ans2) {
140
+ settings[field.key] = ans2.value;
141
+ if (field.key === "theme") ui.setTheme(settings.theme);
142
+ auth.saveSettings(settings);
143
+ ui.renderResponse(field.label + ": " + ans2.value, "system");
144
+ showPrompt();
145
+ });
146
+ });
147
+ }
148
+
149
+ console.log();
150
+ showPrompt();
103
151
 
104
152
  return new Promise(function (resolveChat) {
105
153
  rl.on("line", function (line) {
106
154
  var input = line.trim();
107
155
 
108
156
  if (!input) {
109
- showPrompt(rl, username, model);
157
+ showPrompt();
110
158
  return;
111
159
  }
112
160
 
113
- ui.renderInputBoxClose();
161
+ console.log();
114
162
 
115
- // Commands
163
+ // ── Commands ────────────────────────────────
116
164
  if (input === "/exit" || input === "/quit") {
117
165
  ui.renderResponse("Goodbye!", "system");
118
166
  rl.close();
@@ -122,245 +170,194 @@ function startChat() {
122
170
  if (input === "/clear") {
123
171
  console.clear();
124
172
  ui.renderBanner();
125
- showPrompt(rl, username, model);
173
+ showPrompt();
174
+ return;
175
+ }
176
+
177
+ if (input === "/settings") {
178
+ openSettingsPanel();
126
179
  return;
127
180
  }
128
181
 
129
182
  if (input === "/status") {
130
- ui.renderStatusBar(ui.getStats());
131
- showPrompt(rl, username, model);
183
+ var stats = ui.getStats();
184
+ var t = ui.getTheme();
185
+ console.log();
186
+ console.log(t.header(" Status"));
187
+ console.log(t.muted(" " + "\u2500".repeat(40)));
188
+ console.log(" Model: " + chalk.white(settings.model || "auto"));
189
+ console.log(" Permission: " + ui.formatSettingValue({ key: "permissionMode" }, settings.permissionMode));
190
+ console.log(" Theme: " + chalk.white(settings.theme || "neon"));
191
+ console.log(" Tokens: " + chalk.white(ui.formatNumber(stats.tokens)));
192
+ console.log(" Cost: " + chalk.white(ui.estimateCost(stats.tokens, settings.model)));
193
+ console.log(" Uptime: " + chalk.white(ui.formatDuration(stats.uptime)));
194
+ console.log();
195
+ showPrompt();
132
196
  return;
133
197
  }
134
198
 
135
199
  if (input === "/perm" || input === "/permission") {
136
- var newMode = cyclePermission();
137
- settings.permissionMode = newMode;
200
+ var modes = ui.PERMISSION_MODES;
201
+ var idx = modes.indexOf(settings.permissionMode);
202
+ settings.permissionMode = modes[(idx + 1) % modes.length];
138
203
  auth.saveSettings(settings);
139
- ui.renderPermissionLine(newMode);
140
- console.log();
141
- showPrompt(rl, username, model);
204
+ ui.renderResponse("Permission: " + settings.permissionMode, "system");
205
+ showPrompt();
142
206
  return;
143
207
  }
144
208
 
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
- });
209
+ if (input.indexOf("/theme") === 0) {
210
+ var parts = input.split(/\s+/);
211
+ if (parts[1] && ui.THEMES[parts[1]]) {
212
+ settings.theme = parts[1];
213
+ ui.setTheme(parts[1]);
214
+ auth.saveSettings(settings);
215
+ ui.renderResponse("Theme: " + parts[1], "system");
216
+ } else {
217
+ var modes = Object.keys(ui.THEMES);
218
+ var idx = modes.indexOf(settings.theme || "neon");
219
+ settings.theme = modes[(idx + 1) % modes.length];
220
+ ui.setTheme(settings.theme);
221
+ auth.saveSettings(settings);
222
+ ui.renderResponse("Theme: " + settings.theme, "system");
223
+ }
224
+ showPrompt();
160
225
  return;
161
226
  }
162
227
 
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);
228
+ if (input.indexOf("/model") === 0) {
229
+ var parts = input.split(/\s+/);
230
+ if (parts[1]) {
231
+ settings.model = parts[1];
232
+ model = parts[1];
233
+ auth.saveSettings(settings);
234
+ ui.renderResponse("Model: " + parts[1], "system");
235
+ showPrompt();
236
+ } else {
237
+ var inquirer = require("inquirer");
238
+ inquirer.prompt([{
239
+ type: "list",
240
+ name: "model",
241
+ message: chalk.cyan("Select Model"),
242
+ choices: [
243
+ { name: "Auto (recommended)", value: "auto" },
244
+ { name: "Gemma 4", value: "gemma4" },
245
+ { name: "Claude", value: "claude" },
246
+ { name: "Llama", value: "llama" },
247
+ ],
248
+ }]).then(function (ans) {
249
+ if (ans.model) {
250
+ model = ans.model;
251
+ settings.model = ans.model;
252
+ auth.saveSettings(settings);
253
+ ui.renderResponse("Model: " + ans.model, "system");
254
+ }
255
+ showPrompt();
171
256
  });
257
+ }
172
258
  return;
173
259
  }
174
260
 
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
- });
261
+ if (input === "/login") {
262
+ auth.login().then(function () {
263
+ localMode = false;
264
+ creds = auth.getStoredCredentials();
265
+ username = (creds ? creds.name : null) || "local";
266
+ ui.renderResponse("Logged in!", "system");
267
+ }).catch(function (e) {
268
+ ui.renderResponse(e.message, "error");
269
+ }).then(function () { showPrompt(); });
184
270
  return;
185
271
  }
186
272
 
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
- });
273
+ if (input === "/whoami") {
274
+ auth.whoami().catch(function (e) {
275
+ ui.renderResponse(e.message, "error");
276
+ }).then(function () { showPrompt(); });
204
277
  return;
205
278
  }
206
279
 
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
- });
280
+ if (input === "/keys") {
281
+ auth.listApiKeys().catch(function (e) {
282
+ ui.renderResponse(e.message, "error");
283
+ }).then(function () { showPrompt(); });
262
284
  return;
263
285
  }
264
286
 
265
287
  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]));
288
+ var t = ui.getTheme();
289
+ console.log();
290
+ console.log(t.header(" Commands"));
291
+ console.log(t.muted(" " + "\u2500".repeat(40)));
292
+ SLASH_COMMANDS.forEach(function (c) {
293
+ console.log(" " + chalk.green(c[0]) + " ".repeat(Math.max(1, 16 - c[0].length)) + chalk.gray(c[1]));
281
294
  });
282
295
  console.log();
283
- showPrompt(rl, username, model);
296
+ console.log(t.muted(" Tip: Type / and press Tab for autocomplete"));
297
+ console.log();
298
+ showPrompt();
284
299
  return;
285
300
  }
286
301
 
287
- // Slash command picker - type just "/" to open interactive menu
302
+ // ── Slash command picker (just "/") ──────────
288
303
  if (input === "/") {
289
304
  var inquirer = require("inquirer");
290
305
  inquirer.prompt([{
291
306
  type: "list",
292
307
  name: "cmd",
293
308
  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" },
309
+ choices: SLASH_COMMANDS.map(function (c) {
310
+ return { name: chalk.green(c[0]) + chalk.gray(" " + c[1]), value: c[0] };
311
+ }).concat([
304
312
  new inquirer.Separator(),
305
- { name: chalk.red("/exit") + chalk.dim(" Exit chat"), value: "/exit" },
306
313
  { name: chalk.dim("Cancel"), value: "" },
307
- ],
314
+ ]),
308
315
  pageSize: 12,
309
316
  }]).then(function (ans) {
310
317
  if (ans.cmd) {
311
318
  rl.emit("line", ans.cmd);
312
319
  } else {
313
- showPrompt(rl, username, model);
320
+ showPrompt();
314
321
  }
315
322
  });
316
323
  return;
317
324
  }
318
325
 
319
- // Send message
326
+ // ── Send message ────────────────────────────
320
327
  if (localMode) {
321
- ui.renderResponse(
322
- "Local mode - cannot send messages. Use /login to authenticate.",
323
- "error"
324
- );
325
- showPrompt(rl, username, model);
328
+ ui.renderResponse("Local mode - cannot send messages. Use /login to authenticate.", "error");
329
+ showPrompt();
326
330
  return;
327
331
  }
328
332
 
329
- // Send message
330
333
  var spinner = ui.renderSpinner("Thinking...");
331
- client
332
- .sendMessage(input, { model: settings.model || "auto" })
334
+ client.sendMessage(input, { model: settings.model || "auto" })
333
335
  .then(function (res) {
334
336
  ui.stopSpinner(true, "Response received");
335
337
  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");
338
+ var text = res.data.answer || res.data.response || res.data.text || res.data.message || JSON.stringify(res.data);
343
339
  var tok = res.data.tokens || res.data.usage || {};
344
340
  var tokTotal = tok.total || tok.total_tokens || 0;
345
341
  if (!tokTotal && text) {
346
342
  tokTotal = Math.ceil(text.length / 4) + Math.ceil(input.length / 4);
347
343
  }
348
344
  if (tokTotal) ui.addTokens(tokTotal);
349
- ui.renderMiniStatus(settings.model || "chat", tokTotal);
345
+ ui.renderResponse(text, "agent", {
346
+ model: settings.model || "auto",
347
+ tokens: tokTotal,
348
+ cost: settings.costVisibility,
349
+ });
350
350
  } else if (res.status === 401 || res.status === 403) {
351
351
  ui.renderResponse("Auth error. Use /login to re-authenticate.", "error");
352
352
  } else {
353
353
  ui.renderResponse("Error: " + JSON.stringify(res.data), "error");
354
354
  }
355
- showPrompt(rl, username, model);
355
+ showPrompt();
356
356
  })
357
357
  .catch(function (e) {
358
358
  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);
359
+ ui.renderResponse("Request failed: " + e.message + "\n Try /login if auth expired.", "error");
360
+ showPrompt();
364
361
  });
365
362
  });
366
363
 
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.1",
4
4
  "description": "BLUN AI Assistant - Command Line Interface",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"