blun-king-cli 6.4.2 → 7.0.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 CHANGED
@@ -1,166 +1,320 @@
1
- const readline = require('readline');
2
- const chalk = require('chalk');
3
- const ui = require('./ui');
4
- const auth = require('./auth');
5
- const client = require('./client');
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
- let localMode = false;
8
- let firstPrompt = true;
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
- async function startChat() {
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
- try {
20
- const result = await auth.showWelcomeMenu();
21
- if (result === 'local') localMode = true;
22
- } catch (e) {
23
- console.log(chalk.red('\n ' + e.message));
24
- console.log(chalk.yellow(' Trying again...\n'));
25
- try {
26
- const retry = await auth.showWelcomeMenu();
27
- if (retry === 'local') localMode = true;
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
- const creds = auth.getStoredCredentials();
36
- if (creds) {
37
- ui.renderResponse('Connected as ' + (creds.name || creds.email || 'user') + ' | Plan: ' + (creds.plan || 'free'), 'system');
38
- } else if (localMode) {
39
- ui.renderResponse('Local mode - no API calls available', 'system');
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
- const settings = auth.getSettings();
43
- const username = (creds ? creds.name : null) || 'local';
44
- const model = settings.model || 'auto';
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
- const rl = readline.createInterface({
47
- input: process.stdin,
48
- output: process.stdout,
49
- prompt: ui.renderPrompt(username, model),
50
- terminal: true
51
- });
70
+ // Show permission line
71
+ ui.renderPermissionLine(permissionMode);
72
+ console.log();
52
73
 
53
- showPrompt(rl, username, model);
74
+ var rl = readline.createInterface({
75
+ input: process.stdin,
76
+ output: process.stdout,
77
+ prompt: ui.renderPrompt(),
78
+ terminal: true,
79
+ });
54
80
 
55
- return new Promise((resolveChat) => {
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
- if (input === '/exit' || input === '/quit') {
62
- ui.renderResponse('Goodbye!', 'system'); process.exit(0);
63
- }
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; }
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
- if (localMode) {
133
- ui.renderResponse('Local mode - cannot send messages. Use /login to authenticate.', 'error');
134
- showPrompt(rl, username, model); return;
135
- }
88
+ if (!input) {
89
+ showPrompt(rl, username, model);
90
+ return;
91
+ }
136
92
 
137
- const spinner = ui.renderSpinner('Thinking...');
138
- try {
139
- const settings = auth.getSettings();
140
- const res = await client.sendMessage(input, { model: settings.model || 'auto' });
141
- ui.stopSpinner(true, 'Response received');
142
- if (res.status === 200 && res.data) {
143
- const text = res.data.answer || res.data.response || res.data.text || res.data.message || JSON.stringify(res.data);
144
- ui.renderResponse(text, 'agent');
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);
150
- } else if (res.status === 401 || res.status === 403) {
151
- ui.renderResponse('Auth error. Use /login to re-authenticate.', 'error');
152
- } else {
153
- ui.renderResponse('Error: ' + JSON.stringify(res.data), 'error');
154
- }
155
- } catch (e) {
156
- ui.stopSpinner(false, e.message);
157
- ui.renderResponse('Request failed: ' + e.message + '\n Try /login if auth expired.', 'error');
158
- }
159
- showPrompt(rl, username, model);
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
+ // Send message
275
+ var spinner = ui.renderSpinner("Thinking...");
276
+ client
277
+ .sendMessage(input, { model: settings.model || "auto" })
278
+ .then(function (res) {
279
+ ui.stopSpinner(true, "Response received");
280
+ if (res.status === 200 && res.data) {
281
+ var text =
282
+ res.data.answer ||
283
+ res.data.response ||
284
+ res.data.text ||
285
+ res.data.message ||
286
+ JSON.stringify(res.data);
287
+ ui.renderResponse(text, "agent");
288
+ var tok = res.data.tokens || res.data.usage || {};
289
+ var tokTotal = tok.total || tok.total_tokens || 0;
290
+ if (!tokTotal && text) {
291
+ tokTotal = Math.ceil(text.length / 4) + Math.ceil(input.length / 4);
292
+ }
293
+ if (tokTotal) ui.addTokens(tokTotal);
294
+ ui.renderMiniStatus(settings.model || "chat", tokTotal);
295
+ } else if (res.status === 401 || res.status === 403) {
296
+ ui.renderResponse("Auth error. Use /login to re-authenticate.", "error");
297
+ } else {
298
+ ui.renderResponse("Error: " + JSON.stringify(res.data), "error");
299
+ }
300
+ showPrompt(rl, username, model);
301
+ })
302
+ .catch(function (e) {
303
+ ui.stopSpinner(false, e.message);
304
+ ui.renderResponse(
305
+ "Request failed: " + e.message + "\n Try /login if auth expired.",
306
+ "error"
307
+ );
308
+ showPrompt(rl, username, model);
309
+ });
310
+ });
161
311
 
162
- rl.on('close', () => { console.log(''); resolveChat(); });
312
+ rl.on("close", function () {
313
+ console.log("");
314
+ resolveChat();
315
+ });
316
+ });
163
317
  });
164
318
  }
165
319
 
166
- module.exports = { startChat };
320
+ module.exports = { startChat: startChat };
package/lib/client.js CHANGED
@@ -1,65 +1,127 @@
1
- const https = require('https');
2
- const auth = require('./auth');
1
+ /**
2
+ * BLUN King CLI v7.0.0 - API Client
3
+ * CommonJS - uses native https + fetch
4
+ */
3
5
 
4
- const BASE_HOST = 'blun.ai';
6
+ var https = require("https");
7
+ var auth = require("./auth");
8
+
9
+ var BASE_HOST = "blun.ai";
5
10
 
6
11
  function request(method, urlPath, body) {
7
- return new Promise((resolve, reject) => {
8
- const authHeader = auth.getAuthHeader() || {};
9
- const headers = { 'Content-Type': 'application/json', ...authHeader };
10
- const req = https.request({ hostname: BASE_HOST, path: urlPath, method, headers }, (res) => {
11
- let data = '';
12
- res.on('data', (c) => data += c);
13
- res.on('end', () => {
14
- try { resolve({ status: res.statusCode, data: JSON.parse(data) }); }
15
- catch { resolve({ status: res.statusCode, data }); }
16
- });
12
+ return new Promise(function (resolve, reject) {
13
+ var authHeader = auth.getAuthHeader() || {};
14
+ var headers = { "Content-Type": "application/json" };
15
+ Object.keys(authHeader).forEach(function (k) {
16
+ headers[k] = authHeader[k];
17
17
  });
18
- req.on('error', reject);
18
+ var req = https.request(
19
+ { hostname: BASE_HOST, path: urlPath, method: method, headers: headers },
20
+ function (res) {
21
+ var data = "";
22
+ res.on("data", function (c) {
23
+ data += c;
24
+ });
25
+ res.on("end", function () {
26
+ try {
27
+ resolve({ status: res.statusCode, data: JSON.parse(data) });
28
+ } catch (e) {
29
+ resolve({ status: res.statusCode, data: data });
30
+ }
31
+ });
32
+ }
33
+ );
34
+ req.on("error", reject);
19
35
  if (body) req.write(JSON.stringify(body));
20
36
  req.end();
21
37
  });
22
38
  }
23
39
 
24
- async function sendMessage(message, options) {
40
+ function sendMessage(message, options) {
25
41
  options = options || {};
26
- const body = { message, model: options.model || 'auto', stream: false };
27
- return request('POST', '/api/king/chat', body);
42
+ var body = { message: message, model: options.model || "auto", stream: false };
43
+ return request("POST", "/api/king/chat", body);
28
44
  }
29
45
 
30
- async function sendMessageStream(message, options) {
46
+ function sendMessageStream(message, options) {
31
47
  options = options || {};
32
- return new Promise((resolve, reject) => {
33
- const body = JSON.stringify({ message, model: options.model || 'auto', stream: true });
34
- const authHeader = auth.getAuthHeader() || {};
35
- const headers = { 'Content-Type': 'application/json', ...authHeader };
36
- const req = https.request({ hostname: BASE_HOST, path: '/api/king/chat', method: 'POST', headers }, (res) => {
37
- let fullText = '';
38
- res.on('data', (chunk) => {
39
- const lines = chunk.toString().split('\n');
40
- for (const line of lines) {
41
- if (line.startsWith('data: ')) {
48
+ return new Promise(function (resolve, reject) {
49
+ var body = JSON.stringify({
50
+ message: message,
51
+ model: options.model || "auto",
52
+ stream: true,
53
+ });
54
+ var authHeader = auth.getAuthHeader() || {};
55
+ var headers = { "Content-Type": "application/json" };
56
+ Object.keys(authHeader).forEach(function (k) {
57
+ headers[k] = authHeader[k];
58
+ });
59
+ var req = https.request(
60
+ {
61
+ hostname: BASE_HOST,
62
+ path: "/api/king/chat",
63
+ method: "POST",
64
+ headers: headers,
65
+ },
66
+ function (res) {
67
+ var fullText = "";
68
+ var lastUsage = null;
69
+ var buffer = "";
70
+ res.on("data", function (chunk) {
71
+ buffer += chunk.toString();
72
+ var lines = buffer.split("\n");
73
+ // Keep last incomplete line in buffer
74
+ buffer = lines.pop() || "";
75
+ for (var i = 0; i < lines.length; i++) {
76
+ var line = lines[i].trim();
77
+ if (line.startsWith("data: ")) {
78
+ var payload = line.slice(6).trim();
79
+ if (payload === "[DONE]") continue;
80
+ try {
81
+ var parsed = JSON.parse(payload);
82
+ if (parsed.text) {
83
+ fullText += parsed.text;
84
+ if (options.onChunk) options.onChunk(parsed.text);
85
+ }
86
+ if (parsed.usage) {
87
+ lastUsage = parsed.usage;
88
+ if (options.onUsage) options.onUsage(parsed.usage);
89
+ }
90
+ } catch (e) {
91
+ // ignore parse errors on partial lines
92
+ }
93
+ }
94
+ }
95
+ });
96
+ res.on("end", function () {
97
+ // Process remaining buffer
98
+ if (buffer.trim().startsWith("data: ")) {
42
99
  try {
43
- const parsed = JSON.parse(line.slice(6));
100
+ var parsed = JSON.parse(buffer.trim().slice(6));
44
101
  if (parsed.text) {
45
102
  fullText += parsed.text;
46
103
  if (options.onChunk) options.onChunk(parsed.text);
47
104
  }
48
- if (parsed.usage && options.onUsage) options.onUsage(parsed.usage);
49
- } catch {}
105
+ if (parsed.usage) lastUsage = parsed.usage;
106
+ } catch (e) {}
50
107
  }
51
- }
52
- });
53
- res.on('end', () => resolve({ text: fullText }));
54
- });
55
- req.on('error', reject);
108
+ resolve({ text: fullText, usage: lastUsage });
109
+ });
110
+ res.on("error", reject);
111
+ }
112
+ );
113
+ req.on("error", reject);
56
114
  req.write(body);
57
115
  req.end();
58
116
  });
59
117
  }
60
118
 
61
- async function getHealth() {
62
- return request('GET', '/api/monitoring/health');
119
+ function getHealth() {
120
+ return request("GET", "/api/monitoring/health");
63
121
  }
64
122
 
65
- module.exports = { sendMessage, sendMessageStream, getHealth };
123
+ module.exports = {
124
+ sendMessage: sendMessage,
125
+ sendMessageStream: sendMessageStream,
126
+ getHealth: getHealth,
127
+ };