nothumanallowed 9.4.15 → 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 +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 +17 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "9.
|
|
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": {
|
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.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
|
|
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
|
@@ -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>' +
|