nothumanallowed 12.6.4 → 12.6.6
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 +97 -17
- package/src/constants.mjs +1 -1
- package/src/services/google-oauth.mjs +8 -3
- package/src/services/web-ui.mjs +6 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "12.6.
|
|
3
|
+
"version": "12.6.6",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools. Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/commands/ui.mjs
CHANGED
|
@@ -335,15 +335,40 @@ export async function cmdUI(args) {
|
|
|
335
335
|
|
|
336
336
|
// POST /api/google/auth — trigger Google OAuth flow from web UI
|
|
337
337
|
if (method === 'POST' && pathname === '/api/google/auth') {
|
|
338
|
+
// Check if Google credentials are configured first
|
|
339
|
+
const clientId = config.google?.clientId || '';
|
|
340
|
+
const clientSecret = config.google?.clientSecret || '';
|
|
341
|
+
if (!clientId) {
|
|
342
|
+
sendJSON(res, 200, {
|
|
343
|
+
ok: false,
|
|
344
|
+
needsSetup: true,
|
|
345
|
+
message: 'Google OAuth not configured yet.\n\n' +
|
|
346
|
+
'To connect Google services, you need OAuth credentials:\n\n' +
|
|
347
|
+
'1. Go to https://console.cloud.google.com/apis/credentials\n' +
|
|
348
|
+
'2. Create an OAuth 2.0 Client ID (Desktop app type)\n' +
|
|
349
|
+
'3. Enable Gmail API, Calendar API, Drive API, People API, Tasks API\n' +
|
|
350
|
+
'4. Add authorized redirect URIs: http://127.0.0.1:19847/callback through http://127.0.0.1:19851/callback\n' +
|
|
351
|
+
'5. In the NHA terminal, run:\n' +
|
|
352
|
+
' nha config set google-client-id YOUR_CLIENT_ID\n' +
|
|
353
|
+
' nha config set google-client-secret YOUR_CLIENT_SECRET\n' +
|
|
354
|
+
'6. Then click "Connect Google" again.',
|
|
355
|
+
});
|
|
356
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
338
359
|
try {
|
|
339
360
|
const { runAuthFlow } = await import('../services/google-oauth.mjs');
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
361
|
+
const success = await runAuthFlow(config);
|
|
362
|
+
if (success) {
|
|
363
|
+
config._googleConnected = true;
|
|
364
|
+
const freshConfig = await loadConfig();
|
|
365
|
+
Object.assign(config, freshConfig);
|
|
366
|
+
sendJSON(res, 200, { ok: true, message: 'Google connected successfully! You can now use email, calendar, contacts, and Drive.' });
|
|
367
|
+
} else {
|
|
368
|
+
sendJSON(res, 200, { ok: false, message: 'Google OAuth failed. The browser window should have opened at accounts.google.com. If it didn\'t, try running "nha google" from the terminal.' });
|
|
369
|
+
}
|
|
345
370
|
} catch (e) {
|
|
346
|
-
sendJSON(res, 500, { error: e.message });
|
|
371
|
+
sendJSON(res, 500, { error: `Google OAuth error: ${e.message}. Try running "nha google" from the terminal.` });
|
|
347
372
|
}
|
|
348
373
|
logRequest(method, pathname, 200, Date.now() - start);
|
|
349
374
|
return;
|
|
@@ -1257,12 +1282,14 @@ export async function cmdUI(args) {
|
|
|
1257
1282
|
if (sLines.length > 0) parts.push(`[CONTEXT — ${sLines.length} earlier exchanges]\n${sLines.join('\n')}\n[END CONTEXT]`);
|
|
1258
1283
|
}
|
|
1259
1284
|
for (const turn of requestHistory.slice(-RECENT)) {
|
|
1260
|
-
|
|
1285
|
+
// Use llmContent (file context) when available, otherwise display content
|
|
1286
|
+
const turnContent = turn.llmContent || turn.content;
|
|
1287
|
+
parts.push(`${turn.role === 'user' ? '[User]' : '[Assistant]'} ${turnContent.slice(0, 4000)}`);
|
|
1261
1288
|
}
|
|
1262
1289
|
parts.push(`[User] ${body.message}`);
|
|
1263
1290
|
let userMessage = parts.join('\n\n');
|
|
1264
1291
|
|
|
1265
|
-
// Inject episodic memory
|
|
1292
|
+
// Inject episodic memory + cross-conversation memory into the system prompt
|
|
1266
1293
|
const basePrompt = effectiveSystemPrompt || chatSystemPrompt;
|
|
1267
1294
|
let enrichedSystemPrompt = basePrompt;
|
|
1268
1295
|
try {
|
|
@@ -1270,6 +1297,37 @@ export async function cmdUI(args) {
|
|
|
1270
1297
|
if (memCtx) enrichedSystemPrompt = basePrompt + memCtx;
|
|
1271
1298
|
} catch { /* memory unavailable */ }
|
|
1272
1299
|
|
|
1300
|
+
// Cross-conversation memory — summaries of recent conversations
|
|
1301
|
+
try {
|
|
1302
|
+
const allConvs = listConversations();
|
|
1303
|
+
if (allConvs.length > 1) {
|
|
1304
|
+
const summaries = [];
|
|
1305
|
+
let totalChars = 0;
|
|
1306
|
+
for (const c of allConvs) {
|
|
1307
|
+
if (c.id === (body.conversationId || activeConvId)) continue;
|
|
1308
|
+
if (summaries.length >= 8 || totalChars > 2000) break;
|
|
1309
|
+
if (!c.messages || c.messages.length === 0) continue;
|
|
1310
|
+
const firstUser = c.messages.find(m => m.role === 'user');
|
|
1311
|
+
const lastAssistant = [...c.messages].reverse().find(m => m.role === 'assistant');
|
|
1312
|
+
if (!firstUser) continue;
|
|
1313
|
+
const date = c.updatedAt?.split('T')[0] || '?';
|
|
1314
|
+
const title = c.title !== 'New Chat' ? c.title : firstUser.content.slice(0, 60);
|
|
1315
|
+
let s = `• [${date}] "${title}" (${c.messages.length} msgs)`;
|
|
1316
|
+
s += `\n User: ${firstUser.content.replace(/\s+/g, ' ').slice(0, 120)}`;
|
|
1317
|
+
if (lastAssistant) {
|
|
1318
|
+
const preview = lastAssistant.content.replace(/<think>[\s\S]*?<\/think>/g, '').replace(/\s+/g, ' ').slice(0, 150);
|
|
1319
|
+
s += `\n Result: ${preview}`;
|
|
1320
|
+
}
|
|
1321
|
+
if (totalChars + s.length > 2000) break;
|
|
1322
|
+
summaries.push(s);
|
|
1323
|
+
totalChars += s.length;
|
|
1324
|
+
}
|
|
1325
|
+
if (summaries.length > 0) {
|
|
1326
|
+
enrichedSystemPrompt += `\n\n--- CONVERSATION MEMORY ---\nYou remember these past conversations:\n\n${summaries.join('\n\n')}\n\nUse this to maintain continuity. Never say "I don't have access to previous conversations".\n--- END MEMORY ---`;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
} catch { /* non-critical */ }
|
|
1330
|
+
|
|
1273
1331
|
// Handle image attachment — vision API
|
|
1274
1332
|
if (body.imageBase64 && body.imageMimeType) {
|
|
1275
1333
|
try {
|
|
@@ -1362,13 +1420,33 @@ export async function cmdUI(args) {
|
|
|
1362
1420
|
const pdfPrompt = body.message || `Read and analyze this PDF document "${body.pdfName}". Extract all text content, summarize key information.`;
|
|
1363
1421
|
let pdfResponse = '';
|
|
1364
1422
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1423
|
+
// Step 1: Extract text — try server (pdftotext) first, then local fallback
|
|
1424
|
+
let pdfText = '';
|
|
1425
|
+
try {
|
|
1426
|
+
const extractRes = await fetch('https://nothumanallowed.com/api/v1/tools/extract-pdf', {
|
|
1427
|
+
method: 'POST',
|
|
1428
|
+
headers: { 'Content-Type': 'application/json', 'X-NHA-Client': 'desktop' },
|
|
1429
|
+
body: JSON.stringify({ base64: body.pdfBase64 }),
|
|
1430
|
+
signal: AbortSignal.timeout(30000),
|
|
1431
|
+
});
|
|
1432
|
+
if (extractRes.ok) {
|
|
1433
|
+
const d = await extractRes.json();
|
|
1434
|
+
pdfText = d.text || '';
|
|
1435
|
+
}
|
|
1436
|
+
} catch { /* server unreachable */ }
|
|
1437
|
+
// Local fallback if server extraction failed
|
|
1438
|
+
if (pdfText.length < 20) {
|
|
1368
1439
|
const pdfBuffer = Buffer.from(body.pdfBase64, 'base64');
|
|
1369
|
-
|
|
1440
|
+
pdfText = extractTextFromPdf(pdfBuffer);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// Save extracted text as llmContent so it persists across turns
|
|
1444
|
+
const pdfLlmContent = pdfText.length > 20
|
|
1445
|
+
? `[PDF: ${body.pdfName}]\n\n${pdfText.slice(0, 12000)}\n\n---\n\nUser question: ${pdfPrompt}`
|
|
1446
|
+
: '';
|
|
1447
|
+
|
|
1448
|
+
if (provider === 'nha') {
|
|
1370
1449
|
if (!pdfText || pdfText.length < 10) {
|
|
1371
|
-
// Fallback: send first page as image to vision model
|
|
1372
1450
|
const r = await fetch('https://nothumanallowed.com/api/v1/liara/vision', {
|
|
1373
1451
|
method: 'POST',
|
|
1374
1452
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -1381,7 +1459,6 @@ export async function cmdUI(args) {
|
|
|
1381
1459
|
pdfResponse = 'Could not read this PDF. Try a text-based PDF or use Claude/Gemini for scanned documents.';
|
|
1382
1460
|
}
|
|
1383
1461
|
} else {
|
|
1384
|
-
// Send extracted text to Liara chat
|
|
1385
1462
|
const truncatedText = pdfText.slice(0, 12000);
|
|
1386
1463
|
const r = await fetch('https://nothumanallowed.com/api/v1/liara/chat', {
|
|
1387
1464
|
method: 'POST',
|
|
@@ -1439,7 +1516,8 @@ export async function cmdUI(args) {
|
|
|
1439
1516
|
pdfResponse = `PDF reading requires Anthropic (Claude) or Gemini. Your provider "${provider}" does not support native PDF documents.`;
|
|
1440
1517
|
}
|
|
1441
1518
|
|
|
1442
|
-
|
|
1519
|
+
// Return llmContent so frontend can persist the PDF text across turns
|
|
1520
|
+
sendJSON(res, 200, { response: pdfResponse, llmContent: pdfLlmContent || undefined });
|
|
1443
1521
|
logRequest(method, pathname, 200, Date.now() - start);
|
|
1444
1522
|
return;
|
|
1445
1523
|
} catch (e) {
|
|
@@ -1449,12 +1527,14 @@ export async function cmdUI(args) {
|
|
|
1449
1527
|
}
|
|
1450
1528
|
}
|
|
1451
1529
|
|
|
1452
|
-
// Handle text file attachment
|
|
1530
|
+
// Handle text file attachment — include file content as llmContent for persistence
|
|
1531
|
+
let fileLlmContent = '';
|
|
1453
1532
|
if (body.fileContent && body.fileName) {
|
|
1454
1533
|
const filePrompt = body.message
|
|
1455
1534
|
? `User asks about file "${body.fileName}": ${body.message}\n\nFile content:\n${body.fileContent.slice(0, 8000)}`
|
|
1456
1535
|
: `Analyze this file "${body.fileName}":\n\n${body.fileContent.slice(0, 8000)}`;
|
|
1457
1536
|
userMessage = filePrompt;
|
|
1537
|
+
fileLlmContent = filePrompt;
|
|
1458
1538
|
}
|
|
1459
1539
|
|
|
1460
1540
|
try {
|
|
@@ -1551,7 +1631,7 @@ export async function cmdUI(args) {
|
|
|
1551
1631
|
} catch { /* non-critical */ }
|
|
1552
1632
|
try { extractMemory('chat', body.message, fullResponse); } catch { /* non-critical */ }
|
|
1553
1633
|
|
|
1554
|
-
sendJSON(res, 200, { response: fullResponse, toolResults, actions });
|
|
1634
|
+
sendJSON(res, 200, { response: fullResponse, toolResults, actions, ...(fileLlmContent ? { llmContent: fileLlmContent } : {}) });
|
|
1555
1635
|
} catch (e) {
|
|
1556
1636
|
sendJSON(res, 200, { response: null, error: e.message });
|
|
1557
1637
|
}
|
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 = '12.6.
|
|
8
|
+
export const VERSION = '12.6.6';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -44,9 +44,14 @@ function generatePKCE() {
|
|
|
44
44
|
function openBrowser(url) {
|
|
45
45
|
const platform = os.platform();
|
|
46
46
|
try {
|
|
47
|
-
if (platform === 'darwin')
|
|
48
|
-
|
|
49
|
-
else
|
|
47
|
+
if (platform === 'darwin') {
|
|
48
|
+
execSync(`open "${url}"`);
|
|
49
|
+
} else if (platform === 'win32') {
|
|
50
|
+
// Use rundll32 — more reliable than 'start' in batch/npm contexts
|
|
51
|
+
execSync(`rundll32 url.dll,FileProtocolHandler "${url}"`);
|
|
52
|
+
} else {
|
|
53
|
+
execSync(`xdg-open "${url}"`);
|
|
54
|
+
}
|
|
50
55
|
} catch {
|
|
51
56
|
warn('Could not open browser automatically.');
|
|
52
57
|
info(`Open this URL manually:\n\n ${url}\n`);
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -925,6 +925,11 @@ function sendChat(){
|
|
|
925
925
|
clearChatAttach();
|
|
926
926
|
apiPost('/api/chat',payload).then(function(r){
|
|
927
927
|
chatHistory.pop();
|
|
928
|
+
// Save llmContent in the user message so file context persists across turns
|
|
929
|
+
if(r&&r.llmContent){
|
|
930
|
+
var lastUser=chatHistory[chatHistory.length-1];
|
|
931
|
+
if(lastUser&&lastUser.role==='user')lastUser.llmContent=r.llmContent;
|
|
932
|
+
}
|
|
928
933
|
if(r&&r.response){chatHistory.push({role:'assistant',content:r.response})}
|
|
929
934
|
else if(r&&r.error){chatHistory.push({role:'assistant',content:'Error: '+r.error})}
|
|
930
935
|
else{chatHistory.push({role:'assistant',content:'Error: no response'})}
|
|
@@ -943,7 +948,7 @@ function sendChat(){
|
|
|
943
948
|
chatHistory.push({role:'assistant',content:''});
|
|
944
949
|
renderMessages();
|
|
945
950
|
var streamIdx=chatHistory.length-1;
|
|
946
|
-
var allHistory=chatHistory.slice(0,-1).map(function(m){
|
|
951
|
+
var allHistory=chatHistory.slice(0,-1).map(function(m){var h={role:m.role,content:(m.content||'').replace(/!\\[Screenshot\\]\\(data:image\\/[^)]+\\)/g,'[Screenshot taken]')};if(m.llmContent)h.llmContent=m.llmContent;return h;});
|
|
947
952
|
var payload={message:msg,history:allHistory,conversationId:activeConvId,isRetry:isRetry};
|
|
948
953
|
|
|
949
954
|
fetch(API+'/api/chat/stream',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload),signal:chatAbortController.signal}).then(function(response){
|