blun-king-cli 7.0.4 → 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 +210 -211
- package/lib/ui.js +284 -288
- 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,27 +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
|
-
|
|
73
|
-
ui.renderPermissionLine(permissionMode);
|
|
74
|
-
console.log();
|
|
72
|
+
ui.setTheme(settings.theme || "neon");
|
|
75
73
|
|
|
76
|
-
var
|
|
77
|
-
|
|
78
|
-
["/model", "Switch AI model"],
|
|
79
|
-
["/settings", "Show/change settings"],
|
|
80
|
-
["/perm", "Cycle permission mode (safe/normal/god)"],
|
|
81
|
-
["/status", "Token usage & uptime"],
|
|
82
|
-
["/whoami", "Current user info"],
|
|
83
|
-
["/keys", "List API keys"],
|
|
84
|
-
["/login", "Login from chat"],
|
|
85
|
-
["/clear", "Clear screen"],
|
|
86
|
-
["/exit", "Exit chat"],
|
|
87
|
-
];
|
|
88
|
-
var slashNames = SLASH_COMMANDS.map(function (c) { return c[0]; });
|
|
74
|
+
var username = (creds ? creds.name : null) || "local";
|
|
75
|
+
var model = settings.model || "auto";
|
|
89
76
|
|
|
90
77
|
var rl = readline.createInterface({
|
|
91
78
|
input: process.stdin,
|
|
@@ -94,26 +81,89 @@ function startChat() {
|
|
|
94
81
|
terminal: true,
|
|
95
82
|
completer: function (line) {
|
|
96
83
|
if (line.startsWith("/")) {
|
|
97
|
-
var hits = slashNames.filter(function (c) { return c.
|
|
84
|
+
var hits = slashNames.filter(function (c) { return c.indexOf(line) === 0; });
|
|
98
85
|
return [hits.length ? hits : slashNames, line];
|
|
99
86
|
}
|
|
100
87
|
return [[], line];
|
|
101
88
|
},
|
|
102
89
|
});
|
|
103
90
|
|
|
104
|
-
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();
|
|
105
154
|
|
|
106
155
|
return new Promise(function (resolveChat) {
|
|
107
156
|
rl.on("line", function (line) {
|
|
108
157
|
var input = line.trim();
|
|
109
|
-
ui.renderInputBoxClose();
|
|
110
158
|
|
|
111
159
|
if (!input) {
|
|
112
|
-
showPrompt(
|
|
160
|
+
showPrompt();
|
|
113
161
|
return;
|
|
114
162
|
}
|
|
115
163
|
|
|
116
|
-
|
|
164
|
+
ui.renderInputPanelClose(settings);
|
|
165
|
+
|
|
166
|
+
// ── Commands ────────────────────────────────
|
|
117
167
|
if (input === "/exit" || input === "/quit") {
|
|
118
168
|
ui.renderResponse("Goodbye!", "system");
|
|
119
169
|
rl.close();
|
|
@@ -123,245 +173,194 @@ function startChat() {
|
|
|
123
173
|
if (input === "/clear") {
|
|
124
174
|
console.clear();
|
|
125
175
|
ui.renderBanner();
|
|
126
|
-
showPrompt(
|
|
176
|
+
showPrompt();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (input === "/settings") {
|
|
181
|
+
openSettingsPanel();
|
|
127
182
|
return;
|
|
128
183
|
}
|
|
129
184
|
|
|
130
185
|
if (input === "/status") {
|
|
131
|
-
ui.
|
|
132
|
-
|
|
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();
|
|
133
199
|
return;
|
|
134
200
|
}
|
|
135
201
|
|
|
136
202
|
if (input === "/perm" || input === "/permission") {
|
|
137
|
-
var
|
|
138
|
-
settings.permissionMode
|
|
203
|
+
var modes = ui.PERMISSION_MODES;
|
|
204
|
+
var idx = modes.indexOf(settings.permissionMode);
|
|
205
|
+
settings.permissionMode = modes[(idx + 1) % modes.length];
|
|
139
206
|
auth.saveSettings(settings);
|
|
140
|
-
ui.
|
|
141
|
-
|
|
142
|
-
showPrompt(rl, username, model);
|
|
207
|
+
ui.renderResponse("Permission: " + settings.permissionMode, "system");
|
|
208
|
+
showPrompt();
|
|
143
209
|
return;
|
|
144
210
|
}
|
|
145
211
|
|
|
146
|
-
if (input
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
.
|
|
159
|
-
|
|
160
|
-
|
|
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();
|
|
161
228
|
return;
|
|
162
229
|
}
|
|
163
230
|
|
|
164
|
-
if (input
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
.
|
|
171
|
-
|
|
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();
|
|
172
259
|
});
|
|
260
|
+
}
|
|
173
261
|
return;
|
|
174
262
|
}
|
|
175
263
|
|
|
176
|
-
if (input === "/
|
|
177
|
-
auth
|
|
178
|
-
|
|
179
|
-
.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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(); });
|
|
185
273
|
return;
|
|
186
274
|
}
|
|
187
275
|
|
|
188
|
-
if (input === "/
|
|
189
|
-
auth
|
|
190
|
-
.
|
|
191
|
-
|
|
192
|
-
{ label: "Gemma 4", value: "gemma4" },
|
|
193
|
-
{ label: "Claude", value: "claude" },
|
|
194
|
-
{ label: "Llama", value: "llama" },
|
|
195
|
-
])
|
|
196
|
-
.then(function (choice) {
|
|
197
|
-
if (choice) {
|
|
198
|
-
model = choice;
|
|
199
|
-
settings.model = choice;
|
|
200
|
-
auth.saveSettings(settings);
|
|
201
|
-
ui.renderResponse("Model set to: " + choice, "system");
|
|
202
|
-
}
|
|
203
|
-
showPrompt(rl, username, model);
|
|
204
|
-
});
|
|
276
|
+
if (input === "/whoami") {
|
|
277
|
+
auth.whoami().catch(function (e) {
|
|
278
|
+
ui.renderResponse(e.message, "error");
|
|
279
|
+
}).then(function () { showPrompt(); });
|
|
205
280
|
return;
|
|
206
281
|
}
|
|
207
282
|
|
|
208
|
-
if (input === "/
|
|
209
|
-
|
|
210
|
-
"
|
|
211
|
-
|
|
212
|
-
"Model: " + (settings.model || "auto"),
|
|
213
|
-
"Language: " + (settings.language || "de"),
|
|
214
|
-
"Voice: " + (settings.voice ? "on" : "off"),
|
|
215
|
-
"Permission: " + (permissionMode || "normal"),
|
|
216
|
-
].join("\n"),
|
|
217
|
-
"cyan"
|
|
218
|
-
);
|
|
219
|
-
auth
|
|
220
|
-
.promptMenu("Change Setting", [
|
|
221
|
-
{ label: "Model", value: "model" },
|
|
222
|
-
{ label: "Language (de/en)", value: "language" },
|
|
223
|
-
{ label: "Voice on/off", value: "voice" },
|
|
224
|
-
{ label: "Back", value: "back" },
|
|
225
|
-
])
|
|
226
|
-
.then(function (action) {
|
|
227
|
-
if (action === "model") {
|
|
228
|
-
return auth
|
|
229
|
-
.promptMenu("Select Model", [
|
|
230
|
-
{ label: "Auto", value: "auto" },
|
|
231
|
-
{ label: "Gemma 4", value: "gemma4" },
|
|
232
|
-
{ label: "Claude", value: "claude" },
|
|
233
|
-
{ label: "Llama", value: "llama" },
|
|
234
|
-
])
|
|
235
|
-
.then(function (m) {
|
|
236
|
-
if (m) {
|
|
237
|
-
model = m;
|
|
238
|
-
settings.model = m;
|
|
239
|
-
auth.saveSettings(settings);
|
|
240
|
-
ui.renderResponse("Model: " + m, "system");
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
} else if (action === "language") {
|
|
244
|
-
return auth.prompt(chalk.green(" Language (de/en): ")).then(function (l) {
|
|
245
|
-
if (l) {
|
|
246
|
-
settings.language = l;
|
|
247
|
-
auth.saveSettings(settings);
|
|
248
|
-
ui.renderResponse("Language: " + l, "system");
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
} else if (action === "voice") {
|
|
252
|
-
settings.voice = !settings.voice;
|
|
253
|
-
auth.saveSettings(settings);
|
|
254
|
-
ui.renderResponse(
|
|
255
|
-
"Voice: " + (settings.voice ? "on" : "off"),
|
|
256
|
-
"system"
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
})
|
|
260
|
-
.then(function () {
|
|
261
|
-
showPrompt(rl, username, model);
|
|
262
|
-
});
|
|
283
|
+
if (input === "/keys") {
|
|
284
|
+
auth.listApiKeys().catch(function (e) {
|
|
285
|
+
ui.renderResponse(e.message, "error");
|
|
286
|
+
}).then(function () { showPrompt(); });
|
|
263
287
|
return;
|
|
264
288
|
}
|
|
265
289
|
|
|
266
290
|
if (input === "/help") {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
["
|
|
273
|
-
["/whoami", "Show current user info"],
|
|
274
|
-
["/keys", "List API keys"],
|
|
275
|
-
["/model", "Switch AI model"],
|
|
276
|
-
["/settings", "Show/change settings"],
|
|
277
|
-
["/perm", "Cycle permission mode (safe/normal/god)"],
|
|
278
|
-
["/help", "Show this help"],
|
|
279
|
-
];
|
|
280
|
-
helpLines.forEach(function (h) {
|
|
281
|
-
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]));
|
|
282
297
|
});
|
|
283
298
|
console.log();
|
|
284
|
-
|
|
299
|
+
console.log(t.muted(" Tip: Type / and press Tab for autocomplete"));
|
|
300
|
+
console.log();
|
|
301
|
+
showPrompt();
|
|
285
302
|
return;
|
|
286
303
|
}
|
|
287
304
|
|
|
288
|
-
// Slash command picker
|
|
305
|
+
// ── Slash command picker (just "/") ──────────
|
|
289
306
|
if (input === "/") {
|
|
290
307
|
var inquirer = require("inquirer");
|
|
291
308
|
inquirer.prompt([{
|
|
292
309
|
type: "list",
|
|
293
310
|
name: "cmd",
|
|
294
311
|
message: chalk.cyan("Select command"),
|
|
295
|
-
choices:
|
|
296
|
-
{ name: chalk.green(
|
|
297
|
-
|
|
298
|
-
{ name: chalk.green("/perm") + chalk.dim(" Cycle permission mode"), value: "/perm" },
|
|
299
|
-
{ name: chalk.green("/status") + chalk.dim(" Token usage & uptime"), value: "/status" },
|
|
300
|
-
{ name: chalk.green("/whoami") + chalk.dim(" Current user info"), value: "/whoami" },
|
|
301
|
-
{ name: chalk.green("/keys") + chalk.dim(" List API keys"), value: "/keys" },
|
|
302
|
-
{ name: chalk.green("/login") + chalk.dim(" Login from chat"), value: "/login" },
|
|
303
|
-
{ name: chalk.green("/clear") + chalk.dim(" Clear screen"), value: "/clear" },
|
|
304
|
-
{ 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([
|
|
305
315
|
new inquirer.Separator(),
|
|
306
|
-
{ name: chalk.red("/exit") + chalk.dim(" Exit chat"), value: "/exit" },
|
|
307
316
|
{ name: chalk.dim("Cancel"), value: "" },
|
|
308
|
-
],
|
|
317
|
+
]),
|
|
309
318
|
pageSize: 12,
|
|
310
319
|
}]).then(function (ans) {
|
|
311
320
|
if (ans.cmd) {
|
|
312
321
|
rl.emit("line", ans.cmd);
|
|
313
322
|
} else {
|
|
314
|
-
showPrompt(
|
|
323
|
+
showPrompt();
|
|
315
324
|
}
|
|
316
325
|
});
|
|
317
326
|
return;
|
|
318
327
|
}
|
|
319
328
|
|
|
320
|
-
// Send message
|
|
329
|
+
// ── Send message ────────────────────────────
|
|
321
330
|
if (localMode) {
|
|
322
|
-
ui.renderResponse(
|
|
323
|
-
|
|
324
|
-
"error"
|
|
325
|
-
);
|
|
326
|
-
showPrompt(rl, username, model);
|
|
331
|
+
ui.renderResponse("Local mode - cannot send messages. Use /login to authenticate.", "error");
|
|
332
|
+
showPrompt();
|
|
327
333
|
return;
|
|
328
334
|
}
|
|
329
335
|
|
|
330
|
-
// Send message
|
|
331
336
|
var spinner = ui.renderSpinner("Thinking...");
|
|
332
|
-
client
|
|
333
|
-
.sendMessage(input, { model: settings.model || "auto" })
|
|
337
|
+
client.sendMessage(input, { model: settings.model || "auto" })
|
|
334
338
|
.then(function (res) {
|
|
335
339
|
ui.stopSpinner(true, "Response received");
|
|
336
340
|
if (res.status === 200 && res.data) {
|
|
337
|
-
var text =
|
|
338
|
-
res.data.answer ||
|
|
339
|
-
res.data.response ||
|
|
340
|
-
res.data.text ||
|
|
341
|
-
res.data.message ||
|
|
342
|
-
JSON.stringify(res.data);
|
|
343
|
-
ui.renderResponse(text, "agent");
|
|
341
|
+
var text = res.data.answer || res.data.response || res.data.text || res.data.message || JSON.stringify(res.data);
|
|
344
342
|
var tok = res.data.tokens || res.data.usage || {};
|
|
345
343
|
var tokTotal = tok.total || tok.total_tokens || 0;
|
|
346
344
|
if (!tokTotal && text) {
|
|
347
345
|
tokTotal = Math.ceil(text.length / 4) + Math.ceil(input.length / 4);
|
|
348
346
|
}
|
|
349
347
|
if (tokTotal) ui.addTokens(tokTotal);
|
|
350
|
-
ui.
|
|
348
|
+
ui.renderResponse(text, "agent", {
|
|
349
|
+
model: settings.model || "auto",
|
|
350
|
+
tokens: tokTotal,
|
|
351
|
+
cost: settings.costVisibility,
|
|
352
|
+
});
|
|
351
353
|
} else if (res.status === 401 || res.status === 403) {
|
|
352
354
|
ui.renderResponse("Auth error. Use /login to re-authenticate.", "error");
|
|
353
355
|
} else {
|
|
354
356
|
ui.renderResponse("Error: " + JSON.stringify(res.data), "error");
|
|
355
357
|
}
|
|
356
|
-
showPrompt(
|
|
358
|
+
showPrompt();
|
|
357
359
|
})
|
|
358
360
|
.catch(function (e) {
|
|
359
361
|
ui.stopSpinner(false, e.message);
|
|
360
|
-
ui.renderResponse(
|
|
361
|
-
|
|
362
|
-
"error"
|
|
363
|
-
);
|
|
364
|
-
showPrompt(rl, username, model);
|
|
362
|
+
ui.renderResponse("Request failed: " + e.message + "\n Try /login if auth expired.", "error");
|
|
363
|
+
showPrompt();
|
|
365
364
|
});
|
|
366
365
|
});
|
|
367
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 text with green prefix, no box
|
|
118
184
|
var lines = rendered.split("\n");
|
|
119
185
|
lines.forEach(function (line) {
|
|
120
|
-
console.log(" " +
|
|
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
|
};
|