@buildinternet/releases-api-types 0.1.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.
Files changed (3) hide show
  1. package/README.md +38 -0
  2. package/package.json +22 -0
  3. package/src/api-types.ts +707 -0
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # @buildinternet/releases-api-types
2
+
3
+ Wire protocol types for the [Releases](https://releases.sh) registry API.
4
+
5
+ Pure TypeScript types — zero runtime dependencies — describing request/response
6
+ shapes for the public HTTP API served at `api.releases.sh`. Consumed by the web
7
+ frontend, the remote MCP server, and the [Releases CLI](https://github.com/buildinternet/releases-cli).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @buildinternet/releases-api-types
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import type { OrgListItem } from "@buildinternet/releases-api-types";
19
+
20
+ async function listOrgs(): Promise<OrgListItem[]> {
21
+ const res = await fetch("https://api.releases.sh/v1/orgs");
22
+ return (await res.json()) as OrgListItem[];
23
+ }
24
+ ```
25
+
26
+ Some list endpoints wrap results in a `ListResponse<T>` envelope (`{ items, pagination }`) — that contract lives in
27
+ `@buildinternet/releases-core/cli-contracts`. Check the route docs at [releases.sh/docs/api](https://releases.sh/docs/api) for the exact shape per endpoint.
28
+
29
+ ## Versioning
30
+
31
+ This package follows semantic versioning against the `api.releases.sh` wire
32
+ contract. Additive changes (new optional fields, new types) bump the minor
33
+ version. Renames and removals get a deprecation cycle — the old name remains as
34
+ a deprecated alias for one minor version before being removed in the next major.
35
+
36
+ ## License
37
+
38
+ MIT
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@buildinternet/releases-api-types",
3
+ "version": "0.1.0",
4
+ "description": "Wire protocol — request/response types for the Releases registry API. Shared by web, workers, and clients.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/buildinternet/releases.git",
10
+ "directory": "packages/api-types"
11
+ },
12
+ "exports": {
13
+ ".": "./src/api-types.ts"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "README.md"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ }
22
+ }
@@ -0,0 +1,707 @@
1
+ /**
2
+ * Shared API response types.
3
+ *
4
+ * This is the single source of truth for all API response shapes.
5
+ * Consumed by: web frontend, CLI client, and (optionally) API worker routes.
6
+ */
7
+
8
+ // ── Media ──
9
+
10
+ export interface MediaItem {
11
+ type: "image" | "video" | "gif";
12
+ url: string;
13
+ alt?: string;
14
+ r2Url?: string;
15
+ }
16
+
17
+ // ── Stats ──
18
+
19
+ export interface Stats {
20
+ orgs: number;
21
+ sources: number;
22
+ releases: number;
23
+ products: number;
24
+ }
25
+
26
+ // ── Sitemap (bulk URL emission) ──
27
+
28
+ export interface SitemapPayload {
29
+ orgs: Array<{ slug: string; lastActivity: string | null }>;
30
+ sources: Array<{ orgSlug: string; slug: string; latestDate: string | null }>;
31
+ products: Array<{ orgSlug: string; slug: string }>;
32
+ }
33
+
34
+ // ── Organizations ──
35
+
36
+ export interface OrgListItem {
37
+ slug: string;
38
+ name: string;
39
+ domain: string | null;
40
+ avatarUrl: string | null;
41
+ githubHandle: string | null;
42
+ sourceCount: number;
43
+ releaseCount: number;
44
+ recentReleaseCount: number;
45
+ lastActivity: string | null;
46
+ topProducts: string[];
47
+ sparkline: number[];
48
+ }
49
+
50
+ export interface OrgDetail {
51
+ id?: string;
52
+ slug: string;
53
+ name: string;
54
+ domain: string | null;
55
+ description?: string | null;
56
+ category?: string | null;
57
+ avatarUrl: string | null;
58
+ tags?: string[];
59
+ sourceCount: number;
60
+ releaseCount: number;
61
+ releasesLast30Days: number;
62
+ avgReleasesPerWeek: number;
63
+ lastFetchedAt: string | null;
64
+ lastPolledAt: string | null;
65
+ trackingSince: string;
66
+ aliases?: string[];
67
+ accounts: { platform: string; handle: string }[];
68
+ products: Array<{
69
+ id: string;
70
+ slug: string;
71
+ name: string;
72
+ url: string | null;
73
+ description: string | null;
74
+ sourceCount: number;
75
+ }>;
76
+ sources: SourceListItem[];
77
+ overview?: OverviewPageItem | null;
78
+ playbook?: { scope: "playbook"; content: string; updatedAt: string } | null;
79
+ }
80
+
81
+ // ── Sources ──
82
+
83
+ export interface SourceListItem {
84
+ slug: string;
85
+ name: string;
86
+ type: string;
87
+ url?: string;
88
+ orgSlug?: string | null;
89
+ releaseCount: number;
90
+ latestVersion: string | null;
91
+ latestDate: string | null;
92
+ latestAddedAt?: string | null;
93
+ isPrimary?: boolean;
94
+ isHidden?: boolean;
95
+ fetchPriority?: "normal" | "low" | "paused" | null;
96
+ lastFetchedAt?: string | null;
97
+ lastPolledAt?: string | null;
98
+ changeDetectedAt?: string | null;
99
+ consecutiveNoChange?: number | null;
100
+ consecutiveErrors?: number | null;
101
+ nextFetchAfter?: string | null;
102
+ medianGapDays?: number | null;
103
+ lastRetieredAt?: string | null;
104
+ metadata?: string | null;
105
+ productName?: string | null;
106
+ productSlug?: string | null;
107
+ }
108
+
109
+ /**
110
+ * Canonical shape returned by GET /v1/sources (list) and used by both
111
+ * local-mode queries and the remote API client. Superset of SourceListItem
112
+ * with required fields (id, orgName, orgSlug) that the CLI `list` command needs.
113
+ */
114
+ export interface SourceWithOrg {
115
+ id: string;
116
+ name: string;
117
+ slug: string;
118
+ type: string;
119
+ url: string;
120
+ orgName: string | null;
121
+ orgSlug: string | null;
122
+ productName: string | null;
123
+ productSlug: string | null;
124
+ isPrimary: boolean;
125
+ isHidden: boolean | null;
126
+ metadata: string | null;
127
+ releaseCount: number;
128
+ latestVersion: string | null;
129
+ latestDate: string | null;
130
+ lastFetchedAt: string | null;
131
+ lastPolledAt: string | null;
132
+ fetchPriority: string | null;
133
+ changeDetectedAt: string | null;
134
+ consecutiveNoChange: number | null;
135
+ consecutiveErrors: number | null;
136
+ nextFetchAfter: string | null;
137
+ medianGapDays: number | null;
138
+ lastRetieredAt: string | null;
139
+ }
140
+
141
+ /** Fields accepted by PATCH /v1/sources/:slug. */
142
+ export interface SourcePatchInput {
143
+ name?: string;
144
+ url?: string;
145
+ type?: string;
146
+ slug?: string;
147
+ metadata?: string;
148
+ orgId?: string | null;
149
+ productId?: string | null;
150
+ lastFetchedAt?: string | null;
151
+ lastContentHash?: string | null;
152
+ fetchPriority?: string;
153
+ consecutiveNoChange?: number;
154
+ consecutiveErrors?: number;
155
+ nextFetchAfter?: string | null;
156
+ isPrimary?: boolean;
157
+ isHidden?: boolean;
158
+ changeDetectedAt?: string | null;
159
+ lastPolledAt?: string | null;
160
+ }
161
+
162
+ /** Lightweight summary of a changelog file — used for the file index. */
163
+ export interface ChangelogFileSummary {
164
+ path: string;
165
+ filename: string;
166
+ url: string;
167
+ bytes: number;
168
+ fetchedAt: string;
169
+ }
170
+
171
+ export interface SourceChangelogResponse {
172
+ path: string;
173
+ filename: string;
174
+ url: string;
175
+ rawUrl: string;
176
+ content: string;
177
+ bytes: number;
178
+ fetchedAt: string;
179
+ /** Character offset of the first character in `content` within the full file. */
180
+ offset: number;
181
+ /** The limit (in chars) that was applied to produce this slice. */
182
+ limit: number;
183
+ /** Next offset to request for the next slice, or null if `content` is the tail. */
184
+ nextOffset: number | null;
185
+ /** Total length of the full file in characters. */
186
+ totalChars: number;
187
+ /** Requested token budget when in token mode (cl100k_base). */
188
+ tokens?: number;
189
+ /** Encoded token count of the returned `content`. Set in token mode. */
190
+ sliceTokens?: number;
191
+ /** Full-file token count (cl100k_base). Always populated. */
192
+ totalTokens: number;
193
+ /** True when the upstream file exceeded the 1MB cap and content was sliced. */
194
+ truncated: boolean;
195
+ /** Byte offset where the file was truncated, or null when not truncated. */
196
+ truncatedAt: number | null;
197
+ /**
198
+ * Index of every changelog file tracked for this source (root plus any
199
+ * discovered per-package files). Always present even for single-file
200
+ * sources so clients can lazily render a file picker.
201
+ */
202
+ files: ChangelogFileSummary[];
203
+ }
204
+
205
+ export interface SourceDetail {
206
+ slug: string;
207
+ name: string;
208
+ type: string;
209
+ url: string;
210
+ changelogUrl?: string | null;
211
+ hasChangelogFile?: boolean;
212
+ org: { slug: string; name: string } | null;
213
+ releaseCount: number;
214
+ releasesLast30Days: number;
215
+ avgReleasesPerWeek: number;
216
+ latestVersion: string | null;
217
+ latestDate: string | null;
218
+ lastFetchedAt: string | null;
219
+ lastPolledAt: string | null;
220
+ trackingSince: string;
221
+ releases: ReleaseItem[];
222
+ pagination: { page: number; pageSize: number; totalPages: number; totalItems: number };
223
+ summaries: {
224
+ rolling: ReleaseSummaryItem | null;
225
+ monthly: ReleaseSummaryItem[];
226
+ };
227
+ }
228
+
229
+ // ── Releases ──
230
+
231
+ export interface ReleaseItem {
232
+ id?: string;
233
+ version: string | null;
234
+ title: string;
235
+ summary: string;
236
+ content?: string;
237
+ publishedAt: string | null;
238
+ url: string | null;
239
+ media?: MediaItem[];
240
+ }
241
+
242
+ export interface ReleaseDetail {
243
+ id: string;
244
+ sourceId: string;
245
+ version: string | null;
246
+ title: string;
247
+ content: string;
248
+ contentSummary: string | null;
249
+ url: string | null;
250
+ media: MediaItem[];
251
+ publishedAt: string | null;
252
+ fetchedAt: string;
253
+ sourceName: string;
254
+ sourceSlug: string;
255
+ sourceType: string;
256
+ org: { slug: string; name: string } | null;
257
+ }
258
+
259
+ export interface ReleaseCoverageRow {
260
+ coverageId: string;
261
+ canonicalId: string;
262
+ reason: string | null;
263
+ decidedBy: string;
264
+ decidedAt: string;
265
+ }
266
+
267
+ export type ReleaseCoverageResponse =
268
+ | { role: "standalone"; canonical: null; covers: [] }
269
+ | { role: "coverage"; canonical: ReleaseCoverageRow; covers: [] }
270
+ | { role: "canonical"; canonical: null; covers: ReleaseCoverageRow[] };
271
+
272
+ export interface ReleaseSummaryItem {
273
+ year?: number | null;
274
+ month?: number | null;
275
+ windowDays?: number | null;
276
+ summary: string;
277
+ releaseCount: number;
278
+ generatedAt: string;
279
+ }
280
+
281
+ // ── Search ──
282
+
283
+ export interface SearchOrgHit {
284
+ slug: string;
285
+ name: string;
286
+ domain: string | null;
287
+ avatarUrl: string | null;
288
+ category: string | null;
289
+ }
290
+
291
+ /**
292
+ * Unified catalog entry — either a product row or a standalone source
293
+ * presented as product-shaped. `kind` routes clicks to the right URL but
294
+ * the two forms are otherwise interchangeable for display. `kind` (not
295
+ * `type`) because source rows already carry `type: github|scrape|feed|agent`
296
+ * on the wire.
297
+ */
298
+ export interface SearchCatalogHit {
299
+ slug: string;
300
+ name: string;
301
+ orgSlug: string | null;
302
+ orgName: string | null;
303
+ category: string | null;
304
+ kind: "product" | "source";
305
+ sourceSlug?: string;
306
+ sourceType?: string;
307
+ }
308
+
309
+ /** @deprecated Use SearchCatalogHit. */
310
+ export type SearchProductHit = SearchCatalogHit;
311
+
312
+ export interface SearchSourceHit {
313
+ slug: string;
314
+ name: string;
315
+ type: string;
316
+ orgSlug: string | null;
317
+ orgName: string | null;
318
+ productSlug: string | null;
319
+ }
320
+
321
+ export interface RawSourceHit extends SearchSourceHit {
322
+ productName?: string;
323
+ productCategory?: string;
324
+ }
325
+
326
+ /**
327
+ * Fold source hits into the catalog list. Sources under a matched product
328
+ * are dropped (product wins); orphan sources become `kind: "source"`.
329
+ */
330
+ export function foldSourcesIntoCatalog(
331
+ existingProducts: SearchCatalogHit[],
332
+ rawSources: RawSourceHit[],
333
+ ): SearchCatalogHit[] {
334
+ const result: SearchCatalogHit[] = existingProducts.map((p) => ({ ...p, kind: "product" }));
335
+ const seen = new Set(result.map((p) => p.slug));
336
+ for (const s of rawSources) {
337
+ if (s.productSlug) {
338
+ if (seen.has(s.productSlug)) continue;
339
+ result.push({
340
+ slug: s.productSlug,
341
+ name: s.productName ?? s.name,
342
+ orgSlug: s.orgSlug,
343
+ orgName: s.orgName,
344
+ category: s.productCategory ?? null,
345
+ kind: "product",
346
+ });
347
+ seen.add(s.productSlug);
348
+ } else {
349
+ result.push({
350
+ slug: s.slug,
351
+ name: s.name,
352
+ orgSlug: s.orgSlug,
353
+ orgName: s.orgName,
354
+ category: null,
355
+ kind: "source",
356
+ sourceSlug: s.slug,
357
+ sourceType: s.type,
358
+ });
359
+ }
360
+ }
361
+ return result;
362
+ }
363
+
364
+ /** @deprecated Use foldSourcesIntoCatalog. */
365
+ export const foldSourcesIntoProducts = foldSourcesIntoCatalog;
366
+
367
+ export interface SearchReleaseHit {
368
+ id: string;
369
+ sourceSlug: string;
370
+ sourceName: string;
371
+ /** Source type (github, scrape, feed, agent) — drives the byline icon. */
372
+ sourceType?: string;
373
+ orgSlug: string | null;
374
+ /** Owning organization's display name — byline disambiguation. */
375
+ orgName?: string | null;
376
+ version: string | null;
377
+ title: string;
378
+ summary: string;
379
+ /**
380
+ * Full release body, media URLs hydrated through the MEDIA_ORIGIN proxy.
381
+ * Present so the web can render the same markdown + thumbnail treatment
382
+ * as the org/source feeds instead of a plain summary snippet.
383
+ */
384
+ content?: string;
385
+ /** Release media with r2Url resolved. Undefined means "not hydrated". */
386
+ media?: MediaItem[];
387
+ publishedAt: string | null;
388
+ /**
389
+ * Hybrid fusion score. Present on hybrid/semantic responses (including
390
+ * degraded fallbacks); absent on the legacy lexical path. Clients can
391
+ * use this to interleave release and chunk hits into a single ranked list.
392
+ */
393
+ score?: number;
394
+ }
395
+
396
+ /**
397
+ * A heading-aware CHANGELOG.md slice returned by hybrid / semantic search.
398
+ * Clients can deep-link to `/source/<sourceSlug>?tab=changelog&offset=<offset>`
399
+ * to read the surrounding file content.
400
+ */
401
+ export interface SearchChunkHit {
402
+ sourceSlug: string;
403
+ sourceName: string;
404
+ orgSlug: string | null;
405
+ /** Owning organization's display name — byline disambiguation. */
406
+ orgName?: string | null;
407
+ filePath: string;
408
+ offset: number;
409
+ length: number;
410
+ heading: string | null;
411
+ snippet: string;
412
+ score: number;
413
+ }
414
+
415
+ export interface UnifiedSearchResponse {
416
+ query: string;
417
+ orgs: SearchOrgHit[];
418
+ /** Products and standalone sources folded into a single list. */
419
+ catalog: SearchCatalogHit[];
420
+ /** @deprecated Use `catalog`. Kept as an alias populated with the same array. */
421
+ products: SearchCatalogHit[];
422
+ sources: SearchSourceHit[];
423
+ releases: SearchReleaseHit[];
424
+ /** Present on hybrid/semantic responses; omitted on pure lexical. */
425
+ chunks?: SearchChunkHit[];
426
+ /** Mode actually used by the server. Present for semantic/hybrid responses. */
427
+ mode?: "lexical" | "semantic" | "hybrid";
428
+ /** True when a hybrid/semantic request fell back to lexical. */
429
+ degraded?: boolean;
430
+ /** Human-readable reason for degradation (e.g., missing Vectorize binding). */
431
+ degradedReason?: string;
432
+ }
433
+
434
+ // ── Overview Pages ──
435
+
436
+ export interface OverviewPageItem {
437
+ scope: "org" | "product";
438
+ orgSlug?: string | null;
439
+ productSlug?: string | null;
440
+ content: string;
441
+ releaseCount: number;
442
+ lastContributingReleaseAt: string | null;
443
+ generatedAt: string;
444
+ updatedAt: string;
445
+ }
446
+
447
+ /** @deprecated Use OverviewPageItem */
448
+ export type KnowledgePageItem = OverviewPageItem;
449
+
450
+ /** @deprecated Use UnifiedSearchResponse */
451
+ export type SearchResult = SearchReleaseHit;
452
+ /** @deprecated Use UnifiedSearchResponse */
453
+ export interface SearchResponse {
454
+ query: string;
455
+ results: SearchResult[];
456
+ }
457
+
458
+ // ── Activity ──
459
+
460
+ export interface WeeklyBucket {
461
+ weekStart: string;
462
+ count: number;
463
+ earliestVersion: string | null;
464
+ latestVersion: string | null;
465
+ }
466
+
467
+ export interface SourceActivity {
468
+ source: { slug: string; name: string; orgSlug: string | null; orgName: string | null };
469
+ range: { from: string; to: string };
470
+ weeklyBuckets: WeeklyBucket[];
471
+ }
472
+
473
+ export interface OrgActivitySource {
474
+ slug: string;
475
+ name: string;
476
+ releaseCount: number;
477
+ avgReleasesPerWeek: number;
478
+ earliestVersion: string | null;
479
+ latestVersion: string | null;
480
+ latestDate: string | null;
481
+ weeklyBuckets: WeeklyBucket[];
482
+ }
483
+
484
+ export interface OrgActivity {
485
+ org: { slug: string; name: string };
486
+ range: { from: string; to: string };
487
+ sources: OrgActivitySource[];
488
+ aggregateWeekly: Array<{ weekStart: string; count: number }>;
489
+ }
490
+
491
+ // ── Org Sparklines (per-source/product breakdown) ──
492
+
493
+ export interface OrgSparklines {
494
+ org: { slug: string; name: string };
495
+ range: { from: string; to: string };
496
+ aggregate: number[];
497
+ sources: Array<{ slug: string; name: string; sparkline: number[] }>;
498
+ products: Array<{ slug: string; name: string; sparkline: number[] }>;
499
+ }
500
+
501
+ // ── Org Heatmap ──
502
+
503
+ export interface OrgHeatmap {
504
+ org: { slug: string; name: string };
505
+ range: { from: string; to: string };
506
+ dailyCounts: Array<{ date: string; count: number }>;
507
+ total: number;
508
+ }
509
+
510
+ // ── Source Heatmap ──
511
+
512
+ export interface SourceHeatmap {
513
+ source: { slug: string; name: string };
514
+ range: { from: string; to: string };
515
+ dailyCounts: Array<{ date: string; count: number }>;
516
+ total: number;
517
+ }
518
+
519
+ // ── Org Releases ──
520
+
521
+ export interface OrgReleaseItem extends ReleaseItem {
522
+ source: { slug: string; name: string; type: string };
523
+ }
524
+
525
+ export interface OrgReleasesResponse {
526
+ releases: OrgReleaseItem[];
527
+ pagination: { nextCursor: string | null; limit: number };
528
+ }
529
+
530
+ // ── Products ──
531
+
532
+ export interface ProductListItem {
533
+ id: string;
534
+ name: string;
535
+ slug: string;
536
+ orgId: string;
537
+ url: string | null;
538
+ description: string | null;
539
+ category: string | null;
540
+ createdAt: string;
541
+ sourceCount: number;
542
+ }
543
+
544
+ export interface ProductDetail {
545
+ id: string;
546
+ name: string;
547
+ slug: string;
548
+ orgId: string;
549
+ url: string | null;
550
+ description: string | null;
551
+ category: string | null;
552
+ createdAt: string;
553
+ sources: Array<{ id: string; slug: string; name: string; type: string; url: string }>;
554
+ tags: string[];
555
+ }
556
+
557
+ export interface ProductAdoptResult {
558
+ product: ProductDetail;
559
+ sourcesMoved: number;
560
+ accountsMoved: number;
561
+ sourceOrgDeleted: string;
562
+ }
563
+
564
+ // ── Releases (enriched) ──
565
+
566
+ /** Flat release shape returned by GET /v1/releases/:id with source metadata. */
567
+ export interface ReleaseWithSource {
568
+ id: string;
569
+ sourceId: string;
570
+ version: string | null;
571
+ title: string;
572
+ content: string;
573
+ contentSummary: string | null;
574
+ url: string | null;
575
+ contentHash: string | null;
576
+ metadata: string | null;
577
+ publishedAt: string | null;
578
+ suppressed: boolean;
579
+ suppressedReason: string | null;
580
+ fetchedAt: string;
581
+ sourceName: string | null;
582
+ sourceSlug: string | null;
583
+ }
584
+
585
+ export interface LatestRelease {
586
+ id: string;
587
+ title: string;
588
+ version: string | null;
589
+ publishedAt: string | null;
590
+ sourceName: string;
591
+ sourceSlug: string;
592
+ contentSummary: string | null;
593
+ media: MediaItem[];
594
+ }
595
+
596
+ // ── Stats ──
597
+
598
+ export interface StatsSummary {
599
+ period: { days: number; cutoff: string };
600
+ totals: {
601
+ organizations: number;
602
+ sources: number;
603
+ releases: number;
604
+ releasesInPeriod: number;
605
+ };
606
+ sourceHealth: {
607
+ upToDate: number;
608
+ stale: number;
609
+ neverFetched: number;
610
+ };
611
+ sourceActivity: Array<{
612
+ sourceName: string;
613
+ sourceSlug: string;
614
+ sourceType: string;
615
+ orgName: string | null;
616
+ lastFetchedAt: string | null;
617
+ totalReleases: number;
618
+ recentReleases: number;
619
+ }>;
620
+ recentActivity: Array<{
621
+ sourceName: string;
622
+ sourceSlug: string;
623
+ orgName: string | null;
624
+ releasesFound: number;
625
+ releasesInserted: number;
626
+ totalReleases: number;
627
+ status: string;
628
+ durationMs: number | null;
629
+ error: string | null;
630
+ createdAt: string;
631
+ }>;
632
+ }
633
+
634
+ // ── Fetch log ──
635
+
636
+ export interface FetchLogEntry {
637
+ id: string;
638
+ sourceName: string;
639
+ sourceSlug: string;
640
+ status: string;
641
+ releasesFound: number;
642
+ releasesInserted: number;
643
+ durationMs: number | null;
644
+ error: string | null;
645
+ createdAt: string;
646
+ }
647
+
648
+ // ── Usage ──
649
+
650
+ export interface UsageBreakdownRow {
651
+ label: string | null;
652
+ totalInput: number;
653
+ totalOutput: number;
654
+ count: number;
655
+ }
656
+
657
+ export interface UsageStatsResponse {
658
+ totals: { totalInput: number; totalOutput: number; count: number };
659
+ byOperation: UsageBreakdownRow[];
660
+ byModel: UsageBreakdownRow[];
661
+ bySource: UsageBreakdownRow[];
662
+ }
663
+
664
+ // ── Sessions ──
665
+
666
+ export interface Session {
667
+ sessionId: string;
668
+ company: string;
669
+ type: "onboard" | "update";
670
+ status: "running" | "complete" | "error" | "cancelled";
671
+ step?: string;
672
+ totalSources?: number;
673
+ sourcesFetched?: number;
674
+ releasesFound?: number;
675
+ releasesInserted?: number;
676
+ currentAction?: string;
677
+ startedAt: number;
678
+ lastUpdatedAt: number;
679
+ error?: string;
680
+ activeSources?: string[];
681
+ cancelRequested?: boolean;
682
+ }
683
+
684
+ // ── Embed (admin) ──
685
+
686
+ export interface EmbedBackfillResponse {
687
+ processed: number;
688
+ succeeded: number;
689
+ failed: number;
690
+ remaining: number;
691
+ dryRun?: boolean;
692
+ }
693
+
694
+ export interface EmbedStatusResponse {
695
+ releases: { total: number; embedded: number; unembedded: number };
696
+ entities: {
697
+ total: number;
698
+ embedded: number;
699
+ unembedded: number;
700
+ breakdown: {
701
+ org: { total: number; embedded: number; unembedded: number };
702
+ product: { total: number; embedded: number; unembedded: number };
703
+ source: { total: number; embedded: number; unembedded: number };
704
+ };
705
+ };
706
+ chunks: { total: number; embedded: number; unembedded: number };
707
+ }