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.
Files changed (2) hide show
  1. package/bin/blun.js +172 -21
  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);
@@ -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
- printAnswer(resp.data.answer, meta);
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
- if (d.files && d.files.length > 0) totalFiles = totalFiles.concat(d.files);
1488
- 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 });
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 + "\n\nLast step result: " + lastAnswer.slice(0, 1000) +
1503
- "\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'.";
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 meta = loop + " loops" + (totalFiles.length > 0 ? " " + BOX.dot + " " + totalFiles.length + " files total" : "");
1509
- 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
+
1510
1650
  if (totalFiles.length > 0) {
1511
- console.log(C.green + " Files:" + C.reset);
1512
- 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
+ });
1513
1656
  console.log("");
1514
1657
  }
1515
- } catch(e) { printError(e.message); }
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: auto-route to autonomous agent
2736
- 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
+ }
2737
2888
  } else {
2738
2889
  await sendChat(input);
2739
2890
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "2.9.1",
3
+ "version": "3.0.0",
4
4
  "description": "BLUN King CLI — Premium KI Console",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"