nothumanallowed 13.2.64 → 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 +29 -16
- 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,25 +98,27 @@ 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, then deduplicate same-day same-title events (holiday feeds)
|
|
114
115
|
allEvents.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());
|
|
115
|
-
const
|
|
116
|
+
const holidayDates = new Set();
|
|
116
117
|
return allEvents.filter(e => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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);
|
|
120
122
|
return true;
|
|
121
123
|
});
|
|
122
124
|
}
|
|
@@ -125,8 +127,15 @@ export async function getTodayEvents(config) {
|
|
|
125
127
|
* Get events for a specific date.
|
|
126
128
|
*/
|
|
127
129
|
export async function getEventsForDate(config, date) {
|
|
128
|
-
|
|
129
|
-
|
|
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
|
+
}
|
|
130
139
|
const endOfDay = new Date(startOfDay.getTime() + 86400000);
|
|
131
140
|
|
|
132
141
|
// Load from all calendars so calendarId is always accurate
|
|
@@ -134,26 +143,30 @@ export async function getEventsForDate(config, date) {
|
|
|
134
143
|
const allEvents = [];
|
|
135
144
|
for (const cal of calendars) {
|
|
136
145
|
if (cal.accessRole === 'freeBusyReader') continue;
|
|
146
|
+
const isHolidayFeed = cal.id.includes('#holiday@group');
|
|
137
147
|
try {
|
|
138
148
|
const events = await listEvents(config, cal.id, startOfDay, endOfDay);
|
|
139
149
|
for (const e of events) {
|
|
140
150
|
e.calendarName = cal.summary;
|
|
141
151
|
e.calendarId = cal.id;
|
|
142
152
|
e.readOnly = cal.accessRole === 'reader' || cal.accessRole === 'freeBusyReader';
|
|
153
|
+
e._isHoliday = isHolidayFeed;
|
|
143
154
|
allEvents.push(e);
|
|
144
155
|
}
|
|
145
156
|
} catch { /* skip */ }
|
|
146
157
|
}
|
|
147
158
|
allEvents.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());
|
|
148
159
|
|
|
149
|
-
// Deduplicate:
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
});
|
|
157
170
|
}
|
|
158
171
|
|
|
159
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
|
|