@runalabs/rill-cli 0.1.11 → 0.1.12

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 (3) hide show
  1. package/README.md +4 -0
  2. package/dist/cli.js +505 -481
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -288,3 +288,7 @@ Progress runs through `uploading` → `queued` → `encoding` → `processing`
288
288
  Run `rill connect --email you@example.com` to authorize account access or keep your anonymous recordings. Open the displayed link, sign in, and enter the code yourself. Approval allows creation and reading of your existing and new recordings for 90 days, with automatic renewal of one-hour access tokens. Revoke the connection in Rill's Credentials page to stop access and renewal. Deletion and changes to existing sharing settings are not included.
289
289
 
290
290
  Use `rill record list` to list accessible recordings and `rill inspect <recording-id>` for private recording details. `rill inspect <share-url>` continues to work without account access. The existing `rill login` and `RILL_TOKEN` flows remain supported. Local credential storage requires macOS Keychain or Linux Secret Service.
291
+
292
+ ### Check the connected account
293
+
294
+ Run `rill whoami` (or `rill --pretty whoami`) to verify your current identity. JSON output includes status, account email when available, workspace, scopes, and access expiry. It uses RILL_TOKEN when set, otherwise the saved OS credential. Existing access can renew automatically; it never creates an identity or opens an approval flow. Missing credentials return `not_connected` and exit 1. Invalid or revoked credentials return an error.
package/dist/cli.js CHANGED
@@ -5,9 +5,389 @@ var __export = (target, all) => {
5
5
  __defProp(target, name, { get: all[name], enumerable: true });
6
6
  };
7
7
 
8
+ // src/agent-session.ts
9
+ import { hostname, homedir } from "os";
10
+ import { mkdir, rm, stat } from "fs/promises";
11
+ import { join } from "path";
12
+ import { createHash, randomBytes } from "crypto";
13
+ var JWT_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer";
14
+ var CLAIM_GRANT = "urn:workos:agent-auth:grant-type:claim";
15
+ var AgentAuthError = class extends Error {
16
+ constructor(code, message) {
17
+ super(`${code}: ${message}`);
18
+ this.code = code;
19
+ }
20
+ };
21
+ function parseAuthorization(value) {
22
+ if (!value?.startsWith("{")) return null;
23
+ const saved = JSON.parse(value);
24
+ if (saved.version !== 1 || typeof saved.anonymous !== "boolean") throw new Error("Saved Rill authorization is invalid. Run rill connect --email <your-email>.");
25
+ return saved;
26
+ }
27
+ async function request(apiUrl, path, body, form = false) {
28
+ const url2 = new URL(apiUrl);
29
+ if (url2.protocol !== "https:" && !["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname)) throw new Error("Agent authentication requires HTTPS.");
30
+ const response = await fetch(`${apiUrl}${path}`, {
31
+ method: "POST",
32
+ redirect: "error",
33
+ signal: AbortSignal.timeout(15e3),
34
+ headers: { "Content-Type": form ? "application/x-www-form-urlencoded" : "application/json" },
35
+ body: form ? new URLSearchParams(Object.entries(body).map(([key, value2]) => [key, String(value2)])) : JSON.stringify(body)
36
+ });
37
+ const value = await response.json();
38
+ if (!response.ok) throw new AgentAuthError(value.error ?? "authentication_failed", value.error_description ?? "Could not authorize Rill.");
39
+ return value;
40
+ }
41
+ function withTokens(saved, tokens) {
42
+ return {
43
+ ...saved,
44
+ accessToken: tokens.access_token,
45
+ accessExpiresAt: Date.now() + tokens.expires_in * 1e3,
46
+ ...tokens.identity_assertion ? { identityAssertion: tokens.identity_assertion, assertionExpires: tokens.assertion_expires } : {}
47
+ };
48
+ }
49
+ var save = (apiUrl, value) => saveCredential(apiUrl, JSON.stringify(value));
50
+ var pending = /* @__PURE__ */ new Map();
51
+ async function loadAgentCredential(apiUrl) {
52
+ if (process.env.RILL_TOKEN) return process.env.RILL_TOKEN;
53
+ const inFlight = pending.get(apiUrl);
54
+ if (inFlight) return inFlight;
55
+ const promise2 = (async () => {
56
+ const raw = await loadStoredCredential(apiUrl);
57
+ const saved = parseAuthorization(raw);
58
+ if (!saved) return raw;
59
+ if (saved.accessToken && (saved.accessExpiresAt ?? 0) > Date.now() + 6e4) return saved.accessToken;
60
+ if (!saved.identityAssertion && saved.anonymous && saved.registrationToken) return null;
61
+ if (!saved.identityAssertion) throw new Error("Connection is awaiting approval. Run rill connect --email <your-email>.");
62
+ const tokens = await request(apiUrl, "/oauth2/token", { grant_type: JWT_GRANT, assertion: saved.identityAssertion }, true);
63
+ await save(apiUrl, withTokens(saved, tokens));
64
+ return tokens.access_token;
65
+ })();
66
+ pending.set(apiUrl, promise2);
67
+ try {
68
+ return await promise2;
69
+ } finally {
70
+ pending.delete(apiUrl);
71
+ }
72
+ }
73
+ async function ensureAnonymousCredential(apiUrl) {
74
+ const root = join(homedir(), ".rill", "auth-locks");
75
+ await mkdir(root, { recursive: true, mode: 448 });
76
+ const lock = join(root, createHash("sha256").update(apiUrl).digest("hex"));
77
+ const deadline = Date.now() + 3e4;
78
+ while (true) {
79
+ try {
80
+ await mkdir(lock, { mode: 448 });
81
+ break;
82
+ } catch (error51) {
83
+ if (error51.code !== "EEXIST") throw error51;
84
+ const info = await stat(lock).catch(() => null);
85
+ if (info && Date.now() - info.mtimeMs > 12e4) {
86
+ await rm(lock, { recursive: true, force: true });
87
+ continue;
88
+ }
89
+ if (Date.now() > deadline) throw new Error("Another Rill process is setting up authorization. Retry shortly.");
90
+ await new Promise((resolve) => setTimeout(resolve, 200));
91
+ }
92
+ }
93
+ try {
94
+ return await createAnonymousCredential(apiUrl);
95
+ } finally {
96
+ await rm(lock, { recursive: true, force: true });
97
+ }
98
+ }
99
+ async function createAnonymousCredential(apiUrl) {
100
+ const existing = await loadAgentCredential(apiUrl);
101
+ if (existing) return existing;
102
+ const stored = parseAuthorization(await loadStoredCredential(apiUrl));
103
+ const initial = stored?.registrationToken ? stored : { version: 1, anonymous: true, registrationToken: randomBytes(32).toString("hex") };
104
+ await save(apiUrl, initial);
105
+ const registered = await request(apiUrl, "/agent/identity", { type: "anonymous", analytics_enabled: process.env.RILL_ANONYMOUS_ANALYTICS !== "0" && process.env.DO_NOT_TRACK !== "1", registration_token: initial.registrationToken, name: `${hostname()} \xB7 ${process.platform}` });
106
+ const saved = { ...initial, registrationToken: void 0, identityAssertion: registered.identity_assertion, assertionExpires: registered.assertion_expires, claimToken: registered.claim_token };
107
+ await save(apiUrl, saved);
108
+ process.stderr.write("Recording anonymously: three recordings, up to five minutes each. Recordings and share links expire after seven days. Run rill connect --email <your-email> to keep them.\n");
109
+ return await loadAgentCredential(apiUrl);
110
+ }
111
+ async function connectAgent(apiUrl, email3) {
112
+ if (process.env.RILL_TOKEN) throw new Error("Unset RILL_TOKEN before connecting a local identity.");
113
+ let saved = parseAuthorization(await loadStoredCredential(apiUrl));
114
+ let claim;
115
+ if (saved?.claimToken && saved.anonymous) {
116
+ try {
117
+ const result = await request(apiUrl, "/agent/identity/claim", { claim_token: saved.claimToken, email: email3 });
118
+ claim = result.claim_attempt;
119
+ } catch (error51) {
120
+ if (!(error51 instanceof AgentAuthError) || error51.code !== "claim_expired") throw error51;
121
+ }
122
+ }
123
+ if (!claim) {
124
+ await save(apiUrl, saved ?? { version: 1, anonymous: true });
125
+ const result = await request(apiUrl, "/agent/identity", { type: "service_auth", login_hint: email3, name: `${hostname()} \xB7 ${process.platform}` });
126
+ saved = { version: 1, anonymous: true, claimToken: result.claim_token };
127
+ await save(apiUrl, saved);
128
+ claim = result.claim;
129
+ }
130
+ process.stderr.write(`Open ${claim.verification_uri}
131
+ Sign in as ${email3} and enter ${claim.user_code}. Approve only the request you started.
132
+ `);
133
+ const deadline = Date.now() + claim.expires_in * 1e3;
134
+ let interval = claim.interval;
135
+ while (Date.now() < deadline) {
136
+ await new Promise((resolve) => setTimeout(resolve, interval * 1e3));
137
+ try {
138
+ const tokens = await request(apiUrl, "/oauth2/token", { grant_type: CLAIM_GRANT, claim_token: saved.claimToken }, true);
139
+ const connected = withTokens({ version: 1, anonymous: false }, tokens);
140
+ await save(apiUrl, connected);
141
+ return { status: "authenticated", expiresAt: connected.assertionExpires, apiUrl };
142
+ } catch (error51) {
143
+ if (error51 instanceof AgentAuthError && error51.code === "expired_token") throw new AgentAuthError("expired_token", "The approval attempt expired. Run rill connect again to request a fresh code using the same saved claim token.");
144
+ if (error51 instanceof AgentAuthError && error51.code === "authorization_pending") continue;
145
+ if (error51 instanceof AgentAuthError && error51.code === "slow_down") {
146
+ interval += 5;
147
+ continue;
148
+ }
149
+ throw error51;
150
+ }
151
+ }
152
+ throw new AgentAuthError("expired_token", "Connection request expired. Run rill connect again to restart the approval attempt using the same saved claim token.");
153
+ }
154
+
155
+ // src/keychain.ts
156
+ import { spawn } from "child_process";
157
+ var service = "rill";
158
+ async function saveCredential(apiUrl, token) {
159
+ if (process.platform === "darwin") {
160
+ await run("security", ["add-generic-password", "-U", "-a", apiUrl, "-s", service, "-w", token]);
161
+ return;
162
+ }
163
+ if (process.platform === "linux") {
164
+ await run("secret-tool", ["store", "--label=Rill agent credential", "service", service, "api-url", apiUrl], token);
165
+ return;
166
+ }
167
+ throw new Error("OS keychain storage is not supported on this platform yet. Use RILL_TOKEN.");
168
+ }
169
+ function loadCredential(apiUrl) {
170
+ return loadAgentCredential(apiUrl);
171
+ }
172
+ async function loadStoredCredential(apiUrl) {
173
+ try {
174
+ if (process.platform === "darwin") return (await run("security", ["find-generic-password", "-a", apiUrl, "-s", service, "-w"])).trim() || null;
175
+ if (process.platform === "linux") return (await run("secret-tool", ["lookup", "service", service, "api-url", apiUrl])).trim() || null;
176
+ } catch (error51) {
177
+ const code = error51.exitCode;
178
+ if (process.platform === "darwin" && code === 44) return null;
179
+ if (process.platform === "linux" && code === 1 && !error51.message.trim()) return null;
180
+ throw new Error("Cannot read the OS credential store. Unlock your keychain or enable Secret Service; your identity has not been replaced.");
181
+ }
182
+ throw new Error("Local authorization requires macOS Keychain or Linux Secret Service.");
183
+ }
184
+ function run(command, args, input) {
185
+ return new Promise((resolve, reject) => {
186
+ const child = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"] });
187
+ const stdout = [];
188
+ const stderr = [];
189
+ child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
190
+ child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk)));
191
+ child.once("error", reject);
192
+ child.once("exit", (code) => code === 0 ? resolve(Buffer.concat(stdout).toString("utf8")) : reject(Object.assign(new Error(Buffer.concat(stderr).toString("utf8").trim()), { exitCode: code })));
193
+ if (input) child.stdin.end(input);
194
+ else child.stdin.end();
195
+ });
196
+ }
197
+
198
+ // src/control-plane.ts
199
+ import { createReadStream } from "fs";
200
+ import { readFile, stat as stat2 } from "fs/promises";
201
+ import { Upload } from "tus-js-client";
202
+ var ControlPlaneError = class extends Error {
203
+ constructor(code, message, recovery, details, httpStatus) {
204
+ super(`${code}: ${message}${recovery ? `: ${recovery}` : ""}`);
205
+ this.code = code;
206
+ this.recovery = recovery;
207
+ this.details = details;
208
+ this.httpStatus = httpStatus;
209
+ this.name = "ControlPlaneError";
210
+ }
211
+ };
212
+ var ControlPlaneHttpError = class extends Error {
213
+ constructor(httpStatus) {
214
+ super(`Request failed (${httpStatus}).`);
215
+ this.httpStatus = httpStatus;
216
+ this.name = "ControlPlaneHttpError";
217
+ }
218
+ };
219
+ var ControlPlaneClient = class {
220
+ constructor(apiUrl, token) {
221
+ this.apiUrl = apiUrl;
222
+ this.token = token;
223
+ }
224
+ encodingCapabilities() {
225
+ return this.request("/api/encoding/capabilities");
226
+ }
227
+ beginEncoding(id, manifest) {
228
+ return this.request(`/api/encoding/${encodeURIComponent(id)}`, { method: "POST", body: JSON.stringify(manifest) });
229
+ }
230
+ encodingStatus(id) {
231
+ return this.request(`/api/encoding/${encodeURIComponent(id)}`);
232
+ }
233
+ uploadEncodingPart(id, part, bytes) {
234
+ return this.request(`/api/encoding/${encodeURIComponent(id)}/parts/${part}`, { method: "PUT", headers: { "Content-Type": "application/octet-stream", "Content-Length": String(bytes.length) }, body: new Uint8Array(bytes), signal: AbortSignal.timeout(12e4) });
235
+ }
236
+ completeEncoding(id) {
237
+ return this.request(`/api/encoding/${encodeURIComponent(id)}/complete`, { method: "POST" });
238
+ }
239
+ retryEncoding(id) {
240
+ return this.request(`/api/encoding/${encodeURIComponent(id)}/retry`, { method: "POST" });
241
+ }
242
+ createCloudRun(input) {
243
+ return this.request("/api/cloud/runs", { method: "POST", body: JSON.stringify(input) });
244
+ }
245
+ cloudRun(id) {
246
+ return this.request(`/api/cloud/runs/${encodeURIComponent(id)}`);
247
+ }
248
+ cancelCloudRun(id) {
249
+ return this.request(`/api/cloud/runs/${encodeURIComponent(id)}/cancel`, { method: "POST" });
250
+ }
251
+ health() {
252
+ return this.request("/api/health", {}, false);
253
+ }
254
+ agentSession() {
255
+ return this.request("/api/agent/session", { signal: AbortSignal.timeout(5e3) });
256
+ }
257
+ reportCliCommand(input) {
258
+ return this.request("/api/analytics/cli-command", { method: "POST", body: JSON.stringify(input) });
259
+ }
260
+ createRecording(input) {
261
+ return this.request("/api/recordings", { method: "POST", body: JSON.stringify(input) });
262
+ }
263
+ markRecordingStarted(recordingId, input) {
264
+ return this.request(`/api/recordings/${recordingId}/local-started`, {
265
+ method: "POST",
266
+ body: JSON.stringify(input)
267
+ });
268
+ }
269
+ markRecordingStopped(recordingId, input) {
270
+ return this.request(`/api/recordings/${recordingId}/local-stopped`, {
271
+ method: "POST",
272
+ body: JSON.stringify(input)
273
+ });
274
+ }
275
+ submitRecordingFeedback(recordingId, input) {
276
+ return this.request(recordingId ? `/api/recordings/${recordingId}/feedback` : "/api/feedback", {
277
+ method: "POST",
278
+ body: JSON.stringify(input)
279
+ });
280
+ }
281
+ provisionVideo(recordingId, input) {
282
+ return this.request(`/api/recordings/${recordingId}/video-upload`, { method: "POST", body: JSON.stringify(input) });
283
+ }
284
+ recordingStatus(recordingId) {
285
+ return this.request(`/api/recordings/${recordingId}`);
286
+ }
287
+ cancelRecording(recordingId, reason = "cancelled_by_agent") {
288
+ return this.request(`/api/recordings/${recordingId}/cancel`, {
289
+ method: "POST",
290
+ body: JSON.stringify({ reason })
291
+ });
292
+ }
293
+ async waitUntilPlayable(recordingId, timeoutMs = 10 * 6e4) {
294
+ const deadline = Date.now() + timeoutMs;
295
+ let intervalMs = 1e3;
296
+ while (Date.now() < deadline) {
297
+ const recording = await this.recordingStatus(recordingId);
298
+ if (recording.status === "ready" && recording.shareUrl) {
299
+ return { ...recording, shareUrl: new URL(recording.shareUrl, this.apiUrl).toString() };
300
+ }
301
+ if (recording.status === "failed") throw new Error(`processing_failed: ${recording.failure_message ?? "Cloudflare Stream could not process the recording."}`);
302
+ process.stderr.write(`\rprocessing ${recording.status.padEnd(12)}`);
303
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
304
+ intervalMs = Math.min(5e3, Math.round(intervalMs * 1.4));
305
+ }
306
+ throw new Error("processing_failed: Timed out waiting for the recording to become playable. Run `rill record status <recording-id>` to continue checking.");
307
+ }
308
+ async uploadVideo(uploadUrl, filePath) {
309
+ const file2 = await stat2(filePath);
310
+ if (uploadUrl.startsWith(this.apiUrl)) {
311
+ const response = await fetch(uploadUrl, { method: "PUT", headers: { "Content-Length": String(file2.size) }, body: createReadStream(filePath), duplex: "half" });
312
+ if (!response.ok) throw new Error(`Video upload failed (${response.status}).`);
313
+ return;
314
+ }
315
+ await new Promise((resolve, reject) => {
316
+ const upload = new Upload(createReadStream(filePath), {
317
+ uploadUrl,
318
+ uploadSize: file2.size,
319
+ retryDelays: [0, 1e3, 3e3, 5e3, 1e4],
320
+ removeFingerprintOnSuccess: true,
321
+ onError: reject,
322
+ onSuccess: () => resolve(),
323
+ onProgress: (sent, total) => process.stderr.write(`\ruploading ${Math.round(sent / total * 100)}%`)
324
+ });
325
+ upload.start();
326
+ });
327
+ process.stderr.write("\n");
328
+ }
329
+ async uploadDiagnostics(recordingId, filePath, summary) {
330
+ const body = await readFile(filePath);
331
+ return this.request(`/api/recordings/${recordingId}/diagnostics`, { method: "PUT", headers: { "Content-Encoding": "gzip", "Content-Type": "application/x-ndjson", "Content-Length": String(body.length), "X-Rill-Summary": JSON.stringify(summary) }, body });
332
+ }
333
+ listRecordings() {
334
+ return this.request("/api/recordings");
335
+ }
336
+ createDeviceAuthorization(input) {
337
+ return this.request("/api/device/authorizations", { method: "POST", body: JSON.stringify(input) }, false);
338
+ }
339
+ pollDeviceAuthorization(deviceCode) {
340
+ return this.request("/api/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) }, false);
341
+ }
342
+ inspect(shareUrl) {
343
+ if (/^rec_[a-zA-Z0-9_-]+$/.test(shareUrl)) return this.request(`/api/recordings/${encodeURIComponent(shareUrl)}`);
344
+ const url2 = new URL(shareUrl);
345
+ const slug = url2.pathname.split("/").filter(Boolean).at(-1);
346
+ if (!slug) throw new Error("The share URL does not contain a slug.");
347
+ return fetch(`${url2.origin}/api/public/shares/${encodeURIComponent(slug)}/context`, { headers: { Accept: "application/json" } }).then(async (response) => {
348
+ if (!response.ok) throw new Error(`Could not inspect the share link (${response.status}).`);
349
+ return response.json();
350
+ });
351
+ }
352
+ async request(path, init = {}, authenticated = true) {
353
+ const headers = new Headers(init.headers);
354
+ if (init.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
355
+ if (authenticated) {
356
+ const token = typeof this.token === "function" ? await this.token() : this.token;
357
+ if (!token) throw new Error("authentication_required: Run `rill login` or set RILL_TOKEN.");
358
+ headers.set("Authorization", `Bearer ${token}`);
359
+ }
360
+ const response = await fetch(`${this.apiUrl}${path}`, { ...init, headers });
361
+ const value = await response.json().catch(() => null);
362
+ if (!response.ok && value?.error?.code && value.error.message) {
363
+ throw new ControlPlaneError(value.error.code, value.error.message, value.error.recovery, value.error.details, response.status);
364
+ }
365
+ if (!response.ok) throw new ControlPlaneHttpError(response.status);
366
+ return value;
367
+ }
368
+ };
369
+
370
+ // src/whoami.ts
371
+ async function whoami(apiUrl) {
372
+ const token = await loadCredential(apiUrl);
373
+ if (!token) return { schemaVersion: 1, status: "not_connected", authenticated: false, apiUrl };
374
+ const session = await new ControlPlaneClient(apiUrl, token).agentSession();
375
+ return {
376
+ schemaVersion: 1,
377
+ status: session.anonymous ? "anonymous" : "connected",
378
+ authenticated: true,
379
+ apiUrl,
380
+ anonymous: Boolean(session.anonymous),
381
+ email: session.email ?? null,
382
+ workspaceId: session.workspaceId ?? null,
383
+ scopes: session.scopes,
384
+ expiresAt: session.expiresAt
385
+ };
386
+ }
387
+
8
388
  // src/remote-encoding.ts
9
389
  import { open, readFile as readFile2 } from "fs/promises";
10
- import { join } from "path";
390
+ import { join as join2 } from "path";
11
391
 
12
392
  // ../../node_modules/zod/v4/classic/external.js
13
393
  var external_exports = {};
@@ -142,7 +522,7 @@ __export(external_exports, {
142
522
  guid: () => guid2,
143
523
  hash: () => hash,
144
524
  hex: () => hex2,
145
- hostname: () => hostname2,
525
+ hostname: () => hostname3,
146
526
  httpUrl: () => httpUrl,
147
527
  includes: () => _includes,
148
528
  instanceof: () => _instanceof,
@@ -1553,7 +1933,7 @@ __export(regexes_exports, {
1553
1933
  extendedDuration: () => extendedDuration,
1554
1934
  guid: () => guid,
1555
1935
  hex: () => hex,
1556
- hostname: () => hostname,
1936
+ hostname: () => hostname2,
1557
1937
  html5Email: () => html5Email,
1558
1938
  httpProtocol: () => httpProtocol,
1559
1939
  idnEmail: () => idnEmail,
@@ -1631,7 +2011,7 @@ var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]
1631
2011
  var cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
1632
2012
  var base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
1633
2013
  var base64url = /^[A-Za-z0-9_-]*$/;
1634
- var hostname = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
2014
+ var hostname2 = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
1635
2015
  var domain = /^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
1636
2016
  var httpProtocol = /^https?$/;
1637
2017
  var e164 = /^\+[1-9]\d{6,14}$/;
@@ -12503,7 +12883,7 @@ __export(schemas_exports2, {
12503
12883
  guid: () => guid2,
12504
12884
  hash: () => hash,
12505
12885
  hex: () => hex2,
12506
- hostname: () => hostname2,
12886
+ hostname: () => hostname3,
12507
12887
  httpUrl: () => httpUrl,
12508
12888
  instanceof: () => _instanceof,
12509
12889
  int: () => int,
@@ -13123,7 +13503,7 @@ var ZodCustomStringFormat = /* @__PURE__ */ $constructor("ZodCustomStringFormat"
13123
13503
  function stringFormat(format, fnOrRegex, _params = {}) {
13124
13504
  return _stringFormat(ZodCustomStringFormat, format, fnOrRegex, _params);
13125
13505
  }
13126
- function hostname2(_params) {
13506
+ function hostname3(_params) {
13127
13507
  return _stringFormat(ZodCustomStringFormat, "hostname", regexes_exports.hostname, _params);
13128
13508
  }
13129
13509
  function hex2(_params) {
@@ -14471,244 +14851,72 @@ function convertSchema(schema, ctx) {
14471
14851
  baseSchema = baseSchema.describe(schema.description);
14472
14852
  }
14473
14853
  return baseSchema;
14474
- }
14475
- function fromJSONSchema(schema, params) {
14476
- if (typeof schema === "boolean") {
14477
- return schema ? z.any() : z.never();
14478
- }
14479
- let normalized;
14480
- try {
14481
- normalized = JSON.parse(JSON.stringify(schema));
14482
- } catch {
14483
- throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas");
14484
- }
14485
- const version2 = detectVersion(normalized, params?.defaultTarget);
14486
- const defs = normalized.$defs || normalized.definitions || {};
14487
- const ctx = {
14488
- version: version2,
14489
- defs,
14490
- refs: /* @__PURE__ */ new Map(),
14491
- processing: /* @__PURE__ */ new Set(),
14492
- rootSchema: normalized,
14493
- registry: params?.registry ?? globalRegistry
14494
- };
14495
- return convertSchema(normalized, ctx);
14496
- }
14497
-
14498
- // ../../node_modules/zod/v4/classic/coerce.js
14499
- var coerce_exports = {};
14500
- __export(coerce_exports, {
14501
- bigint: () => bigint3,
14502
- boolean: () => boolean3,
14503
- date: () => date4,
14504
- number: () => number3,
14505
- string: () => string3
14506
- });
14507
- function string3(params) {
14508
- return _coercedString(ZodString, params);
14509
- }
14510
- function number3(params) {
14511
- return _coercedNumber(ZodNumber, params);
14512
- }
14513
- function boolean3(params) {
14514
- return _coercedBoolean(ZodBoolean, params);
14515
- }
14516
- function bigint3(params) {
14517
- return _coercedBigint(ZodBigInt, params);
14518
- }
14519
- function date4(params) {
14520
- return _coercedDate(ZodDate, params);
14521
- }
14522
-
14523
- // ../../node_modules/zod/v4/classic/external.js
14524
- config(en_default());
14525
-
14526
- // ../contracts/src/encoding.ts
14527
- var ENCODING_PART_BYTES = 4 * 1024 * 1024;
14528
- var ENCODING_MAX_BYTES = 1024 * 1024 * 1024;
14529
- var frameManifestSchema = external_exports.object({
14530
- version: external_exports.literal(1),
14531
- durationSeconds: external_exports.number().positive().max(600),
14532
- width: external_exports.number().int().min(320).max(3840),
14533
- height: external_exports.number().int().min(240).max(2160),
14534
- frames: external_exports.array(external_exports.object({ timestamp: external_exports.number().nonnegative().finite(), size: external_exports.number().int().positive().max(ENCODING_PART_BYTES) }).strict()).min(1).max(36e3)
14535
- }).strict().superRefine((m, ctx) => {
14536
- if (m.frames.reduce((n, f) => n + f.size, 0) > ENCODING_MAX_BYTES) ctx.addIssue({ code: "custom", message: "Frame upload exceeds the trial byte limit." });
14537
- for (let i = 1; i < m.frames.length; i++) if (m.frames[i].timestamp < m.frames[i - 1].timestamp) ctx.addIssue({ code: "custom", message: "Frame timestamps must be ordered." });
14538
- if (m.frames.at(-1).timestamp - m.frames[0].timestamp > m.durationSeconds + 1) ctx.addIssue({ code: "custom", message: "Frame timestamps exceed capture duration." });
14539
- });
14540
-
14541
- // src/control-plane.ts
14542
- import { createReadStream } from "fs";
14543
- import { readFile, stat } from "fs/promises";
14544
- import { Upload } from "tus-js-client";
14545
- var ControlPlaneError = class extends Error {
14546
- constructor(code, message, recovery, details, httpStatus) {
14547
- super(`${code}: ${message}${recovery ? `: ${recovery}` : ""}`);
14548
- this.code = code;
14549
- this.recovery = recovery;
14550
- this.details = details;
14551
- this.httpStatus = httpStatus;
14552
- this.name = "ControlPlaneError";
14553
- }
14554
- };
14555
- var ControlPlaneHttpError = class extends Error {
14556
- constructor(httpStatus) {
14557
- super(`Request failed (${httpStatus}).`);
14558
- this.httpStatus = httpStatus;
14559
- this.name = "ControlPlaneHttpError";
14560
- }
14561
- };
14562
- var ControlPlaneClient = class {
14563
- constructor(apiUrl, token) {
14564
- this.apiUrl = apiUrl;
14565
- this.token = token;
14566
- }
14567
- encodingCapabilities() {
14568
- return this.request("/api/encoding/capabilities");
14569
- }
14570
- beginEncoding(id, manifest) {
14571
- return this.request(`/api/encoding/${encodeURIComponent(id)}`, { method: "POST", body: JSON.stringify(manifest) });
14572
- }
14573
- encodingStatus(id) {
14574
- return this.request(`/api/encoding/${encodeURIComponent(id)}`);
14575
- }
14576
- uploadEncodingPart(id, part, bytes) {
14577
- return this.request(`/api/encoding/${encodeURIComponent(id)}/parts/${part}`, { method: "PUT", headers: { "Content-Type": "application/octet-stream", "Content-Length": String(bytes.length) }, body: new Uint8Array(bytes), signal: AbortSignal.timeout(12e4) });
14578
- }
14579
- completeEncoding(id) {
14580
- return this.request(`/api/encoding/${encodeURIComponent(id)}/complete`, { method: "POST" });
14581
- }
14582
- retryEncoding(id) {
14583
- return this.request(`/api/encoding/${encodeURIComponent(id)}/retry`, { method: "POST" });
14584
- }
14585
- createCloudRun(input) {
14586
- return this.request("/api/cloud/runs", { method: "POST", body: JSON.stringify(input) });
14587
- }
14588
- cloudRun(id) {
14589
- return this.request(`/api/cloud/runs/${encodeURIComponent(id)}`);
14590
- }
14591
- cancelCloudRun(id) {
14592
- return this.request(`/api/cloud/runs/${encodeURIComponent(id)}/cancel`, { method: "POST" });
14593
- }
14594
- health() {
14595
- return this.request("/api/health", {}, false);
14596
- }
14597
- agentSession() {
14598
- return this.request("/api/agent/session", { signal: AbortSignal.timeout(5e3) });
14599
- }
14600
- reportCliCommand(input) {
14601
- return this.request("/api/analytics/cli-command", { method: "POST", body: JSON.stringify(input) });
14602
- }
14603
- createRecording(input) {
14604
- return this.request("/api/recordings", { method: "POST", body: JSON.stringify(input) });
14605
- }
14606
- markRecordingStarted(recordingId, input) {
14607
- return this.request(`/api/recordings/${recordingId}/local-started`, {
14608
- method: "POST",
14609
- body: JSON.stringify(input)
14610
- });
14611
- }
14612
- markRecordingStopped(recordingId, input) {
14613
- return this.request(`/api/recordings/${recordingId}/local-stopped`, {
14614
- method: "POST",
14615
- body: JSON.stringify(input)
14616
- });
14617
- }
14618
- submitRecordingFeedback(recordingId, input) {
14619
- return this.request(recordingId ? `/api/recordings/${recordingId}/feedback` : "/api/feedback", {
14620
- method: "POST",
14621
- body: JSON.stringify(input)
14622
- });
14623
- }
14624
- provisionVideo(recordingId, input) {
14625
- return this.request(`/api/recordings/${recordingId}/video-upload`, { method: "POST", body: JSON.stringify(input) });
14626
- }
14627
- recordingStatus(recordingId) {
14628
- return this.request(`/api/recordings/${recordingId}`);
14629
- }
14630
- cancelRecording(recordingId, reason = "cancelled_by_agent") {
14631
- return this.request(`/api/recordings/${recordingId}/cancel`, {
14632
- method: "POST",
14633
- body: JSON.stringify({ reason })
14634
- });
14635
- }
14636
- async waitUntilPlayable(recordingId, timeoutMs = 10 * 6e4) {
14637
- const deadline = Date.now() + timeoutMs;
14638
- let intervalMs = 1e3;
14639
- while (Date.now() < deadline) {
14640
- const recording = await this.recordingStatus(recordingId);
14641
- if (recording.status === "ready" && recording.shareUrl) {
14642
- return { ...recording, shareUrl: new URL(recording.shareUrl, this.apiUrl).toString() };
14643
- }
14644
- if (recording.status === "failed") throw new Error(`processing_failed: ${recording.failure_message ?? "Cloudflare Stream could not process the recording."}`);
14645
- process.stderr.write(`\rprocessing ${recording.status.padEnd(12)}`);
14646
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
14647
- intervalMs = Math.min(5e3, Math.round(intervalMs * 1.4));
14648
- }
14649
- throw new Error("processing_failed: Timed out waiting for the recording to become playable. Run `rill record status <recording-id>` to continue checking.");
14650
- }
14651
- async uploadVideo(uploadUrl, filePath) {
14652
- const file2 = await stat(filePath);
14653
- if (uploadUrl.startsWith(this.apiUrl)) {
14654
- const response = await fetch(uploadUrl, { method: "PUT", headers: { "Content-Length": String(file2.size) }, body: createReadStream(filePath), duplex: "half" });
14655
- if (!response.ok) throw new Error(`Video upload failed (${response.status}).`);
14656
- return;
14657
- }
14658
- await new Promise((resolve, reject) => {
14659
- const upload = new Upload(createReadStream(filePath), {
14660
- uploadUrl,
14661
- uploadSize: file2.size,
14662
- retryDelays: [0, 1e3, 3e3, 5e3, 1e4],
14663
- removeFingerprintOnSuccess: true,
14664
- onError: reject,
14665
- onSuccess: () => resolve(),
14666
- onProgress: (sent, total) => process.stderr.write(`\ruploading ${Math.round(sent / total * 100)}%`)
14667
- });
14668
- upload.start();
14669
- });
14670
- process.stderr.write("\n");
14671
- }
14672
- async uploadDiagnostics(recordingId, filePath, summary) {
14673
- const body = await readFile(filePath);
14674
- return this.request(`/api/recordings/${recordingId}/diagnostics`, { method: "PUT", headers: { "Content-Encoding": "gzip", "Content-Type": "application/x-ndjson", "Content-Length": String(body.length), "X-Rill-Summary": JSON.stringify(summary) }, body });
14675
- }
14676
- listRecordings() {
14677
- return this.request("/api/recordings");
14678
- }
14679
- createDeviceAuthorization(input) {
14680
- return this.request("/api/device/authorizations", { method: "POST", body: JSON.stringify(input) }, false);
14681
- }
14682
- pollDeviceAuthorization(deviceCode) {
14683
- return this.request("/api/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) }, false);
14684
- }
14685
- inspect(shareUrl) {
14686
- if (/^rec_[a-zA-Z0-9_-]+$/.test(shareUrl)) return this.request(`/api/recordings/${encodeURIComponent(shareUrl)}`);
14687
- const url2 = new URL(shareUrl);
14688
- const slug = url2.pathname.split("/").filter(Boolean).at(-1);
14689
- if (!slug) throw new Error("The share URL does not contain a slug.");
14690
- return fetch(`${url2.origin}/api/public/shares/${encodeURIComponent(slug)}/context`, { headers: { Accept: "application/json" } }).then(async (response) => {
14691
- if (!response.ok) throw new Error(`Could not inspect the share link (${response.status}).`);
14692
- return response.json();
14693
- });
14694
- }
14695
- async request(path, init = {}, authenticated = true) {
14696
- const headers = new Headers(init.headers);
14697
- if (init.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
14698
- if (authenticated) {
14699
- const token = typeof this.token === "function" ? await this.token() : this.token;
14700
- if (!token) throw new Error("authentication_required: Run `rill login` or set RILL_TOKEN.");
14701
- headers.set("Authorization", `Bearer ${token}`);
14702
- }
14703
- const response = await fetch(`${this.apiUrl}${path}`, { ...init, headers });
14704
- const value = await response.json().catch(() => null);
14705
- if (!response.ok && value?.error?.code && value.error.message) {
14706
- throw new ControlPlaneError(value.error.code, value.error.message, value.error.recovery, value.error.details, response.status);
14707
- }
14708
- if (!response.ok) throw new ControlPlaneHttpError(response.status);
14709
- return value;
14854
+ }
14855
+ function fromJSONSchema(schema, params) {
14856
+ if (typeof schema === "boolean") {
14857
+ return schema ? z.any() : z.never();
14710
14858
  }
14711
- };
14859
+ let normalized;
14860
+ try {
14861
+ normalized = JSON.parse(JSON.stringify(schema));
14862
+ } catch {
14863
+ throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas");
14864
+ }
14865
+ const version2 = detectVersion(normalized, params?.defaultTarget);
14866
+ const defs = normalized.$defs || normalized.definitions || {};
14867
+ const ctx = {
14868
+ version: version2,
14869
+ defs,
14870
+ refs: /* @__PURE__ */ new Map(),
14871
+ processing: /* @__PURE__ */ new Set(),
14872
+ rootSchema: normalized,
14873
+ registry: params?.registry ?? globalRegistry
14874
+ };
14875
+ return convertSchema(normalized, ctx);
14876
+ }
14877
+
14878
+ // ../../node_modules/zod/v4/classic/coerce.js
14879
+ var coerce_exports = {};
14880
+ __export(coerce_exports, {
14881
+ bigint: () => bigint3,
14882
+ boolean: () => boolean3,
14883
+ date: () => date4,
14884
+ number: () => number3,
14885
+ string: () => string3
14886
+ });
14887
+ function string3(params) {
14888
+ return _coercedString(ZodString, params);
14889
+ }
14890
+ function number3(params) {
14891
+ return _coercedNumber(ZodNumber, params);
14892
+ }
14893
+ function boolean3(params) {
14894
+ return _coercedBoolean(ZodBoolean, params);
14895
+ }
14896
+ function bigint3(params) {
14897
+ return _coercedBigint(ZodBigInt, params);
14898
+ }
14899
+ function date4(params) {
14900
+ return _coercedDate(ZodDate, params);
14901
+ }
14902
+
14903
+ // ../../node_modules/zod/v4/classic/external.js
14904
+ config(en_default());
14905
+
14906
+ // ../contracts/src/encoding.ts
14907
+ var ENCODING_PART_BYTES = 4 * 1024 * 1024;
14908
+ var ENCODING_MAX_BYTES = 1024 * 1024 * 1024;
14909
+ var frameManifestSchema = external_exports.object({
14910
+ version: external_exports.literal(1),
14911
+ durationSeconds: external_exports.number().positive().max(600),
14912
+ width: external_exports.number().int().min(320).max(3840),
14913
+ height: external_exports.number().int().min(240).max(2160),
14914
+ frames: external_exports.array(external_exports.object({ timestamp: external_exports.number().nonnegative().finite(), size: external_exports.number().int().positive().max(ENCODING_PART_BYTES) }).strict()).min(1).max(36e3)
14915
+ }).strict().superRefine((m, ctx) => {
14916
+ if (m.frames.reduce((n, f) => n + f.size, 0) > ENCODING_MAX_BYTES) ctx.addIssue({ code: "custom", message: "Frame upload exceeds the trial byte limit." });
14917
+ for (let i = 1; i < m.frames.length; i++) if (m.frames[i].timestamp < m.frames[i - 1].timestamp) ctx.addIssue({ code: "custom", message: "Frame timestamps must be ordered." });
14918
+ if (m.frames.at(-1).timestamp - m.frames[0].timestamp > m.durationSeconds + 1) ctx.addIssue({ code: "custom", message: "Frame timestamps exceed capture duration." });
14919
+ });
14712
14920
 
14713
14921
  // src/remote-encoding.ts
14714
14922
  async function withFrameUploadRetry(upload, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
@@ -14728,14 +14936,14 @@ Frame upload interrupted; retrying in ${delay / 1e3}s.
14728
14936
  }
14729
14937
  }
14730
14938
  async function submitFrameCapture(client, id, directory) {
14731
- const manifest = frameManifestSchema.parse(JSON.parse(await readFile2(join(directory, "capture-manifest.json"), "utf8")));
14939
+ const manifest = frameManifestSchema.parse(JSON.parse(await readFile2(join2(directory, "capture-manifest.json"), "utf8")));
14732
14940
  let status = await client.beginEncoding(id, manifest);
14733
14941
  if (status.status === "failed") status = await client.retryEncoding(id);
14734
14942
  if (status.status !== "uploading") {
14735
14943
  if (["expired", "cancelled"].includes(status.status)) throw new Error(`Capture ${status.status}; it cannot be retried.`);
14736
14944
  return status;
14737
14945
  }
14738
- const file2 = await open(join(directory, "frames.bin"), "r");
14946
+ const file2 = await open(join2(directory, "frames.bin"), "r");
14739
14947
  try {
14740
14948
  const total = manifest.frames.reduce((n, f) => n + f.size, 0);
14741
14949
  if ((await file2.stat()).size !== total) throw new Error("Capture bytes do not match the manifest.");
@@ -14777,16 +14985,16 @@ import { basename, dirname as dirname2 } from "path";
14777
14985
  import { readdir as readdir2, rm as rm5, stat as stat4 } from "fs/promises";
14778
14986
 
14779
14987
  // ../recorder/src/client.ts
14780
- import { request } from "http";
14781
- import { spawn } from "child_process";
14988
+ import { request as request2 } from "http";
14989
+ import { spawn as spawn2 } from "child_process";
14782
14990
 
14783
14991
  // ../recorder/src/paths.ts
14784
- import { homedir, tmpdir } from "os";
14785
- import { join as join2 } from "path";
14786
- var rillDirectory = () => process.env.RILL_HOME ?? join2(homedir(), ".rill");
14787
- var daemonSocketPath = () => process.env.RILL_SOCKET ?? join2(rillDirectory(), "recorder-v2.sock");
14788
- var daemonStatePath = () => join2(rillDirectory(), "sessions-v2.json");
14789
- var recordingTempRoot = () => process.env.RILL_TEMP ?? join2(tmpdir(), "rill-recordings");
14992
+ import { homedir as homedir2, tmpdir } from "os";
14993
+ import { join as join3 } from "path";
14994
+ var rillDirectory = () => process.env.RILL_HOME ?? join3(homedir2(), ".rill");
14995
+ var daemonSocketPath = () => process.env.RILL_SOCKET ?? join3(rillDirectory(), "recorder-v2.sock");
14996
+ var daemonStatePath = () => join3(rillDirectory(), "sessions-v2.json");
14997
+ var recordingTempRoot = () => process.env.RILL_TEMP ?? join3(tmpdir(), "rill-recordings");
14790
14998
 
14791
14999
  // ../recorder/src/client.ts
14792
15000
  async function ensureDaemon(cliEntryPath) {
@@ -14795,7 +15003,7 @@ async function ensureDaemon(cliEntryPath) {
14795
15003
  return;
14796
15004
  } catch {
14797
15005
  }
14798
- const child = spawn(process.execPath, [cliEntryPath, "daemon"], { detached: true, stdio: "ignore", env: process.env });
15006
+ const child = spawn2(process.execPath, [cliEntryPath, "daemon"], { detached: true, stdio: "ignore", env: process.env });
14799
15007
  child.unref();
14800
15008
  const deadline = Date.now() + 1e4;
14801
15009
  while (Date.now() < deadline) {
@@ -14820,7 +15028,7 @@ function recordingStatus(recordingId) {
14820
15028
  function callDaemon(method, path, body) {
14821
15029
  return new Promise((resolve, reject) => {
14822
15030
  const payload = body === void 0 ? void 0 : Buffer.from(JSON.stringify(body));
14823
- const outgoing = request({ socketPath: daemonSocketPath(), method, path, headers: payload ? { "Content-Type": "application/json", "Content-Length": payload.length } : void 0 }, (incoming) => {
15031
+ const outgoing = request2({ socketPath: daemonSocketPath(), method, path, headers: payload ? { "Content-Type": "application/json", "Content-Length": payload.length } : void 0 }, (incoming) => {
14824
15032
  const chunks = [];
14825
15033
  incoming.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
14826
15034
  incoming.on("end", () => {
@@ -14840,17 +15048,17 @@ function callDaemon(method, path, body) {
14840
15048
  }
14841
15049
 
14842
15050
  // ../recorder/src/retention.ts
14843
- import { readdir, readFile as readFile3, rm } from "fs/promises";
14844
- import { join as join3 } from "path";
15051
+ import { readdir, readFile as readFile3, rm as rm2 } from "fs/promises";
15052
+ import { join as join4 } from "path";
14845
15053
  async function purgeExpiredRemoteCaptures(root, now = Date.now()) {
14846
15054
  const removed = [];
14847
15055
  for (const id of await readdir(root).catch(() => [])) {
14848
15056
  if (!/^rec_[a-zA-Z0-9_-]+$/.test(id)) continue;
14849
- const directory = join3(root, id);
15057
+ const directory = join4(root, id);
14850
15058
  try {
14851
- const marker = JSON.parse(await readFile3(join3(directory, "remote-capture.json"), "utf8"));
15059
+ const marker = JSON.parse(await readFile3(join4(directory, "remote-capture.json"), "utf8"));
14852
15060
  if (typeof marker.expiresAt !== "number" || marker.expiresAt > now) continue;
14853
- await rm(directory, { recursive: true, force: true });
15061
+ await rm2(directory, { recursive: true, force: true });
14854
15062
  removed.push(id);
14855
15063
  } catch {
14856
15064
  }
@@ -14861,7 +15069,7 @@ async function hasRetainedRemoteCaptures(root) {
14861
15069
  for (const id of await readdir(root).catch(() => [])) {
14862
15070
  if (!/^rec_[a-zA-Z0-9_-]+$/.test(id)) continue;
14863
15071
  try {
14864
- const marker = JSON.parse(await readFile3(join3(root, id, "remote-capture.json"), "utf8"));
15072
+ const marker = JSON.parse(await readFile3(join4(root, id, "remote-capture.json"), "utf8"));
14865
15073
  if (typeof marker.expiresAt === "number" && Number.isFinite(marker.expiresAt)) return true;
14866
15074
  } catch {
14867
15075
  }
@@ -14871,8 +15079,8 @@ async function hasRetainedRemoteCaptures(root) {
14871
15079
 
14872
15080
  // ../recorder/src/daemon.ts
14873
15081
  import { createServer as createServer2 } from "http";
14874
- import { mkdir as mkdir4, readFile as readFile5, unlink, writeFile as writeFile4, rename, stat as stat2 } from "fs/promises";
14875
- import { dirname, join as join7 } from "path";
15082
+ import { mkdir as mkdir5, readFile as readFile5, unlink, writeFile as writeFile4, rename, stat as stat3 } from "fs/promises";
15083
+ import { dirname, join as join8 } from "path";
14876
15084
 
14877
15085
  // ../contracts/src/recording.ts
14878
15086
  var cursorAppearanceSchema = external_exports.object({
@@ -15033,10 +15241,10 @@ async function attachCursorOverlay(page, cdp, input) {
15033
15241
 
15034
15242
  // ../recorder/src/session.ts
15035
15243
  import { chromium } from "playwright-core";
15036
- import { spawn as spawn3 } from "child_process";
15244
+ import { spawn as spawn4 } from "child_process";
15037
15245
  import { createServer } from "net";
15038
- import { mkdir as mkdir3, mkdtemp, rm as rm2, writeFile as writeFile3 } from "fs/promises";
15039
- import { join as join6 } from "path";
15246
+ import { mkdir as mkdir4, mkdtemp, rm as rm3, writeFile as writeFile3 } from "fs/promises";
15247
+ import { join as join7 } from "path";
15040
15248
  import { tmpdir as tmpdir2 } from "os";
15041
15249
 
15042
15250
  // ../recorder/src/bundle.ts
@@ -15116,9 +15324,9 @@ function redactJson(value, depth = 0) {
15116
15324
  }
15117
15325
 
15118
15326
  // ../recorder/src/encoding.ts
15119
- import { open as open2, writeFile, mkdir } from "fs/promises";
15120
- import { spawn as spawn2 } from "child_process";
15121
- import { join as join4 } from "path";
15327
+ import { open as open2, writeFile, mkdir as mkdir2 } from "fs/promises";
15328
+ import { spawn as spawn3 } from "child_process";
15329
+ import { join as join5 } from "path";
15122
15330
  var active = 0;
15123
15331
  var waiting = [];
15124
15332
  function encodingConcurrency() {
@@ -15148,11 +15356,11 @@ async function encodeFrames(directory, manifest, output, signal) {
15148
15356
  lines.push(`file 'frame-${String(i).padStart(6, "0")}.jpg'`, `duration ${Math.max(1 / 60, duration3).toFixed(6)}`);
15149
15357
  }
15150
15358
  lines.push(`file 'frame-${String(manifest.frames.length - 1).padStart(6, "0")}.jpg'`);
15151
- await writeFile(join4(directory, "frames.ffconcat"), lines.join("\n") + "\n", { mode: 384 });
15359
+ await writeFile(join5(directory, "frames.ffconcat"), lines.join("\n") + "\n", { mode: 384 });
15152
15360
  const threads = Number(process.env.RILL_FFMPEG_THREADS ?? 1);
15153
15361
  if (!Number.isInteger(threads) || threads < 1 || threads > 16) throw new Error("Invalid RILL_FFMPEG_THREADS");
15154
15362
  await new Promise((resolve, reject) => {
15155
- const child = spawn2(process.env.RILL_FFMPEG ?? "ffmpeg", ["-hide_banner", "-loglevel", "error", "-protocol_whitelist", "file", "-f", "concat", "-safe", "1", "-i", "frames.ffconcat", "-vsync", "vfr", "-pix_fmt", "yuv420p", "-c:v", "libvpx-vp9", "-deadline", "good", "-cpu-used", "4", "-threads", String(threads), "-b:v", "0", "-crf", "32", "-y", output], { cwd: directory, stdio: ["ignore", "ignore", "pipe"] });
15363
+ const child = spawn3(process.env.RILL_FFMPEG ?? "ffmpeg", ["-hide_banner", "-loglevel", "error", "-protocol_whitelist", "file", "-f", "concat", "-safe", "1", "-i", "frames.ffconcat", "-vsync", "vfr", "-pix_fmt", "yuv420p", "-c:v", "libvpx-vp9", "-deadline", "good", "-cpu-used", "4", "-threads", String(threads), "-b:v", "0", "-crf", "32", "-y", output], { cwd: directory, stdio: ["ignore", "ignore", "pipe"] });
15156
15364
  let stderr = "";
15157
15365
  child.stderr.on("data", (chunk) => {
15158
15366
  stderr = (stderr + String(chunk)).slice(-2e3);
@@ -15177,8 +15385,8 @@ async function encodeFrames(directory, manifest, output, signal) {
15177
15385
  }
15178
15386
 
15179
15387
  // ../recorder/src/screencast.ts
15180
- import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile4, open as open3 } from "fs/promises";
15181
- import { join as join5 } from "path";
15388
+ import { mkdir as mkdir3, writeFile as writeFile2, readFile as readFile4, open as open3 } from "fs/promises";
15389
+ import { join as join6 } from "path";
15182
15390
  var ScreencastRecorder = class {
15183
15391
  constructor(session, directory, width, height) {
15184
15392
  this.session = session;
@@ -15193,7 +15401,7 @@ var ScreencastRecorder = class {
15193
15401
  frameBytes = 0;
15194
15402
  captureError;
15195
15403
  async start() {
15196
- await mkdir2(this.directory, { recursive: true, mode: 448 });
15404
+ await mkdir3(this.directory, { recursive: true, mode: 448 });
15197
15405
  this.session.on("Page.screencastFrame", (payload) => {
15198
15406
  const bytes = Buffer.from(payload.data, "base64");
15199
15407
  if (this.frameCounter >= 36e3 || this.frameBytes + bytes.length > ENCODING_MAX_BYTES || bytes.length > ENCODING_PART_BYTES) {
@@ -15202,7 +15410,7 @@ var ScreencastRecorder = class {
15202
15410
  return;
15203
15411
  }
15204
15412
  this.frameBytes += bytes.length;
15205
- const path = join5(this.directory, `frame-${String(this.frameCounter).padStart(6, "0")}.jpg`);
15413
+ const path = join6(this.directory, `frame-${String(this.frameCounter).padStart(6, "0")}.jpg`);
15206
15414
  this.frameCounter += 1;
15207
15415
  const write = writeFile2(path, bytes, { mode: 384 }).then(() => {
15208
15416
  this.frames.push({ path, timestamp: payload.metadata.timestamp ?? Date.now() / 1e3 });
@@ -15226,7 +15434,7 @@ var ScreencastRecorder = class {
15226
15434
  if (!this.frames.length) throw new Error("No video frames were captured.");
15227
15435
  const ordered = this.frames.toSorted((a, b) => a.timestamp - b.timestamp);
15228
15436
  const frames = [];
15229
- const bundle = await open3(join5(this.directory, "frames.bin"), "w", 384);
15437
+ const bundle = await open3(join6(this.directory, "frames.bin"), "w", 384);
15230
15438
  try {
15231
15439
  for (const frame of ordered) {
15232
15440
  const bytes = await readFile4(frame.path);
@@ -15237,7 +15445,7 @@ var ScreencastRecorder = class {
15237
15445
  await bundle.close();
15238
15446
  }
15239
15447
  const manifest = frameManifestSchema.parse({ version: 1, durationSeconds: Math.min(600, expectedDurationSeconds), width: this.width, height: this.height, frames });
15240
- await writeFile2(join5(this.directory, "capture-manifest.json"), JSON.stringify(manifest), { mode: 384 });
15448
+ await writeFile2(join6(this.directory, "capture-manifest.json"), JSON.stringify(manifest), { mode: 384 });
15241
15449
  if (!captureOnly) await encodeFrames(this.directory, manifest, outputPath);
15242
15450
  return { frameCount: this.frames.length, outputPath };
15243
15451
  }
@@ -15286,11 +15494,11 @@ var RecordingSession = class _RecordingSession {
15286
15494
  static async start(options) {
15287
15495
  const width = options.width ?? 1280;
15288
15496
  const height = options.height ?? 720;
15289
- await mkdir3(options.outputDirectory, { recursive: true, mode: 448 });
15290
- if (options.encoding === "remote") await writeFile3(join6(options.outputDirectory, "remote-capture.json"), JSON.stringify({ expiresAt: Date.now() + 864e5 }), { mode: 384 });
15497
+ await mkdir4(options.outputDirectory, { recursive: true, mode: 448 });
15498
+ if (options.encoding === "remote") await writeFile3(join7(options.outputDirectory, "remote-capture.json"), JSON.stringify({ expiresAt: Date.now() + 864e5 }), { mode: 384 });
15291
15499
  const runtime = options.cdpUrl ? await connectAttached(options.cdpUrl, options.outputDirectory) : await launchManaged({ headed: options.headed ?? false, width, height, outputDirectory: options.outputDirectory, proxyServer: options.proxyServer });
15292
15500
  const cdpSession = await runtime.context.newCDPSession(runtime.page);
15293
- const screencast = new ScreencastRecorder(cdpSession, join6(options.outputDirectory, "frames"), width, height);
15501
+ const screencast = new ScreencastRecorder(cdpSession, join7(options.outputDirectory, "frames"), width, height);
15294
15502
  const session = new _RecordingSession(options, { ...runtime, screencast });
15295
15503
  try {
15296
15504
  await session.attachDiagnostics(options.captureNetworkBodies ?? false);
@@ -15322,8 +15530,8 @@ var RecordingSession = class _RecordingSession {
15322
15530
  if (this.maxTimer) clearTimeout(this.maxTimer);
15323
15531
  const durationSeconds = Math.max(0.1, (Date.now() - this.startedAt.getTime()) / 1e3);
15324
15532
  const outputDirectory = this.outputDirectory;
15325
- const videoPath = join6(outputDirectory, "recording.webm");
15326
- const diagnosticsPath = join6(outputDirectory, "diagnostics-v1.ndjson.gz");
15533
+ const videoPath = join7(outputDirectory, "recording.webm");
15534
+ const diagnosticsPath = join7(outputDirectory, "diagnostics-v1.ndjson.gz");
15327
15535
  const bundle = {
15328
15536
  schemaVersion: 1,
15329
15537
  capturePolicyVersion: "safe-default-v1",
@@ -15346,13 +15554,13 @@ var RecordingSession = class _RecordingSession {
15346
15554
  if (this.browserProcess) {
15347
15555
  await this.browser.close().catch(() => void 0);
15348
15556
  await terminateBrowser(this.browserProcess);
15349
- if (this.profileDirectory) await rm2(this.profileDirectory, { recursive: true, force: true }).catch(() => void 0);
15557
+ if (this.profileDirectory) await rm3(this.profileDirectory, { recursive: true, force: true }).catch(() => void 0);
15350
15558
  }
15351
15559
  }
15352
15560
  const consoleErrors = this.consoleEvents.filter((event) => event.severity === "error").length;
15353
15561
  const failedRequests = this.networkEvents.filter((event) => event.failureReason).length;
15354
15562
  const httpErrors = this.networkEvents.filter((event) => (event.status ?? 0) >= 400).length;
15355
- return { recordingId: this.recordingId, videoPath, ...this.encoding === "remote" ? { frameDirectory: join6(outputDirectory, "frames") } : {}, diagnosticsPath, durationSeconds, stoppedReason: reason, diagnostics: { consoleErrors, failedRequests, httpErrors, truncated: this.truncated, bodyCaptureEnabled: this.captureNetworkBodies } };
15563
+ return { recordingId: this.recordingId, videoPath, ...this.encoding === "remote" ? { frameDirectory: join7(outputDirectory, "frames") } : {}, diagnosticsPath, durationSeconds, stoppedReason: reason, diagnostics: { consoleErrors, failedRequests, httpErrors, truncated: this.truncated, bodyCaptureEnabled: this.captureNetworkBodies } };
15356
15564
  }
15357
15565
  captureNetworkBodies = false;
15358
15566
  encoding = "local";
@@ -15464,7 +15672,7 @@ var RecordingSession = class _RecordingSession {
15464
15672
  };
15465
15673
  async function launchManaged(options) {
15466
15674
  const port = await getAvailablePort();
15467
- const profileDirectory = await mkdtemp(join6(tmpdir2(), "rill-chrome-"));
15675
+ const profileDirectory = await mkdtemp(join7(tmpdir2(), "rill-chrome-"));
15468
15676
  const executable = findChromeExecutable();
15469
15677
  const args = [
15470
15678
  `--remote-debugging-port=${port}`,
@@ -15482,7 +15690,7 @@ async function launchManaged(options) {
15482
15690
  "about:blank"
15483
15691
  ];
15484
15692
  const env = options.proxyServer ? Object.fromEntries(Object.entries(process.env).filter(([key]) => ["PATH", "HOME", "TMPDIR", "DISPLAY", "LANG", "LD_LIBRARY_PATH", "XDG_RUNTIME_DIR"].includes(key))) : process.env;
15485
- const browserProcess = spawn3(executable, args, { stdio: ["ignore", "ignore", "pipe"], detached: false, env });
15693
+ const browserProcess = spawn4(executable, args, { stdio: ["ignore", "ignore", "pipe"], detached: false, env });
15486
15694
  const cdpUrl = `http://127.0.0.1:${port}`;
15487
15695
  try {
15488
15696
  await waitForCdp(cdpUrl, browserProcess);
@@ -15493,7 +15701,7 @@ async function launchManaged(options) {
15493
15701
  return { cdpUrl, browser, context, page, browserProcess, profileDirectory };
15494
15702
  } catch (error51) {
15495
15703
  await terminateBrowser(browserProcess);
15496
- await rm2(profileDirectory, { recursive: true, force: true }).catch(() => void 0);
15704
+ await rm3(profileDirectory, { recursive: true, force: true }).catch(() => void 0);
15497
15705
  throw error51;
15498
15706
  }
15499
15707
  }
@@ -15544,8 +15752,8 @@ var sessions = /* @__PURE__ */ new Map();
15544
15752
  var lastActivity = Date.now();
15545
15753
  async function runDaemon() {
15546
15754
  const socketPath = daemonSocketPath();
15547
- await mkdir4(dirname(socketPath), { recursive: true, mode: 448 });
15548
- await mkdir4(recordingTempRoot(), { recursive: true, mode: 448 });
15755
+ await mkdir5(dirname(socketPath), { recursive: true, mode: 448 });
15756
+ await mkdir5(recordingTempRoot(), { recursive: true, mode: 448 });
15549
15757
  await restoreRecoverableSessions();
15550
15758
  await purgeExpiredCaptures();
15551
15759
  await unlink(socketPath).catch((error51) => {
@@ -15579,7 +15787,7 @@ async function route(request3, response) {
15579
15787
  try {
15580
15788
  const input = await readJson(request3);
15581
15789
  if (!/^rec_[a-zA-Z0-9_-]+$/.test(input.recordingId)) return json2(response, 400, { error: { code: "validation_failed", message: "recordingId is required." } });
15582
- const outputDirectory = join7(recordingTempRoot(), input.recordingId);
15790
+ const outputDirectory = join8(recordingTempRoot(), input.recordingId);
15583
15791
  const session = await RecordingSession.start({ ...input, outputDirectory });
15584
15792
  const status = session.status();
15585
15793
  const entry = { session, base: { recordingId: status.recordingId, cdpUrl: status.cdpUrl, startedAt: status.startedAt }, state: "recording" };
@@ -15657,7 +15865,7 @@ async function purgeExpiredCaptures() {
15657
15865
  const removed = await purgeExpiredRemoteCaptures(recordingTempRoot());
15658
15866
  for (const id of removed) sessions.delete(id);
15659
15867
  let changed = removed.length > 0;
15660
- for (const [id, entry] of sessions) if (entry.result?.frameDirectory && !await stat2(entry.result.frameDirectory).catch(() => null)) {
15868
+ for (const [id, entry] of sessions) if (entry.result?.frameDirectory && !await stat3(entry.result.frameDirectory).catch(() => null)) {
15661
15869
  sessions.delete(id);
15662
15870
  changed = true;
15663
15871
  }
@@ -15694,196 +15902,6 @@ function cliFailureCode(error51) {
15694
15902
  return message.match(/^([a-z][a-z0-9_]{0,79}):/)?.[1] ?? "unexpected_error";
15695
15903
  }
15696
15904
 
15697
- // src/agent-session.ts
15698
- import { hostname as hostname3, homedir as homedir2 } from "os";
15699
- import { mkdir as mkdir5, rm as rm4, stat as stat3 } from "fs/promises";
15700
- import { join as join8 } from "path";
15701
- import { createHash, randomBytes } from "crypto";
15702
- var JWT_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer";
15703
- var CLAIM_GRANT = "urn:workos:agent-auth:grant-type:claim";
15704
- var AgentAuthError = class extends Error {
15705
- constructor(code, message) {
15706
- super(`${code}: ${message}`);
15707
- this.code = code;
15708
- }
15709
- };
15710
- function parseAuthorization(value) {
15711
- if (!value?.startsWith("{")) return null;
15712
- const saved = JSON.parse(value);
15713
- if (saved.version !== 1 || typeof saved.anonymous !== "boolean") throw new Error("Saved Rill authorization is invalid. Run rill connect --email <your-email>.");
15714
- return saved;
15715
- }
15716
- async function request2(apiUrl, path, body, form = false) {
15717
- const url2 = new URL(apiUrl);
15718
- if (url2.protocol !== "https:" && !["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname)) throw new Error("Agent authentication requires HTTPS.");
15719
- const response = await fetch(`${apiUrl}${path}`, {
15720
- method: "POST",
15721
- redirect: "error",
15722
- signal: AbortSignal.timeout(15e3),
15723
- headers: { "Content-Type": form ? "application/x-www-form-urlencoded" : "application/json" },
15724
- body: form ? new URLSearchParams(Object.entries(body).map(([key, value2]) => [key, String(value2)])) : JSON.stringify(body)
15725
- });
15726
- const value = await response.json();
15727
- if (!response.ok) throw new AgentAuthError(value.error ?? "authentication_failed", value.error_description ?? "Could not authorize Rill.");
15728
- return value;
15729
- }
15730
- function withTokens(saved, tokens) {
15731
- return {
15732
- ...saved,
15733
- accessToken: tokens.access_token,
15734
- accessExpiresAt: Date.now() + tokens.expires_in * 1e3,
15735
- ...tokens.identity_assertion ? { identityAssertion: tokens.identity_assertion, assertionExpires: tokens.assertion_expires } : {}
15736
- };
15737
- }
15738
- var save = (apiUrl, value) => saveCredential(apiUrl, JSON.stringify(value));
15739
- var pending = /* @__PURE__ */ new Map();
15740
- async function loadAgentCredential(apiUrl) {
15741
- if (process.env.RILL_TOKEN) return process.env.RILL_TOKEN;
15742
- const inFlight = pending.get(apiUrl);
15743
- if (inFlight) return inFlight;
15744
- const promise2 = (async () => {
15745
- const raw = await loadStoredCredential(apiUrl);
15746
- const saved = parseAuthorization(raw);
15747
- if (!saved) return raw;
15748
- if (saved.accessToken && (saved.accessExpiresAt ?? 0) > Date.now() + 6e4) return saved.accessToken;
15749
- if (!saved.identityAssertion && saved.anonymous && saved.registrationToken) return null;
15750
- if (!saved.identityAssertion) throw new Error("Connection is awaiting approval. Run rill connect --email <your-email>.");
15751
- const tokens = await request2(apiUrl, "/oauth2/token", { grant_type: JWT_GRANT, assertion: saved.identityAssertion }, true);
15752
- await save(apiUrl, withTokens(saved, tokens));
15753
- return tokens.access_token;
15754
- })();
15755
- pending.set(apiUrl, promise2);
15756
- try {
15757
- return await promise2;
15758
- } finally {
15759
- pending.delete(apiUrl);
15760
- }
15761
- }
15762
- async function ensureAnonymousCredential(apiUrl) {
15763
- const root = join8(homedir2(), ".rill", "auth-locks");
15764
- await mkdir5(root, { recursive: true, mode: 448 });
15765
- const lock = join8(root, createHash("sha256").update(apiUrl).digest("hex"));
15766
- const deadline = Date.now() + 3e4;
15767
- while (true) {
15768
- try {
15769
- await mkdir5(lock, { mode: 448 });
15770
- break;
15771
- } catch (error51) {
15772
- if (error51.code !== "EEXIST") throw error51;
15773
- const info = await stat3(lock).catch(() => null);
15774
- if (info && Date.now() - info.mtimeMs > 12e4) {
15775
- await rm4(lock, { recursive: true, force: true });
15776
- continue;
15777
- }
15778
- if (Date.now() > deadline) throw new Error("Another Rill process is setting up authorization. Retry shortly.");
15779
- await new Promise((resolve) => setTimeout(resolve, 200));
15780
- }
15781
- }
15782
- try {
15783
- return await createAnonymousCredential(apiUrl);
15784
- } finally {
15785
- await rm4(lock, { recursive: true, force: true });
15786
- }
15787
- }
15788
- async function createAnonymousCredential(apiUrl) {
15789
- const existing = await loadAgentCredential(apiUrl);
15790
- if (existing) return existing;
15791
- const stored = parseAuthorization(await loadStoredCredential(apiUrl));
15792
- const initial = stored?.registrationToken ? stored : { version: 1, anonymous: true, registrationToken: randomBytes(32).toString("hex") };
15793
- await save(apiUrl, initial);
15794
- const registered = await request2(apiUrl, "/agent/identity", { type: "anonymous", analytics_enabled: process.env.RILL_ANONYMOUS_ANALYTICS !== "0" && process.env.DO_NOT_TRACK !== "1", registration_token: initial.registrationToken, name: `${hostname3()} \xB7 ${process.platform}` });
15795
- const saved = { ...initial, registrationToken: void 0, identityAssertion: registered.identity_assertion, assertionExpires: registered.assertion_expires, claimToken: registered.claim_token };
15796
- await save(apiUrl, saved);
15797
- process.stderr.write("Recording anonymously: three recordings, up to five minutes each. Recordings and share links expire after seven days. Run rill connect --email <your-email> to keep them.\n");
15798
- return await loadAgentCredential(apiUrl);
15799
- }
15800
- async function connectAgent(apiUrl, email3) {
15801
- if (process.env.RILL_TOKEN) throw new Error("Unset RILL_TOKEN before connecting a local identity.");
15802
- let saved = parseAuthorization(await loadStoredCredential(apiUrl));
15803
- let claim;
15804
- if (saved?.claimToken && saved.anonymous) {
15805
- try {
15806
- const result = await request2(apiUrl, "/agent/identity/claim", { claim_token: saved.claimToken, email: email3 });
15807
- claim = result.claim_attempt;
15808
- } catch (error51) {
15809
- if (!(error51 instanceof AgentAuthError) || error51.code !== "claim_expired") throw error51;
15810
- }
15811
- }
15812
- if (!claim) {
15813
- await save(apiUrl, saved ?? { version: 1, anonymous: true });
15814
- const result = await request2(apiUrl, "/agent/identity", { type: "service_auth", login_hint: email3, name: `${hostname3()} \xB7 ${process.platform}` });
15815
- saved = { version: 1, anonymous: true, claimToken: result.claim_token };
15816
- await save(apiUrl, saved);
15817
- claim = result.claim;
15818
- }
15819
- process.stderr.write(`Open ${claim.verification_uri}
15820
- Sign in as ${email3} and enter ${claim.user_code}. Approve only the request you started.
15821
- `);
15822
- const deadline = Date.now() + claim.expires_in * 1e3;
15823
- let interval = claim.interval;
15824
- while (Date.now() < deadline) {
15825
- await new Promise((resolve) => setTimeout(resolve, interval * 1e3));
15826
- try {
15827
- const tokens = await request2(apiUrl, "/oauth2/token", { grant_type: CLAIM_GRANT, claim_token: saved.claimToken }, true);
15828
- const connected = withTokens({ version: 1, anonymous: false }, tokens);
15829
- await save(apiUrl, connected);
15830
- return { status: "authenticated", expiresAt: connected.assertionExpires, apiUrl };
15831
- } catch (error51) {
15832
- if (error51 instanceof AgentAuthError && error51.code === "expired_token") throw new AgentAuthError("expired_token", "The approval attempt expired. Run rill connect again to request a fresh code using the same saved claim token.");
15833
- if (error51 instanceof AgentAuthError && error51.code === "authorization_pending") continue;
15834
- if (error51 instanceof AgentAuthError && error51.code === "slow_down") {
15835
- interval += 5;
15836
- continue;
15837
- }
15838
- throw error51;
15839
- }
15840
- }
15841
- throw new AgentAuthError("expired_token", "Connection request expired. Run rill connect again to restart the approval attempt using the same saved claim token.");
15842
- }
15843
-
15844
- // src/keychain.ts
15845
- import { spawn as spawn4 } from "child_process";
15846
- var service = "rill";
15847
- async function saveCredential(apiUrl, token) {
15848
- if (process.platform === "darwin") {
15849
- await run("security", ["add-generic-password", "-U", "-a", apiUrl, "-s", service, "-w", token]);
15850
- return;
15851
- }
15852
- if (process.platform === "linux") {
15853
- await run("secret-tool", ["store", "--label=Rill agent credential", "service", service, "api-url", apiUrl], token);
15854
- return;
15855
- }
15856
- throw new Error("OS keychain storage is not supported on this platform yet. Use RILL_TOKEN.");
15857
- }
15858
- function loadCredential(apiUrl) {
15859
- return loadAgentCredential(apiUrl);
15860
- }
15861
- async function loadStoredCredential(apiUrl) {
15862
- try {
15863
- if (process.platform === "darwin") return (await run("security", ["find-generic-password", "-a", apiUrl, "-s", service, "-w"])).trim() || null;
15864
- if (process.platform === "linux") return (await run("secret-tool", ["lookup", "service", service, "api-url", apiUrl])).trim() || null;
15865
- } catch (error51) {
15866
- const code = error51.exitCode;
15867
- if (process.platform === "darwin" && code === 44) return null;
15868
- if (process.platform === "linux" && code === 1 && !error51.message.trim()) return null;
15869
- throw new Error("Cannot read the OS credential store. Unlock your keychain or enable Secret Service; your identity has not been replaced.");
15870
- }
15871
- throw new Error("Local authorization requires macOS Keychain or Linux Secret Service.");
15872
- }
15873
- function run(command, args, input) {
15874
- return new Promise((resolve, reject) => {
15875
- const child = spawn4(command, args, { stdio: ["pipe", "pipe", "pipe"] });
15876
- const stdout = [];
15877
- const stderr = [];
15878
- child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
15879
- child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk)));
15880
- child.once("error", reject);
15881
- child.once("exit", (code) => code === 0 ? resolve(Buffer.concat(stdout).toString("utf8")) : reject(Object.assign(new Error(Buffer.concat(stderr).toString("utf8").trim()), { exitCode: code })));
15882
- if (input) child.stdin.end(input);
15883
- else child.stdin.end();
15884
- });
15885
- }
15886
-
15887
15905
  // src/output.ts
15888
15906
  function emit(value, pretty = false) {
15889
15907
  process.stdout.write(`${JSON.stringify(value, null, pretty ? 2 : void 0)}
@@ -16187,6 +16205,12 @@ async function authorize(quiet = false) {
16187
16205
  }
16188
16206
  throw new Error("Device authorization expired.");
16189
16207
  }
16208
+ program.command("whoami").description("Show the current verified agent identity without starting sign-in.").action(async () => {
16209
+ const options = program.opts();
16210
+ const identity = await whoami(options.apiUrl);
16211
+ emit(identity, options.pretty);
16212
+ if (!identity.authenticated) process.exitCode = 1;
16213
+ });
16190
16214
  program.command("connect").description("Connect this agent once, with renewable account access.").requiredOption("--email <email>", "Email of the Rill account approving access").action(async (input) => {
16191
16215
  const options = program.opts();
16192
16216
  emit({ schemaVersion: 1, ...await connectAgent(options.apiUrl, input.email) }, options.pretty);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runalabs/rill-cli",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Record, inspect, and share agent-controlled browser runs with Rill.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {