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/cli.js +137 -103
- package/dist/cli.js.map +1 -1
- package/dist/index.js +139 -101
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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,
|
|
364
|
-
return { session, pid: parseInt(pid, 10) || 0, path:
|
|
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
|
|
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
|
|
393
|
-
if (trimmed)
|
|
394
|
-
return { file: "wsl.exe", args };
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
|
|
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,
|
|
428
|
-
var BASH_INIT = (0,
|
|
429
|
-
var ZSH_DIR = (0,
|
|
430
|
-
var ZSH_RC = (0,
|
|
431
|
-
var ZSH_ENV = (0,
|
|
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,
|
|
435
|
-
if ((0,
|
|
461
|
+
const versionFile = (0, import_path5.join)(INIT_DIR, ".version");
|
|
462
|
+
if ((0, import_fs5.existsSync)(versionFile)) {
|
|
436
463
|
try {
|
|
437
|
-
if ((0,
|
|
464
|
+
if ((0, import_fs5.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
|
|
438
465
|
} catch {
|
|
439
466
|
}
|
|
440
467
|
}
|
|
441
|
-
(0,
|
|
442
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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:
|
|
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
|
|
712
|
-
var
|
|
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 =
|
|
741
|
+
var SECRET_DIR = import_path7.default.join(import_os5.default.homedir(), ".crc-agent");
|
|
715
742
|
function isInSecretDir(resolved) {
|
|
716
|
-
const rel =
|
|
717
|
-
return rel === "" || !rel.startsWith(".." +
|
|
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 =
|
|
748
|
+
const resolved = import_path7.default.resolve(dirPath);
|
|
722
749
|
if (isInSecretDir(resolved)) {
|
|
723
750
|
return { entries: [], error: "Access denied" };
|
|
724
751
|
}
|
|
725
|
-
if (!
|
|
752
|
+
if (!import_fs6.default.existsSync(resolved)) {
|
|
726
753
|
return { entries: [], error: "Path does not exist" };
|
|
727
754
|
}
|
|
728
|
-
const stat =
|
|
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 =
|
|
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 =
|
|
737
|
-
const s =
|
|
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 =
|
|
786
|
+
const resolved = import_path7.default.resolve(filePath);
|
|
760
787
|
if (isInSecretDir(resolved)) {
|
|
761
788
|
return { error: "Access denied" };
|
|
762
789
|
}
|
|
763
|
-
if (!
|
|
790
|
+
if (!import_fs6.default.existsSync(resolved)) {
|
|
764
791
|
return { error: "File does not exist" };
|
|
765
792
|
}
|
|
766
|
-
const stat =
|
|
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 =
|
|
774
|
-
const fileStream =
|
|
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
|
|
808
|
-
var
|
|
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 =
|
|
813
|
-
function
|
|
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 (
|
|
832
|
-
return
|
|
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 '${
|
|
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 '${
|
|
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 '${
|
|
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 '${
|
|
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 (!
|
|
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 '${
|
|
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 (!
|
|
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 (!
|
|
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 '${
|
|
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 (!
|
|
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 '${
|
|
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
|
|
1133
|
-
var
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
1239
|
+
if (!import_fs8.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
|
|
1213
1240
|
const isDirSafe = (d) => {
|
|
1214
1241
|
try {
|
|
1215
|
-
return
|
|
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 =
|
|
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 =
|
|
1262
|
+
const dirPath = import_path9.default.join(CLAUDE_PROJECTS_DIR, dirName);
|
|
1236
1263
|
let files;
|
|
1237
1264
|
try {
|
|
1238
|
-
files =
|
|
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(
|
|
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
|
|
1265
|
-
var
|
|
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 =
|
|
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 =
|
|
1272
|
-
if (
|
|
1273
|
-
if (!
|
|
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 =
|
|
1303
|
+
allDirs = import_fs9.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
|
|
1277
1304
|
try {
|
|
1278
|
-
return
|
|
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
|
|
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 (!
|
|
1321
|
+
if (!import_fs9.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
|
|
1295
1322
|
let dirs;
|
|
1296
1323
|
try {
|
|
1297
|
-
dirs =
|
|
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 =
|
|
1329
|
+
const candidate = import_path10.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
|
|
1303
1330
|
try {
|
|
1304
|
-
if (
|
|
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 =
|
|
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:
|
|
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:
|
|
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 ?
|
|
1339
|
-
if (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 =
|
|
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
|
-
|
|
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,
|