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.
- package/lib/chat.js +35 -22
- 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,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(
|
|
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
|
|
|
47
54
|
return new Promise((resolveChat) => {
|
|
48
55
|
rl.on('line', async (line) => {
|
|
49
56
|
const input = line.trim();
|
|
50
|
-
|
|
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
|
|
56
|
-
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; }
|
|
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
|
|
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
|
|
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
|
|
69
|
-
}
|
|
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
|
|
87
|
+
showPrompt(rl, username, model); return;
|
|
79
88
|
}
|
|
80
89
|
if (input === '/settings') {
|
|
81
90
|
const s = auth.getSettings();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
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,
|