itwillsync 1.2.0 → 1.3.0
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/dist/hub/daemon.js +4523 -0
- package/dist/hub/daemon.js.map +1 -0
- package/dist/hub/dashboard/assets/index-DgUZUPW_.js +2 -0
- package/dist/hub/dashboard/assets/index-Erqx_a0N.css +1 -0
- package/dist/hub/dashboard/index.html +31 -0
- package/dist/index.js +413 -27
- package/dist/index.js.map +1 -1
- package/dist/web-client/assets/{index-la8_PIAu.js → index--759FJd4.js} +16 -16
- package/dist/web-client/assets/{index-Cj7rG9re.css → index-DI3EO9qL.css} +1 -1
- package/dist/web-client/index.html +3 -2
- package/package.json +1 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))o(a);new MutationObserver(a=>{for(const s of a)if(s.type==="childList")for(const i of s.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&o(i)}).observe(document,{childList:!0,subtree:!0});function n(a){const s={};return a.integrity&&(s.integrity=a.integrity),a.referrerPolicy&&(s.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?s.credentials="include":a.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function o(a){if(a.ep)return;a.ep=!0;const s=n(a);fetch(a.href,s)}})();function B(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),a=n%60;return`${o}h ${a}m`}function z(e){const t=e.match(/^\/(?:Users|home)\/[^/]+/)?.[0];return t?"~"+e.slice(t.length):e}function X(e){return e<1024?`${e} KB`:`${(e/1024).toFixed(1)} MB`}function H(e,t,n){const o=document.createElement("div");o.className=`session-card${e.status==="attention"?" attention":""}`,o.dataset.sessionId=e.id;const a=B(Date.now()-e.connectedAt),s=z(e.cwd),i=document.createElement("div");i.className="card-header";const c=document.createElement("div");c.className="card-agent";const p=document.createElement("div");p.className=`agent-dot ${e.status}`;const P=document.createElement("span");P.className="agent-name",P.textContent=e.name||e.agent,c.appendChild(p),c.appendChild(P);const k=document.createElement("span");k.className="card-uptime",k.textContent=a,i.appendChild(c),i.appendChild(k);const I=document.createElement("div");I.className="card-cwd",I.textContent=s;const h=document.createElement("div");h.className="card-preview";const $=document.createElement("pre");$.className="card-preview-text",$.textContent="Waiting for output...",h.appendChild($),h.addEventListener("click",l=>{l.stopPropagation(),n.onOpen(e)});const g=document.createElement("div");g.className="card-status";const E=document.createElement("span");E.className=`status-badge ${e.status}`,E.textContent=e.status,e.status==="attention"&&(E.style.display="none");const v=document.createElement("span");v.className="attention-badge",v.textContent="Needs your attention",e.status!=="attention"&&(v.style.display="none"),g.appendChild(E),g.appendChild(v);const f=document.createElement("div");f.className="card-actions";const C=document.createElement("button");C.className="action-btn stop",C.textContent="Stop",C.addEventListener("click",l=>{l.stopPropagation(),Q(o,e.id,n.onStop)});const w=document.createElement("button");w.className="action-btn rename",w.textContent="Rename",w.addEventListener("click",l=>{l.stopPropagation(),Z(o,e.id,n.onRename)});const N=document.createElement("button");N.className="action-btn info",N.textContent="Info",N.addEventListener("click",l=>{l.stopPropagation(),n.onInfo(e.id)});const b=document.createElement("button");b.className="action-btn open",b.textContent="Open",b.addEventListener("click",l=>{l.stopPropagation(),n.onOpen(e)}),f.appendChild(b),f.appendChild(w),f.appendChild(N),f.appendChild(C);const R=document.createElement("div");return R.className="card-metadata hidden",o.addEventListener("click",()=>{n.onOpen(e)}),o.appendChild(i),o.appendChild(I),o.appendChild(h),o.appendChild(g),o.appendChild(f),o.appendChild(R),o}function Q(e,t,n){e.querySelector(".confirm-overlay")?.remove();const o=document.createElement("div");o.className="confirm-overlay";const a=document.createElement("span");a.className="confirm-msg",a.textContent="Stop this session?";const s=document.createElement("button");s.className="confirm-btn yes",s.textContent="Yes",s.addEventListener("click",c=>{c.stopPropagation(),n(t),o.remove()});const i=document.createElement("button");i.className="confirm-btn no",i.textContent="No",i.addEventListener("click",c=>{c.stopPropagation(),o.remove()}),o.addEventListener("click",c=>c.stopPropagation()),o.appendChild(a),o.appendChild(s),o.appendChild(i),e.appendChild(o)}function Z(e,t,n){const o=e.querySelector(".agent-name");if(!o)return;const a=o.textContent||"",s=document.createElement("input");s.className="rename-input",s.type="text",s.value=a;const i=()=>{const c=s.value.trim();c&&c!==a&&n(t,c),o.textContent=c||a,o.style.display="",s.remove()};s.addEventListener("keydown",c=>{c.stopPropagation(),c.key==="Enter"?i():c.key==="Escape"&&(o.textContent=a,o.style.display="",s.remove())}),s.addEventListener("blur",i),s.addEventListener("click",c=>c.stopPropagation()),o.style.display="none",o.parentElement?.insertBefore(s,o.nextSibling),s.focus(),s.select()}function G(e,t){const n=e.querySelector(".agent-dot"),o=e.querySelector(".status-badge"),a=e.querySelector(".card-uptime"),s=e.querySelector(".agent-name");n&&(n.className=`agent-dot ${t.status}`),o&&(o.className=`status-badge ${t.status}`,o.textContent=t.status),a&&(a.textContent=B(Date.now()-t.connectedAt)),s&&!s.style.display&&(s.textContent=t.name||t.agent);const i=t.status==="attention";o&&(o.style.display=i?"none":"");const c=e.querySelector(".attention-badge");c&&(c.style.display=i?"":"none"),t.status==="attention"?e.classList.add("attention"):e.classList.remove("attention")}function ee(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 te(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",X(t.memoryKB)],["Uptime",B(t.uptimeMs)]];for(const[a,s]of o){const i=document.createElement("div");i.className="meta-row";const c=document.createElement("span");c.className="meta-label",c.textContent=a;const p=document.createElement("span");p.className="meta-value",p.textContent=s,i.appendChild(c),i.appendChild(p),n.appendChild(i)}n.classList.toggle("hidden")}let r=null,q=!1;function ne(){if(!q){q=!0;try{r=new AudioContext,r.state==="suspended"&&r.resume()}catch{}}}function U(){if(!r)return;r.state==="suspended"&&r.resume();const e=r.currentTime,t=r.createOscillator(),n=r.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(r.destination),t.start(e),t.stop(e+.3);const o=r.createOscillator(),a=r.createGain();o.frequency.value=880,o.type="sine",a.gain.setValueAtTime(.3,e+.15),a.gain.exponentialRampToValueAtTime(.01,e+.45),o.connect(a),a.connect(r.destination),o.start(e+.15),o.stop(e+.45)}const D=120*1e3,u=new Map;function _(e){if(u.has(e))return;U();const t=setTimeout(function n(){U();const o=u.get(e);o&&(o.timerId=setTimeout(n,D))},D);u.set(e,{timerId:t})}function A(e){const t=u.get(e);t&&(clearTimeout(t.timerId),u.delete(e))}function V(){for(const e of u.values())clearTimeout(e.timerId);u.clear()}const oe=new URLSearchParams(window.location.search),J=oe.get("token");if(!J)throw document.body.textContent="Missing authentication token.",new Error("No token in URL");const ae=window.location.protocol==="https:"?"wss:":"ws:",se=`${ae}//${window.location.host}?token=${J}`,Y=window.location.hostname,y=new Map,m=new Map,ce=document.getElementById("session-list"),O=document.getElementById("empty-state"),ie=document.getElementById("session-count"),W=document.getElementById("status-dot");let M=null;function x(){ne(),document.removeEventListener("click",x),document.removeEventListener("touchstart",x)}document.addEventListener("click",x);document.addEventListener("touchstart",x);function S(e){d&&d.readyState===WebSocket.OPEN&&d.send(JSON.stringify(e))}const re={onOpen(e){const t=y.get(e.id)||e;t.status==="attention"&&(S({type:"clear-attention",sessionId:t.id}),A(t.id));const n=window.location.href,o=`http://${Y}:${t.port}?token=${t.token}&hub=${encodeURIComponent(n)}`;window.open(o,"_blank")},onStop(e){S({type:"stop-session",sessionId:e})},onRename(e,t){S({type:"rename-session",sessionId:e,name:t})},onInfo(e){S({type:"get-metadata",sessionId:e})}};function j(){const e=y.size;ie.textContent=`${e} session${e!==1?"s":""}`,e===0?O.style.display="flex":O.style.display="none"}function F(e){y.set(e.id,e);const t=H(e,Y,re);m.set(e.id,t),ce.insertBefore(t,O),j()}function K(e){y.delete(e);const t=m.get(e);t&&(t.remove(),m.delete(e)),j()}function de(e){y.set(e.id,e);const t=m.get(e.id);t&&G(t,e)}function le(){for(const[e,t]of y){const n=m.get(e);n&&G(n,t)}}let d=null,L=0;const me=1e4;function T(){d=new WebSocket(se),d.onopen=()=>{W.className="connected",L=0,M&&clearInterval(M),M=setInterval(le,1e4)},d.onmessage=e=>{try{const t=JSON.parse(e.data);switch(t.type){case"sessions":{V();for(const n of m.keys())K(n);for(const n of t.sessions)F(n),n.status==="attention"&&_(n.id);break}case"session-added":{F(t.session);break}case"session-removed":{const n=t.sessionId;A(n),K(n);break}case"session-updated":{const n=t.session;de(n),n.status==="attention"?_(n.id):A(n.id);break}case"preview":{const n=m.get(t.sessionId);n&&ee(n,t.lines);break}case"metadata":{const n=m.get(t.sessionId);n&&te(n,t.metadata);break}case"operation-error":{console.warn(`Operation "${t.operation}" failed for session ${t.sessionId}: ${t.error}`);break}}}catch{}},d.onclose=()=>{W.className="reconnecting",V(),ue()},d.onerror=()=>{d?.close()}}function ue(){const e=Math.min(1e3*Math.pow(1.5,L),me);L++,setTimeout(T,e)}document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&d?.readyState!==WebSocket.OPEN&&(L=0,T())});T();
|
|
@@ -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}#session-count{font-size:13px;color:#a0a0b0}#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)}}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no" />
|
|
6
|
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
7
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
8
|
+
<meta name="theme-color" content="#1a1a2e" />
|
|
9
|
+
<title>itwillsync Dashboard</title>
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-DgUZUPW_.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Erqx_a0N.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<header id="header">
|
|
15
|
+
<div class="header-left">
|
|
16
|
+
<span class="logo">itwillsync</span>
|
|
17
|
+
<span id="status-dot"></span>
|
|
18
|
+
</div>
|
|
19
|
+
<span id="session-count">0 sessions</span>
|
|
20
|
+
</header>
|
|
21
|
+
|
|
22
|
+
<main id="session-list">
|
|
23
|
+
<div id="empty-state">
|
|
24
|
+
<div class="empty-icon">💻</div>
|
|
25
|
+
<p>No active sessions</p>
|
|
26
|
+
<p class="empty-hint">Start an agent with <code>itwillsync -- claude</code></p>
|
|
27
|
+
</div>
|
|
28
|
+
</main>
|
|
29
|
+
|
|
30
|
+
</body>
|
|
31
|
+
</html>
|
package/dist/index.js
CHANGED
|
@@ -2766,7 +2766,7 @@ var require_websocket = __commonJS({
|
|
|
2766
2766
|
}
|
|
2767
2767
|
const defaultPort = isSecure ? 443 : 80;
|
|
2768
2768
|
const key = randomBytes2(16).toString("base64");
|
|
2769
|
-
const
|
|
2769
|
+
const request2 = isSecure ? https.request : http.request;
|
|
2770
2770
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
2771
2771
|
let perMessageDeflate;
|
|
2772
2772
|
opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
|
|
@@ -2843,12 +2843,12 @@ var require_websocket = __commonJS({
|
|
|
2843
2843
|
if (opts.auth && !options.headers.authorization) {
|
|
2844
2844
|
options.headers.authorization = "Basic " + Buffer.from(opts.auth).toString("base64");
|
|
2845
2845
|
}
|
|
2846
|
-
req = websocket._req =
|
|
2846
|
+
req = websocket._req = request2(opts);
|
|
2847
2847
|
if (websocket._redirects) {
|
|
2848
2848
|
websocket.emit("redirect", websocket.url, req);
|
|
2849
2849
|
}
|
|
2850
2850
|
} else {
|
|
2851
|
-
req = websocket._req =
|
|
2851
|
+
req = websocket._req = request2(opts);
|
|
2852
2852
|
}
|
|
2853
2853
|
if (opts.timeout) {
|
|
2854
2854
|
req.on("timeout", () => {
|
|
@@ -3933,6 +3933,7 @@ function createSyncServer(options) {
|
|
|
3933
3933
|
const clients = /* @__PURE__ */ new Set();
|
|
3934
3934
|
const aliveMap = /* @__PURE__ */ new WeakMap();
|
|
3935
3935
|
let scrollbackBuffer = "";
|
|
3936
|
+
let seq = 0;
|
|
3936
3937
|
const httpServer = createServer2(async (req, res) => {
|
|
3937
3938
|
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
3938
3939
|
let pathname = url.pathname;
|
|
@@ -3969,7 +3970,7 @@ function createSyncServer(options) {
|
|
|
3969
3970
|
clients.add(ws);
|
|
3970
3971
|
aliveMap.set(ws, true);
|
|
3971
3972
|
if (scrollbackBuffer.length > 0) {
|
|
3972
|
-
ws.send(scrollbackBuffer);
|
|
3973
|
+
ws.send(JSON.stringify({ type: "data", data: scrollbackBuffer, seq }));
|
|
3973
3974
|
}
|
|
3974
3975
|
ws.on("pong", () => {
|
|
3975
3976
|
aliveMap.set(ws, true);
|
|
@@ -3981,6 +3982,12 @@ function createSyncServer(options) {
|
|
|
3981
3982
|
ptyManager.write(message.data);
|
|
3982
3983
|
} else if (message.type === "resize" && typeof message.cols === "number" && typeof message.rows === "number") {
|
|
3983
3984
|
ptyManager.resize(message.cols, message.rows);
|
|
3985
|
+
} else if (message.type === "resume" && typeof message.lastSeq === "number") {
|
|
3986
|
+
const missed = seq - message.lastSeq;
|
|
3987
|
+
if (missed > 0 && scrollbackBuffer.length > 0) {
|
|
3988
|
+
const delta = missed <= scrollbackBuffer.length ? scrollbackBuffer.slice(-missed) : scrollbackBuffer;
|
|
3989
|
+
ws.send(JSON.stringify({ type: "data", data: delta, seq }));
|
|
3990
|
+
}
|
|
3984
3991
|
}
|
|
3985
3992
|
} catch {
|
|
3986
3993
|
}
|
|
@@ -3993,13 +4000,15 @@ function createSyncServer(options) {
|
|
|
3993
4000
|
});
|
|
3994
4001
|
});
|
|
3995
4002
|
ptyManager.onData((data) => {
|
|
4003
|
+
seq += data.length;
|
|
3996
4004
|
scrollbackBuffer += data;
|
|
3997
4005
|
if (scrollbackBuffer.length > SCROLLBACK_BUFFER_SIZE) {
|
|
3998
4006
|
scrollbackBuffer = scrollbackBuffer.slice(-SCROLLBACK_BUFFER_SIZE);
|
|
3999
4007
|
}
|
|
4008
|
+
const msg = JSON.stringify({ type: "data", data, seq });
|
|
4000
4009
|
for (const client of clients) {
|
|
4001
4010
|
if (client.readyState === client.OPEN) {
|
|
4002
|
-
client.send(
|
|
4011
|
+
client.send(msg);
|
|
4003
4012
|
}
|
|
4004
4013
|
}
|
|
4005
4014
|
});
|
|
@@ -4140,8 +4149,233 @@ async function runSetupWizard() {
|
|
|
4140
4149
|
return config;
|
|
4141
4150
|
}
|
|
4142
4151
|
|
|
4152
|
+
// src/hub-client.ts
|
|
4153
|
+
import { spawn as spawn2 } from "child_process";
|
|
4154
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
4155
|
+
import { homedir as homedir2 } from "os";
|
|
4156
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
4157
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4158
|
+
import { request } from "http";
|
|
4159
|
+
var HUB_INTERNAL_PORT = 7963;
|
|
4160
|
+
var HUB_EXTERNAL_PORT = 7962;
|
|
4161
|
+
var SESSION_PORT_START = 7964;
|
|
4162
|
+
function getHubDir() {
|
|
4163
|
+
return process.env.ITWILLSYNC_CONFIG_DIR || join4(homedir2(), ".itwillsync");
|
|
4164
|
+
}
|
|
4165
|
+
function getHubConfigPath() {
|
|
4166
|
+
return join4(getHubDir(), "hub.json");
|
|
4167
|
+
}
|
|
4168
|
+
async function discoverHub() {
|
|
4169
|
+
return new Promise((resolve) => {
|
|
4170
|
+
const req = request(
|
|
4171
|
+
{
|
|
4172
|
+
hostname: "127.0.0.1",
|
|
4173
|
+
port: HUB_INTERNAL_PORT,
|
|
4174
|
+
path: "/api/health",
|
|
4175
|
+
method: "GET",
|
|
4176
|
+
timeout: 2e3
|
|
4177
|
+
},
|
|
4178
|
+
(res) => {
|
|
4179
|
+
let data = "";
|
|
4180
|
+
res.on("data", (chunk) => {
|
|
4181
|
+
data += chunk;
|
|
4182
|
+
});
|
|
4183
|
+
res.on("end", () => {
|
|
4184
|
+
try {
|
|
4185
|
+
const json = JSON.parse(data);
|
|
4186
|
+
resolve(json.status === "ok");
|
|
4187
|
+
} catch {
|
|
4188
|
+
resolve(false);
|
|
4189
|
+
}
|
|
4190
|
+
});
|
|
4191
|
+
}
|
|
4192
|
+
);
|
|
4193
|
+
req.on("error", () => resolve(false));
|
|
4194
|
+
req.on("timeout", () => {
|
|
4195
|
+
req.destroy();
|
|
4196
|
+
resolve(false);
|
|
4197
|
+
});
|
|
4198
|
+
req.end();
|
|
4199
|
+
});
|
|
4200
|
+
}
|
|
4201
|
+
async function spawnHub() {
|
|
4202
|
+
const __dirname = dirname2(fileURLToPath2(import.meta.url));
|
|
4203
|
+
const hubPath = join4(__dirname, "hub", "daemon.js");
|
|
4204
|
+
return new Promise((resolve, reject) => {
|
|
4205
|
+
const child = spawn2("node", [hubPath], {
|
|
4206
|
+
detached: true,
|
|
4207
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4208
|
+
env: {
|
|
4209
|
+
...process.env,
|
|
4210
|
+
// Pass config dir to hub
|
|
4211
|
+
ITWILLSYNC_CONFIG_DIR: process.env.ITWILLSYNC_CONFIG_DIR || ""
|
|
4212
|
+
}
|
|
4213
|
+
});
|
|
4214
|
+
child.unref();
|
|
4215
|
+
let output = "";
|
|
4216
|
+
const timeout = setTimeout(() => {
|
|
4217
|
+
reject(new Error("Hub daemon startup timed out"));
|
|
4218
|
+
}, 1e4);
|
|
4219
|
+
child.stdout?.on("data", (data) => {
|
|
4220
|
+
output += data.toString();
|
|
4221
|
+
if (output.includes("hub:ready:")) {
|
|
4222
|
+
clearTimeout(timeout);
|
|
4223
|
+
child.stdout?.destroy();
|
|
4224
|
+
resolve();
|
|
4225
|
+
}
|
|
4226
|
+
});
|
|
4227
|
+
child.on("error", (err) => {
|
|
4228
|
+
clearTimeout(timeout);
|
|
4229
|
+
reject(new Error(`Failed to spawn hub daemon: ${err.message}`));
|
|
4230
|
+
});
|
|
4231
|
+
child.on("exit", (code) => {
|
|
4232
|
+
clearTimeout(timeout);
|
|
4233
|
+
if (code !== null && code !== 0) {
|
|
4234
|
+
reject(new Error(`Hub daemon exited with code ${code}`));
|
|
4235
|
+
}
|
|
4236
|
+
});
|
|
4237
|
+
});
|
|
4238
|
+
}
|
|
4239
|
+
function getHubConfig() {
|
|
4240
|
+
const configPath = getHubConfigPath();
|
|
4241
|
+
if (!existsSync2(configPath)) {
|
|
4242
|
+
return null;
|
|
4243
|
+
}
|
|
4244
|
+
try {
|
|
4245
|
+
const raw = readFileSync2(configPath, "utf-8");
|
|
4246
|
+
return JSON.parse(raw);
|
|
4247
|
+
} catch {
|
|
4248
|
+
return null;
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
async function registerSession(registration) {
|
|
4252
|
+
const body = JSON.stringify(registration);
|
|
4253
|
+
return new Promise((resolve, reject) => {
|
|
4254
|
+
const req = request(
|
|
4255
|
+
{
|
|
4256
|
+
hostname: "127.0.0.1",
|
|
4257
|
+
port: HUB_INTERNAL_PORT,
|
|
4258
|
+
path: "/api/sessions",
|
|
4259
|
+
method: "POST",
|
|
4260
|
+
headers: {
|
|
4261
|
+
"Content-Type": "application/json",
|
|
4262
|
+
"Content-Length": Buffer.byteLength(body)
|
|
4263
|
+
},
|
|
4264
|
+
timeout: 5e3
|
|
4265
|
+
},
|
|
4266
|
+
(res) => {
|
|
4267
|
+
let data = "";
|
|
4268
|
+
res.on("data", (chunk) => {
|
|
4269
|
+
data += chunk;
|
|
4270
|
+
});
|
|
4271
|
+
res.on("end", () => {
|
|
4272
|
+
try {
|
|
4273
|
+
const json = JSON.parse(data);
|
|
4274
|
+
if (res.statusCode === 201 && json.session) {
|
|
4275
|
+
resolve(json.session);
|
|
4276
|
+
} else {
|
|
4277
|
+
reject(new Error(`Registration failed: ${json.error || "Unknown error"}`));
|
|
4278
|
+
}
|
|
4279
|
+
} catch {
|
|
4280
|
+
reject(new Error("Invalid response from hub"));
|
|
4281
|
+
}
|
|
4282
|
+
});
|
|
4283
|
+
}
|
|
4284
|
+
);
|
|
4285
|
+
req.on("error", (err) => reject(new Error(`Failed to register with hub: ${err.message}`)));
|
|
4286
|
+
req.on("timeout", () => {
|
|
4287
|
+
req.destroy();
|
|
4288
|
+
reject(new Error("Registration request timed out"));
|
|
4289
|
+
});
|
|
4290
|
+
req.end(body);
|
|
4291
|
+
});
|
|
4292
|
+
}
|
|
4293
|
+
async function unregisterSession(sessionId) {
|
|
4294
|
+
return new Promise((resolve) => {
|
|
4295
|
+
const req = request(
|
|
4296
|
+
{
|
|
4297
|
+
hostname: "127.0.0.1",
|
|
4298
|
+
port: HUB_INTERNAL_PORT,
|
|
4299
|
+
path: `/api/sessions/${sessionId}`,
|
|
4300
|
+
method: "DELETE",
|
|
4301
|
+
timeout: 3e3
|
|
4302
|
+
},
|
|
4303
|
+
() => resolve()
|
|
4304
|
+
);
|
|
4305
|
+
req.on("error", () => resolve());
|
|
4306
|
+
req.on("timeout", () => {
|
|
4307
|
+
req.destroy();
|
|
4308
|
+
resolve();
|
|
4309
|
+
});
|
|
4310
|
+
req.end();
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
async function listSessions() {
|
|
4314
|
+
return new Promise((resolve) => {
|
|
4315
|
+
const req = request(
|
|
4316
|
+
{
|
|
4317
|
+
hostname: "127.0.0.1",
|
|
4318
|
+
port: HUB_INTERNAL_PORT,
|
|
4319
|
+
path: "/api/sessions",
|
|
4320
|
+
method: "GET",
|
|
4321
|
+
timeout: 3e3
|
|
4322
|
+
},
|
|
4323
|
+
(res) => {
|
|
4324
|
+
let data = "";
|
|
4325
|
+
res.on("data", (chunk) => {
|
|
4326
|
+
data += chunk;
|
|
4327
|
+
});
|
|
4328
|
+
res.on("end", () => {
|
|
4329
|
+
try {
|
|
4330
|
+
const json = JSON.parse(data);
|
|
4331
|
+
resolve(json.sessions || []);
|
|
4332
|
+
} catch {
|
|
4333
|
+
resolve([]);
|
|
4334
|
+
}
|
|
4335
|
+
});
|
|
4336
|
+
}
|
|
4337
|
+
);
|
|
4338
|
+
req.on("error", () => resolve([]));
|
|
4339
|
+
req.on("timeout", () => {
|
|
4340
|
+
req.destroy();
|
|
4341
|
+
resolve([]);
|
|
4342
|
+
});
|
|
4343
|
+
req.end();
|
|
4344
|
+
});
|
|
4345
|
+
}
|
|
4346
|
+
function stopHub() {
|
|
4347
|
+
const config = getHubConfig();
|
|
4348
|
+
if (!config) return false;
|
|
4349
|
+
try {
|
|
4350
|
+
process.kill(config.pid, "SIGTERM");
|
|
4351
|
+
return true;
|
|
4352
|
+
} catch {
|
|
4353
|
+
return false;
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
async function sendHeartbeat(sessionId) {
|
|
4357
|
+
return new Promise((resolve) => {
|
|
4358
|
+
const req = request(
|
|
4359
|
+
{
|
|
4360
|
+
hostname: "127.0.0.1",
|
|
4361
|
+
port: HUB_INTERNAL_PORT,
|
|
4362
|
+
path: `/api/sessions/${sessionId}/heartbeat`,
|
|
4363
|
+
method: "PUT",
|
|
4364
|
+
timeout: 2e3
|
|
4365
|
+
},
|
|
4366
|
+
() => resolve()
|
|
4367
|
+
);
|
|
4368
|
+
req.on("error", () => resolve());
|
|
4369
|
+
req.on("timeout", () => {
|
|
4370
|
+
req.destroy();
|
|
4371
|
+
resolve();
|
|
4372
|
+
});
|
|
4373
|
+
req.end();
|
|
4374
|
+
});
|
|
4375
|
+
}
|
|
4376
|
+
|
|
4143
4377
|
// src/cli-options.ts
|
|
4144
|
-
var DEFAULT_PORT =
|
|
4378
|
+
var DEFAULT_PORT = SESSION_PORT_START;
|
|
4145
4379
|
function parseArgs(argv) {
|
|
4146
4380
|
const options = {
|
|
4147
4381
|
port: DEFAULT_PORT,
|
|
@@ -4150,13 +4384,24 @@ function parseArgs(argv) {
|
|
|
4150
4384
|
command: [],
|
|
4151
4385
|
subcommand: null,
|
|
4152
4386
|
tailscale: false,
|
|
4153
|
-
local: false
|
|
4387
|
+
local: false,
|
|
4388
|
+
hubInfo: false,
|
|
4389
|
+
hubStop: false,
|
|
4390
|
+
hubStatus: false
|
|
4154
4391
|
};
|
|
4155
4392
|
const args = argv.slice(2);
|
|
4156
4393
|
if (args.length > 0 && args[0] === "setup") {
|
|
4157
4394
|
options.subcommand = "setup";
|
|
4158
4395
|
return options;
|
|
4159
4396
|
}
|
|
4397
|
+
if (args.length > 0 && args[0] === "hub") {
|
|
4398
|
+
const hubAction = args[1] || "info";
|
|
4399
|
+
if (hubAction === "info") options.hubInfo = true;
|
|
4400
|
+
else if (hubAction === "stop") options.hubStop = true;
|
|
4401
|
+
else if (hubAction === "status") options.hubStatus = true;
|
|
4402
|
+
else options.hubInfo = true;
|
|
4403
|
+
return options;
|
|
4404
|
+
}
|
|
4160
4405
|
let i = 0;
|
|
4161
4406
|
while (i < args.length) {
|
|
4162
4407
|
const arg = args[i];
|
|
@@ -4178,6 +4423,15 @@ function parseArgs(argv) {
|
|
|
4178
4423
|
} else if (arg === "--no-qr") {
|
|
4179
4424
|
options.noQr = true;
|
|
4180
4425
|
i++;
|
|
4426
|
+
} else if (arg === "--hub-info") {
|
|
4427
|
+
options.hubInfo = true;
|
|
4428
|
+
i++;
|
|
4429
|
+
} else if (arg === "--hub-stop") {
|
|
4430
|
+
options.hubStop = true;
|
|
4431
|
+
i++;
|
|
4432
|
+
} else if (arg === "--hub-status") {
|
|
4433
|
+
options.hubStatus = true;
|
|
4434
|
+
i++;
|
|
4181
4435
|
} else if (arg === "--help" || arg === "-h") {
|
|
4182
4436
|
printHelp();
|
|
4183
4437
|
process.exit(0);
|
|
@@ -4199,6 +4453,7 @@ Usage:
|
|
|
4199
4453
|
itwillsync [options] -- <command> [args...]
|
|
4200
4454
|
itwillsync [options] <command> [args...]
|
|
4201
4455
|
itwillsync setup
|
|
4456
|
+
itwillsync hub [info|stop|status]
|
|
4202
4457
|
|
|
4203
4458
|
Examples:
|
|
4204
4459
|
itwillsync -- claude
|
|
@@ -4207,9 +4462,14 @@ Examples:
|
|
|
4207
4462
|
itwillsync --port 8080 -- claude
|
|
4208
4463
|
itwillsync --tailscale -- claude
|
|
4209
4464
|
itwillsync setup
|
|
4465
|
+
itwillsync hub info
|
|
4466
|
+
itwillsync hub stop
|
|
4210
4467
|
|
|
4211
4468
|
Commands:
|
|
4212
4469
|
setup Run the setup wizard (configure networking mode)
|
|
4470
|
+
hub info Show dashboard URL, QR code, and hub status
|
|
4471
|
+
hub stop Stop the hub daemon and all sessions
|
|
4472
|
+
hub status List all active sessions
|
|
4213
4473
|
|
|
4214
4474
|
Options:
|
|
4215
4475
|
--port <number> Port to listen on (default: ${DEFAULT_PORT})
|
|
@@ -4219,24 +4479,29 @@ Options:
|
|
|
4219
4479
|
--no-qr Don't display QR code
|
|
4220
4480
|
-h, --help Show this help
|
|
4221
4481
|
-v, --version Show version
|
|
4482
|
+
|
|
4483
|
+
Hub Management:
|
|
4484
|
+
--hub-info Show dashboard URL, QR code, and hub status
|
|
4485
|
+
--hub-stop Stop the hub daemon and all sessions
|
|
4486
|
+
--hub-status List all active sessions
|
|
4222
4487
|
`);
|
|
4223
4488
|
}
|
|
4224
4489
|
|
|
4225
4490
|
// src/index.ts
|
|
4226
|
-
import { fileURLToPath as
|
|
4227
|
-
import { join as
|
|
4228
|
-
import { spawn as
|
|
4491
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4492
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
4493
|
+
import { spawn as spawn3 } from "child_process";
|
|
4229
4494
|
function preventSleep() {
|
|
4230
4495
|
try {
|
|
4231
4496
|
if (process.platform === "darwin") {
|
|
4232
|
-
const child =
|
|
4497
|
+
const child = spawn3("caffeinate", ["-i", "-w", String(process.pid)], {
|
|
4233
4498
|
stdio: "ignore",
|
|
4234
4499
|
detached: true
|
|
4235
4500
|
});
|
|
4236
4501
|
child.unref();
|
|
4237
4502
|
return child;
|
|
4238
4503
|
} else if (process.platform === "linux") {
|
|
4239
|
-
return
|
|
4504
|
+
return spawn3("systemd-inhibit", [
|
|
4240
4505
|
"--what=idle",
|
|
4241
4506
|
"--who=itwillsync",
|
|
4242
4507
|
"--why=Terminal sync session active",
|
|
@@ -4248,12 +4513,96 @@ function preventSleep() {
|
|
|
4248
4513
|
}
|
|
4249
4514
|
return null;
|
|
4250
4515
|
}
|
|
4516
|
+
async function ensureHub() {
|
|
4517
|
+
const hubRunning = await discoverHub();
|
|
4518
|
+
if (hubRunning) {
|
|
4519
|
+
return false;
|
|
4520
|
+
}
|
|
4521
|
+
try {
|
|
4522
|
+
await spawnHub();
|
|
4523
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
4524
|
+
return true;
|
|
4525
|
+
} catch (err) {
|
|
4526
|
+
console.warn(`
|
|
4527
|
+
Warning: Could not start hub daemon: ${err.message}`);
|
|
4528
|
+
console.warn(" Running in standalone mode (no dashboard).\n");
|
|
4529
|
+
return true;
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
async function handleHubCommand(options) {
|
|
4533
|
+
const hubConfig = getHubConfig();
|
|
4534
|
+
const hubRunning = await discoverHub();
|
|
4535
|
+
if (options.hubStop) {
|
|
4536
|
+
if (!hubRunning || !hubConfig) {
|
|
4537
|
+
console.log("\n No hub daemon is running.\n");
|
|
4538
|
+
return;
|
|
4539
|
+
}
|
|
4540
|
+
const stopped = stopHub();
|
|
4541
|
+
if (stopped) {
|
|
4542
|
+
console.log("\n Hub daemon stopped.\n");
|
|
4543
|
+
} else {
|
|
4544
|
+
console.log("\n Failed to stop hub daemon.\n");
|
|
4545
|
+
}
|
|
4546
|
+
return;
|
|
4547
|
+
}
|
|
4548
|
+
if (options.hubStatus) {
|
|
4549
|
+
if (!hubRunning) {
|
|
4550
|
+
console.log("\n No hub daemon is running.\n");
|
|
4551
|
+
return;
|
|
4552
|
+
}
|
|
4553
|
+
const sessions = await listSessions();
|
|
4554
|
+
console.log(`
|
|
4555
|
+
Hub is running. ${sessions.length} active session(s).
|
|
4556
|
+
`);
|
|
4557
|
+
if (sessions.length > 0) {
|
|
4558
|
+
for (const s of sessions) {
|
|
4559
|
+
const uptime = Math.floor((Date.now() - s.connectedAt) / 6e4);
|
|
4560
|
+
console.log(` ${s.name || s.agent} (${s.status}, ${uptime}m, port ${s.port})`);
|
|
4561
|
+
}
|
|
4562
|
+
console.log("");
|
|
4563
|
+
}
|
|
4564
|
+
return;
|
|
4565
|
+
}
|
|
4566
|
+
if (options.hubInfo) {
|
|
4567
|
+
if (!hubRunning || !hubConfig) {
|
|
4568
|
+
console.log("\n No hub daemon is running.");
|
|
4569
|
+
console.log(" Start a session with: itwillsync -- <agent>\n");
|
|
4570
|
+
return;
|
|
4571
|
+
}
|
|
4572
|
+
let networkingMode = "local";
|
|
4573
|
+
if (options.tailscale) {
|
|
4574
|
+
networkingMode = "tailscale";
|
|
4575
|
+
} else if (options.local) {
|
|
4576
|
+
networkingMode = "local";
|
|
4577
|
+
} else if (configExists()) {
|
|
4578
|
+
networkingMode = loadConfig().networkingMode;
|
|
4579
|
+
}
|
|
4580
|
+
const ip = await resolveSessionIP(networkingMode, false);
|
|
4581
|
+
const dashboardUrl = `http://${ip}:${HUB_EXTERNAL_PORT}?token=${hubConfig.masterToken}`;
|
|
4582
|
+
displayQR(dashboardUrl);
|
|
4583
|
+
console.log(` Dashboard: ${dashboardUrl}`);
|
|
4584
|
+
const sessions = await listSessions();
|
|
4585
|
+
console.log(` Sessions: ${sessions.length} active`);
|
|
4586
|
+
if (sessions.length > 0) {
|
|
4587
|
+
for (const s of sessions) {
|
|
4588
|
+
const uptime = Math.floor((Date.now() - s.connectedAt) / 6e4);
|
|
4589
|
+
console.log(` ${s.name || s.agent} (${s.status}, ${uptime}m)`);
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
console.log("");
|
|
4593
|
+
return;
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4251
4596
|
async function main() {
|
|
4252
4597
|
const options = parseArgs(process.argv);
|
|
4253
4598
|
if (options.subcommand === "setup") {
|
|
4254
4599
|
await runSetupWizard();
|
|
4255
4600
|
return;
|
|
4256
4601
|
}
|
|
4602
|
+
if (options.hubInfo || options.hubStop || options.hubStatus) {
|
|
4603
|
+
await handleHubCommand(options);
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4257
4606
|
if (options.tailscale && options.local) {
|
|
4258
4607
|
console.error("Error: Cannot use both --tailscale and --local.\n");
|
|
4259
4608
|
process.exit(1);
|
|
@@ -4276,13 +4625,14 @@ async function main() {
|
|
|
4276
4625
|
networkingMode = config.networkingMode;
|
|
4277
4626
|
}
|
|
4278
4627
|
const [cmd, ...cmdArgs] = options.command;
|
|
4628
|
+
const isFirstSession = await ensureHub();
|
|
4629
|
+
const hubConfig = getHubConfig();
|
|
4279
4630
|
const token = generateToken();
|
|
4280
4631
|
const port = await findAvailablePort(options.port);
|
|
4281
4632
|
const host = options.localhost ? "127.0.0.1" : "0.0.0.0";
|
|
4282
4633
|
const ip = await resolveSessionIP(networkingMode, options.localhost);
|
|
4283
|
-
const
|
|
4284
|
-
const
|
|
4285
|
-
const webClientPath = join4(__dirname, "web-client");
|
|
4634
|
+
const __dirname = dirname3(fileURLToPath3(import.meta.url));
|
|
4635
|
+
const webClientPath = join5(__dirname, "web-client");
|
|
4286
4636
|
const ptyManager = new PtyManager(cmd, cmdArgs);
|
|
4287
4637
|
const server = createSyncServer({
|
|
4288
4638
|
ptyManager,
|
|
@@ -4291,12 +4641,42 @@ async function main() {
|
|
|
4291
4641
|
host,
|
|
4292
4642
|
port
|
|
4293
4643
|
});
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4644
|
+
let registeredSession = null;
|
|
4645
|
+
let heartbeatInterval = null;
|
|
4646
|
+
if (hubConfig) {
|
|
4647
|
+
try {
|
|
4648
|
+
registeredSession = await registerSession({
|
|
4649
|
+
name: cmd,
|
|
4650
|
+
port,
|
|
4651
|
+
token,
|
|
4652
|
+
agent: cmd,
|
|
4653
|
+
cwd: process.cwd(),
|
|
4654
|
+
pid: ptyManager.pid
|
|
4655
|
+
});
|
|
4656
|
+
heartbeatInterval = setInterval(() => {
|
|
4657
|
+
if (registeredSession) {
|
|
4658
|
+
sendHeartbeat(registeredSession.id);
|
|
4659
|
+
}
|
|
4660
|
+
}, 1e4);
|
|
4661
|
+
} catch (err) {
|
|
4662
|
+
console.warn(` Warning: Failed to register with hub: ${err.message}`);
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
const dashboardUrl = hubConfig ? `http://${ip}:${HUB_EXTERNAL_PORT}?token=${hubConfig.masterToken}` : null;
|
|
4666
|
+
if (isFirstSession && dashboardUrl && !options.noQr) {
|
|
4667
|
+
displayQR(dashboardUrl);
|
|
4668
|
+
console.log(` Dashboard: ${dashboardUrl}`);
|
|
4669
|
+
} else if (isFirstSession && !options.noQr) {
|
|
4670
|
+
const directUrl = `http://${ip}:${port}?token=${token}`;
|
|
4671
|
+
displayQR(directUrl);
|
|
4672
|
+
} else if (dashboardUrl) {
|
|
4297
4673
|
console.log(`
|
|
4298
|
-
|
|
4299
|
-
`);
|
|
4674
|
+
Session "${cmd}" registered with hub.`);
|
|
4675
|
+
console.log(` Dashboard: ${dashboardUrl}`);
|
|
4676
|
+
console.log("");
|
|
4677
|
+
} else if (!options.noQr) {
|
|
4678
|
+
const directUrl = `http://${ip}:${port}?token=${token}`;
|
|
4679
|
+
displayQR(directUrl);
|
|
4300
4680
|
}
|
|
4301
4681
|
const sleepGuard = preventSleep();
|
|
4302
4682
|
console.log(` Server listening on ${host}:${port}`);
|
|
@@ -4322,26 +4702,32 @@ async function main() {
|
|
|
4322
4702
|
}
|
|
4323
4703
|
process.stdout.on("resize", handleResize);
|
|
4324
4704
|
handleResize();
|
|
4325
|
-
function cleanup() {
|
|
4705
|
+
async function cleanup() {
|
|
4326
4706
|
if (process.stdin.isTTY) {
|
|
4327
4707
|
process.stdin.setRawMode(false);
|
|
4328
4708
|
}
|
|
4709
|
+
if (heartbeatInterval) {
|
|
4710
|
+
clearInterval(heartbeatInterval);
|
|
4711
|
+
}
|
|
4712
|
+
if (registeredSession) {
|
|
4713
|
+
await unregisterSession(registeredSession.id);
|
|
4714
|
+
}
|
|
4329
4715
|
sleepGuard?.kill();
|
|
4330
4716
|
server.close();
|
|
4331
4717
|
ptyManager.kill();
|
|
4332
4718
|
}
|
|
4333
|
-
ptyManager.onExit((exitCode) => {
|
|
4719
|
+
ptyManager.onExit(async (exitCode) => {
|
|
4334
4720
|
console.log(`
|
|
4335
4721
|
Agent exited with code ${exitCode}`);
|
|
4336
|
-
cleanup();
|
|
4722
|
+
await cleanup();
|
|
4337
4723
|
process.exit(exitCode);
|
|
4338
4724
|
});
|
|
4339
|
-
process.on("SIGINT", () => {
|
|
4340
|
-
cleanup();
|
|
4725
|
+
process.on("SIGINT", async () => {
|
|
4726
|
+
await cleanup();
|
|
4341
4727
|
process.exit(0);
|
|
4342
4728
|
});
|
|
4343
|
-
process.on("SIGTERM", () => {
|
|
4344
|
-
cleanup();
|
|
4729
|
+
process.on("SIGTERM", async () => {
|
|
4730
|
+
await cleanup();
|
|
4345
4731
|
process.exit(0);
|
|
4346
4732
|
});
|
|
4347
4733
|
}
|