blun-king-cli 1.7.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 +261 -58
- package/package.json +1 -1
package/bin/blun.js
CHANGED
|
@@ -2067,14 +2067,54 @@ async function main() {
|
|
|
2067
2067
|
}
|
|
2068
2068
|
|
|
2069
2069
|
// Interactive mode
|
|
2070
|
-
// Workdir selection (like Claude Code)
|
|
2071
|
-
|
|
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")) {
|
|
2072
2074
|
var cwdHasBlun = fs.existsSync(path.join(process.cwd(), ".blun"));
|
|
2073
2075
|
var cwdHasGit = fs.existsSync(path.join(process.cwd(), ".git"));
|
|
2074
|
-
|
|
2075
|
-
|
|
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("");
|
|
2076
2117
|
}
|
|
2077
|
-
// Show workdir in header
|
|
2078
2118
|
}
|
|
2079
2119
|
|
|
2080
2120
|
printHeader();
|
|
@@ -2104,76 +2144,239 @@ async function main() {
|
|
|
2104
2144
|
"/compact", "/review", "/cost", "/exit"
|
|
2105
2145
|
];
|
|
2106
2146
|
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
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();
|
|
2115
2220
|
}
|
|
2116
|
-
|
|
2221
|
+
} else {
|
|
2222
|
+
if (menuVisible) clearMenu();
|
|
2117
2223
|
}
|
|
2118
|
-
}
|
|
2224
|
+
}
|
|
2119
2225
|
|
|
2120
|
-
|
|
2226
|
+
function drawPrompt() {
|
|
2227
|
+
if (processing) return;
|
|
2228
|
+
console.log(""); // spacing
|
|
2229
|
+
renderInputBox();
|
|
2230
|
+
updateMenu();
|
|
2231
|
+
}
|
|
2121
2232
|
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
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");
|
|
2125
2239
|
|
|
2126
|
-
// ! prefix = direct bash
|
|
2127
2240
|
if (input.startsWith("!")) {
|
|
2128
2241
|
cmdShell(input.slice(1).trim());
|
|
2129
|
-
}
|
|
2130
|
-
// # prefix = memory shortcut
|
|
2131
|
-
else if (input.startsWith("#save ")) {
|
|
2242
|
+
} else if (input.startsWith("#save ")) {
|
|
2132
2243
|
var memParts = input.slice(6).split(/\s+/);
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
fs.writeFileSync(path.join(MEMORY_DIR, memKey + ".txt"), memVal);
|
|
2137
|
-
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]);
|
|
2138
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);
|
|
2139
2259
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
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();
|
|
2147
2318
|
}
|
|
2319
|
+
return;
|
|
2148
2320
|
}
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
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;
|
|
2152
2332
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
if (
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
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();
|
|
2162
2345
|
}
|
|
2346
|
+
return;
|
|
2163
2347
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
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;
|
|
2167
2360
|
}
|
|
2168
|
-
rl.prompt();
|
|
2169
|
-
});
|
|
2170
2361
|
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
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();
|
|
2177
2380
|
});
|
|
2178
2381
|
}
|
|
2179
2382
|
|