blun-king-cli 7.0.5 → 7.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chat.js +206 -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,86 @@ 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
|
+
rl.setPrompt(chalk.bold.green("> "));
|
|
93
|
+
rl.prompt();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Settings via inquirer (Windows-compatible) ──────
|
|
97
|
+
function openSettingsPanel() {
|
|
98
|
+
var inquirer = require("inquirer");
|
|
99
|
+
var t = ui.getTheme();
|
|
100
|
+
|
|
101
|
+
// Show current settings
|
|
102
|
+
console.log();
|
|
103
|
+
console.log(t.header(" Current Settings"));
|
|
104
|
+
console.log(t.muted(" " + "\u2500".repeat(40)));
|
|
105
|
+
ui.SETTINGS_FIELDS.forEach(function (f) {
|
|
106
|
+
console.log(" " + chalk.white(f.label + ":") + " ".repeat(Math.max(1, 14 - f.label.length)) + ui.formatSettingValue(f, settings[f.key]));
|
|
107
|
+
});
|
|
108
|
+
console.log();
|
|
109
|
+
|
|
110
|
+
inquirer.prompt([{
|
|
111
|
+
type: "list",
|
|
112
|
+
name: "field",
|
|
113
|
+
message: "Change setting",
|
|
114
|
+
choices: ui.SETTINGS_FIELDS.map(function (f) {
|
|
115
|
+
return { name: f.label + " — " + ui.formatSettingValue(f, settings[f.key]), value: f.key };
|
|
116
|
+
}).concat([{ name: chalk.dim("Back"), value: "back" }]),
|
|
117
|
+
pageSize: 12,
|
|
118
|
+
}]).then(function (ans) {
|
|
119
|
+
if (ans.field === "back") {
|
|
120
|
+
showPrompt();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
var field = null;
|
|
124
|
+
for (var i = 0; i < ui.SETTINGS_FIELDS.length; i++) {
|
|
125
|
+
if (ui.SETTINGS_FIELDS[i].key === ans.field) { field = ui.SETTINGS_FIELDS[i]; break; }
|
|
126
|
+
}
|
|
127
|
+
if (!field) { showPrompt(); return; }
|
|
128
|
+
|
|
129
|
+
return inquirer.prompt([{
|
|
130
|
+
type: "list",
|
|
131
|
+
name: "value",
|
|
132
|
+
message: field.label + " (" + (field.help || "") + ")",
|
|
133
|
+
choices: field.values.map(function (v) {
|
|
134
|
+
var isCurrent = settings[field.key] === v;
|
|
135
|
+
var label = String(v);
|
|
136
|
+
if (typeof v === "boolean") label = v ? "On" : "Off";
|
|
137
|
+
return { name: label + (isCurrent ? chalk.green(" (current)") : ""), value: v };
|
|
138
|
+
}),
|
|
139
|
+
}]).then(function (ans2) {
|
|
140
|
+
settings[field.key] = ans2.value;
|
|
141
|
+
if (field.key === "theme") ui.setTheme(settings.theme);
|
|
142
|
+
auth.saveSettings(settings);
|
|
143
|
+
ui.renderResponse(field.label + ": " + ans2.value, "system");
|
|
144
|
+
showPrompt();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log();
|
|
150
|
+
showPrompt();
|
|
103
151
|
|
|
104
152
|
return new Promise(function (resolveChat) {
|
|
105
153
|
rl.on("line", function (line) {
|
|
106
154
|
var input = line.trim();
|
|
107
155
|
|
|
108
156
|
if (!input) {
|
|
109
|
-
showPrompt(
|
|
157
|
+
showPrompt();
|
|
110
158
|
return;
|
|
111
159
|
}
|
|
112
160
|
|
|
113
|
-
|
|
161
|
+
console.log();
|
|
114
162
|
|
|
115
|
-
// Commands
|
|
163
|
+
// ── Commands ────────────────────────────────
|
|
116
164
|
if (input === "/exit" || input === "/quit") {
|
|
117
165
|
ui.renderResponse("Goodbye!", "system");
|
|
118
166
|
rl.close();
|
|
@@ -122,245 +170,194 @@ function startChat() {
|
|
|
122
170
|
if (input === "/clear") {
|
|
123
171
|
console.clear();
|
|
124
172
|
ui.renderBanner();
|
|
125
|
-
showPrompt(
|
|
173
|
+
showPrompt();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (input === "/settings") {
|
|
178
|
+
openSettingsPanel();
|
|
126
179
|
return;
|
|
127
180
|
}
|
|
128
181
|
|
|
129
182
|
if (input === "/status") {
|
|
130
|
-
ui.
|
|
131
|
-
|
|
183
|
+
var stats = ui.getStats();
|
|
184
|
+
var t = ui.getTheme();
|
|
185
|
+
console.log();
|
|
186
|
+
console.log(t.header(" Status"));
|
|
187
|
+
console.log(t.muted(" " + "\u2500".repeat(40)));
|
|
188
|
+
console.log(" Model: " + chalk.white(settings.model || "auto"));
|
|
189
|
+
console.log(" Permission: " + ui.formatSettingValue({ key: "permissionMode" }, settings.permissionMode));
|
|
190
|
+
console.log(" Theme: " + chalk.white(settings.theme || "neon"));
|
|
191
|
+
console.log(" Tokens: " + chalk.white(ui.formatNumber(stats.tokens)));
|
|
192
|
+
console.log(" Cost: " + chalk.white(ui.estimateCost(stats.tokens, settings.model)));
|
|
193
|
+
console.log(" Uptime: " + chalk.white(ui.formatDuration(stats.uptime)));
|
|
194
|
+
console.log();
|
|
195
|
+
showPrompt();
|
|
132
196
|
return;
|
|
133
197
|
}
|
|
134
198
|
|
|
135
199
|
if (input === "/perm" || input === "/permission") {
|
|
136
|
-
var
|
|
137
|
-
settings.permissionMode
|
|
200
|
+
var modes = ui.PERMISSION_MODES;
|
|
201
|
+
var idx = modes.indexOf(settings.permissionMode);
|
|
202
|
+
settings.permissionMode = modes[(idx + 1) % modes.length];
|
|
138
203
|
auth.saveSettings(settings);
|
|
139
|
-
ui.
|
|
140
|
-
|
|
141
|
-
showPrompt(rl, username, model);
|
|
204
|
+
ui.renderResponse("Permission: " + settings.permissionMode, "system");
|
|
205
|
+
showPrompt();
|
|
142
206
|
return;
|
|
143
207
|
}
|
|
144
208
|
|
|
145
|
-
if (input
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.
|
|
158
|
-
|
|
159
|
-
|
|
209
|
+
if (input.indexOf("/theme") === 0) {
|
|
210
|
+
var parts = input.split(/\s+/);
|
|
211
|
+
if (parts[1] && ui.THEMES[parts[1]]) {
|
|
212
|
+
settings.theme = parts[1];
|
|
213
|
+
ui.setTheme(parts[1]);
|
|
214
|
+
auth.saveSettings(settings);
|
|
215
|
+
ui.renderResponse("Theme: " + parts[1], "system");
|
|
216
|
+
} else {
|
|
217
|
+
var modes = Object.keys(ui.THEMES);
|
|
218
|
+
var idx = modes.indexOf(settings.theme || "neon");
|
|
219
|
+
settings.theme = modes[(idx + 1) % modes.length];
|
|
220
|
+
ui.setTheme(settings.theme);
|
|
221
|
+
auth.saveSettings(settings);
|
|
222
|
+
ui.renderResponse("Theme: " + settings.theme, "system");
|
|
223
|
+
}
|
|
224
|
+
showPrompt();
|
|
160
225
|
return;
|
|
161
226
|
}
|
|
162
227
|
|
|
163
|
-
if (input
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
.
|
|
170
|
-
|
|
228
|
+
if (input.indexOf("/model") === 0) {
|
|
229
|
+
var parts = input.split(/\s+/);
|
|
230
|
+
if (parts[1]) {
|
|
231
|
+
settings.model = parts[1];
|
|
232
|
+
model = parts[1];
|
|
233
|
+
auth.saveSettings(settings);
|
|
234
|
+
ui.renderResponse("Model: " + parts[1], "system");
|
|
235
|
+
showPrompt();
|
|
236
|
+
} else {
|
|
237
|
+
var inquirer = require("inquirer");
|
|
238
|
+
inquirer.prompt([{
|
|
239
|
+
type: "list",
|
|
240
|
+
name: "model",
|
|
241
|
+
message: chalk.cyan("Select Model"),
|
|
242
|
+
choices: [
|
|
243
|
+
{ name: "Auto (recommended)", value: "auto" },
|
|
244
|
+
{ name: "Gemma 4", value: "gemma4" },
|
|
245
|
+
{ name: "Claude", value: "claude" },
|
|
246
|
+
{ name: "Llama", value: "llama" },
|
|
247
|
+
],
|
|
248
|
+
}]).then(function (ans) {
|
|
249
|
+
if (ans.model) {
|
|
250
|
+
model = ans.model;
|
|
251
|
+
settings.model = ans.model;
|
|
252
|
+
auth.saveSettings(settings);
|
|
253
|
+
ui.renderResponse("Model: " + ans.model, "system");
|
|
254
|
+
}
|
|
255
|
+
showPrompt();
|
|
171
256
|
});
|
|
257
|
+
}
|
|
172
258
|
return;
|
|
173
259
|
}
|
|
174
260
|
|
|
175
|
-
if (input === "/
|
|
176
|
-
auth
|
|
177
|
-
|
|
178
|
-
.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
261
|
+
if (input === "/login") {
|
|
262
|
+
auth.login().then(function () {
|
|
263
|
+
localMode = false;
|
|
264
|
+
creds = auth.getStoredCredentials();
|
|
265
|
+
username = (creds ? creds.name : null) || "local";
|
|
266
|
+
ui.renderResponse("Logged in!", "system");
|
|
267
|
+
}).catch(function (e) {
|
|
268
|
+
ui.renderResponse(e.message, "error");
|
|
269
|
+
}).then(function () { showPrompt(); });
|
|
184
270
|
return;
|
|
185
271
|
}
|
|
186
272
|
|
|
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
|
-
});
|
|
273
|
+
if (input === "/whoami") {
|
|
274
|
+
auth.whoami().catch(function (e) {
|
|
275
|
+
ui.renderResponse(e.message, "error");
|
|
276
|
+
}).then(function () { showPrompt(); });
|
|
204
277
|
return;
|
|
205
278
|
}
|
|
206
279
|
|
|
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
|
-
});
|
|
280
|
+
if (input === "/keys") {
|
|
281
|
+
auth.listApiKeys().catch(function (e) {
|
|
282
|
+
ui.renderResponse(e.message, "error");
|
|
283
|
+
}).then(function () { showPrompt(); });
|
|
262
284
|
return;
|
|
263
285
|
}
|
|
264
286
|
|
|
265
287
|
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]));
|
|
288
|
+
var t = ui.getTheme();
|
|
289
|
+
console.log();
|
|
290
|
+
console.log(t.header(" Commands"));
|
|
291
|
+
console.log(t.muted(" " + "\u2500".repeat(40)));
|
|
292
|
+
SLASH_COMMANDS.forEach(function (c) {
|
|
293
|
+
console.log(" " + chalk.green(c[0]) + " ".repeat(Math.max(1, 16 - c[0].length)) + chalk.gray(c[1]));
|
|
281
294
|
});
|
|
282
295
|
console.log();
|
|
283
|
-
|
|
296
|
+
console.log(t.muted(" Tip: Type / and press Tab for autocomplete"));
|
|
297
|
+
console.log();
|
|
298
|
+
showPrompt();
|
|
284
299
|
return;
|
|
285
300
|
}
|
|
286
301
|
|
|
287
|
-
// Slash command picker
|
|
302
|
+
// ── Slash command picker (just "/") ──────────
|
|
288
303
|
if (input === "/") {
|
|
289
304
|
var inquirer = require("inquirer");
|
|
290
305
|
inquirer.prompt([{
|
|
291
306
|
type: "list",
|
|
292
307
|
name: "cmd",
|
|
293
308
|
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" },
|
|
309
|
+
choices: SLASH_COMMANDS.map(function (c) {
|
|
310
|
+
return { name: chalk.green(c[0]) + chalk.gray(" " + c[1]), value: c[0] };
|
|
311
|
+
}).concat([
|
|
304
312
|
new inquirer.Separator(),
|
|
305
|
-
{ name: chalk.red("/exit") + chalk.dim(" Exit chat"), value: "/exit" },
|
|
306
313
|
{ name: chalk.dim("Cancel"), value: "" },
|
|
307
|
-
],
|
|
314
|
+
]),
|
|
308
315
|
pageSize: 12,
|
|
309
316
|
}]).then(function (ans) {
|
|
310
317
|
if (ans.cmd) {
|
|
311
318
|
rl.emit("line", ans.cmd);
|
|
312
319
|
} else {
|
|
313
|
-
showPrompt(
|
|
320
|
+
showPrompt();
|
|
314
321
|
}
|
|
315
322
|
});
|
|
316
323
|
return;
|
|
317
324
|
}
|
|
318
325
|
|
|
319
|
-
// Send message
|
|
326
|
+
// ── Send message ────────────────────────────
|
|
320
327
|
if (localMode) {
|
|
321
|
-
ui.renderResponse(
|
|
322
|
-
|
|
323
|
-
"error"
|
|
324
|
-
);
|
|
325
|
-
showPrompt(rl, username, model);
|
|
328
|
+
ui.renderResponse("Local mode - cannot send messages. Use /login to authenticate.", "error");
|
|
329
|
+
showPrompt();
|
|
326
330
|
return;
|
|
327
331
|
}
|
|
328
332
|
|
|
329
|
-
// Send message
|
|
330
333
|
var spinner = ui.renderSpinner("Thinking...");
|
|
331
|
-
client
|
|
332
|
-
.sendMessage(input, { model: settings.model || "auto" })
|
|
334
|
+
client.sendMessage(input, { model: settings.model || "auto" })
|
|
333
335
|
.then(function (res) {
|
|
334
336
|
ui.stopSpinner(true, "Response received");
|
|
335
337
|
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");
|
|
338
|
+
var text = res.data.answer || res.data.response || res.data.text || res.data.message || JSON.stringify(res.data);
|
|
343
339
|
var tok = res.data.tokens || res.data.usage || {};
|
|
344
340
|
var tokTotal = tok.total || tok.total_tokens || 0;
|
|
345
341
|
if (!tokTotal && text) {
|
|
346
342
|
tokTotal = Math.ceil(text.length / 4) + Math.ceil(input.length / 4);
|
|
347
343
|
}
|
|
348
344
|
if (tokTotal) ui.addTokens(tokTotal);
|
|
349
|
-
ui.
|
|
345
|
+
ui.renderResponse(text, "agent", {
|
|
346
|
+
model: settings.model || "auto",
|
|
347
|
+
tokens: tokTotal,
|
|
348
|
+
cost: settings.costVisibility,
|
|
349
|
+
});
|
|
350
350
|
} else if (res.status === 401 || res.status === 403) {
|
|
351
351
|
ui.renderResponse("Auth error. Use /login to re-authenticate.", "error");
|
|
352
352
|
} else {
|
|
353
353
|
ui.renderResponse("Error: " + JSON.stringify(res.data), "error");
|
|
354
354
|
}
|
|
355
|
-
showPrompt(
|
|
355
|
+
showPrompt();
|
|
356
356
|
})
|
|
357
357
|
.catch(function (e) {
|
|
358
358
|
ui.stopSpinner(false, e.message);
|
|
359
|
-
ui.renderResponse(
|
|
360
|
-
|
|
361
|
-
"error"
|
|
362
|
-
);
|
|
363
|
-
showPrompt(rl, username, model);
|
|
359
|
+
ui.renderResponse("Request failed: " + e.message + "\n Try /login if auth expired.", "error");
|
|
360
|
+
showPrompt();
|
|
364
361
|
});
|
|
365
362
|
});
|
|
366
363
|
|
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
|
};
|