itwillsync 1.3.8 → 1.5.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.
@@ -1,2 +1,2 @@
1
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();
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 V(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 W(){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"),_=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,`session-${t.id}`)},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=()=>{_.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":{W();for(const n of m.keys())K(n);for(const n of t.sessions)F(n),n.status==="attention"&&V(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"?V(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=()=>{_.className="reconnecting",W(),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();
@@ -7,7 +7,7 @@
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-DgUZUPW_.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-DdOxsvuU.js"></script>
11
11
  <link rel="stylesheet" crossorigin href="/assets/index-Erqx_a0N.css">
12
12
  </head>
13
13
  <body>
package/dist/index.js CHANGED
@@ -3732,9 +3732,11 @@ var PtyManager = class {
3732
3732
  */
3733
3733
  resize(cols, rows) {
3734
3734
  try {
3735
- this.ptyProcess.resize(cols, rows);
3736
- this._cols = cols;
3737
- this._rows = rows;
3735
+ const clampedCols = Math.max(1, Math.min(500, Math.floor(cols)));
3736
+ const clampedRows = Math.max(1, Math.min(200, Math.floor(rows)));
3737
+ this.ptyProcess.resize(clampedCols, clampedRows);
3738
+ this._cols = clampedCols;
3739
+ this._rows = clampedRows;
3738
3740
  } catch {
3739
3741
  }
3740
3742
  }
@@ -3937,12 +3939,25 @@ async function serveStaticFile(webClientPath, filePath, req, res) {
3937
3939
  }
3938
3940
  }
3939
3941
  var PING_INTERVAL_MS = 3e4;
3940
- var SCROLLBACK_BUFFER_SIZE = 5e4;
3942
+ var DEFAULT_SCROLLBACK_SIZE = 10485760;
3943
+ var DEFAULT_CLIENT_BUFFER_LIMIT = 262144;
3941
3944
  function createSyncServer(options) {
3942
- const { ptyManager, token, webClientPath, host, port, localTerminalOwnsResize = false } = options;
3945
+ const {
3946
+ ptyManager,
3947
+ token,
3948
+ webClientPath,
3949
+ host,
3950
+ port,
3951
+ resizePolicy = "last-writer-wins",
3952
+ scrollbackBufferSize = DEFAULT_SCROLLBACK_SIZE,
3953
+ clientBufferLimit = DEFAULT_CLIENT_BUFFER_LIMIT,
3954
+ logger
3955
+ } = options;
3943
3956
  const clients = /* @__PURE__ */ new Set();
3944
3957
  const aliveMap = /* @__PURE__ */ new WeakMap();
3958
+ const dropCounters = /* @__PURE__ */ new WeakMap();
3945
3959
  let scrollbackBuffer = "";
3960
+ let scrollbackBytes = 0;
3946
3961
  let seq = 0;
3947
3962
  const httpServer = createServer2(async (req, res) => {
3948
3963
  const url = new URL(req.url || "/", `http://${req.headers.host}`);
@@ -3979,9 +3994,12 @@ function createSyncServer(options) {
3979
3994
  wssServer.on("connection", (ws) => {
3980
3995
  clients.add(ws);
3981
3996
  aliveMap.set(ws, true);
3982
- if (scrollbackBuffer.length > 0) {
3983
- ws.send(JSON.stringify({ type: "data", data: scrollbackBuffer, seq }));
3984
- }
3997
+ let syncReceived = false;
3998
+ const fallbackTimer = setTimeout(() => {
3999
+ if (!syncReceived && scrollbackBuffer.length > 0) {
4000
+ ws.send(JSON.stringify({ type: "data", data: scrollbackBuffer, seq }));
4001
+ }
4002
+ }, 150);
3985
4003
  ws.send(JSON.stringify({ type: "resize", cols: ptyManager.cols, rows: ptyManager.rows }));
3986
4004
  ws.on("pong", () => {
3987
4005
  aliveMap.set(ws, true);
@@ -3992,10 +4010,32 @@ function createSyncServer(options) {
3992
4010
  if (message.type === "input" && typeof message.data === "string") {
3993
4011
  ptyManager.write(message.data);
3994
4012
  } else if (message.type === "resize" && typeof message.cols === "number" && typeof message.rows === "number") {
3995
- if (!localTerminalOwnsResize) {
4013
+ if (resizePolicy === "last-writer-wins") {
3996
4014
  ptyManager.resize(message.cols, message.rows);
4015
+ const resizeMsg = JSON.stringify({ type: "resize", cols: ptyManager.cols, rows: ptyManager.rows });
4016
+ for (const c of clients) {
4017
+ if (c.readyState === c.OPEN) {
4018
+ c.send(resizeMsg);
4019
+ }
4020
+ }
4021
+ }
4022
+ } else if (message.type === "sync" && typeof message.lastSeq === "number") {
4023
+ syncReceived = true;
4024
+ clearTimeout(fallbackTimer);
4025
+ if (message.lastSeq === -1 || message.lastSeq < seq - scrollbackBuffer.length) {
4026
+ if (scrollbackBuffer.length > 0) {
4027
+ ws.send(JSON.stringify({ type: "data", data: scrollbackBuffer, seq }));
4028
+ }
4029
+ } else {
4030
+ const missed = seq - message.lastSeq;
4031
+ if (missed > 0 && scrollbackBuffer.length > 0) {
4032
+ const delta = missed <= scrollbackBuffer.length ? scrollbackBuffer.slice(-missed) : scrollbackBuffer;
4033
+ ws.send(JSON.stringify({ type: "data", data: delta, seq }));
4034
+ }
3997
4035
  }
3998
4036
  } else if (message.type === "resume" && typeof message.lastSeq === "number") {
4037
+ syncReceived = true;
4038
+ clearTimeout(fallbackTimer);
3999
4039
  const missed = seq - message.lastSeq;
4000
4040
  if (missed > 0 && scrollbackBuffer.length > 0) {
4001
4041
  const delta = missed <= scrollbackBuffer.length ? scrollbackBuffer.slice(-missed) : scrollbackBuffer;
@@ -4013,16 +4053,31 @@ function createSyncServer(options) {
4013
4053
  });
4014
4054
  });
4015
4055
  ptyManager.onData((data) => {
4056
+ if (logger) logger.write(data);
4016
4057
  seq += data.length;
4017
4058
  scrollbackBuffer += data;
4018
- if (scrollbackBuffer.length > SCROLLBACK_BUFFER_SIZE) {
4019
- scrollbackBuffer = scrollbackBuffer.slice(-SCROLLBACK_BUFFER_SIZE);
4059
+ scrollbackBytes += Buffer.byteLength(data, "utf-8");
4060
+ if (scrollbackBytes > scrollbackBufferSize) {
4061
+ const target = Math.floor(scrollbackBufferSize * 0.5);
4062
+ const excessBytes = scrollbackBytes - target;
4063
+ const avgBytesPerChar = scrollbackBytes / scrollbackBuffer.length;
4064
+ const trimChars = Math.min(Math.ceil(excessBytes / avgBytesPerChar), scrollbackBuffer.length);
4065
+ const trimmed = scrollbackBuffer.slice(0, trimChars);
4066
+ scrollbackBuffer = scrollbackBuffer.slice(trimChars);
4067
+ scrollbackBytes -= Buffer.byteLength(trimmed, "utf-8");
4020
4068
  }
4021
4069
  const msg = JSON.stringify({ type: "data", data, seq });
4022
4070
  for (const client of clients) {
4023
- if (client.readyState === client.OPEN) {
4024
- client.send(msg);
4071
+ if (client.readyState !== client.OPEN) continue;
4072
+ if (client.bufferedAmount > clientBufferLimit) {
4073
+ const count = (dropCounters.get(client) || 0) + 1;
4074
+ dropCounters.set(client, count);
4075
+ if (count === 100 || count > 100 && count % 1e3 === 0) {
4076
+ console.warn(` Warning: Dropped ${count} messages for slow client`);
4077
+ }
4078
+ continue;
4025
4079
  }
4080
+ client.send(msg);
4026
4081
  }
4027
4082
  });
4028
4083
  httpServer.listen(port, host);
@@ -4038,8 +4093,9 @@ function createSyncServer(options) {
4038
4093
  wssServer.close();
4039
4094
  httpServer.close();
4040
4095
  },
4041
- broadcastResize(cols, rows) {
4042
- const msg = JSON.stringify({ type: "resize", cols, rows });
4096
+ resizeFromLocal(cols, rows) {
4097
+ ptyManager.resize(cols, rows);
4098
+ const msg = JSON.stringify({ type: "resize", cols: ptyManager.cols, rows: ptyManager.rows });
4043
4099
  for (const client of clients) {
4044
4100
  if (client.readyState === client.OPEN) {
4045
4101
  client.send(msg);
@@ -4072,7 +4128,17 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
4072
4128
  import { homedir } from "os";
4073
4129
  import { join as join3 } from "path";
4074
4130
  var DEFAULT_CONFIG = {
4075
- networkingMode: "local"
4131
+ networkingMode: "local",
4132
+ scrollbackBufferSize: 10485760,
4133
+ // 10MB
4134
+ maxSessions: 20,
4135
+ idleTimeoutMs: 864e5,
4136
+ // 24 hours
4137
+ logRetentionDays: 30,
4138
+ clientBufferLimit: 262144,
4139
+ // 256KB
4140
+ maxTerminalCols: 500,
4141
+ maxTerminalRows: 200
4076
4142
  };
4077
4143
  function getConfigDir() {
4078
4144
  return process.env.ITWILLSYNC_CONFIG_DIR || join3(homedir(), ".itwillsync");
@@ -4102,6 +4168,65 @@ function saveConfig(config) {
4102
4168
  );
4103
4169
  }
4104
4170
 
4171
+ // src/session-logger.ts
4172
+ import { createWriteStream, mkdirSync as mkdirSync2, unlinkSync } from "fs";
4173
+ import { createGzip } from "zlib";
4174
+ import { pipeline } from "stream/promises";
4175
+ import { createReadStream } from "fs";
4176
+ import { join as join4 } from "path";
4177
+ var FLUSH_INTERVAL_MS = 100;
4178
+ var BUFFER_SIZE = 4096;
4179
+ function getLogsDir() {
4180
+ return join4(getConfigDir(), "logs");
4181
+ }
4182
+ var SessionLogger = class {
4183
+ buffer = "";
4184
+ stream;
4185
+ logPath;
4186
+ flushTimer;
4187
+ closed = false;
4188
+ constructor(sessionId) {
4189
+ const logsDir = getLogsDir();
4190
+ mkdirSync2(logsDir, { recursive: true });
4191
+ this.logPath = join4(logsDir, `${sessionId}.log`);
4192
+ this.stream = createWriteStream(this.logPath, { flags: "a" });
4193
+ this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
4194
+ }
4195
+ write(data) {
4196
+ if (this.closed) return;
4197
+ this.buffer += data;
4198
+ if (this.buffer.length >= BUFFER_SIZE) {
4199
+ this.flush();
4200
+ }
4201
+ }
4202
+ flush() {
4203
+ if (this.buffer.length === 0 || this.closed) return;
4204
+ this.stream.write(this.buffer);
4205
+ this.buffer = "";
4206
+ }
4207
+ async close() {
4208
+ if (this.closed) return;
4209
+ this.closed = true;
4210
+ clearInterval(this.flushTimer);
4211
+ this.flush();
4212
+ await new Promise((resolve) => {
4213
+ this.stream.end(() => resolve());
4214
+ });
4215
+ try {
4216
+ const gzPath = this.logPath + ".gz";
4217
+ const source = createReadStream(this.logPath);
4218
+ const gzip = createGzip();
4219
+ const dest = createWriteStream(gzPath);
4220
+ await pipeline(source, gzip, dest);
4221
+ try {
4222
+ unlinkSync(this.logPath);
4223
+ } catch {
4224
+ }
4225
+ } catch {
4226
+ }
4227
+ }
4228
+ };
4229
+
4105
4230
  // src/wizard.ts
4106
4231
  import * as p from "@clack/prompts";
4107
4232
  async function runSetupWizard() {
@@ -4173,23 +4298,23 @@ async function runSetupWizard() {
4173
4298
  // src/cli-options.ts
4174
4299
  import { readFileSync as readFileSync3 } from "fs";
4175
4300
  import { fileURLToPath as fileURLToPath3 } from "url";
4176
- import { dirname as dirname3, join as join5 } from "path";
4301
+ import { dirname as dirname3, join as join6 } from "path";
4177
4302
 
4178
4303
  // src/hub-client.ts
4179
4304
  import { spawn as spawn2 } from "child_process";
4180
- import { readFileSync as readFileSync2, existsSync as existsSync2, unlinkSync } from "fs";
4305
+ import { readFileSync as readFileSync2, existsSync as existsSync2, unlinkSync as unlinkSync2 } from "fs";
4181
4306
  import { homedir as homedir2 } from "os";
4182
- import { join as join4, dirname as dirname2 } from "path";
4307
+ import { join as join5, dirname as dirname2 } from "path";
4183
4308
  import { fileURLToPath as fileURLToPath2 } from "url";
4184
4309
  import { request } from "http";
4185
4310
  var HUB_INTERNAL_PORT = 7963;
4186
4311
  var HUB_EXTERNAL_PORT = 7962;
4187
4312
  var SESSION_PORT_START = 7964;
4188
4313
  function getHubDir() {
4189
- return process.env.ITWILLSYNC_CONFIG_DIR || join4(homedir2(), ".itwillsync");
4314
+ return process.env.ITWILLSYNC_CONFIG_DIR || join5(homedir2(), ".itwillsync");
4190
4315
  }
4191
4316
  function getHubConfigPath() {
4192
- return join4(getHubDir(), "hub.json");
4317
+ return join5(getHubDir(), "hub.json");
4193
4318
  }
4194
4319
  async function discoverHub() {
4195
4320
  return new Promise((resolve) => {
@@ -4272,18 +4397,18 @@ async function killStaleHub() {
4272
4397
  }
4273
4398
  const hubDir = getHubDir();
4274
4399
  try {
4275
- unlinkSync(join4(hubDir, "hub.json"));
4400
+ unlinkSync2(join5(hubDir, "hub.json"));
4276
4401
  } catch {
4277
4402
  }
4278
4403
  try {
4279
- unlinkSync(join4(hubDir, "hub.pid"));
4404
+ unlinkSync2(join5(hubDir, "hub.pid"));
4280
4405
  } catch {
4281
4406
  }
4282
4407
  return true;
4283
4408
  }
4284
4409
  async function spawnHub() {
4285
4410
  const __dirname2 = dirname2(fileURLToPath2(import.meta.url));
4286
- const hubPath = join4(__dirname2, "hub", "daemon.js");
4411
+ const hubPath = join5(__dirname2, "hub", "daemon.js");
4287
4412
  return new Promise((resolve, reject) => {
4288
4413
  const child = spawn2("node", [hubPath], {
4289
4414
  detached: true,
@@ -4478,7 +4603,7 @@ async function sendHeartbeat(sessionId) {
4478
4603
  var __filename = fileURLToPath3(import.meta.url);
4479
4604
  var __dirname = dirname3(__filename);
4480
4605
  var { version } = JSON.parse(
4481
- readFileSync3(join5(__dirname, "..", "package.json"), "utf-8")
4606
+ readFileSync3(join6(__dirname, "..", "package.json"), "utf-8")
4482
4607
  );
4483
4608
  var DEFAULT_PORT = SESSION_PORT_START;
4484
4609
  function parseArgs(argv) {
@@ -4594,7 +4719,7 @@ Hub Management:
4594
4719
 
4595
4720
  // src/index.ts
4596
4721
  import { fileURLToPath as fileURLToPath4 } from "url";
4597
- import { join as join6, dirname as dirname4 } from "path";
4722
+ import { join as join7, dirname as dirname4 } from "path";
4598
4723
  import { spawn as spawn3 } from "child_process";
4599
4724
  function preventSleep() {
4600
4725
  try {
@@ -4729,13 +4854,13 @@ async function main() {
4729
4854
  printHelp();
4730
4855
  process.exit(1);
4731
4856
  }
4857
+ const config = loadConfig();
4732
4858
  let networkingMode = "local";
4733
4859
  if (options.tailscale) {
4734
4860
  networkingMode = "tailscale";
4735
4861
  } else if (options.local) {
4736
4862
  networkingMode = "local";
4737
4863
  } else {
4738
- const config = loadConfig();
4739
4864
  networkingMode = config.networkingMode;
4740
4865
  }
4741
4866
  const [cmd, ...cmdArgs] = options.command;
@@ -4746,15 +4871,20 @@ async function main() {
4746
4871
  const host = options.localhost ? "127.0.0.1" : "0.0.0.0";
4747
4872
  const ip = await resolveSessionIP(networkingMode, options.localhost);
4748
4873
  const __dirname2 = dirname4(fileURLToPath4(import.meta.url));
4749
- const webClientPath = join6(__dirname2, "web-client");
4874
+ const webClientPath = join7(__dirname2, "web-client");
4750
4875
  const ptyManager = new PtyManager(cmd, cmdArgs);
4876
+ const sessionId = `${cmd}-${Date.now().toString(36)}`;
4877
+ const sessionLogger = new SessionLogger(sessionId);
4751
4878
  const server = createSyncServer({
4752
4879
  ptyManager,
4753
4880
  token,
4754
4881
  webClientPath,
4755
4882
  host,
4756
4883
  port,
4757
- localTerminalOwnsResize: true
4884
+ resizePolicy: "last-writer-wins",
4885
+ scrollbackBufferSize: config.scrollbackBufferSize,
4886
+ clientBufferLimit: config.clientBufferLimit,
4887
+ logger: sessionLogger
4758
4888
  });
4759
4889
  let registeredSession = null;
4760
4890
  let heartbeatInterval = null;
@@ -4805,6 +4935,11 @@ async function main() {
4805
4935
  process.stdin.resume();
4806
4936
  process.stdin.setEncoding("utf-8");
4807
4937
  process.stdin.on("data", (data) => {
4938
+ if (process.stdout.columns && process.stdout.rows) {
4939
+ if (process.stdout.columns !== ptyManager.cols || process.stdout.rows !== ptyManager.rows) {
4940
+ server.resizeFromLocal(process.stdout.columns, process.stdout.rows);
4941
+ }
4942
+ }
4808
4943
  ptyManager.write(data);
4809
4944
  });
4810
4945
  ptyManager.onData((data) => {
@@ -4812,8 +4947,7 @@ async function main() {
4812
4947
  });
4813
4948
  function handleResize() {
4814
4949
  if (process.stdout.columns && process.stdout.rows) {
4815
- ptyManager.resize(process.stdout.columns, process.stdout.rows);
4816
- server.broadcastResize(process.stdout.columns, process.stdout.rows);
4950
+ server.resizeFromLocal(process.stdout.columns, process.stdout.rows);
4817
4951
  }
4818
4952
  }
4819
4953
  process.stdout.on("resize", handleResize);
@@ -4835,6 +4969,7 @@ async function main() {
4835
4969
  sleepGuard?.kill();
4836
4970
  server.close();
4837
4971
  ptyManager.kill();
4972
+ await sessionLogger.close();
4838
4973
  }
4839
4974
  ptyManager.onExit(async (exitCode) => {
4840
4975
  console.log(`