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.
- package/bin/blun.js +198 -62
- 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:
|
|
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
|
|
623
|
+
// Show thinking animation
|
|
589
624
|
var dots = 0;
|
|
590
625
|
var thinkFrames = ["thinking", "thinking.", "thinking..", "thinking..."];
|
|
591
|
-
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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:
|
|
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(
|
|
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
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
(
|
|
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
|
-
//
|
|
640
|
-
|
|
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
|
|
1538
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
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
|
-
|
|
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("");
|