ragent-cli 1.11.3 → 1.11.5
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 +399 -79
- package/dist/sbom.json +5 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "ragent-cli",
|
|
34
|
-
version: "1.11.
|
|
34
|
+
version: "1.11.5",
|
|
35
35
|
description: "CLI agent for rAgent Live \u2014 browser-first terminal control plane for AI coding agents",
|
|
36
36
|
main: "dist/index.js",
|
|
37
37
|
bin: {
|
|
@@ -111,7 +111,7 @@ var require_package = __commonJS({
|
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
// src/index.ts
|
|
114
|
-
var
|
|
114
|
+
var fs8 = __toESM(require("fs"));
|
|
115
115
|
var import_commander = require("commander");
|
|
116
116
|
|
|
117
117
|
// src/constants.ts
|
|
@@ -311,12 +311,12 @@ async function maybeWarnUpdate() {
|
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
// src/commands/connect.ts
|
|
314
|
-
var
|
|
314
|
+
var os10 = __toESM(require("os"));
|
|
315
315
|
|
|
316
316
|
// src/agent.ts
|
|
317
|
-
var
|
|
318
|
-
var
|
|
319
|
-
var
|
|
317
|
+
var fs6 = __toESM(require("fs"));
|
|
318
|
+
var os9 = __toESM(require("os"));
|
|
319
|
+
var path5 = __toESM(require("path"));
|
|
320
320
|
var import_ws5 = __toESM(require("ws"));
|
|
321
321
|
|
|
322
322
|
// src/auth.ts
|
|
@@ -1288,14 +1288,14 @@ function pathMatchesAgentTranscript(target, agentType) {
|
|
|
1288
1288
|
if (agentType === "Gemini CLI") return isGeminiTranscript;
|
|
1289
1289
|
return isClaudeTranscript || isCodexTranscript || isGeminiTranscript;
|
|
1290
1290
|
}
|
|
1291
|
-
function getNewestPath(paths) {
|
|
1291
|
+
function getNewestPath(paths, options) {
|
|
1292
1292
|
const ranked = paths.map((target) => {
|
|
1293
1293
|
try {
|
|
1294
1294
|
return { target, mtimeMs: fs2.statSync(target).mtimeMs };
|
|
1295
1295
|
} catch {
|
|
1296
1296
|
return null;
|
|
1297
1297
|
}
|
|
1298
|
-
}).filter((entry) => entry !== null).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1298
|
+
}).filter((entry) => entry !== null).filter((entry) => options?.minMtimeMs === void 0 || entry.mtimeMs >= options.minMtimeMs).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1299
1299
|
return ranked[0]?.target ?? null;
|
|
1300
1300
|
}
|
|
1301
1301
|
function discoverTranscriptForPid(pid, agentType) {
|
|
@@ -1327,6 +1327,79 @@ function listChildPids(pid) {
|
|
|
1327
1327
|
return [];
|
|
1328
1328
|
}
|
|
1329
1329
|
}
|
|
1330
|
+
function getProcessCommand(pid) {
|
|
1331
|
+
try {
|
|
1332
|
+
return (0, import_child_process.execFileSync)("ps", ["-p", String(pid), "-o", "args="], {
|
|
1333
|
+
encoding: "utf-8",
|
|
1334
|
+
timeout: 3e3,
|
|
1335
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1336
|
+
}).trim() || null;
|
|
1337
|
+
} catch {
|
|
1338
|
+
return null;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function commandMatchesAgent(command, agentType) {
|
|
1342
|
+
if (!command || !agentType) return false;
|
|
1343
|
+
const parts = command.toLowerCase().trim().split(/\s+/);
|
|
1344
|
+
const binary = parts[0]?.split("/").pop() ?? "";
|
|
1345
|
+
const scriptArg = parts[1]?.split("/").pop() ?? "";
|
|
1346
|
+
if (agentType === "Claude Code") {
|
|
1347
|
+
return binary === "claude" || binary === "claude-code" || command.includes("claude-code");
|
|
1348
|
+
}
|
|
1349
|
+
if (agentType === "Codex CLI") {
|
|
1350
|
+
return binary === "codex" || scriptArg === "codex" || command.includes("codex-cli");
|
|
1351
|
+
}
|
|
1352
|
+
if (agentType === "Gemini CLI") {
|
|
1353
|
+
return binary === "gemini" || scriptArg === "gemini";
|
|
1354
|
+
}
|
|
1355
|
+
return false;
|
|
1356
|
+
}
|
|
1357
|
+
function getProcessStartEpochMs(pid) {
|
|
1358
|
+
try {
|
|
1359
|
+
const stat = fs2.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
1360
|
+
const closeParen = stat.lastIndexOf(")");
|
|
1361
|
+
if (closeParen < 0) return null;
|
|
1362
|
+
const rest = stat.slice(closeParen + 2).split(" ");
|
|
1363
|
+
const startTicks = Number(rest[19]);
|
|
1364
|
+
if (!Number.isFinite(startTicks) || startTicks < 0) return null;
|
|
1365
|
+
const procStat = fs2.readFileSync("/proc/stat", "utf8");
|
|
1366
|
+
const bootLine = procStat.split("\n").find((line) => line.startsWith("btime "));
|
|
1367
|
+
const bootSeconds = Number(bootLine?.split(/\s+/)[1]);
|
|
1368
|
+
if (!Number.isFinite(bootSeconds) || bootSeconds <= 0) return null;
|
|
1369
|
+
const ticksPerSecond = Number((0, import_child_process.execFileSync)("getconf", ["CLK_TCK"], {
|
|
1370
|
+
encoding: "utf-8",
|
|
1371
|
+
timeout: 3e3
|
|
1372
|
+
}).trim());
|
|
1373
|
+
if (!Number.isFinite(ticksPerSecond) || ticksPerSecond <= 0) return null;
|
|
1374
|
+
return (bootSeconds + startTicks / ticksPerSecond) * 1e3;
|
|
1375
|
+
} catch {
|
|
1376
|
+
return null;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
function findAgentProcessInfos(rootPid, agentType) {
|
|
1380
|
+
const queue = [rootPid];
|
|
1381
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1382
|
+
const infos = [];
|
|
1383
|
+
while (queue.length > 0 && visited.size < 64) {
|
|
1384
|
+
const pid = queue.shift();
|
|
1385
|
+
if (!pid || visited.has(pid)) continue;
|
|
1386
|
+
visited.add(pid);
|
|
1387
|
+
const command = getProcessCommand(pid);
|
|
1388
|
+
if (commandMatchesAgent(command, agentType)) {
|
|
1389
|
+
let cwd = null;
|
|
1390
|
+
try {
|
|
1391
|
+
cwd = fs2.readlinkSync(`/proc/${pid}/cwd`);
|
|
1392
|
+
} catch {
|
|
1393
|
+
cwd = null;
|
|
1394
|
+
}
|
|
1395
|
+
infos.push({ pid, command, startEpochMs: getProcessStartEpochMs(pid), cwd });
|
|
1396
|
+
}
|
|
1397
|
+
for (const childPid of listChildPids(pid)) {
|
|
1398
|
+
if (!visited.has(childPid)) queue.push(childPid);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return infos;
|
|
1402
|
+
}
|
|
1330
1403
|
function discoverViaProc(rootPid, agentType) {
|
|
1331
1404
|
const queue = [rootPid];
|
|
1332
1405
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -1347,10 +1420,10 @@ function discoverViaProc(rootPid, agentType) {
|
|
|
1347
1420
|
}
|
|
1348
1421
|
return getNewestPath(candidates);
|
|
1349
1422
|
}
|
|
1350
|
-
function discoverViaProcessCwd(pid) {
|
|
1423
|
+
function discoverViaProcessCwd(pid, minMtimeMs) {
|
|
1351
1424
|
try {
|
|
1352
1425
|
const processCwd = fs2.readlinkSync(`/proc/${pid}/cwd`);
|
|
1353
|
-
return processCwd ? discoverViaCwd(processCwd, pid) : null;
|
|
1426
|
+
return processCwd ? discoverViaCwd(processCwd, pid, minMtimeMs) : null;
|
|
1354
1427
|
} catch {
|
|
1355
1428
|
return null;
|
|
1356
1429
|
}
|
|
@@ -1378,6 +1451,35 @@ function resolveUserHome(pid) {
|
|
|
1378
1451
|
return null;
|
|
1379
1452
|
}
|
|
1380
1453
|
}
|
|
1454
|
+
function discoverCodexTranscriptFromHome(pid, minMtimeMs) {
|
|
1455
|
+
const userHome = resolveUserHome(pid);
|
|
1456
|
+
if (!userHome) return null;
|
|
1457
|
+
const sessionsDir = path2.join(userHome, ".codex", "sessions");
|
|
1458
|
+
if (!fs2.existsSync(sessionsDir)) return null;
|
|
1459
|
+
const matches = [];
|
|
1460
|
+
const stack = [sessionsDir];
|
|
1461
|
+
let visited = 0;
|
|
1462
|
+
while (stack.length > 0 && visited < 3e3) {
|
|
1463
|
+
const current = stack.pop();
|
|
1464
|
+
if (!current) continue;
|
|
1465
|
+
visited++;
|
|
1466
|
+
let entries;
|
|
1467
|
+
try {
|
|
1468
|
+
entries = fs2.readdirSync(current, { withFileTypes: true });
|
|
1469
|
+
} catch {
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
for (const entry of entries) {
|
|
1473
|
+
const fullPath = path2.join(current, entry.name);
|
|
1474
|
+
if (entry.isDirectory()) {
|
|
1475
|
+
stack.push(fullPath);
|
|
1476
|
+
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
1477
|
+
matches.push(fullPath);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
return getNewestPath(matches, { minMtimeMs });
|
|
1482
|
+
}
|
|
1381
1483
|
function discoverGeminiTranscriptFromHome(pid) {
|
|
1382
1484
|
const userHome = resolveUserHome(pid);
|
|
1383
1485
|
if (!userHome) return null;
|
|
@@ -1407,27 +1509,21 @@ function discoverGeminiTranscriptFromHome(pid) {
|
|
|
1407
1509
|
}
|
|
1408
1510
|
return getNewestPath(matches);
|
|
1409
1511
|
}
|
|
1410
|
-
function discoverViaCwd(paneCwd, pid) {
|
|
1512
|
+
function discoverViaCwd(paneCwd, pid, minMtimeMs) {
|
|
1411
1513
|
const userHome = resolveUserHome(pid);
|
|
1412
1514
|
if (!userHome) return null;
|
|
1413
1515
|
const claudeProjectsDir = path2.join(userHome, ".claude", "projects");
|
|
1414
1516
|
if (!fs2.existsSync(claudeProjectsDir)) return null;
|
|
1415
|
-
|
|
1517
|
+
const possibleCwds = /* @__PURE__ */ new Set([paneCwd]);
|
|
1416
1518
|
try {
|
|
1417
|
-
|
|
1519
|
+
possibleCwds.add(fs2.realpathSync(paneCwd));
|
|
1418
1520
|
} catch {
|
|
1419
|
-
return null;
|
|
1420
1521
|
}
|
|
1421
|
-
const
|
|
1422
|
-
|
|
1423
|
-
if (!fs2.existsSync(projectDir)) return null;
|
|
1522
|
+
const projectDirs = Array.from(possibleCwds).map((cwd) => path2.join(claudeProjectsDir, cwd.replace(/\//g, "-"))).filter((projectDir, index, arr) => arr.indexOf(projectDir) === index).filter((projectDir) => fs2.existsSync(projectDir));
|
|
1523
|
+
if (projectDirs.length === 0) return null;
|
|
1424
1524
|
try {
|
|
1425
|
-
const jsonlFiles = fs2.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).map((f) =>
|
|
1426
|
-
|
|
1427
|
-
const stat = fs2.statSync(fullPath);
|
|
1428
|
-
return { path: fullPath, mtime: stat.mtimeMs };
|
|
1429
|
-
}).sort((a, b) => b.mtime - a.mtime);
|
|
1430
|
-
return jsonlFiles.length > 0 ? jsonlFiles[0].path : null;
|
|
1525
|
+
const jsonlFiles = projectDirs.flatMap((projectDir) => fs2.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).map((f) => path2.join(projectDir, f)));
|
|
1526
|
+
return getNewestPath(jsonlFiles, { minMtimeMs });
|
|
1431
1527
|
} catch {
|
|
1432
1528
|
return null;
|
|
1433
1529
|
}
|
|
@@ -1438,9 +1534,18 @@ function discoverTranscriptFile(sessionId, agentType) {
|
|
|
1438
1534
|
const procResult = discoverViaProc(processPid, agentType);
|
|
1439
1535
|
if (procResult) return procResult;
|
|
1440
1536
|
if (agentType === "Claude Code") {
|
|
1441
|
-
const
|
|
1537
|
+
const startMs = getProcessStartEpochMs(processPid);
|
|
1538
|
+
const cwdResult = discoverViaProcessCwd(processPid, startMs ? startMs - 6e4 : void 0);
|
|
1442
1539
|
if (cwdResult) return cwdResult;
|
|
1443
1540
|
}
|
|
1541
|
+
if (agentType === "Codex CLI") {
|
|
1542
|
+
const startMs = getProcessStartEpochMs(processPid);
|
|
1543
|
+
const command = getProcessCommand(processPid);
|
|
1544
|
+
if (commandMatchesAgent(command, agentType)) {
|
|
1545
|
+
const codexResult = discoverCodexTranscriptFromHome(processPid, startMs ? startMs - 6e4 : void 0);
|
|
1546
|
+
if (codexResult) return codexResult;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1444
1549
|
if (agentType === "Gemini CLI") {
|
|
1445
1550
|
const geminiResult = discoverGeminiTranscriptFromHome(processPid);
|
|
1446
1551
|
if (geminiResult) return geminiResult;
|
|
@@ -1469,6 +1574,16 @@ function discoverTranscriptFile(sessionId, agentType) {
|
|
|
1469
1574
|
} catch {
|
|
1470
1575
|
}
|
|
1471
1576
|
if (agentType === "Claude Code") {
|
|
1577
|
+
const agentInfos = panePid ? findAgentProcessInfos(panePid, agentType) : [];
|
|
1578
|
+
for (const info of agentInfos) {
|
|
1579
|
+
if (!info.cwd) continue;
|
|
1580
|
+
const cwdResult = discoverViaCwd(
|
|
1581
|
+
info.cwd,
|
|
1582
|
+
info.pid,
|
|
1583
|
+
info.startEpochMs ? info.startEpochMs - 6e4 : void 0
|
|
1584
|
+
);
|
|
1585
|
+
if (cwdResult) return cwdResult;
|
|
1586
|
+
}
|
|
1472
1587
|
try {
|
|
1473
1588
|
const paneCwd = (0, import_child_process.execFileSync)(
|
|
1474
1589
|
"tmux",
|
|
@@ -1479,6 +1594,16 @@ function discoverTranscriptFile(sessionId, agentType) {
|
|
|
1479
1594
|
} catch {
|
|
1480
1595
|
}
|
|
1481
1596
|
}
|
|
1597
|
+
if (agentType === "Codex CLI") {
|
|
1598
|
+
const agentInfos = panePid ? findAgentProcessInfos(panePid, agentType) : [];
|
|
1599
|
+
const newestForAgent = getNewestPath(
|
|
1600
|
+
agentInfos.map((info) => discoverCodexTranscriptFromHome(
|
|
1601
|
+
info.pid,
|
|
1602
|
+
info.startEpochMs ? info.startEpochMs - 6e4 : void 0
|
|
1603
|
+
)).filter((value) => Boolean(value))
|
|
1604
|
+
);
|
|
1605
|
+
if (newestForAgent) return newestForAgent;
|
|
1606
|
+
}
|
|
1482
1607
|
if (agentType === "Gemini CLI") {
|
|
1483
1608
|
return discoverGeminiTranscriptFromHome(panePid ?? void 0);
|
|
1484
1609
|
}
|
|
@@ -1742,10 +1867,25 @@ var TranscriptWatcherManager = class {
|
|
|
1742
1867
|
}
|
|
1743
1868
|
/** Enable markdown streaming for a session. */
|
|
1744
1869
|
enableMarkdown(sessionId, agentType) {
|
|
1870
|
+
const requestedAgentType = agentType ?? "";
|
|
1745
1871
|
const existing = this.active.get(sessionId);
|
|
1746
1872
|
if (existing) {
|
|
1747
|
-
|
|
1748
|
-
|
|
1873
|
+
const newPath = discoverTranscriptFile(sessionId, agentType);
|
|
1874
|
+
const agentChanged = existing.agentType !== requestedAgentType;
|
|
1875
|
+
const pathChanged = Boolean(newPath && newPath !== existing.filePath);
|
|
1876
|
+
const existingPathGone = !fs2.existsSync(existing.filePath);
|
|
1877
|
+
if (!agentChanged && !pathChanged && !existingPathGone) {
|
|
1878
|
+
existing.watcher.addSubscriber();
|
|
1879
|
+
return { ok: true };
|
|
1880
|
+
}
|
|
1881
|
+
existing.watcher.stop();
|
|
1882
|
+
this.active.delete(sessionId);
|
|
1883
|
+
this.stopRediscovery(sessionId);
|
|
1884
|
+
this.clearTranscriptState(sessionId);
|
|
1885
|
+
if (!newPath) {
|
|
1886
|
+
log5.info("no replacement transcript file found", { sessionId, agentType });
|
|
1887
|
+
return { ok: false, reason: "no_transcript" };
|
|
1888
|
+
}
|
|
1749
1889
|
}
|
|
1750
1890
|
const parser = getParser(agentType);
|
|
1751
1891
|
if (!parser) {
|
|
@@ -1791,6 +1931,12 @@ var TranscriptWatcherManager = class {
|
|
|
1791
1931
|
this.startRediscovery(sessionId, agentType);
|
|
1792
1932
|
return { ok: true };
|
|
1793
1933
|
}
|
|
1934
|
+
clearTranscriptState(sessionId) {
|
|
1935
|
+
this.sendSnapshotFn(sessionId, [], 0);
|
|
1936
|
+
if (this.sendV2SnapshotFn) {
|
|
1937
|
+
this.sendV2SnapshotFn(sessionId, createEmptySnapshot(sessionId), 0);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1794
1940
|
/** Disable markdown streaming for a session. */
|
|
1795
1941
|
disableMarkdown(sessionId) {
|
|
1796
1942
|
const session = this.active.get(sessionId);
|
|
@@ -3120,26 +3266,23 @@ var SessionStreamer = class {
|
|
|
3120
3266
|
alternateScreen = altFlag === "1";
|
|
3121
3267
|
} catch {
|
|
3122
3268
|
}
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
);
|
|
3132
|
-
if (scrollback && scrollback.length > 0) {
|
|
3133
|
-
this.sendFn(sessionId, scrollback.replace(/\n/g, "\r\n"));
|
|
3134
|
-
}
|
|
3135
|
-
} catch {
|
|
3269
|
+
try {
|
|
3270
|
+
const scrollback = (0, import_node_child_process2.execFileSync)(
|
|
3271
|
+
"tmux",
|
|
3272
|
+
["capture-pane", "-t", paneTarget, "-p", "-e", "-S", "-5000", "-E", "-1"],
|
|
3273
|
+
{ env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
|
|
3274
|
+
);
|
|
3275
|
+
if (scrollback && scrollback.length > 0) {
|
|
3276
|
+
this.sendFn(sessionId, scrollback.replace(/\n/g, "\r\n"));
|
|
3136
3277
|
}
|
|
3137
|
-
|
|
3278
|
+
} catch {
|
|
3138
3279
|
}
|
|
3280
|
+
this.sendFn(sessionId, "\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
|
|
3139
3281
|
try {
|
|
3282
|
+
const captureArgs = alternateScreen ? ["capture-pane", "-a", "-t", paneTarget, "-p", "-e"] : ["capture-pane", "-t", paneTarget, "-p", "-e"];
|
|
3140
3283
|
const initial = (0, import_node_child_process2.execFileSync)(
|
|
3141
3284
|
"tmux",
|
|
3142
|
-
|
|
3285
|
+
captureArgs,
|
|
3143
3286
|
{ env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
|
|
3144
3287
|
);
|
|
3145
3288
|
if (initial) {
|
|
@@ -3261,26 +3404,23 @@ var SessionStreamer = class {
|
|
|
3261
3404
|
} catch {
|
|
3262
3405
|
}
|
|
3263
3406
|
stream.alternateScreen = alternateScreen;
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
);
|
|
3273
|
-
if (scrollback && scrollback.length > 0) {
|
|
3274
|
-
this.sendFn(sessionId, scrollback.replace(/\n/g, "\r\n"));
|
|
3275
|
-
}
|
|
3276
|
-
} catch {
|
|
3407
|
+
try {
|
|
3408
|
+
const scrollback = (0, import_node_child_process2.execFileSync)(
|
|
3409
|
+
"tmux",
|
|
3410
|
+
["capture-pane", "-t", paneTarget, "-p", "-e", "-S", "-5000", "-E", "-1"],
|
|
3411
|
+
{ env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
|
|
3412
|
+
);
|
|
3413
|
+
if (scrollback && scrollback.length > 0) {
|
|
3414
|
+
this.sendFn(sessionId, scrollback.replace(/\n/g, "\r\n"));
|
|
3277
3415
|
}
|
|
3278
|
-
|
|
3416
|
+
} catch {
|
|
3279
3417
|
}
|
|
3418
|
+
this.sendFn(sessionId, "\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
|
|
3280
3419
|
try {
|
|
3420
|
+
const captureArgs = alternateScreen ? ["capture-pane", "-a", "-t", paneTarget, "-p", "-e"] : ["capture-pane", "-t", paneTarget, "-p", "-e"];
|
|
3281
3421
|
const initial = (0, import_node_child_process2.execFileSync)(
|
|
3282
3422
|
"tmux",
|
|
3283
|
-
|
|
3423
|
+
captureArgs,
|
|
3284
3424
|
{ env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
|
|
3285
3425
|
);
|
|
3286
3426
|
if (initial) {
|
|
@@ -3642,6 +3782,105 @@ var SequenceTracker = class {
|
|
|
3642
3782
|
var import_node_child_process3 = require("child_process");
|
|
3643
3783
|
var pty2 = __toESM(require("node-pty"));
|
|
3644
3784
|
var log9 = createLogger("pty");
|
|
3785
|
+
var ANSI_KEY_SEQUENCES = /* @__PURE__ */ new Map([
|
|
3786
|
+
["\x1B[A", "Up"],
|
|
3787
|
+
["\x1B[B", "Down"],
|
|
3788
|
+
["\x1B[C", "Right"],
|
|
3789
|
+
["\x1B[D", "Left"],
|
|
3790
|
+
["\x1B[H", "Home"],
|
|
3791
|
+
["\x1B[F", "End"],
|
|
3792
|
+
["\x1BOH", "Home"],
|
|
3793
|
+
["\x1BOF", "End"],
|
|
3794
|
+
["\x1B[1~", "Home"],
|
|
3795
|
+
["\x1B[4~", "End"],
|
|
3796
|
+
["\x1B[3~", "Delete"],
|
|
3797
|
+
["\x1B[5~", "PageUp"],
|
|
3798
|
+
["\x1B[6~", "PageDown"]
|
|
3799
|
+
]);
|
|
3800
|
+
function controlKeyName(charCode) {
|
|
3801
|
+
if (charCode >= 1 && charCode <= 26) {
|
|
3802
|
+
return `C-${String.fromCharCode(charCode + 96)}`;
|
|
3803
|
+
}
|
|
3804
|
+
switch (charCode) {
|
|
3805
|
+
case 0:
|
|
3806
|
+
return "C-Space";
|
|
3807
|
+
case 27:
|
|
3808
|
+
return "Escape";
|
|
3809
|
+
case 28:
|
|
3810
|
+
return "C-\\";
|
|
3811
|
+
case 29:
|
|
3812
|
+
return "C-]";
|
|
3813
|
+
case 30:
|
|
3814
|
+
return "C-^";
|
|
3815
|
+
case 31:
|
|
3816
|
+
return "C-_";
|
|
3817
|
+
default:
|
|
3818
|
+
return null;
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
function appendLiteralSegment(segments, value) {
|
|
3822
|
+
if (!value) return;
|
|
3823
|
+
const previous = segments[segments.length - 1];
|
|
3824
|
+
if (previous?.kind === "literal") {
|
|
3825
|
+
previous.value += value;
|
|
3826
|
+
} else {
|
|
3827
|
+
segments.push({ kind: "literal", value });
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
function encodeTmuxInput(data) {
|
|
3831
|
+
const segments = [];
|
|
3832
|
+
let literal = "";
|
|
3833
|
+
const flushLiteral = () => {
|
|
3834
|
+
appendLiteralSegment(segments, literal);
|
|
3835
|
+
literal = "";
|
|
3836
|
+
};
|
|
3837
|
+
for (let i = 0; i < data.length; i++) {
|
|
3838
|
+
const remaining = data.slice(i);
|
|
3839
|
+
const matchedSequence = Array.from(ANSI_KEY_SEQUENCES.entries()).find(([sequence]) => remaining.startsWith(sequence));
|
|
3840
|
+
if (matchedSequence) {
|
|
3841
|
+
flushLiteral();
|
|
3842
|
+
const [sequence, key] = matchedSequence;
|
|
3843
|
+
segments.push({ kind: "key", value: key });
|
|
3844
|
+
i += sequence.length - 1;
|
|
3845
|
+
continue;
|
|
3846
|
+
}
|
|
3847
|
+
const charCode = data.charCodeAt(i);
|
|
3848
|
+
if (data[i] === "\r" || data[i] === "\n") {
|
|
3849
|
+
flushLiteral();
|
|
3850
|
+
segments.push({ kind: "key", value: "Enter" });
|
|
3851
|
+
continue;
|
|
3852
|
+
}
|
|
3853
|
+
if (data[i] === " ") {
|
|
3854
|
+
flushLiteral();
|
|
3855
|
+
segments.push({ kind: "key", value: "Tab" });
|
|
3856
|
+
continue;
|
|
3857
|
+
}
|
|
3858
|
+
if (data[i] === "\x7F" || data[i] === "\b") {
|
|
3859
|
+
flushLiteral();
|
|
3860
|
+
segments.push({ kind: "key", value: "BSpace" });
|
|
3861
|
+
continue;
|
|
3862
|
+
}
|
|
3863
|
+
if (charCode < 32) {
|
|
3864
|
+
const keyName = controlKeyName(charCode);
|
|
3865
|
+
if (keyName) {
|
|
3866
|
+
flushLiteral();
|
|
3867
|
+
segments.push({ kind: "key", value: keyName });
|
|
3868
|
+
continue;
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
3871
|
+
literal += data[i];
|
|
3872
|
+
}
|
|
3873
|
+
flushLiteral();
|
|
3874
|
+
return segments;
|
|
3875
|
+
}
|
|
3876
|
+
function execTmuxSendKeys(args) {
|
|
3877
|
+
return new Promise((resolve, reject) => {
|
|
3878
|
+
(0, import_node_child_process3.execFile)("tmux", args, { timeout: 5e3 }, (err) => {
|
|
3879
|
+
if (err) reject(err);
|
|
3880
|
+
else resolve();
|
|
3881
|
+
});
|
|
3882
|
+
});
|
|
3883
|
+
}
|
|
3645
3884
|
function isInteractiveShell(command) {
|
|
3646
3885
|
const trimmed = String(command).trim();
|
|
3647
3886
|
return ["bash", "sh", "zsh", "fish"].includes(trimmed);
|
|
@@ -3678,12 +3917,13 @@ async function sendInputToTmux(sessionId, data) {
|
|
|
3678
3917
|
return;
|
|
3679
3918
|
}
|
|
3680
3919
|
try {
|
|
3681
|
-
|
|
3682
|
-
(
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3920
|
+
for (const segment of encodeTmuxInput(data)) {
|
|
3921
|
+
if (segment.kind === "literal") {
|
|
3922
|
+
await execTmuxSendKeys(["send-keys", "-t", target, "-l", "--", segment.value]);
|
|
3923
|
+
} else {
|
|
3924
|
+
await execTmuxSendKeys(["send-keys", "-t", target, "--", segment.value]);
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3687
3927
|
} catch (error) {
|
|
3688
3928
|
const message = error instanceof Error ? error.message : String(error);
|
|
3689
3929
|
log9.warn("failed to send input", { sessionId, error: message });
|
|
@@ -4081,6 +4321,9 @@ var ConnectionManager = class {
|
|
|
4081
4321
|
// src/control-dispatcher.ts
|
|
4082
4322
|
var crypto2 = __toESM(require("crypto"));
|
|
4083
4323
|
var import_child_process5 = require("child_process");
|
|
4324
|
+
var fs5 = __toESM(require("fs"));
|
|
4325
|
+
var os8 = __toESM(require("os"));
|
|
4326
|
+
var path4 = __toESM(require("path"));
|
|
4084
4327
|
var import_ws4 = __toESM(require("ws"));
|
|
4085
4328
|
|
|
4086
4329
|
// src/service.ts
|
|
@@ -4738,6 +4981,10 @@ function isProcessAlive(pid) {
|
|
|
4738
4981
|
function delay(ms) {
|
|
4739
4982
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4740
4983
|
}
|
|
4984
|
+
function safeAttachmentName(name) {
|
|
4985
|
+
const base = path4.basename(name || "attachment").replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
4986
|
+
return base.slice(0, 120) || "attachment";
|
|
4987
|
+
}
|
|
4741
4988
|
var ControlDispatcher = class {
|
|
4742
4989
|
shell;
|
|
4743
4990
|
streamer;
|
|
@@ -4816,9 +5063,10 @@ var ControlDispatcher = class {
|
|
|
4816
5063
|
"disconnect",
|
|
4817
5064
|
"kill-process",
|
|
4818
5065
|
"start-agent",
|
|
4819
|
-
"provision"
|
|
5066
|
+
"provision",
|
|
5067
|
+
"upload-file"
|
|
4820
5068
|
]);
|
|
4821
|
-
if (dangerousActions.has(action) && this.connection.sessionKey) {
|
|
5069
|
+
if (dangerousActions.has(action) && this.connection.sessionKey && payload._verifiedEncrypted !== true) {
|
|
4822
5070
|
if (!this.verifyMessageHmac(payload)) {
|
|
4823
5071
|
log13.warn("rejecting control action \u2014 HMAC verification failed", { action });
|
|
4824
5072
|
return;
|
|
@@ -4950,9 +5198,42 @@ var ControlDispatcher = class {
|
|
|
4950
5198
|
this.transcriptWatcher.handleSyncRequest(sessionId, fromSeq);
|
|
4951
5199
|
}
|
|
4952
5200
|
return;
|
|
5201
|
+
case "upload-file":
|
|
5202
|
+
this.handleUploadFile(sessionId, payload);
|
|
5203
|
+
return;
|
|
4953
5204
|
default:
|
|
4954
5205
|
}
|
|
4955
5206
|
}
|
|
5207
|
+
handleUploadFile(sessionId, payload) {
|
|
5208
|
+
if (!sessionId) return;
|
|
5209
|
+
const fileName = typeof payload.fileName === "string" ? payload.fileName : "attachment";
|
|
5210
|
+
const contentBase64 = typeof payload.contentBase64 === "string" ? payload.contentBase64 : "";
|
|
5211
|
+
if (!contentBase64 || contentBase64.length > 8 * 1024 * 1024) {
|
|
5212
|
+
log13.warn("rejecting attachment upload \u2014 invalid size", { sessionId, fileName });
|
|
5213
|
+
return;
|
|
5214
|
+
}
|
|
5215
|
+
let content;
|
|
5216
|
+
try {
|
|
5217
|
+
content = Buffer.from(contentBase64, "base64");
|
|
5218
|
+
} catch {
|
|
5219
|
+
log13.warn("rejecting attachment upload \u2014 invalid base64", { sessionId, fileName });
|
|
5220
|
+
return;
|
|
5221
|
+
}
|
|
5222
|
+
if (content.length === 0 || content.length > 6 * 1024 * 1024) {
|
|
5223
|
+
log13.warn("rejecting attachment upload \u2014 decoded size out of bounds", {
|
|
5224
|
+
sessionId,
|
|
5225
|
+
fileName,
|
|
5226
|
+
bytes: content.length
|
|
5227
|
+
});
|
|
5228
|
+
return;
|
|
5229
|
+
}
|
|
5230
|
+
const uploadDir = path4.join(os8.homedir(), ".cache", "ragent", "attachments");
|
|
5231
|
+
fs5.mkdirSync(uploadDir, { recursive: true, mode: 448 });
|
|
5232
|
+
const writtenPath = path4.join(uploadDir, `${Date.now()}-${safeAttachmentName(fileName)}`);
|
|
5233
|
+
fs5.writeFileSync(writtenPath, content, { mode: 384 });
|
|
5234
|
+
log13.info("attachment uploaded", { sessionId, path: writtenPath, bytes: content.length });
|
|
5235
|
+
this.handleInput(writtenPath, sessionId);
|
|
5236
|
+
}
|
|
4956
5237
|
/** Handle input routing to the correct target. */
|
|
4957
5238
|
handleInput(data, sessionId) {
|
|
4958
5239
|
this.connection.requestInventorySync(800);
|
|
@@ -5530,11 +5811,11 @@ var ApprovalEnforcer = class {
|
|
|
5530
5811
|
var log15 = createLogger("agent");
|
|
5531
5812
|
var MAX_TRANSPORT_CHUNK_BYTES = 24 * 1024;
|
|
5532
5813
|
function pidFilePath(hostId) {
|
|
5533
|
-
return
|
|
5814
|
+
return path5.join(CONFIG_DIR, `agent-${hostId}.pid`);
|
|
5534
5815
|
}
|
|
5535
5816
|
function readPidFile(filePath) {
|
|
5536
5817
|
try {
|
|
5537
|
-
const raw =
|
|
5818
|
+
const raw = fs6.readFileSync(filePath, "utf8").trim();
|
|
5538
5819
|
const pid = Number.parseInt(raw, 10);
|
|
5539
5820
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
5540
5821
|
} catch {
|
|
@@ -5559,7 +5840,7 @@ function acquirePidLock(hostId) {
|
|
|
5559
5840
|
Stop it first with: kill ${existingPid} \u2014 or: ragent service stop`
|
|
5560
5841
|
);
|
|
5561
5842
|
}
|
|
5562
|
-
|
|
5843
|
+
fs6.writeFileSync(lockPath, `${process.pid}
|
|
5563
5844
|
`, "utf8");
|
|
5564
5845
|
return lockPath;
|
|
5565
5846
|
}
|
|
@@ -5567,7 +5848,7 @@ function releasePidLock(lockPath) {
|
|
|
5567
5848
|
try {
|
|
5568
5849
|
const currentPid = readPidFile(lockPath);
|
|
5569
5850
|
if (currentPid === process.pid) {
|
|
5570
|
-
|
|
5851
|
+
fs6.unlinkSync(lockPath);
|
|
5571
5852
|
}
|
|
5572
5853
|
} catch {
|
|
5573
5854
|
}
|
|
@@ -5576,7 +5857,7 @@ function resolveRunOptions(commandOptions) {
|
|
|
5576
5857
|
const config = loadConfig();
|
|
5577
5858
|
const portal = commandOptions.portal || config.portal || DEFAULT_PORTAL;
|
|
5578
5859
|
const hostId = sanitizeHostId(commandOptions.id || config.hostId || inferHostId());
|
|
5579
|
-
const hostName = commandOptions.name || config.hostName ||
|
|
5860
|
+
const hostName = commandOptions.name || config.hostName || os9.hostname();
|
|
5580
5861
|
const command = commandOptions.command || config.command || "bash";
|
|
5581
5862
|
const agentToken = commandOptions.agentToken || config.agentToken || "";
|
|
5582
5863
|
return { portal, hostId, hostName, command, agentToken };
|
|
@@ -5812,7 +6093,7 @@ async function runAgent(rawOptions) {
|
|
|
5812
6093
|
sendToGroup(ws, groups.privateGroup, {
|
|
5813
6094
|
type: "register",
|
|
5814
6095
|
hostName: options.hostName,
|
|
5815
|
-
environment:
|
|
6096
|
+
environment: os9.platform()
|
|
5816
6097
|
});
|
|
5817
6098
|
conn.replayBufferedOutput((chunk) => {
|
|
5818
6099
|
for (const outputChunk of splitTransportChunks(chunk)) {
|
|
@@ -5904,7 +6185,46 @@ async function runAgent(rawOptions) {
|
|
|
5904
6185
|
const sid = typeof payload.sessionId === "string" ? payload.sessionId.trim() : "";
|
|
5905
6186
|
dispatcher.handleResize(payload.cols, payload.rows, sid);
|
|
5906
6187
|
} else if (payload.type === "control" && typeof payload.action === "string") {
|
|
5907
|
-
|
|
6188
|
+
let controlPayload = payload;
|
|
6189
|
+
if (typeof payload.enc === "string" && typeof payload.iv === "string" && conn.sessionKey) {
|
|
6190
|
+
const inSeq = typeof payload.seq === "number" ? payload.seq : null;
|
|
6191
|
+
const rxSeq = conn.getRxSeqForViewer(payload.viewerId);
|
|
6192
|
+
if (inSeq === null) {
|
|
6193
|
+
log15.warn("rejecting encrypted control frame without seq");
|
|
6194
|
+
return;
|
|
6195
|
+
}
|
|
6196
|
+
if (!rxSeq.accept(inSeq)) {
|
|
6197
|
+
log15.warn("rejecting replayed/out-of-order control", {
|
|
6198
|
+
seq: inSeq,
|
|
6199
|
+
lastSeen: rxSeq.lastSeq,
|
|
6200
|
+
viewerId: typeof payload.viewerId === "string" ? payload.viewerId : "_legacy"
|
|
6201
|
+
});
|
|
6202
|
+
return;
|
|
6203
|
+
}
|
|
6204
|
+
const decrypted = decryptPayload(
|
|
6205
|
+
payload.enc,
|
|
6206
|
+
payload.iv,
|
|
6207
|
+
conn.sessionKey,
|
|
6208
|
+
inSeq
|
|
6209
|
+
);
|
|
6210
|
+
if (decrypted === null) {
|
|
6211
|
+
log15.warn("failed to decrypt control \u2014 ignoring");
|
|
6212
|
+
return;
|
|
6213
|
+
}
|
|
6214
|
+
try {
|
|
6215
|
+
controlPayload = {
|
|
6216
|
+
...JSON.parse(decrypted),
|
|
6217
|
+
_verifiedEncrypted: true
|
|
6218
|
+
};
|
|
6219
|
+
} catch {
|
|
6220
|
+
log15.warn("failed to parse encrypted control payload");
|
|
6221
|
+
return;
|
|
6222
|
+
}
|
|
6223
|
+
} else if (conn.sessionKey && payload.action === "upload-file") {
|
|
6224
|
+
log15.warn("rejecting plaintext attachment upload while encrypted channel is active");
|
|
6225
|
+
return;
|
|
6226
|
+
}
|
|
6227
|
+
await dispatcher.handleControlAction(controlPayload);
|
|
5908
6228
|
} else if (payload.type === "start-agent") {
|
|
5909
6229
|
await dispatcher.handleControlAction({ ...payload, action: "start-agent" });
|
|
5910
6230
|
} else if (payload.type === "provision") {
|
|
@@ -6075,7 +6395,7 @@ var log16 = createLogger("connect");
|
|
|
6075
6395
|
async function connectMachine(opts) {
|
|
6076
6396
|
const portal = opts.portal || DEFAULT_PORTAL;
|
|
6077
6397
|
const hostId = sanitizeHostId(opts.id || inferHostId());
|
|
6078
|
-
const hostName = opts.name ||
|
|
6398
|
+
const hostName = opts.name || os10.hostname();
|
|
6079
6399
|
const command = opts.command || "bash";
|
|
6080
6400
|
if (!opts.token) {
|
|
6081
6401
|
throw new Error("Connection token is required.");
|
|
@@ -6150,13 +6470,13 @@ function registerRunCommand(program2) {
|
|
|
6150
6470
|
}
|
|
6151
6471
|
|
|
6152
6472
|
// src/commands/doctor.ts
|
|
6153
|
-
var
|
|
6473
|
+
var os11 = __toESM(require("os"));
|
|
6154
6474
|
var log18 = createLogger("doctor");
|
|
6155
6475
|
async function runDoctor(opts) {
|
|
6156
6476
|
const options = resolveRunOptions(opts);
|
|
6157
6477
|
const checks = [];
|
|
6158
|
-
const platformOk =
|
|
6159
|
-
checks.push({ name: "platform", ok: platformOk, detail:
|
|
6478
|
+
const platformOk = os11.platform() === "linux";
|
|
6479
|
+
checks.push({ name: "platform", ok: platformOk, detail: os11.platform() });
|
|
6160
6480
|
checks.push({
|
|
6161
6481
|
name: "node",
|
|
6162
6482
|
ok: Number(process.versions.node.split(".")[0]) >= 20,
|
|
@@ -6341,7 +6661,7 @@ function registerServiceCommand(program2) {
|
|
|
6341
6661
|
}
|
|
6342
6662
|
|
|
6343
6663
|
// src/commands/uninstall.ts
|
|
6344
|
-
var
|
|
6664
|
+
var fs7 = __toESM(require("fs"));
|
|
6345
6665
|
var log21 = createLogger("uninstall");
|
|
6346
6666
|
async function uninstallAgent(opts) {
|
|
6347
6667
|
const config = loadConfig();
|
|
@@ -6373,8 +6693,8 @@ async function uninstallAgent(opts) {
|
|
|
6373
6693
|
}
|
|
6374
6694
|
log21.info("stopping and removing service");
|
|
6375
6695
|
await uninstallService().catch(() => void 0);
|
|
6376
|
-
if (
|
|
6377
|
-
|
|
6696
|
+
if (fs7.existsSync(CONFIG_DIR)) {
|
|
6697
|
+
fs7.rmSync(CONFIG_DIR, { recursive: true, force: true });
|
|
6378
6698
|
log21.info("removed config directory", { path: CONFIG_DIR });
|
|
6379
6699
|
}
|
|
6380
6700
|
try {
|
|
@@ -6477,7 +6797,7 @@ function showStatus() {
|
|
|
6477
6797
|
ragent v${CURRENT_VERSION}
|
|
6478
6798
|
`);
|
|
6479
6799
|
try {
|
|
6480
|
-
const raw =
|
|
6800
|
+
const raw = fs8.readFileSync(CONFIG_FILE, "utf8");
|
|
6481
6801
|
const config = JSON.parse(raw);
|
|
6482
6802
|
if (config.portal && config.agentToken) {
|
|
6483
6803
|
console.log(` Status: Connected`);
|
package/dist/sbom.json
CHANGED
|
@@ -1166,8 +1166,8 @@
|
|
|
1166
1166
|
{
|
|
1167
1167
|
"type": "library",
|
|
1168
1168
|
"name": "ragent-cli",
|
|
1169
|
-
"version": "1.11.
|
|
1170
|
-
"bom-ref": "ragent-live|ragent-cli@1.11.
|
|
1169
|
+
"version": "1.11.5",
|
|
1170
|
+
"bom-ref": "ragent-live|ragent-cli@1.11.5",
|
|
1171
1171
|
"author": "Intellimetrics",
|
|
1172
1172
|
"description": "CLI agent for rAgent Live — browser-first terminal control plane for AI coding agents",
|
|
1173
1173
|
"licenses": [
|
|
@@ -1178,7 +1178,7 @@
|
|
|
1178
1178
|
}
|
|
1179
1179
|
}
|
|
1180
1180
|
],
|
|
1181
|
-
"purl": "pkg:npm/ragent-cli@1.11.
|
|
1181
|
+
"purl": "pkg:npm/ragent-cli@1.11.5",
|
|
1182
1182
|
"externalReferences": [
|
|
1183
1183
|
{
|
|
1184
1184
|
"url": "https://github.com/chadlindell/ragent-live/issues",
|
|
@@ -1303,7 +1303,7 @@
|
|
|
1303
1303
|
"ragent-live|@emnapi/wasi-threads@1.2.1",
|
|
1304
1304
|
"ragent-live|@pkgjs/parseargs@0.11.0",
|
|
1305
1305
|
"ragent-live|@tybys/wasm-util@0.10.1",
|
|
1306
|
-
"ragent-live|ragent-cli@1.11.
|
|
1306
|
+
"ragent-live|ragent-cli@1.11.5"
|
|
1307
1307
|
]
|
|
1308
1308
|
},
|
|
1309
1309
|
{
|
|
@@ -1436,7 +1436,7 @@
|
|
|
1436
1436
|
]
|
|
1437
1437
|
},
|
|
1438
1438
|
{
|
|
1439
|
-
"ref": "ragent-live|ragent-cli@1.11.
|
|
1439
|
+
"ref": "ragent-live|ragent-cli@1.11.5",
|
|
1440
1440
|
"dependsOn": [
|
|
1441
1441
|
"ragent-live|@azure/web-pubsub-client@1.0.4",
|
|
1442
1442
|
"ragent-live|commander@14.0.3",
|