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