blun-king-cli 2.9.0 → 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.
Files changed (2) hide show
  1. package/bin/blun.js +188 -24
  2. 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);
@@ -520,13 +577,23 @@ async function handleCommand(input) {
520
577
  async function sendChat(message) {
521
578
  try {
522
579
  printUserMessage(message);
523
- // Animated thinking
580
+ // Animated thinking + keep input box visible
524
581
  var dots = 0;
525
582
  var thinkFrames = ["thinking", "thinking.", "thinking..", "thinking..."];
583
+ console.log(""); // line for thinking text
526
584
  var thinkTimer = setInterval(function() {
527
- process.stdout.write("\r" + C.dim + " " + BOX.bot + " " + thinkFrames[dots % 4] + " " + C.reset);
585
+ process.stdout.write("\x1b[s"); // save cursor
586
+ // Move to thinking line (above the UI)
587
+ if (uiStartRow > 0) process.stdout.write("\x1b[" + (uiStartRow + 1) + "A");
588
+ process.stdout.write("\r\x1b[2K" + C.dim + " " + BOX.bot + " " + thinkFrames[dots % 4] + " " + C.reset);
589
+ process.stdout.write("\x1b[u"); // restore cursor
528
590
  dots++;
529
591
  }, 300);
592
+ // Redraw input box below thinking line
593
+ processing = false;
594
+ uiStartRow = -1;
595
+ drawPrompt();
596
+ processing = true;
530
597
 
531
598
  var resp = await apiCall("POST", "/chat", {
532
599
  message: message,
@@ -534,7 +601,10 @@ async function sendChat(message) {
534
601
  });
535
602
 
536
603
  clearInterval(thinkTimer);
537
- process.stdout.write("\r" + " ".repeat(40) + "\r");
604
+ // Erase UI before printing answer
605
+ eraseUI();
606
+ // Clear thinking line
607
+ process.stdout.write("\x1b[1A\r\x1b[2K");
538
608
 
539
609
  if (resp.status !== 200) {
540
610
  printError(resp.data.error || "API Error " + resp.status);
@@ -555,7 +625,9 @@ async function sendChat(message) {
555
625
  var tokInfo = C.dim + (inTok + outTok) + " tok" + C.reset;
556
626
  var meta = resp.data.task_type + "/" + resp.data.role + " " + BOX.dot + " score: " + (q.score || "?") +
557
627
  (q.retried ? " " + BOX.dot + " retried" : "") + " " + BOX.dot + " " + tokInfo;
558
- printAnswer(resp.data.answer, meta);
628
+
629
+ // Streaming typewriter effect
630
+ await streamAnswer(resp.data.answer, meta);
559
631
 
560
632
  // Auto Memory Dream Mode
561
633
  dreamCounter++;
@@ -1450,56 +1522,143 @@ async function cmdAgent(args) {
1450
1522
  var lastAnswer = "";
1451
1523
  var goal = args;
1452
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;
1453
1540
 
1454
1541
  while (loop < maxLoops) {
1455
1542
  loop++;
1456
- // Phase indicator
1457
- var phase = loop === 1 ? "planning" : "step " + loop;
1458
- process.stdout.write("\r\x1b[K" + C.dim + " " + BOX.bot + " [" + phase + "/" + maxLoops + "] working..." + C.reset);
1459
1543
 
1460
1544
  var resp = await apiCall("POST", "/agent", {
1461
1545
  goal: goal,
1546
+ workdir: workdir,
1462
1547
  context: context.slice(-5),
1463
1548
  verbose: false
1464
1549
  });
1465
- process.stdout.write("\r\x1b[K");
1466
1550
 
1467
1551
  if (resp.status !== 200) {
1552
+ clearInterval(thinkTimer);
1553
+ eraseUI();
1554
+ process.stdout.write("\x1b[1A\r\x1b[2K");
1468
1555
  printError("Step " + loop + " failed: " + (resp.data.error || "Error"));
1469
1556
  break;
1470
1557
  }
1471
1558
 
1472
1559
  var d = resp.data;
1473
1560
  lastAnswer = d.answer || "";
1474
- if (d.files && d.files.length > 0) totalFiles = totalFiles.concat(d.files);
1475
- context.push({ step: loop, answer: lastAnswer.slice(0, 500), files: (d.files || []).length });
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 });
1476
1617
 
1477
1618
  // Check if the agent says it's done
1478
1619
  var doneSignals = ["done", "complete", "finished", "fertig", "erledigt", "all steps", "nothing left"];
1479
1620
  var isDone = doneSignals.some(function(s) { return lastAnswer.toLowerCase().includes(s); });
1480
1621
 
1481
- // Show brief step result
1482
- console.log(C.dim + " [" + loop + "] " + C.reset + C.green + (d.steps_executed || 1) + " steps" +
1483
- (d.files && d.files.length > 0 ? ", " + d.files.length + " files" : "") + C.reset +
1484
- (isDone ? C.yellow + " ✓ done" + C.reset : ""));
1485
-
1486
1622
  if (isDone) break;
1487
1623
 
1488
1624
  // Feed result back as next goal
1489
- goal = "Continue with the original goal: " + args + "\n\nLast step result: " + lastAnswer.slice(0, 1000) +
1490
- "\n\nWhat is the next step? If everything is done, say 'done'.";
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'.";
1491
1630
  }
1492
1631
 
1632
+ clearInterval(thinkTimer);
1633
+ eraseUI();
1634
+ process.stdout.write("\x1b[1A\r\x1b[2K");
1635
+
1493
1636
  // Final summary
1494
1637
  console.log("");
1495
- var meta = loop + " loops" + (totalFiles.length > 0 ? " " + BOX.dot + " " + totalFiles.length + " files total" : "");
1496
- printAnswer(lastAnswer, meta);
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
+
1497
1650
  if (totalFiles.length > 0) {
1498
- console.log(C.green + " Files:" + C.reset);
1499
- totalFiles.forEach(function(f) { console.log(" " + C.brightCyan + f.name + C.reset + " \u2192 " + config.api.base_url + f.download); });
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
+ });
1500
1656
  console.log("");
1501
1657
  }
1502
- } catch(e) { printError(e.message); }
1658
+ } catch(e) {
1659
+ if (typeof thinkTimer !== "undefined") clearInterval(thinkTimer);
1660
+ printError(e.message);
1661
+ }
1503
1662
  }
1504
1663
 
1505
1664
  // ── Screenshot/Render (CLI wrapper) ──
@@ -2719,8 +2878,13 @@ async function main() {
2719
2878
  await handleCommand(detected);
2720
2879
  runHook("post", detected.split(/\s+/)[0].slice(1));
2721
2880
  } else if (sessionMode === "agent") {
2722
- // Agent mode: auto-route to autonomous agent
2723
- await cmdAgent(input);
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
+ }
2724
2888
  } else {
2725
2889
  await sendChat(input);
2726
2890
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "2.9.0",
3
+ "version": "3.0.0",
4
4
  "description": "BLUN King CLI — Premium KI Console",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"