blun-king-cli 3.0.6 → 4.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 +198 -62
  2. package/package.json +1 -1
package/bin/blun.js CHANGED
@@ -27,7 +27,7 @@ if (!fs.existsSync(SKILLS_DIR)) fs.mkdirSync(SKILLS_DIR, { recursive: true });
27
27
  function loadConfig() {
28
28
  var defaults = {
29
29
  auth: { type: "api_key", api_key: "", oauth_token: "", oauth_expires: null },
30
- api: { base_url: "http://176.9.158.30:3200", timeout: 60000, key: "" },
30
+ api: { base_url: "http://176.9.158.30:3200", timeout: 300000, key: "" },
31
31
  telegram: { enabled: false, bot_token: "", chat_id: "" },
32
32
  model: "blun-king-v100",
33
33
  workdir: process.cwd(),
@@ -581,63 +581,218 @@ var _globalEraseUI = null;
581
581
  var _globalUILines = 5; // how many lines the input box takes up
582
582
 
583
583
  // ── Chat ──
584
+ // ── SSE Stream Request ──
585
+ function apiStreamCall(endpoint, body) {
586
+ return new Promise(function(resolve, reject) {
587
+ var url = new URL(config.api.base_url + endpoint);
588
+ var isHttps = url.protocol === "https:";
589
+ var options = {
590
+ hostname: url.hostname, port: url.port, path: url.pathname,
591
+ method: "POST",
592
+ headers: { "Content-Type": "application/json" }
593
+ };
594
+ var authToken = config.api.key || config.auth.api_key;
595
+ if (authToken) options.headers["Authorization"] = "Bearer " + authToken;
596
+
597
+ var data = JSON.stringify(body);
598
+ options.headers["Content-Length"] = Buffer.byteLength(data);
599
+
600
+ var mod = isHttps ? https : http;
601
+ var req = mod.request(options, function(res) {
602
+ if (res.statusCode !== 200) {
603
+ var chunks = [];
604
+ res.on("data", function(c) { chunks.push(c); });
605
+ res.on("end", function() {
606
+ try { reject(new Error(JSON.parse(Buffer.concat(chunks).toString()).error || "API Error " + res.statusCode)); }
607
+ catch(e) { reject(new Error("API Error " + res.statusCode)); }
608
+ });
609
+ return;
610
+ }
611
+ resolve(res);
612
+ });
613
+ req.on("error", function(e) { reject(e); });
614
+ req.write(data);
615
+ req.end();
616
+ });
617
+ }
618
+
584
619
  async function sendChat(message) {
585
620
  try {
586
621
  printUserMessage(message);
587
622
 
588
- // Show thinking status line
623
+ // Show thinking animation
589
624
  var dots = 0;
590
625
  var thinkFrames = ["thinking", "thinking.", "thinking..", "thinking..."];
591
- var thinkText = C.dim + " " + BOX.bot + " thinking " + C.reset;
592
- process.stdout.write("\n" + thinkText + "\n");
593
-
594
- // Draw input box below thinking — user can type ahead
595
- if (_globalDrawPrompt) { processing = false; _globalDrawPrompt(); processing = true; }
596
-
597
- // Animate thinking: move up to thinking line, update, move back down
626
+ console.log("");
598
627
  var thinkTimer = setInterval(function() {
599
- process.stdout.write("\x1b[s"); // save cursor position
600
- // Move up past the input box to the thinking line
601
- var uiLines = _globalDrawPrompt ? (_globalUILines || 4) + 1 : 1;
602
- process.stdout.write("\x1b[" + uiLines + "A"); // move up
603
628
  process.stdout.write("\r\x1b[2K" + C.dim + " " + BOX.bot + " " + thinkFrames[dots % 4] + " " + C.reset);
604
- process.stdout.write("\x1b[u"); // restore cursor position
605
629
  dots++;
606
630
  }, 300);
607
631
 
608
- var resp = await apiCall("POST", "/chat", {
609
- message: message,
610
- history: chatHistory.slice(-10)
611
- });
632
+ // Try streaming first, fall back to non-streaming
633
+ var useStreaming = true;
634
+ var fullAnswer = "";
635
+ var receivedFiles = [];
636
+ var meta = "";
637
+ var headerPrinted = false;
612
638
 
613
- clearInterval(thinkTimer);
614
- // Erase the input box drawn during thinking
615
- if (_globalEraseUI) _globalEraseUI();
616
- // Clear thinking line: move up 1 from current pos, clear, move back
617
- process.stdout.write("\x1b[1A\r\x1b[2K\x1b[1B");
639
+ try {
640
+ var stream = await apiStreamCall("/chat/stream", {
641
+ message: message,
642
+ workdir: config.workdir,
643
+ history: chatHistory.slice(-10)
644
+ });
618
645
 
619
- if (resp.status !== 200) {
620
- printError(resp.data.error || "API Error " + resp.status);
621
- return;
646
+ clearInterval(thinkTimer);
647
+ process.stdout.write("\r\x1b[2K");
648
+
649
+ var buffer = "";
650
+ var tokenBuffer = "";
651
+ var flushTimer = null;
652
+
653
+ await new Promise(function(resolve, reject) {
654
+ stream.on("data", function(chunk) {
655
+ buffer += chunk.toString();
656
+ var lines = buffer.split("\n");
657
+ buffer = lines.pop() || "";
658
+
659
+ for (var i = 0; i < lines.length; i++) {
660
+ var line = lines[i].trim();
661
+ if (line.startsWith("event: ")) {
662
+ var eventType = line.slice(7);
663
+ // Next line should be data:
664
+ i++;
665
+ if (i < lines.length && lines[i].trim().startsWith("data: ")) {
666
+ var dataStr = lines[i].trim().slice(6);
667
+ try {
668
+ var eventData = JSON.parse(dataStr);
669
+ handleSSEEvent(eventType, eventData);
670
+ } catch(e) {}
671
+ }
672
+ }
673
+ }
674
+ });
675
+
676
+ stream.on("end", function() {
677
+ // Flush remaining token buffer
678
+ if (tokenBuffer.length > 0) {
679
+ process.stdout.write(tokenBuffer);
680
+ tokenBuffer = "";
681
+ }
682
+ resolve();
683
+ });
684
+
685
+ stream.on("error", function(e) { reject(e); });
686
+
687
+ function handleSSEEvent(type, data) {
688
+ switch(type) {
689
+ case "meta":
690
+ // Print header
691
+ if (!headerPrinted) {
692
+ console.log("");
693
+ console.log(C.green + C.bold + " " + BOX.bot + " BLUN King" + C.reset +
694
+ C.gray + " " + BOX.dot + " " + (data.task_type || "") + "/" + (data.role || "") + C.reset);
695
+ console.log(C.green + " " + BOX.h.repeat(40) + C.reset);
696
+ process.stdout.write(" ");
697
+ headerPrinted = true;
698
+ }
699
+ break;
700
+
701
+ case "token":
702
+ fullAnswer += data.text;
703
+ // Write token directly to stdout for live streaming
704
+ process.stdout.write(data.text);
705
+ break;
706
+
707
+ case "retry":
708
+ process.stdout.write("\n" + C.yellow + " ↻ retry: " + (data.reason || []).join(", ") + C.reset + "\n ");
709
+ fullAnswer = "";
710
+ break;
711
+
712
+ case "tool":
713
+ process.stdout.write("\n" + C.cyan + " ⚙ " + data.name + C.reset + "\n ");
714
+ break;
715
+
716
+ case "tool_result":
717
+ process.stdout.write(C.dim + " → " + (data.result || "").slice(0, 100) + C.reset + "\n ");
718
+ break;
719
+
720
+ case "files":
721
+ receivedFiles = data.files || [];
722
+ break;
723
+
724
+ case "done":
725
+ meta = "score: " + ((data.quality || {}).score || "?") +
726
+ (data.status ? " " + BOX.dot + " " + data.status : "");
727
+ break;
728
+
729
+ case "error":
730
+ process.stdout.write("\n" + C.red + " ERROR: " + data.error + C.reset + "\n");
731
+ break;
732
+ }
733
+ }
734
+ });
735
+
736
+ process.stdout.write("\n\n");
737
+
738
+ } catch(streamErr) {
739
+ // Fallback to non-streaming
740
+ useStreaming = false;
741
+ clearInterval(thinkTimer);
742
+ process.stdout.write("\r\x1b[2K");
743
+
744
+ var resp = await apiCall("POST", "/chat", {
745
+ message: message,
746
+ workdir: config.workdir,
747
+ history: chatHistory.slice(-10)
748
+ });
749
+
750
+ if (resp.status !== 200) {
751
+ printError(resp.data.error || "API Error " + resp.status);
752
+ return;
753
+ }
754
+
755
+ fullAnswer = resp.data.answer || "";
756
+ receivedFiles = resp.data.files || [];
757
+ var q = resp.data.quality || {};
758
+ meta = (resp.data.task_type || "") + "/" + (resp.data.role || "") + " " + BOX.dot + " score: " + (q.score || "?") +
759
+ (q.retried ? " " + BOX.dot + " retried" : "") +
760
+ (resp.data.status ? " " + BOX.dot + " " + resp.data.status : "");
761
+
762
+ await streamAnswer(fullAnswer, meta);
622
763
  }
623
764
 
624
765
  chatHistory.push({ role: "user", content: message });
625
- chatHistory.push({ role: "assistant", content: resp.data.answer });
766
+ chatHistory.push({ role: "assistant", content: fullAnswer });
626
767
 
627
768
  // Token tracking
628
769
  var inTok = Math.ceil(message.length / 3.5);
629
- var outTok = Math.ceil((resp.data.answer || "").length / 3.5);
770
+ var outTok = Math.ceil(fullAnswer.length / 3.5);
630
771
  sessionCost.requests++;
631
772
  sessionCost.inputTokensEst += inTok;
632
773
  sessionCost.outputTokensEst += outTok;
633
774
 
634
- var q = resp.data.quality || {};
635
- var tokInfo = C.dim + (inTok + outTok) + " tok" + C.reset;
636
- var meta = resp.data.task_type + "/" + resp.data.role + " " + BOX.dot + " score: " + (q.score || "?") +
637
- (q.retried ? " " + BOX.dot + " retried" : "") + " " + BOX.dot + " " + tokInfo;
775
+ // ── Save received files to local workdir ──
776
+ if (receivedFiles.length > 0) {
777
+ console.log(C.green + " 📁 Dateien gespeichert:" + C.reset);
778
+ for (var fi = 0; fi < receivedFiles.length; fi++) {
779
+ var f = receivedFiles[fi];
780
+ if (f.content && f.name) {
781
+ var localPath = path.join(config.workdir, f.name);
782
+ var localDir = path.dirname(localPath);
783
+ if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
784
+ fs.writeFileSync(localPath, f.content, "utf8");
785
+ console.log(" " + C.brightCyan + localPath + C.reset + C.dim + " (" + f.size + " bytes)" + C.reset);
786
+ }
787
+ }
788
+ console.log("");
789
+ }
638
790
 
639
- // Streaming typewriter effect
640
- await streamAnswer(resp.data.answer, meta);
791
+ // Show meta for streaming mode
792
+ if (useStreaming && meta) {
793
+ console.log(C.dim + " " + meta + C.reset);
794
+ console.log("");
795
+ }
641
796
 
642
797
  // Auto Memory Dream Mode
643
798
  dreamCounter++;
@@ -1534,17 +1689,11 @@ async function cmdAgent(args) {
1534
1689
  var context = [];
1535
1690
  var workdir = config.workdir;
1536
1691
 
1537
- // Show thinking status + input box below
1538
- process.stdout.write("\n" + C.dim + " " + BOX.bot + " [planning] working..." + C.reset + "\n");
1539
- if (_globalDrawPrompt) { processing = false; _globalDrawPrompt(); processing = true; }
1540
-
1692
+ // Show thinking status on single line
1693
+ console.log("");
1541
1694
  var thinkTimer = setInterval(function() {
1542
1695
  var phase = loop === 0 ? "planning" : "step " + loop + "/" + maxLoops;
1543
- process.stdout.write("\x1b[s");
1544
- var uiLines = _globalDrawPrompt ? (_globalUILines || 4) + 1 : 1;
1545
- process.stdout.write("\x1b[" + uiLines + "A");
1546
1696
  process.stdout.write("\r\x1b[2K" + C.dim + " " + BOX.bot + " [" + phase + "] working..." + C.reset);
1547
- process.stdout.write("\x1b[u");
1548
1697
  }, 300);
1549
1698
 
1550
1699
  while (loop < maxLoops) {
@@ -1559,8 +1708,7 @@ async function cmdAgent(args) {
1559
1708
 
1560
1709
  if (resp.status !== 200) {
1561
1710
  clearInterval(thinkTimer);
1562
- if (_globalEraseUI) _globalEraseUI();
1563
- process.stdout.write("\x1b[1A\r\x1b[2K\x1b[1B");
1711
+ process.stdout.write("\r\x1b[2K");
1564
1712
  printError("Step " + loop + " failed: " + (resp.data.error || "Error"));
1565
1713
  break;
1566
1714
  }
@@ -1568,27 +1716,16 @@ async function cmdAgent(args) {
1568
1716
  var d = resp.data;
1569
1717
  lastAnswer = d.answer || "";
1570
1718
 
1571
- // Save files locally to workspace
1719
+ // Save files locally to workspace — API now returns file contents
1572
1720
  if (d.files && d.files.length > 0) {
1573
1721
  for (var fi = 0; fi < d.files.length; fi++) {
1574
1722
  var f = d.files[fi];
1575
- var localPath = path.join(workdir, f.name);
1576
- var localDir = path.dirname(localPath);
1577
- if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
1578
- // If file has content directly, write it
1579
- if (f.content) {
1723
+ if (f.content && f.name) {
1724
+ var localPath = path.join(workdir, f.name);
1725
+ var localDir = path.dirname(localPath);
1726
+ if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
1580
1727
  fs.writeFileSync(localPath, f.content, "utf8");
1581
1728
  f.localPath = localPath;
1582
- } else if (f.download) {
1583
- // Download from server
1584
- try {
1585
- var dlResp = await apiCall("GET", f.download);
1586
- if (dlResp.status === 200) {
1587
- var content = typeof dlResp.data === "string" ? dlResp.data : JSON.stringify(dlResp.data, null, 2);
1588
- fs.writeFileSync(localPath, content, "utf8");
1589
- f.localPath = localPath;
1590
- }
1591
- } catch(dlErr) {}
1592
1729
  }
1593
1730
  totalFiles.push(f);
1594
1731
  }
@@ -1639,8 +1776,7 @@ async function cmdAgent(args) {
1639
1776
  }
1640
1777
 
1641
1778
  clearInterval(thinkTimer);
1642
- if (_globalEraseUI) _globalEraseUI();
1643
- process.stdout.write("\x1b[1A\r\x1b[2K\x1b[1B");
1779
+ process.stdout.write("\r\x1b[2K");
1644
1780
 
1645
1781
  // Final summary
1646
1782
  console.log("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "3.0.6",
3
+ "version": "4.0.0",
4
4
  "description": "BLUN King CLI — Premium KI Console",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"