nothumanallowed 14.1.67 → 14.1.69

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.67",
3
+ "version": "14.1.69",
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.67';
8
+ export const VERSION = '14.1.69';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -4,13 +4,74 @@
4
4
 
5
5
  import { sendJSON, sendError, parseBody } from '../index.mjs';
6
6
  import { loadConfig } from '../../config.mjs';
7
- import { getTodayEvents, getUpcomingEvents, createEvent, updateEvent, deleteEvent, getEventsForDate } from '../../services/mail-router.mjs';
7
+ import { getTodayEvents, getUpcomingEvents, createEvent, updateEvent, deleteEvent, getEventsForDate, listEvents, detectMailProvider } from '../../services/mail-router.mjs';
8
8
  import { getTasks, addTask, completeTask, getDayStats } from '../../services/task-store.mjs';
9
9
  import { runPlanningPipeline } from '../../services/ops-pipeline.mjs';
10
10
  import { NHA_DIR } from '../../constants.mjs';
11
11
  import fs from 'fs';
12
12
  import path from 'path';
13
13
 
14
+ /**
15
+ * Load all events for a given month from all calendars.
16
+ * Returns { byDate: { "2026-05-01": [...], "2026-05-02": [...] } }
17
+ */
18
+ async function getMonthEvents(config, monthStr) {
19
+ // monthStr = "2026-05"
20
+ const [y, m] = monthStr.split('-').map(Number);
21
+ const startOfMonth = new Date(y, m - 1, 1);
22
+ const endOfMonth = new Date(y, m, 1); // first day of next month
23
+
24
+ const provider = detectMailProvider(config);
25
+ if (!provider) throw new Error('No mail provider authenticated.');
26
+
27
+ // Load calendars list
28
+ let listCalendars;
29
+ if (provider === 'microsoft') {
30
+ const ms = await import('../../services/microsoft-calendar.mjs');
31
+ listCalendars = ms.listCalendars || (() => [{ id: 'primary', accessRole: 'owner' }]);
32
+ } else {
33
+ const gc = await import('../../services/google-calendar.mjs');
34
+ listCalendars = gc.listCalendars;
35
+ }
36
+
37
+ const calendars = await listCalendars(config);
38
+ const byDate = {};
39
+
40
+ for (const cal of calendars) {
41
+ if (cal.accessRole === 'freeBusyReader') continue;
42
+ const isHolidayFeed = (cal.id || '').includes('#holiday@group');
43
+ try {
44
+ const events = await listEvents(config, cal.id, startOfMonth, endOfMonth);
45
+ for (const e of events) {
46
+ e.calendarName = cal.summary;
47
+ e.calendarId = cal.id;
48
+ e.readOnly = cal.accessRole === 'reader' || cal.accessRole === 'freeBusyReader';
49
+ e._isHoliday = isHolidayFeed;
50
+
51
+ // Determine which date this event belongs to
52
+ const dateKey = (e.start || '').slice(0, 10);
53
+ if (!dateKey) continue;
54
+ if (!byDate[dateKey]) byDate[dateKey] = [];
55
+ byDate[dateKey].push(e);
56
+ }
57
+ } catch { /* skip failed calendars */ }
58
+ }
59
+
60
+ // Sort events within each day and deduplicate holidays
61
+ for (const dateKey of Object.keys(byDate)) {
62
+ byDate[dateKey].sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());
63
+ const holidaySeen = new Set();
64
+ byDate[dateKey] = byDate[dateKey].filter(e => {
65
+ if (!e._isHoliday) return true;
66
+ if (holidaySeen.has(dateKey)) return false;
67
+ holidaySeen.add(dateKey);
68
+ return true;
69
+ });
70
+ }
71
+
72
+ return { byDate };
73
+ }
74
+
14
75
  export function register(router) {
15
76
  // ── Calendar ──────────────────────────────────────────────────────────
16
77
 
@@ -18,10 +79,10 @@ export function register(router) {
18
79
  try {
19
80
  const config = loadConfig();
20
81
 
21
- // ENTERPRISE OAUTH CHECK: Validate Google configuration upfront
22
82
  if (!config.google?.clientId && !config.google?.tokens?.access_token) {
23
83
  return sendJSON(res, 200, {
24
84
  events: [],
85
+ byDate: {},
25
86
  authRequired: true,
26
87
  message: 'Google Calendar requires authentication. Setup OAuth to view events.',
27
88
  setupUrl: 'https://console.cloud.google.com/apis/credentials'
@@ -29,16 +90,28 @@ export function register(router) {
29
90
  }
30
91
 
31
92
  const url = new URL(req.url, 'http://localhost');
93
+ const month = url.searchParams.get('month');
32
94
  const date = url.searchParams.get('date');
33
- const events = date ? await getEventsForDate(config, date) : await getTodayEvents(config);
95
+
96
+ if (month) {
97
+ // Month view: return all events grouped by date
98
+ const result = await getMonthEvents(config, month);
99
+ return sendJSON(res, 200, result);
100
+ }
101
+
102
+ if (date) {
103
+ const events = await getEventsForDate(config, date);
104
+ return sendJSON(res, 200, { events });
105
+ }
106
+
107
+ // Default: today's events
108
+ const events = await getTodayEvents(config);
34
109
  sendJSON(res, 200, { events });
35
110
  } catch (e) {
36
111
  const msg = e.message || '';
37
-
38
- // ENHANCED ERROR DETECTION
39
112
  if (msg.includes('invalid_grant') || msg.includes('unauthorized') || msg.includes('token')) {
40
113
  return sendJSON(res, 200, {
41
- events: [],
114
+ events: [], byDate: {},
42
115
  authRequired: true,
43
116
  message: 'Google OAuth expired. Please re-authenticate.',
44
117
  error: 'Authentication required'
@@ -51,7 +124,7 @@ export function register(router) {
51
124
  });
52
125
  }
53
126
  if (msg.includes('No mail provider') || msg.includes('not authenticated') || msg.includes('No Google')) {
54
- return sendJSON(res, 200, { events: [], authRequired: true, error: msg });
127
+ return sendJSON(res, 200, { events: [], byDate: {}, authRequired: true, error: msg });
55
128
  }
56
129
  sendError(res, 500, msg);
57
130
  }
@@ -73,23 +146,46 @@ export function register(router) {
73
146
  }
74
147
  });
75
148
 
149
+ // Create a new event
76
150
  router.post('/api/calendar', async (req, res) => {
77
151
  try {
78
152
  const body = await parseBody(req);
79
153
  const config = loadConfig();
80
- if (body.action === 'update' && body.id) {
81
- const updated = await updateEvent(config, body.id, body);
82
- return sendJSON(res, 200, { event: updated });
83
- }
84
- if (body.action === 'delete' && body.id) {
85
- await deleteEvent(config, body.id);
86
- return sendJSON(res, 200, { ok: true });
87
- }
88
154
  const event = await createEvent(config, body);
89
155
  sendJSON(res, 201, { event });
90
156
  } catch (e) { sendError(res, 500, e.message); }
91
157
  });
92
158
 
159
+ // Update an existing event (PATCH)
160
+ router.patch('/api/calendar/:calId/:eventId', async (req, res) => {
161
+ try {
162
+ const body = await parseBody(req);
163
+ const config = loadConfig();
164
+ const { calId, eventId } = req.params;
165
+
166
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
167
+ const patch = {};
168
+ if (body.summary) patch.summary = body.summary;
169
+ if (body.description !== undefined) patch.description = body.description;
170
+ if (body.location !== undefined) patch.location = body.location;
171
+ if (body.start) patch.start = { dateTime: body.start, timeZone: tz };
172
+ if (body.end) patch.end = { dateTime: body.end, timeZone: tz };
173
+
174
+ const updated = await updateEvent(config, calId, eventId, patch);
175
+ sendJSON(res, 200, { event: updated });
176
+ } catch (e) { sendError(res, 500, e.message); }
177
+ });
178
+
179
+ // Delete an event
180
+ router.delete('/api/calendar/:calId/:eventId', async (req, res) => {
181
+ try {
182
+ const config = loadConfig();
183
+ const { calId, eventId } = req.params;
184
+ await deleteEvent(config, calId, eventId);
185
+ sendJSON(res, 200, { ok: true });
186
+ } catch (e) { sendError(res, 500, e.message); }
187
+ });
188
+
93
189
  // ── Tasks ─────────────────────────────────────────────────────────────
94
190
 
95
191
  router.get('/api/tasks', async (req, res) => {
@@ -215,12 +215,24 @@ export function register(router) {
215
215
  const contextBlock = context ? `\n\n## CONTEXT FROM PREVIOUS STEPS:\n${context.slice(0, 8000)}` : '';
216
216
  const proposalContextBlock = body.proposalContext ? `\n\n## OTHER AGENTS' PROPOSALS (CROSS-READING):\n${body.proposalContext.slice(0, 6000)}` : '';
217
217
 
218
+ const formatInstructions = `\n\nFORMATTING RULES (CRITICAL — your output will be rendered as HTML):
219
+ - Use MARKDOWN TABLES with | pipes | for ALL tabular data. Example:
220
+ | Header 1 | Header 2 | Header 3 |
221
+ |----------|----------|----------|
222
+ | data | data | data |
223
+ - NEVER use ASCII art boxes (┌─┐│└─┘╔═╗║╚═╝). They render as ugly <pre> blocks.
224
+ - NEVER use ASCII art charts/graphs (├─●──┤). Describe data in tables instead.
225
+ - Use **bold**, *italic*, headers (## ##), bullet points, numbered lists, blockquotes (>).
226
+ - For emphasis boxes, use blockquotes: > **Key insight:** text here
227
+ - Write COMPLETE content under every heading — never leave a section empty.`;
228
+
218
229
  const sysParts = [
219
230
  agentSysDef || `You are ${agent}, a specialist AI agent in NHA Studio. Respond entirely in ${language}. Today is ${today}.`,
220
231
  `\n\n## WORKFLOW GOAL: ${task}`,
221
232
  contextBlock,
222
233
  proposalContextBlock,
223
234
  stepDef?.prompt ? `\n\n## YOUR SPECIFIC TASK:\n${stepDef.prompt}` : '',
235
+ formatInstructions,
224
236
  ];
225
237
  const systemPrompt = sysParts.join('');
226
238
  const userMessage = stepDef?.prompt || task;