blun-king-cli 2.9.1 → 3.0.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.
Files changed (2) hide show
  1. package/bin/blun.js +164 -37
  2. package/package.json +1 -1
package/bin/blun.js CHANGED
@@ -233,10 +233,66 @@ 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);
239
- console.log(C.brightBlue + " " + BOX.h.repeat(40) + C.reset);
240
296
  console.log(C.brightWhite + " " + msg + C.reset);
241
297
  }
242
298
 
@@ -520,23 +576,13 @@ async function handleCommand(input) {
520
576
  async function sendChat(message) {
521
577
  try {
522
578
  printUserMessage(message);
523
- // Animated thinking + keep input box visible
579
+ // Animated thinking
524
580
  var dots = 0;
525
581
  var thinkFrames = ["thinking", "thinking.", "thinking..", "thinking..."];
526
- console.log(""); // line for thinking text
527
582
  var thinkTimer = setInterval(function() {
528
- process.stdout.write("\x1b[s"); // save cursor
529
- // Move to thinking line (above the UI)
530
- if (uiStartRow > 0) process.stdout.write("\x1b[" + (uiStartRow + 1) + "A");
531
583
  process.stdout.write("\r\x1b[2K" + C.dim + " " + BOX.bot + " " + thinkFrames[dots % 4] + " " + C.reset);
532
- process.stdout.write("\x1b[u"); // restore cursor
533
584
  dots++;
534
585
  }, 300);
535
- // Redraw input box below thinking line
536
- processing = false;
537
- uiStartRow = -1;
538
- drawPrompt();
539
- processing = true;
540
586
 
541
587
  var resp = await apiCall("POST", "/chat", {
542
588
  message: message,
@@ -544,10 +590,7 @@ async function sendChat(message) {
544
590
  });
545
591
 
546
592
  clearInterval(thinkTimer);
547
- // Erase UI before printing answer
548
- eraseUI();
549
- // Clear thinking line
550
- process.stdout.write("\x1b[1A\r\x1b[2K");
593
+ process.stdout.write("\r\x1b[2K");
551
594
 
552
595
  if (resp.status !== 200) {
553
596
  printError(resp.data.error || "API Error " + resp.status);
@@ -568,7 +611,9 @@ async function sendChat(message) {
568
611
  var tokInfo = C.dim + (inTok + outTok) + " tok" + C.reset;
569
612
  var meta = resp.data.task_type + "/" + resp.data.role + " " + BOX.dot + " score: " + (q.score || "?") +
570
613
  (q.retried ? " " + BOX.dot + " retried" : "") + " " + BOX.dot + " " + tokInfo;
571
- printAnswer(resp.data.answer, meta);
614
+
615
+ // Streaming typewriter effect
616
+ await streamAnswer(resp.data.answer, meta);
572
617
 
573
618
  // Auto Memory Dream Mode
574
619
  dreamCounter++;
@@ -1463,56 +1508,133 @@ async function cmdAgent(args) {
1463
1508
  var lastAnswer = "";
1464
1509
  var goal = args;
1465
1510
  var context = [];
1511
+ var workdir = config.workdir;
1512
+
1513
+ // Show thinking animation
1514
+ var thinkTimer = setInterval(function() {
1515
+ var phase = loop === 0 ? "planning" : "step " + loop + "/" + maxLoops;
1516
+ process.stdout.write("\r\x1b[2K" + C.dim + " " + BOX.bot + " [" + phase + "] working..." + C.reset);
1517
+ }, 300);
1466
1518
 
1467
1519
  while (loop < maxLoops) {
1468
1520
  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
1521
 
1473
1522
  var resp = await apiCall("POST", "/agent", {
1474
1523
  goal: goal,
1524
+ workdir: workdir,
1475
1525
  context: context.slice(-5),
1476
1526
  verbose: false
1477
1527
  });
1478
- process.stdout.write("\r\x1b[K");
1479
1528
 
1480
1529
  if (resp.status !== 200) {
1530
+ clearInterval(thinkTimer);
1531
+ process.stdout.write("\r\x1b[2K");
1481
1532
  printError("Step " + loop + " failed: " + (resp.data.error || "Error"));
1482
1533
  break;
1483
1534
  }
1484
1535
 
1485
1536
  var d = resp.data;
1486
1537
  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 });
1538
+
1539
+ // Save files locally to workspace
1540
+ if (d.files && d.files.length > 0) {
1541
+ for (var fi = 0; fi < d.files.length; fi++) {
1542
+ var f = d.files[fi];
1543
+ var localPath = path.join(workdir, f.name);
1544
+ var localDir = path.dirname(localPath);
1545
+ if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
1546
+ // If file has content directly, write it
1547
+ if (f.content) {
1548
+ fs.writeFileSync(localPath, f.content, "utf8");
1549
+ f.localPath = localPath;
1550
+ } else if (f.download) {
1551
+ // Download from server
1552
+ try {
1553
+ var dlResp = await apiCall("GET", f.download);
1554
+ if (dlResp.status === 200) {
1555
+ var content = typeof dlResp.data === "string" ? dlResp.data : JSON.stringify(dlResp.data, null, 2);
1556
+ fs.writeFileSync(localPath, content, "utf8");
1557
+ f.localPath = localPath;
1558
+ }
1559
+ } catch(dlErr) {}
1560
+ }
1561
+ totalFiles.push(f);
1562
+ }
1563
+ }
1564
+
1565
+ // If answer contains code blocks with filenames, extract and save them
1566
+ var codeBlockRe = /```(?:\w+)?\s*\n\/\/\s*(.+\.(?:html|css|js|json|md|txt|py))\n([\s\S]*?)```/g;
1567
+ var cbMatch;
1568
+ while ((cbMatch = codeBlockRe.exec(lastAnswer)) !== null) {
1569
+ var cbFile = cbMatch[1].trim();
1570
+ var cbContent = cbMatch[2];
1571
+ var cbPath = path.join(workdir, cbFile);
1572
+ var cbDir = path.dirname(cbPath);
1573
+ if (!fs.existsSync(cbDir)) fs.mkdirSync(cbDir, { recursive: true });
1574
+ fs.writeFileSync(cbPath, cbContent, "utf8");
1575
+ totalFiles.push({ name: cbFile, localPath: cbPath });
1576
+ }
1577
+
1578
+ // Also extract filename: pattern like "**index.html**:" or "Datei: index.html"
1579
+ var fileHeaderRe = /(?:\*\*|Datei:\s*)([a-zA-Z0-9_\-/.]+\.(?:html|css|js|json|md|txt|py))\*?\*?:?\s*\n```(?:\w+)?\n([\s\S]*?)```/g;
1580
+ var fhMatch;
1581
+ while ((fhMatch = fileHeaderRe.exec(lastAnswer)) !== null) {
1582
+ var fhFile = fhMatch[1].trim();
1583
+ var fhContent = fhMatch[2];
1584
+ var fhPath = path.join(workdir, fhFile);
1585
+ var fhDir = path.dirname(fhPath);
1586
+ if (!fs.existsSync(fhDir)) fs.mkdirSync(fhDir, { recursive: true });
1587
+ if (!totalFiles.some(function(tf) { return tf.name === fhFile; })) {
1588
+ fs.writeFileSync(fhPath, fhContent, "utf8");
1589
+ totalFiles.push({ name: fhFile, localPath: fhPath });
1590
+ }
1591
+ }
1592
+
1593
+ context.push({ step: loop, answer: lastAnswer.slice(0, 500), files: totalFiles.length, workdir: workdir });
1489
1594
 
1490
1595
  // Check if the agent says it's done
1491
1596
  var doneSignals = ["done", "complete", "finished", "fertig", "erledigt", "all steps", "nothing left"];
1492
1597
  var isDone = doneSignals.some(function(s) { return lastAnswer.toLowerCase().includes(s); });
1493
1598
 
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
1599
  if (isDone) break;
1500
1600
 
1501
1601
  // 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'.";
1602
+ goal = "Continue with the original goal: " + args +
1603
+ "\nWorkspace: " + workdir +
1604
+ "\nFiles created so far: " + totalFiles.map(function(f) { return f.name; }).join(", ") +
1605
+ "\n\nLast step result: " + lastAnswer.slice(0, 1000) +
1606
+ "\n\nWrite the actual file contents as code blocks. If everything is done, say 'done'.";
1504
1607
  }
1505
1608
 
1609
+ clearInterval(thinkTimer);
1610
+ process.stdout.write("\r\x1b[2K");
1611
+
1506
1612
  // Final summary
1507
1613
  console.log("");
1508
- var meta = loop + " loops" + (totalFiles.length > 0 ? " " + BOX.dot + " " + totalFiles.length + " files total" : "");
1509
- printAnswer(lastAnswer, meta);
1614
+ var savedCount = totalFiles.filter(function(f) { return f.localPath; }).length;
1615
+ var meta = loop + " loops" + (totalFiles.length > 0 ? " " + BOX.dot + " " + savedCount + " files saved" : "");
1616
+
1617
+ // Token tracking
1618
+ var inTok = Math.ceil(args.length / 3.5);
1619
+ var outTok = Math.ceil(lastAnswer.length / 3.5);
1620
+ sessionCost.requests += loop;
1621
+ sessionCost.inputTokensEst += inTok * loop;
1622
+ sessionCost.outputTokensEst += outTok;
1623
+
1624
+ await streamAnswer(lastAnswer, meta);
1625
+
1510
1626
  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); });
1627
+ console.log(C.green + " 📁 Saved to workspace:" + C.reset);
1628
+ totalFiles.forEach(function(f) {
1629
+ if (f.localPath) console.log(" " + C.brightCyan + f.localPath + C.reset);
1630
+ else console.log(" " + C.dim + f.name + " (not saved)" + C.reset);
1631
+ });
1513
1632
  console.log("");
1514
1633
  }
1515
- } catch(e) { printError(e.message); }
1634
+ } catch(e) {
1635
+ if (typeof thinkTimer !== "undefined") clearInterval(thinkTimer);
1636
+ printError(e.message);
1637
+ }
1516
1638
  }
1517
1639
 
1518
1640
  // ── Screenshot/Render (CLI wrapper) ──
@@ -2732,8 +2854,13 @@ async function main() {
2732
2854
  await handleCommand(detected);
2733
2855
  runHook("post", detected.split(/\s+/)[0].slice(1));
2734
2856
  } else if (sessionMode === "agent") {
2735
- // Agent mode: auto-route to autonomous agent
2736
- await cmdAgent(input);
2857
+ // Agent mode: detect if question or task
2858
+ 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());
2859
+ if (isQuestion) {
2860
+ await sendChat(input);
2861
+ } else {
2862
+ await cmdAgent(input);
2863
+ }
2737
2864
  } else {
2738
2865
  await sendChat(input);
2739
2866
  }
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.1",
4
4
  "description": "BLUN King CLI — Premium KI Console",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"