blun-king-cli 6.3.5 → 6.4.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 +35 -22
  2. package/lib/ui.js +77 -14
  3. package/package.json +1 -1
package/lib/chat.js CHANGED
@@ -6,6 +6,11 @@ const client = require('./client');
6
6
 
7
7
  let localMode = false;
8
8
 
9
+ function showPrompt(rl, username, model) {
10
+ ui.renderInputBox(username, model);
11
+ rl.prompt();
12
+ }
13
+
9
14
  async function startChat() {
10
15
  ui.renderBanner();
11
16
 
@@ -34,39 +39,43 @@ async function startChat() {
34
39
  }
35
40
 
36
41
  const settings = auth.getSettings();
42
+ const username = (creds ? creds.name : null) || 'local';
43
+ const model = settings.model || 'auto';
44
+
37
45
  const rl = readline.createInterface({
38
46
  input: process.stdin,
39
47
  output: process.stdout,
40
- prompt: ui.renderPrompt((creds ? creds.name : null) || 'local', settings.model || 'auto'),
48
+ prompt: ui.renderPrompt(username, model),
41
49
  terminal: true
42
50
  });
43
51
 
44
- console.log(' Type /help for commands, /exit to quit.\n');
45
- rl.prompt();
52
+ showPrompt(rl, username, model);
46
53
 
47
54
  return new Promise((resolveChat) => {
48
55
  rl.on('line', async (line) => {
49
56
  const input = line.trim();
50
- if (!input) { rl.prompt(); return; }
57
+ ui.renderInputBoxClose();
58
+ if (!input) { showPrompt(rl, username, model); return; }
51
59
 
52
60
  if (input === '/exit' || input === '/quit') {
53
61
  ui.renderResponse('Goodbye!', 'system'); process.exit(0);
54
62
  }
55
- if (input === '/clear') { console.clear(); ui.renderBanner(); rl.prompt(); return; }
56
- if (input === '/status') { ui.renderStatusBar(ui.getStats()); rl.prompt(); return; }
63
+ if (input === '/clear') { console.clear(); ui.renderBanner(); showPrompt(rl, username, model); return; }
64
+ if (input === '/status') { ui.renderStatusBar(ui.getStats()); showPrompt(rl, username, model); return; }
57
65
  if (input === '/login') {
58
66
  try { await auth.login(); localMode = false; ui.renderResponse('Logged in!', 'system'); }
59
67
  catch(e) { ui.renderResponse(e.message, 'error'); }
60
- rl.prompt(); return;
68
+ showPrompt(rl, username, model); return;
61
69
  }
62
70
  if (input === '/whoami') {
63
71
  try { await auth.whoami(); } catch(e) { ui.renderResponse(e.message, 'error'); }
64
- rl.prompt(); return;
72
+ showPrompt(rl, username, model); return;
65
73
  }
66
74
  if (input === '/keys') {
67
75
  try { await auth.listApiKeys(); } catch(e) { ui.renderResponse(e.message, 'error'); }
68
- rl.prompt(); return;
69
- } if (input === '/model') {
76
+ showPrompt(rl, username, model); return;
77
+ }
78
+ if (input === '/model') {
70
79
  const s = auth.getSettings();
71
80
  const choice = await auth.promptMenu('Select Model', [
72
81
  { label: 'Auto (recommended)', value: 'auto' },
@@ -75,15 +84,15 @@ async function startChat() {
75
84
  { label: 'Llama', value: 'llama' }
76
85
  ]);
77
86
  if (choice) { s.model = choice; auth.saveSettings(s); ui.renderResponse('Model set to: ' + choice, 'system'); }
78
- rl.prompt(); return;
87
+ showPrompt(rl, username, model); return;
79
88
  }
80
89
  if (input === '/settings') {
81
90
  const s = auth.getSettings();
82
- console.log(chalk.bold.cyan('\n Current Settings:'));
83
- console.log(chalk.white(' Model: ') + (s.model || 'auto'));
84
- console.log(chalk.white(' Language: ') + (s.language || 'de'));
85
- console.log(chalk.white(' Voice: ') + (s.voice ? 'on' : 'off'));
86
- console.log('');
91
+ ui.renderBox('Settings', [
92
+ 'Model: ' + (s.model || 'auto'),
93
+ 'Language: ' + (s.language || 'de'),
94
+ 'Voice: ' + (s.voice ? 'on' : 'off')
95
+ ].join('\n'), 'cyan');
87
96
  const action = await auth.promptMenu('Change Setting', [
88
97
  { label: 'Model', value: 'model' },
89
98
  { label: 'Language (de/en)', value: 'language' },
@@ -102,7 +111,7 @@ async function startChat() {
102
111
  } else if (action === 'voice') {
103
112
  s.voice = !s.voice; auth.saveSettings(s); ui.renderResponse('Voice: ' + (s.voice ? 'on' : 'off'), 'system');
104
113
  }
105
- rl.prompt(); return;
114
+ showPrompt(rl, username, model); return;
106
115
  }
107
116
  if (input === '/help') {
108
117
  ui.renderBox('Chat Commands', [
@@ -116,12 +125,12 @@ async function startChat() {
116
125
  '/settings Show/change settings',
117
126
  '/help Show this help'
118
127
  ].join('\n'), 'cyan');
119
- rl.prompt(); return;
128
+ showPrompt(rl, username, model); return;
120
129
  }
121
130
 
122
131
  if (localMode) {
123
132
  ui.renderResponse('Local mode - cannot send messages. Use /login to authenticate.', 'error');
124
- rl.prompt(); return;
133
+ showPrompt(rl, username, model); return;
125
134
  }
126
135
 
127
136
  const spinner = ui.renderSpinner('Thinking...');
@@ -132,7 +141,11 @@ async function startChat() {
132
141
  if (res.status === 200 && res.data) {
133
142
  const text = res.data.answer || res.data.response || res.data.text || res.data.message || JSON.stringify(res.data);
134
143
  ui.renderResponse(text, 'agent');
135
- if (res.data.usage) ui.addTokens(res.data.usage.total_tokens || 0, 0.000003);
144
+ const tok = res.data.tokens || res.data.usage || {};
145
+ const tokTotal = tok.total || tok.total_tokens || 0;
146
+ if (tokTotal) ui.addTokens(tokTotal, 0.000003);
147
+ const usedModel = res.data.routing && res.data.routing.task && res.data.routing.task.type || settings.model || 'auto';
148
+ ui.renderMiniStatus(usedModel, tokTotal);
136
149
  } else if (res.status === 401 || res.status === 403) {
137
150
  ui.renderResponse('Auth error. Use /login to re-authenticate.', 'error');
138
151
  } else {
@@ -142,11 +155,11 @@ async function startChat() {
142
155
  ui.stopSpinner(false, e.message);
143
156
  ui.renderResponse('Request failed: ' + e.message + '\n Try /login if auth expired.', 'error');
144
157
  }
145
- rl.prompt();
158
+ showPrompt(rl, username, model);
146
159
  });
147
160
 
148
161
  rl.on('close', () => { console.log(''); resolveChat(); });
149
162
  });
150
163
  }
151
164
 
152
- module.exports = { startChat };
165
+ module.exports = { startChat };
package/lib/ui.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * BLUN King CLI - Rich Terminal UI Module
3
- * Task 11: Claude Code-style terminal experience
3
+ * Claude Code-style terminal experience
4
4
  * CommonJS - chalk@4, ora@5, boxen@5
5
5
  */
6
6
 
@@ -47,27 +47,74 @@ function renderBanner() {
47
47
  console.log(chalk.dim(" " + "\u2500".repeat(53) + "\n"));
48
48
  }
49
49
 
50
+ function getInputBoxWidth() {
51
+ return Math.min((process.stdout.columns || 80) - 2, 76);
52
+ }
53
+
54
+ function renderInputBox(username, model) {
55
+ var w = getInputBoxWidth();
56
+ var u = username || "user";
57
+ var m = model || "auto";
58
+ var hints = "/help \u2502 /settings \u2502 /model \u2502 /exit";
59
+ var labelText = " " + u + " [" + m + "] ";
60
+ var hintsText = " " + hints + " ";
61
+ var topFill = w - labelText.length - hintsText.length;
62
+ if (topFill < 2) topFill = 2;
63
+ var topLine = chalk.dim("\u256d") + chalk.cyan(labelText) + chalk.dim("\u2500".repeat(topFill)) + chalk.dim(hintsText) + chalk.dim("\u256e");
64
+ console.log(topLine);
65
+ }
66
+
67
+ function renderInputBoxClose() {
68
+ var w = getInputBoxWidth();
69
+ console.log(chalk.dim("\u2570" + "\u2500".repeat(w) + "\u256f"));
70
+ }
71
+
50
72
  function renderPrompt(username, model) {
51
- var u = chalk.cyan.bold(username || "user");
52
- var m = chalk.dim("[" + (model || "auto") + "]");
53
- return chalk.dim("\u250c") + " " + u + " " + m + "\n" + chalk.dim("\u2514") + " " + chalk.green.bold("\u276f") + " ";
73
+ // Called by readline as the prompt string - keep it simple
74
+ return chalk.dim("\u2502") + " " + chalk.green.bold("\u276f") + " ";
54
75
  }
55
76
 
56
77
  function renderResponse(text, type) {
57
78
  var colorFn = colors[type] || colors.agent;
58
79
  var labels = { user: "YOU", agent: "BLUN", system: "SYS", error: "ERR" };
59
80
  var label = labels[type] || "BLUN";
60
- var prefix = colorFn.bold(" [" + label + "] ");
61
81
  var rendered = renderMarkdown(text);
62
- var lines = rendered.split("\n");
63
- lines.forEach(function (line, i) {
64
- if (i === 0) {
65
- console.log(prefix + colorFn(line));
66
- } else {
67
- console.log(" " + colorFn(line));
68
- }
69
- });
70
- console.log();
82
+
83
+ if (type === "agent") {
84
+ // Agent responses in a box
85
+ console.log(
86
+ boxen(rendered, {
87
+ title: chalk.green.bold(" \u2728 BLUN "),
88
+ titleAlignment: "left",
89
+ padding: { top: 0, bottom: 0, left: 1, right: 1 },
90
+ margin: { top: 0, bottom: 0, left: 1, right: 1 },
91
+ borderStyle: "round",
92
+ borderColor: "green",
93
+ })
94
+ );
95
+ } else if (type === "error") {
96
+ console.log(
97
+ boxen(chalk.red(rendered), {
98
+ title: chalk.red.bold(" \u2718 Error "),
99
+ titleAlignment: "left",
100
+ padding: { top: 0, bottom: 0, left: 1, right: 1 },
101
+ margin: { top: 0, bottom: 0, left: 1, right: 1 },
102
+ borderStyle: "round",
103
+ borderColor: "red",
104
+ })
105
+ );
106
+ } else {
107
+ var prefix = colorFn.bold(" [" + label + "] ");
108
+ var lines = rendered.split("\n");
109
+ lines.forEach(function (line, i) {
110
+ if (i === 0) {
111
+ console.log(prefix + colorFn(line));
112
+ } else {
113
+ console.log(" " + colorFn(line));
114
+ }
115
+ });
116
+ console.log();
117
+ }
71
118
  }
72
119
 
73
120
  function renderMarkdown(text) {
@@ -154,6 +201,19 @@ function renderCodeBlock(code, language) {
154
201
  console.log();
155
202
  }
156
203
 
204
+ function renderMiniStatus(model, tokens) {
205
+ var elapsed = Math.floor((Date.now() - startTime) / 1000);
206
+ var mins = Math.floor(elapsed / 60);
207
+ var secs = elapsed % 60;
208
+ var parts = [];
209
+ parts.push(chalk.bgCyan.black(" " + (model || "auto") + " "));
210
+ if (tokens) parts.push(chalk.dim("Tokens: " + tokens.toLocaleString()));
211
+ parts.push(chalk.dim("Total: " + totalTokens.toLocaleString()));
212
+ parts.push(chalk.dim(mins + "m " + secs + "s"));
213
+ console.log(" " + parts.join(chalk.dim(" \u2502 ")));
214
+ console.log();
215
+ }
216
+
157
217
  function renderStatusBar(stats) {
158
218
  var s = stats || {};
159
219
  var model = s.model || "auto";
@@ -200,6 +260,9 @@ function getStats() {
200
260
  module.exports = {
201
261
  renderBanner: renderBanner,
202
262
  renderPrompt: renderPrompt,
263
+ renderInputBox: renderInputBox,
264
+ renderInputBoxClose: renderInputBoxClose,
265
+ renderMiniStatus: renderMiniStatus,
203
266
  renderResponse: renderResponse,
204
267
  renderSpinner: renderSpinner,
205
268
  stopSpinner: stopSpinner,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "6.3.5",
3
+ "version": "6.4.0",
4
4
  "description": "BLUN AI Assistant - Command Line Interface",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"