nothumanallowed 16.0.57 → 16.0.58

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": "16.0.57",
3
+ "version": "16.0.58",
4
4
  "description": "Local AI assistant: 80 tools (Gmail, Calendar, Drive, GitHub, Slack, browser, code, files), 38 agents, visual workflows (Studio, AWF, WebCraft). Install with `npm i -g nothumanallowed`, run with `nha ui`. Free tier built-in (Liara), no API key required. Your data stays on your PC — OAuth tokens local, no cloud. Open-source MIT.",
5
5
  "type": "module",
6
6
  "bin": {
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 = '16.0.57';
8
+ export const VERSION = '16.0.58';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -125,6 +125,10 @@ function findChromePath() {
125
125
  '/snap/bin/chromium',
126
126
  '/usr/bin/brave-browser',
127
127
  '/usr/bin/microsoft-edge',
128
+ // Termux on Android — chromium installed via "pkg install chromium"
129
+ '/data/data/com.termux/files/usr/bin/chromium',
130
+ '/data/data/com.termux/files/usr/bin/chromium-browser',
131
+ '/data/data/com.termux/files/usr/bin/google-chrome',
128
132
  ],
129
133
  win32: [
130
134
  'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
@@ -657,6 +661,104 @@ async function getBrowser() {
657
661
  * @param {boolean} [options.waitForLoad] - Wait for page load event (default true)
658
662
  * @returns {Promise<{ title: string, url: string, status: number }>}
659
663
  */
664
+ /**
665
+ * Lightweight HTTP fallback when Chrome/Chromium is not available.
666
+ * Uses fetch() + regex-based HTML→text extraction. No JS rendering, no clicks.
667
+ * Good enough for: news sites, blog posts, static pages, API responses,
668
+ * documentation pages. NOT good for: SPAs, login flows, dynamic dashboards.
669
+ */
670
+ async function browserOpenViaFetch(url, options = {}) {
671
+ const timeout = options.timeout || 15000;
672
+ try {
673
+ const ac = new AbortController();
674
+ const timer = setTimeout(() => ac.abort(), timeout);
675
+ const res = await fetch(url, {
676
+ redirect: 'follow',
677
+ signal: ac.signal,
678
+ headers: {
679
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
680
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
681
+ 'Accept-Language': 'en-US,en;q=0.9,it;q=0.8',
682
+ },
683
+ });
684
+ clearTimeout(timer);
685
+
686
+ const status = res.status;
687
+ const finalUrl = res.url || url;
688
+ const ct = res.headers.get('content-type') || '';
689
+ const isHtml = /html/i.test(ct);
690
+ const raw = await res.text();
691
+
692
+ if (!isHtml) {
693
+ return {
694
+ title: finalUrl,
695
+ url: finalUrl,
696
+ status,
697
+ mode: 'fetch-fallback',
698
+ warning: 'Chrome not installed — used HTTP fetch. No JS rendering. Limited interactivity.',
699
+ content: raw.slice(0, 50_000),
700
+ };
701
+ }
702
+
703
+ // Extract title
704
+ const titleMatch = raw.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
705
+ const title = titleMatch ? titleMatch[1].replace(/\s+/g, ' ').trim() : finalUrl;
706
+
707
+ // Extract main text content: strip script/style/svg/comments, then strip tags
708
+ let textContent = raw
709
+ .replace(/<!--[\s\S]*?-->/g, '')
710
+ .replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ')
711
+ .replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ')
712
+ .replace(/<svg\b[^>]*>[\s\S]*?<\/svg>/gi, ' ')
713
+ .replace(/<noscript\b[^>]*>[\s\S]*?<\/noscript>/gi, ' ')
714
+ .replace(/<header\b[^>]*>[\s\S]*?<\/header>/gi, ' ') // strip nav/header noise
715
+ .replace(/<nav\b[^>]*>[\s\S]*?<\/nav>/gi, ' ')
716
+ .replace(/<footer\b[^>]*>[\s\S]*?<\/footer>/gi, ' ')
717
+ .replace(/<aside\b[^>]*>[\s\S]*?<\/aside>/gi, ' ');
718
+
719
+ // Extract headlines + links separately for news sites
720
+ const headlines = [];
721
+ const linkRe = /<a\b[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi;
722
+ let lm;
723
+ while ((lm = linkRe.exec(raw)) !== null && headlines.length < 50) {
724
+ const linkText = lm[2].replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
725
+ const href = lm[1];
726
+ if (linkText.length > 20 && linkText.length < 200 && !href.startsWith('#') && !href.startsWith('javascript:')) {
727
+ const absHref = href.startsWith('http') ? href : new URL(href, finalUrl).toString();
728
+ headlines.push({ text: linkText, url: absHref });
729
+ }
730
+ }
731
+
732
+ // Strip remaining tags to get plain text
733
+ textContent = textContent
734
+ .replace(/<[^>]+>/g, ' ')
735
+ .replace(/&nbsp;/g, ' ')
736
+ .replace(/&amp;/g, '&')
737
+ .replace(/&lt;/g, '<')
738
+ .replace(/&gt;/g, '>')
739
+ .replace(/&quot;/g, '"')
740
+ .replace(/&#39;/g, "'")
741
+ .replace(/&#x?[0-9a-f]+;/gi, '')
742
+ .replace(/\s+/g, ' ')
743
+ .trim();
744
+
745
+ return {
746
+ title,
747
+ url: finalUrl,
748
+ status,
749
+ mode: 'fetch-fallback',
750
+ warning: 'Chrome/Chromium not installed — used HTTP fetch fallback. No JS rendering, no interactive clicks/forms. To install: macOS use brew, Linux apt-get, Termux "pkg install chromium".',
751
+ headlines: headlines.slice(0, 30),
752
+ content: textContent.slice(0, 30_000),
753
+ };
754
+ } catch (e) {
755
+ if (e.name === 'AbortError') {
756
+ return { error: true, message: `HTTP fetch timeout after ${timeout / 1000}s. The site may be slow or blocking the request.` };
757
+ }
758
+ return { error: true, message: `HTTP fetch failed: ${e.message}. ${/ENOTFOUND|ECONNREFUSED/.test(e.message) ? 'Network or DNS issue.' : ''}` };
759
+ }
760
+ }
761
+
660
762
  export async function browserOpen(url, options = {}) {
661
763
  // SSRF check
662
764
  const check = await isSafeUrl(url);
@@ -664,7 +766,23 @@ export async function browserOpen(url, options = {}) {
664
766
  return { error: true, message: `SSRF blocked: ${check.reason}` };
665
767
  }
666
768
 
667
- const browser = await getBrowser();
769
+ // Fast fallback: if Chrome/Chromium is not installed (e.g. Termux on Android),
770
+ // do a plain HTTP fetch and extract text content. Limited (no JS rendering,
771
+ // no clicks), but works for news sites / blog posts / static pages.
772
+ if (!findChromePath()) {
773
+ return browserOpenViaFetch(url, options);
774
+ }
775
+
776
+ let browser;
777
+ try {
778
+ browser = await getBrowser();
779
+ } catch (e) {
780
+ // Chrome detection failed at launch — same fallback
781
+ if (/Chrome\/Chromium not found/i.test(e.message || '')) {
782
+ return browserOpenViaFetch(url, options);
783
+ }
784
+ throw e;
785
+ }
668
786
  const timeout = options.timeout || NAV_TIMEOUT_MS;
669
787
  const waitForLoad = options.waitForLoad !== false;
670
788
 
@@ -12,8 +12,13 @@ import os from 'os';
12
12
  import { saveTokens, loadTokens, deleteTokens } from './token-store.mjs';
13
13
  import { info, ok, fail, warn } from '../ui.mjs';
14
14
 
15
- // NHA published OAuth client (Desktop app type client_id is not a secret)
16
- const DEFAULT_CLIENT_ID = '516893094132-8u2jf6h6h3j6h8j9k0l1m2n3o4p5q6r7.apps.googleusercontent.com'; // NHA Official OAuth Client
15
+ // IMPORTANT: NHA does NOT ship a default Google OAuth client ID.
16
+ // The previous placeholder (516893094132-8u2jf...) was a fake-looking value
17
+ // that always returned `invalid_client` from Google. Each user must register
18
+ // their own OAuth client in Google Cloud Console — this is by design for
19
+ // privacy (no shared client app), and it's a one-time 3-minute setup.
20
+ // See https://nothumanallowed.com/docs/google for the full guide.
21
+ const DEFAULT_CLIENT_ID = '';
17
22
  const SCOPES = [
18
23
  'https://www.googleapis.com/auth/gmail.modify',
19
24
  'https://www.googleapis.com/auth/gmail.send',
@@ -221,14 +226,27 @@ export async function runAuthFlow(config, manual = false) {
221
226
 
222
227
  if (!clientId) {
223
228
  fail('Google OAuth client ID not configured.');
224
- info('Get credentials from Google Cloud Console:');
225
- info(' 1. Go to https://console.cloud.google.com/apis/credentials');
226
- info(' 2. Create an OAuth 2.0 Client ID (Desktop app type)');
227
- info(' 3. Enable Gmail API and Calendar API');
228
- info(' 4. Run:');
229
+ info('');
230
+ info('NHA does not ship a shared OAuth client (your data never goes through');
231
+ info('our servers Gmail/Calendar API calls go from your PC directly to');
232
+ info('Google). You need a 3-minute one-time setup of your own OAuth client.');
233
+ info('');
234
+ info('STEPS:');
235
+ info(' 1. Open https://console.cloud.google.com/apis/credentials');
236
+ info(' 2. Click + CREATE CREDENTIALS → OAuth client ID');
237
+ info(' 3. Application type: "Desktop app", give it a name (e.g. "NHA local")');
238
+ info(' 4. Click CREATE. Google shows you Client ID + Client Secret.');
239
+ info(' 5. Enable the APIs you need:');
240
+ info(' - Gmail API: https://console.cloud.google.com/apis/library/gmail.googleapis.com');
241
+ info(' - Calendar: https://console.cloud.google.com/apis/library/calendar-json.googleapis.com');
242
+ info(' - Drive: https://console.cloud.google.com/apis/library/drive.googleapis.com');
243
+ info(' - People API: https://console.cloud.google.com/apis/library/people.googleapis.com');
244
+ info(' 6. Save the credentials in NHA:');
229
245
  info(' nha config set google-client-id YOUR_CLIENT_ID');
230
246
  info(' nha config set google-client-secret YOUR_CLIENT_SECRET');
231
- info(' 5. Run: nha google auth');
247
+ info(' 7. Re-run: nha google auth');
248
+ info('');
249
+ info('Full guide with screenshots: https://nothumanallowed.com/docs/google');
232
250
  return false;
233
251
  }
234
252