@madewithremy/admin 0.1.24 → 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/README.md +2 -1
- package/dist/index.d.ts +150 -7
- package/dist/index.js +135 -8
- package/dist/index.js.map +1 -1
- package/dist/prod.js +257 -33
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @madewithremy/admin
|
|
2
2
|
|
|
3
|
-
Admin SDK + CLI for managing production Remy apps: request logs, analytics, releases, users, secrets, database, files, email (including custom sending/receiving domains), scheduled jobs, and more.
|
|
3
|
+
Admin SDK + CLI for managing production Remy apps: request logs, analytics, releases, users, secrets, database, files, email (including custom sending/receiving domains), scheduled jobs, QA replays, and more. Almost every operation wraps the same management API the dashboard uses, so anything answerable in the console is answerable here — from the terminal or from code. The exception is `qa-recordings export`, which renders a video on the dev box and therefore only runs inside a sandbox.
|
|
4
4
|
|
|
5
5
|
One surface, two skins: every CLI command (`remy-admin <group> <sub>`) is also a typed client method (`admin.<group>.<sub>()`). Both are thin layers over the same ops core.
|
|
6
6
|
|
|
@@ -28,6 +28,7 @@ Configuration is read from the environment and the workspace — there are no co
|
|
|
28
28
|
| `API_BASE_URL` | API origin (default `https://api.mindstudio.ai`) |
|
|
29
29
|
| `WORKSPACE_DIR` | Workspace root (default `/home/vercel-sandbox/workspace`) |
|
|
30
30
|
| `${WORKSPACE_DIR}/mindstudio.json` | `appId` — which app the CLI manages |
|
|
31
|
+
| `REMY_SIDECAR_URL` | Sandbox sidecar for `qa-recordings export` (default `http://127.0.0.1:4388`) |
|
|
31
32
|
|
|
32
33
|
## Import layer
|
|
33
34
|
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,18 @@ interface AdminContext {
|
|
|
11
11
|
apiKey: string;
|
|
12
12
|
/** The app every operation is scoped to. */
|
|
13
13
|
appId: string;
|
|
14
|
+
/**
|
|
15
|
+
* Who is acting, when that is not `appId` itself — set only when the caller
|
|
16
|
+
* has retargeted at another app (`--app`, or `forApp()`), and carrying the
|
|
17
|
+
* app they came FROM.
|
|
18
|
+
*
|
|
19
|
+
* Writes that record authorship attach it, so a cross-app issue says which
|
|
20
|
+
* app filed it without the caller having to remember a second flag. That
|
|
21
|
+
* matters more than it sounds: the information was always available here and
|
|
22
|
+
* being optional at the call site is precisely how it came to be omitted
|
|
23
|
+
* everywhere.
|
|
24
|
+
*/
|
|
25
|
+
originAppId?: string;
|
|
14
26
|
/** API origin, no trailing slash. */
|
|
15
27
|
baseUrl: string;
|
|
16
28
|
}
|
|
@@ -4648,7 +4660,7 @@ interface FilesLsParams {
|
|
|
4648
4660
|
* prefix: 'images/',
|
|
4649
4661
|
* });
|
|
4650
4662
|
*/
|
|
4651
|
-
declare function ls(ctx: AdminContext, params: FilesLsParams): Promise<FilesLsResult>;
|
|
4663
|
+
declare function ls$1(ctx: AdminContext, params: FilesLsParams): Promise<FilesLsResult>;
|
|
4652
4664
|
/**
|
|
4653
4665
|
* Store-level summary: every store with object counts, total bytes, and
|
|
4654
4666
|
* AV/PII scan status histograms.
|
|
@@ -4672,14 +4684,13 @@ type files_FileRef = FileRef;
|
|
|
4672
4684
|
type files_FilesLsParams = FilesLsParams;
|
|
4673
4685
|
type files_FilesPutParams = FilesPutParams;
|
|
4674
4686
|
declare const files_fetchBytes: typeof fetchBytes;
|
|
4675
|
-
declare const files_ls: typeof ls;
|
|
4676
4687
|
declare const files_put: typeof put;
|
|
4677
4688
|
declare const files_remove: typeof remove;
|
|
4678
4689
|
declare const files_sign: typeof sign;
|
|
4679
4690
|
declare const files_stat: typeof stat;
|
|
4680
4691
|
declare const files_summary: typeof summary;
|
|
4681
4692
|
declare namespace files {
|
|
4682
|
-
export { type files_FileAccess as FileAccess, type files_FileRef as FileRef, type files_FilesLsParams as FilesLsParams, type files_FilesPutParams as FilesPutParams, files_fetchBytes as fetchBytes,
|
|
4693
|
+
export { type files_FileAccess as FileAccess, type files_FileRef as FileRef, type files_FilesLsParams as FilesLsParams, type files_FilesPutParams as FilesPutParams, files_fetchBytes as fetchBytes, ls$1 as ls, files_put as put, files_remove as remove, files_sign as sign, files_stat as stat, files_summary as summary };
|
|
4683
4694
|
}
|
|
4684
4695
|
|
|
4685
4696
|
/**
|
|
@@ -5226,9 +5237,10 @@ interface IssuesCreateParams {
|
|
|
5226
5237
|
/** Labels to file it under. Open set, at most 20, 64 chars each. */
|
|
5227
5238
|
labels?: string[];
|
|
5228
5239
|
/**
|
|
5229
|
-
* The app filing this, when it isn't the app the issue lands in.
|
|
5230
|
-
*
|
|
5231
|
-
*
|
|
5240
|
+
* The app filing this, when it isn't the app the issue lands in. Defaults to
|
|
5241
|
+
* `ctx.originAppId` — set whenever the caller retargeted at another app — so
|
|
5242
|
+
* cross-app authorship is recorded without anyone opting in. Pass it
|
|
5243
|
+
* explicitly only to override that.
|
|
5232
5244
|
*/
|
|
5233
5245
|
originAppId?: string;
|
|
5234
5246
|
}
|
|
@@ -6386,6 +6398,129 @@ declare namespace prerender {
|
|
|
6386
6398
|
export { type prerender_PrerenderInvalidateParams as PrerenderInvalidateParams, type prerender_PrerenderPagesParams as PrerenderPagesParams, type prerender_PrerenderViewParams as PrerenderViewParams, prerender_invalidate as invalidate, prerender_pages as pages, prerender_view as view };
|
|
6387
6399
|
}
|
|
6388
6400
|
|
|
6401
|
+
/**
|
|
6402
|
+
* Response types for browser-QA replay recordings.
|
|
6403
|
+
*
|
|
6404
|
+
* Chunks are ordinary private files in the app's `qa-recordings` store, keyed
|
|
6405
|
+
* `{sessionId}/{runId}/{seq}.json`, so `ls` here is a grouped view over
|
|
6406
|
+
* `files.ls` rather than its own endpoint. Transcribed from youai-api:
|
|
6407
|
+
* src/http/routes/V2Apps/develop/recordings.ts
|
|
6408
|
+
* src/common/Recordings/sessions.ts
|
|
6409
|
+
*
|
|
6410
|
+
* Export is the exception — it renders on the dev box (see ../ops/recordings).
|
|
6411
|
+
*/
|
|
6412
|
+
/** One document lifetime within a session: a page load and everything after it
|
|
6413
|
+
* until the next one. Its lowest-seq chunk carries the rrweb FullSnapshot. */
|
|
6414
|
+
interface QaRecordingRun {
|
|
6415
|
+
runId: string;
|
|
6416
|
+
chunks: number;
|
|
6417
|
+
bytes: number;
|
|
6418
|
+
/** Store-relative keys, in seq order. */
|
|
6419
|
+
keys: string[];
|
|
6420
|
+
}
|
|
6421
|
+
/** One recording session — a single tunnel process's continuous recording. */
|
|
6422
|
+
interface QaRecordingSession {
|
|
6423
|
+
sessionId: string;
|
|
6424
|
+
chunks: number;
|
|
6425
|
+
bytes: number;
|
|
6426
|
+
/** ISO-8601, from the chunk objects' last-modified times. */
|
|
6427
|
+
startedAt?: string;
|
|
6428
|
+
endedAt?: string;
|
|
6429
|
+
runs: QaRecordingRun[];
|
|
6430
|
+
}
|
|
6431
|
+
interface QaRecordingsLsResult {
|
|
6432
|
+
store: string;
|
|
6433
|
+
sessions: QaRecordingSession[];
|
|
6434
|
+
/** Present when the listing was truncated — pass it back as `cursor`. */
|
|
6435
|
+
cursor?: string;
|
|
6436
|
+
}
|
|
6437
|
+
interface QaRecordingsExportResult {
|
|
6438
|
+
jobId: string;
|
|
6439
|
+
status: 'completed' | 'failed' | 'cancelled';
|
|
6440
|
+
/** Public target only. A private one gives `store`/`key`; sign it with
|
|
6441
|
+
* `files sign` when you want a link. */
|
|
6442
|
+
url?: string;
|
|
6443
|
+
store?: string;
|
|
6444
|
+
key?: string;
|
|
6445
|
+
width?: number;
|
|
6446
|
+
height?: number;
|
|
6447
|
+
/** Length of the rendered clip, dead air already collapsed. */
|
|
6448
|
+
durationMs?: number;
|
|
6449
|
+
error?: string;
|
|
6450
|
+
errorCode?: string;
|
|
6451
|
+
}
|
|
6452
|
+
|
|
6453
|
+
/**
|
|
6454
|
+
* Browser-QA replay recordings: list what the QA agent recorded, and render a
|
|
6455
|
+
* replay to an mp4.
|
|
6456
|
+
*
|
|
6457
|
+
* `ls` is a grouped view over `files.ls` — a replay chunk is an ordinary
|
|
6458
|
+
* private file in the app's `qa-recordings` store and needs no endpoint of its
|
|
6459
|
+
* own.
|
|
6460
|
+
*
|
|
6461
|
+
* `export` is the one operation in this SDK that does NOT go through the
|
|
6462
|
+
* management API. Rendering a replay means playing it in a real browser and
|
|
6463
|
+
* encoding the frames, and the only machine set up to do that is the dev box
|
|
6464
|
+
* the recording was made on: it already has the headless Chrome, the rrweb
|
|
6465
|
+
* replayer and ffmpeg. So this talks to the sandbox's local sidecar, and says
|
|
6466
|
+
* so plainly when there isn't one. (CLI-only paths are established here —
|
|
6467
|
+
* `methods invoke --stream`, `jewels export --file` — but this is the first
|
|
6468
|
+
* that needs local compute rather than local disk.)
|
|
6469
|
+
*/
|
|
6470
|
+
|
|
6471
|
+
/** The app store QA replay chunks are written to (private). */
|
|
6472
|
+
declare const RECORDING_STORE = "qa-recordings";
|
|
6473
|
+
interface QaRecordingsLsParams {
|
|
6474
|
+
/** Only this session. Omitted → every session in the store. */
|
|
6475
|
+
sessionId?: string;
|
|
6476
|
+
cursor?: string;
|
|
6477
|
+
/** Chunk objects to scan, not sessions returned. */
|
|
6478
|
+
limit?: number;
|
|
6479
|
+
}
|
|
6480
|
+
/**
|
|
6481
|
+
* Recording sessions in this app, grouped from the chunk keys.
|
|
6482
|
+
*
|
|
6483
|
+
* @example
|
|
6484
|
+
* const { sessions } = await admin.qaRecordings.ls({});
|
|
6485
|
+
* // → [{ sessionId, chunks, bytes, runs: [{ runId, keys }] }]
|
|
6486
|
+
*/
|
|
6487
|
+
declare function ls(ctx: AdminContext, params?: QaRecordingsLsParams): Promise<QaRecordingsLsResult>;
|
|
6488
|
+
interface QaRecordingsExportParams {
|
|
6489
|
+
/** The recording session to render (32 hex characters). */
|
|
6490
|
+
sessionId: string;
|
|
6491
|
+
/** Window of the session, as epoch milliseconds. Both required: a replay is
|
|
6492
|
+
* a slice of a continuous recording, not a file. */
|
|
6493
|
+
from: number;
|
|
6494
|
+
to: number;
|
|
6495
|
+
/** Where the mp4 is written. Defaults to `assets`, public. */
|
|
6496
|
+
store?: string;
|
|
6497
|
+
access?: 'public' | 'private';
|
|
6498
|
+
/** Milliseconds to wait for the render. */
|
|
6499
|
+
timeoutMs?: number;
|
|
6500
|
+
}
|
|
6501
|
+
/**
|
|
6502
|
+
* Render a window of a recording session to an mp4 on the dev box.
|
|
6503
|
+
*
|
|
6504
|
+
* Blocks for the whole render — the clip plays in real time, then encodes and
|
|
6505
|
+
* uploads — and answers with where the video landed. Runs only on a Remy dev
|
|
6506
|
+
* box, since that is where the browser and ffmpeg are.
|
|
6507
|
+
*
|
|
6508
|
+
* @example
|
|
6509
|
+
* const { url } = await admin.qaRecordings.exportVideo({
|
|
6510
|
+
* sessionId, from: 1758000000000, to: 1758000042000,
|
|
6511
|
+
* });
|
|
6512
|
+
*/
|
|
6513
|
+
declare function exportVideo(_ctx: AdminContext, params: QaRecordingsExportParams): Promise<QaRecordingsExportResult>;
|
|
6514
|
+
|
|
6515
|
+
type qaRecordings_QaRecordingsExportParams = QaRecordingsExportParams;
|
|
6516
|
+
type qaRecordings_QaRecordingsLsParams = QaRecordingsLsParams;
|
|
6517
|
+
declare const qaRecordings_RECORDING_STORE: typeof RECORDING_STORE;
|
|
6518
|
+
declare const qaRecordings_exportVideo: typeof exportVideo;
|
|
6519
|
+
declare const qaRecordings_ls: typeof ls;
|
|
6520
|
+
declare namespace qaRecordings {
|
|
6521
|
+
export { type qaRecordings_QaRecordingsExportParams as QaRecordingsExportParams, type qaRecordings_QaRecordingsLsParams as QaRecordingsLsParams, qaRecordings_RECORDING_STORE as RECORDING_STORE, qaRecordings_exportVideo as exportVideo, qaRecordings_ls as ls };
|
|
6522
|
+
}
|
|
6523
|
+
|
|
6389
6524
|
/**
|
|
6390
6525
|
* Release operations: list, get, by-commit, dashboard live-release, and the
|
|
6391
6526
|
* composite waitForCommit op that owns the two poll loops.
|
|
@@ -7531,7 +7666,14 @@ type BoundOps<T> = {
|
|
|
7531
7666
|
declare function buildClient(ctx: AdminContext): {
|
|
7532
7667
|
/** The resolved context this client is bound to. */
|
|
7533
7668
|
context: Readonly<AdminContext>;
|
|
7534
|
-
/**
|
|
7669
|
+
/**
|
|
7670
|
+
* A sibling client for another app, sharing credentials.
|
|
7671
|
+
*
|
|
7672
|
+
* The app we came from becomes the origin, so a write through the returned
|
|
7673
|
+
* client records which app acted — the same rule the CLI's `--app` follows.
|
|
7674
|
+
* Chaining `forApp` twice keeps the FIRST app as the origin, since that is
|
|
7675
|
+
* the one whose workspace this code is running in.
|
|
7676
|
+
*/
|
|
7535
7677
|
forApp(appId: string): /*elided*/ any;
|
|
7536
7678
|
agent: BoundOps<typeof agent>;
|
|
7537
7679
|
analytics: BoundOps<typeof analytics>;
|
|
@@ -7570,6 +7712,7 @@ declare function buildClient(ctx: AdminContext): {
|
|
|
7570
7712
|
jewels: BoundOps<typeof jewels>;
|
|
7571
7713
|
methods: BoundOps<typeof methods>;
|
|
7572
7714
|
prerender: BoundOps<typeof prerender>;
|
|
7715
|
+
qaRecordings: BoundOps<typeof qaRecordings>;
|
|
7573
7716
|
releases: BoundOps<typeof releases>;
|
|
7574
7717
|
requests: BoundOps<typeof requests>;
|
|
7575
7718
|
secrets: BoundOps<typeof secrets>;
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import path from "path";
|
|
|
10
10
|
|
|
11
11
|
// src/config.ts
|
|
12
12
|
var WORKSPACE_DIR = process.env["WORKSPACE_DIR"] || "/home/remy/workspace";
|
|
13
|
+
var SIDECAR_URL = process.env["REMY_SIDECAR_URL"] || "http://127.0.0.1:4388";
|
|
13
14
|
|
|
14
15
|
// src/parseJsonConfig.ts
|
|
15
16
|
import JSON5 from "json5";
|
|
@@ -2116,6 +2117,9 @@ __export(issues_exports, {
|
|
|
2116
2117
|
list: () => list8,
|
|
2117
2118
|
reopen: () => reopen
|
|
2118
2119
|
});
|
|
2120
|
+
function authorship(ctx) {
|
|
2121
|
+
return ctx.originAppId ? { originAppId: ctx.originAppId } : {};
|
|
2122
|
+
}
|
|
2119
2123
|
function listQuery(params) {
|
|
2120
2124
|
return qs({
|
|
2121
2125
|
status: params.status,
|
|
@@ -2157,8 +2161,9 @@ function create2(ctx, params) {
|
|
|
2157
2161
|
if (params.labels !== void 0) {
|
|
2158
2162
|
requestBody.labels = params.labels;
|
|
2159
2163
|
}
|
|
2160
|
-
|
|
2161
|
-
|
|
2164
|
+
const originAppId = params.originAppId ?? ctx.originAppId;
|
|
2165
|
+
if (originAppId !== void 0) {
|
|
2166
|
+
requestBody.originAppId = originAppId;
|
|
2162
2167
|
}
|
|
2163
2168
|
return call(
|
|
2164
2169
|
ctx,
|
|
@@ -2172,13 +2177,14 @@ function comment(ctx, number, body) {
|
|
|
2172
2177
|
ctx,
|
|
2173
2178
|
"POST",
|
|
2174
2179
|
`/_internal/v2/apps/${ctx.appId}/issues/${seg(number)}/comments`,
|
|
2175
|
-
{ body, authorKind: "agent" }
|
|
2180
|
+
{ body, authorKind: "agent", ...authorship(ctx) }
|
|
2176
2181
|
);
|
|
2177
2182
|
}
|
|
2178
2183
|
function close(ctx, number, comment2) {
|
|
2179
2184
|
const requestBody = {
|
|
2180
2185
|
status: "closed",
|
|
2181
|
-
authorKind: "agent"
|
|
2186
|
+
authorKind: "agent",
|
|
2187
|
+
...authorship(ctx)
|
|
2182
2188
|
};
|
|
2183
2189
|
if (comment2 !== void 0) {
|
|
2184
2190
|
requestBody.comment = comment2;
|
|
@@ -2195,11 +2201,14 @@ function reopen(ctx, number) {
|
|
|
2195
2201
|
ctx,
|
|
2196
2202
|
"POST",
|
|
2197
2203
|
`/_internal/v2/apps/${ctx.appId}/issues/${seg(number)}/update`,
|
|
2198
|
-
{ status: "open", authorKind: "agent" }
|
|
2204
|
+
{ status: "open", authorKind: "agent", ...authorship(ctx) }
|
|
2199
2205
|
);
|
|
2200
2206
|
}
|
|
2201
2207
|
function edit(ctx, number, params) {
|
|
2202
|
-
const requestBody = {
|
|
2208
|
+
const requestBody = {
|
|
2209
|
+
authorKind: "agent",
|
|
2210
|
+
...authorship(ctx)
|
|
2211
|
+
};
|
|
2203
2212
|
if (params.title !== void 0) {
|
|
2204
2213
|
requestBody.title = params.title;
|
|
2205
2214
|
}
|
|
@@ -2407,6 +2416,112 @@ function invalidate(ctx, params = {}) {
|
|
|
2407
2416
|
);
|
|
2408
2417
|
}
|
|
2409
2418
|
|
|
2419
|
+
// src/ops/qaRecordings.ts
|
|
2420
|
+
var qaRecordings_exports = {};
|
|
2421
|
+
__export(qaRecordings_exports, {
|
|
2422
|
+
RECORDING_STORE: () => RECORDING_STORE,
|
|
2423
|
+
exportVideo: () => exportVideo,
|
|
2424
|
+
ls: () => ls2
|
|
2425
|
+
});
|
|
2426
|
+
var RECORDING_STORE = "qa-recordings";
|
|
2427
|
+
async function ls2(ctx, params = {}) {
|
|
2428
|
+
const page = await ls(ctx, {
|
|
2429
|
+
store: RECORDING_STORE,
|
|
2430
|
+
access: "private",
|
|
2431
|
+
...params.sessionId ? { prefix: `${params.sessionId}/` } : {},
|
|
2432
|
+
...params.cursor ? { cursor: params.cursor } : {},
|
|
2433
|
+
...params.limit ? { limit: params.limit } : {}
|
|
2434
|
+
});
|
|
2435
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
2436
|
+
for (const entry of page.files) {
|
|
2437
|
+
const parts = entry.key.split("/");
|
|
2438
|
+
if (parts.length !== 3) {
|
|
2439
|
+
continue;
|
|
2440
|
+
}
|
|
2441
|
+
const [sessionId, runId] = parts;
|
|
2442
|
+
let session = sessions.get(sessionId);
|
|
2443
|
+
if (!session) {
|
|
2444
|
+
session = { sessionId, chunks: 0, bytes: 0, runs: [] };
|
|
2445
|
+
sessions.set(sessionId, session);
|
|
2446
|
+
}
|
|
2447
|
+
let run3 = session.runs.find((r) => r.runId === runId);
|
|
2448
|
+
if (!run3) {
|
|
2449
|
+
run3 = { runId, chunks: 0, bytes: 0, keys: [] };
|
|
2450
|
+
session.runs.push(run3);
|
|
2451
|
+
}
|
|
2452
|
+
const size = entry.size ?? 0;
|
|
2453
|
+
session.chunks += 1;
|
|
2454
|
+
session.bytes += size;
|
|
2455
|
+
run3.chunks += 1;
|
|
2456
|
+
run3.bytes += size;
|
|
2457
|
+
run3.keys.push(entry.key);
|
|
2458
|
+
if (entry.updatedAt) {
|
|
2459
|
+
if (!session.startedAt || entry.updatedAt < session.startedAt) {
|
|
2460
|
+
session.startedAt = entry.updatedAt;
|
|
2461
|
+
}
|
|
2462
|
+
if (!session.endedAt || entry.updatedAt > session.endedAt) {
|
|
2463
|
+
session.endedAt = entry.updatedAt;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
for (const session of sessions.values()) {
|
|
2468
|
+
for (const run3 of session.runs) {
|
|
2469
|
+
run3.keys.sort((a, b) => seqOf(a) - seqOf(b));
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
return {
|
|
2473
|
+
store: RECORDING_STORE,
|
|
2474
|
+
sessions: [...sessions.values()].sort(
|
|
2475
|
+
(a, b) => (b.endedAt ?? "").localeCompare(a.endedAt ?? "")
|
|
2476
|
+
),
|
|
2477
|
+
...page.cursor ? { cursor: page.cursor } : {}
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
function seqOf(key) {
|
|
2481
|
+
const n = Number(
|
|
2482
|
+
key.split("/").pop()?.replace(/\.json$/, "")
|
|
2483
|
+
);
|
|
2484
|
+
return Number.isFinite(n) ? n : 0;
|
|
2485
|
+
}
|
|
2486
|
+
var DEFAULT_EXPORT_TIMEOUT_MS = 66e4;
|
|
2487
|
+
async function exportVideo(_ctx, params) {
|
|
2488
|
+
const body = {
|
|
2489
|
+
recordingSessionId: params.sessionId,
|
|
2490
|
+
startTs: params.from,
|
|
2491
|
+
endTs: params.to,
|
|
2492
|
+
...params.store ? { store: params.store } : {},
|
|
2493
|
+
...params.access ? { access: params.access } : {}
|
|
2494
|
+
};
|
|
2495
|
+
let res;
|
|
2496
|
+
try {
|
|
2497
|
+
res = await fetch(`${SIDECAR_URL}/export-recording`, {
|
|
2498
|
+
method: "POST",
|
|
2499
|
+
headers: { "Content-Type": "application/json" },
|
|
2500
|
+
body: JSON.stringify(body),
|
|
2501
|
+
signal: AbortSignal.timeout(
|
|
2502
|
+
params.timeoutMs ?? DEFAULT_EXPORT_TIMEOUT_MS
|
|
2503
|
+
)
|
|
2504
|
+
});
|
|
2505
|
+
} catch (err) {
|
|
2506
|
+
throw new Error(
|
|
2507
|
+
`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.`
|
|
2508
|
+
);
|
|
2509
|
+
}
|
|
2510
|
+
const payload = await res.json().catch(() => ({}));
|
|
2511
|
+
if (!res.ok) {
|
|
2512
|
+
throw new Error(
|
|
2513
|
+
typeof payload.error === "string" ? payload.error : `The sandbox refused the export (HTTP ${res.status}).`
|
|
2514
|
+
);
|
|
2515
|
+
}
|
|
2516
|
+
const status2 = payload.export;
|
|
2517
|
+
if (!status2) {
|
|
2518
|
+
throw new Error(
|
|
2519
|
+
"The sandbox accepted the export but reported no result. Check the box's tunnel logs."
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
2522
|
+
return status2;
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2410
2525
|
// src/ops/requests.ts
|
|
2411
2526
|
var requests_exports = {};
|
|
2412
2527
|
__export(requests_exports, {
|
|
@@ -2694,9 +2809,20 @@ function buildClient(ctx) {
|
|
|
2694
2809
|
return {
|
|
2695
2810
|
/** The resolved context this client is bound to. */
|
|
2696
2811
|
context: ctx,
|
|
2697
|
-
/**
|
|
2812
|
+
/**
|
|
2813
|
+
* A sibling client for another app, sharing credentials.
|
|
2814
|
+
*
|
|
2815
|
+
* The app we came from becomes the origin, so a write through the returned
|
|
2816
|
+
* client records which app acted — the same rule the CLI's `--app` follows.
|
|
2817
|
+
* Chaining `forApp` twice keeps the FIRST app as the origin, since that is
|
|
2818
|
+
* the one whose workspace this code is running in.
|
|
2819
|
+
*/
|
|
2698
2820
|
forApp(appId) {
|
|
2699
|
-
return buildClient({
|
|
2821
|
+
return buildClient({
|
|
2822
|
+
...ctx,
|
|
2823
|
+
appId,
|
|
2824
|
+
originAppId: appId === ctx.appId ? ctx.originAppId : ctx.originAppId ?? ctx.appId
|
|
2825
|
+
});
|
|
2700
2826
|
},
|
|
2701
2827
|
agent: bindOps(agent_exports, ctx),
|
|
2702
2828
|
analytics: bindOps(analytics_exports, ctx),
|
|
@@ -2729,6 +2855,7 @@ function buildClient(ctx) {
|
|
|
2729
2855
|
jewels: bindOps(jewels_exports, ctx),
|
|
2730
2856
|
methods: bindOps(methods_exports, ctx),
|
|
2731
2857
|
prerender: bindOps(prerender_exports, ctx),
|
|
2858
|
+
qaRecordings: bindOps(qaRecordings_exports, ctx),
|
|
2732
2859
|
releases: bindOps(releases_exports, ctx),
|
|
2733
2860
|
requests: bindOps(requests_exports, ctx),
|
|
2734
2861
|
secrets: bindOps(secrets_exports, ctx),
|