@mmmbuto/nexuscrew 0.8.28 → 0.8.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to NexusCrew are tracked here.
4
4
 
5
+ ## 0.8.29 — 2026-07-21 — "Stable Fleet Boot"
6
+
7
+ - Keeps the optional Fleet boot companion rooted at the stable user home on Linux, macOS and
8
+ Termux. It can no longer create the shared tmux server from a replaceable npm package directory
9
+ and leave later cells with an orphaned working directory after an update.
10
+ - Makes smart startup inspect and migrate both the main service and the Fleet companion. Startup
11
+ fails closed unless regeneration, service activation and an explicit runtime restart all succeed,
12
+ so an already-active unit cannot keep the old working directory. A private durable marker retries
13
+ interrupted or failed migrations on the next start; a missing optional companion remains a
14
+ non-blocking doctor warning.
15
+ - Validates imported Fleet working directories before persistence and redacts the active Shell
16
+ command together with the existing prompt and environment secret values.
17
+ - Extends source-side diagnostic redaction to macOS `/Users/<name>/...` paths in addition to Linux
18
+ and Android home paths.
19
+ - Gate: **987 isolated Node tests** (986 pass / 1 platform skip), **93/93 frontend component
20
+ tests**, production PWA build and zero production dependency vulnerabilities in both dependency
21
+ trees.
22
+
5
23
  ## 0.8.28 — 2026-07-21 — "Portable Workspaces"
6
24
 
7
25
  - Makes Fleet backups portable across devices. Version 3 archives store a validated home-relative
package/README.md CHANGED
@@ -469,7 +469,7 @@ See [CHANGELOG.md](CHANGELOG.md) for released changes.
469
469
 
470
470
  ## Status
471
471
 
472
- The current stable release is **v0.8.27** on npm and GitHub.
472
+ The current stable release is **v0.8.29** on npm and GitHub.
473
473
 
474
474
  ## License
475
475
 
@@ -90,4 +90,4 @@ Minimum version required to store current data is: `+K+`.
90
90
  `).filter(o=>o!==""),promptMode:e.promptMode};return e.modelFlag&&(s.model={flag:e.modelFlag,value:e.modelValue||""}),e.promptMode==="flag"&&(s.promptFlag=e.promptFlag),t&&(s.env=Object.fromEntries(e.envRows.filter(o=>!o.remove&&o.key).map(o=>[o.key,o.value]))),s}function gr({children:e,onClose:t,label:i,error:s=""}){const o=R.useRef(null),a=R.useRef(null),u=R.useRef(t);return u.current=t,R.useEffect(()=>{const h=document.activeElement,p=o.current,f=()=>Array.from((p==null?void 0:p.querySelectorAll('button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [href], [tabindex]:not([tabindex="-1"])'))||[]).filter(S=>S.offsetParent!==null),_=requestAnimationFrame(()=>{var S;return(S=f()[0]||p)==null?void 0:S.focus({preventScroll:!0})}),b=S=>{var k;if(S.key==="Escape"){S.preventDefault(),(k=u.current)==null||k.call(u);return}if(S.key!=="Tab")return;const w=f();if(!w.length){S.preventDefault(),p==null||p.focus();return}const y=w[0],x=w[w.length-1];S.shiftKey&&document.activeElement===y?(S.preventDefault(),x.focus()):!S.shiftKey&&document.activeElement===x&&(S.preventDefault(),y.focus())};return document.addEventListener("keydown",b),()=>{cancelAnimationFrame(_),document.removeEventListener("keydown",b),h&&h.isConnected&&typeof h.focus=="function"&&h.focus({preventScroll:!0})}},[]),R.useEffect(()=>{s&&requestAnimationFrame(()=>{var h;return(h=a.current)==null?void 0:h.scrollIntoView({block:"nearest"})})},[s]),c.jsx("div",{className:"nc-fleet-modal",role:"presentation",onMouseDown:h=>{h.target===h.currentTarget&&t()},children:c.jsxs("div",{ref:o,className:"nc-fleet-modal-dialog",role:"dialog","aria-modal":"true","aria-label":i||g("settings"),tabIndex:-1,children:[e,s&&c.jsx("div",{ref:a,className:"nc-err nc-fleet-modal-error",role:"alert","aria-live":"assertive",children:s})]})})}function nd({token:e,targets:t=[],readonly:i=!1,onPower:s,onImport:o}){const[a,u]=R.useState([]),[h,p]=R.useState(0);R.useEffect(()=>{let k=!0;async function C(){const A=[{route:[],label:g("local")}].concat((t||[]).map(ee=>({route:Array.isArray(ee.route)?ee.route:[],label:ee.label||(ee.route||[]).join(" › ")}))),M=await Promise.all(A.map(async ee=>{const q={...ee,available:!1,readonly:!1,capabilities:[],provider:null,cells:[],unmanaged:[],err:""};try{const B=await Rr(e,ee.route);q.available=!!B.available,q.capabilities=B.capabilities||[],q.provider=B.provider||null,q.cells=(B.cells||[]).map(Z=>({...Z,route:ee.route}));try{q.readonly=!!(await dh(e,ee.route)).readonlyDefault}catch{}try{const Z=await Xv(e,ee.route),H=new Set(q.cells.map(te=>te.tmuxSession).filter(Boolean));q.unmanaged=(Z.sessions||[]).filter(te=>te&&!H.has(te.name))}catch{}}catch(B){q.err=String(B&&B.message||B)}return q}));k&&u(M)}C();const P=setInterval(C,6e3);return()=>{k=!1,clearInterval(P)}},[e,t,h]);const f=(k,C)=>Array.isArray(k.capabilities)&&k.capabilities.includes(C)&&!i&&!k.readonly,_=()=>p(k=>k+1),b=(k,C)=>{s&&s({...k,route:C})},S=(k,C)=>{s&&s({...k,route:C})},w=async(k,C)=>{try{await ho(e,k.cell,C)}catch{}_()},y=async(k,C)=>{if(window.confirm(g("fleet-remove-cell").replace("{id}",k.cell))){try{await qv(e,k.cell,!0,C)}catch{}_()}},x=async(k,C)=>{if(window.confirm(g("terminate-confirm").replace("{name}",k.name))){try{await ch(e,k.name,C)}catch{}_()}};return c.jsxs("div",{className:"nc-fleet-inventory",children:[c.jsxs("div",{className:"nc-fleet-section-head",children:[c.jsx("b",{children:g("fleet-inventory")}),c.jsx("small",{children:g("fleet-inventory-help")})]}),a.map(k=>{const C=k.route.length?k.route.join("/"):"local";return c.jsxs("div",{className:"nc-fleet-pos",children:[c.jsxs("div",{className:"nc-fleet-pos-title",children:[c.jsx("span",{className:`dot ${k.available?"on":"warn"}`}),c.jsx("b",{children:k.label}),c.jsx("small",{children:k.available?`${k.cells.length} ${g("fleet-cells")} · ${k.unmanaged.length} ${g("fleet-tmux")}`:k.err||g("fleet-not-available")})]}),k.cells.map(P=>c.jsxs("div",{className:"nc-fleet-item nc-fleet-cell",children:[c.jsxs("span",{children:[c.jsx("b",{children:P.cell}),c.jsx("small",{children:`${P.engine||""}${P.key?`·${P.key}`:""}${P.active?"":` · ${g("cell-off")}`}`})]}),c.jsxs("span",{className:"nc-fleet-cell-actions",children:[P.active&&f(k,"down")&&c.jsx("button",{className:"nc-btn ghost",title:g("power-off"),onClick:()=>S(P,k.route),children:g("stop")}),!P.active&&f(k,"up")&&c.jsx("button",{className:"nc-btn ghost",title:g("power-on"),onClick:()=>b(P,k.route),children:g("start")}),f(k,"restart")&&P.active&&c.jsx("button",{className:"nc-btn ghost",title:g("restart"),onClick:()=>w(P,k.route),children:g("restart")}),f(k,"remove")&&c.jsx("button",{className:"nc-btn danger",title:g("delete"),onClick:()=>y(P,k.route),children:"×"})]})]},`${C}:${P.cell}`)),k.unmanaged.map(P=>c.jsxs("div",{className:"nc-fleet-item nc-fleet-unmanaged",children:[c.jsxs("span",{children:[c.jsx("b",{children:P.name}),c.jsx("small",{children:g("fleet-tmux")})]}),c.jsxs("span",{className:"nc-fleet-cell-actions",children:[f(k,"import")&&o&&c.jsx("button",{className:"nc-btn ghost",title:g("import-as-cell"),onClick:()=>o(P,k.route),children:g("import-as-cell")}),c.jsx("button",{className:"nc-btn danger",title:g("terminate"),onClick:()=>x(P,k.route),children:"×"})]})]},`${C}:u:${P.name}`)),k.available&&k.cells.length===0&&k.unmanaged.length===0&&c.jsx("div",{className:"nc-empty",children:g("fleet-inventory-empty")})]},C)})]})}function kE({cells:e=[],engines:t=[],busy:i,canRestore:s=!1,onRestore:o,onClose:a}){const[u,h]=R.useState("export"),[p,f]=R.useState(()=>new Set(e.map(E=>E.id))),[_,b]=R.useState(()=>new Set(t.map(E=>E.id))),[S,w]=R.useState([]),[y,x]=R.useState([]),[k,C]=R.useState(""),P=new Set(t.map(E=>E.id)),A=new Set(e.map(E=>E.id)),M=(E,j)=>E(D=>{const U=new Set(D);return U.has(j)?U.delete(j):U.add(j),U}),ee=()=>{const E=_E(e,p,t,_);if(E.ok===!1){C(g(`fleet-backup-${E.error}`));return}const j=e.filter(V=>p.has(V.id)).length,D=t.filter(V=>_.has(V.id)).length;if(E.cells.length!==j){C(g("fleet-backup-invalid-cell"));return}if(E.engines.length!==D){C(g("fleet-backup-invalid-engine"));return}if(!E.cells.length&&!E.engines.length){C(g("fleet-backup-select-one"));return}const U=new Blob([`${JSON.stringify(E,null,2)}
91
91
  `],{type:"application/json"}),re=URL.createObjectURL(U),I=document.createElement("a");I.href=re,I.download=`nexuscrew-fleet-${new Date().toISOString().slice(0,10)}.json`,document.body.appendChild(I),I.click(),I.remove(),setTimeout(()=>URL.revokeObjectURL(re),0),C("")},q=async E=>{if(C(""),w([]),x([]),!!E){if(E.size>1024*1024){C(g("fleet-backup-too-large"));return}try{const j=yE(await E.text());if(!j.ok){C(g(`fleet-backup-${j.error}`));return}const D=new Set(j.engines.map(U=>U.id));x(j.engines.map(U=>({engine:U,exists:P.has(U.id),selected:!P.has(U.id)}))),w(j.cells.map(U=>{const re=P.has(U.engine)||D.has(U.engine),I=A.has(U.id);return{cell:U,engine:re?U.engine:"",selected:re&&!I,exists:I}}))}catch(j){C(String(j.message||j))}}},B=(E,j)=>w(D=>D.map((U,re)=>re===E?{...U,...j}:U)),Z=(E,j)=>x(D=>D.map((U,re)=>re===E?{...U,...j}:U)),H=new Set([...P,...y.filter(E=>E.selected).map(E=>E.engine.id)]),te=S.filter(E=>E.selected&&E.engine&&H.has(E.engine)),K=y.filter(E=>E.selected),T=[...new Map([...t.map(E=>[E.id,E.label||E.id]),...y.filter(E=>E.selected).map(E=>[E.engine.id,E.engine.label||E.engine.id])]).entries()];return c.jsxs("div",{className:"nc-set-form nc-fleet-form nc-backup-dialog",children:[c.jsx("b",{children:g("fleet-backup")}),c.jsx("small",{children:g("fleet-backup-help")}),c.jsxs("div",{className:"nc-set-tabs nc-backup-tabs",children:[c.jsx("button",{type:"button",className:`nc-set-tabbtn${u==="export"?" on":""}`,onClick:()=>{h("export"),C("")},children:g("fleet-backup-export")}),c.jsx("button",{type:"button",className:`nc-set-tabbtn${u==="import"?" on":""}`,disabled:!s,title:s?"":g("fleet-backup-restore-unavailable"),onClick:()=>{h("import"),C("")},children:g("fleet-backup-import")})]}),u==="export"?c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:()=>{f(new Set(e.map(E=>E.id))),b(new Set(t.map(E=>E.id)))},children:g("select-all")}),c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:()=>{f(new Set),b(new Set)},children:g("select-none")})]}),c.jsxs("div",{className:"nc-backup-list",children:[c.jsx("b",{children:g("fleet-engines")}),t.map(E=>{var j;return c.jsxs("label",{className:"nc-check nc-backup-row",children:[c.jsx("input",{type:"checkbox",checked:_.has(E.id),onChange:()=>M(b,E.id)}),c.jsxs("span",{children:[c.jsx("b",{children:E.label||E.id}),c.jsxs("small",{children:[E.id,(j=E.envKeys)!=null&&j.length?` · ${E.envKeys.length} ${g("fleet-backup-env-names")}`:""]})]})]},E.id)}),c.jsx("b",{children:g("fleet-cells")}),e.map(E=>c.jsxs("label",{className:"nc-check nc-backup-row",children:[c.jsx("input",{type:"checkbox",checked:p.has(E.id),onChange:()=>M(f,E.id)}),c.jsxs("span",{children:[c.jsx("b",{children:E.id}),c.jsxs("small",{children:[E.engine," · ",g("fleet-system-prompt")," ",(E.prompt||"").length," ",g("characters")]})]})]},E.id)),!e.length&&!t.length&&c.jsx("div",{className:"nc-empty",children:g("fleet-backup-empty")})]}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:a,children:g("cancel")}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:!p.size&&!_.size,onClick:ee,children:g("fleet-backup-download")})]})]}):c.jsxs(c.Fragment,{children:[c.jsx("input",{type:"file",accept:"application/json,.json",disabled:i,onChange:E=>q(E.target.files&&E.target.files[0])}),c.jsx("small",{children:g("fleet-backup-import-help")}),!!(S.length||y.length)&&c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:()=>{x(E=>E.map(j=>({...j,selected:!0}))),w(E=>E.map(j=>({...j,selected:!!j.engine})))},children:g("select-all")}),c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:()=>{x(E=>E.map(j=>({...j,selected:!1}))),w(E=>E.map(j=>({...j,selected:!1})))},children:g("select-none")})]}),c.jsxs("div",{className:"nc-backup-list",children:[!!y.length&&c.jsx("b",{children:g("fleet-engines")}),y.map((E,j)=>{var D;return c.jsx("div",{className:"nc-backup-import-row",children:c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:E.selected,onChange:U=>Z(j,{selected:U.target.checked})}),c.jsxs("span",{children:[c.jsx("b",{children:E.engine.label||E.engine.id}),c.jsxs("small",{children:[E.engine.id," · ",E.exists?g("fleet-backup-overwrite"):g("fleet-backup-new"),(D=E.engine.envKeys)!=null&&D.length?` · ${g("fleet-backup-env-values-required")}`:""]})]})]})},E.engine.id)}),!!S.length&&c.jsx("b",{children:g("fleet-cells")}),S.map((E,j)=>c.jsxs("div",{className:"nc-backup-import-row",children:[c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:E.selected,disabled:!E.engine||!H.has(E.engine),onChange:D=>B(j,{selected:D.target.checked})}),c.jsxs("span",{children:[c.jsx("b",{children:E.cell.id}),c.jsxs("small",{children:[E.exists?g("fleet-backup-overwrite"):g("fleet-backup-new")," · ",g("fleet-system-prompt")," ",E.cell.systemPrompt.length," ",g("characters")]})]})]}),c.jsxs("select",{value:E.engine,onChange:D=>B(j,{engine:D.target.value,selected:!!D.target.value}),children:[c.jsx("option",{value:"",children:g("fleet-backup-engine-missing")}),T.map(([D,U])=>c.jsx("option",{value:D,children:U},D))]})]},E.cell.id))]}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:a,children:g("cancel")}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:i||!te.length&&!K.length,onClick:()=>o({engineRows:y,cellRows:S}),children:g("fleet-backup-restore")})]})]}),k&&c.jsx("div",{className:"nc-err",children:k})]})}function EE({state:e,setState:t,busy:i,onSave:s,catalog:o}){var k;const a=e.form,u=C=>t({...e,form:{...a,...C}}),h=a.envRows||[],p=[...new Map(o.map(C=>[C.client,C.clientLabel||C.client])).entries()],f=o.filter(C=>C.client===a.client),_=xo(o,a),b=typeof(_==null?void 0:_.credentialEnv)=="string"?_.credentialEnv:"",S=Array.isArray(_==null?void 0:_.credentialUsedBy)?_.credentialUsedBy:[],w=(_==null?void 0:_.credentialSource)||"missing",y=!!b&&e.mode==="new"&&(_==null?void 0:_.authConfigured)!==!0&&!a.credentialValue&&!a.allowMissingCredential,x=C=>{C&&u({client:C.client,provider:C.provider,credentialProfile:C.credentialProfile||"",managedModel:C.model||"",protocol:C.protocol||"",permissionPolicy:C.permissionPolicyDefault||"standard",rc:!!C.rc,displayName:C.custom?g("fleet-custom-provider-default"):"",baseUrl:C.custom?"":C.endpoint||"",envKey:C.defaultEnvKey||"",providerId:"nexuscrew-custom",credentialValue:"",credentialReveal:!1,allowMissingCredential:!1,...e.mode==="new"?{id:C.id,label:""}:{}})};return c.jsxs("div",{className:"nc-set-form nc-fleet-form",children:[c.jsx("b",{children:e.mode==="new"?g("fleet-new-engine"):`${g("edit")} ${a.id}`}),c.jsx("input",{value:a.id,disabled:e.mode!=="new",placeholder:"id",onChange:C=>u({id:C.target.value})}),c.jsx("input",{value:a.label,placeholder:g("label"),onChange:C=>u({label:C.target.value})}),c.jsxs("select",{value:a.kind,disabled:e.mode!=="new",onChange:C=>u({kind:C.target.value}),children:[c.jsx("option",{value:"managed",children:g("fleet-managed")}),c.jsx("option",{value:"custom",children:g("fleet-custom")})]}),a.kind==="managed"?c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:"nc-fleet-pair",children:[c.jsx("select",{value:a.client,disabled:e.mode!=="new",onChange:C=>x(o.find(P=>P.client===C.target.value&&P.default)||o.find(P=>P.client===C.target.value)),children:p.map(([C,P])=>c.jsx("option",{value:C,children:P},C))}),c.jsx("select",{value:((k=xo(o,a))==null?void 0:k.id)||"",disabled:e.mode!=="new",onChange:C=>x(o.find(P=>P.id===C.target.value)),children:f.map(C=>c.jsx("option",{value:C.id,children:C.label},C.id))})]}),a.client!=="shell"&&c.jsxs(c.Fragment,{children:[c.jsx("input",{value:a.managedModel,list:"nc-managed-models",placeholder:g(_!=null&&_.requiresModel?"fleet-model-required":"fleet-model-default"),onChange:C=>u({managedModel:C.target.value})}),c.jsx("datalist",{id:"nc-managed-models",children:[...(_==null?void 0:_.models)||[],...a.modelOptions||[]].filter((C,P,A)=>C&&A.indexOf(C)===P).map(C=>c.jsx("option",{value:C},C))})]}),_!=null&&_.supportsUnsafe?c.jsxs("select",{value:a.permissionPolicy,onChange:C=>u({permissionPolicy:C.target.value}),children:[c.jsx("option",{value:"standard",children:g("fleet-standard-permissions")}),c.jsx("option",{value:"unsafe",children:g("fleet-unsafe-permissions")})]}):c.jsx("small",{children:g("fleet-standard-permissions")}),(_==null?void 0:_.supportsUnsafe)&&a.permissionPolicy==="unsafe"&&c.jsx("small",{className:"nc-err",children:g("fleet-unsafe-warning")}),(_==null?void 0:_.credentialEnv)===!0&&c.jsxs(c.Fragment,{children:[c.jsx("input",{value:a.envKey,placeholder:g("fleet-api-key-env"),onChange:C=>u({envKey:C.target.value})}),c.jsx("small",{children:g("fleet-custom-secret-help")})]}),b&&c.jsxs("section",{className:"nc-fleet-key","aria-label":g("fleet-key-section"),children:[c.jsxs("div",{className:"nc-fleet-key-head",children:[c.jsx("b",{children:g("fleet-key-section")}),c.jsx("span",{children:g(`fleet-credential-source-${w}`)})]}),c.jsxs("label",{className:"nc-field",children:[g("fleet-key-variable"),c.jsx("input",{value:b,readOnly:!0,"aria-label":g("fleet-key-variable")})]}),c.jsx("small",{children:S.length?g("fleet-key-used-by").replace("{engines}",S.join(", ")):g("fleet-key-used-by-none")}),S.length>0&&c.jsx("small",{children:g("fleet-key-shared-impact").replace("{key}",b).replace("{engines}",S.join(", "))}),c.jsxs("div",{className:"nc-fleet-key-input",children:[c.jsx("input",{type:a.credentialReveal?"text":"password",value:a.credentialValue||"","aria-label":g("fleet-key-value").replace("{key}",b),autoComplete:"new-password",spellCheck:!1,autoCapitalize:"none",autoCorrect:"off",placeholder:_!=null&&_.authConfigured||e.mode==="edit"?g("fleet-key-blank-keeps"):g("fleet-key-enter"),onChange:C=>u({credentialValue:C.target.value,allowMissingCredential:C.target.value?!1:a.allowMissingCredential})}),c.jsx("button",{type:"button",className:"nc-btn ghost","aria-label":g(a.credentialReveal?"fleet-key-hide":"fleet-key-reveal"),onClick:()=>u({credentialReveal:!a.credentialReveal}),children:g(a.credentialReveal?"fleet-key-hide":"fleet-key-reveal")})]}),c.jsx("small",{children:g("fleet-key-local-help")}),e.mode==="new"&&(_==null?void 0:_.authConfigured)!==!0&&!a.credentialValue&&c.jsxs("label",{className:"nc-check nc-fleet-key-missing",children:[c.jsx("input",{type:"checkbox",checked:!!a.allowMissingCredential,onChange:C=>u({allowMissingCredential:C.target.checked})}),g("fleet-key-create-missing")]}),(_==null?void 0:_.notice)&&c.jsx("small",{className:"nc-fleet-provider-notice",children:g(`fleet-provider-notice-${_.notice}`)})]}),a.provider==="custom"&&c.jsxs(c.Fragment,{children:[c.jsx("input",{value:a.displayName,placeholder:g("fleet-provider-display"),onChange:C=>u({displayName:C.target.value})}),c.jsx("input",{value:a.baseUrl,placeholder:"https://api.example.com/v1",onChange:C=>u({baseUrl:C.target.value})}),((_==null?void 0:_.protocols)||[]).length>1&&c.jsx("select",{value:a.protocol,onChange:C=>u({protocol:C.target.value}),children:_.protocols.map(C=>c.jsx("option",{value:C,children:C},C))}),c.jsxs("div",{className:"nc-fleet-pair",children:[c.jsx("input",{value:a.envKey,placeholder:g("fleet-api-key-env"),onChange:C=>u({envKey:C.target.value})}),c.jsx("input",{value:a.providerId,placeholder:g("fleet-provider-id"),onChange:C=>u({providerId:C.target.value})})]}),c.jsxs("small",{children:[a.protocol," · ",g("fleet-custom-secret-help")]})]}),c.jsx("small",{children:g(a.client==="shell"?"fleet-shell-engine-help":"fleet-managed-help")})]}):c.jsxs(c.Fragment,{children:[c.jsx("input",{value:a.command,placeholder:g("command-path"),onChange:C=>u({command:C.target.value})}),c.jsx("textarea",{value:a.argsText,placeholder:g("args-lines"),onChange:C=>u({argsText:C.target.value})}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:a.rc,onChange:C=>u({rc:C.target.checked})})," remote control"]}),c.jsxs("select",{value:a.promptMode,onChange:C=>u({promptMode:C.target.value}),children:[c.jsx("option",{value:"send-keys",children:"send-keys"}),c.jsx("option",{value:"flag",children:"flag"})]}),a.promptMode==="flag"&&c.jsx("input",{value:a.promptFlag,placeholder:"prompt flag",onChange:C=>u({promptFlag:C.target.value})}),c.jsxs("div",{className:"nc-fleet-pair",children:[c.jsx("input",{value:a.modelFlag,placeholder:"model flag",onChange:C=>u({modelFlag:C.target.value})}),c.jsx("input",{value:a.modelValue,placeholder:"model default",onChange:C=>u({modelValue:C.target.value})})]}),c.jsx("small",{children:g("env-write-only")}),h.map((C,P)=>c.jsxs("div",{className:"nc-fleet-env",children:[c.jsx("input",{value:C.key,disabled:C.configured,placeholder:"ENV_KEY",onChange:A=>{const M=h.slice();M[P]={...C,key:A.target.value},u({envRows:M})}}),c.jsx("input",{type:"password",value:C.value,placeholder:C.configured?"•••••• (unchanged)":"value",onChange:A=>{const M=h.slice();M[P]={...C,value:A.target.value},u({envRows:M})}}),c.jsx("button",{className:"nc-btn danger",onClick:()=>u({envRows:h.filter((A,M)=>M!==P)}),children:"×"})]},`${C.key}-${P}`)),c.jsx("button",{className:"nc-btn ghost",onClick:()=>u({envRows:[...h,{key:"",value:"",configured:!1}]}),children:"+ env"})]}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{className:"nc-btn ghost",onClick:()=>t(null),children:g("cancel")}),c.jsx("button",{className:"nc-btn primary",disabled:i||!a.id||y||a.kind==="custom"&&!a.command||a.kind==="managed"&&(_==null?void 0:_.requiresModel)&&!a.managedModel||a.kind==="managed"&&(_==null?void 0:_.credentialEnv)===!0&&!a.envKey||a.kind==="managed"&&a.provider==="custom"&&(!a.displayName||!a.baseUrl||!a.envKey||!a.providerId),onClick:s,children:g("save")})]})]})}function NE({token:e,route:t,targets:i=[],location:s,setLocation:o,state:a,setState:u,engines:h,busy:p,onSave:f}){var q;const[_,b]=R.useState(null),[S,w]=R.useState(""),y=a.form,x=B=>u({...a,form:{...y,...B}}),k=h.find(B=>B.id===y.engine),C=((q=k==null?void 0:k.managed)==null?void 0:q.client)==="shell",P=B=>{var te,K,T,E;const Z=h.find(j=>j.id===B),H={...y.commands||{}};((te=k==null?void 0:k.managed)==null?void 0:te.client)==="shell"&&(y.command?H[y.engine]=y.command:delete H[y.engine]),x({engine:B,model:((K=y.models)==null?void 0:K[B])||((T=Z==null?void 0:Z.managed)==null?void 0:T.model)||((E=Z==null?void 0:Z.model)==null?void 0:E.value)||"",commands:H,command:H[B]||""})},A=B=>{const Z={...y.commands||{}};B?Z[y.engine]=B:delete Z[y.engine],x({command:B,commands:Z})},M=C&&/[|&;<>()`$*?{}\[\]~]/.test(y.command||""),ee=async B=>{try{const Z=await uh(e,B,t);b(Z),x({cwd:Z.path}),w("")}catch(Z){w(String(Z.message||Z))}};return c.jsxs("div",{className:"nc-set-form nc-fleet-form",children:[c.jsx("b",{children:a.mode==="new"?g("fleet-new-cell"):`${g("edit")} ${y.id}`}),a.mode==="new"&&c.jsxs("label",{className:"nc-field",children:[g("location"),c.jsx("span",{className:"nc-req",children:" *"}),c.jsxs("select",{value:s,onChange:B=>{o(B.target.value),x({engine:""})},children:[c.jsx("option",{value:"",children:g("local")}),i.map(B=>c.jsxs("option",{value:B.route.join("/"),disabled:B.status&&B.status!=="up",children:[B.label,B.status&&B.status!=="up"?` · ${g("node-offline")}`:""]},B.route.join("/")))]})]}),c.jsx("input",{value:y.id,disabled:a.mode!=="new",placeholder:"id",onChange:B=>x({id:B.target.value})}),c.jsxs("div",{className:"nc-fleet-pair",children:[c.jsx("input",{value:y.cwd,placeholder:g("cwd"),onChange:B=>x({cwd:B.target.value})}),c.jsx("button",{className:"nc-btn ghost",onClick:()=>_?b(null):ee(y.cwd),children:g("browse")})]}),_&&c.jsxs("div",{className:"nc-fs",children:[c.jsx("div",{className:"nc-fs-path",children:_.path}),c.jsxs("div",{className:"nc-fs-list",children:[_.parent&&c.jsxs("button",{className:"nc-fs-item nc-fs-nav",onClick:()=>ee(_.parent),children:["↑ ",g("fs-parent")]}),(_.dirs||[]).map(B=>c.jsxs("button",{className:"nc-fs-item",onClick:()=>ee(`${_.path.replace(/\/$/,"")}/${B}`),children:["📁 ",B]},B))]})]}),S&&c.jsx("div",{className:"nc-err",children:S}),c.jsx("select",{value:y.engine,onChange:B=>P(B.target.value),children:h.map(B=>c.jsx("option",{value:B.id,children:B.label},B.id))}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:!!y.boot,onChange:B=>x({boot:B.target.checked})})," boot"]}),C?c.jsxs(c.Fragment,{children:[c.jsx("input",{value:y.command||"",maxLength:4096,placeholder:g("fleet-shell-command-placeholder"),onChange:B=>A(B.target.value)}),c.jsx("small",{children:y.command?g("fleet-shell-command-help"):g("fleet-shell-interactive")}),M&&c.jsx("small",{className:"nc-note",children:g("fleet-shell-command-metachar")})]}):c.jsxs(c.Fragment,{children:[c.jsx("input",{value:y.model||"",list:"nc-cell-models",placeholder:g("fleet-model-override"),onChange:B=>x({model:B.target.value})}),c.jsx("datalist",{id:"nc-cell-models",children:((k==null?void 0:k.availableModels)||[]).map(B=>c.jsx("option",{value:B},B))}),c.jsx("textarea",{value:y.prompt||"",placeholder:"prompt",onChange:B=>x({prompt:B.target.value})})]}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{className:"nc-btn ghost",onClick:()=>u(null),children:g("cancel")}),c.jsx("button",{className:"nc-btn primary",disabled:p||!y.id||!y.cwd||!y.engine,onClick:f,children:g("save")})]})]})}function Tv({token:e,route:t=[],state:i,setState:s,busy:o,onSave:a}){const[u,h]=R.useState(null),[p,f]=R.useState([]),[_,b]=R.useState(""),S=Array.isArray(t)?t.join("/"):"";R.useEffect(()=>{let C=!0;return lh(e,S?S.split("/"):[]).then(P=>{C&&(f(P.engines||[]),b(""))}).catch(P=>{C&&(f([]),b(String(P.message||P)))}),()=>{C=!1}},[e,S]);const w=i.form,y=C=>s({...i,form:{...w,...C}}),x=async C=>{try{const P=await uh(e,C,t);h(P),y({cwd:P.path})}catch{}},k=!w.id&&w.tmuxSession?w.tmuxSession.replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,32):w.id;return c.jsxs("div",{className:"nc-set-form nc-fleet-form",children:[c.jsxs("b",{children:[g("import-as-cell")," · ",w.tmuxSession]}),c.jsx("small",{children:g("import-help")}),c.jsx("input",{value:w.tmuxSession,disabled:!0,placeholder:"tmux session",readOnly:!0}),c.jsx("input",{value:w.id,placeholder:g("name"),onChange:C=>y({id:C.target.value})}),c.jsxs("div",{className:"nc-fleet-pair",children:[c.jsx("input",{value:w.cwd,placeholder:g("cwd"),onChange:C=>y({cwd:C.target.value})}),c.jsx("button",{className:"nc-btn ghost",type:"button",onClick:()=>u?h(null):x(w.cwd),children:g("browse")})]}),u&&c.jsxs("div",{className:"nc-fs",children:[c.jsx("div",{className:"nc-fs-path",children:u.path}),c.jsxs("div",{className:"nc-fs-list",children:[u.parent&&c.jsxs("button",{className:"nc-fs-item nc-fs-nav",onClick:()=>x(u.parent),children:["↑ ",g("fs-parent")]}),(u.dirs||[]).map(C=>c.jsxs("button",{className:"nc-fs-item",onClick:()=>x(`${u.path.replace(/\/$/,"")}/${C}`),children:["📁 ",C]},C))]})]}),c.jsxs("select",{value:w.engine,onChange:C=>y({engine:C.target.value}),children:[c.jsx("option",{value:"",children:g("import-engine-required")}),p.map(C=>c.jsx("option",{value:C.id,children:C.label},C.id))]}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:!!w.boot,onChange:C=>y({boot:C.target.checked})})," boot"]}),(i.err||_)&&c.jsx("div",{className:"nc-err",children:i.err||_}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{className:"nc-btn ghost",onClick:()=>s(null),children:g("cancel")}),c.jsx("button",{className:"nc-btn primary",disabled:o||!w.tmuxSession||!w.engine||!p.length,onClick:a,children:g(k?"import-as-cell":"save")})]})]})}function Ga(e){const t=wr(e);return t===null?`~/${e}`:t?`~/${t}`:"~"}function RE(e){if(!e||typeof e.path!="string"||typeof e.home!="string")return"";const{path:t,home:i}=e;return!i||t===i||!t.startsWith(i+"/")?"":t.slice(i.length+1).replace(/\/+$/,"")}function PE({token:e,route:t,cell:i,busy:s=!1,onSaved:o,onClose:a}){const[u,h]=R.useState(""),[p,f]=R.useState(null),[_,b]=R.useState(!1),[S,w]=R.useState(""),y=typeof(i==null?void 0:i.cwdSuggestion)=="string"&&wr(i.cwdSuggestion)!==null?wr(i.cwdSuggestion):null,[x,k]=R.useState(y),C=i==null?void 0:i.id,P=s||_,A=async H=>{const te=wr(H);if(te===null){f({rel:"",dirs:[],loading:!1,err:g("fleet-cwd-repair-invalid")});return}f({rel:te,dirs:[],loading:!0,err:""});try{const K=await uh(e,te,t),T=RE(K);f({rel:T,dirs:Array.isArray(K.dirs)?K.dirs:[],loading:!1,err:""})}catch(K){f({rel:te,dirs:[],loading:!1,err:String(K&&K.message||K)})}},M=H=>{if(!p)return;const te=p.rel?`${p.rel}/${H}`:H;A(te)},ee=()=>{p!=null&&p.rel&&A(p.rel.split("/").slice(0,-1).join("/"))},q=()=>{p&&(h(p.rel||""),f(null),w(""),k(null))},B=()=>{x!==null&&(h(x),k(null),w(""))},Z=async()=>{if(P)return;const H=wr(u);if(H===null){w(g("fleet-cwd-repair-invalid"));return}const te=Ga(H);let K=!0;try{K=window.confirm(g("fleet-cwd-repair-confirm").replace("{id}",String(C||"")).replace("{path}",te))}catch{K=!1}if(K){b(!0),w(""),k(null);try{const T=await Kv(e,C,{cwdRel:H},t);o==null||o(T)}catch(T){const E=T&&T.data||{};if(E.code==="unportable-cwd"){w(g("fleet-cwd-repair-unportable"));const j=Array.isArray(E.cells)?E.cells.find(U=>U&&U.id===C):null,D=j&&typeof j.suggestion=="string"?j.suggestion:null;D!==null&&wr(D)!==null&&k(D)}else w(String(T&&T.message||T))}finally{b(!1)}}};return c.jsxs("div",{className:"nc-set-form nc-fleet-form nc-cwd-repair",children:[c.jsx("b",{children:g("fleet-cwd-repair-title").replace("{id}",String(C||""))}),c.jsx("small",{className:"nc-set-info",children:g("fleet-cwd-repair-help")}),c.jsx("small",{className:"nc-cwd-no-source",children:g("fleet-cwd-repair-no-source-path")}),c.jsxs("label",{className:"nc-field",children:[g("cwd"),c.jsxs("div",{className:"nc-cwd-rel-row",children:[c.jsx("span",{className:"nc-cwd-rel-prefix",children:"~/"}),c.jsx("input",{value:u,placeholder:g("fleet-cwd-repair-placeholder"),autoComplete:"off",spellCheck:"false",autoCapitalize:"none",onChange:H=>h(H.target.value)}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:P,onClick:()=>p?f(null):A(""),children:g(p?"close":"fleet-cwd-repair-browse")})]})]}),c.jsx("div",{className:"nc-cwd-preview",children:g("fleet-cwd-repair-preview").replace("{path}",Ga(u))}),p&&c.jsxs("div",{className:"nc-fs",children:[c.jsx("div",{className:"nc-fs-path",children:Ga(p.rel)}),c.jsxs("div",{className:"nc-fs-list",children:[p.rel&&c.jsxs("button",{type:"button",className:"nc-fs-item nc-fs-nav",onClick:ee,children:["↑ ",g("fs-parent")]}),p.loading&&c.jsx("div",{className:"nc-fs-empty",children:"…"}),!p.loading&&p.dirs.length===0&&c.jsx("div",{className:"nc-fs-empty",children:g("fleet-cwd-repair-fs-empty")}),!p.loading&&p.dirs.map(H=>c.jsxs("button",{type:"button",className:"nc-fs-item",onClick:()=>M(H),children:["📁 ",H]},H))]}),p.err&&c.jsx("div",{className:"nc-err",children:p.err}),c.jsx("div",{className:"nc-sheet-actions",children:c.jsx("button",{type:"button",className:"nc-btn primary",disabled:P||p.loading||!!p.err,onClick:q,children:g("fleet-cwd-repair-use-current")})})]}),S&&c.jsx("div",{className:"nc-err",role:"alert",children:S}),x!==null&&c.jsxs("div",{className:"nc-cwd-suggestion",role:"status",children:[c.jsx("span",{children:g("fleet-cwd-repair-suggestion")}),c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:B,children:g("fleet-cwd-repair-use-suggestion").replace("{path}",Ga(x))})]}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:_,onClick:a,children:g("cancel")}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:P,onClick:Z,children:_?"…":g("save")})]})]})}function jE({token:e,readonly:t,targets:i=[],startNewCell:s=!1,initialLocation:o=""}){const[a,u]=R.useState({engines:[],cells:[],managedCatalog:[]}),[h,p]=R.useState({available:!1,capabilities:[]}),[f,_]=R.useState(!1),[b,S]=R.useState(null),[w,y]=R.useState(null),[x,k]=R.useState(!1),[C,P]=R.useState(""),[A,M]=R.useState(""),[ee,q]=R.useState(o),[B,Z]=R.useState(!1),[H,te]=R.useState(null),[K,T]=R.useState(null),[E,j]=R.useState(null),[D,U]=R.useState(!1),[re,I]=R.useState([]),[V,$]=R.useState(null),[L,z]=R.useState("manage"),oe=R.useRef(!1),ce=ee?ee.split("/"):[],he=R.useCallback(async()=>{try{const ae=await Rr(e,ce);p(ae);try{const xe=await dh(e,ce);Z(!!xe.readonlyDefault)}catch{Z(!1)}if(ae.provider==="builtin"&&(ae.capabilities||[]).includes("definitions")){const xe=await lh(e,ce),de=new Map((ae.engines||[]).map(Ne=>[Ne.id,Ne]));if(xe.engines=(xe.engines||[]).map(Ne=>{var Ee;return{...Ne,availableModels:((Ee=de.get(Ne.id))==null?void 0:Ee.models)||[]}}),u(xe),(ae.capabilities||[]).includes("credentials"))try{I((await QS(e,ce)).credentials||[])}catch{I([])}else I([])}P("")}catch(ae){P(String(ae.message||ae))}finally{_(!0)}},[e,ee]);R.useEffect(()=>{he();const ae=setInterval(he,5e3);return()=>clearInterval(ae)},[he]);const me=new Set((h.cells||[]).filter(ae=>ae.active).map(ae=>ae.cell)),G=h.provider==="builtin"&&(h.capabilities||[]).includes("edit"),J=(h.capabilities||[]).includes("restore");R.useEffect(()=>{!s||oe.current||!G||!a.engines.length||(oe.current=!0,y({mode:"new",form:Mv(a.engines[0].id)}))},[s,G,a.engines]);const ge=async ae=>{k(!0),P(""),M("");try{await ae(),await he()}catch(xe){P(String(xe.message||xe))}k(!1)},Me=()=>ge(async()=>{var Ce;const ae=b.mode==="new",xe=b.form,de=a.managedCatalog||[],Ne=xe.kind==="managed"?xo(de,xe):null,Ee=typeof(Ne==null?void 0:Ne.credentialEnv)=="string"?Ne.credentialEnv:"",X=Ee&&xe.credentialValue||"",ie=CE(xe,ae,de);!ae&&b.form.kind==="custom"&&!b.form.modelFlag&&(ie.model=null),!ae&&b.form.kind==="custom"&&b.form.promptMode!=="flag"&&(ie.promptFlag=null);let F;if(ae)F=await ZS(e,ie,ce);else{const _e=b.original,Re=new Set(b.form.envRows.filter(ze=>!ze.remove).map(ze=>ze.key)),Qe=(_e.envKeys||[]).filter(ze=>!Re.has(ze)),zt=Object.fromEntries(b.form.envRows.filter(ze=>!ze.remove&&ze.key&&(!ze.configured||ze.value!=="")).map(ze=>[ze.key,ze.value]));F=await e0(e,_e.id,ie,b.form.kind==="custom"?{set:zt,remove:Qe}:void 0,ce)}let W=null;if(Ee&&X)try{W=await xm(e,Ee,X,ce),I(W.credentials||[])}catch{throw await he(),ae&&S({mode:"edit",original:{...ie,id:xe.id},form:{...xe,credentialReveal:!1}}),new Error(g(ae?"fleet-key-partial-create":"fleet-key-partial-edit"))}await he(),S(null),M(g("fleet-saved"));const ue=((Ce=((W==null?void 0:W.credentials)||[]).find(_e=>_e.envKey===Ee))==null?void 0:Ce.activeCells)||[],ve=[...new Set([...(F==null?void 0:F.activeCells)||[],...ue])];if(ve.length&&window.confirm(g("fleet-restart-confirm").replace("{cells}",ve.join(", "))))for(const _e of ve)await ho(e,_e,ce)}),Ze=()=>ge(async()=>{var F,W,ue,ve,Ce,_e;const ae=w.mode==="new",xe=w.form,de={...ae?{id:xe.id}:{},cwd:xe.cwd,engine:xe.engine,boot:!!xe.boot},Ne={...xe.commands||{}},Ee=a.engines.find(Re=>Re.id===xe.engine);((F=Ee==null?void 0:Ee.managed)==null?void 0:F.client)==="shell"&&typeof xe.command=="string"&&xe.command.length?Ne[xe.engine]=xe.command:((W=Ee==null?void 0:Ee.managed)==null?void 0:W.client)==="shell"&&delete Ne[xe.engine],(Object.keys(Ne).length||!ae)&&(de.commands=Ne),ae?(((ue=Ee==null?void 0:Ee.managed)==null?void 0:ue.client)!=="shell"&&xe.model&&(de.model=xe.model),((ve=Ee==null?void 0:Ee.managed)==null?void 0:ve.client)!=="shell"&&xe.prompt&&(de.prompt=xe.prompt)):(de.model=((Ce=Ee==null?void 0:Ee.managed)==null?void 0:Ce.client)==="shell"?null:xe.model||null,((_e=Ee==null?void 0:Ee.managed)==null?void 0:_e.client)!=="shell"&&(de.prompt=xe.prompt||null));const X=ae?await i0(e,de,ce):await Kv(e,w.original.id,de,ce),ie=ae?xe.id:w.original.id;y(null),M(g("fleet-saved")),!ae&&(X!=null&&X.active)&&window.confirm(g("fleet-restart-confirm").replace("{cells}",ie))&&await ho(e,ie,ce)}),Be=t||B,Fe=ae=>re.find(xe=>{var de;return(de=xe.engines)==null?void 0:de.includes(ae.id)})||null,ct=()=>ge(async()=>{var de;const ae=await xm(e,V.envKey,V.value,ce);I(ae.credentials||[]),$(null),M(g("fleet-credential-saved"));const xe=((de=(ae.credentials||[]).find(Ne=>Ne.envKey===V.envKey))==null?void 0:de.activeCells)||[];if(xe.length&&window.confirm(g("fleet-restart-confirm").replace("{cells}",xe.join(", "))))for(const Ne of xe)await ho(e,Ne,ce)}),Ve=ae=>ge(async()=>{if(!window.confirm(g("fleet-credential-remove-confirm").replace("{key}",ae.envKey)))return;const xe=await JS(e,ae.envKey,ce);I(xe.credentials||[]),M(g("fleet-credential-removed"))}),gt=ae=>te({...ae,route:Array.isArray(ae==null?void 0:ae.route)?ae.route:ce}),ut=async ae=>{if(!H)return;const xe=H.cell||H.id,de=Array.isArray(H.route)?H.route:ce;ae.action==="up"?await ah(e,{cell:xe,boot:!!ae.boot,...ae.engine?{engine:ae.engine}:{},...ae.model!==void 0?{model:ae.model}:{},...ae.permissionPolicy?{permissionPolicy:ae.permissionPolicy}:{}},de):await sl(e,{cell:xe,boot:!!ae.boot},de)},lt=(ae,xe)=>{P(""),T({mode:"new",route:Array.isArray(xe)?xe:ce,form:{tmuxSession:ae.name,id:"",engine:"",cwd:"",boot:!1},err:""})},nt=()=>ge(async()=>{const ae=K.form;if(!ae.engine){T({...K,err:g("import-engine-required")});return}await n0(e,{tmuxSession:ae.tmuxSession,id:ae.id||void 0,engine:ae.engine,cwd:ae.cwd||void 0,boot:!!ae.boot},ae.route||ce),T(null),M(g("fleet-saved"))}),dt=({engineRows:ae=[],cellRows:xe=[]})=>ge(async()=>{const de=ae.filter(Ce=>Ce.selected),Ne=de.map(Ce=>By(Ce.engine));if(Ne.some(Ce=>!Ce))throw new Error(g("fleet-backup-invalid-engine"));const Ee=de.filter(Ce=>Ce.exists).map(Ce=>Ce.engine.id);if(Ee.length&&!window.confirm(g("fleet-backup-confirm-engine-overwrite").replace("{engines}",Ee.join(", "))))return;const X=[...new Set([...a.engines.map(Ce=>Ce.id),...Ne.map(Ce=>Ce.id)])],ie=[];for(const Ce of xe.filter(_e=>_e.selected)){const _e=wE(Ce.cell,Ce.engine,X);if(!_e)throw new Error(`${Ce.cell.id}: ${g("fleet-backup-engine-missing")}`);ie.push(_e)}const F=xe.filter(Ce=>Ce.selected&&Ce.exists).map(Ce=>Ce.cell.id);if(F.length&&!window.confirm(g("fleet-backup-confirm-overwrite").replace("{cells}",F.join(", "))))return;const W=Ne.length?await s0(e,Ne,Ee.length>0,ce):{needsRestart:[]},ue=ie.length?await r0(e,ie,ce):{needsRestart:[]},ve=[...new Set([...W.needsRestart||[],...ue.needsRestart||[]])];U(!1),M(`${g("fleet-backup-restored").replace("{n}",String(ie.length))}${Ne.length?` · ${Ne.length} ${g("fleet-engines").toLowerCase()}`:""}${ve.length?` · ${g("fleet-backup-needs-restart").replace("{cells}",ve.join(", "))}`:""}`)}),rt=c.jsxs("label",{className:"nc-field",children:[g("location"),c.jsxs("select",{value:ee,onChange:ae=>{q(ae.target.value),S(null),y(null),P(""),M(""),Z(!1),_(!1),p({available:!1,capabilities:[]}),u({engines:[],cells:[],managedCatalog:[]}),I([]),$(null)},children:[c.jsx("option",{value:"",children:g("local")}),i.map(ae=>c.jsxs("option",{value:ae.route.join("/"),disabled:ae.status&&ae.status!=="up",children:[ae.label,ae.status&&ae.status!=="up"?` · ${g("node-offline")}`:""]},ae.route.join("/")))]})]}),ot=c.jsxs("div",{className:"nc-fleet-view-tabs",role:"tablist","aria-label":g("tab-fleet"),children:[c.jsx("button",{type:"button",role:"tab","aria-selected":L==="manage",className:`nc-set-tabbtn${L==="manage"?" on":""}`,onClick:()=>z("manage"),children:g("fleet-manage-location")}),c.jsx("button",{type:"button",role:"tab","aria-selected":L==="overview",className:`nc-set-tabbtn${L==="overview"?" on":""}`,onClick:()=>z("overview"),children:g("fleet-network-overview")})]});return f?G?c.jsxs("div",{className:"nc-set-tab nc-fleet-editor",children:[ot,L==="overview"?c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"nc-set-info",children:g("fleet-overview-help")}),c.jsx(nd,{token:e,targets:i,readonly:t,onPower:gt,onImport:lt})]}):c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"nc-set-info",children:g("fleet-manage-help")}),rt,c.jsxs("div",{className:"nc-set-row nc-fleet-backup-actions",children:[c.jsx("button",{className:"nc-btn ghost",disabled:Be||x,onClick:()=>{P(""),U(!0)},children:g("fleet-backup")}),c.jsx("small",{children:g("fleet-backup-help")})]}),c.jsxs("div",{className:"nc-fleet-section-head",children:[c.jsx("b",{children:g("fleet-cells")}),c.jsx("span",{className:"nc-fleet-head-actions",children:c.jsxs("button",{className:"nc-btn primary",disabled:Be||x||!a.engines.length,onClick:()=>{var ae;P(""),y({mode:"new",form:Mv((ae=a.engines[0])==null?void 0:ae.id)})},children:["+ ",g("add")]})})]}),a.cells.map(ae=>{const xe=me.has(ae.id),de=h.capabilities||[],Ne=ae.needsRepair===!0;return c.jsxs("div",{className:"nc-fleet-item",children:[c.jsxs("span",{children:[c.jsx("b",{children:ae.id}),c.jsxs("small",{children:[ae.engine," · ",Ne?c.jsx("span",{className:"nc-fleet-tag nc-fleet-tag-warn",children:g("fleet-cwd-needs-repair")}):ae.cwd,xe?` · ${g("service-active")}`:` · ${g("cell-off")}`]})]}),c.jsxs("span",{children:[xe&&de.includes("down")&&c.jsx("button",{className:"nc-btn ghost",disabled:Be||x,onClick:()=>ge(()=>sl(e,{cell:ae.id},ce)),children:g("stop")}),!xe&&de.includes("up")&&c.jsx("button",{className:"nc-btn primary",disabled:Be||x,onClick:()=>gt({cell:ae.id,id:ae.id,engine:ae.engine,model:ae.model,models:ae.models,permissionPolicies:ae.permissionPolicies,active:!1,boot:ae.boot}),children:g("start")}),xe&&de.includes("restart")&&c.jsx("button",{className:"nc-btn ghost",disabled:Be||x,onClick:()=>ge(()=>ho(e,ae.id,ce)),children:g("restart")}),Ne?c.jsx("button",{className:"nc-btn ghost",disabled:Be||x,onClick:()=>{P(""),j(ae)},children:g("fleet-cwd-repair")}):c.jsx("button",{className:"nc-btn ghost",disabled:Be||x,onClick:()=>{var Ee;P(""),y({mode:"edit",original:ae,form:{...ae,commands:{...ae.commands||{}},command:((Ee=ae.commands)==null?void 0:Ee[ae.engine])||""}})},children:g("edit")}),c.jsx("button",{className:"nc-btn danger",disabled:Be||x,onClick:()=>ge(async()=>{window.confirm(g("fleet-remove-cell").replace("{id}",ae.id))&&await qv(e,ae.id,!0,ce)}),children:"×"})]})]},ae.id)}),c.jsxs("div",{className:"nc-fleet-section-head",children:[c.jsx("b",{children:g("fleet-engines")}),c.jsxs("button",{className:"nc-btn primary",disabled:Be||x,onClick:()=>{P(""),S({mode:"new",form:SE()})},children:["+ ",g("add")]})]}),a.engines.map(ae=>{var de,Ne;const xe=Fe(ae);return c.jsxs("div",{className:"nc-fleet-item",children:[c.jsxs("span",{children:[c.jsx("b",{children:ae.label}),c.jsx("small",{children:ae.managed?`${ae.id} · ${ae.managed.client} / ${ae.managed.provider} · ${(de=ae.managedInfo)!=null&&de.configured?g("fleet-ready"):((Ne=ae.managedInfo)==null?void 0:Ne.reason)||g("fleet-not-ready")}`:`${ae.id} · ${ae.command}`}),xe&&c.jsxs("small",{children:[xe.envKey," · ",g(`fleet-credential-source-${xe.source||"missing"}`)]})]}),c.jsxs("span",{children:[xe&&c.jsx("button",{className:"nc-btn ghost",disabled:Be||x,onClick:()=>{P(""),$({envKey:xe.envKey,value:""})},children:xe.configured?g("fleet-credential-change"):g("fleet-credential-set")}),(xe==null?void 0:xe.source)==="local"&&c.jsx("button",{className:"nc-btn danger",disabled:Be||x,onClick:()=>Ve(xe),children:g("fleet-credential-forget")}),c.jsx("button",{className:"nc-btn ghost",disabled:Be||x,onClick:()=>{P(""),S({mode:"edit",original:ae,form:xE(ae)})},children:g("edit")}),c.jsx("button",{className:"nc-btn danger",disabled:Be||x,onClick:()=>ge(async()=>{window.confirm(g("fleet-remove-engine").replace("{id}",ae.id))&&await t0(e,ae.id,ce)}),children:"×"})]})]},ae.id)})]}),b&&c.jsx(gr,{onClose:()=>S(null),label:g("fleet-new-engine"),error:C,children:c.jsx(EE,{state:b,setState:S,busy:x,onSave:Me,catalog:a.managedCatalog||[]})}),w&&c.jsx(gr,{onClose:()=>y(null),label:g("fleet-new-cell"),error:C,children:c.jsx(NE,{token:e,route:ce,targets:i,location:ee,setLocation:q,state:w,setState:y,engines:a.engines,busy:x,onSave:Ze})}),E&&c.jsx(gr,{onClose:()=>j(null),label:g("fleet-cwd-repair-title").replace("{id}",E.id),error:"",children:c.jsx(PE,{token:e,route:ce,cell:E,busy:x,onSaved:async()=>{j(null),M(g("fleet-cwd-repaired")),await he()},onClose:()=>j(null)})}),A&&c.jsx("div",{className:"nc-set-note",children:A}),C&&c.jsx("div",{className:"nc-err",children:C}),D&&c.jsx(gr,{onClose:()=>U(!1),label:g("fleet-backup"),error:C,children:c.jsx(kE,{cells:a.cells,engines:a.engines,busy:x,canRestore:J,onRestore:dt,onClose:()=>U(!1)})}),V&&c.jsx(gr,{onClose:()=>$(null),label:g("fleet-credential-title"),error:C,children:c.jsxs("div",{className:"nc-fleet-form nc-credential-form",children:[c.jsx("b",{children:g("fleet-credential-title")}),c.jsx("small",{children:g("fleet-credential-help")}),c.jsxs("label",{className:"nc-field",children:[V.envKey,c.jsx("input",{type:"password",autoComplete:"off",spellCheck:"false",autoCapitalize:"none",value:V.value,onChange:ae=>$({...V,value:ae.target.value})})]}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{className:"nc-btn ghost",onClick:()=>$(null),children:g("cancel")}),c.jsx("button",{className:"nc-btn primary",disabled:x||!V.value,onClick:ct,children:g("save")})]})]})}),K&&c.jsx(gr,{onClose:()=>T(null),label:g("import-as-cell"),error:C,children:c.jsx(Tv,{token:e,route:K.route||ce,state:K,setState:T,busy:x,onSave:nt})}),H&&c.jsx(ol,{cell:H,token:e,route:Array.isArray(H.route)?H.route:ce,onConfirm:async ae=>{try{await ut(ae)}finally{await he()}},onClose:()=>te(null)})]}):c.jsxs("div",{className:"nc-set-tab",children:[ot,L==="overview"?c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"nc-set-info",children:g("fleet-overview-help")}),c.jsx(nd,{token:e,targets:i,readonly:t,onPower:gt,onImport:lt})]}):c.jsxs(c.Fragment,{children:[rt,c.jsxs("div",{className:"nc-set-info",children:[g(C?"fleet-editor-load-error":"fleet-editor-unavailable"),!C&&h.reason?` ${h.reason}`:""]})]}),C&&c.jsx("div",{className:"nc-err",children:C}),K&&c.jsx(gr,{onClose:()=>T(null),label:g("import-as-cell"),error:C,children:c.jsx(Tv,{token:e,route:K.route||ce,state:K,setState:T,busy:x,onSave:nt})}),H&&c.jsx(ol,{cell:H,token:e,route:Array.isArray(H.route)?H.route:ce,onConfirm:async ae=>{try{await ut(ae)}finally{await he()}},onClose:()=>te(null)})]}):c.jsxs("div",{className:"nc-set-tab",children:[ot,L==="overview"?c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"nc-set-info",children:g("fleet-overview-help")}),c.jsx(nd,{token:e,targets:i,readonly:t,onPower:gt,onImport:lt})]}):c.jsxs(c.Fragment,{children:[rt,c.jsx("div",{className:"nc-set-info",children:g("fleet-editor-loading")})]})]})}function DE({text:e}){const[t,i]=R.useState(!1),s=async()=>{try{await navigator.clipboard.writeText(e),i(!0),setTimeout(()=>i(!1),1500)}catch{}};return c.jsxs("div",{className:"nc-set-copyline",children:[c.jsx("code",{children:e}),c.jsxs("button",{type:"button",className:"nc-btn ghost",onClick:s,children:[c.jsx(et,{name:"copy",size:14})," ",g(t?"copied":"copy")]})]})}function LE({value:e}){const[t,i]=R.useState("");return R.useEffect(()=>{let s=!0;return Uk.toDataURL(e,{margin:1,width:220}).then(o=>{s&&i(o)}).catch(()=>{}),()=>{s=!1}},[e]),t?c.jsx("img",{src:t,width:"220",height:"220",alt:"NexusCrew pairing QR"}):null}function ME({token:e,nodes:t,roster:i,settings:s,readonly:o,refresh:a,refreshAliases:u}){const[h,p]=R.useState(null),[f,_]=R.useState(null),[b,S]=R.useState({}),[w,y]=R.useState(null),[x,k]=R.useState({ssh:"",sshPort:"",name:""}),[C,P]=R.useState(""),[A,M]=R.useState(""),[ee,q]=R.useState(""),[B,Z]=R.useState(!1),[H,te]=R.useState(null),[K,T]=R.useState(null),E=Date.now(),j=s&&s.deviceName||"",D=(t||[]).filter(G=>G&&G.direction==="outbound"&&G.name&&G.ssh),U=D.find(G=>G.name===C)||D[0]||null,re=D.find(G=>G.name===A)||D[0]||null,I=re?Hm(re.tunnel,E):null,V=re!=null&&re.shared?I!=null&&I.up?"share-local-active":"share-local-pending":I!=null&&I.up?"share-local-private":"share-local-private-down",$=[{key:"peer-group-hubs",rows:(t||[]).filter(G=>G.kind!=="transitive"&&G.relation!=="client")},{key:"peer-group-clients",rows:(t||[]).filter(G=>G.kind!=="transitive"&&G.relation==="client")},{key:"peer-group-routed",rows:(t||[]).filter(G=>G.kind==="transitive")}],L=async(G,J)=>{p(null),_(`${G}:${J}`);try{const ge=await hh(e,G,J);J==="test"&&S(Me=>({...Me,[G]:ge})),await a()}catch(ge){p(`${G}: ${String(ge.message||ge)}`)}_(null)},z=async G=>{p(null),_(`${G}:remove`);try{await p0(e,G),T(null),await a()}catch(J){p(`${G}: ${String(J.message||J)}`)}_(null)},oe=G=>te({name:G.name,direction:G.direction,label:G.label||G.name,ssh:G.ssh||"",sshPort:G.sshPort?String(G.sshPort):"",autostart:G.autostart===!0,visibility:G.visibility||"network",selected:[...G.selected||[]]}),ce=async()=>{if(!H||!ds(H.label)){p(g("err-label"));return}const G=H.direction==="inbound"?{label:H.label,visibility:H.visibility,selected:H.visibility==="selected"?H.selected:[]}:{label:H.label,ssh:H.ssh,autostart:H.autostart,...H.sshPort?{sshPort:Number(H.sshPort)}:{}};p(null),_(`${H.name}:edit`);try{await f0(e,H.name,G),te(null),await a()}catch(J){p(`${H.name}: ${String(J.message||J)}`)}_(null)},he=async G=>{if(re){p(null),_(`${re.name}:share`);try{await m0(e,re.name,G),await a()}catch(J){const ge=J!=null&&J.data&&typeof J.data.hint=="string"?J.data.hint:"";p(`${re.name}: ${String(J.message||J)}${ge?` — ${ge}`:""}`)}_(null)}},me=async()=>{if(p(null),U){const ge=$m({name:U.name,ssh:U.ssh,sshPort:U.sshPort||""});if(!ge.ok){p(g(ge.error));return}_("invite");try{y(await km(e,{ssh:ge.value.ssh,...ge.value.sshPort?{sshPort:ge.value.sshPort}:{}},[U.name]))}catch(Me){p(String(Me.message||Me))}_(null);return}const G=Zn(x.name||ee||j||"NexusCrew"),J=$m({name:G,ssh:x.ssh,sshPort:x.sshPort});if(!J.ok){p(g(J.error));return}_("invite");try{y(await km(e,{...ee||j?{label:ee||j}:{},name:G,ssh:J.value.ssh,...J.value.sshPort?{sshPort:J.value.sshPort}:{}}))}catch(ge){p(String(ge.message||ge))}_(null)};return c.jsxs("div",{className:"nc-set-tab",children:[c.jsx("div",{className:"nc-sheet-label",children:g("peer-inventory-title")}),(t||[]).length===0&&c.jsx("div",{className:"nc-empty",children:g("no-nodes")}),$.map(G=>G.rows.length>0&&c.jsxs("div",{className:"nc-peer-group",children:[c.jsxs("div",{className:"nc-peer-group-title",children:[g(G.key)," ",c.jsx("span",{children:G.rows.length})]}),G.rows.map(J=>{var Fe,ct;const ge=J.kind==="transitive",Me=ge?{up:J.stale!==!0,label:J.stale===!0?"peer-routed-stale":"peer-routed"}:Hm(J.tunnel,E),Ze=b[J.name],Be=J.actions||{};return c.jsxs("div",{className:`nc-set-node${ge?" routed":""}`,children:[c.jsxs("div",{className:"nc-set-node-head",children:[c.jsx("span",{className:`nc-dot ${Me.up?"on":""}`}),c.jsx("b",{children:J.label||J.name}),c.jsxs("small",{children:[J.name,ge?` · ${J.route.join(" → ")}`:J.direction==="outbound"?` · SSH ${J.ssh||""}`:` · ${g("node-connected-client")}`,(Fe=J.tunnel)!=null&&Fe.transport?` · ${J.tunnel.transport} ${g("transport-used")}`:""]}),c.jsxs("span",{className:`nc-set-tunnel${Me.up?" up":""}`,children:[g(Me.label),Me.since?` · ${Me.since}`:""]})]}),!ge&&J.direction==="inbound"&&c.jsx("div",{className:"nc-set-info",children:g(J.shared?"peer-shared":"peer-private")}),Be.visibility&&J.shared&&c.jsxs(c.Fragment,{children:[c.jsxs("select",{value:J.visibility||"network",disabled:o||!!f,onChange:async Ve=>{_(`${J.name}:visibility`);try{await Nm(e,J.name,Ve.target.value),await a()}catch(gt){p(String(gt.message||gt))}_(null)},children:[c.jsx("option",{value:"network",children:g("visibility-network")}),c.jsx("option",{value:"relay-only",children:g("visibility-relay")}),c.jsx("option",{value:"selected",children:g("visibility-selected")})]}),J.visibility==="selected"&&c.jsx("div",{className:"nc-set-row",children:(t||[]).filter(Ve=>Ve.name!==J.name&&Ve.nodeId).map(Ve=>{const gt=(J.selected||[]).includes(Ve.nodeId);return c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:gt,disabled:o||!!f,onChange:async ut=>{const lt=ut.target.checked?[...J.selected||[],Ve.nodeId]:(J.selected||[]).filter(nt=>nt!==Ve.nodeId);_(`${J.name}:visibility`);try{await Nm(e,J.name,"selected",lt),await a()}catch(nt){p(String(nt.message||nt))}_(null)}})," ",Ve.label||Ve.name]},Ve.nodeId)})})]}),c.jsxs("div",{className:"nc-set-node-actions",children:[Be.edit&&c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:o||!!f,onClick:()=>oe(J),children:g("edit")}),Be.test&&c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:!!f,onClick:()=>L(J.name,"test"),children:g("node-test")}),Be.disconnect&&Me.up&&c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:o||!!f,onClick:()=>L(J.name,"down"),children:g("tunnel-stop")}),Be.connect&&!Me.up&&c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:o||!!f,onClick:()=>L(J.name,"up"),children:g("tunnel-start")}),Be.restart&&c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:o||!!f,onClick:()=>L(J.name,"restart"),children:g("tunnel-restart")}),Be.remove&&c.jsxs("button",{type:"button",className:"nc-btn danger",disabled:o||!!f,title:g(o?"settings-readonly":"delete"),onClick:()=>T(J.name),children:[c.jsx(et,{name:"trash",size:14})," ",g("delete")]})]}),(H==null?void 0:H.name)===J.name&&c.jsxs("div",{className:"nc-set-form nc-node-editor",children:[c.jsxs("label",{className:"nc-field",children:[g("node-display-label"),c.jsx("input",{value:H.label,disabled:!!f,onChange:Ve=>te({...H,label:Ve.target.value})})]}),H.direction==="outbound"?c.jsxs(c.Fragment,{children:[c.jsxs("label",{className:"nc-field",children:[g("node-ssh-label"),c.jsx("input",{value:H.ssh,disabled:!!f,onChange:Ve=>te({...H,ssh:Ve.target.value})})]}),c.jsxs("label",{className:"nc-field",children:[g("node-ssh-port-label"),c.jsx("input",{inputMode:"numeric",value:H.sshPort,disabled:!!f,onChange:Ve=>te({...H,sshPort:Ve.target.value.replace(/[^0-9]/g,"").slice(0,5)})})]}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:H.autostart,disabled:!!f,onChange:Ve=>te({...H,autostart:Ve.target.checked})})," ",g("boot-persist")]})]}):c.jsxs("label",{className:"nc-field",children:[g("peer-visibility"),c.jsxs("select",{value:H.visibility,disabled:!!f,onChange:Ve=>te({...H,visibility:Ve.target.value}),children:[c.jsx("option",{value:"network",children:g("visibility-network")}),c.jsx("option",{value:"relay-only",children:g("visibility-relay")}),c.jsx("option",{value:"selected",children:g("visibility-selected")})]})]}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn primary",disabled:!!f,onClick:ce,children:g("save")}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:!!f,onClick:()=>te(null),children:g("cancel")})]})]}),K===J.name&&c.jsxs("div",{className:"nc-set-confirm",children:[c.jsx("b",{children:g("node-remove-confirm").replace("{name}",J.label||J.name)}),c.jsx("small",{children:g("node-remove-warning")}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn danger",disabled:!!f,onClick:()=>z(J.name),children:g("delete")}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:!!f,onClick:()=>T(null),children:g("cancel")})]})]}),((ct=J.health)==null?void 0:ct.detail)&&c.jsx("div",{className:`nc-set-test${J.health.status==="healthy"?" ok":J.health.status==="passive"?"":" ko"}`,children:J.health.detail}),Ze&&c.jsxs("div",{className:`nc-set-test${Ze.ok?" ok":" ko"}`,children:[Ze.result,Ze.detail?` — ${Ze.detail}`:""]})]},`${J.kind||"direct"}:${J.nodeId||J.name}`)})]},G.key)),(i||[]).some(G=>!G.direct&&G.instanceId)&&c.jsxs("div",{className:"nc-set-form",children:[c.jsx("div",{className:"nc-sheet-label",children:g("routed-node-aliases")}),c.jsx("small",{className:"nc-set-hint",children:g("routed-node-aliases-help")}),(i||[]).filter(G=>!G.direct&&G.instanceId).map(G=>c.jsxs("div",{className:"nc-set-node",children:[c.jsxs("div",{className:"nc-set-node-head",children:[c.jsx("span",{className:`nc-dot ${G.status==="up"?"on":""}`}),c.jsx("b",{children:G.label||G.name}),c.jsx("small",{children:(G.route||[]).join(" › ")})]}),c.jsxs("div",{className:"nc-set-node-actions",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:o||!!f,onClick:async()=>{const J=window.prompt(g("node-local-alias"),G.alias||"");if(J!=null){if(J.trim()&&!ds(J.normalize("NFC"))){p(g("err-label"));return}_(`${G.instanceId}:alias`),p(null);try{J.trim()?await h0(e,G.instanceId,J):await Em(e,G.instanceId),u()}catch(ge){p(String(ge.message||ge))}_(null)}},children:g("alias-on-device")}),G.alias&&c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:o||!!f,onClick:async()=>{_(`${G.instanceId}:alias-reset`),p(null);try{await Em(e,G.instanceId),u()}catch(J){p(String(J.message||J))}_(null)},children:g("reset-alias")})]})]},G.instanceId))]}),re&&c.jsxs("div",{className:"nc-set-form nc-local-share",children:[c.jsx("div",{className:"nc-sheet-label",children:g("share-local-heading")}),D.length>1&&c.jsxs("label",{className:"nc-field",children:[g("share-local-hub"),c.jsx("select",{value:re.name,disabled:o||!!f,onChange:G=>M(G.target.value),children:D.map(G=>c.jsx("option",{value:G.name,children:G.label||G.name},G.name))})]}),c.jsxs("label",{className:"nc-check nc-node-share",children:[c.jsx("input",{type:"checkbox",checked:re.shared===!0,disabled:o||!!f,onChange:G=>he(G.target.checked)}),c.jsxs("span",{children:[c.jsx("b",{children:g("share-local-through").replace("{device}",j||g("local")).replace("{hub}",re.label||re.name)}),c.jsx("small",{children:g(re.shared?"share-node-on-desc":"share-node-off-desc")})]})]}),c.jsx("div",{className:`nc-set-test${re.shared&&(I!=null&&I.up)?" ok":I!=null&&I.up?"":" ko"}`,children:g(V)}),!(I!=null&&I.up)&&c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:o||!!f,onClick:()=>he(re.shared===!0),children:g("share-local-reconnect")})]}),c.jsx(Py,{token:e,deviceDefault:j,localNodeId:s&&s.nodeId||"",localNameDefault:s&&s.localName||"",readonly:o,onSuccess:a}),c.jsxs("div",{className:"nc-set-form",children:[c.jsx("div",{className:"nc-sheet-label",children:g("invite-node")}),c.jsx("small",{className:"nc-set-hint",children:g(U?"invite-network-hint":"invite-v2-hint")}),U?c.jsxs(c.Fragment,{children:[D.length>1&&c.jsxs("label",{className:"nc-field",children:[g("invite-network-label"),c.jsx("select",{value:U.name,disabled:o||!!f,onChange:G=>{P(G.target.value),y(null)},children:D.map(G=>c.jsx("option",{value:G.name,children:G.label||G.name},G.name))})]}),c.jsxs("div",{className:"nc-set-info nc-invite-endpoint",children:[g("invite-network-via"),": ",c.jsx("b",{children:U.label||U.name})," · ",U.ssh,U.sshPort?`:${U.sshPort}`:""]}),c.jsx("small",{className:"nc-set-hint",children:g("invite-network-route")})]}):c.jsxs("label",{className:"nc-field",children:[g("invite-endpoint-label"),c.jsx("input",{placeholder:"user@host",value:x.ssh,disabled:o,onChange:G=>k({...x,ssh:G.target.value})}),c.jsx("small",{className:"nc-set-hint",children:g("invite-endpoint-needed")})]}),!U&&B&&c.jsxs("div",{className:"nc-invite-advanced",children:[c.jsxs("label",{className:"nc-field",children:[g("node-ssh-port-label"),c.jsx("input",{inputMode:"numeric",placeholder:"22",value:x.sshPort,disabled:o,onChange:G=>k({...x,sshPort:G.target.value.replace(/[^0-9]/g,"").slice(0,5)})})]}),c.jsxs("label",{className:"nc-field",children:[g("device-name-label"),c.jsx("input",{placeholder:j||"NexusCrew",value:ee,disabled:o,onChange:G=>q(G.target.value)}),c.jsx("small",{className:"nc-set-hint",children:g("device-name-hint")})]}),c.jsxs("label",{className:"nc-field",children:[g("node-name-label"),c.jsx("input",{placeholder:Zn(ee||j||g("node-name-ph")),value:x.name,disabled:o,onChange:G=>k({...x,name:G.target.value?Zn(G.target.value):""})})]})]}),c.jsxs("div",{className:"nc-set-row nc-invite-actions",children:[c.jsx("button",{type:"button",className:"nc-btn primary",disabled:o||!!f||!U&&!x.ssh.trim(),onClick:me,children:g("create-pairing-link")}),!U&&c.jsxs("button",{type:"button",className:"nc-btn ghost",disabled:!!f,onClick:()=>Z(G=>!G),children:[B?"▾":"▸"," ",g("pair-advanced")]})]}),w&&c.jsxs(c.Fragment,{children:[c.jsx(LE,{value:w.pairingUrl}),c.jsx(DE,{text:w.pairingUrl}),c.jsx("div",{className:"nc-set-info",children:g("invite-next-steps")})]})]}),h&&c.jsx("div",{className:"nc-err",children:h})]})}function TE({token:e,readonly:t}){const[i,s]=R.useState("idle"),[o,a]=R.useState(!1),[u,h]=R.useState(null);R.useEffect(()=>{let f=!1;return iE().then(_=>{f||s(_)}),()=>{f=!0}},[]);const p=async()=>{h(null),a(!0);try{i==="subscribed"?(await rE(e),s("idle")):(await nE(e),s("subscribed"))}catch(f){const _=String(f.message||f);_==="push-denied"?(s("denied"),h(g("push-denied"))):_==="push-unsupported"?(s("unsupported"),h(g("push-unsupported"))):h(_)}a(!1)};return i==="unsupported"?c.jsx("div",{className:"nc-set-info",children:g("push-unsupported")}):c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:t||o||i==="denied",title:t?g("settings-readonly"):"",onClick:p,children:g(i==="subscribed"?"push-disable":"push-enable")}),c.jsx("span",{className:"nc-set-info",children:g(i==="subscribed"?"push-on":i==="denied"?"push-denied":"push-off")})]}),u&&c.jsx("div",{className:"nc-err",children:u})]})}function AE({token:e,roster:t=[],readonly:i}){const s=[{key:"local",route:[],label:g("diagnostics-local"),status:"up"},...t.filter(D=>Array.isArray(D.route)&&D.route.length).map(D=>({key:D.route.join("/"),route:D.route,label:D.label||D.name,status:D.status}))],[o,a]=R.useState("local"),u=s.find(D=>D.key===o)||s[0],[h,p]=R.useState(null),[f,_]=R.useState([]),[b,S]=R.useState(""),[w,y]=R.useState(!1),[x,k]=R.useState(!0),[C,P]=R.useState(900),[A,M]=R.useState("all"),[ee,q]=R.useState(""),B=R.useRef(0),Z=R.useRef(null),H=R.useCallback(async()=>{try{const D=await y0(e,u.route);if(p(D),S(""),!w){const U=await w0(e,{after:B.current,limit:200},u.route);Array.isArray(U.records)&&U.records.length&&(B.current=U.cursor,_(re=>[...re,...U.records].slice(-500)))}}catch(D){S(D&&D.status===404?g("diagnostics-unsupported"):String(D.message||D))}},[e,u.key,w]);R.useEffect(()=>{B.current=0,_([]),p(null),S("")},[u.key]),R.useEffect(()=>{let D=null,U=!0;const re=()=>{!U||document.visibilityState==="hidden"||H()},I=()=>{D&&clearInterval(D),D=document.visibilityState==="hidden"?null:setInterval(re,2e3),document.visibilityState!=="hidden"&&re()};return document.addEventListener("visibilitychange",I),I(),()=>{U=!1,D&&clearInterval(D),document.removeEventListener("visibilitychange",I)}},[H]),R.useEffect(()=>{x&&Z.current&&(Z.current.scrollTop=Z.current.scrollHeight)},[f,x]);const te=f.filter(D=>(A==="all"||D.level===A)&&(!ee||String(D.component||"").toLowerCase().includes(ee.toLowerCase()))),K=te.map(D=>`${D.ts} ${D.level.toUpperCase()} ${D.component} ${D.code} ${D.message}${Object.keys(D.meta||{}).length?` ${JSON.stringify(D.meta)}`:""}`).join(`
92
92
  `),T=async D=>{S("");try{const U=await D();p(U),await H()}catch(U){S(String(U.message||U))}},E=async()=>{try{await navigator.clipboard.writeText(K)}catch{S(g("copy-manual"))}},j=()=>{try{const D=new Blob([`${JSON.stringify(te,null,2)}
93
- `],{type:"application/json"}),U=URL.createObjectURL(D),re=document.createElement("a");re.href=U,re.download="nexuscrew-diagnostics.json",re.click(),URL.revokeObjectURL(U)}catch{S(g("diagnostics-export-failed"))}};return c.jsxs("div",{className:"nc-set-tab nc-diagnostics",children:[c.jsxs("label",{className:"nc-field",children:[g("diagnostics-target"),c.jsx("select",{value:u.key,onChange:D=>a(D.target.value),children:s.map(D=>c.jsxs("option",{value:D.key,children:[D.label,D.status!=="up"?` · ${D.status}`:""]},D.key))})]}),c.jsxs("div",{className:"nc-set-form nc-diag-controls",children:[c.jsx("div",{className:"nc-sheet-label",children:g("diagnostics-verbose")}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("select",{"aria-label":g("diagnostics-duration"),value:C,onChange:D=>P(Number(D.target.value)),disabled:i,children:[300,900,1800,3600].map(D=>c.jsxs("option",{value:D,children:[D/60," min"]},D))}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:i||(h==null?void 0:h.verbose)===!0,onClick:()=>T(()=>Rm(e,!0,C,u.route)),children:g("enable")}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:i||(h==null?void 0:h.verbose)!==!0,onClick:()=>T(()=>Rm(e,!1,C,u.route)),children:g("stop")})]}),c.jsx("small",{className:"nc-set-hint",children:h!=null&&h.verbose?g("diagnostics-expires").replace("{time}",new Date(h.expiresAt).toLocaleTimeString()):g("diagnostics-off")})]}),c.jsxs("div",{className:"nc-diag-filters",children:[c.jsxs("select",{"aria-label":g("diagnostics-level"),value:A,onChange:D=>M(D.target.value),children:[c.jsx("option",{value:"all",children:g("diagnostics-all-levels")}),["debug","info","warn","error"].map(D=>c.jsx("option",{value:D,children:D},D))]}),c.jsx("input",{"aria-label":g("diagnostics-component"),placeholder:g("diagnostics-component"),value:ee,onChange:D=>q(D.target.value)})]}),c.jsxs("div",{className:"nc-set-row",children:[c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:w,onChange:D=>y(D.target.checked)})," ",g("diagnostics-pause")]}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:x,onChange:D=>k(D.target.checked)})," ",g("diagnostics-autoscroll")]})]}),c.jsx("pre",{ref:Z,className:"nc-diag-log","aria-label":g("diagnostics-log"),children:K||g("diagnostics-empty")}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:E,children:g("copy")}),c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:j,children:g("diagnostics-export")}),c.jsx("button",{type:"button",className:"nc-btn danger",disabled:i,onClick:()=>{window.confirm(g("diagnostics-clear-confirm"))&&T(async()=>{const D=await S0(e,u.route);return B.current=0,_([]),D})},children:g("diagnostics-clear")})]}),b&&c.jsx("div",{className:"nc-err",children:b})]})}function IE({token:e,settings:t,readonly:i,refresh:s}){const[o,a]=R.useState(null),[u,h]=R.useState(null),[p,f]=R.useState(!1),[_,b]=R.useState(!1),[S,w]=R.useState(null),[y,x]=R.useState(!0);R.useEffect(()=>{w(t&&t.update||null),x(!t||t.autoUpdate!==!1)},[t]);const k=async()=>{a(null),h(null),b(!0);try{const B=await c0(e);h(B.note||"ok"),f(!1)}catch(B){a(String(B.message||B))}b(!1)},C=async()=>{a(null),h(null),b(!0);try{const B=await g0(e);h(`${B.note||"ok"}${B.target?` (${B.target})`:""}`)}catch(B){a(String(B.message||B))}b(!1)},P=async B=>{a(null),h(null),b(!0);try{await Gv(e,{autoUpdate:B}),x(B),s&&await s()}catch(Z){a(String(Z.message||Z))}b(!1)},A=async()=>{a(null),h(null),b(!0);try{w(await v0(e))}catch(B){a(String(B.message||B))}b(!1)},M=async()=>{a(null),h(null),b(!0);try{const B=await _0(e);w(B),h(g("npm-update-restarting"))}catch(B){a(String(B.message||B))}b(!1)},ee=()=>{window.confirm(g("composer-clear-confirm"))&&(a(null),GC()?(window.dispatchEvent(new Event(Xd)),h(g("composer-clear-done"))):a(g("composer-clear-failed")))},q=t&&t.service;return c.jsxs("div",{className:"nc-set-tab",children:[t&&c.jsxs("div",{className:"nc-set-info",children:["v",t.version," · ",t.platform," · :",t.port,c.jsx("br",{}),q&&q.installed?g("service-installed"):g("service-missing"),q&&q.installed?` · ${q.active?g("service-active"):g("service-inactive")}`:"",q?` · boot ${q.boot?"on":"off"}`:""]}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:i||_,title:i?g("settings-readonly"):"",onClick:()=>{a(null),h(null),f(!0)},children:g("token-rotate")}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:i||_,title:i?g("settings-readonly"):"",onClick:C,children:g("service-regenerate")})]}),c.jsxs("div",{className:"nc-set-form nc-update-settings",children:[c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:y,disabled:i||_||S&&!S.supported,onChange:B=>P(B.target.checked)}),c.jsxs("span",{children:[c.jsx("b",{children:g("npm-auto-update")}),c.jsx("small",{children:g("npm-auto-update-help")})]})]}),S&&c.jsxs("div",{className:"nc-set-info",children:[g("npm-update-current")," v",S.current,S.latest?` · ${g("npm-update-latest")} v${S.latest}`:""," · ",g(`npm-update-${S.supported?S.phase:"unsupported"}`)]}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:i||_||!S||!S.supported,onClick:A,children:g("npm-update-check")}),S&&S.available&&c.jsx("button",{type:"button",className:"nc-btn primary",disabled:i||_||S.phase==="installing",onClick:M,children:g("npm-update-install")})]}),S&&S.lastError&&c.jsx("div",{className:"nc-err",children:S.lastError})]}),c.jsx(TE,{token:e,readonly:i}),c.jsxs("div",{className:"nc-set-form",children:[c.jsx("div",{className:"nc-sheet-label",children:g("composer-clear-data")}),c.jsx("small",{className:"nc-set-hint",children:g("composer-clear-data-help")}),c.jsx("div",{className:"nc-set-row",children:c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:ee,children:g("composer-clear-data")})})]}),p&&c.jsxs("div",{className:"nc-set-confirm",children:[c.jsx("div",{children:g("token-rotate-explain")}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:_,onClick:()=>f(!1),children:g("cancel")}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:_,onClick:k,children:g("confirm")})]})]}),u&&c.jsx("div",{className:"nc-set-note",children:u}),o&&c.jsx("div",{className:"nc-err",children:o})]})}function BE({token:e,onClose:t,initialTab:i="nodes",initialLocation:s="",startNewCell:o=!1}){ji();const[a,u]=R.useState(i),[h,p]=R.useState(null),[f,_]=R.useState([]),[b,S]=R.useState(!1),[w,y]=R.useState(null),[x,k]=R.useState(0),C=ph(e,!0,x),P=R.useCallback(async()=>{try{const A=await Yv(e);p(A),y(null)}catch(A){y(String(A.message||A))}try{const A=await a0(e);_(A.peers||[])}catch(A){y(String(A.message||A))}},[e]);return R.useEffect(()=>{P();const A=setInterval(P,5e3);return()=>clearInterval(A)},[P]),R.useEffect(()=>{let A=!1;return Vt("/api/config",e).then(M=>M.json()).then(M=>{A||S(!!M.readonlyDefault)}).catch(()=>{}),()=>{A=!0}},[e]),c.jsx("div",{className:"nc-set-overlay",onClick:t,children:c.jsxs("div",{className:"nc-set-panel",onClick:A=>A.stopPropagation(),children:[c.jsxs("div",{className:"nc-set-head",children:[c.jsx(et,{name:"gear",size:18}),c.jsx("b",{children:g("settings")}),c.jsx("button",{type:"button",className:"nc-set-close",onClick:t,title:g("close"),children:c.jsx(et,{name:"x",size:18})})]}),b&&c.jsx("div",{className:"nc-set-readonly",children:g("settings-readonly")}),w&&c.jsx("div",{className:"nc-err",children:w}),c.jsx("div",{className:"nc-set-tabs",children:["nodes","fleet","diagnostics","system"].map(A=>c.jsx("button",{type:"button",className:`nc-set-tabbtn${a===A?" on":""}`,onClick:()=>u(A),children:g(`tab-${A}`)},A))}),c.jsxs("div",{className:"nc-set-body",children:[a==="nodes"&&c.jsx(ME,{token:e,nodes:f,roster:C,settings:h,readonly:b,refresh:P,refreshAliases:()=>k(A=>A+1)}),a==="fleet"&&c.jsx(jE,{token:e,readonly:b,startNewCell:o,initialLocation:s,targets:C.map(A=>({route:A.route,label:A.label||A.name,status:A.status}))}),a==="diagnostics"&&c.jsx(AE,{token:e,roster:C,readonly:b}),a==="system"&&c.jsx(IE,{token:e,settings:h,readonly:b,refresh:P})]})]})})}function OE({token:e,initialPair:t,deviceDefault:i="",localNodeId:s="",localNameDefault:o="",onPairDone:a,onDone:u}){ji();const[h,p]=R.useState(t?"pair":"welcome"),[f,_]=R.useState(!1),[b,S]=R.useState(null),w=async()=>{_(!0),S(null);try{await Gv(e,{wizardDone:!0}),a&&a(),u()}catch(y){S(String(y.message||y)),_(!1)}};return c.jsx("div",{className:"nc-wiz-overlay",children:c.jsxs("div",{className:"nc-wiz",children:[c.jsxs("div",{className:"nc-wiz-head",children:[c.jsx("b",{children:g("wizard-title")}),c.jsx("small",{children:g("hydra-simple")})]}),h==="welcome"&&c.jsxs("div",{className:"nc-wiz-body",children:[c.jsx("div",{className:"nc-wiz-done",children:g("local-ready")}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{className:"nc-btn ghost",disabled:f,onClick:w,children:g("local-only")}),c.jsx("button",{className:"nc-btn primary",disabled:f,onClick:()=>p("pair"),children:g("add-node")})]})]}),h==="pair"&&c.jsxs("div",{className:"nc-wiz-body",children:[c.jsx(Py,{token:e,initial:t||"",autoStart:!!t,deviceDefault:i,localNodeId:s,localNameDefault:o,onBusyChange:_,onSuccess:async()=>{a&&a(),p("done")}}),c.jsx("div",{className:"nc-sheet-actions",children:c.jsx("button",{className:"nc-btn ghost",disabled:f,onClick:()=>{a&&a(),p("welcome")},children:g("back")})})]}),h==="done"&&c.jsxs("div",{className:"nc-wiz-body",children:[c.jsx("div",{className:"nc-wiz-done",children:g("node-connected")}),c.jsx("div",{className:"nc-sheet-actions",children:c.jsx("button",{className:"nc-btn primary",disabled:f,onClick:w,children:g("finish")})})]}),b&&c.jsx("div",{className:"nc-err",children:b})]})})}function zE(e,t){if(typeof EventSource>"u"||!e)return()=>{};const i=new EventSource(`/api/events?token=${encodeURIComponent(e)}`);return i.onmessage=s=>{try{const o=JSON.parse(s.data);o&&typeof o=="object"&&typeof o.type=="string"&&t(o)}catch{}},()=>{try{i.close()}catch{}}}const FE=6e3,$E=12e3;function HE({n:e,onClose:t}){return c.jsxs("div",{className:`nc-ntf-toast${e.urgency==="high"?" high":""}`,role:"status","aria-live":"polite",children:[c.jsxs("div",{className:"nc-ntf-toast-txt",children:[c.jsx("b",{children:e.title}),e.body&&c.jsx("small",{children:e.body}),e.session&&c.jsx("span",{className:"nc-ntf-from",children:e.session})]}),c.jsx("button",{type:"button",className:"nc-ntf-x",onClick:t,title:g("close"),children:c.jsx(et,{name:"x",size:14})})]})}function WE({ask:e,token:t,onAnswered:i}){const[s,o]=R.useState(""),[a,u]=R.useState(!1),[h,p]=R.useState(null),f=async _=>{const b=String(_||"").trim();if(!(!b||a)){p(null),u(!0);try{await x0(t,e.id,b),i(e.id)}catch(S){p(String(S.message||S))}u(!1)}};return c.jsxs("div",{className:"nc-ask-card",children:[c.jsxs("div",{className:"nc-ask-head",children:[c.jsx("span",{className:"nc-ntf-from",children:e.session}),c.jsxs("code",{className:"nc-ask-id",children:["#",e.id]})]}),c.jsx("div",{className:"nc-ask-q",children:e.question}),Array.isArray(e.options)&&e.options.length>0&&c.jsx("div",{className:"nc-ask-opts",children:e.options.map(_=>c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:a,onClick:()=>f(_),children:_},_))}),c.jsxs("div",{className:"nc-ask-reply",children:[c.jsx("textarea",{rows:2,placeholder:g("ask-reply-ph"),value:s,disabled:a,onChange:_=>o(_.target.value)}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:a||!s.trim(),onClick:()=>f(s),children:g("send")})]}),h&&c.jsx("div",{className:"nc-err",children:h})]})}function UE({token:e}){ji();const[t,i]=R.useState([]),[s,o]=R.useState([]),[a,u]=R.useState(()=>{try{return/(?:^|[#&])ask=/.test(location.hash)}catch{return!1}}),h=R.useRef(0),p=R.useCallback(b=>{i(S=>S.filter(w=>w.key!==b))},[]),f=R.useCallback(b=>{const S=`t${h.current+=1}`;i(w=>[...w.slice(-3),{...b,key:S}]),setTimeout(()=>p(S),b.urgency==="high"?$E:FE)},[p]);R.useEffect(()=>{if(!e)return;let b=!1;b0(e).then(w=>{b||o(w.asks||[])}).catch(()=>{});const S=zE(e,w=>{w.type==="notify"?f(w):w.type==="ask"&&w.ask&&w.ask.id?o(y=>y.some(x=>x.id===w.ask.id)?y:[...y,w.ask]):w.type==="ask-answered"&&w.id&&o(y=>y.filter(x=>x.id!==w.id))});return()=>{b=!0,S()}},[e,f]);const _=b=>o(S=>S.filter(w=>w.id!==b));return c.jsxs(c.Fragment,{children:[t.length>0&&c.jsx("div",{className:"nc-ntf-toasts",children:t.map(b=>c.jsx(HE,{n:b,onClose:()=>p(b.key)},b.key))}),s.length>0&&!a&&c.jsxs("button",{type:"button",className:"nc-ask-badge",onClick:()=>u(!0),title:g("asks-title"),children:["? ",c.jsx("span",{className:"nc-ask-count",children:s.length})]}),s.length>0&&a&&c.jsxs("div",{className:"nc-ask-panel",children:[c.jsxs("div",{className:"nc-ask-panel-head",children:[c.jsx("b",{children:g("asks-title")}),c.jsx("span",{className:"nc-ask-count",children:s.length}),c.jsx("button",{type:"button",className:"nc-ntf-x",onClick:()=>u(!1),title:g("close"),children:c.jsx(et,{name:"x",size:16})})]}),c.jsx("div",{className:"nc-ask-panel-body",children:s.map(b=>c.jsx(WE,{ask:b,token:e,onAnswered:_},b.id))})]})]})}const rd=e=>ln(Cr(e)).length===0,sd=e=>Array.isArray(e)?e.join("/"):"";function KE(e){const t=new Set,i=[];for(const s of Array.isArray(e)?e:[])!s||!Hi.test(String(s.instanceId||""))||!Array.isArray(s.route)||!s.route.length||t.has(s.instanceId)||(t.add(s.instanceId),i.push({instanceId:s.instanceId,route:[...s.route],label:String(s.label||s.name||s.route.join(" › ")),status:s.status||"offline"}));return i.sort((s,o)=>s.label.localeCompare(o.label))}function uo(e){return{instanceId:e.ownerId,route:e.ownerRoute||[],label:e.ownerLabel,status:e.available===!1?"offline":"up"}}function vr(e,t,i,s=!1,o=!0){const a=t.instanceId;return{...e,id:ls(s?null:a,e.name),ownerId:a,ownerRoute:s?[]:[...t.route],ownerLabel:s?"Local":t.label,local:s,available:o,ownerTopology:Array.isArray(i)?i:[],layout:Qd(e.layout,a,i)}}function qE(e,t,i,s,o=[]){const[a,u]=R.useState([]),[h,p]=R.useState(!1),[f,_]=R.useState("idle"),[b,S]=R.useState(""),[w,y]=R.useState(""),x=R.useRef([]),k=R.useRef([]),C=R.useRef(""),P=R.useRef(t),A=R.useRef(i),M=R.useRef(!1),ee=R.useRef(!0),q=R.useRef(""),B=R.useMemo(()=>KE(o),[o]),Z=B.map($=>`${$.instanceId}:${sd($.route)}:${$.status}:${$.label}`).join("|");k.current=B,A.current=i,P.current=t,x.current=a;const H=R.useCallback($=>Jd($.layout,C.current,k.current),[]),te=R.useCallback(($,L=!0,z=P.current)=>{const oe=Jg($,Ya()),ce=x.current.some(me=>me.id===z);x.current=oe,u(oe);const he=oe.find(me=>me.id===z);if(L&&he){ee.current=!0;const me=H(he);s(me),he.local&&yk(he.name,me)}else ce&&!he&&(ee.current=!0,s(as()),S("deck non più condiviso dal nodo owner"));mk(oe.filter(me=>me.local).map(me=>me.name))},[s,H]),K=R.useCallback(async $=>{let L=await za(e);const z=L.decks.find(oe=>oe.name==="main");if(L.decks.length===1&&z&&z.revision===0&&rd(z.layout)){const oe=pk(),ce=Cr(Zd("main")||as());if(!rd(ce)){const he=Qd(ce,$,[]);L.decks[0]=await Fa(e,"main",he,0,[])}for(const he of oe.filter(me=>me!=="main"))try{let me=await Pm(e,he,[]);const G=Cr(Zd(he)||as());rd(G)||(me=await Fa(e,he,Qd(G,$,[]),me.revision,[]))}catch{}L=await za(e)}return L},[e]),T=R.useCallback(async({migrate:$=!1}={})=>{const L=await dh(e,[]),z=Hi.test(String(L.instanceId||""))?L.instanceId:"";if(!z)throw new Error("instanceId locale non disponibile");C.current=z,y(z);const[oe,ce]=await Promise.all([$?K(z):za(e),Cm(e,[]).catch(()=>({nodes:[]}))]),he={instanceId:z,route:[],label:"Local"},me=oe.decks.map(J=>vr(J,he,ce.nodes,!0,!0)),G=x.current;return await Promise.all(k.current.map(async J=>{if(J.status!=="up"){me.push(...G.filter(ge=>!ge.local&&ge.ownerId===J.instanceId).map(ge=>({...ge,ownerRoute:[...J.route],ownerLabel:J.label,available:!1})));return}try{const[ge,Me]=await Promise.all([za(e,J.route),Cm(e,J.route).catch(()=>({nodes:[]}))]);me.push(...ge.decks.map(Ze=>vr(Ze,J,Me.nodes,!1,!0)))}catch{me.push(...G.filter(Me=>!Me.local&&Me.ownerId===J.instanceId).map(Me=>({...Me,ownerRoute:[...J.route],ownerLabel:J.label,available:!1})))}})),me.sort((J,ge)=>J.local===ge.local?J.ownerLabel.localeCompare(ge.ownerLabel)||(J.name==="main"?-1:ge.name==="main"?1:J.name.localeCompare(ge.name)):J.local?-1:1),me},[e,K,Z]);R.useEffect(()=>{if(!e)return;let $=!1;const L=q.current!==e;return q.current=e,T({migrate:L}).then(z=>{if($)return;const oe=x.current.find(me=>me.id===P.current),ce=z.find(me=>me.id===P.current),he=ce&&(!oe||ce.revision>oe.revision||oe.available!==ce.available||sd(oe.ownerRoute)!==sd(ce.ownerRoute));te(z,L||!!he&&!M.current),p(!0),ce&&S("")}).catch(z=>{$||(p(!0),S(String(z.message||z)))}),()=>{$=!0}},[te,T,Z,e]);const E=R.useCallback(async($=P.current)=>{if(!h||!M.current)return!0;const L=x.current.find(z=>z.id===$);if(!L)return S(`deck inesistente: ${$}`),!1;if(L.available===!1)return S(`nodo owner offline: ${L.ownerLabel}`),!1;_("saving");try{const z=Gg(Cr(A.current),L.ownerId,L.ownerTopology),oe=await Fa(e,L.name,z,L.revision,L.ownerRoute),ce=vr(oe,uo(L),L.ownerTopology,L.local,!0);return te(x.current.map(he=>he.id===$?ce:he),!1),M.current=!1,_("saved"),S(""),setTimeout(()=>_("idle"),1500),!0}catch(z){if(_("error"),S(String(z.message||z)),z.status===409&&z.data&&z.data.current){const oe=x.current.find(he=>he.id===$),ce=vr(z.data.current,uo(oe),oe.ownerTopology,oe.local,!0);te(x.current.map(he=>he.id===$?ce:he),!1)}return!1}},[h,e,te]);return R.useEffect(()=>{if(!h)return;if(ee.current){ee.current=!1;return}M.current=!0,_("saving");const $=setTimeout(E,650);return()=>clearTimeout($)},[i,h,E]),R.useEffect(()=>{if(!h)return;const $=setInterval(async()=>{try{const L=await T(),z=x.current.find(he=>he.id===P.current),oe=L.find(he=>he.id===P.current),ce=oe&&(!z||oe.revision>z.revision||z.available!==oe.available);te(L,ce&&!M.current)}catch{}},5e3);return()=>clearInterval($)},[h,T,te]),{decks:a,records:a,localNodeId:w,ready:h,saveState:f,error:b,setError:S,saveNow:E,select:async $=>{if(M.current&&!await E(P.current))throw new Error("salvataggio del deck corrente fallito: cambio annullato");const L=x.current.find(z=>z.id===$);if(!L)throw new Error(`deck inesistente: ${$}`);return M.current=!1,ee.current=!0,H(L)},add:async($,L=null)=>{const z=L===Sn?x.current.find(he=>he.local):L?x.current.find(he=>he.ownerId===L):null;if(!z||z.available===!1)throw new Error("nodo owner non disponibile");const oe=await Pm(e,$,z.ownerRoute),ce=vr(oe,uo(z),z.ownerTopology,z.local,!0);return te([...x.current,ce],!1),ce},rename:async($,L)=>{const z=x.current.find(G=>G.id===$);if(!z)throw new Error("deck inesistente");if($===P.current&&M.current&&!await E($))throw new Error(`salvataggio di "${z.name}" fallito: rinomina annullata`);const oe=x.current.find(G=>G.id===$);if(!oe)throw new Error("deck inesistente");const ce=await C0(e,oe.name,L,oe.revision,oe.ownerRoute),he=vr(ce,uo(oe),oe.ownerTopology,oe.local,!0),me=oe.local?Sn:oe.ownerId;return Mu(vk(Ya(),me,$,he.id)),te(x.current.map(G=>G.id===$?he:G),!1),he},remove:async $=>{const L=x.current.find(oe=>oe.id===$);if(!L)throw new Error("deck inesistente");$===P.current&&(M.current=!1),await k0(e,L.name,L.revision,L.ownerRoute);const z=L.local?Sn:L.ownerId;Mu(_k(Ya(),z,$)),te(x.current.filter(oe=>oe.id!==$),!1)},reorder:($,L)=>{const z=x.current.find(J=>J.id===$),oe=x.current.find(J=>J.id===L);if(!z||!oe)return!1;const ce=z.local?Sn:z.ownerId,he=oe.local?Sn:oe.ownerId;if(ce!==he)return!1;const me=x.current.filter(J=>(J.local?Sn:J.ownerId)===ce).map(J=>J.id),G=Mu(gk(Ya(),ce,$,L,me));return te(Jg(x.current,G),!1),!0},addTileTo:async($,L)=>{const z=x.current.find(J=>J.id===$);if(!z)throw new Error("deck inesistente");if(z.available===!1)throw new Error(`nodo owner offline: ${z.ownerLabel}`);const oe=yy(L,C.current,k.current);if(!oe)throw new Error("riferimento sessione non valido");const ce=H(z),he=Gg(f_(ce,oe),z.ownerId,z.ownerTopology),me=await Fa(e,z.name,he,z.revision,z.ownerRoute),G=vr(me,uo(z),z.ownerTopology,z.local,!0);return te(x.current.map(J=>J.id===$?G:J),!1),G},localMainId:ls(null,"main"),parseDeckId:jh}}const VE="/sw.js",th="nc-sw-update";let ih=null,nh=!1,rh=null,zy=Object.freeze({needed:!1,kind:null,version:""}),Fy=!1;function $y(){typeof window<"u"&&window.dispatchEvent(new Event(th))}function Hy(){const e=rh||(nh?{kind:"reload",version:""}:null);zy=Object.freeze({needed:!!e,kind:(e==null?void 0:e.kind)||null,version:(e==null?void 0:e.version)||""})}function Av(){return zy}function YE(e){return typeof window>"u"?()=>{}:(window.addEventListener(th,e),()=>window.removeEventListener(th,e))}function Ah(e){nh!==e&&(nh=e,Hy(),$y())}function XE(e,t,i){let s=null;e&&t&&e!==t?s={kind:"install",version:e}:t&&i&&t!==i&&(s={kind:"reload",version:t});const o=JSON.stringify(s)===JSON.stringify(rh);rh=s,Hy(),o||$y()}function GE(e){e.addEventListener("statechange",()=>{e.state==="installed"&&navigator.serviceWorker.controller&&Ah(!0)})}function QE(){typeof navigator>"u"||!("serviceWorker"in navigator)||(navigator.serviceWorker.addEventListener("controllerchange",()=>{Fy&&location.reload()}),navigator.serviceWorker.register(VE).then(e=>{ih=e,e.waiting&&Ah(!0),e.addEventListener("updatefound",()=>{const t=e.installing;t&&GE(t)}),setInterval(()=>{e.update().catch(()=>{})},3600*1e3)}).catch(()=>{}))}function JE(){Fy=!0,Ah(!1);const e=ih&&ih.waiting;e?(e.postMessage({type:"SKIP_WAITING"}),setTimeout(()=>location.reload(),4e3)):location.reload()}function ZE({hash:e="",origin:t="",pathname:i="",search:s=""}={}){const o=String(e||"").replace(/^#/,""),a=`${i||""}${s||""}`;if(!o)return{token:"",pair:"",nextUrl:a};const u=new URLSearchParams(o),h=u.get("token")||"",p=u.get("pair")||"",f=p?`${t||""}${i||""}#pair=${p}`:"";return{token:h,pair:f,nextUrl:a}}const Wy=9,Uy=24,Ky="nc_side_w",Iv="nc_side_min",e2=240,Bv="(min-width:1024px) and (pointer:fine)";function t2(){const e=Number(localStorage.getItem(Ky));return e>=180&&e<=480?e:e2}function i2(){const e=Number(localStorage.getItem("nc_fontsize"));return e>=Wy&&e<=Uy?e:13}function n2(){const e={token:"",pair:""};try{const{token:t,pair:i,nextUrl:s}=ZE({hash:location.hash,origin:location.origin,pathname:location.pathname,search:location.search});if(t){e.token=t;try{localStorage.setItem("nc_token",t)}catch{}}if(i){e.pair=i;try{sessionStorage.setItem("nc_pair",i)}catch{}}if(location.hash)try{history.replaceState(null,"",s)}catch{}}catch{}if(e.token||(e.token=sessionStorage.getItem("nc_token")||localStorage.getItem("nc_token")||""),!e.pair){const t=sessionStorage.getItem("nc_pair");t&&(e.pair=t)}return e}function r2(e){try{return Cr(Zd(e))}catch{return as()}}function Ov(e){if(!e)return"";const t=Math.floor(Date.now()/1e3)-e;return t<0||t<60?"ora":t<3600?`${Math.floor(t/60)}m`:t<86400?`${Math.floor(t/3600)}h`:`${Math.floor(t/86400)}g`}function s2(){const[e,t]=R.useState(()=>window.matchMedia(Bv).matches);return R.useEffect(()=>{const i=window.matchMedia(Bv),s=o=>t(o.matches);return i.addEventListener("change",s),()=>i.removeEventListener("change",s)},[]),e}function zv({session:e,node:t,ownerId:i,cellName:s,token:o,readonly:a=!1,onBack:u}){ji();const[h,p]=R.useState(!1),[f,_]=R.useState(()=>window.matchMedia("(pointer: coarse)").matches),[b,S]=R.useState(null),[w,y]=R.useState(i2),[x,k]=R.useState(s||e),[C,P]=R.useState(""),A=E=>y(j=>{const D=Math.max(Wy,Math.min(Uy,j+E));return localStorage.setItem("nc_fontsize",String(D)),D}),M=R.useRef(()=>{}),ee=R.useRef(()=>!1),q=R.useRef(()=>{}),B=R.useRef(!1),[Z,H]=R.useState(!1),[te,K]=R.useState(!1),T=()=>{B.current=!B.current,H(B.current)};return R.useEffect(()=>{k(s||e)},[s,e]),R.useEffect(()=>{let E=!0;const j=t?t.split("/"):[],D=t?`/api/route/${t.split("/").map(encodeURIComponent).join("/")}/_`:"/api";async function U(){let I=null,V=null;try{const z=await(await Vt(`${D}/sessions`,o)).json();Array.isArray(z.sessions)&&(I=z.sessions.find(oe=>oe.name===e))}catch{}try{const L=await Rr(o,j);L.available&&Array.isArray(L.cells)&&(V=L.cells.find(z=>z.tmuxSession===e))}catch{}if(!E)return;k(Ph({session:e,cell:V||(s?{cell:s}:null)}));let $="";V?$=`${V.engine}${V.key?`·${V.key}`:""}`:I&&($=I.attached?`attached · ${Ov(I.activity)}`:I.activity?Ov(I.activity):""),P($)}U();const re=setInterval(U,4e3);return()=>{E=!1,clearInterval(re)}},[e,t,o]),c.jsxs("div",{className:"nc-app",children:[c.jsxs("header",{className:"nc-bar nc-bar-single",children:[c.jsxs("button",{onClick:u,title:g("sessions"),children:[c.jsx(et,{name:"chevronLeft",size:18}),c.jsx("span",{className:"nc-bar-label",children:g("sessions")})]}),c.jsxs("span",{className:"nc-bar-center",children:[c.jsx("b",{title:t?`${x} · ${t}`:x,children:x}),C&&c.jsx("small",{className:"nc-bar-sub",children:C})]}),c.jsxs("span",{className:"nc-bar-right",children:[c.jsx("button",{onClick:()=>A(-1),title:g("zoom-out"),children:c.jsx(et,{name:"zoomOut",size:18})}),c.jsx("button",{onClick:()=>A(1),title:g("zoom-in"),children:c.jsx(et,{name:"zoomIn",size:18})}),c.jsx("button",{onClick:()=>_(E=>!E),title:g("composer"),children:c.jsx(et,{name:"keyboard",size:20})}),c.jsx("button",{onClick:()=>p(E=>!E),title:g("files"),children:c.jsx(et,{name:"folder",size:20})})]})]}),c.jsx("div",{className:"nc-termwrap",children:c.jsx(ay,{session:e,node:t,token:o,readonly:a,takeSize:!0,sendRef:M,composerRef:ee,actionRef:q,ctrlRef:B,setCtrlArmed:H,onFiles:S,fontSize:w,selectionMode:te,onSelectionModeChange:K})}),c.jsx(zC,{onKeyboard:()=>_(E=>!E),send:E=>M.current(E),action:E=>q.current(E),ctrlArmed:Z,onCtrl:T,selectionMode:te,onSelectionMode:K}),f&&c.jsx(vy,{submitText:E=>ee.current(E),token:o,session:e,node:t,ownerId:i}),h&&c.jsx(ly,{session:e,node:t,token:o,filesEvent:b,onClose:()=>p(!1)})]})}function o2(){ji();const[e]=R.useState(n2),[t,i]=R.useState(e.token),[s,o]=R.useState(e.pair||""),a=R.useCallback(()=>{o("");try{sessionStorage.removeItem("nc_pair")}catch{}},[]),[u,h]=R.useState(!1),p=s2(),[f]=R.useState(()=>fk(typeof location<"u"?location.pathname:"/")),[_,b]=R.useState(f.id),S=_===ls(null,Pi),[w,y]=R.useState(null),x=fe=>{const Pe=tr(fe);y(Pe?{...Pe,...typeof(fe==null?void 0:fe.cellName)=="string"&&fe.cellName?{cellName:fe.cellName}:{}}:null)},[k,C]=R.useState([]),[P,A]=R.useState([]),[M,ee]=R.useState([]),[q,B]=R.useState(()=>f.ownerId?as():r2(f.name)),[Z,H]=R.useState(null),[te,K]=R.useState(null),T=fe=>K(tr(fe)),E=ph(t,p),j=R.useMemo(()=>(E||[]).filter(fe=>fe.instanceId).map(fe=>({instanceId:fe.instanceId,route:fe.route,label:fe.label,status:fe.status})),[E]),D=qE(t,_,q,B,j),U=D.decks;R.useEffect(()=>{const fe=new Map;for(const Pe of E||[]){const qe=(Pe.route||[Pe.name]).join("/"),xt=new Set((Pe.sessions||[]).map(oi=>oi.name));for(const oi of Pe.cells||[])!oi.cell||!oi.tmuxSession||oi.cell===oi.tmuxSession||xt.has(oi.cell)||fe.set(`${qe}:${oi.cell}`,`${qe}:${oi.tmuxSession}`)}B(Pe=>Z0(Pe,fe))},[E]),R.useEffect(()=>{D.localNodeId&&B(fe=>{const Pe=Jd(fe,D.localNodeId,j);return JSON.stringify(Pe)===JSON.stringify(fe)?fe:Pe})},[j,D.localNodeId]);const[re,I]=R.useState(null),[V,$]=R.useState(null),L=R.useRef(0),[z,oe]=R.useState(!1),[ce,he]=R.useState(t2),[me,G]=R.useState(!S),[J,ge]=R.useState(()=>S?localStorage.getItem(Iv)==="1":!0),[Me,Ze]=R.useState(!1),[Be,Fe]=R.useState("nodes"),[ct,Ve]=R.useState(!1),[gt,ut]=R.useState(""),lt=(fe="nodes",Pe=!1,qe="")=>{Fe(fe),Ve(Pe),ut(qe),Ze(!0)},[nt,dt]=R.useState(!1),[rt,ot]=R.useState({deviceDefault:"",localNodeId:"",localNameDefault:""}),[ae,xe]=R.useState(!1);R.useEffect(()=>{try{localStorage.setItem(Ky,String(ce))}catch{}},[ce]),R.useEffect(()=>{try{localStorage.setItem(Iv,J?"1":"")}catch{}},[J]),R.useEffect(()=>{if(!t)return;let fe=!1;return Promise.all([Yv(t),Vt("/api/config",t).then(Pe=>Pe.json())]).then(([Pe,qe])=>{fe||(ot({deviceDefault:Pe.deviceName||"",localNodeId:Pe.nodeId||"",localNameDefault:Pe.localName||""}),xe(!!qe.readonlyDefault),(Pe.firstRun===!0&&!qe.readonlyDefault||s)&&dt(!0))}).catch(()=>{}),()=>{fe=!0}},[t,s]);const de=R.useCallback(async()=>{try{const Pe=await(await Vt("/api/sessions",t)).json();Pe.error||C(Pe.sessions||[])}catch{}try{const fe=await Rr(t);A(fe.available?fe.cells||[]:[]),ee(fe.available?fe.capabilities||[]:[])}catch{A([]),ee([])}},[t]);R.useEffect(()=>{if(!p)return;de();const fe=setInterval(de,4e3);return()=>clearInterval(fe)},[p,de]),R.useEffect(()=>{let fe=!1;return Vt("/api/config",t).then(Pe=>Pe.json()).then(Pe=>{fe||XE(Pe.version,Pe.uiVersion,"0.8.28")}).catch(()=>{}),()=>{fe=!0}},[t]);const Ne=new Set([...k.map(fe=>fe.name),...E.flatMap(fe=>fe.sessions.map(Pe=>Pe.key))]),Ee=ln(q),X=fe=>B(Pe=>{const qe=yy(fe,D.localNodeId,j)||fe,xt=f_(Pe,qe);return xt===Pe&&ln(Pe).length>=9&&D.setError(g("grid-full")),xt}),ie=async(fe,Pe=[])=>{try{await ch(t,fe,Pe)}catch{return}const qe=Pe.length?`${Pe.join("/")}:${fe}`:fe;B(xt=>dl(xt,qe)),de()},F=async(fe,Pe,qe=[])=>{try{await Vv(t,fe,Pe,qe)}catch{return}de()},W=async(fe,Pe,qe=[])=>{await Uv(t,{cell:fe,enabled:!!Pe},qe),de()},ue=async fe=>{if(!re)return;const{cell:Pe}=re,qe=Array.isArray(re.route)?re.route:[];fe.action==="up"?await ah(t,{cell:Pe,boot:!!fe.boot,...fe.engine?{engine:fe.engine}:{},...fe.model!==void 0?{model:fe.model}:{},...fe.permissionPolicy?{permissionPolicy:fe.permissionPolicy}:{}},qe):await sl(t,{cell:Pe,boot:!!fe.boot},qe);const xt=fe.action==="up"?!!fe.boot:fe.boot?!1:!!re.boot;$({id:++L.current,cell:Pe,route:qe,enabled:xt}),de()},ve=R.useCallback(fe=>{$(Pe=>(Pe==null?void 0:Pe.id)===fe?null:Pe)},[]),Ce=async fe=>{if(!(!(fe!=null&&fe.direct)||z)){oe(!0);try{await hh(t,fe.name,fe.tunnelStatus==="up"?"down":"up")}finally{oe(!1)}}},_e=async(fe,Pe)=>{const qe=String(Pe||"").trim();return!(fe!=null&&fe.direct)||!ds(qe)?!1:(await Qv(t,fe.name,qe),!0)},Re=fe=>{const Pe=U.find(qe=>qe.id===fe);if(!Pe)return!1;try{const qe=window.open(Lu(Pe,t),"_blank");return qe&&(qe.opener=null),!!qe}catch{return!1}},Qe=async fe=>{if(!fe||fe===_)return;const Pe=await D.select(fe),qe=D.records.find(xt=>xt.id===fe);b(fe),B(Pe),H(null),K(null);try{history.replaceState(null,"",Lu(qe||fe,null))}catch{}},zt=async(fe,Pe)=>{const qe=await D.add(fe,Pe);await Qe(qe.id)},ze=async(fe,Pe)=>{const qe=await D.rename(fe,Pe);if(fe===_){b(qe.id),B(Jd(qe.layout,D.localNodeId,j)),H(null),K(null);try{history.replaceState(null,"",Lu(qe,null))}catch{}}},Di=async fe=>{await D.remove(fe),fe===_&&await Qe(D.localMainId)},Nt=async(fe,Pe)=>{!Pe||Pe===_||(await D.addTileTo(Pe,fe),B(qe=>dl(qe,fe)))};if(!t)return c.jsxs("div",{className:"nc-auth",children:[c.jsx("p",{children:g("auth-prompt")}),c.jsx("input",{onChange:fe=>i(fe.target.value.trim()),placeholder:"token"}),c.jsxs("label",{children:[c.jsx("input",{type:"checkbox",checked:u,onChange:fe=>h(fe.target.checked)})," ",g("remember-device")]}),c.jsx("button",{onClick:()=>{(u?localStorage:sessionStorage).setItem("nc_token",t)},children:"ok"})]});const Ke=c.jsxs(c.Fragment,{children:[Me&&c.jsx(BE,{token:t,initialTab:Be,initialLocation:gt,startNewCell:ct,onClose:()=>{Ze(!1),Ve(!1),ut("")}}),nt&&c.jsx(OE,{token:t,initialPair:s,...rt,onPairDone:a,onDone:()=>dt(!1)}),c.jsx(UE,{token:t})]});if(!p)return w?c.jsxs(c.Fragment,{children:[c.jsx(zv,{session:w.session,node:w.node,ownerId:w.ownerId,cellName:w.cellName,token:t,readonly:ae,onBack:()=>y(null)}),Ke]}):c.jsxs(c.Fragment,{children:[c.jsx(pb,{onPick:x,token:t,onSettings:lt}),Ke]});const ei=S||!me;return c.jsxs("div",{className:"nc-workspace",children:[ei&&c.jsx(ik,{sessions:k,cells:P,activeSessions:Ee,nodeGroups:E,fleetCapabilities:M,bootSettlement:V,onBootSettlementApplied:ve,localNodeId:D.localNodeId,onPick:T,onAddTile:X,onPower:I,onBoot:W,onBootError:fe=>D.setError(String((fe==null?void 0:fe.message)||fe)),onNodePower:Ce,onNodeRename:_e,onKill:ie,onVisibility:F,onNew:()=>lt("fleet",!0),onSettings:lt,width:ce,collapsed:J,onResize:he,onToggleCollapse:()=>ge(fe=>!fe)}),c.jsxs("div",{className:"nc-workspace-main",children:[c.jsx(Sk,{decks:U,currentDeck:_,onCreate:zt,onRename:ze,onDelete:Di,onReorder:D.reorder,onOpenWindow:Re,onNavigate:Qe,saveState:D.saveState,error:D.error,sidebarVisible:ei,onToggleSidebar:S?null:()=>G(fe=>!fe)}),c.jsx(lk,{layout:q,onLayoutChange:B,token:t,readonly:ae,sessionsAlive:Ne,focusSession:Z,onFocus:H,onOpenSingle:T,decks:U,currentDeck:_,onSendToDeck:Nt,cells:P,nodeGroups:E})]}),te&&c.jsx("div",{className:"nc-single-overlay",children:c.jsx(zv,{session:te.session,node:te.node,ownerId:te.ownerId,cellName:Ph({session:te.session,node:te.node,ownerId:te.ownerId,cells:P,nodeGroups:E}),token:t,readonly:ae,onBack:()=>K(null)})}),re&&c.jsx(ol,{cell:re,token:t,route:Array.isArray(re.route)?re.route:[],onConfirm:ue,onClose:()=>I(null)}),Ke]})}function a2(){ji();const e=R.useSyncExternalStore(YE,Av,Av);if(!e.needed)return null;const t=e.kind==="install"?g("install-version-mismatch").replace("{v}",e.version):g("update-available").replace("{v}",e.version).replace(" "," ").trim();return c.jsxs("div",{className:"nc-update",role:"status","aria-live":"polite",children:[c.jsx("span",{className:"nc-update-msg",children:t}),e.kind==="reload"&&c.jsx("button",{className:"nc-update-btn",onClick:JE,children:g("reload")})]})}QE();GS.createRoot(document.getElementById("root")).render(c.jsxs(WS.StrictMode,{children:[c.jsx(o2,{}),c.jsx(a2,{})]}));
93
+ `],{type:"application/json"}),U=URL.createObjectURL(D),re=document.createElement("a");re.href=U,re.download="nexuscrew-diagnostics.json",re.click(),URL.revokeObjectURL(U)}catch{S(g("diagnostics-export-failed"))}};return c.jsxs("div",{className:"nc-set-tab nc-diagnostics",children:[c.jsxs("label",{className:"nc-field",children:[g("diagnostics-target"),c.jsx("select",{value:u.key,onChange:D=>a(D.target.value),children:s.map(D=>c.jsxs("option",{value:D.key,children:[D.label,D.status!=="up"?` · ${D.status}`:""]},D.key))})]}),c.jsxs("div",{className:"nc-set-form nc-diag-controls",children:[c.jsx("div",{className:"nc-sheet-label",children:g("diagnostics-verbose")}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("select",{"aria-label":g("diagnostics-duration"),value:C,onChange:D=>P(Number(D.target.value)),disabled:i,children:[300,900,1800,3600].map(D=>c.jsxs("option",{value:D,children:[D/60," min"]},D))}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:i||(h==null?void 0:h.verbose)===!0,onClick:()=>T(()=>Rm(e,!0,C,u.route)),children:g("enable")}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:i||(h==null?void 0:h.verbose)!==!0,onClick:()=>T(()=>Rm(e,!1,C,u.route)),children:g("stop")})]}),c.jsx("small",{className:"nc-set-hint",children:h!=null&&h.verbose?g("diagnostics-expires").replace("{time}",new Date(h.expiresAt).toLocaleTimeString()):g("diagnostics-off")})]}),c.jsxs("div",{className:"nc-diag-filters",children:[c.jsxs("select",{"aria-label":g("diagnostics-level"),value:A,onChange:D=>M(D.target.value),children:[c.jsx("option",{value:"all",children:g("diagnostics-all-levels")}),["debug","info","warn","error"].map(D=>c.jsx("option",{value:D,children:D},D))]}),c.jsx("input",{"aria-label":g("diagnostics-component"),placeholder:g("diagnostics-component"),value:ee,onChange:D=>q(D.target.value)})]}),c.jsxs("div",{className:"nc-set-row",children:[c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:w,onChange:D=>y(D.target.checked)})," ",g("diagnostics-pause")]}),c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:x,onChange:D=>k(D.target.checked)})," ",g("diagnostics-autoscroll")]})]}),c.jsx("pre",{ref:Z,className:"nc-diag-log","aria-label":g("diagnostics-log"),children:K||g("diagnostics-empty")}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:E,children:g("copy")}),c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:j,children:g("diagnostics-export")}),c.jsx("button",{type:"button",className:"nc-btn danger",disabled:i,onClick:()=>{window.confirm(g("diagnostics-clear-confirm"))&&T(async()=>{const D=await S0(e,u.route);return B.current=0,_([]),D})},children:g("diagnostics-clear")})]}),b&&c.jsx("div",{className:"nc-err",children:b})]})}function IE({token:e,settings:t,readonly:i,refresh:s}){const[o,a]=R.useState(null),[u,h]=R.useState(null),[p,f]=R.useState(!1),[_,b]=R.useState(!1),[S,w]=R.useState(null),[y,x]=R.useState(!0);R.useEffect(()=>{w(t&&t.update||null),x(!t||t.autoUpdate!==!1)},[t]);const k=async()=>{a(null),h(null),b(!0);try{const B=await c0(e);h(B.note||"ok"),f(!1)}catch(B){a(String(B.message||B))}b(!1)},C=async()=>{a(null),h(null),b(!0);try{const B=await g0(e);h(`${B.note||"ok"}${B.target?` (${B.target})`:""}`)}catch(B){a(String(B.message||B))}b(!1)},P=async B=>{a(null),h(null),b(!0);try{await Gv(e,{autoUpdate:B}),x(B),s&&await s()}catch(Z){a(String(Z.message||Z))}b(!1)},A=async()=>{a(null),h(null),b(!0);try{w(await v0(e))}catch(B){a(String(B.message||B))}b(!1)},M=async()=>{a(null),h(null),b(!0);try{const B=await _0(e);w(B),h(g("npm-update-restarting"))}catch(B){a(String(B.message||B))}b(!1)},ee=()=>{window.confirm(g("composer-clear-confirm"))&&(a(null),GC()?(window.dispatchEvent(new Event(Xd)),h(g("composer-clear-done"))):a(g("composer-clear-failed")))},q=t&&t.service;return c.jsxs("div",{className:"nc-set-tab",children:[t&&c.jsxs("div",{className:"nc-set-info",children:["v",t.version," · ",t.platform," · :",t.port,c.jsx("br",{}),q&&q.installed?g("service-installed"):g("service-missing"),q&&q.installed?` · ${q.active?g("service-active"):g("service-inactive")}`:"",q?` · boot ${q.boot?"on":"off"}`:""]}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:i||_,title:i?g("settings-readonly"):"",onClick:()=>{a(null),h(null),f(!0)},children:g("token-rotate")}),c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:i||_,title:i?g("settings-readonly"):"",onClick:C,children:g("service-regenerate")})]}),c.jsxs("div",{className:"nc-set-form nc-update-settings",children:[c.jsxs("label",{className:"nc-check",children:[c.jsx("input",{type:"checkbox",checked:y,disabled:i||_||S&&!S.supported,onChange:B=>P(B.target.checked)}),c.jsxs("span",{children:[c.jsx("b",{children:g("npm-auto-update")}),c.jsx("small",{children:g("npm-auto-update-help")})]})]}),S&&c.jsxs("div",{className:"nc-set-info",children:[g("npm-update-current")," v",S.current,S.latest?` · ${g("npm-update-latest")} v${S.latest}`:""," · ",g(`npm-update-${S.supported?S.phase:"unsupported"}`)]}),c.jsxs("div",{className:"nc-set-row",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:i||_||!S||!S.supported,onClick:A,children:g("npm-update-check")}),S&&S.available&&c.jsx("button",{type:"button",className:"nc-btn primary",disabled:i||_||S.phase==="installing",onClick:M,children:g("npm-update-install")})]}),S&&S.lastError&&c.jsx("div",{className:"nc-err",children:S.lastError})]}),c.jsx(TE,{token:e,readonly:i}),c.jsxs("div",{className:"nc-set-form",children:[c.jsx("div",{className:"nc-sheet-label",children:g("composer-clear-data")}),c.jsx("small",{className:"nc-set-hint",children:g("composer-clear-data-help")}),c.jsx("div",{className:"nc-set-row",children:c.jsx("button",{type:"button",className:"nc-btn ghost",onClick:ee,children:g("composer-clear-data")})})]}),p&&c.jsxs("div",{className:"nc-set-confirm",children:[c.jsx("div",{children:g("token-rotate-explain")}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:_,onClick:()=>f(!1),children:g("cancel")}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:_,onClick:k,children:g("confirm")})]})]}),u&&c.jsx("div",{className:"nc-set-note",children:u}),o&&c.jsx("div",{className:"nc-err",children:o})]})}function BE({token:e,onClose:t,initialTab:i="nodes",initialLocation:s="",startNewCell:o=!1}){ji();const[a,u]=R.useState(i),[h,p]=R.useState(null),[f,_]=R.useState([]),[b,S]=R.useState(!1),[w,y]=R.useState(null),[x,k]=R.useState(0),C=ph(e,!0,x),P=R.useCallback(async()=>{try{const A=await Yv(e);p(A),y(null)}catch(A){y(String(A.message||A))}try{const A=await a0(e);_(A.peers||[])}catch(A){y(String(A.message||A))}},[e]);return R.useEffect(()=>{P();const A=setInterval(P,5e3);return()=>clearInterval(A)},[P]),R.useEffect(()=>{let A=!1;return Vt("/api/config",e).then(M=>M.json()).then(M=>{A||S(!!M.readonlyDefault)}).catch(()=>{}),()=>{A=!0}},[e]),c.jsx("div",{className:"nc-set-overlay",onClick:t,children:c.jsxs("div",{className:"nc-set-panel",onClick:A=>A.stopPropagation(),children:[c.jsxs("div",{className:"nc-set-head",children:[c.jsx(et,{name:"gear",size:18}),c.jsx("b",{children:g("settings")}),c.jsx("button",{type:"button",className:"nc-set-close",onClick:t,title:g("close"),children:c.jsx(et,{name:"x",size:18})})]}),b&&c.jsx("div",{className:"nc-set-readonly",children:g("settings-readonly")}),w&&c.jsx("div",{className:"nc-err",children:w}),c.jsx("div",{className:"nc-set-tabs",children:["nodes","fleet","diagnostics","system"].map(A=>c.jsx("button",{type:"button",className:`nc-set-tabbtn${a===A?" on":""}`,onClick:()=>u(A),children:g(`tab-${A}`)},A))}),c.jsxs("div",{className:"nc-set-body",children:[a==="nodes"&&c.jsx(ME,{token:e,nodes:f,roster:C,settings:h,readonly:b,refresh:P,refreshAliases:()=>k(A=>A+1)}),a==="fleet"&&c.jsx(jE,{token:e,readonly:b,startNewCell:o,initialLocation:s,targets:C.map(A=>({route:A.route,label:A.label||A.name,status:A.status}))}),a==="diagnostics"&&c.jsx(AE,{token:e,roster:C,readonly:b}),a==="system"&&c.jsx(IE,{token:e,settings:h,readonly:b,refresh:P})]})]})})}function OE({token:e,initialPair:t,deviceDefault:i="",localNodeId:s="",localNameDefault:o="",onPairDone:a,onDone:u}){ji();const[h,p]=R.useState(t?"pair":"welcome"),[f,_]=R.useState(!1),[b,S]=R.useState(null),w=async()=>{_(!0),S(null);try{await Gv(e,{wizardDone:!0}),a&&a(),u()}catch(y){S(String(y.message||y)),_(!1)}};return c.jsx("div",{className:"nc-wiz-overlay",children:c.jsxs("div",{className:"nc-wiz",children:[c.jsxs("div",{className:"nc-wiz-head",children:[c.jsx("b",{children:g("wizard-title")}),c.jsx("small",{children:g("hydra-simple")})]}),h==="welcome"&&c.jsxs("div",{className:"nc-wiz-body",children:[c.jsx("div",{className:"nc-wiz-done",children:g("local-ready")}),c.jsxs("div",{className:"nc-sheet-actions",children:[c.jsx("button",{className:"nc-btn ghost",disabled:f,onClick:w,children:g("local-only")}),c.jsx("button",{className:"nc-btn primary",disabled:f,onClick:()=>p("pair"),children:g("add-node")})]})]}),h==="pair"&&c.jsxs("div",{className:"nc-wiz-body",children:[c.jsx(Py,{token:e,initial:t||"",autoStart:!!t,deviceDefault:i,localNodeId:s,localNameDefault:o,onBusyChange:_,onSuccess:async()=>{a&&a(),p("done")}}),c.jsx("div",{className:"nc-sheet-actions",children:c.jsx("button",{className:"nc-btn ghost",disabled:f,onClick:()=>{a&&a(),p("welcome")},children:g("back")})})]}),h==="done"&&c.jsxs("div",{className:"nc-wiz-body",children:[c.jsx("div",{className:"nc-wiz-done",children:g("node-connected")}),c.jsx("div",{className:"nc-sheet-actions",children:c.jsx("button",{className:"nc-btn primary",disabled:f,onClick:w,children:g("finish")})})]}),b&&c.jsx("div",{className:"nc-err",children:b})]})})}function zE(e,t){if(typeof EventSource>"u"||!e)return()=>{};const i=new EventSource(`/api/events?token=${encodeURIComponent(e)}`);return i.onmessage=s=>{try{const o=JSON.parse(s.data);o&&typeof o=="object"&&typeof o.type=="string"&&t(o)}catch{}},()=>{try{i.close()}catch{}}}const FE=6e3,$E=12e3;function HE({n:e,onClose:t}){return c.jsxs("div",{className:`nc-ntf-toast${e.urgency==="high"?" high":""}`,role:"status","aria-live":"polite",children:[c.jsxs("div",{className:"nc-ntf-toast-txt",children:[c.jsx("b",{children:e.title}),e.body&&c.jsx("small",{children:e.body}),e.session&&c.jsx("span",{className:"nc-ntf-from",children:e.session})]}),c.jsx("button",{type:"button",className:"nc-ntf-x",onClick:t,title:g("close"),children:c.jsx(et,{name:"x",size:14})})]})}function WE({ask:e,token:t,onAnswered:i}){const[s,o]=R.useState(""),[a,u]=R.useState(!1),[h,p]=R.useState(null),f=async _=>{const b=String(_||"").trim();if(!(!b||a)){p(null),u(!0);try{await x0(t,e.id,b),i(e.id)}catch(S){p(String(S.message||S))}u(!1)}};return c.jsxs("div",{className:"nc-ask-card",children:[c.jsxs("div",{className:"nc-ask-head",children:[c.jsx("span",{className:"nc-ntf-from",children:e.session}),c.jsxs("code",{className:"nc-ask-id",children:["#",e.id]})]}),c.jsx("div",{className:"nc-ask-q",children:e.question}),Array.isArray(e.options)&&e.options.length>0&&c.jsx("div",{className:"nc-ask-opts",children:e.options.map(_=>c.jsx("button",{type:"button",className:"nc-btn ghost",disabled:a,onClick:()=>f(_),children:_},_))}),c.jsxs("div",{className:"nc-ask-reply",children:[c.jsx("textarea",{rows:2,placeholder:g("ask-reply-ph"),value:s,disabled:a,onChange:_=>o(_.target.value)}),c.jsx("button",{type:"button",className:"nc-btn primary",disabled:a||!s.trim(),onClick:()=>f(s),children:g("send")})]}),h&&c.jsx("div",{className:"nc-err",children:h})]})}function UE({token:e}){ji();const[t,i]=R.useState([]),[s,o]=R.useState([]),[a,u]=R.useState(()=>{try{return/(?:^|[#&])ask=/.test(location.hash)}catch{return!1}}),h=R.useRef(0),p=R.useCallback(b=>{i(S=>S.filter(w=>w.key!==b))},[]),f=R.useCallback(b=>{const S=`t${h.current+=1}`;i(w=>[...w.slice(-3),{...b,key:S}]),setTimeout(()=>p(S),b.urgency==="high"?$E:FE)},[p]);R.useEffect(()=>{if(!e)return;let b=!1;b0(e).then(w=>{b||o(w.asks||[])}).catch(()=>{});const S=zE(e,w=>{w.type==="notify"?f(w):w.type==="ask"&&w.ask&&w.ask.id?o(y=>y.some(x=>x.id===w.ask.id)?y:[...y,w.ask]):w.type==="ask-answered"&&w.id&&o(y=>y.filter(x=>x.id!==w.id))});return()=>{b=!0,S()}},[e,f]);const _=b=>o(S=>S.filter(w=>w.id!==b));return c.jsxs(c.Fragment,{children:[t.length>0&&c.jsx("div",{className:"nc-ntf-toasts",children:t.map(b=>c.jsx(HE,{n:b,onClose:()=>p(b.key)},b.key))}),s.length>0&&!a&&c.jsxs("button",{type:"button",className:"nc-ask-badge",onClick:()=>u(!0),title:g("asks-title"),children:["? ",c.jsx("span",{className:"nc-ask-count",children:s.length})]}),s.length>0&&a&&c.jsxs("div",{className:"nc-ask-panel",children:[c.jsxs("div",{className:"nc-ask-panel-head",children:[c.jsx("b",{children:g("asks-title")}),c.jsx("span",{className:"nc-ask-count",children:s.length}),c.jsx("button",{type:"button",className:"nc-ntf-x",onClick:()=>u(!1),title:g("close"),children:c.jsx(et,{name:"x",size:16})})]}),c.jsx("div",{className:"nc-ask-panel-body",children:s.map(b=>c.jsx(WE,{ask:b,token:e,onAnswered:_},b.id))})]})]})}const rd=e=>ln(Cr(e)).length===0,sd=e=>Array.isArray(e)?e.join("/"):"";function KE(e){const t=new Set,i=[];for(const s of Array.isArray(e)?e:[])!s||!Hi.test(String(s.instanceId||""))||!Array.isArray(s.route)||!s.route.length||t.has(s.instanceId)||(t.add(s.instanceId),i.push({instanceId:s.instanceId,route:[...s.route],label:String(s.label||s.name||s.route.join(" › ")),status:s.status||"offline"}));return i.sort((s,o)=>s.label.localeCompare(o.label))}function uo(e){return{instanceId:e.ownerId,route:e.ownerRoute||[],label:e.ownerLabel,status:e.available===!1?"offline":"up"}}function vr(e,t,i,s=!1,o=!0){const a=t.instanceId;return{...e,id:ls(s?null:a,e.name),ownerId:a,ownerRoute:s?[]:[...t.route],ownerLabel:s?"Local":t.label,local:s,available:o,ownerTopology:Array.isArray(i)?i:[],layout:Qd(e.layout,a,i)}}function qE(e,t,i,s,o=[]){const[a,u]=R.useState([]),[h,p]=R.useState(!1),[f,_]=R.useState("idle"),[b,S]=R.useState(""),[w,y]=R.useState(""),x=R.useRef([]),k=R.useRef([]),C=R.useRef(""),P=R.useRef(t),A=R.useRef(i),M=R.useRef(!1),ee=R.useRef(!0),q=R.useRef(""),B=R.useMemo(()=>KE(o),[o]),Z=B.map($=>`${$.instanceId}:${sd($.route)}:${$.status}:${$.label}`).join("|");k.current=B,A.current=i,P.current=t,x.current=a;const H=R.useCallback($=>Jd($.layout,C.current,k.current),[]),te=R.useCallback(($,L=!0,z=P.current)=>{const oe=Jg($,Ya()),ce=x.current.some(me=>me.id===z);x.current=oe,u(oe);const he=oe.find(me=>me.id===z);if(L&&he){ee.current=!0;const me=H(he);s(me),he.local&&yk(he.name,me)}else ce&&!he&&(ee.current=!0,s(as()),S("deck non più condiviso dal nodo owner"));mk(oe.filter(me=>me.local).map(me=>me.name))},[s,H]),K=R.useCallback(async $=>{let L=await za(e);const z=L.decks.find(oe=>oe.name==="main");if(L.decks.length===1&&z&&z.revision===0&&rd(z.layout)){const oe=pk(),ce=Cr(Zd("main")||as());if(!rd(ce)){const he=Qd(ce,$,[]);L.decks[0]=await Fa(e,"main",he,0,[])}for(const he of oe.filter(me=>me!=="main"))try{let me=await Pm(e,he,[]);const G=Cr(Zd(he)||as());rd(G)||(me=await Fa(e,he,Qd(G,$,[]),me.revision,[]))}catch{}L=await za(e)}return L},[e]),T=R.useCallback(async({migrate:$=!1}={})=>{const L=await dh(e,[]),z=Hi.test(String(L.instanceId||""))?L.instanceId:"";if(!z)throw new Error("instanceId locale non disponibile");C.current=z,y(z);const[oe,ce]=await Promise.all([$?K(z):za(e),Cm(e,[]).catch(()=>({nodes:[]}))]),he={instanceId:z,route:[],label:"Local"},me=oe.decks.map(J=>vr(J,he,ce.nodes,!0,!0)),G=x.current;return await Promise.all(k.current.map(async J=>{if(J.status!=="up"){me.push(...G.filter(ge=>!ge.local&&ge.ownerId===J.instanceId).map(ge=>({...ge,ownerRoute:[...J.route],ownerLabel:J.label,available:!1})));return}try{const[ge,Me]=await Promise.all([za(e,J.route),Cm(e,J.route).catch(()=>({nodes:[]}))]);me.push(...ge.decks.map(Ze=>vr(Ze,J,Me.nodes,!1,!0)))}catch{me.push(...G.filter(Me=>!Me.local&&Me.ownerId===J.instanceId).map(Me=>({...Me,ownerRoute:[...J.route],ownerLabel:J.label,available:!1})))}})),me.sort((J,ge)=>J.local===ge.local?J.ownerLabel.localeCompare(ge.ownerLabel)||(J.name==="main"?-1:ge.name==="main"?1:J.name.localeCompare(ge.name)):J.local?-1:1),me},[e,K,Z]);R.useEffect(()=>{if(!e)return;let $=!1;const L=q.current!==e;return q.current=e,T({migrate:L}).then(z=>{if($)return;const oe=x.current.find(me=>me.id===P.current),ce=z.find(me=>me.id===P.current),he=ce&&(!oe||ce.revision>oe.revision||oe.available!==ce.available||sd(oe.ownerRoute)!==sd(ce.ownerRoute));te(z,L||!!he&&!M.current),p(!0),ce&&S("")}).catch(z=>{$||(p(!0),S(String(z.message||z)))}),()=>{$=!0}},[te,T,Z,e]);const E=R.useCallback(async($=P.current)=>{if(!h||!M.current)return!0;const L=x.current.find(z=>z.id===$);if(!L)return S(`deck inesistente: ${$}`),!1;if(L.available===!1)return S(`nodo owner offline: ${L.ownerLabel}`),!1;_("saving");try{const z=Gg(Cr(A.current),L.ownerId,L.ownerTopology),oe=await Fa(e,L.name,z,L.revision,L.ownerRoute),ce=vr(oe,uo(L),L.ownerTopology,L.local,!0);return te(x.current.map(he=>he.id===$?ce:he),!1),M.current=!1,_("saved"),S(""),setTimeout(()=>_("idle"),1500),!0}catch(z){if(_("error"),S(String(z.message||z)),z.status===409&&z.data&&z.data.current){const oe=x.current.find(he=>he.id===$),ce=vr(z.data.current,uo(oe),oe.ownerTopology,oe.local,!0);te(x.current.map(he=>he.id===$?ce:he),!1)}return!1}},[h,e,te]);return R.useEffect(()=>{if(!h)return;if(ee.current){ee.current=!1;return}M.current=!0,_("saving");const $=setTimeout(E,650);return()=>clearTimeout($)},[i,h,E]),R.useEffect(()=>{if(!h)return;const $=setInterval(async()=>{try{const L=await T(),z=x.current.find(he=>he.id===P.current),oe=L.find(he=>he.id===P.current),ce=oe&&(!z||oe.revision>z.revision||z.available!==oe.available);te(L,ce&&!M.current)}catch{}},5e3);return()=>clearInterval($)},[h,T,te]),{decks:a,records:a,localNodeId:w,ready:h,saveState:f,error:b,setError:S,saveNow:E,select:async $=>{if(M.current&&!await E(P.current))throw new Error("salvataggio del deck corrente fallito: cambio annullato");const L=x.current.find(z=>z.id===$);if(!L)throw new Error(`deck inesistente: ${$}`);return M.current=!1,ee.current=!0,H(L)},add:async($,L=null)=>{const z=L===Sn?x.current.find(he=>he.local):L?x.current.find(he=>he.ownerId===L):null;if(!z||z.available===!1)throw new Error("nodo owner non disponibile");const oe=await Pm(e,$,z.ownerRoute),ce=vr(oe,uo(z),z.ownerTopology,z.local,!0);return te([...x.current,ce],!1),ce},rename:async($,L)=>{const z=x.current.find(G=>G.id===$);if(!z)throw new Error("deck inesistente");if($===P.current&&M.current&&!await E($))throw new Error(`salvataggio di "${z.name}" fallito: rinomina annullata`);const oe=x.current.find(G=>G.id===$);if(!oe)throw new Error("deck inesistente");const ce=await C0(e,oe.name,L,oe.revision,oe.ownerRoute),he=vr(ce,uo(oe),oe.ownerTopology,oe.local,!0),me=oe.local?Sn:oe.ownerId;return Mu(vk(Ya(),me,$,he.id)),te(x.current.map(G=>G.id===$?he:G),!1),he},remove:async $=>{const L=x.current.find(oe=>oe.id===$);if(!L)throw new Error("deck inesistente");$===P.current&&(M.current=!1),await k0(e,L.name,L.revision,L.ownerRoute);const z=L.local?Sn:L.ownerId;Mu(_k(Ya(),z,$)),te(x.current.filter(oe=>oe.id!==$),!1)},reorder:($,L)=>{const z=x.current.find(J=>J.id===$),oe=x.current.find(J=>J.id===L);if(!z||!oe)return!1;const ce=z.local?Sn:z.ownerId,he=oe.local?Sn:oe.ownerId;if(ce!==he)return!1;const me=x.current.filter(J=>(J.local?Sn:J.ownerId)===ce).map(J=>J.id),G=Mu(gk(Ya(),ce,$,L,me));return te(Jg(x.current,G),!1),!0},addTileTo:async($,L)=>{const z=x.current.find(J=>J.id===$);if(!z)throw new Error("deck inesistente");if(z.available===!1)throw new Error(`nodo owner offline: ${z.ownerLabel}`);const oe=yy(L,C.current,k.current);if(!oe)throw new Error("riferimento sessione non valido");const ce=H(z),he=Gg(f_(ce,oe),z.ownerId,z.ownerTopology),me=await Fa(e,z.name,he,z.revision,z.ownerRoute),G=vr(me,uo(z),z.ownerTopology,z.local,!0);return te(x.current.map(J=>J.id===$?G:J),!1),G},localMainId:ls(null,"main"),parseDeckId:jh}}const VE="/sw.js",th="nc-sw-update";let ih=null,nh=!1,rh=null,zy=Object.freeze({needed:!1,kind:null,version:""}),Fy=!1;function $y(){typeof window<"u"&&window.dispatchEvent(new Event(th))}function Hy(){const e=rh||(nh?{kind:"reload",version:""}:null);zy=Object.freeze({needed:!!e,kind:(e==null?void 0:e.kind)||null,version:(e==null?void 0:e.version)||""})}function Av(){return zy}function YE(e){return typeof window>"u"?()=>{}:(window.addEventListener(th,e),()=>window.removeEventListener(th,e))}function Ah(e){nh!==e&&(nh=e,Hy(),$y())}function XE(e,t,i){let s=null;e&&t&&e!==t?s={kind:"install",version:e}:t&&i&&t!==i&&(s={kind:"reload",version:t});const o=JSON.stringify(s)===JSON.stringify(rh);rh=s,Hy(),o||$y()}function GE(e){e.addEventListener("statechange",()=>{e.state==="installed"&&navigator.serviceWorker.controller&&Ah(!0)})}function QE(){typeof navigator>"u"||!("serviceWorker"in navigator)||(navigator.serviceWorker.addEventListener("controllerchange",()=>{Fy&&location.reload()}),navigator.serviceWorker.register(VE).then(e=>{ih=e,e.waiting&&Ah(!0),e.addEventListener("updatefound",()=>{const t=e.installing;t&&GE(t)}),setInterval(()=>{e.update().catch(()=>{})},3600*1e3)}).catch(()=>{}))}function JE(){Fy=!0,Ah(!1);const e=ih&&ih.waiting;e?(e.postMessage({type:"SKIP_WAITING"}),setTimeout(()=>location.reload(),4e3)):location.reload()}function ZE({hash:e="",origin:t="",pathname:i="",search:s=""}={}){const o=String(e||"").replace(/^#/,""),a=`${i||""}${s||""}`;if(!o)return{token:"",pair:"",nextUrl:a};const u=new URLSearchParams(o),h=u.get("token")||"",p=u.get("pair")||"",f=p?`${t||""}${i||""}#pair=${p}`:"";return{token:h,pair:f,nextUrl:a}}const Wy=9,Uy=24,Ky="nc_side_w",Iv="nc_side_min",e2=240,Bv="(min-width:1024px) and (pointer:fine)";function t2(){const e=Number(localStorage.getItem(Ky));return e>=180&&e<=480?e:e2}function i2(){const e=Number(localStorage.getItem("nc_fontsize"));return e>=Wy&&e<=Uy?e:13}function n2(){const e={token:"",pair:""};try{const{token:t,pair:i,nextUrl:s}=ZE({hash:location.hash,origin:location.origin,pathname:location.pathname,search:location.search});if(t){e.token=t;try{localStorage.setItem("nc_token",t)}catch{}}if(i){e.pair=i;try{sessionStorage.setItem("nc_pair",i)}catch{}}if(location.hash)try{history.replaceState(null,"",s)}catch{}}catch{}if(e.token||(e.token=sessionStorage.getItem("nc_token")||localStorage.getItem("nc_token")||""),!e.pair){const t=sessionStorage.getItem("nc_pair");t&&(e.pair=t)}return e}function r2(e){try{return Cr(Zd(e))}catch{return as()}}function Ov(e){if(!e)return"";const t=Math.floor(Date.now()/1e3)-e;return t<0||t<60?"ora":t<3600?`${Math.floor(t/60)}m`:t<86400?`${Math.floor(t/3600)}h`:`${Math.floor(t/86400)}g`}function s2(){const[e,t]=R.useState(()=>window.matchMedia(Bv).matches);return R.useEffect(()=>{const i=window.matchMedia(Bv),s=o=>t(o.matches);return i.addEventListener("change",s),()=>i.removeEventListener("change",s)},[]),e}function zv({session:e,node:t,ownerId:i,cellName:s,token:o,readonly:a=!1,onBack:u}){ji();const[h,p]=R.useState(!1),[f,_]=R.useState(()=>window.matchMedia("(pointer: coarse)").matches),[b,S]=R.useState(null),[w,y]=R.useState(i2),[x,k]=R.useState(s||e),[C,P]=R.useState(""),A=E=>y(j=>{const D=Math.max(Wy,Math.min(Uy,j+E));return localStorage.setItem("nc_fontsize",String(D)),D}),M=R.useRef(()=>{}),ee=R.useRef(()=>!1),q=R.useRef(()=>{}),B=R.useRef(!1),[Z,H]=R.useState(!1),[te,K]=R.useState(!1),T=()=>{B.current=!B.current,H(B.current)};return R.useEffect(()=>{k(s||e)},[s,e]),R.useEffect(()=>{let E=!0;const j=t?t.split("/"):[],D=t?`/api/route/${t.split("/").map(encodeURIComponent).join("/")}/_`:"/api";async function U(){let I=null,V=null;try{const z=await(await Vt(`${D}/sessions`,o)).json();Array.isArray(z.sessions)&&(I=z.sessions.find(oe=>oe.name===e))}catch{}try{const L=await Rr(o,j);L.available&&Array.isArray(L.cells)&&(V=L.cells.find(z=>z.tmuxSession===e))}catch{}if(!E)return;k(Ph({session:e,cell:V||(s?{cell:s}:null)}));let $="";V?$=`${V.engine}${V.key?`·${V.key}`:""}`:I&&($=I.attached?`attached · ${Ov(I.activity)}`:I.activity?Ov(I.activity):""),P($)}U();const re=setInterval(U,4e3);return()=>{E=!1,clearInterval(re)}},[e,t,o]),c.jsxs("div",{className:"nc-app",children:[c.jsxs("header",{className:"nc-bar nc-bar-single",children:[c.jsxs("button",{onClick:u,title:g("sessions"),children:[c.jsx(et,{name:"chevronLeft",size:18}),c.jsx("span",{className:"nc-bar-label",children:g("sessions")})]}),c.jsxs("span",{className:"nc-bar-center",children:[c.jsx("b",{title:t?`${x} · ${t}`:x,children:x}),C&&c.jsx("small",{className:"nc-bar-sub",children:C})]}),c.jsxs("span",{className:"nc-bar-right",children:[c.jsx("button",{onClick:()=>A(-1),title:g("zoom-out"),children:c.jsx(et,{name:"zoomOut",size:18})}),c.jsx("button",{onClick:()=>A(1),title:g("zoom-in"),children:c.jsx(et,{name:"zoomIn",size:18})}),c.jsx("button",{onClick:()=>_(E=>!E),title:g("composer"),children:c.jsx(et,{name:"keyboard",size:20})}),c.jsx("button",{onClick:()=>p(E=>!E),title:g("files"),children:c.jsx(et,{name:"folder",size:20})})]})]}),c.jsx("div",{className:"nc-termwrap",children:c.jsx(ay,{session:e,node:t,token:o,readonly:a,takeSize:!0,sendRef:M,composerRef:ee,actionRef:q,ctrlRef:B,setCtrlArmed:H,onFiles:S,fontSize:w,selectionMode:te,onSelectionModeChange:K})}),c.jsx(zC,{onKeyboard:()=>_(E=>!E),send:E=>M.current(E),action:E=>q.current(E),ctrlArmed:Z,onCtrl:T,selectionMode:te,onSelectionMode:K}),f&&c.jsx(vy,{submitText:E=>ee.current(E),token:o,session:e,node:t,ownerId:i}),h&&c.jsx(ly,{session:e,node:t,token:o,filesEvent:b,onClose:()=>p(!1)})]})}function o2(){ji();const[e]=R.useState(n2),[t,i]=R.useState(e.token),[s,o]=R.useState(e.pair||""),a=R.useCallback(()=>{o("");try{sessionStorage.removeItem("nc_pair")}catch{}},[]),[u,h]=R.useState(!1),p=s2(),[f]=R.useState(()=>fk(typeof location<"u"?location.pathname:"/")),[_,b]=R.useState(f.id),S=_===ls(null,Pi),[w,y]=R.useState(null),x=fe=>{const Pe=tr(fe);y(Pe?{...Pe,...typeof(fe==null?void 0:fe.cellName)=="string"&&fe.cellName?{cellName:fe.cellName}:{}}:null)},[k,C]=R.useState([]),[P,A]=R.useState([]),[M,ee]=R.useState([]),[q,B]=R.useState(()=>f.ownerId?as():r2(f.name)),[Z,H]=R.useState(null),[te,K]=R.useState(null),T=fe=>K(tr(fe)),E=ph(t,p),j=R.useMemo(()=>(E||[]).filter(fe=>fe.instanceId).map(fe=>({instanceId:fe.instanceId,route:fe.route,label:fe.label,status:fe.status})),[E]),D=qE(t,_,q,B,j),U=D.decks;R.useEffect(()=>{const fe=new Map;for(const Pe of E||[]){const qe=(Pe.route||[Pe.name]).join("/"),xt=new Set((Pe.sessions||[]).map(oi=>oi.name));for(const oi of Pe.cells||[])!oi.cell||!oi.tmuxSession||oi.cell===oi.tmuxSession||xt.has(oi.cell)||fe.set(`${qe}:${oi.cell}`,`${qe}:${oi.tmuxSession}`)}B(Pe=>Z0(Pe,fe))},[E]),R.useEffect(()=>{D.localNodeId&&B(fe=>{const Pe=Jd(fe,D.localNodeId,j);return JSON.stringify(Pe)===JSON.stringify(fe)?fe:Pe})},[j,D.localNodeId]);const[re,I]=R.useState(null),[V,$]=R.useState(null),L=R.useRef(0),[z,oe]=R.useState(!1),[ce,he]=R.useState(t2),[me,G]=R.useState(!S),[J,ge]=R.useState(()=>S?localStorage.getItem(Iv)==="1":!0),[Me,Ze]=R.useState(!1),[Be,Fe]=R.useState("nodes"),[ct,Ve]=R.useState(!1),[gt,ut]=R.useState(""),lt=(fe="nodes",Pe=!1,qe="")=>{Fe(fe),Ve(Pe),ut(qe),Ze(!0)},[nt,dt]=R.useState(!1),[rt,ot]=R.useState({deviceDefault:"",localNodeId:"",localNameDefault:""}),[ae,xe]=R.useState(!1);R.useEffect(()=>{try{localStorage.setItem(Ky,String(ce))}catch{}},[ce]),R.useEffect(()=>{try{localStorage.setItem(Iv,J?"1":"")}catch{}},[J]),R.useEffect(()=>{if(!t)return;let fe=!1;return Promise.all([Yv(t),Vt("/api/config",t).then(Pe=>Pe.json())]).then(([Pe,qe])=>{fe||(ot({deviceDefault:Pe.deviceName||"",localNodeId:Pe.nodeId||"",localNameDefault:Pe.localName||""}),xe(!!qe.readonlyDefault),(Pe.firstRun===!0&&!qe.readonlyDefault||s)&&dt(!0))}).catch(()=>{}),()=>{fe=!0}},[t,s]);const de=R.useCallback(async()=>{try{const Pe=await(await Vt("/api/sessions",t)).json();Pe.error||C(Pe.sessions||[])}catch{}try{const fe=await Rr(t);A(fe.available?fe.cells||[]:[]),ee(fe.available?fe.capabilities||[]:[])}catch{A([]),ee([])}},[t]);R.useEffect(()=>{if(!p)return;de();const fe=setInterval(de,4e3);return()=>clearInterval(fe)},[p,de]),R.useEffect(()=>{let fe=!1;return Vt("/api/config",t).then(Pe=>Pe.json()).then(Pe=>{fe||XE(Pe.version,Pe.uiVersion,"0.8.29")}).catch(()=>{}),()=>{fe=!0}},[t]);const Ne=new Set([...k.map(fe=>fe.name),...E.flatMap(fe=>fe.sessions.map(Pe=>Pe.key))]),Ee=ln(q),X=fe=>B(Pe=>{const qe=yy(fe,D.localNodeId,j)||fe,xt=f_(Pe,qe);return xt===Pe&&ln(Pe).length>=9&&D.setError(g("grid-full")),xt}),ie=async(fe,Pe=[])=>{try{await ch(t,fe,Pe)}catch{return}const qe=Pe.length?`${Pe.join("/")}:${fe}`:fe;B(xt=>dl(xt,qe)),de()},F=async(fe,Pe,qe=[])=>{try{await Vv(t,fe,Pe,qe)}catch{return}de()},W=async(fe,Pe,qe=[])=>{await Uv(t,{cell:fe,enabled:!!Pe},qe),de()},ue=async fe=>{if(!re)return;const{cell:Pe}=re,qe=Array.isArray(re.route)?re.route:[];fe.action==="up"?await ah(t,{cell:Pe,boot:!!fe.boot,...fe.engine?{engine:fe.engine}:{},...fe.model!==void 0?{model:fe.model}:{},...fe.permissionPolicy?{permissionPolicy:fe.permissionPolicy}:{}},qe):await sl(t,{cell:Pe,boot:!!fe.boot},qe);const xt=fe.action==="up"?!!fe.boot:fe.boot?!1:!!re.boot;$({id:++L.current,cell:Pe,route:qe,enabled:xt}),de()},ve=R.useCallback(fe=>{$(Pe=>(Pe==null?void 0:Pe.id)===fe?null:Pe)},[]),Ce=async fe=>{if(!(!(fe!=null&&fe.direct)||z)){oe(!0);try{await hh(t,fe.name,fe.tunnelStatus==="up"?"down":"up")}finally{oe(!1)}}},_e=async(fe,Pe)=>{const qe=String(Pe||"").trim();return!(fe!=null&&fe.direct)||!ds(qe)?!1:(await Qv(t,fe.name,qe),!0)},Re=fe=>{const Pe=U.find(qe=>qe.id===fe);if(!Pe)return!1;try{const qe=window.open(Lu(Pe,t),"_blank");return qe&&(qe.opener=null),!!qe}catch{return!1}},Qe=async fe=>{if(!fe||fe===_)return;const Pe=await D.select(fe),qe=D.records.find(xt=>xt.id===fe);b(fe),B(Pe),H(null),K(null);try{history.replaceState(null,"",Lu(qe||fe,null))}catch{}},zt=async(fe,Pe)=>{const qe=await D.add(fe,Pe);await Qe(qe.id)},ze=async(fe,Pe)=>{const qe=await D.rename(fe,Pe);if(fe===_){b(qe.id),B(Jd(qe.layout,D.localNodeId,j)),H(null),K(null);try{history.replaceState(null,"",Lu(qe,null))}catch{}}},Di=async fe=>{await D.remove(fe),fe===_&&await Qe(D.localMainId)},Nt=async(fe,Pe)=>{!Pe||Pe===_||(await D.addTileTo(Pe,fe),B(qe=>dl(qe,fe)))};if(!t)return c.jsxs("div",{className:"nc-auth",children:[c.jsx("p",{children:g("auth-prompt")}),c.jsx("input",{onChange:fe=>i(fe.target.value.trim()),placeholder:"token"}),c.jsxs("label",{children:[c.jsx("input",{type:"checkbox",checked:u,onChange:fe=>h(fe.target.checked)})," ",g("remember-device")]}),c.jsx("button",{onClick:()=>{(u?localStorage:sessionStorage).setItem("nc_token",t)},children:"ok"})]});const Ke=c.jsxs(c.Fragment,{children:[Me&&c.jsx(BE,{token:t,initialTab:Be,initialLocation:gt,startNewCell:ct,onClose:()=>{Ze(!1),Ve(!1),ut("")}}),nt&&c.jsx(OE,{token:t,initialPair:s,...rt,onPairDone:a,onDone:()=>dt(!1)}),c.jsx(UE,{token:t})]});if(!p)return w?c.jsxs(c.Fragment,{children:[c.jsx(zv,{session:w.session,node:w.node,ownerId:w.ownerId,cellName:w.cellName,token:t,readonly:ae,onBack:()=>y(null)}),Ke]}):c.jsxs(c.Fragment,{children:[c.jsx(pb,{onPick:x,token:t,onSettings:lt}),Ke]});const ei=S||!me;return c.jsxs("div",{className:"nc-workspace",children:[ei&&c.jsx(ik,{sessions:k,cells:P,activeSessions:Ee,nodeGroups:E,fleetCapabilities:M,bootSettlement:V,onBootSettlementApplied:ve,localNodeId:D.localNodeId,onPick:T,onAddTile:X,onPower:I,onBoot:W,onBootError:fe=>D.setError(String((fe==null?void 0:fe.message)||fe)),onNodePower:Ce,onNodeRename:_e,onKill:ie,onVisibility:F,onNew:()=>lt("fleet",!0),onSettings:lt,width:ce,collapsed:J,onResize:he,onToggleCollapse:()=>ge(fe=>!fe)}),c.jsxs("div",{className:"nc-workspace-main",children:[c.jsx(Sk,{decks:U,currentDeck:_,onCreate:zt,onRename:ze,onDelete:Di,onReorder:D.reorder,onOpenWindow:Re,onNavigate:Qe,saveState:D.saveState,error:D.error,sidebarVisible:ei,onToggleSidebar:S?null:()=>G(fe=>!fe)}),c.jsx(lk,{layout:q,onLayoutChange:B,token:t,readonly:ae,sessionsAlive:Ne,focusSession:Z,onFocus:H,onOpenSingle:T,decks:U,currentDeck:_,onSendToDeck:Nt,cells:P,nodeGroups:E})]}),te&&c.jsx("div",{className:"nc-single-overlay",children:c.jsx(zv,{session:te.session,node:te.node,ownerId:te.ownerId,cellName:Ph({session:te.session,node:te.node,ownerId:te.ownerId,cells:P,nodeGroups:E}),token:t,readonly:ae,onBack:()=>K(null)})}),re&&c.jsx(ol,{cell:re,token:t,route:Array.isArray(re.route)?re.route:[],onConfirm:ue,onClose:()=>I(null)}),Ke]})}function a2(){ji();const e=R.useSyncExternalStore(YE,Av,Av);if(!e.needed)return null;const t=e.kind==="install"?g("install-version-mismatch").replace("{v}",e.version):g("update-available").replace("{v}",e.version).replace(" "," ").trim();return c.jsxs("div",{className:"nc-update",role:"status","aria-live":"polite",children:[c.jsx("span",{className:"nc-update-msg",children:t}),e.kind==="reload"&&c.jsx("button",{className:"nc-update-btn",onClick:JE,children:g("reload")})]})}QE();GS.createRoot(document.getElementById("root")).render(c.jsxs(WS.StrictMode,{children:[c.jsx(o2,{}),c.jsx(a2,{})]}));
@@ -11,7 +11,7 @@
11
11
  <meta name="apple-mobile-web-app-title" content="NexusCrew" />
12
12
  <link rel="manifest" href="/manifest.json" />
13
13
  <title>NexusCrew</title>
14
- <script type="module" crossorigin src="/assets/index-DiKMLCvW.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-BJdAdNOq.js"></script>
15
15
  <link rel="stylesheet" crossorigin href="/assets/index-BAzlX2nP.css">
16
16
  </head>
17
17
  <body>
@@ -1 +1 @@
1
- {"version":"0.8.28"}
1
+ {"version":"0.8.29"}
@@ -346,9 +346,64 @@ function servicePinsLegacyPort(platform, home, installPath) {
346
346
  try { return /NEXUSCREW_PORT/.test(fs.readFileSync(target, 'utf8')); } catch (_) { return false; }
347
347
  }
348
348
 
349
- function serviceDefinitionNeedsRefresh(platform, home, installPath) {
349
+ function fleetServiceDefinitionNeedsRefresh(platform, home, installPath) {
350
+ let target;
351
+ try { target = installPath || fleetInstallPath(platform, home); } catch (_) { return false; }
352
+ try { fs.lstatSync(target); }
353
+ catch (error) { return error.code !== 'ENOENT'; }
354
+ return !checkServiceWorkingDirectory(platform, home, target).ok;
355
+ }
356
+
357
+ function serviceDefinitionNeedsRefresh(platform, home, installPath, fleetInstallPathOverride) {
350
358
  if (servicePinsLegacyPort(platform, home, installPath)) return true;
351
- return !checkServiceWorkingDirectory(platform, home, installPath).ok;
359
+ if (!checkServiceWorkingDirectory(platform, home, installPath).ok) return true;
360
+ return fleetServiceDefinitionNeedsRefresh(platform, home, fleetInstallPathOverride);
361
+ }
362
+
363
+ function serviceMigrationMarkerPath(home, override) {
364
+ return override || path.join(home, '.nexuscrew', 'service-cwd-migration.pending');
365
+ }
366
+
367
+ function serviceMigrationPending(home, override) {
368
+ const target = serviceMigrationMarkerPath(home, override);
369
+ try {
370
+ const st = fs.lstatSync(target);
371
+ if (st.isSymbolicLink() || !st.isFile()) throw new Error(`unsafe service migration marker: ${target}`);
372
+ return true;
373
+ } catch (error) {
374
+ if (error.code === 'ENOENT') return false;
375
+ throw error;
376
+ }
377
+ }
378
+
379
+ function ensureServiceMigrationMarker(home, override) {
380
+ const target = serviceMigrationMarkerPath(home, override);
381
+ if (serviceMigrationPending(home, override)) return target;
382
+ fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
383
+ let fd;
384
+ try {
385
+ fd = fs.openSync(target, 'wx', 0o600);
386
+ fs.writeFileSync(fd, 'pending\n');
387
+ fs.fsyncSync(fd);
388
+ fs.fchmodSync(fd, 0o600);
389
+ } catch (error) {
390
+ if (error.code === 'EEXIST' && serviceMigrationPending(home, override)) return target;
391
+ throw error;
392
+ } finally {
393
+ if (fd !== undefined) fs.closeSync(fd);
394
+ }
395
+ return target;
396
+ }
397
+
398
+ function clearServiceMigrationMarker(home, override) {
399
+ const target = serviceMigrationMarkerPath(home, override);
400
+ try {
401
+ const st = fs.lstatSync(target);
402
+ if (st.isSymbolicLink() || !st.isFile()) throw new Error(`unsafe service migration marker: ${target}`);
403
+ fs.unlinkSync(target);
404
+ } catch (error) {
405
+ if (error.code !== 'ENOENT') throw error;
406
+ }
352
407
  }
353
408
 
354
409
  function portAvailable(port, host = '127.0.0.1') {
@@ -471,18 +526,37 @@ async function smartUp(opts = {}) {
471
526
  const home = opts.home || require('node:os').homedir();
472
527
  const persistent = bootState({ ...opts, platform }).enabled;
473
528
  let serviceRefreshed = false;
474
- if (persistent && serviceDefinitionNeedsRefresh(platform, home, opts.installPath)) {
475
- runInitImpl({ ...opts, log: quiet, platform, installBoot: true, printUrl: false });
476
- serviceRefreshed = checkServiceWorkingDirectory(platform, home, opts.installPath).ok;
477
- // Termux:Boot has no service manager, so replacing the script alone cannot
478
- // change the cwd of the already-running pidfile owner. Restart exactly once
479
- // after a verified migration; Linux/macOS are restarted by installService.
480
- if (serviceRefreshed && platform === 'termux') {
481
- const migrated = restartImpl({ ...opts, platform, log: quiet });
482
- if (!migrated || migrated.restarted !== true) {
483
- throw new Error(`service cwd migration restart failed: ${(migrated && migrated.reason) || 'unknown'}`);
484
- }
529
+ const migrationPending = persistent
530
+ ? serviceMigrationPending(home, opts.serviceMigrationMarkerPath) : false;
531
+ if (persistent && (migrationPending || serviceDefinitionNeedsRefresh(
532
+ platform, home, opts.installPath, opts.fleetInstallPath,
533
+ ))) {
534
+ ensureServiceMigrationMarker(home, opts.serviceMigrationMarkerPath);
535
+ const initResult = runInitImpl({ ...opts, log: quiet, platform, installBoot: true, printUrl: false });
536
+ serviceRefreshed = !serviceDefinitionNeedsRefresh(
537
+ platform, home, opts.installPath, opts.fleetInstallPath,
538
+ );
539
+ if (!serviceRefreshed) {
540
+ throw new Error('service cwd migration failed verification; refusing to continue with a replaceable working directory');
541
+ }
542
+ const installFailures = Array.isArray(initResult?.installFailures) ? initResult.installFailures : [];
543
+ if (installFailures.length) {
544
+ const components = [...new Set(installFailures.map((failure) => failure?.component)
545
+ .filter((component) => component === 'service' || component === 'fleet-companion'))];
546
+ throw new Error(`service cwd migration activation failed verification${components.length ? ` (${components.join(', ')})` : ''}; refusing to continue with an unverified service runtime`);
547
+ }
548
+ // Writing and activating a definition does not prove that an already-active
549
+ // Linux unit adopted it (`systemctl enable --now` is a no-op for that
550
+ // process). Restart every persistent runtime exactly once after the files
551
+ // and activation outcomes are verified. The lifecycle helper preserves the
552
+ // shared tmux server on Linux and uses PID-pinned portable restart on Termux.
553
+ const migrated = restartImpl({ ...opts, platform, log: quiet });
554
+ if (!migrated || migrated.restarted !== true) {
555
+ throw new Error(`service cwd migration restart failed verification (${platform}); refusing to continue with an unverified service runtime`);
485
556
  }
557
+ // Only a verified file migration plus successful service activation (and,
558
+ // on Termux, a verified runtime restart) clears the durable retry marker.
559
+ clearServiceMigrationMarker(home, opts.serviceMigrationMarkerPath);
486
560
  }
487
561
 
488
562
  if (!port) port = urlmod.loadPort(opts);
@@ -989,6 +1063,8 @@ module.exports = {
989
1063
  dispatch, dispatchNodes, serve, start, stop, status, parseFlags, HELP, NODES_HELP,
990
1064
  smartUp, url, tokenRotate, logs, doctor, update, restart,
991
1065
  serviceDefinitionNeedsRefresh,
1066
+ fleetServiceDefinitionNeedsRefresh,
1067
+ serviceMigrationPending,
992
1068
  portAvailable, findAvailablePort, probeNexusCrew, waitForNexusCrew, openPwa, startPortable, wizardComplete,
993
1069
  servicePinsLegacyPort,
994
1070
  isServiceRunning, readRoles,
package/lib/cli/doctor.js CHANGED
@@ -11,6 +11,7 @@ const path = require('node:path');
11
11
  const { execFileSync } = require('node:child_process');
12
12
  const { detectPlatform, uid } = require('./platform.js');
13
13
  const { installPath } = require('./service.js');
14
+ const { fleetInstallPath } = require('./fleet-service.js');
14
15
  const { resolvePaths } = require('./url.js');
15
16
  const { commandExists } = require('./path.js');
16
17
  const { loadDefinitions } = require('../fleet/definitions.js');
@@ -126,6 +127,26 @@ function checkServiceWorkingDirectory(platform, home, installPathOverride) {
126
127
  };
127
128
  }
128
129
 
130
+ // The Fleet boot companion can win the boot race and create the shared tmux
131
+ // server before the HTTP service. Apply the same stable-HOME invariant to it.
132
+ // A missing companion is non-fatal because Fleet boot is optional; an installed
133
+ // but stale/unsafe definition is a real failure.
134
+ function checkFleetServiceWorkingDirectory(platform, home, installPathOverride) {
135
+ if (!['linux', 'mac', 'termux'].includes(platform)) {
136
+ return { name: 'fleet service cwd stabile', ok: true, detail: `${platform}: non applicabile` };
137
+ }
138
+ const target = installPathOverride || fleetInstallPath(platform, home);
139
+ try {
140
+ fs.lstatSync(target);
141
+ } catch (error) {
142
+ if (error.code === 'ENOENT') {
143
+ return { name: 'fleet service cwd stabile', ok: true, warn: true, detail: `companion non installato (${target})` };
144
+ }
145
+ }
146
+ const result = checkServiceWorkingDirectory(platform, home, target);
147
+ return { ...result, name: 'fleet service cwd stabile' };
148
+ }
149
+
129
150
  function checkBoot(platform, home, execImpl) {
130
151
  if (platform === 'termux') {
131
152
  const p = path.join(home, '.termux', 'boot', 'nexuscrew.sh');
@@ -425,6 +446,9 @@ function doctor(opts = {}) {
425
446
  checkPty(ptyLoad),
426
447
  checkService(platform, home, execImpl, uidVal, opts.installPath),
427
448
  checkServiceWorkingDirectory(platform, home, opts.installPath),
449
+ fleetEnabled
450
+ ? checkFleetServiceWorkingDirectory(platform, home, opts.fleetInstallPath)
451
+ : { name: 'fleet service cwd stabile', ok: true, warn: true, detail: 'Fleet disabilitata' },
428
452
  checkBoot(platform, home, execImpl),
429
453
  checkUserLinger(platform, execImpl, uidVal),
430
454
  checkTmuxSurvival(platform, execImpl),
@@ -454,6 +478,7 @@ module.exports = {
454
478
  checkMacServiceWorkingDirectory,
455
479
  checkFleetDefinitions,
456
480
  checkServiceWorkingDirectory,
481
+ checkFleetServiceWorkingDirectory,
457
482
  checkTmuxSurvival, checkUserLinger,
458
483
  checkSshClient, checkAutossh, checkSshPermitlisten,
459
484
  checkMcpIdentity, checkTermuxExec,
@@ -41,13 +41,16 @@ function generateFleetService(opts) {
41
41
  }
42
42
 
43
43
  function generateFleetLinux(opts) {
44
- const repoRoot = opts.repoRoot || deriveRepoRoot(opts.entryPath);
44
+ const home = opts.home || os.homedir();
45
45
  const { nodeBin, entryPath } = opts;
46
46
  // Parita' di hardening con service.js (M3): reject char non gestibili in systemd.
47
- assertSystemdSafe('repoRoot', repoRoot);
47
+ assertSystemdSafe('home', home);
48
48
  assertSystemdSafe('nodeBin', nodeBin);
49
49
  assertSystemdSafe('entryPath', entryPath);
50
- const repo = escapeSystemdPath(repoRoot);
50
+ // fleet-boot can create the shared tmux server before the HTTP service. Its
51
+ // cwd must therefore be the stable HOME, never the replaceable npm package
52
+ // directory containing entryPath.
53
+ const stableCwd = escapeSystemdPath(home);
51
54
  const node = escapeSystemdExec(nodeBin);
52
55
  const entry = escapeSystemdExec(entryPath);
53
56
  const nodeDir = escapeSystemdPath(path.dirname(nodeBin));
@@ -61,7 +64,7 @@ Type=oneshot
61
64
  # fleet-boot may be the process that creates the shared tmux server. Keep
62
65
  # systemd from reaping that server and its cells when this oneshot exits.
63
66
  KillMode=process
64
- WorkingDirectory=${repo}
67
+ WorkingDirectory=${stableCwd}
65
68
  Environment=PATH=${nodeDir}:/usr/local/bin:/usr/bin:/bin
66
69
  ExecStart=${node} ${entry} fleet-boot
67
70
 
@@ -71,11 +74,9 @@ WantedBy=default.target
71
74
  }
72
75
 
73
76
  function generateFleetMac(opts) {
74
- const repoRoot = opts.repoRoot || deriveRepoRoot(opts.entryPath);
75
77
  const home = opts.home || os.homedir();
76
78
  const nodeXml = escapeXml(opts.nodeBin);
77
79
  const entryXml = escapeXml(opts.entryPath);
78
- const repoXml = escapeXml(repoRoot);
79
80
  const homeXml = escapeXml(home);
80
81
  const launchPath = [...new Set([
81
82
  path.dirname(opts.nodeBin), '/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/bin',
@@ -94,7 +95,7 @@ function generateFleetMac(opts) {
94
95
  <string>fleet-boot</string>
95
96
  </array>
96
97
  <key>WorkingDirectory</key>
97
- <string>${repoXml}</string>
98
+ <string>${homeXml}</string>
98
99
  <key>EnvironmentVariables</key>
99
100
  <dict>
100
101
  <key>PATH</key>
@@ -112,10 +113,8 @@ function generateFleetMac(opts) {
112
113
  }
113
114
 
114
115
  function generateFleetTermux(opts) {
115
- const repoRoot = opts.repoRoot || deriveRepoRoot(opts.entryPath);
116
116
  const nodeQ = shellQuote(opts.nodeBin);
117
117
  const entryQ = shellQuote(opts.entryPath);
118
- const repoQ = shellQuote(repoRoot);
119
118
  return `#!/data/data/com.termux/files/usr/bin/sh
120
119
  # NexusCrew fleet boot companion (Termux) - avvia le celle boot:true
121
120
  export PATH=/data/data/com.termux/files/usr/bin:$PATH
@@ -124,7 +123,7 @@ export PREFIX=/data/data/com.termux/files/usr
124
123
  export TMPDIR=$PREFIX/tmp
125
124
  export TMUX_TMPDIR=$PREFIX/var/run
126
125
  mkdir -p "$TMPDIR" "$TMUX_TMPDIR"
127
- cd -- ${repoQ}
126
+ cd -- "$HOME"
128
127
  exec ${nodeQ} ${entryQ} fleet-boot >> "$HOME/.nexuscrew/fleet-boot.log" 2>&1
129
128
  `;
130
129
  }
package/lib/cli/init.js CHANGED
@@ -109,6 +109,9 @@ function runInit(opts = {}) {
109
109
  }
110
110
 
111
111
  const actions = [];
112
+ // Structured, value-free install outcomes for callers that must fail closed
113
+ // (notably smart-up migrations). Human-readable actions remain compatible.
114
+ const installFailures = [];
112
115
 
113
116
  // porta: opts.port > migration rule > config esistente > default 41820
114
117
  let port = opts.port;
@@ -214,12 +217,14 @@ function runInit(opts = {}) {
214
217
  const r = installService(platform, content, svcCtx, { execImpl: opts.execImpl });
215
218
  if (r.failures && r.failures.length) {
216
219
  // file installato MA activation (systemctl/launchctl) fallita [M1]
220
+ installFailures.push({ component: 'service', phase: 'activation' });
217
221
  actions.push(`WARN: service file installato ${r.target} MA activation fallita: ${r.failures.map((f) => f.cmd).join('; ')} (file preservato, diagnosi)`);
218
222
  } else {
219
223
  actions.push(`service installed ${r.target} (mode 0${fileMode(platform).toString(8)})`);
220
224
  }
221
225
  } catch (e) {
222
226
  // failure: preserve file + diagnosi (no rollback) [M8]
227
+ installFailures.push({ component: 'service', phase: 'install' });
223
228
  actions.push(`WARN: service install fallito: ${e.message} (file generati preservati)`);
224
229
  }
225
230
  }
@@ -273,6 +278,7 @@ function runInit(opts = {}) {
273
278
  );
274
279
  if (fr.failures && fr.failures.length) {
275
280
  // file installato MA activation (systemctl/launchctl) fallita [M1]
281
+ installFailures.push({ component: 'fleet-companion', phase: 'activation' });
276
282
  actions.push(`WARN: fleet companion file installato ${fr.target} MA activation fallita: ${fr.failures.map((f) => f.cmd).join('; ')} (file preservato, diagnosi)`);
277
283
  } else {
278
284
  actions.push(`fleet companion installed ${fr.target} (mode 0${fleetFileMode(platform).toString(8)})`);
@@ -282,6 +288,7 @@ function runInit(opts = {}) {
282
288
  }
283
289
  } catch (e) {
284
290
  // qualunque errore del companion: WARN + init principale prosegue intatto.
291
+ installFailures.push({ component: 'fleet-companion', phase: 'install' });
285
292
  actions.push(`WARN: fleet companion fallito: ${e.message} (init principale prosegue)`);
286
293
  }
287
294
 
@@ -303,7 +310,7 @@ function runInit(opts = {}) {
303
310
 
304
311
  for (const a of actions) log(a);
305
312
 
306
- return { platform, port, token, url: urlWithToken, actions, tmuxOk, dryRun };
313
+ return { platform, port, token, url: urlWithToken, actions, installFailures, tmuxOk, dryRun };
307
314
  }
308
315
 
309
316
  module.exports = { runInit, readExistingPort, haveTmux, nodeMajor, writeConfigAtomic, ensureFleetDefaults };
@@ -18,7 +18,7 @@ function redactText(value, max = 160) {
18
18
  .replace(/\bBearer\s+\S+/gi, 'Bearer [redacted]')
19
19
  .replace(/\b(?:sk|xox[baprs]|gh[pousr]|npm)_[A-Za-z0-9_-]{8,}\b/g, '[redacted]')
20
20
  .replace(/\b(?:token|password|secret|api[_-]?key)\s*[=:]\s*\S+/gi, '[redacted]')
21
- .replace(/(?:\/home\/[^/\s]+|\/data\/(?:data|user\/\d+)\/[^/\s]+)(?:\/[^\s]*)?/g, '[path]')
21
+ .replace(/(?:\/home\/[^/\s]+|\/Users\/[^/\s]+|\/data\/(?:data|user\/\d+)\/[^/\s]+)(?:\/[^\s]*)?/g, '[path]')
22
22
  .replace(/\b[A-Z][A-Z0-9_]{2,}\s*=\s*\S+/g, '[env]');
23
23
  return text.slice(0, max);
24
24
  }
@@ -620,8 +620,12 @@ async function createBuiltinFleet(cfg = {}) {
620
620
  if (defs.cells.some((c) => c.id === id)) throw httpError(409, `id cella già usato: ${id}`);
621
621
  const cwd = typeof b.cwd === 'string' && b.cwd.trim() ? b.cwd.trim() : home;
622
622
  const cellDef = { id, cwd, engine: engineId, boot: b.boot === true, tmuxSession };
623
+ // Import is a write boundary just like define/edit/restore. Resolve and
624
+ // confine the cwd before mutate so a foreign or missing path is never
625
+ // persisted as a cell that can only fail later at up().
626
+ const [resolvedCell] = resolveCellsOrFail([cellDef], home);
623
627
  try {
624
- await mutate(defs, (d) => { d.cells.push(cellDef); });
628
+ await mutate(defs, (d) => { d.cells.push(resolvedCell); });
625
629
  } catch (e) { throw httpError(400, `import non valido: ${e.message}`); }
626
630
  return { ok: true, id, tmuxSession, imported: true };
627
631
  }
@@ -63,6 +63,7 @@ const REDACTED = '‹redacted›';
63
63
  // - valori di engine.env (le CHIAVI restano, i VALUES vengono redatti)
64
64
  // - testo del prompt della cella (cell.prompt)
65
65
  // - testo del prompt dell'engine (engine.prompt) se presente
66
+ // - comando Shell attivo per cella (cell.commands[cell.engine])
66
67
  // Applicato a OGNI messaggio d'errore che incorpora stderr/stdout dei comandi
67
68
  // tmux falliti (up / down / injectPrompt): tmux puo' ecoare argv/env del comando
68
69
  // lanciato nei suoi log di errore. Pura + senza dipendenze: testabile direttamente.
@@ -76,6 +77,11 @@ function redactSecrets(text, engine, cell) {
76
77
  }
77
78
  if (engine && typeof engine.prompt === 'string' && engine.prompt) secrets.push(engine.prompt);
78
79
  if (cell && typeof cell.prompt === 'string' && cell.prompt) secrets.push(cell.prompt);
80
+ if (cell && typeof cell.engine === 'string'
81
+ && cell.commands && typeof cell.commands === 'object' && !Array.isArray(cell.commands)) {
82
+ const activeCommand = cell.commands[cell.engine];
83
+ if (typeof activeCommand === 'string' && activeCommand) secrets.push(activeCommand);
84
+ }
79
85
  // Ordina per lunghezza DECRESCENTE: i segreti piu' lunghi prima, cosi' un segreto
80
86
  // che e' prefisso/sottostringa di un altro non ne maschera il rimpiazzo completo.
81
87
  secrets.sort((a, b) => b.length - a.length);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmmbuto/nexuscrew",
3
- "version": "0.8.28",
3
+ "version": "0.8.29",
4
4
  "description": "Faithful browser tmux client — attach to live sessions over a real PTY, localhost-only, mobile-easy",
5
5
  "main": "lib/server.js",
6
6
  "bin": {