nothumanallowed 14.1.60 → 14.1.62

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.60",
3
+ "version": "14.1.62",
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.60';
8
+ export const VERSION = '14.1.62';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -98,9 +98,26 @@ export function register(router) {
98
98
  success: true,
99
99
  message: newVersion ? `Updated to v${newVersion}` : 'Update completed',
100
100
  newVersion,
101
+ restart: true,
101
102
  stdout: stdout.trim(),
102
103
  stderr: stderr.trim()
103
104
  });
105
+
106
+ // Self-restart: spawn new process with same args, then exit
107
+ // Delay to allow HTTP response to flush
108
+ setTimeout(async () => {
109
+ try {
110
+ const { spawn } = await import('child_process');
111
+ const args = process.argv.slice(1);
112
+ const child = spawn(process.argv[0], args, {
113
+ detached: true,
114
+ stdio: 'ignore',
115
+ env: process.env,
116
+ });
117
+ child.unref();
118
+ } catch { /* ignore spawn errors */ }
119
+ process.exit(0);
120
+ }, 1500);
104
121
  } catch (e) {
105
122
  const isPermission = e.message?.includes('EACCES') || e.message?.includes('permission denied');
106
123
  sendJSON(res, 200, {
@@ -205,6 +205,7 @@ export function register(router) {
205
205
 
206
206
  const url = new URL(req.url, 'http://localhost');
207
207
  const folder = url.searchParams.get('folder') || 'inbox';
208
+ const searchQuery = url.searchParams.get('search') || '';
208
209
  const limit = parseInt(url.searchParams.get('pageSize') || url.searchParams.get('limit') || '200');
209
210
  const offset = parseInt(url.searchParams.get('page') || url.searchParams.get('offset') || '0') * (url.searchParams.get('page') ? limit : 1);
210
211
  try {
@@ -219,7 +220,7 @@ export function register(router) {
219
220
  : folder.toUpperCase() === 'STARRED' ? 'STARRED'
220
221
  : folder.toUpperCase() === 'IMPORTANT' ? 'IMPORTANT'
221
222
  : folder;
222
- const emails = await getAllEmails(config, gmailFolder, limit + offset);
223
+ const emails = await getAllEmails(config, gmailFolder, limit + offset, searchQuery);
223
224
  sendJSON(res, 200, { emails: emails.slice(offset, offset + limit), total: emails.length });
224
225
 
225
226
  // Persist fetched emails to local SQLite DB (fire-and-forget, non-blocking)
@@ -571,13 +571,32 @@ export function listMessages(accountId, labelId, limit, offset, search, folder)
571
571
  const params = [accountId];
572
572
 
573
573
  if (labelId) {
574
- // Filter by label — also include messages saved with matching imap_folder_path
574
+ // Filter by label — look up the actual folder path from email_folders for this account
575
575
  const lbl = db.prepare('SELECT system_type FROM email_labels WHERE id = ?').get(labelId);
576
- const folderFallback = lbl?.system_type ? lbl.system_type.charAt(0).toUpperCase() + lbl.system_type.slice(1) : null;
577
- if (folderFallback) {
578
- where += ' AND (EXISTS (SELECT 1 FROM email_message_labels j WHERE j.message_id = m.id AND j.label_id = ?) OR m.imap_folder_path = ?)';
579
- params.push(labelId, folderFallback);
576
+ if (lbl?.system_type) {
577
+ // Find the actual folder(s) in email_folders that match this system_type (folder_type)
578
+ const folders = db.prepare('SELECT path FROM email_folders WHERE account_id = ? AND folder_type = ?').all(accountId, lbl.system_type);
579
+ if (folders.length > 0) {
580
+ // Filter by actual folder paths OR by message_labels join OR by folder_id
581
+ const placeholders = folders.map(() => '?').join(',');
582
+ where += ` AND (m.imap_folder_path IN (${placeholders}) OR EXISTS (SELECT 1 FROM email_message_labels j WHERE j.message_id = m.id AND j.label_id = ?))`;
583
+ params.push(...folders.map(f => f.path), labelId);
584
+ } else {
585
+ // No folder found with correct folder_type — also try matching by name pattern
586
+ // Also look for folders whose name/path contains the system_type keyword (handles legacy 'custom' type)
587
+ const foldersByName = db.prepare('SELECT path FROM email_folders WHERE account_id = ? AND (LOWER(path) LIKE ? OR LOWER(name) LIKE ?)').all(accountId, `%${lbl.system_type}%`, `%${lbl.system_type}%`);
588
+ if (foldersByName.length > 0) {
589
+ const placeholders = foldersByName.map(() => '?').join(',');
590
+ where += ` AND (m.imap_folder_path IN (${placeholders}) OR EXISTS (SELECT 1 FROM email_message_labels j WHERE j.message_id = m.id AND j.label_id = ?))`;
591
+ params.push(...foldersByName.map(f => f.path), labelId);
592
+ } else {
593
+ // Last resort — LIKE match on imap_folder_path
594
+ 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 ?)';
595
+ params.push(labelId, `%${lbl.system_type}%`);
596
+ }
597
+ }
580
598
  } else {
599
+ // Custom label (no system_type) — only use message_labels join
581
600
  where += ' AND EXISTS (SELECT 1 FROM email_message_labels j WHERE j.message_id = m.id AND j.label_id = ?)';
582
601
  params.push(labelId);
583
602
  }
@@ -148,10 +148,12 @@ function stripQuotedReplies(text) {
148
148
  return text.split('\n').filter(l => !l.startsWith('>')).join('\n').trim();
149
149
  }
150
150
 
151
- export async function syncFolder(accountId, folderPath, fullResync, limitMessages = 0) {
151
+ export async function syncFolder(accountId, folderPath, fullResync, limitMessages = 0, folderType = null) {
152
152
  const client = await getImapClient(accountId);
153
153
  const db = getDb();
154
154
  const folderMeta = getFolder(accountId, folderPath);
155
+ // Use passed folderType or existing metadata (fixes first-sync creating folders as 'custom')
156
+ const resolvedFolderType = folderType || resolvedFolderType;
155
157
 
156
158
  // Open mailbox to get uidValidity BEFORE getMailboxLock
157
159
  const lock = await client.getMailboxLock(folderPath);
@@ -271,7 +273,7 @@ export async function syncFolder(accountId, folderPath, fullResync, limitMessage
271
273
  const hash = contentHash(msgId || '', fromAddr || '', hdr.internalDate);
272
274
 
273
275
  const folderRec = upsertFolder(accountId, folderPath, folderPath,
274
- folderMeta?.folder_type || 'custom', currentUidValidity, newLastUid);
276
+ resolvedFolderType, currentUidValidity, newLastUid);
275
277
 
276
278
  const msgDbId = insertMessage({
277
279
  account_id: accountId,
@@ -313,7 +315,7 @@ export async function syncFolder(accountId, folderPath, fullResync, limitMessage
313
315
  }
314
316
 
315
317
  upsertFolder(accountId, folderPath, folderPath,
316
- folderMeta?.folder_type || 'custom', currentUidValidity, newLastUid);
318
+ resolvedFolderType, currentUidValidity, newLastUid);
317
319
 
318
320
  updateLabelCounts(accountId);
319
321
  return { synced, lastUid: newLastUid, total: totalMessages };
@@ -339,7 +341,7 @@ export async function syncAccount(accountId, opts = {}) {
339
341
  for (const f of toSync) {
340
342
  try {
341
343
  const limit = opts.full ? 0 : FIRST_SYNC_LIMIT;
342
- const result = await syncFolder(accountId, f.path, false, limit);
344
+ const result = await syncFolder(accountId, f.path, false, limit, f.folderType);
343
345
  totalSynced += result.synced;
344
346
  console.log(`[email:sync] ${f.path}: ${result.synced} new messages (total on server: ${result.total})`);
345
347
  } catch (err) {
@@ -99,7 +99,7 @@ export async function getUnreadImportant(config, maxResults = 30) {
99
99
  * Get ALL emails from a folder (read + unread) for local-first architecture.
100
100
  * This replaces getUnreadImportant to ensure ALL emails are downloaded locally.
101
101
  */
102
- export async function getAllEmails(config, folder = 'INBOX', maxResults = 200) {
102
+ export async function getAllEmails(config, folder = 'INBOX', maxResults = 200, search = '') {
103
103
  // Map folder names to Gmail queries
104
104
  const folderQueries = {
105
105
  'INBOX': 'in:inbox',
@@ -111,7 +111,8 @@ export async function getAllEmails(config, folder = 'INBOX', maxResults = 200) {
111
111
  'IMPORTANT': 'is:important'
112
112
  };
113
113
 
114
- const query = folderQueries[folder.toUpperCase()] || `in:${folder.toLowerCase()}`;
114
+ let query = folderQueries[folder.toUpperCase()] || `in:${folder.toLowerCase()}`;
115
+ if (search) query += ` ${search}`;
115
116
  const messageRefs = await listMessages(config, query, maxResults);
116
117
  const messages = [];
117
118