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 +1 -1
- package/src/commands/chat.mjs +86 -7
- package/src/commands/ui.mjs +26 -3
- package/src/commands/voice.mjs +22 -4
- package/src/constants.mjs +1 -1
- package/src/services/google-gmail.mjs +54 -0
- package/src/services/mail-router.mjs +40 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "6.
|
|
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": {
|
package/src/commands/chat.mjs
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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',
|
|
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) ──────────────────────────
|
package/src/commands/ui.mjs
CHANGED
|
@@ -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',
|
|
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 || '');
|
package/src/commands/voice.mjs
CHANGED
|
@@ -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',
|
|
236
|
-
|
|
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.
|
|
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
|
+
}
|