nothumanallowed 9.2.4 → 9.3.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/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # NotHumanAllowed (v9.2)
1
+ # NotHumanAllowed (v9.3)
2
2
 
3
3
  **Tell the AI what you need. It does it.**
4
4
 
5
5
  Your AI assistant for email, calendar, web search, files, and everything else. Type what you want in plain language — NHA handles it. Read emails, schedule meetings, search the web, manage tasks, analyze documents, browse Google Drive, check GitHub, Slack, Notion — all from one place.
6
6
 
7
- 38 AI agents. 50 tools. Web search built in. Streaming chat. Multiple conversations with history and export. Voice chat. Available on **PC, Mac, Linux, and Android**.
7
+ 38 AI agents. 58 tools. Web search built in. Streaming chat. Multiple conversations with history and export. Browser automation. Voice chat. Available on **PC, Mac, Linux, and Android**.
8
8
 
9
9
  100% private — your data never leaves your machine. Zero data on our servers. Free and open source.
10
10
 
@@ -26,13 +26,13 @@ nha config set key sk-ant-api03-YOUR_KEY
26
26
  # Connect Google (Gmail, Calendar, Drive, Contacts, Tasks)
27
27
  nha google auth
28
28
 
29
- # Start chatting — 50 tools available
29
+ # Start chatting — 58 tools available
30
30
  nha chat
31
31
  ```
32
32
 
33
33
  ## What You Can Do
34
34
 
35
- ### Chat with 50 Tools + Web Search
35
+ ### Chat with 58 Tools + Web Search + Browser
36
36
 
37
37
  ```bash
38
38
  nha chat
@@ -62,7 +62,34 @@ NHA: Available 60-min slots:
62
62
  3. Wed Apr 9, 02:00 PM - 04:00 PM
63
63
  ```
64
64
 
65
- ### 50 Tools
65
+ ### Browser Automation (Zero Dependencies)
66
+
67
+ ```bash
68
+ nha browse open https://example.com # Open page in headless Chrome
69
+ nha browse screenshot # Capture screenshot
70
+ nha browse extract "h1" # Extract content by CSS selector
71
+ nha browse js "document.title" # Execute JavaScript
72
+ nha browse close # Close browser
73
+ ```
74
+
75
+ Control Chrome headless via the Chrome DevTools Protocol — no Puppeteer, no Playwright, no npm dependencies. Pure WebSocket CDP client built from scratch.
76
+
77
+ All browser tools are also available in `nha chat` and `nha ui`:
78
+
79
+ ```
80
+ You: Open google.com and search for "NHA AI agents"
81
+ NHA: [Browser: google.com] Page loaded: "Google"
82
+ [Typed: NHA AI agents] into search field
83
+ [Key: Enter] submitted search
84
+ [Extracted: results] Found 10 results...
85
+
86
+ You: Take a screenshot
87
+ NHA: [Screenshot] Captured and saved to ~/nha-screenshot.png
88
+ ```
89
+
90
+ 10 browser tools: `browser_open`, `browser_screenshot`, `browser_click`, `browser_type`, `browser_extract`, `browser_js`, `browser_wait`, `browser_scroll`, `browser_key`, `browser_close`. SSRF-protected — blocks localhost and private IPs.
91
+
92
+ ### 58 Tools
66
93
 
67
94
  | Category | Tools |
68
95
  |----------|-------|
@@ -75,6 +102,7 @@ NHA: Available 60-min slots:
75
102
  | **Slack** | List channels, read messages, send messages |
76
103
  | **Notion** | Search pages/databases, read page content |
77
104
  | **Web** | Web search (DuckDuckGo), fetch URL content, deep search with page extraction |
105
+ | **Browser** | Open pages, screenshot, click, type, extract text/HTML, execute JS, wait for elements, scroll, keyboard input, close |
78
106
  | **Other** | Maps directions, reminders, file reading |
79
107
 
80
108
  Every tool is called directly from your machine to the provider's API. NHA servers are never involved.
@@ -109,7 +137,7 @@ nha ask jarvis "Build a REST API for user management"
109
137
  nha voice
110
138
  ```
111
139
 
112
- Same 50 tools, spoken responses. Opens a local web UI with speech recognition.
140
+ Same 58 tools, spoken responses. Opens a local web UI with speech recognition.
113
141
 
114
142
  ### Background Daemon
115
143
 
@@ -198,7 +226,7 @@ Everything the CLI does, on your phone:
198
226
  - **Streaming chat** — see words appear as the AI thinks
199
227
  - **Web search** — search the internet and read any webpage, right from chat
200
228
  - **Multiple conversations** — save, switch, export your chats
201
- - **50 tools** — email, calendar, tasks, contacts, Drive, GitHub, Slack, Notion
229
+ - **58 tools** — email, calendar, tasks, contacts, Drive, GitHub, Slack, Notion, browser
202
230
  - **38 agents** — tap any agent, ask anything
203
231
  - **Voice chat** — talk instead of typing, in 6 languages
204
232
  - **Daily plan** — AI analyzes your emails + calendar every morning
@@ -223,7 +251,7 @@ All API calls go directly from your phone to providers. Zero data through NHA se
223
251
  Your Machine Provider APIs
224
252
  ┌──────────────────┐ ┌─────────────────────┐
225
253
  │ 38 agents │────→│ Google (Gmail, Cal, │
226
- 50 tools │ │ Drive, Contacts) │
254
+ 58 tools │ │ Drive, Contacts) │
227
255
  │ Your API keys │────→│ Your LLM provider │
228
256
  │ Your data │────→│ GitHub, Slack, Notion│
229
257
  │ (encrypted) │ └─────────────────────┘
@@ -256,6 +284,13 @@ Inside `nha chat`, use slash commands:
256
284
  /clear Clear current conversation
257
285
  /help Show all commands
258
286
  /quit Exit
287
+
288
+ # Browser (in chat)
289
+ "Open example.com" → browser_open
290
+ "Take a screenshot" → browser_screenshot
291
+ "Click the submit button" → browser_click
292
+ "Type hello in the search" → browser_type
293
+ "Extract all links" → browser_extract
259
294
  ```
260
295
 
261
296
  Inline agent routing: `@saber audit this function for SQL injection`
@@ -263,7 +298,7 @@ Inline agent routing: `@saber audit this function for SQL injection`
263
298
  ## Commands
264
299
 
265
300
  ```bash
266
- # Chat (50 tools + web search, streaming)
301
+ # Chat (58 tools + web search, streaming)
267
302
  nha chat # Interactive chat with tools
268
303
  nha voice # Voice chat with TTS
269
304
 
@@ -281,6 +316,13 @@ nha ops start # Background daemon
281
316
  # Google
282
317
  nha google auth # Connect Google account
283
318
 
319
+ # Browser
320
+ nha browse open <url> # Open in headless Chrome
321
+ nha browse screenshot # Save screenshot to ~/
322
+ nha browse extract "selector" # Extract text by CSS selector
323
+ nha browse js "code" # Execute JavaScript
324
+ nha browse close # Close browser
325
+
284
326
  # Config
285
327
  nha config # Show settings
286
328
  nha config set provider anthropic # Set LLM provider
@@ -296,7 +338,7 @@ nha mcp # Start MCP server
296
338
  nha ui
297
339
  ```
298
340
 
299
- Opens a local web dashboard on `localhost:3847` with streaming chat, multi-conversation, email, calendar, tasks, contacts, drive, agents, notes, settings — all in your browser. Zero framework, pure HTML/CSS/JS.
341
+ Opens a local web dashboard on `localhost:3847` with streaming chat, multi-conversation, email, calendar, tasks, contacts, drive, agents, notes, settings — all in your browser. Zero framework, pure HTML/CSS/JS. Screenshots from browser automation appear inline in the chat.
300
342
 
301
343
  ## NHA vs OpenClaw
302
344
 
@@ -306,9 +348,10 @@ NHA was built after the [OpenClaw/Moltbook security breach](https://nothumanallo
306
348
  |---|---|---|
307
349
  | Security | 7 CVEs, no WAF | SENTINEL WAF (Rust), 0 CVEs |
308
350
  | Agents | 1 generic | 38 specialists |
309
- | Tools | Install from hub | 50 built-in + web search |
351
+ | Tools | Install from hub | 58 built-in + web search + browser automation |
310
352
  | Knowledge | None | 2.6M facts from 16 datasets |
311
353
  | Daily Plan | None | 5-agent analysis |
354
+ | Browser | Chrome instance (heavy, attack surface) | Custom CDP engine, zero deps, SSRF-protected |
312
355
  | Dependencies | Electron + Chrome | Zero |
313
356
  | Privacy | Moltbook leaked 1.49M records | Zero data on our servers |
314
357
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "9.2.4",
4
- "description": "NotHumanAllowed — 38 AI agents + 50 tools + web search. Streaming chat, multi-conversation, export. Gmail, Calendar, Drive, GitHub, Notion, Slack. Zero-dependency CLI.",
3
+ "version": "9.3.1",
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": {
7
7
  "nha": "./bin/nha.mjs",
package/src/cli.mjs CHANGED
@@ -128,6 +128,10 @@ export async function main(argv) {
128
128
  case 'voice':
129
129
  return cmdVoice(args);
130
130
 
131
+ case 'browse':
132
+ case 'browser':
133
+ return cmdBrowse(args);
134
+
131
135
  case 'plugin':
132
136
  case 'plugins':
133
137
  return cmdPlugin(args);
@@ -247,6 +251,111 @@ async function cmdResponder(args) {
247
251
  }
248
252
  }
249
253
 
254
+ // ── nha browse ────────────────────────────────────────────────────────────
255
+ async function cmdBrowse(args) {
256
+ const sub = args[0];
257
+
258
+ if (sub === 'open' && args[1]) {
259
+ const { browserOpen, browserInfo } = await import('./services/browser-engine.mjs');
260
+ info(`Opening ${args[1]}...`);
261
+ const result = await browserOpen(args[1]);
262
+ if (result.error) {
263
+ fail(result.message);
264
+ process.exit(1);
265
+ }
266
+ ok(`${result.title}`);
267
+ info(`URL: ${result.url}`);
268
+
269
+ // If there are more args, execute them as a sequence
270
+ if (args.length <= 2) {
271
+ info('Browser is running. Use "nha browse screenshot", "nha browse extract", etc.');
272
+ info('Or use "nha chat" — browser tools are available in chat.');
273
+ }
274
+ return;
275
+ }
276
+
277
+ if (sub === 'screenshot') {
278
+ const { browserScreenshot, isBrowserRunning } = await import('./services/browser-engine.mjs');
279
+ if (!isBrowserRunning()) {
280
+ fail('No browser open. Run: nha browse open <url>');
281
+ process.exit(1);
282
+ }
283
+ const saveTo = args[1] || path.join(os.homedir(), `nha-screenshot-${Date.now()}.png`);
284
+ info('Capturing screenshot...');
285
+ const result = await browserScreenshot({ saveTo });
286
+ if (result.error) {
287
+ fail(result.message);
288
+ process.exit(1);
289
+ }
290
+ ok(`Screenshot saved: ${result.savedTo}`);
291
+ return;
292
+ }
293
+
294
+ if (sub === 'extract') {
295
+ const { browserExtract, isBrowserRunning } = await import('./services/browser-engine.mjs');
296
+ if (!isBrowserRunning()) {
297
+ fail('No browser open. Run: nha browse open <url>');
298
+ process.exit(1);
299
+ }
300
+ const selector = args[1] || 'body';
301
+ const result = await browserExtract({ selector, mode: 'text' });
302
+ if (result.error) {
303
+ fail(result.message);
304
+ process.exit(1);
305
+ }
306
+ console.log(result.content);
307
+ return;
308
+ }
309
+
310
+ if (sub === 'js' && args[1]) {
311
+ const { browserEval, isBrowserRunning } = await import('./services/browser-engine.mjs');
312
+ if (!isBrowserRunning()) {
313
+ fail('No browser open. Run: nha browse open <url>');
314
+ process.exit(1);
315
+ }
316
+ const code = args.slice(1).join(' ');
317
+ const result = await browserEval(code);
318
+ if (result.error) {
319
+ fail(result.message);
320
+ process.exit(1);
321
+ }
322
+ console.log(result.result);
323
+ return;
324
+ }
325
+
326
+ if (sub === 'close') {
327
+ const { browserClose } = await import('./services/browser-engine.mjs');
328
+ const result = await browserClose();
329
+ ok(result.message);
330
+ return;
331
+ }
332
+
333
+ // Help
334
+ console.log(`
335
+ ${BOLD}${C}NHA Browser${NC} ${D}— Headless Chrome automation${NC}
336
+
337
+ ${C}Usage:${NC}
338
+ nha browse open <url> Open a page in headless Chrome
339
+ nha browse screenshot Save screenshot to ~/
340
+ nha browse screenshot out.png Save to specific path
341
+ nha browse extract Extract all text from page
342
+ nha browse extract "h1" Extract text from CSS selector
343
+ nha browse js "code" Execute JavaScript in page
344
+ nha browse close Close the browser
345
+
346
+ ${C}In Chat:${NC} All browser tools are available in ${W}nha chat${NC}:
347
+ "Open google.com and search for NHA"
348
+ "Take a screenshot of the page"
349
+ "Click the submit button"
350
+ "Fill the email field with test@example.com"
351
+ "Extract all links from the page"
352
+
353
+ ${D}Requires Chrome or Chromium installed. Set CHROME_PATH to override.${NC}
354
+ ${D}SSRF-protected — blocks localhost and private IPs.${NC}
355
+ ${D}Zero npm dependencies — pure CDP WebSocket.${NC}
356
+ `);
357
+ }
358
+
250
359
  // ── nha run ────────────────────────────────────────────────────────────────
251
360
  async function cmdRun(args) {
252
361
  if (args.length === 0) {
@@ -568,6 +677,14 @@ function cmdHelp() {
568
677
  console.log(` run "prompt" Multi-agent collaboration (server-routed)`);
569
678
  console.log(` run "prompt" ${D}--agents saber,zero${NC} Collaborate with specific agents\n`);
570
679
 
680
+ console.log(` ${C}Browser Automation${NC} ${D}(headless Chrome, zero dependencies)${NC}`);
681
+ console.log(` browse open <url> Open page in headless Chrome (CDP)`);
682
+ console.log(` browse screenshot Capture screenshot to file`);
683
+ console.log(` browse extract Extract page text (CSS selector)`);
684
+ console.log(` browse js "code" Execute JavaScript in page`);
685
+ console.log(` browse close Close browser`);
686
+ console.log(` ${D}Also available as tools in nha chat (browser_open, browser_click, etc.)${NC}\n`);
687
+
571
688
  console.log(` ${C}Daily Operations${NC} ${D}(Gmail + Calendar + Tasks)${NC}`);
572
689
  console.log(` ui Open local web dashboard (http://localhost:3847)`);
573
690
  console.log(` ui --lan Access from phone/tablet on the same Wi-Fi`);
@@ -22,7 +22,7 @@ import { AGENTS_DIR, AGENTS } from '../constants.mjs';
22
22
  import { callLLMStream, parseAgentFile } from '../services/llm.mjs';
23
23
 
24
24
  import { extractMemory } from '../services/memory.mjs';
25
- import { fail, info, ok, warn, C, G, Y, D, W, BOLD, NC, R } from '../ui.mjs';
25
+ import { fail, info, ok, warn, C, G, Y, D, W, BOLD, NC, R, M } from '../ui.mjs';
26
26
  import {
27
27
  DESTRUCTIVE_ACTIONS,
28
28
  parseActions,
@@ -433,6 +433,26 @@ function formatToolLabel(action, params) {
433
433
  return `Searching the web for "${params.query || '...'}"...`;
434
434
  case 'fetch_url':
435
435
  return `Fetching ${params.url || 'URL'}...`;
436
+ case 'browser_open':
437
+ return `Opening ${params.url || 'page'} in browser...`;
438
+ case 'browser_screenshot':
439
+ return `Taking screenshot...`;
440
+ case 'browser_click':
441
+ return `Clicking ${params.selector || `(${params.x}, ${params.y})`}...`;
442
+ case 'browser_type':
443
+ return `Typing into ${params.selector || 'field'}...`;
444
+ case 'browser_extract':
445
+ return `Extracting content from ${params.selector || 'page'}...`;
446
+ case 'browser_js':
447
+ return `Executing JavaScript...`;
448
+ case 'browser_wait':
449
+ return `Waiting for ${params.selector || 'element'}...`;
450
+ case 'browser_scroll':
451
+ return `Scrolling ${params.direction || 'down'}...`;
452
+ case 'browser_key':
453
+ return `Pressing ${params.key || 'key'}...`;
454
+ case 'browser_close':
455
+ return `Closing browser...`;
436
456
  case 'gmail_list':
437
457
  return `Searching emails...`;
438
458
  case 'gmail_read':
@@ -476,6 +496,28 @@ function formatToolResult(action, params, result) {
476
496
  const domain = (params.url || '').replace(/^https?:\/\//, '').split('/')[0];
477
497
  return `${C}[Fetched: ${domain}]${NC}`;
478
498
  }
499
+ case 'browser_open': {
500
+ const domain = (params.url || '').replace(/^https?:\/\//, '').split('/')[0];
501
+ return `${M}[Browser: ${domain}]${NC}`;
502
+ }
503
+ case 'browser_screenshot':
504
+ return `${M}[Screenshot]${NC}`;
505
+ case 'browser_click':
506
+ return `${M}[Click: ${params.selector || `(${params.x}, ${params.y})`}]${NC}`;
507
+ case 'browser_type':
508
+ return `${M}[Typed: ${(params.text || '').slice(0, 30)}${(params.text || '').length > 30 ? '...' : ''}]${NC}`;
509
+ case 'browser_extract':
510
+ return `${M}[Extracted: ${params.selector || 'page'}]${NC}`;
511
+ case 'browser_js':
512
+ return `${M}[JS executed]${NC}`;
513
+ case 'browser_wait':
514
+ return `${M}[Found: ${params.selector}]${NC}`;
515
+ case 'browser_scroll':
516
+ return `${M}[Scrolled ${params.direction || 'down'}]${NC}`;
517
+ case 'browser_key':
518
+ return `${M}[Key: ${params.key}]${NC}`;
519
+ case 'browser_close':
520
+ return `${M}[Browser closed]${NC}`;
479
521
  case 'gmail_list':
480
522
  case 'gmail_read':
481
523
  case 'gmail_send':
@@ -1164,6 +1164,30 @@ export async function cmdUI(args) {
1164
1164
  for (const { action, params } of actions) {
1165
1165
  sendSSE('tool', { action, status: 'executing' });
1166
1166
  try {
1167
+ // For browser_screenshot in web UI: capture and send base64 image
1168
+ if (action === 'browser_screenshot') {
1169
+ const be = await import('../services/browser-engine.mjs');
1170
+ if (!be.isBrowserRunning()) {
1171
+ toolResults.push({ action, result: 'No browser open. Use browser_open first.' });
1172
+ sendSSE('tool', { action, status: 'error', error: 'No browser open' });
1173
+ continue;
1174
+ }
1175
+ const ssResult = await be.browserScreenshot({
1176
+ fullPage: params.fullPage || false,
1177
+ format: params.format || 'png',
1178
+ quality: params.quality,
1179
+ });
1180
+ if (!ssResult.error) {
1181
+ sendSSE('screenshot', { base64: ssResult.base64, format: params.format || 'png' });
1182
+ toolResults.push({ action, result: `Screenshot captured (${Math.round(ssResult.size / 1024)}KB)` });
1183
+ sendSSE('tool', { action, status: 'done', result: 'Screenshot captured' });
1184
+ } else {
1185
+ toolResults.push({ action, result: `Error: ${ssResult.message}` });
1186
+ sendSSE('tool', { action, status: 'error', error: ssResult.message });
1187
+ }
1188
+ continue;
1189
+ }
1190
+
1167
1191
  const result = await executeTool(action, params, config);
1168
1192
  toolResults.push({ action, result: typeof result === 'object' ? JSON.stringify(result) : String(result) });
1169
1193
  sendSSE('tool', { action, status: 'done', result: typeof result === 'string' ? result.slice(0, 500) : '' });
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.2.4';
8
+ export const VERSION = '9.3.1';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11