blun-king-cli 6.3.4 → 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 +38 -23
  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,38 +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
 
54
+ return new Promise((resolveChat) => {
47
55
  rl.on('line', async (line) => {
48
56
  const input = line.trim();
49
- if (!input) { rl.prompt(); return; }
57
+ ui.renderInputBoxClose();
58
+ if (!input) { showPrompt(rl, username, model); return; }
50
59
 
51
60
  if (input === '/exit' || input === '/quit') {
52
61
  ui.renderResponse('Goodbye!', 'system'); process.exit(0);
53
62
  }
54
- if (input === '/clear') { console.clear(); ui.renderBanner(); rl.prompt(); return; }
55
- 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; }
56
65
  if (input === '/login') {
57
66
  try { await auth.login(); localMode = false; ui.renderResponse('Logged in!', 'system'); }
58
67
  catch(e) { ui.renderResponse(e.message, 'error'); }
59
- rl.prompt(); return;
68
+ showPrompt(rl, username, model); return;
60
69
  }
61
70
  if (input === '/whoami') {
62
71
  try { await auth.whoami(); } catch(e) { ui.renderResponse(e.message, 'error'); }
63
- rl.prompt(); return;
72
+ showPrompt(rl, username, model); return;
64
73
  }
65
74
  if (input === '/keys') {
66
75
  try { await auth.listApiKeys(); } catch(e) { ui.renderResponse(e.message, 'error'); }
67
- rl.prompt(); return;
68
- } if (input === '/model') {
76
+ showPrompt(rl, username, model); return;
77
+ }
78
+ if (input === '/model') {
69
79
  const s = auth.getSettings();
70
80
  const choice = await auth.promptMenu('Select Model', [
71
81
  { label: 'Auto (recommended)', value: 'auto' },
@@ -74,15 +84,15 @@ async function startChat() {
74
84
  { label: 'Llama', value: 'llama' }
75
85
  ]);
76
86
  if (choice) { s.model = choice; auth.saveSettings(s); ui.renderResponse('Model set to: ' + choice, 'system'); }
77
- rl.prompt(); return;
87
+ showPrompt(rl, username, model); return;
78
88
  }
79
89
  if (input === '/settings') {
80
90
  const s = auth.getSettings();
81
- console.log(chalk.bold.cyan('\n Current Settings:'));
82
- console.log(chalk.white(' Model: ') + (s.model || 'auto'));
83
- console.log(chalk.white(' Language: ') + (s.language || 'de'));
84
- console.log(chalk.white(' Voice: ') + (s.voice ? 'on' : 'off'));
85
- 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');
86
96
  const action = await auth.promptMenu('Change Setting', [
87
97
  { label: 'Model', value: 'model' },
88
98
  { label: 'Language (de/en)', value: 'language' },
@@ -101,7 +111,7 @@ async function startChat() {
101
111
  } else if (action === 'voice') {
102
112
  s.voice = !s.voice; auth.saveSettings(s); ui.renderResponse('Voice: ' + (s.voice ? 'on' : 'off'), 'system');
103
113
  }
104
- rl.prompt(); return;
114
+ showPrompt(rl, username, model); return;
105
115
  }
106
116
  if (input === '/help') {
107
117
  ui.renderBox('Chat Commands', [
@@ -115,12 +125,12 @@ async function startChat() {
115
125
  '/settings Show/change settings',
116
126
  '/help Show this help'
117
127
  ].join('\n'), 'cyan');
118
- rl.prompt(); return;
128
+ showPrompt(rl, username, model); return;
119
129
  }
120
130
 
121
131
  if (localMode) {
122
132
  ui.renderResponse('Local mode - cannot send messages. Use /login to authenticate.', 'error');
123
- rl.prompt(); return;
133
+ showPrompt(rl, username, model); return;
124
134
  }
125
135
 
126
136
  const spinner = ui.renderSpinner('Thinking...');
@@ -131,7 +141,11 @@ async function startChat() {
131
141
  if (res.status === 200 && res.data) {
132
142
  const text = res.data.answer || res.data.response || res.data.text || res.data.message || JSON.stringify(res.data);
133
143
  ui.renderResponse(text, 'agent');
134
- 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);
135
149
  } else if (res.status === 401 || res.status === 403) {
136
150
  ui.renderResponse('Auth error. Use /login to re-authenticate.', 'error');
137
151
  } else {
@@ -141,10 +155,11 @@ async function startChat() {
141
155
  ui.stopSpinner(false, e.message);
142
156
  ui.renderResponse('Request failed: ' + e.message + '\n Try /login if auth expired.', 'error');
143
157
  }
144
- rl.prompt();
158
+ showPrompt(rl, username, model);
145
159
  });
146
160
 
147
- rl.on('close', () => { console.log(''); process.exit(0); });
161
+ rl.on('close', () => { console.log(''); resolveChat(); });
162
+ });
148
163
  }
149
164
 
150
- 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.4",
3
+ "version": "6.4.0",
4
4
  "description": "BLUN AI Assistant - Command Line Interface",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"