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.
- package/bin/blun.js +181 -90
- 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(
|
|
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
|
|
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
|
-
|
|
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
|
-
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
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
process.stdout.write("\x1b[
|
|
2201
|
-
|
|
2202
|
-
|
|
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
|
|
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
|
-
|
|
2215
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2230
|
-
|
|
2339
|
+
drawnLines = 0;
|
|
2340
|
+
refreshUI();
|
|
2231
2341
|
}
|
|
2232
2342
|
|
|
2233
2343
|
async function processInput(input) {
|
|
2234
2344
|
processing = true;
|
|
2235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2328
|
-
|
|
2329
|
-
updateMenu();
|
|
2422
|
+
menuSelected = 0;
|
|
2423
|
+
refreshUI();
|
|
2330
2424
|
}
|
|
2331
2425
|
return;
|
|
2332
2426
|
}
|
|
2333
2427
|
|
|
2334
|
-
// Arrow keys
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2352
|
-
renderMenu();
|
|
2444
|
+
refreshUI();
|
|
2353
2445
|
} else if (historyIdx > 0) {
|
|
2354
2446
|
historyIdx--;
|
|
2355
2447
|
inputBuffer = inputHistory[historyIdx];
|
|
2356
2448
|
cursorPos = inputBuffer.length;
|
|
2357
|
-
|
|
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
|
-
|
|
2378
|
-
|
|
2379
|
-
updateMenu();
|
|
2469
|
+
menuSelected = 0;
|
|
2470
|
+
refreshUI();
|
|
2380
2471
|
});
|
|
2381
2472
|
}
|
|
2382
2473
|
|