itwillsync 1.2.1 → 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 +402 -25
- package/dist/index.js.map +1 -1
- package/dist/web-client/assets/{index-sjFayRPB.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", () => {
|
|
@@ -4149,8 +4149,233 @@ async function runSetupWizard() {
|
|
|
4149
4149
|
return config;
|
|
4150
4150
|
}
|
|
4151
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
|
+
|
|
4152
4377
|
// src/cli-options.ts
|
|
4153
|
-
var DEFAULT_PORT =
|
|
4378
|
+
var DEFAULT_PORT = SESSION_PORT_START;
|
|
4154
4379
|
function parseArgs(argv) {
|
|
4155
4380
|
const options = {
|
|
4156
4381
|
port: DEFAULT_PORT,
|
|
@@ -4159,13 +4384,24 @@ function parseArgs(argv) {
|
|
|
4159
4384
|
command: [],
|
|
4160
4385
|
subcommand: null,
|
|
4161
4386
|
tailscale: false,
|
|
4162
|
-
local: false
|
|
4387
|
+
local: false,
|
|
4388
|
+
hubInfo: false,
|
|
4389
|
+
hubStop: false,
|
|
4390
|
+
hubStatus: false
|
|
4163
4391
|
};
|
|
4164
4392
|
const args = argv.slice(2);
|
|
4165
4393
|
if (args.length > 0 && args[0] === "setup") {
|
|
4166
4394
|
options.subcommand = "setup";
|
|
4167
4395
|
return options;
|
|
4168
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
|
+
}
|
|
4169
4405
|
let i = 0;
|
|
4170
4406
|
while (i < args.length) {
|
|
4171
4407
|
const arg = args[i];
|
|
@@ -4187,6 +4423,15 @@ function parseArgs(argv) {
|
|
|
4187
4423
|
} else if (arg === "--no-qr") {
|
|
4188
4424
|
options.noQr = true;
|
|
4189
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++;
|
|
4190
4435
|
} else if (arg === "--help" || arg === "-h") {
|
|
4191
4436
|
printHelp();
|
|
4192
4437
|
process.exit(0);
|
|
@@ -4208,6 +4453,7 @@ Usage:
|
|
|
4208
4453
|
itwillsync [options] -- <command> [args...]
|
|
4209
4454
|
itwillsync [options] <command> [args...]
|
|
4210
4455
|
itwillsync setup
|
|
4456
|
+
itwillsync hub [info|stop|status]
|
|
4211
4457
|
|
|
4212
4458
|
Examples:
|
|
4213
4459
|
itwillsync -- claude
|
|
@@ -4216,9 +4462,14 @@ Examples:
|
|
|
4216
4462
|
itwillsync --port 8080 -- claude
|
|
4217
4463
|
itwillsync --tailscale -- claude
|
|
4218
4464
|
itwillsync setup
|
|
4465
|
+
itwillsync hub info
|
|
4466
|
+
itwillsync hub stop
|
|
4219
4467
|
|
|
4220
4468
|
Commands:
|
|
4221
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
|
|
4222
4473
|
|
|
4223
4474
|
Options:
|
|
4224
4475
|
--port <number> Port to listen on (default: ${DEFAULT_PORT})
|
|
@@ -4228,24 +4479,29 @@ Options:
|
|
|
4228
4479
|
--no-qr Don't display QR code
|
|
4229
4480
|
-h, --help Show this help
|
|
4230
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
|
|
4231
4487
|
`);
|
|
4232
4488
|
}
|
|
4233
4489
|
|
|
4234
4490
|
// src/index.ts
|
|
4235
|
-
import { fileURLToPath as
|
|
4236
|
-
import { join as
|
|
4237
|
-
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";
|
|
4238
4494
|
function preventSleep() {
|
|
4239
4495
|
try {
|
|
4240
4496
|
if (process.platform === "darwin") {
|
|
4241
|
-
const child =
|
|
4497
|
+
const child = spawn3("caffeinate", ["-i", "-w", String(process.pid)], {
|
|
4242
4498
|
stdio: "ignore",
|
|
4243
4499
|
detached: true
|
|
4244
4500
|
});
|
|
4245
4501
|
child.unref();
|
|
4246
4502
|
return child;
|
|
4247
4503
|
} else if (process.platform === "linux") {
|
|
4248
|
-
return
|
|
4504
|
+
return spawn3("systemd-inhibit", [
|
|
4249
4505
|
"--what=idle",
|
|
4250
4506
|
"--who=itwillsync",
|
|
4251
4507
|
"--why=Terminal sync session active",
|
|
@@ -4257,12 +4513,96 @@ function preventSleep() {
|
|
|
4257
4513
|
}
|
|
4258
4514
|
return null;
|
|
4259
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
|
+
}
|
|
4260
4596
|
async function main() {
|
|
4261
4597
|
const options = parseArgs(process.argv);
|
|
4262
4598
|
if (options.subcommand === "setup") {
|
|
4263
4599
|
await runSetupWizard();
|
|
4264
4600
|
return;
|
|
4265
4601
|
}
|
|
4602
|
+
if (options.hubInfo || options.hubStop || options.hubStatus) {
|
|
4603
|
+
await handleHubCommand(options);
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4266
4606
|
if (options.tailscale && options.local) {
|
|
4267
4607
|
console.error("Error: Cannot use both --tailscale and --local.\n");
|
|
4268
4608
|
process.exit(1);
|
|
@@ -4285,13 +4625,14 @@ async function main() {
|
|
|
4285
4625
|
networkingMode = config.networkingMode;
|
|
4286
4626
|
}
|
|
4287
4627
|
const [cmd, ...cmdArgs] = options.command;
|
|
4628
|
+
const isFirstSession = await ensureHub();
|
|
4629
|
+
const hubConfig = getHubConfig();
|
|
4288
4630
|
const token = generateToken();
|
|
4289
4631
|
const port = await findAvailablePort(options.port);
|
|
4290
4632
|
const host = options.localhost ? "127.0.0.1" : "0.0.0.0";
|
|
4291
4633
|
const ip = await resolveSessionIP(networkingMode, options.localhost);
|
|
4292
|
-
const
|
|
4293
|
-
const
|
|
4294
|
-
const webClientPath = join4(__dirname, "web-client");
|
|
4634
|
+
const __dirname = dirname3(fileURLToPath3(import.meta.url));
|
|
4635
|
+
const webClientPath = join5(__dirname, "web-client");
|
|
4295
4636
|
const ptyManager = new PtyManager(cmd, cmdArgs);
|
|
4296
4637
|
const server = createSyncServer({
|
|
4297
4638
|
ptyManager,
|
|
@@ -4300,12 +4641,42 @@ async function main() {
|
|
|
4300
4641
|
host,
|
|
4301
4642
|
port
|
|
4302
4643
|
});
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
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) {
|
|
4306
4673
|
console.log(`
|
|
4307
|
-
|
|
4308
|
-
`);
|
|
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);
|
|
4309
4680
|
}
|
|
4310
4681
|
const sleepGuard = preventSleep();
|
|
4311
4682
|
console.log(` Server listening on ${host}:${port}`);
|
|
@@ -4331,26 +4702,32 @@ async function main() {
|
|
|
4331
4702
|
}
|
|
4332
4703
|
process.stdout.on("resize", handleResize);
|
|
4333
4704
|
handleResize();
|
|
4334
|
-
function cleanup() {
|
|
4705
|
+
async function cleanup() {
|
|
4335
4706
|
if (process.stdin.isTTY) {
|
|
4336
4707
|
process.stdin.setRawMode(false);
|
|
4337
4708
|
}
|
|
4709
|
+
if (heartbeatInterval) {
|
|
4710
|
+
clearInterval(heartbeatInterval);
|
|
4711
|
+
}
|
|
4712
|
+
if (registeredSession) {
|
|
4713
|
+
await unregisterSession(registeredSession.id);
|
|
4714
|
+
}
|
|
4338
4715
|
sleepGuard?.kill();
|
|
4339
4716
|
server.close();
|
|
4340
4717
|
ptyManager.kill();
|
|
4341
4718
|
}
|
|
4342
|
-
ptyManager.onExit((exitCode) => {
|
|
4719
|
+
ptyManager.onExit(async (exitCode) => {
|
|
4343
4720
|
console.log(`
|
|
4344
4721
|
Agent exited with code ${exitCode}`);
|
|
4345
|
-
cleanup();
|
|
4722
|
+
await cleanup();
|
|
4346
4723
|
process.exit(exitCode);
|
|
4347
4724
|
});
|
|
4348
|
-
process.on("SIGINT", () => {
|
|
4349
|
-
cleanup();
|
|
4725
|
+
process.on("SIGINT", async () => {
|
|
4726
|
+
await cleanup();
|
|
4350
4727
|
process.exit(0);
|
|
4351
4728
|
});
|
|
4352
|
-
process.on("SIGTERM", () => {
|
|
4353
|
-
cleanup();
|
|
4729
|
+
process.on("SIGTERM", async () => {
|
|
4730
|
+
await cleanup();
|
|
4354
4731
|
process.exit(0);
|
|
4355
4732
|
});
|
|
4356
4733
|
}
|