nothumanallowed 13.2.64 → 13.2.66

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": "13.2.64",
3
+ "version": "13.2.66",
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": {
@@ -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
- let events;
1017
- if (dateParam && dateParam !== new Date().toISOString().split('T')[0]) {
1018
- events = await getEventsForDate(config, new Date(dateParam));
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 = await getTodayEvents(config);
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.64';
8
+ export const VERSION = '13.2.66';
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; // skip minimal access
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 seen = new Set();
116
+ const holidayDates = new Set();
116
117
  return allEvents.filter(e => {
117
- const key = (e.start || '').slice(0, 10) + '|' + (e.summary || '').toLowerCase().trim();
118
- if (seen.has(key)) return false;
119
- seen.add(key);
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
- const d = new Date(date);
129
- const startOfDay = new Date(d.getFullYear(), d.getMonth(), d.getDate());
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: same start date + same normalized title = same event (e.g. IT + EN holiday feeds)
150
- const seen = new Set();
151
- const deduped = [];
152
- for (const e of allEvents) {
153
- const key = (e.start || '').slice(0, 10) + '|' + (e.summary || '').toLowerCase().trim();
154
- if (!seen.has(key)) { seen.add(key); deduped.push(e); }
155
- }
156
- return deduped;
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
  /**
@@ -420,8 +420,11 @@ function renderMessages(){
420
420
  if(isA&&m.inlineHtml){
421
421
  inlineBlock=m.inlineHtml.replace(/\\[INLINE_CARD\\]([\\s\\S]*?)\\[\\/INLINE_CARD\\]/g,function(_,htm){return '<div class="inline-card">'+htm+'</div>';}).replace(/\\[INLINE_BROWSER\\]([^|]+)\\|([^\\]]+)\\[\\/INLINE_BROWSER\\]/g,function(_,file,url){return '<div class="inline-browser"><div class="inline-browser-bar"><span class="inline-browser-dot"></span><span class="inline-browser-dot"></span><span class="inline-browser-dot"></span><span class="inline-browser-url">'+esc(url)+'</span></div><img src="/api/screenshots/'+esc(file)+'" alt="'+esc(url)+'" onclick="openLightbox(this.src)" style="cursor:zoom-in"></div>';});
422
422
  }
423
+ var isStreaming=isA&&(m.content==='Thinking...'||m.content==='');
423
424
  var bubbleCls=isA?'msg__bubble md-body':'msg__bubble';
424
- h+='<div class="msg msg--'+esc(m.role)+'"><div class="msg__label">'+esc(m.role==='user'?'You':'NHA')+'</div><div class="'+bubbleCls+'">'+content+'</div>'+inlineBlock+acts+'</div>';
425
+ var displayContent=isStreaming?'<div class="typing-dots"><span></span><span></span><span></span></div>':content;
426
+ var streamCls=isStreaming?' msg--streaming':'';
427
+ h+='<div class="msg msg--'+esc(m.role)+streamCls+'"><div class="msg__label">'+esc(m.role==='user'?'You':'NHA')+'</div><div class="'+bubbleCls+'">'+displayContent+'</div>'+inlineBlock+acts+'</div>';
425
428
  });
426
429
  el.innerHTML=h;el.scrollTop=el.scrollHeight;
427
430
  // Load fork info for messages that have IDs
@@ -882,7 +885,7 @@ function sendChat(){
882
885
  displayContent=displayContent.replace(/<think>[\\s\\S]*?<\\/think>/g,'').trim();
883
886
  }
884
887
  var el=document.getElementById('chatMessages');
885
- if(el){var msgs=el.querySelectorAll('.msg');var last=msgs[msgs.length-1];if(last){var bub=last.querySelector('.msg__bubble');if(bub){bub.className='msg__bubble md-body';bub.innerHTML=isThinking?displayContent:renderMd(displayContent)||'Thinking...';}}el.scrollTop=el.scrollHeight;}
888
+ if(el){var msgs=el.querySelectorAll('.msg');var last=msgs[msgs.length-1];if(last){last.classList.add('msg--streaming');var bub=last.querySelector('.msg__bubble');if(bub){bub.className='msg__bubble md-body';var renderedContent=isThinking?displayContent:renderMd(displayContent);bub.innerHTML=renderedContent||'<div class="typing-dots"><span></span><span></span><span></span></div>';}}el.scrollTop=el.scrollHeight;}
886
889
  }
887
890
  if(currentEvent==='tool'){
888
891
  var toolLabels={browser_open:'Opening page',browser_screenshot:'Taking screenshot',browser_click:'Clicking element',browser_type:'Typing text',browser_extract:'Extracting content',browser_js:'Running JavaScript',browser_wait:'Waiting for element',browser_scroll:'Scrolling page',browser_key:'Pressing key',browser_close:'Closing browser',web_search:'Searching the web',fetch_url:'Fetching URL',gmail_list:'Searching emails',gmail_read:'Reading email',gmail_send:'Sending email',calendar_today:'Loading calendar',calendar_create:'Creating event'};
@@ -1115,29 +1118,38 @@ function renderCalendar(el){
1115
1118
 
1116
1119
  // Day headers
1117
1120
  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
- h+='<div style="text-align:center;font-size:10px;color:var(--dim);padding:4px">'+d+'</div>';
1121
+ ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'].forEach(function(d,i){
1122
+ var isWe=i>=5;
1123
+ 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
1124
  });
1121
1125
  h+='</div>';
1122
1126
 
1123
1127
  // Calendar grid - square cells
1124
1128
  h+='<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:3px">';
1125
1129
  // Empty cells before first day
1126
- for(var i=0;i<startDay;i++){h+='<div style="aspect-ratio:1;background:var(--bg);border-radius:6px"></div>'}
1130
+ for(var i=0;i<startDay;i++){
1131
+ var isWeCol=i>=5;
1132
+ h+='<div style="aspect-ratio:1;background:'+(isWeCol?'rgba(255,80,80,0.04)':'var(--bg)')+';border-radius:6px"></div>';
1133
+ }
1127
1134
  // Day cells
1128
1135
  for(var d=1;d<=daysInMonth;d++){
1129
1136
  var key=calKey(calYear,calMonth,d);
1130
1137
  var today=isToday(calYear,calMonth,d);
1131
1138
  var evts=calEventsCache[key]||[];
1132
1139
  var count=evts.length;
1133
- var bg=today?'var(--greendim)':'var(--bg2)';
1134
- var bdr=today?'var(--green3)':count>0?'var(--amber)':'var(--border)';
1140
+ var dayOfWeek=(startDay+d-1)%7; // 0=Mon … 5=Sat, 6=Sun
1141
+ var isWeekend=dayOfWeek>=5;
1142
+ var hasHoliday=evts.some(function(e){return e._isHoliday||e.readOnly});
1143
+ var bg=today?'var(--greendim)':isWeekend?'rgba(255,80,80,0.06)':'var(--bg2)';
1144
+ var bdr=today?'var(--green3)':hasHoliday?'var(--red)':count>0?'var(--amber)':'var(--border)';
1145
+ var numColor=today?'var(--green)':isWeekend?'var(--red)':'var(--text)';
1135
1146
  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:'+(today?'var(--green)':'var(--text)')+'">'+d+'</div>';
1147
+ h+='<div style="font-size:14px;font-weight:'+(today?'800':'500')+';color:'+numColor+'">'+d+'</div>';
1137
1148
  if(count>0){
1138
1149
  h+='<div style="flex:1;display:flex;flex-direction:column;justify-content:flex-end;gap:1px;min-height:0">';
1139
1150
  evts.slice(0,2).forEach(function(x){
1140
- h+='<div style="font-size:8px;color:var(--amber);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;background:var(--bg3);border-radius:2px;padding:1px 3px">'+esc(x.summary)+'</div>';
1151
+ var evtColor=x._isHoliday||x.readOnly?'var(--red)':'var(--amber)';
1152
+ 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
1153
  });
1142
1154
  if(count>2)h+='<div style="font-size:8px;color:var(--dim);text-align:center">+'+String(count-2)+'</div>';
1143
1155
  h+='</div>';
@@ -1154,26 +1166,25 @@ function renderCalendar(el){
1154
1166
  }
1155
1167
 
1156
1168
  function loadMonthEvents(){
1169
+ var monthKey=calYear+'-'+String(calMonth+1).padStart(2,'0');
1170
+ // Check if already loaded
1157
1171
  var daysInMonth=new Date(calYear,calMonth+1,0).getDate();
1158
- var promises=[];
1159
- for(var d=1;d<=daysInMonth;d++){
1160
- var key=calKey(calYear,calMonth,d);
1161
- if(!calEventsCache[key]){
1162
- (function(k,day){
1163
- promises.push(apiGet('/api/calendar?date='+k).then(function(r){
1164
- calEventsCache[k]=(r&&r.events)||[];
1165
- }));
1166
- })(key,d);
1172
+ var allLoaded=true;
1173
+ for(var d=1;d<=daysInMonth;d++){if(!calEventsCache[calKey(calYear,calMonth,d)]){allLoaded=false;break}}
1174
+ if(allLoaded){var li=document.getElementById('calLoading');if(li)li.style.display='none';return;}
1175
+
1176
+ apiGet('/api/calendar?month='+monthKey).then(function(r){
1177
+ if(r&&r.byDate){
1178
+ // Fill all days — ensure days with no events get empty array so we don't re-fetch
1179
+ for(var d=1;d<=daysInMonth;d++){
1180
+ var k=calKey(calYear,calMonth,d);
1181
+ calEventsCache[k]=r.byDate[k]||[];
1182
+ }
1167
1183
  }
1168
- }
1169
- if(promises.length===0){
1170
1184
  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
1185
  renderCalendar(document.getElementById('content'));
1186
+ }).catch(function(){
1187
+ var li=document.getElementById('calLoading');if(li)li.style.display='none';
1177
1188
  });
1178
1189
  }
1179
1190
 
@@ -4136,6 +4147,10 @@ function init(){
4136
4147
  if(bv)makeDraggable(bv,\x27.browser-viewer__header\x27);
4137
4148
  var cp=document.getElementById('canvasPanel');
4138
4149
  if(cp)makeDraggable(cp,\x27.cvs-header\x27);
4150
+ // Telemetry ping — fire and forget
4151
+ setTimeout(function(){
4152
+ fetch(\x27https://nothumanallowed.com/api/v1/telemetry/ping\x27,{method:\x27POST\x27,headers:{\x27Content-Type\x27:\x27application/json\x27},body:JSON.stringify({platform:\x27web-ui\x27,version:VERSION})}).catch(function(){});
4153
+ },3000);
4139
4154
  }
4140
4155
  init();
4141
4156
  `;
@@ -4240,9 +4255,15 @@ input:focus,textarea:focus{border-color:var(--green3)}
4240
4255
  .chat__empty-hint{font-size:11px;margin-top:12px}
4241
4256
  .msg{margin-bottom:12px}
4242
4257
  .msg--user .msg__bubble{background:var(--bg3);border:1px solid var(--border2);border-radius:8px 8px 2px 8px;padding:10px 14px;max-width:85%;margin-left:auto;color:var(--bright)}
4243
- .msg--assistant .msg__bubble{background:var(--greendim);border:1px solid var(--green3);border-radius:8px 8px 8px 2px;padding:10px 14px;max-width:85%;color:var(--text);white-space:pre-wrap;word-wrap:break-word;line-height:1.5}
4258
+ .msg--assistant .msg__bubble{background:var(--greendim);border:1px solid var(--green3);border-radius:8px 8px 8px 2px;padding:10px 14px;max-width:85%;color:var(--text);white-space:pre-wrap;word-wrap:break-word;line-height:1.5;min-height:40px;min-width:60px}
4244
4259
  .msg--assistant .msg__bubble img{max-width:100%;border-radius:8px;margin:8px 0;border:1px solid rgba(0,255,65,0.2)}
4260
+ .msg--assistant.msg--streaming .msg__bubble{border-color:var(--green);box-shadow:0 0 8px rgba(0,255,65,0.15)}
4245
4261
  .msg__label{font-size:10px;color:var(--dim);margin-bottom:2px}
4262
+ .typing-dots{display:inline-flex;align-items:center;gap:4px;padding:4px 0}
4263
+ .typing-dots span{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--green);opacity:0.3;animation:tdot 1.2s ease-in-out infinite}
4264
+ .typing-dots span:nth-child(2){animation-delay:0.2s}
4265
+ .typing-dots span:nth-child(3){animation-delay:0.4s}
4266
+ @keyframes tdot{0%,80%,100%{opacity:0.2;transform:scale(0.8)}40%{opacity:1;transform:scale(1.2)}}
4246
4267
  .msg__actions{display:flex;gap:6px;margin-top:4px;opacity:0.4;transition:opacity 0.2s}
4247
4268
  .msg:hover .msg__actions{opacity:1}
4248
4269
  .msg__actions button{background:none;border:none;color:var(--dim);cursor:pointer;font-size:10px;font-family:var(--mono);padding:2px 4px}