itwillsync 1.6.2 → 1.7.1

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.
@@ -0,0 +1 @@
1
+ *{margin:0;padding:0;box-sizing:border-box}html,body{height:100%;width:100%;background:#1a1a2e;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;color:#e0e0e0;overflow-x:hidden;-webkit-overflow-scrolling:touch}#header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#16213e;border-bottom:1px solid #0f3460;position:sticky;top:0;z-index:10}.header-left{display:flex;align-items:center;gap:10px}.logo{font-size:16px;font-weight:700;color:#e94560;letter-spacing:-.5px}#status-dot{width:8px;height:8px;border-radius:50%;background:#e74c3c;transition:background .3s ease}#status-dot.connected{background:#2ecc71}#status-dot.reconnecting{background:#f39c12;animation:pulse 1s infinite}.header-right{display:flex;align-items:center;gap:12px}#session-count{font-size:13px;color:#a0a0b0}.header-icon-btn{background:none;border:1px solid #2a2a44;border-radius:8px;color:#a0a0b0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:color .15s,border-color .15s}.header-icon-btn:active{color:#e94560;border-color:#e94560}#session-list{padding:12px 12px 80px;display:flex;flex-direction:column;gap:12px}#empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh;color:#606080;text-align:center;gap:8px}#empty-state .empty-icon{font-size:48px;margin-bottom:8px;opacity:.5}#empty-state p{font-size:16px}#empty-state .empty-hint{font-size:13px;color:#505070}#empty-state code{background:#252540;padding:2px 8px;border-radius:4px;font-family:Cascadia Code,Fira Code,JetBrains Mono,monospace;font-size:12px;color:#e94560}.session-card{background:#16213e;border:1px solid #0f3460;border-radius:12px;padding:14px 16px;cursor:pointer;transition:transform .15s ease,border-color .2s ease;-webkit-tap-highlight-color:transparent}.session-card:active{transform:scale(.98)}.session-card:hover{border-color:#e94560}.session-card.attention{border-color:#e94560;animation:attention-glow 2s ease-in-out infinite}@keyframes attention-glow{0%,to{box-shadow:0 0 #e9456000}50%{box-shadow:0 0 12px 2px #e945604d}}.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.card-agent{display:flex;align-items:center;gap:8px}.agent-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}.agent-dot.active{background:#2ecc71}.agent-dot.idle{background:#f39c12}.agent-dot.attention{background:#e94560;animation:pulse 1s infinite}.agent-name{font-size:15px;font-weight:600;color:#f0f0f0}.card-uptime{font-size:12px;color:#707090;font-variant-numeric:tabular-nums}.card-cwd{font-size:12px;color:#808098;margin-bottom:10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:Cascadia Code,Fira Code,JetBrains Mono,monospace}.card-preview{background:#112;border-radius:6px;padding:8px 10px;margin-bottom:10px;overflow:hidden;min-height:24px;max-height:90px}.card-preview-text{font-family:Cascadia Code,Fira Code,JetBrains Mono,monospace;font-size:11px;line-height:1.4;color:#a0a0b0;white-space:pre;overflow:hidden;text-overflow:ellipsis;margin:0}.card-preview-text.empty{color:#505070;font-style:italic}.card-status{display:flex;align-items:center;gap:6px;font-size:12px;color:#a0a0b0}.status-badge{padding:2px 8px;border-radius:10px;font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:.5px}.status-badge.active{background:#2ecc7126;color:#2ecc71}.status-badge.idle{background:#f39c1226;color:#f39c12}.status-badge.attention{background:#e9456026;color:#e94560}.attention-badge{padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;background:#e9456033;color:#e94560;animation:attention-pulse 2s ease-in-out infinite}@keyframes attention-pulse{0%,to{opacity:1}50%{opacity:.6}}.card-actions{display:flex;gap:6px;margin-top:10px;padding-top:10px;border-top:1px solid #0f3460}.action-btn{flex:1;padding:6px 0;border:1px solid #2a2a44;border-radius:6px;background:#1e1e36;color:#a0a0b0;font-size:12px;font-weight:500;font-family:inherit;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s,color .15s}.action-btn:active{background:#2a2a44}.action-btn.open{color:#2ecc71;border-color:#2ecc714d}.action-btn.stop{color:#e94560;border-color:#e945604d}.action-btn.stop:active{background:#e9456026}.confirm-overlay{display:flex;align-items:center;justify-content:center;gap:10px;padding:10px;margin-top:8px;background:#2d1810;border:1px solid #e94560;border-radius:8px;animation:fadeIn .15s ease}.confirm-msg{font-size:13px;color:#f0f0f0;flex:1}.confirm-btn{padding:5px 14px;border:none;border-radius:5px;font-size:12px;font-weight:600;font-family:inherit;cursor:pointer}.confirm-btn.yes{background:#e94560;color:#fff}.confirm-btn.no{background:#2a2a44;color:#a0a0b0}.rename-input{background:#112;border:1px solid #e94560;border-radius:4px;color:#f0f0f0;font-size:15px;font-weight:600;font-family:inherit;padding:2px 6px;outline:none;width:120px}.card-metadata{margin-top:8px;padding:8px 10px;background:#112;border-radius:6px;animation:fadeIn .2s ease}.card-metadata.hidden{display:none}.meta-row{display:flex;justify-content:space-between;padding:3px 0;font-size:11px}.meta-label{color:#707090;font-weight:500}.meta-value{color:#c0c0d0;font-family:Cascadia Code,Fira Code,JetBrains Mono,monospace;font-size:11px;text-align:right;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(min-width:768px){#session-list{max-width:600px;margin:0 auto}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.4}}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.session-card{animation:fadeIn .3s ease}#reconnect-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:#1a1a2ef2;display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:100;color:#e0e0e0;font-size:16px;gap:12px}#reconnect-overlay .spinner{width:32px;height:32px;border:3px solid #0f3460;border-top-color:#e94560;border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}#fab-create{position:fixed;bottom:24px;right:24px;width:56px;height:56px;border-radius:50%;background:#e94560;color:#fff;font-size:28px;font-weight:300;border:none;cursor:pointer;box-shadow:0 4px 16px #e9456066;z-index:20;-webkit-tap-highlight-color:transparent;transition:transform .15s,box-shadow .15s;display:flex;align-items:center;justify-content:center;line-height:1}#fab-create:active{transform:scale(.92)}.modal-overlay{position:fixed;inset:0;background:#0a0a14d9;z-index:50;display:flex;align-items:flex-end;justify-content:center;animation:fadeIn .15s ease}.modal-overlay.hidden{display:none}.modal-content{background:#16213e;border-radius:16px 16px 0 0;width:100%;max-width:480px;max-height:85vh;max-height:85dvh;overflow-y:auto;padding:20px;animation:slideUp .2s ease}@keyframes slideUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}.modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px}.modal-title{font-size:18px;font-weight:600;color:#f0f0f0}.modal-close{background:none;border:none;color:#707090;font-size:24px;cursor:pointer;padding:4px 8px;line-height:1}.form-label{display:block;font-size:13px;font-weight:500;color:#a0a0b0;margin-bottom:6px;margin-top:16px}.form-label:first-of-type{margin-top:0}.form-input{width:100%;padding:10px 12px;background:#112;border:1px solid #2a2a44;border-radius:8px;color:#f0f0f0;font-size:15px;font-family:Cascadia Code,Fira Code,JetBrains Mono,monospace;outline:none;transition:border-color .15s}.form-input:focus{border-color:#e94560}.form-input.hidden{display:none}.chip-row{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}.chip{padding:5px 12px;background:#1e1e36;border:1px solid #2a2a44;border-radius:16px;font-size:12px;color:#c0c0d0;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s,border-color .15s;white-space:nowrap;font-family:Cascadia Code,Fira Code,JetBrains Mono,monospace}.chip:active,.chip.selected{background:#e9456026;border-color:#e94560;color:#e94560}.dir-display{padding:10px 12px;background:#112;border:1px solid #2a2a44;border-radius:8px;font-size:13px;font-family:Cascadia Code,Fira Code,JetBrains Mono,monospace;color:#c0c0d0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dir-actions{display:flex;gap:8px;margin-top:10px}.dir-action-btn{flex:1;padding:8px 0;background:#1e1e36;border:1px solid #2a2a44;border-radius:8px;color:#a0a0b0;font-size:13px;font-family:inherit;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s}.dir-action-btn:active{background:#2a2a44}.btn-create{width:100%;margin-top:20px;padding:14px;background:#e94560;color:#fff;border:none;border-radius:10px;font-size:16px;font-weight:600;font-family:inherit;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:opacity .15s}.btn-create:disabled{opacity:.4;cursor:default}.btn-create:not(:disabled):active{opacity:.8}.create-error{margin-top:12px;padding:8px 12px;background:#e9456026;border-radius:6px;font-size:13px;color:#e94560}.create-error.hidden{display:none}.create-spinner{display:flex;flex-direction:column;align-items:center;gap:12px;padding:32px 0;color:#a0a0b0;font-size:14px}.create-spinner.hidden{display:none}.browse-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.browse-back{background:none;border:none;color:#a0a0b0;font-size:14px;font-family:inherit;cursor:pointer;padding:4px 0}.browse-select{padding:6px 14px;background:#e94560;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:500;font-family:inherit;cursor:pointer}.browse-breadcrumb{display:flex;gap:4px;align-items:center;margin-bottom:12px;font-size:13px;font-family:Cascadia Code,Fira Code,JetBrains Mono,monospace;color:#808098;flex-wrap:wrap}.breadcrumb-segment{cursor:pointer;color:#a0a0b0;transition:color .15s}.breadcrumb-segment:hover,.breadcrumb-segment:active{color:#e94560}.breadcrumb-sep{color:#505070}.browse-list{display:flex;flex-direction:column;gap:2px;max-height:50vh;max-height:50dvh;overflow-y:auto}.browse-item{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:#112;border-radius:6px;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .1s}.browse-item:active{background:#1e1e36}.browse-item-name{font-size:14px;color:#d0d0e0;font-family:Cascadia Code,Fira Code,JetBrains Mono,monospace}.browse-item-arrow{color:#505070;font-size:14px}.browse-empty{text-align:center;padding:24px;color:#505070;font-size:13px}.hidden{display:none!important}.setting-row{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;background:#112;border-radius:10px}.setting-info{display:flex;align-items:center;gap:12px;flex:1;min-width:0}.setting-icon{font-size:22px;flex-shrink:0}.setting-label{font-size:15px;font-weight:600;color:#f0f0f0}.setting-sublabel{font-size:11px;color:#707090;margin-top:2px}.toggle-btn{width:48px;height:28px;border-radius:14px;background:#2a2a44;border:none;cursor:pointer;position:relative;flex-shrink:0;transition:background .25s ease;-webkit-tap-highlight-color:transparent}.toggle-btn[aria-checked=true]{background:#2ecc71}.toggle-btn:disabled{opacity:.4;cursor:default}.toggle-knob{display:block;width:22px;height:22px;border-radius:50%;background:#707090;position:absolute;top:3px;left:3px;transition:transform .25s ease,background .25s ease}.toggle-btn[aria-checked=true] .toggle-knob{transform:translate(20px);background:#fff}.setting-warning{margin-top:12px;padding:10px 14px;background:#f39c121a;border:1px solid rgba(243,156,18,.25);border-radius:8px;font-size:12px;color:#f39c12;line-height:1.5}.setting-note{margin-top:8px;font-size:11px;color:#505070;text-align:center}.setting-tip{margin-top:10px;padding:8px 12px;background:#0f346066;border-radius:6px;font-size:12px;color:#808098;line-height:1.5}#sleep-password-section{margin-top:16px;padding-top:16px;border-top:1px solid #0f3460}#sleep-password-section .form-label{margin-top:0}.sleep-actions{display:flex;gap:10px;margin-top:14px}.sleep-actions .action-btn{flex:1;padding:12px;font-size:14px}.sleep-actions .btn-create{flex:1;margin-top:0;padding:12px;font-size:14px}
@@ -0,0 +1,2 @@
1
+ (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))o(s);new MutationObserver(s=>{for(const a of s)if(a.type==="childList")for(const c of a.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&o(c)}).observe(document,{childList:!0,subtree:!0});function n(s){const a={};return s.integrity&&(a.integrity=s.integrity),s.referrerPolicy&&(a.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?a.credentials="include":s.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function o(s){if(s.ep)return;s.ep=!0;const a=n(s);fetch(s.href,a)}})();function Z(e){const t=Math.floor(e/1e3);if(t<60)return`${t}s`;const n=Math.floor(t/60);if(n<60)return`${n}m`;const o=Math.floor(n/60),s=n%60;return`${o}h ${s}m`}function Be(e){const t=e.match(/^\/(?:Users|home)\/[^/]+/)?.[0];return t?"~"+e.slice(t.length):e}function Se(e){return e<1024?`${e} KB`:`${(e/1024).toFixed(1)} MB`}function Ne(e,t,n){const o=document.createElement("div");o.className=`session-card${e.status==="attention"?" attention":""}`,o.dataset.sessionId=e.id;const s=Z(Date.now()-e.connectedAt),a=Be(e.cwd),c=document.createElement("div");c.className="card-header";const i=document.createElement("div");i.className="card-agent";const y=document.createElement("div");y.className=`agent-dot ${e.status}`;const W=document.createElement("span");W.className="agent-name",W.textContent=e.name||e.agent,i.appendChild(y),i.appendChild(W);const _=document.createElement("span");_.className="card-uptime",_.textContent=s,c.appendChild(i),c.appendChild(_);const j=document.createElement("div");j.className="card-cwd",j.textContent=a;const N=document.createElement("div");N.className="card-preview";const K=document.createElement("pre");K.className="card-preview-text",K.textContent="Waiting for output...",N.appendChild(K),N.addEventListener("click",l=>{l.stopPropagation(),n.onOpen(e)});const x=document.createElement("div");x.className="card-status";const P=document.createElement("span");P.className=`status-badge ${e.status}`,P.textContent=e.status,e.status==="attention"&&(P.style.display="none");const $=document.createElement("span");$.className="attention-badge",$.textContent="Needs your attention",e.status!=="attention"&&($.style.display="none"),x.appendChild(P),x.appendChild($);const g=document.createElement("div");g.className="card-actions";const M=document.createElement("button");M.className="action-btn stop",M.textContent="Stop",M.addEventListener("click",l=>{l.stopPropagation(),xe(o,e.id,n.onStop)});const A=document.createElement("button");A.className="action-btn rename",A.textContent="Rename",A.addEventListener("click",l=>{l.stopPropagation(),Pe(o,e.id,n.onRename)});const O=document.createElement("button");O.className="action-btn info",O.textContent="Info",O.addEventListener("click",l=>{l.stopPropagation(),n.onInfo(e.id)});const T=document.createElement("button");T.className="action-btn open",T.textContent="Open",T.addEventListener("click",l=>{l.stopPropagation(),n.onOpen(e)}),g.appendChild(T),g.appendChild(A),g.appendChild(O),g.appendChild(M);const ie=document.createElement("div");return ie.className="card-metadata hidden",o.addEventListener("click",()=>{n.onOpen(e)}),o.appendChild(c),o.appendChild(j),o.appendChild(N),o.appendChild(x),o.appendChild(g),o.appendChild(ie),o}function xe(e,t,n){e.querySelector(".confirm-overlay")?.remove();const o=document.createElement("div");o.className="confirm-overlay";const s=document.createElement("span");s.className="confirm-msg",s.textContent="Stop this session?";const a=document.createElement("button");a.className="confirm-btn yes",a.textContent="Yes",a.addEventListener("click",i=>{i.stopPropagation(),n(t),o.remove()});const c=document.createElement("button");c.className="confirm-btn no",c.textContent="No",c.addEventListener("click",i=>{i.stopPropagation(),o.remove()}),o.addEventListener("click",i=>i.stopPropagation()),o.appendChild(s),o.appendChild(a),o.appendChild(c),e.appendChild(o)}function Pe(e,t,n){const o=e.querySelector(".agent-name");if(!o)return;const s=o.textContent||"",a=document.createElement("input");a.className="rename-input",a.type="text",a.value=s;const c=()=>{const i=a.value.trim();i&&i!==s&&n(t,i),o.textContent=i||s,o.style.display="",a.remove()};a.addEventListener("keydown",i=>{i.stopPropagation(),i.key==="Enter"?c():i.key==="Escape"&&(o.textContent=s,o.style.display="",a.remove())}),a.addEventListener("blur",c),a.addEventListener("click",i=>i.stopPropagation()),o.style.display="none",o.parentElement?.insertBefore(a,o.nextSibling),a.focus(),a.select()}function be(e,t){const n=e.querySelector(".agent-dot"),o=e.querySelector(".status-badge"),s=e.querySelector(".card-uptime"),a=e.querySelector(".agent-name");n&&(n.className=`agent-dot ${t.status}`),o&&(o.className=`status-badge ${t.status}`,o.textContent=t.status),s&&(s.textContent=Z(Date.now()-t.connectedAt)),a&&!a.style.display&&(a.textContent=t.name||t.agent);const c=t.status==="attention";o&&(o.style.display=c?"none":"");const i=e.querySelector(".attention-badge");i&&(i.style.display=c?"":"none"),t.status==="attention"?e.classList.add("attention"):e.classList.remove("attention")}function $e(e,t){const n=e.querySelector(".card-preview-text");n&&(t.length>0?(n.textContent=t.join(`
2
+ `),n.classList.remove("empty")):(n.textContent="Waiting for output...",n.classList.add("empty")))}function Me(e,t){const n=e.querySelector(".card-metadata");if(!n)return;for(;n.firstChild;)n.removeChild(n.firstChild);const o=[["PID",String(t.pid)],["Agent",t.agent],["Port",String(t.port)],["Directory",t.cwd],["Memory",Se(t.memoryKB)],["Uptime",Z(t.uptimeMs)]];for(const[s,a]of o){const c=document.createElement("div");c.className="meta-row";const i=document.createElement("span");i.className="meta-label",i.textContent=s;const y=document.createElement("span");y.className="meta-value",y.textContent=a,c.appendChild(i),c.appendChild(y),n.appendChild(c)}n.classList.toggle("hidden")}let d=null,de=!1;function Ae(){if(!de){de=!0;try{d=new AudioContext,d.state==="suspended"&&d.resume()}catch{}}}function re(){if(!d)return;d.state==="suspended"&&d.resume();const e=d.currentTime,t=d.createOscillator(),n=d.createGain();t.frequency.value=587.33,t.type="sine",n.gain.setValueAtTime(.3,e),n.gain.exponentialRampToValueAtTime(.01,e+.3),t.connect(n),n.connect(d.destination),t.start(e),t.stop(e+.3);const o=d.createOscillator(),s=d.createGain();o.frequency.value=880,o.type="sine",s.gain.setValueAtTime(.3,e+.15),s.gain.exponentialRampToValueAtTime(.01,e+.45),o.connect(s),s.connect(d.destination),o.start(e+.15),o.stop(e+.45)}const le=120*1e3,h=new Map;function me(e){if(h.has(e))return;re();const t=setTimeout(function n(){re();const o=h.get(e);o&&(o.timerId=setTimeout(n,le))},le);h.set(e,{timerId:t})}function Y(e){const t=h.get(e);t&&(clearTimeout(t.timerId),h.delete(e))}function ue(){for(const e of h.values())clearTimeout(e.timerId);h.clear()}let z=null,k,u,I,f,b,D,H,pe;function Oe(e){z=e;const t=document.getElementById("btn-settings");k=document.getElementById("settings-modal");const n=document.getElementById("settings-modal-close");u=document.getElementById("sleep-toggle"),I=document.getElementById("sleep-password-section"),f=document.getElementById("sleep-password"),b=document.getElementById("sleep-error"),D=document.getElementById("sleep-spinner"),H=document.getElementById("sleep-unsupported"),pe=document.getElementById("sleep-enable");const o=document.getElementById("sleep-cancel");t.addEventListener("click",()=>{k.classList.remove("hidden")}),n.addEventListener("click",he),k.addEventListener("click",s=>{s.target===k&&he()}),u.addEventListener("click",()=>{if(u.disabled)return;u.getAttribute("aria-checked")==="true"?z?.({type:"disable-sleep-prevention"}):(I.classList.remove("hidden"),b.classList.add("hidden"),f.value="",f.focus())}),pe.addEventListener("click",fe),f.addEventListener("keydown",s=>{s.key==="Enter"&&(s.preventDefault(),fe())}),o.addEventListener("click",ee)}function ee(){I.classList.add("hidden"),f.value="",b.classList.add("hidden")}function fe(){const e=f.value;if(!e){Ce("Password is required");return}f.value="",b.classList.add("hidden"),I.classList.add("hidden"),D.classList.remove("hidden"),z?.({type:"enable-sleep-prevention",password:e})}function he(){k.classList.add("hidden"),ee()}function Ce(e){b.textContent=e,b.classList.remove("hidden")}function Te(e){if(D.classList.add("hidden"),!e.supported){u.disabled=!0,u.setAttribute("aria-checked","false"),H.classList.remove("hidden");return}H.classList.add("hidden"),u.disabled=!1,u.setAttribute("aria-checked",e.enabled?"true":"false"),ee()}function Re(e){D.classList.add("hidden"),I.classList.remove("hidden"),Ce(e),u.setAttribute("aria-checked","false")}const Ue=new URLSearchParams(window.location.search),V=Ue.get("token");if(!V)throw document.body.textContent="Missing authentication token.",new Error("No token in URL");const qe=window.location.protocol==="https:"?"wss:":"ws:",De=`${qe}//${window.location.host}?token=${V}`,we=window.location.hostname,w=new Map,p=new Map,Ve=document.getElementById("session-list"),X=document.getElementById("empty-state"),Fe=document.getElementById("session-count"),ye=document.getElementById("status-dot");let G=null;function R(){Ae(),document.removeEventListener("click",R),document.removeEventListener("touchstart",R)}document.addEventListener("click",R);document.addEventListener("touchstart",R);function v(e){r&&r.readyState===WebSocket.OPEN&&r.send(JSON.stringify(e))}Oe(v);const We={onOpen(e){const t=w.get(e.id)||e;t.status==="attention"&&(v({type:"clear-attention",sessionId:t.id}),Y(t.id));const n=window.location.href,o=`http://${we}:${t.port}?token=${t.token}&hub=${encodeURIComponent(n)}`;window.location.href=o},onStop(e){v({type:"stop-session",sessionId:e})},onRename(e,t){v({type:"rename-session",sessionId:e,name:t})},onInfo(e){v({type:"get-metadata",sessionId:e})}};function Le(){const e=w.size;Fe.textContent=`${e} session${e!==1?"s":""}`,e===0?X.style.display="flex":X.style.display="none"}function ge(e){w.set(e.id,e);const t=Ne(e,we,We);p.set(e.id,t),Ve.insertBefore(t,X),Le()}function Ee(e){w.delete(e);const t=p.get(e);t&&(t.remove(),p.delete(e)),Le()}function _e(e){w.set(e.id,e);const t=p.get(e.id);t&&be(t,e)}function je(){for(const[e,t]of w){const n=p.get(e);n&&be(n,t)}}const te=document.getElementById("fab-create"),B=document.getElementById("create-modal"),Ke=document.getElementById("modal-close"),C=document.getElementById("tool-input"),ve=document.getElementById("tool-chips"),ke=document.getElementById("dir-selected"),Ge=document.getElementById("btn-browse"),Ie=document.getElementById("btn-create-session"),U=document.getElementById("create-error"),L=document.getElementById("create-form-view"),F=document.getElementById("browse-view"),J=document.getElementById("browse-breadcrumb"),E=document.getElementById("browse-list"),Je=document.getElementById("browse-back"),Ye=document.getElementById("browse-select"),ne=document.getElementById("create-spinner");let S="~",Q="~";function m(e,t,n){const o=document.createElement(e);return o.className=t,o.textContent=n,o}function ze(){B.classList.remove("hidden"),L.classList.remove("hidden"),F.classList.add("hidden"),ne.classList.add("hidden"),U.classList.add("hidden"),C.value="",S="~",ke.textContent="~",se(),He(),C.focus()}function oe(){B.classList.add("hidden")}function se(){Ie.disabled=!C.value.trim()}async function He(){try{const t=await(await fetch(`/api/tool-history?token=${V}`)).json();ve.replaceChildren();for(const n of t.tools.slice(0,6)){const o=m("span","chip",n);o.addEventListener("click",()=>{C.value=n,se()}),ve.appendChild(o)}}catch{}}async function ae(e){Q=e,E.replaceChildren(m("div","browse-empty","Loading..."));try{const n=await(await fetch(`/api/browse?path=${encodeURIComponent(e)}&token=${V}`)).json();if(n.error){E.replaceChildren(m("div","browse-empty",n.error));return}const o=n.path||e;Q=o,Xe(o);const s=n.entries;if(s.length===0){E.replaceChildren(m("div","browse-empty","No subdirectories"));return}E.replaceChildren();for(const a of s){const c=document.createElement("div");c.className="browse-item",c.appendChild(m("span","browse-item-name",a)),c.appendChild(m("span","browse-item-arrow","›")),c.addEventListener("click",()=>ae(`${o}/${a}`)),E.appendChild(c)}}catch{E.replaceChildren(m("div","browse-empty","Failed to load"))}}function Xe(e){J.replaceChildren();const t=e.split("/").filter(Boolean);for(let n=0;n<t.length;n++){n>0&&J.appendChild(m("span","breadcrumb-sep"," / "));const o=m("span","breadcrumb-segment",t[n]),s=t.slice(0,n+1).join("/");o.addEventListener("click",()=>ae(s)),J.appendChild(o)}}function Qe(){const e=C.value.trim();e&&(U.classList.add("hidden"),L.classList.add("hidden"),ne.classList.remove("hidden"),v({type:"create-session",tool:e,cwd:S}))}te.addEventListener("click",ze);Ke.addEventListener("click",oe);B.addEventListener("click",e=>{e.target===B&&oe()});C.addEventListener("input",se);Ie.addEventListener("click",Qe);Ge.addEventListener("click",()=>{L.classList.add("hidden"),F.classList.remove("hidden"),ae(S)});Je.addEventListener("click",()=>{F.classList.add("hidden"),L.classList.remove("hidden")});Ye.addEventListener("click",()=>{S=Q,ke.textContent=S,F.classList.add("hidden"),L.classList.remove("hidden")});let r=null,q=0;const Ze=1e4;function ce(){r=new WebSocket(De),r.onopen=()=>{ye.className="connected",q=0,G&&clearInterval(G),G=setInterval(je,1e4)},r.onmessage=e=>{try{const t=JSON.parse(e.data);switch(t.type){case"sessions":{ue();for(const n of p.keys())Ee(n);for(const n of t.sessions)ge(n),n.status==="attention"&&me(n.id);break}case"session-added":{ge(t.session),B.classList.contains("hidden")||oe();break}case"session-removed":{const n=t.sessionId;Y(n),Ee(n);break}case"session-updated":{const n=t.session;_e(n),n.status==="attention"?me(n.id):Y(n.id);break}case"preview":{const n=p.get(t.sessionId);n&&$e(n,t.lines);break}case"metadata":{const n=p.get(t.sessionId);n&&Me(n,t.metadata);break}case"session-creating":break;case"session-create-error":{ne.classList.add("hidden"),L.classList.remove("hidden"),U.textContent=t.error,U.classList.remove("hidden");break}case"sleep-state":{Te(t.state);break}case"sleep-error":{Re(t.error);break}case"operation-error":{console.warn(`Operation "${t.operation}" failed for session ${t.sessionId}: ${t.error}`);break}}}catch{}},r.onclose=()=>{ye.className="reconnecting",ue(),et()},r.onerror=()=>{r?.close()}}function et(){const e=Math.min(1e3*Math.pow(1.5,q),Ze);q++,setTimeout(ce,e)}document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&r?.readyState!==WebSocket.OPEN&&(q=0,ce())});ce();document.addEventListener("focusin",e=>{const t=e.target;t.matches("input, textarea")&&(te.style.display="none",setTimeout(()=>{t.scrollIntoView({block:"center",behavior:"smooth"})},300))});document.addEventListener("focusout",()=>{te.style.display=""});
@@ -2,13 +2,13 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no, interactive-widget=resizes-content" />
6
6
  <meta name="apple-mobile-web-app-capable" content="yes" />
7
7
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
8
8
  <meta name="theme-color" content="#1a1a2e" />
9
9
  <title>itwillsync Dashboard</title>
10
- <script type="module" crossorigin src="/assets/index-C0aiYBkq.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-D6z7Ixhf.css">
10
+ <script type="module" crossorigin src="/assets/index-hWUVy-IH.js"></script>
11
+ <link rel="stylesheet" crossorigin href="/assets/index-CUdWjWFv.css">
12
12
  </head>
13
13
  <body>
14
14
  <header id="header">
@@ -16,7 +16,15 @@
16
16
  <span class="logo">itwillsync</span>
17
17
  <span id="status-dot"></span>
18
18
  </div>
19
- <span id="session-count">0 sessions</span>
19
+ <div class="header-right">
20
+ <span id="session-count">0 sessions</span>
21
+ <button id="btn-settings" class="header-icon-btn" aria-label="Settings">
22
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
23
+ <circle cx="12" cy="12" r="3"/>
24
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
25
+ </svg>
26
+ </button>
27
+ </div>
20
28
  </header>
21
29
 
22
30
  <main id="session-list">
@@ -72,5 +80,54 @@
72
80
  </div>
73
81
  </div>
74
82
 
83
+ <!-- Settings Modal -->
84
+ <div id="settings-modal" class="modal-overlay hidden">
85
+ <div class="modal-content">
86
+ <div class="modal-header">
87
+ <span class="modal-title">Settings</span>
88
+ <button class="modal-close" id="settings-modal-close">&times;</button>
89
+ </div>
90
+ <div id="settings-body">
91
+ <div class="setting-row">
92
+ <div class="setting-info">
93
+ <span class="setting-icon">&#9749;</span>
94
+ <div>
95
+ <div class="setting-label">Prevent Sleep</div>
96
+ <div class="setting-sublabel">Keep computer awake even with lid closed</div>
97
+ </div>
98
+ </div>
99
+ <button id="sleep-toggle" class="toggle-btn" role="switch" aria-checked="false">
100
+ <span class="toggle-knob"></span>
101
+ </button>
102
+ </div>
103
+
104
+ <div class="setting-warning">
105
+ &#9888;&#65039; <strong>Battery warning:</strong> Your laptop may shut down if running on battery. Keep it plugged in.
106
+ </div>
107
+ <div class="setting-note">Requires admin password &middot; Auto-reverts when hub stops</div>
108
+ <div class="setting-tip">&#128187; Enable this while you're near your laptop &mdash; you'll need to type your system password.</div>
109
+
110
+ <div id="sleep-password-section" class="hidden">
111
+ <label class="form-label">System password</label>
112
+ <input type="password" id="sleep-password" class="form-input" placeholder="Enter your password" autocomplete="off" />
113
+ <div id="sleep-error" class="create-error hidden"></div>
114
+ <div class="sleep-actions">
115
+ <button id="sleep-cancel" class="action-btn">Cancel</button>
116
+ <button id="sleep-enable" class="btn-create">Enable</button>
117
+ </div>
118
+ </div>
119
+
120
+ <div id="sleep-spinner" class="create-spinner hidden">
121
+ <div class="spinner"></div>
122
+ <span>Applying...</span>
123
+ </div>
124
+
125
+ <div id="sleep-unsupported" class="setting-note hidden">
126
+ Sleep prevention is not available on this platform.
127
+ </div>
128
+ </div>
129
+ </div>
130
+ </div>
131
+
75
132
  </body>
76
133
  </html>
package/dist/index.js CHANGED
@@ -3772,19 +3772,26 @@ function validateToken(provided, expected) {
3772
3772
  import { networkInterfaces } from "os";
3773
3773
  import { createServer } from "net";
3774
3774
 
3775
- // src/tailscale.ts
3775
+ // src/exec-utils.ts
3776
3776
  import { execFile } from "child_process";
3777
- function execCommand(cmd, args) {
3777
+ function execFileAsync(cmd, args, opts) {
3778
3778
  return new Promise((resolve, reject) => {
3779
- execFile(cmd, args, { timeout: 5e3 }, (error, stdout, stderr) => {
3780
- if (error) {
3781
- reject(error);
3782
- } else {
3783
- resolve({ stdout: stdout.trim(), stderr: stderr.trim() });
3779
+ execFile(
3780
+ cmd,
3781
+ args,
3782
+ { timeout: opts?.timeout ?? 5e3 },
3783
+ (error, stdout, stderr) => {
3784
+ if (error) {
3785
+ reject(error);
3786
+ } else {
3787
+ resolve({ stdout: stdout.trim(), stderr: stderr.trim() });
3788
+ }
3784
3789
  }
3785
- });
3790
+ );
3786
3791
  });
3787
3792
  }
3793
+
3794
+ // src/tailscale.ts
3788
3795
  function getTailscalePaths() {
3789
3796
  const paths = ["tailscale"];
3790
3797
  if (process.platform === "darwin") {
@@ -3796,7 +3803,7 @@ async function tryExec(args) {
3796
3803
  let lastError = null;
3797
3804
  for (const bin of getTailscalePaths()) {
3798
3805
  try {
3799
- const result = await execCommand(bin, args);
3806
+ const result = await execFileAsync(bin, args);
3800
3807
  return { status: "success", ...result };
3801
3808
  } catch (err) {
3802
3809
  if (err.code === "ENOENT") {
@@ -4744,6 +4751,40 @@ Hub Management:
4744
4751
  `);
4745
4752
  }
4746
4753
 
4754
+ // src/resolve-command.ts
4755
+ async function resolveCommand(command) {
4756
+ if (process.platform !== "win32") {
4757
+ return command;
4758
+ }
4759
+ if (/^[a-zA-Z]:[/\\]/.test(command) || command.startsWith("\\\\")) {
4760
+ return command;
4761
+ }
4762
+ try {
4763
+ const { stdout } = await execFileAsync("where.exe", [command]);
4764
+ const firstMatch = stdout.split("\n")[0]?.trim();
4765
+ if (firstMatch) {
4766
+ return firstMatch;
4767
+ }
4768
+ throw new Error(`where.exe returned empty output for "${command}"`);
4769
+ } catch (err) {
4770
+ if (err.code === "ENOENT") {
4771
+ return command;
4772
+ }
4773
+ throw new Error(
4774
+ `Could not find "${command}" on this system.
4775
+
4776
+ To fix this:
4777
+ 1. Make sure "${command}" is installed
4778
+ 2. Open a new terminal and run: ${command} --version
4779
+ 3. If that works, try running itwillsync again
4780
+
4781
+ If "${command}" was just installed, you may need to restart your
4782
+ terminal so Windows can find it in your PATH.`,
4783
+ { cause: err }
4784
+ );
4785
+ }
4786
+ }
4787
+
4747
4788
  // src/index.ts
4748
4789
  import { fileURLToPath as fileURLToPath4 } from "url";
4749
4790
  import { join as join7, dirname as dirname4 } from "path";
@@ -4883,14 +4924,7 @@ async function main() {
4883
4924
  }
4884
4925
  const headless = options.headless;
4885
4926
  const config = loadConfig();
4886
- let networkingMode = "local";
4887
- if (options.tailscale) {
4888
- networkingMode = "tailscale";
4889
- } else if (options.local) {
4890
- networkingMode = "local";
4891
- } else {
4892
- networkingMode = config.networkingMode;
4893
- }
4927
+ const networkingMode = options.tailscale ? "tailscale" : options.local ? "local" : config.networkingMode;
4894
4928
  const [cmd, ...cmdArgs] = options.command;
4895
4929
  const isFirstSession = await ensureHub();
4896
4930
  const hubConfig = getHubConfig();
@@ -4900,7 +4934,23 @@ async function main() {
4900
4934
  const ip = await resolveSessionIP(networkingMode, options.localhost);
4901
4935
  const __dirname2 = dirname4(fileURLToPath4(import.meta.url));
4902
4936
  const webClientPath = join7(__dirname2, "web-client");
4903
- const ptyManager = new PtyManager(cmd, cmdArgs);
4937
+ const resolvedCmd = await resolveCommand(cmd);
4938
+ let ptyManager;
4939
+ try {
4940
+ ptyManager = new PtyManager(resolvedCmd, cmdArgs);
4941
+ } catch {
4942
+ console.error(
4943
+ `
4944
+ Could not start "${cmd}".
4945
+
4946
+ To fix this:
4947
+ 1. Make sure "${cmd}" is installed
4948
+ 2. Open a new terminal and run: ${cmd} --version
4949
+ 3. If that works, try running itwillsync again
4950
+ `
4951
+ );
4952
+ process.exit(1);
4953
+ }
4904
4954
  const sessionId = `${cmd}-${Date.now().toString(36)}`;
4905
4955
  const sessionLogger = new SessionLogger(sessionId);
4906
4956
  const server = createSyncServer({