cli-remote-agent 1.0.2 → 1.0.4

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
@@ -57,6 +57,8 @@ var CLAUDE_SESSIONS_RESULT = "claude:sessions:result";
57
57
  var CLAUDE_HOOK = "claude:hook";
58
58
  var TMUX_LIST = "tmux:list";
59
59
  var TMUX_LIST_RESULT = "tmux:list:result";
60
+ var TMUX_KILL = "tmux:kill";
61
+ var TMUX_KILL_RESULT = "tmux:kill:result";
60
62
 
61
63
  // ../shared/src/constants.ts
62
64
  var HEARTBEAT_INTERVAL = 15e3;
@@ -250,9 +252,73 @@ function startLocalControl(port, onHook) {
250
252
  }
251
253
 
252
254
  // src/tmux.ts
255
+ var import_child_process2 = require("child_process");
256
+ var import_util2 = require("util");
257
+
258
+ // src/claude-live.ts
259
+ var import_fs3 = __toESM(require("fs"));
260
+ var import_path3 = __toESM(require("path"));
261
+ var import_os2 = __toESM(require("os"));
253
262
  var import_child_process = require("child_process");
254
263
  var import_util = require("util");
255
264
  var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
265
+ var LIVE_SESSIONS_DIR = import_path3.default.join(import_os2.default.homedir(), ".claude", "sessions");
266
+ function isAlive(pid) {
267
+ try {
268
+ process.kill(pid, 0);
269
+ return true;
270
+ } catch {
271
+ return false;
272
+ }
273
+ }
274
+ function getLiveClaudeSessions() {
275
+ let files;
276
+ try {
277
+ files = import_fs3.default.readdirSync(LIVE_SESSIONS_DIR).filter((f) => f.endsWith(".json"));
278
+ } catch {
279
+ return [];
280
+ }
281
+ const out = [];
282
+ for (const f of files) {
283
+ try {
284
+ const obj = JSON.parse(import_fs3.default.readFileSync(import_path3.default.join(LIVE_SESSIONS_DIR, f), "utf-8"));
285
+ if (typeof obj?.pid !== "number" || !isAlive(obj.pid)) continue;
286
+ out.push({
287
+ pid: obj.pid,
288
+ cwd: typeof obj.cwd === "string" ? obj.cwd : "",
289
+ name: typeof obj.name === "string" ? obj.name : void 0,
290
+ status: typeof obj.status === "string" ? obj.status : void 0,
291
+ updatedAt: typeof obj.updatedAt === "number" ? obj.updatedAt : void 0
292
+ });
293
+ } catch {
294
+ }
295
+ }
296
+ out.sort((a, b) => (a.updatedAt || 0) - (b.updatedAt || 0));
297
+ return out;
298
+ }
299
+ async function getPidParents() {
300
+ const map = /* @__PURE__ */ new Map();
301
+ try {
302
+ const { stdout } = await execFileAsync("ps", ["-axo", "pid=,ppid="], { timeout: 5e3 });
303
+ for (const line of stdout.split("\n")) {
304
+ const m = line.trim().match(/^(\d+)\s+(\d+)$/);
305
+ if (m) map.set(parseInt(m[1], 10), parseInt(m[2], 10));
306
+ }
307
+ } catch {
308
+ }
309
+ return map;
310
+ }
311
+ function isDescendantOf(pid, ancestor, parents) {
312
+ let cur = pid;
313
+ for (let i = 0; i < 25 && cur !== void 0 && cur > 1; i++) {
314
+ if (cur === ancestor) return true;
315
+ cur = parents.get(cur);
316
+ }
317
+ return false;
318
+ }
319
+
320
+ // src/tmux.ts
321
+ var execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
256
322
  var IS_WIN = process.platform === "win32";
257
323
  function tmuxCmd(args) {
258
324
  return IS_WIN ? { file: "wsl.exe", args: ["tmux", ...args] } : { file: "tmux", args };
@@ -261,8 +327,8 @@ async function listTmuxSessions() {
261
327
  const fmt = "#{session_name} #{session_windows} #{session_attached} #{session_activity}";
262
328
  const { file, args } = tmuxCmd(["list-sessions", "-F", fmt]);
263
329
  try {
264
- const { stdout } = await execFileAsync(file, args, { timeout: 6e3 });
265
- return stdout.split("\n").map((line) => line.replace(/\r$/, "").trim()).filter(Boolean).map((line) => {
330
+ const { stdout } = await execFileAsync2(file, args, { timeout: 6e3 });
331
+ const sessions2 = stdout.split("\n").map((line) => line.replace(/\r$/, "").trim()).filter(Boolean).map((line) => {
266
332
  const [name, windows, attached, activity] = line.split(" ");
267
333
  return {
268
334
  name,
@@ -271,10 +337,52 @@ async function listTmuxSessions() {
271
337
  activity: parseInt(activity, 10) || void 0
272
338
  };
273
339
  });
340
+ await enrichWithPanesAndClaude(sessions2);
341
+ return sessions2;
274
342
  } catch {
275
343
  return [];
276
344
  }
277
345
  }
346
+ async function killTmuxSession(name) {
347
+ const { file, args } = tmuxCmd(["kill-session", "-t", `=${name}`]);
348
+ try {
349
+ await execFileAsync2(file, args, { timeout: 6e3 });
350
+ return { ok: true };
351
+ } catch (err) {
352
+ const msg = (err?.stderr || err?.message || "tmux kill failed").toString().trim();
353
+ return { ok: false, error: msg };
354
+ }
355
+ }
356
+ async function enrichWithPanesAndClaude(sessions2) {
357
+ if (IS_WIN || sessions2.length === 0) return;
358
+ try {
359
+ const paneFmt = "#{session_name} #{pane_pid} #{pane_current_path} #{window_active}#{pane_active}";
360
+ const { file, args } = tmuxCmd(["list-panes", "-a", "-F", paneFmt]);
361
+ const { stdout } = await execFileAsync2(file, args, { timeout: 6e3 });
362
+ const panes = stdout.split("\n").map((line) => line.replace(/\r$/, "").trim()).filter(Boolean).map((line) => {
363
+ const [session, pid, path8, activeFlags] = line.split(" ");
364
+ return { session, pid: parseInt(pid, 10) || 0, path: path8 || "", active: activeFlags === "11" };
365
+ });
366
+ const byName = new Map(sessions2.map((s) => [s.name, s]));
367
+ for (const pane of panes) {
368
+ const s = byName.get(pane.session);
369
+ if (s && (!s.path || pane.active)) s.path = pane.path || s.path;
370
+ }
371
+ const live = getLiveClaudeSessions();
372
+ if (live.length === 0) return;
373
+ const parents = await getPidParents();
374
+ for (const cs of live) {
375
+ const target = parents.size > 0 ? panes.find((p) => p.pid > 0 && isDescendantOf(cs.pid, p.pid, parents)) : panes.find((p) => cs.cwd && p.path === cs.cwd);
376
+ if (!target) continue;
377
+ const s = byName.get(target.session);
378
+ if (!s) continue;
379
+ s.claudeTitle = cs.name;
380
+ s.claudeStatus = cs.status;
381
+ if (cs.cwd) s.path = cs.cwd;
382
+ }
383
+ } catch {
384
+ }
385
+ }
278
386
  function shq(v) {
279
387
  return `'${v.replace(/'/g, `'\\''`)}'`;
280
388
  }
@@ -291,12 +399,12 @@ function buildTmuxLaunch(name, launch, shell) {
291
399
  }
292
400
 
293
401
  // src/shell.ts
294
- var import_child_process2 = require("child_process");
402
+ var import_child_process3 = require("child_process");
295
403
  function detectShell(preference) {
296
404
  if (preference !== "auto") return preference;
297
405
  if (process.platform === "win32") {
298
406
  try {
299
- (0, import_child_process2.execSync)("where pwsh", { stdio: "ignore" });
407
+ (0, import_child_process3.execSync)("where pwsh", { stdio: "ignore" });
300
408
  return "pwsh.exe";
301
409
  } catch {
302
410
  return "powershell.exe";
@@ -306,32 +414,32 @@ function detectShell(preference) {
306
414
  }
307
415
 
308
416
  // src/heartbeat.ts
309
- var import_os3 = __toESM(require("os"));
310
- var import_path4 = __toESM(require("path"));
311
- var import_child_process3 = require("child_process");
417
+ var import_os4 = __toESM(require("os"));
418
+ var import_path5 = __toESM(require("path"));
419
+ var import_child_process4 = require("child_process");
312
420
 
313
421
  // src/pty-session.ts
314
422
  var pty = __toESM(require("node-pty"));
315
- var import_fs3 = require("fs");
316
- var import_path3 = require("path");
317
- var import_os2 = require("os");
423
+ var import_fs4 = require("fs");
424
+ var import_path4 = require("path");
425
+ var import_os3 = require("os");
318
426
  var THRESHOLD = 3;
319
- var INIT_DIR = (0, import_path3.join)((0, import_os2.tmpdir)(), "crc-shell-init");
320
- var BASH_INIT = (0, import_path3.join)(INIT_DIR, "bashrc");
321
- var ZSH_DIR = (0, import_path3.join)(INIT_DIR, "zsh");
322
- var ZSH_RC = (0, import_path3.join)(ZSH_DIR, ".zshrc");
323
- var ZSH_ENV = (0, import_path3.join)(ZSH_DIR, ".zshenv");
427
+ var INIT_DIR = (0, import_path4.join)((0, import_os3.tmpdir)(), "crc-shell-init");
428
+ var BASH_INIT = (0, import_path4.join)(INIT_DIR, "bashrc");
429
+ var ZSH_DIR = (0, import_path4.join)(INIT_DIR, "zsh");
430
+ var ZSH_RC = (0, import_path4.join)(ZSH_DIR, ".zshrc");
431
+ var ZSH_ENV = (0, import_path4.join)(ZSH_DIR, ".zshenv");
324
432
  var INIT_VERSION = "2";
325
433
  function ensureInitFiles() {
326
- const versionFile = (0, import_path3.join)(INIT_DIR, ".version");
327
- if ((0, import_fs3.existsSync)(versionFile)) {
434
+ const versionFile = (0, import_path4.join)(INIT_DIR, ".version");
435
+ if ((0, import_fs4.existsSync)(versionFile)) {
328
436
  try {
329
- if ((0, import_fs3.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
437
+ if ((0, import_fs4.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
330
438
  } catch {
331
439
  }
332
440
  }
333
- (0, import_fs3.mkdirSync)(ZSH_DIR, { recursive: true });
334
- (0, import_fs3.writeFileSync)(BASH_INIT, [
441
+ (0, import_fs4.mkdirSync)(ZSH_DIR, { recursive: true });
442
+ (0, import_fs4.writeFileSync)(BASH_INIT, [
335
443
  "[ -f ~/.bash_profile ] && source ~/.bash_profile",
336
444
  "[ -f ~/.bashrc ] && source ~/.bashrc",
337
445
  "__crc_s=$SECONDS",
@@ -342,11 +450,11 @@ function ensureInitFiles() {
342
450
  `PROMPT_COMMAND='[ "$__crc_armed" = 1 ] && [ \${__crc_elapsed:-0} -ge ${THRESHOLD} ] && printf "\\a"; __crc_armed=1; eval "$__crc_orig_pc"'`,
343
451
  ""
344
452
  ].join("\n"), "utf-8");
345
- (0, import_fs3.writeFileSync)(ZSH_ENV, [
453
+ (0, import_fs4.writeFileSync)(ZSH_ENV, [
346
454
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshenv" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshenv"',
347
455
  ""
348
456
  ].join("\n"), "utf-8");
349
- (0, import_fs3.writeFileSync)(ZSH_RC, [
457
+ (0, import_fs4.writeFileSync)(ZSH_RC, [
350
458
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshrc" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshrc"',
351
459
  "__crc_s=$SECONDS",
352
460
  "__crc_armed=0",
@@ -359,19 +467,19 @@ function ensureInitFiles() {
359
467
  "fi",
360
468
  ""
361
469
  ].join("\n"), "utf-8");
362
- (0, import_fs3.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
470
+ (0, import_fs4.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
363
471
  }
364
472
  function resolveCwd(cwd) {
365
- const candidates = process.platform === "win32" ? [cwd, (0, import_os2.homedir)(), process.env.USERPROFILE, process.env.HOME] : [cwd, (0, import_os2.homedir)(), process.env.HOME, process.env.USERPROFILE];
473
+ const candidates = process.platform === "win32" ? [cwd, (0, import_os3.homedir)(), process.env.USERPROFILE, process.env.HOME] : [cwd, (0, import_os3.homedir)(), process.env.HOME, process.env.USERPROFILE];
366
474
  for (const c of candidates) {
367
475
  if (c) {
368
476
  try {
369
- if ((0, import_fs3.existsSync)(c)) return c;
477
+ if ((0, import_fs4.existsSync)(c)) return c;
370
478
  } catch {
371
479
  }
372
480
  }
373
481
  }
374
- return (0, import_os2.homedir)();
482
+ return (0, import_os3.homedir)();
375
483
  }
376
484
  var PtySession = class {
377
485
  id;
@@ -415,7 +523,7 @@ var PtySession = class {
415
523
  name: "xterm-256color",
416
524
  cols,
417
525
  rows,
418
- cwd: (0, import_os2.homedir)(),
526
+ cwd: (0, import_os3.homedir)(),
419
527
  env
420
528
  });
421
529
  }
@@ -553,7 +661,7 @@ function reapDetachedSessions(maxIdleMs) {
553
661
  // src/heartbeat.ts
554
662
  var prevCpu = null;
555
663
  function getCpuUsage() {
556
- const cpus = import_os3.default.cpus();
664
+ const cpus = import_os4.default.cpus();
557
665
  let idle = 0;
558
666
  let total = 0;
559
667
  for (const cpu of cpus) {
@@ -573,7 +681,7 @@ function getCpuUsage() {
573
681
  function getRootPaths() {
574
682
  if (process.platform === "win32") {
575
683
  try {
576
- const output = (0, import_child_process3.execSync)("wmic logicaldisk get name", { encoding: "utf-8" });
684
+ const output = (0, import_child_process4.execSync)("wmic logicaldisk get name", { encoding: "utf-8" });
577
685
  return output.split("\n").map((l) => l.trim()).filter((l) => /^[A-Z]:$/.test(l)).map((l) => l + "\\");
578
686
  } catch {
579
687
  return ["C:\\"];
@@ -582,51 +690,51 @@ function getRootPaths() {
582
690
  return ["/"];
583
691
  }
584
692
  function buildHeartbeat(homeDir) {
585
- const totalMem = import_os3.default.totalmem();
586
- const freeMem = import_os3.default.freemem();
693
+ const totalMem = import_os4.default.totalmem();
694
+ const freeMem = import_os4.default.freemem();
587
695
  return {
588
- hostname: import_os3.default.hostname(),
696
+ hostname: import_os4.default.hostname(),
589
697
  platform: process.platform,
590
- arch: import_os3.default.arch(),
698
+ arch: import_os4.default.arch(),
591
699
  cpuUsage: getCpuUsage(),
592
700
  memoryUsage: Math.round((totalMem - freeMem) / totalMem * 100),
593
- uptime: Math.round(import_os3.default.uptime()),
701
+ uptime: Math.round(import_os4.default.uptime()),
594
702
  activeSessions: getActiveSessionCount(),
595
- pathSeparator: import_path4.default.sep,
596
- homeDirectory: homeDir || import_os3.default.homedir(),
703
+ pathSeparator: import_path5.default.sep,
704
+ homeDirectory: homeDir || import_os4.default.homedir(),
597
705
  rootPaths: getRootPaths(),
598
706
  capabilities: { terminal: true, fileTransfer: false }
599
707
  };
600
708
  }
601
709
 
602
710
  // src/file-explorer.ts
603
- var import_fs4 = __toESM(require("fs"));
604
- var import_path5 = __toESM(require("path"));
605
- var import_os4 = __toESM(require("os"));
606
- var SECRET_DIR = import_path5.default.join(import_os4.default.homedir(), ".crc-agent");
711
+ var import_fs5 = __toESM(require("fs"));
712
+ var import_path6 = __toESM(require("path"));
713
+ var import_os5 = __toESM(require("os"));
714
+ var SECRET_DIR = import_path6.default.join(import_os5.default.homedir(), ".crc-agent");
607
715
  function isInSecretDir(resolved) {
608
- const rel = import_path5.default.relative(SECRET_DIR, resolved);
609
- return rel === "" || !rel.startsWith(".." + import_path5.default.sep) && rel !== ".." && !import_path5.default.isAbsolute(rel);
716
+ const rel = import_path6.default.relative(SECRET_DIR, resolved);
717
+ return rel === "" || !rel.startsWith(".." + import_path6.default.sep) && rel !== ".." && !import_path6.default.isAbsolute(rel);
610
718
  }
611
719
  function listDirectory(dirPath) {
612
720
  try {
613
- const resolved = import_path5.default.resolve(dirPath);
721
+ const resolved = import_path6.default.resolve(dirPath);
614
722
  if (isInSecretDir(resolved)) {
615
723
  return { entries: [], error: "Access denied" };
616
724
  }
617
- if (!import_fs4.default.existsSync(resolved)) {
725
+ if (!import_fs5.default.existsSync(resolved)) {
618
726
  return { entries: [], error: "Path does not exist" };
619
727
  }
620
- const stat = import_fs4.default.statSync(resolved);
728
+ const stat = import_fs5.default.statSync(resolved);
621
729
  if (!stat.isDirectory()) {
622
730
  return { entries: [], error: "Not a directory" };
623
731
  }
624
- const dirents = import_fs4.default.readdirSync(resolved, { withFileTypes: true });
732
+ const dirents = import_fs5.default.readdirSync(resolved, { withFileTypes: true });
625
733
  const entries = [];
626
734
  for (const dirent of dirents) {
627
735
  try {
628
- const fullPath = import_path5.default.join(resolved, dirent.name);
629
- const s = import_fs4.default.statSync(fullPath);
736
+ const fullPath = import_path6.default.join(resolved, dirent.name);
737
+ const s = import_fs5.default.statSync(fullPath);
630
738
  entries.push({
631
739
  name: dirent.name,
632
740
  isDirectory: dirent.isDirectory(),
@@ -648,22 +756,22 @@ function listDirectory(dirPath) {
648
756
  }
649
757
  async function downloadFile(filePath, serverUrl, secret, agentId) {
650
758
  try {
651
- const resolved = import_path5.default.resolve(filePath);
759
+ const resolved = import_path6.default.resolve(filePath);
652
760
  if (isInSecretDir(resolved)) {
653
761
  return { error: "Access denied" };
654
762
  }
655
- if (!import_fs4.default.existsSync(resolved)) {
763
+ if (!import_fs5.default.existsSync(resolved)) {
656
764
  return { error: "File does not exist" };
657
765
  }
658
- const stat = import_fs4.default.statSync(resolved);
766
+ const stat = import_fs5.default.statSync(resolved);
659
767
  if (stat.isDirectory()) {
660
768
  return { error: "Cannot download a directory" };
661
769
  }
662
770
  if (stat.size > FILE_MAX_SIZE) {
663
771
  return { error: `File too large (max ${FILE_MAX_SIZE} bytes)` };
664
772
  }
665
- const fileName = import_path5.default.basename(resolved);
666
- const fileStream = import_fs4.default.createReadStream(resolved);
773
+ const fileName = import_path6.default.basename(resolved);
774
+ const fileStream = import_fs5.default.createReadStream(resolved);
667
775
  const baseUrl = serverUrl.replace("wss://", "https://").replace("ws://", "http://");
668
776
  const url = `${baseUrl}/api/files/receive`;
669
777
  const res = await fetch(url, {
@@ -694,14 +802,14 @@ async function downloadFile(filePath, serverUrl, secret, agentId) {
694
802
  }
695
803
 
696
804
  // src/vpn-manager.ts
697
- var import_child_process4 = require("child_process");
698
- var import_util2 = require("util");
699
- var import_fs5 = __toESM(require("fs"));
700
- var import_path6 = __toESM(require("path"));
701
- var import_os5 = __toESM(require("os"));
702
- var execAsync = (0, import_util2.promisify)(import_child_process4.exec);
805
+ var import_child_process5 = require("child_process");
806
+ var import_util3 = require("util");
807
+ var import_fs6 = __toESM(require("fs"));
808
+ var import_path7 = __toESM(require("path"));
809
+ var import_os6 = __toESM(require("os"));
810
+ var execAsync = (0, import_util3.promisify)(import_child_process5.exec);
703
811
  var isWindows = process.platform === "win32";
704
- var VPN_DIR = import_path6.default.join(import_os5.default.homedir(), ".crc-agent", "vpn");
812
+ var VPN_DIR = import_path7.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
705
813
  function shq2(value) {
706
814
  return value.replace(/'/g, "'\\''");
707
815
  }
@@ -720,8 +828,8 @@ async function runCmd(cmd, shell) {
720
828
  }
721
829
  function getConfigPath(profile) {
722
830
  const file = profile.configFile || `${profile.id}.conf`;
723
- if (import_path6.default.isAbsolute(file)) return file;
724
- return import_path6.default.join(VPN_DIR, file);
831
+ if (import_path7.default.isAbsolute(file)) return file;
832
+ return import_path7.default.join(VPN_DIR, file);
725
833
  }
726
834
  async function getScutilStatus(serviceName) {
727
835
  const { stdout } = await runCmd(`scutil --nc status '${shq2(serviceName)}'`);
@@ -837,7 +945,7 @@ async function connectWireGuard(profile) {
837
945
  const tunnelName = profile.tunnelName || profile.id;
838
946
  if (isWindows) {
839
947
  const confPath2 = getConfigPath(profile);
840
- if (!import_fs5.default.existsSync(confPath2)) {
948
+ if (!import_fs6.default.existsSync(confPath2)) {
841
949
  return `Config file not found: ${confPath2}`;
842
950
  }
843
951
  const script = `
@@ -863,7 +971,7 @@ async function connectWireGuard(profile) {
863
971
  async function connectOpenVpn(profile) {
864
972
  if (isWindows) {
865
973
  const confPath2 = getConfigPath(profile);
866
- if (!import_fs5.default.existsSync(confPath2)) {
974
+ if (!import_fs6.default.existsSync(confPath2)) {
867
975
  return `Config file not found: ${confPath2}`;
868
976
  }
869
977
  const connector = "C:\\Program Files\\OpenVPN Connect\\ovpnconnector.exe";
@@ -890,7 +998,7 @@ async function connectOpenVpn(profile) {
890
998
  return scutilStart(profile.serviceName);
891
999
  }
892
1000
  const confPath = getConfigPath(profile);
893
- if (!import_fs5.default.existsSync(confPath)) {
1001
+ if (!import_fs6.default.existsSync(confPath)) {
894
1002
  return `Config file not found: ${confPath}`;
895
1003
  }
896
1004
  const { stderr } = await runCmd(`sudo openvpn --config '${shq2(confPath)}' --daemon`);
@@ -899,7 +1007,7 @@ async function connectOpenVpn(profile) {
899
1007
  async function connectAzure(profile) {
900
1008
  if (isWindows) {
901
1009
  const confPath = getConfigPath(profile);
902
- if (!import_fs5.default.existsSync(confPath)) {
1010
+ if (!import_fs6.default.existsSync(confPath)) {
903
1011
  return `Config file not found: ${confPath}`;
904
1012
  }
905
1013
  await runCmd(`& 'AzureVpn.exe' -i '${psq(confPath)}' 2>&1`);
@@ -1021,9 +1129,9 @@ async function disconnectVpn(configs, profileId) {
1021
1129
  }
1022
1130
 
1023
1131
  // src/claude-sessions.ts
1024
- var import_fs6 = __toESM(require("fs"));
1025
- var import_path7 = __toESM(require("path"));
1026
- var import_os6 = __toESM(require("os"));
1132
+ var import_fs7 = __toESM(require("fs"));
1133
+ var import_path8 = __toESM(require("path"));
1134
+ var import_os7 = __toESM(require("os"));
1027
1135
  var import_readline = __toESM(require("readline"));
1028
1136
 
1029
1137
  // src/claude-path.ts
@@ -1050,10 +1158,10 @@ function findProjectDirs(allDirs, filterProjectPath) {
1050
1158
  }
1051
1159
 
1052
1160
  // src/claude-sessions.ts
1053
- var CLAUDE_PROJECTS_DIR = import_path7.default.join(import_os6.default.homedir(), ".claude", "projects");
1161
+ var CLAUDE_PROJECTS_DIR = import_path8.default.join(import_os7.default.homedir(), ".claude", "projects");
1054
1162
  async function parseSessionFile(filePath) {
1055
1163
  try {
1056
- const stream = import_fs6.default.createReadStream(filePath, { encoding: "utf-8" });
1164
+ const stream = import_fs7.default.createReadStream(filePath, { encoding: "utf-8" });
1057
1165
  const rl = import_readline.default.createInterface({ input: stream, crlfDelay: Infinity });
1058
1166
  let firstMessage = "";
1059
1167
  let lastTimestamp = "";
@@ -1101,17 +1209,17 @@ async function parseSessionFile(filePath) {
1101
1209
  }
1102
1210
  async function listClaudeSessions(filterProjectPath) {
1103
1211
  const sessions2 = [];
1104
- if (!import_fs6.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1212
+ if (!import_fs7.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1105
1213
  const isDirSafe = (d) => {
1106
1214
  try {
1107
- return import_fs6.default.statSync(import_path7.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1215
+ return import_fs7.default.statSync(import_path8.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1108
1216
  } catch {
1109
1217
  return false;
1110
1218
  }
1111
1219
  };
1112
1220
  let allEntries;
1113
1221
  try {
1114
- allEntries = import_fs6.default.readdirSync(CLAUDE_PROJECTS_DIR);
1222
+ allEntries = import_fs7.default.readdirSync(CLAUDE_PROJECTS_DIR);
1115
1223
  } catch {
1116
1224
  return sessions2;
1117
1225
  }
@@ -1124,16 +1232,16 @@ async function listClaudeSessions(filterProjectPath) {
1124
1232
  projectDirs = allEntries.filter((d) => isDirSafe(d) && !d.startsWith("-private-"));
1125
1233
  }
1126
1234
  for (const dirName of projectDirs) {
1127
- const dirPath = import_path7.default.join(CLAUDE_PROJECTS_DIR, dirName);
1235
+ const dirPath = import_path8.default.join(CLAUDE_PROJECTS_DIR, dirName);
1128
1236
  let files;
1129
1237
  try {
1130
- files = import_fs6.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1238
+ files = import_fs7.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1131
1239
  } catch {
1132
1240
  continue;
1133
1241
  }
1134
1242
  for (const file of files) {
1135
1243
  const sessionId = file.replace(".jsonl", "");
1136
- const parsed = await parseSessionFile(import_path7.default.join(dirPath, file));
1244
+ const parsed = await parseSessionFile(import_path8.default.join(dirPath, file));
1137
1245
  if (!parsed) continue;
1138
1246
  const projectPath = parsed.cwd || dirName.replace(/^-/, "/").replace(/-/g, "/");
1139
1247
  sessions2.push({
@@ -1153,21 +1261,21 @@ async function listClaudeSessions(filterProjectPath) {
1153
1261
  }
1154
1262
 
1155
1263
  // src/claude-conversation.ts
1156
- var import_fs7 = __toESM(require("fs"));
1157
- var import_path8 = __toESM(require("path"));
1158
- var import_os7 = __toESM(require("os"));
1264
+ var import_fs8 = __toESM(require("fs"));
1265
+ var import_path9 = __toESM(require("path"));
1266
+ var import_os8 = __toESM(require("os"));
1159
1267
  var import_readline2 = __toESM(require("readline"));
1160
- var CLAUDE_PROJECTS_DIR2 = import_path8.default.join(import_os7.default.homedir(), ".claude", "projects");
1268
+ var CLAUDE_PROJECTS_DIR2 = import_path9.default.join(import_os8.default.homedir(), ".claude", "projects");
1161
1269
  function findProjectDir(projectPath) {
1162
1270
  const encoded = encodeProjectPath(projectPath);
1163
- const dirPath = import_path8.default.join(CLAUDE_PROJECTS_DIR2, encoded);
1164
- if (import_fs7.default.existsSync(dirPath)) return dirPath;
1165
- if (!import_fs7.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1271
+ const dirPath = import_path9.default.join(CLAUDE_PROJECTS_DIR2, encoded);
1272
+ if (import_fs8.default.existsSync(dirPath)) return dirPath;
1273
+ if (!import_fs8.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1166
1274
  let allDirs;
1167
1275
  try {
1168
- allDirs = import_fs7.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1276
+ allDirs = import_fs8.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1169
1277
  try {
1170
- return import_fs7.default.statSync(import_path8.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1278
+ return import_fs8.default.statSync(import_path9.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1171
1279
  } catch {
1172
1280
  return false;
1173
1281
  }
@@ -1177,23 +1285,23 @@ function findProjectDir(projectPath) {
1177
1285
  }
1178
1286
  const matches = findProjectDirs(allDirs, projectPath);
1179
1287
  if (matches.length > 0) {
1180
- return import_path8.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1288
+ return import_path9.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1181
1289
  }
1182
1290
  return null;
1183
1291
  }
1184
1292
  function findSessionFileById(sessionId) {
1185
1293
  if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return null;
1186
- if (!import_fs7.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1294
+ if (!import_fs8.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1187
1295
  let dirs;
1188
1296
  try {
1189
- dirs = import_fs7.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1297
+ dirs = import_fs8.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1190
1298
  } catch {
1191
1299
  return null;
1192
1300
  }
1193
1301
  for (const dir of dirs) {
1194
- const candidate = import_path8.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1302
+ const candidate = import_path9.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1195
1303
  try {
1196
- if (import_fs7.default.statSync(candidate).isFile()) return candidate;
1304
+ if (import_fs8.default.statSync(candidate).isFile()) return candidate;
1197
1305
  } catch {
1198
1306
  }
1199
1307
  }
@@ -1204,20 +1312,20 @@ function findLatestSessionFile(projectPath) {
1204
1312
  if (!dirPath) return null;
1205
1313
  let entries;
1206
1314
  try {
1207
- entries = import_fs7.default.readdirSync(dirPath);
1315
+ entries = import_fs8.default.readdirSync(dirPath);
1208
1316
  } catch {
1209
1317
  return null;
1210
1318
  }
1211
1319
  const files = entries.filter((f) => f.endsWith(".jsonl") && !f.includes("/")).map((f) => {
1212
1320
  try {
1213
- return { name: f, mtime: import_fs7.default.statSync(import_path8.default.join(dirPath, f)).mtimeMs };
1321
+ return { name: f, mtime: import_fs8.default.statSync(import_path9.default.join(dirPath, f)).mtimeMs };
1214
1322
  } catch {
1215
1323
  return null;
1216
1324
  }
1217
1325
  }).filter((f) => f !== null).sort((a, b) => b.mtime - a.mtime);
1218
1326
  if (files.length === 0) return null;
1219
1327
  return {
1220
- filePath: import_path8.default.join(dirPath, files[0].name),
1328
+ filePath: import_path9.default.join(dirPath, files[0].name),
1221
1329
  sessionId: files[0].name.replace(".jsonl", "")
1222
1330
  };
1223
1331
  }
@@ -1227,8 +1335,8 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1227
1335
  if (specificSessionId) {
1228
1336
  sessionId = specificSessionId;
1229
1337
  const dirPath = findProjectDir(projectPath);
1230
- const direct = dirPath ? import_path8.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1231
- if (direct && import_fs7.default.existsSync(direct)) {
1338
+ const direct = dirPath ? import_path9.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1339
+ if (direct && import_fs8.default.existsSync(direct)) {
1232
1340
  filePath = direct;
1233
1341
  } else {
1234
1342
  const byId = findSessionFileById(specificSessionId);
@@ -1242,7 +1350,7 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1242
1350
  sessionId = found.sessionId;
1243
1351
  }
1244
1352
  try {
1245
- const stream = import_fs7.default.createReadStream(filePath, { encoding: "utf-8" });
1353
+ const stream = import_fs8.default.createReadStream(filePath, { encoding: "utf-8" });
1246
1354
  const rl = import_readline2.default.createInterface({ input: stream, crlfDelay: Infinity });
1247
1355
  const messages = [];
1248
1356
  let lineNum = 0;
@@ -1416,6 +1524,13 @@ socket.on(TMUX_LIST, async (payload) => {
1416
1524
  socket.emit(TMUX_LIST_RESULT, { requestId, sessions: [], error: err?.message || "tmux list failed" });
1417
1525
  }
1418
1526
  });
1527
+ socket.on(TMUX_KILL, async (payload) => {
1528
+ const { requestId, name } = payload;
1529
+ if (!requestId || !name) return;
1530
+ const result = await killTmuxSession(name);
1531
+ logger.info({ name, ok: result.ok, error: result.error }, "tmux kill requested");
1532
+ socket.emit(TMUX_KILL_RESULT, { requestId, name, ok: result.ok, error: result.error });
1533
+ });
1419
1534
  socket.on(TERMINAL_INPUT, (payload) => {
1420
1535
  writeToSession(payload.sessionId, payload.data);
1421
1536
  });
@@ -1484,8 +1599,8 @@ socket.on(AGENT_EXEC, async (payload) => {
1484
1599
  const { requestId, command, cwd } = payload;
1485
1600
  if (!requestId) return;
1486
1601
  const { exec: exec2 } = await import("child_process");
1487
- const { promisify: promisify3 } = await import("util");
1488
- const execAsync2 = promisify3(exec2);
1602
+ const { promisify: promisify4 } = await import("util");
1603
+ const execAsync2 = promisify4(exec2);
1489
1604
  try {
1490
1605
  const { stdout, stderr } = await execAsync2(command, { cwd, timeout: 3e4 });
1491
1606
  socket.emit(AGENT_EXEC_RESULT, { requestId, stdout: stdout || "", stderr: stderr || "" });