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.
Files changed (2) hide show
  1. package/bin/blun.js +263 -59
  2. 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 + " v1.4" + C.reset + " ".repeat(Math.max(0, w - 24)) + C.brightBlue + BOX.v + C.reset);
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
- 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")) {
2071
2074
  var cwdHasBlun = fs.existsSync(path.join(process.cwd(), ".blun"));
2072
2075
  var cwdHasGit = fs.existsSync(path.join(process.cwd(), ".git"));
2073
- if (cwdHasBlun || cwdHasGit) {
2074
- 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("");
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
- var rl = readline.createInterface({
2107
- input: process.stdin,
2108
- output: process.stdout,
2109
- prompt: "\n" + C.brightBlue + C.bold + " " + BOX.arrow + " " + C.reset,
2110
- completer: function(line) {
2111
- if (line.startsWith("/")) {
2112
- var hits = ALL_COMMANDS.filter(function(c) { return c.startsWith(line); });
2113
- 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();
2114
2220
  }
2115
- return [[], line];
2221
+ } else {
2222
+ if (menuVisible) clearMenu();
2116
2223
  }
2117
- });
2224
+ }
2118
2225
 
2119
- rl.prompt();
2226
+ function drawPrompt() {
2227
+ if (processing) return;
2228
+ console.log(""); // spacing
2229
+ renderInputBox();
2230
+ updateMenu();
2231
+ }
2120
2232
 
2121
- rl.on("line", async function(line) {
2122
- var input = line.trim();
2123
- 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");
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
- var memKey = memParts[0];
2133
- var memVal = memParts.slice(1).join(" ");
2134
- if (memKey && memVal) {
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
- else if (input.startsWith("#") && !input.startsWith("#save")) {
2140
- var memKey2 = input.slice(1).trim();
2141
- var memFile = path.join(MEMORY_DIR, memKey2 + ".txt");
2142
- if (fs.existsSync(memFile)) {
2143
- console.log(C.brightCyan + fs.readFileSync(memFile, "utf8") + C.reset);
2144
- } else {
2145
- 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();
2146
2318
  }
2319
+ return;
2147
2320
  }
2148
- // / alone = show menu
2149
- else if (input === "/") {
2150
- 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;
2151
2332
  }
2152
- // / prefix = command
2153
- else if (input.startsWith("/")) {
2154
- // If it's a partial command without space, show filtered menu
2155
- if (!input.includes(" ") && !ALL_COMMANDS.includes(input)) {
2156
- showSlashMenu(input);
2157
- } else {
2158
- runHook("pre", input.split(/\s+/)[0].slice(1));
2159
- await handleCommand(input);
2160
- 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();
2161
2345
  }
2346
+ return;
2162
2347
  }
2163
- // plain text = chat
2164
- else {
2165
- 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;
2166
2360
  }
2167
- rl.prompt();
2168
- });
2169
2361
 
2170
- rl.on("close", function() {
2171
- // Save session history
2172
- session.history = chatHistory.slice(-50);
2173
- saveSessionHistory(session);
2174
- console.log(C.dim + "\nBye." + C.reset);
2175
- 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();
2176
2380
  });
2177
2381
  }
2178
2382
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "1.6.0",
3
+ "version": "2.0.0",
4
4
  "description": "BLUN King CLI — Premium KI Console",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"