cli-remote-agent 1.0.7 → 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
@@ -472,6 +472,60 @@ var init_pty_helper_fix = __esm({
472
472
  }
473
473
  });
474
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
+
475
529
  // src/claude-live.ts
476
530
  function isAlive(pid) {
477
531
  try {
@@ -484,14 +538,14 @@ function isAlive(pid) {
484
538
  function getLiveClaudeSessions() {
485
539
  let files;
486
540
  try {
487
- files = import_fs5.default.readdirSync(LIVE_SESSIONS_DIR).filter((f) => f.endsWith(".json"));
541
+ files = import_fs6.default.readdirSync(LIVE_SESSIONS_DIR).filter((f) => f.endsWith(".json"));
488
542
  } catch {
489
543
  return [];
490
544
  }
491
545
  const out = [];
492
546
  for (const f of files) {
493
547
  try {
494
- const obj = JSON.parse(import_fs5.default.readFileSync(import_path4.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"));
495
549
  if (typeof obj?.pid !== "number" || !isAlive(obj.pid)) continue;
496
550
  out.push({
497
551
  pid: obj.pid,
@@ -526,31 +580,31 @@ function isDescendantOf(pid, ancestor, parents) {
526
580
  }
527
581
  return false;
528
582
  }
529
- var import_fs5, import_path4, 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;
530
584
  var init_claude_live = __esm({
531
585
  "src/claude-live.ts"() {
532
586
  "use strict";
533
- import_fs5 = __toESM(require("fs"));
534
- import_path4 = __toESM(require("path"));
587
+ import_fs6 = __toESM(require("fs"));
588
+ import_path5 = __toESM(require("path"));
535
589
  import_os2 = __toESM(require("os"));
536
- import_child_process = require("child_process");
590
+ import_child_process2 = require("child_process");
537
591
  import_util = require("util");
538
- execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
539
- LIVE_SESSIONS_DIR = import_path4.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");
540
594
  }
541
595
  });
542
596
 
543
597
  // src/tmux.ts
544
598
  function resolveTmuxPath() {
545
599
  if (cachedTmuxPath !== void 0) return cachedTmuxPath;
546
- const dirs = (process.env.PATH || "").split(import_path5.default.delimiter).filter(Boolean);
600
+ const dirs = (process.env.PATH || "").split(import_path6.default.delimiter).filter(Boolean);
547
601
  for (const extra of ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/opt/local/bin"]) {
548
602
  if (!dirs.includes(extra)) dirs.push(extra);
549
603
  }
550
604
  for (const dir of dirs) {
551
- const candidate = import_path5.default.join(dir, "tmux");
605
+ const candidate = import_path6.default.join(dir, "tmux");
552
606
  try {
553
- import_fs6.default.accessSync(candidate, import_fs6.default.constants.X_OK);
607
+ import_fs7.default.accessSync(candidate, import_fs7.default.constants.X_OK);
554
608
  cachedTmuxPath = candidate;
555
609
  return candidate;
556
610
  } catch {
@@ -600,8 +654,8 @@ async function enrichWithPanesAndClaude(sessions2) {
600
654
  const { file, args } = tmuxCmd(["list-panes", "-a", "-F", paneFmt]);
601
655
  const { stdout } = await execFileAsync2(file, args, { timeout: 6e3 });
602
656
  const panes = stdout.split("\n").map((line) => line.replace(/\r$/, "").trim()).filter(Boolean).map((line) => {
603
- const [session, pid, path10, activeFlags] = line.split(" ");
604
- return { session, pid: parseInt(pid, 10) || 0, path: path10 || "", active: activeFlags === "11" };
657
+ const [session, pid, path11, activeFlags] = line.split(" ");
658
+ return { session, pid: parseInt(pid, 10) || 0, path: path11 || "", active: activeFlags === "11" };
605
659
  });
606
660
  const byName = new Map(sessions2.map((s) => [s.name, s]));
607
661
  for (const pane of panes) {
@@ -640,16 +694,16 @@ function buildTmuxLaunch(name, launch) {
640
694
  if (trimmed) args.push(trimmed);
641
695
  return { file: tmuxBin, args };
642
696
  }
643
- var import_child_process2, import_util2, import_fs6, import_path5, execFileAsync2, IS_WIN, cachedTmuxPath;
697
+ var import_child_process3, import_util2, import_fs7, import_path6, execFileAsync2, IS_WIN, cachedTmuxPath;
644
698
  var init_tmux = __esm({
645
699
  "src/tmux.ts"() {
646
700
  "use strict";
647
- import_child_process2 = require("child_process");
701
+ import_child_process3 = require("child_process");
648
702
  import_util2 = require("util");
649
- import_fs6 = __toESM(require("fs"));
650
- import_path5 = __toESM(require("path"));
703
+ import_fs7 = __toESM(require("fs"));
704
+ import_path6 = __toESM(require("path"));
651
705
  init_claude_live();
652
- execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
706
+ execFileAsync2 = (0, import_util2.promisify)(import_child_process3.execFile);
653
707
  IS_WIN = process.platform === "win32";
654
708
  }
655
709
  });
@@ -659,7 +713,7 @@ function detectShell(preference) {
659
713
  if (preference !== "auto") return preference;
660
714
  if (process.platform === "win32") {
661
715
  try {
662
- (0, import_child_process3.execSync)("where pwsh", { stdio: "ignore" });
716
+ (0, import_child_process4.execSync)("where pwsh", { stdio: "ignore" });
663
717
  return "pwsh.exe";
664
718
  } catch {
665
719
  return "powershell.exe";
@@ -667,25 +721,25 @@ function detectShell(preference) {
667
721
  }
668
722
  return process.env.SHELL || "/bin/bash";
669
723
  }
670
- var import_child_process3;
724
+ var import_child_process4;
671
725
  var init_shell = __esm({
672
726
  "src/shell.ts"() {
673
727
  "use strict";
674
- import_child_process3 = require("child_process");
728
+ import_child_process4 = require("child_process");
675
729
  }
676
730
  });
677
731
 
678
732
  // src/pty-session.ts
679
733
  function ensureInitFiles() {
680
- const versionFile = (0, import_path6.join)(INIT_DIR, ".version");
681
- if ((0, import_fs7.existsSync)(versionFile)) {
734
+ const versionFile = (0, import_path7.join)(INIT_DIR, ".version");
735
+ if ((0, import_fs8.existsSync)(versionFile)) {
682
736
  try {
683
- if ((0, import_fs7.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
737
+ if ((0, import_fs8.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
684
738
  } catch {
685
739
  }
686
740
  }
687
- (0, import_fs7.mkdirSync)(ZSH_DIR, { recursive: true });
688
- (0, import_fs7.writeFileSync)(BASH_INIT, [
741
+ (0, import_fs8.mkdirSync)(ZSH_DIR, { recursive: true });
742
+ (0, import_fs8.writeFileSync)(BASH_INIT, [
689
743
  "[ -f ~/.bash_profile ] && source ~/.bash_profile",
690
744
  "[ -f ~/.bashrc ] && source ~/.bashrc",
691
745
  "__crc_s=$SECONDS",
@@ -696,11 +750,11 @@ function ensureInitFiles() {
696
750
  `PROMPT_COMMAND='[ "$__crc_armed" = 1 ] && [ \${__crc_elapsed:-0} -ge ${THRESHOLD} ] && printf "\\a"; __crc_armed=1; eval "$__crc_orig_pc"'`,
697
751
  ""
698
752
  ].join("\n"), "utf-8");
699
- (0, import_fs7.writeFileSync)(ZSH_ENV, [
753
+ (0, import_fs8.writeFileSync)(ZSH_ENV, [
700
754
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshenv" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshenv"',
701
755
  ""
702
756
  ].join("\n"), "utf-8");
703
- (0, import_fs7.writeFileSync)(ZSH_RC, [
757
+ (0, import_fs8.writeFileSync)(ZSH_RC, [
704
758
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshrc" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshrc"',
705
759
  "__crc_s=$SECONDS",
706
760
  "__crc_armed=0",
@@ -713,36 +767,36 @@ function ensureInitFiles() {
713
767
  "fi",
714
768
  ""
715
769
  ].join("\n"), "utf-8");
716
- (0, import_fs7.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
770
+ (0, import_fs8.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
717
771
  }
718
772
  function resolveCwd(cwd) {
719
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];
720
774
  for (const c of candidates) {
721
775
  if (c) {
722
776
  try {
723
- if ((0, import_fs7.existsSync)(c)) return c;
777
+ if ((0, import_fs8.existsSync)(c)) return c;
724
778
  } catch {
725
779
  }
726
780
  }
727
781
  }
728
782
  return (0, import_os3.homedir)();
729
783
  }
730
- var pty, import_fs7, import_path6, 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;
731
785
  var init_pty_session = __esm({
732
786
  "src/pty-session.ts"() {
733
787
  "use strict";
734
788
  pty = __toESM(require("node-pty"));
735
- import_fs7 = require("fs");
736
- import_path6 = require("path");
789
+ import_fs8 = require("fs");
790
+ import_path7 = require("path");
737
791
  import_os3 = require("os");
738
792
  init_src();
739
793
  init_shell();
740
794
  THRESHOLD = 3;
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");
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");
746
800
  INIT_VERSION = "2";
747
801
  PtySession = class {
748
802
  id;
@@ -953,7 +1007,7 @@ function getCpuUsage() {
953
1007
  function getRootPaths() {
954
1008
  if (process.platform === "win32") {
955
1009
  try {
956
- 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" });
957
1011
  return output.split("\n").map((l) => l.trim()).filter((l) => /^[A-Z]:$/.test(l)).map((l) => l + "\\");
958
1012
  } catch {
959
1013
  return ["C:\\"];
@@ -972,19 +1026,19 @@ function buildHeartbeat(homeDir) {
972
1026
  memoryUsage: Math.round((totalMem - freeMem) / totalMem * 100),
973
1027
  uptime: Math.round(import_os4.default.uptime()),
974
1028
  activeSessions: getActiveSessionCount(),
975
- pathSeparator: import_path7.default.sep,
1029
+ pathSeparator: import_path8.default.sep,
976
1030
  homeDirectory: homeDir || import_os4.default.homedir(),
977
1031
  rootPaths: getRootPaths(),
978
1032
  capabilities: { terminal: true, fileTransfer: false }
979
1033
  };
980
1034
  }
981
- var import_os4, import_path7, import_child_process4, prevCpu;
1035
+ var import_os4, import_path8, import_child_process5, prevCpu;
982
1036
  var init_heartbeat = __esm({
983
1037
  "src/heartbeat.ts"() {
984
1038
  "use strict";
985
1039
  import_os4 = __toESM(require("os"));
986
- import_path7 = __toESM(require("path"));
987
- import_child_process4 = require("child_process");
1040
+ import_path8 = __toESM(require("path"));
1041
+ import_child_process5 = require("child_process");
988
1042
  init_terminal_manager();
989
1043
  prevCpu = null;
990
1044
  }
@@ -992,28 +1046,28 @@ var init_heartbeat = __esm({
992
1046
 
993
1047
  // src/file-explorer.ts
994
1048
  function isInSecretDir(resolved) {
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);
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);
997
1051
  }
998
1052
  function listDirectory(dirPath) {
999
1053
  try {
1000
- const resolved = import_path8.default.resolve(dirPath);
1054
+ const resolved = import_path9.default.resolve(dirPath);
1001
1055
  if (isInSecretDir(resolved)) {
1002
1056
  return { entries: [], error: "Access denied" };
1003
1057
  }
1004
- if (!import_fs8.default.existsSync(resolved)) {
1058
+ if (!import_fs9.default.existsSync(resolved)) {
1005
1059
  return { entries: [], error: "Path does not exist" };
1006
1060
  }
1007
- const stat = import_fs8.default.statSync(resolved);
1061
+ const stat = import_fs9.default.statSync(resolved);
1008
1062
  if (!stat.isDirectory()) {
1009
1063
  return { entries: [], error: "Not a directory" };
1010
1064
  }
1011
- const dirents = import_fs8.default.readdirSync(resolved, { withFileTypes: true });
1065
+ const dirents = import_fs9.default.readdirSync(resolved, { withFileTypes: true });
1012
1066
  const entries = [];
1013
1067
  for (const dirent of dirents) {
1014
1068
  try {
1015
- const fullPath = import_path8.default.join(resolved, dirent.name);
1016
- const s = import_fs8.default.statSync(fullPath);
1069
+ const fullPath = import_path9.default.join(resolved, dirent.name);
1070
+ const s = import_fs9.default.statSync(fullPath);
1017
1071
  entries.push({
1018
1072
  name: dirent.name,
1019
1073
  isDirectory: dirent.isDirectory(),
@@ -1035,22 +1089,22 @@ function listDirectory(dirPath) {
1035
1089
  }
1036
1090
  async function downloadFile(filePath, serverUrl, secret, agentId) {
1037
1091
  try {
1038
- const resolved = import_path8.default.resolve(filePath);
1092
+ const resolved = import_path9.default.resolve(filePath);
1039
1093
  if (isInSecretDir(resolved)) {
1040
1094
  return { error: "Access denied" };
1041
1095
  }
1042
- if (!import_fs8.default.existsSync(resolved)) {
1096
+ if (!import_fs9.default.existsSync(resolved)) {
1043
1097
  return { error: "File does not exist" };
1044
1098
  }
1045
- const stat = import_fs8.default.statSync(resolved);
1099
+ const stat = import_fs9.default.statSync(resolved);
1046
1100
  if (stat.isDirectory()) {
1047
1101
  return { error: "Cannot download a directory" };
1048
1102
  }
1049
1103
  if (stat.size > FILE_MAX_SIZE) {
1050
1104
  return { error: `File too large (max ${FILE_MAX_SIZE} bytes)` };
1051
1105
  }
1052
- const fileName = import_path8.default.basename(resolved);
1053
- const fileStream = import_fs8.default.createReadStream(resolved);
1106
+ const fileName = import_path9.default.basename(resolved);
1107
+ const fileStream = import_fs9.default.createReadStream(resolved);
1054
1108
  const baseUrl = serverUrl.replace("wss://", "https://").replace("ws://", "http://");
1055
1109
  const url = `${baseUrl}/api/files/receive`;
1056
1110
  const res = await fetch(url, {
@@ -1079,16 +1133,16 @@ async function downloadFile(filePath, serverUrl, secret, agentId) {
1079
1133
  return { error: err.message };
1080
1134
  }
1081
1135
  }
1082
- var import_fs8, import_path8, import_os5, SECRET_DIR;
1136
+ var import_fs9, import_path9, import_os5, SECRET_DIR;
1083
1137
  var init_file_explorer = __esm({
1084
1138
  "src/file-explorer.ts"() {
1085
1139
  "use strict";
1086
- import_fs8 = __toESM(require("fs"));
1087
- import_path8 = __toESM(require("path"));
1140
+ import_fs9 = __toESM(require("fs"));
1141
+ import_path9 = __toESM(require("path"));
1088
1142
  import_os5 = __toESM(require("os"));
1089
1143
  init_logger();
1090
1144
  init_src();
1091
- SECRET_DIR = import_path8.default.join(import_os5.default.homedir(), ".crc-agent");
1145
+ SECRET_DIR = import_path9.default.join(import_os5.default.homedir(), ".crc-agent");
1092
1146
  }
1093
1147
  });
1094
1148
 
@@ -1111,8 +1165,8 @@ async function runCmd(cmd2, shell) {
1111
1165
  }
1112
1166
  function getConfigPath(profile) {
1113
1167
  const file = profile.configFile || `${profile.id}.conf`;
1114
- if (import_path9.default.isAbsolute(file)) return file;
1115
- return import_path9.default.join(VPN_DIR, file);
1168
+ if (import_path10.default.isAbsolute(file)) return file;
1169
+ return import_path10.default.join(VPN_DIR, file);
1116
1170
  }
1117
1171
  async function getScutilStatus(serviceName) {
1118
1172
  const { stdout } = await runCmd(`scutil --nc status '${shq(serviceName)}'`);
@@ -1228,7 +1282,7 @@ async function connectWireGuard(profile) {
1228
1282
  const tunnelName = profile.tunnelName || profile.id;
1229
1283
  if (isWindows) {
1230
1284
  const confPath2 = getConfigPath(profile);
1231
- if (!import_fs9.default.existsSync(confPath2)) {
1285
+ if (!import_fs10.default.existsSync(confPath2)) {
1232
1286
  return `Config file not found: ${confPath2}`;
1233
1287
  }
1234
1288
  const script = `
@@ -1254,7 +1308,7 @@ async function connectWireGuard(profile) {
1254
1308
  async function connectOpenVpn(profile) {
1255
1309
  if (isWindows) {
1256
1310
  const confPath2 = getConfigPath(profile);
1257
- if (!import_fs9.default.existsSync(confPath2)) {
1311
+ if (!import_fs10.default.existsSync(confPath2)) {
1258
1312
  return `Config file not found: ${confPath2}`;
1259
1313
  }
1260
1314
  const connector = "C:\\Program Files\\OpenVPN Connect\\ovpnconnector.exe";
@@ -1281,7 +1335,7 @@ async function connectOpenVpn(profile) {
1281
1335
  return scutilStart(profile.serviceName);
1282
1336
  }
1283
1337
  const confPath = getConfigPath(profile);
1284
- if (!import_fs9.default.existsSync(confPath)) {
1338
+ if (!import_fs10.default.existsSync(confPath)) {
1285
1339
  return `Config file not found: ${confPath}`;
1286
1340
  }
1287
1341
  const { stderr } = await runCmd(`sudo openvpn --config '${shq(confPath)}' --daemon`);
@@ -1290,7 +1344,7 @@ async function connectOpenVpn(profile) {
1290
1344
  async function connectAzure(profile) {
1291
1345
  if (isWindows) {
1292
1346
  const confPath = getConfigPath(profile);
1293
- if (!import_fs9.default.existsSync(confPath)) {
1347
+ if (!import_fs10.default.existsSync(confPath)) {
1294
1348
  return `Config file not found: ${confPath}`;
1295
1349
  }
1296
1350
  await runCmd(`& 'AzureVpn.exe' -i '${psq(confPath)}' 2>&1`);
@@ -1410,19 +1464,19 @@ async function disconnectVpn(configs, profileId) {
1410
1464
  }
1411
1465
  return profiles;
1412
1466
  }
1413
- var import_child_process5, import_util3, import_fs9, import_path9, import_os6, execAsync, isWindows, VPN_DIR;
1467
+ var import_child_process6, import_util3, import_fs10, import_path10, import_os6, execAsync, isWindows, VPN_DIR;
1414
1468
  var init_vpn_manager = __esm({
1415
1469
  "src/vpn-manager.ts"() {
1416
1470
  "use strict";
1417
- import_child_process5 = require("child_process");
1471
+ import_child_process6 = require("child_process");
1418
1472
  import_util3 = require("util");
1419
- import_fs9 = __toESM(require("fs"));
1420
- import_path9 = __toESM(require("path"));
1473
+ import_fs10 = __toESM(require("fs"));
1474
+ import_path10 = __toESM(require("path"));
1421
1475
  import_os6 = __toESM(require("os"));
1422
1476
  init_logger();
1423
- execAsync = (0, import_util3.promisify)(import_child_process5.exec);
1477
+ execAsync = (0, import_util3.promisify)(import_child_process6.exec);
1424
1478
  isWindows = process.platform === "win32";
1425
- VPN_DIR = import_path9.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
1479
+ VPN_DIR = import_path10.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
1426
1480
  }
1427
1481
  });
1428
1482
 
@@ -1457,7 +1511,7 @@ var init_claude_path = __esm({
1457
1511
  // src/claude-sessions.ts
1458
1512
  async function parseSessionFile(filePath) {
1459
1513
  try {
1460
- const stream = import_fs10.default.createReadStream(filePath, { encoding: "utf-8" });
1514
+ const stream = import_fs11.default.createReadStream(filePath, { encoding: "utf-8" });
1461
1515
  const rl = import_readline2.default.createInterface({ input: stream, crlfDelay: Infinity });
1462
1516
  let firstMessage = "";
1463
1517
  let lastTimestamp = "";
@@ -1505,17 +1559,17 @@ async function parseSessionFile(filePath) {
1505
1559
  }
1506
1560
  async function listClaudeSessions(filterProjectPath) {
1507
1561
  const sessions2 = [];
1508
- if (!import_fs10.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1562
+ if (!import_fs11.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1509
1563
  const isDirSafe = (d) => {
1510
1564
  try {
1511
- return import_fs10.default.statSync(import_path10.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1565
+ return import_fs11.default.statSync(import_path11.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1512
1566
  } catch {
1513
1567
  return false;
1514
1568
  }
1515
1569
  };
1516
1570
  let allEntries;
1517
1571
  try {
1518
- allEntries = import_fs10.default.readdirSync(CLAUDE_PROJECTS_DIR);
1572
+ allEntries = import_fs11.default.readdirSync(CLAUDE_PROJECTS_DIR);
1519
1573
  } catch {
1520
1574
  return sessions2;
1521
1575
  }
@@ -1528,16 +1582,16 @@ async function listClaudeSessions(filterProjectPath) {
1528
1582
  projectDirs = allEntries.filter((d) => isDirSafe(d) && !d.startsWith("-private-"));
1529
1583
  }
1530
1584
  for (const dirName of projectDirs) {
1531
- const dirPath = import_path10.default.join(CLAUDE_PROJECTS_DIR, dirName);
1585
+ const dirPath = import_path11.default.join(CLAUDE_PROJECTS_DIR, dirName);
1532
1586
  let files;
1533
1587
  try {
1534
- files = import_fs10.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1588
+ files = import_fs11.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1535
1589
  } catch {
1536
1590
  continue;
1537
1591
  }
1538
1592
  for (const file of files) {
1539
1593
  const sessionId = file.replace(".jsonl", "");
1540
- const parsed = await parseSessionFile(import_path10.default.join(dirPath, file));
1594
+ const parsed = await parseSessionFile(import_path11.default.join(dirPath, file));
1541
1595
  if (!parsed) continue;
1542
1596
  const projectPath = parsed.cwd || dirName.replace(/^-/, "/").replace(/-/g, "/");
1543
1597
  sessions2.push({
@@ -1555,31 +1609,31 @@ async function listClaudeSessions(filterProjectPath) {
1555
1609
  sessions2.sort((a, b) => b.lastTimestamp > a.lastTimestamp ? 1 : -1);
1556
1610
  return sessions2;
1557
1611
  }
1558
- var import_fs10, import_path10, import_os7, import_readline2, CLAUDE_PROJECTS_DIR;
1612
+ var import_fs11, import_path11, import_os7, import_readline2, CLAUDE_PROJECTS_DIR;
1559
1613
  var init_claude_sessions = __esm({
1560
1614
  "src/claude-sessions.ts"() {
1561
1615
  "use strict";
1562
- import_fs10 = __toESM(require("fs"));
1563
- import_path10 = __toESM(require("path"));
1616
+ import_fs11 = __toESM(require("fs"));
1617
+ import_path11 = __toESM(require("path"));
1564
1618
  import_os7 = __toESM(require("os"));
1565
1619
  import_readline2 = __toESM(require("readline"));
1566
1620
  init_logger();
1567
1621
  init_claude_path();
1568
- CLAUDE_PROJECTS_DIR = import_path10.default.join(import_os7.default.homedir(), ".claude", "projects");
1622
+ CLAUDE_PROJECTS_DIR = import_path11.default.join(import_os7.default.homedir(), ".claude", "projects");
1569
1623
  }
1570
1624
  });
1571
1625
 
1572
1626
  // src/claude-conversation.ts
1573
1627
  function findProjectDir(projectPath) {
1574
1628
  const encoded = encodeProjectPath(projectPath);
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;
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;
1578
1632
  let allDirs;
1579
1633
  try {
1580
- allDirs = import_fs11.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1634
+ allDirs = import_fs12.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1581
1635
  try {
1582
- return import_fs11.default.statSync(import_path11.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1636
+ return import_fs12.default.statSync(import_path12.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1583
1637
  } catch {
1584
1638
  return false;
1585
1639
  }
@@ -1589,23 +1643,23 @@ function findProjectDir(projectPath) {
1589
1643
  }
1590
1644
  const matches = findProjectDirs(allDirs, projectPath);
1591
1645
  if (matches.length > 0) {
1592
- return import_path11.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1646
+ return import_path12.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1593
1647
  }
1594
1648
  return null;
1595
1649
  }
1596
1650
  function findSessionFileById(sessionId) {
1597
1651
  if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return null;
1598
- if (!import_fs11.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1652
+ if (!import_fs12.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1599
1653
  let dirs;
1600
1654
  try {
1601
- dirs = import_fs11.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1655
+ dirs = import_fs12.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1602
1656
  } catch {
1603
1657
  return null;
1604
1658
  }
1605
1659
  for (const dir of dirs) {
1606
- const candidate = import_path11.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1660
+ const candidate = import_path12.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1607
1661
  try {
1608
- if (import_fs11.default.statSync(candidate).isFile()) return candidate;
1662
+ if (import_fs12.default.statSync(candidate).isFile()) return candidate;
1609
1663
  } catch {
1610
1664
  }
1611
1665
  }
@@ -1616,20 +1670,20 @@ function findLatestSessionFile(projectPath) {
1616
1670
  if (!dirPath) return null;
1617
1671
  let entries;
1618
1672
  try {
1619
- entries = import_fs11.default.readdirSync(dirPath);
1673
+ entries = import_fs12.default.readdirSync(dirPath);
1620
1674
  } catch {
1621
1675
  return null;
1622
1676
  }
1623
1677
  const files = entries.filter((f) => f.endsWith(".jsonl") && !f.includes("/")).map((f) => {
1624
1678
  try {
1625
- return { name: f, mtime: import_fs11.default.statSync(import_path11.default.join(dirPath, f)).mtimeMs };
1679
+ return { name: f, mtime: import_fs12.default.statSync(import_path12.default.join(dirPath, f)).mtimeMs };
1626
1680
  } catch {
1627
1681
  return null;
1628
1682
  }
1629
1683
  }).filter((f) => f !== null).sort((a, b) => b.mtime - a.mtime);
1630
1684
  if (files.length === 0) return null;
1631
1685
  return {
1632
- filePath: import_path11.default.join(dirPath, files[0].name),
1686
+ filePath: import_path12.default.join(dirPath, files[0].name),
1633
1687
  sessionId: files[0].name.replace(".jsonl", "")
1634
1688
  };
1635
1689
  }
@@ -1639,8 +1693,8 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1639
1693
  if (specificSessionId) {
1640
1694
  sessionId = specificSessionId;
1641
1695
  const dirPath = findProjectDir(projectPath);
1642
- const direct = dirPath ? import_path11.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1643
- if (direct && import_fs11.default.existsSync(direct)) {
1696
+ const direct = dirPath ? import_path12.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1697
+ if (direct && import_fs12.default.existsSync(direct)) {
1644
1698
  filePath = direct;
1645
1699
  } else {
1646
1700
  const byId = findSessionFileById(specificSessionId);
@@ -1654,7 +1708,7 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1654
1708
  sessionId = found.sessionId;
1655
1709
  }
1656
1710
  try {
1657
- const stream = import_fs11.default.createReadStream(filePath, { encoding: "utf-8" });
1711
+ const stream = import_fs12.default.createReadStream(filePath, { encoding: "utf-8" });
1658
1712
  const rl = import_readline3.default.createInterface({ input: stream, crlfDelay: Infinity });
1659
1713
  const messages = [];
1660
1714
  let lineNum = 0;
@@ -1735,23 +1789,23 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1735
1789
  return null;
1736
1790
  }
1737
1791
  }
1738
- var import_fs11, import_path11, import_os8, import_readline3, CLAUDE_PROJECTS_DIR2;
1792
+ var import_fs12, import_path12, import_os8, import_readline3, CLAUDE_PROJECTS_DIR2;
1739
1793
  var init_claude_conversation = __esm({
1740
1794
  "src/claude-conversation.ts"() {
1741
1795
  "use strict";
1742
- import_fs11 = __toESM(require("fs"));
1743
- import_path11 = __toESM(require("path"));
1796
+ import_fs12 = __toESM(require("fs"));
1797
+ import_path12 = __toESM(require("path"));
1744
1798
  import_os8 = __toESM(require("os"));
1745
1799
  import_readline3 = __toESM(require("readline"));
1746
1800
  init_logger();
1747
1801
  init_claude_path();
1748
- CLAUDE_PROJECTS_DIR2 = import_path11.default.join(import_os8.default.homedir(), ".claude", "projects");
1802
+ CLAUDE_PROJECTS_DIR2 = import_path12.default.join(import_os8.default.homedir(), ".claude", "projects");
1749
1803
  }
1750
1804
  });
1751
1805
 
1752
1806
  // src/index.ts
1753
1807
  var index_exports = {};
1754
- var import_socket, config, socket, SESSION_MAX_IDLE_MS, reaperInterval, vpnProfiles;
1808
+ var import_socket, config, lock, socket, SESSION_MAX_IDLE_MS, reaperInterval, vpnProfiles;
1755
1809
  var init_index = __esm({
1756
1810
  "src/index.ts"() {
1757
1811
  "use strict";
@@ -1762,6 +1816,7 @@ var init_index = __esm({
1762
1816
  init_claude_plugin_installer();
1763
1817
  init_local_control();
1764
1818
  init_pty_helper_fix();
1819
+ init_single_instance();
1765
1820
  init_tmux();
1766
1821
  init_heartbeat();
1767
1822
  init_file_explorer();
@@ -1775,6 +1830,13 @@ var init_index = __esm({
1775
1830
  }
1776
1831
  config = loadConfig();
1777
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
+ }
1778
1840
  ensurePtyHelperExecutable();
1779
1841
  process.on("unhandledRejection", (reason) => {
1780
1842
  logger.error({ reason }, "Unhandled promise rejection (agent kept alive)");