blun-king-cli 5.3.6 → 5.4.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/blun-cli.js +458 -116
- package/blun-cli.js.claude +3578 -0
- package/browser-controller.js +16 -2
- package/package.json +1 -1
- package/runtime.js +9 -8
package/blun-cli.js
CHANGED
|
@@ -7,6 +7,12 @@ const { execSync, spawn } = require("child_process");
|
|
|
7
7
|
const https = require("https");
|
|
8
8
|
const http = require("http");
|
|
9
9
|
|
|
10
|
+
if (process.platform === "win32") {
|
|
11
|
+
try {
|
|
12
|
+
execSync("chcp 65001>nul", { stdio: "ignore", shell: process.env.ComSpec || "cmd.exe" });
|
|
13
|
+
} catch (e) {}
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
// ── Config ──
|
|
11
17
|
var _rawHome = process.env.BLUN_HOME || process.env.REAL_HOME || require("os").homedir();
|
|
12
18
|
// Strip .claude-telegram-profile suffix if present (Claude Code Telegram session override)
|
|
@@ -112,16 +118,15 @@ function apiCall(method, endpoint, body) {
|
|
|
112
118
|
});
|
|
113
119
|
}
|
|
114
120
|
|
|
115
|
-
function
|
|
121
|
+
function downloadBinary(urlOrPath) {
|
|
116
122
|
return new Promise(function(resolve, reject) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
var url = new URL(config.api.base_url + downloadPath);
|
|
123
|
+
var absolute = new URL(String(urlOrPath || ""), config.api.base_url).toString();
|
|
124
|
+
var url = new URL(absolute);
|
|
120
125
|
var isHttps = url.protocol === "https:";
|
|
121
126
|
var options = {
|
|
122
127
|
hostname: url.hostname,
|
|
123
128
|
port: url.port,
|
|
124
|
-
path: url.pathname +
|
|
129
|
+
path: url.pathname + url.search,
|
|
125
130
|
method: "GET",
|
|
126
131
|
headers: {},
|
|
127
132
|
timeout: config.api.timeout
|
|
@@ -136,84 +141,158 @@ function downloadArtifactToWorkdir(downloadPath, filename) {
|
|
|
136
141
|
|
|
137
142
|
var mod = isHttps ? https : http;
|
|
138
143
|
var req = mod.request(options, function(res) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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);
|
|
144
|
+
var chunks = [];
|
|
145
|
+
res.on("data", function(c) { chunks.push(c); });
|
|
146
|
+
res.on("end", function() {
|
|
147
|
+
var buffer = Buffer.concat(chunks);
|
|
148
|
+
if ((res.statusCode || 500) >= 400) {
|
|
149
|
+
return reject(new Error("HTTP " + res.statusCode + " " + buffer.toString("utf8").slice(0, 400)));
|
|
150
|
+
}
|
|
151
|
+
resolve({ buffer: buffer, contentType: res.headers["content-type"] || "", url: absolute });
|
|
171
152
|
});
|
|
172
153
|
});
|
|
173
|
-
|
|
174
154
|
req.on("error", function(e) { reject(e); });
|
|
175
|
-
req.on("timeout", function() { req.destroy(); reject(new Error("
|
|
155
|
+
req.on("timeout", function() { req.destroy(); reject(new Error("Timeout")); });
|
|
176
156
|
req.end();
|
|
177
157
|
});
|
|
178
158
|
}
|
|
179
159
|
|
|
180
|
-
|
|
181
|
-
var
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
160
|
+
function ensureLocalDir(filePath) {
|
|
161
|
+
var dir = path.dirname(filePath);
|
|
162
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function saveTextFileToWorkdir(fileName, content) {
|
|
166
|
+
var localPath = path.join(config.workdir, fileName);
|
|
167
|
+
ensureLocalDir(localPath);
|
|
168
|
+
fs.writeFileSync(localPath, content, "utf8");
|
|
169
|
+
return localPath;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function printSavedFiles(savedFiles) {
|
|
173
|
+
if (!savedFiles.length) return;
|
|
174
|
+
console.log(C.green + " Dateien gespeichert:" + C.reset);
|
|
175
|
+
savedFiles.forEach(function(file) {
|
|
176
|
+
var sizeLabel = typeof file.size === "number" ? C.dim + " (" + file.size + " bytes)" + C.reset : "";
|
|
177
|
+
console.log(" " + C.brightCyan + file.path + C.reset + sizeLabel);
|
|
178
|
+
});
|
|
179
|
+
console.log("");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function rememberBrowserContext(snapshot, extra) {
|
|
183
|
+
var snap = snapshot || {};
|
|
184
|
+
lastBrowserContext = {
|
|
185
|
+
url: snap.url || (lastBrowserContext && lastBrowserContext.url) || "",
|
|
186
|
+
title: snap.title || (lastBrowserContext && lastBrowserContext.title) || "",
|
|
187
|
+
text: String(snap.text || (lastBrowserContext && lastBrowserContext.text) || "").slice(0, 2000),
|
|
188
|
+
updatedAt: new Date().toISOString()
|
|
189
|
+
};
|
|
190
|
+
if (extra && typeof extra === "object") {
|
|
191
|
+
Object.keys(extra).forEach(function(key) {
|
|
192
|
+
lastBrowserContext[key] = extra[key];
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return lastBrowserContext;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function browserContextPrompt(input) {
|
|
199
|
+
if (!lastBrowserContext || !lastBrowserContext.url) return input;
|
|
200
|
+
return input + "\n\n[Browser-Kontext: " + (lastBrowserContext.title || lastBrowserContext.url) + " - " + String(lastBrowserContext.text || "").slice(0, 1000) + "\nURL: " + lastBrowserContext.url + "]";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function rememberBrowserAction(type, payload) {
|
|
204
|
+
lastBrowserAction = {
|
|
205
|
+
type: type,
|
|
206
|
+
payload: payload || {},
|
|
207
|
+
updatedAt: new Date().toISOString()
|
|
208
|
+
};
|
|
209
|
+
return lastBrowserAction;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function saveArtifactToWorkdir(artifact, references) {
|
|
213
|
+
if (!artifact) return [];
|
|
214
|
+
|
|
215
|
+
var referenceList = Array.isArray(references) ? references : [];
|
|
216
|
+
var downloadUrl = artifact.download || artifact.url || "";
|
|
217
|
+
var fileName = artifact.filename || artifact.name || "";
|
|
218
|
+
|
|
219
|
+
if (!fileName && downloadUrl) {
|
|
220
|
+
try {
|
|
221
|
+
fileName = path.basename(new URL(downloadUrl, config.api.base_url).pathname) || "";
|
|
222
|
+
} catch (e) {}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!fileName) {
|
|
226
|
+
fileName = "artifact-" + Date.now();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
var localPath = path.join(config.workdir, fileName);
|
|
230
|
+
ensureLocalDir(localPath);
|
|
231
|
+
|
|
232
|
+
if (artifact.content && !artifact.encoding) {
|
|
233
|
+
fs.writeFileSync(localPath, artifact.content, "utf8");
|
|
234
|
+
return [{ name: fileName, path: localPath, size: Buffer.byteLength(artifact.content, "utf8") }];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
if (!downloadUrl) throw new Error("No artifact download URL provided.");
|
|
239
|
+
var result = await downloadBinary(downloadUrl);
|
|
240
|
+
fs.writeFileSync(localPath, result.buffer);
|
|
241
|
+
return [{ name: fileName, path: localPath, size: result.buffer.length }];
|
|
242
|
+
} catch (downloadErr) {
|
|
243
|
+
var screenshotRef = referenceList.find(function(ref) { return ref && ref.url; });
|
|
244
|
+
if (/\.(png|jpg|jpeg|webp)$/i.test(fileName) && screenshotRef) {
|
|
245
|
+
var bc = require(path.join(__dirname, "browser-controller"));
|
|
246
|
+
try {
|
|
247
|
+
await bc.open(screenshotRef.url);
|
|
248
|
+
await bc.screenshot(localPath);
|
|
249
|
+
return [{ name: fileName, path: localPath, size: fs.statSync(localPath).size }];
|
|
250
|
+
} finally {
|
|
251
|
+
await bc.close().catch(function() {});
|
|
252
|
+
}
|
|
199
253
|
}
|
|
254
|
+
throw new Error("Artifact download failed: " + downloadErr.message);
|
|
200
255
|
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function saveApiOutputs(receivedFiles, artifact, references) {
|
|
259
|
+
var savedFiles = [];
|
|
260
|
+
var files = Array.isArray(receivedFiles) ? receivedFiles : [];
|
|
201
261
|
|
|
202
|
-
|
|
262
|
+
for (var fi = 0; fi < files.length; fi++) {
|
|
263
|
+
var f = files[fi];
|
|
264
|
+
if (f && f.content && f.name) {
|
|
265
|
+
var localPath = saveTextFileToWorkdir(f.name, f.content);
|
|
266
|
+
savedFiles.push({ name: f.name, path: localPath, size: f.size || Buffer.byteLength(f.content, "utf8") });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (artifact) {
|
|
203
271
|
try {
|
|
204
|
-
var
|
|
205
|
-
|
|
272
|
+
var artifactFiles = await saveArtifactToWorkdir(artifact, references);
|
|
273
|
+
artifactFiles.forEach(function(file) {
|
|
274
|
+
if (!savedFiles.some(function(existing) { return existing.path === file.path; })) {
|
|
275
|
+
savedFiles.push(file);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
206
278
|
} catch (artifactErr) {
|
|
207
279
|
printError("Artefakt konnte nicht lokal gespeichert werden: " + artifactErr.message);
|
|
208
280
|
}
|
|
209
281
|
}
|
|
210
282
|
|
|
211
|
-
|
|
283
|
+
if (savedFiles.length) {
|
|
284
|
+
lastCreatedFiles = savedFiles.slice();
|
|
285
|
+
printSavedFiles(savedFiles);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return savedFiles;
|
|
212
289
|
}
|
|
213
290
|
|
|
214
291
|
// ── Chat History ──
|
|
215
292
|
var chatHistory = [];
|
|
216
293
|
var lastCreatedFiles = []; // Track files from last response for follow-up context
|
|
294
|
+
var lastBrowserContext = null; // Persist browser follow-up context even after local browser closes
|
|
295
|
+
var lastBrowserAction = null; // Repeatable local browser action state
|
|
217
296
|
|
|
218
297
|
// ── Print Helpers ──
|
|
219
298
|
function printHeader() {
|
|
@@ -655,6 +734,11 @@ async function handleCommand(input) {
|
|
|
655
734
|
await cmdAgent(args);
|
|
656
735
|
break;
|
|
657
736
|
|
|
737
|
+
case "/tg":
|
|
738
|
+
case "/telegram":
|
|
739
|
+
await cmdTelegram(args);
|
|
740
|
+
break;
|
|
741
|
+
|
|
658
742
|
case "/screenshot":
|
|
659
743
|
await cmdScreenshot(args);
|
|
660
744
|
break;
|
|
@@ -734,7 +818,6 @@ async function sendChat(message) {
|
|
|
734
818
|
|
|
735
819
|
var fullAnswer = "";
|
|
736
820
|
var receivedFiles = [];
|
|
737
|
-
var receivedArtifact = null;
|
|
738
821
|
var meta = "";
|
|
739
822
|
|
|
740
823
|
try {
|
|
@@ -755,7 +838,6 @@ async function sendChat(message) {
|
|
|
755
838
|
|
|
756
839
|
fullAnswer = resp.data.answer || "";
|
|
757
840
|
receivedFiles = resp.data.files || [];
|
|
758
|
-
receivedArtifact = resp.data.artifact || null;
|
|
759
841
|
var q = resp.data.quality || {};
|
|
760
842
|
meta = (resp.data.task_type || "") + "/" + (resp.data.role || "") + " " + BOX.dot + " score: " + (q.score || "?") +
|
|
761
843
|
(q.retried ? " " + BOX.dot + " retried" : "") +
|
|
@@ -779,18 +861,7 @@ async function sendChat(message) {
|
|
|
779
861
|
sessionCost.inputTokensEst += inTok;
|
|
780
862
|
sessionCost.outputTokensEst += outTok;
|
|
781
863
|
|
|
782
|
-
|
|
783
|
-
var savedOutputs = await saveApiOutputs(receivedFiles, receivedArtifact);
|
|
784
|
-
if (savedOutputs.length > 0) {
|
|
785
|
-
console.log(C.green + " \uD83D\uDCC1 Dateien gespeichert:" + C.reset);
|
|
786
|
-
lastCreatedFiles = [];
|
|
787
|
-
for (var fi = 0; fi < savedOutputs.length; fi++) {
|
|
788
|
-
var f = savedOutputs[fi];
|
|
789
|
-
console.log(" " + C.brightCyan + f.path + C.reset + C.dim + " (" + f.size + " bytes)" + C.reset);
|
|
790
|
-
lastCreatedFiles.push({ name: f.name, path: f.path, size: f.size });
|
|
791
|
-
}
|
|
792
|
-
console.log("");
|
|
793
|
-
}
|
|
864
|
+
await saveApiOutputs(receivedFiles, resp.data.artifact || null, resp.data.references || []);
|
|
794
865
|
|
|
795
866
|
// Auto Memory Dream Mode
|
|
796
867
|
dreamCounter++;
|
|
@@ -1100,6 +1171,20 @@ function cmdSet(args) {
|
|
|
1100
1171
|
break;
|
|
1101
1172
|
|
|
1102
1173
|
default:
|
|
1174
|
+
if (key && key.startsWith("plugin.")) {
|
|
1175
|
+
var pluginParts = key.split(".");
|
|
1176
|
+
if (pluginParts.length === 3) {
|
|
1177
|
+
var pName = pluginParts[1];
|
|
1178
|
+
var pField = pluginParts[2];
|
|
1179
|
+
var plugins = loadPlugins();
|
|
1180
|
+
if (!plugins[pName]) { printError("Plugin '" + pName + "' not installed. /plugin add " + pName); break; }
|
|
1181
|
+
if (!plugins[pName].config) plugins[pName].config = {};
|
|
1182
|
+
plugins[pName].config[pField] = val;
|
|
1183
|
+
savePlugins(plugins);
|
|
1184
|
+
printSuccess("Plugin " + pName + "." + pField + " = " + (pField.includes("token") ? "***" + val.slice(-6) : val));
|
|
1185
|
+
break;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1103
1188
|
printError("Unknown setting. Use /settings to see all options.");
|
|
1104
1189
|
}
|
|
1105
1190
|
}
|
|
@@ -1638,6 +1723,7 @@ function cmdPlugin(args) {
|
|
|
1638
1723
|
if (!plugins[parts[1]]) { printError("Plugin not found: " + parts[1]); return; }
|
|
1639
1724
|
plugins[parts[1]].running = true;
|
|
1640
1725
|
savePlugins(plugins);
|
|
1726
|
+
if (parts[1] === "telegram") { ensureTgBot(); }
|
|
1641
1727
|
printSuccess("Plugin '" + parts[1] + "' started.");
|
|
1642
1728
|
|
|
1643
1729
|
} else if (action === "stop" && parts[1]) {
|
|
@@ -1651,6 +1737,134 @@ function cmdPlugin(args) {
|
|
|
1651
1737
|
}
|
|
1652
1738
|
}
|
|
1653
1739
|
|
|
1740
|
+
// ── Telegram Plugin ──
|
|
1741
|
+
var _tgBot = null;
|
|
1742
|
+
var _tgMessages = [];
|
|
1743
|
+
var _tgMaxMessages = 50;
|
|
1744
|
+
|
|
1745
|
+
function getTgConfig() {
|
|
1746
|
+
var plugins = loadPlugins();
|
|
1747
|
+
var tg = plugins.telegram;
|
|
1748
|
+
if (!tg || !tg.config) return null;
|
|
1749
|
+
return tg.config;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
function ensureTgBot() {
|
|
1753
|
+
if (_tgBot) return _tgBot;
|
|
1754
|
+
var cfg = getTgConfig();
|
|
1755
|
+
if (!cfg || !cfg.bot_token) {
|
|
1756
|
+
printError("Telegram not configured. Run: /plugin add telegram");
|
|
1757
|
+
printInfo("Then set token: /set plugin.telegram.bot_token <YOUR_BOT_TOKEN>");
|
|
1758
|
+
printInfo("And chat ID: /set plugin.telegram.chat_id <YOUR_CHAT_ID>");
|
|
1759
|
+
return null;
|
|
1760
|
+
}
|
|
1761
|
+
try {
|
|
1762
|
+
var TelegramBot = require("node-telegram-bot-api");
|
|
1763
|
+
_tgBot = new TelegramBot(cfg.bot_token, { polling: true });
|
|
1764
|
+
_tgBot.on("message", function(msg) {
|
|
1765
|
+
_tgMessages.push({
|
|
1766
|
+
id: msg.message_id,
|
|
1767
|
+
from: msg.from ? (msg.from.first_name || msg.from.username || "?") : "?",
|
|
1768
|
+
text: msg.text || "(media)",
|
|
1769
|
+
date: new Date(msg.date * 1000).toLocaleTimeString(),
|
|
1770
|
+
chat_id: String(msg.chat.id)
|
|
1771
|
+
});
|
|
1772
|
+
if (_tgMessages.length > _tgMaxMessages) _tgMessages.shift();
|
|
1773
|
+
});
|
|
1774
|
+
printSuccess("Telegram Bot connected.");
|
|
1775
|
+
return _tgBot;
|
|
1776
|
+
} catch(e) {
|
|
1777
|
+
printError("Telegram connection failed: " + e.message);
|
|
1778
|
+
return null;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
async function cmdTelegram(args) {
|
|
1783
|
+
var parts = (args || "").trim().split(/\s+/);
|
|
1784
|
+
var action = parts[0] || "status";
|
|
1785
|
+
|
|
1786
|
+
if (action === "status") {
|
|
1787
|
+
var cfg = getTgConfig();
|
|
1788
|
+
if (!cfg || !cfg.bot_token) {
|
|
1789
|
+
printError("Telegram not configured.");
|
|
1790
|
+
printInfo(" /plugin add telegram");
|
|
1791
|
+
printInfo(" /set plugin.telegram.bot_token <TOKEN>");
|
|
1792
|
+
printInfo(" /set plugin.telegram.chat_id <CHAT_ID>");
|
|
1793
|
+
} else {
|
|
1794
|
+
console.log("");
|
|
1795
|
+
console.log(C.bold + " Telegram Plugin" + C.reset);
|
|
1796
|
+
console.log(" Token: " + C.green + (cfg.bot_token ? "***" + cfg.bot_token.slice(-6) : "not set") + C.reset);
|
|
1797
|
+
console.log(" Chat: " + C.green + (cfg.chat_id || "not set") + C.reset);
|
|
1798
|
+
console.log(" Bot: " + (_tgBot ? C.green + "connected" : C.red + "disconnected") + C.reset);
|
|
1799
|
+
console.log(" Messages buffered: " + _tgMessages.length);
|
|
1800
|
+
console.log("");
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
} else if (action === "connect" || action === "start") {
|
|
1804
|
+
ensureTgBot();
|
|
1805
|
+
|
|
1806
|
+
} else if (action === "send") {
|
|
1807
|
+
var cfg = getTgConfig();
|
|
1808
|
+
if (!cfg || !cfg.chat_id) { printError("No chat_id set. /set plugin.telegram.chat_id <ID>"); return; }
|
|
1809
|
+
var bot = ensureTgBot();
|
|
1810
|
+
if (!bot) return;
|
|
1811
|
+
var text = parts.slice(1).join(" ");
|
|
1812
|
+
if (!text) { printError("Usage: /tg send <message>"); return; }
|
|
1813
|
+
try {
|
|
1814
|
+
await bot.sendMessage(cfg.chat_id, text);
|
|
1815
|
+
printSuccess("Message sent.");
|
|
1816
|
+
} catch(e) {
|
|
1817
|
+
printError("Send failed: " + e.message);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
} else if (action === "read" || action === "messages" || action === "inbox") {
|
|
1821
|
+
if (_tgMessages.length === 0) {
|
|
1822
|
+
printInfo("No messages yet. Connect first: /tg connect");
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
console.log("");
|
|
1826
|
+
console.log(C.bold + " Last " + Math.min(_tgMessages.length, 20) + " messages:" + C.reset);
|
|
1827
|
+
_tgMessages.slice(-20).forEach(function(m) {
|
|
1828
|
+
console.log(C.dim + " [" + m.date + "] " + C.reset + C.brightCyan + m.from + C.reset + ": " + m.text);
|
|
1829
|
+
});
|
|
1830
|
+
console.log("");
|
|
1831
|
+
|
|
1832
|
+
} else if (action === "disconnect" || action === "stop") {
|
|
1833
|
+
if (_tgBot) {
|
|
1834
|
+
_tgBot.stopPolling();
|
|
1835
|
+
_tgBot = null;
|
|
1836
|
+
printSuccess("Telegram disconnected.");
|
|
1837
|
+
} else {
|
|
1838
|
+
printInfo("Not connected.");
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
} else if (action === "file" || action === "photo") {
|
|
1842
|
+
var cfg = getTgConfig();
|
|
1843
|
+
if (!cfg || !cfg.chat_id) { printError("No chat_id set."); return; }
|
|
1844
|
+
var bot = ensureTgBot();
|
|
1845
|
+
if (!bot) return;
|
|
1846
|
+
var filePath = parts.slice(1).join(" ");
|
|
1847
|
+
if (!filePath || !fs.existsSync(filePath)) { printError("File not found: " + filePath); return; }
|
|
1848
|
+
try {
|
|
1849
|
+
await bot.sendDocument(cfg.chat_id, filePath);
|
|
1850
|
+
printSuccess("File sent: " + path.basename(filePath));
|
|
1851
|
+
} catch(e) {
|
|
1852
|
+
printError("Send failed: " + e.message);
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
} else {
|
|
1856
|
+
console.log("");
|
|
1857
|
+
console.log(C.bold + " Telegram Commands:" + C.reset);
|
|
1858
|
+
console.log(" /tg status Show connection status");
|
|
1859
|
+
console.log(" /tg connect Connect to Telegram");
|
|
1860
|
+
console.log(" /tg send <msg> Send a message");
|
|
1861
|
+
console.log(" /tg read Show received messages");
|
|
1862
|
+
console.log(" /tg file <path> Send a file/photo");
|
|
1863
|
+
console.log(" /tg disconnect Disconnect");
|
|
1864
|
+
console.log("");
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1654
1868
|
function cmdPermissions(args) {
|
|
1655
1869
|
var perms = loadPermissions();
|
|
1656
1870
|
if (!args) {
|
|
@@ -1714,17 +1928,10 @@ async function cmdAgent(args) {
|
|
|
1714
1928
|
var d = resp.data;
|
|
1715
1929
|
lastAnswer = d.answer || "";
|
|
1716
1930
|
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
totalFiles.push({
|
|
1722
|
-
name: savedStepOutputs[fi].name,
|
|
1723
|
-
localPath: savedStepOutputs[fi].path,
|
|
1724
|
-
size: savedStepOutputs[fi].size
|
|
1725
|
-
});
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1931
|
+
var savedApiFiles = await saveApiOutputs(d.files || [], d.artifact || null, d.references || []);
|
|
1932
|
+
savedApiFiles.forEach(function(file) {
|
|
1933
|
+
totalFiles.push({ name: file.name, localPath: file.path, size: file.size });
|
|
1934
|
+
});
|
|
1728
1935
|
|
|
1729
1936
|
// If answer contains code blocks with filenames, extract and save them
|
|
1730
1937
|
var codeBlockRe = /```(?:\w+)?\s*\n\/\/\s*(.+\.(?:html|css|js|json|md|txt|py))\n([\s\S]*?)```/g;
|
|
@@ -3157,23 +3364,157 @@ async function main() {
|
|
|
3157
3364
|
await handleCommand(input);
|
|
3158
3365
|
runHook("post", input.split(/\s+/)[0].slice(1));
|
|
3159
3366
|
} else {
|
|
3160
|
-
|
|
3367
|
+
var lowerInput = String(input || "").toLowerCase();
|
|
3368
|
+
var normalizedInput = String(input || "")
|
|
3369
|
+
.normalize("NFD")
|
|
3370
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
3371
|
+
.toLowerCase();
|
|
3372
|
+
var trimmedInput = String(input || "").trim();
|
|
3161
3373
|
var urlMatch = input.match(/(https?:\/\/\S+)/i) || input.match(/\b((?:www\.)?[a-zA-Z0-9-]+\.[a-z]{2,}(?:\/\S*)?)\b/i);
|
|
3162
|
-
var
|
|
3163
|
-
|
|
3374
|
+
var screenshotish = /\b(screenshot|screen ?shot|scre+ns?h[o0]t|screesnhot|screnshot|bildschirmfoto|bild)\b/i;
|
|
3375
|
+
var browserWords = /\b(offne|oeffne|open|schau|zeig|geh auf|gehen|navigate|browse|screenshot|bildschirmfoto|aufrufen|besuch|check|checken|pruef|analysier)\b/i;
|
|
3376
|
+
var localBrowserRequest = /\b(browser|bowser|brwoser|broser|browserfenster|lokalen browser|local browser|auf meinem pc im browser|im browser|browser lokal|lokal im browser)\b/i.test(normalizedInput);
|
|
3377
|
+
var looksLikeSearch = !urlMatch &&
|
|
3378
|
+
trimmedInput.length > 1 &&
|
|
3379
|
+
trimmedInput.length <= 80 &&
|
|
3380
|
+
trimmedInput.split(/\s+/).length <= 5 &&
|
|
3381
|
+
!/^(hi|hallo|hey|moin|servus|yo|sup|ok|ja|nein|danke|thanks|bye|tschuess|ciao)$/i.test(normalizedInput) &&
|
|
3382
|
+
!/^(wo|was|wie|wer|wann|warum)\b/i.test(normalizedInput) &&
|
|
3383
|
+
!/(screenshot|bildschirmfoto|png|datei|file|ordner|link|pfad|screesnhot|screnshot)\b/i.test(normalizedInput);
|
|
3384
|
+
var browserFollowupRequest = !urlMatch &&
|
|
3385
|
+
!!(lastBrowserContext && lastBrowserContext.url) &&
|
|
3386
|
+
/^(und|auch|noch|dann|danach|davon|darunter|weiter|weiterhin)\b/i.test(normalizedInput);
|
|
3387
|
+
var browserController = null;
|
|
3388
|
+
var browserOpen = false;
|
|
3389
|
+
try {
|
|
3390
|
+
browserController = require(path.join(__dirname, "browser-controller"));
|
|
3391
|
+
browserOpen = typeof browserController.isOpen === "function" && browserController.isOpen();
|
|
3392
|
+
} catch (browserStateErr) {}
|
|
3393
|
+
|
|
3394
|
+
if (lastCreatedFiles.length > 0 &&
|
|
3395
|
+
/(wo|welcher|welche|link|pfad|ordner|liegt|finde|zeig|anzeigen)/i.test(lowerInput) &&
|
|
3396
|
+
/(screenshot|bild|png|datei|file)/i.test(lowerInput)) {
|
|
3397
|
+
printInfo("Letzte gespeicherte Datei(en):");
|
|
3398
|
+
printSavedFiles(lastCreatedFiles);
|
|
3399
|
+
}
|
|
3400
|
+
else if (!urlMatch &&
|
|
3401
|
+
/\b(browser|browserfenster|lokalen browser|local browser|lokalen browsr|lokalen brwoser)\b/i.test(normalizedInput) &&
|
|
3402
|
+
!/(screenshot|bildschirmfoto|png|bild)\b/i.test(normalizedInput)) {
|
|
3403
|
+
try {
|
|
3404
|
+
printInfo("Oeffne lokalen Browser...");
|
|
3405
|
+
await browserController.open("about:blank");
|
|
3406
|
+
rememberBrowserAction("open", { url: "about:blank" });
|
|
3407
|
+
printSuccess("Lokaler Browser geoeffnet.");
|
|
3408
|
+
} catch (bcOpenErr) {
|
|
3409
|
+
printError("Lokaler Browser konnte nicht geoeffnet werden: " + bcOpenErr.message);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
else if (!urlMatch && lastBrowserAction && /\b(nochmal|noch mal|erneut|wieder|same again|again)\b/i.test(normalizedInput)) {
|
|
3413
|
+
try {
|
|
3414
|
+
if (lastBrowserAction.type === "search" && lastBrowserAction.payload && lastBrowserAction.payload.query) {
|
|
3415
|
+
printInfo("Suche im lokalen Browser: " + lastBrowserAction.payload.query);
|
|
3416
|
+
var repeatSearchSnap = await browserController.search(lastBrowserAction.payload.query);
|
|
3417
|
+
rememberBrowserContext(repeatSearchSnap, { query: lastBrowserAction.payload.query, source: "search_repeat" });
|
|
3418
|
+
printSuccess(repeatSearchSnap.title || "Google-Suche geoeffnet.");
|
|
3419
|
+
console.log(" " + C.gray + "URL: " + C.reset + repeatSearchSnap.url);
|
|
3420
|
+
} else if (lastBrowserAction.type === "screenshot" && (browserOpen || (lastBrowserContext && lastBrowserContext.url))) {
|
|
3421
|
+
var repeatSnap = browserOpen ? await browserController.snapshot() : await browserController.open(lastBrowserContext.url);
|
|
3422
|
+
rememberBrowserContext(repeatSnap, { source: "snapshot_repeat" });
|
|
3423
|
+
var repeatShot = path.join(config.workdir, "screenshot-" + Date.now() + ".png");
|
|
3424
|
+
await browserController.screenshot(repeatShot);
|
|
3425
|
+
lastCreatedFiles = [{ name: path.basename(repeatShot), path: repeatShot, size: fs.statSync(repeatShot).size }];
|
|
3426
|
+
rememberBrowserAction("screenshot", { url: repeatSnap.url, path: repeatShot });
|
|
3427
|
+
printSuccess("Screenshot gespeichert: " + repeatShot);
|
|
3428
|
+
console.log(" " + C.gray + "URL: " + C.reset + repeatSnap.url);
|
|
3429
|
+
} else if (lastBrowserAction.type === "open" && lastBrowserAction.payload && lastBrowserAction.payload.url) {
|
|
3430
|
+
printInfo("Oeffne " + lastBrowserAction.payload.url + "...");
|
|
3431
|
+
var repeatOpenSnap = await browserController.open(lastBrowserAction.payload.url);
|
|
3432
|
+
rememberBrowserContext(repeatOpenSnap, { source: "open_repeat", requestedUrl: lastBrowserAction.payload.url });
|
|
3433
|
+
printSuccess(repeatOpenSnap.title || lastBrowserAction.payload.url);
|
|
3434
|
+
console.log(" " + C.gray + "URL: " + C.reset + repeatOpenSnap.url);
|
|
3435
|
+
} else {
|
|
3436
|
+
await sendChat(browserContextPrompt(input));
|
|
3437
|
+
}
|
|
3438
|
+
} catch (repeatErr) {
|
|
3439
|
+
await sendChat(browserContextPrompt(input));
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
else if (!urlMatch && (browserOpen || (lastBrowserContext && lastBrowserContext.url)) && screenshotish.test(normalizedInput)) {
|
|
3443
|
+
try {
|
|
3444
|
+
var currentSnap = browserOpen ? await browserController.snapshot() : await browserController.open(lastBrowserContext.url);
|
|
3445
|
+
rememberBrowserContext(currentSnap, { source: "snapshot_followup" });
|
|
3446
|
+
var currentShot = path.join(config.workdir, "screenshot-" + Date.now() + ".png");
|
|
3447
|
+
await browserController.screenshot(currentShot);
|
|
3448
|
+
lastCreatedFiles = [{ name: path.basename(currentShot), path: currentShot, size: fs.statSync(currentShot).size }];
|
|
3449
|
+
rememberBrowserAction("screenshot", { url: currentSnap.url, path: currentShot });
|
|
3450
|
+
printSuccess("Screenshot der offenen Seite gespeichert: " + currentShot);
|
|
3451
|
+
console.log(" " + C.gray + "URL: " + C.reset + currentSnap.url);
|
|
3452
|
+
} catch (followErr) {
|
|
3453
|
+
printError("Screenshot der offenen Seite fehlgeschlagen: " + followErr.message);
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
else if (!urlMatch && (browserOpen || (lastBrowserContext && lastBrowserContext.url)) && /(welche seite|wo bist du|welche url|url|was ist offen|wo gerade)/i.test(normalizedInput)) {
|
|
3457
|
+
try {
|
|
3458
|
+
var statusSnap = browserOpen ? await browserController.snapshot() : lastBrowserContext;
|
|
3459
|
+
rememberBrowserContext(statusSnap, { source: "status_followup" });
|
|
3460
|
+
printSuccess(statusSnap.title || "Browserseite aktiv.");
|
|
3461
|
+
console.log(" " + C.gray + "URL: " + C.reset + statusSnap.url);
|
|
3462
|
+
} catch (statusErr) {
|
|
3463
|
+
printError("Browserstatus konnte nicht gelesen werden: " + statusErr.message);
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
else if (!urlMatch && (browserOpen || (lastBrowserContext && lastBrowserContext.url)) && /(analysier|analyse|schau|check|pruef|was siehst du|was ist auf der seite)/i.test(normalizedInput)) {
|
|
3467
|
+
try {
|
|
3468
|
+
var ctxSnap = browserOpen ? await browserController.snapshot() : lastBrowserContext;
|
|
3469
|
+
rememberBrowserContext(ctxSnap, { source: "analysis_followup" });
|
|
3470
|
+
await sendChat(browserContextPrompt(input));
|
|
3471
|
+
} catch (ctxErr) {
|
|
3472
|
+
await sendChat(input);
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
else if (browserFollowupRequest) {
|
|
3476
|
+
await sendChat(browserContextPrompt(input));
|
|
3477
|
+
}
|
|
3478
|
+
else if (looksLikeSearch) {
|
|
3479
|
+
try {
|
|
3480
|
+
if (browserOpen || (lastBrowserContext && lastBrowserContext.url)) {
|
|
3481
|
+
printInfo("Suche im lokalen Browser: " + trimmedInput);
|
|
3482
|
+
var searchSnap = await browserController.search(trimmedInput);
|
|
3483
|
+
rememberBrowserContext(searchSnap, { query: trimmedInput, source: "search" });
|
|
3484
|
+
rememberBrowserAction("search", { query: trimmedInput });
|
|
3485
|
+
printSuccess(searchSnap.title || "Google-Suche geoeffnet.");
|
|
3486
|
+
console.log(" " + C.gray + "URL: " + C.reset + searchSnap.url);
|
|
3487
|
+
if (searchSnap.text) {
|
|
3488
|
+
console.log(C.dim + " " + BOX.h.repeat(40) + C.reset);
|
|
3489
|
+
console.log(" " + searchSnap.text.slice(0, 500).replace(/\n{3,}/g, "\n\n"));
|
|
3490
|
+
}
|
|
3491
|
+
console.log("");
|
|
3492
|
+
} else {
|
|
3493
|
+
await sendChat(input);
|
|
3494
|
+
}
|
|
3495
|
+
} catch (searchErr) {
|
|
3496
|
+
await sendChat(input);
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
else if (urlMatch && (localBrowserRequest || browserWords.test(normalizedInput) || screenshotish.test(normalizedInput) || /\blokal|mein pc|pc\b/i.test(normalizedInput) || normalizedInput.trim() === String(urlMatch[0]).toLowerCase() || normalizedInput.trim() === String(urlMatch[1]).toLowerCase())) {
|
|
3164
3500
|
var browserUrl = urlMatch[1];
|
|
3165
3501
|
try {
|
|
3166
3502
|
var bc = require(path.join(__dirname, "browser-controller"));
|
|
3167
|
-
if (
|
|
3503
|
+
if (screenshotish.test(normalizedInput)) {
|
|
3168
3504
|
printInfo("Screenshot: " + browserUrl);
|
|
3169
3505
|
var snap = await bc.open(browserUrl);
|
|
3506
|
+
rememberBrowserContext(snap, { source: "open", requestedUrl: browserUrl });
|
|
3170
3507
|
var shotFile = path.join(config.workdir, "screenshot-" + Date.now() + ".png");
|
|
3171
3508
|
await bc.screenshot(shotFile);
|
|
3509
|
+
lastCreatedFiles = [{ name: path.basename(shotFile), path: shotFile, size: fs.statSync(shotFile).size }];
|
|
3510
|
+
rememberBrowserAction("screenshot", { url: snap.url, path: shotFile });
|
|
3172
3511
|
printSuccess("Screenshot gespeichert: " + shotFile);
|
|
3173
3512
|
console.log(" " + C.gray + "Title: " + C.reset + snap.title);
|
|
3174
3513
|
} else {
|
|
3175
|
-
printInfo("
|
|
3514
|
+
printInfo("Oeffne " + browserUrl + "...");
|
|
3176
3515
|
var snap = await bc.open(browserUrl);
|
|
3516
|
+
rememberBrowserContext(snap, { source: "open", requestedUrl: browserUrl });
|
|
3517
|
+
rememberBrowserAction("open", { url: snap.url });
|
|
3177
3518
|
printSuccess(snap.title);
|
|
3178
3519
|
console.log(" " + C.gray + "URL: " + C.reset + snap.url);
|
|
3179
3520
|
if (snap.text) {
|
|
@@ -3182,37 +3523,38 @@ async function main() {
|
|
|
3182
3523
|
console.log(" " + preview);
|
|
3183
3524
|
}
|
|
3184
3525
|
console.log("");
|
|
3185
|
-
|
|
3186
|
-
|
|
3526
|
+
if (!localBrowserRequest) {
|
|
3527
|
+
await sendChat(browserContextPrompt(input));
|
|
3528
|
+
}
|
|
3187
3529
|
}
|
|
3188
|
-
} catch(bcErr) {
|
|
3189
|
-
// Playwright not available, send to chat normally
|
|
3530
|
+
} catch (bcErr) {
|
|
3190
3531
|
await sendChat(input);
|
|
3191
3532
|
}
|
|
3192
3533
|
}
|
|
3193
|
-
// Intent detection — natural language → command
|
|
3194
3534
|
else {
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3535
|
+
var detected = detectIntent(input);
|
|
3536
|
+
if (detected) {
|
|
3537
|
+
printInfo("-> " + detected);
|
|
3538
|
+
runHook("pre", detected.split(/\s+/)[0].slice(1));
|
|
3539
|
+
await handleCommand(detected);
|
|
3540
|
+
runHook("post", detected.split(/\s+/)[0].slice(1));
|
|
3541
|
+
} else if (lastBrowserContext && lastBrowserContext.url && !/^(hi|hallo|hey|moin|servus|yo|sup|ok|ja|nein|danke|thanks|bye|tschuess|ciao)$/i.test(normalizedInput)) {
|
|
3542
|
+
await sendChat(browserContextPrompt(input));
|
|
3543
|
+
} else if (sessionMode === "agent") {
|
|
3544
|
+
// Agent mode: detect if question or task
|
|
3545
|
+
var trimmed = input.trim();
|
|
3546
|
+
var isChat = trimmed.length < 15 || // short messages = chat
|
|
3547
|
+
/^(hi|hallo|hey|moin|servus|yo|sup|ok|ja|nein|danke|thanks|bye|tschuess|ciao)/i.test(trimmed) ||
|
|
3548
|
+
/^(wo |was |wie |wer |wann |warum |kannst|hast|ist |sind |where|what|how|who|when|why|can you|do you|is |are )/i.test(trimmed) ||
|
|
3549
|
+
/\?$/.test(trimmed);
|
|
3550
|
+
if (isChat) {
|
|
3551
|
+
await sendChat(input);
|
|
3552
|
+
} else {
|
|
3553
|
+
await cmdAgent(input);
|
|
3554
|
+
}
|
|
3210
3555
|
} else {
|
|
3211
|
-
await
|
|
3556
|
+
await sendChat(input);
|
|
3212
3557
|
}
|
|
3213
|
-
} else {
|
|
3214
|
-
await sendChat(input);
|
|
3215
|
-
}
|
|
3216
3558
|
}
|
|
3217
3559
|
}
|
|
3218
3560
|
|