cli-remote-agent 1.0.5 → 1.0.6

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
@@ -254,6 +254,8 @@ function startLocalControl(port, onHook) {
254
254
  // src/tmux.ts
255
255
  var import_child_process2 = require("child_process");
256
256
  var import_util2 = require("util");
257
+ var import_fs4 = __toESM(require("fs"));
258
+ var import_path4 = __toESM(require("path"));
257
259
 
258
260
  // src/claude-live.ts
259
261
  var import_fs3 = __toESM(require("fs"));
@@ -320,8 +322,28 @@ function isDescendantOf(pid, ancestor, parents) {
320
322
  // src/tmux.ts
321
323
  var execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
322
324
  var IS_WIN = process.platform === "win32";
325
+ var cachedTmuxPath;
326
+ function resolveTmuxPath() {
327
+ if (cachedTmuxPath !== void 0) return cachedTmuxPath;
328
+ const dirs = (process.env.PATH || "").split(import_path4.default.delimiter).filter(Boolean);
329
+ for (const extra of ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/opt/local/bin"]) {
330
+ if (!dirs.includes(extra)) dirs.push(extra);
331
+ }
332
+ for (const dir of dirs) {
333
+ const candidate = import_path4.default.join(dir, "tmux");
334
+ try {
335
+ import_fs4.default.accessSync(candidate, import_fs4.default.constants.X_OK);
336
+ cachedTmuxPath = candidate;
337
+ return candidate;
338
+ } catch {
339
+ }
340
+ }
341
+ cachedTmuxPath = null;
342
+ return null;
343
+ }
323
344
  function tmuxCmd(args) {
324
- return IS_WIN ? { file: "wsl.exe", args: ["tmux", ...args] } : { file: "tmux", args };
345
+ if (IS_WIN) return { file: "wsl.exe", args: ["tmux", ...args] };
346
+ return { file: resolveTmuxPath() || "tmux", args };
325
347
  }
326
348
  async function listTmuxSessions() {
327
349
  const fmt = "#{session_name} #{session_windows} #{session_attached} #{session_activity}";
@@ -360,8 +382,8 @@ async function enrichWithPanesAndClaude(sessions2) {
360
382
  const { file, args } = tmuxCmd(["list-panes", "-a", "-F", paneFmt]);
361
383
  const { stdout } = await execFileAsync2(file, args, { timeout: 6e3 });
362
384
  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" };
385
+ const [session, pid, path9, activeFlags] = line.split(" ");
386
+ return { session, pid: parseInt(pid, 10) || 0, path: path9 || "", active: activeFlags === "11" };
365
387
  });
366
388
  const byName = new Map(sessions2.map((s) => [s.name, s]));
367
389
  for (const pane of panes) {
@@ -383,21 +405,35 @@ async function enrichWithPanesAndClaude(sessions2) {
383
405
  } catch {
384
406
  }
385
407
  }
386
- function shq(v) {
387
- return `'${v.replace(/'/g, `'\\''`)}'`;
388
- }
389
- function buildTmuxLaunch(name, launch, shell) {
408
+ function buildTmuxLaunch(name, launch) {
390
409
  const trimmed = launch && launch.trim() ? launch.trim() : void 0;
391
410
  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] };
411
+ const args2 = ["tmux", "new-session", "-A", "-s", name];
412
+ if (trimmed) args2.push(trimmed);
413
+ return { file: "wsl.exe", args: args2 };
414
+ }
415
+ const tmuxBin = resolveTmuxPath();
416
+ if (!tmuxBin) return null;
417
+ execFileAsync2(tmuxBin, ["set-option", "-g", "aggressive-resize", "on"], { timeout: 4e3 }).catch(
418
+ () => {
419
+ }
420
+ );
421
+ const args = ["new-session", "-A", "-s", name];
422
+ if (trimmed) args.push(trimmed);
423
+ return { file: tmuxBin, args };
399
424
  }
400
425
 
426
+ // src/heartbeat.ts
427
+ var import_os4 = __toESM(require("os"));
428
+ var import_path6 = __toESM(require("path"));
429
+ var import_child_process4 = require("child_process");
430
+
431
+ // src/pty-session.ts
432
+ var pty = __toESM(require("node-pty"));
433
+ var import_fs5 = require("fs");
434
+ var import_path5 = require("path");
435
+ var import_os3 = require("os");
436
+
401
437
  // src/shell.ts
402
438
  var import_child_process3 = require("child_process");
403
439
  function detectShell(preference) {
@@ -413,33 +449,24 @@ function detectShell(preference) {
413
449
  return process.env.SHELL || "/bin/bash";
414
450
  }
415
451
 
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
452
  // 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
453
  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");
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");
432
459
  var INIT_VERSION = "2";
433
460
  function ensureInitFiles() {
434
- const versionFile = (0, import_path4.join)(INIT_DIR, ".version");
435
- if ((0, import_fs4.existsSync)(versionFile)) {
461
+ const versionFile = (0, import_path5.join)(INIT_DIR, ".version");
462
+ if ((0, import_fs5.existsSync)(versionFile)) {
436
463
  try {
437
- if ((0, import_fs4.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
464
+ if ((0, import_fs5.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
438
465
  } catch {
439
466
  }
440
467
  }
441
- (0, import_fs4.mkdirSync)(ZSH_DIR, { recursive: true });
442
- (0, import_fs4.writeFileSync)(BASH_INIT, [
468
+ (0, import_fs5.mkdirSync)(ZSH_DIR, { recursive: true });
469
+ (0, import_fs5.writeFileSync)(BASH_INIT, [
443
470
  "[ -f ~/.bash_profile ] && source ~/.bash_profile",
444
471
  "[ -f ~/.bashrc ] && source ~/.bashrc",
445
472
  "__crc_s=$SECONDS",
@@ -450,11 +477,11 @@ function ensureInitFiles() {
450
477
  `PROMPT_COMMAND='[ "$__crc_armed" = 1 ] && [ \${__crc_elapsed:-0} -ge ${THRESHOLD} ] && printf "\\a"; __crc_armed=1; eval "$__crc_orig_pc"'`,
451
478
  ""
452
479
  ].join("\n"), "utf-8");
453
- (0, import_fs4.writeFileSync)(ZSH_ENV, [
480
+ (0, import_fs5.writeFileSync)(ZSH_ENV, [
454
481
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshenv" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshenv"',
455
482
  ""
456
483
  ].join("\n"), "utf-8");
457
- (0, import_fs4.writeFileSync)(ZSH_RC, [
484
+ (0, import_fs5.writeFileSync)(ZSH_RC, [
458
485
  '[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshrc" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshrc"',
459
486
  "__crc_s=$SECONDS",
460
487
  "__crc_armed=0",
@@ -467,14 +494,14 @@ function ensureInitFiles() {
467
494
  "fi",
468
495
  ""
469
496
  ].join("\n"), "utf-8");
470
- (0, import_fs4.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
497
+ (0, import_fs5.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
471
498
  }
472
499
  function resolveCwd(cwd) {
473
500
  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
501
  for (const c of candidates) {
475
502
  if (c) {
476
503
  try {
477
- if ((0, import_fs4.existsSync)(c)) return c;
504
+ if ((0, import_fs5.existsSync)(c)) return c;
478
505
  } catch {
479
506
  }
480
507
  }
@@ -700,7 +727,7 @@ function buildHeartbeat(homeDir) {
700
727
  memoryUsage: Math.round((totalMem - freeMem) / totalMem * 100),
701
728
  uptime: Math.round(import_os4.default.uptime()),
702
729
  activeSessions: getActiveSessionCount(),
703
- pathSeparator: import_path5.default.sep,
730
+ pathSeparator: import_path6.default.sep,
704
731
  homeDirectory: homeDir || import_os4.default.homedir(),
705
732
  rootPaths: getRootPaths(),
706
733
  capabilities: { terminal: true, fileTransfer: false }
@@ -708,33 +735,33 @@ function buildHeartbeat(homeDir) {
708
735
  }
709
736
 
710
737
  // src/file-explorer.ts
711
- var import_fs5 = __toESM(require("fs"));
712
- var import_path6 = __toESM(require("path"));
738
+ var import_fs6 = __toESM(require("fs"));
739
+ var import_path7 = __toESM(require("path"));
713
740
  var import_os5 = __toESM(require("os"));
714
- var SECRET_DIR = import_path6.default.join(import_os5.default.homedir(), ".crc-agent");
741
+ var SECRET_DIR = import_path7.default.join(import_os5.default.homedir(), ".crc-agent");
715
742
  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);
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);
718
745
  }
719
746
  function listDirectory(dirPath) {
720
747
  try {
721
- const resolved = import_path6.default.resolve(dirPath);
748
+ const resolved = import_path7.default.resolve(dirPath);
722
749
  if (isInSecretDir(resolved)) {
723
750
  return { entries: [], error: "Access denied" };
724
751
  }
725
- if (!import_fs5.default.existsSync(resolved)) {
752
+ if (!import_fs6.default.existsSync(resolved)) {
726
753
  return { entries: [], error: "Path does not exist" };
727
754
  }
728
- const stat = import_fs5.default.statSync(resolved);
755
+ const stat = import_fs6.default.statSync(resolved);
729
756
  if (!stat.isDirectory()) {
730
757
  return { entries: [], error: "Not a directory" };
731
758
  }
732
- const dirents = import_fs5.default.readdirSync(resolved, { withFileTypes: true });
759
+ const dirents = import_fs6.default.readdirSync(resolved, { withFileTypes: true });
733
760
  const entries = [];
734
761
  for (const dirent of dirents) {
735
762
  try {
736
- const fullPath = import_path6.default.join(resolved, dirent.name);
737
- const s = import_fs5.default.statSync(fullPath);
763
+ const fullPath = import_path7.default.join(resolved, dirent.name);
764
+ const s = import_fs6.default.statSync(fullPath);
738
765
  entries.push({
739
766
  name: dirent.name,
740
767
  isDirectory: dirent.isDirectory(),
@@ -756,22 +783,22 @@ function listDirectory(dirPath) {
756
783
  }
757
784
  async function downloadFile(filePath, serverUrl, secret, agentId) {
758
785
  try {
759
- const resolved = import_path6.default.resolve(filePath);
786
+ const resolved = import_path7.default.resolve(filePath);
760
787
  if (isInSecretDir(resolved)) {
761
788
  return { error: "Access denied" };
762
789
  }
763
- if (!import_fs5.default.existsSync(resolved)) {
790
+ if (!import_fs6.default.existsSync(resolved)) {
764
791
  return { error: "File does not exist" };
765
792
  }
766
- const stat = import_fs5.default.statSync(resolved);
793
+ const stat = import_fs6.default.statSync(resolved);
767
794
  if (stat.isDirectory()) {
768
795
  return { error: "Cannot download a directory" };
769
796
  }
770
797
  if (stat.size > FILE_MAX_SIZE) {
771
798
  return { error: `File too large (max ${FILE_MAX_SIZE} bytes)` };
772
799
  }
773
- const fileName = import_path6.default.basename(resolved);
774
- const fileStream = import_fs5.default.createReadStream(resolved);
800
+ const fileName = import_path7.default.basename(resolved);
801
+ const fileStream = import_fs6.default.createReadStream(resolved);
775
802
  const baseUrl = serverUrl.replace("wss://", "https://").replace("ws://", "http://");
776
803
  const url = `${baseUrl}/api/files/receive`;
777
804
  const res = await fetch(url, {
@@ -804,13 +831,13 @@ async function downloadFile(filePath, serverUrl, secret, agentId) {
804
831
  // src/vpn-manager.ts
805
832
  var import_child_process5 = require("child_process");
806
833
  var import_util3 = require("util");
807
- var import_fs6 = __toESM(require("fs"));
808
- var import_path7 = __toESM(require("path"));
834
+ var import_fs7 = __toESM(require("fs"));
835
+ var import_path8 = __toESM(require("path"));
809
836
  var import_os6 = __toESM(require("os"));
810
837
  var execAsync = (0, import_util3.promisify)(import_child_process5.exec);
811
838
  var isWindows = process.platform === "win32";
812
- var VPN_DIR = import_path7.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
813
- function shq2(value) {
839
+ var VPN_DIR = import_path8.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
840
+ function shq(value) {
814
841
  return value.replace(/'/g, "'\\''");
815
842
  }
816
843
  function psq(value) {
@@ -828,11 +855,11 @@ async function runCmd(cmd, shell) {
828
855
  }
829
856
  function getConfigPath(profile) {
830
857
  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);
858
+ if (import_path8.default.isAbsolute(file)) return file;
859
+ return import_path8.default.join(VPN_DIR, file);
833
860
  }
834
861
  async function getScutilStatus(serviceName) {
835
- const { stdout } = await runCmd(`scutil --nc status '${shq2(serviceName)}'`);
862
+ const { stdout } = await runCmd(`scutil --nc status '${shq(serviceName)}'`);
836
863
  const firstLine = stdout.trim().split("\n")[0];
837
864
  if (firstLine === "Connected") return "connected";
838
865
  if (firstLine === "Connecting") return "connecting";
@@ -840,11 +867,11 @@ async function getScutilStatus(serviceName) {
840
867
  return "disconnected";
841
868
  }
842
869
  async function scutilStart(serviceName) {
843
- const { stderr } = await runCmd(`scutil --nc start '${shq2(serviceName)}'`);
870
+ const { stderr } = await runCmd(`scutil --nc start '${shq(serviceName)}'`);
844
871
  return stderr || null;
845
872
  }
846
873
  async function scutilStop(serviceName) {
847
- const { stderr } = await runCmd(`scutil --nc stop '${shq2(serviceName)}'`);
874
+ const { stderr } = await runCmd(`scutil --nc stop '${shq(serviceName)}'`);
848
875
  return stderr || null;
849
876
  }
850
877
  async function runOsascript(script) {
@@ -893,7 +920,7 @@ async function getWireGuardStatus(profile) {
893
920
  return getScutilStatus(profile.serviceName);
894
921
  }
895
922
  const tunnelName = profile.tunnelName || profile.id;
896
- const { stdout } = await runCmd(`wg show '${shq2(tunnelName)}' 2>/dev/null`);
923
+ const { stdout } = await runCmd(`wg show '${shq(tunnelName)}' 2>/dev/null`);
897
924
  return stdout.trim() ? "connected" : "disconnected";
898
925
  }
899
926
  async function getOpenVpnStatus(profile) {
@@ -945,7 +972,7 @@ async function connectWireGuard(profile) {
945
972
  const tunnelName = profile.tunnelName || profile.id;
946
973
  if (isWindows) {
947
974
  const confPath2 = getConfigPath(profile);
948
- if (!import_fs6.default.existsSync(confPath2)) {
975
+ if (!import_fs7.default.existsSync(confPath2)) {
949
976
  return `Config file not found: ${confPath2}`;
950
977
  }
951
978
  const script = `
@@ -965,13 +992,13 @@ async function connectWireGuard(profile) {
965
992
  return scutilStart(profile.serviceName);
966
993
  }
967
994
  const confPath = getConfigPath(profile);
968
- const { stderr } = await runCmd(`sudo wg-quick up '${shq2(confPath)}'`);
995
+ const { stderr } = await runCmd(`sudo wg-quick up '${shq(confPath)}'`);
969
996
  return stderr || null;
970
997
  }
971
998
  async function connectOpenVpn(profile) {
972
999
  if (isWindows) {
973
1000
  const confPath2 = getConfigPath(profile);
974
- if (!import_fs6.default.existsSync(confPath2)) {
1001
+ if (!import_fs7.default.existsSync(confPath2)) {
975
1002
  return `Config file not found: ${confPath2}`;
976
1003
  }
977
1004
  const connector = "C:\\Program Files\\OpenVPN Connect\\ovpnconnector.exe";
@@ -998,16 +1025,16 @@ async function connectOpenVpn(profile) {
998
1025
  return scutilStart(profile.serviceName);
999
1026
  }
1000
1027
  const confPath = getConfigPath(profile);
1001
- if (!import_fs6.default.existsSync(confPath)) {
1028
+ if (!import_fs7.default.existsSync(confPath)) {
1002
1029
  return `Config file not found: ${confPath}`;
1003
1030
  }
1004
- const { stderr } = await runCmd(`sudo openvpn --config '${shq2(confPath)}' --daemon`);
1031
+ const { stderr } = await runCmd(`sudo openvpn --config '${shq(confPath)}' --daemon`);
1005
1032
  return stderr || null;
1006
1033
  }
1007
1034
  async function connectAzure(profile) {
1008
1035
  if (isWindows) {
1009
1036
  const confPath = getConfigPath(profile);
1010
- if (!import_fs6.default.existsSync(confPath)) {
1037
+ if (!import_fs7.default.existsSync(confPath)) {
1011
1038
  return `Config file not found: ${confPath}`;
1012
1039
  }
1013
1040
  await runCmd(`& 'AzureVpn.exe' -i '${psq(confPath)}' 2>&1`);
@@ -1051,7 +1078,7 @@ async function disconnectWireGuard(profile) {
1051
1078
  return scutilStop(profile.serviceName);
1052
1079
  }
1053
1080
  const confPath = getConfigPath(profile);
1054
- const { stderr } = await runCmd(`sudo wg-quick down '${shq2(confPath)}'`);
1081
+ const { stderr } = await runCmd(`sudo wg-quick down '${shq(confPath)}'`);
1055
1082
  return stderr || null;
1056
1083
  }
1057
1084
  async function disconnectOpenVpn(profile) {
@@ -1129,8 +1156,8 @@ async function disconnectVpn(configs, profileId) {
1129
1156
  }
1130
1157
 
1131
1158
  // src/claude-sessions.ts
1132
- var import_fs7 = __toESM(require("fs"));
1133
- var import_path8 = __toESM(require("path"));
1159
+ var import_fs8 = __toESM(require("fs"));
1160
+ var import_path9 = __toESM(require("path"));
1134
1161
  var import_os7 = __toESM(require("os"));
1135
1162
  var import_readline = __toESM(require("readline"));
1136
1163
 
@@ -1158,10 +1185,10 @@ function findProjectDirs(allDirs, filterProjectPath) {
1158
1185
  }
1159
1186
 
1160
1187
  // src/claude-sessions.ts
1161
- var CLAUDE_PROJECTS_DIR = import_path8.default.join(import_os7.default.homedir(), ".claude", "projects");
1188
+ var CLAUDE_PROJECTS_DIR = import_path9.default.join(import_os7.default.homedir(), ".claude", "projects");
1162
1189
  async function parseSessionFile(filePath) {
1163
1190
  try {
1164
- const stream = import_fs7.default.createReadStream(filePath, { encoding: "utf-8" });
1191
+ const stream = import_fs8.default.createReadStream(filePath, { encoding: "utf-8" });
1165
1192
  const rl = import_readline.default.createInterface({ input: stream, crlfDelay: Infinity });
1166
1193
  let firstMessage = "";
1167
1194
  let lastTimestamp = "";
@@ -1209,17 +1236,17 @@ async function parseSessionFile(filePath) {
1209
1236
  }
1210
1237
  async function listClaudeSessions(filterProjectPath) {
1211
1238
  const sessions2 = [];
1212
- if (!import_fs7.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1239
+ if (!import_fs8.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
1213
1240
  const isDirSafe = (d) => {
1214
1241
  try {
1215
- return import_fs7.default.statSync(import_path8.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1242
+ return import_fs8.default.statSync(import_path9.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
1216
1243
  } catch {
1217
1244
  return false;
1218
1245
  }
1219
1246
  };
1220
1247
  let allEntries;
1221
1248
  try {
1222
- allEntries = import_fs7.default.readdirSync(CLAUDE_PROJECTS_DIR);
1249
+ allEntries = import_fs8.default.readdirSync(CLAUDE_PROJECTS_DIR);
1223
1250
  } catch {
1224
1251
  return sessions2;
1225
1252
  }
@@ -1232,16 +1259,16 @@ async function listClaudeSessions(filterProjectPath) {
1232
1259
  projectDirs = allEntries.filter((d) => isDirSafe(d) && !d.startsWith("-private-"));
1233
1260
  }
1234
1261
  for (const dirName of projectDirs) {
1235
- const dirPath = import_path8.default.join(CLAUDE_PROJECTS_DIR, dirName);
1262
+ const dirPath = import_path9.default.join(CLAUDE_PROJECTS_DIR, dirName);
1236
1263
  let files;
1237
1264
  try {
1238
- files = import_fs7.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1265
+ files = import_fs8.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
1239
1266
  } catch {
1240
1267
  continue;
1241
1268
  }
1242
1269
  for (const file of files) {
1243
1270
  const sessionId = file.replace(".jsonl", "");
1244
- const parsed = await parseSessionFile(import_path8.default.join(dirPath, file));
1271
+ const parsed = await parseSessionFile(import_path9.default.join(dirPath, file));
1245
1272
  if (!parsed) continue;
1246
1273
  const projectPath = parsed.cwd || dirName.replace(/^-/, "/").replace(/-/g, "/");
1247
1274
  sessions2.push({
@@ -1261,21 +1288,21 @@ async function listClaudeSessions(filterProjectPath) {
1261
1288
  }
1262
1289
 
1263
1290
  // src/claude-conversation.ts
1264
- var import_fs8 = __toESM(require("fs"));
1265
- var import_path9 = __toESM(require("path"));
1291
+ var import_fs9 = __toESM(require("fs"));
1292
+ var import_path10 = __toESM(require("path"));
1266
1293
  var import_os8 = __toESM(require("os"));
1267
1294
  var import_readline2 = __toESM(require("readline"));
1268
- var CLAUDE_PROJECTS_DIR2 = import_path9.default.join(import_os8.default.homedir(), ".claude", "projects");
1295
+ var CLAUDE_PROJECTS_DIR2 = import_path10.default.join(import_os8.default.homedir(), ".claude", "projects");
1269
1296
  function findProjectDir(projectPath) {
1270
1297
  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;
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;
1274
1301
  let allDirs;
1275
1302
  try {
1276
- allDirs = import_fs8.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1303
+ allDirs = import_fs9.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
1277
1304
  try {
1278
- return import_fs8.default.statSync(import_path9.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1305
+ return import_fs9.default.statSync(import_path10.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
1279
1306
  } catch {
1280
1307
  return false;
1281
1308
  }
@@ -1285,23 +1312,23 @@ function findProjectDir(projectPath) {
1285
1312
  }
1286
1313
  const matches = findProjectDirs(allDirs, projectPath);
1287
1314
  if (matches.length > 0) {
1288
- return import_path9.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1315
+ return import_path10.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
1289
1316
  }
1290
1317
  return null;
1291
1318
  }
1292
1319
  function findSessionFileById(sessionId) {
1293
1320
  if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return null;
1294
- if (!import_fs8.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1321
+ if (!import_fs9.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
1295
1322
  let dirs;
1296
1323
  try {
1297
- dirs = import_fs8.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1324
+ dirs = import_fs9.default.readdirSync(CLAUDE_PROJECTS_DIR2);
1298
1325
  } catch {
1299
1326
  return null;
1300
1327
  }
1301
1328
  for (const dir of dirs) {
1302
- const candidate = import_path9.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1329
+ const candidate = import_path10.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
1303
1330
  try {
1304
- if (import_fs8.default.statSync(candidate).isFile()) return candidate;
1331
+ if (import_fs9.default.statSync(candidate).isFile()) return candidate;
1305
1332
  } catch {
1306
1333
  }
1307
1334
  }
@@ -1312,20 +1339,20 @@ function findLatestSessionFile(projectPath) {
1312
1339
  if (!dirPath) return null;
1313
1340
  let entries;
1314
1341
  try {
1315
- entries = import_fs8.default.readdirSync(dirPath);
1342
+ entries = import_fs9.default.readdirSync(dirPath);
1316
1343
  } catch {
1317
1344
  return null;
1318
1345
  }
1319
1346
  const files = entries.filter((f) => f.endsWith(".jsonl") && !f.includes("/")).map((f) => {
1320
1347
  try {
1321
- return { name: f, mtime: import_fs8.default.statSync(import_path9.default.join(dirPath, f)).mtimeMs };
1348
+ return { name: f, mtime: import_fs9.default.statSync(import_path10.default.join(dirPath, f)).mtimeMs };
1322
1349
  } catch {
1323
1350
  return null;
1324
1351
  }
1325
1352
  }).filter((f) => f !== null).sort((a, b) => b.mtime - a.mtime);
1326
1353
  if (files.length === 0) return null;
1327
1354
  return {
1328
- filePath: import_path9.default.join(dirPath, files[0].name),
1355
+ filePath: import_path10.default.join(dirPath, files[0].name),
1329
1356
  sessionId: files[0].name.replace(".jsonl", "")
1330
1357
  };
1331
1358
  }
@@ -1335,8 +1362,8 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1335
1362
  if (specificSessionId) {
1336
1363
  sessionId = specificSessionId;
1337
1364
  const dirPath = findProjectDir(projectPath);
1338
- const direct = dirPath ? import_path9.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1339
- if (direct && import_fs8.default.existsSync(direct)) {
1365
+ const direct = dirPath ? import_path10.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
1366
+ if (direct && import_fs9.default.existsSync(direct)) {
1340
1367
  filePath = direct;
1341
1368
  } else {
1342
1369
  const byId = findSessionFileById(specificSessionId);
@@ -1350,7 +1377,7 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
1350
1377
  sessionId = found.sessionId;
1351
1378
  }
1352
1379
  try {
1353
- const stream = import_fs8.default.createReadStream(filePath, { encoding: "utf-8" });
1380
+ const stream = import_fs9.default.createReadStream(filePath, { encoding: "utf-8" });
1354
1381
  const rl = import_readline2.default.createInterface({ input: stream, crlfDelay: Infinity });
1355
1382
  const messages = [];
1356
1383
  let lineNum = 0;
@@ -1498,7 +1525,18 @@ startLocalControl(LOCAL_CONTROL_PORT, (raw) => {
1498
1525
  socket.on(TERMINAL_OPEN, (payload) => {
1499
1526
  const { sessionId, cols, rows, tmux, launch } = payload;
1500
1527
  if (!sessionId) return;
1501
- const launchSpec = tmux ? buildTmuxLaunch(tmux, launch, detectShell(config.shell)) : void 0;
1528
+ let launchSpec;
1529
+ if (tmux) {
1530
+ const spec = buildTmuxLaunch(tmux, launch);
1531
+ if (!spec) {
1532
+ 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";
1533
+ socket.emit(TERMINAL_OUTPUT, { sessionId, data: msg });
1534
+ socket.emit(TERMINAL_EXIT, { sessionId, exitCode: 1 });
1535
+ logger.warn({ sessionId, tmux }, "tmux launch requested but tmux not found");
1536
+ return;
1537
+ }
1538
+ launchSpec = spec;
1539
+ }
1502
1540
  createTerminalSession(
1503
1541
  sessionId,
1504
1542
  cols,