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.
- package/lib/chat.js +38 -23
- package/lib/ui.js +77 -14
- 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(
|
|
48
|
+
prompt: ui.renderPrompt(username, model),
|
|
41
49
|
terminal: true
|
|
42
50
|
});
|
|
43
51
|
|
|
44
|
-
|
|
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
|
-
|
|
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
|
|
55
|
-
if (input === '/status') { ui.renderStatusBar(ui.getStats()); rl
|
|
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
|
|
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
|
|
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
|
|
68
|
-
}
|
|
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
|
|
87
|
+
showPrompt(rl, username, model); return;
|
|
78
88
|
}
|
|
79
89
|
if (input === '/settings') {
|
|
80
90
|
const s = auth.getSettings();
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
158
|
+
showPrompt(rl, username, model);
|
|
145
159
|
});
|
|
146
160
|
|
|
147
|
-
rl.on('close', () => { console.log('');
|
|
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
|
-
*
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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,
|