cli-remote-agent 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -251,18 +251,58 @@ function startLocalControl(port, onHook) {
251
251
  return server;
252
252
  }
253
253
 
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
+
254
292
  // src/tmux.ts
255
293
  var import_child_process2 = require("child_process");
256
294
  var import_util2 = require("util");
295
+ var import_fs5 = __toESM(require("fs"));
296
+ var import_path5 = __toESM(require("path"));
257
297
 
258
298
  // src/claude-live.ts
259
- var import_fs3 = __toESM(require("fs"));
260
- var import_path3 = __toESM(require("path"));
299
+ var import_fs4 = __toESM(require("fs"));
300
+ var import_path4 = __toESM(require("path"));
261
301
  var import_os2 = __toESM(require("os"));
262
302
  var import_child_process = require("child_process");
263
303
  var import_util = require("util");
264
304
  var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
265
- var LIVE_SESSIONS_DIR = import_path3.default.join(import_os2.default.homedir(), ".claude", "sessions");
305
+ var LIVE_SESSIONS_DIR = import_path4.default.join(import_os2.default.homedir(), ".claude", "sessions");
266
306
  function isAlive(pid) {
267
307
  try {
268
308
  process.kill(pid, 0);
@@ -274,14 +314,14 @@ function isAlive(pid) {
274
314
  function getLiveClaudeSessions() {
275
315
  let files;
276
316
  try {
277
- files = import_fs3.default.readdirSync(LIVE_SESSIONS_DIR).filter((f) => f.endsWith(".json"));
317
+ files = import_fs4.default.readdirSync(LIVE_SESSIONS_DIR).filter((f) => f.endsWith(".json"));
278
318
  } catch {
279
319
  return [];
280
320
  }
281
321
  const out = [];
282
322
  for (const f of files) {
283
323
  try {
284
- const obj = JSON.parse(import_fs3.default.readFileSync(import_path3.default.join(LIVE_SESSIONS_DIR, f), "utf-8"));
324
+ const obj = JSON.parse(import_fs4.default.readFileSync(import_path4.default.join(LIVE_SESSIONS_DIR, f), "utf-8"));
285
325
  if (typeof obj?.pid !== "number" || !isAlive(obj.pid)) continue;
286
326
  out.push({
287
327
  pid: obj.pid,
@@ -320,8 +360,28 @@ function isDescendantOf(pid, ancestor, parents) {
320
360
  // src/tmux.ts
321
361
  var execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
322
362
  var IS_WIN = process.platform === "win32";
363
+ var cachedTmuxPath;
364
+ function resolveTmuxPath() {
365
+ if (cachedTmuxPath !== void 0) return cachedTmuxPath;
366
+ const dirs = (process.env.PATH || "").split(import_path5.default.delimiter).filter(Boolean);
367
+ for (const extra of ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/opt/local/bin"]) {
368
+ if (!dirs.includes(extra)) dirs.push(extra);
369
+ }
370
+ for (const dir of dirs) {
371
+ const candidate = import_path5.default.join(dir, "tmux");
372
+ try {
373
+ import_fs5.default.accessSync(candidate, import_fs5.default.constants.X_OK);
374
+ cachedTmuxPath = candidate;
375
+ return candidate;
376
+ } catch {
377
+ }
378
+ }
379
+ cachedTmuxPath = null;
380
+ return null;
381
+ }
323
382
  function tmuxCmd(args) {
324
- return IS_WIN ? { file: "wsl.exe", args: ["tmux", ...args] } : { file: "tmux", args };
383
+ if (IS_WIN) return { file: "wsl.exe", args: ["tmux", ...args] };
384
+ return { file: resolveTmuxPath() || "tmux", args };
325
385
  }
326
386
  async function listTmuxSessions() {
327
387
  const fmt = "#{session_name} #{session_windows} #{session_attached} #{session_activity}";
@@ -360,8 +420,8 @@ async function enrichWithPanesAndClaude(sessions2) {
360
420
  const { file, args } = tmuxCmd(["list-panes", "-a", "-F", paneFmt]);
361
421
  const { stdout } = await execFileAsync2(file, args, { timeout: 6e3 });
362
422
  const panes = stdout.split("\n").map((line) => line.replace(/\r$/, "").trim()).filter(Boolean).map((line) => {
363
- const [session, pid, path8, activeFlags] = line.split(" ");
364
- return { session, pid: parseInt(pid, 10) || 0, path: path8 || "", active: activeFlags === "11" };
423
+ const [session, pid, path10, activeFlags] = line.split(" ");
424
+ return { session, pid: parseInt(pid, 10) || 0, path: path10 || "", active: activeFlags === "11" };
365
425
  });
366
426
  const byName = new Map(sessions2.map((s) => [s.name, s]));
367
427
  for (const pane of panes) {
@@ -383,21 +443,35 @@ async function enrichWithPanesAndClaude(sessions2) {
383
443
  } catch {
384
444
  }
385
445
  }
386
- function shq(v) {
387
- return `'${v.replace(/'/g, `'\\''`)}'`;
388
- }
389
- function buildTmuxLaunch(name, launch, shell) {
446
+ function buildTmuxLaunch(name, launch) {
390
447
  const trimmed = launch && launch.trim() ? launch.trim() : void 0;
391
448
  if (IS_WIN) {
392
- const args = ["tmux", "new-session", "-A", "-s", name];
393
- if (trimmed) args.push(trimmed);
394
- return { file: "wsl.exe", args };
395
- }
396
- let cmd = `tmux set-option -g aggressive-resize on 2>/dev/null; exec tmux new-session -A -s ${shq(name)}`;
397
- if (trimmed) cmd += ` ${shq(trimmed)}`;
398
- return { file: shell, args: ["-l", "-c", cmd] };
449
+ const args2 = ["tmux", "new-session", "-A", "-s", name];
450
+ if (trimmed) args2.push(trimmed);
451
+ return { file: "wsl.exe", args: args2 };
452
+ }
453
+ const tmuxBin = resolveTmuxPath();
454
+ if (!tmuxBin) return null;
455
+ execFileAsync2(tmuxBin, ["set-option", "-g", "aggressive-resize", "on"], { timeout: 4e3 }).catch(
456
+ () => {
457
+ }
458
+ );
459
+ const args = ["new-session", "-A", "-s", name];
460
+ if (trimmed) args.push(trimmed);
461
+ return { file: tmuxBin, args };
399
462
  }
400
463
 
464
+ // src/heartbeat.ts
465
+ var import_os4 = __toESM(require("os"));
466
+ var import_path7 = __toESM(require("path"));
467
+ var import_child_process4 = require("child_process");
468
+
469
+ // src/pty-session.ts
470
+ var pty = __toESM(require("node-pty"));
471
+ var import_fs6 = require("fs");
472
+ var import_path6 = require("path");
473
+ var import_os3 = require("os");
474
+
401
475
  // src/shell.ts
402
476
  var import_child_process3 = require("child_process");
403
477
  function detectShell(preference) {
@@ -413,33 +487,24 @@ function detectShell(preference) {
413
487
  return process.env.SHELL || "/bin/bash";
414
488
  }
415
489
 
416
- // src/heartbeat.ts
417
- var import_os4 = __toESM(require("os"));
418
- var import_path5 = __toESM(require("path"));
419
- var import_child_process4 = require("child_process");
420
-
421
490
  // src/pty-session.ts
422
- var pty = __toESM(require("node-pty"));
423
- var import_fs4 = require("fs");
424
- var import_path4 = require("path");
425
- var import_os3 = require("os");
426
491
  var THRESHOLD = 3;
427
- var INIT_DIR = (0, import_path4.join)((0, import_os3.tmpdir)(), "crc-shell-init");
428
- var BASH_INIT = (0, import_path4.join)(INIT_DIR, "bashrc");
429
- var ZSH_DIR = (0, import_path4.join)(INIT_DIR, "zsh");
430
- var ZSH_RC = (0, import_path4.join)(ZSH_DIR, ".zshrc");
431
- var ZSH_ENV = (0, import_path4.join)(ZSH_DIR, ".zshenv");
492
+ var INIT_DIR = (0, import_path6.join)((0, import_os3.tmpdir)(), "crc-shell-init");
493
+ var BASH_INIT = (0, import_path6.join)(INIT_DIR, "bashrc");
494
+ var ZSH_DIR = (0, import_path6.join)(INIT_DIR, "zsh");
495
+ var ZSH_RC = (0, import_path6.join)(ZSH_DIR, ".zshrc");
496
+ var ZSH_ENV = (0, import_path6.join)(ZSH_DIR, ".zshenv");
432
497
  var INIT_VERSION = "2";
433
498
  function ensureInitFiles() {
434
- const versionFile = (0, import_path4.join)(INIT_DIR, ".version");
435
- if ((0, import_fs4.existsSync)(versionFile)) {
499
+ const versionFile = (0, import_path6.join)(INIT_DIR, ".version");
500
+ if ((0, import_fs6.existsSync)(versionFile)) {
436
501
  try {
437
- if ((0, import_fs4.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
502
+ if ((0, import_fs6.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
438
503
  } catch {
439
504
  }
440
505
  }
441
- (0, import_fs4.mkdirSync)(ZSH_DIR, { recursive: true });
442
- (0, import_fs4.writeFileSync)(BASH_INIT, [
506
+ (0, import_fs6.mkdirSync)(ZSH_DIR, { recursive: true });
507
+ (0, import_fs6.writeFileSync)(BASH_INIT, [
443
508
  "[ -f ~/.bash_profile ] && source ~/.bash_profile",
444
509
  "[ -f ~/.bashrc ] && source ~/.bashrc",
445
510
  "__crc_s=$SECONDS",
@@ -450,11 +515,11 @@ function ensureInitFiles() {
450
515
  `PROMPT_COMMAND='[ "$__crc_armed" = 1 ] && [ \${__crc_elapsed:-0} -ge ${THRESHOLD} ] && printf "\\a"; __crc_armed=1; eval "$__crc_orig_pc"'`,
451
516
  ""
452
517
  ].join("\n"), "utf-8");
453
- (0, import_fs4.writeFileSync)(ZSH_ENV, [
518
+ (0, import_fs6.writeFileSync)(ZSH_ENV, [
454
519
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshenv" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshenv"',
455
520
  ""
456
521
  ].join("\n"), "utf-8");
457
- (0, import_fs4.writeFileSync)(ZSH_RC, [
522
+ (0, import_fs6.writeFileSync)(ZSH_RC, [
458
523
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshrc" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshrc"',
459
524
  "__crc_s=$SECONDS",
460
525
  "__crc_armed=0",
@@ -467,14 +532,14 @@ function ensureInitFiles() {
467
532
  "fi",
468
533
  ""
469
534
  ].join("\n"), "utf-8");
470
- (0, import_fs4.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
535
+ (0, import_fs6.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
471
536
  }
472
537
  function resolveCwd(cwd) {
473
538
  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];
474
539
  for (const c of candidates) {
475
540
  if (c) {
476
541
  try {
477
- if ((0, import_fs4.existsSync)(c)) return c;
542
+ if ((0, import_fs6.existsSync)(c)) return c;
478
543
  } catch {
479
544
  }
480
545
  }
@@ -700,7 +765,7 @@ function buildHeartbeat(homeDir) {
700
765
  memoryUsage: Math.round((totalMem - freeMem) / totalMem * 100),
701
766
  uptime: Math.round(import_os4.default.uptime()),
702
767
  activeSessions: getActiveSessionCount(),
703
- pathSeparator: import_path5.default.sep,
768
+ pathSeparator: import_path7.default.sep,
704
769
  homeDirectory: homeDir || import_os4.default.homedir(),
705
770
  rootPaths: getRootPaths(),
706
771
  capabilities: { terminal: true, fileTransfer: false }
@@ -708,33 +773,33 @@ function buildHeartbeat(homeDir) {
708
773
  }
709
774
 
710
775
  // src/file-explorer.ts
711
- var import_fs5 = __toESM(require("fs"));
712
- var import_path6 = __toESM(require("path"));
776
+ var import_fs7 = __toESM(require("fs"));
777
+ var import_path8 = __toESM(require("path"));
713
778
  var import_os5 = __toESM(require("os"));
714
- var SECRET_DIR = import_path6.default.join(import_os5.default.homedir(), ".crc-agent");
779
+ var SECRET_DIR = import_path8.default.join(import_os5.default.homedir(), ".crc-agent");
715
780
  function isInSecretDir(resolved) {
716
- const rel = import_path6.default.relative(SECRET_DIR, resolved);
717
- return rel === "" || !rel.startsWith(".." + import_path6.default.sep) && rel !== ".." && !import_path6.default.isAbsolute(rel);
781
+ const rel = import_path8.default.relative(SECRET_DIR, resolved);
782
+ return rel === "" || !rel.startsWith(".." + import_path8.default.sep) && rel !== ".." && !import_path8.default.isAbsolute(rel);
718
783
  }
719
784
  function listDirectory(dirPath) {
720
785
  try {
721
- const resolved = import_path6.default.resolve(dirPath);
786
+ const resolved = import_path8.default.resolve(dirPath);
722
787
  if (isInSecretDir(resolved)) {
723
788
  return { entries: [], error: "Access denied" };
724
789
  }
725
- if (!import_fs5.default.existsSync(resolved)) {
790
+ if (!import_fs7.default.existsSync(resolved)) {
726
791
  return { entries: [], error: "Path does not exist" };
727
792
  }
728
- const stat = import_fs5.default.statSync(resolved);
793
+ const stat = import_fs7.default.statSync(resolved);
729
794
  if (!stat.isDirectory()) {
730
795
  return { entries: [], error: "Not a directory" };
731
796
  }
732
- const dirents = import_fs5.default.readdirSync(resolved, { withFileTypes: true });
797
+ const dirents = import_fs7.default.readdirSync(resolved, { withFileTypes: true });
733
798
  const entries = [];
734
799
  for (const dirent of dirents) {
735
800
  try {
736
- const fullPath = import_path6.default.join(resolved, dirent.name);
737
- const s = import_fs5.default.statSync(fullPath);
801
+ const fullPath = import_path8.default.join(resolved, dirent.name);
802
+ const s = import_fs7.default.statSync(fullPath);
738
803
  entries.push({
739
804
  name: dirent.name,
740
805
  isDirectory: dirent.isDirectory(),
@@ -756,22 +821,22 @@ function listDirectory(dirPath) {
756
821
  }
757
822
  async function downloadFile(filePath, serverUrl, secret, agentId) {
758
823
  try {
759
- const resolved = import_path6.default.resolve(filePath);
824
+ const resolved = import_path8.default.resolve(filePath);
760
825
  if (isInSecretDir(resolved)) {
761
826
  return { error: "Access denied" };
762
827
  }
763
- if (!import_fs5.default.existsSync(resolved)) {
828
+ if (!import_fs7.default.existsSync(resolved)) {
764
829
  return { error: "File does not exist" };
765
830
  }
766
- const stat = import_fs5.default.statSync(resolved);
831
+ const stat = import_fs7.default.statSync(resolved);
767
832
  if (stat.isDirectory()) {
768
833
  return { error: "Cannot download a directory" };
769
834
  }
770
835
  if (stat.size > FILE_MAX_SIZE) {
771
836
  return { error: `File too large (max ${FILE_MAX_SIZE} bytes)` };
772
837
  }
773
- const fileName = import_path6.default.basename(resolved);
774
- const fileStream = import_fs5.default.createReadStream(resolved);
838
+ const fileName = import_path8.default.basename(resolved);
839
+ const fileStream = import_fs7.default.createReadStream(resolved);
775
840
  const baseUrl = serverUrl.replace("wss://", "https://").replace("ws://", "http://");
776
841
  const url = `${baseUrl}/api/files/receive`;
777
842
  const res = await fetch(url, {
@@ -804,13 +869,13 @@ async function downloadFile(filePath, serverUrl, secret, agentId) {
804
869
  // src/vpn-manager.ts
805
870
  var import_child_process5 = require("child_process");
806
871
  var import_util3 = require("util");
807
- var import_fs6 = __toESM(require("fs"));
808
- var import_path7 = __toESM(require("path"));
872
+ var import_fs8 = __toESM(require("fs"));
873
+ var import_path9 = __toESM(require("path"));
809
874
  var import_os6 = __toESM(require("os"));
810
875
  var execAsync = (0, import_util3.promisify)(import_child_process5.exec);
811
876
  var isWindows = process.platform === "win32";
812
- var VPN_DIR = import_path7.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
813
- function shq2(value) {
877
+ var VPN_DIR = import_path9.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
878
+ function shq(value) {
814
879
  return value.replace(/'/g, "'\\''");
815
880
  }
816
881
  function psq(value) {
@@ -828,11 +893,11 @@ async function runCmd(cmd, shell) {
828
893
  }
829
894
  function getConfigPath(profile) {
830
895
  const file = profile.configFile || `${profile.id}.conf`;
831
- if (import_path7.default.isAbsolute(file)) return file;
832
- return import_path7.default.join(VPN_DIR, file);
896
+ if (import_path9.default.isAbsolute(file)) return file;
897
+ return import_path9.default.join(VPN_DIR, file);
833
898
  }
834
899
  async function getScutilStatus(serviceName) {
835
- const { stdout } = await runCmd(`scutil --nc status '${shq2(serviceName)}'`);
900
+ const { stdout } = await runCmd(`scutil --nc status '${shq(serviceName)}'`);
836
901
  const firstLine = stdout.trim().split("\n")[0];
837
902
  if (firstLine === "Connected") return "connected";
838
903
  if (firstLine === "Connecting") return "connecting";
@@ -840,11 +905,11 @@ async function getScutilStatus(serviceName) {
840
905
  return "disconnected";
841
906
  }
842
907
  async function scutilStart(serviceName) {
843
- const { stderr } = await runCmd(`scutil --nc start '${shq2(serviceName)}'`);
908
+ const { stderr } = await runCmd(`scutil --nc start '${shq(serviceName)}'`);
844
909
  return stderr || null;
845
910
  }
846
911
  async function scutilStop(serviceName) {
847
- const { stderr } = await runCmd(`scutil --nc stop '${shq2(serviceName)}'`);
912
+ const { stderr } = await runCmd(`scutil --nc stop '${shq(serviceName)}'`);
848
913
  return stderr || null;
849
914
  }
850
915
  async function runOsascript(script) {
@@ -893,7 +958,7 @@ async function getWireGuardStatus(profile) {
893
958
  return getScutilStatus(profile.serviceName);
894
959
  }
895
960
  const tunnelName = profile.tunnelName || profile.id;
896
- const { stdout } = await runCmd(`wg show '${shq2(tunnelName)}' 2>/dev/null`);
961
+ const { stdout } = await runCmd(`wg show '${shq(tunnelName)}' 2>/dev/null`);
897
962
  return stdout.trim() ? "connected" : "disconnected";
898
963
  }
899
964
  async function getOpenVpnStatus(profile) {
@@ -945,7 +1010,7 @@ async function connectWireGuard(profile) {
945
1010
  const tunnelName = profile.tunnelName || profile.id;
946
1011
  if (isWindows) {
947
1012
  const confPath2 = getConfigPath(profile);
948
- if (!import_fs6.default.existsSync(confPath2)) {
1013
+ if (!import_fs8.default.existsSync(confPath2)) {
949
1014
  return `Config file not found: ${confPath2}`;
950
1015
  }
951
1016
  const script = `
@@ -965,13 +1030,13 @@ async function connectWireGuard(profile) {
965
1030
  return scutilStart(profile.serviceName);
966
1031
  }
967
1032
  const confPath = getConfigPath(profile);
968
- const { stderr } = await runCmd(`sudo wg-quick up '${shq2(confPath)}'`);
1033
+ const { stderr } = await runCmd(`sudo wg-quick up '${shq(confPath)}'`);
969
1034
  return stderr || null;
970
1035
  }
971
1036
  async function connectOpenVpn(profile) {
972
1037
  if (isWindows) {
973
1038
  const confPath2 = getConfigPath(profile);
974
- if (!import_fs6.default.existsSync(confPath2)) {
1039
+ if (!import_fs8.default.existsSync(confPath2)) {
975
1040
  return `Config file not found: ${confPath2}`;
976
1041
  }
977
1042
  const connector = "C:\\Program Files\\OpenVPN Connect\\ovpnconnector.exe";
@@ -998,16 +1063,16 @@ async function connectOpenVpn(profile) {
998
1063
  return scutilStart(profile.serviceName);
999
1064
  }
1000
1065
  const confPath = getConfigPath(profile);
1001
- if (!import_fs6.default.existsSync(confPath)) {
1066
+ if (!import_fs8.default.existsSync(confPath)) {
1002
1067
  return `Config file not found: ${confPath}`;
1003
1068
  }
1004
- const { stderr } = await runCmd(`sudo openvpn --config '${shq2(confPath)}' --daemon`);
1069
+ const { stderr } = await runCmd(`sudo openvpn --config '${shq(confPath)}' --daemon`);
1005
1070
  return stderr || null;
1006
1071
  }
1007
1072
  async function connectAzure(profile) {
1008
1073
  if (isWindows) {
1009
1074
  const confPath = getConfigPath(profile);
1010
- if (!import_fs6.default.existsSync(confPath)) {
1075
+ if (!import_fs8.default.existsSync(confPath)) {
1011
1076
  return `Config file not found: ${confPath}`;
1012
1077
  }
1013
1078
  await runCmd(`& 'AzureVpn.exe' -i '${psq(confPath)}' 2>&1`);
@@ -1051,7 +1116,7 @@ async function disconnectWireGuard(profile) {
1051
1116
  return scutilStop(profile.serviceName);
1052
1117
  }
1053
1118
  const confPath = getConfigPath(profile);
1054
- const { stderr } = await runCmd(`sudo wg-quick down '${shq2(confPath)}'`);
1119
+ const { stderr } = await runCmd(`sudo wg-quick down '${shq(confPath)}'`);
1055
1120
  return stderr || null;
1056
1121
  }
1057
1122
  async function disconnectOpenVpn(profile) {
@@ -1129,8 +1194,8 @@ async function disconnectVpn(configs, profileId) {
1129
1194
  }
1130
1195
 
1131
1196
  // src/claude-sessions.ts
1132
- var import_fs7 = __toESM(require("fs"));
1133
- var import_path8 = __toESM(require("path"));
1197
+ var import_fs9 = __toESM(require("fs"));
1198
+ var import_path10 = __toESM(require("path"));
1134
1199
  var import_os7 = __toESM(require("os"));
1135
1200
  var import_readline = __toESM(require("readline"));
1136
1201
 
@@ -1158,10 +1223,10 @@ function findProjectDirs(allDirs, filterProjectPath) {
1158
1223
  }
1159
1224
 
1160
1225
  // src/claude-sessions.ts
1161
- var CLAUDE_PROJECTS_DIR = import_path8.default.join(import_os7.default.homedir(), ".claude", "projects");
1226
+ var CLAUDE_PROJECTS_DIR = import_path10.default.join(import_os7.default.homedir(), ".claude", "projects");
1162
1227
  async function parseSessionFile(filePath) {
1163
1228
  try {
1164
- const stream = import_fs7.default.createReadStream(filePath, { encoding: "utf-8" });
1229
+ const stream = import_fs9.default.createReadStream(filePath, { encoding: "utf-8" });
1165
1230
  const rl = import_readline.default.createInterface({ input: stream, crlfDelay: Infinity });
1166
1231
  let firstMessage = "";
1167
1232
  let lastTimestamp = "";
@@ -1209,17 +1274,17 @@ async function parseSessionFile(filePath) {
1209
1274
  }
1210
1275
  async function listClaudeSessions(filterProjectPath) {
1211
1276
  const sessions2 = [];
1212
- if (!import_fs7.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1277
+ if (!import_fs9.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1213
1278
  const isDirSafe = (d) => {
1214
1279
  try {
1215
- return import_fs7.default.statSync(import_path8.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1280
+ return import_fs9.default.statSync(import_path10.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1216
1281
  } catch {
1217
1282
  return false;
1218
1283
  }
1219
1284
  };
1220
1285
  let allEntries;
1221
1286
  try {
1222
- allEntries = import_fs7.default.readdirSync(CLAUDE_PROJECTS_DIR);
1287
+ allEntries = import_fs9.default.readdirSync(CLAUDE_PROJECTS_DIR);
1223
1288
  } catch {
1224
1289
  return sessions2;
1225
1290
  }
@@ -1232,16 +1297,16 @@ async function listClaudeSessions(filterProjectPath) {
1232
1297
  projectDirs = allEntries.filter((d) => isDirSafe(d) && !d.startsWith("-private-"));
1233
1298
  }
1234
1299
  for (const dirName of projectDirs) {
1235
- const dirPath = import_path8.default.join(CLAUDE_PROJECTS_DIR, dirName);
1300
+ const dirPath = import_path10.default.join(CLAUDE_PROJECTS_DIR, dirName);
1236
1301
  let files;
1237
1302
  try {
1238
- files = import_fs7.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1303
+ files = import_fs9.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1239
1304
  } catch {
1240
1305
  continue;
1241
1306
  }
1242
1307
  for (const file of files) {
1243
1308
  const sessionId = file.replace(".jsonl", "");
1244
- const parsed = await parseSessionFile(import_path8.default.join(dirPath, file));
1309
+ const parsed = await parseSessionFile(import_path10.default.join(dirPath, file));
1245
1310
  if (!parsed) continue;
1246
1311
  const projectPath = parsed.cwd || dirName.replace(/^-/, "/").replace(/-/g, "/");
1247
1312
  sessions2.push({
@@ -1261,21 +1326,21 @@ async function listClaudeSessions(filterProjectPath) {
1261
1326
  }
1262
1327
 
1263
1328
  // src/claude-conversation.ts
1264
- var import_fs8 = __toESM(require("fs"));
1265
- var import_path9 = __toESM(require("path"));
1329
+ var import_fs10 = __toESM(require("fs"));
1330
+ var import_path11 = __toESM(require("path"));
1266
1331
  var import_os8 = __toESM(require("os"));
1267
1332
  var import_readline2 = __toESM(require("readline"));
1268
- var CLAUDE_PROJECTS_DIR2 = import_path9.default.join(import_os8.default.homedir(), ".claude", "projects");
1333
+ var CLAUDE_PROJECTS_DIR2 = import_path11.default.join(import_os8.default.homedir(), ".claude", "projects");
1269
1334
  function findProjectDir(projectPath) {
1270
1335
  const encoded = encodeProjectPath(projectPath);
1271
- const dirPath = import_path9.default.join(CLAUDE_PROJECTS_DIR2, encoded);
1272
- if (import_fs8.default.existsSync(dirPath)) return dirPath;
1273
- if (!import_fs8.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1336
+ const dirPath = import_path11.default.join(CLAUDE_PROJECTS_DIR2, encoded);
1337
+ if (import_fs10.default.existsSync(dirPath)) return dirPath;
1338
+ if (!import_fs10.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1274
1339
  let allDirs;
1275
1340
  try {
1276
- allDirs = import_fs8.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1341
+ allDirs = import_fs10.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1277
1342
  try {
1278
- return import_fs8.default.statSync(import_path9.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1343
+ return import_fs10.default.statSync(import_path11.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1279
1344
  } catch {
1280
1345
  return false;
1281
1346
  }
@@ -1285,23 +1350,23 @@ function findProjectDir(projectPath) {
1285
1350
  }
1286
1351
  const matches = findProjectDirs(allDirs, projectPath);
1287
1352
  if (matches.length > 0) {
1288
- return import_path9.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1353
+ return import_path11.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1289
1354
  }
1290
1355
  return null;
1291
1356
  }
1292
1357
  function findSessionFileById(sessionId) {
1293
1358
  if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return null;
1294
- if (!import_fs8.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1359
+ if (!import_fs10.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1295
1360
  let dirs;
1296
1361
  try {
1297
- dirs = import_fs8.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1362
+ dirs = import_fs10.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1298
1363
  } catch {
1299
1364
  return null;
1300
1365
  }
1301
1366
  for (const dir of dirs) {
1302
- const candidate = import_path9.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1367
+ const candidate = import_path11.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1303
1368
  try {
1304
- if (import_fs8.default.statSync(candidate).isFile()) return candidate;
1369
+ if (import_fs10.default.statSync(candidate).isFile()) return candidate;
1305
1370
  } catch {
1306
1371
  }
1307
1372
  }
@@ -1312,20 +1377,20 @@ function findLatestSessionFile(projectPath) {
1312
1377
  if (!dirPath) return null;
1313
1378
  let entries;
1314
1379
  try {
1315
- entries = import_fs8.default.readdirSync(dirPath);
1380
+ entries = import_fs10.default.readdirSync(dirPath);
1316
1381
  } catch {
1317
1382
  return null;
1318
1383
  }
1319
1384
  const files = entries.filter((f) => f.endsWith(".jsonl") && !f.includes("/")).map((f) => {
1320
1385
  try {
1321
- return { name: f, mtime: import_fs8.default.statSync(import_path9.default.join(dirPath, f)).mtimeMs };
1386
+ return { name: f, mtime: import_fs10.default.statSync(import_path11.default.join(dirPath, f)).mtimeMs };
1322
1387
  } catch {
1323
1388
  return null;
1324
1389
  }
1325
1390
  }).filter((f) => f !== null).sort((a, b) => b.mtime - a.mtime);
1326
1391
  if (files.length === 0) return null;
1327
1392
  return {
1328
- filePath: import_path9.default.join(dirPath, files[0].name),
1393
+ filePath: import_path11.default.join(dirPath, files[0].name),
1329
1394
  sessionId: files[0].name.replace(".jsonl", "")
1330
1395
  };
1331
1396
  }
@@ -1335,8 +1400,8 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1335
1400
  if (specificSessionId) {
1336
1401
  sessionId = specificSessionId;
1337
1402
  const dirPath = findProjectDir(projectPath);
1338
- const direct = dirPath ? import_path9.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1339
- if (direct && import_fs8.default.existsSync(direct)) {
1403
+ const direct = dirPath ? import_path11.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1404
+ if (direct && import_fs10.default.existsSync(direct)) {
1340
1405
  filePath = direct;
1341
1406
  } else {
1342
1407
  const byId = findSessionFileById(specificSessionId);
@@ -1350,7 +1415,7 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1350
1415
  sessionId = found.sessionId;
1351
1416
  }
1352
1417
  try {
1353
- const stream = import_fs8.default.createReadStream(filePath, { encoding: "utf-8" });
1418
+ const stream = import_fs10.default.createReadStream(filePath, { encoding: "utf-8" });
1354
1419
  const rl = import_readline2.default.createInterface({ input: stream, crlfDelay: Infinity });
1355
1420
  const messages = [];
1356
1421
  let lineNum = 0;
@@ -1439,6 +1504,7 @@ if (!isConfigured()) {
1439
1504
  }
1440
1505
  var config = loadConfig();
1441
1506
  logger.info({ agentId: config.agentId, serverUrl: config.serverUrl }, "Starting agent");
1507
+ ensurePtyHelperExecutable();
1442
1508
  process.on("unhandledRejection", (reason) => {
1443
1509
  logger.error({ reason }, "Unhandled promise rejection (agent kept alive)");
1444
1510
  });
@@ -1498,7 +1564,18 @@ startLocalControl(LOCAL_CONTROL_PORT, (raw) => {
1498
1564
  socket.on(TERMINAL_OPEN, (payload) => {
1499
1565
  const { sessionId, cols, rows, tmux, launch } = payload;
1500
1566
  if (!sessionId) return;
1501
- const launchSpec = tmux ? buildTmuxLaunch(tmux, launch, detectShell(config.shell)) : void 0;
1567
+ let launchSpec;
1568
+ if (tmux) {
1569
+ const spec = buildTmuxLaunch(tmux, launch);
1570
+ if (!spec) {
1571
+ const msg = "\r\n\x1B[31mtmux is not installed on this machine.\x1B[0m\r\nInstall it to use tmux mirroring:\r\n macOS: brew install tmux\r\n Linux: sudo apt install tmux (or your package manager)\r\n\r\n";
1572
+ socket.emit(TERMINAL_OUTPUT, { sessionId, data: msg });
1573
+ socket.emit(TERMINAL_EXIT, { sessionId, exitCode: 1 });
1574
+ logger.warn({ sessionId, tmux }, "tmux launch requested but tmux not found");
1575
+ return;
1576
+ }
1577
+ launchSpec = spec;
1578
+ }
1502
1579
  createTerminalSession(
1503
1580
  sessionId,
1504
1581
  cols,