nothumanallowed 6.5.1 → 6.6.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "6.5.1",
3
+ "version": "6.6.0",
4
4
  "description": "NotHumanAllowed — 38 AI agents for security, code, DevOps, data & daily ops. Per-agent memory, Telegram + Discord auto-responder, proactive intelligence daemon, voice chat, plugin system.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -125,12 +125,24 @@ TOOLS:
125
125
  18. schedule_draft_email(clientName: string, subject: string, location: string, durationMinutes: number, dateFrom: string, dateTo: string)
126
126
  Same as schedule_meeting but also generates a professional email proposing the top 3 slots to the client.
127
127
 
128
- 19. calendar_update(eventId: string, summary?: string, location?: string, description?: string, start?: string, end?: string)
128
+ 19. calendar_find(query: string, daysAhead?: number)
129
+ Search for a calendar event by name/keyword in the next N days (default 7). Returns matching events with their IDs.
130
+ ALWAYS use this FIRST when the user wants to modify an event — you need the eventId.
131
+
132
+ 20. calendar_update(eventId: string, summary?: string, location?: string, description?: string, start?: string, end?: string)
129
133
  Update ANY field of an existing calendar event: title, location, description, start time, end time.
130
- Use this to modify existing events. You must provide the eventId (from calendar_today or calendar_week).
131
- Only include fields that need to change. ALWAYS confirm with the user before updating.
134
+ You MUST call calendar_find first to get the eventId. Only include fields that need to change. ALWAYS confirm before updating.
135
+
136
+ 21. gmail_mark_read(messageId?: string, all?: boolean)
137
+ Mark email(s) as read. If all=true, marks ALL unread emails as read. If messageId provided, marks that single email.
138
+
139
+ 22. gmail_mark_unread(messageId: string)
140
+ Mark a specific email as unread.
132
141
 
133
- 20. maps_directions(from: string, to: string)
142
+ 23. gmail_archive(messageId: string)
143
+ Archive a specific email (removes from inbox).
144
+
145
+ 24. maps_directions(from: string, to: string)
134
146
  Generate a Google Maps directions link between two locations. Returns a clickable URL.
135
147
  Use this when the user asks for directions, route, or "how to get to" somewhere.
136
148
 
@@ -360,9 +372,49 @@ async function executeTool(action, params, config) {
360
372
  return `${proposal}\n\n--- DRAFT EMAIL ---\n\n${email}`;
361
373
  }
362
374
 
375
+ // ── Calendar Find (search event by name) ────────────────────────────
376
+ case 'calendar_find': {
377
+ const { listEvents: listEventsRouter } = await import('../services/mail-router.mjs');
378
+ const query = (params.query || '').toLowerCase();
379
+ const daysAhead = params.daysAhead || 7;
380
+ const from = new Date();
381
+ const to = new Date(from.getTime() + daysAhead * 86400000);
382
+ const events = await listEventsRouter(config, 'primary', from, to);
383
+
384
+ const matches = events.filter(e =>
385
+ (e.summary || '').toLowerCase().includes(query) ||
386
+ (e.description || '').toLowerCase().includes(query)
387
+ );
388
+
389
+ if (matches.length === 0) return `No events found matching "${params.query}" in the next ${daysAhead} days.`;
390
+
391
+ return matches.map((e, i) => {
392
+ const time = e.isAllDay ? 'All day' : `${formatTime(e.start)} - ${formatTime(e.end)}`;
393
+ const date = e.start.split('T')[0];
394
+ const loc = e.location ? ` | Location: ${e.location}` : '';
395
+ return `${i + 1}. [eventId: ${e.id}] ${date} ${time} — ${e.summary}${loc}`;
396
+ }).join('\n');
397
+ }
398
+
363
399
  // ── Calendar Update (modify any field of existing event) ──────────────
364
400
  case 'calendar_update': {
365
- const { updateEvent: updateCal } = await import('../services/mail-router.mjs');
401
+ const { updateEvent: updateCal, listEvents: listEvR } = await import('../services/mail-router.mjs');
402
+
403
+ // Smart eventId resolution: if it looks like a name instead of a Google Calendar ID, search for it
404
+ let eventId = params.eventId;
405
+ if (eventId && (eventId.includes(' ') || eventId.length < 10 || /[A-Z]/.test(eventId))) {
406
+ // Probably a name, not an ID — search for it
407
+ const from = new Date();
408
+ const to = new Date(from.getTime() + 14 * 86400000);
409
+ const events = await listEvR(config, 'primary', from, to);
410
+ const match = events.find(e => (e.summary || '').toLowerCase().includes(eventId.toLowerCase()));
411
+ if (match) {
412
+ eventId = match.id;
413
+ } else {
414
+ return `Could not find event matching "${params.eventId}" in the next 2 weeks. Use calendar_find to search.`;
415
+ }
416
+ }
417
+
366
418
  const patch = {};
367
419
  if (params.summary) patch.summary = params.summary;
368
420
  if (params.location) patch.location = params.location;
@@ -375,9 +427,36 @@ async function executeTool(action, params, config) {
375
427
  const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
376
428
  patch.end = { dateTime: new Date(params.end).toISOString(), timeZone: tz };
377
429
  }
378
- await updateCal(config, 'primary', params.eventId, patch);
430
+ await updateCal(config, 'primary', eventId, patch);
379
431
  const changes = Object.keys(patch).join(', ');
380
- return `Event updated (${changes}). ${params.location ? `New location: ${params.location}` : ''}`;
432
+ return `Event updated successfully (${changes}). ${params.location ? `New location: ${params.location}` : ''}`;
433
+ }
434
+
435
+ // ── Gmail Mark Read/Unread/Archive ──────────────────────────────────
436
+ case 'gmail_mark_read': {
437
+ if (params.all) {
438
+ const { markAllAsRead } = await import('../services/mail-router.mjs');
439
+ const result = await markAllAsRead(config);
440
+ return `Done! ${result.count} email${result.count !== 1 ? 's' : ''} marked as read.`;
441
+ }
442
+ if (params.messageId) {
443
+ const { markAsRead } = await import('../services/mail-router.mjs');
444
+ await markAsRead(config, params.messageId);
445
+ return `Email ${params.messageId} marked as read.`;
446
+ }
447
+ return 'Specify a messageId or set all=true to mark all as read.';
448
+ }
449
+
450
+ case 'gmail_mark_unread': {
451
+ const { markAsUnread } = await import('../services/mail-router.mjs');
452
+ await markAsUnread(config, params.messageId);
453
+ return `Email ${params.messageId} marked as unread.`;
454
+ }
455
+
456
+ case 'gmail_archive': {
457
+ const { archiveMessage } = await import('../services/mail-router.mjs');
458
+ await archiveMessage(config, params.messageId);
459
+ return `Email ${params.messageId} archived.`;
381
460
  }
382
461
 
383
462
  // ── Maps Directions (free Google Maps link) ──────────────────────────
@@ -306,8 +306,31 @@ async function executeTool(action, params, config) {
306
306
  return `${proposal}\n\n--- DRAFT EMAIL ---\n\n${email}`;
307
307
  }
308
308
 
309
+ case 'calendar_find': {
310
+ const { listEvents: listEventsR } = await import('../services/mail-router.mjs');
311
+ const query = (params.query || '').toLowerCase();
312
+ const daysAhead = params.daysAhead || 7;
313
+ const from = new Date();
314
+ const to = new Date(from.getTime() + daysAhead * 86400000);
315
+ const events = await listEventsR(config, 'primary', from, to);
316
+ const matches = events.filter(e => (e.summary || '').toLowerCase().includes(query) || (e.description || '').toLowerCase().includes(query));
317
+ if (matches.length === 0) return `No events matching "${params.query}" in the next ${daysAhead} days.`;
318
+ return matches.map((e, i) => {
319
+ const time = e.isAllDay ? 'All day' : `${fmtTime(e.start)} - ${fmtTime(e.end)}`;
320
+ return `${i + 1}. [eventId: ${e.id}] ${e.start.split('T')[0]} ${time} — ${e.summary}${e.location ? ' @ ' + e.location : ''}`;
321
+ }).join('\n');
322
+ }
309
323
  case 'calendar_update': {
310
- const { updateEvent: updateCal } = await import('../services/mail-router.mjs');
324
+ const { updateEvent: updateCal, listEvents: listEvAuto } = await import('../services/mail-router.mjs');
325
+ let eventId = params.eventId;
326
+ if (eventId && (eventId.includes(' ') || eventId.length < 10 || /[A-Z]/.test(eventId))) {
327
+ const from = new Date();
328
+ const to = new Date(from.getTime() + 14 * 86400000);
329
+ const events = await listEvAuto(config, 'primary', from, to);
330
+ const match = events.find(e => (e.summary || '').toLowerCase().includes(eventId.toLowerCase()));
331
+ if (match) eventId = match.id;
332
+ else return `Could not find event matching "${params.eventId}".`;
333
+ }
311
334
  const patch = {};
312
335
  if (params.summary) patch.summary = params.summary;
313
336
  if (params.location) patch.location = params.location;
@@ -320,9 +343,9 @@ async function executeTool(action, params, config) {
320
343
  const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
321
344
  patch.end = { dateTime: new Date(params.end).toISOString(), timeZone: tz };
322
345
  }
323
- await updateCal(config, 'primary', params.eventId, patch);
346
+ await updateCal(config, 'primary', eventId, patch);
324
347
  const changes = Object.keys(patch).join(', ');
325
- return `Event updated (${changes}). ${params.location ? 'New location: ' + params.location : ''}`;
348
+ return `Event updated successfully (${changes}). ${params.location ? 'New location: ' + params.location : ''}`;
326
349
  }
327
350
  case 'maps_directions': {
328
351
  const from = encodeURIComponent(params.from || '');
@@ -218,8 +218,27 @@ async function executeTool(action, params, config) {
218
218
  });
219
219
  return formatSlotProposal(slots, params.clientName || 'the client', params.subject || 'meeting', params.location || '');
220
220
  }
221
+ case 'calendar_find': {
222
+ const { listEvents: listEventsR } = await import('../services/mail-router.mjs');
223
+ const query = (params.query || '').toLowerCase();
224
+ const from = new Date();
225
+ const to = new Date(from.getTime() + 7 * 86400000);
226
+ const events = await listEventsR(config, 'primary', from, to);
227
+ const matches = events.filter(e => (e.summary || '').toLowerCase().includes(query));
228
+ if (matches.length === 0) return 'No events matching that name found this week.';
229
+ return matches.map((e, i) => `${i + 1}. Event ID ${e.id}, ${e.start.split('T')[0]}, ${e.summary}`).join('. ');
230
+ }
221
231
  case 'calendar_update': {
222
- const { updateEvent: updateCal } = await import('../services/mail-router.mjs');
232
+ const { updateEvent: updateCal, listEvents: listEvAuto } = await import('../services/mail-router.mjs');
233
+ let eventId = params.eventId;
234
+ if (eventId && (eventId.includes(' ') || eventId.length < 10 || /[A-Z]/.test(eventId))) {
235
+ const from = new Date();
236
+ const to = new Date(from.getTime() + 14 * 86400000);
237
+ const events = await listEvAuto(config, 'primary', from, to);
238
+ const match = events.find(e => (e.summary || '').toLowerCase().includes(eventId.toLowerCase()));
239
+ if (match) eventId = match.id;
240
+ else return 'Could not find that event.';
241
+ }
223
242
  const patch = {};
224
243
  if (params.summary) patch.summary = params.summary;
225
244
  if (params.location) patch.location = params.location;
@@ -232,9 +251,8 @@ async function executeTool(action, params, config) {
232
251
  const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
233
252
  patch.end = { dateTime: new Date(params.end).toISOString(), timeZone: tz };
234
253
  }
235
- await updateCal(config, 'primary', params.eventId, patch);
236
- const changes = Object.keys(patch).join(', ');
237
- return `Event updated (${changes}). ${params.location ? 'New location: ' + params.location : ''}`;
254
+ await updateCal(config, 'primary', eventId, patch);
255
+ return 'Event updated. ' + (params.location ? 'Location set to ' + params.location : '');
238
256
  }
239
257
  case 'maps_directions': {
240
258
  const from = encodeURIComponent(params.from || '');
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 = '6.5.1';
8
+ export const VERSION = '6.6.0';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -178,6 +178,60 @@ export async function listLabels(config) {
178
178
  return data.labels || [];
179
179
  }
180
180
 
181
+ /**
182
+ * Modify labels on a message (add/remove).
183
+ */
184
+ export async function modifyMessage(config, messageId, addLabels = [], removeLabels = []) {
185
+ return gmailFetch(config, `/messages/${messageId}/modify`, {
186
+ method: 'POST',
187
+ headers: { 'Content-Type': 'application/json' },
188
+ body: JSON.stringify({ addLabelIds: addLabels, removeLabelIds: removeLabels }),
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Mark a single message as read.
194
+ */
195
+ export async function markAsRead(config, messageId) {
196
+ return modifyMessage(config, messageId, [], ['UNREAD']);
197
+ }
198
+
199
+ /**
200
+ * Mark a single message as unread.
201
+ */
202
+ export async function markAsUnread(config, messageId) {
203
+ return modifyMessage(config, messageId, ['UNREAD'], []);
204
+ }
205
+
206
+ /**
207
+ * Archive a message (remove INBOX label).
208
+ */
209
+ export async function archiveMessage(config, messageId) {
210
+ return modifyMessage(config, messageId, [], ['INBOX']);
211
+ }
212
+
213
+ /**
214
+ * Batch modify multiple messages (mark all as read, archive all, etc.)
215
+ */
216
+ export async function batchModify(config, messageIds, addLabels = [], removeLabels = []) {
217
+ return gmailFetch(config, '/messages/batchModify', {
218
+ method: 'POST',
219
+ headers: { 'Content-Type': 'application/json' },
220
+ body: JSON.stringify({ ids: messageIds, addLabelIds: addLabels, removeLabelIds: removeLabels }),
221
+ });
222
+ }
223
+
224
+ /**
225
+ * Mark ALL unread messages as read.
226
+ */
227
+ export async function markAllAsRead(config) {
228
+ const unread = await listMessages(config, 'is:unread', 500);
229
+ if (unread.length === 0) return { count: 0 };
230
+ const ids = unread.map(m => m.id);
231
+ await batchModify(config, ids, [], ['UNREAD']);
232
+ return { count: ids.length };
233
+ }
234
+
181
235
  // ── Message Parser ─────────────────────────────────────────────────────────
182
236
 
183
237
  function parseMessage(raw) {
@@ -296,3 +296,43 @@ export async function listEvents(config, calendarId = 'primary', timeMin, timeMa
296
296
  const gc = await getGoogleCalendar();
297
297
  return gc.listEvents(config, calendarId, timeMin, timeMax);
298
298
  }
299
+
300
+ /**
301
+ * Mark a message as read.
302
+ */
303
+ export async function markAsRead(config, messageId) {
304
+ const provider = detectMailProvider(config);
305
+ if (!provider) throw new Error('No mail provider authenticated.');
306
+ const gm = await getGoogleMail();
307
+ return gm.markAsRead(config, messageId);
308
+ }
309
+
310
+ /**
311
+ * Mark a message as unread.
312
+ */
313
+ export async function markAsUnread(config, messageId) {
314
+ const provider = detectMailProvider(config);
315
+ if (!provider) throw new Error('No mail provider authenticated.');
316
+ const gm = await getGoogleMail();
317
+ return gm.markAsUnread(config, messageId);
318
+ }
319
+
320
+ /**
321
+ * Archive a message.
322
+ */
323
+ export async function archiveMessage(config, messageId) {
324
+ const provider = detectMailProvider(config);
325
+ if (!provider) throw new Error('No mail provider authenticated.');
326
+ const gm = await getGoogleMail();
327
+ return gm.archiveMessage(config, messageId);
328
+ }
329
+
330
+ /**
331
+ * Mark ALL unread messages as read.
332
+ */
333
+ export async function markAllAsRead(config) {
334
+ const provider = detectMailProvider(config);
335
+ if (!provider) throw new Error('No mail provider authenticated.');
336
+ const gm = await getGoogleMail();
337
+ return gm.markAllAsRead(config);
338
+ }