blun-king-cli 5.3.5 → 5.3.6

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 (3) hide show
  1. package/blun-cli.js +115 -22
  2. package/package.json +1 -1
  3. 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
@@ -635,6 +734,7 @@ async function sendChat(message) {
635
734
 
636
735
  var fullAnswer = "";
637
736
  var receivedFiles = [];
737
+ var receivedArtifact = null;
638
738
  var meta = "";
639
739
 
640
740
  try {
@@ -655,6 +755,7 @@ async function sendChat(message) {
655
755
 
656
756
  fullAnswer = resp.data.answer || "";
657
757
  receivedFiles = resp.data.files || [];
758
+ receivedArtifact = resp.data.artifact || null;
658
759
  var q = resp.data.quality || {};
659
760
  meta = (resp.data.task_type || "") + "/" + (resp.data.role || "") + " " + BOX.dot + " score: " + (q.score || "?") +
660
761
  (q.retried ? " " + BOX.dot + " retried" : "") +
@@ -679,19 +780,14 @@ async function sendChat(message) {
679
780
  sessionCost.outputTokensEst += outTok;
680
781
 
681
782
  // ── Save received files to local workdir ──
682
- if (receivedFiles.length > 0) {
783
+ var savedOutputs = await saveApiOutputs(receivedFiles, receivedArtifact);
784
+ if (savedOutputs.length > 0) {
683
785
  console.log(C.green + " \uD83D\uDCC1 Dateien gespeichert:" + C.reset);
684
786
  lastCreatedFiles = [];
685
- for (var fi = 0; fi < receivedFiles.length; fi++) {
686
- var f = receivedFiles[fi];
687
- if (f.content && f.name) {
688
- var localPath = path.join(config.workdir, f.name);
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
- }
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 });
695
791
  }
696
792
  console.log("");
697
793
  }
@@ -1619,17 +1715,14 @@ async function cmdAgent(args) {
1619
1715
  lastAnswer = d.answer || "";
1620
1716
 
1621
1717
  // Save files locally to workspace — API now returns file contents
1622
- if (d.files && d.files.length > 0) {
1623
- for (var fi = 0; fi < d.files.length; fi++) {
1624
- var f = d.files[fi];
1625
- if (f.content && f.name) {
1626
- var localPath = path.join(workdir, f.name);
1627
- var localDir = path.dirname(localPath);
1628
- if (!fs.existsSync(localDir)) fs.mkdirSync(localDir, { recursive: true });
1629
- fs.writeFileSync(localPath, f.content, "utf8");
1630
- f.localPath = localPath;
1631
- }
1632
- totalFiles.push(f);
1718
+ var savedStepOutputs = await saveApiOutputs(d.files || [], d.artifact || null);
1719
+ if (savedStepOutputs.length > 0) {
1720
+ for (var fi = 0; fi < savedStepOutputs.length; fi++) {
1721
+ totalFiles.push({
1722
+ name: savedStepOutputs[fi].name,
1723
+ localPath: savedStepOutputs[fi].path,
1724
+ size: savedStepOutputs[fi].size
1725
+ });
1633
1726
  }
1634
1727
  }
1635
1728
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "5.3.5",
3
+ "version": "5.3.6",
4
4
  "description": "BLUN King CLI — Your local AI assistant powered by Gemma4",
5
5
  "type": "commonjs",
6
6
  "bin": {
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
  }