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.
|
|
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.
|
|
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(/ /g, ' ')
|
|
736
|
+
.replace(/&/g, '&')
|
|
737
|
+
.replace(/</g, '<')
|
|
738
|
+
.replace(/>/g, '>')
|
|
739
|
+
.replace(/"/g, '"')
|
|
740
|
+
.replace(/'/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
|
-
|
|
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
|
|
16
|
-
|
|
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('
|
|
225
|
-
info('
|
|
226
|
-
info('
|
|
227
|
-
info('
|
|
228
|
-
info('
|
|
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('
|
|
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
|
|