blun-king-cli 1.7.0 → 2.0.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.
Files changed (2) hide show
  1. package/bin/blun.js +264 -59
  2. package/package.json +1 -1
package/bin/blun.js CHANGED
@@ -2018,7 +2018,7 @@ function showSlashMenu(filter) {
2018
2018
  var menuLines = [];
2019
2019
  show.forEach(function(c) {
2020
2020
  var cmdPad = (c.cmd + " ".repeat(20)).slice(0, 18);
2021
- menuLines.push(C.dim + " " + C.brightCyan + cmdPad + C.gray + c.desc.slice(0, 55) + C.reset);
2021
+ menuLines.push(" " + C.green + cmdPad + C.white + c.desc.slice(0, 55) + C.reset);
2022
2022
  });
2023
2023
  if (filtered.length > 8) {
2024
2024
  menuLines.push(C.dim + " ... " + (filtered.length - 8) + " more" + C.reset);
@@ -2067,14 +2067,54 @@ async function main() {
2067
2067
  }
2068
2068
 
2069
2069
  // Interactive mode
2070
- // Workdir selection (like Claude Code)
2071
- if (!process.argv.includes("--no-workdir-prompt")) {
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
- if (cwdHasBlun || cwdHasGit) {
2075
- config.workdir = process.cwd();
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,241 @@ async function main() {
2104
2144
  "/compact", "/review", "/cost", "/exit"
2105
2145
  ];
2106
2146
 
2107
- var rl = readline.createInterface({
2108
- input: process.stdin,
2109
- output: process.stdout,
2110
- prompt: "\n" + C.brightBlue + C.bold + " " + BOX.arrow + " " + C.reset,
2111
- completer: function(line) {
2112
- if (line.startsWith("/")) {
2113
- var hits = ALL_COMMANDS.filter(function(c) { return c.startsWith(line); });
2114
- return [hits.length ? hits : ALL_COMMANDS, line];
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.green + C.bold + " " + BOX.arrow + " " : " ";
2187
+ var cmdStr = (item.cmd + " ".repeat(20)).slice(0, 18);
2188
+ var descStr = item.desc.slice(0, getTermWidth() - 28);
2189
+ var cmdColor = i === menuSelected ? C.green + C.bold : C.green;
2190
+ var descColor = i === menuSelected ? C.brightWhite : C.white;
2191
+ process.stdout.write(prefix + cmdColor + cmdStr + descColor + descStr + C.reset + "\x1b[K\n");
2192
+ });
2193
+ if (menuItems.length > 8) {
2194
+ process.stdout.write(C.dim + " ... " + (menuItems.length - 8) + " more" + C.reset + "\x1b[K\n");
2195
+ }
2196
+ process.stdout.write("\x1b[u"); // restore cursor
2197
+ }
2198
+
2199
+ function clearMenu() {
2200
+ if (!menuVisible) return;
2201
+ var lines = Math.min(menuItems.length, 8) + (menuItems.length > 8 ? 1 : 0) + 2;
2202
+ process.stdout.write("\x1b[s"); // save
2203
+ for (var i = 0; i < lines; i++) {
2204
+ process.stdout.write("\n\x1b[K");
2205
+ }
2206
+ process.stdout.write("\x1b[u"); // restore
2207
+ menuVisible = false;
2208
+ }
2209
+
2210
+ function updateMenu() {
2211
+ if (inputBuffer.startsWith("/")) {
2212
+ var filter = inputBuffer.toLowerCase();
2213
+ menuItems = COMMAND_DESCRIPTIONS.filter(function(c) {
2214
+ return c.cmd.includes(filter) || c.desc.toLowerCase().includes(filter.slice(1));
2215
+ });
2216
+ if (menuItems.length > 0) {
2217
+ menuVisible = true;
2218
+ menuSelected = Math.min(menuSelected, menuItems.length - 1);
2219
+ renderMenu();
2220
+ } else {
2221
+ clearMenu();
2115
2222
  }
2116
- return [[], line];
2223
+ } else {
2224
+ if (menuVisible) clearMenu();
2117
2225
  }
2118
- });
2226
+ }
2119
2227
 
2120
- rl.prompt();
2228
+ function drawPrompt() {
2229
+ if (processing) return;
2230
+ console.log(""); // spacing
2231
+ renderInputBox();
2232
+ updateMenu();
2233
+ }
2121
2234
 
2122
- rl.on("line", async function(line) {
2123
- var input = line.trim();
2124
- if (!input) { rl.prompt(); return; }
2235
+ async function processInput(input) {
2236
+ processing = true;
2237
+ clearMenu();
2238
+ clearInputBox();
2239
+ // Move below the box area
2240
+ process.stdout.write("\x1b[2B\r\x1b[K\x1b[1A\r\x1b[K\x1b[1A\r\x1b[K");
2125
2241
 
2126
- // ! prefix = direct bash
2127
2242
  if (input.startsWith("!")) {
2128
2243
  cmdShell(input.slice(1).trim());
2129
- }
2130
- // # prefix = memory shortcut
2131
- else if (input.startsWith("#save ")) {
2244
+ } else if (input.startsWith("#save ")) {
2132
2245
  var memParts = input.slice(6).split(/\s+/);
2133
- var memKey = memParts[0];
2134
- var memVal = memParts.slice(1).join(" ");
2135
- if (memKey && memVal) {
2136
- fs.writeFileSync(path.join(MEMORY_DIR, memKey + ".txt"), memVal);
2137
- printSuccess("Memory saved: " + memKey);
2246
+ if (memParts[0] && memParts.length > 1) {
2247
+ fs.writeFileSync(path.join(MEMORY_DIR, memParts[0] + ".txt"), memParts.slice(1).join(" "));
2248
+ printSuccess("Memory saved: " + memParts[0]);
2138
2249
  }
2250
+ } else if (input.startsWith("#")) {
2251
+ var memKey = input.slice(1).trim();
2252
+ var memFile = path.join(MEMORY_DIR, memKey + ".txt");
2253
+ if (fs.existsSync(memFile)) console.log(C.brightCyan + fs.readFileSync(memFile, "utf8") + C.reset);
2254
+ else printError("Memory not found: " + memKey);
2255
+ } else if (input.startsWith("/")) {
2256
+ runHook("pre", input.split(/\s+/)[0].slice(1));
2257
+ await handleCommand(input);
2258
+ runHook("post", input.split(/\s+/)[0].slice(1));
2259
+ } else {
2260
+ await sendChat(input);
2139
2261
  }
2140
- else if (input.startsWith("#") && !input.startsWith("#save")) {
2141
- var memKey2 = input.slice(1).trim();
2142
- var memFile = path.join(MEMORY_DIR, memKey2 + ".txt");
2143
- if (fs.existsSync(memFile)) {
2144
- console.log(C.brightCyan + fs.readFileSync(memFile, "utf8") + C.reset);
2145
- } else {
2146
- printError("Memory not found: " + memKey2);
2262
+
2263
+ inputBuffer = "";
2264
+ cursorPos = 0;
2265
+ menuSelected = 0;
2266
+ processing = false;
2267
+ drawPrompt();
2268
+ }
2269
+
2270
+ // ── Raw Input Handler ──
2271
+ process.stdin.setRawMode(true);
2272
+ process.stdin.resume();
2273
+ process.stdin.setEncoding("utf8");
2274
+
2275
+ drawPrompt();
2276
+
2277
+ process.stdin.on("data", function(key) {
2278
+ if (processing) return;
2279
+
2280
+ // Ctrl+C
2281
+ if (key === "\x03") {
2282
+ session.history = chatHistory.slice(-50);
2283
+ saveSessionHistory(session);
2284
+ clearMenu();
2285
+ console.log(C.dim + "\nBye." + C.reset);
2286
+ process.exit(0);
2287
+ }
2288
+
2289
+ // Ctrl+D
2290
+ if (key === "\x04") {
2291
+ session.history = chatHistory.slice(-50);
2292
+ saveSessionHistory(session);
2293
+ clearMenu();
2294
+ console.log(C.dim + "\nBye." + C.reset);
2295
+ process.exit(0);
2296
+ }
2297
+
2298
+ // Enter
2299
+ if (key === "\r" || key === "\n") {
2300
+ var input = inputBuffer.trim();
2301
+ // If menu is visible and a command is selected, use that
2302
+ if (menuVisible && menuItems.length > 0 && inputBuffer.startsWith("/") && !inputBuffer.includes(" ")) {
2303
+ input = menuItems[menuSelected].cmd;
2304
+ }
2305
+ if (!input) return;
2306
+ inputHistory.unshift(input);
2307
+ historyIdx = -1;
2308
+ processInput(input);
2309
+ return;
2310
+ }
2311
+
2312
+ // Tab — autocomplete from menu
2313
+ if (key === "\t") {
2314
+ if (menuVisible && menuItems.length > 0) {
2315
+ inputBuffer = menuItems[menuSelected].cmd + " ";
2316
+ cursorPos = inputBuffer.length;
2317
+ clearMenu();
2318
+ renderInputBox();
2319
+ updateMenu();
2147
2320
  }
2321
+ return;
2148
2322
  }
2149
- // / alone = show menu
2150
- else if (input === "/") {
2151
- showSlashMenu("/");
2323
+
2324
+ // Backspace
2325
+ if (key === "\x7f" || key === "\b") {
2326
+ if (inputBuffer.length > 0) {
2327
+ inputBuffer = inputBuffer.slice(0, -1);
2328
+ cursorPos = inputBuffer.length;
2329
+ clearMenu();
2330
+ renderInputBox();
2331
+ updateMenu();
2332
+ }
2333
+ return;
2152
2334
  }
2153
- // / prefix = command
2154
- else if (input.startsWith("/")) {
2155
- // If it's a partial command without space, show filtered menu
2156
- if (!input.includes(" ") && !ALL_COMMANDS.includes(input)) {
2157
- showSlashMenu(input);
2158
- } else {
2159
- runHook("pre", input.split(/\s+/)[0].slice(1));
2160
- await handleCommand(input);
2161
- runHook("post", input.split(/\s+/)[0].slice(1));
2335
+
2336
+ // Arrow keys (escape sequences)
2337
+ if (key === "\x1b[A") { // Up
2338
+ if (menuVisible && menuItems.length > 0) {
2339
+ menuSelected = Math.max(0, menuSelected - 1);
2340
+ clearMenu();
2341
+ renderMenu();
2342
+ } else if (inputHistory.length > 0) {
2343
+ historyIdx = Math.min(historyIdx + 1, inputHistory.length - 1);
2344
+ inputBuffer = inputHistory[historyIdx];
2345
+ cursorPos = inputBuffer.length;
2346
+ renderInputBox();
2162
2347
  }
2348
+ return;
2163
2349
  }
2164
- // plain text = chat
2165
- else {
2166
- await sendChat(input);
2350
+ if (key === "\x1b[B") { // Down
2351
+ if (menuVisible && menuItems.length > 0) {
2352
+ menuSelected = Math.min(menuItems.length - 1, menuSelected + 1);
2353
+ clearMenu();
2354
+ renderMenu();
2355
+ } else if (historyIdx > 0) {
2356
+ historyIdx--;
2357
+ inputBuffer = inputHistory[historyIdx];
2358
+ cursorPos = inputBuffer.length;
2359
+ renderInputBox();
2360
+ }
2361
+ return;
2167
2362
  }
2168
- rl.prompt();
2169
- });
2170
2363
 
2171
- rl.on("close", function() {
2172
- // Save session history
2173
- session.history = chatHistory.slice(-50);
2174
- saveSessionHistory(session);
2175
- console.log(C.dim + "\nBye." + C.reset);
2176
- process.exit(0);
2364
+ // Escape — close menu
2365
+ if (key === "\x1b" || key === "\x1b\x1b") {
2366
+ if (menuVisible) {
2367
+ clearMenu();
2368
+ menuVisible = false;
2369
+ }
2370
+ return;
2371
+ }
2372
+
2373
+ // Ignore other escape sequences
2374
+ if (key.startsWith("\x1b")) return;
2375
+
2376
+ // Regular character
2377
+ inputBuffer += key;
2378
+ cursorPos = inputBuffer.length;
2379
+ clearMenu();
2380
+ renderInputBox();
2381
+ updateMenu();
2177
2382
  });
2178
2383
  }
2179
2384
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "1.7.0",
3
+ "version": "2.0.1",
4
4
  "description": "BLUN King CLI — Premium KI Console",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"