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.
- package/bin/blun.js +180 -91
- 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
|
|
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
|
-
|
|
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
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
process.stdout.write(C.brightBlue + " " +
|
|
2168
|
-
//
|
|
2169
|
-
process.stdout.write("\
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
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
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
process.stdout.write("\x1b[
|
|
2203
|
-
|
|
2204
|
-
|
|
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
|
|
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
|
-
|
|
2217
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2232
|
-
|
|
2339
|
+
drawnLines = 0;
|
|
2340
|
+
refreshUI();
|
|
2233
2341
|
}
|
|
2234
2342
|
|
|
2235
2343
|
async function processInput(input) {
|
|
2236
2344
|
processing = true;
|
|
2237
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2330
|
-
|
|
2331
|
-
updateMenu();
|
|
2422
|
+
menuSelected = 0;
|
|
2423
|
+
refreshUI();
|
|
2332
2424
|
}
|
|
2333
2425
|
return;
|
|
2334
2426
|
}
|
|
2335
2427
|
|
|
2336
|
-
// Arrow keys
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2354
|
-
renderMenu();
|
|
2444
|
+
refreshUI();
|
|
2355
2445
|
} else if (historyIdx > 0) {
|
|
2356
2446
|
historyIdx--;
|
|
2357
2447
|
inputBuffer = inputHistory[historyIdx];
|
|
2358
2448
|
cursorPos = inputBuffer.length;
|
|
2359
|
-
|
|
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
|
-
|
|
2380
|
-
|
|
2381
|
-
updateMenu();
|
|
2469
|
+
menuSelected = 0;
|
|
2470
|
+
refreshUI();
|
|
2382
2471
|
});
|
|
2383
2472
|
}
|
|
2384
2473
|
|