nothumanallowed 9.3.19 → 9.4.1

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.19",
3
+ "version": "9.4.1",
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": {
@@ -253,6 +253,13 @@ export async function cmdUI(args) {
253
253
  return;
254
254
  }
255
255
 
256
+ // GET /api/health — simple health check
257
+ if (method === 'GET' && pathname === '/api/health') {
258
+ sendJSON(res, 200, { ok: true, version: VERSION });
259
+ logRequest(method, pathname, 200, Date.now() - start);
260
+ return;
261
+ }
262
+
256
263
  // GET /api/status
257
264
  if (method === 'GET' && pathname === '/api/status') {
258
265
  sendJSON(res, 200, {
@@ -1263,18 +1270,31 @@ export async function cmdUI(args) {
1263
1270
  toolResults.push({ action, result: resultStr });
1264
1271
  sendSSE('tool', { action, status: 'done', result: typeof resultStr === 'string' ? resultStr.slice(0, 500) : '' });
1265
1272
 
1266
- // If the tool produced a screenshot (web_search with screenshot=true), send it via SSE and save to disk
1267
- if (resultStr.includes('[Screenshot of results captured')) {
1273
+ // Send live browser frame after browser actions (low-quality thumbnail for viewer)
1274
+ if (action.startsWith('browser_') && action !== 'browser_close') {
1268
1275
  try {
1269
1276
  const be = await import('../services/browser-engine.mjs');
1270
1277
  if (be.isBrowserRunning()) {
1271
- const ssResult = await be.browserScreenshot({ fullPage: false, format: 'jpeg', quality: 75 });
1272
- if (!ssResult.error) {
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
+ const frame = await be.browserScreenshot({ fullPage: false, format: 'jpeg', quality: 30 });
1279
+ if (!frame.error) {
1280
+ const info = await be.browserInfo();
1281
+ sendSSE('browser_frame', { base64: frame.base64, format: 'jpeg', url: (info.url || '').slice(0, 80) });
1282
+ }
1283
+ }
1284
+ } catch { /* frame capture failed, non-critical */ }
1285
+ }
1286
+
1287
+ // If the tool produced a screenshot (web_search with screenshot=true), send it via SSE
1288
+ if (resultStr.includes('[Screenshot of results captured')) {
1289
+ try {
1290
+ // Extract filename from result (tool-executor already saved it)
1291
+ const fileMatch = resultStr.match(/file:(ss-\d+\.jpg)/);
1292
+ if (fileMatch) {
1293
+ const ssFilename = fileMatch[1];
1294
+ const ssPath = path.join(NHA_DIR, 'screenshots', ssFilename);
1295
+ if (fs.existsSync(ssPath)) {
1296
+ const ssBase64 = fs.readFileSync(ssPath).toString('base64');
1297
+ sendSSE('screenshot', { base64: ssBase64, format: 'jpeg', filename: ssFilename });
1278
1298
  if (!res._screenshotFiles) res._screenshotFiles = [];
1279
1299
  res._screenshotFiles.push(ssFilename);
1280
1300
  }
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.19';
8
+ export const VERSION = '9.4.1';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -1337,7 +1337,13 @@ export async function executeTool(action, params, config) {
1337
1337
  await new Promise(r => setTimeout(r, 300));
1338
1338
  const ss = await be.browserScreenshot({ fullPage: false, format: 'jpeg', quality: 75 });
1339
1339
  if (!ss.error) {
1340
- return textResult + `\n\n[Screenshot of results captured (${Math.round(ss.size / 1024)}KB)]`;
1340
+ // Save to disk for persistence
1341
+ const ssDir = path.join(os.homedir(), '.nha', 'screenshots');
1342
+ const fsMod = await import('fs');
1343
+ fsMod.mkdirSync(ssDir, { recursive: true });
1344
+ const ssFilename = `ss-${Date.now()}.jpg`;
1345
+ fsMod.writeFileSync(path.join(ssDir, ssFilename), Buffer.from(ss.base64, 'base64'));
1346
+ return textResult + `\n\n[Screenshot of results captured (${Math.round(ss.size / 1024)}KB) file:${ssFilename}]`;
1341
1347
  }
1342
1348
  } catch { /* screenshot failed, return text only */ }
1343
1349
  }
@@ -101,6 +101,19 @@ input:focus,textarea:focus{border-color:var(--green3)}
101
101
  .tool-indicator--web{border-color:var(--cyan);color:var(--cyan)}
102
102
  .tool-indicator--email{border-color:var(--green3);color:var(--green)}
103
103
  .screenshot-preview{max-width:100%;border-radius:var(--r);margin:8px 0;border:1px solid var(--border)}
104
+ .browser-viewer{position:fixed;top:12px;left:12px;width:440px;background:var(--bg2);border:2px solid #9c27b0;border-radius:8px;box-shadow:0 8px 32px rgba(0,0,0,0.6);z-index:300;overflow:hidden;display:none;transition:all .3s ease}
105
+ .browser-viewer--open{display:block}
106
+ .browser-viewer__header{display:flex;align-items:center;gap:6px;padding:6px 10px;background:#1a1a2e;border-bottom:1px solid #9c27b0;font-size:10px;color:#ce93d8}
107
+ .browser-viewer__dot{width:6px;height:6px;border-radius:50%;background:#9c27b0;animation:bvpulse 1.5s infinite}
108
+ @keyframes bvpulse{0%,100%{opacity:1}50%{opacity:.3}}
109
+ .browser-viewer__title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
110
+ .browser-viewer__close{background:none;border:none;color:#666;cursor:pointer;font-size:14px;padding:0 4px}
111
+ .browser-viewer__close:hover{color:#fff}
112
+ .browser-viewer__frame{width:100%;aspect-ratio:16/9;background:#000;display:flex;align-items:center;justify-content:center}
113
+ .browser-viewer__frame img{width:100%;height:100%;object-fit:contain}
114
+ .browser-viewer__status{padding:4px 10px;font-size:9px;color:var(--dim);border-top:1px solid var(--border)}
115
+ @media(max-width:600px){.browser-viewer{width:calc(100vw - 24px);top:8px;left:8px}}
116
+ @media(min-width:901px){.browser-viewer{left:232px}}
104
117
  .chat__bar{display:flex;gap:8px;padding:10px 0 12px 0;border-top:1px solid var(--border);flex-shrink:0}
105
118
  .chat__input{flex:1;resize:none;min-height:40px;max-height:100px;padding:10px 14px}
106
119
  .chat__send{background:var(--green3);color:var(--bg);padding:10px 16px;border-radius:var(--r);font-weight:700;font-size:12px}
@@ -226,6 +239,24 @@ function stopChat(){
226
239
  renderMessages();
227
240
  }
228
241
 
242
+ // ---- BROWSER VIEWER (live preview of headless Chrome) ----
243
+ function showBrowserViewer(title,status){
244
+ var v=document.getElementById('browserViewer');if(!v)return;
245
+ v.classList.add('browser-viewer--open');
246
+ var t=document.getElementById('bvTitle');if(t)t.textContent=title||'Browser';
247
+ var s=document.getElementById('bvStatus');if(s)s.textContent=status||'Loading...';
248
+ }
249
+ function updateBrowserFrame(base64,format){
250
+ var f=document.getElementById('bvFrame');if(!f)return;
251
+ f.innerHTML='<img src="data:image/'+(format||'jpeg')+';base64,'+base64+'" alt="Browser view">';
252
+ }
253
+ function updateBrowserStatus(status){
254
+ var s=document.getElementById('bvStatus');if(s)s.textContent=status;
255
+ }
256
+ function closeBrowserViewer(){
257
+ var v=document.getElementById('browserViewer');if(v)v.classList.remove('browser-viewer--open');
258
+ }
259
+
229
260
  function loadConvList(){return apiGet('/api/conversations').then(function(r){convList=(r&&r.conversations)||[];renderConvSidebar();})}
230
261
  function loadConv(id){return apiGet('/api/conversations/'+id).then(function(r){if(r&&r.conversation){activeConvId=r.conversation.id;chatHistory=r.conversation.messages||[];renderMessages();renderConvSidebar();}})}
231
262
  function createNewConv(){return apiPost('/api/conversations',{}).then(function(r){if(r&&r.conversation){activeConvId=r.conversation.id;chatHistory=[];renderMessages();loadConvList();}})}
@@ -533,6 +564,12 @@ function sendChat(){
533
564
  var label=toolLabels[data.action]||data.action;
534
565
  var indicator=data.status==='executing'?'\\u23f3 '+label+'...':'\\u2705 '+label;
535
566
  if(data.status==='error')indicator='\\u274c '+label+' failed';
567
+ // Show browser viewer for browser actions
568
+ var isBrowserAction=data.action&&data.action.startsWith('browser_');
569
+ if(isBrowserAction&&data.status==='executing'){showBrowserViewer(label,'Executing...');}
570
+ if(isBrowserAction&&data.status==='done'){updateBrowserStatus('\\u2705 '+label);}
571
+ if(isBrowserAction&&data.status==='error'){updateBrowserStatus('\\u274c '+label);}
572
+ if(data.action==='browser_close'&&data.status==='done'){setTimeout(closeBrowserViewer,2000);}
536
573
  // Strip JSON action blocks from streamed content (they are internal tool calls, not for the user)
537
574
  if(data.status==='executing'){chatHistory[streamIdx].content=chatHistory[streamIdx].content.replace(new RegExp('\\x60\\x60\\x60json[\\\\s\\\\S]*?\\x60\\x60\\x60','g'),'').trim()+'\\n';}
538
575
  chatHistory[streamIdx].content+=indicator+'\\n';
@@ -540,11 +577,18 @@ function sendChat(){
540
577
  }
541
578
  if(currentEvent==='screenshot'&&data.base64){
542
579
  if(!chatHistory[streamIdx]._screenshots)chatHistory[streamIdx]._screenshots=[];
543
- // Use file URL if available (persists across sessions), fall back to base64
544
580
  var ssUrl=data.filename?'/api/screenshots/'+data.filename:'data:image/'+(data.format||'jpeg')+';base64,'+data.base64;
545
581
  chatHistory[streamIdx]._screenshots.push(ssUrl);
546
582
  chatHistory[streamIdx].content+='\\n![Screenshot]('+ssUrl+')\\n';
547
583
  renderMessages();
584
+ // Update browser viewer with the screenshot
585
+ updateBrowserFrame(data.base64,data.format||'jpeg');
586
+ updateBrowserStatus('Screenshot captured');
587
+ }
588
+ if(currentEvent==='browser_frame'&&data.base64){
589
+ // Live frame update (thumbnail from browser during operations)
590
+ updateBrowserFrame(data.base64,data.format||'jpeg');
591
+ if(data.url)updateBrowserStatus(data.url);
548
592
  }
549
593
  if(currentEvent==='tool_synthesis'){chatHistory[streamIdx].content='';renderMessages();}
550
594
  if(currentEvent==='done'){endStreaming();if(data.content)chatHistory[streamIdx].content=data.content;var ss=chatHistory[streamIdx]._screenshots;if(ss&&ss.length>0){for(var si=0;si<ss.length;si++){chatHistory[streamIdx].content+='\\n![Screenshot]('+ss[si]+')\\n';}}renderMessages();loadConvList();}
@@ -1837,6 +1881,18 @@ init();
1837
1881
  <button onclick="openSidebar()" style="position:fixed;top:8px;left:8px;z-index:100;background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);color:var(--green);font-size:20px;padding:4px 10px;cursor:pointer;line-height:1" id="mobileBurger">&#9776;</button>
1838
1882
 
1839
1883
  <div class="content" id="content"></div>
1884
+
1885
+ <div class="browser-viewer" id="browserViewer">
1886
+ <div class="browser-viewer__header">
1887
+ <span class="browser-viewer__dot"></span>
1888
+ <span class="browser-viewer__title" id="bvTitle">Browser</span>
1889
+ <button class="browser-viewer__close" onclick="closeBrowserViewer()">&times;</button>
1890
+ </div>
1891
+ <div class="browser-viewer__frame" id="bvFrame">
1892
+ <span style="color:var(--dim);font-size:11px">Waiting...</span>
1893
+ </div>
1894
+ <div class="browser-viewer__status" id="bvStatus">Idle</div>
1895
+ </div>
1840
1896
  </div>
1841
1897
 
1842
1898
  <div class="modal-overlay" id="agentModal">