nothumanallowed 14.1.58 → 14.1.60

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.58",
3
+ "version": "14.1.60",
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": {
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.58';
8
+ export const VERSION = '14.1.60';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -96,7 +96,92 @@ async function persistGmailMessageToDb(config, msg) {
96
96
  }
97
97
 
98
98
  export function register(router) {
99
- // ── Gmail ──────────────────────────────────────────────────────────────
99
+ // ── Gmail DB-first routes ──────────────────────────────────────────────
100
+
101
+ // GET /api/gmail/messages — reads from local SQLite DB (fast, offline)
102
+ router.get('/api/gmail/messages', async (req, res) => {
103
+ try {
104
+ const { SQLITE_AVAILABLE, listMessages } = await import('../../services/email-db.mjs');
105
+ if (!SQLITE_AVAILABLE) return sendJSON(res, 200, { messages: [], total: 0, dbUnavailable: true });
106
+
107
+ const url = new URL(req.url, 'http://localhost');
108
+ const folder = url.searchParams.get('folder') || 'INBOX';
109
+ const limit = parseInt(url.searchParams.get('limit') || '50');
110
+ const offset = parseInt(url.searchParams.get('offset') || '0');
111
+ const search = url.searchParams.get('search') || '';
112
+
113
+ const result = listMessages('google', null, limit, offset, search, folder);
114
+ sendJSON(res, 200, { messages: result.messages ?? [], total: result.total ?? 0, source: 'db' });
115
+ } catch (e) { sendJSON(res, 200, { messages: [], total: 0, error: e.message }); }
116
+ });
117
+
118
+ // POST /api/gmail/sync — fetches from Gmail API and saves to local DB
119
+ router.post('/api/gmail/sync', async (req, res) => {
120
+ try {
121
+ const body = await parseBody(req);
122
+ const config = loadConfig();
123
+
124
+ if (!config.google?.clientId && !config.google?.tokens?.access_token) {
125
+ return sendJSON(res, 200, { synced: 0, authRequired: true });
126
+ }
127
+
128
+ const folder = (body.folder || 'INBOX').toUpperCase();
129
+ const limit = body.limit || 50;
130
+
131
+ const { getAllEmails } = await import('../../services/google-gmail.mjs');
132
+ const emails = await getAllEmails(config, folder, limit);
133
+
134
+ // getAllEmails already calls cacheMessages internally which saves to SQLite DB
135
+ // Also persist via persistGmailMessageToDb for body_html
136
+ for (const msg of emails) {
137
+ await persistGmailMessageToDb(config, msg).catch(() => {});
138
+ }
139
+
140
+ sendJSON(res, 200, { synced: emails.length, folder });
141
+ } catch (e) {
142
+ sendJSON(res, 200, { synced: 0, error: e.message });
143
+ }
144
+ });
145
+
146
+ // GET /api/gmail/message — read single message from DB, fallback to Gmail API
147
+ router.get('/api/gmail/message', async (req, res) => {
148
+ try {
149
+ const url = new URL(req.url, 'http://localhost');
150
+ const id = url.searchParams.get('id');
151
+ if (!id) return sendError(res, 400, 'id required');
152
+
153
+ // Try DB first
154
+ const { getMessage: dbGetMessage } = await import('../../services/email-db.mjs');
155
+ const dbMsg = dbGetMessage(id);
156
+ if (dbMsg && (dbMsg.body_text || dbMsg.body_html)) {
157
+ return sendJSON(res, 200, { message: dbMsg, source: 'db' });
158
+ }
159
+
160
+ // Fallback to Gmail API
161
+ const config = loadConfig();
162
+ const msg = await getMessage(config, id);
163
+ if (msg) {
164
+ persistGmailMessageToDb(config, msg).catch(() => {});
165
+ return sendJSON(res, 200, {
166
+ message: {
167
+ id: msg.id,
168
+ subject: msg.subject,
169
+ from_address: msg.from ? (msg.from.match(/<(.+?)>$/)?.[1] || msg.from) : '',
170
+ from_name: msg.from ? (msg.from.match(/^(.+?)\s*<.+>$/)?.[1]?.replace(/['"]/g, '') || '') : '',
171
+ to_addresses: msg.to || '',
172
+ internal_date: msg.date || '',
173
+ body_text: msg.body || '',
174
+ body_html: msg.bodyHtml || '',
175
+ body_preview: msg.snippet || '',
176
+ },
177
+ source: 'gmail_api',
178
+ });
179
+ }
180
+ sendJSON(res, 200, { message: null });
181
+ } catch (e) { sendError(res, 500, e.message); }
182
+ });
183
+
184
+ // ── Gmail legacy ──────────────────────────────────────────────────────
100
185
 
101
186
  router.get('/api/emails', async (req, res) => {
102
187
  try {
@@ -325,12 +410,12 @@ export function register(router) {
325
410
  const { listMessages } = await import('../../services/email-db.mjs');
326
411
  const url = new URL(req.url, 'http://localhost');
327
412
  const accountId = url.searchParams.get('accountId');
328
- const folder = url.searchParams.get('folder') || 'INBOX';
413
+ const labelId = url.searchParams.get('labelId') || null;
414
+ const folder = url.searchParams.get('folder') || null;
329
415
  const limit = parseInt(url.searchParams.get('limit') || '50');
330
416
  const offset = parseInt(url.searchParams.get('offset') || '0');
331
417
  const search = url.searchParams.get('search') || '';
332
- // listMessages(accountId, labelId, limit, offset, search)
333
- const result = listMessages(accountId, null, limit, offset, search);
418
+ const result = listMessages(accountId, labelId, limit, offset, search, folder);
334
419
  sendJSON(res, 200, { messages: result.messages ?? result, total: result.total ?? 0 });
335
420
  } catch (e) { sendError(res, 500, e.message); }
336
421
  });
@@ -565,13 +565,13 @@ export function getMessageLabels(messageId) {
565
565
 
566
566
  // ── MESSAGE QUERIES ────────────────────────────────────────────────────────
567
567
 
568
- export function listMessages(accountId, labelId, limit, offset, search) {
568
+ export function listMessages(accountId, labelId, limit, offset, search, folder) {
569
569
  const db = getDb();
570
570
  let where = 'm.account_id = ? AND m.permanently_deleted = 0';
571
571
  const params = [accountId];
572
572
 
573
573
  if (labelId) {
574
- // Also include messages saved with matching imap_folder_path in case label link was missing
574
+ // Filter by label — also include messages saved with matching imap_folder_path
575
575
  const lbl = db.prepare('SELECT system_type FROM email_labels WHERE id = ?').get(labelId);
576
576
  const folderFallback = lbl?.system_type ? lbl.system_type.charAt(0).toUpperCase() + lbl.system_type.slice(1) : null;
577
577
  if (folderFallback) {
@@ -581,12 +581,20 @@ export function listMessages(accountId, labelId, limit, offset, search) {
581
581
  where += ' AND EXISTS (SELECT 1 FROM email_message_labels j WHERE j.message_id = m.id AND j.label_id = ?)';
582
582
  params.push(labelId);
583
583
  }
584
+ } else if (folder) {
585
+ // Filter by folder path (used by Gmail — messages stored with imap_folder_path like INBOX, SENT, etc.)
586
+ where += ' AND m.imap_folder_path = ?';
587
+ params.push(folder.toUpperCase());
584
588
  }
585
589
  if (search) {
586
- where += ' AND (m.subject LIKE ? OR m.from_address LIKE ? OR m.from_name LIKE ? OR m.body_preview LIKE ?)';
590
+ where += ' AND (m.subject LIKE ? OR m.from_address LIKE ? OR m.from_name LIKE ? OR m.body_preview LIKE ? OR m.body_text LIKE ?)';
587
591
  const q = `%${search}%`;
588
- params.push(q, q, q, q);
592
+ params.push(q, q, q, q, q);
589
593
  }
594
+
595
+ const countWhere = where;
596
+ const countParams = [...params];
597
+
590
598
  params.push(limit || 50, offset || 0);
591
599
 
592
600
  const rows = db.prepare(`
@@ -601,7 +609,7 @@ export function listMessages(accountId, labelId, limit, offset, search) {
601
609
  LIMIT ? OFFSET ?
602
610
  `).all(...params);
603
611
 
604
- const total = db.prepare(`SELECT COUNT(*) as cnt FROM email_messages m WHERE ${where.replace('LIMIT ? OFFSET ?', '')}`).get(...params.slice(0, -2));
612
+ const total = db.prepare(`SELECT COUNT(*) as cnt FROM email_messages m WHERE ${countWhere}`).get(...countParams);
605
613
 
606
614
  return { messages: rows, total: total?.cnt || 0 };
607
615
  }