cli-remote-agent 1.0.7 → 1.0.10
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 +194 -105
- package/dist/cli.js.map +1 -1
- package/dist/index.js +174 -95
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -472,6 +472,60 @@ var init_pty_helper_fix = __esm({
|
|
|
472
472
|
}
|
|
473
473
|
});
|
|
474
474
|
|
|
475
|
+
// src/single-instance.ts
|
|
476
|
+
function isRunningAgent(pid) {
|
|
477
|
+
try {
|
|
478
|
+
process.kill(pid, 0);
|
|
479
|
+
} catch {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
if (process.platform === "win32") return true;
|
|
483
|
+
try {
|
|
484
|
+
const out = (0, import_child_process.execFileSync)("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" });
|
|
485
|
+
return /crc-agent|cli-remote-agent|agent[\/\\](dist|src)[\/\\](cli|index)/.test(out);
|
|
486
|
+
} catch {
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function acquireSingleInstanceLock() {
|
|
491
|
+
if (process.env.CRC_ALLOW_MULTIPLE === "1") return { ok: true };
|
|
492
|
+
try {
|
|
493
|
+
if (import_fs5.default.existsSync(LOCK_FILE)) {
|
|
494
|
+
const prev = parseInt(import_fs5.default.readFileSync(LOCK_FILE, "utf8").trim(), 10);
|
|
495
|
+
if (prev && prev !== process.pid && isRunningAgent(prev)) {
|
|
496
|
+
return { ok: false, pid: prev };
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
import_fs5.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
500
|
+
import_fs5.default.writeFileSync(LOCK_FILE, String(process.pid));
|
|
501
|
+
const release = () => {
|
|
502
|
+
try {
|
|
503
|
+
if (parseInt(import_fs5.default.readFileSync(LOCK_FILE, "utf8").trim(), 10) === process.pid) {
|
|
504
|
+
import_fs5.default.unlinkSync(LOCK_FILE);
|
|
505
|
+
}
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
process.on("exit", release);
|
|
510
|
+
return { ok: true };
|
|
511
|
+
} catch (err) {
|
|
512
|
+
logger.warn({ error: err?.message }, "Could not manage single-instance lock");
|
|
513
|
+
return { ok: true };
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
var import_fs5, import_path4, import_child_process, LOCK_FILE;
|
|
517
|
+
var init_single_instance = __esm({
|
|
518
|
+
"src/single-instance.ts"() {
|
|
519
|
+
"use strict";
|
|
520
|
+
import_fs5 = __toESM(require("fs"));
|
|
521
|
+
import_path4 = __toESM(require("path"));
|
|
522
|
+
import_child_process = require("child_process");
|
|
523
|
+
init_config();
|
|
524
|
+
init_logger();
|
|
525
|
+
LOCK_FILE = import_path4.default.join(CONFIG_DIR, "agent.lock");
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
475
529
|
// src/claude-live.ts
|
|
476
530
|
function isAlive(pid) {
|
|
477
531
|
try {
|
|
@@ -484,18 +538,19 @@ function isAlive(pid) {
|
|
|
484
538
|
function getLiveClaudeSessions() {
|
|
485
539
|
let files;
|
|
486
540
|
try {
|
|
487
|
-
files =
|
|
541
|
+
files = import_fs6.default.readdirSync(LIVE_SESSIONS_DIR).filter((f) => f.endsWith(".json"));
|
|
488
542
|
} catch {
|
|
489
543
|
return [];
|
|
490
544
|
}
|
|
491
545
|
const out = [];
|
|
492
546
|
for (const f of files) {
|
|
493
547
|
try {
|
|
494
|
-
const obj = JSON.parse(
|
|
548
|
+
const obj = JSON.parse(import_fs6.default.readFileSync(import_path5.default.join(LIVE_SESSIONS_DIR, f), "utf-8"));
|
|
495
549
|
if (typeof obj?.pid !== "number" || !isAlive(obj.pid)) continue;
|
|
496
550
|
out.push({
|
|
497
551
|
pid: obj.pid,
|
|
498
552
|
cwd: typeof obj.cwd === "string" ? obj.cwd : "",
|
|
553
|
+
sessionId: typeof obj.sessionId === "string" ? obj.sessionId : void 0,
|
|
499
554
|
name: typeof obj.name === "string" ? obj.name : void 0,
|
|
500
555
|
status: typeof obj.status === "string" ? obj.status : void 0,
|
|
501
556
|
updatedAt: typeof obj.updatedAt === "number" ? obj.updatedAt : void 0
|
|
@@ -526,31 +581,40 @@ function isDescendantOf(pid, ancestor, parents) {
|
|
|
526
581
|
}
|
|
527
582
|
return false;
|
|
528
583
|
}
|
|
529
|
-
|
|
584
|
+
async function findClaudeSessionForPtyPid(ptyPid) {
|
|
585
|
+
const live = getLiveClaudeSessions().filter((s) => s.sessionId);
|
|
586
|
+
if (live.length === 0) return null;
|
|
587
|
+
const parents = await getPidParents();
|
|
588
|
+
for (let i = live.length - 1; i >= 0; i--) {
|
|
589
|
+
if (isDescendantOf(live[i].pid, ptyPid, parents)) return live[i].sessionId;
|
|
590
|
+
}
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
var import_fs6, import_path5, import_os2, import_child_process2, import_util, execFileAsync, LIVE_SESSIONS_DIR;
|
|
530
594
|
var init_claude_live = __esm({
|
|
531
595
|
"src/claude-live.ts"() {
|
|
532
596
|
"use strict";
|
|
533
|
-
|
|
534
|
-
|
|
597
|
+
import_fs6 = __toESM(require("fs"));
|
|
598
|
+
import_path5 = __toESM(require("path"));
|
|
535
599
|
import_os2 = __toESM(require("os"));
|
|
536
|
-
|
|
600
|
+
import_child_process2 = require("child_process");
|
|
537
601
|
import_util = require("util");
|
|
538
|
-
execFileAsync = (0, import_util.promisify)(
|
|
539
|
-
LIVE_SESSIONS_DIR =
|
|
602
|
+
execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
|
|
603
|
+
LIVE_SESSIONS_DIR = import_path5.default.join(import_os2.default.homedir(), ".claude", "sessions");
|
|
540
604
|
}
|
|
541
605
|
});
|
|
542
606
|
|
|
543
607
|
// src/tmux.ts
|
|
544
608
|
function resolveTmuxPath() {
|
|
545
609
|
if (cachedTmuxPath !== void 0) return cachedTmuxPath;
|
|
546
|
-
const dirs = (process.env.PATH || "").split(
|
|
610
|
+
const dirs = (process.env.PATH || "").split(import_path6.default.delimiter).filter(Boolean);
|
|
547
611
|
for (const extra of ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/opt/local/bin"]) {
|
|
548
612
|
if (!dirs.includes(extra)) dirs.push(extra);
|
|
549
613
|
}
|
|
550
614
|
for (const dir of dirs) {
|
|
551
|
-
const candidate =
|
|
615
|
+
const candidate = import_path6.default.join(dir, "tmux");
|
|
552
616
|
try {
|
|
553
|
-
|
|
617
|
+
import_fs7.default.accessSync(candidate, import_fs7.default.constants.X_OK);
|
|
554
618
|
cachedTmuxPath = candidate;
|
|
555
619
|
return candidate;
|
|
556
620
|
} catch {
|
|
@@ -600,8 +664,8 @@ async function enrichWithPanesAndClaude(sessions2) {
|
|
|
600
664
|
const { file, args } = tmuxCmd(["list-panes", "-a", "-F", paneFmt]);
|
|
601
665
|
const { stdout } = await execFileAsync2(file, args, { timeout: 6e3 });
|
|
602
666
|
const panes = stdout.split("\n").map((line) => line.replace(/\r$/, "").trim()).filter(Boolean).map((line) => {
|
|
603
|
-
const [session, pid,
|
|
604
|
-
return { session, pid: parseInt(pid, 10) || 0, path:
|
|
667
|
+
const [session, pid, path11, activeFlags] = line.split(" ");
|
|
668
|
+
return { session, pid: parseInt(pid, 10) || 0, path: path11 || "", active: activeFlags === "11" };
|
|
605
669
|
});
|
|
606
670
|
const byName = new Map(sessions2.map((s) => [s.name, s]));
|
|
607
671
|
for (const pane of panes) {
|
|
@@ -640,16 +704,16 @@ function buildTmuxLaunch(name, launch) {
|
|
|
640
704
|
if (trimmed) args.push(trimmed);
|
|
641
705
|
return { file: tmuxBin, args };
|
|
642
706
|
}
|
|
643
|
-
var
|
|
707
|
+
var import_child_process3, import_util2, import_fs7, import_path6, execFileAsync2, IS_WIN, cachedTmuxPath;
|
|
644
708
|
var init_tmux = __esm({
|
|
645
709
|
"src/tmux.ts"() {
|
|
646
710
|
"use strict";
|
|
647
|
-
|
|
711
|
+
import_child_process3 = require("child_process");
|
|
648
712
|
import_util2 = require("util");
|
|
649
|
-
|
|
650
|
-
|
|
713
|
+
import_fs7 = __toESM(require("fs"));
|
|
714
|
+
import_path6 = __toESM(require("path"));
|
|
651
715
|
init_claude_live();
|
|
652
|
-
execFileAsync2 = (0, import_util2.promisify)(
|
|
716
|
+
execFileAsync2 = (0, import_util2.promisify)(import_child_process3.execFile);
|
|
653
717
|
IS_WIN = process.platform === "win32";
|
|
654
718
|
}
|
|
655
719
|
});
|
|
@@ -659,7 +723,7 @@ function detectShell(preference) {
|
|
|
659
723
|
if (preference !== "auto") return preference;
|
|
660
724
|
if (process.platform === "win32") {
|
|
661
725
|
try {
|
|
662
|
-
(0,
|
|
726
|
+
(0, import_child_process4.execSync)("where pwsh", { stdio: "ignore" });
|
|
663
727
|
return "pwsh.exe";
|
|
664
728
|
} catch {
|
|
665
729
|
return "powershell.exe";
|
|
@@ -667,25 +731,25 @@ function detectShell(preference) {
|
|
|
667
731
|
}
|
|
668
732
|
return process.env.SHELL || "/bin/bash";
|
|
669
733
|
}
|
|
670
|
-
var
|
|
734
|
+
var import_child_process4;
|
|
671
735
|
var init_shell = __esm({
|
|
672
736
|
"src/shell.ts"() {
|
|
673
737
|
"use strict";
|
|
674
|
-
|
|
738
|
+
import_child_process4 = require("child_process");
|
|
675
739
|
}
|
|
676
740
|
});
|
|
677
741
|
|
|
678
742
|
// src/pty-session.ts
|
|
679
743
|
function ensureInitFiles() {
|
|
680
|
-
const versionFile = (0,
|
|
681
|
-
if ((0,
|
|
744
|
+
const versionFile = (0, import_path7.join)(INIT_DIR, ".version");
|
|
745
|
+
if ((0, import_fs8.existsSync)(versionFile)) {
|
|
682
746
|
try {
|
|
683
|
-
if ((0,
|
|
747
|
+
if ((0, import_fs8.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
|
|
684
748
|
} catch {
|
|
685
749
|
}
|
|
686
750
|
}
|
|
687
|
-
(0,
|
|
688
|
-
(0,
|
|
751
|
+
(0, import_fs8.mkdirSync)(ZSH_DIR, { recursive: true });
|
|
752
|
+
(0, import_fs8.writeFileSync)(BASH_INIT, [
|
|
689
753
|
"[ -f ~/.bash_profile ] && source ~/.bash_profile",
|
|
690
754
|
"[ -f ~/.bashrc ] && source ~/.bashrc",
|
|
691
755
|
"__crc_s=$SECONDS",
|
|
@@ -696,11 +760,11 @@ function ensureInitFiles() {
|
|
|
696
760
|
`PROMPT_COMMAND='[ "$__crc_armed" = 1 ] && [ \${__crc_elapsed:-0} -ge ${THRESHOLD} ] && printf "\\a"; __crc_armed=1; eval "$__crc_orig_pc"'`,
|
|
697
761
|
""
|
|
698
762
|
].join("\n"), "utf-8");
|
|
699
|
-
(0,
|
|
763
|
+
(0, import_fs8.writeFileSync)(ZSH_ENV, [
|
|
700
764
|
'[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshenv" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshenv"',
|
|
701
765
|
""
|
|
702
766
|
].join("\n"), "utf-8");
|
|
703
|
-
(0,
|
|
767
|
+
(0, import_fs8.writeFileSync)(ZSH_RC, [
|
|
704
768
|
'[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshrc" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshrc"',
|
|
705
769
|
"__crc_s=$SECONDS",
|
|
706
770
|
"__crc_armed=0",
|
|
@@ -713,36 +777,36 @@ function ensureInitFiles() {
|
|
|
713
777
|
"fi",
|
|
714
778
|
""
|
|
715
779
|
].join("\n"), "utf-8");
|
|
716
|
-
(0,
|
|
780
|
+
(0, import_fs8.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
|
|
717
781
|
}
|
|
718
782
|
function resolveCwd(cwd) {
|
|
719
783
|
const candidates = process.platform === "win32" ? [cwd, (0, import_os3.homedir)(), process.env.USERPROFILE, process.env.HOME] : [cwd, (0, import_os3.homedir)(), process.env.HOME, process.env.USERPROFILE];
|
|
720
784
|
for (const c of candidates) {
|
|
721
785
|
if (c) {
|
|
722
786
|
try {
|
|
723
|
-
if ((0,
|
|
787
|
+
if ((0, import_fs8.existsSync)(c)) return c;
|
|
724
788
|
} catch {
|
|
725
789
|
}
|
|
726
790
|
}
|
|
727
791
|
}
|
|
728
792
|
return (0, import_os3.homedir)();
|
|
729
793
|
}
|
|
730
|
-
var pty,
|
|
794
|
+
var pty, import_fs8, import_path7, import_os3, THRESHOLD, INIT_DIR, BASH_INIT, ZSH_DIR, ZSH_RC, ZSH_ENV, INIT_VERSION, PtySession;
|
|
731
795
|
var init_pty_session = __esm({
|
|
732
796
|
"src/pty-session.ts"() {
|
|
733
797
|
"use strict";
|
|
734
798
|
pty = __toESM(require("node-pty"));
|
|
735
|
-
|
|
736
|
-
|
|
799
|
+
import_fs8 = require("fs");
|
|
800
|
+
import_path7 = require("path");
|
|
737
801
|
import_os3 = require("os");
|
|
738
802
|
init_src();
|
|
739
803
|
init_shell();
|
|
740
804
|
THRESHOLD = 3;
|
|
741
|
-
INIT_DIR = (0,
|
|
742
|
-
BASH_INIT = (0,
|
|
743
|
-
ZSH_DIR = (0,
|
|
744
|
-
ZSH_RC = (0,
|
|
745
|
-
ZSH_ENV = (0,
|
|
805
|
+
INIT_DIR = (0, import_path7.join)((0, import_os3.tmpdir)(), "crc-shell-init");
|
|
806
|
+
BASH_INIT = (0, import_path7.join)(INIT_DIR, "bashrc");
|
|
807
|
+
ZSH_DIR = (0, import_path7.join)(INIT_DIR, "zsh");
|
|
808
|
+
ZSH_RC = (0, import_path7.join)(ZSH_DIR, ".zshrc");
|
|
809
|
+
ZSH_ENV = (0, import_path7.join)(ZSH_DIR, ".zshenv");
|
|
746
810
|
INIT_VERSION = "2";
|
|
747
811
|
PtySession = class {
|
|
748
812
|
id;
|
|
@@ -791,6 +855,9 @@ var init_pty_session = __esm({
|
|
|
791
855
|
});
|
|
792
856
|
}
|
|
793
857
|
}
|
|
858
|
+
get pid() {
|
|
859
|
+
return this.pty.pid;
|
|
860
|
+
}
|
|
794
861
|
write(data) {
|
|
795
862
|
this.pty.write(data);
|
|
796
863
|
}
|
|
@@ -857,6 +924,9 @@ function createTerminalSession(sessionId, cols, rows, shellPreference, cwd, onDa
|
|
|
857
924
|
sessions.set(sessionId, session);
|
|
858
925
|
logger.info({ sessionId, cols, rows }, "PTY session created");
|
|
859
926
|
}
|
|
927
|
+
function getSessionPid(sessionId) {
|
|
928
|
+
return sessions.get(sessionId)?.pid;
|
|
929
|
+
}
|
|
860
930
|
function writeToSession(sessionId, data) {
|
|
861
931
|
sessions.get(sessionId)?.write(data);
|
|
862
932
|
}
|
|
@@ -953,7 +1023,7 @@ function getCpuUsage() {
|
|
|
953
1023
|
function getRootPaths() {
|
|
954
1024
|
if (process.platform === "win32") {
|
|
955
1025
|
try {
|
|
956
|
-
const output = (0,
|
|
1026
|
+
const output = (0, import_child_process5.execSync)("wmic logicaldisk get name", { encoding: "utf-8" });
|
|
957
1027
|
return output.split("\n").map((l) => l.trim()).filter((l) => /^[A-Z]:$/.test(l)).map((l) => l + "\\");
|
|
958
1028
|
} catch {
|
|
959
1029
|
return ["C:\\"];
|
|
@@ -972,19 +1042,19 @@ function buildHeartbeat(homeDir) {
|
|
|
972
1042
|
memoryUsage: Math.round((totalMem - freeMem) / totalMem * 100),
|
|
973
1043
|
uptime: Math.round(import_os4.default.uptime()),
|
|
974
1044
|
activeSessions: getActiveSessionCount(),
|
|
975
|
-
pathSeparator:
|
|
1045
|
+
pathSeparator: import_path8.default.sep,
|
|
976
1046
|
homeDirectory: homeDir || import_os4.default.homedir(),
|
|
977
1047
|
rootPaths: getRootPaths(),
|
|
978
1048
|
capabilities: { terminal: true, fileTransfer: false }
|
|
979
1049
|
};
|
|
980
1050
|
}
|
|
981
|
-
var import_os4,
|
|
1051
|
+
var import_os4, import_path8, import_child_process5, prevCpu;
|
|
982
1052
|
var init_heartbeat = __esm({
|
|
983
1053
|
"src/heartbeat.ts"() {
|
|
984
1054
|
"use strict";
|
|
985
1055
|
import_os4 = __toESM(require("os"));
|
|
986
|
-
|
|
987
|
-
|
|
1056
|
+
import_path8 = __toESM(require("path"));
|
|
1057
|
+
import_child_process5 = require("child_process");
|
|
988
1058
|
init_terminal_manager();
|
|
989
1059
|
prevCpu = null;
|
|
990
1060
|
}
|
|
@@ -992,28 +1062,28 @@ var init_heartbeat = __esm({
|
|
|
992
1062
|
|
|
993
1063
|
// src/file-explorer.ts
|
|
994
1064
|
function isInSecretDir(resolved) {
|
|
995
|
-
const rel =
|
|
996
|
-
return rel === "" || !rel.startsWith(".." +
|
|
1065
|
+
const rel = import_path9.default.relative(SECRET_DIR, resolved);
|
|
1066
|
+
return rel === "" || !rel.startsWith(".." + import_path9.default.sep) && rel !== ".." && !import_path9.default.isAbsolute(rel);
|
|
997
1067
|
}
|
|
998
1068
|
function listDirectory(dirPath) {
|
|
999
1069
|
try {
|
|
1000
|
-
const resolved =
|
|
1070
|
+
const resolved = import_path9.default.resolve(dirPath);
|
|
1001
1071
|
if (isInSecretDir(resolved)) {
|
|
1002
1072
|
return { entries: [], error: "Access denied" };
|
|
1003
1073
|
}
|
|
1004
|
-
if (!
|
|
1074
|
+
if (!import_fs9.default.existsSync(resolved)) {
|
|
1005
1075
|
return { entries: [], error: "Path does not exist" };
|
|
1006
1076
|
}
|
|
1007
|
-
const stat =
|
|
1077
|
+
const stat = import_fs9.default.statSync(resolved);
|
|
1008
1078
|
if (!stat.isDirectory()) {
|
|
1009
1079
|
return { entries: [], error: "Not a directory" };
|
|
1010
1080
|
}
|
|
1011
|
-
const dirents =
|
|
1081
|
+
const dirents = import_fs9.default.readdirSync(resolved, { withFileTypes: true });
|
|
1012
1082
|
const entries = [];
|
|
1013
1083
|
for (const dirent of dirents) {
|
|
1014
1084
|
try {
|
|
1015
|
-
const fullPath =
|
|
1016
|
-
const s =
|
|
1085
|
+
const fullPath = import_path9.default.join(resolved, dirent.name);
|
|
1086
|
+
const s = import_fs9.default.statSync(fullPath);
|
|
1017
1087
|
entries.push({
|
|
1018
1088
|
name: dirent.name,
|
|
1019
1089
|
isDirectory: dirent.isDirectory(),
|
|
@@ -1035,22 +1105,22 @@ function listDirectory(dirPath) {
|
|
|
1035
1105
|
}
|
|
1036
1106
|
async function downloadFile(filePath, serverUrl, secret, agentId) {
|
|
1037
1107
|
try {
|
|
1038
|
-
const resolved =
|
|
1108
|
+
const resolved = import_path9.default.resolve(filePath);
|
|
1039
1109
|
if (isInSecretDir(resolved)) {
|
|
1040
1110
|
return { error: "Access denied" };
|
|
1041
1111
|
}
|
|
1042
|
-
if (!
|
|
1112
|
+
if (!import_fs9.default.existsSync(resolved)) {
|
|
1043
1113
|
return { error: "File does not exist" };
|
|
1044
1114
|
}
|
|
1045
|
-
const stat =
|
|
1115
|
+
const stat = import_fs9.default.statSync(resolved);
|
|
1046
1116
|
if (stat.isDirectory()) {
|
|
1047
1117
|
return { error: "Cannot download a directory" };
|
|
1048
1118
|
}
|
|
1049
1119
|
if (stat.size > FILE_MAX_SIZE) {
|
|
1050
1120
|
return { error: `File too large (max ${FILE_MAX_SIZE} bytes)` };
|
|
1051
1121
|
}
|
|
1052
|
-
const fileName =
|
|
1053
|
-
const fileStream =
|
|
1122
|
+
const fileName = import_path9.default.basename(resolved);
|
|
1123
|
+
const fileStream = import_fs9.default.createReadStream(resolved);
|
|
1054
1124
|
const baseUrl = serverUrl.replace("wss://", "https://").replace("ws://", "http://");
|
|
1055
1125
|
const url = `${baseUrl}/api/files/receive`;
|
|
1056
1126
|
const res = await fetch(url, {
|
|
@@ -1079,16 +1149,16 @@ async function downloadFile(filePath, serverUrl, secret, agentId) {
|
|
|
1079
1149
|
return { error: err.message };
|
|
1080
1150
|
}
|
|
1081
1151
|
}
|
|
1082
|
-
var
|
|
1152
|
+
var import_fs9, import_path9, import_os5, SECRET_DIR;
|
|
1083
1153
|
var init_file_explorer = __esm({
|
|
1084
1154
|
"src/file-explorer.ts"() {
|
|
1085
1155
|
"use strict";
|
|
1086
|
-
|
|
1087
|
-
|
|
1156
|
+
import_fs9 = __toESM(require("fs"));
|
|
1157
|
+
import_path9 = __toESM(require("path"));
|
|
1088
1158
|
import_os5 = __toESM(require("os"));
|
|
1089
1159
|
init_logger();
|
|
1090
1160
|
init_src();
|
|
1091
|
-
SECRET_DIR =
|
|
1161
|
+
SECRET_DIR = import_path9.default.join(import_os5.default.homedir(), ".crc-agent");
|
|
1092
1162
|
}
|
|
1093
1163
|
});
|
|
1094
1164
|
|
|
@@ -1111,8 +1181,8 @@ async function runCmd(cmd2, shell) {
|
|
|
1111
1181
|
}
|
|
1112
1182
|
function getConfigPath(profile) {
|
|
1113
1183
|
const file = profile.configFile || `${profile.id}.conf`;
|
|
1114
|
-
if (
|
|
1115
|
-
return
|
|
1184
|
+
if (import_path10.default.isAbsolute(file)) return file;
|
|
1185
|
+
return import_path10.default.join(VPN_DIR, file);
|
|
1116
1186
|
}
|
|
1117
1187
|
async function getScutilStatus(serviceName) {
|
|
1118
1188
|
const { stdout } = await runCmd(`scutil --nc status '${shq(serviceName)}'`);
|
|
@@ -1228,7 +1298,7 @@ async function connectWireGuard(profile) {
|
|
|
1228
1298
|
const tunnelName = profile.tunnelName || profile.id;
|
|
1229
1299
|
if (isWindows) {
|
|
1230
1300
|
const confPath2 = getConfigPath(profile);
|
|
1231
|
-
if (!
|
|
1301
|
+
if (!import_fs10.default.existsSync(confPath2)) {
|
|
1232
1302
|
return `Config file not found: ${confPath2}`;
|
|
1233
1303
|
}
|
|
1234
1304
|
const script = `
|
|
@@ -1254,7 +1324,7 @@ async function connectWireGuard(profile) {
|
|
|
1254
1324
|
async function connectOpenVpn(profile) {
|
|
1255
1325
|
if (isWindows) {
|
|
1256
1326
|
const confPath2 = getConfigPath(profile);
|
|
1257
|
-
if (!
|
|
1327
|
+
if (!import_fs10.default.existsSync(confPath2)) {
|
|
1258
1328
|
return `Config file not found: ${confPath2}`;
|
|
1259
1329
|
}
|
|
1260
1330
|
const connector = "C:\\Program Files\\OpenVPN Connect\\ovpnconnector.exe";
|
|
@@ -1281,7 +1351,7 @@ async function connectOpenVpn(profile) {
|
|
|
1281
1351
|
return scutilStart(profile.serviceName);
|
|
1282
1352
|
}
|
|
1283
1353
|
const confPath = getConfigPath(profile);
|
|
1284
|
-
if (!
|
|
1354
|
+
if (!import_fs10.default.existsSync(confPath)) {
|
|
1285
1355
|
return `Config file not found: ${confPath}`;
|
|
1286
1356
|
}
|
|
1287
1357
|
const { stderr } = await runCmd(`sudo openvpn --config '${shq(confPath)}' --daemon`);
|
|
@@ -1290,7 +1360,7 @@ async function connectOpenVpn(profile) {
|
|
|
1290
1360
|
async function connectAzure(profile) {
|
|
1291
1361
|
if (isWindows) {
|
|
1292
1362
|
const confPath = getConfigPath(profile);
|
|
1293
|
-
if (!
|
|
1363
|
+
if (!import_fs10.default.existsSync(confPath)) {
|
|
1294
1364
|
return `Config file not found: ${confPath}`;
|
|
1295
1365
|
}
|
|
1296
1366
|
await runCmd(`& 'AzureVpn.exe' -i '${psq(confPath)}' 2>&1`);
|
|
@@ -1410,19 +1480,19 @@ async function disconnectVpn(configs, profileId) {
|
|
|
1410
1480
|
}
|
|
1411
1481
|
return profiles;
|
|
1412
1482
|
}
|
|
1413
|
-
var
|
|
1483
|
+
var import_child_process6, import_util3, import_fs10, import_path10, import_os6, execAsync, isWindows, VPN_DIR;
|
|
1414
1484
|
var init_vpn_manager = __esm({
|
|
1415
1485
|
"src/vpn-manager.ts"() {
|
|
1416
1486
|
"use strict";
|
|
1417
|
-
|
|
1487
|
+
import_child_process6 = require("child_process");
|
|
1418
1488
|
import_util3 = require("util");
|
|
1419
|
-
|
|
1420
|
-
|
|
1489
|
+
import_fs10 = __toESM(require("fs"));
|
|
1490
|
+
import_path10 = __toESM(require("path"));
|
|
1421
1491
|
import_os6 = __toESM(require("os"));
|
|
1422
1492
|
init_logger();
|
|
1423
|
-
execAsync = (0, import_util3.promisify)(
|
|
1493
|
+
execAsync = (0, import_util3.promisify)(import_child_process6.exec);
|
|
1424
1494
|
isWindows = process.platform === "win32";
|
|
1425
|
-
VPN_DIR =
|
|
1495
|
+
VPN_DIR = import_path10.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
|
|
1426
1496
|
}
|
|
1427
1497
|
});
|
|
1428
1498
|
|
|
@@ -1457,7 +1527,7 @@ var init_claude_path = __esm({
|
|
|
1457
1527
|
// src/claude-sessions.ts
|
|
1458
1528
|
async function parseSessionFile(filePath) {
|
|
1459
1529
|
try {
|
|
1460
|
-
const stream =
|
|
1530
|
+
const stream = import_fs11.default.createReadStream(filePath, { encoding: "utf-8" });
|
|
1461
1531
|
const rl = import_readline2.default.createInterface({ input: stream, crlfDelay: Infinity });
|
|
1462
1532
|
let firstMessage = "";
|
|
1463
1533
|
let lastTimestamp = "";
|
|
@@ -1505,17 +1575,17 @@ async function parseSessionFile(filePath) {
|
|
|
1505
1575
|
}
|
|
1506
1576
|
async function listClaudeSessions(filterProjectPath) {
|
|
1507
1577
|
const sessions2 = [];
|
|
1508
|
-
if (!
|
|
1578
|
+
if (!import_fs11.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
|
|
1509
1579
|
const isDirSafe = (d) => {
|
|
1510
1580
|
try {
|
|
1511
|
-
return
|
|
1581
|
+
return import_fs11.default.statSync(import_path11.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
|
|
1512
1582
|
} catch {
|
|
1513
1583
|
return false;
|
|
1514
1584
|
}
|
|
1515
1585
|
};
|
|
1516
1586
|
let allEntries;
|
|
1517
1587
|
try {
|
|
1518
|
-
allEntries =
|
|
1588
|
+
allEntries = import_fs11.default.readdirSync(CLAUDE_PROJECTS_DIR);
|
|
1519
1589
|
} catch {
|
|
1520
1590
|
return sessions2;
|
|
1521
1591
|
}
|
|
@@ -1528,16 +1598,16 @@ async function listClaudeSessions(filterProjectPath) {
|
|
|
1528
1598
|
projectDirs = allEntries.filter((d) => isDirSafe(d) && !d.startsWith("-private-"));
|
|
1529
1599
|
}
|
|
1530
1600
|
for (const dirName of projectDirs) {
|
|
1531
|
-
const dirPath =
|
|
1601
|
+
const dirPath = import_path11.default.join(CLAUDE_PROJECTS_DIR, dirName);
|
|
1532
1602
|
let files;
|
|
1533
1603
|
try {
|
|
1534
|
-
files =
|
|
1604
|
+
files = import_fs11.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
|
|
1535
1605
|
} catch {
|
|
1536
1606
|
continue;
|
|
1537
1607
|
}
|
|
1538
1608
|
for (const file of files) {
|
|
1539
1609
|
const sessionId = file.replace(".jsonl", "");
|
|
1540
|
-
const parsed = await parseSessionFile(
|
|
1610
|
+
const parsed = await parseSessionFile(import_path11.default.join(dirPath, file));
|
|
1541
1611
|
if (!parsed) continue;
|
|
1542
1612
|
const projectPath = parsed.cwd || dirName.replace(/^-/, "/").replace(/-/g, "/");
|
|
1543
1613
|
sessions2.push({
|
|
@@ -1555,31 +1625,31 @@ async function listClaudeSessions(filterProjectPath) {
|
|
|
1555
1625
|
sessions2.sort((a, b) => b.lastTimestamp > a.lastTimestamp ? 1 : -1);
|
|
1556
1626
|
return sessions2;
|
|
1557
1627
|
}
|
|
1558
|
-
var
|
|
1628
|
+
var import_fs11, import_path11, import_os7, import_readline2, CLAUDE_PROJECTS_DIR;
|
|
1559
1629
|
var init_claude_sessions = __esm({
|
|
1560
1630
|
"src/claude-sessions.ts"() {
|
|
1561
1631
|
"use strict";
|
|
1562
|
-
|
|
1563
|
-
|
|
1632
|
+
import_fs11 = __toESM(require("fs"));
|
|
1633
|
+
import_path11 = __toESM(require("path"));
|
|
1564
1634
|
import_os7 = __toESM(require("os"));
|
|
1565
1635
|
import_readline2 = __toESM(require("readline"));
|
|
1566
1636
|
init_logger();
|
|
1567
1637
|
init_claude_path();
|
|
1568
|
-
CLAUDE_PROJECTS_DIR =
|
|
1638
|
+
CLAUDE_PROJECTS_DIR = import_path11.default.join(import_os7.default.homedir(), ".claude", "projects");
|
|
1569
1639
|
}
|
|
1570
1640
|
});
|
|
1571
1641
|
|
|
1572
1642
|
// src/claude-conversation.ts
|
|
1573
1643
|
function findProjectDir(projectPath) {
|
|
1574
1644
|
const encoded = encodeProjectPath(projectPath);
|
|
1575
|
-
const dirPath =
|
|
1576
|
-
if (
|
|
1577
|
-
if (!
|
|
1645
|
+
const dirPath = import_path12.default.join(CLAUDE_PROJECTS_DIR2, encoded);
|
|
1646
|
+
if (import_fs12.default.existsSync(dirPath)) return dirPath;
|
|
1647
|
+
if (!import_fs12.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
|
|
1578
1648
|
let allDirs;
|
|
1579
1649
|
try {
|
|
1580
|
-
allDirs =
|
|
1650
|
+
allDirs = import_fs12.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
|
|
1581
1651
|
try {
|
|
1582
|
-
return
|
|
1652
|
+
return import_fs12.default.statSync(import_path12.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
|
|
1583
1653
|
} catch {
|
|
1584
1654
|
return false;
|
|
1585
1655
|
}
|
|
@@ -1589,23 +1659,23 @@ function findProjectDir(projectPath) {
|
|
|
1589
1659
|
}
|
|
1590
1660
|
const matches = findProjectDirs(allDirs, projectPath);
|
|
1591
1661
|
if (matches.length > 0) {
|
|
1592
|
-
return
|
|
1662
|
+
return import_path12.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
|
|
1593
1663
|
}
|
|
1594
1664
|
return null;
|
|
1595
1665
|
}
|
|
1596
1666
|
function findSessionFileById(sessionId) {
|
|
1597
1667
|
if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return null;
|
|
1598
|
-
if (!
|
|
1668
|
+
if (!import_fs12.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
|
|
1599
1669
|
let dirs;
|
|
1600
1670
|
try {
|
|
1601
|
-
dirs =
|
|
1671
|
+
dirs = import_fs12.default.readdirSync(CLAUDE_PROJECTS_DIR2);
|
|
1602
1672
|
} catch {
|
|
1603
1673
|
return null;
|
|
1604
1674
|
}
|
|
1605
1675
|
for (const dir of dirs) {
|
|
1606
|
-
const candidate =
|
|
1676
|
+
const candidate = import_path12.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
|
|
1607
1677
|
try {
|
|
1608
|
-
if (
|
|
1678
|
+
if (import_fs12.default.statSync(candidate).isFile()) return candidate;
|
|
1609
1679
|
} catch {
|
|
1610
1680
|
}
|
|
1611
1681
|
}
|
|
@@ -1616,20 +1686,20 @@ function findLatestSessionFile(projectPath) {
|
|
|
1616
1686
|
if (!dirPath) return null;
|
|
1617
1687
|
let entries;
|
|
1618
1688
|
try {
|
|
1619
|
-
entries =
|
|
1689
|
+
entries = import_fs12.default.readdirSync(dirPath);
|
|
1620
1690
|
} catch {
|
|
1621
1691
|
return null;
|
|
1622
1692
|
}
|
|
1623
1693
|
const files = entries.filter((f) => f.endsWith(".jsonl") && !f.includes("/")).map((f) => {
|
|
1624
1694
|
try {
|
|
1625
|
-
return { name: f, mtime:
|
|
1695
|
+
return { name: f, mtime: import_fs12.default.statSync(import_path12.default.join(dirPath, f)).mtimeMs };
|
|
1626
1696
|
} catch {
|
|
1627
1697
|
return null;
|
|
1628
1698
|
}
|
|
1629
1699
|
}).filter((f) => f !== null).sort((a, b) => b.mtime - a.mtime);
|
|
1630
1700
|
if (files.length === 0) return null;
|
|
1631
1701
|
return {
|
|
1632
|
-
filePath:
|
|
1702
|
+
filePath: import_path12.default.join(dirPath, files[0].name),
|
|
1633
1703
|
sessionId: files[0].name.replace(".jsonl", "")
|
|
1634
1704
|
};
|
|
1635
1705
|
}
|
|
@@ -1639,8 +1709,8 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
|
|
|
1639
1709
|
if (specificSessionId) {
|
|
1640
1710
|
sessionId = specificSessionId;
|
|
1641
1711
|
const dirPath = findProjectDir(projectPath);
|
|
1642
|
-
const direct = dirPath ?
|
|
1643
|
-
if (direct &&
|
|
1712
|
+
const direct = dirPath ? import_path12.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
|
|
1713
|
+
if (direct && import_fs12.default.existsSync(direct)) {
|
|
1644
1714
|
filePath = direct;
|
|
1645
1715
|
} else {
|
|
1646
1716
|
const byId = findSessionFileById(specificSessionId);
|
|
@@ -1654,7 +1724,7 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
|
|
|
1654
1724
|
sessionId = found.sessionId;
|
|
1655
1725
|
}
|
|
1656
1726
|
try {
|
|
1657
|
-
const stream =
|
|
1727
|
+
const stream = import_fs12.default.createReadStream(filePath, { encoding: "utf-8" });
|
|
1658
1728
|
const rl = import_readline3.default.createInterface({ input: stream, crlfDelay: Infinity });
|
|
1659
1729
|
const messages = [];
|
|
1660
1730
|
let lineNum = 0;
|
|
@@ -1735,23 +1805,23 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
|
|
|
1735
1805
|
return null;
|
|
1736
1806
|
}
|
|
1737
1807
|
}
|
|
1738
|
-
var
|
|
1808
|
+
var import_fs12, import_path12, import_os8, import_readline3, CLAUDE_PROJECTS_DIR2;
|
|
1739
1809
|
var init_claude_conversation = __esm({
|
|
1740
1810
|
"src/claude-conversation.ts"() {
|
|
1741
1811
|
"use strict";
|
|
1742
|
-
|
|
1743
|
-
|
|
1812
|
+
import_fs12 = __toESM(require("fs"));
|
|
1813
|
+
import_path12 = __toESM(require("path"));
|
|
1744
1814
|
import_os8 = __toESM(require("os"));
|
|
1745
1815
|
import_readline3 = __toESM(require("readline"));
|
|
1746
1816
|
init_logger();
|
|
1747
1817
|
init_claude_path();
|
|
1748
|
-
CLAUDE_PROJECTS_DIR2 =
|
|
1818
|
+
CLAUDE_PROJECTS_DIR2 = import_path12.default.join(import_os8.default.homedir(), ".claude", "projects");
|
|
1749
1819
|
}
|
|
1750
1820
|
});
|
|
1751
1821
|
|
|
1752
1822
|
// src/index.ts
|
|
1753
1823
|
var index_exports = {};
|
|
1754
|
-
var import_socket, config, socket, SESSION_MAX_IDLE_MS, reaperInterval, vpnProfiles;
|
|
1824
|
+
var import_socket, config, lock, socket, SESSION_MAX_IDLE_MS, reaperInterval, vpnProfiles;
|
|
1755
1825
|
var init_index = __esm({
|
|
1756
1826
|
"src/index.ts"() {
|
|
1757
1827
|
"use strict";
|
|
@@ -1762,6 +1832,7 @@ var init_index = __esm({
|
|
|
1762
1832
|
init_claude_plugin_installer();
|
|
1763
1833
|
init_local_control();
|
|
1764
1834
|
init_pty_helper_fix();
|
|
1835
|
+
init_single_instance();
|
|
1765
1836
|
init_tmux();
|
|
1766
1837
|
init_heartbeat();
|
|
1767
1838
|
init_file_explorer();
|
|
@@ -1769,12 +1840,20 @@ var init_index = __esm({
|
|
|
1769
1840
|
init_claude_sessions();
|
|
1770
1841
|
init_claude_conversation();
|
|
1771
1842
|
init_terminal_manager();
|
|
1843
|
+
init_claude_live();
|
|
1772
1844
|
if (!isConfigured()) {
|
|
1773
1845
|
console.error("No agent configured. Run: crc-agent setup");
|
|
1774
1846
|
process.exit(1);
|
|
1775
1847
|
}
|
|
1776
1848
|
config = loadConfig();
|
|
1777
1849
|
logger.info({ agentId: config.agentId, serverUrl: config.serverUrl }, "Starting agent");
|
|
1850
|
+
lock = acquireSingleInstanceLock();
|
|
1851
|
+
if (!lock.ok) {
|
|
1852
|
+
console.error(
|
|
1853
|
+
`Another crc-agent is already running on this machine (PID ${lock.pid}). Stop it first, or set CRC_ALLOW_MULTIPLE=1 to override.`
|
|
1854
|
+
);
|
|
1855
|
+
process.exit(1);
|
|
1856
|
+
}
|
|
1778
1857
|
ensurePtyHelperExecutable();
|
|
1779
1858
|
process.on("unhandledRejection", (reason) => {
|
|
1780
1859
|
logger.error({ reason }, "Unhandled promise rejection (agent kept alive)");
|
|
@@ -1963,7 +2042,17 @@ var init_index = __esm({
|
|
|
1963
2042
|
});
|
|
1964
2043
|
socket.on(CLAUDE_CONV_READ, async (payload) => {
|
|
1965
2044
|
try {
|
|
1966
|
-
|
|
2045
|
+
let sessionId = payload.sessionId;
|
|
2046
|
+
if (!sessionId && payload.terminalSessionId) {
|
|
2047
|
+
const ptyPid = getSessionPid(payload.terminalSessionId);
|
|
2048
|
+
const resolved = ptyPid ? await findClaudeSessionForPtyPid(ptyPid) : null;
|
|
2049
|
+
if (!resolved) {
|
|
2050
|
+
socket.emit(CLAUDE_CONV_DATA, { sessionId: "", messages: [], totalLines: 0 });
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
sessionId = resolved;
|
|
2054
|
+
}
|
|
2055
|
+
const result = await readConversation(payload.projectPath, payload.afterLine || 0, sessionId);
|
|
1967
2056
|
if (result) {
|
|
1968
2057
|
socket.emit(CLAUDE_CONV_DATA, {
|
|
1969
2058
|
sessionId: result.sessionId,
|