nothumanallowed 15.1.27 → 15.1.29

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": "15.1.27",
3
+ "version": "15.1.29",
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 = '15.1.27';
8
+ export const VERSION = '15.1.29';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -108,6 +108,16 @@ class SandboxManager {
108
108
  const MAX_RETRIES = 3;
109
109
  this._stoppedByUser = false;
110
110
 
111
+ // Server version banner — lets the user verify their `nha ui` process is
112
+ // actually running the latest code (npm install only updates files on disk;
113
+ // the running process must be killed and restarted to pick them up).
114
+ if (_attempt === 1) {
115
+ try {
116
+ const { VERSION } = await import('../../constants.mjs');
117
+ emit({ type: 'status', msg: `nha sandbox manager v${VERSION} — if this is older than what you installed, kill nha ui and restart it (the running process won't pick up new code by itself)` });
118
+ } catch { /* non-fatal */ }
119
+ }
120
+
111
121
  // Kill any existing sandbox
112
122
  if (this.isRunning()) {
113
123
  emit({ type: 'phase', phase: 'cleanup', msg: 'Stopping previous sandbox...' });
@@ -714,7 +714,12 @@ calendar_update(eventId, summary?, location?, description?, start?, end?)
714
714
  calendar_delete(eventId) — Delete an event you have a REAL eventId for. Same rules as update: never invent, never use placeholders.
715
715
  calendar_move(eventId, newStart, newEnd) — Reschedule.
716
716
  calendar_find(query, daysAhead?) — Search events by name. Returns eventIds. Use ONLY when you don't already have the id.
717
- calendar_today() / calendar_tomorrow() / calendar_date(date) / calendar_upcoming(hours?) / calendar_week(startDate?) / calendar_month(month?) — Read-only listings. **When the user says "elimina gli appuntamenti di [date]", call calendar_date(date) FIRST to get real eventIds, then calendar_delete with each real id.**
717
+ - daysAhead default is 7 pass 30 or 60 for broader search if not found in 7.
718
+ - Query is matched case-insensitively against summary AND description.
719
+ - If a user names an event ambiguously (e.g. "Italiano della BMW" via voice-to-text typo for "Tagliando della BMW"), try BOTH the literal query AND alternative spellings.
720
+ calendar_today() / calendar_tomorrow() / calendar_date(date) / calendar_upcoming(hours?) / calendar_week(startDate?) / calendar_month(month?) — Read-only listings.
721
+ - **calendar_week with NO arguments** returns the CURRENT calendar week (Mon..Sun). Use this when the user says "questa settimana" / "this week". Do NOT pass startDate — let the tool compute the right Monday. Only pass startDate when the user explicitly names a different week.
722
+ - **"elimina gli appuntamenti di [date]"** → call calendar_date(date) FIRST to get real eventIds, then calendar_delete with each real id.
718
723
  schedule_meeting(clientName, subject, location, durationMinutes, dateFrom, dateTo) — Find optimal slots considering travel time.
719
724
 
720
725
  ### Gmail / IMAP
@@ -1494,7 +1499,18 @@ export async function executeTool(action, params, config) {
1494
1499
  }
1495
1500
 
1496
1501
  case 'calendar_week': {
1497
- const startDate = params.startDate || new Date().toISOString().split('T')[0];
1502
+ // Default to MONDAY of the current ISO week, not "today". When a user
1503
+ // asks "questa settimana"/"this week", they mean Mon..Sun of the calendar
1504
+ // week — not "next 7 days from today". If they want the latter they can
1505
+ // pass startDate explicitly.
1506
+ const isoMondayOfCurrentWeek = () => {
1507
+ const d = new Date();
1508
+ const day = d.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
1509
+ const offset = day === 0 ? -6 : 1 - day; // shift back to Monday
1510
+ const monday = new Date(d.getTime() + offset * 86400000);
1511
+ return `${monday.getFullYear()}-${String(monday.getMonth() + 1).padStart(2, '0')}-${String(monday.getDate()).padStart(2, '0')}`;
1512
+ };
1513
+ const startDate = params.startDate || isoMondayOfCurrentWeek();
1498
1514
  const from = new Date(startDate + 'T00:00:00');
1499
1515
  const to = new Date(from.getTime() + 7 * 86400000);
1500
1516
  const events = await listEvents(config, 'primary', from, to);
@@ -1561,25 +1577,57 @@ export async function executeTool(action, params, config) {
1561
1577
  }
1562
1578
 
1563
1579
  case 'calendar_find': {
1564
- const query = (params.query || '').toLowerCase();
1565
- const daysAhead = params.daysAhead || 30;
1566
- const from = new Date();
1567
- const to = new Date(from.getTime() + daysAhead * 86400000);
1568
- const events = await listEvents(config, 'primary', from, to);
1580
+ const queryRaw = (params.query || '').trim();
1581
+ const query = queryRaw.toLowerCase();
1582
+ // Token-level fuzzy match: handles voice-to-text typos like
1583
+ // "Italiano della BMW" matching event "Tagliando BMW" via the shared
1584
+ // significant token "BMW". Stop-words and articles are ignored.
1585
+ const STOP_WORDS = new Set(['il','la','i','le','gli','un','una','di','del','dello','della','dei','degli','delle','a','al','allo','alla','ai','agli','alle','con','per','su','da','in','e','o','che','the','a','an','of','for','to','in','on','with','and','or','my','your']);
1586
+ const tokens = query.split(/\s+/).filter(t => t.length >= 3 && !STOP_WORDS.has(t));
1587
+
1588
+ const requestedDays = params.daysAhead || 30;
1589
+ // Try the requested range first, then auto-broaden to 90 days if empty.
1590
+ // Voice users frequently underestimate how far ahead an event is.
1591
+ const tryRange = async (days) => {
1592
+ const from = new Date();
1593
+ const to = new Date(from.getTime() + days * 86400000);
1594
+ const events = await listEvents(config, 'primary', from, to);
1569
1595
 
1570
- const matches = events.filter(e =>
1571
- (e.summary || '').toLowerCase().includes(query) ||
1572
- (e.description || '').toLowerCase().includes(query)
1573
- );
1596
+ // Strict substring match first
1597
+ let matches = events.filter(e =>
1598
+ (e.summary || '').toLowerCase().includes(query) ||
1599
+ (e.description || '').toLowerCase().includes(query)
1600
+ );
1601
+
1602
+ // Fuzzy fallback: any significant token (3+ chars, no stop-word) matches
1603
+ if (matches.length === 0 && tokens.length > 0) {
1604
+ matches = events.filter(e => {
1605
+ const hay = ((e.summary || '') + ' ' + (e.description || '')).toLowerCase();
1606
+ return tokens.some(t => hay.includes(t));
1607
+ });
1608
+ }
1609
+ return { events, matches };
1610
+ };
1574
1611
 
1575
- if (matches.length === 0) return `No events found matching "${params.query}" in the next ${daysAhead} days.`;
1612
+ let { matches } = await tryRange(requestedDays);
1613
+ let effectiveDays = requestedDays;
1614
+ if (matches.length === 0 && requestedDays < 90) {
1615
+ const broad = await tryRange(90);
1616
+ matches = broad.matches;
1617
+ effectiveDays = 90;
1618
+ }
1576
1619
 
1620
+ if (matches.length === 0) {
1621
+ return `No events found matching "${queryRaw}" in the next ${effectiveDays} days (tried fuzzy match on tokens: ${tokens.join(', ') || '(none)'}). Try calendar_week or calendar_month for a broader view.`;
1622
+ }
1623
+
1624
+ const widened = effectiveDays > requestedDays ? ` (auto-broadened to ${effectiveDays} days)` : '';
1577
1625
  return matches.map((e, i) => {
1578
1626
  const time = e.isAllDay ? 'All day' : `${formatTime(e.start)} - ${formatTime(e.end)}`;
1579
1627
  const date = e.start.split('T')[0];
1580
1628
  const loc = e.location ? ` | Location: ${e.location}` : '';
1581
1629
  return `${i + 1}. [eventId: ${e.id}] ${date} ${time} — ${e.summary}${loc}`;
1582
- }).join('\n');
1630
+ }).join('\n') + (widened ? `\n${widened}` : '');
1583
1631
  }
1584
1632
 
1585
1633
  case 'calendar_update': {