replicas-cli 0.2.349 → 0.2.350
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.mjs +75 -37
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -9601,7 +9601,7 @@ var HOOK_EXEC_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
|
9601
9601
|
var REPLICAS_CONFIG_FILENAMES = ["replicas.json", "replicas.yaml", "replicas.yml"];
|
|
9602
9602
|
|
|
9603
9603
|
// ../shared/src/cli-version.ts
|
|
9604
|
-
var CLI_VERSION = "0.2.
|
|
9604
|
+
var CLI_VERSION = "0.2.350";
|
|
9605
9605
|
|
|
9606
9606
|
// ../shared/src/version.ts
|
|
9607
9607
|
function compareVersions(v1, v2) {
|
|
@@ -14796,21 +14796,23 @@ async function learningsDeleteCommand(id) {
|
|
|
14796
14796
|
// src/commands/computer/index.ts
|
|
14797
14797
|
import { spawn as spawn4, spawnSync as spawnSync3 } from "child_process";
|
|
14798
14798
|
import { createHash as createHash2 } from "crypto";
|
|
14799
|
-
import { closeSync, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as
|
|
14800
|
-
import { dirname as
|
|
14799
|
+
import { closeSync, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync3, readSync, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
14800
|
+
import { dirname as dirname3 } from "path";
|
|
14801
14801
|
import chalk21 from "chalk";
|
|
14802
14802
|
|
|
14803
14803
|
// src/commands/computer/desktop.ts
|
|
14804
14804
|
import { spawnSync } from "child_process";
|
|
14805
|
-
import { existsSync } from "fs";
|
|
14806
|
-
import { isAbsolute, resolve } from "path";
|
|
14805
|
+
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
14806
|
+
import { dirname, isAbsolute, resolve } from "path";
|
|
14807
14807
|
var STATE_DIR = process.env.REPLICAS_DESKTOP_STATE_DIR || "/tmp/replicas-computer";
|
|
14808
14808
|
var DEFAULT_DISPLAY = process.env.REPLICAS_DESKTOP_DISPLAY || ":99";
|
|
14809
14809
|
var NOVNC_PORT = process.env.REPLICAS_DESKTOP_NOVNC_PORT ? parseInt(process.env.REPLICAS_DESKTOP_NOVNC_PORT, 10) : DESKTOP_NOVNC_PORT;
|
|
14810
14810
|
var SERVICES_SCRIPT = "/usr/local/bin/replicas-start-desktop-services";
|
|
14811
|
+
var INPUT_LOCK_FILE = process.env.REPLICAS_DESKTOP_INPUT_LOCK_FILE || `${STATE_DIR}/input.lock`;
|
|
14811
14812
|
var CHROME_WRAPPER = "/usr/local/bin/replicas-chrome";
|
|
14812
14813
|
var INFO_WAIT_TIMEOUT_MS = 1e4;
|
|
14813
14814
|
var INFO_WAIT_INTERVAL_MS = 500;
|
|
14815
|
+
var lastDesktopHealthAt = 0;
|
|
14814
14816
|
var CHROME_DEBUG_PORT = (() => {
|
|
14815
14817
|
const value = process.env.REPLICAS_DESKTOP_CHROME_DEBUG_PORT;
|
|
14816
14818
|
if (!value) return 9222;
|
|
@@ -14823,16 +14825,49 @@ var CHROME_DEBUG_PORT = (() => {
|
|
|
14823
14825
|
function fail(msg) {
|
|
14824
14826
|
throw new Error(msg);
|
|
14825
14827
|
}
|
|
14828
|
+
function getDesktopBridgeStatus() {
|
|
14829
|
+
const r = spawnSync("bash", [SERVICES_SCRIPT, "--status-json"], { stdio: "pipe" });
|
|
14830
|
+
if (r.status !== 0) return null;
|
|
14831
|
+
try {
|
|
14832
|
+
return JSON.parse(r.stdout.toString());
|
|
14833
|
+
} catch {
|
|
14834
|
+
return null;
|
|
14835
|
+
}
|
|
14836
|
+
}
|
|
14837
|
+
function desktopStackHealthy() {
|
|
14838
|
+
const pids = {};
|
|
14839
|
+
for (const name of ["openbox", "tint2", "x11vnc", "novnc"]) {
|
|
14840
|
+
try {
|
|
14841
|
+
const pid = Number.parseInt(readFileSync(`${STATE_DIR}/${name}.pid`, "utf8").trim(), 10);
|
|
14842
|
+
if (!Number.isFinite(pid)) return false;
|
|
14843
|
+
process.kill(pid, 0);
|
|
14844
|
+
pids[name] = pid;
|
|
14845
|
+
} catch {
|
|
14846
|
+
return false;
|
|
14847
|
+
}
|
|
14848
|
+
}
|
|
14849
|
+
if (spawnSync("xdpyinfo", [], { env: withDisplay(), stdio: "ignore" }).status !== 0) return false;
|
|
14850
|
+
const wm = spawnSync("xprop", ["-root", "_NET_SUPPORTING_WM_CHECK"], { env: withDisplay(), stdio: "pipe" });
|
|
14851
|
+
if (wm.status !== 0 || !wm.stdout?.toString().includes("window id")) return false;
|
|
14852
|
+
const bridge = getDesktopBridgeStatus();
|
|
14853
|
+
return !!bridge?.x11vnc.listenerPids?.includes(pids.x11vnc) && !!bridge.websockify.listenerPids?.includes(pids.novnc);
|
|
14854
|
+
}
|
|
14826
14855
|
function ensureServicesRunning() {
|
|
14827
14856
|
if (!existsSync(SERVICES_SCRIPT)) {
|
|
14828
14857
|
fail(
|
|
14829
14858
|
`Desktop services script missing at ${SERVICES_SCRIPT}. The workspace image is out of date \u2014 Xvfb / openbox / x11vnc / websockify must be installed and \`replicas-start-desktop-services\` baked in.`
|
|
14830
14859
|
);
|
|
14831
14860
|
}
|
|
14861
|
+
if (Date.now() - lastDesktopHealthAt < 1e3) return;
|
|
14862
|
+
if (desktopStackHealthy()) {
|
|
14863
|
+
lastDesktopHealthAt = Date.now();
|
|
14864
|
+
return;
|
|
14865
|
+
}
|
|
14832
14866
|
const r = spawnSync("bash", [SERVICES_SCRIPT], { stdio: "pipe" });
|
|
14833
14867
|
if (r.status !== 0) {
|
|
14834
14868
|
fail(`Failed to start desktop services: ${r.stderr?.toString() || "unknown error"}`);
|
|
14835
14869
|
}
|
|
14870
|
+
lastDesktopHealthAt = Date.now();
|
|
14836
14871
|
}
|
|
14837
14872
|
function withDisplay(env = process.env) {
|
|
14838
14873
|
return { ...env, DISPLAY: DEFAULT_DISPLAY };
|
|
@@ -14845,6 +14880,17 @@ function runDisplayCmd(bin, args) {
|
|
|
14845
14880
|
}
|
|
14846
14881
|
return r.stdout?.toString() ?? "";
|
|
14847
14882
|
}
|
|
14883
|
+
function runDesktopInputCmd(args) {
|
|
14884
|
+
mkdirSync(dirname(INPUT_LOCK_FILE), { recursive: true });
|
|
14885
|
+
return runDisplayCmd("flock", [
|
|
14886
|
+
"--exclusive",
|
|
14887
|
+
"--wait",
|
|
14888
|
+
process.env.REPLICAS_DESKTOP_INPUT_LOCK_WAIT_SECONDS || "60",
|
|
14889
|
+
INPUT_LOCK_FILE,
|
|
14890
|
+
"xdotool",
|
|
14891
|
+
...args
|
|
14892
|
+
]);
|
|
14893
|
+
}
|
|
14848
14894
|
function tryDisplayCmd(bin, args) {
|
|
14849
14895
|
ensureServicesRunning();
|
|
14850
14896
|
const r = spawnSync(bin, args, { env: withDisplay(), stdio: "pipe" });
|
|
@@ -14910,8 +14956,8 @@ var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
|
14910
14956
|
|
|
14911
14957
|
// src/commands/computer/recording.ts
|
|
14912
14958
|
import { spawn as spawn3 } from "child_process";
|
|
14913
|
-
import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
14914
|
-
import { dirname } from "path";
|
|
14959
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
14960
|
+
import { dirname as dirname2 } from "path";
|
|
14915
14961
|
|
|
14916
14962
|
// src/commands/computer/recording/render.ts
|
|
14917
14963
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -15393,7 +15439,7 @@ var RECORD_DIMENSIONS_FILE = `${STATE_DIR}/recording-dimensions.json`;
|
|
|
15393
15439
|
var RECORD_ACTIONS_FILE = `${STATE_DIR}/recording-actions.jsonl`;
|
|
15394
15440
|
function recordingStartedAt() {
|
|
15395
15441
|
if (!existsSync2(RECORD_STARTED_AT_FILE)) return null;
|
|
15396
|
-
const startedAt = Number.parseInt(
|
|
15442
|
+
const startedAt = Number.parseInt(readFileSync2(RECORD_STARTED_AT_FILE, "utf8").trim(), 10);
|
|
15397
15443
|
return Number.isFinite(startedAt) ? startedAt : null;
|
|
15398
15444
|
}
|
|
15399
15445
|
function logRecordingAction(action) {
|
|
@@ -15406,7 +15452,7 @@ function logRecordingAction(action) {
|
|
|
15406
15452
|
function readRecordingDimensions() {
|
|
15407
15453
|
if (!existsSync2(RECORD_DIMENSIONS_FILE)) return configuredDesktopDimensions();
|
|
15408
15454
|
try {
|
|
15409
|
-
const dimensions = JSON.parse(
|
|
15455
|
+
const dimensions = JSON.parse(readFileSync2(RECORD_DIMENSIONS_FILE, "utf8"));
|
|
15410
15456
|
const width = dimensions?.width;
|
|
15411
15457
|
const height = dimensions?.height;
|
|
15412
15458
|
if (typeof width === "number" && Number.isFinite(width) && width > 0 && typeof height === "number" && Number.isFinite(height) && height > 0) {
|
|
@@ -15424,7 +15470,7 @@ function isOptionalNumber(value) {
|
|
|
15424
15470
|
}
|
|
15425
15471
|
function readRecordingActions() {
|
|
15426
15472
|
if (!existsSync2(RECORD_ACTIONS_FILE)) return [];
|
|
15427
|
-
return
|
|
15473
|
+
return readFileSync2(RECORD_ACTIONS_FILE, "utf8").split("\n").filter(Boolean).flatMap((line) => {
|
|
15428
15474
|
try {
|
|
15429
15475
|
const value = JSON.parse(line);
|
|
15430
15476
|
if (typeof value !== "object" || value === null) return [];
|
|
@@ -15446,7 +15492,7 @@ function readRecordingActions() {
|
|
|
15446
15492
|
async function computerRecordStartCommand(path6, options) {
|
|
15447
15493
|
ensureServicesRunning();
|
|
15448
15494
|
if (existsSync2(RECORD_PID_FILE)) {
|
|
15449
|
-
const pid = parseInt(
|
|
15495
|
+
const pid = parseInt(readFileSync2(RECORD_PID_FILE, "utf8").trim(), 10);
|
|
15450
15496
|
if (Number.isFinite(pid)) {
|
|
15451
15497
|
let alive = false;
|
|
15452
15498
|
try {
|
|
@@ -15458,10 +15504,10 @@ async function computerRecordStartCommand(path6, options) {
|
|
|
15458
15504
|
}
|
|
15459
15505
|
}
|
|
15460
15506
|
const target = resolvePath(path6);
|
|
15461
|
-
|
|
15507
|
+
mkdirSync2(dirname2(target), { recursive: true });
|
|
15462
15508
|
const fps = options.fps ? parseCoord(options.fps, "--fps") : 60;
|
|
15463
15509
|
const { width, height } = configuredDesktopDimensions();
|
|
15464
|
-
|
|
15510
|
+
mkdirSync2(STATE_DIR, { recursive: true });
|
|
15465
15511
|
const rawTarget = `${target}.raw-${Date.now()}.mp4`;
|
|
15466
15512
|
rmSync2(RECORD_ACTIONS_FILE, { force: true });
|
|
15467
15513
|
const child = spawn3("ffmpeg", [
|
|
@@ -15506,7 +15552,7 @@ async function computerRecordStartCommand(path6, options) {
|
|
|
15506
15552
|
async function computerRecordStopCommand() {
|
|
15507
15553
|
if (!existsSync2(RECORD_PID_FILE) && !existsSync2(RECORD_PATH_FILE)) fail("no recording in progress");
|
|
15508
15554
|
if (existsSync2(RECORD_PID_FILE)) {
|
|
15509
|
-
const pid = parseInt(
|
|
15555
|
+
const pid = parseInt(readFileSync2(RECORD_PID_FILE, "utf8").trim(), 10);
|
|
15510
15556
|
if (!Number.isFinite(pid)) fail("invalid recording pidfile");
|
|
15511
15557
|
try {
|
|
15512
15558
|
process.kill(pid, "SIGINT");
|
|
@@ -15523,9 +15569,9 @@ async function computerRecordStopCommand() {
|
|
|
15523
15569
|
rmSync2(RECORD_PID_FILE, { force: true });
|
|
15524
15570
|
}
|
|
15525
15571
|
if (existsSync2(RECORD_PATH_FILE)) {
|
|
15526
|
-
const target =
|
|
15527
|
-
const rawPath = existsSync2(RECORD_RAW_PATH_FILE) ?
|
|
15528
|
-
const fps = existsSync2(RECORD_FPS_FILE) ? parseInt(
|
|
15572
|
+
const target = readFileSync2(RECORD_PATH_FILE, "utf8").trim();
|
|
15573
|
+
const rawPath = existsSync2(RECORD_RAW_PATH_FILE) ? readFileSync2(RECORD_RAW_PATH_FILE, "utf8").trim() : target;
|
|
15574
|
+
const fps = existsSync2(RECORD_FPS_FILE) ? parseInt(readFileSync2(RECORD_FPS_FILE, "utf8").trim(), 10) : 60;
|
|
15529
15575
|
const size = readRecordingDimensions();
|
|
15530
15576
|
const actions = readRecordingActions();
|
|
15531
15577
|
if (rawPath !== target) {
|
|
@@ -15543,15 +15589,6 @@ async function computerRecordStopCommand() {
|
|
|
15543
15589
|
}
|
|
15544
15590
|
|
|
15545
15591
|
// src/commands/computer/index.ts
|
|
15546
|
-
function desktopBridgeStatus() {
|
|
15547
|
-
const r = spawnSync3("bash", [SERVICES_SCRIPT, "--status-json"], { stdio: "pipe" });
|
|
15548
|
-
if (r.status !== 0) return null;
|
|
15549
|
-
try {
|
|
15550
|
-
return JSON.parse(r.stdout.toString());
|
|
15551
|
-
} catch {
|
|
15552
|
-
return null;
|
|
15553
|
-
}
|
|
15554
|
-
}
|
|
15555
15592
|
function bridgeStatus(details, includeBacklog = false, rootsOnly = false) {
|
|
15556
15593
|
const pids = rootsOnly ? details.rootPids ?? [] : details.listenerPids ?? [];
|
|
15557
15594
|
const listenerCount = rootsOnly ? details.rootListeners ?? pids.length : details.listeners ?? pids.length;
|
|
@@ -15591,7 +15628,7 @@ async function computerInfoCommand() {
|
|
|
15591
15628
|
}
|
|
15592
15629
|
async function computerStatusCommand() {
|
|
15593
15630
|
ensureServicesRunning();
|
|
15594
|
-
const bridge =
|
|
15631
|
+
const bridge = getDesktopBridgeStatus();
|
|
15595
15632
|
const procs = ["Xvfb", "openbox", "tint2", "x11vnc", "websockify"];
|
|
15596
15633
|
for (const p of procs) {
|
|
15597
15634
|
const r = spawnSync3("pgrep", ["-af", p], { stdio: "pipe" });
|
|
@@ -15631,7 +15668,7 @@ function loadBrandSvg(canvasW, canvasH) {
|
|
|
15631
15668
|
`Brand wallpaper SVG missing at ${path6}. The workspace image is out of date \u2014 \`desktop/brand-wallpaper.svg\` must be installed at $REPLICAS_DESKTOP_TEMPLATES.`
|
|
15632
15669
|
);
|
|
15633
15670
|
}
|
|
15634
|
-
return
|
|
15671
|
+
return readFileSync3(path6, "utf8").replace(/(<svg[^>]*\s)width="\d+"/, `$1width="${canvasW}"`).replace(/(<svg[^>]*\s)height="\d+"/, `$1height="${canvasH}"`);
|
|
15635
15672
|
}
|
|
15636
15673
|
var BRAND_PAD_FRACTION = 0.06;
|
|
15637
15674
|
var SCREENSHOT_CORNER_FRACTION = 0.022;
|
|
@@ -15694,7 +15731,7 @@ function overlayGrid(rawPath, target, width, height, gridSize, gridPath) {
|
|
|
15694
15731
|
}
|
|
15695
15732
|
async function computerScreenshotCommand(path6, options = {}) {
|
|
15696
15733
|
const target = resolvePath(path6);
|
|
15697
|
-
|
|
15734
|
+
mkdirSync3(dirname3(target), { recursive: true });
|
|
15698
15735
|
const stamp = `${process.pid}-${Date.now()}`;
|
|
15699
15736
|
const rawPath = `/tmp/replicas-screenshot-${stamp}.raw.png`;
|
|
15700
15737
|
const svgPath = `/tmp/replicas-screenshot-${stamp}.brand.svg`;
|
|
@@ -15773,7 +15810,7 @@ async function computerScreenshotCommand(path6, options = {}) {
|
|
|
15773
15810
|
console.log(target);
|
|
15774
15811
|
}
|
|
15775
15812
|
function hashFile(path6) {
|
|
15776
|
-
return createHash2("sha256").update(
|
|
15813
|
+
return createHash2("sha256").update(readFileSync3(path6)).digest("hex");
|
|
15777
15814
|
}
|
|
15778
15815
|
async function captureStableRawScreenshot(target, options) {
|
|
15779
15816
|
const start = Date.now();
|
|
@@ -15824,7 +15861,7 @@ function recordingMousePosition() {
|
|
|
15824
15861
|
}
|
|
15825
15862
|
async function computerObserveCommand(path6, options = {}) {
|
|
15826
15863
|
const target = resolvePath(path6);
|
|
15827
|
-
|
|
15864
|
+
mkdirSync3(dirname3(target), { recursive: true });
|
|
15828
15865
|
const timeoutMs = options.timeout ? parseCoord(options.timeout, "--timeout") : 3e3;
|
|
15829
15866
|
const stableMs = options.stableMs ? parseCoord(options.stableMs, "--stable-ms") : 600;
|
|
15830
15867
|
const pollMs = options.pollMs ? parseCoord(options.pollMs, "--poll-ms") : 200;
|
|
@@ -15835,6 +15872,7 @@ async function computerObserveCommand(path6, options = {}) {
|
|
|
15835
15872
|
const rawPath = `/tmp/replicas-observe-${stamp}.raw.png`;
|
|
15836
15873
|
const gridPath = `/tmp/replicas-observe-${stamp}.grid.svg`;
|
|
15837
15874
|
try {
|
|
15875
|
+
ensureServicesRunning();
|
|
15838
15876
|
const capture = await captureStableRawScreenshot(rawPath, { timeoutMs, stableMs, pollMs });
|
|
15839
15877
|
const gridSize = options.raw ? null : parseGridSize(options.grid ?? true);
|
|
15840
15878
|
if (gridSize === null) {
|
|
@@ -16340,7 +16378,7 @@ async function computerClickCommand(xStr, yStr, options) {
|
|
|
16340
16378
|
args.push("keyup", mod);
|
|
16341
16379
|
}
|
|
16342
16380
|
}
|
|
16343
|
-
|
|
16381
|
+
runDesktopInputCmd(args);
|
|
16344
16382
|
logRecordingAction({ type: "click", x, y });
|
|
16345
16383
|
console.log(`clicked ${button === "1" ? "left" : button === "2" ? "middle" : button === "3" ? "right" : `button ${button}`} at (${x},${y})${options.double ? " x2" : ""}`);
|
|
16346
16384
|
}
|
|
@@ -16348,19 +16386,19 @@ async function computerMoveCommand(xStr, yStr) {
|
|
|
16348
16386
|
const dimensions = getDisplayDimensions();
|
|
16349
16387
|
const x = parseScreenCoord(xStr, "x", dimensions.width);
|
|
16350
16388
|
const y = parseScreenCoord(yStr, "y", dimensions.height);
|
|
16351
|
-
|
|
16389
|
+
runDesktopInputCmd(["mousemove", "--sync", String(x), String(y)]);
|
|
16352
16390
|
logRecordingAction({ type: "move", x, y });
|
|
16353
16391
|
console.log(`moved to (${x},${y})`);
|
|
16354
16392
|
}
|
|
16355
16393
|
async function computerTypeCommand(text, options) {
|
|
16356
16394
|
const delay = options.delay ? parseCoord(options.delay, "--delay") : 12;
|
|
16357
|
-
|
|
16395
|
+
runDesktopInputCmd(["type", "--delay", String(delay), "--", text]);
|
|
16358
16396
|
const position = recordingMousePosition();
|
|
16359
16397
|
logRecordingAction({ type: "type", ...position ?? {} });
|
|
16360
16398
|
console.log(`typed ${text.length} char${text.length === 1 ? "" : "s"}`);
|
|
16361
16399
|
}
|
|
16362
16400
|
async function computerKeyCommand(combo) {
|
|
16363
|
-
|
|
16401
|
+
runDesktopInputCmd(["key", "--", combo]);
|
|
16364
16402
|
const position = recordingMousePosition();
|
|
16365
16403
|
logRecordingAction({ type: "key", ...position ?? {} });
|
|
16366
16404
|
console.log(`pressed ${combo}`);
|
|
@@ -16387,7 +16425,7 @@ async function computerScrollCommand(direction, options) {
|
|
|
16387
16425
|
);
|
|
16388
16426
|
}
|
|
16389
16427
|
args.push("click", "--repeat", String(amount), "--delay", "30", button);
|
|
16390
|
-
|
|
16428
|
+
runDesktopInputCmd(args);
|
|
16391
16429
|
const position = hoverPosition ?? recordingMousePosition();
|
|
16392
16430
|
logRecordingAction({ type: "scroll", ...position ?? {} });
|
|
16393
16431
|
console.log(`scrolled ${dir} x${amount}`);
|
|
@@ -16414,7 +16452,7 @@ async function computerDragCommand(fx, fy, tx, ty) {
|
|
|
16414
16452
|
args.push("mousemove", "--sync", String(x), String(y), "sleep", "0.018");
|
|
16415
16453
|
}
|
|
16416
16454
|
args.push("mouseup", "1");
|
|
16417
|
-
|
|
16455
|
+
runDesktopInputCmd(args);
|
|
16418
16456
|
logRecordingAction({ type: "drag", x: fromX, y: fromY, toX, toY });
|
|
16419
16457
|
console.log(`dragged (${fromX},${fromY}) -> (${toX},${toY})`);
|
|
16420
16458
|
}
|