@madewithremy/admin 0.1.25 → 0.1.26

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/prod.js CHANGED
@@ -987,6 +987,7 @@ import { execFileSync } from "child_process";
987
987
 
988
988
  // src/config.ts
989
989
  var WORKSPACE_DIR = process.env["WORKSPACE_DIR"] || "/home/remy/workspace";
990
+ var SIDECAR_URL = process.env["REMY_SIDECAR_URL"] || "http://127.0.0.1:4388";
990
991
 
991
992
  // src/ops/releases.ts
992
993
  function list3(ctx, params = {}) {
@@ -8263,6 +8264,205 @@ Billing and the log:
8263
8264
  --wait exit codes: 0 reached the target phase \xB7 1 the platform reported failure \xB7
8264
8265
  2 still transitioning when --timeout elapsed (default 600s).`;
8265
8266
 
8267
+ // src/ops/qaRecordings.ts
8268
+ var RECORDING_STORE = "qa-recordings";
8269
+ async function ls2(ctx, params = {}) {
8270
+ const page = await ls(ctx, {
8271
+ store: RECORDING_STORE,
8272
+ access: "private",
8273
+ ...params.sessionId ? { prefix: `${params.sessionId}/` } : {},
8274
+ ...params.cursor ? { cursor: params.cursor } : {},
8275
+ ...params.limit ? { limit: params.limit } : {}
8276
+ });
8277
+ const sessions = /* @__PURE__ */ new Map();
8278
+ for (const entry of page.files) {
8279
+ const parts = entry.key.split("/");
8280
+ if (parts.length !== 3) {
8281
+ continue;
8282
+ }
8283
+ const [sessionId, runId] = parts;
8284
+ let session = sessions.get(sessionId);
8285
+ if (!session) {
8286
+ session = { sessionId, chunks: 0, bytes: 0, runs: [] };
8287
+ sessions.set(sessionId, session);
8288
+ }
8289
+ let run3 = session.runs.find((r) => r.runId === runId);
8290
+ if (!run3) {
8291
+ run3 = { runId, chunks: 0, bytes: 0, keys: [] };
8292
+ session.runs.push(run3);
8293
+ }
8294
+ const size = entry.size ?? 0;
8295
+ session.chunks += 1;
8296
+ session.bytes += size;
8297
+ run3.chunks += 1;
8298
+ run3.bytes += size;
8299
+ run3.keys.push(entry.key);
8300
+ if (entry.updatedAt) {
8301
+ if (!session.startedAt || entry.updatedAt < session.startedAt) {
8302
+ session.startedAt = entry.updatedAt;
8303
+ }
8304
+ if (!session.endedAt || entry.updatedAt > session.endedAt) {
8305
+ session.endedAt = entry.updatedAt;
8306
+ }
8307
+ }
8308
+ }
8309
+ for (const session of sessions.values()) {
8310
+ for (const run3 of session.runs) {
8311
+ run3.keys.sort((a, b) => seqOf(a) - seqOf(b));
8312
+ }
8313
+ }
8314
+ return {
8315
+ store: RECORDING_STORE,
8316
+ sessions: [...sessions.values()].sort(
8317
+ (a, b) => (b.endedAt ?? "").localeCompare(a.endedAt ?? "")
8318
+ ),
8319
+ ...page.cursor ? { cursor: page.cursor } : {}
8320
+ };
8321
+ }
8322
+ function seqOf(key) {
8323
+ const n = Number(
8324
+ key.split("/").pop()?.replace(/\.json$/, "")
8325
+ );
8326
+ return Number.isFinite(n) ? n : 0;
8327
+ }
8328
+ var DEFAULT_EXPORT_TIMEOUT_MS = 66e4;
8329
+ async function exportVideo(_ctx, params) {
8330
+ const body = {
8331
+ recordingSessionId: params.sessionId,
8332
+ startTs: params.from,
8333
+ endTs: params.to,
8334
+ ...params.store ? { store: params.store } : {},
8335
+ ...params.access ? { access: params.access } : {}
8336
+ };
8337
+ let res;
8338
+ try {
8339
+ res = await fetch(`${SIDECAR_URL}/export-recording`, {
8340
+ method: "POST",
8341
+ headers: { "Content-Type": "application/json" },
8342
+ body: JSON.stringify(body),
8343
+ signal: AbortSignal.timeout(
8344
+ params.timeoutMs ?? DEFAULT_EXPORT_TIMEOUT_MS
8345
+ )
8346
+ });
8347
+ } catch (err) {
8348
+ throw new Error(
8349
+ `Could not reach the sandbox at ${SIDECAR_URL} (${err instanceof Error ? err.message : String(err)}). Rendering a replay needs the dev box that recorded it \u2014 this command only works inside a Remy sandbox.`
8350
+ );
8351
+ }
8352
+ const payload = await res.json().catch(() => ({}));
8353
+ if (!res.ok) {
8354
+ throw new Error(
8355
+ typeof payload.error === "string" ? payload.error : `The sandbox refused the export (HTTP ${res.status}).`
8356
+ );
8357
+ }
8358
+ const status2 = payload.export;
8359
+ if (!status2) {
8360
+ throw new Error(
8361
+ "The sandbox accepted the export but reported no result. Check the box's tunnel logs."
8362
+ );
8363
+ }
8364
+ return status2;
8365
+ }
8366
+
8367
+ // src/commands/qaRecordings.ts
8368
+ var qaRecordingsSpecs = {
8369
+ "qa-recordings ls": {
8370
+ usage: "Usage: remy-admin qa-recordings ls [--session <id>] [--cursor <cursor>] [--limit <n>]",
8371
+ flags: {
8372
+ session: { type: "string" },
8373
+ cursor: { type: "string" },
8374
+ limit: { type: "number", min: 1 }
8375
+ }
8376
+ },
8377
+ "qa-recordings export": {
8378
+ usage: "Usage: remy-admin qa-recordings export --session <id> --from <epochMs> --to <epochMs> [--store <name>] [--private]",
8379
+ flags: {
8380
+ session: { type: "string" },
8381
+ from: { type: "number" },
8382
+ to: { type: "number" },
8383
+ store: { type: "string" },
8384
+ private: { type: "boolean" }
8385
+ }
8386
+ }
8387
+ };
8388
+ async function qaRecordingsLs(ctx, a) {
8389
+ out(
8390
+ await ls2(ctx, {
8391
+ sessionId: a.str("session"),
8392
+ cursor: a.str("cursor"),
8393
+ limit: a.num("limit")
8394
+ })
8395
+ );
8396
+ }
8397
+ async function qaRecordingsExport(ctx, a) {
8398
+ const sessionId = a.str("session");
8399
+ const from = a.num("from");
8400
+ const to = a.num("to");
8401
+ if (!sessionId) {
8402
+ fatal("--session is required (see `remy-admin qa-recordings ls`).");
8403
+ }
8404
+ if (from === void 0 || to === void 0) {
8405
+ fatal(
8406
+ "--from and --to are required (epoch milliseconds). A QA run reports both on its result."
8407
+ );
8408
+ }
8409
+ const result = await exportVideo(ctx, {
8410
+ sessionId,
8411
+ from,
8412
+ to,
8413
+ store: a.str("store"),
8414
+ access: a.bool("private") ? "private" : void 0
8415
+ });
8416
+ if (result.status !== "completed") {
8417
+ fatal(
8418
+ result.errorCode === "FFMPEG_UNAVAILABLE" ? "This sandbox has no ffmpeg \u2014 video export needs a box on the current devbox image." : result.error ?? `Export ${result.status}.`
8419
+ );
8420
+ }
8421
+ out(result);
8422
+ }
8423
+ var qaRecordingsHandlers = {
8424
+ "qa-recordings ls": qaRecordingsLs,
8425
+ "qa-recordings export": qaRecordingsExport
8426
+ };
8427
+ var qaRecordingsHelp = `remy-admin qa-recordings \u2014 Browser-QA session replays.
8428
+
8429
+ Every automated browser test records what the page did, as rrweb (a DOM
8430
+ recording, not video). The chunks are ordinary private files in the
8431
+ 'qa-recordings' store, so 'remy-admin files' works on them directly: sign one
8432
+ for a link, download one, or copy one into a public store to embed it.
8433
+
8434
+ 'export' turns a replay into an mp4 \u2014 the shareable artifact, for a changelog
8435
+ entry, a release note or a bug report. It renders on the dev box (real browser,
8436
+ real ffmpeg, no AI-generated frames), so it only works inside a sandbox, it
8437
+ blocks for the length of the clip, and it won't start while a video export is
8438
+ already running.
8439
+
8440
+ Subcommands:
8441
+ ls Recording sessions in this app, grouped into runs
8442
+ export Render a window of a session to an mp4 and print where it landed
8443
+
8444
+ Usage:
8445
+ remy-admin qa-recordings ls [--session <id>] [--limit <n>]
8446
+ remy-admin qa-recordings export --session <id> --from <epochMs> --to <epochMs> [--store <name>] [--private]
8447
+
8448
+ Options:
8449
+ --session Recording session id (32 hex characters)
8450
+ --from/--to The window to render, as epoch milliseconds. A QA run prints the
8451
+ session and both bounds on its result.
8452
+ --store Destination store for the mp4 (default: assets)
8453
+ --private Write the mp4 privately instead of publicly. No URL is returned;
8454
+ use 'files sign' to make one.
8455
+
8456
+ Examples:
8457
+ # What was recorded recently
8458
+ remy-admin qa-recordings ls --limit 200
8459
+
8460
+ # Render a run and get a public URL to embed
8461
+ remy-admin qa-recordings export --session 9f2c\u2026 --from 1758000000000 --to 1758000042000
8462
+
8463
+ # The raw rrweb chunks for a session (download, inspect, or rehost)
8464
+ remy-admin files ls --private --store qa-recordings --prefix 9f2c\u2026/`;
8465
+
8266
8466
  // src/commands/index.ts
8267
8467
  var SPECS = {
8268
8468
  ...requestsSpecs,
@@ -8291,7 +8491,8 @@ var SPECS = {
8291
8491
  ...settingsSpecs,
8292
8492
  ...emailSpecs,
8293
8493
  ...cronSpecs,
8294
- ...infraSpecs
8494
+ ...infraSpecs,
8495
+ ...qaRecordingsSpecs
8295
8496
  };
8296
8497
  var HANDLERS = {
8297
8498
  ...requestsHandlers,
@@ -8320,7 +8521,8 @@ var HANDLERS = {
8320
8521
  ...settingsHandlers,
8321
8522
  ...emailHandlers,
8322
8523
  ...cronHandlers,
8323
- ...infraHandlers
8524
+ ...infraHandlers,
8525
+ ...qaRecordingsHandlers
8324
8526
  };
8325
8527
  var GROUP_HELP = {
8326
8528
  requests: requestsHelp,
@@ -8345,7 +8547,8 @@ var GROUP_HELP = {
8345
8547
  settings: settingsHelp,
8346
8548
  email: emailHelp,
8347
8549
  cron: cronHelp,
8348
- infra: infraHelp
8550
+ infra: infraHelp,
8551
+ "qa-recordings": qaRecordingsHelp
8349
8552
  };
8350
8553
 
8351
8554
  // src/ctx.ts
@@ -8405,29 +8608,30 @@ var HELP = `remy-admin \u2014 Manage your production Remy app.
8405
8608
  Usage: remy-admin <command> <subcommand> [options]
8406
8609
 
8407
8610
  Commands:
8408
- requests View request logs and metrics
8409
- crashes View frontend (browser) crash groups and events
8410
- analytics View traffic, top-N, geo, and AI-referral insights
8411
- releases View and monitor releases
8412
- diagnostics View the post-deploy Lighthouse audit (scores, issues, raw report)
8413
- domains Manage custom subdomain
8414
- users Manage app users and roles
8415
- db Query the production database
8416
- secrets Manage app secrets (env vars)
8417
- methods List and invoke methods
8418
- data Sync databases between dev and live (lift-from-dev / lift-from-live)
8419
- issues File and manage issues, in this app or another app in the workspace
8420
- prerender Manage + verify prerendered snapshots served to bots/crawlers
8421
- files Store, retrieve, share (signed links) + manage files in the app's stores
8422
- datasources Build and query searchable document corpora (RAG)
8423
- voice Phone numbers, voice call log + transcripts, voice policy settings
8424
- agent Agent (chat) conversation log + transcripts
8425
- events Realtime events \u2014 live tail, test publish, channel activity
8426
- jewels Monitor jewel shadowing; review + approve the proposal queue
8427
- settings App settings \u2014 signup allowlist, test accounts, embed origins, toggles
8428
- email Email \u2014 delivery log, blast stats, unsubscribes, inbox, custom domains
8429
- cron Scheduled jobs \u2014 schedules, run history, manual trigger
8430
- infra Dedicated infrastructure \u2014 provision, hibernate, resize + destroy retrieval capacity
8611
+ requests View request logs and metrics
8612
+ crashes View frontend (browser) crash groups and events
8613
+ analytics View traffic, top-N, geo, and AI-referral insights
8614
+ releases View and monitor releases
8615
+ diagnostics View the post-deploy Lighthouse audit (scores, issues, raw report)
8616
+ domains Manage custom subdomain
8617
+ users Manage app users and roles
8618
+ db Query the production database
8619
+ secrets Manage app secrets (env vars)
8620
+ methods List and invoke methods
8621
+ data Sync databases between dev and live (lift-from-dev / lift-from-live)
8622
+ issues File and manage issues, in this app or another app in the workspace
8623
+ prerender Manage + verify prerendered snapshots served to bots/crawlers
8624
+ files Store, retrieve, share (signed links) + manage files in the app's stores
8625
+ datasources Build and query searchable document corpora (RAG)
8626
+ voice Phone numbers, voice call log + transcripts, voice policy settings
8627
+ agent Agent (chat) conversation log + transcripts
8628
+ events Realtime events \u2014 live tail, test publish, channel activity
8629
+ jewels Monitor jewel shadowing; review + approve the proposal queue
8630
+ settings App settings \u2014 signup allowlist, test accounts, embed origins, toggles
8631
+ email Email \u2014 delivery log, blast stats, unsubscribes, inbox, custom domains
8632
+ cron Scheduled jobs \u2014 schedules, run history, manual trigger
8633
+ infra Dedicated infrastructure \u2014 provision, hibernate, resize + destroy retrieval capacity
8634
+ qa-recordings Browser-QA session replays \u2014 list them, render one as an mp4
8431
8635
 
8432
8636
  Run 'remy-admin <command> --help' for details on each command.
8433
8637
 
@@ -8460,7 +8664,8 @@ var GROUPS = [
8460
8664
  "settings",
8461
8665
  "email",
8462
8666
  "cron",
8463
- "infra"
8667
+ "infra",
8668
+ "qa-recordings"
8464
8669
  ];
8465
8670
  var KEYS = new Set(Object.keys(SPECS));
8466
8671
  function resolveRoute(argv) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madewithremy/admin",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "Admin SDK + CLI for managing production Remy apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,9 +21,11 @@
21
21
  "scripts": {
22
22
  "build": "tsup",
23
23
  "dev": "tsup --watch",
24
- "typecheck": "tsc --noEmit",
24
+ "typecheck": "npm run tsc -- --noEmit",
25
25
  "lint:fix": "prettier --write ./src",
26
- "prepublishOnly": "npm run build"
26
+ "prepublishOnly": "npm run build",
27
+ "tsc": "node node_modules/@typescript/native/bin/tsc",
28
+ "//tsc": "The native TypeScript 7 compiler by explicit path. It (typescript@7 under the @typescript/native alias) and the JS compiler API behind the `typescript` alias — which tsup's dts step needs — both declare a `tsc` bin, so node_modules/.bin/tsc is whichever npm linked last."
27
29
  },
28
30
  "engines": {
29
31
  "node": ">=22.0.0"
@@ -53,9 +55,10 @@
53
55
  },
54
56
  "devDependencies": {
55
57
  "@types/node": "^25.3.2",
58
+ "@typescript/native": "npm:typescript@^7.0.2",
56
59
  "prettier": "^3.8.1",
57
60
  "tsup": "^8",
58
61
  "tsx": "^4",
59
- "typescript": "^5.5"
62
+ "typescript": "npm:@typescript/typescript6@^6.0.2"
60
63
  }
61
64
  }