replicas-cli 0.2.423 → 0.2.425

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.
Files changed (2) hide show
  1. package/dist/index.mjs +179 -47
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -24492,7 +24492,7 @@ function formatTurnElapsed(ms) {
24492
24492
  }
24493
24493
 
24494
24494
  // ../shared/src/cli-version.ts
24495
- var CLI_VERSION = "0.2.423";
24495
+ var CLI_VERSION = "0.2.425";
24496
24496
 
24497
24497
  // ../shared/src/version.ts
24498
24498
  function compareVersions(v1, v2) {
@@ -24671,6 +24671,15 @@ function lifecyclePolicySupportsAutoStop(policy) {
24671
24671
  return policy === "default";
24672
24672
  }
24673
24673
 
24674
+ // ../shared/src/automations/github-checks.ts
24675
+ var AUTOMATION_CHECK_CONCLUSIONS = ["success", "failure"];
24676
+ function isAutomationCheckConclusion(value) {
24677
+ return AUTOMATION_CHECK_CONCLUSIONS.some((conclusion) => conclusion === value);
24678
+ }
24679
+ function normalizeAutomationGithubCheckNames(names) {
24680
+ return names.map((name) => name.trim()).filter(Boolean);
24681
+ }
24682
+
24674
24683
  // ../shared/src/automations/agent-validation.ts
24675
24684
  function validateAgentSelection(body, existing) {
24676
24685
  const agentProvider = body.agent_provider;
@@ -29153,6 +29162,9 @@ function printAutomation(automation2) {
29153
29162
  if (automation2.triggers.length > 0) {
29154
29163
  console.log(chalk17.gray(` Triggers: ${automation2.triggers.map(formatTrigger).join(", ")}`));
29155
29164
  }
29165
+ if (automation2.github_check_names.length > 0) {
29166
+ console.log(chalk17.gray(` GitHub checks: ${automation2.github_check_names.join(", ")}`));
29167
+ }
29156
29168
  console.log(chalk17.gray(` Prompt: ${truncate2(automation2.prompt, 80)}`));
29157
29169
  if (automation2.cron_next_fire_at) {
29158
29170
  console.log(chalk17.gray(` Next Run: ${formatDate2(automation2.cron_next_fire_at)}`));
@@ -29179,6 +29191,9 @@ async function automationListCommand(options) {
29179
29191
  if (options.limit) params.set("limit", options.limit);
29180
29192
  const scope = parseScopeFilter(options.owner);
29181
29193
  if (scope) params.set("scope", scope);
29194
+ if (options.triggerType) params.set("trigger_type", options.triggerType);
29195
+ if (options.enabled) params.set("enabled", options.enabled);
29196
+ if (options.search) params.set("search", options.search);
29182
29197
  const query = params.toString();
29183
29198
  const response = await orgAuthenticatedFetch(
29184
29199
  `/v1/automations${query ? "?" + query : ""}`
@@ -29217,6 +29232,9 @@ Automation: ${automation2.name}
29217
29232
  console.log(chalk17.gray(` - ${formatTrigger(trigger)}`));
29218
29233
  }
29219
29234
  }
29235
+ if (automation2.github_check_names.length > 0) {
29236
+ console.log(chalk17.gray(` GitHub checks: ${automation2.github_check_names.join(", ")}`));
29237
+ }
29220
29238
  console.log(chalk17.gray(` Prompt: ${automation2.prompt}`));
29221
29239
  console.log(chalk17.gray(` Environment: ${automation2.environment_id}`));
29222
29240
  if (automation2.cron_expression) {
@@ -29510,6 +29528,7 @@ async function automationCreateCommand(name, options) {
29510
29528
  environment_id: selectedEnvironmentId,
29511
29529
  triggers,
29512
29530
  enabled: options.enabled !== false,
29531
+ ...options.githubChecks !== void 0 ? { github_check_names: normalizeAutomationGithubCheckNames(options.githubChecks.split(",")) } : {},
29513
29532
  config: workspaceConfigWithPrFollowups(void 0, prFollowups),
29514
29533
  ...lifecyclePolicy ? { workspace_lifecycle_policy: lifecyclePolicy } : {},
29515
29534
  ...options.autoStopMinutes ? { workspace_auto_stop_minutes: parseInt(options.autoStopMinutes, 10) } : {},
@@ -29576,7 +29595,7 @@ async function automationEditCommand(id, options) {
29576
29595
  model: existing.automation.model
29577
29596
  });
29578
29597
  const body = {};
29579
- const hasOptions = options.name || options.prompt || options.enabled !== void 0 || options.prFollowups !== void 0 || options.triggerCron || options.triggerGithub || options.triggerGitlab || options.environment || options.triggerGithubExcludeUsers !== void 0 || options.triggerGitlabExcludeUsers !== void 0 || options.lifecycle || options.sleepWhenDone || options.autoStopMinutes || options.agentProvider !== void 0 || options.model !== void 0 || options.thinkingLevel !== void 0 || options.planMode !== void 0 || options.goalMode !== void 0 || options.fastMode !== void 0;
29598
+ const hasOptions = options.name || options.prompt || options.enabled !== void 0 || options.prFollowups !== void 0 || options.triggerCron || options.triggerGithub || options.triggerGitlab || options.environment || options.triggerGithubExcludeUsers !== void 0 || options.triggerGitlabExcludeUsers !== void 0 || options.githubChecks !== void 0 || options.addGithubChecks !== void 0 || options.lifecycle || options.sleepWhenDone || options.autoStopMinutes || options.agentProvider !== void 0 || options.model !== void 0 || options.thinkingLevel !== void 0 || options.planMode !== void 0 || options.goalMode !== void 0 || options.fastMode !== void 0;
29580
29599
  if (!hasOptions) {
29581
29600
  const nameResponse = await prompts5({
29582
29601
  type: "text",
@@ -29636,6 +29655,15 @@ async function automationEditCommand(id, options) {
29636
29655
  if (options.prFollowups !== void 0) {
29637
29656
  body.config = workspaceConfigWithPrFollowups(existing.automation.config, options.prFollowups);
29638
29657
  }
29658
+ if (options.githubChecks !== void 0) {
29659
+ body.github_check_names = normalizeAutomationGithubCheckNames(options.githubChecks.split(","));
29660
+ }
29661
+ if (options.addGithubChecks !== void 0) {
29662
+ body.github_check_names = [.../* @__PURE__ */ new Set([
29663
+ ...existing.automation.github_check_names,
29664
+ ...normalizeAutomationGithubCheckNames(options.addGithubChecks.split(","))
29665
+ ])];
29666
+ }
29639
29667
  if (options.triggerCron || options.triggerGithub || options.triggerGitlab) {
29640
29668
  const triggers = [];
29641
29669
  if (options.triggerCron) {
@@ -29816,6 +29844,46 @@ Automation "${automationName}" (${id}) deleted.
29816
29844
  process.exit(1);
29817
29845
  }
29818
29846
  }
29847
+ async function automationCheckCommand(checkRunId, options) {
29848
+ const id = Number(checkRunId);
29849
+ if (!Number.isSafeInteger(id)) {
29850
+ console.error(chalk17.red("Error: check run ID must be an integer"));
29851
+ process.exit(1);
29852
+ }
29853
+ if (!options.token) {
29854
+ console.error(chalk17.red("Error: --token is required"));
29855
+ process.exit(1);
29856
+ }
29857
+ const { conclusion } = options;
29858
+ if (!isAutomationCheckConclusion(conclusion)) {
29859
+ console.error(chalk17.red(`Error: --conclusion must be one of ${AUTOMATION_CHECK_CONCLUSIONS.join(", ")}`));
29860
+ process.exit(1);
29861
+ }
29862
+ if (!options.title?.trim()) {
29863
+ console.error(chalk17.red("Error: --title is required"));
29864
+ process.exit(1);
29865
+ }
29866
+ const body = {
29867
+ token: options.token,
29868
+ conclusion,
29869
+ title: options.title.trim(),
29870
+ ...options.summary?.trim() ? { summary: options.summary.trim() } : {}
29871
+ };
29872
+ try {
29873
+ const response = await orgAuthenticatedFetch(
29874
+ `/v1/automations/checks/${id}`,
29875
+ { method: "POST", body }
29876
+ );
29877
+ console.log(response.reported ? chalk17.green(`
29878
+ Reported ${conclusion} for "${response.name}".
29879
+ `) : chalk17.yellow(`
29880
+ Skipped "${response.name}" \u2014 a newer run of this automation owns it and will report the verdict.
29881
+ `));
29882
+ } catch (error51) {
29883
+ console.error(chalk17.red(`Error: ${error51 instanceof Error ? error51.message : "Unknown error"}`));
29884
+ process.exit(1);
29885
+ }
29886
+ }
29819
29887
 
29820
29888
  // src/commands/preview.ts
29821
29889
  import chalk18 from "chalk";
@@ -30297,6 +30365,42 @@ async function serviceLogsCommand(name, options) {
30297
30365
  }
30298
30366
 
30299
30367
  // src/commands/learnings.ts
30368
+ import { createHash as createHash2 } from "crypto";
30369
+ import { mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
30370
+ import { join as join2 } from "path";
30371
+ var SESSIONS_DIR = join2(CONFIG_DIR, "learnings-sessions");
30372
+ var REPRINT_AFTER_READS = 50;
30373
+ var SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
30374
+ function sessionFilePath() {
30375
+ const key = process.env.REPLICAS_CHAT_ID || process.env.CLAUDE_CODE_SESSION_ID;
30376
+ return key ? join2(SESSIONS_DIR, `${key.replace(/[^a-zA-Z0-9_-]/g, "-")}.json`) : null;
30377
+ }
30378
+ function readSession(path6) {
30379
+ const session = { reads: 0, shown: {} };
30380
+ try {
30381
+ const parsed = JSON.parse(readFileSync2(path6, "utf-8"));
30382
+ if (typeof parsed?.reads === "number") session.reads = parsed.reads;
30383
+ const shown = parsed?.shown ?? {};
30384
+ for (const [id, entry] of Object.entries(shown)) {
30385
+ if (typeof entry?.read === "number" && typeof entry?.hash === "string") {
30386
+ session.shown[id] = { read: entry.read, hash: entry.hash };
30387
+ }
30388
+ }
30389
+ } catch {
30390
+ }
30391
+ return session;
30392
+ }
30393
+ function writeSession(path6, session) {
30394
+ try {
30395
+ mkdirSync2(SESSIONS_DIR, { recursive: true, mode: 448 });
30396
+ writeFileSync2(path6, JSON.stringify(session), { mode: 384 });
30397
+ for (const entry of readdirSync2(SESSIONS_DIR)) {
30398
+ const file2 = join2(SESSIONS_DIR, entry);
30399
+ if (Date.now() - statSync(file2).mtimeMs > SESSION_TTL_MS) unlinkSync(file2);
30400
+ }
30401
+ } catch {
30402
+ }
30403
+ }
30300
30404
  function printBlocks(learnings) {
30301
30405
  for (const learning of learnings) {
30302
30406
  const labels = [
@@ -30317,14 +30421,30 @@ async function learningsReadCommand(options) {
30317
30421
  method: "POST",
30318
30422
  body: { query }
30319
30423
  });
30424
+ const path6 = sessionFilePath();
30425
+ const session = path6 ? readSession(path6) : null;
30426
+ if (session) session.reads += 1;
30427
+ const unseen = response.learnings.filter((learning) => {
30428
+ if (!session) return true;
30429
+ const hash2 = createHash2("sha256").update(learning.content).digest("hex").slice(0, 16);
30430
+ const shown = session.shown[learning.id];
30431
+ if (!options.fresh && shown && shown.hash === hash2 && session.reads - shown.read < REPRINT_AFTER_READS) {
30432
+ return false;
30433
+ }
30434
+ session.shown[learning.id] = { read: session.reads, hash: hash2 };
30435
+ return true;
30436
+ });
30320
30437
  if (response.learnings.length === 0) {
30321
30438
  console.log("No learnings matched this query.");
30439
+ } else if (unseen.length === 0) {
30440
+ console.log("No new learnings \u2014 the matching ones were already shown earlier in this session.");
30322
30441
  } else {
30323
- printBlocks(response.learnings);
30442
+ printBlocks(unseen);
30324
30443
  }
30325
30444
  if (response.matching_degraded) {
30326
30445
  console.log("Note: trigger matching was unavailable \u2014 only always-apply learnings are shown.");
30327
30446
  }
30447
+ if (path6 && session) writeSession(path6, session);
30328
30448
  }
30329
30449
  async function propose(request) {
30330
30450
  const { proposal } = await agentFetch("/v1/agent/learnings/proposals", {
@@ -30374,14 +30494,14 @@ async function learningsWithdrawCommand(id) {
30374
30494
 
30375
30495
  // src/commands/computer/index.ts
30376
30496
  import { spawn as spawn5, spawnSync as spawnSync3 } from "child_process";
30377
- import { createHash as createHash2 } from "crypto";
30378
- import { closeSync as closeSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync4, readSync, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
30497
+ import { createHash as createHash3 } from "crypto";
30498
+ import { closeSync as closeSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as readFileSync5, readSync, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
30379
30499
  import { dirname as dirname3 } from "path";
30380
30500
  import chalk21 from "chalk";
30381
30501
 
30382
30502
  // src/commands/computer/desktop.ts
30383
30503
  import { spawnSync } from "child_process";
30384
- import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2 } from "fs";
30504
+ import { existsSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3 } from "fs";
30385
30505
  import { dirname, isAbsolute, resolve as resolve2 } from "path";
30386
30506
  var STATE_DIR = process.env.REPLICAS_DESKTOP_STATE_DIR || "/tmp/replicas-computer";
30387
30507
  var DEFAULT_DISPLAY = process.env.REPLICAS_DESKTOP_DISPLAY || ":99";
@@ -30417,7 +30537,7 @@ function desktopStackHealthy() {
30417
30537
  const pids = {};
30418
30538
  for (const name of ["openbox", "tint2", "x11vnc", "novnc"]) {
30419
30539
  try {
30420
- const pid = Number.parseInt(readFileSync2(`${STATE_DIR}/${name}.pid`, "utf8").trim(), 10);
30540
+ const pid = Number.parseInt(readFileSync3(`${STATE_DIR}/${name}.pid`, "utf8").trim(), 10);
30421
30541
  if (!Number.isFinite(pid)) return false;
30422
30542
  process.kill(pid, 0);
30423
30543
  pids[name] = pid;
@@ -30460,7 +30580,7 @@ function runDisplayCmd(bin, args) {
30460
30580
  return r.stdout?.toString() ?? "";
30461
30581
  }
30462
30582
  function runDesktopInputCmd(args) {
30463
- mkdirSync2(dirname(INPUT_LOCK_FILE), { recursive: true });
30583
+ mkdirSync3(dirname(INPUT_LOCK_FILE), { recursive: true });
30464
30584
  return runDisplayCmd("flock", [
30465
30585
  "--exclusive",
30466
30586
  "--wait",
@@ -30535,12 +30655,12 @@ var sleep2 = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
30535
30655
 
30536
30656
  // src/commands/computer/recording.ts
30537
30657
  import { spawn as spawn4 } from "child_process";
30538
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync as rmSync3, statSync, writeFileSync as writeFileSync3 } from "fs";
30658
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
30539
30659
  import { dirname as dirname2 } from "path";
30540
30660
 
30541
30661
  // src/commands/computer/recording/render.ts
30542
30662
  import { spawnSync as spawnSync2 } from "child_process";
30543
- import { copyFileSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
30663
+ import { copyFileSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
30544
30664
 
30545
30665
  // src/commands/computer/recording/config.ts
30546
30666
  var cameraMotion = {
@@ -30945,7 +31065,7 @@ function renderRecording(rawPath, target, actions, fps, size) {
30945
31065
  }
30946
31066
  const stamp = `${process.pid}-${Date.now()}`;
30947
31067
  const cursor = cursorAssets(stamp);
30948
- writeFileSync2(cursor.path, cursorSvg(cursor.size));
31068
+ writeFileSync3(cursor.path, cursorSvg(cursor.size));
30949
31069
  const spans = renderedSegmentSpans(segments);
30950
31070
  const renderedDuration = spans.length ? spans[spans.length - 1].outputEnd : duration3;
30951
31071
  const renderedActions = actionsOnRenderedTimeline(actions, spans);
@@ -30968,7 +31088,7 @@ function renderRecording(rawPath, target, actions, fps, size) {
30968
31088
  const concatInputs = segments.map((_, index) => `[v${index}]`).join("");
30969
31089
  const filter = `${screenSplitFilter};${filters.join(";")};${concatInputs}concat=n=${segments.length}:v=1:a=0[screen];${cursorFilter}`;
30970
31090
  const filterPath = `/tmp/replicas-recording-filter-${stamp}.ffgraph`;
30971
- writeFileSync2(filterPath, filter);
31091
+ writeFileSync3(filterPath, filter);
30972
31092
  try {
30973
31093
  const r = spawnSync2("ffmpeg", [
30974
31094
  "-y",
@@ -31030,7 +31150,7 @@ function clearRecordingState() {
31030
31150
  }
31031
31151
  function recordingStartedAt() {
31032
31152
  if (!existsSync2(RECORD_STARTED_AT_FILE)) return null;
31033
- const startedAt = Number.parseInt(readFileSync3(RECORD_STARTED_AT_FILE, "utf8").trim(), 10);
31153
+ const startedAt = Number.parseInt(readFileSync4(RECORD_STARTED_AT_FILE, "utf8").trim(), 10);
31034
31154
  return Number.isFinite(startedAt) ? startedAt : null;
31035
31155
  }
31036
31156
  function logRecordingAction(action) {
@@ -31043,7 +31163,7 @@ function logRecordingAction(action) {
31043
31163
  function readRecordingDimensions() {
31044
31164
  if (!existsSync2(RECORD_DIMENSIONS_FILE)) return configuredDesktopDimensions();
31045
31165
  try {
31046
- const dimensions = JSON.parse(readFileSync3(RECORD_DIMENSIONS_FILE, "utf8"));
31166
+ const dimensions = JSON.parse(readFileSync4(RECORD_DIMENSIONS_FILE, "utf8"));
31047
31167
  const width = dimensions?.width;
31048
31168
  const height = dimensions?.height;
31049
31169
  if (typeof width === "number" && Number.isFinite(width) && width > 0 && typeof height === "number" && Number.isFinite(height) && height > 0) {
@@ -31061,7 +31181,7 @@ function isOptionalNumber(value) {
31061
31181
  }
31062
31182
  function readRecordingActions() {
31063
31183
  if (!existsSync2(RECORD_ACTIONS_FILE)) return [];
31064
- return readFileSync3(RECORD_ACTIONS_FILE, "utf8").split("\n").filter(Boolean).flatMap((line) => {
31184
+ return readFileSync4(RECORD_ACTIONS_FILE, "utf8").split("\n").filter(Boolean).flatMap((line) => {
31065
31185
  try {
31066
31186
  const value = JSON.parse(line);
31067
31187
  if (typeof value !== "object" || value === null) return [];
@@ -31083,7 +31203,7 @@ function readRecordingActions() {
31083
31203
  async function computerRecordStartCommand(path6, options) {
31084
31204
  ensureServicesRunning();
31085
31205
  if (existsSync2(RECORD_PID_FILE)) {
31086
- const pid = parseInt(readFileSync3(RECORD_PID_FILE, "utf8").trim(), 10);
31206
+ const pid = parseInt(readFileSync4(RECORD_PID_FILE, "utf8").trim(), 10);
31087
31207
  if (Number.isFinite(pid)) {
31088
31208
  let alive2 = false;
31089
31209
  try {
@@ -31095,10 +31215,10 @@ async function computerRecordStartCommand(path6, options) {
31095
31215
  }
31096
31216
  }
31097
31217
  const target = resolvePath(path6);
31098
- mkdirSync3(dirname2(target), { recursive: true });
31218
+ mkdirSync4(dirname2(target), { recursive: true });
31099
31219
  const fps = options.fps ? parseCoord(options.fps, "--fps") : 60;
31100
31220
  const { width, height } = configuredDesktopDimensions();
31101
- mkdirSync3(STATE_DIR, { recursive: true });
31221
+ mkdirSync4(STATE_DIR, { recursive: true });
31102
31222
  const rawTarget = `${target}.raw-${Date.now()}.mp4`;
31103
31223
  rmSync3(RECORD_ACTIONS_FILE, { force: true });
31104
31224
  const child = spawn4("ffmpeg", [
@@ -31132,17 +31252,17 @@ async function computerRecordStartCommand(path6, options) {
31132
31252
  ], { detached: true, stdio: "ignore" });
31133
31253
  child.unref();
31134
31254
  if (!child.pid) fail("failed to launch ffmpeg");
31135
- writeFileSync3(RECORD_PID_FILE, String(child.pid));
31136
- writeFileSync3(RECORD_PATH_FILE, target);
31137
- writeFileSync3(RECORD_RAW_PATH_FILE, rawTarget);
31138
- writeFileSync3(RECORD_STARTED_AT_FILE, String(Date.now()));
31139
- writeFileSync3(RECORD_FPS_FILE, String(fps));
31140
- writeFileSync3(RECORD_DIMENSIONS_FILE, JSON.stringify({ width, height }));
31255
+ writeFileSync4(RECORD_PID_FILE, String(child.pid));
31256
+ writeFileSync4(RECORD_PATH_FILE, target);
31257
+ writeFileSync4(RECORD_RAW_PATH_FILE, rawTarget);
31258
+ writeFileSync4(RECORD_STARTED_AT_FILE, String(Date.now()));
31259
+ writeFileSync4(RECORD_FPS_FILE, String(fps));
31260
+ writeFileSync4(RECORD_DIMENSIONS_FILE, JSON.stringify({ width, height }));
31141
31261
  const startedAt = Date.now();
31142
31262
  while (Date.now() - startedAt < 5e3) {
31143
31263
  try {
31144
31264
  process.kill(child.pid, 0);
31145
- if (existsSync2(rawTarget) && statSync(rawTarget).size > 0) {
31265
+ if (existsSync2(rawTarget) && statSync2(rawTarget).size > 0) {
31146
31266
  console.log(`${target} (recording ready in ${Date.now() - startedAt}ms)`);
31147
31267
  return;
31148
31268
  }
@@ -31165,7 +31285,7 @@ async function computerRecordStartCommand(path6, options) {
31165
31285
  async function computerRecordStopCommand() {
31166
31286
  if (!existsSync2(RECORD_PID_FILE) && !existsSync2(RECORD_PATH_FILE)) fail("no recording in progress");
31167
31287
  if (existsSync2(RECORD_PID_FILE)) {
31168
- const pid = parseInt(readFileSync3(RECORD_PID_FILE, "utf8").trim(), 10);
31288
+ const pid = parseInt(readFileSync4(RECORD_PID_FILE, "utf8").trim(), 10);
31169
31289
  if (!Number.isFinite(pid)) fail("invalid recording pidfile");
31170
31290
  try {
31171
31291
  process.kill(pid, "SIGINT");
@@ -31184,9 +31304,9 @@ async function computerRecordStopCommand() {
31184
31304
  if (alive) fail(`ffmpeg did not finalize recording within 30 seconds (pid ${pid})`);
31185
31305
  }
31186
31306
  if (existsSync2(RECORD_PATH_FILE)) {
31187
- const target = readFileSync3(RECORD_PATH_FILE, "utf8").trim();
31188
- const rawPath = existsSync2(RECORD_RAW_PATH_FILE) ? readFileSync3(RECORD_RAW_PATH_FILE, "utf8").trim() : target;
31189
- const fps = existsSync2(RECORD_FPS_FILE) ? parseInt(readFileSync3(RECORD_FPS_FILE, "utf8").trim(), 10) : 60;
31307
+ const target = readFileSync4(RECORD_PATH_FILE, "utf8").trim();
31308
+ const rawPath = existsSync2(RECORD_RAW_PATH_FILE) ? readFileSync4(RECORD_RAW_PATH_FILE, "utf8").trim() : target;
31309
+ const fps = existsSync2(RECORD_FPS_FILE) ? parseInt(readFileSync4(RECORD_FPS_FILE, "utf8").trim(), 10) : 60;
31190
31310
  const size = readRecordingDimensions();
31191
31311
  const actions = readRecordingActions();
31192
31312
  if (rawPath !== target) {
@@ -31278,7 +31398,7 @@ function loadBrandSvg(canvasW, canvasH) {
31278
31398
  `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.`
31279
31399
  );
31280
31400
  }
31281
- return readFileSync4(path6, "utf8").replace(/(<svg[^>]*\s)width="\d+"/, `$1width="${canvasW}"`).replace(/(<svg[^>]*\s)height="\d+"/, `$1height="${canvasH}"`);
31401
+ return readFileSync5(path6, "utf8").replace(/(<svg[^>]*\s)width="\d+"/, `$1width="${canvasW}"`).replace(/(<svg[^>]*\s)height="\d+"/, `$1height="${canvasH}"`);
31282
31402
  }
31283
31403
  var BRAND_PAD_FRACTION = 0.06;
31284
31404
  var SCREENSHOT_CORNER_FRACTION = 0.022;
@@ -31313,7 +31433,7 @@ ${labels.join("\n")}
31313
31433
  </svg>`;
31314
31434
  }
31315
31435
  function overlayGrid(rawPath, target, width, height, gridSize, gridPath) {
31316
- writeFileSync4(gridPath, buildGridSvg(width, height, gridSize));
31436
+ writeFileSync5(gridPath, buildGridSvg(width, height, gridSize));
31317
31437
  const r = spawnSync3(
31318
31438
  "ffmpeg",
31319
31439
  [
@@ -31341,7 +31461,7 @@ function overlayGrid(rawPath, target, width, height, gridSize, gridPath) {
31341
31461
  }
31342
31462
  async function computerScreenshotCommand(path6, options = {}) {
31343
31463
  const target = resolvePath(path6);
31344
- mkdirSync4(dirname3(target), { recursive: true });
31464
+ mkdirSync5(dirname3(target), { recursive: true });
31345
31465
  const stamp = `${process.pid}-${Date.now()}`;
31346
31466
  const rawPath = `/tmp/replicas-screenshot-${stamp}.raw.png`;
31347
31467
  const svgPath = `/tmp/replicas-screenshot-${stamp}.brand.svg`;
@@ -31373,12 +31493,12 @@ async function computerScreenshotCommand(path6, options = {}) {
31373
31493
  const shadowMargin = shadowSigma * 3;
31374
31494
  const shadowW = width + shadowMargin * 2;
31375
31495
  const shadowH = height + shadowMargin * 2;
31376
- writeFileSync4(svgPath, loadBrandSvg(canvasW, canvasH));
31377
- writeFileSync4(
31496
+ writeFileSync5(svgPath, loadBrandSvg(canvasW, canvasH));
31497
+ writeFileSync5(
31378
31498
  maskPath,
31379
31499
  SCREENSHOT_MASK_TEMPLATE.replace(/__W__/g, String(width)).replace(/__H__/g, String(height)).replace(/__R__/g, String(cornerR))
31380
31500
  );
31381
- writeFileSync4(
31501
+ writeFileSync5(
31382
31502
  shadowPath,
31383
31503
  SHADOW_MASK_TEMPLATE.replace(/__SW__/g, String(shadowW)).replace(/__SH__/g, String(shadowH)).replace(/__M__/g, String(shadowMargin)).replace(/__W__/g, String(width)).replace(/__H__/g, String(height)).replace(/__R__/g, String(cornerR))
31384
31504
  );
@@ -31420,7 +31540,7 @@ async function computerScreenshotCommand(path6, options = {}) {
31420
31540
  console.log(target);
31421
31541
  }
31422
31542
  function hashFile(path6) {
31423
- return createHash2("sha256").update(readFileSync4(path6)).digest("hex");
31543
+ return createHash3("sha256").update(readFileSync5(path6)).digest("hex");
31424
31544
  }
31425
31545
  async function captureStableRawScreenshot(target, options) {
31426
31546
  const start = Date.now();
@@ -31471,7 +31591,7 @@ function recordingMousePosition() {
31471
31591
  }
31472
31592
  async function computerObserveCommand(path6, options = {}) {
31473
31593
  const target = resolvePath(path6);
31474
- mkdirSync4(dirname3(target), { recursive: true });
31594
+ mkdirSync5(dirname3(target), { recursive: true });
31475
31595
  const timeoutMs = options.timeout ? parseCoord(options.timeout, "--timeout") : 3e3;
31476
31596
  const stableMs = options.stableMs ? parseCoord(options.stableMs, "--stable-ms") : 600;
31477
31597
  const pollMs = options.pollMs ? parseCoord(options.pollMs, "--poll-ms") : 200;
@@ -31706,7 +31826,7 @@ function browserStateProperties(node) {
31706
31826
  function readBrowserStateCache(path6) {
31707
31827
  let value;
31708
31828
  try {
31709
- value = JSON.parse(readFileSync4(path6, "utf8"));
31829
+ value = JSON.parse(readFileSync5(path6, "utf8"));
31710
31830
  } catch {
31711
31831
  return null;
31712
31832
  }
@@ -32052,7 +32172,7 @@ async function captureBrowserSnapshot(page, options) {
32052
32172
  visibleTextLength += Math.min(name.length, remaining) + 1;
32053
32173
  }
32054
32174
  }
32055
- const revision = createHash2("sha256").update(JSON.stringify([page.url, entries.map(({ key, semantic }) => [key, semantic])])).digest("hex").slice(0, 16);
32175
+ const revision = createHash3("sha256").update(JSON.stringify([page.url, entries.map(({ key, semantic }) => [key, semantic])])).digest("hex").slice(0, 16);
32056
32176
  const snapshot = {
32057
32177
  title: (page.title ?? "").slice(0, 1e3),
32058
32178
  url: (page.url ?? "").slice(0, 2e3),
@@ -32232,7 +32352,7 @@ async function computerBrowserStateCommand(path6, options = {}) {
32232
32352
  const page = await selectChromePage(options);
32233
32353
  const stability = await waitForBrowserStability(page, { timeoutMs, stableMs, pollMs });
32234
32354
  const target = resolvePath(path6);
32235
- mkdirSync4(dirname3(target), { recursive: true });
32355
+ mkdirSync5(dirname3(target), { recursive: true });
32236
32356
  const [{ snapshot, entries }, screenshotResult] = await Promise.all([
32237
32357
  captureBrowserSnapshot(page, { textLimit, elementLimit }),
32238
32358
  sendChromeCommand(page.webSocketDebuggerUrl, "Page.captureScreenshot", {
@@ -32243,7 +32363,7 @@ async function computerBrowserStateCommand(path6, options = {}) {
32243
32363
  ]);
32244
32364
  const data = screenshotResult.data;
32245
32365
  if (typeof data !== "string") fail("Chrome did not return screenshot data");
32246
- writeFileSync4(target, Buffer.from(data, "base64"));
32366
+ writeFileSync5(target, Buffer.from(data, "base64"));
32247
32367
  const screenshot = readPngDimensions(target);
32248
32368
  const targetId = page.id ?? "unknown";
32249
32369
  const cachePath = browserStateCachePath(targetId);
@@ -32259,8 +32379,8 @@ async function computerBrowserStateCommand(path6, options = {}) {
32259
32379
  changes = { added: diff.added, changed: diff.changed, removed: diff.removed };
32260
32380
  }
32261
32381
  }
32262
- mkdirSync4(STATE_DIR, { recursive: true });
32263
- writeFileSync4(cachePath, JSON.stringify({ url: snapshot.url, documentId: snapshot.documentId, entries }));
32382
+ mkdirSync5(STATE_DIR, { recursive: true });
32383
+ writeFileSync5(cachePath, JSON.stringify({ url: snapshot.url, documentId: snapshot.documentId, entries }));
32264
32384
  const stableState = isRecord(stability.state) ? stability.state : {};
32265
32385
  const state = {
32266
32386
  title: snapshot.title,
@@ -37127,7 +37247,7 @@ program.command("read <id>").description("Read conversation history of a replica
37127
37247
  }
37128
37248
  });
37129
37249
  var automation = program.command("automation").alias("auto").description("Manage automations");
37130
- automation.command("list").description("List all automations").option("-p, --page <page>", "Page number").option("-l, --limit <limit>", "Number of items per page").option("--owner <owner>", "Ownership filter: org, user, all").action(async (options) => {
37250
+ automation.command("list").description("List all automations").option("-p, --page <page>", "Page number").option("-l, --limit <limit>", "Number of items per page").option("--owner <owner>", "Ownership filter: org, user, all").option("--trigger-type <type>", "Only automations with a trigger of this type (cron, github, gitlab, slack, sentry, custom)").option("--enabled <enabled>", "Only enabled or disabled automations (true/false)").option("--search <search>", "Case-insensitive substring match on the name").action(async (options) => {
37131
37251
  try {
37132
37252
  await automationListCommand(options);
37133
37253
  } catch (error51) {
@@ -37151,7 +37271,7 @@ automation.command("get <id>").description("Get automation details by ID").actio
37151
37271
  process.exit(1);
37152
37272
  }
37153
37273
  });
37154
- automation.command("create [name]").description("Create a new automation").option("-p, --prompt <prompt>", "Prompt for the automation").option("-e, --environment <environment>", "Environment name or ID").option("--trigger-cron <schedule>", 'Cron schedule expression (e.g. "0 9 * * 1-5")').option("--trigger-cron-timezone <timezone>", "Timezone for cron trigger (default: UTC)").option("--trigger-github <event>", 'GitHub event (e.g. "pull_request.opened")').option("--trigger-github-repos <repos>", "Comma-separated repo names to filter GitHub trigger").option("--trigger-github-exclude-users <users>", "Comma-separated GitHub usernames to exclude from triggering (pass empty string to clear the list)").option("--trigger-gitlab <event>", 'GitLab event (e.g. "merge_request.opened")').option("--trigger-gitlab-repos <repos>", "Comma-separated repo names to filter GitLab trigger").option("--trigger-gitlab-exclude-users <users>", "Comma-separated GitLab usernames to exclude from triggering (pass empty string to clear the list)").option("--lifecycle <policy>", "Workspace lifecycle: archive_when_done, sleep_when_done, default").option("--sleep-when-done", "Sleep automation workspaces as soon as the agent finishes").option("--auto-stop-minutes <minutes>", "Inactivity timeout in minutes (3-1440, requires --lifecycle default)").option("--pr-followups", "Allow follow-up actions on matching PRs").option("--agent-provider <provider>", 'Coding agent to use: claude, codex, cursor, relay (or "none" to inherit org default)').option("--model <model>", 'Model identifier (must be valid for --agent-provider; pass "none" to clear)').option("--thinking-level <level>", 'Thinking/reasoning level: low, medium, high, xhigh, max (or "none" to clear)').option("--plan-mode", "Run automation messages in plan mode").option("--goal-mode", "Set automation messages as Codex goals").option("--fast-mode", "Run automation messages in fast mode").option("--personal", "Create a personal automation owned by the authenticated user").option("--disabled", "Create in disabled state").action(async (name, options) => {
37274
+ automation.command("create [name]").description("Create a new automation").option("-p, --prompt <prompt>", "Prompt for the automation").option("-e, --environment <environment>", "Environment name or ID").option("--trigger-cron <schedule>", 'Cron schedule expression (e.g. "0 9 * * 1-5")').option("--trigger-cron-timezone <timezone>", "Timezone for cron trigger (default: UTC)").option("--trigger-github <event>", 'GitHub event (e.g. "pull_request.opened")').option("--trigger-github-repos <repos>", "Comma-separated repo names to filter GitHub trigger").option("--trigger-github-exclude-users <users>", "Comma-separated GitHub usernames to exclude from triggering (pass empty string to clear the list)").option("--trigger-gitlab <event>", 'GitLab event (e.g. "merge_request.opened")').option("--trigger-gitlab-repos <repos>", "Comma-separated repo names to filter GitLab trigger").option("--trigger-gitlab-exclude-users <users>", "Comma-separated GitLab usernames to exclude from triggering (pass empty string to clear the list)").option("--github-checks <names>", "Comma-separated GitHub check names to create on matching PRs (requires a PR opened/updated trigger)").option("--lifecycle <policy>", "Workspace lifecycle: archive_when_done, sleep_when_done, default").option("--sleep-when-done", "Sleep automation workspaces as soon as the agent finishes").option("--auto-stop-minutes <minutes>", "Inactivity timeout in minutes (3-1440, requires --lifecycle default)").option("--pr-followups", "Allow follow-up actions on matching PRs").option("--agent-provider <provider>", 'Coding agent to use: claude, codex, cursor, relay (or "none" to inherit org default)').option("--model <model>", 'Model identifier (must be valid for --agent-provider; pass "none" to clear)').option("--thinking-level <level>", 'Thinking/reasoning level: low, medium, high, xhigh, max (or "none" to clear)').option("--plan-mode", "Run automation messages in plan mode").option("--goal-mode", "Set automation messages as Codex goals").option("--fast-mode", "Run automation messages in fast mode").option("--personal", "Create a personal automation owned by the authenticated user").option("--disabled", "Create in disabled state").action(async (name, options) => {
37155
37275
  try {
37156
37276
  await automationCreateCommand(name, {
37157
37277
  ...options,
@@ -37166,7 +37286,7 @@ automation.command("create [name]").description("Create a new automation").optio
37166
37286
  process.exit(1);
37167
37287
  }
37168
37288
  });
37169
- automation.command("edit <id>").description("Edit an existing automation").option("-n, --name <name>", "New name").option("-p, --prompt <prompt>", "New prompt").option("-e, --enabled <enabled>", "Enable or disable (true/false)").option("--trigger-cron <schedule>", "Set cron schedule (replaces existing triggers)").option("--trigger-cron-timezone <timezone>", "Timezone for cron trigger").option("--trigger-github <event>", "Set GitHub event (replaces existing triggers)").option("--trigger-github-repos <repos>", "Comma-separated repo names to filter GitHub trigger").option("--trigger-github-exclude-users <users>", "Comma-separated GitHub usernames to exclude from triggering (pass empty string to clear the list)").option("--trigger-gitlab <event>", "Set GitLab event (replaces existing triggers)").option("--trigger-gitlab-repos <repos>", "Comma-separated repo names to filter GitLab trigger").option("--trigger-gitlab-exclude-users <users>", "Comma-separated GitLab usernames to exclude from triggering (pass empty string to clear the list)").option("--environment <environment>", "Environment name or ID").option("--lifecycle <policy>", "Workspace lifecycle: archive_when_done, sleep_when_done, default").option("--sleep-when-done", "Sleep automation workspaces as soon as the agent finishes").option("--auto-stop-minutes <minutes>", "Inactivity timeout in minutes (3-1440, requires --lifecycle default)").option("--pr-followups <enabled>", "Allow follow-up actions on matching PRs (true/false)", parseBooleanOption).option("--agent-provider <provider>", 'Coding agent to use: claude, codex, cursor, relay (or "none" to inherit org default)').option("--model <model>", 'Model identifier (must be valid for --agent-provider; pass "none" to clear)').option("--thinking-level <level>", 'Thinking/reasoning level: low, medium, high, xhigh, max (or "none" to clear)').option("--plan-mode <enabled>", "Run automation messages in plan mode (true/false)", parseBooleanOption).option("--goal-mode <enabled>", "Set automation messages as Codex goals (true/false)", parseBooleanOption).option("--fast-mode <enabled>", "Run automation messages in fast mode (true/false)", parseBooleanOption).action(async (id, options) => {
37289
+ automation.command("edit <id>").description("Edit an existing automation").option("-n, --name <name>", "New name").option("-p, --prompt <prompt>", "New prompt").option("-e, --enabled <enabled>", "Enable or disable (true/false)").option("--trigger-cron <schedule>", "Set cron schedule (replaces existing triggers)").option("--trigger-cron-timezone <timezone>", "Timezone for cron trigger").option("--trigger-github <event>", "Set GitHub event (replaces existing triggers)").option("--trigger-github-repos <repos>", "Comma-separated repo names to filter GitHub trigger").option("--trigger-github-exclude-users <users>", "Comma-separated GitHub usernames to exclude from triggering (pass empty string to clear the list)").option("--trigger-gitlab <event>", "Set GitLab event (replaces existing triggers)").option("--trigger-gitlab-repos <repos>", "Comma-separated repo names to filter GitLab trigger").option("--trigger-gitlab-exclude-users <users>", "Comma-separated GitLab usernames to exclude from triggering (pass empty string to clear the list)").option("--github-checks <names>", "Comma-separated GitHub check names to create on matching PRs (replaces the current list; pass empty string to remove them)").option("--add-github-checks <names>", "Comma-separated GitHub check names to add, keeping the ones already configured").option("--environment <environment>", "Environment name or ID").option("--lifecycle <policy>", "Workspace lifecycle: archive_when_done, sleep_when_done, default").option("--sleep-when-done", "Sleep automation workspaces as soon as the agent finishes").option("--auto-stop-minutes <minutes>", "Inactivity timeout in minutes (3-1440, requires --lifecycle default)").option("--pr-followups <enabled>", "Allow follow-up actions on matching PRs (true/false)", parseBooleanOption).option("--agent-provider <provider>", 'Coding agent to use: claude, codex, cursor, relay (or "none" to inherit org default)').option("--model <model>", 'Model identifier (must be valid for --agent-provider; pass "none" to clear)').option("--thinking-level <level>", 'Thinking/reasoning level: low, medium, high, xhigh, max (or "none" to clear)').option("--plan-mode <enabled>", "Run automation messages in plan mode (true/false)", parseBooleanOption).option("--goal-mode <enabled>", "Set automation messages as Codex goals (true/false)", parseBooleanOption).option("--fast-mode <enabled>", "Run automation messages in fast mode (true/false)", parseBooleanOption).action(async (id, options) => {
37170
37290
  try {
37171
37291
  await automationEditCommand(id, options);
37172
37292
  } catch (error51) {
@@ -37202,6 +37322,18 @@ automation.command("delete <id>").description("Delete an automation").option("-f
37202
37322
  process.exit(1);
37203
37323
  }
37204
37324
  });
37325
+ automation.command("check <checkRunId>").description("Report this automation run's verdict on one of its GitHub checks").requiredOption("--token <token>", "Ownership token for this check, exactly as given in the prompt").requiredOption("--conclusion <conclusion>", "success or failure").requiredOption("--title <title>", "One-line verdict shown on the check").option("--summary <summary>", "What was checked and why it passed or failed").action(async (checkRunId, options) => {
37326
+ try {
37327
+ await automationCheckCommand(checkRunId, options);
37328
+ } catch (error51) {
37329
+ if (error51 instanceof Error) {
37330
+ console.error(chalk24.red(`
37331
+ \u2717 ${error51.message}
37332
+ `));
37333
+ }
37334
+ process.exit(1);
37335
+ }
37336
+ });
37205
37337
  automation.action(async () => {
37206
37338
  try {
37207
37339
  await automationListCommand({});
@@ -37616,7 +37748,7 @@ if (isAgentMode()) {
37616
37748
  }
37617
37749
  });
37618
37750
  const learnings = program.command("learnings").description("Fetch curated org knowledge and propose changes for human review");
37619
- learnings.command("read").description("Fetch learnings relevant to a task. Enrich the query with codebase specifics, not the raw user request.").requiredOption("-q, --query <query>", "Enriched task description to match against learning triggers").action(async (options) => {
37751
+ learnings.command("read").description("Fetch learnings relevant to a task. Enrich the query with codebase specifics, not the raw user request.").requiredOption("-q, --query <query>", "Enriched task description to match against learning triggers").option("-f, --fresh", "Print full text even for learnings already shown in this session").action(async (options) => {
37620
37752
  try {
37621
37753
  await learningsReadCommand(options);
37622
37754
  } catch (error51) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-cli",
3
- "version": "0.2.423",
3
+ "version": "0.2.425",
4
4
  "description": "CLI for managing Replicas workspaces - SSH into cloud dev environments with automatic port forwarding",
5
5
  "main": "dist/index.mjs",
6
6
  "bin": {