glitchgrab 1.48.1 → 1.50.0

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 CHANGED
@@ -886,7 +886,63 @@ interface GuideReadOptions {
886
886
  }
887
887
  ```
888
888
 
889
- Never pass the `ggw_` guides write key here for a public page — it reads drafts too.
889
+ Never pass the `ggc_` server key here for a public page — it reads drafts too, and client calls.
890
+
891
+ ## How do I show recorded client calls in my admin panel?
892
+
893
+ On a call page in Glitchgrab, press **share with client**, then issue a **calls
894
+ key** (`ggc_…`) on API Tokens → Server keys. Put it in your server env as `GLITCHGRAB_CALLS_KEY` — never
895
+ a `NEXT_PUBLIC_` variable, because it plays and deletes calls. Your admin panel
896
+ then reads only the calls you shared:
897
+
898
+ ```tsx
899
+ // app/admin/calls/[id]/page.tsx — server component, behind your admin auth
900
+ import { notFound } from "next/navigation";
901
+ import { getCall } from "glitchgrab/server";
902
+
903
+ export default async function CallPage({ params }: { params: Promise<{ id: string }> }) {
904
+ const { id } = await params;
905
+ const call = await getCall(id);
906
+ if (!call) notFound();
907
+
908
+ return (
909
+ <article>
910
+ <h1>{call.title}</h1>
911
+ {call.audio.map((a) => (
912
+ <audio key={a.track} controls src={a.url} />
913
+ ))}
914
+ <p>{call.summary?.overview}</p>
915
+ <pre>{call.transcript}</pre>
916
+ </article>
917
+ );
918
+ }
919
+ ```
920
+
921
+ - **Shared calls only.** Everything else on the project — internal calls,
922
+ prospects — is a 404 to this key.
923
+ - **Links expire after 15 minutes** (`linksExpireInSeconds`). Read the call when
924
+ the page renders; never store `url`s. Nothing is cached for the same reason.
925
+ - **Audio tracks:** a bot recording has one (`call`). An extension recording has
926
+ two — `client` (everyone else on the call) and `recorder` (your side).
927
+ - **Match a call to a customer** by `participants[].email`.
928
+ - **Screenshots come a page at a time** — `getCall(id, { framesOffset: 50, framesLimit: 50 })`;
929
+ `frames.total` says how many exist.
930
+ - **`deleteCall(id)` is permanent** — recording, screenshots and transcript. It
931
+ resolves `{ deleted: false, error }` rather than throwing, e.g. while the bot
932
+ is still in that call.
933
+ - **Never throws.** `listCalls()` → `[]`, `getCall()` → `null` on any failure.
934
+
935
+ ```ts
936
+ listCalls(options?: { limit?: number; before?: string } & CallsOptions): Promise<CallListItem[]>
937
+ getCall(id: string, options?: { framesOffset?: number; framesLimit?: number } & CallsOptions): Promise<Call | null>
938
+ deleteCall(id: string, options?: CallsOptions): Promise<{ deleted: true } | { deleted: false; error: string }>
939
+
940
+ interface CallsOptions {
941
+ key?: string; // default: GLITCHGRAB_CALLS_KEY
942
+ baseUrl?: string; // default: GLITCHGRAB_BASE_URL, then https://glitchgrab.dev
943
+ timeoutMs?: number; // default 5000
944
+ }
945
+ ```
890
946
 
891
947
  ## What configuration options are available?
892
948
 
package/dist/server.d.mts CHANGED
@@ -136,7 +136,7 @@ interface GuideSummary {
136
136
  category: string | null;
137
137
  /** The order set in Glitchgrab. The list already arrives sorted by it. */
138
138
  orderIndex: number;
139
- /** Always `"PUBLISHED"` with a `gg_` token; only a `ggw_` write key sees drafts. */
139
+ /** Always `"PUBLISHED"` with a `gg_` token; only the project's `ggc_` server key sees drafts. */
140
140
  status: "DRAFT" | "PUBLISHED";
141
141
  /** When someone last re-checked the steps against the product. */
142
142
  lastVerifiedAt: string | null;
@@ -170,7 +170,7 @@ interface GuideReadOptions {
170
170
  * then `GLITCHGRAB_TOKEN`, then `NEXT_PUBLIC_GLITCHGRAB_TOKEN` — the token is
171
171
  * public, so the one your provider already uses is fine.
172
172
  *
173
- * Never pass the `ggw_` guides write key for a public page: it reads drafts too.
173
+ * Never pass the `ggc_` server key for a public page: it reads drafts too, and client calls.
174
174
  */
175
175
  token?: string;
176
176
  /** Override the API host. Falls back to `GLITCHGRAB_BASE_URL`, then `https://glitchgrab.dev`. */
@@ -209,6 +209,131 @@ declare function listGuides(options?: GuideReadOptions): Promise<GuideSummary[]>
209
209
  * `notFound()` on it. Never throws.
210
210
  */
211
211
  declare function getGuide(slug: string, options?: GuideReadOptions): Promise<Guide | null>;
212
+ /** Someone on the call, as the recorder saw them. */
213
+ interface CallParticipant {
214
+ name: string | null;
215
+ email: string | null;
216
+ }
217
+ /** One shared call as the list returns it — no transcript, no links. */
218
+ interface CallListItem {
219
+ id: string;
220
+ title: string | null;
221
+ startsAt: string | null;
222
+ endsAt: string | null;
223
+ durationSec: number | null;
224
+ /** `"bot"` (joined the Meet) or `"extension"` (recorded from someone's Chrome). */
225
+ recorder: string | null;
226
+ /** Match a call to your customer by these emails. */
227
+ participants: CallParticipant[];
228
+ hasRecording: boolean;
229
+ /** `IDLE`, `RUNNING`, `DONE` or `FAILED`. */
230
+ transcriptStatus: string;
231
+ /** One line on what the call was about, once Glitchgrab has read it. */
232
+ overview: string | null;
233
+ createdAt: string;
234
+ }
235
+ interface CallAudio {
236
+ /**
237
+ * `call` — a bot recording's single mixed track. An extension recording has
238
+ * two instead: `client` (everyone else on the call) and `recorder` (the
239
+ * person who recorded it).
240
+ */
241
+ track: "call" | "client" | "recorder";
242
+ /** Plays for {@link Call.linksExpireInSeconds}. */
243
+ url: string;
244
+ }
245
+ interface CallMoment {
246
+ /** Milliseconds into the call — seek the audio here. */
247
+ tMs: number | null;
248
+ speaker: string | null;
249
+ text: string;
250
+ }
251
+ /** One shared call in full. */
252
+ interface Call extends CallListItem {
253
+ meetUrl: string | null;
254
+ audio: CallAudio[];
255
+ /** Audio and screenshot links stop working after this many seconds. Read the call per render; never store them. */
256
+ linksExpireInSeconds: number;
257
+ transcript: string | null;
258
+ summary: {
259
+ overview: string;
260
+ moments: CallMoment[];
261
+ } | null;
262
+ /** Screenshots grabbed during the call, a page at a time. `tMs` is on the transcript's clock. */
263
+ frames: {
264
+ total: number;
265
+ offset: number;
266
+ limit: number;
267
+ items: {
268
+ tMs: number;
269
+ url: string;
270
+ }[];
271
+ };
272
+ }
273
+ /** Options for {@link listCalls}, {@link getCall} and {@link deleteCall}. */
274
+ interface CallsOptions {
275
+ /**
276
+ * The project's server key (`ggc_…`, API Tokens → Server keys). Falls back to `GLITCHGRAB_CALLS_KEY`.
277
+ * Server only — never a `NEXT_PUBLIC_` variable: it plays and deletes calls.
278
+ */
279
+ key?: string;
280
+ /** Override the API host. Falls back to `GLITCHGRAB_BASE_URL`, then `https://glitchgrab.dev`. */
281
+ baseUrl?: string;
282
+ /** Give up after this many ms. Default 5000. */
283
+ timeoutMs?: number;
284
+ }
285
+ interface ListCallsOptions extends CallsOptions {
286
+ /** Default 20, max 100. */
287
+ limit?: number;
288
+ /** ISO date: only calls recorded before it. Pass the last call's `createdAt` for the next page. */
289
+ before?: string;
290
+ }
291
+ interface GetCallOptions extends CallsOptions {
292
+ /** First screenshot to return. Default 0. */
293
+ framesOffset?: number;
294
+ /** Screenshots per page. Default 50, max 200, `0` for none. */
295
+ framesLimit?: number;
296
+ }
297
+ type DeleteCallResult = {
298
+ deleted: true;
299
+ } | {
300
+ deleted: false;
301
+ error: string;
302
+ };
303
+ /**
304
+ * Calls shared with the client on the key's project, newest first. No links —
305
+ * open one with {@link getCall}.
306
+ *
307
+ * ```ts
308
+ * // app/admin/calls/page.tsx — server component
309
+ * import { listCalls } from "glitchgrab/server";
310
+ *
311
+ * export default async function CallsPage() {
312
+ * const calls = await listCalls(); // GLITCHGRAB_CALLS_KEY=ggc_…
313
+ * return calls.map((c) => <a key={c.id} href={`/admin/calls/${c.id}`}>{c.title}</a>);
314
+ * }
315
+ * ```
316
+ *
317
+ * Returns `[]` on any failure. Never throws.
318
+ */
319
+ declare function listCalls(options?: ListCallsOptions): Promise<CallListItem[]>;
320
+ /**
321
+ * One shared call: audio links, transcript, summary and a page of screenshots.
322
+ * Links expire after `linksExpireInSeconds` — read the call when the page
323
+ * renders and never store them.
324
+ *
325
+ * Returns `null` when the call does not exist, is not shared, and on any
326
+ * failure — call `notFound()` on it. Never throws.
327
+ */
328
+ declare function getCall(id: string, options?: GetCallOptions): Promise<Call | null>;
329
+ /**
330
+ * Permanently delete one shared call — the recording, the screenshots, the
331
+ * transcript. Cannot be undone.
332
+ *
333
+ * Resolves `{ deleted: false, error }` instead of throwing: the reason is worth
334
+ * showing ("The bot is still in this call — delete it after the call ends").
335
+ */
336
+ declare function deleteCall(id: string, options?: CallsOptions): Promise<DeleteCallResult>;
212
337
  /**
213
338
  * `JSON.stringify` that is safe inside `<script type="application/ld+json">`.
214
339
  *
@@ -229,4 +354,4 @@ declare function getGuide(slug: string, options?: GuideReadOptions): Promise<Gui
229
354
  */
230
355
  declare function escapeJsonForScript(value: unknown): string;
231
356
 
232
- export { type CaptureServerErrorsOptions, type Guide, type GuideReadOptions, type GuideSummary, ReportResult, ReportSeverity, ReportType, type ServerReportOptions, type ServerReporterConfig, type ServerReporterIdentity, type SignSessionOptions, captureServerErrors, configureServerReporter, escapeJsonForScript, getGuide, listGuides, reportServerError, resetServerReporter, signSession };
357
+ export { type Call, type CallAudio, type CallListItem, type CallMoment, type CallParticipant, type CallsOptions, type CaptureServerErrorsOptions, type DeleteCallResult, type GetCallOptions, type Guide, type GuideReadOptions, type GuideSummary, type ListCallsOptions, ReportResult, ReportSeverity, ReportType, type ServerReportOptions, type ServerReporterConfig, type ServerReporterIdentity, type SignSessionOptions, captureServerErrors, configureServerReporter, deleteCall, escapeJsonForScript, getCall, getGuide, listCalls, listGuides, reportServerError, resetServerReporter, signSession };
package/dist/server.d.ts CHANGED
@@ -136,7 +136,7 @@ interface GuideSummary {
136
136
  category: string | null;
137
137
  /** The order set in Glitchgrab. The list already arrives sorted by it. */
138
138
  orderIndex: number;
139
- /** Always `"PUBLISHED"` with a `gg_` token; only a `ggw_` write key sees drafts. */
139
+ /** Always `"PUBLISHED"` with a `gg_` token; only the project's `ggc_` server key sees drafts. */
140
140
  status: "DRAFT" | "PUBLISHED";
141
141
  /** When someone last re-checked the steps against the product. */
142
142
  lastVerifiedAt: string | null;
@@ -170,7 +170,7 @@ interface GuideReadOptions {
170
170
  * then `GLITCHGRAB_TOKEN`, then `NEXT_PUBLIC_GLITCHGRAB_TOKEN` — the token is
171
171
  * public, so the one your provider already uses is fine.
172
172
  *
173
- * Never pass the `ggw_` guides write key for a public page: it reads drafts too.
173
+ * Never pass the `ggc_` server key for a public page: it reads drafts too, and client calls.
174
174
  */
175
175
  token?: string;
176
176
  /** Override the API host. Falls back to `GLITCHGRAB_BASE_URL`, then `https://glitchgrab.dev`. */
@@ -209,6 +209,131 @@ declare function listGuides(options?: GuideReadOptions): Promise<GuideSummary[]>
209
209
  * `notFound()` on it. Never throws.
210
210
  */
211
211
  declare function getGuide(slug: string, options?: GuideReadOptions): Promise<Guide | null>;
212
+ /** Someone on the call, as the recorder saw them. */
213
+ interface CallParticipant {
214
+ name: string | null;
215
+ email: string | null;
216
+ }
217
+ /** One shared call as the list returns it — no transcript, no links. */
218
+ interface CallListItem {
219
+ id: string;
220
+ title: string | null;
221
+ startsAt: string | null;
222
+ endsAt: string | null;
223
+ durationSec: number | null;
224
+ /** `"bot"` (joined the Meet) or `"extension"` (recorded from someone's Chrome). */
225
+ recorder: string | null;
226
+ /** Match a call to your customer by these emails. */
227
+ participants: CallParticipant[];
228
+ hasRecording: boolean;
229
+ /** `IDLE`, `RUNNING`, `DONE` or `FAILED`. */
230
+ transcriptStatus: string;
231
+ /** One line on what the call was about, once Glitchgrab has read it. */
232
+ overview: string | null;
233
+ createdAt: string;
234
+ }
235
+ interface CallAudio {
236
+ /**
237
+ * `call` — a bot recording's single mixed track. An extension recording has
238
+ * two instead: `client` (everyone else on the call) and `recorder` (the
239
+ * person who recorded it).
240
+ */
241
+ track: "call" | "client" | "recorder";
242
+ /** Plays for {@link Call.linksExpireInSeconds}. */
243
+ url: string;
244
+ }
245
+ interface CallMoment {
246
+ /** Milliseconds into the call — seek the audio here. */
247
+ tMs: number | null;
248
+ speaker: string | null;
249
+ text: string;
250
+ }
251
+ /** One shared call in full. */
252
+ interface Call extends CallListItem {
253
+ meetUrl: string | null;
254
+ audio: CallAudio[];
255
+ /** Audio and screenshot links stop working after this many seconds. Read the call per render; never store them. */
256
+ linksExpireInSeconds: number;
257
+ transcript: string | null;
258
+ summary: {
259
+ overview: string;
260
+ moments: CallMoment[];
261
+ } | null;
262
+ /** Screenshots grabbed during the call, a page at a time. `tMs` is on the transcript's clock. */
263
+ frames: {
264
+ total: number;
265
+ offset: number;
266
+ limit: number;
267
+ items: {
268
+ tMs: number;
269
+ url: string;
270
+ }[];
271
+ };
272
+ }
273
+ /** Options for {@link listCalls}, {@link getCall} and {@link deleteCall}. */
274
+ interface CallsOptions {
275
+ /**
276
+ * The project's server key (`ggc_…`, API Tokens → Server keys). Falls back to `GLITCHGRAB_CALLS_KEY`.
277
+ * Server only — never a `NEXT_PUBLIC_` variable: it plays and deletes calls.
278
+ */
279
+ key?: string;
280
+ /** Override the API host. Falls back to `GLITCHGRAB_BASE_URL`, then `https://glitchgrab.dev`. */
281
+ baseUrl?: string;
282
+ /** Give up after this many ms. Default 5000. */
283
+ timeoutMs?: number;
284
+ }
285
+ interface ListCallsOptions extends CallsOptions {
286
+ /** Default 20, max 100. */
287
+ limit?: number;
288
+ /** ISO date: only calls recorded before it. Pass the last call's `createdAt` for the next page. */
289
+ before?: string;
290
+ }
291
+ interface GetCallOptions extends CallsOptions {
292
+ /** First screenshot to return. Default 0. */
293
+ framesOffset?: number;
294
+ /** Screenshots per page. Default 50, max 200, `0` for none. */
295
+ framesLimit?: number;
296
+ }
297
+ type DeleteCallResult = {
298
+ deleted: true;
299
+ } | {
300
+ deleted: false;
301
+ error: string;
302
+ };
303
+ /**
304
+ * Calls shared with the client on the key's project, newest first. No links —
305
+ * open one with {@link getCall}.
306
+ *
307
+ * ```ts
308
+ * // app/admin/calls/page.tsx — server component
309
+ * import { listCalls } from "glitchgrab/server";
310
+ *
311
+ * export default async function CallsPage() {
312
+ * const calls = await listCalls(); // GLITCHGRAB_CALLS_KEY=ggc_…
313
+ * return calls.map((c) => <a key={c.id} href={`/admin/calls/${c.id}`}>{c.title}</a>);
314
+ * }
315
+ * ```
316
+ *
317
+ * Returns `[]` on any failure. Never throws.
318
+ */
319
+ declare function listCalls(options?: ListCallsOptions): Promise<CallListItem[]>;
320
+ /**
321
+ * One shared call: audio links, transcript, summary and a page of screenshots.
322
+ * Links expire after `linksExpireInSeconds` — read the call when the page
323
+ * renders and never store them.
324
+ *
325
+ * Returns `null` when the call does not exist, is not shared, and on any
326
+ * failure — call `notFound()` on it. Never throws.
327
+ */
328
+ declare function getCall(id: string, options?: GetCallOptions): Promise<Call | null>;
329
+ /**
330
+ * Permanently delete one shared call — the recording, the screenshots, the
331
+ * transcript. Cannot be undone.
332
+ *
333
+ * Resolves `{ deleted: false, error }` instead of throwing: the reason is worth
334
+ * showing ("The bot is still in this call — delete it after the call ends").
335
+ */
336
+ declare function deleteCall(id: string, options?: CallsOptions): Promise<DeleteCallResult>;
212
337
  /**
213
338
  * `JSON.stringify` that is safe inside `<script type="application/ld+json">`.
214
339
  *
@@ -229,4 +354,4 @@ declare function getGuide(slug: string, options?: GuideReadOptions): Promise<Gui
229
354
  */
230
355
  declare function escapeJsonForScript(value: unknown): string;
231
356
 
232
- export { type CaptureServerErrorsOptions, type Guide, type GuideReadOptions, type GuideSummary, ReportResult, ReportSeverity, ReportType, type ServerReportOptions, type ServerReporterConfig, type ServerReporterIdentity, type SignSessionOptions, captureServerErrors, configureServerReporter, escapeJsonForScript, getGuide, listGuides, reportServerError, resetServerReporter, signSession };
357
+ export { type Call, type CallAudio, type CallListItem, type CallMoment, type CallParticipant, type CallsOptions, type CaptureServerErrorsOptions, type DeleteCallResult, type GetCallOptions, type Guide, type GuideReadOptions, type GuideSummary, type ListCallsOptions, ReportResult, ReportSeverity, ReportType, type ServerReportOptions, type ServerReporterConfig, type ServerReporterIdentity, type SignSessionOptions, captureServerErrors, configureServerReporter, deleteCall, escapeJsonForScript, getCall, getGuide, listCalls, listGuides, reportServerError, resetServerReporter, signSession };
package/dist/server.js CHANGED
@@ -22,8 +22,11 @@ var server_exports = {};
22
22
  __export(server_exports, {
23
23
  captureServerErrors: () => captureServerErrors,
24
24
  configureServerReporter: () => configureServerReporter,
25
+ deleteCall: () => deleteCall,
25
26
  escapeJsonForScript: () => escapeJsonForScript,
27
+ getCall: () => getCall,
26
28
  getGuide: () => getGuide,
29
+ listCalls: () => listCalls,
27
30
  listGuides: () => listGuides,
28
31
  reportServerError: () => reportServerError,
29
32
  resetServerReporter: () => resetServerReporter,
@@ -251,31 +254,39 @@ function signSession(options) {
251
254
  return `v1.${exp}.${sig}`;
252
255
  }
253
256
  var GUIDES_REVALIDATE_SECONDS = 300;
254
- var GUIDES_TIMEOUT_MS = 5e3;
255
- async function readGuidesApi(query, options) {
256
- var _a, _b, _c, _d, _e, _f, _g, _h;
257
- const token = (_c = (_b = (_a = options.token) != null ? _a : defaults.token) != null ? _b : readEnv("GLITCHGRAB_TOKEN")) != null ? _c : readEnv("NEXT_PUBLIC_GLITCHGRAB_TOKEN");
258
- if (!token) return null;
259
- const baseUrl = ((_f = (_e = (_d = options.baseUrl) != null ? _d : defaults.baseUrl) != null ? _e : readEnv("GLITCHGRAB_BASE_URL")) != null ? _f : DEFAULT_BASE_URL).replace(/\/+$/, "");
260
- const revalidate = typeof options.revalidate === "number" && Number.isFinite(options.revalidate) && options.revalidate >= 0 ? options.revalidate : GUIDES_REVALIDATE_SECONDS;
257
+ var SDK_API_TIMEOUT_MS = 5e3;
258
+ async function callSdkApi(path, token, options, init) {
259
+ var _a, _b, _c, _d, _e, _f;
260
+ const baseUrl = ((_c = (_b = (_a = options.baseUrl) != null ? _a : defaults.baseUrl) != null ? _b : readEnv("GLITCHGRAB_BASE_URL")) != null ? _c : DEFAULT_BASE_URL).replace(/\/+$/, "");
261
261
  const controller = typeof AbortController === "function" ? new AbortController() : void 0;
262
- const timer = controller ? setTimeout(() => controller.abort(), (_g = options.timeoutMs) != null ? _g : GUIDES_TIMEOUT_MS) : void 0;
262
+ const timer = controller ? setTimeout(() => controller.abort(), (_d = options.timeoutMs) != null ? _d : SDK_API_TIMEOUT_MS) : void 0;
263
263
  try {
264
- const init = {
264
+ const request = {
265
+ method: (_e = init.method) != null ? _e : "GET",
265
266
  headers: { Authorization: `Bearer ${token}` },
266
- next: { revalidate },
267
+ ...init.cache === "no-store" ? { cache: "no-store" } : { next: { revalidate: init.cache.revalidate } },
267
268
  ...controller ? { signal: controller.signal } : {}
268
269
  };
269
- const response = await fetch(`${baseUrl}/api/v1/sdk/guides${query}`, init);
270
- if (!response.ok) return null;
271
- const body = await response.json();
272
- return (body == null ? void 0 : body.success) === true ? (_h = body.data) != null ? _h : null : null;
270
+ const response = await fetch(`${baseUrl}${path}`, request);
271
+ const body = await response.json().catch(() => null);
272
+ if (!response.ok || (body == null ? void 0 : body.success) !== true) {
273
+ return { data: null, error: typeof (body == null ? void 0 : body.error) === "string" ? body.error : `HTTP ${response.status}` };
274
+ }
275
+ return { data: (_f = body.data) != null ? _f : null, error: null };
273
276
  } catch (e) {
274
- return null;
277
+ return { data: null, error: "Could not reach Glitchgrab" };
275
278
  } finally {
276
279
  if (timer) clearTimeout(timer);
277
280
  }
278
281
  }
282
+ async function readGuidesApi(query, options) {
283
+ var _a, _b, _c;
284
+ const token = (_c = (_b = (_a = options.token) != null ? _a : defaults.token) != null ? _b : readEnv("GLITCHGRAB_TOKEN")) != null ? _c : readEnv("NEXT_PUBLIC_GLITCHGRAB_TOKEN");
285
+ if (!token) return null;
286
+ const revalidate = typeof options.revalidate === "number" && Number.isFinite(options.revalidate) && options.revalidate >= 0 ? options.revalidate : GUIDES_REVALIDATE_SECONDS;
287
+ const result = await callSdkApi(`/api/v1/sdk/guides${query}`, token, options, { cache: { revalidate } });
288
+ return result.data;
289
+ }
279
290
  function readGuideSummary(value) {
280
291
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
281
292
  const guide = value;
@@ -320,6 +331,110 @@ async function getGuide(slug, options = {}) {
320
331
  return null;
321
332
  }
322
333
  }
334
+ var CALLS_KEY_PREFIX = "ggc_";
335
+ function callsKey(options) {
336
+ var _a;
337
+ const key = (_a = options.key) != null ? _a : readEnv("GLITCHGRAB_CALLS_KEY");
338
+ return typeof key === "string" && key.startsWith(CALLS_KEY_PREFIX) ? key : null;
339
+ }
340
+ function isRecord(value) {
341
+ return !!value && typeof value === "object" && !Array.isArray(value);
342
+ }
343
+ function stringOrNull(value) {
344
+ return typeof value === "string" ? value : null;
345
+ }
346
+ function numberOrNull(value) {
347
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
348
+ }
349
+ function readCallListItem(value) {
350
+ if (!isRecord(value) || typeof value.id !== "string") return null;
351
+ return {
352
+ id: value.id,
353
+ title: stringOrNull(value.title),
354
+ startsAt: stringOrNull(value.startsAt),
355
+ endsAt: stringOrNull(value.endsAt),
356
+ durationSec: numberOrNull(value.durationSec),
357
+ recorder: stringOrNull(value.recorder),
358
+ participants: Array.isArray(value.participants) ? value.participants.filter(isRecord).map((p) => ({ name: stringOrNull(p.name), email: stringOrNull(p.email) })) : [],
359
+ hasRecording: value.hasRecording === true,
360
+ transcriptStatus: typeof value.transcriptStatus === "string" ? value.transcriptStatus : "IDLE",
361
+ overview: stringOrNull(value.overview),
362
+ createdAt: typeof value.createdAt === "string" ? value.createdAt : ""
363
+ };
364
+ }
365
+ var AUDIO_TRACKS = /* @__PURE__ */ new Set(["call", "client", "recorder"]);
366
+ function readCall(value) {
367
+ var _a, _b, _c, _d;
368
+ const base = readCallListItem(value);
369
+ if (!base || !isRecord(value)) return null;
370
+ const rawSummary = value.summary;
371
+ const summary = isRecord(rawSummary) && typeof rawSummary.overview === "string" ? {
372
+ overview: rawSummary.overview,
373
+ moments: Array.isArray(rawSummary.moments) ? rawSummary.moments.filter(isRecord).filter((m) => typeof m.text === "string").map((m) => ({ tMs: numberOrNull(m.tMs), speaker: stringOrNull(m.speaker), text: m.text })) : []
374
+ } : null;
375
+ const frames = isRecord(value.frames) ? value.frames : {};
376
+ return {
377
+ ...base,
378
+ meetUrl: stringOrNull(value.meetUrl),
379
+ audio: Array.isArray(value.audio) ? value.audio.filter(isRecord).filter((a) => typeof a.url === "string" && typeof a.track === "string" && AUDIO_TRACKS.has(a.track)).map((a) => ({ track: a.track, url: a.url })) : [],
380
+ linksExpireInSeconds: (_a = numberOrNull(value.linksExpireInSeconds)) != null ? _a : 0,
381
+ transcript: stringOrNull(value.transcript),
382
+ summary,
383
+ frames: {
384
+ total: (_b = numberOrNull(frames.total)) != null ? _b : 0,
385
+ offset: (_c = numberOrNull(frames.offset)) != null ? _c : 0,
386
+ limit: (_d = numberOrNull(frames.limit)) != null ? _d : 0,
387
+ items: Array.isArray(frames.items) ? frames.items.filter(isRecord).filter((f) => typeof f.url === "string" && typeof f.tMs === "number").map((f) => ({ tMs: f.tMs, url: f.url })) : []
388
+ }
389
+ };
390
+ }
391
+ function setNumber(query, name, value) {
392
+ if (typeof value === "number" && Number.isFinite(value)) query.set(name, String(Math.floor(value)));
393
+ }
394
+ async function listCalls(options = {}) {
395
+ try {
396
+ const key = callsKey(options);
397
+ if (!key) return [];
398
+ const query = new URLSearchParams();
399
+ setNumber(query, "limit", options.limit);
400
+ if (typeof options.before === "string" && options.before) query.set("before", options.before);
401
+ const qs = query.toString();
402
+ const { data } = await callSdkApi(`/api/v1/sdk/calls${qs ? `?${qs}` : ""}`, key, options, { cache: "no-store" });
403
+ if (!Array.isArray(data)) return [];
404
+ return data.map(readCallListItem).filter((call) => call !== null);
405
+ } catch (e) {
406
+ return [];
407
+ }
408
+ }
409
+ async function getCall(id, options = {}) {
410
+ try {
411
+ const wanted = typeof id === "string" ? id.trim() : "";
412
+ const key = callsKey(options);
413
+ if (!wanted || !key) return null;
414
+ const query = new URLSearchParams({ id: wanted });
415
+ setNumber(query, "framesOffset", options.framesOffset);
416
+ setNumber(query, "framesLimit", options.framesLimit);
417
+ const { data } = await callSdkApi(`/api/v1/sdk/calls?${query.toString()}`, key, options, { cache: "no-store" });
418
+ return readCall(data);
419
+ } catch (e) {
420
+ return null;
421
+ }
422
+ }
423
+ async function deleteCall(id, options = {}) {
424
+ try {
425
+ const wanted = typeof id === "string" ? id.trim() : "";
426
+ if (!wanted) return { deleted: false, error: "No call id" };
427
+ const key = callsKey(options);
428
+ if (!key) return { deleted: false, error: "No server key \u2014 set GLITCHGRAB_CALLS_KEY (ggc_\u2026)" };
429
+ const { error } = await callSdkApi(`/api/v1/sdk/calls?id=${encodeURIComponent(wanted)}`, key, options, {
430
+ method: "DELETE",
431
+ cache: "no-store"
432
+ });
433
+ return error === null ? { deleted: true } : { deleted: false, error };
434
+ } catch (e) {
435
+ return { deleted: false, error: "Could not delete the call" };
436
+ }
437
+ }
323
438
  function escapeJsonForScript(value) {
324
439
  try {
325
440
  const json = JSON.stringify(value);
@@ -333,8 +448,11 @@ function escapeJsonForScript(value) {
333
448
  0 && (module.exports = {
334
449
  captureServerErrors,
335
450
  configureServerReporter,
451
+ deleteCall,
336
452
  escapeJsonForScript,
453
+ getCall,
337
454
  getGuide,
455
+ listCalls,
338
456
  listGuides,
339
457
  reportServerError,
340
458
  resetServerReporter,