blun-king-cli 6.4.2 → 7.0.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/auth.js +378 -176
- package/lib/chat.js +347 -143
- package/lib/client.js +101 -39
- package/lib/ui.js +150 -34
- package/lib/workspace.js +79 -51
- package/package.json +1 -1
package/lib/chat.js
CHANGED
|
@@ -1,166 +1,370 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* BLUN King CLI v7.0.0 - Chat Module
|
|
3
|
+
* Claude Code-style interactive chat with framed input, streaming, permission modes
|
|
4
|
+
* CommonJS
|
|
5
|
+
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
var readline = require("readline");
|
|
8
|
+
var chalk = require("chalk");
|
|
9
|
+
var ui = require("./ui");
|
|
10
|
+
var auth = require("./auth");
|
|
11
|
+
var client = require("./client");
|
|
12
|
+
|
|
13
|
+
var localMode = false;
|
|
14
|
+
var permissionMode = "normal"; // safe, normal, god
|
|
9
15
|
|
|
10
16
|
function showPrompt(rl, username, model) {
|
|
11
|
-
ui.renderInputBox(username, model);
|
|
17
|
+
ui.renderInputBox(username, model, permissionMode);
|
|
12
18
|
rl.prompt();
|
|
13
19
|
}
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
function cyclePermission() {
|
|
22
|
+
var modes = ui.PERMISSION_MODES;
|
|
23
|
+
var idx = modes.indexOf(permissionMode);
|
|
24
|
+
permissionMode = modes[(idx + 1) % modes.length];
|
|
25
|
+
return permissionMode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function startChat() {
|
|
29
|
+
// Show banner
|
|
16
30
|
ui.renderBanner();
|
|
17
31
|
|
|
32
|
+
// Check auth
|
|
33
|
+
var authPromise;
|
|
18
34
|
if (!auth.isAuthenticated()) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
console.log(chalk.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} catch (e2) {
|
|
29
|
-
console.log(chalk.red('\n ' + e2.message));
|
|
35
|
+
authPromise = auth.showWelcomeMenu().then(function (result) {
|
|
36
|
+
if (result === "local") localMode = true;
|
|
37
|
+
}).catch(function (e) {
|
|
38
|
+
console.log(chalk.red("\n " + e.message));
|
|
39
|
+
console.log(chalk.yellow(" Trying again...\n"));
|
|
40
|
+
return auth.showWelcomeMenu().then(function (retry) {
|
|
41
|
+
if (retry === "local") localMode = true;
|
|
42
|
+
}).catch(function (e2) {
|
|
43
|
+
console.log(chalk.red("\n " + e2.message));
|
|
30
44
|
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
} else {
|
|
48
|
+
authPromise = Promise.resolve();
|
|
33
49
|
}
|
|
34
50
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
return authPromise.then(function () {
|
|
52
|
+
var creds = auth.getStoredCredentials();
|
|
53
|
+
if (creds) {
|
|
54
|
+
ui.renderResponse(
|
|
55
|
+
"Connected as " +
|
|
56
|
+
(creds.name || creds.email || "user") +
|
|
57
|
+
" | Plan: " +
|
|
58
|
+
(creds.plan || "free"),
|
|
59
|
+
"system"
|
|
60
|
+
);
|
|
61
|
+
} else if (localMode) {
|
|
62
|
+
ui.renderResponse("Local mode - no API calls available", "system");
|
|
63
|
+
}
|
|
41
64
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
var settings = auth.getSettings();
|
|
66
|
+
permissionMode = settings.permissionMode || "normal";
|
|
67
|
+
var username = (creds ? creds.name : null) || "local";
|
|
68
|
+
var model = settings.model || "auto";
|
|
45
69
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
prompt: ui.renderPrompt(username, model),
|
|
50
|
-
terminal: true
|
|
51
|
-
});
|
|
70
|
+
// Show permission line
|
|
71
|
+
ui.renderPermissionLine(permissionMode);
|
|
72
|
+
console.log();
|
|
52
73
|
|
|
53
|
-
|
|
74
|
+
var rl = readline.createInterface({
|
|
75
|
+
input: process.stdin,
|
|
76
|
+
output: process.stdout,
|
|
77
|
+
prompt: ui.renderPrompt(),
|
|
78
|
+
terminal: true,
|
|
79
|
+
});
|
|
54
80
|
|
|
55
|
-
|
|
56
|
-
rl.on('line', async (line) => {
|
|
57
|
-
const input = line.trim();
|
|
58
|
-
ui.renderInputBoxClose();
|
|
59
|
-
if (!input) { showPrompt(rl, username, model); return; }
|
|
81
|
+
showPrompt(rl, username, model);
|
|
60
82
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (input === '/status') { ui.renderStatusBar(ui.getStats()); showPrompt(rl, username, model); return; }
|
|
66
|
-
if (input === '/login') {
|
|
67
|
-
try { await auth.login(); localMode = false; ui.renderResponse('Logged in!', 'system'); }
|
|
68
|
-
catch(e) { ui.renderResponse(e.message, 'error'); }
|
|
69
|
-
showPrompt(rl, username, model); return;
|
|
70
|
-
}
|
|
71
|
-
if (input === '/whoami') {
|
|
72
|
-
try { await auth.whoami(); } catch(e) { ui.renderResponse(e.message, 'error'); }
|
|
73
|
-
showPrompt(rl, username, model); return;
|
|
74
|
-
}
|
|
75
|
-
if (input === '/keys') {
|
|
76
|
-
try { await auth.listApiKeys(); } catch(e) { ui.renderResponse(e.message, 'error'); }
|
|
77
|
-
showPrompt(rl, username, model); return;
|
|
78
|
-
}
|
|
79
|
-
if (input === '/model') {
|
|
80
|
-
const s = auth.getSettings();
|
|
81
|
-
const choice = await auth.promptMenu('Select Model', [
|
|
82
|
-
{ label: 'Auto (recommended)', value: 'auto' },
|
|
83
|
-
{ label: 'Gemma 4', value: 'gemma4' },
|
|
84
|
-
{ label: 'Claude', value: 'claude' },
|
|
85
|
-
{ label: 'Llama', value: 'llama' }
|
|
86
|
-
]);
|
|
87
|
-
if (choice) { s.model = choice; auth.saveSettings(s); ui.renderResponse('Model set to: ' + choice, 'system'); }
|
|
88
|
-
showPrompt(rl, username, model); return;
|
|
89
|
-
}
|
|
90
|
-
if (input === '/settings') {
|
|
91
|
-
const s = auth.getSettings();
|
|
92
|
-
ui.renderBox('Settings', [
|
|
93
|
-
'Model: ' + (s.model || 'auto'),
|
|
94
|
-
'Language: ' + (s.language || 'de'),
|
|
95
|
-
'Voice: ' + (s.voice ? 'on' : 'off')
|
|
96
|
-
].join('\n'), 'cyan');
|
|
97
|
-
const action = await auth.promptMenu('Change Setting', [
|
|
98
|
-
{ label: 'Model', value: 'model' },
|
|
99
|
-
{ label: 'Language (de/en)', value: 'language' },
|
|
100
|
-
{ label: 'Voice on/off', value: 'voice' },
|
|
101
|
-
{ label: 'Back', value: 'back' }
|
|
102
|
-
]);
|
|
103
|
-
if (action === 'model') {
|
|
104
|
-
const m = await auth.promptMenu('Select Model', [
|
|
105
|
-
{ label: 'Auto', value: 'auto' }, { label: 'Gemma 4', value: 'gemma4' },
|
|
106
|
-
{ label: 'Claude', value: 'claude' }, { label: 'Llama', value: 'llama' }
|
|
107
|
-
]);
|
|
108
|
-
if (m) { s.model = m; auth.saveSettings(s); ui.renderResponse('Model: ' + m, 'system'); }
|
|
109
|
-
} else if (action === 'language') {
|
|
110
|
-
const l = await auth.prompt(chalk.green(' Language (de/en): '));
|
|
111
|
-
if (l) { s.language = l; auth.saveSettings(s); ui.renderResponse('Language: ' + l, 'system'); }
|
|
112
|
-
} else if (action === 'voice') {
|
|
113
|
-
s.voice = !s.voice; auth.saveSettings(s); ui.renderResponse('Voice: ' + (s.voice ? 'on' : 'off'), 'system');
|
|
114
|
-
}
|
|
115
|
-
showPrompt(rl, username, model); return;
|
|
116
|
-
}
|
|
117
|
-
if (input === '/help') {
|
|
118
|
-
ui.renderBox('Chat Commands', [
|
|
119
|
-
'/exit Exit chat',
|
|
120
|
-
'/clear Clear screen',
|
|
121
|
-
'/status Show token usage & uptime',
|
|
122
|
-
'/login Login from chat',
|
|
123
|
-
'/whoami Show current user info',
|
|
124
|
-
'/keys List API keys',
|
|
125
|
-
'/model Switch AI model',
|
|
126
|
-
'/settings Show/change settings',
|
|
127
|
-
'/help Show this help'
|
|
128
|
-
].join('\n'), 'cyan');
|
|
129
|
-
showPrompt(rl, username, model); return;
|
|
130
|
-
}
|
|
83
|
+
return new Promise(function (resolveChat) {
|
|
84
|
+
rl.on("line", function (line) {
|
|
85
|
+
var input = line.trim();
|
|
86
|
+
ui.renderInputBoxClose();
|
|
131
87
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
88
|
+
if (!input) {
|
|
89
|
+
showPrompt(rl, username, model);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
136
92
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
93
|
+
// Commands
|
|
94
|
+
if (input === "/exit" || input === "/quit") {
|
|
95
|
+
ui.renderResponse("Goodbye!", "system");
|
|
96
|
+
rl.close();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (input === "/clear") {
|
|
101
|
+
console.clear();
|
|
102
|
+
ui.renderBanner();
|
|
103
|
+
showPrompt(rl, username, model);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (input === "/status") {
|
|
108
|
+
ui.renderStatusBar(ui.getStats());
|
|
109
|
+
showPrompt(rl, username, model);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (input === "/perm" || input === "/permission") {
|
|
114
|
+
var newMode = cyclePermission();
|
|
115
|
+
settings.permissionMode = newMode;
|
|
116
|
+
auth.saveSettings(settings);
|
|
117
|
+
ui.renderPermissionLine(newMode);
|
|
118
|
+
console.log();
|
|
119
|
+
showPrompt(rl, username, model);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (input === "/login") {
|
|
124
|
+
auth
|
|
125
|
+
.login()
|
|
126
|
+
.then(function () {
|
|
127
|
+
localMode = false;
|
|
128
|
+
creds = auth.getStoredCredentials();
|
|
129
|
+
username = (creds ? creds.name : null) || "local";
|
|
130
|
+
ui.renderResponse("Logged in!", "system");
|
|
131
|
+
})
|
|
132
|
+
.catch(function (e) {
|
|
133
|
+
ui.renderResponse(e.message, "error");
|
|
134
|
+
})
|
|
135
|
+
.then(function () {
|
|
136
|
+
showPrompt(rl, username, model);
|
|
137
|
+
});
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (input === "/whoami") {
|
|
142
|
+
auth
|
|
143
|
+
.whoami()
|
|
144
|
+
.catch(function (e) {
|
|
145
|
+
ui.renderResponse(e.message, "error");
|
|
146
|
+
})
|
|
147
|
+
.then(function () {
|
|
148
|
+
showPrompt(rl, username, model);
|
|
149
|
+
});
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (input === "/keys") {
|
|
154
|
+
auth
|
|
155
|
+
.listApiKeys()
|
|
156
|
+
.catch(function (e) {
|
|
157
|
+
ui.renderResponse(e.message, "error");
|
|
158
|
+
})
|
|
159
|
+
.then(function () {
|
|
160
|
+
showPrompt(rl, username, model);
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (input === "/model") {
|
|
166
|
+
auth
|
|
167
|
+
.promptMenu("Select Model", [
|
|
168
|
+
{ label: "Auto (recommended)", value: "auto" },
|
|
169
|
+
{ label: "Gemma 4", value: "gemma4" },
|
|
170
|
+
{ label: "Claude", value: "claude" },
|
|
171
|
+
{ label: "Llama", value: "llama" },
|
|
172
|
+
])
|
|
173
|
+
.then(function (choice) {
|
|
174
|
+
if (choice) {
|
|
175
|
+
model = choice;
|
|
176
|
+
settings.model = choice;
|
|
177
|
+
auth.saveSettings(settings);
|
|
178
|
+
ui.renderResponse("Model set to: " + choice, "system");
|
|
179
|
+
}
|
|
180
|
+
showPrompt(rl, username, model);
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (input === "/settings") {
|
|
186
|
+
ui.renderBox(
|
|
187
|
+
"Settings",
|
|
188
|
+
[
|
|
189
|
+
"Model: " + (settings.model || "auto"),
|
|
190
|
+
"Language: " + (settings.language || "de"),
|
|
191
|
+
"Voice: " + (settings.voice ? "on" : "off"),
|
|
192
|
+
"Permission: " + (permissionMode || "normal"),
|
|
193
|
+
].join("\n"),
|
|
194
|
+
"cyan"
|
|
195
|
+
);
|
|
196
|
+
auth
|
|
197
|
+
.promptMenu("Change Setting", [
|
|
198
|
+
{ label: "Model", value: "model" },
|
|
199
|
+
{ label: "Language (de/en)", value: "language" },
|
|
200
|
+
{ label: "Voice on/off", value: "voice" },
|
|
201
|
+
{ label: "Back", value: "back" },
|
|
202
|
+
])
|
|
203
|
+
.then(function (action) {
|
|
204
|
+
if (action === "model") {
|
|
205
|
+
return auth
|
|
206
|
+
.promptMenu("Select Model", [
|
|
207
|
+
{ label: "Auto", value: "auto" },
|
|
208
|
+
{ label: "Gemma 4", value: "gemma4" },
|
|
209
|
+
{ label: "Claude", value: "claude" },
|
|
210
|
+
{ label: "Llama", value: "llama" },
|
|
211
|
+
])
|
|
212
|
+
.then(function (m) {
|
|
213
|
+
if (m) {
|
|
214
|
+
model = m;
|
|
215
|
+
settings.model = m;
|
|
216
|
+
auth.saveSettings(settings);
|
|
217
|
+
ui.renderResponse("Model: " + m, "system");
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
} else if (action === "language") {
|
|
221
|
+
return auth.prompt(chalk.green(" Language (de/en): ")).then(function (l) {
|
|
222
|
+
if (l) {
|
|
223
|
+
settings.language = l;
|
|
224
|
+
auth.saveSettings(settings);
|
|
225
|
+
ui.renderResponse("Language: " + l, "system");
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
} else if (action === "voice") {
|
|
229
|
+
settings.voice = !settings.voice;
|
|
230
|
+
auth.saveSettings(settings);
|
|
231
|
+
ui.renderResponse(
|
|
232
|
+
"Voice: " + (settings.voice ? "on" : "off"),
|
|
233
|
+
"system"
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
.then(function () {
|
|
238
|
+
showPrompt(rl, username, model);
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (input === "/help") {
|
|
244
|
+
ui.renderBox(
|
|
245
|
+
"Chat Commands",
|
|
246
|
+
[
|
|
247
|
+
"/exit Exit chat",
|
|
248
|
+
"/clear Clear screen",
|
|
249
|
+
"/status Show token usage & uptime",
|
|
250
|
+
"/login Login from chat",
|
|
251
|
+
"/whoami Show current user info",
|
|
252
|
+
"/keys List API keys",
|
|
253
|
+
"/model Switch AI model",
|
|
254
|
+
"/settings Show/change settings",
|
|
255
|
+
"/perm Cycle permission mode (safe/normal/god)",
|
|
256
|
+
"/help Show this help",
|
|
257
|
+
].join("\n"),
|
|
258
|
+
"cyan"
|
|
259
|
+
);
|
|
260
|
+
showPrompt(rl, username, model);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Send message
|
|
265
|
+
if (localMode) {
|
|
266
|
+
ui.renderResponse(
|
|
267
|
+
"Local mode - cannot send messages. Use /login to authenticate.",
|
|
268
|
+
"error"
|
|
269
|
+
);
|
|
270
|
+
showPrompt(rl, username, model);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Use streaming
|
|
275
|
+
var spinner = ui.renderSpinner("Thinking...");
|
|
276
|
+
client
|
|
277
|
+
.sendMessageStream(input, {
|
|
278
|
+
model: settings.model || "auto",
|
|
279
|
+
onChunk: function (chunk) {
|
|
280
|
+
if (spinner) {
|
|
281
|
+
ui.stopSpinner(true, "Streaming...");
|
|
282
|
+
spinner = null;
|
|
283
|
+
ui.renderStreamStart();
|
|
284
|
+
}
|
|
285
|
+
ui.renderStreamChunk(chunk);
|
|
286
|
+
},
|
|
287
|
+
})
|
|
288
|
+
.then(function (result) {
|
|
289
|
+
if (spinner) {
|
|
290
|
+
// No streaming happened, fallback to non-stream
|
|
291
|
+
ui.stopSpinner(true, "Response received");
|
|
292
|
+
ui.renderResponse(result.text || "(empty response)", "agent");
|
|
293
|
+
} else {
|
|
294
|
+
ui.renderStreamEnd();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Token tracking
|
|
298
|
+
var tokTotal = 0;
|
|
299
|
+
if (result.usage) {
|
|
300
|
+
tokTotal =
|
|
301
|
+
result.usage.total ||
|
|
302
|
+
result.usage.total_tokens ||
|
|
303
|
+
(result.usage.input || 0) + (result.usage.output || 0) ||
|
|
304
|
+
0;
|
|
305
|
+
}
|
|
306
|
+
// Estimate tokens from text length if no usage data
|
|
307
|
+
if (!tokTotal && result.text) {
|
|
308
|
+
tokTotal = Math.ceil(result.text.length / 4) + Math.ceil(input.length / 4);
|
|
309
|
+
}
|
|
310
|
+
if (tokTotal) ui.addTokens(tokTotal);
|
|
311
|
+
|
|
312
|
+
var usedModel = settings.model || "chat";
|
|
313
|
+
ui.renderMiniStatus(usedModel, tokTotal);
|
|
314
|
+
showPrompt(rl, username, model);
|
|
315
|
+
})
|
|
316
|
+
.catch(function (e) {
|
|
317
|
+
if (spinner) {
|
|
318
|
+
ui.stopSpinner(false, e.message);
|
|
319
|
+
} else {
|
|
320
|
+
ui.renderStreamEnd();
|
|
321
|
+
}
|
|
322
|
+
// Fallback to non-streaming
|
|
323
|
+
ui.renderResponse("Stream failed, trying non-stream...", "system");
|
|
324
|
+
return client
|
|
325
|
+
.sendMessage(input, { model: settings.model || "auto" })
|
|
326
|
+
.then(function (res) {
|
|
327
|
+
if (res.status === 200 && res.data) {
|
|
328
|
+
var text =
|
|
329
|
+
res.data.answer ||
|
|
330
|
+
res.data.response ||
|
|
331
|
+
res.data.text ||
|
|
332
|
+
res.data.message ||
|
|
333
|
+
JSON.stringify(res.data);
|
|
334
|
+
ui.renderResponse(text, "agent");
|
|
335
|
+
var tok = res.data.tokens || res.data.usage || {};
|
|
336
|
+
var tokTotal = tok.total || tok.total_tokens || 0;
|
|
337
|
+
if (tokTotal) ui.addTokens(tokTotal);
|
|
338
|
+
ui.renderMiniStatus(settings.model || "chat", tokTotal);
|
|
339
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
340
|
+
ui.renderResponse(
|
|
341
|
+
"Auth error. Use /login to re-authenticate.",
|
|
342
|
+
"error"
|
|
343
|
+
);
|
|
344
|
+
} else {
|
|
345
|
+
ui.renderResponse(
|
|
346
|
+
"Error: " + JSON.stringify(res.data),
|
|
347
|
+
"error"
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
showPrompt(rl, username, model);
|
|
351
|
+
})
|
|
352
|
+
.catch(function (e2) {
|
|
353
|
+
ui.renderResponse(
|
|
354
|
+
"Request failed: " + e2.message + "\n Try /login if auth expired.",
|
|
355
|
+
"error"
|
|
356
|
+
);
|
|
357
|
+
showPrompt(rl, username, model);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
});
|
|
161
361
|
|
|
162
|
-
|
|
362
|
+
rl.on("close", function () {
|
|
363
|
+
console.log("");
|
|
364
|
+
resolveChat();
|
|
365
|
+
});
|
|
366
|
+
});
|
|
163
367
|
});
|
|
164
368
|
}
|
|
165
369
|
|
|
166
|
-
module.exports = { startChat };
|
|
370
|
+
module.exports = { startChat: startChat };
|