nothumanallowed 9.4.14 → 9.5.0

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.4.14",
3
+ "version": "9.5.0",
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,22 @@ export async function cmdUI(args) {
253
253
  return;
254
254
  }
255
255
 
256
+ // POST /api/google/auth — trigger Google OAuth flow from web UI
257
+ if (method === 'POST' && pathname === '/api/google/auth') {
258
+ try {
259
+ const { runAuthFlow } = await import('../services/google-oauth.mjs');
260
+ // Run auth flow in background — opens browser
261
+ runAuthFlow(config).then(success => {
262
+ if (success) config._googleConnected = true;
263
+ }).catch(() => {});
264
+ sendJSON(res, 200, { ok: true, message: 'OAuth flow started. Check the browser window that opened.' });
265
+ } catch (e) {
266
+ sendJSON(res, 500, { error: e.message });
267
+ }
268
+ logRequest(method, pathname, 200, Date.now() - start);
269
+ return;
270
+ }
271
+
256
272
  // GET /api/health — simple health check
257
273
  if (method === 'GET' && pathname === '/api/health') {
258
274
  sendJSON(res, 200, { ok: true, version: VERSION });
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.4.14';
8
+ export const VERSION = '9.5.0';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -11,8 +11,9 @@ import os from 'os';
11
11
  import { saveTokens, loadTokens, deleteTokens } from './token-store.mjs';
12
12
  import { info, ok, fail, warn } from '../ui.mjs';
13
13
 
14
- // NHA published OAuth client (Desktop app type client_id is not a secret)
15
- const DEFAULT_CLIENT_ID = ''; // Will be set when Google Cloud project is verified
14
+ // NHA published OAuth client same credentials as the mobile app
15
+ const DEFAULT_CLIENT_ID = '740354841334-o0l6rhkn900jtjtfcls3bkr2n9f1l3tt.apps.googleusercontent.com';
16
+ const DEFAULT_CLIENT_SECRET = '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';
16
17
  const SCOPES = [
17
18
  'https://www.googleapis.com/auth/gmail.modify',
18
19
  'https://www.googleapis.com/auth/gmail.send',
@@ -79,7 +80,7 @@ function waitForCallback(state, port) {
79
80
  return;
80
81
  }
81
82
 
82
- if (!code || returnedState !== state) {
83
+ if (!code || !returnedState?.startsWith(state.split('|')[0])) {
83
84
  res.writeHead(400, { 'Content-Type': 'text/html' });
84
85
  res.end('<html><body><h2>Invalid callback</h2><p>Missing code or state mismatch.</p></body></html>');
85
86
  server.close();
@@ -159,20 +160,7 @@ async function getUserEmail(accessToken) {
159
160
  */
160
161
  export async function runAuthFlow(config) {
161
162
  const clientId = config.google?.clientId || DEFAULT_CLIENT_ID;
162
- const clientSecret = config.google?.clientSecret || '';
163
-
164
- if (!clientId) {
165
- fail('Google OAuth client ID not configured.');
166
- info('Get credentials from Google Cloud Console:');
167
- info(' 1. Go to https://console.cloud.google.com/apis/credentials');
168
- info(' 2. Create an OAuth 2.0 Client ID (Desktop app type)');
169
- info(' 3. Enable Gmail API and Calendar API');
170
- info(' 4. Run:');
171
- info(' nha config set google-client-id YOUR_CLIENT_ID');
172
- info(' nha config set google-client-secret YOUR_CLIENT_SECRET');
173
- info(' 5. Run: nha google auth');
174
- return false;
175
- }
163
+ const clientSecret = config.google?.clientSecret || DEFAULT_CLIENT_SECRET;
176
164
 
177
165
  // Find available port
178
166
  let port = 0;
@@ -192,13 +180,16 @@ export async function runAuthFlow(config) {
192
180
  return false;
193
181
  }
194
182
 
195
- const redirectUri = `http://127.0.0.1:${port}/callback`;
183
+ // Use our server as OAuth redirect (same as mobile app)
184
+ // The callback page extracts the code and redirects to localhost
185
+ const serverRedirectUri = 'https://nothumanallowed.com/auth/callback';
186
+ const localCallbackUrl = `http://127.0.0.1:${port}/callback`;
196
187
  const { verifier, challenge } = generatePKCE();
197
- const state = crypto.randomBytes(32).toString('hex');
188
+ const state = crypto.randomBytes(32).toString('hex') + `|localhost:${port}`;
198
189
 
199
190
  const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
200
191
  authUrl.searchParams.set('client_id', clientId);
201
- authUrl.searchParams.set('redirect_uri', redirectUri);
192
+ authUrl.searchParams.set('redirect_uri', serverRedirectUri);
202
193
  authUrl.searchParams.set('response_type', 'code');
203
194
  authUrl.searchParams.set('scope', SCOPES);
204
195
  authUrl.searchParams.set('state', state);
@@ -215,7 +206,7 @@ export async function runAuthFlow(config) {
215
206
  const { code } = await waitForCallback(state, port);
216
207
  info('Authorization code received. Exchanging for tokens...');
217
208
 
218
- const tokenData = await exchangeCode(code, verifier, clientId, clientSecret, redirectUri);
209
+ const tokenData = await exchangeCode(code, verifier, clientId, clientSecret, serverRedirectUri);
219
210
  const email = await getUserEmail(tokenData.access_token);
220
211
 
221
212
  const tokens = {
@@ -349,7 +349,8 @@ RULES:
349
349
  - The user's timezone is {{TIMEZONE}}.
350
350
  - CRITICAL: when creating calendar events, always use LOCAL time in format "YYYY-MM-DDTHH:MM:SS" WITHOUT any Z suffix or timezone offset.
351
351
  - LANGUAGE: Respond in {{LANGUAGE}}. All conversational text, explanations, and descriptions must be in {{LANGUAGE}}. Tool names and JSON blocks remain in English.
352
- - BROWSER TIP: When extracting data from a page, prefer browser_js with simple JavaScript over complex CSS selectors. Example: browser_js with code "document.body.innerText.slice(0, 3000)" to get all visible text, then parse it yourself. This is more reliable than guessing CSS selectors.
352
+ - BROWSER TIP: When extracting data from a page, prefer browser_js with code "document.body.innerText.slice(0, 3000)" to get all visible text. This is more reliable than guessing CSS selectors.
353
+ - API TIP: For npm package info, use fetch_url with the registry API: fetch_url("https://registry.npmjs.org/PACKAGE/latest") for version/description, and fetch_url("https://api.npmjs.org/downloads/point/last-week/PACKAGE") for weekly downloads. These are JSON APIs, much more reliable than scraping the npm website.
353
354
  `.trim();
354
355
 
355
356
  // ── Action Parser ────────────────────────────────────────────────────────────
@@ -1498,9 +1498,26 @@ function renderSettings(el) {
1498
1498
  ['summary-time', 'Summary Time', '18:00'],
1499
1499
  ['meeting-alert', 'Meeting Alert (minutes)', '30'],
1500
1500
  ]) +
1501
+ '<div class="card" style="margin-top:16px"><div class="card__title">Google Account</div>' +
1502
+ '<div style="font-size:11px;color:var(--dim);margin-bottom:8px">Connect Gmail, Calendar, Drive, Contacts, and Tasks. Opens a browser window for Google sign-in.</div>' +
1503
+ (settingsData.hasGoogle ? '<div style="color:var(--green);font-size:12px;margin-bottom:8px">\\u2705 Connected</div>' : '') +
1504
+ '<button onclick="connectGoogle()" style="background:var(--green3);color:var(--bg);padding:8px 20px;border-radius:var(--r);font-weight:700;font-size:12px;cursor:pointer;border:none">' + (settingsData.hasGoogle ? 'Reconnect Google' : 'Connect Google') + '</button>' +
1505
+ '<div id="googleStatus" style="margin-top:8px;font-size:10px;color:var(--dim)"></div>' +
1506
+ '</div>' +
1501
1507
  '</div>';
1502
1508
  }
1503
1509
 
1510
+ function connectGoogle() {
1511
+ var s = document.getElementById('googleStatus');
1512
+ if (s) s.textContent = 'Starting Google sign-in...';
1513
+ apiPost('/api/google/auth', {}).then(function(r) {
1514
+ if (s) s.textContent = r.message || 'Check the browser window that opened.';
1515
+ if (s) s.style.color = 'var(--green)';
1516
+ }).catch(function(e) {
1517
+ if (s) { s.textContent = 'Error: ' + e.message; s.style.color = 'var(--red)'; }
1518
+ });
1519
+ }
1520
+
1504
1521
  function settingsSection(id, title, desc, fields) {
1505
1522
  var h = '<form class="card" style="margin-bottom:16px" id="settings-' + id + '" onsubmit="event.preventDefault();saveSettingsSection(\\x27' + id + '\\x27)">' +
1506
1523
  '<div class="card__title" style="color:var(--green);font-size:14px;margin-bottom:4px">' + esc(title) + '</div>' +