blun-king-cli 1.6.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/blun.js +263 -59
- package/package.json +1 -1
package/bin/blun.js
CHANGED
|
@@ -17,6 +17,7 @@ const MEMORY_DIR = path.join(CONFIG_DIR, "memory");
|
|
|
17
17
|
const SKILLS_DIR = path.join(CONFIG_DIR, "skills");
|
|
18
18
|
const HISTORY_FILE = path.join(CONFIG_DIR, "history.json");
|
|
19
19
|
const LOG_FILE = path.join(CONFIG_DIR, "cli.log");
|
|
20
|
+
const PKG_VERSION = (() => { try { return require(path.join(__dirname, "..", "package.json")).version; } catch(e) { return "1.7.0"; } })();
|
|
20
21
|
|
|
21
22
|
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
22
23
|
if (!fs.existsSync(MEMORY_DIR)) fs.mkdirSync(MEMORY_DIR, { recursive: true });
|
|
@@ -108,7 +109,7 @@ function printHeader() {
|
|
|
108
109
|
var line = BOX.h.repeat(w - 4);
|
|
109
110
|
console.log("");
|
|
110
111
|
console.log(C.brightBlue + " " + BOX.tl + line + BOX.tr + C.reset);
|
|
111
|
-
console.log(C.brightBlue + " " + BOX.v + C.reset + C.bold + C.brightWhite + " " + BOX.bot + " BLUN KING CLI" + C.reset + C.dim + "
|
|
112
|
+
console.log(C.brightBlue + " " + BOX.v + C.reset + C.bold + C.brightWhite + " " + BOX.bot + " BLUN KING CLI" + C.reset + C.dim + " v" + PKG_VERSION + C.reset + " ".repeat(Math.max(0, w - 24)) + C.brightBlue + BOX.v + C.reset);
|
|
112
113
|
console.log(C.brightBlue + " " + BOX.v + C.reset + C.gray + " Premium KI — Local First — Autonom" + C.reset + " ".repeat(Math.max(0, w - 41)) + C.brightBlue + BOX.v + C.reset);
|
|
113
114
|
console.log(C.brightBlue + " " + BOX.v + line + BOX.v + C.reset);
|
|
114
115
|
console.log(C.brightBlue + " " + BOX.v + C.reset + C.gray + " API " + C.brightCyan + config.api.base_url + C.reset + " ".repeat(Math.max(0, w - 14 - config.api.base_url.length)) + C.brightBlue + BOX.v + C.reset);
|
|
@@ -2066,14 +2067,54 @@ async function main() {
|
|
|
2066
2067
|
}
|
|
2067
2068
|
|
|
2068
2069
|
// Interactive mode
|
|
2069
|
-
// Workdir selection (like Claude Code)
|
|
2070
|
-
|
|
2070
|
+
// Workdir selection (like Claude Code — trust prompt)
|
|
2071
|
+
config.workdir = process.cwd();
|
|
2072
|
+
|
|
2073
|
+
if (!process.argv.includes("--no-workdir-prompt") && !process.argv.includes("--trust")) {
|
|
2071
2074
|
var cwdHasBlun = fs.existsSync(path.join(process.cwd(), ".blun"));
|
|
2072
2075
|
var cwdHasGit = fs.existsSync(path.join(process.cwd(), ".git"));
|
|
2073
|
-
|
|
2074
|
-
|
|
2076
|
+
var trustedDirs = [];
|
|
2077
|
+
var trustFile = path.join(CONFIG_DIR, "trusted.json");
|
|
2078
|
+
if (fs.existsSync(trustFile)) {
|
|
2079
|
+
try { trustedDirs = JSON.parse(fs.readFileSync(trustFile, "utf8")); } catch(e) {}
|
|
2080
|
+
}
|
|
2081
|
+
var isTrusted = trustedDirs.includes(process.cwd());
|
|
2082
|
+
|
|
2083
|
+
if (!isTrusted && (cwdHasBlun || cwdHasGit)) {
|
|
2084
|
+
console.log("");
|
|
2085
|
+
console.log(C.yellow + " " + BOX.bot + " Working in: " + C.brightWhite + process.cwd() + C.reset);
|
|
2086
|
+
console.log(C.gray + " Do you trust this folder? BLUN King will read/write files here." + C.reset);
|
|
2087
|
+
console.log(C.gray + " [y] Yes, trust [n] Choose another folder [a] Always trust" + C.reset);
|
|
2088
|
+
|
|
2089
|
+
var trustAnswer = await new Promise(function(resolve) {
|
|
2090
|
+
process.stdin.setRawMode(true);
|
|
2091
|
+
process.stdin.resume();
|
|
2092
|
+
process.stdin.setEncoding("utf8");
|
|
2093
|
+
process.stdin.once("data", function(key) {
|
|
2094
|
+
process.stdin.setRawMode(false);
|
|
2095
|
+
resolve(key.toLowerCase());
|
|
2096
|
+
});
|
|
2097
|
+
});
|
|
2098
|
+
|
|
2099
|
+
if (trustAnswer === "a") {
|
|
2100
|
+
trustedDirs.push(process.cwd());
|
|
2101
|
+
fs.writeFileSync(trustFile, JSON.stringify(trustedDirs, null, 2));
|
|
2102
|
+
printSuccess("Folder trusted permanently.");
|
|
2103
|
+
} else if (trustAnswer === "n") {
|
|
2104
|
+
var rlDir = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2105
|
+
var newDir = await new Promise(function(resolve) {
|
|
2106
|
+
rlDir.question(C.brightBlue + " Enter path: " + C.reset, resolve);
|
|
2107
|
+
});
|
|
2108
|
+
rlDir.close();
|
|
2109
|
+
if (newDir.trim() && fs.existsSync(newDir.trim())) {
|
|
2110
|
+
config.workdir = newDir.trim();
|
|
2111
|
+
} else {
|
|
2112
|
+
printError("Invalid path, using current directory.");
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
// y or anything else = trust for this session
|
|
2116
|
+
console.log("");
|
|
2075
2117
|
}
|
|
2076
|
-
// Show workdir in header
|
|
2077
2118
|
}
|
|
2078
2119
|
|
|
2079
2120
|
printHeader();
|
|
@@ -2103,76 +2144,239 @@ async function main() {
|
|
|
2103
2144
|
"/compact", "/review", "/cost", "/exit"
|
|
2104
2145
|
];
|
|
2105
2146
|
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2147
|
+
// ── Bordered Input Box + Live Slash Menu ──
|
|
2148
|
+
var inputBuffer = "";
|
|
2149
|
+
var inputHistory = [];
|
|
2150
|
+
var historyIdx = -1;
|
|
2151
|
+
var menuVisible = false;
|
|
2152
|
+
var menuItems = [];
|
|
2153
|
+
var menuSelected = 0;
|
|
2154
|
+
var cursorPos = 0;
|
|
2155
|
+
var processing = false;
|
|
2156
|
+
|
|
2157
|
+
function getTermWidth() { return process.stdout.columns || 80; }
|
|
2158
|
+
|
|
2159
|
+
function renderInputBox() {
|
|
2160
|
+
var w = Math.min(getTermWidth() - 4, 76);
|
|
2161
|
+
var displayText = inputBuffer;
|
|
2162
|
+
if (displayText.length > w - 4) displayText = displayText.slice(-(w - 4));
|
|
2163
|
+
var pad = Math.max(0, w - 2 - displayText.length);
|
|
2164
|
+
process.stdout.write("\r\x1b[K"); // clear line
|
|
2165
|
+
process.stdout.write(C.brightBlue + " " + BOX.tl + BOX.h.repeat(w - 2) + BOX.tr + C.reset + "\n");
|
|
2166
|
+
process.stdout.write(C.brightBlue + " " + BOX.v + " " + C.reset + C.brightWhite + displayText + C.reset + " ".repeat(pad) + C.brightBlue + BOX.v + C.reset + "\n");
|
|
2167
|
+
process.stdout.write(C.brightBlue + " " + BOX.bl + BOX.h.repeat(w - 2) + BOX.br + C.reset);
|
|
2168
|
+
// Move cursor back to input position
|
|
2169
|
+
process.stdout.write("\x1b[1A"); // up 1
|
|
2170
|
+
process.stdout.write("\r\x1b[" + (4 + displayText.length) + "C"); // position cursor
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
function clearInputBox() {
|
|
2174
|
+
process.stdout.write("\r\x1b[K"); // clear current line
|
|
2175
|
+
process.stdout.write("\x1b[1B\r\x1b[K"); // clear line below
|
|
2176
|
+
process.stdout.write("\x1b[1A\r"); // back up
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
function renderMenu() {
|
|
2180
|
+
if (!menuVisible || menuItems.length === 0) return;
|
|
2181
|
+
// Save cursor, move below input box, render menu
|
|
2182
|
+
var show = menuItems.slice(0, 8);
|
|
2183
|
+
process.stdout.write("\x1b[s"); // save cursor
|
|
2184
|
+
process.stdout.write("\n\n"); // below input box
|
|
2185
|
+
show.forEach(function(item, i) {
|
|
2186
|
+
var prefix = i === menuSelected ? C.brightBlue + C.bold + " " + BOX.arrow + " " : " ";
|
|
2187
|
+
var cmdStr = (item.cmd + " ".repeat(20)).slice(0, 18);
|
|
2188
|
+
var descStr = item.desc.slice(0, getTermWidth() - 28);
|
|
2189
|
+
process.stdout.write(prefix + C.brightCyan + cmdStr + C.gray + descStr + C.reset + "\x1b[K\n");
|
|
2190
|
+
});
|
|
2191
|
+
if (menuItems.length > 8) {
|
|
2192
|
+
process.stdout.write(C.dim + " ... " + (menuItems.length - 8) + " more" + C.reset + "\x1b[K\n");
|
|
2193
|
+
}
|
|
2194
|
+
process.stdout.write("\x1b[u"); // restore cursor
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
function clearMenu() {
|
|
2198
|
+
if (!menuVisible) return;
|
|
2199
|
+
var lines = Math.min(menuItems.length, 8) + (menuItems.length > 8 ? 1 : 0) + 2;
|
|
2200
|
+
process.stdout.write("\x1b[s"); // save
|
|
2201
|
+
for (var i = 0; i < lines; i++) {
|
|
2202
|
+
process.stdout.write("\n\x1b[K");
|
|
2203
|
+
}
|
|
2204
|
+
process.stdout.write("\x1b[u"); // restore
|
|
2205
|
+
menuVisible = false;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
function updateMenu() {
|
|
2209
|
+
if (inputBuffer.startsWith("/")) {
|
|
2210
|
+
var filter = inputBuffer.toLowerCase();
|
|
2211
|
+
menuItems = COMMAND_DESCRIPTIONS.filter(function(c) {
|
|
2212
|
+
return c.cmd.includes(filter) || c.desc.toLowerCase().includes(filter.slice(1));
|
|
2213
|
+
});
|
|
2214
|
+
if (menuItems.length > 0) {
|
|
2215
|
+
menuVisible = true;
|
|
2216
|
+
menuSelected = Math.min(menuSelected, menuItems.length - 1);
|
|
2217
|
+
renderMenu();
|
|
2218
|
+
} else {
|
|
2219
|
+
clearMenu();
|
|
2114
2220
|
}
|
|
2115
|
-
|
|
2221
|
+
} else {
|
|
2222
|
+
if (menuVisible) clearMenu();
|
|
2116
2223
|
}
|
|
2117
|
-
}
|
|
2224
|
+
}
|
|
2118
2225
|
|
|
2119
|
-
|
|
2226
|
+
function drawPrompt() {
|
|
2227
|
+
if (processing) return;
|
|
2228
|
+
console.log(""); // spacing
|
|
2229
|
+
renderInputBox();
|
|
2230
|
+
updateMenu();
|
|
2231
|
+
}
|
|
2120
2232
|
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2233
|
+
async function processInput(input) {
|
|
2234
|
+
processing = true;
|
|
2235
|
+
clearMenu();
|
|
2236
|
+
clearInputBox();
|
|
2237
|
+
// Move below the box area
|
|
2238
|
+
process.stdout.write("\x1b[2B\r\x1b[K\x1b[1A\r\x1b[K\x1b[1A\r\x1b[K");
|
|
2124
2239
|
|
|
2125
|
-
// ! prefix = direct bash
|
|
2126
2240
|
if (input.startsWith("!")) {
|
|
2127
2241
|
cmdShell(input.slice(1).trim());
|
|
2128
|
-
}
|
|
2129
|
-
// # prefix = memory shortcut
|
|
2130
|
-
else if (input.startsWith("#save ")) {
|
|
2242
|
+
} else if (input.startsWith("#save ")) {
|
|
2131
2243
|
var memParts = input.slice(6).split(/\s+/);
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
fs.writeFileSync(path.join(MEMORY_DIR, memKey + ".txt"), memVal);
|
|
2136
|
-
printSuccess("Memory saved: " + memKey);
|
|
2244
|
+
if (memParts[0] && memParts.length > 1) {
|
|
2245
|
+
fs.writeFileSync(path.join(MEMORY_DIR, memParts[0] + ".txt"), memParts.slice(1).join(" "));
|
|
2246
|
+
printSuccess("Memory saved: " + memParts[0]);
|
|
2137
2247
|
}
|
|
2248
|
+
} else if (input.startsWith("#")) {
|
|
2249
|
+
var memKey = input.slice(1).trim();
|
|
2250
|
+
var memFile = path.join(MEMORY_DIR, memKey + ".txt");
|
|
2251
|
+
if (fs.existsSync(memFile)) console.log(C.brightCyan + fs.readFileSync(memFile, "utf8") + C.reset);
|
|
2252
|
+
else printError("Memory not found: " + memKey);
|
|
2253
|
+
} else if (input.startsWith("/")) {
|
|
2254
|
+
runHook("pre", input.split(/\s+/)[0].slice(1));
|
|
2255
|
+
await handleCommand(input);
|
|
2256
|
+
runHook("post", input.split(/\s+/)[0].slice(1));
|
|
2257
|
+
} else {
|
|
2258
|
+
await sendChat(input);
|
|
2138
2259
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2260
|
+
|
|
2261
|
+
inputBuffer = "";
|
|
2262
|
+
cursorPos = 0;
|
|
2263
|
+
menuSelected = 0;
|
|
2264
|
+
processing = false;
|
|
2265
|
+
drawPrompt();
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
// ── Raw Input Handler ──
|
|
2269
|
+
process.stdin.setRawMode(true);
|
|
2270
|
+
process.stdin.resume();
|
|
2271
|
+
process.stdin.setEncoding("utf8");
|
|
2272
|
+
|
|
2273
|
+
drawPrompt();
|
|
2274
|
+
|
|
2275
|
+
process.stdin.on("data", function(key) {
|
|
2276
|
+
if (processing) return;
|
|
2277
|
+
|
|
2278
|
+
// Ctrl+C
|
|
2279
|
+
if (key === "\x03") {
|
|
2280
|
+
session.history = chatHistory.slice(-50);
|
|
2281
|
+
saveSessionHistory(session);
|
|
2282
|
+
clearMenu();
|
|
2283
|
+
console.log(C.dim + "\nBye." + C.reset);
|
|
2284
|
+
process.exit(0);
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
// Ctrl+D
|
|
2288
|
+
if (key === "\x04") {
|
|
2289
|
+
session.history = chatHistory.slice(-50);
|
|
2290
|
+
saveSessionHistory(session);
|
|
2291
|
+
clearMenu();
|
|
2292
|
+
console.log(C.dim + "\nBye." + C.reset);
|
|
2293
|
+
process.exit(0);
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
// Enter
|
|
2297
|
+
if (key === "\r" || key === "\n") {
|
|
2298
|
+
var input = inputBuffer.trim();
|
|
2299
|
+
// If menu is visible and a command is selected, use that
|
|
2300
|
+
if (menuVisible && menuItems.length > 0 && inputBuffer.startsWith("/") && !inputBuffer.includes(" ")) {
|
|
2301
|
+
input = menuItems[menuSelected].cmd;
|
|
2302
|
+
}
|
|
2303
|
+
if (!input) return;
|
|
2304
|
+
inputHistory.unshift(input);
|
|
2305
|
+
historyIdx = -1;
|
|
2306
|
+
processInput(input);
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
// Tab — autocomplete from menu
|
|
2311
|
+
if (key === "\t") {
|
|
2312
|
+
if (menuVisible && menuItems.length > 0) {
|
|
2313
|
+
inputBuffer = menuItems[menuSelected].cmd + " ";
|
|
2314
|
+
cursorPos = inputBuffer.length;
|
|
2315
|
+
clearMenu();
|
|
2316
|
+
renderInputBox();
|
|
2317
|
+
updateMenu();
|
|
2146
2318
|
}
|
|
2319
|
+
return;
|
|
2147
2320
|
}
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2321
|
+
|
|
2322
|
+
// Backspace
|
|
2323
|
+
if (key === "\x7f" || key === "\b") {
|
|
2324
|
+
if (inputBuffer.length > 0) {
|
|
2325
|
+
inputBuffer = inputBuffer.slice(0, -1);
|
|
2326
|
+
cursorPos = inputBuffer.length;
|
|
2327
|
+
clearMenu();
|
|
2328
|
+
renderInputBox();
|
|
2329
|
+
updateMenu();
|
|
2330
|
+
}
|
|
2331
|
+
return;
|
|
2151
2332
|
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
if (
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2333
|
+
|
|
2334
|
+
// Arrow keys (escape sequences)
|
|
2335
|
+
if (key === "\x1b[A") { // Up
|
|
2336
|
+
if (menuVisible && menuItems.length > 0) {
|
|
2337
|
+
menuSelected = Math.max(0, menuSelected - 1);
|
|
2338
|
+
clearMenu();
|
|
2339
|
+
renderMenu();
|
|
2340
|
+
} else if (inputHistory.length > 0) {
|
|
2341
|
+
historyIdx = Math.min(historyIdx + 1, inputHistory.length - 1);
|
|
2342
|
+
inputBuffer = inputHistory[historyIdx];
|
|
2343
|
+
cursorPos = inputBuffer.length;
|
|
2344
|
+
renderInputBox();
|
|
2161
2345
|
}
|
|
2346
|
+
return;
|
|
2162
2347
|
}
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2348
|
+
if (key === "\x1b[B") { // Down
|
|
2349
|
+
if (menuVisible && menuItems.length > 0) {
|
|
2350
|
+
menuSelected = Math.min(menuItems.length - 1, menuSelected + 1);
|
|
2351
|
+
clearMenu();
|
|
2352
|
+
renderMenu();
|
|
2353
|
+
} else if (historyIdx > 0) {
|
|
2354
|
+
historyIdx--;
|
|
2355
|
+
inputBuffer = inputHistory[historyIdx];
|
|
2356
|
+
cursorPos = inputBuffer.length;
|
|
2357
|
+
renderInputBox();
|
|
2358
|
+
}
|
|
2359
|
+
return;
|
|
2166
2360
|
}
|
|
2167
|
-
rl.prompt();
|
|
2168
|
-
});
|
|
2169
2361
|
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2362
|
+
// Escape — close menu
|
|
2363
|
+
if (key === "\x1b" || key === "\x1b\x1b") {
|
|
2364
|
+
if (menuVisible) {
|
|
2365
|
+
clearMenu();
|
|
2366
|
+
menuVisible = false;
|
|
2367
|
+
}
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
// Ignore other escape sequences
|
|
2372
|
+
if (key.startsWith("\x1b")) return;
|
|
2373
|
+
|
|
2374
|
+
// Regular character
|
|
2375
|
+
inputBuffer += key;
|
|
2376
|
+
cursorPos = inputBuffer.length;
|
|
2377
|
+
clearMenu();
|
|
2378
|
+
renderInputBox();
|
|
2379
|
+
updateMenu();
|
|
2176
2380
|
});
|
|
2177
2381
|
}
|
|
2178
2382
|
|