blun-king-cli 7.0.5 → 7.1.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 +209 -209
- package/lib/ui.js +283 -287
- package/package.json +1 -1
package/lib/chat.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* BLUN King CLI v7.
|
|
3
|
-
*
|
|
2
|
+
* BLUN King CLI v7.1.0 - Chat Module
|
|
3
|
+
* Full-screen REPL with Settings Panel, Slash Suggestions, Theme support
|
|
4
|
+
* Adapted from Codex 1 patterns for blun.ai remote API
|
|
4
5
|
* CommonJS
|
|
5
6
|
*/
|
|
6
7
|
|
|
@@ -11,27 +12,26 @@ var auth = require("./auth");
|
|
|
11
12
|
var client = require("./client");
|
|
12
13
|
|
|
13
14
|
var localMode = false;
|
|
14
|
-
var permissionMode = "normal"; // safe, normal, god
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
// ── Slash Commands ────────────────────────────────────────
|
|
17
|
+
var SLASH_COMMANDS = [
|
|
18
|
+
["/help", "Show all commands"],
|
|
19
|
+
["/settings", "Open interactive settings panel"],
|
|
20
|
+
["/model", "Switch AI model"],
|
|
21
|
+
["/theme", "Switch terminal theme (neon/midnight/minimal)"],
|
|
22
|
+
["/perm", "Cycle permission mode (safe/normal/god)"],
|
|
23
|
+
["/status", "Token usage & uptime"],
|
|
24
|
+
["/whoami", "Current user info"],
|
|
25
|
+
["/keys", "List API keys"],
|
|
26
|
+
["/login", "Login from chat"],
|
|
27
|
+
["/clear", "Clear screen"],
|
|
28
|
+
["/exit", "Exit chat"],
|
|
29
|
+
];
|
|
30
|
+
var slashNames = SLASH_COMMANDS.map(function (c) { return c[0]; });
|
|
29
31
|
|
|
30
32
|
function startChat() {
|
|
31
|
-
// Show banner
|
|
32
33
|
ui.renderBanner();
|
|
33
34
|
|
|
34
|
-
// Check auth
|
|
35
35
|
var authPromise;
|
|
36
36
|
if (!auth.isAuthenticated()) {
|
|
37
37
|
authPromise = auth.showWelcomeMenu().then(function (result) {
|
|
@@ -54,10 +54,7 @@ function startChat() {
|
|
|
54
54
|
var creds = auth.getStoredCredentials();
|
|
55
55
|
if (creds) {
|
|
56
56
|
ui.renderResponse(
|
|
57
|
-
"Connected as " +
|
|
58
|
-
(creds.name || creds.email || "user") +
|
|
59
|
-
" | Plan: " +
|
|
60
|
-
(creds.plan || "free"),
|
|
57
|
+
"Connected as " + (creds.name || creds.email || "user") + " | Plan: " + (creds.plan || "free"),
|
|
61
58
|
"system"
|
|
62
59
|
);
|
|
63
60
|
} else if (localMode) {
|
|
@@ -65,25 +62,17 @@ function startChat() {
|
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
var settings = auth.getSettings();
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
// Ensure all settings fields exist
|
|
66
|
+
if (!settings.theme) settings.theme = "neon";
|
|
67
|
+
if (!settings.streamLevel) settings.streamLevel = "normal";
|
|
68
|
+
if (!settings.permissionMode) settings.permissionMode = "normal";
|
|
69
|
+
if (typeof settings.costVisibility === "undefined") settings.costVisibility = true;
|
|
70
|
+
if (!settings.statuslineMode) settings.statuslineMode = "full";
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
ui.setTheme(settings.theme || "neon");
|
|
73
73
|
|
|
74
|
-
var
|
|
75
|
-
|
|
76
|
-
["/model", "Switch AI model"],
|
|
77
|
-
["/settings", "Show/change settings"],
|
|
78
|
-
["/perm", "Cycle permission mode (safe/normal/god)"],
|
|
79
|
-
["/status", "Token usage & uptime"],
|
|
80
|
-
["/whoami", "Current user info"],
|
|
81
|
-
["/keys", "List API keys"],
|
|
82
|
-
["/login", "Login from chat"],
|
|
83
|
-
["/clear", "Clear screen"],
|
|
84
|
-
["/exit", "Exit chat"],
|
|
85
|
-
];
|
|
86
|
-
var slashNames = SLASH_COMMANDS.map(function (c) { return c[0]; });
|
|
74
|
+
var username = (creds ? creds.name : null) || "local";
|
|
75
|
+
var model = settings.model || "auto";
|
|
87
76
|
|
|
88
77
|
var rl = readline.createInterface({
|
|
89
78
|
input: process.stdin,
|
|
@@ -92,27 +81,89 @@ function startChat() {
|
|
|
92
81
|
terminal: true,
|
|
93
82
|
completer: function (line) {
|
|
94
83
|
if (line.startsWith("/")) {
|
|
95
|
-
var hits = slashNames.filter(function (c) { return c.
|
|
84
|
+
var hits = slashNames.filter(function (c) { return c.indexOf(line) === 0; });
|
|
96
85
|
return [hits.length ? hits : slashNames, line];
|
|
97
86
|
}
|
|
98
87
|
return [[], line];
|
|
99
88
|
},
|
|
100
89
|
});
|
|
101
90
|
|
|
102
|
-
showPrompt(
|
|
91
|
+
function showPrompt() {
|
|
92
|
+
ui.renderInputPanel(settings);
|
|
93
|
+
rl.setPrompt(ui.renderPrompt());
|
|
94
|
+
rl.prompt();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Interactive Settings Panel ──────────────────────
|
|
98
|
+
function openSettingsPanel() {
|
|
99
|
+
var selection = 0;
|
|
100
|
+
|
|
101
|
+
function render() {
|
|
102
|
+
// Clear and redraw
|
|
103
|
+
readline.cursorTo(process.stdout, 0);
|
|
104
|
+
readline.clearScreenDown(process.stdout);
|
|
105
|
+
var lines = ui.buildSettingsPanel(settings, selection);
|
|
106
|
+
console.log(lines.join("\n"));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function cleanup() {
|
|
110
|
+
process.stdin.removeListener("keypress", onKeypress);
|
|
111
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) process.stdin.setRawMode(false);
|
|
112
|
+
process.stdin.resume();
|
|
113
|
+
console.log();
|
|
114
|
+
showPrompt();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function onKeypress(_, key) {
|
|
118
|
+
key = key || {};
|
|
119
|
+
if (key.name === "up") {
|
|
120
|
+
selection = (selection + ui.SETTINGS_FIELDS.length - 1) % ui.SETTINGS_FIELDS.length;
|
|
121
|
+
render();
|
|
122
|
+
} else if (key.name === "down") {
|
|
123
|
+
selection = (selection + 1) % ui.SETTINGS_FIELDS.length;
|
|
124
|
+
render();
|
|
125
|
+
} else if (key.name === "right") {
|
|
126
|
+
var field = ui.SETTINGS_FIELDS[selection];
|
|
127
|
+
settings = ui.cycleSetting(settings, field.key, 1);
|
|
128
|
+
if (field.key === "theme") ui.setTheme(settings.theme);
|
|
129
|
+
auth.saveSettings(settings);
|
|
130
|
+
render();
|
|
131
|
+
} else if (key.name === "left") {
|
|
132
|
+
var field = ui.SETTINGS_FIELDS[selection];
|
|
133
|
+
settings = ui.cycleSetting(settings, field.key, -1);
|
|
134
|
+
if (field.key === "theme") ui.setTheme(settings.theme);
|
|
135
|
+
auth.saveSettings(settings);
|
|
136
|
+
render();
|
|
137
|
+
} else if (key.name === "return" || key.name === "escape") {
|
|
138
|
+
cleanup();
|
|
139
|
+
} else if (key.ctrl && key.name === "c") {
|
|
140
|
+
cleanup();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Pause readline, take raw input
|
|
145
|
+
rl.pause();
|
|
146
|
+
readline.emitKeypressEvents(process.stdin);
|
|
147
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) process.stdin.setRawMode(true);
|
|
148
|
+
process.stdin.on("keypress", onKeypress);
|
|
149
|
+
render();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log();
|
|
153
|
+
showPrompt();
|
|
103
154
|
|
|
104
155
|
return new Promise(function (resolveChat) {
|
|
105
156
|
rl.on("line", function (line) {
|
|
106
157
|
var input = line.trim();
|
|
107
158
|
|
|
108
159
|
if (!input) {
|
|
109
|
-
showPrompt(
|
|
160
|
+
showPrompt();
|
|
110
161
|
return;
|
|
111
162
|
}
|
|
112
163
|
|
|
113
|
-
ui.
|
|
164
|
+
ui.renderInputPanelClose(settings);
|
|
114
165
|
|
|
115
|
-
// Commands
|
|
166
|
+
// ── Commands ────────────────────────────────
|
|
116
167
|
if (input === "/exit" || input === "/quit") {
|
|
117
168
|
ui.renderResponse("Goodbye!", "system");
|
|
118
169
|
rl.close();
|
|
@@ -122,245 +173,194 @@ function startChat() {
|
|
|
122
173
|
if (input === "/clear") {
|
|
123
174
|
console.clear();
|
|
124
175
|
ui.renderBanner();
|
|
125
|
-
showPrompt(
|
|
176
|
+
showPrompt();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (input === "/settings") {
|
|
181
|
+
openSettingsPanel();
|
|
126
182
|
return;
|
|
127
183
|
}
|
|
128
184
|
|
|
129
185
|
if (input === "/status") {
|
|
130
|
-
ui.
|
|
131
|
-
|
|
186
|
+
var stats = ui.getStats();
|
|
187
|
+
var t = ui.getTheme();
|
|
188
|
+
console.log();
|
|
189
|
+
console.log(t.header(" Status"));
|
|
190
|
+
console.log(t.muted(" " + "\u2500".repeat(40)));
|
|
191
|
+
console.log(" Model: " + chalk.white(settings.model || "auto"));
|
|
192
|
+
console.log(" Permission: " + ui.formatSettingValue({ key: "permissionMode" }, settings.permissionMode));
|
|
193
|
+
console.log(" Theme: " + chalk.white(settings.theme || "neon"));
|
|
194
|
+
console.log(" Tokens: " + chalk.white(ui.formatNumber(stats.tokens)));
|
|
195
|
+
console.log(" Cost: " + chalk.white(ui.estimateCost(stats.tokens, settings.model)));
|
|
196
|
+
console.log(" Uptime: " + chalk.white(ui.formatDuration(stats.uptime)));
|
|
197
|
+
console.log();
|
|
198
|
+
showPrompt();
|
|
132
199
|
return;
|
|
133
200
|
}
|
|
134
201
|
|
|
135
202
|
if (input === "/perm" || input === "/permission") {
|
|
136
|
-
var
|
|
137
|
-
settings.permissionMode
|
|
203
|
+
var modes = ui.PERMISSION_MODES;
|
|
204
|
+
var idx = modes.indexOf(settings.permissionMode);
|
|
205
|
+
settings.permissionMode = modes[(idx + 1) % modes.length];
|
|
138
206
|
auth.saveSettings(settings);
|
|
139
|
-
ui.
|
|
140
|
-
|
|
141
|
-
showPrompt(rl, username, model);
|
|
207
|
+
ui.renderResponse("Permission: " + settings.permissionMode, "system");
|
|
208
|
+
showPrompt();
|
|
142
209
|
return;
|
|
143
210
|
}
|
|
144
211
|
|
|
145
|
-
if (input
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.
|
|
158
|
-
|
|
159
|
-
|
|
212
|
+
if (input.indexOf("/theme") === 0) {
|
|
213
|
+
var parts = input.split(/\s+/);
|
|
214
|
+
if (parts[1] && ui.THEMES[parts[1]]) {
|
|
215
|
+
settings.theme = parts[1];
|
|
216
|
+
ui.setTheme(parts[1]);
|
|
217
|
+
auth.saveSettings(settings);
|
|
218
|
+
ui.renderResponse("Theme: " + parts[1], "system");
|
|
219
|
+
} else {
|
|
220
|
+
var modes = Object.keys(ui.THEMES);
|
|
221
|
+
var idx = modes.indexOf(settings.theme || "neon");
|
|
222
|
+
settings.theme = modes[(idx + 1) % modes.length];
|
|
223
|
+
ui.setTheme(settings.theme);
|
|
224
|
+
auth.saveSettings(settings);
|
|
225
|
+
ui.renderResponse("Theme: " + settings.theme, "system");
|
|
226
|
+
}
|
|
227
|
+
showPrompt();
|
|
160
228
|
return;
|
|
161
229
|
}
|
|
162
230
|
|
|
163
|
-
if (input
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
.
|
|
170
|
-
|
|
231
|
+
if (input.indexOf("/model") === 0) {
|
|
232
|
+
var parts = input.split(/\s+/);
|
|
233
|
+
if (parts[1]) {
|
|
234
|
+
settings.model = parts[1];
|
|
235
|
+
model = parts[1];
|
|
236
|
+
auth.saveSettings(settings);
|
|
237
|
+
ui.renderResponse("Model: " + parts[1], "system");
|
|
238
|
+
showPrompt();
|
|
239
|
+
} else {
|
|
240
|
+
var inquirer = require("inquirer");
|
|
241
|
+
inquirer.prompt([{
|
|
242
|
+
type: "list",
|
|
243
|
+
name: "model",
|
|
244
|
+
message: chalk.cyan("Select Model"),
|
|
245
|
+
choices: [
|
|
246
|
+
{ name: "Auto (recommended)", value: "auto" },
|
|
247
|
+
{ name: "Gemma 4", value: "gemma4" },
|
|
248
|
+
{ name: "Claude", value: "claude" },
|
|
249
|
+
{ name: "Llama", value: "llama" },
|
|
250
|
+
],
|
|
251
|
+
}]).then(function (ans) {
|
|
252
|
+
if (ans.model) {
|
|
253
|
+
model = ans.model;
|
|
254
|
+
settings.model = ans.model;
|
|
255
|
+
auth.saveSettings(settings);
|
|
256
|
+
ui.renderResponse("Model: " + ans.model, "system");
|
|
257
|
+
}
|
|
258
|
+
showPrompt();
|
|
171
259
|
});
|
|
260
|
+
}
|
|
172
261
|
return;
|
|
173
262
|
}
|
|
174
263
|
|
|
175
|
-
if (input === "/
|
|
176
|
-
auth
|
|
177
|
-
|
|
178
|
-
.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
264
|
+
if (input === "/login") {
|
|
265
|
+
auth.login().then(function () {
|
|
266
|
+
localMode = false;
|
|
267
|
+
creds = auth.getStoredCredentials();
|
|
268
|
+
username = (creds ? creds.name : null) || "local";
|
|
269
|
+
ui.renderResponse("Logged in!", "system");
|
|
270
|
+
}).catch(function (e) {
|
|
271
|
+
ui.renderResponse(e.message, "error");
|
|
272
|
+
}).then(function () { showPrompt(); });
|
|
184
273
|
return;
|
|
185
274
|
}
|
|
186
275
|
|
|
187
|
-
if (input === "/
|
|
188
|
-
auth
|
|
189
|
-
.
|
|
190
|
-
|
|
191
|
-
{ label: "Gemma 4", value: "gemma4" },
|
|
192
|
-
{ label: "Claude", value: "claude" },
|
|
193
|
-
{ label: "Llama", value: "llama" },
|
|
194
|
-
])
|
|
195
|
-
.then(function (choice) {
|
|
196
|
-
if (choice) {
|
|
197
|
-
model = choice;
|
|
198
|
-
settings.model = choice;
|
|
199
|
-
auth.saveSettings(settings);
|
|
200
|
-
ui.renderResponse("Model set to: " + choice, "system");
|
|
201
|
-
}
|
|
202
|
-
showPrompt(rl, username, model);
|
|
203
|
-
});
|
|
276
|
+
if (input === "/whoami") {
|
|
277
|
+
auth.whoami().catch(function (e) {
|
|
278
|
+
ui.renderResponse(e.message, "error");
|
|
279
|
+
}).then(function () { showPrompt(); });
|
|
204
280
|
return;
|
|
205
281
|
}
|
|
206
282
|
|
|
207
|
-
if (input === "/
|
|
208
|
-
|
|
209
|
-
"
|
|
210
|
-
|
|
211
|
-
"Model: " + (settings.model || "auto"),
|
|
212
|
-
"Language: " + (settings.language || "de"),
|
|
213
|
-
"Voice: " + (settings.voice ? "on" : "off"),
|
|
214
|
-
"Permission: " + (permissionMode || "normal"),
|
|
215
|
-
].join("\n"),
|
|
216
|
-
"cyan"
|
|
217
|
-
);
|
|
218
|
-
auth
|
|
219
|
-
.promptMenu("Change Setting", [
|
|
220
|
-
{ label: "Model", value: "model" },
|
|
221
|
-
{ label: "Language (de/en)", value: "language" },
|
|
222
|
-
{ label: "Voice on/off", value: "voice" },
|
|
223
|
-
{ label: "Back", value: "back" },
|
|
224
|
-
])
|
|
225
|
-
.then(function (action) {
|
|
226
|
-
if (action === "model") {
|
|
227
|
-
return auth
|
|
228
|
-
.promptMenu("Select Model", [
|
|
229
|
-
{ label: "Auto", value: "auto" },
|
|
230
|
-
{ label: "Gemma 4", value: "gemma4" },
|
|
231
|
-
{ label: "Claude", value: "claude" },
|
|
232
|
-
{ label: "Llama", value: "llama" },
|
|
233
|
-
])
|
|
234
|
-
.then(function (m) {
|
|
235
|
-
if (m) {
|
|
236
|
-
model = m;
|
|
237
|
-
settings.model = m;
|
|
238
|
-
auth.saveSettings(settings);
|
|
239
|
-
ui.renderResponse("Model: " + m, "system");
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
} else if (action === "language") {
|
|
243
|
-
return auth.prompt(chalk.green(" Language (de/en): ")).then(function (l) {
|
|
244
|
-
if (l) {
|
|
245
|
-
settings.language = l;
|
|
246
|
-
auth.saveSettings(settings);
|
|
247
|
-
ui.renderResponse("Language: " + l, "system");
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
} else if (action === "voice") {
|
|
251
|
-
settings.voice = !settings.voice;
|
|
252
|
-
auth.saveSettings(settings);
|
|
253
|
-
ui.renderResponse(
|
|
254
|
-
"Voice: " + (settings.voice ? "on" : "off"),
|
|
255
|
-
"system"
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
})
|
|
259
|
-
.then(function () {
|
|
260
|
-
showPrompt(rl, username, model);
|
|
261
|
-
});
|
|
283
|
+
if (input === "/keys") {
|
|
284
|
+
auth.listApiKeys().catch(function (e) {
|
|
285
|
+
ui.renderResponse(e.message, "error");
|
|
286
|
+
}).then(function () { showPrompt(); });
|
|
262
287
|
return;
|
|
263
288
|
}
|
|
264
289
|
|
|
265
290
|
if (input === "/help") {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
["
|
|
272
|
-
["/whoami", "Show current user info"],
|
|
273
|
-
["/keys", "List API keys"],
|
|
274
|
-
["/model", "Switch AI model"],
|
|
275
|
-
["/settings", "Show/change settings"],
|
|
276
|
-
["/perm", "Cycle permission mode (safe/normal/god)"],
|
|
277
|
-
["/help", "Show this help"],
|
|
278
|
-
];
|
|
279
|
-
helpLines.forEach(function (h) {
|
|
280
|
-
console.log(" " + chalk.green(h[0]) + " ".repeat(14 - h[0].length) + chalk.dim(h[1]));
|
|
291
|
+
var t = ui.getTheme();
|
|
292
|
+
console.log();
|
|
293
|
+
console.log(t.header(" Commands"));
|
|
294
|
+
console.log(t.muted(" " + "\u2500".repeat(40)));
|
|
295
|
+
SLASH_COMMANDS.forEach(function (c) {
|
|
296
|
+
console.log(" " + chalk.green(c[0]) + " ".repeat(Math.max(1, 16 - c[0].length)) + chalk.gray(c[1]));
|
|
281
297
|
});
|
|
282
298
|
console.log();
|
|
283
|
-
|
|
299
|
+
console.log(t.muted(" Tip: Type / and press Tab for autocomplete"));
|
|
300
|
+
console.log();
|
|
301
|
+
showPrompt();
|
|
284
302
|
return;
|
|
285
303
|
}
|
|
286
304
|
|
|
287
|
-
// Slash command picker
|
|
305
|
+
// ── Slash command picker (just "/") ──────────
|
|
288
306
|
if (input === "/") {
|
|
289
307
|
var inquirer = require("inquirer");
|
|
290
308
|
inquirer.prompt([{
|
|
291
309
|
type: "list",
|
|
292
310
|
name: "cmd",
|
|
293
311
|
message: chalk.cyan("Select command"),
|
|
294
|
-
choices:
|
|
295
|
-
{ name: chalk.green(
|
|
296
|
-
|
|
297
|
-
{ name: chalk.green("/perm") + chalk.dim(" Cycle permission mode"), value: "/perm" },
|
|
298
|
-
{ name: chalk.green("/status") + chalk.dim(" Token usage & uptime"), value: "/status" },
|
|
299
|
-
{ name: chalk.green("/whoami") + chalk.dim(" Current user info"), value: "/whoami" },
|
|
300
|
-
{ name: chalk.green("/keys") + chalk.dim(" List API keys"), value: "/keys" },
|
|
301
|
-
{ name: chalk.green("/login") + chalk.dim(" Login from chat"), value: "/login" },
|
|
302
|
-
{ name: chalk.green("/clear") + chalk.dim(" Clear screen"), value: "/clear" },
|
|
303
|
-
{ name: chalk.green("/help") + chalk.dim(" Show all commands"), value: "/help" },
|
|
312
|
+
choices: SLASH_COMMANDS.map(function (c) {
|
|
313
|
+
return { name: chalk.green(c[0]) + chalk.gray(" " + c[1]), value: c[0] };
|
|
314
|
+
}).concat([
|
|
304
315
|
new inquirer.Separator(),
|
|
305
|
-
{ name: chalk.red("/exit") + chalk.dim(" Exit chat"), value: "/exit" },
|
|
306
316
|
{ name: chalk.dim("Cancel"), value: "" },
|
|
307
|
-
],
|
|
317
|
+
]),
|
|
308
318
|
pageSize: 12,
|
|
309
319
|
}]).then(function (ans) {
|
|
310
320
|
if (ans.cmd) {
|
|
311
321
|
rl.emit("line", ans.cmd);
|
|
312
322
|
} else {
|
|
313
|
-
showPrompt(
|
|
323
|
+
showPrompt();
|
|
314
324
|
}
|
|
315
325
|
});
|
|
316
326
|
return;
|
|
317
327
|
}
|
|
318
328
|
|
|
319
|
-
// Send message
|
|
329
|
+
// ── Send message ────────────────────────────
|
|
320
330
|
if (localMode) {
|
|
321
|
-
ui.renderResponse(
|
|
322
|
-
|
|
323
|
-
"error"
|
|
324
|
-
);
|
|
325
|
-
showPrompt(rl, username, model);
|
|
331
|
+
ui.renderResponse("Local mode - cannot send messages. Use /login to authenticate.", "error");
|
|
332
|
+
showPrompt();
|
|
326
333
|
return;
|
|
327
334
|
}
|
|
328
335
|
|
|
329
|
-
// Send message
|
|
330
336
|
var spinner = ui.renderSpinner("Thinking...");
|
|
331
|
-
client
|
|
332
|
-
.sendMessage(input, { model: settings.model || "auto" })
|
|
337
|
+
client.sendMessage(input, { model: settings.model || "auto" })
|
|
333
338
|
.then(function (res) {
|
|
334
339
|
ui.stopSpinner(true, "Response received");
|
|
335
340
|
if (res.status === 200 && res.data) {
|
|
336
|
-
var text =
|
|
337
|
-
res.data.answer ||
|
|
338
|
-
res.data.response ||
|
|
339
|
-
res.data.text ||
|
|
340
|
-
res.data.message ||
|
|
341
|
-
JSON.stringify(res.data);
|
|
342
|
-
ui.renderResponse(text, "agent");
|
|
341
|
+
var text = res.data.answer || res.data.response || res.data.text || res.data.message || JSON.stringify(res.data);
|
|
343
342
|
var tok = res.data.tokens || res.data.usage || {};
|
|
344
343
|
var tokTotal = tok.total || tok.total_tokens || 0;
|
|
345
344
|
if (!tokTotal && text) {
|
|
346
345
|
tokTotal = Math.ceil(text.length / 4) + Math.ceil(input.length / 4);
|
|
347
346
|
}
|
|
348
347
|
if (tokTotal) ui.addTokens(tokTotal);
|
|
349
|
-
ui.
|
|
348
|
+
ui.renderResponse(text, "agent", {
|
|
349
|
+
model: settings.model || "auto",
|
|
350
|
+
tokens: tokTotal,
|
|
351
|
+
cost: settings.costVisibility,
|
|
352
|
+
});
|
|
350
353
|
} else if (res.status === 401 || res.status === 403) {
|
|
351
354
|
ui.renderResponse("Auth error. Use /login to re-authenticate.", "error");
|
|
352
355
|
} else {
|
|
353
356
|
ui.renderResponse("Error: " + JSON.stringify(res.data), "error");
|
|
354
357
|
}
|
|
355
|
-
showPrompt(
|
|
358
|
+
showPrompt();
|
|
356
359
|
})
|
|
357
360
|
.catch(function (e) {
|
|
358
361
|
ui.stopSpinner(false, e.message);
|
|
359
|
-
ui.renderResponse(
|
|
360
|
-
|
|
361
|
-
"error"
|
|
362
|
-
);
|
|
363
|
-
showPrompt(rl, username, model);
|
|
362
|
+
ui.renderResponse("Request failed: " + e.message + "\n Try /login if auth expired.", "error");
|
|
363
|
+
showPrompt();
|
|
364
364
|
});
|
|
365
365
|
});
|
|
366
366
|
|
package/lib/ui.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* BLUN King CLI v7.
|
|
3
|
-
* Claude Code
|
|
4
|
-
* CommonJS - chalk@4, ora@5
|
|
2
|
+
* BLUN King CLI v7.1.0 - Rich Terminal UI Module
|
|
3
|
+
* Adapted from Codex 1 patterns + Claude Code style
|
|
4
|
+
* CommonJS - chalk@4, ora@5
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
var chalk = require("chalk");
|
|
8
8
|
var ora = require("ora");
|
|
9
|
-
var Table = require("cli-table3");
|
|
10
|
-
var boxen = require("boxen");
|
|
11
9
|
var gradient = require("gradient-string");
|
|
12
10
|
var figlet = require("figlet");
|
|
13
11
|
|
|
@@ -16,25 +14,104 @@ var startTime = Date.now();
|
|
|
16
14
|
var totalTokens = 0;
|
|
17
15
|
var totalCost = 0;
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
// ── Theme System ──────────────────────────────────────────
|
|
18
|
+
var THEMES = {
|
|
19
|
+
neon: {
|
|
20
|
+
frame: chalk.cyan,
|
|
21
|
+
input: chalk.blue,
|
|
22
|
+
response: chalk.green,
|
|
23
|
+
error: chalk.red,
|
|
24
|
+
header: chalk.cyan,
|
|
25
|
+
accent: chalk.yellow,
|
|
26
|
+
muted: chalk.gray,
|
|
27
|
+
status: chalk.dim,
|
|
28
|
+
},
|
|
29
|
+
midnight: {
|
|
30
|
+
frame: chalk.magenta,
|
|
31
|
+
input: chalk.gray,
|
|
32
|
+
response: chalk.green,
|
|
33
|
+
error: chalk.red,
|
|
34
|
+
header: chalk.blue,
|
|
35
|
+
accent: chalk.magenta,
|
|
36
|
+
muted: chalk.gray,
|
|
37
|
+
status: chalk.dim,
|
|
38
|
+
},
|
|
39
|
+
minimal: {
|
|
40
|
+
frame: chalk.gray,
|
|
41
|
+
input: chalk.gray,
|
|
42
|
+
response: chalk.blue,
|
|
43
|
+
error: chalk.red,
|
|
44
|
+
header: chalk.gray,
|
|
45
|
+
accent: chalk.white,
|
|
46
|
+
muted: chalk.gray,
|
|
47
|
+
status: chalk.dim,
|
|
48
|
+
},
|
|
28
49
|
};
|
|
29
50
|
|
|
30
|
-
|
|
51
|
+
var currentTheme = "neon";
|
|
52
|
+
|
|
53
|
+
function getTheme() {
|
|
54
|
+
return THEMES[currentTheme] || THEMES.neon;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function setTheme(name) {
|
|
58
|
+
if (THEMES[name]) currentTheme = name;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Settings Fields ───────────────────────────────────────
|
|
62
|
+
var SETTINGS_FIELDS = [
|
|
63
|
+
{ section: "Core", key: "model", label: "Model", values: ["auto", "gemma4", "claude", "llama"], help: "Welches Modell standardmaessig antwortet." },
|
|
64
|
+
{ section: "Core", key: "language", label: "Language", values: ["de", "en"], help: "Primaere Antwortsprache." },
|
|
65
|
+
{ section: "Console", key: "theme", label: "Theme", values: ["neon", "midnight", "minimal"], help: "Farbstimmung der Konsole." },
|
|
66
|
+
{ section: "Console", key: "streamLevel", label: "Stream", values: ["off", "normal"], help: "Ob Antworten gestreamt werden." },
|
|
67
|
+
{ section: "Runtime", key: "voice", label: "Voice", values: [true, false], help: "Voice-Features an/aus." },
|
|
68
|
+
{ section: "Runtime", key: "permissionMode", label: "Permission", values: ["safe", "normal", "god"], help: "Welche Aktionen BLUN ausfuehren darf." },
|
|
69
|
+
{ section: "Workflow", key: "costVisibility", label: "Cost", values: [true, false], help: "Token-/Kosten-Footer anzeigen." },
|
|
70
|
+
{ section: "Workflow", key: "statuslineMode", label: "Statusline", values: ["compact", "full"], help: "Wie detailliert die Statuszeile ist." },
|
|
71
|
+
];
|
|
72
|
+
|
|
31
73
|
var PERMISSION_MODES = ["safe", "normal", "god"];
|
|
32
|
-
var permissionLabels = {
|
|
33
|
-
safe: chalk.green("safe"),
|
|
34
|
-
normal: chalk.yellow("normal"),
|
|
35
|
-
god: chalk.red("god mode"),
|
|
36
|
-
};
|
|
37
74
|
|
|
75
|
+
// ── Helpers ───────────────────────────────────────────────
|
|
76
|
+
function getWidth() {
|
|
77
|
+
return Math.min((process.stdout.columns || 80) - 4, 96);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function fitPlain(text, width) {
|
|
81
|
+
var raw = String(text || "");
|
|
82
|
+
if (raw.length <= width) return raw + " ".repeat(width - raw.length);
|
|
83
|
+
if (width <= 3) return "...";
|
|
84
|
+
return raw.slice(0, width - 3) + "...";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function formatNumber(v) {
|
|
88
|
+
return Number(v || 0).toLocaleString("de-DE");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function formatDuration(ms) {
|
|
92
|
+
var s = Math.floor((ms || 0) / 1000);
|
|
93
|
+
var m = Math.floor(s / 60);
|
|
94
|
+
s = s % 60;
|
|
95
|
+
return m + "m " + (s < 10 ? "0" : "") + s + "s";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function estimateCost(tokens, model) {
|
|
99
|
+
var rates = { auto: 0.004, gemma4: 0.0015, claude: 0.015, llama: 0.002 };
|
|
100
|
+
var rate = rates[String(model || "auto").toLowerCase()] || rates.auto;
|
|
101
|
+
return ((Number(tokens || 0) / 1000) * rate).toFixed(4) + " EUR";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function formatSettingValue(field, value) {
|
|
105
|
+
if (field.key === "language") return value === "en" ? "English" : "Deutsch";
|
|
106
|
+
if (field.key === "permissionMode") {
|
|
107
|
+
var colors = { safe: chalk.green, normal: chalk.yellow, god: chalk.red };
|
|
108
|
+
return (colors[value] || chalk.white)(String(value));
|
|
109
|
+
}
|
|
110
|
+
if (typeof value === "boolean") return value ? chalk.green("On") : chalk.gray("Off");
|
|
111
|
+
return chalk.white(String(value || "-"));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── Banner ────────────────────────────────────────────────
|
|
38
115
|
function renderBanner() {
|
|
39
116
|
var art;
|
|
40
117
|
try {
|
|
@@ -43,179 +120,184 @@ function renderBanner() {
|
|
|
43
120
|
art = figlet.textSync("BLUN King", { font: "Standard" });
|
|
44
121
|
}
|
|
45
122
|
console.log(gradient.pastel.multiline(art));
|
|
46
|
-
|
|
123
|
+
var t = getTheme();
|
|
124
|
+
console.log(t.muted(" " + "\u2500".repeat(53)));
|
|
47
125
|
console.log(
|
|
48
|
-
chalk.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
chalk.yellow("Multi-KI Engine")
|
|
126
|
+
" " + chalk.bold.white("BLUN King CLI") +
|
|
127
|
+
t.muted(" \u2502 ") +
|
|
128
|
+
chalk.green("v" + require("../package.json").version) +
|
|
129
|
+
t.muted(" \u2502 ") +
|
|
130
|
+
t.accent("Multi-KI Engine")
|
|
54
131
|
);
|
|
55
|
-
console.log(
|
|
132
|
+
console.log(t.muted(" " + "\u2500".repeat(53) + "\n"));
|
|
56
133
|
}
|
|
57
134
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
135
|
+
// ── Input Panel (framed) ──────────────────────────────────
|
|
136
|
+
function renderInputPanel(settings) {
|
|
137
|
+
var t = getTheme();
|
|
138
|
+
var w = getWidth();
|
|
139
|
+
var inner = w - 2;
|
|
140
|
+
var model = (settings && settings.model) || "auto";
|
|
141
|
+
var perm = (settings && settings.permissionMode) || "normal";
|
|
61
142
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
var pm = permMode || currentPermMode || "normal";
|
|
65
|
-
var label = permissionLabels[pm] || permissionLabels.normal;
|
|
66
|
-
var modelTag = chalk.bgCyan.black(" " + (model || "auto") + " ");
|
|
67
|
-
var info = modelTag + chalk.dim(" | ") + label;
|
|
68
|
-
console.log(chalk.green("\u256d\u2500") + chalk.green.bold(" BLUN ") + chalk.green("\u2500".repeat(Math.max(2, w - 10))) + chalk.green("\u256e"));
|
|
143
|
+
// Top frame
|
|
144
|
+
console.log(t.frame("\u256D\u2500") + t.frame.bold(" BLUN ") + t.frame("\u2500".repeat(Math.max(2, inner - 8))) + t.frame("\u256E"));
|
|
69
145
|
}
|
|
70
146
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
var
|
|
76
|
-
var
|
|
77
|
-
var
|
|
78
|
-
var
|
|
79
|
-
var elapsed = Math.floor(stats.uptime / 1000);
|
|
80
|
-
var mins = Math.floor(elapsed / 60);
|
|
81
|
-
var secs = elapsed % 60;
|
|
82
|
-
var infoLine = label + chalk.dim(" | Tokens: " + totalTokens.toLocaleString() + " | " + mins + "m " + secs + "s");
|
|
83
|
-
console.log(chalk.green("\u2570") + chalk.green("\u2500") + chalk.dim(" ") + infoLine + chalk.dim(" ") + chalk.green("\u2500".repeat(Math.max(2, w - 40))) + chalk.green("\u256f"));
|
|
84
|
-
}
|
|
147
|
+
function renderInputPanelClose(settings) {
|
|
148
|
+
var t = getTheme();
|
|
149
|
+
var w = getWidth();
|
|
150
|
+
var inner = w - 2;
|
|
151
|
+
var model = (settings && settings.model) || "auto";
|
|
152
|
+
var perm = (settings && settings.permissionMode) || "normal";
|
|
153
|
+
var permColor = { safe: chalk.green, normal: chalk.yellow, god: chalk.red };
|
|
154
|
+
var permLabel = (permColor[perm] || chalk.white)(perm);
|
|
85
155
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
156
|
+
var elapsed = formatDuration(Date.now() - startTime);
|
|
157
|
+
var tokens = formatNumber(totalTokens);
|
|
158
|
+
var cost = estimateCost(totalTokens, model);
|
|
89
159
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
);
|
|
160
|
+
var statusParts = [];
|
|
161
|
+
statusParts.push(permLabel);
|
|
162
|
+
statusParts.push(t.muted("Tokens: " + tokens));
|
|
163
|
+
statusParts.push(t.muted(elapsed));
|
|
164
|
+
if (settings && settings.costVisibility !== false) {
|
|
165
|
+
statusParts.push(t.muted(cost));
|
|
166
|
+
}
|
|
167
|
+
var statusLine = statusParts.join(t.muted(" \u2502 "));
|
|
168
|
+
|
|
169
|
+
console.log(t.frame("\u2570\u2500 ") + statusLine + " " + t.frame("\u2500".repeat(Math.max(1, inner - 50))) + t.frame("\u256F"));
|
|
98
170
|
}
|
|
99
171
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
chalk.dim("\u2502") +
|
|
105
|
-
chalk.dim(" Tip: Type a message and press Enter. Use /help for all commands.")
|
|
106
|
-
);
|
|
107
|
-
console.log(chalk.dim("\u2570" + "\u2500".repeat(w) + "\u256f"));
|
|
172
|
+
// ── Prompt ────────────────────────────────────────────────
|
|
173
|
+
function renderPrompt() {
|
|
174
|
+
var t = getTheme();
|
|
175
|
+
return t.frame("\u2502 ") + chalk.bold("> ");
|
|
108
176
|
}
|
|
109
177
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
var
|
|
113
|
-
var label = labels[type] || "BLUN";
|
|
178
|
+
// ── Response Panel ────────────────────────────────────────
|
|
179
|
+
function renderResponse(text, type, meta) {
|
|
180
|
+
var t = getTheme();
|
|
114
181
|
var rendered = renderMarkdown(text);
|
|
115
182
|
|
|
116
183
|
if (type === "agent") {
|
|
117
|
-
// Claude Code style - plain white text, no box, no color
|
|
118
184
|
var lines = rendered.split("\n");
|
|
119
185
|
lines.forEach(function (line) {
|
|
120
186
|
console.log(" " + line);
|
|
121
187
|
});
|
|
188
|
+
if (meta) {
|
|
189
|
+
var metaParts = [];
|
|
190
|
+
if (meta.model) metaParts.push(chalk.bgCyan.black(" " + meta.model + " "));
|
|
191
|
+
if (meta.tokens) metaParts.push(t.muted("Tokens: " + formatNumber(meta.tokens)));
|
|
192
|
+
metaParts.push(t.muted("Total: " + formatNumber(totalTokens)));
|
|
193
|
+
metaParts.push(t.muted(formatDuration(Date.now() - startTime)));
|
|
194
|
+
if (meta.cost !== false) metaParts.push(t.muted(estimateCost(totalTokens, meta.model)));
|
|
195
|
+
console.log(" " + metaParts.join(t.muted(" \u2502 ")));
|
|
196
|
+
}
|
|
122
197
|
console.log();
|
|
123
198
|
} else if (type === "error") {
|
|
124
|
-
console.log(
|
|
199
|
+
console.log(t.error(" \u2718 " + rendered));
|
|
125
200
|
console.log();
|
|
126
201
|
} else if (type === "system") {
|
|
127
|
-
console.log(
|
|
128
|
-
console.log();
|
|
129
|
-
} else {
|
|
130
|
-
var prefix = colorFn.bold(" [" + label + "] ");
|
|
131
|
-
var lines = rendered.split("\n");
|
|
132
|
-
lines.forEach(function (line, i) {
|
|
133
|
-
if (i === 0) {
|
|
134
|
-
console.log(prefix + colorFn(line));
|
|
135
|
-
} else {
|
|
136
|
-
console.log(" " + colorFn(line));
|
|
137
|
-
}
|
|
138
|
-
});
|
|
202
|
+
console.log(t.accent(" [SYS] ") + t.accent(rendered));
|
|
139
203
|
console.log();
|
|
140
204
|
}
|
|
141
205
|
}
|
|
142
206
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
var
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
)
|
|
153
|
-
|
|
207
|
+
// ── Settings Panel (arrow-key navigated) ──────────────────
|
|
208
|
+
function buildSettingsPanel(settings, selection) {
|
|
209
|
+
var t = getTheme();
|
|
210
|
+
var w = getWidth();
|
|
211
|
+
var inner = w - 6;
|
|
212
|
+
var lines = [];
|
|
213
|
+
var currentSection = "";
|
|
214
|
+
var sel = selection || 0;
|
|
215
|
+
|
|
216
|
+
for (var i = 0; i < SETTINGS_FIELDS.length; i++) {
|
|
217
|
+
var field = SETTINGS_FIELDS[i];
|
|
218
|
+
if (field.section !== currentSection) {
|
|
219
|
+
currentSection = field.section;
|
|
220
|
+
if (lines.length) lines.push("");
|
|
221
|
+
lines.push(t.header(" " + currentSection));
|
|
222
|
+
}
|
|
223
|
+
var marker = i === sel ? chalk.cyan("> ") : " ";
|
|
224
|
+
var label = field.label;
|
|
225
|
+
var value = settings[field.key];
|
|
226
|
+
lines.push(marker + chalk.white(fitPlain(label, 14)) + formatSettingValue(field, value));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
var selectedField = SETTINGS_FIELDS[Math.max(0, Math.min(sel, SETTINGS_FIELDS.length - 1))];
|
|
230
|
+
lines.push("");
|
|
231
|
+
lines.push(t.accent(" Active: " + selectedField.label + " " + formatSettingValue(selectedField, settings[selectedField.key])));
|
|
232
|
+
lines.push(t.muted(" " + (selectedField.help || "")));
|
|
233
|
+
lines.push("");
|
|
234
|
+
lines.push(t.muted(" Up/Down = select Left/Right = change Enter/Esc = close"));
|
|
235
|
+
|
|
236
|
+
// Frame it
|
|
237
|
+
var framed = [];
|
|
238
|
+
framed.push(t.frame("\u256D\u2500 Settings \u2500 CLI Preferences " + "\u2500".repeat(Math.max(2, inner - 30)) + "\u256E"));
|
|
239
|
+
lines.forEach(function (line) {
|
|
240
|
+
framed.push(t.frame("\u2502") + " " + line);
|
|
241
|
+
});
|
|
242
|
+
framed.push(t.frame("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
243
|
+
return framed;
|
|
154
244
|
}
|
|
155
245
|
|
|
156
|
-
function
|
|
157
|
-
|
|
246
|
+
function cycleSetting(settings, key, direction) {
|
|
247
|
+
var field = null;
|
|
248
|
+
for (var i = 0; i < SETTINGS_FIELDS.length; i++) {
|
|
249
|
+
if (SETTINGS_FIELDS[i].key === key) { field = SETTINGS_FIELDS[i]; break; }
|
|
250
|
+
}
|
|
251
|
+
if (!field) return settings;
|
|
252
|
+
var values = field.values || [];
|
|
253
|
+
var currentIndex = -1;
|
|
254
|
+
for (var j = 0; j < values.length; j++) {
|
|
255
|
+
if (values[j] === settings[key]) { currentIndex = j; break; }
|
|
256
|
+
}
|
|
257
|
+
if (currentIndex < 0) currentIndex = 0;
|
|
258
|
+
var nextIndex = (currentIndex + (direction || 1) + values.length) % values.length;
|
|
259
|
+
var result = {};
|
|
260
|
+
for (var k in settings) result[k] = settings[k];
|
|
261
|
+
result[key] = values[nextIndex];
|
|
262
|
+
return result;
|
|
158
263
|
}
|
|
159
264
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
265
|
+
// ── Slash Command Suggestions ─────────────────────────────
|
|
266
|
+
function getSlashSuggestions(input, commands, limit) {
|
|
267
|
+
var query = String(input || "").trim().toLowerCase();
|
|
268
|
+
if (!query.startsWith("/")) return [];
|
|
269
|
+
limit = limit || 8;
|
|
270
|
+
|
|
271
|
+
var scored = [];
|
|
272
|
+
for (var i = 0; i < commands.length; i++) {
|
|
273
|
+
var cmd = commands[i][0].toLowerCase();
|
|
274
|
+
var score = -1;
|
|
275
|
+
if (cmd === query) score = 1000;
|
|
276
|
+
else if (cmd.indexOf(query) === 0) score = 900 - (cmd.length - query.length);
|
|
277
|
+
else if (cmd.indexOf(query.slice(1)) >= 0) score = 600;
|
|
278
|
+
if (score >= 0) scored.push({ cmd: commands[i][0], desc: commands[i][1], score: score });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
scored.sort(function (a, b) { return b.score - a.score; });
|
|
282
|
+
return scored.slice(0, limit);
|
|
169
283
|
}
|
|
170
284
|
|
|
171
|
-
function
|
|
172
|
-
var
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
lines.join("\n") +
|
|
185
|
-
"\n" +
|
|
186
|
-
chalk.dim(" \u2514" + "\u2500".repeat(52))
|
|
187
|
-
);
|
|
188
|
-
});
|
|
189
|
-
out = out.replace(/^### (.+)$/gm, function (_, h) {
|
|
190
|
-
return chalk.bold.underline(h);
|
|
191
|
-
});
|
|
192
|
-
out = out.replace(/^## (.+)$/gm, function (_, h) {
|
|
193
|
-
return chalk.bold.yellow(h);
|
|
194
|
-
});
|
|
195
|
-
out = out.replace(/^# (.+)$/gm, function (_, h) {
|
|
196
|
-
return chalk.bold.magenta.underline(h);
|
|
197
|
-
});
|
|
198
|
-
out = out.replace(/\*\*\*(.+?)\*\*\*/g, function (_, t) {
|
|
199
|
-
return chalk.bold.italic(t);
|
|
200
|
-
});
|
|
201
|
-
out = out.replace(/\*\*(.+?)\*\*/g, function (_, t) {
|
|
202
|
-
return chalk.bold(t);
|
|
203
|
-
});
|
|
204
|
-
out = out.replace(/\*(.+?)\*/g, function (_, t) {
|
|
205
|
-
return chalk.italic(t);
|
|
206
|
-
});
|
|
207
|
-
out = out.replace(/`([^`]+)`/g, function (_, c) {
|
|
208
|
-
return chalk.bgGray.white(" " + c + " ");
|
|
209
|
-
});
|
|
210
|
-
out = out.replace(/^(\s*)[-*] (.+)$/gm, function (_, sp, t) {
|
|
211
|
-
return sp + chalk.green("\u25cf") + " " + t;
|
|
212
|
-
});
|
|
213
|
-
out = out.replace(/^(\s*)\d+\. (.+)$/gm, function (_, sp, t) {
|
|
214
|
-
return sp + chalk.yellow("\u25b8") + " " + t;
|
|
215
|
-
});
|
|
216
|
-
return out;
|
|
285
|
+
function renderSuggestions(suggestions, selectedIndex) {
|
|
286
|
+
var t = getTheme();
|
|
287
|
+
var w = getWidth();
|
|
288
|
+
if (!suggestions || !suggestions.length) return;
|
|
289
|
+
var inner = w - 6;
|
|
290
|
+
|
|
291
|
+
console.log(t.frame(" \u256D" + "\u2500".repeat(inner) + "\u256E"));
|
|
292
|
+
for (var i = 0; i < suggestions.length; i++) {
|
|
293
|
+
var prefix = i === (selectedIndex || 0) ? chalk.cyan("> ") : " ";
|
|
294
|
+
var line = prefix + chalk.green(fitPlain(suggestions[i].cmd, 16)) + " " + chalk.gray(suggestions[i].desc);
|
|
295
|
+
console.log(t.frame(" \u2502") + " " + fitPlain(line, inner - 2) + " " + t.frame("\u2502"));
|
|
296
|
+
}
|
|
297
|
+
console.log(t.frame(" \u2570" + "\u2500".repeat(inner) + "\u256F"));
|
|
217
298
|
}
|
|
218
299
|
|
|
300
|
+
// ── Spinner ───────────────────────────────────────────────
|
|
219
301
|
function renderSpinner(message) {
|
|
220
302
|
if (activeSpinner) activeSpinner.stop();
|
|
221
303
|
activeSpinner = ora({
|
|
@@ -236,125 +318,34 @@ function stopSpinner(success, message) {
|
|
|
236
318
|
activeSpinner = null;
|
|
237
319
|
}
|
|
238
320
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
bottom: "\u2500", "bottom-mid": "\u2534", "bottom-left": "\u2514", "bottom-right": "\u2518",
|
|
247
|
-
left: "\u2502", "left-mid": "\u251c", mid: "\u2500", "mid-mid": "\u253c",
|
|
248
|
-
right: "\u2502", "right-mid": "\u2524", middle: "\u2502",
|
|
249
|
-
},
|
|
250
|
-
style: { "padding-left": 1, "padding-right": 1 },
|
|
251
|
-
});
|
|
252
|
-
rows.forEach(function (r) {
|
|
253
|
-
table.push(r);
|
|
254
|
-
});
|
|
255
|
-
console.log(table.toString());
|
|
256
|
-
console.log();
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function renderBox(title, content, color) {
|
|
260
|
-
var borderColor = color || "green";
|
|
261
|
-
var rendered = renderMarkdown(content);
|
|
262
|
-
console.log(
|
|
263
|
-
boxen(rendered, {
|
|
264
|
-
title: title ? chalk.bold(title) : undefined,
|
|
265
|
-
titleAlignment: "center",
|
|
266
|
-
padding: 1,
|
|
267
|
-
margin: { top: 0, bottom: 1, left: 2, right: 2 },
|
|
268
|
-
borderStyle: "round",
|
|
269
|
-
borderColor: borderColor,
|
|
270
|
-
})
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function renderCodeBlock(code, language) {
|
|
275
|
-
var lang = language ? chalk.dim.italic(" " + language + " ") : "";
|
|
276
|
-
var lineLen = Math.max(1, 50 - (language || "").length);
|
|
277
|
-
console.log(chalk.dim(" \u250c\u2500\u2500") + lang + chalk.dim("\u2500".repeat(lineLen)));
|
|
278
|
-
code.split("\n").forEach(function (line) {
|
|
279
|
-
var highlighted = line;
|
|
280
|
-
highlighted = highlighted.replace(/"[^"]*"/g, function (m) {
|
|
281
|
-
return chalk.yellow(m);
|
|
282
|
-
});
|
|
283
|
-
highlighted = highlighted.replace(/'[^']*'/g, function (m) {
|
|
284
|
-
return chalk.yellow(m);
|
|
285
|
-
});
|
|
286
|
-
var kws = [
|
|
287
|
-
"const", "let", "var", "function", "return", "if", "else", "for",
|
|
288
|
-
"while", "class", "import", "export", "from", "require", "async",
|
|
289
|
-
"await", "try", "catch", "throw", "new",
|
|
290
|
-
];
|
|
291
|
-
var kwRe = new RegExp("\\b(" + kws.join("|") + ")\\b", "g");
|
|
292
|
-
highlighted = highlighted.replace(kwRe, function (m) {
|
|
293
|
-
return chalk.magenta(m);
|
|
294
|
-
});
|
|
295
|
-
highlighted = highlighted.replace(/\b(\d+\.?\d*)\b/g, function (m) {
|
|
296
|
-
return chalk.cyan(m);
|
|
297
|
-
});
|
|
298
|
-
highlighted = highlighted.replace(/(\/\/.*)$/g, function (m) {
|
|
299
|
-
return chalk.dim(m);
|
|
321
|
+
// ── Markdown Rendering ────────────────────────────────────
|
|
322
|
+
function renderMarkdown(text) {
|
|
323
|
+
var out = text;
|
|
324
|
+
out = out.replace(/```(\w*)\n([\s\S]*?)```/g, function (_, lang, code) {
|
|
325
|
+
var header = lang ? chalk.dim.italic(" " + lang + " ") : "";
|
|
326
|
+
var lines = code.split("\n").map(function (l) {
|
|
327
|
+
return chalk.dim(" \u2502 ") + chalk.white(l);
|
|
300
328
|
});
|
|
301
|
-
|
|
329
|
+
return (
|
|
330
|
+
chalk.dim(" \u250c\u2500\u2500") + header +
|
|
331
|
+
chalk.dim("\u2500".repeat(Math.max(1, 48 - (lang || "").length))) +
|
|
332
|
+
"\n" + lines.join("\n") + "\n" +
|
|
333
|
+
chalk.dim(" \u2514" + "\u2500".repeat(52))
|
|
334
|
+
);
|
|
302
335
|
});
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
parts.push(chalk.bgCyan.black(" " + (model || "chat") + " "));
|
|
314
|
-
if (tokens) parts.push(chalk.dim("Tokens: " + tokens.toLocaleString()));
|
|
315
|
-
parts.push(chalk.dim("Total: " + totalTokens.toLocaleString()));
|
|
316
|
-
parts.push(chalk.dim(mins + "m " + (secs < 10 ? "0" : "") + secs + "s"));
|
|
317
|
-
parts.push(chalk.dim(costEst));
|
|
318
|
-
console.log(" " + parts.join(chalk.dim(" \u2502 ")));
|
|
319
|
-
console.log();
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function renderStatusBar(stats) {
|
|
323
|
-
var s = stats || {};
|
|
324
|
-
var model = s.model || "auto";
|
|
325
|
-
var tokens = s.tokens || totalTokens;
|
|
326
|
-
var cost = s.cost || totalCost;
|
|
327
|
-
var elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
328
|
-
var mins = Math.floor(elapsed / 60);
|
|
329
|
-
var secs = elapsed % 60;
|
|
330
|
-
var uptime = mins + "m " + secs + "s";
|
|
331
|
-
var parts = [
|
|
332
|
-
chalk.bgCyan.black(" " + model + " "),
|
|
333
|
-
chalk.bgGreen.black(" Tokens: " + tokens.toLocaleString() + " "),
|
|
334
|
-
chalk.bgYellow.black(" Cost: $" + cost.toFixed(4) + " "),
|
|
335
|
-
chalk.bgMagenta.black(" Uptime: " + uptime + " "),
|
|
336
|
-
];
|
|
337
|
-
var bar = parts.join(chalk.dim(" \u2502 "));
|
|
338
|
-
var width = process.stdout.columns || 80;
|
|
339
|
-
console.log(chalk.dim("\u2500".repeat(width)));
|
|
340
|
-
console.log(bar);
|
|
341
|
-
console.log(chalk.dim("\u2500".repeat(width)));
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function renderProgress(current, total, label) {
|
|
345
|
-
var width = 40;
|
|
346
|
-
var pct = Math.min(1, current / total);
|
|
347
|
-
var filled = Math.round(width * pct);
|
|
348
|
-
var empty = width - filled;
|
|
349
|
-
var bar = chalk.green("\u2588".repeat(filled)) + chalk.dim("\u2591".repeat(empty));
|
|
350
|
-
var pctStr = chalk.bold(Math.round(pct * 100) + "%");
|
|
351
|
-
var lbl = label ? chalk.dim(" " + label) : "";
|
|
352
|
-
process.stdout.write(
|
|
353
|
-
"\r " + bar + " " + pctStr + lbl + " (" + current + "/" + total + ")"
|
|
354
|
-
);
|
|
355
|
-
if (current >= total) console.log();
|
|
336
|
+
out = out.replace(/^### (.+)$/gm, function (_, h) { return chalk.bold.underline(h); });
|
|
337
|
+
out = out.replace(/^## (.+)$/gm, function (_, h) { return chalk.bold.yellow(h); });
|
|
338
|
+
out = out.replace(/^# (.+)$/gm, function (_, h) { return chalk.bold.magenta.underline(h); });
|
|
339
|
+
out = out.replace(/\*\*\*(.+?)\*\*\*/g, function (_, t) { return chalk.bold.italic(t); });
|
|
340
|
+
out = out.replace(/\*\*(.+?)\*\*/g, function (_, t) { return chalk.bold(t); });
|
|
341
|
+
out = out.replace(/\*(.+?)\*/g, function (_, t) { return chalk.italic(t); });
|
|
342
|
+
out = out.replace(/`([^`]+)`/g, function (_, c) { return chalk.bgGray.white(" " + c + " "); });
|
|
343
|
+
out = out.replace(/^(\s*)[-*] (.+)$/gm, function (_, sp, t) { return sp + chalk.green("\u25cf") + " " + t; });
|
|
344
|
+
out = out.replace(/^(\s*)\d+\. (.+)$/gm, function (_, sp, t) { return sp + chalk.yellow("\u25b8") + " " + t; });
|
|
345
|
+
return out;
|
|
356
346
|
}
|
|
357
347
|
|
|
348
|
+
// ── Token Tracking ────────────────────────────────────────
|
|
358
349
|
function addTokens(count, costPerToken) {
|
|
359
350
|
totalTokens += count;
|
|
360
351
|
totalCost += count * (costPerToken || 0.000003);
|
|
@@ -365,28 +356,33 @@ function getStats() {
|
|
|
365
356
|
}
|
|
366
357
|
|
|
367
358
|
module.exports = {
|
|
359
|
+
// Core
|
|
368
360
|
renderBanner: renderBanner,
|
|
369
361
|
renderPrompt: renderPrompt,
|
|
370
|
-
renderInputBox: renderInputBox,
|
|
371
|
-
renderInputBoxClose: renderInputBoxClose,
|
|
372
|
-
setCurrentPerm: setCurrentPerm,
|
|
373
|
-
renderWelcomeInfo: renderWelcomeInfo,
|
|
374
|
-
renderPermissionLine: renderPermissionLine,
|
|
375
|
-
renderMiniStatus: renderMiniStatus,
|
|
376
362
|
renderResponse: renderResponse,
|
|
377
|
-
renderStreamStart: renderStreamStart,
|
|
378
|
-
renderStreamChunk: renderStreamChunk,
|
|
379
|
-
renderStreamEnd: renderStreamEnd,
|
|
380
363
|
renderSpinner: renderSpinner,
|
|
381
364
|
stopSpinner: stopSpinner,
|
|
382
|
-
renderTable: renderTable,
|
|
383
|
-
renderBox: renderBox,
|
|
384
|
-
renderCodeBlock: renderCodeBlock,
|
|
385
|
-
renderStatusBar: renderStatusBar,
|
|
386
|
-
renderProgress: renderProgress,
|
|
387
365
|
renderMarkdown: renderMarkdown,
|
|
366
|
+
// Input Panel
|
|
367
|
+
renderInputPanel: renderInputPanel,
|
|
368
|
+
renderInputPanelClose: renderInputPanelClose,
|
|
369
|
+
// Settings
|
|
370
|
+
buildSettingsPanel: buildSettingsPanel,
|
|
371
|
+
cycleSetting: cycleSetting,
|
|
372
|
+
SETTINGS_FIELDS: SETTINGS_FIELDS,
|
|
373
|
+
// Suggestions
|
|
374
|
+
getSlashSuggestions: getSlashSuggestions,
|
|
375
|
+
renderSuggestions: renderSuggestions,
|
|
376
|
+
// Theme
|
|
377
|
+
setTheme: setTheme,
|
|
378
|
+
getTheme: getTheme,
|
|
379
|
+
// Helpers
|
|
380
|
+
formatSettingValue: formatSettingValue,
|
|
388
381
|
addTokens: addTokens,
|
|
389
382
|
getStats: getStats,
|
|
390
|
-
|
|
383
|
+
estimateCost: estimateCost,
|
|
384
|
+
formatNumber: formatNumber,
|
|
385
|
+
formatDuration: formatDuration,
|
|
391
386
|
PERMISSION_MODES: PERMISSION_MODES,
|
|
387
|
+
THEMES: THEMES,
|
|
392
388
|
};
|