blun-king-cli 2.0.0 → 2.1.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 +181 -90
  2. package/package.json +1 -1
package/bin/blun.js CHANGED
@@ -491,6 +491,12 @@ async function sendChat(message) {
491
491
  (q.retried ? " " + BOX.dot + " retried" : "");
492
492
  printAnswer(resp.data.answer, meta);
493
493
 
494
+ // Auto Memory Dream Mode
495
+ dreamCounter++;
496
+ if (dreamCounter >= DREAM_INTERVAL) {
497
+ autoCompact().catch(function() {});
498
+ }
499
+
494
500
  } catch(e) {
495
501
  if (typeof thinkTimer !== "undefined") clearInterval(thinkTimer);
496
502
  process.stdout.write("\r" + " ".repeat(40) + "\r");
@@ -1957,6 +1963,96 @@ async function cmdReview() {
1957
1963
  } catch(e) { printError(e.message); }
1958
1964
  }
1959
1965
 
1966
+ // ══════════════════════════════════════════════════
1967
+ // ── AUTO MEMORY DREAM MODE ──
1968
+ // ══════════════════════════════════════════════════
1969
+ // After every 10 messages or on exit, BLUN analyzes the conversation
1970
+ // and saves key learnings to ~/.blun/memory/ automatically.
1971
+ var dreamCounter = 0;
1972
+ const DREAM_INTERVAL = 10; // messages between dreams
1973
+ const DREAM_DIR = path.join(MEMORY_DIR, "dreams");
1974
+ if (!fs.existsSync(DREAM_DIR)) fs.mkdirSync(DREAM_DIR, { recursive: true });
1975
+
1976
+ async function dreamMode() {
1977
+ if (chatHistory.length < 4) return; // not enough to dream about
1978
+
1979
+ try {
1980
+ // Ask BLUN to extract key learnings
1981
+ var dreamPrompt = [
1982
+ { role: "system", content: "Du bist der BLUN King Memory Manager. Analysiere diese Konversation und extrahiere die wichtigsten Fakten, Entscheidungen und Erkenntnisse. Antworte NUR mit einem JSON-Objekt:\n{\"learnings\": [\"...\", \"...\"], \"user_preferences\": [\"...\"], \"project_facts\": [\"...\"], \"summary\": \"1 Satz Zusammenfassung\"}\nKein Markdown, NUR JSON." },
1983
+ { role: "user", content: "Konversation:\n" + chatHistory.slice(-10).map(function(m) { return m.role + ": " + m.content.slice(0, 200); }).join("\n") }
1984
+ ];
1985
+
1986
+ var resp = await apiCall("POST", "/chat", { message: dreamPrompt[1].content, history: [dreamPrompt[0]] });
1987
+ if (resp.status !== 200) return;
1988
+
1989
+ var answer = resp.data.answer;
1990
+ var dreamData;
1991
+ try {
1992
+ var jsonMatch = answer.match(/\{[\s\S]*\}/);
1993
+ if (jsonMatch) dreamData = JSON.parse(jsonMatch[0]);
1994
+ } catch(e) { return; }
1995
+
1996
+ if (!dreamData) return;
1997
+
1998
+ // Save dream
1999
+ var dreamFile = path.join(DREAM_DIR, new Date().toISOString().slice(0, 10) + ".json");
2000
+ var existingDreams = [];
2001
+ if (fs.existsSync(dreamFile)) {
2002
+ try { existingDreams = JSON.parse(fs.readFileSync(dreamFile, "utf8")); } catch(e) {}
2003
+ }
2004
+ existingDreams.push({
2005
+ timestamp: new Date().toISOString(),
2006
+ workdir: config.workdir,
2007
+ learnings: dreamData.learnings || [],
2008
+ user_preferences: dreamData.user_preferences || [],
2009
+ project_facts: dreamData.project_facts || [],
2010
+ summary: dreamData.summary || ""
2011
+ });
2012
+ fs.writeFileSync(dreamFile, JSON.stringify(existingDreams, null, 2));
2013
+
2014
+ // Also save learnings to flat memory for quick recall
2015
+ if (dreamData.learnings && dreamData.learnings.length > 0) {
2016
+ var memContent = dreamData.learnings.join("\n");
2017
+ var memFile = path.join(MEMORY_DIR, "auto_" + Date.now() + ".txt");
2018
+ fs.writeFileSync(memFile, memContent);
2019
+ }
2020
+
2021
+ if (config.verbose) printInfo("Dream Mode: " + (dreamData.learnings || []).length + " learnings saved.");
2022
+ } catch(e) {
2023
+ if (config.verbose) log("Dream error: " + e.message);
2024
+ }
2025
+ }
2026
+
2027
+ // Load dream context at startup
2028
+ function loadDreamContext() {
2029
+ var context = [];
2030
+ try {
2031
+ var files = fs.readdirSync(DREAM_DIR).sort().reverse().slice(0, 3); // last 3 days
2032
+ files.forEach(function(f) {
2033
+ var dreams = JSON.parse(fs.readFileSync(path.join(DREAM_DIR, f), "utf8"));
2034
+ dreams.forEach(function(d) {
2035
+ if (d.learnings) context = context.concat(d.learnings);
2036
+ if (d.project_facts) context = context.concat(d.project_facts);
2037
+ });
2038
+ });
2039
+ } catch(e) {}
2040
+ return context.slice(0, 20); // max 20 facts
2041
+ }
2042
+
2043
+ // Auto-compact: summarize every DREAM_INTERVAL messages
2044
+ async function autoCompact() {
2045
+ if (chatHistory.length < DREAM_INTERVAL) return;
2046
+
2047
+ // Dream first
2048
+ await dreamMode();
2049
+
2050
+ // Then compact
2051
+ var oldLen = chatHistory.length;
2052
+ chatHistory = chatHistory.slice(-6);
2053
+ if (config.verbose) printInfo("Auto-compact: " + oldLen + " → " + chatHistory.length);
2054
+ }
2055
+
1960
2056
  // ══════════════════════════════════════════════════
1961
2057
  // ── SLASH COMMAND MENU (Claude Code style) ──
1962
2058
  // ══════════════════════════════════════════════════
@@ -2018,7 +2114,7 @@ function showSlashMenu(filter) {
2018
2114
  var menuLines = [];
2019
2115
  show.forEach(function(c) {
2020
2116
  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);
2117
+ menuLines.push(" " + C.green + cmdPad + C.white + c.desc.slice(0, 55) + C.reset);
2022
2118
  });
2023
2119
  if (filtered.length > 8) {
2024
2120
  menuLines.push(C.dim + " ... " + (filtered.length - 8) + " more" + C.reset);
@@ -2070,9 +2166,17 @@ async function main() {
2070
2166
  // Workdir selection (like Claude Code — trust prompt)
2071
2167
  config.workdir = process.cwd();
2072
2168
 
2169
+ // --dir flag support
2170
+ var dirIdx = process.argv.indexOf("--dir");
2171
+ if (dirIdx !== -1 && process.argv[dirIdx + 1]) {
2172
+ var customDir = process.argv[dirIdx + 1];
2173
+ if (fs.existsSync(customDir)) {
2174
+ config.workdir = path.resolve(customDir);
2175
+ process.chdir(config.workdir);
2176
+ }
2177
+ }
2178
+
2073
2179
  if (!process.argv.includes("--no-workdir-prompt") && !process.argv.includes("--trust")) {
2074
- var cwdHasBlun = fs.existsSync(path.join(process.cwd(), ".blun"));
2075
- var cwdHasGit = fs.existsSync(path.join(process.cwd(), ".git"));
2076
2180
  var trustedDirs = [];
2077
2181
  var trustFile = path.join(CONFIG_DIR, "trusted.json");
2078
2182
  if (fs.existsSync(trustFile)) {
@@ -2080,7 +2184,7 @@ async function main() {
2080
2184
  }
2081
2185
  var isTrusted = trustedDirs.includes(process.cwd());
2082
2186
 
2083
- if (!isTrusted && (cwdHasBlun || cwdHasGit)) {
2187
+ if (!isTrusted) {
2084
2188
  console.log("");
2085
2189
  console.log(C.yellow + " " + BOX.bot + " Working in: " + C.brightWhite + process.cwd() + C.reset);
2086
2190
  console.log(C.gray + " Do you trust this folder? BLUN King will read/write files here." + C.reset);
@@ -2153,89 +2257,92 @@ async function main() {
2153
2257
  var menuSelected = 0;
2154
2258
  var cursorPos = 0;
2155
2259
  var processing = false;
2260
+ var drawnLines = 0; // total lines drawn by the UI (box + menu)
2156
2261
 
2157
2262
  function getTermWidth() { return process.stdout.columns || 80; }
2158
2263
 
2159
- function renderInputBox() {
2264
+ // Erase everything we drew and move cursor back to start
2265
+ function eraseUI() {
2266
+ if (drawnLines <= 0) return;
2267
+ // Move to start of first drawn line
2268
+ process.stdout.write("\x1b[" + drawnLines + "A");
2269
+ for (var i = 0; i < drawnLines; i++) {
2270
+ process.stdout.write("\x1b[2K\n"); // clear entire line
2271
+ }
2272
+ process.stdout.write("\x1b[" + drawnLines + "A"); // back to start
2273
+ drawnLines = 0;
2274
+ }
2275
+
2276
+ // Draw the complete UI (box + menu) from scratch
2277
+ function drawUI() {
2278
+ process.stdout.write("\x1b[?25l"); // hide cursor
2160
2279
  var w = Math.min(getTermWidth() - 4, 76);
2161
2280
  var displayText = inputBuffer;
2162
2281
  if (displayText.length > w - 4) displayText = displayText.slice(-(w - 4));
2163
2282
  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");
2283
+ var lines = 0;
2284
+
2285
+ // Box top
2286
+ process.stdout.write(C.brightBlue + " \u256D" + "\u2500".repeat(w - 2) + "\u256E" + C.reset + "\n"); lines++;
2287
+ // Box content
2288
+ process.stdout.write(C.brightBlue + " \u2502 " + C.reset + C.brightWhite + displayText + C.reset + " ".repeat(pad) + C.brightBlue + "\u2502" + C.reset + "\n"); lines++;
2289
+ // Box bottom
2290
+ process.stdout.write(C.brightBlue + " \u2570" + "\u2500".repeat(w - 2) + "\u256F" + C.reset + "\n"); lines++;
2291
+
2292
+ // Menu
2293
+ if (menuVisible && menuItems.length > 0) {
2294
+ var show = menuItems.slice(0, 8);
2295
+ show.forEach(function(item, i) {
2296
+ var prefix = i === menuSelected ? C.green + C.bold + " \u276F " : " ";
2297
+ var cmdStr = (item.cmd + " ".repeat(20)).slice(0, 18);
2298
+ var descStr = item.desc.slice(0, getTermWidth() - 28);
2299
+ var cmdColor = i === menuSelected ? C.green + C.bold : C.green;
2300
+ var descColor = i === menuSelected ? C.brightWhite : C.white;
2301
+ process.stdout.write(prefix + cmdColor + cmdStr + descColor + descStr + C.reset + "\x1b[K\n");
2302
+ lines++;
2303
+ });
2304
+ if (menuItems.length > 8) {
2305
+ process.stdout.write(C.dim + " ... " + (menuItems.length - 8) + " more" + C.reset + "\x1b[K\n");
2306
+ lines++;
2307
+ }
2193
2308
  }
2194
- process.stdout.write("\x1b[u"); // restore cursor
2195
- }
2196
2309
 
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;
2310
+ drawnLines = lines;
2311
+
2312
+ // Position cursor inside the box (line 2, after text)
2313
+ process.stdout.write("\x1b[" + lines + "A"); // back to top
2314
+ process.stdout.write("\x1b[1B"); // down to content line
2315
+ process.stdout.write("\r\x1b[" + (4 + displayText.length) + "C"); // right to cursor pos
2316
+ process.stdout.write("\x1b[?25h"); // show cursor
2206
2317
  }
2207
2318
 
2208
- function updateMenu() {
2319
+ function refreshUI() {
2320
+ eraseUI();
2321
+ // Compute menu items
2209
2322
  if (inputBuffer.startsWith("/")) {
2210
2323
  var filter = inputBuffer.toLowerCase();
2211
2324
  menuItems = COMMAND_DESCRIPTIONS.filter(function(c) {
2212
2325
  return c.cmd.includes(filter) || c.desc.toLowerCase().includes(filter.slice(1));
2213
2326
  });
2214
- if (menuItems.length > 0) {
2215
- menuVisible = true;
2216
- menuSelected = Math.min(menuSelected, menuItems.length - 1);
2217
- renderMenu();
2218
- } else {
2219
- clearMenu();
2220
- }
2327
+ menuVisible = menuItems.length > 0;
2328
+ if (menuVisible) menuSelected = Math.min(menuSelected, menuItems.length - 1);
2221
2329
  } else {
2222
- if (menuVisible) clearMenu();
2330
+ menuVisible = false;
2331
+ menuItems = [];
2223
2332
  }
2333
+ drawUI();
2224
2334
  }
2225
2335
 
2226
2336
  function drawPrompt() {
2227
2337
  if (processing) return;
2228
2338
  console.log(""); // spacing
2229
- renderInputBox();
2230
- updateMenu();
2339
+ drawnLines = 0;
2340
+ refreshUI();
2231
2341
  }
2232
2342
 
2233
2343
  async function processInput(input) {
2234
2344
  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");
2345
+ eraseUI();
2239
2346
 
2240
2347
  if (input.startsWith("!")) {
2241
2348
  cmdShell(input.slice(1).trim());
@@ -2275,20 +2382,11 @@ async function main() {
2275
2382
  process.stdin.on("data", function(key) {
2276
2383
  if (processing) return;
2277
2384
 
2278
- // Ctrl+C
2279
- if (key === "\x03") {
2385
+ // Ctrl+C / Ctrl+D — exit
2386
+ if (key === "\x03" || key === "\x04") {
2280
2387
  session.history = chatHistory.slice(-50);
2281
2388
  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();
2389
+ eraseUI();
2292
2390
  console.log(C.dim + "\nBye." + C.reset);
2293
2391
  process.exit(0);
2294
2392
  }
@@ -2296,7 +2394,6 @@ async function main() {
2296
2394
  // Enter
2297
2395
  if (key === "\r" || key === "\n") {
2298
2396
  var input = inputBuffer.trim();
2299
- // If menu is visible and a command is selected, use that
2300
2397
  if (menuVisible && menuItems.length > 0 && inputBuffer.startsWith("/") && !inputBuffer.includes(" ")) {
2301
2398
  input = menuItems[menuSelected].cmd;
2302
2399
  }
@@ -2312,9 +2409,7 @@ async function main() {
2312
2409
  if (menuVisible && menuItems.length > 0) {
2313
2410
  inputBuffer = menuItems[menuSelected].cmd + " ";
2314
2411
  cursorPos = inputBuffer.length;
2315
- clearMenu();
2316
- renderInputBox();
2317
- updateMenu();
2412
+ refreshUI();
2318
2413
  }
2319
2414
  return;
2320
2415
  }
@@ -2324,37 +2419,34 @@ async function main() {
2324
2419
  if (inputBuffer.length > 0) {
2325
2420
  inputBuffer = inputBuffer.slice(0, -1);
2326
2421
  cursorPos = inputBuffer.length;
2327
- clearMenu();
2328
- renderInputBox();
2329
- updateMenu();
2422
+ menuSelected = 0;
2423
+ refreshUI();
2330
2424
  }
2331
2425
  return;
2332
2426
  }
2333
2427
 
2334
- // Arrow keys (escape sequences)
2428
+ // Arrow keys
2335
2429
  if (key === "\x1b[A") { // Up
2336
2430
  if (menuVisible && menuItems.length > 0) {
2337
2431
  menuSelected = Math.max(0, menuSelected - 1);
2338
- clearMenu();
2339
- renderMenu();
2432
+ refreshUI();
2340
2433
  } else if (inputHistory.length > 0) {
2341
2434
  historyIdx = Math.min(historyIdx + 1, inputHistory.length - 1);
2342
2435
  inputBuffer = inputHistory[historyIdx];
2343
2436
  cursorPos = inputBuffer.length;
2344
- renderInputBox();
2437
+ refreshUI();
2345
2438
  }
2346
2439
  return;
2347
2440
  }
2348
2441
  if (key === "\x1b[B") { // Down
2349
2442
  if (menuVisible && menuItems.length > 0) {
2350
2443
  menuSelected = Math.min(menuItems.length - 1, menuSelected + 1);
2351
- clearMenu();
2352
- renderMenu();
2444
+ refreshUI();
2353
2445
  } else if (historyIdx > 0) {
2354
2446
  historyIdx--;
2355
2447
  inputBuffer = inputHistory[historyIdx];
2356
2448
  cursorPos = inputBuffer.length;
2357
- renderInputBox();
2449
+ refreshUI();
2358
2450
  }
2359
2451
  return;
2360
2452
  }
@@ -2362,8 +2454,8 @@ async function main() {
2362
2454
  // Escape — close menu
2363
2455
  if (key === "\x1b" || key === "\x1b\x1b") {
2364
2456
  if (menuVisible) {
2365
- clearMenu();
2366
2457
  menuVisible = false;
2458
+ refreshUI();
2367
2459
  }
2368
2460
  return;
2369
2461
  }
@@ -2374,9 +2466,8 @@ async function main() {
2374
2466
  // Regular character
2375
2467
  inputBuffer += key;
2376
2468
  cursorPos = inputBuffer.length;
2377
- clearMenu();
2378
- renderInputBox();
2379
- updateMenu();
2469
+ menuSelected = 0;
2470
+ refreshUI();
2380
2471
  });
2381
2472
  }
2382
2473
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "BLUN King CLI — Premium KI Console",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"