cli-remote-agent 1.0.6 → 1.0.8

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,105 @@ 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
+
475
+ // src/single-instance.ts
476
+ function isRunningAgent(pid) {
477
+ try {
478
+ process.kill(pid, 0);
479
+ } catch {
480
+ return false;
481
+ }
482
+ if (process.platform === "win32") return true;
483
+ try {
484
+ const out = (0, import_child_process.execFileSync)("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" });
485
+ return /crc-agent|cli-remote-agent|agent[\/\\](dist|src)[\/\\](cli|index)/.test(out);
486
+ } catch {
487
+ return true;
488
+ }
489
+ }
490
+ function acquireSingleInstanceLock() {
491
+ if (process.env.CRC_ALLOW_MULTIPLE === "1") return { ok: true };
492
+ try {
493
+ if (import_fs5.default.existsSync(LOCK_FILE)) {
494
+ const prev = parseInt(import_fs5.default.readFileSync(LOCK_FILE, "utf8").trim(), 10);
495
+ if (prev && prev !== process.pid && isRunningAgent(prev)) {
496
+ return { ok: false, pid: prev };
497
+ }
498
+ }
499
+ import_fs5.default.mkdirSync(CONFIG_DIR, { recursive: true });
500
+ import_fs5.default.writeFileSync(LOCK_FILE, String(process.pid));
501
+ const release = () => {
502
+ try {
503
+ if (parseInt(import_fs5.default.readFileSync(LOCK_FILE, "utf8").trim(), 10) === process.pid) {
504
+ import_fs5.default.unlinkSync(LOCK_FILE);
505
+ }
506
+ } catch {
507
+ }
508
+ };
509
+ process.on("exit", release);
510
+ return { ok: true };
511
+ } catch (err) {
512
+ logger.warn({ error: err?.message }, "Could not manage single-instance lock");
513
+ return { ok: true };
514
+ }
515
+ }
516
+ var import_fs5, import_path4, import_child_process, LOCK_FILE;
517
+ var init_single_instance = __esm({
518
+ "src/single-instance.ts"() {
519
+ "use strict";
520
+ import_fs5 = __toESM(require("fs"));
521
+ import_path4 = __toESM(require("path"));
522
+ import_child_process = require("child_process");
523
+ init_config();
524
+ init_logger();
525
+ LOCK_FILE = import_path4.default.join(CONFIG_DIR, "agent.lock");
526
+ }
527
+ });
528
+
430
529
  // src/claude-live.ts
431
530
  function isAlive(pid) {
432
531
  try {
@@ -439,14 +538,14 @@ function isAlive(pid) {
439
538
  function getLiveClaudeSessions() {
440
539
  let files;
441
540
  try {
442
- files = import_fs4.default.readdirSync(LIVE_SESSIONS_DIR).filter((f) => f.endsWith(".json"));
541
+ files = import_fs6.default.readdirSync(LIVE_SESSIONS_DIR).filter((f) => f.endsWith(".json"));
443
542
  } catch {
444
543
  return [];
445
544
  }
446
545
  const out = [];
447
546
  for (const f of files) {
448
547
  try {
449
- const obj = JSON.parse(import_fs4.default.readFileSync(import_path3.default.join(LIVE_SESSIONS_DIR, f), "utf-8"));
548
+ const obj = JSON.parse(import_fs6.default.readFileSync(import_path5.default.join(LIVE_SESSIONS_DIR, f), "utf-8"));
450
549
  if (typeof obj?.pid !== "number" || !isAlive(obj.pid)) continue;
451
550
  out.push({
452
551
  pid: obj.pid,
@@ -481,31 +580,31 @@ function isDescendantOf(pid, ancestor, parents) {
481
580
  }
482
581
  return false;
483
582
  }
484
- var import_fs4, import_path3, import_os2, import_child_process, import_util, execFileAsync, LIVE_SESSIONS_DIR;
583
+ var import_fs6, import_path5, import_os2, import_child_process2, import_util, execFileAsync, LIVE_SESSIONS_DIR;
485
584
  var init_claude_live = __esm({
486
585
  "src/claude-live.ts"() {
487
586
  "use strict";
488
- import_fs4 = __toESM(require("fs"));
489
- import_path3 = __toESM(require("path"));
587
+ import_fs6 = __toESM(require("fs"));
588
+ import_path5 = __toESM(require("path"));
490
589
  import_os2 = __toESM(require("os"));
491
- import_child_process = require("child_process");
590
+ import_child_process2 = require("child_process");
492
591
  import_util = require("util");
493
- execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
494
- LIVE_SESSIONS_DIR = import_path3.default.join(import_os2.default.homedir(), ".claude", "sessions");
592
+ execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
593
+ LIVE_SESSIONS_DIR = import_path5.default.join(import_os2.default.homedir(), ".claude", "sessions");
495
594
  }
496
595
  });
497
596
 
498
597
  // src/tmux.ts
499
598
  function resolveTmuxPath() {
500
599
  if (cachedTmuxPath !== void 0) return cachedTmuxPath;
501
- const dirs = (process.env.PATH || "").split(import_path4.default.delimiter).filter(Boolean);
600
+ const dirs = (process.env.PATH || "").split(import_path6.default.delimiter).filter(Boolean);
502
601
  for (const extra of ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/opt/local/bin"]) {
503
602
  if (!dirs.includes(extra)) dirs.push(extra);
504
603
  }
505
604
  for (const dir of dirs) {
506
- const candidate = import_path4.default.join(dir, "tmux");
605
+ const candidate = import_path6.default.join(dir, "tmux");
507
606
  try {
508
- import_fs5.default.accessSync(candidate, import_fs5.default.constants.X_OK);
607
+ import_fs7.default.accessSync(candidate, import_fs7.default.constants.X_OK);
509
608
  cachedTmuxPath = candidate;
510
609
  return candidate;
511
610
  } catch {
@@ -555,8 +654,8 @@ async function enrichWithPanesAndClaude(sessions2) {
555
654
  const { file, args } = tmuxCmd(["list-panes", "-a", "-F", paneFmt]);
556
655
  const { stdout } = await execFileAsync2(file, args, { timeout: 6e3 });
557
656
  const panes = stdout.split("\n").map((line) => line.replace(/\r$/, "").trim()).filter(Boolean).map((line) => {
558
- const [session, pid, path9, activeFlags] = line.split(" ");
559
- return { session, pid: parseInt(pid, 10) || 0, path: path9 || "", active: activeFlags === "11" };
657
+ const [session, pid, path11, activeFlags] = line.split(" ");
658
+ return { session, pid: parseInt(pid, 10) || 0, path: path11 || "", active: activeFlags === "11" };
560
659
  });
561
660
  const byName = new Map(sessions2.map((s) => [s.name, s]));
562
661
  for (const pane of panes) {
@@ -595,16 +694,16 @@ function buildTmuxLaunch(name, launch) {
595
694
  if (trimmed) args.push(trimmed);
596
695
  return { file: tmuxBin, args };
597
696
  }
598
- var import_child_process2, import_util2, import_fs5, import_path4, execFileAsync2, IS_WIN, cachedTmuxPath;
697
+ var import_child_process3, import_util2, import_fs7, import_path6, execFileAsync2, IS_WIN, cachedTmuxPath;
599
698
  var init_tmux = __esm({
600
699
  "src/tmux.ts"() {
601
700
  "use strict";
602
- import_child_process2 = require("child_process");
701
+ import_child_process3 = require("child_process");
603
702
  import_util2 = require("util");
604
- import_fs5 = __toESM(require("fs"));
605
- import_path4 = __toESM(require("path"));
703
+ import_fs7 = __toESM(require("fs"));
704
+ import_path6 = __toESM(require("path"));
606
705
  init_claude_live();
607
- execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
706
+ execFileAsync2 = (0, import_util2.promisify)(import_child_process3.execFile);
608
707
  IS_WIN = process.platform === "win32";
609
708
  }
610
709
  });
@@ -614,7 +713,7 @@ function detectShell(preference) {
614
713
  if (preference !== "auto") return preference;
615
714
  if (process.platform === "win32") {
616
715
  try {
617
- (0, import_child_process3.execSync)("where pwsh", { stdio: "ignore" });
716
+ (0, import_child_process4.execSync)("where pwsh", { stdio: "ignore" });
618
717
  return "pwsh.exe";
619
718
  } catch {
620
719
  return "powershell.exe";
@@ -622,25 +721,25 @@ function detectShell(preference) {
622
721
  }
623
722
  return process.env.SHELL || "/bin/bash";
624
723
  }
625
- var import_child_process3;
724
+ var import_child_process4;
626
725
  var init_shell = __esm({
627
726
  "src/shell.ts"() {
628
727
  "use strict";
629
- import_child_process3 = require("child_process");
728
+ import_child_process4 = require("child_process");
630
729
  }
631
730
  });
632
731
 
633
732
  // src/pty-session.ts
634
733
  function ensureInitFiles() {
635
- const versionFile = (0, import_path5.join)(INIT_DIR, ".version");
636
- if ((0, import_fs6.existsSync)(versionFile)) {
734
+ const versionFile = (0, import_path7.join)(INIT_DIR, ".version");
735
+ if ((0, import_fs8.existsSync)(versionFile)) {
637
736
  try {
638
- if ((0, import_fs6.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
737
+ if ((0, import_fs8.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
639
738
  } catch {
640
739
  }
641
740
  }
642
- (0, import_fs6.mkdirSync)(ZSH_DIR, { recursive: true });
643
- (0, import_fs6.writeFileSync)(BASH_INIT, [
741
+ (0, import_fs8.mkdirSync)(ZSH_DIR, { recursive: true });
742
+ (0, import_fs8.writeFileSync)(BASH_INIT, [
644
743
  "[ -f ~/.bash_profile ] && source ~/.bash_profile",
645
744
  "[ -f ~/.bashrc ] && source ~/.bashrc",
646
745
  "__crc_s=$SECONDS",
@@ -651,11 +750,11 @@ function ensureInitFiles() {
651
750
  `PROMPT_COMMAND='[ "$__crc_armed" = 1 ] && [ \${__crc_elapsed:-0} -ge ${THRESHOLD} ] && printf "\\a"; __crc_armed=1; eval "$__crc_orig_pc"'`,
652
751
  ""
653
752
  ].join("\n"), "utf-8");
654
- (0, import_fs6.writeFileSync)(ZSH_ENV, [
753
+ (0, import_fs8.writeFileSync)(ZSH_ENV, [
655
754
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshenv" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshenv"',
656
755
  ""
657
756
  ].join("\n"), "utf-8");
658
- (0, import_fs6.writeFileSync)(ZSH_RC, [
757
+ (0, import_fs8.writeFileSync)(ZSH_RC, [
659
758
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshrc" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshrc"',
660
759
  "__crc_s=$SECONDS",
661
760
  "__crc_armed=0",
@@ -668,36 +767,36 @@ function ensureInitFiles() {
668
767
  "fi",
669
768
  ""
670
769
  ].join("\n"), "utf-8");
671
- (0, import_fs6.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
770
+ (0, import_fs8.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
672
771
  }
673
772
  function resolveCwd(cwd) {
674
773
  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];
675
774
  for (const c of candidates) {
676
775
  if (c) {
677
776
  try {
678
- if ((0, import_fs6.existsSync)(c)) return c;
777
+ if ((0, import_fs8.existsSync)(c)) return c;
679
778
  } catch {
680
779
  }
681
780
  }
682
781
  }
683
782
  return (0, import_os3.homedir)();
684
783
  }
685
- var pty, import_fs6, import_path5, import_os3, THRESHOLD, INIT_DIR, BASH_INIT, ZSH_DIR, ZSH_RC, ZSH_ENV, INIT_VERSION, PtySession;
784
+ var pty, import_fs8, import_path7, import_os3, THRESHOLD, INIT_DIR, BASH_INIT, ZSH_DIR, ZSH_RC, ZSH_ENV, INIT_VERSION, PtySession;
686
785
  var init_pty_session = __esm({
687
786
  "src/pty-session.ts"() {
688
787
  "use strict";
689
788
  pty = __toESM(require("node-pty"));
690
- import_fs6 = require("fs");
691
- import_path5 = require("path");
789
+ import_fs8 = require("fs");
790
+ import_path7 = require("path");
692
791
  import_os3 = require("os");
693
792
  init_src();
694
793
  init_shell();
695
794
  THRESHOLD = 3;
696
- INIT_DIR = (0, import_path5.join)((0, import_os3.tmpdir)(), "crc-shell-init");
697
- BASH_INIT = (0, import_path5.join)(INIT_DIR, "bashrc");
698
- ZSH_DIR = (0, import_path5.join)(INIT_DIR, "zsh");
699
- ZSH_RC = (0, import_path5.join)(ZSH_DIR, ".zshrc");
700
- ZSH_ENV = (0, import_path5.join)(ZSH_DIR, ".zshenv");
795
+ INIT_DIR = (0, import_path7.join)((0, import_os3.tmpdir)(), "crc-shell-init");
796
+ BASH_INIT = (0, import_path7.join)(INIT_DIR, "bashrc");
797
+ ZSH_DIR = (0, import_path7.join)(INIT_DIR, "zsh");
798
+ ZSH_RC = (0, import_path7.join)(ZSH_DIR, ".zshrc");
799
+ ZSH_ENV = (0, import_path7.join)(ZSH_DIR, ".zshenv");
701
800
  INIT_VERSION = "2";
702
801
  PtySession = class {
703
802
  id;
@@ -908,7 +1007,7 @@ function getCpuUsage() {
908
1007
  function getRootPaths() {
909
1008
  if (process.platform === "win32") {
910
1009
  try {
911
- const output = (0, import_child_process4.execSync)("wmic logicaldisk get name", { encoding: "utf-8" });
1010
+ const output = (0, import_child_process5.execSync)("wmic logicaldisk get name", { encoding: "utf-8" });
912
1011
  return output.split("\n").map((l) => l.trim()).filter((l) => /^[A-Z]:$/.test(l)).map((l) => l + "\\");
913
1012
  } catch {
914
1013
  return ["C:\\"];
@@ -927,19 +1026,19 @@ function buildHeartbeat(homeDir) {
927
1026
  memoryUsage: Math.round((totalMem - freeMem) / totalMem * 100),
928
1027
  uptime: Math.round(import_os4.default.uptime()),
929
1028
  activeSessions: getActiveSessionCount(),
930
- pathSeparator: import_path6.default.sep,
1029
+ pathSeparator: import_path8.default.sep,
931
1030
  homeDirectory: homeDir || import_os4.default.homedir(),
932
1031
  rootPaths: getRootPaths(),
933
1032
  capabilities: { terminal: true, fileTransfer: false }
934
1033
  };
935
1034
  }
936
- var import_os4, import_path6, import_child_process4, prevCpu;
1035
+ var import_os4, import_path8, import_child_process5, prevCpu;
937
1036
  var init_heartbeat = __esm({
938
1037
  "src/heartbeat.ts"() {
939
1038
  "use strict";
940
1039
  import_os4 = __toESM(require("os"));
941
- import_path6 = __toESM(require("path"));
942
- import_child_process4 = require("child_process");
1040
+ import_path8 = __toESM(require("path"));
1041
+ import_child_process5 = require("child_process");
943
1042
  init_terminal_manager();
944
1043
  prevCpu = null;
945
1044
  }
@@ -947,28 +1046,28 @@ var init_heartbeat = __esm({
947
1046
 
948
1047
  // src/file-explorer.ts
949
1048
  function isInSecretDir(resolved) {
950
- const rel = import_path7.default.relative(SECRET_DIR, resolved);
951
- return rel === "" || !rel.startsWith(".." + import_path7.default.sep) && rel !== ".." && !import_path7.default.isAbsolute(rel);
1049
+ const rel = import_path9.default.relative(SECRET_DIR, resolved);
1050
+ return rel === "" || !rel.startsWith(".." + import_path9.default.sep) && rel !== ".." && !import_path9.default.isAbsolute(rel);
952
1051
  }
953
1052
  function listDirectory(dirPath) {
954
1053
  try {
955
- const resolved = import_path7.default.resolve(dirPath);
1054
+ const resolved = import_path9.default.resolve(dirPath);
956
1055
  if (isInSecretDir(resolved)) {
957
1056
  return { entries: [], error: "Access denied" };
958
1057
  }
959
- if (!import_fs7.default.existsSync(resolved)) {
1058
+ if (!import_fs9.default.existsSync(resolved)) {
960
1059
  return { entries: [], error: "Path does not exist" };
961
1060
  }
962
- const stat = import_fs7.default.statSync(resolved);
1061
+ const stat = import_fs9.default.statSync(resolved);
963
1062
  if (!stat.isDirectory()) {
964
1063
  return { entries: [], error: "Not a directory" };
965
1064
  }
966
- const dirents = import_fs7.default.readdirSync(resolved, { withFileTypes: true });
1065
+ const dirents = import_fs9.default.readdirSync(resolved, { withFileTypes: true });
967
1066
  const entries = [];
968
1067
  for (const dirent of dirents) {
969
1068
  try {
970
- const fullPath = import_path7.default.join(resolved, dirent.name);
971
- const s = import_fs7.default.statSync(fullPath);
1069
+ const fullPath = import_path9.default.join(resolved, dirent.name);
1070
+ const s = import_fs9.default.statSync(fullPath);
972
1071
  entries.push({
973
1072
  name: dirent.name,
974
1073
  isDirectory: dirent.isDirectory(),
@@ -990,22 +1089,22 @@ function listDirectory(dirPath) {
990
1089
  }
991
1090
  async function downloadFile(filePath, serverUrl, secret, agentId) {
992
1091
  try {
993
- const resolved = import_path7.default.resolve(filePath);
1092
+ const resolved = import_path9.default.resolve(filePath);
994
1093
  if (isInSecretDir(resolved)) {
995
1094
  return { error: "Access denied" };
996
1095
  }
997
- if (!import_fs7.default.existsSync(resolved)) {
1096
+ if (!import_fs9.default.existsSync(resolved)) {
998
1097
  return { error: "File does not exist" };
999
1098
  }
1000
- const stat = import_fs7.default.statSync(resolved);
1099
+ const stat = import_fs9.default.statSync(resolved);
1001
1100
  if (stat.isDirectory()) {
1002
1101
  return { error: "Cannot download a directory" };
1003
1102
  }
1004
1103
  if (stat.size > FILE_MAX_SIZE) {
1005
1104
  return { error: `File too large (max ${FILE_MAX_SIZE} bytes)` };
1006
1105
  }
1007
- const fileName = import_path7.default.basename(resolved);
1008
- const fileStream = import_fs7.default.createReadStream(resolved);
1106
+ const fileName = import_path9.default.basename(resolved);
1107
+ const fileStream = import_fs9.default.createReadStream(resolved);
1009
1108
  const baseUrl = serverUrl.replace("wss://", "https://").replace("ws://", "http://");
1010
1109
  const url = `${baseUrl}/api/files/receive`;
1011
1110
  const res = await fetch(url, {
@@ -1034,16 +1133,16 @@ async function downloadFile(filePath, serverUrl, secret, agentId) {
1034
1133
  return { error: err.message };
1035
1134
  }
1036
1135
  }
1037
- var import_fs7, import_path7, import_os5, SECRET_DIR;
1136
+ var import_fs9, import_path9, import_os5, SECRET_DIR;
1038
1137
  var init_file_explorer = __esm({
1039
1138
  "src/file-explorer.ts"() {
1040
1139
  "use strict";
1041
- import_fs7 = __toESM(require("fs"));
1042
- import_path7 = __toESM(require("path"));
1140
+ import_fs9 = __toESM(require("fs"));
1141
+ import_path9 = __toESM(require("path"));
1043
1142
  import_os5 = __toESM(require("os"));
1044
1143
  init_logger();
1045
1144
  init_src();
1046
- SECRET_DIR = import_path7.default.join(import_os5.default.homedir(), ".crc-agent");
1145
+ SECRET_DIR = import_path9.default.join(import_os5.default.homedir(), ".crc-agent");
1047
1146
  }
1048
1147
  });
1049
1148
 
@@ -1066,8 +1165,8 @@ async function runCmd(cmd2, shell) {
1066
1165
  }
1067
1166
  function getConfigPath(profile) {
1068
1167
  const file = profile.configFile || `${profile.id}.conf`;
1069
- if (import_path8.default.isAbsolute(file)) return file;
1070
- return import_path8.default.join(VPN_DIR, file);
1168
+ if (import_path10.default.isAbsolute(file)) return file;
1169
+ return import_path10.default.join(VPN_DIR, file);
1071
1170
  }
1072
1171
  async function getScutilStatus(serviceName) {
1073
1172
  const { stdout } = await runCmd(`scutil --nc status '${shq(serviceName)}'`);
@@ -1183,7 +1282,7 @@ async function connectWireGuard(profile) {
1183
1282
  const tunnelName = profile.tunnelName || profile.id;
1184
1283
  if (isWindows) {
1185
1284
  const confPath2 = getConfigPath(profile);
1186
- if (!import_fs8.default.existsSync(confPath2)) {
1285
+ if (!import_fs10.default.existsSync(confPath2)) {
1187
1286
  return `Config file not found: ${confPath2}`;
1188
1287
  }
1189
1288
  const script = `
@@ -1209,7 +1308,7 @@ async function connectWireGuard(profile) {
1209
1308
  async function connectOpenVpn(profile) {
1210
1309
  if (isWindows) {
1211
1310
  const confPath2 = getConfigPath(profile);
1212
- if (!import_fs8.default.existsSync(confPath2)) {
1311
+ if (!import_fs10.default.existsSync(confPath2)) {
1213
1312
  return `Config file not found: ${confPath2}`;
1214
1313
  }
1215
1314
  const connector = "C:\\Program Files\\OpenVPN Connect\\ovpnconnector.exe";
@@ -1236,7 +1335,7 @@ async function connectOpenVpn(profile) {
1236
1335
  return scutilStart(profile.serviceName);
1237
1336
  }
1238
1337
  const confPath = getConfigPath(profile);
1239
- if (!import_fs8.default.existsSync(confPath)) {
1338
+ if (!import_fs10.default.existsSync(confPath)) {
1240
1339
  return `Config file not found: ${confPath}`;
1241
1340
  }
1242
1341
  const { stderr } = await runCmd(`sudo openvpn --config '${shq(confPath)}' --daemon`);
@@ -1245,7 +1344,7 @@ async function connectOpenVpn(profile) {
1245
1344
  async function connectAzure(profile) {
1246
1345
  if (isWindows) {
1247
1346
  const confPath = getConfigPath(profile);
1248
- if (!import_fs8.default.existsSync(confPath)) {
1347
+ if (!import_fs10.default.existsSync(confPath)) {
1249
1348
  return `Config file not found: ${confPath}`;
1250
1349
  }
1251
1350
  await runCmd(`& 'AzureVpn.exe' -i '${psq(confPath)}' 2>&1`);
@@ -1365,19 +1464,19 @@ async function disconnectVpn(configs, profileId) {
1365
1464
  }
1366
1465
  return profiles;
1367
1466
  }
1368
- var import_child_process5, import_util3, import_fs8, import_path8, import_os6, execAsync, isWindows, VPN_DIR;
1467
+ var import_child_process6, import_util3, import_fs10, import_path10, import_os6, execAsync, isWindows, VPN_DIR;
1369
1468
  var init_vpn_manager = __esm({
1370
1469
  "src/vpn-manager.ts"() {
1371
1470
  "use strict";
1372
- import_child_process5 = require("child_process");
1471
+ import_child_process6 = require("child_process");
1373
1472
  import_util3 = require("util");
1374
- import_fs8 = __toESM(require("fs"));
1375
- import_path8 = __toESM(require("path"));
1473
+ import_fs10 = __toESM(require("fs"));
1474
+ import_path10 = __toESM(require("path"));
1376
1475
  import_os6 = __toESM(require("os"));
1377
1476
  init_logger();
1378
- execAsync = (0, import_util3.promisify)(import_child_process5.exec);
1477
+ execAsync = (0, import_util3.promisify)(import_child_process6.exec);
1379
1478
  isWindows = process.platform === "win32";
1380
- VPN_DIR = import_path8.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
1479
+ VPN_DIR = import_path10.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
1381
1480
  }
1382
1481
  });
1383
1482
 
@@ -1412,7 +1511,7 @@ var init_claude_path = __esm({
1412
1511
  // src/claude-sessions.ts
1413
1512
  async function parseSessionFile(filePath) {
1414
1513
  try {
1415
- const stream = import_fs9.default.createReadStream(filePath, { encoding: "utf-8" });
1514
+ const stream = import_fs11.default.createReadStream(filePath, { encoding: "utf-8" });
1416
1515
  const rl = import_readline2.default.createInterface({ input: stream, crlfDelay: Infinity });
1417
1516
  let firstMessage = "";
1418
1517
  let lastTimestamp = "";
@@ -1460,17 +1559,17 @@ async function parseSessionFile(filePath) {
1460
1559
  }
1461
1560
  async function listClaudeSessions(filterProjectPath) {
1462
1561
  const sessions2 = [];
1463
- if (!import_fs9.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1562
+ if (!import_fs11.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1464
1563
  const isDirSafe = (d) => {
1465
1564
  try {
1466
- return import_fs9.default.statSync(import_path9.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1565
+ return import_fs11.default.statSync(import_path11.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1467
1566
  } catch {
1468
1567
  return false;
1469
1568
  }
1470
1569
  };
1471
1570
  let allEntries;
1472
1571
  try {
1473
- allEntries = import_fs9.default.readdirSync(CLAUDE_PROJECTS_DIR);
1572
+ allEntries = import_fs11.default.readdirSync(CLAUDE_PROJECTS_DIR);
1474
1573
  } catch {
1475
1574
  return sessions2;
1476
1575
  }
@@ -1483,16 +1582,16 @@ async function listClaudeSessions(filterProjectPath) {
1483
1582
  projectDirs = allEntries.filter((d) => isDirSafe(d) && !d.startsWith("-private-"));
1484
1583
  }
1485
1584
  for (const dirName of projectDirs) {
1486
- const dirPath = import_path9.default.join(CLAUDE_PROJECTS_DIR, dirName);
1585
+ const dirPath = import_path11.default.join(CLAUDE_PROJECTS_DIR, dirName);
1487
1586
  let files;
1488
1587
  try {
1489
- files = import_fs9.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1588
+ files = import_fs11.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1490
1589
  } catch {
1491
1590
  continue;
1492
1591
  }
1493
1592
  for (const file of files) {
1494
1593
  const sessionId = file.replace(".jsonl", "");
1495
- const parsed = await parseSessionFile(import_path9.default.join(dirPath, file));
1594
+ const parsed = await parseSessionFile(import_path11.default.join(dirPath, file));
1496
1595
  if (!parsed) continue;
1497
1596
  const projectPath = parsed.cwd || dirName.replace(/^-/, "/").replace(/-/g, "/");
1498
1597
  sessions2.push({
@@ -1510,31 +1609,31 @@ async function listClaudeSessions(filterProjectPath) {
1510
1609
  sessions2.sort((a, b) => b.lastTimestamp > a.lastTimestamp ? 1 : -1);
1511
1610
  return sessions2;
1512
1611
  }
1513
- var import_fs9, import_path9, import_os7, import_readline2, CLAUDE_PROJECTS_DIR;
1612
+ var import_fs11, import_path11, import_os7, import_readline2, CLAUDE_PROJECTS_DIR;
1514
1613
  var init_claude_sessions = __esm({
1515
1614
  "src/claude-sessions.ts"() {
1516
1615
  "use strict";
1517
- import_fs9 = __toESM(require("fs"));
1518
- import_path9 = __toESM(require("path"));
1616
+ import_fs11 = __toESM(require("fs"));
1617
+ import_path11 = __toESM(require("path"));
1519
1618
  import_os7 = __toESM(require("os"));
1520
1619
  import_readline2 = __toESM(require("readline"));
1521
1620
  init_logger();
1522
1621
  init_claude_path();
1523
- CLAUDE_PROJECTS_DIR = import_path9.default.join(import_os7.default.homedir(), ".claude", "projects");
1622
+ CLAUDE_PROJECTS_DIR = import_path11.default.join(import_os7.default.homedir(), ".claude", "projects");
1524
1623
  }
1525
1624
  });
1526
1625
 
1527
1626
  // src/claude-conversation.ts
1528
1627
  function findProjectDir(projectPath) {
1529
1628
  const encoded = encodeProjectPath(projectPath);
1530
- const dirPath = import_path10.default.join(CLAUDE_PROJECTS_DIR2, encoded);
1531
- if (import_fs10.default.existsSync(dirPath)) return dirPath;
1532
- if (!import_fs10.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1629
+ const dirPath = import_path12.default.join(CLAUDE_PROJECTS_DIR2, encoded);
1630
+ if (import_fs12.default.existsSync(dirPath)) return dirPath;
1631
+ if (!import_fs12.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1533
1632
  let allDirs;
1534
1633
  try {
1535
- allDirs = import_fs10.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1634
+ allDirs = import_fs12.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1536
1635
  try {
1537
- return import_fs10.default.statSync(import_path10.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1636
+ return import_fs12.default.statSync(import_path12.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1538
1637
  } catch {
1539
1638
  return false;
1540
1639
  }
@@ -1544,23 +1643,23 @@ function findProjectDir(projectPath) {
1544
1643
  }
1545
1644
  const matches = findProjectDirs(allDirs, projectPath);
1546
1645
  if (matches.length > 0) {
1547
- return import_path10.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1646
+ return import_path12.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1548
1647
  }
1549
1648
  return null;
1550
1649
  }
1551
1650
  function findSessionFileById(sessionId) {
1552
1651
  if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return null;
1553
- if (!import_fs10.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1652
+ if (!import_fs12.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1554
1653
  let dirs;
1555
1654
  try {
1556
- dirs = import_fs10.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1655
+ dirs = import_fs12.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1557
1656
  } catch {
1558
1657
  return null;
1559
1658
  }
1560
1659
  for (const dir of dirs) {
1561
- const candidate = import_path10.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1660
+ const candidate = import_path12.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1562
1661
  try {
1563
- if (import_fs10.default.statSync(candidate).isFile()) return candidate;
1662
+ if (import_fs12.default.statSync(candidate).isFile()) return candidate;
1564
1663
  } catch {
1565
1664
  }
1566
1665
  }
@@ -1571,20 +1670,20 @@ function findLatestSessionFile(projectPath) {
1571
1670
  if (!dirPath) return null;
1572
1671
  let entries;
1573
1672
  try {
1574
- entries = import_fs10.default.readdirSync(dirPath);
1673
+ entries = import_fs12.default.readdirSync(dirPath);
1575
1674
  } catch {
1576
1675
  return null;
1577
1676
  }
1578
1677
  const files = entries.filter((f) => f.endsWith(".jsonl") && !f.includes("/")).map((f) => {
1579
1678
  try {
1580
- return { name: f, mtime: import_fs10.default.statSync(import_path10.default.join(dirPath, f)).mtimeMs };
1679
+ return { name: f, mtime: import_fs12.default.statSync(import_path12.default.join(dirPath, f)).mtimeMs };
1581
1680
  } catch {
1582
1681
  return null;
1583
1682
  }
1584
1683
  }).filter((f) => f !== null).sort((a, b) => b.mtime - a.mtime);
1585
1684
  if (files.length === 0) return null;
1586
1685
  return {
1587
- filePath: import_path10.default.join(dirPath, files[0].name),
1686
+ filePath: import_path12.default.join(dirPath, files[0].name),
1588
1687
  sessionId: files[0].name.replace(".jsonl", "")
1589
1688
  };
1590
1689
  }
@@ -1594,8 +1693,8 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1594
1693
  if (specificSessionId) {
1595
1694
  sessionId = specificSessionId;
1596
1695
  const dirPath = findProjectDir(projectPath);
1597
- const direct = dirPath ? import_path10.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1598
- if (direct && import_fs10.default.existsSync(direct)) {
1696
+ const direct = dirPath ? import_path12.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1697
+ if (direct && import_fs12.default.existsSync(direct)) {
1599
1698
  filePath = direct;
1600
1699
  } else {
1601
1700
  const byId = findSessionFileById(specificSessionId);
@@ -1609,7 +1708,7 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1609
1708
  sessionId = found.sessionId;
1610
1709
  }
1611
1710
  try {
1612
- const stream = import_fs10.default.createReadStream(filePath, { encoding: "utf-8" });
1711
+ const stream = import_fs12.default.createReadStream(filePath, { encoding: "utf-8" });
1613
1712
  const rl = import_readline3.default.createInterface({ input: stream, crlfDelay: Infinity });
1614
1713
  const messages = [];
1615
1714
  let lineNum = 0;
@@ -1690,23 +1789,23 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1690
1789
  return null;
1691
1790
  }
1692
1791
  }
1693
- var import_fs10, import_path10, import_os8, import_readline3, CLAUDE_PROJECTS_DIR2;
1792
+ var import_fs12, import_path12, import_os8, import_readline3, CLAUDE_PROJECTS_DIR2;
1694
1793
  var init_claude_conversation = __esm({
1695
1794
  "src/claude-conversation.ts"() {
1696
1795
  "use strict";
1697
- import_fs10 = __toESM(require("fs"));
1698
- import_path10 = __toESM(require("path"));
1796
+ import_fs12 = __toESM(require("fs"));
1797
+ import_path12 = __toESM(require("path"));
1699
1798
  import_os8 = __toESM(require("os"));
1700
1799
  import_readline3 = __toESM(require("readline"));
1701
1800
  init_logger();
1702
1801
  init_claude_path();
1703
- CLAUDE_PROJECTS_DIR2 = import_path10.default.join(import_os8.default.homedir(), ".claude", "projects");
1802
+ CLAUDE_PROJECTS_DIR2 = import_path12.default.join(import_os8.default.homedir(), ".claude", "projects");
1704
1803
  }
1705
1804
  });
1706
1805
 
1707
1806
  // src/index.ts
1708
1807
  var index_exports = {};
1709
- var import_socket, config, socket, SESSION_MAX_IDLE_MS, reaperInterval, vpnProfiles;
1808
+ var import_socket, config, lock, socket, SESSION_MAX_IDLE_MS, reaperInterval, vpnProfiles;
1710
1809
  var init_index = __esm({
1711
1810
  "src/index.ts"() {
1712
1811
  "use strict";
@@ -1716,6 +1815,8 @@ var init_index = __esm({
1716
1815
  init_logger();
1717
1816
  init_claude_plugin_installer();
1718
1817
  init_local_control();
1818
+ init_pty_helper_fix();
1819
+ init_single_instance();
1719
1820
  init_tmux();
1720
1821
  init_heartbeat();
1721
1822
  init_file_explorer();
@@ -1729,6 +1830,14 @@ var init_index = __esm({
1729
1830
  }
1730
1831
  config = loadConfig();
1731
1832
  logger.info({ agentId: config.agentId, serverUrl: config.serverUrl }, "Starting agent");
1833
+ lock = acquireSingleInstanceLock();
1834
+ if (!lock.ok) {
1835
+ console.error(
1836
+ `Another crc-agent is already running on this machine (PID ${lock.pid}). Stop it first, or set CRC_ALLOW_MULTIPLE=1 to override.`
1837
+ );
1838
+ process.exit(1);
1839
+ }
1840
+ ensurePtyHelperExecutable();
1732
1841
  process.on("unhandledRejection", (reason) => {
1733
1842
  logger.error({ reason }, "Unhandled promise rejection (agent kept alive)");
1734
1843
  });