cli-remote-agent 1.0.5 → 1.0.7

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/cli.js CHANGED
@@ -427,6 +427,51 @@ var init_local_control = __esm({
427
427
  }
428
428
  });
429
429
 
430
+ // src/pty-helper-fix.ts
431
+ function ensurePtyHelperExecutable() {
432
+ if (process.platform === "win32") return;
433
+ try {
434
+ const nodeRequire = (0, import_node_module.createRequire)(__filename);
435
+ const ptyMain = nodeRequire.resolve("node-pty");
436
+ const ptyDir = import_path3.default.dirname(import_path3.default.dirname(ptyMain));
437
+ const candidates = [import_path3.default.join(ptyDir, "build", "Release", "spawn-helper")];
438
+ const prebuilds = import_path3.default.join(ptyDir, "prebuilds");
439
+ try {
440
+ for (const d of import_fs4.default.readdirSync(prebuilds)) {
441
+ candidates.push(import_path3.default.join(prebuilds, d, "spawn-helper"));
442
+ }
443
+ } catch {
444
+ }
445
+ for (const file of candidates) {
446
+ try {
447
+ const mode = import_fs4.default.statSync(file).mode;
448
+ if (!(mode & 73)) {
449
+ import_fs4.default.chmodSync(file, mode | 493);
450
+ logger.info({ file }, "Restored execute bit on node-pty spawn-helper");
451
+ }
452
+ } catch {
453
+ }
454
+ }
455
+ if (process.platform === "darwin") {
456
+ (0, import_node_child_process.execFile)("xattr", ["-dr", "com.apple.quarantine", ptyDir], () => {
457
+ });
458
+ }
459
+ } catch (err) {
460
+ logger.warn({ error: err?.message }, "Could not verify node-pty spawn-helper permissions");
461
+ }
462
+ }
463
+ var import_node_module, import_node_child_process, import_fs4, import_path3;
464
+ var init_pty_helper_fix = __esm({
465
+ "src/pty-helper-fix.ts"() {
466
+ "use strict";
467
+ import_node_module = require("module");
468
+ import_node_child_process = require("child_process");
469
+ import_fs4 = __toESM(require("fs"));
470
+ import_path3 = __toESM(require("path"));
471
+ init_logger();
472
+ }
473
+ });
474
+
430
475
  // src/claude-live.ts
431
476
  function isAlive(pid) {
432
477
  try {
@@ -439,14 +484,14 @@ function isAlive(pid) {
439
484
  function getLiveClaudeSessions() {
440
485
  let files;
441
486
  try {
442
- files = import_fs4.default.readdirSync(LIVE_SESSIONS_DIR).filter((f) => f.endsWith(".json"));
487
+ files = import_fs5.default.readdirSync(LIVE_SESSIONS_DIR).filter((f) => f.endsWith(".json"));
443
488
  } catch {
444
489
  return [];
445
490
  }
446
491
  const out = [];
447
492
  for (const f of files) {
448
493
  try {
449
- const obj = JSON.parse(import_fs4.default.readFileSync(import_path3.default.join(LIVE_SESSIONS_DIR, f), "utf-8"));
494
+ const obj = JSON.parse(import_fs5.default.readFileSync(import_path4.default.join(LIVE_SESSIONS_DIR, f), "utf-8"));
450
495
  if (typeof obj?.pid !== "number" || !isAlive(obj.pid)) continue;
451
496
  out.push({
452
497
  pid: obj.pid,
@@ -481,23 +526,42 @@ function isDescendantOf(pid, ancestor, parents) {
481
526
  }
482
527
  return false;
483
528
  }
484
- var import_fs4, import_path3, import_os2, import_child_process, import_util, execFileAsync, LIVE_SESSIONS_DIR;
529
+ var import_fs5, import_path4, import_os2, import_child_process, import_util, execFileAsync, LIVE_SESSIONS_DIR;
485
530
  var init_claude_live = __esm({
486
531
  "src/claude-live.ts"() {
487
532
  "use strict";
488
- import_fs4 = __toESM(require("fs"));
489
- import_path3 = __toESM(require("path"));
533
+ import_fs5 = __toESM(require("fs"));
534
+ import_path4 = __toESM(require("path"));
490
535
  import_os2 = __toESM(require("os"));
491
536
  import_child_process = require("child_process");
492
537
  import_util = require("util");
493
538
  execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
494
- LIVE_SESSIONS_DIR = import_path3.default.join(import_os2.default.homedir(), ".claude", "sessions");
539
+ LIVE_SESSIONS_DIR = import_path4.default.join(import_os2.default.homedir(), ".claude", "sessions");
495
540
  }
496
541
  });
497
542
 
498
543
  // src/tmux.ts
544
+ function resolveTmuxPath() {
545
+ if (cachedTmuxPath !== void 0) return cachedTmuxPath;
546
+ const dirs = (process.env.PATH || "").split(import_path5.default.delimiter).filter(Boolean);
547
+ for (const extra of ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/opt/local/bin"]) {
548
+ if (!dirs.includes(extra)) dirs.push(extra);
549
+ }
550
+ for (const dir of dirs) {
551
+ const candidate = import_path5.default.join(dir, "tmux");
552
+ try {
553
+ import_fs6.default.accessSync(candidate, import_fs6.default.constants.X_OK);
554
+ cachedTmuxPath = candidate;
555
+ return candidate;
556
+ } catch {
557
+ }
558
+ }
559
+ cachedTmuxPath = null;
560
+ return null;
561
+ }
499
562
  function tmuxCmd(args) {
500
- return IS_WIN ? { file: "wsl.exe", args: ["tmux", ...args] } : { file: "tmux", args };
563
+ if (IS_WIN) return { file: "wsl.exe", args: ["tmux", ...args] };
564
+ return { file: resolveTmuxPath() || "tmux", args };
501
565
  }
502
566
  async function listTmuxSessions() {
503
567
  const fmt = "#{session_name} #{session_windows} #{session_attached} #{session_activity}";
@@ -536,8 +600,8 @@ async function enrichWithPanesAndClaude(sessions2) {
536
600
  const { file, args } = tmuxCmd(["list-panes", "-a", "-F", paneFmt]);
537
601
  const { stdout } = await execFileAsync2(file, args, { timeout: 6e3 });
538
602
  const panes = stdout.split("\n").map((line) => line.replace(/\r$/, "").trim()).filter(Boolean).map((line) => {
539
- const [session, pid, path8, activeFlags] = line.split(" ");
540
- return { session, pid: parseInt(pid, 10) || 0, path: path8 || "", active: activeFlags === "11" };
603
+ const [session, pid, path10, activeFlags] = line.split(" ");
604
+ return { session, pid: parseInt(pid, 10) || 0, path: path10 || "", active: activeFlags === "11" };
541
605
  });
542
606
  const byName = new Map(sessions2.map((s) => [s.name, s]));
543
607
  for (const pane of panes) {
@@ -559,26 +623,31 @@ async function enrichWithPanesAndClaude(sessions2) {
559
623
  } catch {
560
624
  }
561
625
  }
562
- function shq(v) {
563
- return `'${v.replace(/'/g, `'\\''`)}'`;
564
- }
565
- function buildTmuxLaunch(name, launch, shell) {
626
+ function buildTmuxLaunch(name, launch) {
566
627
  const trimmed = launch && launch.trim() ? launch.trim() : void 0;
567
628
  if (IS_WIN) {
568
- const args = ["tmux", "new-session", "-A", "-s", name];
569
- if (trimmed) args.push(trimmed);
570
- return { file: "wsl.exe", args };
571
- }
572
- let cmd2 = `tmux set-option -g aggressive-resize on 2>/dev/null; exec tmux new-session -A -s ${shq(name)}`;
573
- if (trimmed) cmd2 += ` ${shq(trimmed)}`;
574
- return { file: shell, args: ["-l", "-c", cmd2] };
629
+ const args2 = ["tmux", "new-session", "-A", "-s", name];
630
+ if (trimmed) args2.push(trimmed);
631
+ return { file: "wsl.exe", args: args2 };
632
+ }
633
+ const tmuxBin = resolveTmuxPath();
634
+ if (!tmuxBin) return null;
635
+ execFileAsync2(tmuxBin, ["set-option", "-g", "aggressive-resize", "on"], { timeout: 4e3 }).catch(
636
+ () => {
637
+ }
638
+ );
639
+ const args = ["new-session", "-A", "-s", name];
640
+ if (trimmed) args.push(trimmed);
641
+ return { file: tmuxBin, args };
575
642
  }
576
- var import_child_process2, import_util2, execFileAsync2, IS_WIN;
643
+ var import_child_process2, import_util2, import_fs6, import_path5, execFileAsync2, IS_WIN, cachedTmuxPath;
577
644
  var init_tmux = __esm({
578
645
  "src/tmux.ts"() {
579
646
  "use strict";
580
647
  import_child_process2 = require("child_process");
581
648
  import_util2 = require("util");
649
+ import_fs6 = __toESM(require("fs"));
650
+ import_path5 = __toESM(require("path"));
582
651
  init_claude_live();
583
652
  execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
584
653
  IS_WIN = process.platform === "win32";
@@ -608,15 +677,15 @@ var init_shell = __esm({
608
677
 
609
678
  // src/pty-session.ts
610
679
  function ensureInitFiles() {
611
- const versionFile = (0, import_path4.join)(INIT_DIR, ".version");
612
- if ((0, import_fs5.existsSync)(versionFile)) {
680
+ const versionFile = (0, import_path6.join)(INIT_DIR, ".version");
681
+ if ((0, import_fs7.existsSync)(versionFile)) {
613
682
  try {
614
- if ((0, import_fs5.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
683
+ if ((0, import_fs7.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
615
684
  } catch {
616
685
  }
617
686
  }
618
- (0, import_fs5.mkdirSync)(ZSH_DIR, { recursive: true });
619
- (0, import_fs5.writeFileSync)(BASH_INIT, [
687
+ (0, import_fs7.mkdirSync)(ZSH_DIR, { recursive: true });
688
+ (0, import_fs7.writeFileSync)(BASH_INIT, [
620
689
  "[ -f ~/.bash_profile ] && source ~/.bash_profile",
621
690
  "[ -f ~/.bashrc ] && source ~/.bashrc",
622
691
  "__crc_s=$SECONDS",
@@ -627,11 +696,11 @@ function ensureInitFiles() {
627
696
  `PROMPT_COMMAND='[ "$__crc_armed" = 1 ] && [ \${__crc_elapsed:-0} -ge ${THRESHOLD} ] && printf "\\a"; __crc_armed=1; eval "$__crc_orig_pc"'`,
628
697
  ""
629
698
  ].join("\n"), "utf-8");
630
- (0, import_fs5.writeFileSync)(ZSH_ENV, [
699
+ (0, import_fs7.writeFileSync)(ZSH_ENV, [
631
700
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshenv" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshenv"',
632
701
  ""
633
702
  ].join("\n"), "utf-8");
634
- (0, import_fs5.writeFileSync)(ZSH_RC, [
703
+ (0, import_fs7.writeFileSync)(ZSH_RC, [
635
704
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshrc" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshrc"',
636
705
  "__crc_s=$SECONDS",
637
706
  "__crc_armed=0",
@@ -644,36 +713,36 @@ function ensureInitFiles() {
644
713
  "fi",
645
714
  ""
646
715
  ].join("\n"), "utf-8");
647
- (0, import_fs5.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
716
+ (0, import_fs7.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
648
717
  }
649
718
  function resolveCwd(cwd) {
650
719
  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];
651
720
  for (const c of candidates) {
652
721
  if (c) {
653
722
  try {
654
- if ((0, import_fs5.existsSync)(c)) return c;
723
+ if ((0, import_fs7.existsSync)(c)) return c;
655
724
  } catch {
656
725
  }
657
726
  }
658
727
  }
659
728
  return (0, import_os3.homedir)();
660
729
  }
661
- var pty, import_fs5, import_path4, import_os3, THRESHOLD, INIT_DIR, BASH_INIT, ZSH_DIR, ZSH_RC, ZSH_ENV, INIT_VERSION, PtySession;
730
+ var pty, import_fs7, import_path6, import_os3, THRESHOLD, INIT_DIR, BASH_INIT, ZSH_DIR, ZSH_RC, ZSH_ENV, INIT_VERSION, PtySession;
662
731
  var init_pty_session = __esm({
663
732
  "src/pty-session.ts"() {
664
733
  "use strict";
665
734
  pty = __toESM(require("node-pty"));
666
- import_fs5 = require("fs");
667
- import_path4 = require("path");
735
+ import_fs7 = require("fs");
736
+ import_path6 = require("path");
668
737
  import_os3 = require("os");
669
738
  init_src();
670
739
  init_shell();
671
740
  THRESHOLD = 3;
672
- INIT_DIR = (0, import_path4.join)((0, import_os3.tmpdir)(), "crc-shell-init");
673
- BASH_INIT = (0, import_path4.join)(INIT_DIR, "bashrc");
674
- ZSH_DIR = (0, import_path4.join)(INIT_DIR, "zsh");
675
- ZSH_RC = (0, import_path4.join)(ZSH_DIR, ".zshrc");
676
- ZSH_ENV = (0, import_path4.join)(ZSH_DIR, ".zshenv");
741
+ INIT_DIR = (0, import_path6.join)((0, import_os3.tmpdir)(), "crc-shell-init");
742
+ BASH_INIT = (0, import_path6.join)(INIT_DIR, "bashrc");
743
+ ZSH_DIR = (0, import_path6.join)(INIT_DIR, "zsh");
744
+ ZSH_RC = (0, import_path6.join)(ZSH_DIR, ".zshrc");
745
+ ZSH_ENV = (0, import_path6.join)(ZSH_DIR, ".zshenv");
677
746
  INIT_VERSION = "2";
678
747
  PtySession = class {
679
748
  id;
@@ -903,18 +972,18 @@ function buildHeartbeat(homeDir) {
903
972
  memoryUsage: Math.round((totalMem - freeMem) / totalMem * 100),
904
973
  uptime: Math.round(import_os4.default.uptime()),
905
974
  activeSessions: getActiveSessionCount(),
906
- pathSeparator: import_path5.default.sep,
975
+ pathSeparator: import_path7.default.sep,
907
976
  homeDirectory: homeDir || import_os4.default.homedir(),
908
977
  rootPaths: getRootPaths(),
909
978
  capabilities: { terminal: true, fileTransfer: false }
910
979
  };
911
980
  }
912
- var import_os4, import_path5, import_child_process4, prevCpu;
981
+ var import_os4, import_path7, import_child_process4, prevCpu;
913
982
  var init_heartbeat = __esm({
914
983
  "src/heartbeat.ts"() {
915
984
  "use strict";
916
985
  import_os4 = __toESM(require("os"));
917
- import_path5 = __toESM(require("path"));
986
+ import_path7 = __toESM(require("path"));
918
987
  import_child_process4 = require("child_process");
919
988
  init_terminal_manager();
920
989
  prevCpu = null;
@@ -923,28 +992,28 @@ var init_heartbeat = __esm({
923
992
 
924
993
  // src/file-explorer.ts
925
994
  function isInSecretDir(resolved) {
926
- const rel = import_path6.default.relative(SECRET_DIR, resolved);
927
- return rel === "" || !rel.startsWith(".." + import_path6.default.sep) && rel !== ".." && !import_path6.default.isAbsolute(rel);
995
+ const rel = import_path8.default.relative(SECRET_DIR, resolved);
996
+ return rel === "" || !rel.startsWith(".." + import_path8.default.sep) && rel !== ".." && !import_path8.default.isAbsolute(rel);
928
997
  }
929
998
  function listDirectory(dirPath) {
930
999
  try {
931
- const resolved = import_path6.default.resolve(dirPath);
1000
+ const resolved = import_path8.default.resolve(dirPath);
932
1001
  if (isInSecretDir(resolved)) {
933
1002
  return { entries: [], error: "Access denied" };
934
1003
  }
935
- if (!import_fs6.default.existsSync(resolved)) {
1004
+ if (!import_fs8.default.existsSync(resolved)) {
936
1005
  return { entries: [], error: "Path does not exist" };
937
1006
  }
938
- const stat = import_fs6.default.statSync(resolved);
1007
+ const stat = import_fs8.default.statSync(resolved);
939
1008
  if (!stat.isDirectory()) {
940
1009
  return { entries: [], error: "Not a directory" };
941
1010
  }
942
- const dirents = import_fs6.default.readdirSync(resolved, { withFileTypes: true });
1011
+ const dirents = import_fs8.default.readdirSync(resolved, { withFileTypes: true });
943
1012
  const entries = [];
944
1013
  for (const dirent of dirents) {
945
1014
  try {
946
- const fullPath = import_path6.default.join(resolved, dirent.name);
947
- const s = import_fs6.default.statSync(fullPath);
1015
+ const fullPath = import_path8.default.join(resolved, dirent.name);
1016
+ const s = import_fs8.default.statSync(fullPath);
948
1017
  entries.push({
949
1018
  name: dirent.name,
950
1019
  isDirectory: dirent.isDirectory(),
@@ -966,22 +1035,22 @@ function listDirectory(dirPath) {
966
1035
  }
967
1036
  async function downloadFile(filePath, serverUrl, secret, agentId) {
968
1037
  try {
969
- const resolved = import_path6.default.resolve(filePath);
1038
+ const resolved = import_path8.default.resolve(filePath);
970
1039
  if (isInSecretDir(resolved)) {
971
1040
  return { error: "Access denied" };
972
1041
  }
973
- if (!import_fs6.default.existsSync(resolved)) {
1042
+ if (!import_fs8.default.existsSync(resolved)) {
974
1043
  return { error: "File does not exist" };
975
1044
  }
976
- const stat = import_fs6.default.statSync(resolved);
1045
+ const stat = import_fs8.default.statSync(resolved);
977
1046
  if (stat.isDirectory()) {
978
1047
  return { error: "Cannot download a directory" };
979
1048
  }
980
1049
  if (stat.size > FILE_MAX_SIZE) {
981
1050
  return { error: `File too large (max ${FILE_MAX_SIZE} bytes)` };
982
1051
  }
983
- const fileName = import_path6.default.basename(resolved);
984
- const fileStream = import_fs6.default.createReadStream(resolved);
1052
+ const fileName = import_path8.default.basename(resolved);
1053
+ const fileStream = import_fs8.default.createReadStream(resolved);
985
1054
  const baseUrl = serverUrl.replace("wss://", "https://").replace("ws://", "http://");
986
1055
  const url = `${baseUrl}/api/files/receive`;
987
1056
  const res = await fetch(url, {
@@ -1010,21 +1079,21 @@ async function downloadFile(filePath, serverUrl, secret, agentId) {
1010
1079
  return { error: err.message };
1011
1080
  }
1012
1081
  }
1013
- var import_fs6, import_path6, import_os5, SECRET_DIR;
1082
+ var import_fs8, import_path8, import_os5, SECRET_DIR;
1014
1083
  var init_file_explorer = __esm({
1015
1084
  "src/file-explorer.ts"() {
1016
1085
  "use strict";
1017
- import_fs6 = __toESM(require("fs"));
1018
- import_path6 = __toESM(require("path"));
1086
+ import_fs8 = __toESM(require("fs"));
1087
+ import_path8 = __toESM(require("path"));
1019
1088
  import_os5 = __toESM(require("os"));
1020
1089
  init_logger();
1021
1090
  init_src();
1022
- SECRET_DIR = import_path6.default.join(import_os5.default.homedir(), ".crc-agent");
1091
+ SECRET_DIR = import_path8.default.join(import_os5.default.homedir(), ".crc-agent");
1023
1092
  }
1024
1093
  });
1025
1094
 
1026
1095
  // src/vpn-manager.ts
1027
- function shq2(value) {
1096
+ function shq(value) {
1028
1097
  return value.replace(/'/g, "'\\''");
1029
1098
  }
1030
1099
  function psq(value) {
@@ -1042,11 +1111,11 @@ async function runCmd(cmd2, shell) {
1042
1111
  }
1043
1112
  function getConfigPath(profile) {
1044
1113
  const file = profile.configFile || `${profile.id}.conf`;
1045
- if (import_path7.default.isAbsolute(file)) return file;
1046
- return import_path7.default.join(VPN_DIR, file);
1114
+ if (import_path9.default.isAbsolute(file)) return file;
1115
+ return import_path9.default.join(VPN_DIR, file);
1047
1116
  }
1048
1117
  async function getScutilStatus(serviceName) {
1049
- const { stdout } = await runCmd(`scutil --nc status '${shq2(serviceName)}'`);
1118
+ const { stdout } = await runCmd(`scutil --nc status '${shq(serviceName)}'`);
1050
1119
  const firstLine = stdout.trim().split("\n")[0];
1051
1120
  if (firstLine === "Connected") return "connected";
1052
1121
  if (firstLine === "Connecting") return "connecting";
@@ -1054,11 +1123,11 @@ async function getScutilStatus(serviceName) {
1054
1123
  return "disconnected";
1055
1124
  }
1056
1125
  async function scutilStart(serviceName) {
1057
- const { stderr } = await runCmd(`scutil --nc start '${shq2(serviceName)}'`);
1126
+ const { stderr } = await runCmd(`scutil --nc start '${shq(serviceName)}'`);
1058
1127
  return stderr || null;
1059
1128
  }
1060
1129
  async function scutilStop(serviceName) {
1061
- const { stderr } = await runCmd(`scutil --nc stop '${shq2(serviceName)}'`);
1130
+ const { stderr } = await runCmd(`scutil --nc stop '${shq(serviceName)}'`);
1062
1131
  return stderr || null;
1063
1132
  }
1064
1133
  async function runOsascript(script) {
@@ -1107,7 +1176,7 @@ async function getWireGuardStatus(profile) {
1107
1176
  return getScutilStatus(profile.serviceName);
1108
1177
  }
1109
1178
  const tunnelName = profile.tunnelName || profile.id;
1110
- const { stdout } = await runCmd(`wg show '${shq2(tunnelName)}' 2>/dev/null`);
1179
+ const { stdout } = await runCmd(`wg show '${shq(tunnelName)}' 2>/dev/null`);
1111
1180
  return stdout.trim() ? "connected" : "disconnected";
1112
1181
  }
1113
1182
  async function getOpenVpnStatus(profile) {
@@ -1159,7 +1228,7 @@ async function connectWireGuard(profile) {
1159
1228
  const tunnelName = profile.tunnelName || profile.id;
1160
1229
  if (isWindows) {
1161
1230
  const confPath2 = getConfigPath(profile);
1162
- if (!import_fs7.default.existsSync(confPath2)) {
1231
+ if (!import_fs9.default.existsSync(confPath2)) {
1163
1232
  return `Config file not found: ${confPath2}`;
1164
1233
  }
1165
1234
  const script = `
@@ -1179,13 +1248,13 @@ async function connectWireGuard(profile) {
1179
1248
  return scutilStart(profile.serviceName);
1180
1249
  }
1181
1250
  const confPath = getConfigPath(profile);
1182
- const { stderr } = await runCmd(`sudo wg-quick up '${shq2(confPath)}'`);
1251
+ const { stderr } = await runCmd(`sudo wg-quick up '${shq(confPath)}'`);
1183
1252
  return stderr || null;
1184
1253
  }
1185
1254
  async function connectOpenVpn(profile) {
1186
1255
  if (isWindows) {
1187
1256
  const confPath2 = getConfigPath(profile);
1188
- if (!import_fs7.default.existsSync(confPath2)) {
1257
+ if (!import_fs9.default.existsSync(confPath2)) {
1189
1258
  return `Config file not found: ${confPath2}`;
1190
1259
  }
1191
1260
  const connector = "C:\\Program Files\\OpenVPN Connect\\ovpnconnector.exe";
@@ -1212,16 +1281,16 @@ async function connectOpenVpn(profile) {
1212
1281
  return scutilStart(profile.serviceName);
1213
1282
  }
1214
1283
  const confPath = getConfigPath(profile);
1215
- if (!import_fs7.default.existsSync(confPath)) {
1284
+ if (!import_fs9.default.existsSync(confPath)) {
1216
1285
  return `Config file not found: ${confPath}`;
1217
1286
  }
1218
- const { stderr } = await runCmd(`sudo openvpn --config '${shq2(confPath)}' --daemon`);
1287
+ const { stderr } = await runCmd(`sudo openvpn --config '${shq(confPath)}' --daemon`);
1219
1288
  return stderr || null;
1220
1289
  }
1221
1290
  async function connectAzure(profile) {
1222
1291
  if (isWindows) {
1223
1292
  const confPath = getConfigPath(profile);
1224
- if (!import_fs7.default.existsSync(confPath)) {
1293
+ if (!import_fs9.default.existsSync(confPath)) {
1225
1294
  return `Config file not found: ${confPath}`;
1226
1295
  }
1227
1296
  await runCmd(`& 'AzureVpn.exe' -i '${psq(confPath)}' 2>&1`);
@@ -1265,7 +1334,7 @@ async function disconnectWireGuard(profile) {
1265
1334
  return scutilStop(profile.serviceName);
1266
1335
  }
1267
1336
  const confPath = getConfigPath(profile);
1268
- const { stderr } = await runCmd(`sudo wg-quick down '${shq2(confPath)}'`);
1337
+ const { stderr } = await runCmd(`sudo wg-quick down '${shq(confPath)}'`);
1269
1338
  return stderr || null;
1270
1339
  }
1271
1340
  async function disconnectOpenVpn(profile) {
@@ -1341,19 +1410,19 @@ async function disconnectVpn(configs, profileId) {
1341
1410
  }
1342
1411
  return profiles;
1343
1412
  }
1344
- var import_child_process5, import_util3, import_fs7, import_path7, import_os6, execAsync, isWindows, VPN_DIR;
1413
+ var import_child_process5, import_util3, import_fs9, import_path9, import_os6, execAsync, isWindows, VPN_DIR;
1345
1414
  var init_vpn_manager = __esm({
1346
1415
  "src/vpn-manager.ts"() {
1347
1416
  "use strict";
1348
1417
  import_child_process5 = require("child_process");
1349
1418
  import_util3 = require("util");
1350
- import_fs7 = __toESM(require("fs"));
1351
- import_path7 = __toESM(require("path"));
1419
+ import_fs9 = __toESM(require("fs"));
1420
+ import_path9 = __toESM(require("path"));
1352
1421
  import_os6 = __toESM(require("os"));
1353
1422
  init_logger();
1354
1423
  execAsync = (0, import_util3.promisify)(import_child_process5.exec);
1355
1424
  isWindows = process.platform === "win32";
1356
- VPN_DIR = import_path7.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
1425
+ VPN_DIR = import_path9.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
1357
1426
  }
1358
1427
  });
1359
1428
 
@@ -1388,7 +1457,7 @@ var init_claude_path = __esm({
1388
1457
  // src/claude-sessions.ts
1389
1458
  async function parseSessionFile(filePath) {
1390
1459
  try {
1391
- const stream = import_fs8.default.createReadStream(filePath, { encoding: "utf-8" });
1460
+ const stream = import_fs10.default.createReadStream(filePath, { encoding: "utf-8" });
1392
1461
  const rl = import_readline2.default.createInterface({ input: stream, crlfDelay: Infinity });
1393
1462
  let firstMessage = "";
1394
1463
  let lastTimestamp = "";
@@ -1436,17 +1505,17 @@ async function parseSessionFile(filePath) {
1436
1505
  }
1437
1506
  async function listClaudeSessions(filterProjectPath) {
1438
1507
  const sessions2 = [];
1439
- if (!import_fs8.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1508
+ if (!import_fs10.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1440
1509
  const isDirSafe = (d) => {
1441
1510
  try {
1442
- return import_fs8.default.statSync(import_path8.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1511
+ return import_fs10.default.statSync(import_path10.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1443
1512
  } catch {
1444
1513
  return false;
1445
1514
  }
1446
1515
  };
1447
1516
  let allEntries;
1448
1517
  try {
1449
- allEntries = import_fs8.default.readdirSync(CLAUDE_PROJECTS_DIR);
1518
+ allEntries = import_fs10.default.readdirSync(CLAUDE_PROJECTS_DIR);
1450
1519
  } catch {
1451
1520
  return sessions2;
1452
1521
  }
@@ -1459,16 +1528,16 @@ async function listClaudeSessions(filterProjectPath) {
1459
1528
  projectDirs = allEntries.filter((d) => isDirSafe(d) && !d.startsWith("-private-"));
1460
1529
  }
1461
1530
  for (const dirName of projectDirs) {
1462
- const dirPath = import_path8.default.join(CLAUDE_PROJECTS_DIR, dirName);
1531
+ const dirPath = import_path10.default.join(CLAUDE_PROJECTS_DIR, dirName);
1463
1532
  let files;
1464
1533
  try {
1465
- files = import_fs8.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1534
+ files = import_fs10.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1466
1535
  } catch {
1467
1536
  continue;
1468
1537
  }
1469
1538
  for (const file of files) {
1470
1539
  const sessionId = file.replace(".jsonl", "");
1471
- const parsed = await parseSessionFile(import_path8.default.join(dirPath, file));
1540
+ const parsed = await parseSessionFile(import_path10.default.join(dirPath, file));
1472
1541
  if (!parsed) continue;
1473
1542
  const projectPath = parsed.cwd || dirName.replace(/^-/, "/").replace(/-/g, "/");
1474
1543
  sessions2.push({
@@ -1486,31 +1555,31 @@ async function listClaudeSessions(filterProjectPath) {
1486
1555
  sessions2.sort((a, b) => b.lastTimestamp > a.lastTimestamp ? 1 : -1);
1487
1556
  return sessions2;
1488
1557
  }
1489
- var import_fs8, import_path8, import_os7, import_readline2, CLAUDE_PROJECTS_DIR;
1558
+ var import_fs10, import_path10, import_os7, import_readline2, CLAUDE_PROJECTS_DIR;
1490
1559
  var init_claude_sessions = __esm({
1491
1560
  "src/claude-sessions.ts"() {
1492
1561
  "use strict";
1493
- import_fs8 = __toESM(require("fs"));
1494
- import_path8 = __toESM(require("path"));
1562
+ import_fs10 = __toESM(require("fs"));
1563
+ import_path10 = __toESM(require("path"));
1495
1564
  import_os7 = __toESM(require("os"));
1496
1565
  import_readline2 = __toESM(require("readline"));
1497
1566
  init_logger();
1498
1567
  init_claude_path();
1499
- CLAUDE_PROJECTS_DIR = import_path8.default.join(import_os7.default.homedir(), ".claude", "projects");
1568
+ CLAUDE_PROJECTS_DIR = import_path10.default.join(import_os7.default.homedir(), ".claude", "projects");
1500
1569
  }
1501
1570
  });
1502
1571
 
1503
1572
  // src/claude-conversation.ts
1504
1573
  function findProjectDir(projectPath) {
1505
1574
  const encoded = encodeProjectPath(projectPath);
1506
- const dirPath = import_path9.default.join(CLAUDE_PROJECTS_DIR2, encoded);
1507
- if (import_fs9.default.existsSync(dirPath)) return dirPath;
1508
- if (!import_fs9.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1575
+ const dirPath = import_path11.default.join(CLAUDE_PROJECTS_DIR2, encoded);
1576
+ if (import_fs11.default.existsSync(dirPath)) return dirPath;
1577
+ if (!import_fs11.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1509
1578
  let allDirs;
1510
1579
  try {
1511
- allDirs = import_fs9.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1580
+ allDirs = import_fs11.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1512
1581
  try {
1513
- return import_fs9.default.statSync(import_path9.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1582
+ return import_fs11.default.statSync(import_path11.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1514
1583
  } catch {
1515
1584
  return false;
1516
1585
  }
@@ -1520,23 +1589,23 @@ function findProjectDir(projectPath) {
1520
1589
  }
1521
1590
  const matches = findProjectDirs(allDirs, projectPath);
1522
1591
  if (matches.length > 0) {
1523
- return import_path9.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1592
+ return import_path11.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1524
1593
  }
1525
1594
  return null;
1526
1595
  }
1527
1596
  function findSessionFileById(sessionId) {
1528
1597
  if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return null;
1529
- if (!import_fs9.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1598
+ if (!import_fs11.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1530
1599
  let dirs;
1531
1600
  try {
1532
- dirs = import_fs9.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1601
+ dirs = import_fs11.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1533
1602
  } catch {
1534
1603
  return null;
1535
1604
  }
1536
1605
  for (const dir of dirs) {
1537
- const candidate = import_path9.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1606
+ const candidate = import_path11.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1538
1607
  try {
1539
- if (import_fs9.default.statSync(candidate).isFile()) return candidate;
1608
+ if (import_fs11.default.statSync(candidate).isFile()) return candidate;
1540
1609
  } catch {
1541
1610
  }
1542
1611
  }
@@ -1547,20 +1616,20 @@ function findLatestSessionFile(projectPath) {
1547
1616
  if (!dirPath) return null;
1548
1617
  let entries;
1549
1618
  try {
1550
- entries = import_fs9.default.readdirSync(dirPath);
1619
+ entries = import_fs11.default.readdirSync(dirPath);
1551
1620
  } catch {
1552
1621
  return null;
1553
1622
  }
1554
1623
  const files = entries.filter((f) => f.endsWith(".jsonl") && !f.includes("/")).map((f) => {
1555
1624
  try {
1556
- return { name: f, mtime: import_fs9.default.statSync(import_path9.default.join(dirPath, f)).mtimeMs };
1625
+ return { name: f, mtime: import_fs11.default.statSync(import_path11.default.join(dirPath, f)).mtimeMs };
1557
1626
  } catch {
1558
1627
  return null;
1559
1628
  }
1560
1629
  }).filter((f) => f !== null).sort((a, b) => b.mtime - a.mtime);
1561
1630
  if (files.length === 0) return null;
1562
1631
  return {
1563
- filePath: import_path9.default.join(dirPath, files[0].name),
1632
+ filePath: import_path11.default.join(dirPath, files[0].name),
1564
1633
  sessionId: files[0].name.replace(".jsonl", "")
1565
1634
  };
1566
1635
  }
@@ -1570,8 +1639,8 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1570
1639
  if (specificSessionId) {
1571
1640
  sessionId = specificSessionId;
1572
1641
  const dirPath = findProjectDir(projectPath);
1573
- const direct = dirPath ? import_path9.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1574
- if (direct && import_fs9.default.existsSync(direct)) {
1642
+ const direct = dirPath ? import_path11.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1643
+ if (direct && import_fs11.default.existsSync(direct)) {
1575
1644
  filePath = direct;
1576
1645
  } else {
1577
1646
  const byId = findSessionFileById(specificSessionId);
@@ -1585,7 +1654,7 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1585
1654
  sessionId = found.sessionId;
1586
1655
  }
1587
1656
  try {
1588
- const stream = import_fs9.default.createReadStream(filePath, { encoding: "utf-8" });
1657
+ const stream = import_fs11.default.createReadStream(filePath, { encoding: "utf-8" });
1589
1658
  const rl = import_readline3.default.createInterface({ input: stream, crlfDelay: Infinity });
1590
1659
  const messages = [];
1591
1660
  let lineNum = 0;
@@ -1666,17 +1735,17 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1666
1735
  return null;
1667
1736
  }
1668
1737
  }
1669
- var import_fs9, import_path9, import_os8, import_readline3, CLAUDE_PROJECTS_DIR2;
1738
+ var import_fs11, import_path11, import_os8, import_readline3, CLAUDE_PROJECTS_DIR2;
1670
1739
  var init_claude_conversation = __esm({
1671
1740
  "src/claude-conversation.ts"() {
1672
1741
  "use strict";
1673
- import_fs9 = __toESM(require("fs"));
1674
- import_path9 = __toESM(require("path"));
1742
+ import_fs11 = __toESM(require("fs"));
1743
+ import_path11 = __toESM(require("path"));
1675
1744
  import_os8 = __toESM(require("os"));
1676
1745
  import_readline3 = __toESM(require("readline"));
1677
1746
  init_logger();
1678
1747
  init_claude_path();
1679
- CLAUDE_PROJECTS_DIR2 = import_path9.default.join(import_os8.default.homedir(), ".claude", "projects");
1748
+ CLAUDE_PROJECTS_DIR2 = import_path11.default.join(import_os8.default.homedir(), ".claude", "projects");
1680
1749
  }
1681
1750
  });
1682
1751
 
@@ -1692,8 +1761,8 @@ var init_index = __esm({
1692
1761
  init_logger();
1693
1762
  init_claude_plugin_installer();
1694
1763
  init_local_control();
1764
+ init_pty_helper_fix();
1695
1765
  init_tmux();
1696
- init_shell();
1697
1766
  init_heartbeat();
1698
1767
  init_file_explorer();
1699
1768
  init_vpn_manager();
@@ -1706,6 +1775,7 @@ var init_index = __esm({
1706
1775
  }
1707
1776
  config = loadConfig();
1708
1777
  logger.info({ agentId: config.agentId, serverUrl: config.serverUrl }, "Starting agent");
1778
+ ensurePtyHelperExecutable();
1709
1779
  process.on("unhandledRejection", (reason) => {
1710
1780
  logger.error({ reason }, "Unhandled promise rejection (agent kept alive)");
1711
1781
  });
@@ -1765,7 +1835,18 @@ var init_index = __esm({
1765
1835
  socket.on(TERMINAL_OPEN, (payload) => {
1766
1836
  const { sessionId, cols, rows, tmux, launch } = payload;
1767
1837
  if (!sessionId) return;
1768
- const launchSpec = tmux ? buildTmuxLaunch(tmux, launch, detectShell(config.shell)) : void 0;
1838
+ let launchSpec;
1839
+ if (tmux) {
1840
+ const spec = buildTmuxLaunch(tmux, launch);
1841
+ if (!spec) {
1842
+ const msg = "\r\n\x1B[31mtmux is not installed on this machine.\x1B[0m\r\nInstall it to use tmux mirroring:\r\n macOS: brew install tmux\r\n Linux: sudo apt install tmux (or your package manager)\r\n\r\n";
1843
+ socket.emit(TERMINAL_OUTPUT, { sessionId, data: msg });
1844
+ socket.emit(TERMINAL_EXIT, { sessionId, exitCode: 1 });
1845
+ logger.warn({ sessionId, tmux }, "tmux launch requested but tmux not found");
1846
+ return;
1847
+ }
1848
+ launchSpec = spec;
1849
+ }
1769
1850
  createTerminalSession(
1770
1851
  sessionId,
1771
1852
  cols,