nothumanallowed 9.3.16 → 9.3.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "9.3.16",
3
+ "version": "9.3.17",
4
4
  "description": "NotHumanAllowed — 38 AI agents + 58 tools + browser automation + web search. Streaming chat, headless Chrome CDP, multi-conversation, export. Gmail, Calendar, Drive, GitHub, Notion, Slack. Zero-dependency CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -233,6 +233,26 @@ export async function cmdUI(args) {
233
233
 
234
234
  // ── API Routes ────────────────────────────────────────────────────
235
235
 
236
+ // GET /api/screenshots/:filename — serve saved screenshots from disk
237
+ if (method === 'GET' && pathname.startsWith('/api/screenshots/')) {
238
+ const ssName = pathname.split('/').pop();
239
+ if (!ssName || ssName.includes('..') || !ssName.endsWith('.jpg')) {
240
+ sendJSON(res, 404, { error: 'not found' });
241
+ logRequest(method, pathname, 404, Date.now() - start);
242
+ return;
243
+ }
244
+ const ssPath = path.join(NHA_DIR, 'screenshots', ssName);
245
+ if (!fs.existsSync(ssPath)) {
246
+ sendJSON(res, 404, { error: 'screenshot not found' });
247
+ logRequest(method, pathname, 404, Date.now() - start);
248
+ return;
249
+ }
250
+ res.writeHead(200, { 'Content-Type': 'image/jpeg', 'Cache-Control': 'public, max-age=86400' });
251
+ res.end(fs.readFileSync(ssPath));
252
+ logRequest(method, pathname, 200, Date.now() - start);
253
+ return;
254
+ }
255
+
236
256
  // GET /api/status
237
257
  if (method === 'GET' && pathname === '/api/status') {
238
258
  sendJSON(res, 200, {
@@ -1219,8 +1239,17 @@ export async function cmdUI(args) {
1219
1239
  quality: 75,
1220
1240
  });
1221
1241
  if (!ssResult.error) {
1222
- sendSSE('screenshot', { base64: ssResult.base64, format: 'jpeg' });
1223
- toolResults.push({ action, result: `Screenshot captured (${Math.round(ssResult.size / 1024)}KB)` });
1242
+ // Save screenshot to disk for persistence across sessions
1243
+ const ssDir = path.join(NHA_DIR, 'screenshots');
1244
+ fs.mkdirSync(ssDir, { recursive: true });
1245
+ const ssFilename = `ss-${Date.now()}.jpg`;
1246
+ fs.writeFileSync(path.join(ssDir, ssFilename), Buffer.from(ssResult.base64, 'base64'));
1247
+
1248
+ sendSSE('screenshot', { base64: ssResult.base64, format: 'jpeg', filename: ssFilename });
1249
+ toolResults.push({ action, result: `Screenshot captured (${Math.round(ssResult.size / 1024)}KB) [file: ${ssFilename}]` });
1250
+ // Store screenshot ref for persistence
1251
+ if (!res._screenshotFiles) res._screenshotFiles = [];
1252
+ res._screenshotFiles.push(ssFilename);
1224
1253
  sendSSE('tool', { action, status: 'done', result: 'Screenshot captured' });
1225
1254
  } else {
1226
1255
  toolResults.push({ action, result: `Error: ${ssResult.message}` });
@@ -1234,14 +1263,20 @@ export async function cmdUI(args) {
1234
1263
  toolResults.push({ action, result: resultStr });
1235
1264
  sendSSE('tool', { action, status: 'done', result: typeof resultStr === 'string' ? resultStr.slice(0, 500) : '' });
1236
1265
 
1237
- // If the tool produced a screenshot (web_search with screenshot=true), send it via SSE
1266
+ // If the tool produced a screenshot (web_search with screenshot=true), send it via SSE and save to disk
1238
1267
  if (resultStr.includes('[Screenshot of results captured')) {
1239
1268
  try {
1240
1269
  const be = await import('../services/browser-engine.mjs');
1241
1270
  if (be.isBrowserRunning()) {
1242
1271
  const ssResult = await be.browserScreenshot({ fullPage: false, format: 'jpeg', quality: 75 });
1243
1272
  if (!ssResult.error) {
1244
- sendSSE('screenshot', { base64: ssResult.base64, format: 'png' });
1273
+ const ssDir = path.join(NHA_DIR, 'screenshots');
1274
+ fs.mkdirSync(ssDir, { recursive: true });
1275
+ const ssFilename = `ss-${Date.now()}.jpg`;
1276
+ fs.writeFileSync(path.join(ssDir, ssFilename), Buffer.from(ssResult.base64, 'base64'));
1277
+ sendSSE('screenshot', { base64: ssResult.base64, format: 'jpeg', filename: ssFilename });
1278
+ if (!res._screenshotFiles) res._screenshotFiles = [];
1279
+ res._screenshotFiles.push(ssFilename);
1245
1280
  }
1246
1281
  }
1247
1282
  } catch { /* screenshot send failed */ }
@@ -1269,12 +1304,18 @@ export async function cmdUI(args) {
1269
1304
  }
1270
1305
  }
1271
1306
 
1272
- // Persist to conversation
1307
+ // Persist to conversation (append screenshot references so they survive reload)
1273
1308
  if (convId) {
1274
1309
  try {
1310
+ let persistedResponse = finalResponse;
1311
+ const ssFiles = res._screenshotFiles || [];
1312
+ if (ssFiles.length > 0) {
1313
+ const ssRefs = ssFiles.map(f => `\n![Screenshot](/api/screenshots/${f})`).join('');
1314
+ persistedResponse = finalResponse + ssRefs;
1315
+ }
1275
1316
  const conv = loadConversation(convId);
1276
1317
  if (conv) {
1277
- addMessages(conv, msg, finalResponse);
1318
+ addMessages(conv, msg, persistedResponse);
1278
1319
  }
1279
1320
  } catch {}
1280
1321
  }
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '9.3.16';
8
+ export const VERSION = '9.3.17';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -405,7 +405,8 @@ function renderMessages(){
405
405
  var h='';chatHistory.forEach(function(m){
406
406
  var raw=m.content||'';
407
407
  var imgs=[];var idx=0;
408
- var safe=raw.replace(/!\\[([^\\]]*)\\]\\((data:image\\/[a-z]+;base64,[A-Za-z0-9+\\/=]+)\\)/g,function(_,alt,src){var ph='__IMG'+idx+'__';imgs.push({ph:ph,alt:alt,src:src});idx++;return ph;});
408
+ // Match both base64 inline images and /api/screenshots/ URLs
409
+ var safe=raw.replace(/!\\[([^\\]]*)\\]\\(((?:data:image\\/[a-z]+;base64,[A-Za-z0-9+\\/=]+|\\/api\\/screenshots\\/[a-zA-Z0-9._-]+))\\)/g,function(_,alt,src){var ph='__IMG'+idx+'__';imgs.push({ph:ph,alt:alt,src:src});idx++;return ph;});
409
410
  var content=esc(safe);
410
411
  for(var i=0;i<imgs.length;i++){content=content.replace(imgs[i].ph,'<img class="screenshot-preview" alt="'+esc(imgs[i].alt)+'" src="'+imgs[i].src+'">');}
411
412
  h+='<div class="msg msg--'+esc(m.role)+'"><div class="msg__label">'+esc(m.role==='user'?'You':'NHA')+'</div><div class="msg__bubble">'+content+'</div></div>';
@@ -539,8 +540,10 @@ function sendChat(){
539
540
  }
540
541
  if(currentEvent==='screenshot'&&data.base64){
541
542
  if(!chatHistory[streamIdx]._screenshots)chatHistory[streamIdx]._screenshots=[];
542
- chatHistory[streamIdx]._screenshots.push('data:image/'+(data.format||'png')+';base64,'+data.base64);
543
- chatHistory[streamIdx].content+='\\n![Screenshot](data:image/'+(data.format||'png')+';base64,'+data.base64+')\\n';
543
+ // Use file URL if available (persists across sessions), fall back to base64
544
+ var ssUrl=data.filename?'/api/screenshots/'+data.filename:'data:image/'+(data.format||'jpeg')+';base64,'+data.base64;
545
+ chatHistory[streamIdx]._screenshots.push(ssUrl);
546
+ chatHistory[streamIdx].content+='\\n![Screenshot]('+ssUrl+')\\n';
544
547
  renderMessages();
545
548
  }
546
549
  if(currentEvent==='tool_synthesis'){chatHistory[streamIdx].content='';renderMessages();}