nothumanallowed 14.1.79 → 14.1.80

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": "14.1.79",
3
+ "version": "14.1.80",
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 = '14.1.79';
8
+ export const VERSION = '14.1.80';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -851,56 +851,112 @@ ${prevContext ? `Recent files generated (for consistency):\n${prevContext}\n\n`
851
851
  let syntaxError = null;
852
852
  const fileTokensIn = countTokens(fileSys) + countTokens(filePrompt);
853
853
 
854
- try {
855
- // Collect full LLM output first, then stream it to the client in small chunks.
856
- // This gives real word-by-word animation even with non-streaming providers (NHA/Liara).
857
- let rawOutput = '';
858
- await callLLMStream(config, fileSys, filePrompt, (chunk) => {
859
- rawOutput += chunk;
860
- }, { max_tokens: maxTokens });
861
-
862
- // Strip markdown fences if LLM wrapped the output
863
- rawOutput = rawOutput
864
- .replace(/^```[\w]*\n/, '').replace(/\n```$/, '')
865
- .replace(/^```[\w]*\r\n/, '').replace(/\r\n```$/, '').trim();
866
-
867
- // Continuation: if the file appears truncated, ask the model to continue
868
- if (isFileTruncated(rawOutput, fileSpec.name)) {
869
- const contPrompt = `Continue writing the file ${fileSpec.name} exactly from where it was cut off. Output ONLY the continuation (no repetition of what was already written, no explanation):
870
-
871
- ${rawOutput.slice(-800)}`;
872
- let continuation = '';
873
- await callLLMStream(config, fileSys, contPrompt, (chunk) => {
874
- continuation += chunk;
875
- }, { max_tokens: Math.min(maxTokens, 4096) });
876
- continuation = continuation
877
- .replace(/^```[\w]*\n/, '').replace(/\n```$/, '').trim();
878
- rawOutput = rawOutput + '\n' + continuation;
879
- }
854
+ // Retry loop — up to 2 attempts per file
855
+ for (let attempt = 0; attempt < 2; attempt++) {
856
+ try {
857
+ // Stream chunks to browser in real-time during generation
858
+ let rawOutput = '';
859
+ await callLLMStream(config, fileSys, filePrompt, (chunk) => {
860
+ rawOutput += chunk;
861
+ // Real-time streaming to browser
862
+ emit({ type: 'file_chunk', name: fileSpec.name, chunk, fi: fi + 1, total: filePlan.length });
863
+ }, { max_tokens: maxTokens });
864
+
865
+ // Strip markdown fences if LLM wrapped the output
866
+ rawOutput = rawOutput
867
+ .replace(/^```[\w]*\n/, '').replace(/\n```$/, '')
868
+ .replace(/^```[\w]*\r\n/, '').replace(/\r\n```$/, '').trim();
869
+
870
+ // Continuation loop — up to 3 rounds if file appears truncated
871
+ for (let contRound = 0; contRound < 3 && isFileTruncated(rawOutput, fileSpec.name); contRound++) {
872
+ emit({ type: 'file_chunk', name: fileSpec.name, chunk: `\n/* ... continuing (${contRound + 1}) ... */\n`, fi: fi + 1, total: filePlan.length });
873
+ const contPrompt = `The file ${fileSpec.name} was truncated. Continue EXACTLY from the last line. Output ONLY the remaining code (no repetition, no explanation):
874
+
875
+ LAST 600 CHARS OF WHAT WAS WRITTEN:
876
+ ${rawOutput.slice(-600)}
877
+
878
+ Continue from here:`;
879
+ let continuation = '';
880
+ await callLLMStream(config, fileSys, contPrompt, (chunk) => {
881
+ continuation += chunk;
882
+ emit({ type: 'file_chunk', name: fileSpec.name, chunk, fi: fi + 1, total: filePlan.length });
883
+ }, { max_tokens: Math.min(maxTokens, 8192) });
884
+ continuation = continuation
885
+ .replace(/^```[\w]*\n/, '').replace(/\n```$/, '').trim();
886
+ if (continuation.length > 20) {
887
+ rawOutput = rawOutput + '\n' + continuation;
888
+ } else {
889
+ break; // continuation too short — likely done
890
+ }
891
+ }
880
892
 
881
- fileContent = rawOutput;
893
+ fileContent = rawOutput;
894
+ syntaxError = null;
882
895
 
883
- // Stream the final content to the browser in small chunks for animation
884
- await emitTextAsStream(fileContent, (chunk) => {
885
- emit({ type: 'file_chunk', name: fileSpec.name, chunk, fi: fi + 1, total: filePlan.length });
886
- });
896
+ const fileTokensOut = countTokens(fileContent);
897
+ totalTokensIn += fileTokensIn;
898
+ totalTokensOut += fileTokensOut;
887
899
 
888
- const fileTokensOut = countTokens(fileContent);
889
- totalTokensIn += fileTokensIn;
890
- totalTokensOut += fileTokensOut;
900
+ // Quick syntax check for JS/TS files
901
+ if (fileSpec.name.endsWith('.js') || fileSpec.name.endsWith('.mjs')) {
902
+ try { new Function(fileContent); } catch (e) { syntaxError = e.message.replace(/\n.*/s, ''); }
903
+ }
904
+ // HTML completeness check
905
+ if (fileSpec.name.endsWith('.html') && !fileContent.includes('</html>')) {
906
+ syntaxError = 'Missing </html> closing tag';
907
+ }
908
+
909
+ const abs = path.join(projectDir, fileSpec.name);
910
+ ensureDir(path.dirname(abs));
911
+ fs.writeFileSync(abs, fileContent, 'utf-8');
912
+ generatedFiles.push({ name: fileSpec.name, content: fileContent });
913
+ emit({ type: 'file_done', name: fileSpec.name, fi: fi + 1, total: filePlan.length, syntaxError, tokOut: fileTokensOut, cumTokIn: totalTokensIn, cumTokOut: totalTokensOut });
914
+ break; // success — exit retry loop
891
915
 
892
- // Quick syntax check for JS/TS files
893
- if (fileSpec.name.endsWith('.js') || fileSpec.name.endsWith('.mjs')) {
894
- try { new Function(fileContent); } catch (e) { syntaxError = e.message.replace(/\n.*/s, ''); }
916
+ } catch (e) {
917
+ if (attempt === 0) {
918
+ emit({ type: 'file_chunk', name: fileSpec.name, chunk: `\n/* Retry: ${e.message.slice(0, 100)} */\n`, fi: fi + 1, total: filePlan.length });
919
+ continue; // retry once
920
+ }
921
+ emit({ type: 'file_error', name: fileSpec.name, error: e.message });
895
922
  }
923
+ }
924
+ }
896
925
 
897
- const abs = path.join(projectDir, fileSpec.name);
898
- ensureDir(path.dirname(abs));
899
- fs.writeFileSync(abs, fileContent, 'utf-8');
900
- generatedFiles.push({ name: fileSpec.name, content: fileContent });
901
- emit({ type: 'file_done', name: fileSpec.name, fi: fi + 1, total: filePlan.length, syntaxError, tokOut: fileTokensOut, cumTokIn: totalTokensIn, cumTokOut: totalTokensOut });
902
- } catch (e) {
903
- emit({ type: 'file_error', name: fileSpec.name, error: e.message });
926
+ // ── Post-generation integrity check ──────────────────────────────────────
927
+ const brokenFiles = generatedFiles.filter((f) => {
928
+ if (!f.content || f.content.length < 20) return true;
929
+ if (f.name.endsWith('.html') && !f.content.includes('</html>')) return true;
930
+ if ((f.name.endsWith('.js') || f.name.endsWith('.mjs'))) {
931
+ try { new Function(f.content); } catch { return true; }
932
+ }
933
+ if (f.name.endsWith('.json')) {
934
+ try { JSON.parse(f.content); } catch { return true; }
935
+ }
936
+ return isFileTruncated(f.content, f.name);
937
+ });
938
+
939
+ if (brokenFiles.length > 0 && brokenFiles.length <= 10) {
940
+ emit({ type: 'phase', phase: 'autofix', msg: `Post-generation fix: ${brokenFiles.length} file(s) need repair...` });
941
+ for (const broken of brokenFiles) {
942
+ try {
943
+ emit({ type: 'status', msg: `Regenerating ${broken.name}...` });
944
+ let fixedContent = '';
945
+ const fixPrompt = `Regenerate this file COMPLETELY. It was truncated or has errors.\n\nFile: ${broken.name}\nProject: ${projectName}\nDescription: ${description}\nFull file list: ${allFileNames}\n\nOutput the COMPLETE file content only, no explanation.`;
946
+ await callLLMStream(config, fileSys, fixPrompt, (chunk) => {
947
+ fixedContent += chunk;
948
+ }, { max_tokens: 16384 });
949
+ fixedContent = fixedContent
950
+ .replace(/^```[\w]*\n/, '').replace(/\n```$/, '').trim();
951
+ if (fixedContent.length > broken.content.length) {
952
+ broken.content = fixedContent;
953
+ const abs = path.join(projectDir, broken.name);
954
+ fs.writeFileSync(abs, fixedContent, 'utf-8');
955
+ emit({ type: 'status', msg: `Fixed ${broken.name} (${fixedContent.length} chars)` });
956
+ }
957
+ } catch (e) {
958
+ emit({ type: 'status', msg: `Could not fix ${broken.name}: ${e.message.slice(0, 100)}` });
959
+ }
904
960
  }
905
961
  }
906
962
 
@@ -678,7 +678,7 @@ FRONTEND:
678
678
  - apply.html: Application modal/page — job summary header, form (Full Name / Email / Phone / LinkedIn URL / Portfolio URL / Cover Letter textarea with character count / Resume paste textarea), Submit button with loading state, success page with application reference number
679
679
  - public/css/main.css: Modern job board design — tag/badge system (Remote=blue, Onsite=green, Urgent=red), salary range display, company logo placeholder styles, filter sidebar collapse on mobile, job card hover effects, application form validation styles`}],ln=[{key:`auth`,label:`Auth (register/login/JWT)`,icon:`🔒`},{key:`cookieBanner`,label:`GDPR Cookie Banner`,icon:`🍪`},{key:`securityMiddleware`,label:`Security Middleware`,icon:`🛡️`},{key:`emailVerification`,label:`Email Verification`,icon:`✉️`}],un={js:`📄`,ts:`📄`,css:`🎨`,html:`🌐`,json:`📋`,md:`📑`,sql:`🗂`,env:`🔐`,conf:`⚙`,lock:`🔒`};function dn(e){return un[e.split(`.`).pop()?.toLowerCase()??``]??`📄`}function fn(e){let t=new TextEncoder().encode(e).length;return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(2)} MB`}function pn(e){return e===`memory`?`🧠`:e===`provider`?`🤖`:e===`log`?`📄`:`📋`}function mn(){let e=P(),[t,n]=(0,_.useState)(`new`),[r,i]=(0,_.useState)(`files`),[a,o]=(0,_.useState)(``),[s,c]=(0,_.useState)(``),[l,u]=(0,_.useState)({auth:!0,cookieBanner:!0,securityMiddleware:!0,emailVerification:!0}),[d,f]=(0,_.useState)([{label:`Email`,type:`email`,required:!0},{label:`Password`,type:`password`,required:!0},{label:`Name`,type:`text`,required:!0}]),[p,m]=(0,_.useState)([]),[h,g]=(0,_.useState)(0),[v,y]=(0,_.useState)(null),[b,x]=(0,_.useState)(null),[S,C]=(0,_.useState)(null),[w,T]=(0,_.useState)(!1),[E,ee]=(0,_.useState)(!1),[D,te]=(0,_.useState)(0),[ne,A]=(0,_.useState)(0),[re,M]=(0,_.useState)(``),[N,ie]=(0,_.useState)({fi:0,total:0,name:``}),[ae,oe]=(0,_.useState)({tokIn:0,tokOut:0}),[se,ce]=(0,_.useState)(null),[F,le]=(0,_.useState)([]),[ue,de]=(0,_.useState)(``),[fe,pe]=(0,_.useState)(!1),[me,he]=(0,_.useState)([]),[ge,_e]=(0,_.useState)(null),[ve,ye]=(0,_.useState)([]),[I,be]=(0,_.useState)(!1),[xe,Se]=(0,_.useState)(null),[Ce,L]=(0,_.useState)([]),[we,Te]=(0,_.useState)(!1),[Ee,R]=(0,_.useState)(``),[z,De]=(0,_.useState)([]),[Oe,ke]=(0,_.useState)([]),[Ae,je]=(0,_.useState)([]),[Me,Ne]=(0,_.useState)(null),[Pe,Fe]=(0,_.useState)(!1),[Ie,B]=(0,_.useState)([]),[Le,Re]=(0,_.useState)(null),[ze,Be]=(0,_.useState)(!0),Ve=(0,_.useRef)(null),[He,Ue]=(0,_.useState)(`0s`),[We,Ge]=(0,_.useState)(`0s`),Ke=(0,_.useRef)(0),qe=(0,_.useRef)(0),Je=(0,_.useRef)(null),Ye=(0,_.useRef)(null),Xe=(0,_.useRef)(null),Ze=(0,_.useRef)(null),Qe=(0,_.useRef)(null),$e=(0,_.useRef)(null);function et(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function tt(e){let t=Math.floor((Date.now()-e)/1e3),n=Math.floor(t/60);return(n>0?`${n}m `:``)+`${t%60}s`}(0,_.useEffect)(()=>(w||E?Je.current=setInterval(()=>{w&&Ue(tt(Ke.current)),E&&Ge(tt(qe.current))},1e3):Je.current&&=(clearInterval(Je.current),null),()=>{Je.current&&clearInterval(Je.current)}),[w,E]);function nt(){Xe.current&&(Xe.current.scrollTop=Xe.current.scrollHeight)}(0,_.useEffect)(()=>{nt()},[F]),(0,_.useEffect)(()=>{a&&!I&&(be(!0),O(`/api/studio/webcraft/skills/${encodeURIComponent(a)}`).then(e=>{e?.skills&&ye(e.skills)}).catch(()=>{}))},[a,I]);async function rt(e,t){if(w||!e||e.length<5)return;T(!0),m([]),g(0),y(null),ie({fi:0,total:0,name:``}),oe({tokIn:0,tokOut:0}),Ke.current=Date.now(),Ue(`0s`),Ye.current=new AbortController;let n=Date.now();try{let r=await fetch(`/api/studio/webcraft/generate`,{method:`POST`,headers:{"Content-Type":`application/json`},signal:Ye.current.signal,body:JSON.stringify({projectName:t,description:e,blocks:l,authFields:d})});if(!r.ok||!r.body){T(!1);return}let i=r.body.getReader(),a=new TextDecoder,o=``,s=[];for(;;){let{done:e,value:t}=await i.read();if(e)break;o+=a.decode(t,{stream:!0});let r=o.split(`
680
680
 
681
- `);o=r.pop()??``;for(let e of r){let t=e.replace(/^data: /,``).trim();if(t)try{let e=JSON.parse(t);if(e.type===`processing`||e.type===`planning`)ie(t=>({...t,name:e.type===`planning`?`📋 Pianificazione struttura...`:e.msg||`Avvio...`}));else if(e.type===`file_start`)s.push({name:e.name,content:``,_pending:!0}),m([...s]),ie({fi:e.fi,total:e.total,name:e.name}),g(s.length-1),y(``);else if(e.type===`file_chunk`){let t=s.find(t=>t.name===e.name);t&&(t.content+=e.chunk,t._pending=!1),m([...s]),y(t?t.content:null)}else if(e.type===`file_done`){let t=s.find(t=>t.name===e.name);t&&(t._pending=!1,e.syntaxError&&(t._syntaxError=e.syntaxError)),m([...s]),ie({fi:e.fi,total:e.total,name:e.name}),(e.cumTokIn||e.cumTokOut)&&oe({tokIn:e.cumTokIn||0,tokOut:e.cumTokOut||0})}else if(e.type===`file_error`){let t=s.find(t=>t.name===e.name);t&&(t._error=!0,t._pending=!1),m([...s])}else e.type===`done`&&(ce({seconds:Math.round((Date.now()-n)/1e3),tokIn:e.tokIn??0,tokOut:e.tokOut??0,files:s.length}),y(null),T(!1),be(!1),ye([]),s.some(e=>e._error||e._syntaxError)?setTimeout(()=>Qe.current?.(),800):setTimeout(()=>$e.current?.(),800))}catch{}}}}catch(e){e.name!==`AbortError`&&le(t=>[...t,{role:`system`,text:`Errore generazione: `+e.message}]),T(!1)}}async function it(){let e=ue.trim();if(!(a&&p.length>0)){if(!e||e.length<5)return;let t=a||`MyProject`;o(t),c(e),de(``),await rt(e,t);return}if(!e&&me.length===0||fe||w)return;let t=[...me];if(he([]),de(``),e.toLowerCase().startsWith(`/plan `)||e.toLowerCase().startsWith(`piano: `)){let t=e.replace(/^\/plan[ ]*/i,``).replace(/^piano:[ ]*/i,``);le(t=>[...t,{role:`user`,text:e}]),await at(`[MODALITA PIANO] Descrivi cosa modificheresti per: "${t}". Elenca i file e cosa faresti. NON applicare modifiche ancora. Rispondi con il piano in bullet list.`,t,[]);return}le(n=>[...n,{role:`user`,text:e,attachments:t}]),await at(e,null,t)}async function at(e,t,n){if(fe)return;pe(!0);let r={};p.forEach(e=>{r[e.name]=e.content});try{let i=await fetch(`/api/studio/webcraft/agent`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({projectName:a,message:e,attachments:n.map(e=>({name:e.name,mimeType:e.mimeType,base64:e.base64}))})});if(!i.ok||!i.body){le(e=>[...e,{role:`agent`,text:`Errore: ${i.status}`,tools:[]}]),pe(!1);return}let o={role:`agent`,text:``,tools:[]};le(e=>[...e,o]);let s=i.body.getReader(),c=new TextDecoder,l=``,u=!1;for(;;){let{done:e,value:n}=await s.read();if(e)break;l+=c.decode(n,{stream:!0});let i=l.split(`
681
+ `);o=r.pop()??``;for(let e of r){let t=e.replace(/^data: /,``).trim();if(t)try{let e=JSON.parse(t);if(e.type===`processing`||e.type===`planning`)ie(t=>({...t,name:e.type===`planning`?`📋 Pianificazione struttura...`:e.msg||`Avvio...`}));else if(e.type===`file_start`)s.push({name:e.name,content:``,_pending:!0}),m([...s]),ie({fi:e.fi,total:e.total,name:e.name}),g(s.length-1),y(``);else if(e.type===`file_chunk`){let t=s.find(t=>t.name===e.name);t&&(t.content+=e.chunk,t._pending=!1),m([...s]),y(t?t.content:null)}else if(e.type===`file_done`){let t=s.find(t=>t.name===e.name);t&&(t._pending=!1,e.syntaxError&&(t._syntaxError=e.syntaxError)),m([...s]),ie({fi:e.fi,total:e.total,name:e.name}),(e.cumTokIn||e.cumTokOut)&&oe({tokIn:e.cumTokIn||0,tokOut:e.cumTokOut||0})}else if(e.type===`file_error`){let t=s.find(t=>t.name===e.name);t&&(t._error=!0,t._pending=!1),m([...s])}else e.type===`phase`||e.type===`status`&&!e.op?ie(t=>({...t,name:e.msg||e.phase||``})):e.type===`done`&&(ce({seconds:Math.round((Date.now()-n)/1e3),tokIn:e.tokIn??0,tokOut:e.tokOut??0,files:s.length}),y(null),T(!1),be(!1),ye([]),s.some(e=>e._error||e._syntaxError)?setTimeout(()=>Qe.current?.(),800):setTimeout(()=>$e.current?.(),800))}catch{}}}}catch(e){e.name!==`AbortError`&&le(t=>[...t,{role:`system`,text:`Errore generazione: `+e.message}]),T(!1)}}async function it(){let e=ue.trim();if(!(a&&p.length>0)){if(!e||e.length<5)return;let t=a||`MyProject`;o(t),c(e),de(``),await rt(e,t);return}if(!e&&me.length===0||fe||w)return;let t=[...me];if(he([]),de(``),e.toLowerCase().startsWith(`/plan `)||e.toLowerCase().startsWith(`piano: `)){let t=e.replace(/^\/plan[ ]*/i,``).replace(/^piano:[ ]*/i,``);le(t=>[...t,{role:`user`,text:e}]),await at(`[MODALITA PIANO] Descrivi cosa modificheresti per: "${t}". Elenca i file e cosa faresti. NON applicare modifiche ancora. Rispondi con il piano in bullet list.`,t,[]);return}le(n=>[...n,{role:`user`,text:e,attachments:t}]),await at(e,null,t)}async function at(e,t,n){if(fe)return;pe(!0);let r={};p.forEach(e=>{r[e.name]=e.content});try{let i=await fetch(`/api/studio/webcraft/agent`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({projectName:a,message:e,attachments:n.map(e=>({name:e.name,mimeType:e.mimeType,base64:e.base64}))})});if(!i.ok||!i.body){le(e=>[...e,{role:`agent`,text:`Errore: ${i.status}`,tools:[]}]),pe(!1);return}let o={role:`agent`,text:``,tools:[]};le(e=>[...e,o]);let s=i.body.getReader(),c=new TextDecoder,l=``,u=!1;for(;;){let{done:e,value:n}=await s.read();if(e)break;l+=c.decode(n,{stream:!0});let i=l.split(`
682
682
 
683
683
  `);l=i.pop()??``;for(let e of i){let n=e.replace(/^data: /,``).trim();if(n)try{let e=JSON.parse(n);e.type===`text`?(o.text+=e.token,le(e=>{let t=[...e];return t[t.length-1]={...o},t})):e.type===`tool`?(o.tools.push({op:e.op,path:e.path,result:e.result,oldSnippet:e.oldSnippet??``,newSnippet:e.newSnippet??``}),(e.op===`edit`||e.op===`write`)&&e.result===`ok`&&(u=!0),le(e=>{let t=[...e];return t[t.length-1]={...o},t})):e.type===`done`?(t&&!u&&_e({plan:o.text,originalMessage:t}),pe(!1),e.changed&&await ot((o.tools??[]).filter(e=>e.op===`edit`||e.op===`write`).map(e=>e.path),r)):e.type===`error`&&(o.text+=`
684
684
  Errore: `+e.msg,pe(!1))}catch{}}}}catch(e){le(t=>[...t,{role:`agent`,text:`Errore di rete: `+e.message,tools:[]}])}pe(!1)}async function ot(e,t){if(!a)return;let n=await O(`/api/studio/webcraft/projects/load/${encodeURIComponent(a)}`);if(n?.files&&(m(n.files),e.length>0)){let r=e.map(e=>{let r=n.files.find(t=>t.name===e);return r?{file:e,before:t[e]??``,after:r.content}:null}).filter(Boolean);ke(e=>[...e,...r])}}function st(){Ye.current&&=(Ye.current.abort(),null),T(!1),pe(!1),le(e=>[...e,{role:`system`,text:`⏹ Generazione interrotta.`}])}async function ct(){return a?(await k(`/api/studio/webcraft/snapshot`,{projectName:a}))?.snapshot??null:null}async function lt(){let e=await ct();e&&(le(t=>[...t,{role:`system`,text:`💾 Snapshot salvato (${e.slice(0,16).replace(`T`,` `)})`}]),V())}async function V(){if(!a)return;let e=await O(`/api/studio/webcraft/snapshots/${encodeURIComponent(a)}`);e?.snapshots&&L(e.snapshots)}async function ut(e){confirm(`Ripristinare lo snapshot del ${e.replace(`T`,` `).slice(0,16)}? I file attuali verranno sovrascritti.`)&&await k(`/api/studio/webcraft/restore`,{projectName:a,ts:e})!==null&&(le(t=>[...t,{role:`agent`,text:`Snapshot ripristinato (${e}). Ricarico i file...`}]),ot([],{}))}async function dt(){if(!a)return;let e=await k(`/api/studio/webcraft/syntax-check`,{projectName:a});if(e?.results){let t=e.results.filter(e=>!e.ok);t.length>0?le(e=>[...e,{role:`system`,text:`⚠ Syntax check: ${t.length} errore/i trovato/i.`,syntaxErrors:t}]):le(e=>[...e,{role:`system`,text:`✓ Syntax check: tutti i file JS sono validi.`}])}}async function ft(){let e=p.filter(e=>e._error||e._syntaxError);if(e.length===0){$e.current?.();return}ee(!0),te(0),A(e.length),qe.current=Date.now(),Ge(`0s`);for(let t=0;t<e.length;t++){let n=e[t];M(n.name),te(t),await at(`REPAIR FILE: ${n.name}\nErrore: ${n._syntaxError??`generazione fallita`}\nRigenera il file correttamente. Output SOLO il contenuto del file, nessuna spiegazione.`,null,[])}te(e.length),M(``),ee(!1),setTimeout(()=>$e.current?.(),500)}let pt={cleanup:`🧹`,shims:`🔌`,deps:`📦`,start:`🚀`,autofix:`🔧`,ready:`✅`};function mt(e,t=`info`){let n=new Date().toLocaleTimeString();B(r=>[...r,`${t===`phase`?`
@@ -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-B4-XJhib.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-BxGt2gxR.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-UUUprsdb.css">
13
13
  </head>
14
14
  <body>