nothumanallowed 13.5.175 → 13.5.177
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 +41 -0
- package/src/constants.mjs +1 -1
- package/src/services/tool-executor.mjs +63 -1
- package/src/services/web-ui.mjs +66 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.177",
|
|
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
|
@@ -694,6 +694,47 @@ export async function cmdUI(args) {
|
|
|
694
694
|
return;
|
|
695
695
|
}
|
|
696
696
|
|
|
697
|
+
// GET /api/weather?location=<city> — live weather via wttr.in (no API key)
|
|
698
|
+
if (method === 'GET' && pathname === '/api/weather') {
|
|
699
|
+
const loc = (urlObj.searchParams.get('location') || config.location || '').trim();
|
|
700
|
+
if (!loc) { sendJSON(res, 400, { error: 'location required' }); return; }
|
|
701
|
+
try {
|
|
702
|
+
const wttrRes = await fetch(`https://wttr.in/${encodeURIComponent(loc)}?format=j1`, {
|
|
703
|
+
headers: { 'User-Agent': 'nha-cli/1.0' },
|
|
704
|
+
signal: AbortSignal.timeout(8000),
|
|
705
|
+
});
|
|
706
|
+
if (!wttrRes.ok) { sendJSON(res, 502, { error: `wttr.in ${wttrRes.status}` }); return; }
|
|
707
|
+
const w = await wttrRes.json();
|
|
708
|
+
const cur = w.current_condition?.[0];
|
|
709
|
+
const area = w.nearest_area?.[0];
|
|
710
|
+
if (!cur) { sendJSON(res, 404, { error: 'No weather data' }); return; }
|
|
711
|
+
const forecast = (w.weather || []).slice(0, 3).map(d => ({
|
|
712
|
+
date: d.date,
|
|
713
|
+
maxC: d.maxtempC,
|
|
714
|
+
minC: d.mintempC,
|
|
715
|
+
desc: d.hourly?.[4]?.weatherDesc?.[0]?.value || '',
|
|
716
|
+
rain: d.hourly?.[4]?.chanceofrain || '0',
|
|
717
|
+
}));
|
|
718
|
+
sendJSON(res, 200, {
|
|
719
|
+
city: area?.areaName?.[0]?.value || loc,
|
|
720
|
+
country: area?.country?.[0]?.value || '',
|
|
721
|
+
tempC: cur.temp_C,
|
|
722
|
+
feelsC: cur.FeelsLikeC,
|
|
723
|
+
humidity: cur.humidity,
|
|
724
|
+
windKmph: cur.windspeedKmph,
|
|
725
|
+
windDir: cur.winddir16Point,
|
|
726
|
+
uvIndex: cur.uvIndex,
|
|
727
|
+
desc: cur.weatherDesc?.[0]?.value || '',
|
|
728
|
+
cloudcover: cur.cloudcover,
|
|
729
|
+
forecast,
|
|
730
|
+
});
|
|
731
|
+
} catch (e) {
|
|
732
|
+
sendJSON(res, 502, { error: e.message });
|
|
733
|
+
}
|
|
734
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
|
|
697
738
|
// GET /api/status
|
|
698
739
|
if (method === 'GET' && pathname === '/api/status') {
|
|
699
740
|
const { loadTokens: loadTokSt } = await import('../services/token-store.mjs');
|
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.
|
|
8
|
+
export const VERSION = '13.5.177';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -302,6 +302,12 @@ TOOLS:
|
|
|
302
302
|
Returns: title, excerpt, and body text (max 8000 chars). Only fetches text/html/json/xml.
|
|
303
303
|
Use this when the user provides a specific URL to read, summarize, or analyze.
|
|
304
304
|
|
|
305
|
+
49. get_weather(location: string, lang?: string)
|
|
306
|
+
Get current weather and 3-day forecast for any city or location. No API key needed.
|
|
307
|
+
Returns: temperature (°C/°F), feels like, humidity, wind speed, UV index, weather condition, and 3-day forecast.
|
|
308
|
+
ALWAYS use this for weather requests ("meteo", "tempo", "weather", "temperatura", "piove", "sole", "forecast").
|
|
309
|
+
Examples: get_weather("Viterbo"), get_weather("Rome, Italy"), get_weather("New York")
|
|
310
|
+
|
|
305
311
|
--- BROWSER AUTOMATION ---
|
|
306
312
|
|
|
307
313
|
49. browser_open(url: string, waitForLoad?: boolean)
|
|
@@ -599,7 +605,7 @@ note_add · note_list
|
|
|
599
605
|
github_issues · github_prs · github_notifications · github_create_issue
|
|
600
606
|
notion_search · notion_page
|
|
601
607
|
slack_channels · slack_messages · slack_send
|
|
602
|
-
web_search · fetch_url
|
|
608
|
+
web_search · fetch_url · get_weather
|
|
603
609
|
browser_open · browser_screenshot · browser_click · browser_type · browser_extract · browser_js · browser_wait · browser_scroll · browser_key · browser_close
|
|
604
610
|
cron_add · cron_list · cron_remove
|
|
605
611
|
screen_capture · screen_analyze
|
|
@@ -1876,6 +1882,62 @@ export async function executeTool(action, params, config) {
|
|
|
1876
1882
|
return lines.join('\n');
|
|
1877
1883
|
}
|
|
1878
1884
|
|
|
1885
|
+
// ── Weather ──────────────────────────────────────────────────────────
|
|
1886
|
+
case 'get_weather': {
|
|
1887
|
+
const location = (params.location || '').trim();
|
|
1888
|
+
if (!location) return 'A location is required (e.g. "Rome", "Viterbo, Italy").';
|
|
1889
|
+
try {
|
|
1890
|
+
const encodedLoc = encodeURIComponent(location);
|
|
1891
|
+
const wttrRes = await fetch(`https://wttr.in/${encodedLoc}?format=j1`, {
|
|
1892
|
+
headers: { 'User-Agent': 'nha-cli/1.0' },
|
|
1893
|
+
signal: AbortSignal.timeout(8000),
|
|
1894
|
+
});
|
|
1895
|
+
if (!wttrRes.ok) return `Weather service returned ${wttrRes.status} for "${location}". Try a different location name.`;
|
|
1896
|
+
const w = await wttrRes.json();
|
|
1897
|
+
const cur = w.current_condition?.[0];
|
|
1898
|
+
const area = w.nearest_area?.[0];
|
|
1899
|
+
if (!cur) return `No weather data found for "${location}".`;
|
|
1900
|
+
|
|
1901
|
+
const cityName = area?.areaName?.[0]?.value || location;
|
|
1902
|
+
const country = area?.country?.[0]?.value || '';
|
|
1903
|
+
const desc = cur.weatherDesc?.[0]?.value || '';
|
|
1904
|
+
const tempC = cur.temp_C;
|
|
1905
|
+
const feelsC = cur.FeelsLikeC;
|
|
1906
|
+
const humidity = cur.humidity;
|
|
1907
|
+
const windKmph = cur.windspeedKmph;
|
|
1908
|
+
const windDir = cur.winddir16Point;
|
|
1909
|
+
const uvIndex = cur.uvIndex;
|
|
1910
|
+
const visibility = cur.visibility;
|
|
1911
|
+
const cloudcover = cur.cloudcover;
|
|
1912
|
+
|
|
1913
|
+
const lines = [
|
|
1914
|
+
`📍 ${cityName}${country ? ', ' + country : ''}`,
|
|
1915
|
+
`🌡️ ${tempC}°C (feels like ${feelsC}°C) — ${desc}`,
|
|
1916
|
+
`💧 Humidity: ${humidity}% | 💨 Wind: ${windKmph} km/h ${windDir}`,
|
|
1917
|
+
`☀️ UV Index: ${uvIndex} | 👁️ Visibility: ${visibility} km | ☁️ Cloud: ${cloudcover}%`,
|
|
1918
|
+
];
|
|
1919
|
+
|
|
1920
|
+
// 3-day forecast
|
|
1921
|
+
const forecast = w.weather || [];
|
|
1922
|
+
if (forecast.length > 0) {
|
|
1923
|
+
lines.push('');
|
|
1924
|
+
lines.push('📅 3-day forecast:');
|
|
1925
|
+
for (const day of forecast.slice(0, 3)) {
|
|
1926
|
+
const date = day.date;
|
|
1927
|
+
const maxC = day.maxtempC;
|
|
1928
|
+
const minC = day.mintempC;
|
|
1929
|
+
const dayDesc = day.hourly?.[4]?.weatherDesc?.[0]?.value || '';
|
|
1930
|
+
const rain = day.hourly?.[4]?.chanceofrain || '0';
|
|
1931
|
+
lines.push(` ${date}: ${minC}°C → ${maxC}°C ${dayDesc} 🌧️ ${rain}% rain`);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
return lines.join('\n');
|
|
1936
|
+
} catch (e) {
|
|
1937
|
+
return `Weather lookup failed: ${e.message}. Try using web_search("weather ${location}") as fallback.`;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1879
1941
|
// ── Cron / Heartbeat ───────────────────────────────────────────────
|
|
1880
1942
|
case 'cron_add': {
|
|
1881
1943
|
const { addCronJob } = await import('./ops-daemon.mjs');
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -11,8 +11,8 @@ var currentView = 'dashboard';
|
|
|
11
11
|
var chatHistory = [];
|
|
12
12
|
var activeConvId = null;
|
|
13
13
|
var convList = [];
|
|
14
|
-
var dash = {emails:[],events:[],tasks:[],plan:null,status:null};
|
|
15
|
-
var dashLoaded = {emails:false,events:false,tasks:false,contacts:false,notes:false,drive:false,github:false,notion:false,slack:false};
|
|
14
|
+
var dash = {emails:[],events:[],tasks:[],plan:null,status:null,weather:null};
|
|
15
|
+
var dashLoaded = {emails:false,events:false,tasks:false,contacts:false,notes:false,drive:false,github:false,notion:false,slack:false,weather:false};
|
|
16
16
|
var chatStreaming = false;
|
|
17
17
|
var chatAbortController = null;
|
|
18
18
|
|
|
@@ -286,11 +286,43 @@ function apiPost(p,b,m){return fetch(API+p,{method:m||'POST',headers:{'Content-T
|
|
|
286
286
|
function apiPatch(p){return fetch(API+p,{method:'PATCH'}).then(function(r){return r.ok?r.json():null}).catch(function(){return null})}
|
|
287
287
|
|
|
288
288
|
// ---- LOAD DATA ----
|
|
289
|
+
function loadWeather(){
|
|
290
|
+
// Try browser geolocation first, fall back to IP geolocation, then saved config
|
|
291
|
+
var savedLoc=localStorage.getItem('nha_weather_location');
|
|
292
|
+
function fetchWeather(loc){
|
|
293
|
+
apiGet('/api/weather?location='+encodeURIComponent(loc)).then(function(r){
|
|
294
|
+
if(r&&r.tempC){dash.weather=r;dashLoaded.weather=true;if(currentView==='dashboard')render();}
|
|
295
|
+
}).catch(function(){});
|
|
296
|
+
}
|
|
297
|
+
if(savedLoc){fetchWeather(savedLoc);return;}
|
|
298
|
+
if(navigator.geolocation){
|
|
299
|
+
navigator.geolocation.getCurrentPosition(function(pos){
|
|
300
|
+
// Reverse geocode via nominatim (free, no key)
|
|
301
|
+
fetch('https://nominatim.openstreetmap.org/reverse?lat='+pos.coords.latitude+'&lon='+pos.coords.longitude+'&format=json',{headers:{'User-Agent':'nha-cli/1.0'}})
|
|
302
|
+
.then(function(r){return r.json();})
|
|
303
|
+
.then(function(d){
|
|
304
|
+
var city=d.address&&(d.address.city||d.address.town||d.address.village||d.address.county||'');
|
|
305
|
+
if(city){localStorage.setItem('nha_weather_location',city);fetchWeather(city);}
|
|
306
|
+
}).catch(function(){});
|
|
307
|
+
},function(){
|
|
308
|
+
// Geoloc denied — IP fallback
|
|
309
|
+
fetch('https://ipapi.co/json/').then(function(r){return r.json();}).then(function(d){
|
|
310
|
+
var city=d.city||'';if(city){localStorage.setItem('nha_weather_location',city);fetchWeather(city);}
|
|
311
|
+
}).catch(function(){});
|
|
312
|
+
},{timeout:5000});
|
|
313
|
+
} else {
|
|
314
|
+
fetch('https://ipapi.co/json/').then(function(r){return r.json();}).then(function(d){
|
|
315
|
+
var city=d.city||'';if(city){localStorage.setItem('nha_weather_location',city);fetchWeather(city);}
|
|
316
|
+
}).catch(function(){});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
289
320
|
function loadDash(){
|
|
290
321
|
// Load each API independently - render as each arrives (emails are slow)
|
|
291
322
|
apiGet('/api/status').then(function(r){dash.status=r;render()});
|
|
292
323
|
apiGet('/api/tasks').then(function(r){dash.tasks=(r&&r.tasks)||[];dashLoaded.tasks=true;updateBadges();render()});
|
|
293
324
|
apiGet('/api/calendar').then(function(r){dash.events=(r&&r.events)||[];dashLoaded.events=true;updateBadges();render()});
|
|
325
|
+
if(!dashLoaded.weather)loadWeather();
|
|
294
326
|
return apiGet('/api/emails?page=0&pageSize=25').then(function(r){dash.emails=(r&&r.emails)||[];dash._emailHasMore=r&&r.hasMore;dashLoaded.emails=true;emailPage=0;updateBadges();render()});
|
|
295
327
|
}
|
|
296
328
|
function loadAgents(){return apiGet('/api/agents').then(function(r){agentsList=(r&&r.agents)||[]})}
|
|
@@ -358,11 +390,28 @@ function renderDash(el){
|
|
|
358
390
|
var done=t.filter(function(x){return x.status==='done'}).length;
|
|
359
391
|
var pend=t.length-done;
|
|
360
392
|
var pct=t.length>0?Math.round(done/t.length*100):0;
|
|
393
|
+
var weatherCard;
|
|
394
|
+
if(dashLoaded.weather&&dash.weather){
|
|
395
|
+
var wm=dash.weather;
|
|
396
|
+
var wIcons={Sunny:'☀',Clear:'☀','Partly Cloudy':'⛅','Partly cloudy':'⛅',Cloudy:'☁',Overcast:'☁','Light rain':'🌧','Patchy light drizzle':'🌧','Moderate rain':'🌧','Heavy rain':'🌧',Rain:'🌧',Drizzle:'🌧',Snow:'❄','Light snow':'❄',Fog:'🌫',Mist:'🌫',Thunder:'⚡','Thundery outbreaks':'⚡'};
|
|
397
|
+
var wIcon=wIcons[wm.desc]||'🌥';
|
|
398
|
+
weatherCard='<div class="card" style="cursor:default"><div class="card__title" style="display:flex;align-items:center;justify-content:space-between">'+
|
|
399
|
+
'<span>'+esc(wm.city)+(wm.country?', '+esc(wm.country.slice(0,2)):'')+'</span>'+
|
|
400
|
+
'<span style="font-size:10px;color:var(--dim);cursor:pointer" onclick="nhaSetWeatherLocation()">✎ change</span>'+
|
|
401
|
+
'</div>'+
|
|
402
|
+
'<div class="card__value" style="font-size:28px">'+wIcon+' '+esc(wm.tempC)+'°C</div>'+
|
|
403
|
+
'<div class="card__sub">'+esc(wm.desc)+' · feels '+esc(wm.feelsC)+'°C · 💧'+esc(wm.humidity)+'%</div>'+
|
|
404
|
+
'</div>';
|
|
405
|
+
} else {
|
|
406
|
+
weatherCard='<div class="card" style="cursor:default"><div class="card__title" style="display:flex;align-items:center;justify-content:space-between"><span>Weather</span><span style="font-size:10px;color:var(--dim);cursor:pointer" onclick="nhaSetWeatherLocation()">✎ set location</span></div>'+
|
|
407
|
+
'<div class="card__value" style="font-size:22px">--</div>'+
|
|
408
|
+
'<div class="card__sub">'+(dashLoaded.weather?'No data':'Loading...')+'</div></div>';
|
|
409
|
+
}
|
|
361
410
|
var h='<div class="dash-grid">'+
|
|
362
411
|
'<div class="card"><div class="card__title">Tasks</div><div class="card__value">'+pend+'</div><div class="card__sub">'+done+'/'+t.length+' done ('+pct+'%)</div></div>'+
|
|
363
412
|
'<div class="card"><div class="card__title">Emails</div><div class="card__value">'+(dashLoaded.emails?e.length:'<span class="spinner" style="width:14px;height:14px;display:inline-block;vertical-align:middle"></span>')+'</div><div class="card__sub">'+(dashLoaded.emails?(e.length>0?esc(e[0].from):'Inbox zero'):'Loading...')+'</div></div>'+
|
|
364
413
|
'<div class="card"><div class="card__title">Events</div><div class="card__value">'+ev.length+'</div><div class="card__sub">'+(ev.length>0?esc(ev[0].summary):'No events')+'</div></div>'+
|
|
365
|
-
|
|
414
|
+
weatherCard+
|
|
366
415
|
'</div>';
|
|
367
416
|
if(ev.length>0){h+='<div class="section-title">Events</div>';ev.slice(0,5).forEach(function(x){h+='<div class="card event"><span class="event__time">'+(x.isAllDay?'All day':fmtTime(x.start)+' - '+fmtTime(x.end))+'</span><span class="event__title">'+esc(x.summary)+'</span>'+(x.location?'<span class="event__location">'+esc(x.location)+'</span>':'')+'</div>'})}
|
|
368
417
|
if(e.length>0){h+='<div class="section-title">Emails</div>';e.slice(0,5).forEach(function(x){h+='<div class="card email"><div class="email__header"><span class="email__from">'+esc(x.from)+'</span><span class="email__date">'+esc(x.date)+'</span></div><div class="email__subject">'+esc(x.subject)+'</div><div class="email__snippet">'+esc((x.snippet||'').slice(0,120))+'</div></div>'})}
|
|
@@ -3830,6 +3879,20 @@ function handleDaemonEvent(msg) {
|
|
|
3830
3879
|
}
|
|
3831
3880
|
}
|
|
3832
3881
|
|
|
3882
|
+
function nhaSetWeatherLocation() {
|
|
3883
|
+
var current = localStorage.getItem('nha_weather_location') || '';
|
|
3884
|
+
var city = prompt('Enter your city for weather (e.g. "Rome", "Viterbo, Italy"):', current);
|
|
3885
|
+
if (city === null) return;
|
|
3886
|
+
city = city.trim();
|
|
3887
|
+
if (!city) { localStorage.removeItem('nha_weather_location'); dash.weather = null; dashLoaded.weather = false; render(); return; }
|
|
3888
|
+
localStorage.setItem('nha_weather_location', city);
|
|
3889
|
+
dash.weather = null; dashLoaded.weather = false;
|
|
3890
|
+
render();
|
|
3891
|
+
apiGet('/api/weather?location=' + encodeURIComponent(city)).then(function(r) {
|
|
3892
|
+
if (r && r.tempC) { dash.weather = r; dashLoaded.weather = true; render(); }
|
|
3893
|
+
}).catch(function() {});
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3833
3896
|
function npmUpdate() {
|
|
3834
3897
|
var existing = document.getElementById('npmUpdateModal');
|
|
3835
3898
|
if (existing) existing.remove();
|