nothumanallowed 14.1.65 → 14.1.67

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": "14.1.65",
3
+ "version": "14.1.67",
4
4
  "description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). 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": {
@@ -22,8 +22,9 @@ export async function cmdUI(args) {
22
22
  port = parseInt(arg.split('=')[1], 10) || DEFAULT_PORT;
23
23
  } else if (arg === '--no-browser') {
24
24
  noBrowser = true;
25
- } else if (arg === '--lan' || arg === '--host') {
25
+ } else if (arg === '--lan' || arg === '-lan' || arg === '--host' || arg === '-host' || arg === 'lan') {
26
26
  explicitHost = '0.0.0.0';
27
+ noBrowser = true;
27
28
  } else if (arg.startsWith('--host=')) {
28
29
  explicitHost = arg.split('=')[1] || '0.0.0.0';
29
30
  } else if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(arg)) {
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 = '14.1.65';
8
+ export const VERSION = '14.1.67';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -391,6 +391,19 @@ export async function startServer({ port = 3847, host = '127.0.0.1', noBrowser =
391
391
  const { VERSION } = await import('../constants.mjs');
392
392
  console.log(`\n ${BOLD}${G}NHA${NC} ${D}v${VERSION}${NC}`);
393
393
  console.log(` ${G}✓${NC} Server running on ${G}http://${host}:${port}${NC}`);
394
+ if (host === '0.0.0.0') {
395
+ try {
396
+ const os = await import('os');
397
+ const nets = os.default.networkInterfaces();
398
+ for (const iface of Object.values(nets)) {
399
+ for (const info of iface || []) {
400
+ if (info.family === 'IPv4' && !info.internal) {
401
+ console.log(` ${G}✓${NC} LAN: ${G}http://${info.address}:${port}${NC}`);
402
+ }
403
+ }
404
+ }
405
+ } catch {}
406
+ }
394
407
 
395
408
  // ENTERPRISE STARTUP DIAGNOSTIC: Check Google OAuth configuration
396
409
  try {
@@ -440,17 +440,24 @@ export function register(router) {
440
440
 
441
441
  router.get('/api/imap/messages', async (req, res) => {
442
442
  try {
443
- const { listMessages } = await import('../../services/email-db.mjs');
443
+ const emailDb = await import('../../services/email-db.mjs');
444
+ if (!emailDb.SQLITE_AVAILABLE) {
445
+ return sendJSON(res, 200, { messages: [], total: 0, error: 'SQLite not available' });
446
+ }
444
447
  const url = new URL(req.url, 'http://localhost');
445
448
  const accountId = url.searchParams.get('accountId');
449
+ if (!accountId) return sendJSON(res, 200, { messages: [], total: 0 });
446
450
  const labelId = url.searchParams.get('labelId') || null;
447
451
  const folder = url.searchParams.get('folder') || null;
448
452
  const limit = parseInt(url.searchParams.get('limit') || '50');
449
453
  const offset = parseInt(url.searchParams.get('offset') || '0');
450
454
  const search = url.searchParams.get('search') || '';
451
- const result = listMessages(accountId, labelId, limit, offset, search, folder);
455
+ const result = emailDb.listMessages(accountId, labelId, limit, offset, search, folder);
452
456
  sendJSON(res, 200, { messages: result.messages ?? result, total: result.total ?? 0 });
453
- } catch (e) { sendError(res, 500, e.message); }
457
+ } catch (e) {
458
+ console.error('[/api/imap/messages] Error:', e.message);
459
+ sendJSON(res, 200, { messages: [], total: 0, error: e.message });
460
+ }
454
461
  });
455
462
 
456
463
  router.get('/api/imap/message', async (req, res) => {
@@ -571,51 +571,51 @@ export function listMessages(accountId, labelId, limit, offset, search, folder)
571
571
  const params = [accountId];
572
572
 
573
573
  if (labelId) {
574
- // Filter by label — find messages in the corresponding folder(s)
575
- const lbl = db.prepare('SELECT system_type FROM email_labels WHERE id = ?').get(labelId);
576
- if (lbl?.system_type) {
577
- // System label (inbox/sent/drafts/spam/trash) — match via folder_id JOIN
578
- // This works regardless of folder_type being correctly set, because we match
579
- // folders by BOTH folder_type AND common name patterns
580
- const knownNames = {
581
- inbox: ['inbox'],
582
- sent: ['sent', 'sent messages', 'sent items', 'posta inviata', 'inviati', 'inviata'],
583
- drafts: ['drafts', 'draft', 'bozze'],
584
- spam: ['spam', 'junk', 'junk e-mail', 'posta indesiderata'],
585
- trash: ['trash', 'cestino', 'deleted', 'deleted messages', 'deleted items'],
586
- starred: ['starred'],
587
- archived: ['archived', 'archive', 'all mail'],
588
- };
589
- const names = knownNames[lbl.system_type] || [lbl.system_type];
590
- // Match folders by folder_type OR by path/name containing known keywords
591
- // Uses LIKE to handle compound paths like "INBOX.Sent", "[Gmail]/Sent Mail", etc.
592
- const nameConditions = names.map(() => 'LOWER(f.path) LIKE ? OR LOWER(f.name) LIKE ?').join(' OR ');
593
- const folderSql = `SELECT id FROM email_folders WHERE account_id = ? AND (folder_type = ? OR ${nameConditions})`;
594
- const folderParams = [accountId, lbl.system_type, ...names.flatMap(n => [`%${n}%`, `%${n}%`])];
595
- const matchedFolders = db.prepare(folderSql).all(...folderParams);
596
- // For inbox, we need exact match on INBOX (not INBOX.Sent, INBOX.Drafts etc.)
597
- let filtered = matchedFolders;
598
- if (lbl.system_type === 'inbox') {
599
- const exact = db.prepare('SELECT id FROM email_folders WHERE account_id = ? AND UPPER(path) = ?').get(accountId, 'INBOX');
600
- filtered = exact ? [exact] : matchedFolders;
574
+ let labelFilterApplied = false;
575
+ try {
576
+ const lbl = db.prepare('SELECT system_type FROM email_labels WHERE id = ?').get(labelId);
577
+ if (lbl?.system_type) {
578
+ // System label find matching folders by folder_type and name patterns
579
+ const knownNames = {
580
+ inbox: ['inbox'],
581
+ sent: ['sent', 'sent messages', 'sent items', 'posta inviata', 'inviati', 'inviata'],
582
+ drafts: ['drafts', 'draft', 'bozze'],
583
+ spam: ['spam', 'junk', 'junk e-mail', 'posta indesiderata'],
584
+ trash: ['trash', 'cestino', 'deleted', 'deleted messages', 'deleted items'],
585
+ starred: ['starred'],
586
+ archived: ['archived', 'archive', 'all mail'],
587
+ };
588
+ const names = knownNames[lbl.system_type] || [lbl.system_type];
589
+
590
+ // For inbox, use exact path match to avoid INBOX.Sent etc.
591
+ let folderIds = [];
592
+ if (lbl.system_type === 'inbox') {
593
+ const exact = db.prepare('SELECT id FROM email_folders WHERE account_id = ? AND UPPER(path) = ?').get(accountId, 'INBOX');
594
+ if (exact) folderIds = [exact.id];
595
+ }
596
+ // For other types, match by folder_type or name LIKE
597
+ if (folderIds.length === 0) {
598
+ const nameConditions = names.map(() => 'LOWER(f.path) LIKE ? OR LOWER(f.name) LIKE ?').join(' OR ');
599
+ const sql = `SELECT id FROM email_folders f WHERE f.account_id = ? AND (f.folder_type = ? OR ${nameConditions})`;
600
+ const sqlParams = [accountId, lbl.system_type, ...names.flatMap(n => [`%${n}%`, `%${n}%`])];
601
+ folderIds = db.prepare(sql).all(...sqlParams).map(f => f.id);
602
+ }
603
+
604
+ if (folderIds.length > 0) {
605
+ const ph = folderIds.map(() => '?').join(',');
606
+ where += ` AND (m.folder_id IN (${ph}) OR EXISTS (SELECT 1 FROM email_message_labels j WHERE j.message_id = m.id AND j.label_id = ?))`;
607
+ params.push(...folderIds, labelId);
608
+ labelFilterApplied = true;
609
+ }
601
610
  }
611
+ } catch { /* email_folders table might not exist on older DBs */ }
602
612
 
603
- if (filtered.length > 0) {
604
- const placeholders = filtered.map(() => '?').join(',');
605
- where += ` AND (m.folder_id IN (${placeholders}) OR EXISTS (SELECT 1 FROM email_message_labels j WHERE j.message_id = m.id AND j.label_id = ?))`;
606
- params.push(...filtered.map(f => f.id), labelId);
607
- } else {
608
- // No folder found at all — fallback to message_labels join + LIKE on path
609
- where += ' AND (EXISTS (SELECT 1 FROM email_message_labels j WHERE j.message_id = m.id AND j.label_id = ?) OR LOWER(m.imap_folder_path) LIKE ?)';
610
- params.push(labelId, `%${lbl.system_type}%`);
611
- }
612
- } else {
613
- // Custom label — only use message_labels join
613
+ if (!labelFilterApplied) {
614
+ // Fallback: use message_labels join only
614
615
  where += ' AND EXISTS (SELECT 1 FROM email_message_labels j WHERE j.message_id = m.id AND j.label_id = ?)';
615
616
  params.push(labelId);
616
617
  }
617
618
  } else if (folder) {
618
- // Filter by folder path (used by Gmail — messages stored with imap_folder_path like INBOX, SENT, etc.)
619
619
  where += ' AND m.imap_folder_path = ?';
620
620
  params.push(folder.toUpperCase());
621
621
  }