itwillsync 1.3.0 → 1.3.2

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/index.js CHANGED
@@ -3683,6 +3683,14 @@ function ensureSpawnHelperPermissions() {
3683
3683
  }
3684
3684
  var PtyManager = class {
3685
3685
  ptyProcess;
3686
+ _cols = 80;
3687
+ _rows = 24;
3688
+ get cols() {
3689
+ return this._cols;
3690
+ }
3691
+ get rows() {
3692
+ return this._rows;
3693
+ }
3686
3694
  /** The process ID of the spawned PTY process. */
3687
3695
  pid;
3688
3696
  constructor(command, args) {
@@ -3725,6 +3733,8 @@ var PtyManager = class {
3725
3733
  resize(cols, rows) {
3726
3734
  try {
3727
3735
  this.ptyProcess.resize(cols, rows);
3736
+ this._cols = cols;
3737
+ this._rows = rows;
3728
3738
  } catch {
3729
3739
  }
3730
3740
  }
@@ -3929,7 +3939,7 @@ async function serveStaticFile(webClientPath, filePath, req, res) {
3929
3939
  var PING_INTERVAL_MS = 3e4;
3930
3940
  var SCROLLBACK_BUFFER_SIZE = 5e4;
3931
3941
  function createSyncServer(options) {
3932
- const { ptyManager, token, webClientPath, host, port } = options;
3942
+ const { ptyManager, token, webClientPath, host, port, localTerminalOwnsResize = false } = options;
3933
3943
  const clients = /* @__PURE__ */ new Set();
3934
3944
  const aliveMap = /* @__PURE__ */ new WeakMap();
3935
3945
  let scrollbackBuffer = "";
@@ -3972,6 +3982,7 @@ function createSyncServer(options) {
3972
3982
  if (scrollbackBuffer.length > 0) {
3973
3983
  ws.send(JSON.stringify({ type: "data", data: scrollbackBuffer, seq }));
3974
3984
  }
3985
+ ws.send(JSON.stringify({ type: "resize", cols: ptyManager.cols, rows: ptyManager.rows }));
3975
3986
  ws.on("pong", () => {
3976
3987
  aliveMap.set(ws, true);
3977
3988
  });
@@ -3981,7 +3992,9 @@ function createSyncServer(options) {
3981
3992
  if (message.type === "input" && typeof message.data === "string") {
3982
3993
  ptyManager.write(message.data);
3983
3994
  } else if (message.type === "resize" && typeof message.cols === "number" && typeof message.rows === "number") {
3984
- ptyManager.resize(message.cols, message.rows);
3995
+ if (!localTerminalOwnsResize) {
3996
+ ptyManager.resize(message.cols, message.rows);
3997
+ }
3985
3998
  } else if (message.type === "resume" && typeof message.lastSeq === "number") {
3986
3999
  const missed = seq - message.lastSeq;
3987
4000
  if (missed > 0 && scrollbackBuffer.length > 0) {
@@ -4024,6 +4037,14 @@ function createSyncServer(options) {
4024
4037
  clients.clear();
4025
4038
  wssServer.close();
4026
4039
  httpServer.close();
4040
+ },
4041
+ broadcastResize(cols, rows) {
4042
+ const msg = JSON.stringify({ type: "resize", cols, rows });
4043
+ for (const client of clients) {
4044
+ if (client.readyState === client.OPEN) {
4045
+ client.send(msg);
4046
+ }
4047
+ }
4027
4048
  }
4028
4049
  };
4029
4050
  }
@@ -4151,7 +4172,7 @@ async function runSetupWizard() {
4151
4172
 
4152
4173
  // src/hub-client.ts
4153
4174
  import { spawn as spawn2 } from "child_process";
4154
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
4175
+ import { readFileSync as readFileSync2, existsSync as existsSync2, unlinkSync } from "fs";
4155
4176
  import { homedir as homedir2 } from "os";
4156
4177
  import { join as join4, dirname as dirname2 } from "path";
4157
4178
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -4198,13 +4219,70 @@ async function discoverHub() {
4198
4219
  req.end();
4199
4220
  });
4200
4221
  }
4222
+ async function getHubPidFromHealth() {
4223
+ return new Promise((resolve) => {
4224
+ const req = request(
4225
+ {
4226
+ hostname: "127.0.0.1",
4227
+ port: HUB_INTERNAL_PORT,
4228
+ path: "/api/health",
4229
+ method: "GET",
4230
+ timeout: 2e3
4231
+ },
4232
+ (res) => {
4233
+ let data = "";
4234
+ res.on("data", (chunk) => {
4235
+ data += chunk;
4236
+ });
4237
+ res.on("end", () => {
4238
+ try {
4239
+ const json = JSON.parse(data);
4240
+ resolve(json.pid && typeof json.pid === "number" ? json.pid : null);
4241
+ } catch {
4242
+ resolve(null);
4243
+ }
4244
+ });
4245
+ }
4246
+ );
4247
+ req.on("error", () => resolve(null));
4248
+ req.on("timeout", () => {
4249
+ req.destroy();
4250
+ resolve(null);
4251
+ });
4252
+ req.end();
4253
+ });
4254
+ }
4255
+ async function killStaleHub() {
4256
+ const pid = await getHubPidFromHealth();
4257
+ if (!pid) return false;
4258
+ try {
4259
+ process.kill(pid, "SIGTERM");
4260
+ } catch {
4261
+ return false;
4262
+ }
4263
+ for (let i = 0; i < 20; i++) {
4264
+ await new Promise((r) => setTimeout(r, 200));
4265
+ const stillRunning = await discoverHub();
4266
+ if (!stillRunning) break;
4267
+ }
4268
+ const hubDir = getHubDir();
4269
+ try {
4270
+ unlinkSync(join4(hubDir, "hub.json"));
4271
+ } catch {
4272
+ }
4273
+ try {
4274
+ unlinkSync(join4(hubDir, "hub.pid"));
4275
+ } catch {
4276
+ }
4277
+ return true;
4278
+ }
4201
4279
  async function spawnHub() {
4202
4280
  const __dirname = dirname2(fileURLToPath2(import.meta.url));
4203
4281
  const hubPath = join4(__dirname, "hub", "daemon.js");
4204
4282
  return new Promise((resolve, reject) => {
4205
4283
  const child = spawn2("node", [hubPath], {
4206
4284
  detached: true,
4207
- stdio: ["ignore", "pipe", "ignore"],
4285
+ stdio: ["ignore", "pipe", "pipe"],
4208
4286
  env: {
4209
4287
  ...process.env,
4210
4288
  // Pass config dir to hub
@@ -4213,6 +4291,7 @@ async function spawnHub() {
4213
4291
  });
4214
4292
  child.unref();
4215
4293
  let output = "";
4294
+ let stderrOutput = "";
4216
4295
  const timeout = setTimeout(() => {
4217
4296
  reject(new Error("Hub daemon startup timed out"));
4218
4297
  }, 1e4);
@@ -4221,9 +4300,13 @@ async function spawnHub() {
4221
4300
  if (output.includes("hub:ready:")) {
4222
4301
  clearTimeout(timeout);
4223
4302
  child.stdout?.destroy();
4303
+ child.stderr?.destroy();
4224
4304
  resolve();
4225
4305
  }
4226
4306
  });
4307
+ child.stderr?.on("data", (data) => {
4308
+ stderrOutput += data.toString();
4309
+ });
4227
4310
  child.on("error", (err) => {
4228
4311
  clearTimeout(timeout);
4229
4312
  reject(new Error(`Failed to spawn hub daemon: ${err.message}`));
@@ -4231,7 +4314,11 @@ async function spawnHub() {
4231
4314
  child.on("exit", (code) => {
4232
4315
  clearTimeout(timeout);
4233
4316
  if (code !== null && code !== 0) {
4234
- reject(new Error(`Hub daemon exited with code ${code}`));
4317
+ const detail = stderrOutput.trim();
4318
+ reject(new Error(
4319
+ `Hub daemon exited with code ${code}${detail ? `:
4320
+ ${detail}` : ""}`
4321
+ ));
4235
4322
  }
4236
4323
  });
4237
4324
  });
@@ -4343,11 +4430,19 @@ async function listSessions() {
4343
4430
  req.end();
4344
4431
  });
4345
4432
  }
4346
- function stopHub() {
4433
+ async function stopHub() {
4347
4434
  const config = getHubConfig();
4348
- if (!config) return false;
4435
+ if (config) {
4436
+ try {
4437
+ process.kill(config.pid, "SIGTERM");
4438
+ return true;
4439
+ } catch {
4440
+ }
4441
+ }
4442
+ const pid = await getHubPidFromHealth();
4443
+ if (!pid) return false;
4349
4444
  try {
4350
- process.kill(config.pid, "SIGTERM");
4445
+ process.kill(pid, "SIGTERM");
4351
4446
  return true;
4352
4447
  } catch {
4353
4448
  return false;
@@ -4516,11 +4611,15 @@ function preventSleep() {
4516
4611
  async function ensureHub() {
4517
4612
  const hubRunning = await discoverHub();
4518
4613
  if (hubRunning) {
4519
- return false;
4614
+ const config = getHubConfig();
4615
+ if (config) {
4616
+ return false;
4617
+ }
4618
+ console.warn(" Detected stale hub daemon (no config). Restarting...");
4619
+ await killStaleHub();
4520
4620
  }
4521
4621
  try {
4522
4622
  await spawnHub();
4523
- await new Promise((resolve) => setTimeout(resolve, 500));
4524
4623
  return true;
4525
4624
  } catch (err) {
4526
4625
  console.warn(`
@@ -4533,11 +4632,11 @@ async function handleHubCommand(options) {
4533
4632
  const hubConfig = getHubConfig();
4534
4633
  const hubRunning = await discoverHub();
4535
4634
  if (options.hubStop) {
4536
- if (!hubRunning || !hubConfig) {
4635
+ if (!hubRunning) {
4537
4636
  console.log("\n No hub daemon is running.\n");
4538
4637
  return;
4539
4638
  }
4540
- const stopped = stopHub();
4639
+ const stopped = await stopHub();
4541
4640
  if (stopped) {
4542
4641
  console.log("\n Hub daemon stopped.\n");
4543
4642
  } else {
@@ -4564,11 +4663,16 @@ async function handleHubCommand(options) {
4564
4663
  return;
4565
4664
  }
4566
4665
  if (options.hubInfo) {
4567
- if (!hubRunning || !hubConfig) {
4666
+ if (!hubRunning) {
4568
4667
  console.log("\n No hub daemon is running.");
4569
4668
  console.log(" Start a session with: itwillsync -- <agent>\n");
4570
4669
  return;
4571
4670
  }
4671
+ if (!hubConfig) {
4672
+ console.log("\n Hub daemon is running but its config is missing (stale state).");
4673
+ console.log(" Run: itwillsync hub stop\n");
4674
+ return;
4675
+ }
4572
4676
  let networkingMode = "local";
4573
4677
  if (options.tailscale) {
4574
4678
  networkingMode = "tailscale";
@@ -4639,7 +4743,8 @@ async function main() {
4639
4743
  token,
4640
4744
  webClientPath,
4641
4745
  host,
4642
- port
4746
+ port,
4747
+ localTerminalOwnsResize: true
4643
4748
  });
4644
4749
  let registeredSession = null;
4645
4750
  let heartbeatInterval = null;
@@ -4698,6 +4803,7 @@ async function main() {
4698
4803
  function handleResize() {
4699
4804
  if (process.stdout.columns && process.stdout.rows) {
4700
4805
  ptyManager.resize(process.stdout.columns, process.stdout.rows);
4806
+ server.broadcastResize(process.stdout.columns, process.stdout.rows);
4701
4807
  }
4702
4808
  }
4703
4809
  process.stdout.on("resize", handleResize);