blun-king-cli 5.3.5 → 5.3.7
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/blun-cli.js +265 -22
- package/package.json +1 -1
- package/runtime.js +15 -1
package/blun-cli.js
CHANGED
|
@@ -112,6 +112,105 @@ function apiCall(method, endpoint, body) {
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
function downloadArtifactToWorkdir(downloadPath, filename) {
|
|
116
|
+
return new Promise(function(resolve, reject) {
|
|
117
|
+
if (!downloadPath || !filename) return resolve(null);
|
|
118
|
+
|
|
119
|
+
var url = new URL(config.api.base_url + downloadPath);
|
|
120
|
+
var isHttps = url.protocol === "https:";
|
|
121
|
+
var options = {
|
|
122
|
+
hostname: url.hostname,
|
|
123
|
+
port: url.port,
|
|
124
|
+
path: url.pathname + (url.search || ""),
|
|
125
|
+
method: "GET",
|
|
126
|
+
headers: {},
|
|
127
|
+
timeout: config.api.timeout
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
var authToken = config.api.key || config.auth.api_key;
|
|
131
|
+
if (authToken) {
|
|
132
|
+
options.headers["Authorization"] = "Bearer " + authToken;
|
|
133
|
+
} else if (config.auth.type === "oauth" && config.auth.oauth_token) {
|
|
134
|
+
options.headers["Authorization"] = "Bearer " + config.auth.oauth_token;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
var mod = isHttps ? https : http;
|
|
138
|
+
var req = mod.request(options, function(res) {
|
|
139
|
+
if (res.statusCode !== 200) {
|
|
140
|
+
var errChunks = [];
|
|
141
|
+
res.on("data", function(c) { errChunks.push(c); });
|
|
142
|
+
res.on("end", function() {
|
|
143
|
+
reject(new Error("Artifact download failed: HTTP " + res.statusCode + (errChunks.length ? " " + Buffer.concat(errChunks).toString() : "")));
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
var localPath = path.join(config.workdir, path.basename(filename));
|
|
149
|
+
var localDir = path.dirname(localPath);
|
|
150
|
+
if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
|
|
151
|
+
var out = fs.createWriteStream(localPath);
|
|
152
|
+
|
|
153
|
+
res.pipe(out);
|
|
154
|
+
out.on("finish", function() {
|
|
155
|
+
out.close(function() {
|
|
156
|
+
try {
|
|
157
|
+
var stat = fs.statSync(localPath);
|
|
158
|
+
resolve({
|
|
159
|
+
name: path.basename(localPath),
|
|
160
|
+
path: localPath,
|
|
161
|
+
size: stat.size
|
|
162
|
+
});
|
|
163
|
+
} catch (statErr) {
|
|
164
|
+
reject(statErr);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
out.on("error", function(err) {
|
|
169
|
+
out.destroy();
|
|
170
|
+
reject(err);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
req.on("error", function(e) { reject(e); });
|
|
175
|
+
req.on("timeout", function() { req.destroy(); reject(new Error("Artifact download timeout")); });
|
|
176
|
+
req.end();
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function saveApiOutputs(receivedFiles, artifact) {
|
|
181
|
+
var saved = [];
|
|
182
|
+
var seen = new Set();
|
|
183
|
+
|
|
184
|
+
for (var fi = 0; fi < (receivedFiles || []).length; fi++) {
|
|
185
|
+
var f = receivedFiles[fi];
|
|
186
|
+
var relativeName = f && (f.name || f.path);
|
|
187
|
+
if (f && f.content && relativeName) {
|
|
188
|
+
var localPath = path.join(config.workdir, relativeName);
|
|
189
|
+
var localDir = path.dirname(localPath);
|
|
190
|
+
if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
|
|
191
|
+
fs.writeFileSync(localPath, f.content, "utf8");
|
|
192
|
+
var fileInfo = {
|
|
193
|
+
name: relativeName,
|
|
194
|
+
path: localPath,
|
|
195
|
+
size: typeof f.size === "number" ? f.size : Buffer.byteLength(String(f.content), "utf8")
|
|
196
|
+
};
|
|
197
|
+
saved.push(fileInfo);
|
|
198
|
+
seen.add(path.basename(relativeName));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (artifact && artifact.download && artifact.filename && !seen.has(path.basename(artifact.filename))) {
|
|
203
|
+
try {
|
|
204
|
+
var downloaded = await downloadArtifactToWorkdir(artifact.download, artifact.filename);
|
|
205
|
+
if (downloaded) saved.push(downloaded);
|
|
206
|
+
} catch (artifactErr) {
|
|
207
|
+
printError("Artefakt konnte nicht lokal gespeichert werden: " + artifactErr.message);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return saved;
|
|
212
|
+
}
|
|
213
|
+
|
|
115
214
|
// ── Chat History ──
|
|
116
215
|
var chatHistory = [];
|
|
117
216
|
var lastCreatedFiles = []; // Track files from last response for follow-up context
|
|
@@ -556,6 +655,11 @@ async function handleCommand(input) {
|
|
|
556
655
|
await cmdAgent(args);
|
|
557
656
|
break;
|
|
558
657
|
|
|
658
|
+
case "/tg":
|
|
659
|
+
case "/telegram":
|
|
660
|
+
await cmdTelegram(args);
|
|
661
|
+
break;
|
|
662
|
+
|
|
559
663
|
case "/screenshot":
|
|
560
664
|
await cmdScreenshot(args);
|
|
561
665
|
break;
|
|
@@ -635,6 +739,7 @@ async function sendChat(message) {
|
|
|
635
739
|
|
|
636
740
|
var fullAnswer = "";
|
|
637
741
|
var receivedFiles = [];
|
|
742
|
+
var receivedArtifact = null;
|
|
638
743
|
var meta = "";
|
|
639
744
|
|
|
640
745
|
try {
|
|
@@ -655,6 +760,7 @@ async function sendChat(message) {
|
|
|
655
760
|
|
|
656
761
|
fullAnswer = resp.data.answer || "";
|
|
657
762
|
receivedFiles = resp.data.files || [];
|
|
763
|
+
receivedArtifact = resp.data.artifact || null;
|
|
658
764
|
var q = resp.data.quality || {};
|
|
659
765
|
meta = (resp.data.task_type || "") + "/" + (resp.data.role || "") + " " + BOX.dot + " score: " + (q.score || "?") +
|
|
660
766
|
(q.retried ? " " + BOX.dot + " retried" : "") +
|
|
@@ -679,19 +785,14 @@ async function sendChat(message) {
|
|
|
679
785
|
sessionCost.outputTokensEst += outTok;
|
|
680
786
|
|
|
681
787
|
// ── Save received files to local workdir ──
|
|
682
|
-
|
|
788
|
+
var savedOutputs = await saveApiOutputs(receivedFiles, receivedArtifact);
|
|
789
|
+
if (savedOutputs.length > 0) {
|
|
683
790
|
console.log(C.green + " \uD83D\uDCC1 Dateien gespeichert:" + C.reset);
|
|
684
791
|
lastCreatedFiles = [];
|
|
685
|
-
for (var fi = 0; fi <
|
|
686
|
-
var f =
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
var localDir = path.dirname(localPath);
|
|
690
|
-
if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
|
|
691
|
-
fs.writeFileSync(localPath, f.content, "utf8");
|
|
692
|
-
console.log(" " + C.brightCyan + localPath + C.reset + C.dim + " (" + f.size + " bytes)" + C.reset);
|
|
693
|
-
lastCreatedFiles.push({ name: f.name, path: localPath, size: f.size });
|
|
694
|
-
}
|
|
792
|
+
for (var fi = 0; fi < savedOutputs.length; fi++) {
|
|
793
|
+
var f = savedOutputs[fi];
|
|
794
|
+
console.log(" " + C.brightCyan + f.path + C.reset + C.dim + " (" + f.size + " bytes)" + C.reset);
|
|
795
|
+
lastCreatedFiles.push({ name: f.name, path: f.path, size: f.size });
|
|
695
796
|
}
|
|
696
797
|
console.log("");
|
|
697
798
|
}
|
|
@@ -1004,6 +1105,21 @@ function cmdSet(args) {
|
|
|
1004
1105
|
break;
|
|
1005
1106
|
|
|
1006
1107
|
default:
|
|
1108
|
+
// Handle plugin.X.Y settings
|
|
1109
|
+
if (key && key.startsWith("plugin.")) {
|
|
1110
|
+
var pluginParts = key.split(".");
|
|
1111
|
+
if (pluginParts.length === 3) {
|
|
1112
|
+
var pName = pluginParts[1];
|
|
1113
|
+
var pField = pluginParts[2];
|
|
1114
|
+
var plugins = loadPlugins();
|
|
1115
|
+
if (!plugins[pName]) { printError("Plugin '" + pName + "' not installed. /plugin add " + pName); break; }
|
|
1116
|
+
if (!plugins[pName].config) plugins[pName].config = {};
|
|
1117
|
+
plugins[pName].config[pField] = val;
|
|
1118
|
+
savePlugins(plugins);
|
|
1119
|
+
printSuccess("Plugin " + pName + "." + pField + " = " + (pField.includes("token") ? "***" + val.slice(-6) : val));
|
|
1120
|
+
break;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1007
1123
|
printError("Unknown setting. Use /settings to see all options.");
|
|
1008
1124
|
}
|
|
1009
1125
|
}
|
|
@@ -1542,6 +1658,8 @@ function cmdPlugin(args) {
|
|
|
1542
1658
|
if (!plugins[parts[1]]) { printError("Plugin not found: " + parts[1]); return; }
|
|
1543
1659
|
plugins[parts[1]].running = true;
|
|
1544
1660
|
savePlugins(plugins);
|
|
1661
|
+
// Actually start built-in plugins
|
|
1662
|
+
if (parts[1] === "telegram") { ensureTgBot(); }
|
|
1545
1663
|
printSuccess("Plugin '" + parts[1] + "' started.");
|
|
1546
1664
|
|
|
1547
1665
|
} else if (action === "stop" && parts[1]) {
|
|
@@ -1555,6 +1673,134 @@ function cmdPlugin(args) {
|
|
|
1555
1673
|
}
|
|
1556
1674
|
}
|
|
1557
1675
|
|
|
1676
|
+
// ── Telegram Plugin ──
|
|
1677
|
+
var _tgBot = null;
|
|
1678
|
+
var _tgMessages = [];
|
|
1679
|
+
var _tgMaxMessages = 50;
|
|
1680
|
+
|
|
1681
|
+
function getTgConfig() {
|
|
1682
|
+
var plugins = loadPlugins();
|
|
1683
|
+
var tg = plugins.telegram;
|
|
1684
|
+
if (!tg || !tg.config) return null;
|
|
1685
|
+
return tg.config;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
function ensureTgBot() {
|
|
1689
|
+
if (_tgBot) return _tgBot;
|
|
1690
|
+
var cfg = getTgConfig();
|
|
1691
|
+
if (!cfg || !cfg.bot_token) {
|
|
1692
|
+
printError("Telegram not configured. Run: /plugin add telegram");
|
|
1693
|
+
printInfo("Then set token: /set plugin.telegram.bot_token <YOUR_BOT_TOKEN>");
|
|
1694
|
+
printInfo("And chat ID: /set plugin.telegram.chat_id <YOUR_CHAT_ID>");
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
try {
|
|
1698
|
+
var TelegramBot = require("node-telegram-bot-api");
|
|
1699
|
+
_tgBot = new TelegramBot(cfg.bot_token, { polling: true });
|
|
1700
|
+
_tgBot.on("message", function(msg) {
|
|
1701
|
+
_tgMessages.push({
|
|
1702
|
+
id: msg.message_id,
|
|
1703
|
+
from: msg.from ? (msg.from.first_name || msg.from.username || "?") : "?",
|
|
1704
|
+
text: msg.text || "(media)",
|
|
1705
|
+
date: new Date(msg.date * 1000).toLocaleTimeString(),
|
|
1706
|
+
chat_id: String(msg.chat.id)
|
|
1707
|
+
});
|
|
1708
|
+
if (_tgMessages.length > _tgMaxMessages) _tgMessages.shift();
|
|
1709
|
+
});
|
|
1710
|
+
printSuccess("Telegram Bot connected.");
|
|
1711
|
+
return _tgBot;
|
|
1712
|
+
} catch(e) {
|
|
1713
|
+
printError("Telegram connection failed: " + e.message);
|
|
1714
|
+
return null;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
async function cmdTelegram(args) {
|
|
1719
|
+
var parts = (args || "").trim().split(/\s+/);
|
|
1720
|
+
var action = parts[0] || "status";
|
|
1721
|
+
|
|
1722
|
+
if (action === "status") {
|
|
1723
|
+
var cfg = getTgConfig();
|
|
1724
|
+
if (!cfg || !cfg.bot_token) {
|
|
1725
|
+
printError("Telegram not configured.");
|
|
1726
|
+
printInfo(" /plugin add telegram");
|
|
1727
|
+
printInfo(" /set plugin.telegram.bot_token <TOKEN>");
|
|
1728
|
+
printInfo(" /set plugin.telegram.chat_id <CHAT_ID>");
|
|
1729
|
+
} else {
|
|
1730
|
+
console.log("");
|
|
1731
|
+
console.log(C.bold + " Telegram Plugin" + C.reset);
|
|
1732
|
+
console.log(" Token: " + C.green + (cfg.bot_token ? "***" + cfg.bot_token.slice(-6) : "not set") + C.reset);
|
|
1733
|
+
console.log(" Chat: " + C.green + (cfg.chat_id || "not set") + C.reset);
|
|
1734
|
+
console.log(" Bot: " + (_tgBot ? C.green + "connected" : C.red + "disconnected") + C.reset);
|
|
1735
|
+
console.log(" Messages buffered: " + _tgMessages.length);
|
|
1736
|
+
console.log("");
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
} else if (action === "connect" || action === "start") {
|
|
1740
|
+
ensureTgBot();
|
|
1741
|
+
|
|
1742
|
+
} else if (action === "send") {
|
|
1743
|
+
var cfg = getTgConfig();
|
|
1744
|
+
if (!cfg || !cfg.chat_id) { printError("No chat_id set. /set plugin.telegram.chat_id <ID>"); return; }
|
|
1745
|
+
var bot = ensureTgBot();
|
|
1746
|
+
if (!bot) return;
|
|
1747
|
+
var text = parts.slice(1).join(" ");
|
|
1748
|
+
if (!text) { printError("Usage: /tg send <message>"); return; }
|
|
1749
|
+
try {
|
|
1750
|
+
await bot.sendMessage(cfg.chat_id, text);
|
|
1751
|
+
printSuccess("Message sent.");
|
|
1752
|
+
} catch(e) {
|
|
1753
|
+
printError("Send failed: " + e.message);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
} else if (action === "read" || action === "messages" || action === "inbox") {
|
|
1757
|
+
if (_tgMessages.length === 0) {
|
|
1758
|
+
printInfo("No messages yet. Connect first: /tg connect");
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
console.log("");
|
|
1762
|
+
console.log(C.bold + " Last " + Math.min(_tgMessages.length, 20) + " messages:" + C.reset);
|
|
1763
|
+
_tgMessages.slice(-20).forEach(function(m) {
|
|
1764
|
+
console.log(C.dim + " [" + m.date + "] " + C.reset + C.brightCyan + m.from + C.reset + ": " + m.text);
|
|
1765
|
+
});
|
|
1766
|
+
console.log("");
|
|
1767
|
+
|
|
1768
|
+
} else if (action === "disconnect" || action === "stop") {
|
|
1769
|
+
if (_tgBot) {
|
|
1770
|
+
_tgBot.stopPolling();
|
|
1771
|
+
_tgBot = null;
|
|
1772
|
+
printSuccess("Telegram disconnected.");
|
|
1773
|
+
} else {
|
|
1774
|
+
printInfo("Not connected.");
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
} else if (action === "file" || action === "photo") {
|
|
1778
|
+
var cfg = getTgConfig();
|
|
1779
|
+
if (!cfg || !cfg.chat_id) { printError("No chat_id set."); return; }
|
|
1780
|
+
var bot = ensureTgBot();
|
|
1781
|
+
if (!bot) return;
|
|
1782
|
+
var filePath = parts.slice(1).join(" ");
|
|
1783
|
+
if (!filePath || !fs.existsSync(filePath)) { printError("File not found: " + filePath); return; }
|
|
1784
|
+
try {
|
|
1785
|
+
await bot.sendDocument(cfg.chat_id, filePath);
|
|
1786
|
+
printSuccess("File sent: " + path.basename(filePath));
|
|
1787
|
+
} catch(e) {
|
|
1788
|
+
printError("Send failed: " + e.message);
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
} else {
|
|
1792
|
+
console.log("");
|
|
1793
|
+
console.log(C.bold + " Telegram Commands:" + C.reset);
|
|
1794
|
+
console.log(" /tg status Show connection status");
|
|
1795
|
+
console.log(" /tg connect Connect to Telegram");
|
|
1796
|
+
console.log(" /tg send <msg> Send a message");
|
|
1797
|
+
console.log(" /tg read Show received messages");
|
|
1798
|
+
console.log(" /tg file <path> Send a file/photo");
|
|
1799
|
+
console.log(" /tg disconnect Disconnect");
|
|
1800
|
+
console.log("");
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1558
1804
|
function cmdPermissions(args) {
|
|
1559
1805
|
var perms = loadPermissions();
|
|
1560
1806
|
if (!args) {
|
|
@@ -1619,17 +1865,14 @@ async function cmdAgent(args) {
|
|
|
1619
1865
|
lastAnswer = d.answer || "";
|
|
1620
1866
|
|
|
1621
1867
|
// Save files locally to workspace — API now returns file contents
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
f.localPath = localPath;
|
|
1631
|
-
}
|
|
1632
|
-
totalFiles.push(f);
|
|
1868
|
+
var savedStepOutputs = await saveApiOutputs(d.files || [], d.artifact || null);
|
|
1869
|
+
if (savedStepOutputs.length > 0) {
|
|
1870
|
+
for (var fi = 0; fi < savedStepOutputs.length; fi++) {
|
|
1871
|
+
totalFiles.push({
|
|
1872
|
+
name: savedStepOutputs[fi].name,
|
|
1873
|
+
localPath: savedStepOutputs[fi].path,
|
|
1874
|
+
size: savedStepOutputs[fi].size
|
|
1875
|
+
});
|
|
1633
1876
|
}
|
|
1634
1877
|
}
|
|
1635
1878
|
|
package/package.json
CHANGED
package/runtime.js
CHANGED
|
@@ -56,6 +56,10 @@ function clampConfidence(score) {
|
|
|
56
56
|
return Math.max(0, Math.min(1, score / 8));
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function hasResolvableUrl(raw) {
|
|
60
|
+
return /(https?:\/\/\S+)|(?:^|[\s<(])(?:www\.)?[a-z0-9.-]+\.[a-z]{2,}(?:\/[^\s)>"']*)?/i.test(String(raw || ""));
|
|
61
|
+
}
|
|
62
|
+
|
|
59
63
|
function detectIntent(raw, session) {
|
|
60
64
|
const text = normalize(raw);
|
|
61
65
|
const activeTask = session?.activeTask || null;
|
|
@@ -226,6 +230,12 @@ function detectIntent(raw, session) {
|
|
|
226
230
|
const wantsDebug = hasAny(text, debugTerms);
|
|
227
231
|
const wantsOperator = hasAny(text, operatorTerms);
|
|
228
232
|
const wantsCoding = hasAny(text, codingTerms);
|
|
233
|
+
const hasUrl = hasResolvableUrl(raw);
|
|
234
|
+
const screenshotStorageComplaint =
|
|
235
|
+
activeTask?.type === "browser_capture" &&
|
|
236
|
+
wantsScreenshot &&
|
|
237
|
+
!hasUrl &&
|
|
238
|
+
/\b(ordner|datei|gespeichert|download|liegt|finde|fehlt|nicht im ordner|nicht da)\b/i.test(text);
|
|
229
239
|
|
|
230
240
|
const scores = {
|
|
231
241
|
installation:
|
|
@@ -233,7 +243,7 @@ function detectIntent(raw, session) {
|
|
|
233
243
|
scoreApproxWords(text, ["installier", "installieren", "telegram", "telegramm", "plugin", "skill", "playwright"], 1.5) +
|
|
234
244
|
scoreApproxPhrases(text, ["telegram bot", "playwright"], 2),
|
|
235
245
|
browser_capture:
|
|
236
|
-
(wantsWebsite ? 2 : scoreApproxWords(text, ["website", "webseite"], 1.5)) +
|
|
246
|
+
((wantsWebsite || hasUrl) ? 2 : scoreApproxWords(text, ["website", "webseite"], 1.5)) +
|
|
237
247
|
(wantsScreenshot ? 3 : scoreApproxWords(text, ["screenshot", "screen"], 1.5)),
|
|
238
248
|
website_builder:
|
|
239
249
|
(wantsWebsite ? 2 : scoreApproxWords(text, ["website", "webseite", "landingpage", "homepage"], 1.5)) +
|
|
@@ -256,6 +266,10 @@ function detectIntent(raw, session) {
|
|
|
256
266
|
return { kind: "followup", activeTask, scores, confidence: 1 };
|
|
257
267
|
}
|
|
258
268
|
|
|
269
|
+
if (screenshotStorageComplaint) {
|
|
270
|
+
return { kind: "followup", activeTask, scores, confidence: 1 };
|
|
271
|
+
}
|
|
272
|
+
|
|
259
273
|
if (/^(hi|hey|hallo|moin|servus|yo|na|guten morgen|guten abend)\b/.test(text) && text.split(" ").length <= 5) {
|
|
260
274
|
return { kind: "chat", scores, confidence: 1 };
|
|
261
275
|
}
|