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/index.js CHANGED
@@ -251,20 +251,104 @@ function startLocalControl(port, onHook) {
251
251
  return server;
252
252
  }
253
253
 
254
- // src/tmux.ts
255
- var import_child_process2 = require("child_process");
256
- var import_util2 = require("util");
254
+ // src/pty-helper-fix.ts
255
+ var import_node_module = require("module");
256
+ var import_node_child_process = require("child_process");
257
+ var import_fs3 = __toESM(require("fs"));
258
+ var import_path3 = __toESM(require("path"));
259
+ function ensurePtyHelperExecutable() {
260
+ if (process.platform === "win32") return;
261
+ try {
262
+ const nodeRequire = (0, import_node_module.createRequire)(__filename);
263
+ const ptyMain = nodeRequire.resolve("node-pty");
264
+ const ptyDir = import_path3.default.dirname(import_path3.default.dirname(ptyMain));
265
+ const candidates = [import_path3.default.join(ptyDir, "build", "Release", "spawn-helper")];
266
+ const prebuilds = import_path3.default.join(ptyDir, "prebuilds");
267
+ try {
268
+ for (const d of import_fs3.default.readdirSync(prebuilds)) {
269
+ candidates.push(import_path3.default.join(prebuilds, d, "spawn-helper"));
270
+ }
271
+ } catch {
272
+ }
273
+ for (const file of candidates) {
274
+ try {
275
+ const mode = import_fs3.default.statSync(file).mode;
276
+ if (!(mode & 73)) {
277
+ import_fs3.default.chmodSync(file, mode | 493);
278
+ logger.info({ file }, "Restored execute bit on node-pty spawn-helper");
279
+ }
280
+ } catch {
281
+ }
282
+ }
283
+ if (process.platform === "darwin") {
284
+ (0, import_node_child_process.execFile)("xattr", ["-dr", "com.apple.quarantine", ptyDir], () => {
285
+ });
286
+ }
287
+ } catch (err) {
288
+ logger.warn({ error: err?.message }, "Could not verify node-pty spawn-helper permissions");
289
+ }
290
+ }
291
+
292
+ // src/single-instance.ts
257
293
  var import_fs4 = __toESM(require("fs"));
258
294
  var import_path4 = __toESM(require("path"));
295
+ var import_child_process = require("child_process");
296
+ var LOCK_FILE = import_path4.default.join(CONFIG_DIR, "agent.lock");
297
+ function isRunningAgent(pid) {
298
+ try {
299
+ process.kill(pid, 0);
300
+ } catch {
301
+ return false;
302
+ }
303
+ if (process.platform === "win32") return true;
304
+ try {
305
+ const out = (0, import_child_process.execFileSync)("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" });
306
+ return /crc-agent|cli-remote-agent|agent[\/\\](dist|src)[\/\\](cli|index)/.test(out);
307
+ } catch {
308
+ return true;
309
+ }
310
+ }
311
+ function acquireSingleInstanceLock() {
312
+ if (process.env.CRC_ALLOW_MULTIPLE === "1") return { ok: true };
313
+ try {
314
+ if (import_fs4.default.existsSync(LOCK_FILE)) {
315
+ const prev = parseInt(import_fs4.default.readFileSync(LOCK_FILE, "utf8").trim(), 10);
316
+ if (prev && prev !== process.pid && isRunningAgent(prev)) {
317
+ return { ok: false, pid: prev };
318
+ }
319
+ }
320
+ import_fs4.default.mkdirSync(CONFIG_DIR, { recursive: true });
321
+ import_fs4.default.writeFileSync(LOCK_FILE, String(process.pid));
322
+ const release = () => {
323
+ try {
324
+ if (parseInt(import_fs4.default.readFileSync(LOCK_FILE, "utf8").trim(), 10) === process.pid) {
325
+ import_fs4.default.unlinkSync(LOCK_FILE);
326
+ }
327
+ } catch {
328
+ }
329
+ };
330
+ process.on("exit", release);
331
+ return { ok: true };
332
+ } catch (err) {
333
+ logger.warn({ error: err?.message }, "Could not manage single-instance lock");
334
+ return { ok: true };
335
+ }
336
+ }
337
+
338
+ // src/tmux.ts
339
+ var import_child_process3 = require("child_process");
340
+ var import_util2 = require("util");
341
+ var import_fs6 = __toESM(require("fs"));
342
+ var import_path6 = __toESM(require("path"));
259
343
 
260
344
  // src/claude-live.ts
261
- var import_fs3 = __toESM(require("fs"));
262
- var import_path3 = __toESM(require("path"));
345
+ var import_fs5 = __toESM(require("fs"));
346
+ var import_path5 = __toESM(require("path"));
263
347
  var import_os2 = __toESM(require("os"));
264
- var import_child_process = require("child_process");
348
+ var import_child_process2 = require("child_process");
265
349
  var import_util = require("util");
266
- var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
267
- var LIVE_SESSIONS_DIR = import_path3.default.join(import_os2.default.homedir(), ".claude", "sessions");
350
+ var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
351
+ var LIVE_SESSIONS_DIR = import_path5.default.join(import_os2.default.homedir(), ".claude", "sessions");
268
352
  function isAlive(pid) {
269
353
  try {
270
354
  process.kill(pid, 0);
@@ -276,14 +360,14 @@ function isAlive(pid) {
276
360
  function getLiveClaudeSessions() {
277
361
  let files;
278
362
  try {
279
- files = import_fs3.default.readdirSync(LIVE_SESSIONS_DIR).filter((f) => f.endsWith(".json"));
363
+ files = import_fs5.default.readdirSync(LIVE_SESSIONS_DIR).filter((f) => f.endsWith(".json"));
280
364
  } catch {
281
365
  return [];
282
366
  }
283
367
  const out = [];
284
368
  for (const f of files) {
285
369
  try {
286
- const obj = JSON.parse(import_fs3.default.readFileSync(import_path3.default.join(LIVE_SESSIONS_DIR, f), "utf-8"));
370
+ const obj = JSON.parse(import_fs5.default.readFileSync(import_path5.default.join(LIVE_SESSIONS_DIR, f), "utf-8"));
287
371
  if (typeof obj?.pid !== "number" || !isAlive(obj.pid)) continue;
288
372
  out.push({
289
373
  pid: obj.pid,
@@ -320,19 +404,19 @@ function isDescendantOf(pid, ancestor, parents) {
320
404
  }
321
405
 
322
406
  // src/tmux.ts
323
- var execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
407
+ var execFileAsync2 = (0, import_util2.promisify)(import_child_process3.execFile);
324
408
  var IS_WIN = process.platform === "win32";
325
409
  var cachedTmuxPath;
326
410
  function resolveTmuxPath() {
327
411
  if (cachedTmuxPath !== void 0) return cachedTmuxPath;
328
- const dirs = (process.env.PATH || "").split(import_path4.default.delimiter).filter(Boolean);
412
+ const dirs = (process.env.PATH || "").split(import_path6.default.delimiter).filter(Boolean);
329
413
  for (const extra of ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/opt/local/bin"]) {
330
414
  if (!dirs.includes(extra)) dirs.push(extra);
331
415
  }
332
416
  for (const dir of dirs) {
333
- const candidate = import_path4.default.join(dir, "tmux");
417
+ const candidate = import_path6.default.join(dir, "tmux");
334
418
  try {
335
- import_fs4.default.accessSync(candidate, import_fs4.default.constants.X_OK);
419
+ import_fs6.default.accessSync(candidate, import_fs6.default.constants.X_OK);
336
420
  cachedTmuxPath = candidate;
337
421
  return candidate;
338
422
  } catch {
@@ -382,8 +466,8 @@ async function enrichWithPanesAndClaude(sessions2) {
382
466
  const { file, args } = tmuxCmd(["list-panes", "-a", "-F", paneFmt]);
383
467
  const { stdout } = await execFileAsync2(file, args, { timeout: 6e3 });
384
468
  const panes = stdout.split("\n").map((line) => line.replace(/\r$/, "").trim()).filter(Boolean).map((line) => {
385
- const [session, pid, path9, activeFlags] = line.split(" ");
386
- return { session, pid: parseInt(pid, 10) || 0, path: path9 || "", active: activeFlags === "11" };
469
+ const [session, pid, path11, activeFlags] = line.split(" ");
470
+ return { session, pid: parseInt(pid, 10) || 0, path: path11 || "", active: activeFlags === "11" };
387
471
  });
388
472
  const byName = new Map(sessions2.map((s) => [s.name, s]));
389
473
  for (const pane of panes) {
@@ -425,22 +509,22 @@ function buildTmuxLaunch(name, launch) {
425
509
 
426
510
  // src/heartbeat.ts
427
511
  var import_os4 = __toESM(require("os"));
428
- var import_path6 = __toESM(require("path"));
429
- var import_child_process4 = require("child_process");
512
+ var import_path8 = __toESM(require("path"));
513
+ var import_child_process5 = require("child_process");
430
514
 
431
515
  // src/pty-session.ts
432
516
  var pty = __toESM(require("node-pty"));
433
- var import_fs5 = require("fs");
434
- var import_path5 = require("path");
517
+ var import_fs7 = require("fs");
518
+ var import_path7 = require("path");
435
519
  var import_os3 = require("os");
436
520
 
437
521
  // src/shell.ts
438
- var import_child_process3 = require("child_process");
522
+ var import_child_process4 = require("child_process");
439
523
  function detectShell(preference) {
440
524
  if (preference !== "auto") return preference;
441
525
  if (process.platform === "win32") {
442
526
  try {
443
- (0, import_child_process3.execSync)("where pwsh", { stdio: "ignore" });
527
+ (0, import_child_process4.execSync)("where pwsh", { stdio: "ignore" });
444
528
  return "pwsh.exe";
445
529
  } catch {
446
530
  return "powershell.exe";
@@ -451,22 +535,22 @@ function detectShell(preference) {
451
535
 
452
536
  // src/pty-session.ts
453
537
  var THRESHOLD = 3;
454
- var INIT_DIR = (0, import_path5.join)((0, import_os3.tmpdir)(), "crc-shell-init");
455
- var BASH_INIT = (0, import_path5.join)(INIT_DIR, "bashrc");
456
- var ZSH_DIR = (0, import_path5.join)(INIT_DIR, "zsh");
457
- var ZSH_RC = (0, import_path5.join)(ZSH_DIR, ".zshrc");
458
- var ZSH_ENV = (0, import_path5.join)(ZSH_DIR, ".zshenv");
538
+ var INIT_DIR = (0, import_path7.join)((0, import_os3.tmpdir)(), "crc-shell-init");
539
+ var BASH_INIT = (0, import_path7.join)(INIT_DIR, "bashrc");
540
+ var ZSH_DIR = (0, import_path7.join)(INIT_DIR, "zsh");
541
+ var ZSH_RC = (0, import_path7.join)(ZSH_DIR, ".zshrc");
542
+ var ZSH_ENV = (0, import_path7.join)(ZSH_DIR, ".zshenv");
459
543
  var INIT_VERSION = "2";
460
544
  function ensureInitFiles() {
461
- const versionFile = (0, import_path5.join)(INIT_DIR, ".version");
462
- if ((0, import_fs5.existsSync)(versionFile)) {
545
+ const versionFile = (0, import_path7.join)(INIT_DIR, ".version");
546
+ if ((0, import_fs7.existsSync)(versionFile)) {
463
547
  try {
464
- if ((0, import_fs5.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
548
+ if ((0, import_fs7.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
465
549
  } catch {
466
550
  }
467
551
  }
468
- (0, import_fs5.mkdirSync)(ZSH_DIR, { recursive: true });
469
- (0, import_fs5.writeFileSync)(BASH_INIT, [
552
+ (0, import_fs7.mkdirSync)(ZSH_DIR, { recursive: true });
553
+ (0, import_fs7.writeFileSync)(BASH_INIT, [
470
554
  "[ -f ~/.bash_profile ] && source ~/.bash_profile",
471
555
  "[ -f ~/.bashrc ] && source ~/.bashrc",
472
556
  "__crc_s=$SECONDS",
@@ -477,11 +561,11 @@ function ensureInitFiles() {
477
561
  `PROMPT_COMMAND='[ "$__crc_armed" = 1 ] && [ \${__crc_elapsed:-0} -ge ${THRESHOLD} ] && printf "\\a"; __crc_armed=1; eval "$__crc_orig_pc"'`,
478
562
  ""
479
563
  ].join("\n"), "utf-8");
480
- (0, import_fs5.writeFileSync)(ZSH_ENV, [
564
+ (0, import_fs7.writeFileSync)(ZSH_ENV, [
481
565
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshenv" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshenv"',
482
566
  ""
483
567
  ].join("\n"), "utf-8");
484
- (0, import_fs5.writeFileSync)(ZSH_RC, [
568
+ (0, import_fs7.writeFileSync)(ZSH_RC, [
485
569
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshrc" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshrc"',
486
570
  "__crc_s=$SECONDS",
487
571
  "__crc_armed=0",
@@ -494,14 +578,14 @@ function ensureInitFiles() {
494
578
  "fi",
495
579
  ""
496
580
  ].join("\n"), "utf-8");
497
- (0, import_fs5.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
581
+ (0, import_fs7.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
498
582
  }
499
583
  function resolveCwd(cwd) {
500
584
  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];
501
585
  for (const c of candidates) {
502
586
  if (c) {
503
587
  try {
504
- if ((0, import_fs5.existsSync)(c)) return c;
588
+ if ((0, import_fs7.existsSync)(c)) return c;
505
589
  } catch {
506
590
  }
507
591
  }
@@ -708,7 +792,7 @@ function getCpuUsage() {
708
792
  function getRootPaths() {
709
793
  if (process.platform === "win32") {
710
794
  try {
711
- const output = (0, import_child_process4.execSync)("wmic logicaldisk get name", { encoding: "utf-8" });
795
+ const output = (0, import_child_process5.execSync)("wmic logicaldisk get name", { encoding: "utf-8" });
712
796
  return output.split("\n").map((l) => l.trim()).filter((l) => /^[A-Z]:$/.test(l)).map((l) => l + "\\");
713
797
  } catch {
714
798
  return ["C:\\"];
@@ -727,7 +811,7 @@ function buildHeartbeat(homeDir) {
727
811
  memoryUsage: Math.round((totalMem - freeMem) / totalMem * 100),
728
812
  uptime: Math.round(import_os4.default.uptime()),
729
813
  activeSessions: getActiveSessionCount(),
730
- pathSeparator: import_path6.default.sep,
814
+ pathSeparator: import_path8.default.sep,
731
815
  homeDirectory: homeDir || import_os4.default.homedir(),
732
816
  rootPaths: getRootPaths(),
733
817
  capabilities: { terminal: true, fileTransfer: false }
@@ -735,33 +819,33 @@ function buildHeartbeat(homeDir) {
735
819
  }
736
820
 
737
821
  // src/file-explorer.ts
738
- var import_fs6 = __toESM(require("fs"));
739
- var import_path7 = __toESM(require("path"));
822
+ var import_fs8 = __toESM(require("fs"));
823
+ var import_path9 = __toESM(require("path"));
740
824
  var import_os5 = __toESM(require("os"));
741
- var SECRET_DIR = import_path7.default.join(import_os5.default.homedir(), ".crc-agent");
825
+ var SECRET_DIR = import_path9.default.join(import_os5.default.homedir(), ".crc-agent");
742
826
  function isInSecretDir(resolved) {
743
- const rel = import_path7.default.relative(SECRET_DIR, resolved);
744
- return rel === "" || !rel.startsWith(".." + import_path7.default.sep) && rel !== ".." && !import_path7.default.isAbsolute(rel);
827
+ const rel = import_path9.default.relative(SECRET_DIR, resolved);
828
+ return rel === "" || !rel.startsWith(".." + import_path9.default.sep) && rel !== ".." && !import_path9.default.isAbsolute(rel);
745
829
  }
746
830
  function listDirectory(dirPath) {
747
831
  try {
748
- const resolved = import_path7.default.resolve(dirPath);
832
+ const resolved = import_path9.default.resolve(dirPath);
749
833
  if (isInSecretDir(resolved)) {
750
834
  return { entries: [], error: "Access denied" };
751
835
  }
752
- if (!import_fs6.default.existsSync(resolved)) {
836
+ if (!import_fs8.default.existsSync(resolved)) {
753
837
  return { entries: [], error: "Path does not exist" };
754
838
  }
755
- const stat = import_fs6.default.statSync(resolved);
839
+ const stat = import_fs8.default.statSync(resolved);
756
840
  if (!stat.isDirectory()) {
757
841
  return { entries: [], error: "Not a directory" };
758
842
  }
759
- const dirents = import_fs6.default.readdirSync(resolved, { withFileTypes: true });
843
+ const dirents = import_fs8.default.readdirSync(resolved, { withFileTypes: true });
760
844
  const entries = [];
761
845
  for (const dirent of dirents) {
762
846
  try {
763
- const fullPath = import_path7.default.join(resolved, dirent.name);
764
- const s = import_fs6.default.statSync(fullPath);
847
+ const fullPath = import_path9.default.join(resolved, dirent.name);
848
+ const s = import_fs8.default.statSync(fullPath);
765
849
  entries.push({
766
850
  name: dirent.name,
767
851
  isDirectory: dirent.isDirectory(),
@@ -783,22 +867,22 @@ function listDirectory(dirPath) {
783
867
  }
784
868
  async function downloadFile(filePath, serverUrl, secret, agentId) {
785
869
  try {
786
- const resolved = import_path7.default.resolve(filePath);
870
+ const resolved = import_path9.default.resolve(filePath);
787
871
  if (isInSecretDir(resolved)) {
788
872
  return { error: "Access denied" };
789
873
  }
790
- if (!import_fs6.default.existsSync(resolved)) {
874
+ if (!import_fs8.default.existsSync(resolved)) {
791
875
  return { error: "File does not exist" };
792
876
  }
793
- const stat = import_fs6.default.statSync(resolved);
877
+ const stat = import_fs8.default.statSync(resolved);
794
878
  if (stat.isDirectory()) {
795
879
  return { error: "Cannot download a directory" };
796
880
  }
797
881
  if (stat.size > FILE_MAX_SIZE) {
798
882
  return { error: `File too large (max ${FILE_MAX_SIZE} bytes)` };
799
883
  }
800
- const fileName = import_path7.default.basename(resolved);
801
- const fileStream = import_fs6.default.createReadStream(resolved);
884
+ const fileName = import_path9.default.basename(resolved);
885
+ const fileStream = import_fs8.default.createReadStream(resolved);
802
886
  const baseUrl = serverUrl.replace("wss://", "https://").replace("ws://", "http://");
803
887
  const url = `${baseUrl}/api/files/receive`;
804
888
  const res = await fetch(url, {
@@ -829,14 +913,14 @@ async function downloadFile(filePath, serverUrl, secret, agentId) {
829
913
  }
830
914
 
831
915
  // src/vpn-manager.ts
832
- var import_child_process5 = require("child_process");
916
+ var import_child_process6 = require("child_process");
833
917
  var import_util3 = require("util");
834
- var import_fs7 = __toESM(require("fs"));
835
- var import_path8 = __toESM(require("path"));
918
+ var import_fs9 = __toESM(require("fs"));
919
+ var import_path10 = __toESM(require("path"));
836
920
  var import_os6 = __toESM(require("os"));
837
- var execAsync = (0, import_util3.promisify)(import_child_process5.exec);
921
+ var execAsync = (0, import_util3.promisify)(import_child_process6.exec);
838
922
  var isWindows = process.platform === "win32";
839
- var VPN_DIR = import_path8.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
923
+ var VPN_DIR = import_path10.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
840
924
  function shq(value) {
841
925
  return value.replace(/'/g, "'\\''");
842
926
  }
@@ -855,8 +939,8 @@ async function runCmd(cmd, shell) {
855
939
  }
856
940
  function getConfigPath(profile) {
857
941
  const file = profile.configFile || `${profile.id}.conf`;
858
- if (import_path8.default.isAbsolute(file)) return file;
859
- return import_path8.default.join(VPN_DIR, file);
942
+ if (import_path10.default.isAbsolute(file)) return file;
943
+ return import_path10.default.join(VPN_DIR, file);
860
944
  }
861
945
  async function getScutilStatus(serviceName) {
862
946
  const { stdout } = await runCmd(`scutil --nc status '${shq(serviceName)}'`);
@@ -972,7 +1056,7 @@ async function connectWireGuard(profile) {
972
1056
  const tunnelName = profile.tunnelName || profile.id;
973
1057
  if (isWindows) {
974
1058
  const confPath2 = getConfigPath(profile);
975
- if (!import_fs7.default.existsSync(confPath2)) {
1059
+ if (!import_fs9.default.existsSync(confPath2)) {
976
1060
  return `Config file not found: ${confPath2}`;
977
1061
  }
978
1062
  const script = `
@@ -998,7 +1082,7 @@ async function connectWireGuard(profile) {
998
1082
  async function connectOpenVpn(profile) {
999
1083
  if (isWindows) {
1000
1084
  const confPath2 = getConfigPath(profile);
1001
- if (!import_fs7.default.existsSync(confPath2)) {
1085
+ if (!import_fs9.default.existsSync(confPath2)) {
1002
1086
  return `Config file not found: ${confPath2}`;
1003
1087
  }
1004
1088
  const connector = "C:\\Program Files\\OpenVPN Connect\\ovpnconnector.exe";
@@ -1025,7 +1109,7 @@ async function connectOpenVpn(profile) {
1025
1109
  return scutilStart(profile.serviceName);
1026
1110
  }
1027
1111
  const confPath = getConfigPath(profile);
1028
- if (!import_fs7.default.existsSync(confPath)) {
1112
+ if (!import_fs9.default.existsSync(confPath)) {
1029
1113
  return `Config file not found: ${confPath}`;
1030
1114
  }
1031
1115
  const { stderr } = await runCmd(`sudo openvpn --config '${shq(confPath)}' --daemon`);
@@ -1034,7 +1118,7 @@ async function connectOpenVpn(profile) {
1034
1118
  async function connectAzure(profile) {
1035
1119
  if (isWindows) {
1036
1120
  const confPath = getConfigPath(profile);
1037
- if (!import_fs7.default.existsSync(confPath)) {
1121
+ if (!import_fs9.default.existsSync(confPath)) {
1038
1122
  return `Config file not found: ${confPath}`;
1039
1123
  }
1040
1124
  await runCmd(`& 'AzureVpn.exe' -i '${psq(confPath)}' 2>&1`);
@@ -1156,8 +1240,8 @@ async function disconnectVpn(configs, profileId) {
1156
1240
  }
1157
1241
 
1158
1242
  // src/claude-sessions.ts
1159
- var import_fs8 = __toESM(require("fs"));
1160
- var import_path9 = __toESM(require("path"));
1243
+ var import_fs10 = __toESM(require("fs"));
1244
+ var import_path11 = __toESM(require("path"));
1161
1245
  var import_os7 = __toESM(require("os"));
1162
1246
  var import_readline = __toESM(require("readline"));
1163
1247
 
@@ -1185,10 +1269,10 @@ function findProjectDirs(allDirs, filterProjectPath) {
1185
1269
  }
1186
1270
 
1187
1271
  // src/claude-sessions.ts
1188
- var CLAUDE_PROJECTS_DIR = import_path9.default.join(import_os7.default.homedir(), ".claude", "projects");
1272
+ var CLAUDE_PROJECTS_DIR = import_path11.default.join(import_os7.default.homedir(), ".claude", "projects");
1189
1273
  async function parseSessionFile(filePath) {
1190
1274
  try {
1191
- const stream = import_fs8.default.createReadStream(filePath, { encoding: "utf-8" });
1275
+ const stream = import_fs10.default.createReadStream(filePath, { encoding: "utf-8" });
1192
1276
  const rl = import_readline.default.createInterface({ input: stream, crlfDelay: Infinity });
1193
1277
  let firstMessage = "";
1194
1278
  let lastTimestamp = "";
@@ -1236,17 +1320,17 @@ async function parseSessionFile(filePath) {
1236
1320
  }
1237
1321
  async function listClaudeSessions(filterProjectPath) {
1238
1322
  const sessions2 = [];
1239
- if (!import_fs8.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1323
+ if (!import_fs10.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1240
1324
  const isDirSafe = (d) => {
1241
1325
  try {
1242
- return import_fs8.default.statSync(import_path9.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1326
+ return import_fs10.default.statSync(import_path11.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1243
1327
  } catch {
1244
1328
  return false;
1245
1329
  }
1246
1330
  };
1247
1331
  let allEntries;
1248
1332
  try {
1249
- allEntries = import_fs8.default.readdirSync(CLAUDE_PROJECTS_DIR);
1333
+ allEntries = import_fs10.default.readdirSync(CLAUDE_PROJECTS_DIR);
1250
1334
  } catch {
1251
1335
  return sessions2;
1252
1336
  }
@@ -1259,16 +1343,16 @@ async function listClaudeSessions(filterProjectPath) {
1259
1343
  projectDirs = allEntries.filter((d) => isDirSafe(d) && !d.startsWith("-private-"));
1260
1344
  }
1261
1345
  for (const dirName of projectDirs) {
1262
- const dirPath = import_path9.default.join(CLAUDE_PROJECTS_DIR, dirName);
1346
+ const dirPath = import_path11.default.join(CLAUDE_PROJECTS_DIR, dirName);
1263
1347
  let files;
1264
1348
  try {
1265
- files = import_fs8.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1349
+ files = import_fs10.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1266
1350
  } catch {
1267
1351
  continue;
1268
1352
  }
1269
1353
  for (const file of files) {
1270
1354
  const sessionId = file.replace(".jsonl", "");
1271
- const parsed = await parseSessionFile(import_path9.default.join(dirPath, file));
1355
+ const parsed = await parseSessionFile(import_path11.default.join(dirPath, file));
1272
1356
  if (!parsed) continue;
1273
1357
  const projectPath = parsed.cwd || dirName.replace(/^-/, "/").replace(/-/g, "/");
1274
1358
  sessions2.push({
@@ -1288,21 +1372,21 @@ async function listClaudeSessions(filterProjectPath) {
1288
1372
  }
1289
1373
 
1290
1374
  // src/claude-conversation.ts
1291
- var import_fs9 = __toESM(require("fs"));
1292
- var import_path10 = __toESM(require("path"));
1375
+ var import_fs11 = __toESM(require("fs"));
1376
+ var import_path12 = __toESM(require("path"));
1293
1377
  var import_os8 = __toESM(require("os"));
1294
1378
  var import_readline2 = __toESM(require("readline"));
1295
- var CLAUDE_PROJECTS_DIR2 = import_path10.default.join(import_os8.default.homedir(), ".claude", "projects");
1379
+ var CLAUDE_PROJECTS_DIR2 = import_path12.default.join(import_os8.default.homedir(), ".claude", "projects");
1296
1380
  function findProjectDir(projectPath) {
1297
1381
  const encoded = encodeProjectPath(projectPath);
1298
- const dirPath = import_path10.default.join(CLAUDE_PROJECTS_DIR2, encoded);
1299
- if (import_fs9.default.existsSync(dirPath)) return dirPath;
1300
- if (!import_fs9.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1382
+ const dirPath = import_path12.default.join(CLAUDE_PROJECTS_DIR2, encoded);
1383
+ if (import_fs11.default.existsSync(dirPath)) return dirPath;
1384
+ if (!import_fs11.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1301
1385
  let allDirs;
1302
1386
  try {
1303
- allDirs = import_fs9.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1387
+ allDirs = import_fs11.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1304
1388
  try {
1305
- return import_fs9.default.statSync(import_path10.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1389
+ return import_fs11.default.statSync(import_path12.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1306
1390
  } catch {
1307
1391
  return false;
1308
1392
  }
@@ -1312,23 +1396,23 @@ function findProjectDir(projectPath) {
1312
1396
  }
1313
1397
  const matches = findProjectDirs(allDirs, projectPath);
1314
1398
  if (matches.length > 0) {
1315
- return import_path10.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1399
+ return import_path12.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1316
1400
  }
1317
1401
  return null;
1318
1402
  }
1319
1403
  function findSessionFileById(sessionId) {
1320
1404
  if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return null;
1321
- if (!import_fs9.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1405
+ if (!import_fs11.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1322
1406
  let dirs;
1323
1407
  try {
1324
- dirs = import_fs9.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1408
+ dirs = import_fs11.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1325
1409
  } catch {
1326
1410
  return null;
1327
1411
  }
1328
1412
  for (const dir of dirs) {
1329
- const candidate = import_path10.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1413
+ const candidate = import_path12.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1330
1414
  try {
1331
- if (import_fs9.default.statSync(candidate).isFile()) return candidate;
1415
+ if (import_fs11.default.statSync(candidate).isFile()) return candidate;
1332
1416
  } catch {
1333
1417
  }
1334
1418
  }
@@ -1339,20 +1423,20 @@ function findLatestSessionFile(projectPath) {
1339
1423
  if (!dirPath) return null;
1340
1424
  let entries;
1341
1425
  try {
1342
- entries = import_fs9.default.readdirSync(dirPath);
1426
+ entries = import_fs11.default.readdirSync(dirPath);
1343
1427
  } catch {
1344
1428
  return null;
1345
1429
  }
1346
1430
  const files = entries.filter((f) => f.endsWith(".jsonl") && !f.includes("/")).map((f) => {
1347
1431
  try {
1348
- return { name: f, mtime: import_fs9.default.statSync(import_path10.default.join(dirPath, f)).mtimeMs };
1432
+ return { name: f, mtime: import_fs11.default.statSync(import_path12.default.join(dirPath, f)).mtimeMs };
1349
1433
  } catch {
1350
1434
  return null;
1351
1435
  }
1352
1436
  }).filter((f) => f !== null).sort((a, b) => b.mtime - a.mtime);
1353
1437
  if (files.length === 0) return null;
1354
1438
  return {
1355
- filePath: import_path10.default.join(dirPath, files[0].name),
1439
+ filePath: import_path12.default.join(dirPath, files[0].name),
1356
1440
  sessionId: files[0].name.replace(".jsonl", "")
1357
1441
  };
1358
1442
  }
@@ -1362,8 +1446,8 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1362
1446
  if (specificSessionId) {
1363
1447
  sessionId = specificSessionId;
1364
1448
  const dirPath = findProjectDir(projectPath);
1365
- const direct = dirPath ? import_path10.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1366
- if (direct && import_fs9.default.existsSync(direct)) {
1449
+ const direct = dirPath ? import_path12.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1450
+ if (direct && import_fs11.default.existsSync(direct)) {
1367
1451
  filePath = direct;
1368
1452
  } else {
1369
1453
  const byId = findSessionFileById(specificSessionId);
@@ -1377,7 +1461,7 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1377
1461
  sessionId = found.sessionId;
1378
1462
  }
1379
1463
  try {
1380
- const stream = import_fs9.default.createReadStream(filePath, { encoding: "utf-8" });
1464
+ const stream = import_fs11.default.createReadStream(filePath, { encoding: "utf-8" });
1381
1465
  const rl = import_readline2.default.createInterface({ input: stream, crlfDelay: Infinity });
1382
1466
  const messages = [];
1383
1467
  let lineNum = 0;
@@ -1466,6 +1550,14 @@ if (!isConfigured()) {
1466
1550
  }
1467
1551
  var config = loadConfig();
1468
1552
  logger.info({ agentId: config.agentId, serverUrl: config.serverUrl }, "Starting agent");
1553
+ var lock = acquireSingleInstanceLock();
1554
+ if (!lock.ok) {
1555
+ console.error(
1556
+ `Another crc-agent is already running on this machine (PID ${lock.pid}). Stop it first, or set CRC_ALLOW_MULTIPLE=1 to override.`
1557
+ );
1558
+ process.exit(1);
1559
+ }
1560
+ ensurePtyHelperExecutable();
1469
1561
  process.on("unhandledRejection", (reason) => {
1470
1562
  logger.error({ reason }, "Unhandled promise rejection (agent kept alive)");
1471
1563
  });