blun-king-cli 2.0.1 → 2.1.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 +191 -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,103 @@ 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
+ // Status bar (permission mode + model + workdir)
2293
+ var permMode = getSetting("permissions.defaultMode") || "ask";
2294
+ var permIcon = permMode === "allow" ? "\u2714" : permMode === "deny" ? "\u2718" : "\u2753";
2295
+ var permColor = permMode === "allow" ? C.green : permMode === "deny" ? C.red : C.yellow;
2296
+ var modelName = config.model || "default";
2297
+ var wdShort = config.workdir ? path.basename(config.workdir) : "~";
2298
+ var statusLine = permColor + " " + permIcon + " " + permMode + C.reset + C.dim + " \u2502 " + C.reset +
2299
+ C.cyan + modelName + C.reset + C.dim + " \u2502 " + C.reset +
2300
+ C.dim + wdShort + C.reset;
2301
+ process.stdout.write(statusLine + "\x1b[K\n"); lines++;
2302
+
2303
+ // Menu
2304
+ if (menuVisible && menuItems.length > 0) {
2305
+ var show = menuItems.slice(0, 8);
2306
+ show.forEach(function(item, i) {
2307
+ var prefix = i === menuSelected ? C.green + C.bold + " \u276F " : " ";
2308
+ var cmdStr = (item.cmd + " ".repeat(20)).slice(0, 18);
2309
+ var descStr = item.desc.slice(0, getTermWidth() - 28);
2310
+ var cmdColor = i === menuSelected ? C.green + C.bold : C.green;
2311
+ var descColor = i === menuSelected ? C.brightWhite : C.white;
2312
+ process.stdout.write(prefix + cmdColor + cmdStr + descColor + descStr + C.reset + "\x1b[K\n");
2313
+ lines++;
2314
+ });
2315
+ if (menuItems.length > 8) {
2316
+ process.stdout.write(C.dim + " ... " + (menuItems.length - 8) + " more" + C.reset + "\x1b[K\n");
2317
+ lines++;
2318
+ }
2195
2319
  }
2196
- process.stdout.write("\x1b[u"); // restore cursor
2197
- }
2198
2320
 
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;
2321
+ drawnLines = lines;
2322
+
2323
+ // Position cursor inside the box (line 2, after text)
2324
+ process.stdout.write("\x1b[" + lines + "A"); // back to top
2325
+ process.stdout.write("\x1b[1B"); // down to content line
2326
+ process.stdout.write("\r\x1b[" + (4 + displayText.length) + "C"); // right to cursor pos
2327
+ process.stdout.write("\x1b[?25h"); // show cursor
2208
2328
  }
2209
2329
 
2210
- function updateMenu() {
2330
+ function refreshUI() {
2331
+ eraseUI();
2332
+ // Compute menu items
2211
2333
  if (inputBuffer.startsWith("/")) {
2212
2334
  var filter = inputBuffer.toLowerCase();
2213
2335
  menuItems = COMMAND_DESCRIPTIONS.filter(function(c) {
2214
2336
  return c.cmd.includes(filter) || c.desc.toLowerCase().includes(filter.slice(1));
2215
2337
  });
2216
- if (menuItems.length > 0) {
2217
- menuVisible = true;
2218
- menuSelected = Math.min(menuSelected, menuItems.length - 1);
2219
- renderMenu();
2220
- } else {
2221
- clearMenu();
2222
- }
2338
+ menuVisible = menuItems.length > 0;
2339
+ if (menuVisible) menuSelected = Math.min(menuSelected, menuItems.length - 1);
2223
2340
  } else {
2224
- if (menuVisible) clearMenu();
2341
+ menuVisible = false;
2342
+ menuItems = [];
2225
2343
  }
2344
+ drawUI();
2226
2345
  }
2227
2346
 
2228
2347
  function drawPrompt() {
2229
2348
  if (processing) return;
2230
2349
  console.log(""); // spacing
2231
- renderInputBox();
2232
- updateMenu();
2350
+ drawnLines = 0;
2351
+ refreshUI();
2233
2352
  }
2234
2353
 
2235
2354
  async function processInput(input) {
2236
2355
  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");
2356
+ eraseUI();
2241
2357
 
2242
2358
  if (input.startsWith("!")) {
2243
2359
  cmdShell(input.slice(1).trim());
@@ -2277,20 +2393,11 @@ async function main() {
2277
2393
  process.stdin.on("data", function(key) {
2278
2394
  if (processing) return;
2279
2395
 
2280
- // Ctrl+C
2281
- if (key === "\x03") {
2396
+ // Ctrl+C / Ctrl+D — exit
2397
+ if (key === "\x03" || key === "\x04") {
2282
2398
  session.history = chatHistory.slice(-50);
2283
2399
  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();
2400
+ eraseUI();
2294
2401
  console.log(C.dim + "\nBye." + C.reset);
2295
2402
  process.exit(0);
2296
2403
  }
@@ -2298,7 +2405,6 @@ async function main() {
2298
2405
  // Enter
2299
2406
  if (key === "\r" || key === "\n") {
2300
2407
  var input = inputBuffer.trim();
2301
- // If menu is visible and a command is selected, use that
2302
2408
  if (menuVisible && menuItems.length > 0 && inputBuffer.startsWith("/") && !inputBuffer.includes(" ")) {
2303
2409
  input = menuItems[menuSelected].cmd;
2304
2410
  }
@@ -2314,9 +2420,7 @@ async function main() {
2314
2420
  if (menuVisible && menuItems.length > 0) {
2315
2421
  inputBuffer = menuItems[menuSelected].cmd + " ";
2316
2422
  cursorPos = inputBuffer.length;
2317
- clearMenu();
2318
- renderInputBox();
2319
- updateMenu();
2423
+ refreshUI();
2320
2424
  }
2321
2425
  return;
2322
2426
  }
@@ -2326,37 +2430,34 @@ async function main() {
2326
2430
  if (inputBuffer.length > 0) {
2327
2431
  inputBuffer = inputBuffer.slice(0, -1);
2328
2432
  cursorPos = inputBuffer.length;
2329
- clearMenu();
2330
- renderInputBox();
2331
- updateMenu();
2433
+ menuSelected = 0;
2434
+ refreshUI();
2332
2435
  }
2333
2436
  return;
2334
2437
  }
2335
2438
 
2336
- // Arrow keys (escape sequences)
2439
+ // Arrow keys
2337
2440
  if (key === "\x1b[A") { // Up
2338
2441
  if (menuVisible && menuItems.length > 0) {
2339
2442
  menuSelected = Math.max(0, menuSelected - 1);
2340
- clearMenu();
2341
- renderMenu();
2443
+ refreshUI();
2342
2444
  } else if (inputHistory.length > 0) {
2343
2445
  historyIdx = Math.min(historyIdx + 1, inputHistory.length - 1);
2344
2446
  inputBuffer = inputHistory[historyIdx];
2345
2447
  cursorPos = inputBuffer.length;
2346
- renderInputBox();
2448
+ refreshUI();
2347
2449
  }
2348
2450
  return;
2349
2451
  }
2350
2452
  if (key === "\x1b[B") { // Down
2351
2453
  if (menuVisible && menuItems.length > 0) {
2352
2454
  menuSelected = Math.min(menuItems.length - 1, menuSelected + 1);
2353
- clearMenu();
2354
- renderMenu();
2455
+ refreshUI();
2355
2456
  } else if (historyIdx > 0) {
2356
2457
  historyIdx--;
2357
2458
  inputBuffer = inputHistory[historyIdx];
2358
2459
  cursorPos = inputBuffer.length;
2359
- renderInputBox();
2460
+ refreshUI();
2360
2461
  }
2361
2462
  return;
2362
2463
  }
@@ -2364,8 +2465,8 @@ async function main() {
2364
2465
  // Escape — close menu
2365
2466
  if (key === "\x1b" || key === "\x1b\x1b") {
2366
2467
  if (menuVisible) {
2367
- clearMenu();
2368
2468
  menuVisible = false;
2469
+ refreshUI();
2369
2470
  }
2370
2471
  return;
2371
2472
  }
@@ -2376,9 +2477,8 @@ async function main() {
2376
2477
  // Regular character
2377
2478
  inputBuffer += key;
2378
2479
  cursorPos = inputBuffer.length;
2379
- clearMenu();
2380
- renderInputBox();
2381
- updateMenu();
2480
+ menuSelected = 0;
2481
+ refreshUI();
2382
2482
  });
2383
2483
  }
2384
2484
 
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.1",
4
4
  "description": "BLUN King CLI — Premium KI Console",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"