nothumanallowed 15.1.5 → 15.1.7
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.
|
|
3
|
+
"version": "15.1.7",
|
|
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/cli.mjs
CHANGED
|
@@ -52,12 +52,15 @@ export async function main(argv) {
|
|
|
52
52
|
}
|
|
53
53
|
}).catch(() => {});
|
|
54
54
|
|
|
55
|
-
// npm version check (non-blocking)
|
|
55
|
+
// npm version check (non-blocking). The one-liner uses --prefer-online to
|
|
56
|
+
// bypass npm's metadata cache, which is the #1 reason `npm install -g`
|
|
57
|
+
// appears to "do nothing" — it had stale "latest" in the local cache.
|
|
56
58
|
checkNpmVersion().then(result => {
|
|
57
59
|
if (result?.updateAvailable) {
|
|
58
60
|
console.log('');
|
|
59
61
|
warn(`New NHA version available: ${result.current} → ${result.latest}`);
|
|
60
|
-
info(`Run "nha update"
|
|
62
|
+
info(`Run "nha update" (recommended — auto-installs npm + agents)`);
|
|
63
|
+
info(`Or manually: npm cache clean --force && npm install -g nothumanallowed@${result.latest} --prefer-online`);
|
|
61
64
|
}
|
|
62
65
|
}).catch(() => {});
|
|
63
66
|
}
|
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.
|
|
8
|
+
export const VERSION = '15.1.7';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -11,12 +11,43 @@ import { NHA_DIR, AGENTS_DIR } from '../../constants.mjs';
|
|
|
11
11
|
import { callLLM, callLLMStream, parseAgentFile } from '../../services/llm.mjs';
|
|
12
12
|
import { webSearch, fetchUrl, fetchUrlRich, headProbe } from '../../services/web-tools.mjs';
|
|
13
13
|
|
|
14
|
-
// Agents that get web data pre-fetched before their LLM call
|
|
14
|
+
// Agents that get web data pre-fetched (search + URL content) before their LLM call.
|
|
15
|
+
// We err on the side of inclusion: any agent that writes analysis, copy, briefings,
|
|
16
|
+
// or reports benefits from real-time data — without it they hallucinate plausible
|
|
17
|
+
// but generic frameworks. Synthesis-only agents (CanvasAgent, GitHub/Email/Slack
|
|
18
|
+
// connectors) are intentionally excluded.
|
|
15
19
|
const WEB_TOOL_AGENTS = new Set([
|
|
16
|
-
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
+
// Original research/intel set
|
|
21
|
+
'WebSearchAgent', 'TravelAgent',
|
|
22
|
+
'MERCURY', 'mercury',
|
|
23
|
+
'ATHENA', 'athena',
|
|
24
|
+
'ORACLE', 'oracle',
|
|
25
|
+
'CASSANDRA', 'cassandra',
|
|
26
|
+
'HERALD', 'herald',
|
|
27
|
+
'DataAnalystAgent',
|
|
28
|
+
'TEMPEST', 'tempest',
|
|
29
|
+
'EPICURE', 'epicure',
|
|
30
|
+
'CARTOGRAPHER', 'cartographer',
|
|
31
|
+
// Analysis & writing — they MUST ground on the target site, not invent.
|
|
32
|
+
'QUILL', 'quill',
|
|
33
|
+
'ECHO', 'echo',
|
|
34
|
+
'MURASAKI', 'murasaki',
|
|
35
|
+
'SCHEHERAZADE', 'scheherazade',
|
|
36
|
+
'MUSE', 'muse',
|
|
37
|
+
// Security / architecture / reasoning — also benefit from grounded context.
|
|
38
|
+
'FORGE', 'forge',
|
|
39
|
+
'PROMETHEUS', 'prometheus',
|
|
40
|
+
'SABER', 'saber',
|
|
41
|
+
'ADE', 'ade',
|
|
42
|
+
'ZERO', 'zero',
|
|
43
|
+
'SAURON', 'sauron',
|
|
44
|
+
'LOGOS', 'logos',
|
|
45
|
+
'VERITAS', 'veritas',
|
|
46
|
+
'LINK', 'link',
|
|
47
|
+
'NAVI', 'navi',
|
|
48
|
+
'EDI', 'edi',
|
|
49
|
+
// Polyglot needs source content to translate accurately.
|
|
50
|
+
'polyglot',
|
|
20
51
|
]);
|
|
21
52
|
|
|
22
53
|
// ── Complete Agent Registry ─────────────────────────────────────────────────
|
|
@@ -128,10 +159,142 @@ async function runFetchUrl(url) {
|
|
|
128
159
|
}
|
|
129
160
|
}
|
|
130
161
|
|
|
131
|
-
/**
|
|
162
|
+
/**
|
|
163
|
+
* Extract URLs from free-form text — robust matcher.
|
|
164
|
+
*
|
|
165
|
+
* Catches:
|
|
166
|
+
* - Fully-qualified URLs: http(s)://example.com/path
|
|
167
|
+
* - Bare domains with www: www.example.com
|
|
168
|
+
* - Bare domains without www: example.com, example.it, example.co.uk
|
|
169
|
+
* - Markdown-wrapped: [label](example.com) — the URL portion is matched
|
|
170
|
+
*
|
|
171
|
+
* Rejects:
|
|
172
|
+
* - File extensions misread as domains: image.png, doc.pdf, app.js
|
|
173
|
+
* - Single-word tokens without a dot
|
|
174
|
+
* - Email addresses (the @ rules out the domain match)
|
|
175
|
+
*
|
|
176
|
+
* Returns normalized https:// URLs, deduplicated, max 6.
|
|
177
|
+
*/
|
|
178
|
+
const FILE_EXT_BLOCKLIST = new Set([
|
|
179
|
+
'png','jpg','jpeg','gif','webp','svg','ico','bmp','tiff',
|
|
180
|
+
'pdf','doc','docx','xls','xlsx','ppt','pptx','csv','txt','zip','tar','gz','rar','7z',
|
|
181
|
+
'mp3','mp4','wav','ogg','mov','avi','mkv',
|
|
182
|
+
'js','css','json','xml','yaml','yml','md','html','htm',
|
|
183
|
+
]);
|
|
184
|
+
|
|
132
185
|
function extractUrlsFromText(text) {
|
|
133
|
-
|
|
134
|
-
|
|
186
|
+
if (!text) return [];
|
|
187
|
+
const t = String(text);
|
|
188
|
+
const found = new Set();
|
|
189
|
+
|
|
190
|
+
// Full URLs
|
|
191
|
+
for (const m of t.matchAll(/https?:\/\/[^\s,)<>"'`]+/gi)) {
|
|
192
|
+
const clean = m[0].replace(/[.,;:!?)\]]+$/, '');
|
|
193
|
+
found.add(clean);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Bare domains — TLD of 2..24 letters. Accepts 2-label (apple.com),
|
|
197
|
+
// 3-label (www.apple.com), and N-label domains.
|
|
198
|
+
const bareRe = /(?<![\/@\w])(?:www\.)?[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*\.([a-z]{2,24})\b(?:\/[^\s,()<>"'`]*)?/gi;
|
|
199
|
+
for (const m of t.matchAll(bareRe)) {
|
|
200
|
+
const candidate = m[0].replace(/[.,;:!?)\]]+$/, '');
|
|
201
|
+
if (candidate.startsWith('http')) continue;
|
|
202
|
+
const tld = (m[1] || '').toLowerCase();
|
|
203
|
+
if (FILE_EXT_BLOCKLIST.has(tld)) continue;
|
|
204
|
+
if (/^\d+(\.\d+)+$/.test(candidate)) continue;
|
|
205
|
+
found.add('https://' + candidate);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Dedup: if "https://x.com/path" and "https://www.x.com/path" both present,
|
|
209
|
+
// keep the longer (more specific) one only.
|
|
210
|
+
const list = [...found];
|
|
211
|
+
const filtered = list.filter(url => {
|
|
212
|
+
const stripped = url.replace(/^https?:\/\/(www\.)?/, '');
|
|
213
|
+
return !list.some(other =>
|
|
214
|
+
other !== url &&
|
|
215
|
+
other.replace(/^https?:\/\/(www\.)?/, '') === stripped &&
|
|
216
|
+
other.length > url.length,
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return filtered.slice(0, 6);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Automatic fallback: when the primary fetch fails, try to gather indirect
|
|
225
|
+
* intelligence about the domain via web search. Looks for:
|
|
226
|
+
* - site:domain.com listings (whatever Google has indexed)
|
|
227
|
+
* - "<domain> chi è" / "<domain> azienda settore" — public profiles, news
|
|
228
|
+
* - Wikipedia / corporate registries
|
|
229
|
+
*
|
|
230
|
+
* Returns up to 3 fetched pages with real content, formatted for ground truth.
|
|
231
|
+
* Returns null if nothing useful was found.
|
|
232
|
+
*/
|
|
233
|
+
async function gatherIndirectIntel(failedUrl, sse) {
|
|
234
|
+
try {
|
|
235
|
+
const parsed = new URL(failedUrl);
|
|
236
|
+
const domain = parsed.hostname.replace(/^www\./, '');
|
|
237
|
+
const queries = [
|
|
238
|
+
`site:${domain}`,
|
|
239
|
+
`"${domain}" azienda settore prodotti`,
|
|
240
|
+
`${domain} chi è cosa fa`,
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
if (sse) sse({ token: `[Fallback: ricerco info indirette su ${domain}...]` });
|
|
244
|
+
|
|
245
|
+
const allResults = [];
|
|
246
|
+
for (const q of queries) {
|
|
247
|
+
try {
|
|
248
|
+
const r = await webSearch(q, 5);
|
|
249
|
+
if (!r.error && Array.isArray(r.results)) {
|
|
250
|
+
for (const item of r.results) {
|
|
251
|
+
// Skip if the URL is the same failed domain (we already know it's down)
|
|
252
|
+
try {
|
|
253
|
+
const u = new URL(item.url);
|
|
254
|
+
if (u.hostname.replace(/^www\./, '') === domain) continue;
|
|
255
|
+
} catch { /* skip invalid URLs */ }
|
|
256
|
+
allResults.push({ ...item, fromQuery: q });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} catch { /* skip failed query */ }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (allResults.length === 0) return null;
|
|
263
|
+
|
|
264
|
+
// Dedupe by URL, take top 4, fetch them
|
|
265
|
+
const seen = new Set();
|
|
266
|
+
const unique = allResults.filter(r => {
|
|
267
|
+
if (seen.has(r.url)) return false;
|
|
268
|
+
seen.add(r.url);
|
|
269
|
+
return true;
|
|
270
|
+
}).slice(0, 4);
|
|
271
|
+
|
|
272
|
+
if (sse) sse({ token: `[Fallback: ${unique.length} fonti indirette trovate, recupero contenuto...]` });
|
|
273
|
+
|
|
274
|
+
const fetched = await Promise.all(
|
|
275
|
+
unique.map(async (r) => {
|
|
276
|
+
try {
|
|
277
|
+
const full = await fetchUrlRich(r.url, { maxChars: 4_000, timeout: 8_000 });
|
|
278
|
+
if (full.error) return null;
|
|
279
|
+
return {
|
|
280
|
+
url: r.url,
|
|
281
|
+
title: full.title || r.title || '',
|
|
282
|
+
snippet: r.snippet || '',
|
|
283
|
+
content: (full.body || '').slice(0, 3_000),
|
|
284
|
+
description: full.metadata?.description || '',
|
|
285
|
+
fromQuery: r.fromQuery,
|
|
286
|
+
};
|
|
287
|
+
} catch { return null; }
|
|
288
|
+
})
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const good = fetched.filter(Boolean);
|
|
292
|
+
if (good.length === 0) return null;
|
|
293
|
+
|
|
294
|
+
return good;
|
|
295
|
+
} catch {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
135
298
|
}
|
|
136
299
|
|
|
137
300
|
/**
|
|
@@ -373,17 +536,52 @@ ${sanitizeAgentOutput(body.proposalContext).slice(0, 12000)}
|
|
|
373
536
|
const content = (f.content || '').slice(0, 12_000);
|
|
374
537
|
return `### Content from ${f.url}\n${preamble}${content}`;
|
|
375
538
|
}).join('\n\n---\n\n');
|
|
376
|
-
webDataBlock =
|
|
539
|
+
webDataBlock = `
|
|
540
|
+
|
|
541
|
+
## ✅ DATI REALI RACCOLTI DAL SITO (GROUND TRUTH — usa SOLO questi)
|
|
542
|
+
|
|
543
|
+
Quanto segue è il contenuto effettivamente estratto dai siti specificati nel task.
|
|
544
|
+
È OBBLIGATORIO basare l'analisi su questi dati e SOLO su questi. È VIETATO:
|
|
545
|
+
- Inventare il settore, i prodotti, il target o il modello di business.
|
|
546
|
+
- Aggiungere "esempi tipici del settore" o "best practice generiche" senza riferirsi ai dati sopra.
|
|
547
|
+
- Scrivere "da verificare" o "supponiamo che" quando il dato è già presente nel blocco.
|
|
548
|
+
|
|
549
|
+
${okBlock}`;
|
|
377
550
|
}
|
|
378
551
|
|
|
552
|
+
// ── Automatic indirect-intel fallback ──
|
|
553
|
+
// Search the web for info ABOUT each failed domain (registries, news,
|
|
554
|
+
// Wikipedia, profiles). This gives the agent grounded data even when
|
|
555
|
+
// the primary site is down, instead of forcing it to invent a framework.
|
|
556
|
+
let recoveredAny = false;
|
|
379
557
|
if (failedFetches.length > 0) {
|
|
380
558
|
const failBlock = failedFetches.map(f => `- ${f.url} → ${f.code}: ${f.reason}`).join('\n');
|
|
381
|
-
webDataBlock += `\n\n##
|
|
559
|
+
webDataBlock += `\n\n## ⚠ FONTI PRIMARIE NON DISPONIBILI\n${failBlock}`;
|
|
382
560
|
sse({ data_unavailable: failedFetches.map(f => ({ url: f.url, code: f.code, reason: f.reason })) });
|
|
561
|
+
|
|
562
|
+
const indirectBlocks = [];
|
|
563
|
+
for (const f of failedFetches.slice(0, 3)) {
|
|
564
|
+
const intel = await gatherIndirectIntel(f.url, sse);
|
|
565
|
+
if (intel && intel.length > 0) {
|
|
566
|
+
recoveredAny = true;
|
|
567
|
+
const block = intel.map(i =>
|
|
568
|
+
`### ${i.title || '(senza titolo)'}\nURL: ${i.url}\nQuery: "${i.fromQuery}"\n${i.description ? `Descrizione: ${i.description}\n` : ''}\n${i.content}`,
|
|
569
|
+
).join('\n\n---\n\n');
|
|
570
|
+
indirectBlocks.push(`#### Info indirette su ${f.url}:\n\n${block}`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (indirectBlocks.length > 0) {
|
|
575
|
+
webDataBlock += `\n\n## 🔎 FONTI INDIRETTE (raccolte automaticamente perché le fonti primarie sono down)\n\nIl sito principale non è raggiungibile, ma queste fonti pubbliche parlano dello stesso dominio/azienda. **Usa queste informazioni come ground truth** — sono dati reali, non inventati.\n\n${indirectBlocks.join('\n\n---\n\n')}`;
|
|
576
|
+
sse({ token: '[Fallback: recupero indiretto riuscito, procedo con i dati alternativi.]' });
|
|
577
|
+
} else {
|
|
578
|
+
webDataBlock += `\n\nNessuna fonte indiretta utile trovata sul web per i domini sopra. Per le sezioni che riguardano questi URL, scrivi esplicitamente "Fonte non raggiungibile — analisi non eseguibile". NON sostituire con framework generici, esempi tipici, o supposizioni basate sul nome del dominio.`;
|
|
579
|
+
}
|
|
383
580
|
}
|
|
384
581
|
|
|
385
|
-
// Fail-fast: primary research
|
|
386
|
-
|
|
582
|
+
// Fail-fast: only abort if primary research has NO data AT ALL —
|
|
583
|
+
// neither primary fetches nor indirect intel recovered anything.
|
|
584
|
+
if (isPrimaryResearch && okFetches.length === 0 && !recoveredAny && taskUrls.length > 0) {
|
|
387
585
|
clearInterval(keepalive);
|
|
388
586
|
sse({
|
|
389
587
|
aborted: true,
|
|
@@ -574,13 +574,13 @@ ${m}
|
|
|
574
574
|
${h}
|
|
575
575
|
<div class="footer">NHA Studio · nothumanallowed.com · ${new Date().getFullYear()}</div>
|
|
576
576
|
</body></html>`}function it(e){try{localStorage.setItem(`nha_studio_sessions`,JSON.stringify(e.slice(0,20)))}catch{}}function at(){try{return JSON.parse(localStorage.getItem(`nha_studio_sessions`)??`[]`)}catch{return[]}}var ot=`nha_studio_active`;function st(e){try{sessionStorage.setItem(ot,JSON.stringify(e))}catch{}}function ct(){try{let e=sessionStorage.getItem(ot);return e?JSON.parse(e):null}catch{return null}}function lt(e){let t=[];return{clean:e.replace(/```json\s*\n?\s*\{[^`]*"action"\s*:\s*"canvas_render"[^`]*\}\s*```/g,e=>{try{let n=JSON.parse(e.replace(/```json\s*|\s*```/g,``).trim());n?.params?.html&&t.push(n.params.html)}catch{}return``}).replace(/\{"action"\s*:\s*"canvas_render"[^}]*"html"\s*:\s*"((?:[^"\\]|\\.)*)"/g,e=>{try{let n=JSON.parse(e+`}`);n?.params?.html&&t.push(n.params.html)}catch{try{let n=e.match(/"html"\s*:\s*"((?:[^"\\]|\\.)*?)"/);n&&t.push(JSON.parse(`"`+n[1]+`"`))}catch{}}return``}).trim(),canvasBlocks:t}}function ut(){return T.getState().apiBase}async function dt(e,t,n,r){let i=await fetch(ut()+e,{method:`POST`,headers:{"Content-Type":`application/json`,"x-nha-client":`web-ui`},body:JSON.stringify(t),signal:r});if(!i.ok||!i.body)throw Error(`HTTP ${i.status}`);let a=i.body.getReader(),o=new TextDecoder,s=``;for(;;){let{done:e,value:t}=await a.read();if(e)break;s+=o.decode(t,{stream:!0});let r=s.split(`
|
|
577
|
-
`);s=r.pop()??``;for(let e of r){if(!e.startsWith(`data: `))continue;let t=e.slice(6).trim();if(t===`[DONE]`)return;try{n(JSON.parse(t))}catch{}}}}function ft({label:e,value:t}){let n=Math.round(t*100),r=n>=60?`#22c55e`:n>=30?`#f59e0b`:`#ef4444`;return(0,k.jsxs)(`div`,{className:P.convRow,children:[(0,k.jsx)(`span`,{className:P.convLabel,children:e}),(0,k.jsx)(`div`,{className:P.convTrack,children:(0,k.jsx)(`div`,{className:P.convFill,style:{width:`${n}%`,background:r}})}),(0,k.jsxs)(`span`,{className:P.convPct,style:{color:r},children:[n,`%`]})]})}function pt({council:e}){let[t,n]=(0,_.useState)(null);if(e.skipped)return(0,k.jsxs)(`div`,{className:P.councilSkipped,children:[(0,k.jsx)(`span`,{className:P.councilSkipIcon,children:`🏛️`}),(0,k.jsxs)(`span`,{children:[`Council skipped — `,e.skipReason??`fewer than 2 eligible agents`]})]});let r=e.phase===`r2`?`⚡ Cross-reading & Refinement (Round 2)…`:e.phase===`r3`?`🏛️ HERALD Mediation (Round 3)…`:e.phase===`done`?`✅ Council Consensus Reached`:`🏛️ Council…`,i=e.converged?(0,k.jsx)(`span`,{className:P.councilConvergedBadge,children:`CONSENSUS`}):(0,k.jsx)(`span`,{className:P.councilDivergedBadge,children:`MEDIATED`});return(0,k.jsxs)(`div`,{className:P.councilPanel,children:[(0,k.jsxs)(`div`,{className:P.councilHeader,children:[(0,k.jsxs)(`div`,{className:P.councilHeaderLeft,children:[(0,k.jsx)(`span`,{className:P.councilIcon,children:`🏛️`}),(0,k.jsx)(`span`,{className:P.councilTitle,children:`Council · Geth Consensus`}),e.phase===`done`&&i]}),(0,k.jsx)(`div`,{className:P.councilHeaderRight,children:e.phase!==`idle`&&(0,k.jsx)(`span`,{className:`${P.councilPhaseTag} ${e.phase===`done`?P.councilPhaseDone:P.councilPhaseActive}`,children:r})})]}),(e.r1Convergence>0||e.r2Convergence>0)&&(0,k.jsxs)(`div`,{className:P.councilMetrics,children:[e.r1Convergence>0&&(0,k.jsx)(ft,{label:`Round 1`,value:e.r1Convergence}),e.r2Convergence>0&&(0,k.jsx)(ft,{label:`Round 2`,value:e.r2Convergence})]}),e.r2Nodes.length>0&&(0,k.jsxs)(`div`,{className:P.councilNodes,children:[(0,k.jsx)(`div`,{className:P.councilSectionTitle,children:`Refined Positions (Round 2)`}),e.r2Nodes.map(e=>(0,k.jsxs)(`div`,{className:P.councilNode,children:[(0,k.jsxs)(`button`,{className:P.councilNodeHeader,onClick:()=>n(t===e.agent?null:e.agent),children:[(0,k.jsx)(`span`,{className:P.councilNodeIcon,children:e.icon}),(0,k.jsx)(`span`,{className:P.councilNodeLabel,children:e.label}),(0,k.jsx)(`span`,{className:P.councilNodeToggle,children:t===e.agent?`▲`:`▼`})]}),t===e.agent&&(0,k.jsx)(`div`,{className:P.councilNodeBody,dangerouslySetInnerHTML:{__html:Se(e.output)}})]},e.agent))]}),e.synthesis&&(0,k.jsxs)(`div`,{className:P.councilSynthesis,children:[(0,k.jsxs)(`div`,{className:P.councilSynthesisHeader,children:[(0,k.jsx)(`span`,{className:P.heraldIcon,children:`📰`}),(0,k.jsx)(`span`,{className:P.heraldLabel,children:`HERALD — Final Synthesis`}),e.phase===`r3`&&(0,k.jsx)(`span`,{className:P.heraldStreaming,children:(0,k.jsx)(`span`,{className:`studio-cursor`})})]}),(0,k.jsx)(`div`,{className:P.councilSynthesisBody,dangerouslySetInnerHTML:{__html:Se(e.synthesis)+(e.phase===`r3`?`<span class="studio-cursor"></span>`:``)}})]})]})}function mt(){let e=T(e=>e.setView),t=ct(),[n,r]=(0,_.useState)(t?.task??``),[i,a]=(0,_.useState)(t?.nodes??[]),[o,s]=(0,_.useState)(t?.log??[]),[c,l]=(0,_.useState)(t?.result??``),[u,d]=(0,_.useState)(!1),[f,p]=(0,_.useState)(at),[m,h]=(0,_.useState)(!1),[g,v]=(0,_.useState)(null),[y,b]=(0,_.useState)({in:0,out:0}),[x,S]=(0,_.useState)(t?.canvasHtml??``),[C,w]=(0,_.useState)(t?.council??null),[ee,te]=(0,_.useState)(!1),[ne,E]=(0,_.useState)([]),[D,re]=(0,_.useState)(!1),[ie,O]=(0,_.useState)(`canvas`),[ae,A]=(0,_.useState)(``),[oe,se]=(0,_.useState)(null),ce=(0,_.useRef)(null),le=(0,_.useRef)(null),ue=(0,_.useRef)(null),de=(0,_.useRef)(t?.canvasHtml??``),j=(0,_.useRef)(t?.council??null),M=(0,_.useRef)([]),fe=(0,_.useRef)(null);(0,_.useEffect)(()=>{st({task:n,nodes:i,log:o,result:c,canvasHtml:x,council:C})},[n,i,o,c,x,C]),(0,_.useEffect)(()=>{le.current&&(le.current.scrollTop=le.current.scrollHeight)},[o]),(0,_.useEffect)(()=>{u&&ue.current&&(ue.current.scrollTop=ue.current.scrollHeight)},[i,u]),(0,_.useEffect)(()=>{if(!m)return;let e=e=>{fe.current&&!fe.current.contains(e.target)&&h(!1)};return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[m]);let pe=(0,_.useCallback)((e,t,n,r=`agent`)=>{let i=new Date().toLocaleTimeString(`en`,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1});s(a=>[...a,{agent:e,icon:t,text:n,time:i,type:r}])},[]),me=e=>{if(!e)return;let t=e.name.toLowerCase();if(!t.endsWith(`.pdf`)&&!/\.(png|jpe?g|gif|webp)$/i.test(t)){alert(`Supported: PDF, PNG, JPG, GIF, WEBP`);return}let n=new FileReader;n.onload=t=>v({name:e.name,data:t.target?.result}),n.readAsDataURL(e)},he=async()=>{if(!n.trim()||u)return;d(!0),a([]),s([]),l(``),w(null),j.current=null,b({in:0,out:0}),de.current=``,S(``);let e=new AbortController;ce.current=e;try{let t=[
|
|
578
|
-
`)}`,`
|
|
577
|
+
`);s=r.pop()??``;for(let e of r){if(!e.startsWith(`data: `))continue;let t=e.slice(6).trim();if(t===`[DONE]`)return;try{n(JSON.parse(t))}catch{}}}}function ft({label:e,value:t}){let n=Math.round(t*100),r=n>=60?`#22c55e`:n>=30?`#f59e0b`:`#ef4444`;return(0,k.jsxs)(`div`,{className:P.convRow,children:[(0,k.jsx)(`span`,{className:P.convLabel,children:e}),(0,k.jsx)(`div`,{className:P.convTrack,children:(0,k.jsx)(`div`,{className:P.convFill,style:{width:`${n}%`,background:r}})}),(0,k.jsxs)(`span`,{className:P.convPct,style:{color:r},children:[n,`%`]})]})}function pt({council:e}){let[t,n]=(0,_.useState)(null);if(e.skipped)return(0,k.jsxs)(`div`,{className:P.councilSkipped,children:[(0,k.jsx)(`span`,{className:P.councilSkipIcon,children:`🏛️`}),(0,k.jsxs)(`span`,{children:[`Council skipped — `,e.skipReason??`fewer than 2 eligible agents`]})]});let r=e.phase===`r2`?`⚡ Cross-reading & Refinement (Round 2)…`:e.phase===`r3`?`🏛️ HERALD Mediation (Round 3)…`:e.phase===`done`?`✅ Council Consensus Reached`:`🏛️ Council…`,i=e.converged?(0,k.jsx)(`span`,{className:P.councilConvergedBadge,children:`CONSENSUS`}):(0,k.jsx)(`span`,{className:P.councilDivergedBadge,children:`MEDIATED`});return(0,k.jsxs)(`div`,{className:P.councilPanel,children:[(0,k.jsxs)(`div`,{className:P.councilHeader,children:[(0,k.jsxs)(`div`,{className:P.councilHeaderLeft,children:[(0,k.jsx)(`span`,{className:P.councilIcon,children:`🏛️`}),(0,k.jsx)(`span`,{className:P.councilTitle,children:`Council · Geth Consensus`}),e.phase===`done`&&i]}),(0,k.jsx)(`div`,{className:P.councilHeaderRight,children:e.phase!==`idle`&&(0,k.jsx)(`span`,{className:`${P.councilPhaseTag} ${e.phase===`done`?P.councilPhaseDone:P.councilPhaseActive}`,children:r})})]}),(e.r1Convergence>0||e.r2Convergence>0)&&(0,k.jsxs)(`div`,{className:P.councilMetrics,children:[e.r1Convergence>0&&(0,k.jsx)(ft,{label:`Round 1`,value:e.r1Convergence}),e.r2Convergence>0&&(0,k.jsx)(ft,{label:`Round 2`,value:e.r2Convergence})]}),e.r2Nodes.length>0&&(0,k.jsxs)(`div`,{className:P.councilNodes,children:[(0,k.jsx)(`div`,{className:P.councilSectionTitle,children:`Refined Positions (Round 2)`}),e.r2Nodes.map(e=>(0,k.jsxs)(`div`,{className:P.councilNode,children:[(0,k.jsxs)(`button`,{className:P.councilNodeHeader,onClick:()=>n(t===e.agent?null:e.agent),children:[(0,k.jsx)(`span`,{className:P.councilNodeIcon,children:e.icon}),(0,k.jsx)(`span`,{className:P.councilNodeLabel,children:e.label}),(0,k.jsx)(`span`,{className:P.councilNodeToggle,children:t===e.agent?`▲`:`▼`})]}),t===e.agent&&(0,k.jsx)(`div`,{className:P.councilNodeBody,dangerouslySetInnerHTML:{__html:Se(e.output)}})]},e.agent))]}),e.synthesis&&(0,k.jsxs)(`div`,{className:P.councilSynthesis,children:[(0,k.jsxs)(`div`,{className:P.councilSynthesisHeader,children:[(0,k.jsx)(`span`,{className:P.heraldIcon,children:`📰`}),(0,k.jsx)(`span`,{className:P.heraldLabel,children:`HERALD — Final Synthesis`}),e.phase===`r3`&&(0,k.jsx)(`span`,{className:P.heraldStreaming,children:(0,k.jsx)(`span`,{className:`studio-cursor`})})]}),(0,k.jsx)(`div`,{className:P.councilSynthesisBody,dangerouslySetInnerHTML:{__html:Se(e.synthesis)+(e.phase===`r3`?`<span class="studio-cursor"></span>`:``)}})]})]})}function mt(){let e=T(e=>e.setView),t=ct(),[n,r]=(0,_.useState)(t?.task??``),[i,a]=(0,_.useState)(t?.nodes??[]),[o,s]=(0,_.useState)(t?.log??[]),[c,l]=(0,_.useState)(t?.result??``),[u,d]=(0,_.useState)(!1),[f,p]=(0,_.useState)(at),[m,h]=(0,_.useState)(!1),[g,v]=(0,_.useState)(null),[y,b]=(0,_.useState)({in:0,out:0}),[x,S]=(0,_.useState)(t?.canvasHtml??``),[C,w]=(0,_.useState)(t?.council??null),[ee,te]=(0,_.useState)(!1),[ne,E]=(0,_.useState)([]),[D,re]=(0,_.useState)(!1),[ie,O]=(0,_.useState)(`canvas`),[ae,A]=(0,_.useState)(``),[oe,se]=(0,_.useState)(null),ce=(0,_.useRef)(null),le=(0,_.useRef)(null),ue=(0,_.useRef)(null),de=(0,_.useRef)(t?.canvasHtml??``),j=(0,_.useRef)(t?.council??null),M=(0,_.useRef)([]),fe=(0,_.useRef)(null);(0,_.useEffect)(()=>{st({task:n,nodes:i,log:o,result:c,canvasHtml:x,council:C})},[n,i,o,c,x,C]),(0,_.useEffect)(()=>{le.current&&(le.current.scrollTop=le.current.scrollHeight)},[o]),(0,_.useEffect)(()=>{u&&ue.current&&(ue.current.scrollTop=ue.current.scrollHeight)},[i,u]),(0,_.useEffect)(()=>{if(!m)return;let e=e=>{fe.current&&!fe.current.contains(e.target)&&h(!1)};return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[m]);let pe=(0,_.useCallback)((e,t,n,r=`agent`)=>{let i=new Date().toLocaleTimeString(`en`,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1});s(a=>[...a,{agent:e,icon:t,text:n,time:i,type:r}])},[]),me=e=>{if(!e)return;let t=e.name.toLowerCase();if(!t.endsWith(`.pdf`)&&!/\.(png|jpe?g|gif|webp)$/i.test(t)){alert(`Supported: PDF, PNG, JPG, GIF, WEBP`);return}let n=new FileReader;n.onload=t=>v({name:e.name,data:t.target?.result}),n.readAsDataURL(e)},he=async()=>{if(!n.trim()||u)return;d(!0),a([]),s([]),l(``),w(null),j.current=null,b({in:0,out:0}),de.current=``,S(``);let e=new AbortController;ce.current=e;try{let t=new Set([`png`,`jpg`,`jpeg`,`gif`,`webp`,`svg`,`ico`,`pdf`,`doc`,`docx`,`xls`,`xlsx`,`zip`,`mp3`,`mp4`,`js`,`css`,`json`,`xml`,`html`,`htm`,`md`,`txt`]),r=new Set;for(let e of n.matchAll(/https?:\/\/[^\s,)<>"'`]+/gi))r.add(e[0].replace(/[.,;:!?)\]]+$/,``));for(let e of n.matchAll(/(?<![\/@\w])(?:www\.)?[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*\.([a-z]{2,24})\b(?:\/[^\s,()<>"'`]*)?/gi)){let n=e[0].replace(/[.,;:!?)\]]+$/,``);if(n.startsWith(`http`))continue;let i=(e[1]||``).toLowerCase();t.has(i)||/^\d+(\.\d+)+$/.test(n)||r.add(`https://`+n)}let i=[...r];if(i.length>0){pe(`Studio`,`🩺`,`Smoke-test: ${i.length} URL — verifico raggiungibilità...`,`system`);try{let t=await fetch(ut()+`/api/studio/smoke-test`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({urls:i}),signal:e.signal});if(t.ok){let{probes:e}=await t.json(),n=e.filter(e=>!e.ok);n.length>0?pe(`Studio`,`⚠`,`URL primari non raggiungibili — proverò fonti indirette via web_search:\n${n.map(e=>`• ${e.url} → ${e.status||`NET`} ${e.reason}`).join(`
|
|
578
|
+
`)}`,`system`):pe(`Studio`,`✅`,`Smoke-test: tutti gli URL rispondono.`,`system`)}}catch(e){pe(`Studio`,`⚠`,`Smoke-test non eseguibile: ${e.message}. Procedo comunque — il backend gestirà i fetch falliti con fallback indiretto.`,`system`)}}pe(`Studio`,`🎬`,`Planning workflow…`,`system`);let o={task:n.trim()};g&&(o[g.name.endsWith(`.pdf`)?`pdfBase64`:`imageBase64`]=g.data,g.name.endsWith(`.pdf`)&&(o.pdfName=g.name));let s=[];try{let t=await fetch(ut()+`/api/studio/plan`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(o),signal:e.signal});if(t.ok){let e=await t.json();s=e.steps??e.nodes??[]}}catch{}s.length||(s=[{icon:`🤖`,agent:`general`,label:`Assistant`,status:`waiting`,output:``}]);let c=s.map(e=>({...e,status:`waiting`,output:``}));a(c),pe(`Studio`,`📋`,`${c.length} agents planned: ${c.map(e=>e.label).join(` → `)}`,`system`);let u=``,d=!1,f=[...c],m=[],h=e=>{let t=m.filter(t=>t.agent!==e);return t.length===0?``:t.map(e=>`### ${e.icon} ${e.label} (${e.agent}) ha proposto:\n${e.output.slice(0,6e3)}`).join(`
|
|
579
579
|
|
|
580
580
|
---
|
|
581
581
|
|
|
582
|
-
`)},_=e=>e.replace(/<think>[\s\S]*?<\/think>/gi,``).replace(/<tool[^>]*>[\s\S]*?<\/tool>/gi,``).replace(/<scratch[^>]*>[\s\S]*?<\/scratch>/gi,``).replace(/\[(?:Searching|Fetching|GET|POST|Raccolta dati web)[^\]]*\]\s*/gi,``).trim(),v=f.findIndex(e=>/WebSearch|Travel|HERALD|MERCURY|ATHENA|ORACLE|CASSANDRA|TEMPEST|EPICURE|CARTOGRAPHER|DataAnalyst/i.test(e.agent));for(let t=0;t<f.length&&!(e.signal.aborted||
|
|
583
|
-
`)&&!n.includes(`*`)&&!n.includes(`#`)?(f[t]={...f[t],statusLine:n.replace(/^\[|\]\s*$/g,``)},a([...f])):(f[t]={...f[t],output:(f[t].output||``)+n,statusLine:void 0},o=f[t].output,a([...f])))},e.signal),
|
|
582
|
+
`)},_=e=>e.replace(/<think>[\s\S]*?<\/think>/gi,``).replace(/<tool[^>]*>[\s\S]*?<\/tool>/gi,``).replace(/<scratch[^>]*>[\s\S]*?<\/scratch>/gi,``).replace(/\[(?:Searching|Fetching|GET|POST|Raccolta dati web)[^\]]*\]\s*/gi,``).trim(),v=f.findIndex(e=>/WebSearch|Travel|HERALD|MERCURY|ATHENA|ORACLE|CASSANDRA|TEMPEST|EPICURE|CARTOGRAPHER|DataAnalyst/i.test(e.agent));for(let t=0;t<f.length&&!(e.signal.aborted||d);t++){let r=f[t],i=t===v||v===-1&&t===0;f[t]={...r,status:`running`,output:``,isPrimaryResearch:i},a([...f]),pe(r.label,r.icon,`Running ${r.label}…`,`agent`);let o=``,s,c=null,l=h(r.agent);try{if(await dt(`/api/studio/run`,{stepIdx:t,agent:r.agent,task:n.trim(),stepDef:{prompt:r.reason,isPrimaryResearch:i},isPrimaryResearch:i,context:u.length>12e4?u.slice(-12e4):u,proposalContext:l||void 0,...t===0&&g?g.name.endsWith(`.pdf`)?{pdfBase64:g.data,pdfName:g.name}:{imageBase64:g.data}:{}},e=>{if(e.aborted){c={reason:String(e.reason??`UNKNOWN`),message:String(e.message??`Pipeline interrotta dal backend.`)};return}if(e.data_unavailable){let t=e.data_unavailable;for(let e of t)pe(r.label,`⚠`,`Fonte non raggiungibile: ${e.url} → ${e.code} ${e.reason}`,`error`)}e.done&&e.dataQuality&&(s=e.dataQuality);let n=e.token??e.content??``;n&&(n.startsWith(`[`)&&n.trimEnd().endsWith(`]`)&&!n.includes(`
|
|
583
|
+
`)&&!n.includes(`*`)&&!n.includes(`#`)?(f[t]={...f[t],statusLine:n.replace(/^\[|\]\s*$/g,``)},a([...f])):(f[t]={...f[t],output:(f[t].output||``)+n,statusLine:void 0},o=f[t].output,a([...f])))},e.signal),c){let e=c;f[t]={...f[t],status:`aborted`,output:e.message},a([...f]),pe(r.label,`⛔`,`${e.reason}: ${e.message}`,`error`),i&&(pe(`Studio`,`⛔`,`Primary research fallita — pipeline interrotta per evitare contenuto inventato.`,`error`),d=!0);continue}let{clean:p,canvasBlocks:h}=lt(o),v=p||`(no output)`;f[t]={...f[t],status:`done`,output:v,dataQuality:s},a([...f]);let y=_(v);u+=`\n\n[${r.label}]:\n${y}`,m.push({agent:r.agent,label:r.label,icon:r.icon,output:y});let b=s?.label?` · quality:${s.label}(${Math.round(s.score*100)}%)`:``;if(pe(r.label,r.icon,`Done (${v.length} chars)${b}`,`agent`),i&&s&&s.label===`low`&&s.score<.3&&(pe(`Studio`,`⛔`,`Primary research score ${Math.round(s.score*100)}% — pipeline interrotta. Le sezioni a valle non riceveranno dati attendibili.`,`error`),d=!0),h.length>0){let e=h[h.length-1];de.current=e,S(e),re(!0),O(`canvas`),pe(r.label,r.icon,`Canvas render captured`,`system`)}}catch(n){if(e.signal.aborted)break;f[t]={...f[t],status:`error`,output:n.message??`Error`},a([...f]),pe(r.label,r.icon,`Error: ${n.message}`,`error`)}}let y=f.filter(e=>e.status===`done`&&e.output&&e.output!==`(no output)`&&![`CanvasAgent`,`GitHubAgent`,`EmailAgent`,`CalendarAgent`].includes(e.agent)&&(!e.dataQuality||e.dataQuality.label!==`low`));!d&&y.length>=2?(M.current=y,E(y),pe(`Consiglio`,`🏛️`,`${y.length} agenti — avvio deliberazione automatica (cross-reading + sintesi)`,`system`),await _e()):d?pe(`Consiglio`,`ℹ️`,`Deliberazione saltata — pipeline interrotta prima del completamento`,`system`):pe(`Studio`,`ℹ️`,`Deliberazione saltata — meno di 2 agenti specialisti con dati attendibili`,`system`);let b=j.current,x=f.filter(e=>e.output&&e.output!==`(no output)`).map(e=>`## ${e.icon} ${e.label}\n\n${e.output}`).join(`
|
|
584
584
|
|
|
585
585
|
---
|
|
586
586
|
|
package/src/ui-dist/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
|
9
9
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
10
10
|
<title>NHA — NotHumanAllowed</title>
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-1ka48YcO.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-DnJMrYkq.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
package/src/updater.mjs
CHANGED
|
@@ -87,17 +87,95 @@ function compareSemver(a, b) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
*
|
|
90
|
+
* Detect whether the current `nha` binary is reachable from multiple PATH
|
|
91
|
+
* locations. This is the classic "I ran npm install -g but nothing changed"
|
|
92
|
+
* trap on macOS where a system-wide /usr/local/bin/nha shadows a user-space
|
|
93
|
+
* ~/.npm-global/bin/nha (or vice versa).
|
|
94
|
+
*/
|
|
95
|
+
async function detectDuplicateInstall() {
|
|
96
|
+
try {
|
|
97
|
+
const { execSync } = await import('child_process');
|
|
98
|
+
const out = execSync('which -a nha 2>/dev/null', { encoding: 'utf-8' });
|
|
99
|
+
const paths = out.split('\n').map(s => s.trim()).filter(Boolean);
|
|
100
|
+
return paths.length > 1 ? paths : null;
|
|
101
|
+
} catch { return null; }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Run `npm install -g nothumanallowed@latest` from inside the CLI itself.
|
|
106
|
+
* Uses --prefer-online to defeat npm's metadata cache, which is the usual
|
|
107
|
+
* culprit when the user already ran "npm install -g nothumanallowed" but got
|
|
108
|
+
* an older version because the manifest in cache was stale.
|
|
109
|
+
*/
|
|
110
|
+
async function npmSelfInstall(targetVersion) {
|
|
111
|
+
const { spawn } = await import('child_process');
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
info(`Installing nothumanallowed@${targetVersion} via npm (this may take 10-30s)...`);
|
|
114
|
+
const args = [
|
|
115
|
+
'install', '-g', `nothumanallowed@${targetVersion}`,
|
|
116
|
+
'--registry=https://registry.npmjs.org/',
|
|
117
|
+
'--prefer-online',
|
|
118
|
+
'--no-fund',
|
|
119
|
+
'--no-audit',
|
|
120
|
+
];
|
|
121
|
+
const child = spawn('npm', args, { stdio: 'inherit' });
|
|
122
|
+
child.on('exit', (code) => resolve(code === 0));
|
|
123
|
+
child.on('error', (err) => {
|
|
124
|
+
warn(`npm spawn failed: ${err.message}`);
|
|
125
|
+
resolve(false);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Full update: re-download core files + agents + self-upgrade the npm package.
|
|
91
132
|
*/
|
|
92
133
|
export async function runUpdate() {
|
|
93
134
|
info('Checking for updates...');
|
|
94
135
|
|
|
136
|
+
// ── npm package self-update ────────────────────────────────────────────
|
|
137
|
+
// Done FIRST so the freshly installed version applies on the next invocation.
|
|
138
|
+
// We bypass npm's metadata cache (--prefer-online) because that's the
|
|
139
|
+
// single most common reason "I just installed and it's still old".
|
|
140
|
+
let npmUpdated = false;
|
|
141
|
+
const npmCheck = await checkNpmVersion();
|
|
142
|
+
if (npmCheck?.updateAvailable) {
|
|
143
|
+
info(`npm package: ${npmCheck.current} → ${npmCheck.latest}`);
|
|
144
|
+
// Clean the local manifest cache first — defeats the stale-cache trap.
|
|
145
|
+
try {
|
|
146
|
+
const { execSync } = await import('child_process');
|
|
147
|
+
execSync('npm cache clean --force', { stdio: 'pipe' });
|
|
148
|
+
} catch { /* non-fatal */ }
|
|
149
|
+
|
|
150
|
+
const ok2 = await npmSelfInstall(npmCheck.latest);
|
|
151
|
+
if (ok2) {
|
|
152
|
+
ok(`npm package upgraded to ${npmCheck.latest}`);
|
|
153
|
+
npmUpdated = true;
|
|
154
|
+
|
|
155
|
+
// Detect duplicate global installs — common on macOS.
|
|
156
|
+
const dups = await detectDuplicateInstall();
|
|
157
|
+
if (dups && dups.length > 1) {
|
|
158
|
+
warn('Multiple nha installations detected on PATH:');
|
|
159
|
+
for (const p of dups) console.log(` ${p}`);
|
|
160
|
+
warn('Only the FIRST in PATH is what your shell will run. If the version still');
|
|
161
|
+
warn('appears unchanged, remove the older one (e.g. `sudo rm /usr/local/bin/nha`)');
|
|
162
|
+
warn('or reorder your PATH so the newer install is found first.');
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
warn('npm install failed. Run manually:');
|
|
166
|
+
console.log(` npm cache clean --force && npm install -g nothumanallowed@${npmCheck.latest} --prefer-online`);
|
|
167
|
+
}
|
|
168
|
+
} else if (npmCheck) {
|
|
169
|
+
ok(`npm package nothumanallowed@${npmCheck.current} (up to date)`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Agents + Legion + PIF (downloaded from website, not npm) ───────────
|
|
95
173
|
const res = await fetch(`${BASE_URL}/versions.json`, {
|
|
96
174
|
signal: AbortSignal.timeout(15000),
|
|
97
175
|
headers: { 'User-Agent': 'nha-cli/1.0.0' },
|
|
98
176
|
});
|
|
99
177
|
if (!res.ok) {
|
|
100
|
-
warn('Could not reach nothumanallowed.com');
|
|
178
|
+
warn('Could not reach nothumanallowed.com for agent updates.');
|
|
101
179
|
return;
|
|
102
180
|
}
|
|
103
181
|
|
|
@@ -112,7 +190,7 @@ export async function runUpdate() {
|
|
|
112
190
|
const pifCurrent = local['pif']?.latest ?? '?';
|
|
113
191
|
const pifLatest = remote['pif']?.latest ?? '?';
|
|
114
192
|
|
|
115
|
-
let updated =
|
|
193
|
+
let updated = npmUpdated;
|
|
116
194
|
|
|
117
195
|
// Update Legion
|
|
118
196
|
if (legionCurrent !== legionLatest) {
|