nothumanallowed 9.4.15 → 9.5.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 +1 -1
- package/src/commands/ui.mjs +16 -0
- package/src/constants.mjs +1 -1
- package/src/services/google-oauth.mjs +12 -21
- package/src/services/web-ui.mjs +24 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.5.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": {
|
package/src/commands/ui.mjs
CHANGED
|
@@ -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.
|
|
8
|
+
export const VERSION = '9.5.1';
|
|
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
|
|
15
|
-
const DEFAULT_CLIENT_ID = '';
|
|
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
|
|
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
|
-
|
|
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',
|
|
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,
|
|
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 = {
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -314,11 +314,11 @@ function apiPatch(p){return fetch(API+p,{method:'PATCH'}).then(function(r){retur
|
|
|
314
314
|
|
|
315
315
|
// ---- LOAD DATA ----
|
|
316
316
|
function loadDash(){
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
});
|
|
317
|
+
// Load each API independently — render as each arrives (emails are slow)
|
|
318
|
+
apiGet('/api/status').then(function(r){dash.status=r;render()});
|
|
319
|
+
apiGet('/api/tasks').then(function(r){dash.tasks=(r&&r.tasks)||[];dashLoaded.tasks=true;updateBadges();render()});
|
|
320
|
+
apiGet('/api/calendar').then(function(r){dash.events=(r&&r.events)||[];dashLoaded.events=true;updateBadges();render()});
|
|
321
|
+
return apiGet('/api/emails').then(function(r){dash.emails=(r&&r.emails)||[];dashLoaded.emails=true;updateBadges();render()});
|
|
322
322
|
}
|
|
323
323
|
function loadAgents(){return apiGet('/api/agents').then(function(r){agentsList=(r&&r.agents)||[]})}
|
|
324
324
|
function updateBadges(){
|
|
@@ -361,14 +361,14 @@ function render(){
|
|
|
361
361
|
|
|
362
362
|
// ---- DASHBOARD ----
|
|
363
363
|
function renderDash(el){
|
|
364
|
-
if(!dashLoaded.emails){el.innerHTML=loadingHTML('dashboard');return}
|
|
364
|
+
if(!dashLoaded.tasks&&!dashLoaded.events&&!dashLoaded.emails){el.innerHTML=loadingHTML('dashboard');return}
|
|
365
365
|
var t=dash.tasks,e=dash.emails,ev=dash.events;
|
|
366
366
|
var done=t.filter(function(x){return x.status==='done'}).length;
|
|
367
367
|
var pend=t.length-done;
|
|
368
368
|
var pct=t.length>0?Math.round(done/t.length*100):0;
|
|
369
369
|
var h='<div class="dash-grid">'+
|
|
370
370
|
'<div class="card"><div class="card__title">Tasks</div><div class="card__value">'+pend+'</div><div class="card__sub">'+done+'/'+t.length+' done ('+pct+'%)</div></div>'+
|
|
371
|
-
'<div class="card"><div class="card__title">Emails</div><div class="card__value">'+e.length+'</div><div class="card__sub">'+(e.length>0?esc(e[0].from):'Inbox zero')+'</div></div>'+
|
|
371
|
+
'<div class="card"><div class="card__title">Emails</div><div class="card__value">'+(dashLoaded.emails?e.length:'<span class="spinner" style="width:14px;height:14px;display:inline-block;vertical-align:middle"></span>')+'</div><div class="card__sub">'+(dashLoaded.emails?(e.length>0?esc(e[0].from):'Inbox zero'):'Loading...')+'</div></div>'+
|
|
372
372
|
'<div class="card"><div class="card__title">Events</div><div class="card__value">'+ev.length+'</div><div class="card__sub">'+(ev.length>0?esc(ev[0].summary):'No events')+'</div></div>'+
|
|
373
373
|
'<div class="card"><div class="card__title">Agents</div><div class="card__value">38</div><div class="card__sub">Ready</div></div>'+
|
|
374
374
|
'</div>';
|
|
@@ -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>' +
|