blun-king-cli 2.9.1 → 3.0.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 +172 -21
- package/package.json +1 -1
package/bin/blun.js
CHANGED
|
@@ -233,6 +233,63 @@ function printAnswer(answer, meta) {
|
|
|
233
233
|
console.log("");
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
// Streaming typewriter output — word by word
|
|
237
|
+
async function streamAnswer(answer, meta) {
|
|
238
|
+
console.log("");
|
|
239
|
+
console.log(C.green + C.bold + " " + BOX.bot + " BLUN King" + C.reset + (meta ? C.gray + " " + BOX.dot + " " + meta + C.reset : ""));
|
|
240
|
+
console.log(C.green + " " + BOX.h.repeat(40) + C.reset);
|
|
241
|
+
|
|
242
|
+
var lines = answer.split("\n");
|
|
243
|
+
var inCode = false;
|
|
244
|
+
var delay = 15; // ms per word
|
|
245
|
+
|
|
246
|
+
for (var i = 0; i < lines.length; i++) {
|
|
247
|
+
var line = lines[i];
|
|
248
|
+
|
|
249
|
+
if (line.startsWith("```")) {
|
|
250
|
+
inCode = !inCode;
|
|
251
|
+
if (inCode) {
|
|
252
|
+
var lang = line.slice(3).trim();
|
|
253
|
+
console.log(C.dim + " " + BOX.tl + BOX.h.repeat(38) + (lang ? " " + lang + " " : "") + C.reset);
|
|
254
|
+
} else {
|
|
255
|
+
console.log(C.dim + " " + BOX.bl + BOX.h.repeat(38) + C.reset);
|
|
256
|
+
}
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (inCode) {
|
|
261
|
+
console.log(C.cyan + " " + BOX.v + " " + line + C.reset);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Determine prefix and color
|
|
266
|
+
var prefix = " ";
|
|
267
|
+
var color = C.reset;
|
|
268
|
+
var text = line;
|
|
269
|
+
|
|
270
|
+
if (line.startsWith("# ")) { prefix = "\n" + C.bold + C.yellow + " \u2588 "; text = line.slice(2); color = C.reset + "\n"; }
|
|
271
|
+
else if (line.startsWith("## ")) { prefix = "\n" + C.bold + C.magenta + " \u25B6 "; text = line.slice(3); color = C.reset; }
|
|
272
|
+
else if (line.startsWith("### ")) { prefix = C.bold + C.brightWhite + " \u25AA "; text = line.slice(4); color = C.reset; }
|
|
273
|
+
else if (line.match(/^\s*[-*] /)) { prefix = " " + C.green + "\u2022 " + C.reset; text = line.replace(/^\s*[-*] /, ""); }
|
|
274
|
+
else if (line.match(/^\s*\d+\.\s/)) { var m = line.match(/^(\s*\d+\.)\s(.*)/); prefix = " " + C.yellow + m[1] + C.reset + " "; text = m[2]; }
|
|
275
|
+
else if (line.trim() === "") { console.log(""); continue; }
|
|
276
|
+
|
|
277
|
+
// Stream words
|
|
278
|
+
var words = text.split(" ");
|
|
279
|
+
process.stdout.write(prefix);
|
|
280
|
+
for (var w = 0; w < words.length; w++) {
|
|
281
|
+
var word = words[w];
|
|
282
|
+
// Inline formatting
|
|
283
|
+
word = word.replace(/\*\*(.+?)\*\*/g, C.bold + C.brightWhite + "$1" + C.reset);
|
|
284
|
+
word = word.replace(/`(.+?)`/g, C.cyan + "$1" + C.reset);
|
|
285
|
+
process.stdout.write(word + (w < words.length - 1 ? " " : ""));
|
|
286
|
+
await new Promise(function(r) { setTimeout(r, delay); });
|
|
287
|
+
}
|
|
288
|
+
process.stdout.write(color + "\n");
|
|
289
|
+
}
|
|
290
|
+
console.log("");
|
|
291
|
+
}
|
|
292
|
+
|
|
236
293
|
function printUserMessage(msg) {
|
|
237
294
|
console.log("");
|
|
238
295
|
console.log(C.brightBlue + C.bold + " " + BOX.user + " Du" + C.reset);
|
|
@@ -568,7 +625,9 @@ async function sendChat(message) {
|
|
|
568
625
|
var tokInfo = C.dim + (inTok + outTok) + " tok" + C.reset;
|
|
569
626
|
var meta = resp.data.task_type + "/" + resp.data.role + " " + BOX.dot + " score: " + (q.score || "?") +
|
|
570
627
|
(q.retried ? " " + BOX.dot + " retried" : "") + " " + BOX.dot + " " + tokInfo;
|
|
571
|
-
|
|
628
|
+
|
|
629
|
+
// Streaming typewriter effect
|
|
630
|
+
await streamAnswer(resp.data.answer, meta);
|
|
572
631
|
|
|
573
632
|
// Auto Memory Dream Mode
|
|
574
633
|
dreamCounter++;
|
|
@@ -1463,56 +1522,143 @@ async function cmdAgent(args) {
|
|
|
1463
1522
|
var lastAnswer = "";
|
|
1464
1523
|
var goal = args;
|
|
1465
1524
|
var context = [];
|
|
1525
|
+
var workdir = config.workdir;
|
|
1526
|
+
|
|
1527
|
+
// Show thinking + keep input box
|
|
1528
|
+
console.log("");
|
|
1529
|
+
var thinkTimer = setInterval(function() {
|
|
1530
|
+
process.stdout.write("\x1b[s");
|
|
1531
|
+
if (uiStartRow > 0) process.stdout.write("\x1b[" + (uiStartRow + 1) + "A");
|
|
1532
|
+
var phase = loop === 0 ? "planning" : "step " + loop + "/" + maxLoops;
|
|
1533
|
+
process.stdout.write("\r\x1b[2K" + C.dim + " " + BOX.bot + " [" + phase + "] working..." + C.reset);
|
|
1534
|
+
process.stdout.write("\x1b[u");
|
|
1535
|
+
}, 300);
|
|
1536
|
+
processing = false;
|
|
1537
|
+
uiStartRow = -1;
|
|
1538
|
+
drawPrompt();
|
|
1539
|
+
processing = true;
|
|
1466
1540
|
|
|
1467
1541
|
while (loop < maxLoops) {
|
|
1468
1542
|
loop++;
|
|
1469
|
-
// Phase indicator
|
|
1470
|
-
var phase = loop === 1 ? "planning" : "step " + loop;
|
|
1471
|
-
process.stdout.write("\r\x1b[K" + C.dim + " " + BOX.bot + " [" + phase + "/" + maxLoops + "] working..." + C.reset);
|
|
1472
1543
|
|
|
1473
1544
|
var resp = await apiCall("POST", "/agent", {
|
|
1474
1545
|
goal: goal,
|
|
1546
|
+
workdir: workdir,
|
|
1475
1547
|
context: context.slice(-5),
|
|
1476
1548
|
verbose: false
|
|
1477
1549
|
});
|
|
1478
|
-
process.stdout.write("\r\x1b[K");
|
|
1479
1550
|
|
|
1480
1551
|
if (resp.status !== 200) {
|
|
1552
|
+
clearInterval(thinkTimer);
|
|
1553
|
+
eraseUI();
|
|
1554
|
+
process.stdout.write("\x1b[1A\r\x1b[2K");
|
|
1481
1555
|
printError("Step " + loop + " failed: " + (resp.data.error || "Error"));
|
|
1482
1556
|
break;
|
|
1483
1557
|
}
|
|
1484
1558
|
|
|
1485
1559
|
var d = resp.data;
|
|
1486
1560
|
lastAnswer = d.answer || "";
|
|
1487
|
-
|
|
1488
|
-
|
|
1561
|
+
|
|
1562
|
+
// Save files locally to workspace
|
|
1563
|
+
if (d.files && d.files.length > 0) {
|
|
1564
|
+
for (var fi = 0; fi < d.files.length; fi++) {
|
|
1565
|
+
var f = d.files[fi];
|
|
1566
|
+
var localPath = path.join(workdir, f.name);
|
|
1567
|
+
var localDir = path.dirname(localPath);
|
|
1568
|
+
if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
|
|
1569
|
+
// If file has content directly, write it
|
|
1570
|
+
if (f.content) {
|
|
1571
|
+
fs.writeFileSync(localPath, f.content, "utf8");
|
|
1572
|
+
f.localPath = localPath;
|
|
1573
|
+
} else if (f.download) {
|
|
1574
|
+
// Download from server
|
|
1575
|
+
try {
|
|
1576
|
+
var dlResp = await apiCall("GET", f.download);
|
|
1577
|
+
if (dlResp.status === 200) {
|
|
1578
|
+
var content = typeof dlResp.data === "string" ? dlResp.data : JSON.stringify(dlResp.data, null, 2);
|
|
1579
|
+
fs.writeFileSync(localPath, content, "utf8");
|
|
1580
|
+
f.localPath = localPath;
|
|
1581
|
+
}
|
|
1582
|
+
} catch(dlErr) {}
|
|
1583
|
+
}
|
|
1584
|
+
totalFiles.push(f);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
// If answer contains code blocks with filenames, extract and save them
|
|
1589
|
+
var codeBlockRe = /```(?:\w+)?\s*\n\/\/\s*(.+\.(?:html|css|js|json|md|txt|py))\n([\s\S]*?)```/g;
|
|
1590
|
+
var cbMatch;
|
|
1591
|
+
while ((cbMatch = codeBlockRe.exec(lastAnswer)) !== null) {
|
|
1592
|
+
var cbFile = cbMatch[1].trim();
|
|
1593
|
+
var cbContent = cbMatch[2];
|
|
1594
|
+
var cbPath = path.join(workdir, cbFile);
|
|
1595
|
+
var cbDir = path.dirname(cbPath);
|
|
1596
|
+
if (!fs.existsSync(cbDir)) fs.mkdirSync(cbDir, { recursive: true });
|
|
1597
|
+
fs.writeFileSync(cbPath, cbContent, "utf8");
|
|
1598
|
+
totalFiles.push({ name: cbFile, localPath: cbPath });
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
// Also extract filename: pattern like "**index.html**:" or "Datei: index.html"
|
|
1602
|
+
var fileHeaderRe = /(?:\*\*|Datei:\s*)([a-zA-Z0-9_\-/.]+\.(?:html|css|js|json|md|txt|py))\*?\*?:?\s*\n```(?:\w+)?\n([\s\S]*?)```/g;
|
|
1603
|
+
var fhMatch;
|
|
1604
|
+
while ((fhMatch = fileHeaderRe.exec(lastAnswer)) !== null) {
|
|
1605
|
+
var fhFile = fhMatch[1].trim();
|
|
1606
|
+
var fhContent = fhMatch[2];
|
|
1607
|
+
var fhPath = path.join(workdir, fhFile);
|
|
1608
|
+
var fhDir = path.dirname(fhPath);
|
|
1609
|
+
if (!fs.existsSync(fhDir)) fs.mkdirSync(fhDir, { recursive: true });
|
|
1610
|
+
if (!totalFiles.some(function(tf) { return tf.name === fhFile; })) {
|
|
1611
|
+
fs.writeFileSync(fhPath, fhContent, "utf8");
|
|
1612
|
+
totalFiles.push({ name: fhFile, localPath: fhPath });
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
context.push({ step: loop, answer: lastAnswer.slice(0, 500), files: totalFiles.length, workdir: workdir });
|
|
1489
1617
|
|
|
1490
1618
|
// Check if the agent says it's done
|
|
1491
1619
|
var doneSignals = ["done", "complete", "finished", "fertig", "erledigt", "all steps", "nothing left"];
|
|
1492
1620
|
var isDone = doneSignals.some(function(s) { return lastAnswer.toLowerCase().includes(s); });
|
|
1493
1621
|
|
|
1494
|
-
// Show brief step result
|
|
1495
|
-
console.log(C.dim + " [" + loop + "] " + C.reset + C.green + (d.steps_executed || 1) + " steps" +
|
|
1496
|
-
(d.files && d.files.length > 0 ? ", " + d.files.length + " files" : "") + C.reset +
|
|
1497
|
-
(isDone ? C.yellow + " ✓ done" + C.reset : ""));
|
|
1498
|
-
|
|
1499
1622
|
if (isDone) break;
|
|
1500
1623
|
|
|
1501
1624
|
// Feed result back as next goal
|
|
1502
|
-
goal = "Continue with the original goal: " + args +
|
|
1503
|
-
"\
|
|
1625
|
+
goal = "Continue with the original goal: " + args +
|
|
1626
|
+
"\nWorkspace: " + workdir +
|
|
1627
|
+
"\nFiles created so far: " + totalFiles.map(function(f) { return f.name; }).join(", ") +
|
|
1628
|
+
"\n\nLast step result: " + lastAnswer.slice(0, 1000) +
|
|
1629
|
+
"\n\nWrite the actual file contents as code blocks. If everything is done, say 'done'.";
|
|
1504
1630
|
}
|
|
1505
1631
|
|
|
1632
|
+
clearInterval(thinkTimer);
|
|
1633
|
+
eraseUI();
|
|
1634
|
+
process.stdout.write("\x1b[1A\r\x1b[2K");
|
|
1635
|
+
|
|
1506
1636
|
// Final summary
|
|
1507
1637
|
console.log("");
|
|
1508
|
-
var
|
|
1509
|
-
|
|
1638
|
+
var savedCount = totalFiles.filter(function(f) { return f.localPath; }).length;
|
|
1639
|
+
var meta = loop + " loops" + (totalFiles.length > 0 ? " " + BOX.dot + " " + savedCount + " files saved" : "");
|
|
1640
|
+
|
|
1641
|
+
// Token tracking
|
|
1642
|
+
var inTok = Math.ceil(args.length / 3.5);
|
|
1643
|
+
var outTok = Math.ceil(lastAnswer.length / 3.5);
|
|
1644
|
+
sessionCost.requests += loop;
|
|
1645
|
+
sessionCost.inputTokensEst += inTok * loop;
|
|
1646
|
+
sessionCost.outputTokensEst += outTok;
|
|
1647
|
+
|
|
1648
|
+
await streamAnswer(lastAnswer, meta);
|
|
1649
|
+
|
|
1510
1650
|
if (totalFiles.length > 0) {
|
|
1511
|
-
console.log(C.green + "
|
|
1512
|
-
totalFiles.forEach(function(f) {
|
|
1651
|
+
console.log(C.green + " 📁 Saved to workspace:" + C.reset);
|
|
1652
|
+
totalFiles.forEach(function(f) {
|
|
1653
|
+
if (f.localPath) console.log(" " + C.brightCyan + f.localPath + C.reset);
|
|
1654
|
+
else console.log(" " + C.dim + f.name + " (not saved)" + C.reset);
|
|
1655
|
+
});
|
|
1513
1656
|
console.log("");
|
|
1514
1657
|
}
|
|
1515
|
-
} catch(e) {
|
|
1658
|
+
} catch(e) {
|
|
1659
|
+
if (typeof thinkTimer !== "undefined") clearInterval(thinkTimer);
|
|
1660
|
+
printError(e.message);
|
|
1661
|
+
}
|
|
1516
1662
|
}
|
|
1517
1663
|
|
|
1518
1664
|
// ── Screenshot/Render (CLI wrapper) ──
|
|
@@ -2732,8 +2878,13 @@ async function main() {
|
|
|
2732
2878
|
await handleCommand(detected);
|
|
2733
2879
|
runHook("post", detected.split(/\s+/)[0].slice(1));
|
|
2734
2880
|
} else if (sessionMode === "agent") {
|
|
2735
|
-
// Agent mode:
|
|
2736
|
-
|
|
2881
|
+
// Agent mode: detect if question or task
|
|
2882
|
+
var isQuestion = /^(wo |was |wie |wer |wann |warum |kannst|hast|ist |sind |where|what|how|who|when|why|can you|do you|is |are |\?$)/i.test(input.trim());
|
|
2883
|
+
if (isQuestion) {
|
|
2884
|
+
await sendChat(input);
|
|
2885
|
+
} else {
|
|
2886
|
+
await cmdAgent(input);
|
|
2887
|
+
}
|
|
2737
2888
|
} else {
|
|
2738
2889
|
await sendChat(input);
|
|
2739
2890
|
}
|