nothumanallowed 13.5.105 → 13.5.106

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.5.105",
3
+ "version": "13.5.106",
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": {
@@ -3180,120 +3180,190 @@ ${rawText.slice(0, 18000)}`;
3180
3180
 
3181
3181
  // ── Extract parameters from task ──
3182
3182
  // Date: "16 maggio", "16/05", "16-05-2026", "sabato 16", etc.
3183
- const dateMatch = travelTask.match(/(\d{1,2})\s+(gennaio|febbraio|marzo|aprile|maggio|giugno|luglio|agosto|settembre|ottobre|novembre|dicembre)/i)
3184
- || travelTask.match(/(\d{1,2})[\/\-](\d{1,2})(?:[\/\-](\d{4}))?/)
3185
- || travelTask.match(/(monday|tuesday|wednesday|thursday|friday|saturday|sunday|luned|marted|mercoled|gioved|venerd|sabato|domenica)\s+\w+/i);
3183
+ // ── PARAMETER EXTRACTION ──
3186
3184
  const monthNames = {gennaio:1,febbraio:2,marzo:3,aprile:4,maggio:5,giugno:6,luglio:7,agosto:8,settembre:9,ottobre:10,novembre:11,dicembre:12};
3185
+ const dateMatch = travelTask.match(/(\d{1,2})\s+(gennaio|febbraio|marzo|aprile|maggio|giugno|luglio|agosto|settembre|ottobre|novembre|dicembre)/i)
3186
+ || travelTask.match(/(\d{1,2})[\/\-](\d{1,2})(?:[\/\-](\d{4}))?/);
3187
3187
  let targetDate = null;
3188
3188
  let targetDateStr = null;
3189
- if (dateMatch && dateMatch[2] && isNaN(Number(dateMatch[2]))) {
3190
- const day = parseInt(dateMatch[1]);
3191
- const month = monthNames[(dateMatch[2] || '').toLowerCase()];
3192
- if (day && month) {
3193
- const year = new Date().getFullYear();
3194
- targetDate = new Date(year, month - 1, day);
3195
- targetDateStr = String(day).padStart(2, '0') + '/' + String(month).padStart(2, '0') + '/' + year;
3189
+ if (dateMatch) {
3190
+ if (dateMatch[2] && isNaN(Number(dateMatch[2]))) {
3191
+ const day = parseInt(dateMatch[1]);
3192
+ const month = monthNames[(dateMatch[2] || '').toLowerCase()];
3193
+ if (day && month) {
3194
+ const year = new Date().getFullYear();
3195
+ targetDate = new Date(year, month - 1, day);
3196
+ targetDateStr = String(day).padStart(2, '0') + '/' + String(month).padStart(2, '0') + '/' + year;
3197
+ }
3198
+ } else if (dateMatch[1] && dateMatch[2]) {
3199
+ const day = parseInt(dateMatch[1]);
3200
+ const month = parseInt(dateMatch[2]);
3201
+ const year = dateMatch[3] ? parseInt(dateMatch[3]) : new Date().getFullYear();
3202
+ if (day >= 1 && day <= 31 && month >= 1 && month <= 12) {
3203
+ targetDate = new Date(year, month - 1, day);
3204
+ targetDateStr = String(day).padStart(2, '0') + '/' + String(month).padStart(2, '0') + '/' + year;
3205
+ }
3196
3206
  }
3197
3207
  }
3198
3208
 
3199
- // City/location: "a Bologna", "a Roma", "near Milan", "vicino a Firenze"
3200
- const cityMatch = travelTask.match(/\b(?:a|in|near|vicino\s+a|at)\s+([A-Z][a-zA-Z\s]{2,20}?)(?:\s*[,\.!?]|\s+(?:il|la|per|con|un|una|mi|mia|del|della)|$)/);
3201
- const city = cityMatch ? cityMatch[1].trim() : null;
3209
+ // City: "a Mantova", "in Bologna", "near Milan", "vicino a Firenze"
3210
+ const cityMatch = travelTask.match(/\b(?:a|in|near|vicino\s+a|at|around)\s+([A-Za-z][a-zA-Z\s]{2,25}?)(?:\s*[,\.!?\n]|\s+(?:il|la|per|con|un|una|mi|mia|del|della|per\s+la|il\s+|la\s+)|$)/i);
3211
+ const city = cityMatch ? cityMatch[1].trim().replace(/\s+$/, '') : null;
3202
3212
 
3203
- // Cuisine/restaurant type
3204
- const cuisineMatch = travelTask.match(/\b(sushi|pizza|giapponese|japanese|italiano|italian|cinese|chinese|indiano|indian|steakhouse|bistrot|trattoria|osteria)\b/i);
3205
- const cuisine = cuisineMatch ? cuisineMatch[1].toLowerCase() : null;
3213
+ // Cuisine type → OSM cuisine tag
3214
+ const cuisineMap = {sushi:'sushi',pizza:'pizza',giapponese:'japanese',japanese:'japanese',italiano:'italian',italian:'italian',cinese:'chinese',chinese:'chinese',indiano:'indian',indian:'indian',steakhouse:'steak_house',trattoria:'italian',osteria:'italian'};
3215
+ const cuisineMatch = travelTask.match(/\b(sushi|pizza|giapponese|japanese|italiano|italian|cinese|chinese|indiano|indian|steakhouse|trattoria|osteria)\b/i);
3216
+ const cuisineRaw = cuisineMatch ? cuisineMatch[1].toLowerCase() : null;
3217
+ const cuisineOSM = cuisineRaw ? (cuisineMap[cuisineRaw] || cuisineRaw) : null;
3206
3218
 
3207
- // Accommodation type
3208
- const hasAccommodation = /b&b|bed\s*&?\s*breakfast|hotel|albergo|agriturismo|locanda|ostello|hostel|airbnb/i.test(travelTask);
3219
+ const hasAccommodation = /b&b|bed\s*(?:&|and)\s*breakfast|hotel|albergo|agriturismo|locanda|ostello|hostel|pernottament|soggiorno/i.test(travelTask);
3209
3220
  const hasRestaurant = /ristorante|restaurant|sushi|cena|dinner|pranzo|lunch|mangiare|eat/i.test(travelTask);
3210
3221
 
3211
3222
  const summaryParts = [];
3212
- if (city) summaryParts.push('location: ' + city);
3223
+ if (city) summaryParts.push('city: ' + city);
3213
3224
  if (targetDateStr) summaryParts.push('date: ' + targetDateStr);
3214
- if (cuisine) summaryParts.push('cuisine: ' + cuisine);
3225
+ if (cuisineRaw) summaryParts.push('cuisine: ' + cuisineRaw);
3215
3226
  if (hasAccommodation) summaryParts.push('accommodation: yes');
3216
3227
  if (hasRestaurant) summaryParts.push('restaurant: yes');
3217
3228
  sendToken('[Extracted — ' + (summaryParts.join(', ') || 'general search') + '] ');
3218
3229
 
3219
- // ── Build search URLs for portals ──
3230
+ const UA = 'NHA-TravelAgent/1.0 (nothumanallowed.com)';
3220
3231
  const portalResults = [];
3221
- const cityQ = city ? encodeURIComponent(city) : '';
3222
- const cuisineQ = cuisine ? encodeURIComponent(cuisine) : '';
3223
- const dateQ = targetDateStr || '';
3224
-
3225
- // TheFork: restaurant search
3226
- if (hasRestaurant) {
3227
- const theforkQuery = [cuisine, city].filter(Boolean).join(' ');
3228
- const theforkUrl = 'https://www.thefork.it/ristoranti/' + (city ? encodeURIComponent(city.toLowerCase()) : 'italia') + (cuisine ? '?q=' + cuisineQ : '');
3229
- sendToken('[Searching TheFork...] ');
3232
+
3233
+ // ── TIER 1: Nominatim geocoding get lat/lon for city ──
3234
+ let lat = null, lon = null;
3235
+ if (city) {
3236
+ sendToken('[Geocoding ' + city + '...] ');
3230
3237
  try {
3231
- if (!be.isBrowserRunning()) await be.browserOpen(theforkUrl, { waitForLoad: true });
3232
- else await be.browserNavigate(theforkUrl, { waitForLoad: true });
3233
- await new Promise(r => setTimeout(r, 2500));
3234
- const theforkContent = await be.browserExtract({ selector: 'body', format: 'text' });
3235
- if (theforkContent && !theforkContent.error && theforkContent.text && theforkContent.text.length > 200) {
3236
- portalResults.push('## TheFork — ' + theforkUrl + '\n' + theforkContent.text.slice(0, 5000));
3237
- } else {
3238
- const fetchFork = await withTimeout(executeTool('fetch_url', { url: theforkUrl }, config), 20000);
3239
- if (fetchFork && fetchFork.length > 200) portalResults.push('## TheFork — ' + theforkUrl + '\n' + fetchFork.slice(0, 5000));
3238
+ const geoUrl = 'https://nominatim.openstreetmap.org/search?' + new URLSearchParams({ q: city, format: 'json', limit: '1', addressdetails: '0' }).toString();
3239
+ const geoRes = await withTimeout(fetch(geoUrl, { headers: { 'User-Agent': UA, 'Accept': 'application/json' } }), 8000);
3240
+ if (geoRes.ok) {
3241
+ const geoData = await geoRes.json();
3242
+ if (geoData && geoData[0]) {
3243
+ lat = parseFloat(geoData[0].lat);
3244
+ lon = parseFloat(geoData[0].lon);
3245
+ sendToken('[Found: ' + lat.toFixed(4) + ', ' + lon.toFixed(4) + '] ');
3246
+ }
3240
3247
  }
3241
- } catch (e) {
3248
+ } catch {}
3249
+ }
3250
+
3251
+ // ── TIER 1: Overpass API — real OSM data ──
3252
+ if (lat !== null && lon !== null) {
3253
+ const radius = 5000; // 5km radius
3254
+ const formatPlace = (el) => {
3255
+ const t = el.tags || {};
3256
+ const parts = [];
3257
+ if (t.name) parts.push('**' + t.name + '**');
3258
+ if (t['addr:street']) parts.push(t['addr:street'] + (t['addr:housenumber'] ? ' ' + t['addr:housenumber'] : ''));
3259
+ if (t['addr:city']) parts.push(t['addr:city']);
3260
+ if (t.phone || t['contact:phone']) parts.push('Tel: ' + (t.phone || t['contact:phone']));
3261
+ if (t.website || t['contact:website']) parts.push('Web: ' + (t.website || t['contact:website']));
3262
+ if (t.opening_hours) parts.push('Orari: ' + t.opening_hours);
3263
+ if (t.email || t['contact:email']) parts.push('Email: ' + (t.email || t['contact:email']));
3264
+ if (t.cuisine) parts.push('Cucina: ' + t.cuisine);
3265
+ if (t.stars) parts.push('Stelle: ' + t.stars);
3266
+ const coordLat = el.lat || (el.center && el.center.lat);
3267
+ const coordLon = el.lon || (el.center && el.center.lon);
3268
+ if (coordLat && coordLon) parts.push('Maps: https://www.openstreetmap.org/?mlat=' + coordLat + '&mlon=' + coordLon + '&zoom=17');
3269
+ return parts.join(' | ');
3270
+ };
3271
+
3272
+ // Restaurants query
3273
+ if (hasRestaurant) {
3274
+ sendToken('[Overpass: searching restaurants' + (cuisineOSM ? ' (' + cuisineOSM + ')' : '') + '...] ');
3242
3275
  try {
3243
- const fetchFork = await withTimeout(executeTool('fetch_url', { url: theforkUrl }, config), 20000);
3244
- if (fetchFork && fetchFork.length > 200) portalResults.push('## TheFork ' + theforkUrl + '\n' + fetchFork.slice(0, 5000));
3245
- } catch {}
3276
+ const cuisineFilter = cuisineOSM ? '["cuisine"="' + cuisineOSM + '"]' : '';
3277
+ const restQuery = '[out:json][timeout:20];(node["amenity"="restaurant"]' + cuisineFilter + '(around:' + radius + ',' + lat + ',' + lon + ');way["amenity"="restaurant"]' + cuisineFilter + '(around:' + radius + ',' + lat + ',' + lon + '););out center tags;';
3278
+ const restRes = await withTimeout(fetch('https://overpass.kumi.systems/api/interpreter', {
3279
+ method: 'POST',
3280
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': UA },
3281
+ body: 'data=' + encodeURIComponent(restQuery)
3282
+ }), 25000);
3283
+ if (restRes.ok) {
3284
+ const restData = await restRes.json();
3285
+ const elements = (restData.elements || []).filter(el => el.tags && el.tags.name);
3286
+ if (elements.length > 0) {
3287
+ const lines = ['## Ristoranti trovati via OpenStreetMap (' + elements.length + ' risultati, raggio ' + (radius/1000) + 'km da ' + city + ')'];
3288
+ elements.slice(0, 15).forEach((el, i) => { lines.push((i+1) + '. ' + formatPlace(el)); });
3289
+ portalResults.push(lines.join('\n'));
3290
+ sendToken('[Found ' + elements.length + ' restaurants] ');
3291
+ } else if (cuisineOSM) {
3292
+ // Retry without cuisine filter — broader search
3293
+ sendToken('[No ' + cuisineOSM + ' found, retrying broader...] ');
3294
+ const broadQuery = '[out:json][timeout:20];(node["amenity"="restaurant"](around:' + radius + ',' + lat + ',' + lon + ');way["amenity"="restaurant"](around:' + radius + ',' + lat + ',' + lon + '););out center tags;';
3295
+ const broadRes = await withTimeout(fetch('https://overpass.kumi.systems/api/interpreter', {
3296
+ method: 'POST',
3297
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': UA },
3298
+ body: 'data=' + encodeURIComponent(broadQuery)
3299
+ }), 25000);
3300
+ if (broadRes.ok) {
3301
+ const broadData = await broadRes.json();
3302
+ const broadEl = (broadData.elements || []).filter(el => el.tags && el.tags.name);
3303
+ if (broadEl.length > 0) {
3304
+ const lines = ['## Ristoranti a ' + city + ' (tutti i tipi, ' + broadEl.length + ' trovati — nessun ristorante ' + cuisineRaw + ' su OpenStreetMap in questa zona)'];
3305
+ broadEl.slice(0, 10).forEach((el, i) => { lines.push((i+1) + '. ' + formatPlace(el)); });
3306
+ portalResults.push(lines.join('\n'));
3307
+ }
3308
+ }
3309
+ }
3310
+ }
3311
+ } catch (e) { sendToken('[Overpass restaurants error: ' + e.message.slice(0,50) + '] '); }
3246
3312
  }
3247
- }
3248
3313
 
3249
- // Booking.com: B&B / hotel search
3250
- if (hasAccommodation) {
3251
- const checkin = targetDate ? targetDate.toISOString().slice(0, 10) : '';
3252
- const checkout = targetDate ? new Date(targetDate.getTime() + 86400000).toISOString().slice(0, 10) : '';
3253
- const bookingUrl = 'https://www.booking.com/search.html?ss=' + (cityQ || 'Italy') + (checkin ? '&checkin=' + checkin + '&checkout=' + checkout : '') + '&group_adults=2&no_rooms=1&b_h4u_keep_filters=&from_sf=1';
3254
- sendToken('[Searching Booking.com...] ');
3255
- try {
3256
- if (!be.isBrowserRunning()) await be.browserOpen(bookingUrl, { waitForLoad: true });
3257
- else await be.browserNavigate(bookingUrl, { waitForLoad: true });
3258
- await new Promise(r => setTimeout(r, 3000));
3259
- const bookingContent = await be.browserExtract({ selector: 'body', format: 'text' });
3260
- if (bookingContent && !bookingContent.error && bookingContent.text && bookingContent.text.length > 200) {
3261
- portalResults.push('## Booking.com — ' + bookingUrl + '\n' + bookingContent.text.slice(0, 5000));
3262
- } else {
3263
- const fetchBook = await withTimeout(executeTool('fetch_url', { url: bookingUrl }, config), 20000);
3264
- if (fetchBook && fetchBook.length > 200) portalResults.push('## Booking.com — ' + bookingUrl + '\n' + fetchBook.slice(0, 5000));
3265
- }
3266
- } catch (e) {
3314
+ // Accommodation query
3315
+ if (hasAccommodation) {
3316
+ sendToken('[Overpass: searching accommodation...] ');
3267
3317
  try {
3268
- const fetchBook = await withTimeout(executeTool('fetch_url', { url: bookingUrl }, config), 20000);
3269
- if (fetchBook && fetchBook.length > 200) portalResults.push('## Booking.com — ' + bookingUrl + '\n' + fetchBook.slice(0, 5000));
3270
- } catch {}
3318
+ const accQuery = '[out:json][timeout:20];(node["tourism"~"hotel|guest_house|bed_and_breakfast|hostel|motel"](around:' + radius + ',' + lat + ',' + lon + ');way["tourism"~"hotel|guest_house|bed_and_breakfast|hostel|motel"](around:' + radius + ',' + lat + ',' + lon + ');node["amenity"="hotel"](around:' + radius + ',' + lat + ',' + lon + '););out center tags;';
3319
+ const accRes = await withTimeout(fetch('https://overpass.kumi.systems/api/interpreter', {
3320
+ method: 'POST',
3321
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': UA },
3322
+ body: 'data=' + encodeURIComponent(accQuery)
3323
+ }), 25000);
3324
+ if (accRes.ok) {
3325
+ const accData = await accRes.json();
3326
+ const elements = (accData.elements || []).filter(el => el.tags && el.tags.name);
3327
+ if (elements.length > 0) {
3328
+ const lines = ['## Alloggi trovati via OpenStreetMap (' + elements.length + ' risultati, raggio ' + (radius/1000) + 'km da ' + city + ')'];
3329
+ elements.slice(0, 15).forEach((el, i) => {
3330
+ const t = el.tags || {};
3331
+ const type = t.tourism === 'guest_house' ? 'B&B/Guest house' : t.tourism === 'hotel' ? 'Hotel' : t.tourism === 'bed_and_breakfast' ? 'B&B' : (t.tourism || 'Alloggio');
3332
+ lines.push((i+1) + '. [' + type + '] ' + formatPlace(el));
3333
+ });
3334
+ portalResults.push(lines.join('\n'));
3335
+ sendToken('[Found ' + elements.length + ' accommodations] ');
3336
+ }
3337
+ }
3338
+ } catch (e) { sendToken('[Overpass accommodation error: ' + e.message.slice(0,50) + '] '); }
3271
3339
  }
3340
+ } else if (city) {
3341
+ sendToken('[Geocoding failed, skipping Overpass] ');
3272
3342
  }
3273
3343
 
3274
- // TripAdvisor: restaurants
3275
- if (hasRestaurant && city) {
3276
- const taUrl = 'https://www.tripadvisor.it/Search?q=' + [cuisineQ, cityQ].filter(Boolean).join('+') + '&searchSessionId=&sid=&blockRedirect=true';
3277
- sendToken('[Searching TripAdvisor...] ');
3344
+ // ── TIER 2: Web search fallback — targeted queries ──
3345
+ const dataFound = portalResults.length > 0 && portalResults.some(r => r.includes('trovati') && !r.includes('0 risultati'));
3346
+ if (!dataFound) {
3347
+ sendToken('[Web search fallback...] ');
3278
3348
  try {
3279
- const fetchTa = await withTimeout(executeTool('fetch_url', { url: taUrl }, config), 20000);
3280
- if (fetchTa && fetchTa.length > 200) portalResults.push('## TripAdvisor — ' + taUrl + '\n' + fetchTa.slice(0, 5000));
3349
+ const queries = [
3350
+ [cuisineRaw, 'ristorante', city].filter(Boolean).join(' '),
3351
+ hasAccommodation ? ['b&b romantico', city, targetDateStr ? targetDateStr.slice(0,5) : ''].filter(Boolean).join(' ') : null
3352
+ ].filter(Boolean);
3353
+ for (const q of queries) {
3354
+ const sr = await withTimeout(executeTool('web_search', { query: q, deep: true }, config), 30000);
3355
+ if (sr && sr.length > 100) portalResults.push('## Web search: "' + q + '"\n' + (typeof sr === 'string' ? sr : JSON.stringify(sr)));
3356
+ }
3281
3357
  } catch {}
3282
3358
  }
3283
3359
 
3284
- // Also do a DuckDuckGo search for the full task to capture any other results
3285
- sendToken('[Web search backup...] ');
3286
- try {
3287
- const generalQuery = [cuisine, 'ristorante', city, targetDateStr ? 'prenotazione' : '', hasAccommodation ? 'b&b romantico' : ''].filter(Boolean).join(' ');
3288
- const searchResult = await withTimeout(executeTool('web_search', { query: generalQuery, deep: true }, config), 30000);
3289
- if (searchResult && searchResult.length > 100) {
3290
- portalResults.push('## Web search: "' + generalQuery + '"\n' + (typeof searchResult === 'string' ? searchResult : JSON.stringify(searchResult)));
3291
- }
3292
- } catch {}
3293
-
3294
- toolData = portalResults.length > 0
3295
- ? portalResults.join('\n\n')
3296
- : 'No results found on travel portals. Try specifying city, date, and type of place.';
3360
+ // ── Build final output ──
3361
+ if (portalResults.length > 0) {
3362
+ const header = '# TravelAgent — Risultati per: ' + (city || 'zona non specificata') + (targetDateStr ? ' | Data: ' + targetDateStr : '') + (cuisineRaw ? ' | Cucina: ' + cuisineRaw : '') + '\n\n';
3363
+ toolData = header + portalResults.join('\n\n');
3364
+ } else {
3365
+ toolData = '# TravelAgent — Nessun risultato trovato\n\nNon sono stati trovati ristoranti o alloggi per i parametri specificati.\n\n**Parametri cercati:**\n- Città: ' + (city || 'non specificata') + '\n- Data: ' + (targetDateStr || 'non specificata') + '\n- Tipo cucina: ' + (cuisineRaw || 'qualsiasi') + '\n\n**Suggerimento:** Specifica la città esatta (es. "a Mantova") e il tipo di cucina per ottenere risultati migliori.';
3366
+ }
3297
3367
  } catch (e) { toolData = toolData || 'TravelAgent failed: ' + e.message; }
3298
3368
 
3299
3369
  } else if (agent === 'BrowserAgent') {
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.5.105';
8
+ export const VERSION = '13.5.106';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11