itwillsync 1.2.1 → 1.3.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.
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 request = isSecure ? https.request : http.request;
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 = request(opts);
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 = request(opts);
2851
+ req = websocket._req = request2(opts);
2852
2852
  }
2853
2853
  if (opts.timeout) {
2854
2854
  req.on("timeout", () => {
@@ -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
  }
@@ -4149,8 +4170,233 @@ async function runSetupWizard() {
4149
4170
  return config;
4150
4171
  }
4151
4172
 
4173
+ // src/hub-client.ts
4174
+ import { spawn as spawn2 } from "child_process";
4175
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
4176
+ import { homedir as homedir2 } from "os";
4177
+ import { join as join4, dirname as dirname2 } from "path";
4178
+ import { fileURLToPath as fileURLToPath2 } from "url";
4179
+ import { request } from "http";
4180
+ var HUB_INTERNAL_PORT = 7963;
4181
+ var HUB_EXTERNAL_PORT = 7962;
4182
+ var SESSION_PORT_START = 7964;
4183
+ function getHubDir() {
4184
+ return process.env.ITWILLSYNC_CONFIG_DIR || join4(homedir2(), ".itwillsync");
4185
+ }
4186
+ function getHubConfigPath() {
4187
+ return join4(getHubDir(), "hub.json");
4188
+ }
4189
+ async function discoverHub() {
4190
+ return new Promise((resolve) => {
4191
+ const req = request(
4192
+ {
4193
+ hostname: "127.0.0.1",
4194
+ port: HUB_INTERNAL_PORT,
4195
+ path: "/api/health",
4196
+ method: "GET",
4197
+ timeout: 2e3
4198
+ },
4199
+ (res) => {
4200
+ let data = "";
4201
+ res.on("data", (chunk) => {
4202
+ data += chunk;
4203
+ });
4204
+ res.on("end", () => {
4205
+ try {
4206
+ const json = JSON.parse(data);
4207
+ resolve(json.status === "ok");
4208
+ } catch {
4209
+ resolve(false);
4210
+ }
4211
+ });
4212
+ }
4213
+ );
4214
+ req.on("error", () => resolve(false));
4215
+ req.on("timeout", () => {
4216
+ req.destroy();
4217
+ resolve(false);
4218
+ });
4219
+ req.end();
4220
+ });
4221
+ }
4222
+ async function spawnHub() {
4223
+ const __dirname = dirname2(fileURLToPath2(import.meta.url));
4224
+ const hubPath = join4(__dirname, "hub", "daemon.js");
4225
+ return new Promise((resolve, reject) => {
4226
+ const child = spawn2("node", [hubPath], {
4227
+ detached: true,
4228
+ stdio: ["ignore", "pipe", "ignore"],
4229
+ env: {
4230
+ ...process.env,
4231
+ // Pass config dir to hub
4232
+ ITWILLSYNC_CONFIG_DIR: process.env.ITWILLSYNC_CONFIG_DIR || ""
4233
+ }
4234
+ });
4235
+ child.unref();
4236
+ let output = "";
4237
+ const timeout = setTimeout(() => {
4238
+ reject(new Error("Hub daemon startup timed out"));
4239
+ }, 1e4);
4240
+ child.stdout?.on("data", (data) => {
4241
+ output += data.toString();
4242
+ if (output.includes("hub:ready:")) {
4243
+ clearTimeout(timeout);
4244
+ child.stdout?.destroy();
4245
+ resolve();
4246
+ }
4247
+ });
4248
+ child.on("error", (err) => {
4249
+ clearTimeout(timeout);
4250
+ reject(new Error(`Failed to spawn hub daemon: ${err.message}`));
4251
+ });
4252
+ child.on("exit", (code) => {
4253
+ clearTimeout(timeout);
4254
+ if (code !== null && code !== 0) {
4255
+ reject(new Error(`Hub daemon exited with code ${code}`));
4256
+ }
4257
+ });
4258
+ });
4259
+ }
4260
+ function getHubConfig() {
4261
+ const configPath = getHubConfigPath();
4262
+ if (!existsSync2(configPath)) {
4263
+ return null;
4264
+ }
4265
+ try {
4266
+ const raw = readFileSync2(configPath, "utf-8");
4267
+ return JSON.parse(raw);
4268
+ } catch {
4269
+ return null;
4270
+ }
4271
+ }
4272
+ async function registerSession(registration) {
4273
+ const body = JSON.stringify(registration);
4274
+ return new Promise((resolve, reject) => {
4275
+ const req = request(
4276
+ {
4277
+ hostname: "127.0.0.1",
4278
+ port: HUB_INTERNAL_PORT,
4279
+ path: "/api/sessions",
4280
+ method: "POST",
4281
+ headers: {
4282
+ "Content-Type": "application/json",
4283
+ "Content-Length": Buffer.byteLength(body)
4284
+ },
4285
+ timeout: 5e3
4286
+ },
4287
+ (res) => {
4288
+ let data = "";
4289
+ res.on("data", (chunk) => {
4290
+ data += chunk;
4291
+ });
4292
+ res.on("end", () => {
4293
+ try {
4294
+ const json = JSON.parse(data);
4295
+ if (res.statusCode === 201 && json.session) {
4296
+ resolve(json.session);
4297
+ } else {
4298
+ reject(new Error(`Registration failed: ${json.error || "Unknown error"}`));
4299
+ }
4300
+ } catch {
4301
+ reject(new Error("Invalid response from hub"));
4302
+ }
4303
+ });
4304
+ }
4305
+ );
4306
+ req.on("error", (err) => reject(new Error(`Failed to register with hub: ${err.message}`)));
4307
+ req.on("timeout", () => {
4308
+ req.destroy();
4309
+ reject(new Error("Registration request timed out"));
4310
+ });
4311
+ req.end(body);
4312
+ });
4313
+ }
4314
+ async function unregisterSession(sessionId) {
4315
+ return new Promise((resolve) => {
4316
+ const req = request(
4317
+ {
4318
+ hostname: "127.0.0.1",
4319
+ port: HUB_INTERNAL_PORT,
4320
+ path: `/api/sessions/${sessionId}`,
4321
+ method: "DELETE",
4322
+ timeout: 3e3
4323
+ },
4324
+ () => resolve()
4325
+ );
4326
+ req.on("error", () => resolve());
4327
+ req.on("timeout", () => {
4328
+ req.destroy();
4329
+ resolve();
4330
+ });
4331
+ req.end();
4332
+ });
4333
+ }
4334
+ async function listSessions() {
4335
+ return new Promise((resolve) => {
4336
+ const req = request(
4337
+ {
4338
+ hostname: "127.0.0.1",
4339
+ port: HUB_INTERNAL_PORT,
4340
+ path: "/api/sessions",
4341
+ method: "GET",
4342
+ timeout: 3e3
4343
+ },
4344
+ (res) => {
4345
+ let data = "";
4346
+ res.on("data", (chunk) => {
4347
+ data += chunk;
4348
+ });
4349
+ res.on("end", () => {
4350
+ try {
4351
+ const json = JSON.parse(data);
4352
+ resolve(json.sessions || []);
4353
+ } catch {
4354
+ resolve([]);
4355
+ }
4356
+ });
4357
+ }
4358
+ );
4359
+ req.on("error", () => resolve([]));
4360
+ req.on("timeout", () => {
4361
+ req.destroy();
4362
+ resolve([]);
4363
+ });
4364
+ req.end();
4365
+ });
4366
+ }
4367
+ function stopHub() {
4368
+ const config = getHubConfig();
4369
+ if (!config) return false;
4370
+ try {
4371
+ process.kill(config.pid, "SIGTERM");
4372
+ return true;
4373
+ } catch {
4374
+ return false;
4375
+ }
4376
+ }
4377
+ async function sendHeartbeat(sessionId) {
4378
+ return new Promise((resolve) => {
4379
+ const req = request(
4380
+ {
4381
+ hostname: "127.0.0.1",
4382
+ port: HUB_INTERNAL_PORT,
4383
+ path: `/api/sessions/${sessionId}/heartbeat`,
4384
+ method: "PUT",
4385
+ timeout: 2e3
4386
+ },
4387
+ () => resolve()
4388
+ );
4389
+ req.on("error", () => resolve());
4390
+ req.on("timeout", () => {
4391
+ req.destroy();
4392
+ resolve();
4393
+ });
4394
+ req.end();
4395
+ });
4396
+ }
4397
+
4152
4398
  // src/cli-options.ts
4153
- var DEFAULT_PORT = 3456;
4399
+ var DEFAULT_PORT = SESSION_PORT_START;
4154
4400
  function parseArgs(argv) {
4155
4401
  const options = {
4156
4402
  port: DEFAULT_PORT,
@@ -4159,13 +4405,24 @@ function parseArgs(argv) {
4159
4405
  command: [],
4160
4406
  subcommand: null,
4161
4407
  tailscale: false,
4162
- local: false
4408
+ local: false,
4409
+ hubInfo: false,
4410
+ hubStop: false,
4411
+ hubStatus: false
4163
4412
  };
4164
4413
  const args = argv.slice(2);
4165
4414
  if (args.length > 0 && args[0] === "setup") {
4166
4415
  options.subcommand = "setup";
4167
4416
  return options;
4168
4417
  }
4418
+ if (args.length > 0 && args[0] === "hub") {
4419
+ const hubAction = args[1] || "info";
4420
+ if (hubAction === "info") options.hubInfo = true;
4421
+ else if (hubAction === "stop") options.hubStop = true;
4422
+ else if (hubAction === "status") options.hubStatus = true;
4423
+ else options.hubInfo = true;
4424
+ return options;
4425
+ }
4169
4426
  let i = 0;
4170
4427
  while (i < args.length) {
4171
4428
  const arg = args[i];
@@ -4187,6 +4444,15 @@ function parseArgs(argv) {
4187
4444
  } else if (arg === "--no-qr") {
4188
4445
  options.noQr = true;
4189
4446
  i++;
4447
+ } else if (arg === "--hub-info") {
4448
+ options.hubInfo = true;
4449
+ i++;
4450
+ } else if (arg === "--hub-stop") {
4451
+ options.hubStop = true;
4452
+ i++;
4453
+ } else if (arg === "--hub-status") {
4454
+ options.hubStatus = true;
4455
+ i++;
4190
4456
  } else if (arg === "--help" || arg === "-h") {
4191
4457
  printHelp();
4192
4458
  process.exit(0);
@@ -4208,6 +4474,7 @@ Usage:
4208
4474
  itwillsync [options] -- <command> [args...]
4209
4475
  itwillsync [options] <command> [args...]
4210
4476
  itwillsync setup
4477
+ itwillsync hub [info|stop|status]
4211
4478
 
4212
4479
  Examples:
4213
4480
  itwillsync -- claude
@@ -4216,9 +4483,14 @@ Examples:
4216
4483
  itwillsync --port 8080 -- claude
4217
4484
  itwillsync --tailscale -- claude
4218
4485
  itwillsync setup
4486
+ itwillsync hub info
4487
+ itwillsync hub stop
4219
4488
 
4220
4489
  Commands:
4221
4490
  setup Run the setup wizard (configure networking mode)
4491
+ hub info Show dashboard URL, QR code, and hub status
4492
+ hub stop Stop the hub daemon and all sessions
4493
+ hub status List all active sessions
4222
4494
 
4223
4495
  Options:
4224
4496
  --port <number> Port to listen on (default: ${DEFAULT_PORT})
@@ -4228,24 +4500,29 @@ Options:
4228
4500
  --no-qr Don't display QR code
4229
4501
  -h, --help Show this help
4230
4502
  -v, --version Show version
4503
+
4504
+ Hub Management:
4505
+ --hub-info Show dashboard URL, QR code, and hub status
4506
+ --hub-stop Stop the hub daemon and all sessions
4507
+ --hub-status List all active sessions
4231
4508
  `);
4232
4509
  }
4233
4510
 
4234
4511
  // src/index.ts
4235
- import { fileURLToPath as fileURLToPath2 } from "url";
4236
- import { join as join4, dirname as dirname2 } from "path";
4237
- import { spawn as spawn2 } from "child_process";
4512
+ import { fileURLToPath as fileURLToPath3 } from "url";
4513
+ import { join as join5, dirname as dirname3 } from "path";
4514
+ import { spawn as spawn3 } from "child_process";
4238
4515
  function preventSleep() {
4239
4516
  try {
4240
4517
  if (process.platform === "darwin") {
4241
- const child = spawn2("caffeinate", ["-i", "-w", String(process.pid)], {
4518
+ const child = spawn3("caffeinate", ["-i", "-w", String(process.pid)], {
4242
4519
  stdio: "ignore",
4243
4520
  detached: true
4244
4521
  });
4245
4522
  child.unref();
4246
4523
  return child;
4247
4524
  } else if (process.platform === "linux") {
4248
- return spawn2("systemd-inhibit", [
4525
+ return spawn3("systemd-inhibit", [
4249
4526
  "--what=idle",
4250
4527
  "--who=itwillsync",
4251
4528
  "--why=Terminal sync session active",
@@ -4257,12 +4534,96 @@ function preventSleep() {
4257
4534
  }
4258
4535
  return null;
4259
4536
  }
4537
+ async function ensureHub() {
4538
+ const hubRunning = await discoverHub();
4539
+ if (hubRunning) {
4540
+ return false;
4541
+ }
4542
+ try {
4543
+ await spawnHub();
4544
+ await new Promise((resolve) => setTimeout(resolve, 500));
4545
+ return true;
4546
+ } catch (err) {
4547
+ console.warn(`
4548
+ Warning: Could not start hub daemon: ${err.message}`);
4549
+ console.warn(" Running in standalone mode (no dashboard).\n");
4550
+ return true;
4551
+ }
4552
+ }
4553
+ async function handleHubCommand(options) {
4554
+ const hubConfig = getHubConfig();
4555
+ const hubRunning = await discoverHub();
4556
+ if (options.hubStop) {
4557
+ if (!hubRunning || !hubConfig) {
4558
+ console.log("\n No hub daemon is running.\n");
4559
+ return;
4560
+ }
4561
+ const stopped = stopHub();
4562
+ if (stopped) {
4563
+ console.log("\n Hub daemon stopped.\n");
4564
+ } else {
4565
+ console.log("\n Failed to stop hub daemon.\n");
4566
+ }
4567
+ return;
4568
+ }
4569
+ if (options.hubStatus) {
4570
+ if (!hubRunning) {
4571
+ console.log("\n No hub daemon is running.\n");
4572
+ return;
4573
+ }
4574
+ const sessions = await listSessions();
4575
+ console.log(`
4576
+ Hub is running. ${sessions.length} active session(s).
4577
+ `);
4578
+ if (sessions.length > 0) {
4579
+ for (const s of sessions) {
4580
+ const uptime = Math.floor((Date.now() - s.connectedAt) / 6e4);
4581
+ console.log(` ${s.name || s.agent} (${s.status}, ${uptime}m, port ${s.port})`);
4582
+ }
4583
+ console.log("");
4584
+ }
4585
+ return;
4586
+ }
4587
+ if (options.hubInfo) {
4588
+ if (!hubRunning || !hubConfig) {
4589
+ console.log("\n No hub daemon is running.");
4590
+ console.log(" Start a session with: itwillsync -- <agent>\n");
4591
+ return;
4592
+ }
4593
+ let networkingMode = "local";
4594
+ if (options.tailscale) {
4595
+ networkingMode = "tailscale";
4596
+ } else if (options.local) {
4597
+ networkingMode = "local";
4598
+ } else if (configExists()) {
4599
+ networkingMode = loadConfig().networkingMode;
4600
+ }
4601
+ const ip = await resolveSessionIP(networkingMode, false);
4602
+ const dashboardUrl = `http://${ip}:${HUB_EXTERNAL_PORT}?token=${hubConfig.masterToken}`;
4603
+ displayQR(dashboardUrl);
4604
+ console.log(` Dashboard: ${dashboardUrl}`);
4605
+ const sessions = await listSessions();
4606
+ console.log(` Sessions: ${sessions.length} active`);
4607
+ if (sessions.length > 0) {
4608
+ for (const s of sessions) {
4609
+ const uptime = Math.floor((Date.now() - s.connectedAt) / 6e4);
4610
+ console.log(` ${s.name || s.agent} (${s.status}, ${uptime}m)`);
4611
+ }
4612
+ }
4613
+ console.log("");
4614
+ return;
4615
+ }
4616
+ }
4260
4617
  async function main() {
4261
4618
  const options = parseArgs(process.argv);
4262
4619
  if (options.subcommand === "setup") {
4263
4620
  await runSetupWizard();
4264
4621
  return;
4265
4622
  }
4623
+ if (options.hubInfo || options.hubStop || options.hubStatus) {
4624
+ await handleHubCommand(options);
4625
+ return;
4626
+ }
4266
4627
  if (options.tailscale && options.local) {
4267
4628
  console.error("Error: Cannot use both --tailscale and --local.\n");
4268
4629
  process.exit(1);
@@ -4285,27 +4646,59 @@ async function main() {
4285
4646
  networkingMode = config.networkingMode;
4286
4647
  }
4287
4648
  const [cmd, ...cmdArgs] = options.command;
4649
+ const isFirstSession = await ensureHub();
4650
+ const hubConfig = getHubConfig();
4288
4651
  const token = generateToken();
4289
4652
  const port = await findAvailablePort(options.port);
4290
4653
  const host = options.localhost ? "127.0.0.1" : "0.0.0.0";
4291
4654
  const ip = await resolveSessionIP(networkingMode, options.localhost);
4292
- const url = `http://${ip}:${port}?token=${token}`;
4293
- const __dirname = dirname2(fileURLToPath2(import.meta.url));
4294
- const webClientPath = join4(__dirname, "web-client");
4655
+ const __dirname = dirname3(fileURLToPath3(import.meta.url));
4656
+ const webClientPath = join5(__dirname, "web-client");
4295
4657
  const ptyManager = new PtyManager(cmd, cmdArgs);
4296
4658
  const server = createSyncServer({
4297
4659
  ptyManager,
4298
4660
  token,
4299
4661
  webClientPath,
4300
4662
  host,
4301
- port
4663
+ port,
4664
+ localTerminalOwnsResize: true
4302
4665
  });
4303
- if (!options.noQr) {
4304
- displayQR(url);
4305
- } else {
4666
+ let registeredSession = null;
4667
+ let heartbeatInterval = null;
4668
+ if (hubConfig) {
4669
+ try {
4670
+ registeredSession = await registerSession({
4671
+ name: cmd,
4672
+ port,
4673
+ token,
4674
+ agent: cmd,
4675
+ cwd: process.cwd(),
4676
+ pid: ptyManager.pid
4677
+ });
4678
+ heartbeatInterval = setInterval(() => {
4679
+ if (registeredSession) {
4680
+ sendHeartbeat(registeredSession.id);
4681
+ }
4682
+ }, 1e4);
4683
+ } catch (err) {
4684
+ console.warn(` Warning: Failed to register with hub: ${err.message}`);
4685
+ }
4686
+ }
4687
+ const dashboardUrl = hubConfig ? `http://${ip}:${HUB_EXTERNAL_PORT}?token=${hubConfig.masterToken}` : null;
4688
+ if (isFirstSession && dashboardUrl && !options.noQr) {
4689
+ displayQR(dashboardUrl);
4690
+ console.log(` Dashboard: ${dashboardUrl}`);
4691
+ } else if (isFirstSession && !options.noQr) {
4692
+ const directUrl = `http://${ip}:${port}?token=${token}`;
4693
+ displayQR(directUrl);
4694
+ } else if (dashboardUrl) {
4306
4695
  console.log(`
4307
- Connect at: ${url}
4308
- `);
4696
+ Session "${cmd}" registered with hub.`);
4697
+ console.log(` Dashboard: ${dashboardUrl}`);
4698
+ console.log("");
4699
+ } else if (!options.noQr) {
4700
+ const directUrl = `http://${ip}:${port}?token=${token}`;
4701
+ displayQR(directUrl);
4309
4702
  }
4310
4703
  const sleepGuard = preventSleep();
4311
4704
  console.log(` Server listening on ${host}:${port}`);
@@ -4327,30 +4720,37 @@ async function main() {
4327
4720
  function handleResize() {
4328
4721
  if (process.stdout.columns && process.stdout.rows) {
4329
4722
  ptyManager.resize(process.stdout.columns, process.stdout.rows);
4723
+ server.broadcastResize(process.stdout.columns, process.stdout.rows);
4330
4724
  }
4331
4725
  }
4332
4726
  process.stdout.on("resize", handleResize);
4333
4727
  handleResize();
4334
- function cleanup() {
4728
+ async function cleanup() {
4335
4729
  if (process.stdin.isTTY) {
4336
4730
  process.stdin.setRawMode(false);
4337
4731
  }
4732
+ if (heartbeatInterval) {
4733
+ clearInterval(heartbeatInterval);
4734
+ }
4735
+ if (registeredSession) {
4736
+ await unregisterSession(registeredSession.id);
4737
+ }
4338
4738
  sleepGuard?.kill();
4339
4739
  server.close();
4340
4740
  ptyManager.kill();
4341
4741
  }
4342
- ptyManager.onExit((exitCode) => {
4742
+ ptyManager.onExit(async (exitCode) => {
4343
4743
  console.log(`
4344
4744
  Agent exited with code ${exitCode}`);
4345
- cleanup();
4745
+ await cleanup();
4346
4746
  process.exit(exitCode);
4347
4747
  });
4348
- process.on("SIGINT", () => {
4349
- cleanup();
4748
+ process.on("SIGINT", async () => {
4749
+ await cleanup();
4350
4750
  process.exit(0);
4351
4751
  });
4352
- process.on("SIGTERM", () => {
4353
- cleanup();
4752
+ process.on("SIGTERM", async () => {
4753
+ await cleanup();
4354
4754
  process.exit(0);
4355
4755
  });
4356
4756
  }