blun-king-cli 2.0.1 → 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 +180 -91
  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
  // ══════════════════════════════════════════════════
@@ -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,91 +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.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");
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
+ }
2195
2308
  }
2196
- process.stdout.write("\x1b[u"); // restore cursor
2197
- }
2198
2309
 
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;
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
2208
2317
  }
2209
2318
 
2210
- function updateMenu() {
2319
+ function refreshUI() {
2320
+ eraseUI();
2321
+ // Compute menu items
2211
2322
  if (inputBuffer.startsWith("/")) {
2212
2323
  var filter = inputBuffer.toLowerCase();
2213
2324
  menuItems = COMMAND_DESCRIPTIONS.filter(function(c) {
2214
2325
  return c.cmd.includes(filter) || c.desc.toLowerCase().includes(filter.slice(1));
2215
2326
  });
2216
- if (menuItems.length > 0) {
2217
- menuVisible = true;
2218
- menuSelected = Math.min(menuSelected, menuItems.length - 1);
2219
- renderMenu();
2220
- } else {
2221
- clearMenu();
2222
- }
2327
+ menuVisible = menuItems.length > 0;
2328
+ if (menuVisible) menuSelected = Math.min(menuSelected, menuItems.length - 1);
2223
2329
  } else {
2224
- if (menuVisible) clearMenu();
2330
+ menuVisible = false;
2331
+ menuItems = [];
2225
2332
  }
2333
+ drawUI();
2226
2334
  }
2227
2335
 
2228
2336
  function drawPrompt() {
2229
2337
  if (processing) return;
2230
2338
  console.log(""); // spacing
2231
- renderInputBox();
2232
- updateMenu();
2339
+ drawnLines = 0;
2340
+ refreshUI();
2233
2341
  }
2234
2342
 
2235
2343
  async function processInput(input) {
2236
2344
  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");
2345
+ eraseUI();
2241
2346
 
2242
2347
  if (input.startsWith("!")) {
2243
2348
  cmdShell(input.slice(1).trim());
@@ -2277,20 +2382,11 @@ async function main() {
2277
2382
  process.stdin.on("data", function(key) {
2278
2383
  if (processing) return;
2279
2384
 
2280
- // Ctrl+C
2281
- if (key === "\x03") {
2385
+ // Ctrl+C / Ctrl+D — exit
2386
+ if (key === "\x03" || key === "\x04") {
2282
2387
  session.history = chatHistory.slice(-50);
2283
2388
  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();
2389
+ eraseUI();
2294
2390
  console.log(C.dim + "\nBye." + C.reset);
2295
2391
  process.exit(0);
2296
2392
  }
@@ -2298,7 +2394,6 @@ async function main() {
2298
2394
  // Enter
2299
2395
  if (key === "\r" || key === "\n") {
2300
2396
  var input = inputBuffer.trim();
2301
- // If menu is visible and a command is selected, use that
2302
2397
  if (menuVisible && menuItems.length > 0 && inputBuffer.startsWith("/") && !inputBuffer.includes(" ")) {
2303
2398
  input = menuItems[menuSelected].cmd;
2304
2399
  }
@@ -2314,9 +2409,7 @@ async function main() {
2314
2409
  if (menuVisible && menuItems.length > 0) {
2315
2410
  inputBuffer = menuItems[menuSelected].cmd + " ";
2316
2411
  cursorPos = inputBuffer.length;
2317
- clearMenu();
2318
- renderInputBox();
2319
- updateMenu();
2412
+ refreshUI();
2320
2413
  }
2321
2414
  return;
2322
2415
  }
@@ -2326,37 +2419,34 @@ async function main() {
2326
2419
  if (inputBuffer.length > 0) {
2327
2420
  inputBuffer = inputBuffer.slice(0, -1);
2328
2421
  cursorPos = inputBuffer.length;
2329
- clearMenu();
2330
- renderInputBox();
2331
- updateMenu();
2422
+ menuSelected = 0;
2423
+ refreshUI();
2332
2424
  }
2333
2425
  return;
2334
2426
  }
2335
2427
 
2336
- // Arrow keys (escape sequences)
2428
+ // Arrow keys
2337
2429
  if (key === "\x1b[A") { // Up
2338
2430
  if (menuVisible && menuItems.length > 0) {
2339
2431
  menuSelected = Math.max(0, menuSelected - 1);
2340
- clearMenu();
2341
- renderMenu();
2432
+ refreshUI();
2342
2433
  } else if (inputHistory.length > 0) {
2343
2434
  historyIdx = Math.min(historyIdx + 1, inputHistory.length - 1);
2344
2435
  inputBuffer = inputHistory[historyIdx];
2345
2436
  cursorPos = inputBuffer.length;
2346
- renderInputBox();
2437
+ refreshUI();
2347
2438
  }
2348
2439
  return;
2349
2440
  }
2350
2441
  if (key === "\x1b[B") { // Down
2351
2442
  if (menuVisible && menuItems.length > 0) {
2352
2443
  menuSelected = Math.min(menuItems.length - 1, menuSelected + 1);
2353
- clearMenu();
2354
- renderMenu();
2444
+ refreshUI();
2355
2445
  } else if (historyIdx > 0) {
2356
2446
  historyIdx--;
2357
2447
  inputBuffer = inputHistory[historyIdx];
2358
2448
  cursorPos = inputBuffer.length;
2359
- renderInputBox();
2449
+ refreshUI();
2360
2450
  }
2361
2451
  return;
2362
2452
  }
@@ -2364,8 +2454,8 @@ async function main() {
2364
2454
  // Escape — close menu
2365
2455
  if (key === "\x1b" || key === "\x1b\x1b") {
2366
2456
  if (menuVisible) {
2367
- clearMenu();
2368
2457
  menuVisible = false;
2458
+ refreshUI();
2369
2459
  }
2370
2460
  return;
2371
2461
  }
@@ -2376,9 +2466,8 @@ async function main() {
2376
2466
  // Regular character
2377
2467
  inputBuffer += key;
2378
2468
  cursorPos = inputBuffer.length;
2379
- clearMenu();
2380
- renderInputBox();
2381
- updateMenu();
2469
+ menuSelected = 0;
2470
+ refreshUI();
2382
2471
  });
2383
2472
  }
2384
2473
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "BLUN King CLI — Premium KI Console",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"