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.
|
|
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.
|
|
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
|
-
|
|
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;
|