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.
Files changed (2) hide show
  1. package/bin/blun.js +261 -58
  2. 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
- 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,239 @@ 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.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
- return [[], line];
2221
+ } else {
2222
+ if (menuVisible) clearMenu();
2117
2223
  }
2118
- });
2224
+ }
2119
2225
 
2120
- rl.prompt();
2226
+ function drawPrompt() {
2227
+ if (processing) return;
2228
+ console.log(""); // spacing
2229
+ renderInputBox();
2230
+ updateMenu();
2231
+ }
2121
2232
 
2122
- rl.on("line", async function(line) {
2123
- var input = line.trim();
2124
- if (!input) { rl.prompt(); return; }
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
- 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);
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
- 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);
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
- // / alone = show menu
2150
- else if (input === "/") {
2151
- showSlashMenu("/");
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
- // / 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));
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
- // plain text = chat
2165
- else {
2166
- await sendChat(input);
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
- 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);
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
 
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.0",
4
4
  "description": "BLUN King CLI — Premium KI Console",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"