blun-king-cli 3.0.7 → 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.
- package/bin/blun.js +192 -34
- 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
|
|
623
|
+
// Show thinking animation
|
|
589
624
|
var dots = 0;
|
|
590
625
|
var thinkFrames = ["thinking", "thinking.", "thinking..", "thinking..."];
|
|
591
626
|
console.log("");
|
|
@@ -594,36 +629,170 @@ async function sendChat(message) {
|
|
|
594
629
|
dots++;
|
|
595
630
|
}, 300);
|
|
596
631
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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
|
-
|
|
603
|
-
|
|
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
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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);
|
|
608
763
|
}
|
|
609
764
|
|
|
610
765
|
chatHistory.push({ role: "user", content: message });
|
|
611
|
-
chatHistory.push({ role: "assistant", content:
|
|
766
|
+
chatHistory.push({ role: "assistant", content: fullAnswer });
|
|
612
767
|
|
|
613
768
|
// Token tracking
|
|
614
769
|
var inTok = Math.ceil(message.length / 3.5);
|
|
615
|
-
var outTok = Math.ceil(
|
|
770
|
+
var outTok = Math.ceil(fullAnswer.length / 3.5);
|
|
616
771
|
sessionCost.requests++;
|
|
617
772
|
sessionCost.inputTokensEst += inTok;
|
|
618
773
|
sessionCost.outputTokensEst += outTok;
|
|
619
774
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
(
|
|
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
|
+
}
|
|
624
790
|
|
|
625
|
-
//
|
|
626
|
-
|
|
791
|
+
// Show meta for streaming mode
|
|
792
|
+
if (useStreaming && meta) {
|
|
793
|
+
console.log(C.dim + " " + meta + C.reset);
|
|
794
|
+
console.log("");
|
|
795
|
+
}
|
|
627
796
|
|
|
628
797
|
// Auto Memory Dream Mode
|
|
629
798
|
dreamCounter++;
|
|
@@ -1547,27 +1716,16 @@ async function cmdAgent(args) {
|
|
|
1547
1716
|
var d = resp.data;
|
|
1548
1717
|
lastAnswer = d.answer || "";
|
|
1549
1718
|
|
|
1550
|
-
// Save files locally to workspace
|
|
1719
|
+
// Save files locally to workspace — API now returns file contents
|
|
1551
1720
|
if (d.files && d.files.length > 0) {
|
|
1552
1721
|
for (var fi = 0; fi < d.files.length; fi++) {
|
|
1553
1722
|
var f = d.files[fi];
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
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 });
|
|
1559
1727
|
fs.writeFileSync(localPath, f.content, "utf8");
|
|
1560
1728
|
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
1729
|
}
|
|
1572
1730
|
totalFiles.push(f);
|
|
1573
1731
|
}
|