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.
- package/bin/blun.js +191 -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,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
|
-
|
|
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
|
-
var
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
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
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
process.stdout.write("\x1b[
|
|
2203
|
-
|
|
2204
|
-
|
|
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
|
|
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
|
-
|
|
2217
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2232
|
-
|
|
2350
|
+
drawnLines = 0;
|
|
2351
|
+
refreshUI();
|
|
2233
2352
|
}
|
|
2234
2353
|
|
|
2235
2354
|
async function processInput(input) {
|
|
2236
2355
|
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");
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2330
|
-
|
|
2331
|
-
updateMenu();
|
|
2433
|
+
menuSelected = 0;
|
|
2434
|
+
refreshUI();
|
|
2332
2435
|
}
|
|
2333
2436
|
return;
|
|
2334
2437
|
}
|
|
2335
2438
|
|
|
2336
|
-
// Arrow keys
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2354
|
-
renderMenu();
|
|
2455
|
+
refreshUI();
|
|
2355
2456
|
} else if (historyIdx > 0) {
|
|
2356
2457
|
historyIdx--;
|
|
2357
2458
|
inputBuffer = inputHistory[historyIdx];
|
|
2358
2459
|
cursorPos = inputBuffer.length;
|
|
2359
|
-
|
|
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
|
-
|
|
2380
|
-
|
|
2381
|
-
updateMenu();
|
|
2480
|
+
menuSelected = 0;
|
|
2481
|
+
refreshUI();
|
|
2382
2482
|
});
|
|
2383
2483
|
}
|
|
2384
2484
|
|