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/index.js
CHANGED
|
@@ -289,20 +289,66 @@ function ensurePtyHelperExecutable() {
|
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
// src/single-instance.ts
|
|
293
|
+
var import_fs4 = __toESM(require("fs"));
|
|
294
|
+
var import_path4 = __toESM(require("path"));
|
|
295
|
+
var import_child_process = require("child_process");
|
|
296
|
+
var LOCK_FILE = import_path4.default.join(CONFIG_DIR, "agent.lock");
|
|
297
|
+
function isRunningAgent(pid) {
|
|
298
|
+
try {
|
|
299
|
+
process.kill(pid, 0);
|
|
300
|
+
} catch {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
if (process.platform === "win32") return true;
|
|
304
|
+
try {
|
|
305
|
+
const out = (0, import_child_process.execFileSync)("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" });
|
|
306
|
+
return /crc-agent|cli-remote-agent|agent[\/\\](dist|src)[\/\\](cli|index)/.test(out);
|
|
307
|
+
} catch {
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function acquireSingleInstanceLock() {
|
|
312
|
+
if (process.env.CRC_ALLOW_MULTIPLE === "1") return { ok: true };
|
|
313
|
+
try {
|
|
314
|
+
if (import_fs4.default.existsSync(LOCK_FILE)) {
|
|
315
|
+
const prev = parseInt(import_fs4.default.readFileSync(LOCK_FILE, "utf8").trim(), 10);
|
|
316
|
+
if (prev && prev !== process.pid && isRunningAgent(prev)) {
|
|
317
|
+
return { ok: false, pid: prev };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
import_fs4.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
321
|
+
import_fs4.default.writeFileSync(LOCK_FILE, String(process.pid));
|
|
322
|
+
const release = () => {
|
|
323
|
+
try {
|
|
324
|
+
if (parseInt(import_fs4.default.readFileSync(LOCK_FILE, "utf8").trim(), 10) === process.pid) {
|
|
325
|
+
import_fs4.default.unlinkSync(LOCK_FILE);
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
process.on("exit", release);
|
|
331
|
+
return { ok: true };
|
|
332
|
+
} catch (err) {
|
|
333
|
+
logger.warn({ error: err?.message }, "Could not manage single-instance lock");
|
|
334
|
+
return { ok: true };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
292
338
|
// src/tmux.ts
|
|
293
|
-
var
|
|
339
|
+
var import_child_process3 = require("child_process");
|
|
294
340
|
var import_util2 = require("util");
|
|
295
|
-
var
|
|
296
|
-
var
|
|
341
|
+
var import_fs6 = __toESM(require("fs"));
|
|
342
|
+
var import_path6 = __toESM(require("path"));
|
|
297
343
|
|
|
298
344
|
// src/claude-live.ts
|
|
299
|
-
var
|
|
300
|
-
var
|
|
345
|
+
var import_fs5 = __toESM(require("fs"));
|
|
346
|
+
var import_path5 = __toESM(require("path"));
|
|
301
347
|
var import_os2 = __toESM(require("os"));
|
|
302
|
-
var
|
|
348
|
+
var import_child_process2 = require("child_process");
|
|
303
349
|
var import_util = require("util");
|
|
304
|
-
var execFileAsync = (0, import_util.promisify)(
|
|
305
|
-
var LIVE_SESSIONS_DIR =
|
|
350
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
|
|
351
|
+
var LIVE_SESSIONS_DIR = import_path5.default.join(import_os2.default.homedir(), ".claude", "sessions");
|
|
306
352
|
function isAlive(pid) {
|
|
307
353
|
try {
|
|
308
354
|
process.kill(pid, 0);
|
|
@@ -314,18 +360,19 @@ function isAlive(pid) {
|
|
|
314
360
|
function getLiveClaudeSessions() {
|
|
315
361
|
let files;
|
|
316
362
|
try {
|
|
317
|
-
files =
|
|
363
|
+
files = import_fs5.default.readdirSync(LIVE_SESSIONS_DIR).filter((f) => f.endsWith(".json"));
|
|
318
364
|
} catch {
|
|
319
365
|
return [];
|
|
320
366
|
}
|
|
321
367
|
const out = [];
|
|
322
368
|
for (const f of files) {
|
|
323
369
|
try {
|
|
324
|
-
const obj = JSON.parse(
|
|
370
|
+
const obj = JSON.parse(import_fs5.default.readFileSync(import_path5.default.join(LIVE_SESSIONS_DIR, f), "utf-8"));
|
|
325
371
|
if (typeof obj?.pid !== "number" || !isAlive(obj.pid)) continue;
|
|
326
372
|
out.push({
|
|
327
373
|
pid: obj.pid,
|
|
328
374
|
cwd: typeof obj.cwd === "string" ? obj.cwd : "",
|
|
375
|
+
sessionId: typeof obj.sessionId === "string" ? obj.sessionId : void 0,
|
|
329
376
|
name: typeof obj.name === "string" ? obj.name : void 0,
|
|
330
377
|
status: typeof obj.status === "string" ? obj.status : void 0,
|
|
331
378
|
updatedAt: typeof obj.updatedAt === "number" ? obj.updatedAt : void 0
|
|
@@ -356,21 +403,30 @@ function isDescendantOf(pid, ancestor, parents) {
|
|
|
356
403
|
}
|
|
357
404
|
return false;
|
|
358
405
|
}
|
|
406
|
+
async function findClaudeSessionForPtyPid(ptyPid) {
|
|
407
|
+
const live = getLiveClaudeSessions().filter((s) => s.sessionId);
|
|
408
|
+
if (live.length === 0) return null;
|
|
409
|
+
const parents = await getPidParents();
|
|
410
|
+
for (let i = live.length - 1; i >= 0; i--) {
|
|
411
|
+
if (isDescendantOf(live[i].pid, ptyPid, parents)) return live[i].sessionId;
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
359
415
|
|
|
360
416
|
// src/tmux.ts
|
|
361
|
-
var execFileAsync2 = (0, import_util2.promisify)(
|
|
417
|
+
var execFileAsync2 = (0, import_util2.promisify)(import_child_process3.execFile);
|
|
362
418
|
var IS_WIN = process.platform === "win32";
|
|
363
419
|
var cachedTmuxPath;
|
|
364
420
|
function resolveTmuxPath() {
|
|
365
421
|
if (cachedTmuxPath !== void 0) return cachedTmuxPath;
|
|
366
|
-
const dirs = (process.env.PATH || "").split(
|
|
422
|
+
const dirs = (process.env.PATH || "").split(import_path6.default.delimiter).filter(Boolean);
|
|
367
423
|
for (const extra of ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/opt/local/bin"]) {
|
|
368
424
|
if (!dirs.includes(extra)) dirs.push(extra);
|
|
369
425
|
}
|
|
370
426
|
for (const dir of dirs) {
|
|
371
|
-
const candidate =
|
|
427
|
+
const candidate = import_path6.default.join(dir, "tmux");
|
|
372
428
|
try {
|
|
373
|
-
|
|
429
|
+
import_fs6.default.accessSync(candidate, import_fs6.default.constants.X_OK);
|
|
374
430
|
cachedTmuxPath = candidate;
|
|
375
431
|
return candidate;
|
|
376
432
|
} catch {
|
|
@@ -420,8 +476,8 @@ async function enrichWithPanesAndClaude(sessions2) {
|
|
|
420
476
|
const { file, args } = tmuxCmd(["list-panes", "-a", "-F", paneFmt]);
|
|
421
477
|
const { stdout } = await execFileAsync2(file, args, { timeout: 6e3 });
|
|
422
478
|
const panes = stdout.split("\n").map((line) => line.replace(/\r$/, "").trim()).filter(Boolean).map((line) => {
|
|
423
|
-
const [session, pid,
|
|
424
|
-
return { session, pid: parseInt(pid, 10) || 0, path:
|
|
479
|
+
const [session, pid, path11, activeFlags] = line.split(" ");
|
|
480
|
+
return { session, pid: parseInt(pid, 10) || 0, path: path11 || "", active: activeFlags === "11" };
|
|
425
481
|
});
|
|
426
482
|
const byName = new Map(sessions2.map((s) => [s.name, s]));
|
|
427
483
|
for (const pane of panes) {
|
|
@@ -463,22 +519,22 @@ function buildTmuxLaunch(name, launch) {
|
|
|
463
519
|
|
|
464
520
|
// src/heartbeat.ts
|
|
465
521
|
var import_os4 = __toESM(require("os"));
|
|
466
|
-
var
|
|
467
|
-
var
|
|
522
|
+
var import_path8 = __toESM(require("path"));
|
|
523
|
+
var import_child_process5 = require("child_process");
|
|
468
524
|
|
|
469
525
|
// src/pty-session.ts
|
|
470
526
|
var pty = __toESM(require("node-pty"));
|
|
471
|
-
var
|
|
472
|
-
var
|
|
527
|
+
var import_fs7 = require("fs");
|
|
528
|
+
var import_path7 = require("path");
|
|
473
529
|
var import_os3 = require("os");
|
|
474
530
|
|
|
475
531
|
// src/shell.ts
|
|
476
|
-
var
|
|
532
|
+
var import_child_process4 = require("child_process");
|
|
477
533
|
function detectShell(preference) {
|
|
478
534
|
if (preference !== "auto") return preference;
|
|
479
535
|
if (process.platform === "win32") {
|
|
480
536
|
try {
|
|
481
|
-
(0,
|
|
537
|
+
(0, import_child_process4.execSync)("where pwsh", { stdio: "ignore" });
|
|
482
538
|
return "pwsh.exe";
|
|
483
539
|
} catch {
|
|
484
540
|
return "powershell.exe";
|
|
@@ -489,22 +545,22 @@ function detectShell(preference) {
|
|
|
489
545
|
|
|
490
546
|
// src/pty-session.ts
|
|
491
547
|
var THRESHOLD = 3;
|
|
492
|
-
var INIT_DIR = (0,
|
|
493
|
-
var BASH_INIT = (0,
|
|
494
|
-
var ZSH_DIR = (0,
|
|
495
|
-
var ZSH_RC = (0,
|
|
496
|
-
var ZSH_ENV = (0,
|
|
548
|
+
var INIT_DIR = (0, import_path7.join)((0, import_os3.tmpdir)(), "crc-shell-init");
|
|
549
|
+
var BASH_INIT = (0, import_path7.join)(INIT_DIR, "bashrc");
|
|
550
|
+
var ZSH_DIR = (0, import_path7.join)(INIT_DIR, "zsh");
|
|
551
|
+
var ZSH_RC = (0, import_path7.join)(ZSH_DIR, ".zshrc");
|
|
552
|
+
var ZSH_ENV = (0, import_path7.join)(ZSH_DIR, ".zshenv");
|
|
497
553
|
var INIT_VERSION = "2";
|
|
498
554
|
function ensureInitFiles() {
|
|
499
|
-
const versionFile = (0,
|
|
500
|
-
if ((0,
|
|
555
|
+
const versionFile = (0, import_path7.join)(INIT_DIR, ".version");
|
|
556
|
+
if ((0, import_fs7.existsSync)(versionFile)) {
|
|
501
557
|
try {
|
|
502
|
-
if ((0,
|
|
558
|
+
if ((0, import_fs7.readFileSync)(versionFile, "utf-8").trim() === INIT_VERSION) return;
|
|
503
559
|
} catch {
|
|
504
560
|
}
|
|
505
561
|
}
|
|
506
|
-
(0,
|
|
507
|
-
(0,
|
|
562
|
+
(0, import_fs7.mkdirSync)(ZSH_DIR, { recursive: true });
|
|
563
|
+
(0, import_fs7.writeFileSync)(BASH_INIT, [
|
|
508
564
|
"[ -f ~/.bash_profile ] && source ~/.bash_profile",
|
|
509
565
|
"[ -f ~/.bashrc ] && source ~/.bashrc",
|
|
510
566
|
"__crc_s=$SECONDS",
|
|
@@ -515,11 +571,11 @@ function ensureInitFiles() {
|
|
|
515
571
|
`PROMPT_COMMAND='[ "$__crc_armed" = 1 ] && [ \${__crc_elapsed:-0} -ge ${THRESHOLD} ] && printf "\\a"; __crc_armed=1; eval "$__crc_orig_pc"'`,
|
|
516
572
|
""
|
|
517
573
|
].join("\n"), "utf-8");
|
|
518
|
-
(0,
|
|
574
|
+
(0, import_fs7.writeFileSync)(ZSH_ENV, [
|
|
519
575
|
'[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshenv" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshenv"',
|
|
520
576
|
""
|
|
521
577
|
].join("\n"), "utf-8");
|
|
522
|
-
(0,
|
|
578
|
+
(0, import_fs7.writeFileSync)(ZSH_RC, [
|
|
523
579
|
'[ -f "${ZDOTDIR_ORIG:-$HOME}/.zshrc" ] && source "${ZDOTDIR_ORIG:-$HOME}/.zshrc"',
|
|
524
580
|
"__crc_s=$SECONDS",
|
|
525
581
|
"__crc_armed=0",
|
|
@@ -532,14 +588,14 @@ function ensureInitFiles() {
|
|
|
532
588
|
"fi",
|
|
533
589
|
""
|
|
534
590
|
].join("\n"), "utf-8");
|
|
535
|
-
(0,
|
|
591
|
+
(0, import_fs7.writeFileSync)(versionFile, INIT_VERSION, "utf-8");
|
|
536
592
|
}
|
|
537
593
|
function resolveCwd(cwd) {
|
|
538
594
|
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];
|
|
539
595
|
for (const c of candidates) {
|
|
540
596
|
if (c) {
|
|
541
597
|
try {
|
|
542
|
-
if ((0,
|
|
598
|
+
if ((0, import_fs7.existsSync)(c)) return c;
|
|
543
599
|
} catch {
|
|
544
600
|
}
|
|
545
601
|
}
|
|
@@ -593,6 +649,9 @@ var PtySession = class {
|
|
|
593
649
|
});
|
|
594
650
|
}
|
|
595
651
|
}
|
|
652
|
+
get pid() {
|
|
653
|
+
return this.pty.pid;
|
|
654
|
+
}
|
|
596
655
|
write(data) {
|
|
597
656
|
this.pty.write(data);
|
|
598
657
|
}
|
|
@@ -658,6 +717,9 @@ function createTerminalSession(sessionId, cols, rows, shellPreference, cwd, onDa
|
|
|
658
717
|
sessions.set(sessionId, session);
|
|
659
718
|
logger.info({ sessionId, cols, rows }, "PTY session created");
|
|
660
719
|
}
|
|
720
|
+
function getSessionPid(sessionId) {
|
|
721
|
+
return sessions.get(sessionId)?.pid;
|
|
722
|
+
}
|
|
661
723
|
function writeToSession(sessionId, data) {
|
|
662
724
|
sessions.get(sessionId)?.write(data);
|
|
663
725
|
}
|
|
@@ -746,7 +808,7 @@ function getCpuUsage() {
|
|
|
746
808
|
function getRootPaths() {
|
|
747
809
|
if (process.platform === "win32") {
|
|
748
810
|
try {
|
|
749
|
-
const output = (0,
|
|
811
|
+
const output = (0, import_child_process5.execSync)("wmic logicaldisk get name", { encoding: "utf-8" });
|
|
750
812
|
return output.split("\n").map((l) => l.trim()).filter((l) => /^[A-Z]:$/.test(l)).map((l) => l + "\\");
|
|
751
813
|
} catch {
|
|
752
814
|
return ["C:\\"];
|
|
@@ -765,7 +827,7 @@ function buildHeartbeat(homeDir) {
|
|
|
765
827
|
memoryUsage: Math.round((totalMem - freeMem) / totalMem * 100),
|
|
766
828
|
uptime: Math.round(import_os4.default.uptime()),
|
|
767
829
|
activeSessions: getActiveSessionCount(),
|
|
768
|
-
pathSeparator:
|
|
830
|
+
pathSeparator: import_path8.default.sep,
|
|
769
831
|
homeDirectory: homeDir || import_os4.default.homedir(),
|
|
770
832
|
rootPaths: getRootPaths(),
|
|
771
833
|
capabilities: { terminal: true, fileTransfer: false }
|
|
@@ -773,33 +835,33 @@ function buildHeartbeat(homeDir) {
|
|
|
773
835
|
}
|
|
774
836
|
|
|
775
837
|
// src/file-explorer.ts
|
|
776
|
-
var
|
|
777
|
-
var
|
|
838
|
+
var import_fs8 = __toESM(require("fs"));
|
|
839
|
+
var import_path9 = __toESM(require("path"));
|
|
778
840
|
var import_os5 = __toESM(require("os"));
|
|
779
|
-
var SECRET_DIR =
|
|
841
|
+
var SECRET_DIR = import_path9.default.join(import_os5.default.homedir(), ".crc-agent");
|
|
780
842
|
function isInSecretDir(resolved) {
|
|
781
|
-
const rel =
|
|
782
|
-
return rel === "" || !rel.startsWith(".." +
|
|
843
|
+
const rel = import_path9.default.relative(SECRET_DIR, resolved);
|
|
844
|
+
return rel === "" || !rel.startsWith(".." + import_path9.default.sep) && rel !== ".." && !import_path9.default.isAbsolute(rel);
|
|
783
845
|
}
|
|
784
846
|
function listDirectory(dirPath) {
|
|
785
847
|
try {
|
|
786
|
-
const resolved =
|
|
848
|
+
const resolved = import_path9.default.resolve(dirPath);
|
|
787
849
|
if (isInSecretDir(resolved)) {
|
|
788
850
|
return { entries: [], error: "Access denied" };
|
|
789
851
|
}
|
|
790
|
-
if (!
|
|
852
|
+
if (!import_fs8.default.existsSync(resolved)) {
|
|
791
853
|
return { entries: [], error: "Path does not exist" };
|
|
792
854
|
}
|
|
793
|
-
const stat =
|
|
855
|
+
const stat = import_fs8.default.statSync(resolved);
|
|
794
856
|
if (!stat.isDirectory()) {
|
|
795
857
|
return { entries: [], error: "Not a directory" };
|
|
796
858
|
}
|
|
797
|
-
const dirents =
|
|
859
|
+
const dirents = import_fs8.default.readdirSync(resolved, { withFileTypes: true });
|
|
798
860
|
const entries = [];
|
|
799
861
|
for (const dirent of dirents) {
|
|
800
862
|
try {
|
|
801
|
-
const fullPath =
|
|
802
|
-
const s =
|
|
863
|
+
const fullPath = import_path9.default.join(resolved, dirent.name);
|
|
864
|
+
const s = import_fs8.default.statSync(fullPath);
|
|
803
865
|
entries.push({
|
|
804
866
|
name: dirent.name,
|
|
805
867
|
isDirectory: dirent.isDirectory(),
|
|
@@ -821,22 +883,22 @@ function listDirectory(dirPath) {
|
|
|
821
883
|
}
|
|
822
884
|
async function downloadFile(filePath, serverUrl, secret, agentId) {
|
|
823
885
|
try {
|
|
824
|
-
const resolved =
|
|
886
|
+
const resolved = import_path9.default.resolve(filePath);
|
|
825
887
|
if (isInSecretDir(resolved)) {
|
|
826
888
|
return { error: "Access denied" };
|
|
827
889
|
}
|
|
828
|
-
if (!
|
|
890
|
+
if (!import_fs8.default.existsSync(resolved)) {
|
|
829
891
|
return { error: "File does not exist" };
|
|
830
892
|
}
|
|
831
|
-
const stat =
|
|
893
|
+
const stat = import_fs8.default.statSync(resolved);
|
|
832
894
|
if (stat.isDirectory()) {
|
|
833
895
|
return { error: "Cannot download a directory" };
|
|
834
896
|
}
|
|
835
897
|
if (stat.size > FILE_MAX_SIZE) {
|
|
836
898
|
return { error: `File too large (max ${FILE_MAX_SIZE} bytes)` };
|
|
837
899
|
}
|
|
838
|
-
const fileName =
|
|
839
|
-
const fileStream =
|
|
900
|
+
const fileName = import_path9.default.basename(resolved);
|
|
901
|
+
const fileStream = import_fs8.default.createReadStream(resolved);
|
|
840
902
|
const baseUrl = serverUrl.replace("wss://", "https://").replace("ws://", "http://");
|
|
841
903
|
const url = `${baseUrl}/api/files/receive`;
|
|
842
904
|
const res = await fetch(url, {
|
|
@@ -867,14 +929,14 @@ async function downloadFile(filePath, serverUrl, secret, agentId) {
|
|
|
867
929
|
}
|
|
868
930
|
|
|
869
931
|
// src/vpn-manager.ts
|
|
870
|
-
var
|
|
932
|
+
var import_child_process6 = require("child_process");
|
|
871
933
|
var import_util3 = require("util");
|
|
872
|
-
var
|
|
873
|
-
var
|
|
934
|
+
var import_fs9 = __toESM(require("fs"));
|
|
935
|
+
var import_path10 = __toESM(require("path"));
|
|
874
936
|
var import_os6 = __toESM(require("os"));
|
|
875
|
-
var execAsync = (0, import_util3.promisify)(
|
|
937
|
+
var execAsync = (0, import_util3.promisify)(import_child_process6.exec);
|
|
876
938
|
var isWindows = process.platform === "win32";
|
|
877
|
-
var VPN_DIR =
|
|
939
|
+
var VPN_DIR = import_path10.default.join(import_os6.default.homedir(), ".crc-agent", "vpn");
|
|
878
940
|
function shq(value) {
|
|
879
941
|
return value.replace(/'/g, "'\\''");
|
|
880
942
|
}
|
|
@@ -893,8 +955,8 @@ async function runCmd(cmd, shell) {
|
|
|
893
955
|
}
|
|
894
956
|
function getConfigPath(profile) {
|
|
895
957
|
const file = profile.configFile || `${profile.id}.conf`;
|
|
896
|
-
if (
|
|
897
|
-
return
|
|
958
|
+
if (import_path10.default.isAbsolute(file)) return file;
|
|
959
|
+
return import_path10.default.join(VPN_DIR, file);
|
|
898
960
|
}
|
|
899
961
|
async function getScutilStatus(serviceName) {
|
|
900
962
|
const { stdout } = await runCmd(`scutil --nc status '${shq(serviceName)}'`);
|
|
@@ -1010,7 +1072,7 @@ async function connectWireGuard(profile) {
|
|
|
1010
1072
|
const tunnelName = profile.tunnelName || profile.id;
|
|
1011
1073
|
if (isWindows) {
|
|
1012
1074
|
const confPath2 = getConfigPath(profile);
|
|
1013
|
-
if (!
|
|
1075
|
+
if (!import_fs9.default.existsSync(confPath2)) {
|
|
1014
1076
|
return `Config file not found: ${confPath2}`;
|
|
1015
1077
|
}
|
|
1016
1078
|
const script = `
|
|
@@ -1036,7 +1098,7 @@ async function connectWireGuard(profile) {
|
|
|
1036
1098
|
async function connectOpenVpn(profile) {
|
|
1037
1099
|
if (isWindows) {
|
|
1038
1100
|
const confPath2 = getConfigPath(profile);
|
|
1039
|
-
if (!
|
|
1101
|
+
if (!import_fs9.default.existsSync(confPath2)) {
|
|
1040
1102
|
return `Config file not found: ${confPath2}`;
|
|
1041
1103
|
}
|
|
1042
1104
|
const connector = "C:\\Program Files\\OpenVPN Connect\\ovpnconnector.exe";
|
|
@@ -1063,7 +1125,7 @@ async function connectOpenVpn(profile) {
|
|
|
1063
1125
|
return scutilStart(profile.serviceName);
|
|
1064
1126
|
}
|
|
1065
1127
|
const confPath = getConfigPath(profile);
|
|
1066
|
-
if (!
|
|
1128
|
+
if (!import_fs9.default.existsSync(confPath)) {
|
|
1067
1129
|
return `Config file not found: ${confPath}`;
|
|
1068
1130
|
}
|
|
1069
1131
|
const { stderr } = await runCmd(`sudo openvpn --config '${shq(confPath)}' --daemon`);
|
|
@@ -1072,7 +1134,7 @@ async function connectOpenVpn(profile) {
|
|
|
1072
1134
|
async function connectAzure(profile) {
|
|
1073
1135
|
if (isWindows) {
|
|
1074
1136
|
const confPath = getConfigPath(profile);
|
|
1075
|
-
if (!
|
|
1137
|
+
if (!import_fs9.default.existsSync(confPath)) {
|
|
1076
1138
|
return `Config file not found: ${confPath}`;
|
|
1077
1139
|
}
|
|
1078
1140
|
await runCmd(`& 'AzureVpn.exe' -i '${psq(confPath)}' 2>&1`);
|
|
@@ -1194,8 +1256,8 @@ async function disconnectVpn(configs, profileId) {
|
|
|
1194
1256
|
}
|
|
1195
1257
|
|
|
1196
1258
|
// src/claude-sessions.ts
|
|
1197
|
-
var
|
|
1198
|
-
var
|
|
1259
|
+
var import_fs10 = __toESM(require("fs"));
|
|
1260
|
+
var import_path11 = __toESM(require("path"));
|
|
1199
1261
|
var import_os7 = __toESM(require("os"));
|
|
1200
1262
|
var import_readline = __toESM(require("readline"));
|
|
1201
1263
|
|
|
@@ -1223,10 +1285,10 @@ function findProjectDirs(allDirs, filterProjectPath) {
|
|
|
1223
1285
|
}
|
|
1224
1286
|
|
|
1225
1287
|
// src/claude-sessions.ts
|
|
1226
|
-
var CLAUDE_PROJECTS_DIR =
|
|
1288
|
+
var CLAUDE_PROJECTS_DIR = import_path11.default.join(import_os7.default.homedir(), ".claude", "projects");
|
|
1227
1289
|
async function parseSessionFile(filePath) {
|
|
1228
1290
|
try {
|
|
1229
|
-
const stream =
|
|
1291
|
+
const stream = import_fs10.default.createReadStream(filePath, { encoding: "utf-8" });
|
|
1230
1292
|
const rl = import_readline.default.createInterface({ input: stream, crlfDelay: Infinity });
|
|
1231
1293
|
let firstMessage = "";
|
|
1232
1294
|
let lastTimestamp = "";
|
|
@@ -1274,17 +1336,17 @@ async function parseSessionFile(filePath) {
|
|
|
1274
1336
|
}
|
|
1275
1337
|
async function listClaudeSessions(filterProjectPath) {
|
|
1276
1338
|
const sessions2 = [];
|
|
1277
|
-
if (!
|
|
1339
|
+
if (!import_fs10.default.existsSync(CLAUDE_PROJECTS_DIR)) return sessions2;
|
|
1278
1340
|
const isDirSafe = (d) => {
|
|
1279
1341
|
try {
|
|
1280
|
-
return
|
|
1342
|
+
return import_fs10.default.statSync(import_path11.default.join(CLAUDE_PROJECTS_DIR, d)).isDirectory();
|
|
1281
1343
|
} catch {
|
|
1282
1344
|
return false;
|
|
1283
1345
|
}
|
|
1284
1346
|
};
|
|
1285
1347
|
let allEntries;
|
|
1286
1348
|
try {
|
|
1287
|
-
allEntries =
|
|
1349
|
+
allEntries = import_fs10.default.readdirSync(CLAUDE_PROJECTS_DIR);
|
|
1288
1350
|
} catch {
|
|
1289
1351
|
return sessions2;
|
|
1290
1352
|
}
|
|
@@ -1297,16 +1359,16 @@ async function listClaudeSessions(filterProjectPath) {
|
|
|
1297
1359
|
projectDirs = allEntries.filter((d) => isDirSafe(d) && !d.startsWith("-private-"));
|
|
1298
1360
|
}
|
|
1299
1361
|
for (const dirName of projectDirs) {
|
|
1300
|
-
const dirPath =
|
|
1362
|
+
const dirPath = import_path11.default.join(CLAUDE_PROJECTS_DIR, dirName);
|
|
1301
1363
|
let files;
|
|
1302
1364
|
try {
|
|
1303
|
-
files =
|
|
1365
|
+
files = import_fs10.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
|
|
1304
1366
|
} catch {
|
|
1305
1367
|
continue;
|
|
1306
1368
|
}
|
|
1307
1369
|
for (const file of files) {
|
|
1308
1370
|
const sessionId = file.replace(".jsonl", "");
|
|
1309
|
-
const parsed = await parseSessionFile(
|
|
1371
|
+
const parsed = await parseSessionFile(import_path11.default.join(dirPath, file));
|
|
1310
1372
|
if (!parsed) continue;
|
|
1311
1373
|
const projectPath = parsed.cwd || dirName.replace(/^-/, "/").replace(/-/g, "/");
|
|
1312
1374
|
sessions2.push({
|
|
@@ -1326,21 +1388,21 @@ async function listClaudeSessions(filterProjectPath) {
|
|
|
1326
1388
|
}
|
|
1327
1389
|
|
|
1328
1390
|
// src/claude-conversation.ts
|
|
1329
|
-
var
|
|
1330
|
-
var
|
|
1391
|
+
var import_fs11 = __toESM(require("fs"));
|
|
1392
|
+
var import_path12 = __toESM(require("path"));
|
|
1331
1393
|
var import_os8 = __toESM(require("os"));
|
|
1332
1394
|
var import_readline2 = __toESM(require("readline"));
|
|
1333
|
-
var CLAUDE_PROJECTS_DIR2 =
|
|
1395
|
+
var CLAUDE_PROJECTS_DIR2 = import_path12.default.join(import_os8.default.homedir(), ".claude", "projects");
|
|
1334
1396
|
function findProjectDir(projectPath) {
|
|
1335
1397
|
const encoded = encodeProjectPath(projectPath);
|
|
1336
|
-
const dirPath =
|
|
1337
|
-
if (
|
|
1338
|
-
if (!
|
|
1398
|
+
const dirPath = import_path12.default.join(CLAUDE_PROJECTS_DIR2, encoded);
|
|
1399
|
+
if (import_fs11.default.existsSync(dirPath)) return dirPath;
|
|
1400
|
+
if (!import_fs11.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
|
|
1339
1401
|
let allDirs;
|
|
1340
1402
|
try {
|
|
1341
|
-
allDirs =
|
|
1403
|
+
allDirs = import_fs11.default.readdirSync(CLAUDE_PROJECTS_DIR2).filter((d) => {
|
|
1342
1404
|
try {
|
|
1343
|
-
return
|
|
1405
|
+
return import_fs11.default.statSync(import_path12.default.join(CLAUDE_PROJECTS_DIR2, d)).isDirectory();
|
|
1344
1406
|
} catch {
|
|
1345
1407
|
return false;
|
|
1346
1408
|
}
|
|
@@ -1350,23 +1412,23 @@ function findProjectDir(projectPath) {
|
|
|
1350
1412
|
}
|
|
1351
1413
|
const matches = findProjectDirs(allDirs, projectPath);
|
|
1352
1414
|
if (matches.length > 0) {
|
|
1353
|
-
return
|
|
1415
|
+
return import_path12.default.join(CLAUDE_PROJECTS_DIR2, matches[0]);
|
|
1354
1416
|
}
|
|
1355
1417
|
return null;
|
|
1356
1418
|
}
|
|
1357
1419
|
function findSessionFileById(sessionId) {
|
|
1358
1420
|
if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return null;
|
|
1359
|
-
if (!
|
|
1421
|
+
if (!import_fs11.default.existsSync(CLAUDE_PROJECTS_DIR2)) return null;
|
|
1360
1422
|
let dirs;
|
|
1361
1423
|
try {
|
|
1362
|
-
dirs =
|
|
1424
|
+
dirs = import_fs11.default.readdirSync(CLAUDE_PROJECTS_DIR2);
|
|
1363
1425
|
} catch {
|
|
1364
1426
|
return null;
|
|
1365
1427
|
}
|
|
1366
1428
|
for (const dir of dirs) {
|
|
1367
|
-
const candidate =
|
|
1429
|
+
const candidate = import_path12.default.join(CLAUDE_PROJECTS_DIR2, dir, `${sessionId}.jsonl`);
|
|
1368
1430
|
try {
|
|
1369
|
-
if (
|
|
1431
|
+
if (import_fs11.default.statSync(candidate).isFile()) return candidate;
|
|
1370
1432
|
} catch {
|
|
1371
1433
|
}
|
|
1372
1434
|
}
|
|
@@ -1377,20 +1439,20 @@ function findLatestSessionFile(projectPath) {
|
|
|
1377
1439
|
if (!dirPath) return null;
|
|
1378
1440
|
let entries;
|
|
1379
1441
|
try {
|
|
1380
|
-
entries =
|
|
1442
|
+
entries = import_fs11.default.readdirSync(dirPath);
|
|
1381
1443
|
} catch {
|
|
1382
1444
|
return null;
|
|
1383
1445
|
}
|
|
1384
1446
|
const files = entries.filter((f) => f.endsWith(".jsonl") && !f.includes("/")).map((f) => {
|
|
1385
1447
|
try {
|
|
1386
|
-
return { name: f, mtime:
|
|
1448
|
+
return { name: f, mtime: import_fs11.default.statSync(import_path12.default.join(dirPath, f)).mtimeMs };
|
|
1387
1449
|
} catch {
|
|
1388
1450
|
return null;
|
|
1389
1451
|
}
|
|
1390
1452
|
}).filter((f) => f !== null).sort((a, b) => b.mtime - a.mtime);
|
|
1391
1453
|
if (files.length === 0) return null;
|
|
1392
1454
|
return {
|
|
1393
|
-
filePath:
|
|
1455
|
+
filePath: import_path12.default.join(dirPath, files[0].name),
|
|
1394
1456
|
sessionId: files[0].name.replace(".jsonl", "")
|
|
1395
1457
|
};
|
|
1396
1458
|
}
|
|
@@ -1400,8 +1462,8 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
|
|
|
1400
1462
|
if (specificSessionId) {
|
|
1401
1463
|
sessionId = specificSessionId;
|
|
1402
1464
|
const dirPath = findProjectDir(projectPath);
|
|
1403
|
-
const direct = dirPath ?
|
|
1404
|
-
if (direct &&
|
|
1465
|
+
const direct = dirPath ? import_path12.default.join(dirPath, `${specificSessionId}.jsonl`) : null;
|
|
1466
|
+
if (direct && import_fs11.default.existsSync(direct)) {
|
|
1405
1467
|
filePath = direct;
|
|
1406
1468
|
} else {
|
|
1407
1469
|
const byId = findSessionFileById(specificSessionId);
|
|
@@ -1415,7 +1477,7 @@ async function readConversation(projectPath, afterLine = 0, specificSessionId) {
|
|
|
1415
1477
|
sessionId = found.sessionId;
|
|
1416
1478
|
}
|
|
1417
1479
|
try {
|
|
1418
|
-
const stream =
|
|
1480
|
+
const stream = import_fs11.default.createReadStream(filePath, { encoding: "utf-8" });
|
|
1419
1481
|
const rl = import_readline2.default.createInterface({ input: stream, crlfDelay: Infinity });
|
|
1420
1482
|
const messages = [];
|
|
1421
1483
|
let lineNum = 0;
|
|
@@ -1504,6 +1566,13 @@ if (!isConfigured()) {
|
|
|
1504
1566
|
}
|
|
1505
1567
|
var config = loadConfig();
|
|
1506
1568
|
logger.info({ agentId: config.agentId, serverUrl: config.serverUrl }, "Starting agent");
|
|
1569
|
+
var lock = acquireSingleInstanceLock();
|
|
1570
|
+
if (!lock.ok) {
|
|
1571
|
+
console.error(
|
|
1572
|
+
`Another crc-agent is already running on this machine (PID ${lock.pid}). Stop it first, or set CRC_ALLOW_MULTIPLE=1 to override.`
|
|
1573
|
+
);
|
|
1574
|
+
process.exit(1);
|
|
1575
|
+
}
|
|
1507
1576
|
ensurePtyHelperExecutable();
|
|
1508
1577
|
process.on("unhandledRejection", (reason) => {
|
|
1509
1578
|
logger.error({ reason }, "Unhandled promise rejection (agent kept alive)");
|
|
@@ -1692,7 +1761,17 @@ socket.on(AGENT_EXEC, async (payload) => {
|
|
|
1692
1761
|
});
|
|
1693
1762
|
socket.on(CLAUDE_CONV_READ, async (payload) => {
|
|
1694
1763
|
try {
|
|
1695
|
-
|
|
1764
|
+
let sessionId = payload.sessionId;
|
|
1765
|
+
if (!sessionId && payload.terminalSessionId) {
|
|
1766
|
+
const ptyPid = getSessionPid(payload.terminalSessionId);
|
|
1767
|
+
const resolved = ptyPid ? await findClaudeSessionForPtyPid(ptyPid) : null;
|
|
1768
|
+
if (!resolved) {
|
|
1769
|
+
socket.emit(CLAUDE_CONV_DATA, { sessionId: "", messages: [], totalLines: 0 });
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
sessionId = resolved;
|
|
1773
|
+
}
|
|
1774
|
+
const result = await readConversation(payload.projectPath, payload.afterLine || 0, sessionId);
|
|
1696
1775
|
if (result) {
|
|
1697
1776
|
socket.emit(CLAUDE_CONV_DATA, {
|
|
1698
1777
|
sessionId: result.sessionId,
|