nothumanallowed 6.1.1 → 6.2.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.1.1",
3
+ "version": "6.2.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,9 +125,18 @@ 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)
129
+ 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.
132
+
133
+ 20. maps_directions(from: string, to: string)
134
+ Generate a Google Maps directions link between two locations. Returns a clickable URL.
135
+ Use this when the user asks for directions, route, or "how to get to" somewhere.
136
+
128
137
  RULES:
129
138
  - For search/read operations, execute immediately and present results conversationally.
130
- - For write/send/delete operations (gmail_send, gmail_reply, calendar_create, calendar_move, task_done, notify_remind), DESCRIBE what you're about to do and include the JSON block so the system can ask the user for confirmation.
139
+ - For write/send/delete operations (gmail_send, gmail_reply, calendar_create, calendar_move, calendar_update, task_done, notify_remind), DESCRIBE what you're about to do and include the JSON block so the system can ask the user for confirmation.
131
140
  - For schedule_meeting and schedule_draft_email, execute immediately — these are read operations that suggest slots.
132
141
  - When presenting email results, show From, Subject, Date, and a brief snippet. Never dump raw JSON.
133
142
  - When presenting calendar events, show Time, Title, Location/Link. Format times in a human-readable way.
@@ -332,7 +341,7 @@ async function executeTool(action, params, config) {
332
341
  workdayEnd: params.workdayEnd || 18,
333
342
  maxSlots: 5,
334
343
  });
335
- return formatSlotProposal(slots, params.clientName || 'the client', params.subject || 'meeting');
344
+ return formatSlotProposal(slots, params.clientName || 'the client', params.subject || 'meeting', params.location || '');
336
345
  }
337
346
 
338
347
  case 'schedule_draft_email': {
@@ -351,6 +360,34 @@ async function executeTool(action, params, config) {
351
360
  return `${proposal}\n\n--- DRAFT EMAIL ---\n\n${email}`;
352
361
  }
353
362
 
363
+ // ── Calendar Update (modify any field of existing event) ──────────────
364
+ case 'calendar_update': {
365
+ const { updateEvent: updateCal } = await import('../services/mail-router.mjs');
366
+ const patch = {};
367
+ if (params.summary) patch.summary = params.summary;
368
+ if (params.location) patch.location = params.location;
369
+ if (params.description) patch.description = params.description;
370
+ if (params.start) {
371
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
372
+ patch.start = { dateTime: new Date(params.start).toISOString(), timeZone: tz };
373
+ }
374
+ if (params.end) {
375
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
376
+ patch.end = { dateTime: new Date(params.end).toISOString(), timeZone: tz };
377
+ }
378
+ await updateCal(config, 'primary', params.eventId, patch);
379
+ const changes = Object.keys(patch).join(', ');
380
+ return `Event updated (${changes}). ${params.location ? `New location: ${params.location}` : ''}`;
381
+ }
382
+
383
+ // ── Maps Directions (free Google Maps link) ──────────────────────────
384
+ case 'maps_directions': {
385
+ const from = encodeURIComponent(params.from || '');
386
+ const to = encodeURIComponent(params.to || '');
387
+ if (!from || !to) return 'Both "from" and "to" locations are required.';
388
+ return `Google Maps directions:\nhttps://www.google.com/maps/dir/${from}/${to}`;
389
+ }
390
+
354
391
  default:
355
392
  return `Unknown action: ${action}`;
356
393
  }
@@ -90,6 +90,12 @@ TOOLS:
90
90
  14. schedule_draft_email(clientName: string, subject: string, location: string, durationMinutes: number, dateFrom: string, dateTo: string)
91
91
  Same as schedule_meeting but also generates a professional email proposing the top 3 slots.
92
92
 
93
+ 15. calendar_update(eventId: string, summary?: string, location?: string, description?: string, start?: string, end?: string)
94
+ Update ANY field of an existing calendar event. Only include fields that need to change. Confirm with the user first.
95
+
96
+ 16. maps_directions(from: string, to: string)
97
+ Generate a Google Maps directions link between two locations.
98
+
93
99
  RULES:
94
100
  - For search/read operations, execute immediately and present results conversationally.
95
101
  - For write/send/delete operations, describe what you're about to do and include the JSON block.
@@ -280,7 +286,7 @@ async function executeTool(action, params, config) {
280
286
  workdayEnd: params.workdayEnd || 18,
281
287
  maxSlots: 5,
282
288
  });
283
- return formatSlotProposal(slots, params.clientName || 'the client', params.subject || 'meeting');
289
+ return formatSlotProposal(slots, params.clientName || 'the client', params.subject || 'meeting', params.location || '');
284
290
  }
285
291
 
286
292
  case 'schedule_draft_email': {
@@ -299,6 +305,30 @@ async function executeTool(action, params, config) {
299
305
  return `${proposal}\n\n--- DRAFT EMAIL ---\n\n${email}`;
300
306
  }
301
307
 
308
+ case 'calendar_update': {
309
+ const { updateEvent: updateCal } = await import('../services/mail-router.mjs');
310
+ const patch = {};
311
+ if (params.summary) patch.summary = params.summary;
312
+ if (params.location) patch.location = params.location;
313
+ if (params.description) patch.description = params.description;
314
+ if (params.start) {
315
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
316
+ patch.start = { dateTime: new Date(params.start).toISOString(), timeZone: tz };
317
+ }
318
+ if (params.end) {
319
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
320
+ patch.end = { dateTime: new Date(params.end).toISOString(), timeZone: tz };
321
+ }
322
+ await updateCal(config, 'primary', params.eventId, patch);
323
+ const changes = Object.keys(patch).join(', ');
324
+ return `Event updated (${changes}). ${params.location ? 'New location: ' + params.location : ''}`;
325
+ }
326
+ case 'maps_directions': {
327
+ const from = encodeURIComponent(params.from || '');
328
+ const to = encodeURIComponent(params.to || '');
329
+ if (!from || !to) return 'Both "from" and "to" locations are required.';
330
+ return `Google Maps directions:\nhttps://www.google.com/maps/dir/${from}/${to}`;
331
+ }
302
332
  default:
303
333
  return `Unknown action: ${action}`;
304
334
  }
@@ -77,6 +77,12 @@ TOOLS:
77
77
  12. schedule_meeting(clientName: string, subject: string, location: string, durationMinutes: number, dateFrom: string, dateTo: string)
78
78
  Find optimal meeting slots considering calendar, locations, and travel time.
79
79
 
80
+ 13. calendar_update(eventId: string, summary?: string, location?: string, description?: string, start?: string, end?: string)
81
+ Update any field of an existing calendar event. Confirm first.
82
+
83
+ 14. maps_directions(from: string, to: string)
84
+ Generate a Google Maps directions link.
85
+
80
86
  RULES:
81
87
  - For search/read operations, execute immediately and present results conversationally.
82
88
  - For write/send/delete operations, describe what you're about to do and include the JSON block.
@@ -210,7 +216,31 @@ async function executeTool(action, params, config) {
210
216
  workdayEnd: 18,
211
217
  maxSlots: 3,
212
218
  });
213
- return formatSlotProposal(slots, params.clientName || 'the client', params.subject || 'meeting');
219
+ return formatSlotProposal(slots, params.clientName || 'the client', params.subject || 'meeting', params.location || '');
220
+ }
221
+ case 'calendar_update': {
222
+ const { updateEvent: updateCal } = await import('../services/mail-router.mjs');
223
+ const patch = {};
224
+ if (params.summary) patch.summary = params.summary;
225
+ if (params.location) patch.location = params.location;
226
+ if (params.description) patch.description = params.description;
227
+ if (params.start) {
228
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
229
+ patch.start = { dateTime: new Date(params.start).toISOString(), timeZone: tz };
230
+ }
231
+ if (params.end) {
232
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
233
+ patch.end = { dateTime: new Date(params.end).toISOString(), timeZone: tz };
234
+ }
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 : ''}`;
238
+ }
239
+ case 'maps_directions': {
240
+ const from = encodeURIComponent(params.from || '');
241
+ const to = encodeURIComponent(params.to || '');
242
+ if (!from || !to) return 'Both "from" and "to" locations are required.';
243
+ return `Google Maps directions:\nhttps://www.google.com/maps/dir/${from}/${to}`;
214
244
  }
215
245
  default:
216
246
  return `Unknown action: ${action}`;
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.1.1';
8
+ export const VERSION = '6.2.0';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -286,11 +286,11 @@ export async function findAvailableSlots(config, params) {
286
286
  const nextEvent = dayEvents.find(e => new Date(e.start).getTime() >= block.eventStart - 60000);
287
287
 
288
288
  const travelBefore = prevEvent
289
- ? estimateTravelTime(prevEvent.location, meetingLocation)
290
- : { minutes: 0, label: 'no previous event' };
289
+ ? { ...estimateTravelTime(prevEvent.location, meetingLocation), fromLocation: prevEvent.location }
290
+ : { minutes: 0, label: 'no previous event', fromLocation: '' };
291
291
  const travelAfter = nextEvent
292
- ? estimateTravelTime(meetingLocation, nextEvent.location)
293
- : { minutes: 0, label: 'no next event' };
292
+ ? { ...estimateTravelTime(meetingLocation, nextEvent.location), toLocation: nextEvent.location }
293
+ : { minutes: 0, label: 'no next event', toLocation: '' };
294
294
 
295
295
  const slotStart = new Date(cursor + travelBefore.minutes * 60000);
296
296
  const slotEnd = new Date(slotStart.getTime() + durationMs);
@@ -326,8 +326,8 @@ export async function findAvailableSlots(config, params) {
326
326
  if (gapDuration >= durationMs) {
327
327
  const lastEvent = dayEvents[dayEvents.length - 1];
328
328
  const travelBefore = lastEvent
329
- ? estimateTravelTime(lastEvent.location, meetingLocation)
330
- : { minutes: 0, label: 'no previous event' };
329
+ ? { ...estimateTravelTime(lastEvent.location, meetingLocation), fromLocation: lastEvent.location }
330
+ : { minutes: 0, label: 'no previous event', fromLocation: '' };
331
331
 
332
332
  const slotStart = new Date(cursor + travelBefore.minutes * 60000);
333
333
  const slotEnd = new Date(slotStart.getTime() + durationMs);
@@ -360,10 +360,26 @@ export async function findAvailableSlots(config, params) {
360
360
  return slots.slice(0, maxSlots);
361
361
  }
362
362
 
363
+ /**
364
+ * Generate a Google Maps directions link between two locations.
365
+ * Free — just opens the browser, no API key needed.
366
+ *
367
+ * @param {string} from — origin location/city
368
+ * @param {string} to — destination location/city
369
+ * @returns {string} Google Maps URL
370
+ */
371
+ export function getMapsLink(from, to) {
372
+ if (!from || !to) return '';
373
+ const origin = encodeURIComponent(from);
374
+ const dest = encodeURIComponent(to);
375
+ return `https://www.google.com/maps/dir/${origin}/${dest}`;
376
+ }
377
+
363
378
  /**
364
379
  * Format slots into a human-readable proposal string.
380
+ * Includes Google Maps links for travel between locations.
365
381
  */
366
- export function formatSlotProposal(slots, clientName, meetingSubject) {
382
+ export function formatSlotProposal(slots, clientName, meetingSubject, meetingLocation) {
367
383
  if (slots.length === 0) {
368
384
  return 'No available slots found in the requested date range. Try expanding the range or shortening the meeting duration.';
369
385
  }
@@ -375,9 +391,16 @@ export function formatSlotProposal(slots, clientName, meetingSubject) {
375
391
  lines.push(`${i + 1}. ${s.day} ${s.date} — ${s.startTime} to ${s.endTime}`);
376
392
  if (s.travelBefore.minutes > 0) {
377
393
  lines.push(` Travel before: ${s.travelBefore.label}`);
394
+ // Add Maps link if we have a previous location and the meeting location
395
+ if (s.travelBefore.fromLocation && meetingLocation) {
396
+ lines.push(` Route: ${getMapsLink(s.travelBefore.fromLocation, meetingLocation)}`);
397
+ }
378
398
  }
379
399
  if (s.travelAfter.minutes > 0) {
380
400
  lines.push(` Travel after: ${s.travelAfter.label}`);
401
+ if (meetingLocation && s.travelAfter.toLocation) {
402
+ lines.push(` Route: ${getMapsLink(meetingLocation, s.travelAfter.toLocation)}`);
403
+ }
381
404
  }
382
405
  }
383
406
 
@@ -442,7 +442,7 @@ export async function executeTool(action, params, config) {
442
442
  workdayEnd: params.workdayEnd || 18,
443
443
  maxSlots: 5,
444
444
  });
445
- return formatSlotProposal(slots, params.clientName || 'the client', params.subject || 'meeting');
445
+ return formatSlotProposal(slots, params.clientName || 'the client', params.subject || 'meeting', params.location || '');
446
446
  }
447
447
 
448
448
  case 'schedule_draft_email': {