nothumanallowed 13.2.63 → 13.2.65
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/ui.mjs +36 -6
- package/src/constants.mjs +1 -1
- package/src/services/google-calendar.mjs +33 -6
- package/src/services/web-ui.mjs +31 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.2.
|
|
3
|
+
"version": "13.2.65",
|
|
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/commands/ui.mjs
CHANGED
|
@@ -1009,17 +1009,47 @@ export async function cmdUI(args) {
|
|
|
1009
1009
|
return;
|
|
1010
1010
|
}
|
|
1011
1011
|
|
|
1012
|
-
// GET /api/calendar?date=YYYY-MM-DD
|
|
1012
|
+
// GET /api/calendar?date=YYYY-MM-DD OR ?month=YYYY-MM (loads entire month)
|
|
1013
1013
|
if (method === 'GET' && pathname === '/api/calendar') {
|
|
1014
1014
|
try {
|
|
1015
1015
|
const dateParam = url.searchParams.get('date');
|
|
1016
|
-
|
|
1017
|
-
if (
|
|
1018
|
-
|
|
1016
|
+
const monthParam = url.searchParams.get('month'); // e.g. "2026-05"
|
|
1017
|
+
if (monthParam) {
|
|
1018
|
+
// Load entire month across all calendars in one shot
|
|
1019
|
+
const [y, m] = monthParam.split('-').map(Number);
|
|
1020
|
+
const startOfMonth = new Date(y, m - 1, 1);
|
|
1021
|
+
const endOfMonth = new Date(y, m, 1);
|
|
1022
|
+
const { listCalendars: lc, listEvents: le } = await import('../services/google-calendar.mjs');
|
|
1023
|
+
const calendars = await lc(config);
|
|
1024
|
+
const byDate = {};
|
|
1025
|
+
for (const cal of calendars) {
|
|
1026
|
+
if (cal.accessRole === 'freeBusyReader') continue;
|
|
1027
|
+
const isHolidayFeed = cal.id.includes('#holiday@group');
|
|
1028
|
+
try {
|
|
1029
|
+
const evts = await le(config, cal.id, startOfMonth, endOfMonth);
|
|
1030
|
+
for (const e of evts) {
|
|
1031
|
+
e.calendarId = cal.id;
|
|
1032
|
+
e.calendarName = cal.summary;
|
|
1033
|
+
e.readOnly = cal.accessRole === 'reader' || cal.accessRole === 'freeBusyReader';
|
|
1034
|
+
e._isHoliday = isHolidayFeed;
|
|
1035
|
+
const dk = (e.start || '').slice(0, 10);
|
|
1036
|
+
if (!byDate[dk]) byDate[dk] = [];
|
|
1037
|
+
// Dedup holidays per date
|
|
1038
|
+
if (isHolidayFeed && byDate[dk].some(x => x._isHoliday)) continue;
|
|
1039
|
+
byDate[dk].push(e);
|
|
1040
|
+
}
|
|
1041
|
+
} catch { /* skip */ }
|
|
1042
|
+
}
|
|
1043
|
+
sendJSON(res, 200, { byDate, month: monthParam });
|
|
1019
1044
|
} else {
|
|
1020
|
-
events
|
|
1045
|
+
let events;
|
|
1046
|
+
if (dateParam) {
|
|
1047
|
+
events = await getEventsForDate(config, dateParam);
|
|
1048
|
+
} else {
|
|
1049
|
+
events = await getTodayEvents(config);
|
|
1050
|
+
}
|
|
1051
|
+
sendJSON(res, 200, { events, date: dateParam || new Date().toISOString().split('T')[0] });
|
|
1021
1052
|
}
|
|
1022
|
-
sendJSON(res, 200, { events, date: dateParam || new Date().toISOString().split('T')[0] });
|
|
1023
1053
|
} catch (e) {
|
|
1024
1054
|
sendJSON(res, 200, { events: [], error: e.message });
|
|
1025
1055
|
}
|
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 = '13.2.
|
|
8
|
+
export const VERSION = '13.2.65';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -98,29 +98,44 @@ export async function getTodayEvents(config) {
|
|
|
98
98
|
const allEvents = [];
|
|
99
99
|
|
|
100
100
|
for (const cal of calendars) {
|
|
101
|
-
if (cal.accessRole === 'freeBusyReader') continue;
|
|
101
|
+
if (cal.accessRole === 'freeBusyReader') continue;
|
|
102
|
+
const isHolidayFeed = cal.id.includes('#holiday@group');
|
|
102
103
|
try {
|
|
103
104
|
const events = await listEvents(config, cal.id, startOfDay, endOfDay);
|
|
104
105
|
for (const e of events) {
|
|
105
106
|
e.calendarName = cal.summary;
|
|
106
107
|
e.calendarId = cal.id;
|
|
107
108
|
e.readOnly = cal.accessRole === 'reader' || cal.accessRole === 'freeBusyReader';
|
|
109
|
+
e._isHoliday = isHolidayFeed;
|
|
108
110
|
allEvents.push(e);
|
|
109
111
|
}
|
|
110
112
|
} catch { /* skip failed calendars */ }
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
// Sort by start time
|
|
114
115
|
allEvents.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());
|
|
115
|
-
|
|
116
|
+
const holidayDates = new Set();
|
|
117
|
+
return allEvents.filter(e => {
|
|
118
|
+
if (!e._isHoliday) return true;
|
|
119
|
+
const dateKey = (e.start || '').slice(0, 10);
|
|
120
|
+
if (holidayDates.has(dateKey)) return false;
|
|
121
|
+
holidayDates.add(dateKey);
|
|
122
|
+
return true;
|
|
123
|
+
});
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
/**
|
|
119
127
|
* Get events for a specific date.
|
|
120
128
|
*/
|
|
121
129
|
export async function getEventsForDate(config, date) {
|
|
122
|
-
|
|
123
|
-
|
|
130
|
+
// Parse date string as LOCAL time to avoid UTC-midnight timezone shift
|
|
131
|
+
let startOfDay;
|
|
132
|
+
if (typeof date === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
133
|
+
const [y, m, d] = date.split('-').map(Number);
|
|
134
|
+
startOfDay = new Date(y, m - 1, d);
|
|
135
|
+
} else {
|
|
136
|
+
const d = new Date(date);
|
|
137
|
+
startOfDay = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
138
|
+
}
|
|
124
139
|
const endOfDay = new Date(startOfDay.getTime() + 86400000);
|
|
125
140
|
|
|
126
141
|
// Load from all calendars so calendarId is always accurate
|
|
@@ -128,18 +143,30 @@ export async function getEventsForDate(config, date) {
|
|
|
128
143
|
const allEvents = [];
|
|
129
144
|
for (const cal of calendars) {
|
|
130
145
|
if (cal.accessRole === 'freeBusyReader') continue;
|
|
146
|
+
const isHolidayFeed = cal.id.includes('#holiday@group');
|
|
131
147
|
try {
|
|
132
148
|
const events = await listEvents(config, cal.id, startOfDay, endOfDay);
|
|
133
149
|
for (const e of events) {
|
|
134
150
|
e.calendarName = cal.summary;
|
|
135
151
|
e.calendarId = cal.id;
|
|
136
152
|
e.readOnly = cal.accessRole === 'reader' || cal.accessRole === 'freeBusyReader';
|
|
153
|
+
e._isHoliday = isHolidayFeed;
|
|
137
154
|
allEvents.push(e);
|
|
138
155
|
}
|
|
139
156
|
} catch { /* skip */ }
|
|
140
157
|
}
|
|
141
158
|
allEvents.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());
|
|
142
|
-
|
|
159
|
+
|
|
160
|
+
// Deduplicate: for holiday feeds, keep only ONE holiday per date
|
|
161
|
+
// (IT + EN feeds have same day, different language titles — keep first)
|
|
162
|
+
const holidayDates = new Set();
|
|
163
|
+
return allEvents.filter(e => {
|
|
164
|
+
if (!e._isHoliday) return true;
|
|
165
|
+
const dateKey = (e.start || '').slice(0, 10);
|
|
166
|
+
if (holidayDates.has(dateKey)) return false;
|
|
167
|
+
holidayDates.add(dateKey);
|
|
168
|
+
return true;
|
|
169
|
+
});
|
|
143
170
|
}
|
|
144
171
|
|
|
145
172
|
/**
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -1115,29 +1115,38 @@ function renderCalendar(el){
|
|
|
1115
1115
|
|
|
1116
1116
|
// Day headers
|
|
1117
1117
|
h+='<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:2px;margin-bottom:4px">';
|
|
1118
|
-
['Mon','Tue','Wed','Thu','Fri','Sat','Sun'].forEach(function(d){
|
|
1119
|
-
|
|
1118
|
+
['Mon','Tue','Wed','Thu','Fri','Sat','Sun'].forEach(function(d,i){
|
|
1119
|
+
var isWe=i>=5;
|
|
1120
|
+
h+='<div style="text-align:center;font-size:10px;color:'+(isWe?'var(--red)':'var(--dim)')+';padding:4px;font-weight:'+(isWe?'600':'400')+'">'+d+'</div>';
|
|
1120
1121
|
});
|
|
1121
1122
|
h+='</div>';
|
|
1122
1123
|
|
|
1123
1124
|
// Calendar grid - square cells
|
|
1124
1125
|
h+='<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:3px">';
|
|
1125
1126
|
// Empty cells before first day
|
|
1126
|
-
for(var i=0;i<startDay;i++){
|
|
1127
|
+
for(var i=0;i<startDay;i++){
|
|
1128
|
+
var isWeCol=i>=5;
|
|
1129
|
+
h+='<div style="aspect-ratio:1;background:'+(isWeCol?'rgba(255,80,80,0.04)':'var(--bg)')+';border-radius:6px"></div>';
|
|
1130
|
+
}
|
|
1127
1131
|
// Day cells
|
|
1128
1132
|
for(var d=1;d<=daysInMonth;d++){
|
|
1129
1133
|
var key=calKey(calYear,calMonth,d);
|
|
1130
1134
|
var today=isToday(calYear,calMonth,d);
|
|
1131
1135
|
var evts=calEventsCache[key]||[];
|
|
1132
1136
|
var count=evts.length;
|
|
1133
|
-
var
|
|
1134
|
-
var
|
|
1137
|
+
var dayOfWeek=(startDay+d-1)%7; // 0=Mon … 5=Sat, 6=Sun
|
|
1138
|
+
var isWeekend=dayOfWeek>=5;
|
|
1139
|
+
var hasHoliday=evts.some(function(e){return e._isHoliday||e.readOnly});
|
|
1140
|
+
var bg=today?'var(--greendim)':isWeekend?'rgba(255,80,80,0.06)':'var(--bg2)';
|
|
1141
|
+
var bdr=today?'var(--green3)':hasHoliday?'var(--red)':count>0?'var(--amber)':'var(--border)';
|
|
1142
|
+
var numColor=today?'var(--green)':isWeekend?'var(--red)':'var(--text)';
|
|
1135
1143
|
h+='<div onclick="openDayDetail(\\x27'+key+'\\x27)" style="aspect-ratio:1;background:'+bg+';border:1px solid '+bdr+';border-radius:6px;padding:6px;cursor:pointer;display:flex;flex-direction:column;overflow:hidden">';
|
|
1136
|
-
h+='<div style="font-size:14px;font-weight:'+(today?'800':'500')+';color:'+
|
|
1144
|
+
h+='<div style="font-size:14px;font-weight:'+(today?'800':'500')+';color:'+numColor+'">'+d+'</div>';
|
|
1137
1145
|
if(count>0){
|
|
1138
1146
|
h+='<div style="flex:1;display:flex;flex-direction:column;justify-content:flex-end;gap:1px;min-height:0">';
|
|
1139
1147
|
evts.slice(0,2).forEach(function(x){
|
|
1140
|
-
|
|
1148
|
+
var evtColor=x._isHoliday||x.readOnly?'var(--red)':'var(--amber)';
|
|
1149
|
+
h+='<div style="font-size:8px;color:'+evtColor+';overflow:hidden;white-space:nowrap;text-overflow:ellipsis;background:var(--bg3);border-radius:2px;padding:1px 3px">'+esc(x.summary)+'</div>';
|
|
1141
1150
|
});
|
|
1142
1151
|
if(count>2)h+='<div style="font-size:8px;color:var(--dim);text-align:center">+'+String(count-2)+'</div>';
|
|
1143
1152
|
h+='</div>';
|
|
@@ -1154,26 +1163,25 @@ function renderCalendar(el){
|
|
|
1154
1163
|
}
|
|
1155
1164
|
|
|
1156
1165
|
function loadMonthEvents(){
|
|
1166
|
+
var monthKey=calYear+'-'+String(calMonth+1).padStart(2,'0');
|
|
1167
|
+
// Check if already loaded
|
|
1157
1168
|
var daysInMonth=new Date(calYear,calMonth+1,0).getDate();
|
|
1158
|
-
var
|
|
1159
|
-
for(var d=1;d<=daysInMonth;d++){
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1169
|
+
var allLoaded=true;
|
|
1170
|
+
for(var d=1;d<=daysInMonth;d++){if(!calEventsCache[calKey(calYear,calMonth,d)]){allLoaded=false;break}}
|
|
1171
|
+
if(allLoaded){var li=document.getElementById('calLoading');if(li)li.style.display='none';return;}
|
|
1172
|
+
|
|
1173
|
+
apiGet('/api/calendar?month='+monthKey).then(function(r){
|
|
1174
|
+
if(r&&r.byDate){
|
|
1175
|
+
// Fill all days — ensure days with no events get empty array so we don't re-fetch
|
|
1176
|
+
for(var d=1;d<=daysInMonth;d++){
|
|
1177
|
+
var k=calKey(calYear,calMonth,d);
|
|
1178
|
+
calEventsCache[k]=r.byDate[k]||[];
|
|
1179
|
+
}
|
|
1167
1180
|
}
|
|
1168
|
-
}
|
|
1169
|
-
if(promises.length===0){
|
|
1170
1181
|
var li=document.getElementById('calLoading');if(li)li.style.display='none';
|
|
1171
|
-
return;
|
|
1172
|
-
}
|
|
1173
|
-
Promise.all(promises).then(function(){
|
|
1174
|
-
var li=document.getElementById('calLoading');if(li)li.style.display='none';
|
|
1175
|
-
// Re-render just the grid cells with events
|
|
1176
1182
|
renderCalendar(document.getElementById('content'));
|
|
1183
|
+
}).catch(function(){
|
|
1184
|
+
var li=document.getElementById('calLoading');if(li)li.style.display='none';
|
|
1177
1185
|
});
|
|
1178
1186
|
}
|
|
1179
1187
|
|