blun-king-cli 3.0.7 → 4.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 +140 -35
  2. package/package.json +1 -1
package/bin/blun.js CHANGED
@@ -581,11 +581,46 @@ 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 on a single line (no input box during thinking — simpler, more reliable)
623
+ // Show thinking animation
589
624
  var dots = 0;
590
625
  var thinkFrames = ["thinking", "thinking.", "thinking..", "thinking..."];
591
626
  console.log("");
@@ -594,36 +629,117 @@ async function sendChat(message) {
594
629
  dots++;
595
630
  }, 300);
596
631
 
597
- var resp = await apiCall("POST", "/chat", {
598
- message: message,
599
- history: chatHistory.slice(-10)
600
- });
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;
601
638
 
602
- clearInterval(thinkTimer);
603
- process.stdout.write("\r\x1b[2K");
639
+ try {
640
+ var stream = await apiStreamCall("/chat/stream", {
641
+ message: message,
642
+ workdir: config.workdir,
643
+ history: chatHistory.slice(-10)
644
+ });
604
645
 
605
- if (resp.status !== 200) {
606
- printError(resp.data.error || "API Error " + resp.status);
607
- return;
646
+ // Collect tokens silently — only update thinking animation
647
+ var buffer = "";
648
+ var streamMeta = {};
649
+
650
+ await new Promise(function(resolve, reject) {
651
+ stream.on("data", function(chunk) {
652
+ buffer += chunk.toString();
653
+ var lines = buffer.split("\n");
654
+ buffer = lines.pop() || "";
655
+
656
+ for (var i = 0; i < lines.length; i++) {
657
+ var line = lines[i].trim();
658
+ if (line.startsWith("event: ")) {
659
+ var eventType = line.slice(7);
660
+ i++;
661
+ if (i < lines.length && lines[i].trim().startsWith("data: ")) {
662
+ var dataStr = lines[i].trim().slice(6);
663
+ try {
664
+ var eventData = JSON.parse(dataStr);
665
+ // Handle events silently — just collect data
666
+ if (eventType === "meta") streamMeta = eventData;
667
+ else if (eventType === "token") fullAnswer += eventData.text;
668
+ else if (eventType === "retry") fullAnswer = "";
669
+ else if (eventType === "files") receivedFiles = eventData.files || [];
670
+ else if (eventType === "done") {
671
+ meta = (streamMeta.task_type || "") + "/" + (streamMeta.role || "") + " " + BOX.dot +
672
+ " score: " + ((eventData.quality || {}).score || "?") +
673
+ (eventData.status ? " " + BOX.dot + " " + eventData.status : "");
674
+ }
675
+ } catch(e) {}
676
+ }
677
+ }
678
+ }
679
+ });
680
+
681
+ stream.on("end", function() { resolve(); });
682
+ stream.on("error", function(e) { reject(e); });
683
+ });
684
+
685
+ // Now stop thinking and show formatted answer
686
+ clearInterval(thinkTimer);
687
+ process.stdout.write("\r\x1b[2K");
688
+
689
+ await streamAnswer(fullAnswer, meta);
690
+
691
+ } catch(streamErr) {
692
+ // Fallback to non-streaming
693
+ useStreaming = false;
694
+ clearInterval(thinkTimer);
695
+ process.stdout.write("\r\x1b[2K");
696
+
697
+ var resp = await apiCall("POST", "/chat", {
698
+ message: message,
699
+ workdir: config.workdir,
700
+ history: chatHistory.slice(-10)
701
+ });
702
+
703
+ if (resp.status !== 200) {
704
+ printError(resp.data.error || "API Error " + resp.status);
705
+ return;
706
+ }
707
+
708
+ fullAnswer = resp.data.answer || "";
709
+ receivedFiles = resp.data.files || [];
710
+ var q = resp.data.quality || {};
711
+ meta = (resp.data.task_type || "") + "/" + (resp.data.role || "") + " " + BOX.dot + " score: " + (q.score || "?") +
712
+ (q.retried ? " " + BOX.dot + " retried" : "") +
713
+ (resp.data.status ? " " + BOX.dot + " " + resp.data.status : "");
714
+
715
+ await streamAnswer(fullAnswer, meta);
608
716
  }
609
717
 
610
718
  chatHistory.push({ role: "user", content: message });
611
- chatHistory.push({ role: "assistant", content: resp.data.answer });
719
+ chatHistory.push({ role: "assistant", content: fullAnswer });
612
720
 
613
721
  // Token tracking
614
722
  var inTok = Math.ceil(message.length / 3.5);
615
- var outTok = Math.ceil((resp.data.answer || "").length / 3.5);
723
+ var outTok = Math.ceil(fullAnswer.length / 3.5);
616
724
  sessionCost.requests++;
617
725
  sessionCost.inputTokensEst += inTok;
618
726
  sessionCost.outputTokensEst += outTok;
619
727
 
620
- var q = resp.data.quality || {};
621
- var tokInfo = C.dim + (inTok + outTok) + " tok" + C.reset;
622
- var meta = resp.data.task_type + "/" + resp.data.role + " " + BOX.dot + " score: " + (q.score || "?") +
623
- (q.retried ? " " + BOX.dot + " retried" : "") + " " + BOX.dot + " " + tokInfo;
624
-
625
- // Streaming typewriter effect
626
- await streamAnswer(resp.data.answer, meta);
728
+ // ── Save received files to local workdir ──
729
+ if (receivedFiles.length > 0) {
730
+ console.log(C.green + " 📁 Dateien gespeichert:" + C.reset);
731
+ for (var fi = 0; fi < receivedFiles.length; fi++) {
732
+ var f = receivedFiles[fi];
733
+ if (f.content && f.name) {
734
+ var localPath = path.join(config.workdir, f.name);
735
+ var localDir = path.dirname(localPath);
736
+ if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
737
+ fs.writeFileSync(localPath, f.content, "utf8");
738
+ console.log(" " + C.brightCyan + localPath + C.reset + C.dim + " (" + f.size + " bytes)" + C.reset);
739
+ }
740
+ }
741
+ console.log("");
742
+ }
627
743
 
628
744
  // Auto Memory Dream Mode
629
745
  dreamCounter++;
@@ -1547,27 +1663,16 @@ async function cmdAgent(args) {
1547
1663
  var d = resp.data;
1548
1664
  lastAnswer = d.answer || "";
1549
1665
 
1550
- // Save files locally to workspace
1666
+ // Save files locally to workspace — API now returns file contents
1551
1667
  if (d.files && d.files.length > 0) {
1552
1668
  for (var fi = 0; fi < d.files.length; fi++) {
1553
1669
  var f = d.files[fi];
1554
- var localPath = path.join(workdir, f.name);
1555
- var localDir = path.dirname(localPath);
1556
- if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
1557
- // If file has content directly, write it
1558
- if (f.content) {
1670
+ if (f.content && f.name) {
1671
+ var localPath = path.join(workdir, f.name);
1672
+ var localDir = path.dirname(localPath);
1673
+ if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
1559
1674
  fs.writeFileSync(localPath, f.content, "utf8");
1560
1675
  f.localPath = localPath;
1561
- } else if (f.download) {
1562
- // Download from server
1563
- try {
1564
- var dlResp = await apiCall("GET", f.download);
1565
- if (dlResp.status === 200) {
1566
- var content = typeof dlResp.data === "string" ? dlResp.data : JSON.stringify(dlResp.data, null, 2);
1567
- fs.writeFileSync(localPath, content, "utf8");
1568
- f.localPath = localPath;
1569
- }
1570
- } catch(dlErr) {}
1571
1676
  }
1572
1677
  totalFiles.push(f);
1573
1678
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "3.0.7",
3
+ "version": "4.0.1",
4
4
  "description": "BLUN King CLI — Premium KI Console",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"