@taiga-ai/mcp-server 0.18.0-dev.446 → 0.18.0-dev.448

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/src/client.ts CHANGED
@@ -1,6 +1,72 @@
1
- import type { AgentJobType, ApiKeyIntrospection } from '@taiga/types';
1
+ import type {
2
+ ActivityEvent,
3
+ AgentJob,
4
+ AgentJobType,
5
+ ApiKeyIntrospection,
6
+ BacklogItem,
7
+ BacklogItemPlanWithSteps,
8
+ BuildRun,
9
+ BuildRunStep,
10
+ BuildRunWithContext,
11
+ ClientHealthSummary,
12
+ CurrentDeployments,
13
+ CustomDocumentDownloadView,
14
+ CustomDocumentDto,
15
+ Deployment,
16
+ DeploymentInsights,
17
+ DeploymentSyncStatusView,
18
+ DocumentReviewState,
19
+ Environment,
20
+ EnvironmentHealth,
21
+ Factory,
22
+ FactoryDocument,
23
+ FactoryMovePreview,
24
+ GithubInstallation,
25
+ GithubRepository,
26
+ InboxItem,
27
+ MaintenanceFinding,
28
+ MaintenanceFindingDetail,
29
+ MaintenanceRun,
30
+ MaintenanceSummary,
31
+ MonitoringHistory,
32
+ MoveProductResult,
33
+ PolicyConformance,
34
+ PolicyRemediationResult,
35
+ Product,
36
+ ProductDocument,
37
+ ProductDocumentListItem,
38
+ ProductRepository,
39
+ ProductRepositoryWithProtection,
40
+ PublishProductDocumentResponse,
41
+ QueueEntry,
42
+ RepositoryBranch,
43
+ RumSummary,
44
+ SetupTaskListEntry,
45
+ TenantDocument,
46
+ UptimeReport,
47
+ } from '@taiga/types';
2
48
  import { SERVER_VERSION } from './version.js';
3
49
 
50
+ /** A response type as the tools see it: `withoutTenant` has removed every `tenantId`, at any depth. */
51
+ export type WithoutTenant<T> = T extends readonly (infer Item)[]
52
+ ? WithoutTenant<Item>[]
53
+ : T extends object
54
+ ? { [K in keyof T as K extends 'tenantId' ? never : K]: WithoutTenant<T[K]> }
55
+ : T;
56
+
57
+ /** What each monitoring signal route returns. */
58
+ interface MonitoringSignals {
59
+ uptime: UptimeReport;
60
+ rum: RumSummary;
61
+ 'client-health': ClientHealthSummary;
62
+ history: MonitoringHistory;
63
+ }
64
+
65
+ /** The API's single-resource envelope. */
66
+ type One<T> = { data: T };
67
+ /** The API's paginated envelope. */
68
+ type Page<T> = { data: T[]; nextCursor: string | null };
69
+
4
70
  /**
5
71
  * The same value without any `tenantId`, at any depth. Build run and step
6
72
  * rows carry one; the key is scoped to one tenant already, so the id is
@@ -13,7 +79,7 @@ import { SERVER_VERSION } from './version.js';
13
79
  * the parse did not. Every key is defined as a data property, so an own
14
80
  * "__proto__" key stays a key instead of becoming the copy's prototype.
15
81
  */
16
- export function withoutTenant<T>(value: T): T {
82
+ export function withoutTenant<T>(value: T): WithoutTenant<T> {
17
83
  const root: { out?: unknown } = {};
18
84
  // Siblings keep their order and nested values are copied in full: see the loop.
19
85
  const pending: { source: unknown; target: object; key: string | number }[] = [
@@ -38,7 +104,7 @@ export function withoutTenant<T>(value: T): T {
38
104
  }
39
105
  Object.defineProperty(target, key, { value: copy, enumerable: true, writable: true, configurable: true });
40
106
  }
41
- return root.out as T;
107
+ return root.out as WithoutTenant<T>;
42
108
  }
43
109
 
44
110
  export class TaigaApiError extends Error {
@@ -117,19 +183,12 @@ function parseErrorBody(body: string): { code?: string; message?: string; detail
117
183
  }
118
184
 
119
185
  /**
120
- * A tenant or factory document as the API returns it: the fields the tools
121
- * read. `data` holds the content; policies and instructions keep it under
122
- * `sections` (see tools/sections.ts).
186
+ * A tenant, factory or product document as the tools read it. `data` holds
187
+ * the content; policies, instructions and product documents keep it under
188
+ * `sections` (see tools/sections.ts). Only tenant and factory documents have a
189
+ * `name`; a product document is named by its type.
123
190
  */
124
- export interface TaigaDocument {
125
- id: string;
126
- type: string;
127
- name: string;
128
- version: number | null;
129
- isDraft: boolean;
130
- data: Record<string, unknown>;
131
- [key: string]: unknown;
132
- }
191
+ export type TaigaDocument = WithoutTenant<TenantDocument | FactoryDocument | ProductDocument>;
133
192
 
134
193
  /**
135
194
  * HTTP client for the Taiga API.
@@ -145,12 +204,29 @@ export class TaigaClient {
145
204
  this.apiKey = opts.apiKey;
146
205
  }
147
206
 
207
+ /**
208
+ * One API call, its body typed as the route's response. The body is not
209
+ * validated: the server trusts the API, and `T` names what the route
210
+ * returns. Every `tenantId` is removed unless the caller keeps it.
211
+ */
212
+ private request<T>(
213
+ method: string,
214
+ path: string,
215
+ body: unknown,
216
+ options: { keepTenant: true; signal?: AbortSignal | undefined }
217
+ ): Promise<T>;
218
+ private request<T>(
219
+ method: string,
220
+ path: string,
221
+ body?: unknown,
222
+ options?: { signal?: AbortSignal | undefined }
223
+ ): Promise<WithoutTenant<T>>;
148
224
  private async request<T>(
149
225
  method: string,
150
226
  path: string,
151
227
  body?: unknown,
152
228
  options: { keepTenant?: true; signal?: AbortSignal | undefined } = {}
153
- ): Promise<T> {
229
+ ): Promise<T | WithoutTenant<T>> {
154
230
  const url = `${this.baseUrl}/v1${path}`;
155
231
  const headers: Record<string, string> = {
156
232
  Authorization: `Bearer ${this.apiKey}`,
@@ -225,18 +301,15 @@ export class TaigaClient {
225
301
  if (opts?.limit) params.set('limit', String(opts.limit));
226
302
  if (opts?.cursor) params.set('cursor', opts.cursor);
227
303
  const qs = params.toString();
228
- return this.request<{ data: unknown[]; nextCursor: string | null }>('GET', `/products${qs ? `?${qs}` : ''}`);
304
+ return this.request<Page<Product>>('GET', `/products${qs ? `?${qs}` : ''}`);
229
305
  }
230
306
 
231
307
  async getProduct(id: string, signal?: AbortSignal) {
232
- return this.request<{ data: unknown }>('GET', `/products/${seg(id)}`, undefined, { signal });
308
+ return this.request<One<Product>>('GET', `/products/${seg(id)}`, undefined, { signal });
233
309
  }
234
310
 
235
311
  async listProductRepositories(productId: string) {
236
- return this.request<{ data: unknown[]; nextCursor: string | null }>(
237
- 'GET',
238
- `/products/${seg(productId)}/repositories`
239
- );
312
+ return this.request<Page<ProductRepository>>('GET', `/products/${seg(productId)}/repositories`);
240
313
  }
241
314
 
242
315
  // ---------------------------------------------------------------------------
@@ -248,27 +321,24 @@ export class TaigaClient {
248
321
  if (opts?.limit) params.set('limit', String(opts.limit));
249
322
  if (opts?.cursor) params.set('cursor', opts.cursor);
250
323
  const qs = params.toString();
251
- return this.request<{ data: unknown[]; nextCursor: string | null }>(
252
- 'GET',
253
- `/tenants/current/documents${qs ? `?${qs}` : ''}`
254
- );
324
+ return this.request<Page<TenantDocument>>('GET', `/tenants/current/documents${qs ? `?${qs}` : ''}`);
255
325
  }
256
326
 
257
327
  async getTenantDocument(type: string) {
258
- return this.request<{ data: TaigaDocument }>('GET', `/tenants/current/documents/${seg(type)}`);
328
+ return this.request<One<TenantDocument>>('GET', `/tenants/current/documents/${seg(type)}`);
259
329
  }
260
330
 
261
331
  /** The shared draft for a document type, or `{ data: null }` when none exists. */
262
332
  async getTenantDocumentDraft(type: string) {
263
- return this.request<{ data: TaigaDocument | null }>('GET', `/tenants/current/documents/${seg(type)}/draft`);
333
+ return this.request<One<TenantDocument | null>>('GET', `/tenants/current/documents/${seg(type)}/draft`);
264
334
  }
265
335
 
266
336
  async saveTenantDocumentDraft(type: string, body: { data: Record<string, unknown> }) {
267
- return this.request<{ data: TaigaDocument }>('PUT', `/tenants/current/documents/${seg(type)}/draft`, body);
337
+ return this.request<One<TenantDocument>>('PUT', `/tenants/current/documents/${seg(type)}/draft`, body);
268
338
  }
269
339
 
270
340
  async publishTenantDocument(type: string) {
271
- return this.request<{ data: TaigaDocument }>('POST', `/tenants/current/documents/${seg(type)}/publish`);
341
+ return this.request<One<TenantDocument>>('POST', `/tenants/current/documents/${seg(type)}/publish`);
272
342
  }
273
343
 
274
344
  // ---------------------------------------------------------------------------
@@ -280,34 +350,27 @@ export class TaigaClient {
280
350
  if (opts?.limit) params.set('limit', String(opts.limit));
281
351
  if (opts?.cursor) params.set('cursor', opts.cursor);
282
352
  const qs = params.toString();
283
- return this.request<{ data: unknown[]; nextCursor: string | null }>(
284
- 'GET',
285
- `/factories/${seg(factoryId)}/documents${qs ? `?${qs}` : ''}`
286
- );
353
+ return this.request<Page<FactoryDocument>>('GET', `/factories/${seg(factoryId)}/documents${qs ? `?${qs}` : ''}`);
287
354
  }
288
355
 
289
356
  async getFactoryDocument(factoryId: string, type: string) {
290
- return this.request<{ data: TaigaDocument }>('GET', `/factories/${seg(factoryId)}/documents/${seg(type)}`);
357
+ return this.request<One<FactoryDocument>>('GET', `/factories/${seg(factoryId)}/documents/${seg(type)}`);
291
358
  }
292
359
 
293
360
  /** The shared draft for a factory document type, or `{ data: null }` when none exists. */
294
361
  async getFactoryDocumentDraft(factoryId: string, type: string) {
295
- return this.request<{ data: TaigaDocument | null }>(
362
+ return this.request<One<FactoryDocument | null>>(
296
363
  'GET',
297
364
  `/factories/${seg(factoryId)}/documents/${seg(type)}/draft`
298
365
  );
299
366
  }
300
367
 
301
368
  async saveFactoryDocumentDraft(factoryId: string, type: string, body: { data: Record<string, unknown> }) {
302
- return this.request<{ data: TaigaDocument }>(
303
- 'PUT',
304
- `/factories/${seg(factoryId)}/documents/${seg(type)}/draft`,
305
- body
306
- );
369
+ return this.request<One<FactoryDocument>>('PUT', `/factories/${seg(factoryId)}/documents/${seg(type)}/draft`, body);
307
370
  }
308
371
 
309
372
  async publishFactoryDocument(factoryId: string, type: string) {
310
- return this.request<{ data: TaigaDocument }>('POST', `/factories/${seg(factoryId)}/documents/${seg(type)}/publish`);
373
+ return this.request<One<FactoryDocument>>('POST', `/factories/${seg(factoryId)}/documents/${seg(type)}/publish`);
311
374
  }
312
375
 
313
376
  // ---------------------------------------------------------------------------
@@ -319,7 +382,7 @@ export class TaigaClient {
319
382
  if (opts?.origin) params.set('origin', opts.origin);
320
383
  if (opts?.limit) params.set('limit', String(opts.limit));
321
384
  const qs = params.toString();
322
- return this.request<{ data: unknown[]; nextCursor: string | null }>(
385
+ return this.request<Page<ProductDocumentListItem>>(
323
386
  'GET',
324
387
  `/products/${seg(productId)}/documents${qs ? `?${qs}` : ''}`
325
388
  );
@@ -331,7 +394,7 @@ export class TaigaClient {
331
394
  * back up.
332
395
  */
333
396
  async setLineStopped(productId: string, stopped: boolean) {
334
- return this.request<{ data: unknown }>('PUT', `/products/${seg(productId)}/line`, { stopped });
397
+ return this.request<One<Product>>('PUT', `/products/${seg(productId)}/line`, { stopped });
335
398
  }
336
399
 
337
400
  // ---------------------------------------------------------------------------
@@ -346,31 +409,24 @@ export class TaigaClient {
346
409
  documentType?: string | undefined;
347
410
  metadata?: Record<string, unknown> | undefined;
348
411
  }) {
349
- return this.request<{ data: unknown }>('POST', '/agent-jobs', body);
412
+ return this.request<One<AgentJob>>('POST', '/agent-jobs', body);
350
413
  }
351
414
 
352
415
  async getAgentJob(id: string, signal?: AbortSignal) {
353
- return this.request<{ data: unknown }>('GET', `/agent-jobs/${seg(id)}`, undefined, { signal });
416
+ return this.request<One<AgentJob>>('GET', `/agent-jobs/${seg(id)}`, undefined, { signal });
354
417
  }
355
418
 
356
419
  /** The plan-side draft of a product document, or `{ data: null }` when none exists. */
357
420
  async getProductDocumentDraft(productId: string, type: string) {
358
- return this.request<{ data: TaigaDocument | null }>(
359
- 'GET',
360
- `/products/${seg(productId)}/documents/${seg(type)}/draft`
361
- );
421
+ return this.request<One<ProductDocument | null>>('GET', `/products/${seg(productId)}/documents/${seg(type)}/draft`);
362
422
  }
363
423
 
364
424
  async saveProductDocumentDraft(productId: string, type: string, body: { data: Record<string, unknown> }) {
365
- return this.request<{ data: TaigaDocument }>(
366
- 'PUT',
367
- `/products/${seg(productId)}/documents/${seg(type)}/draft`,
368
- body
369
- );
425
+ return this.request<One<ProductDocument>>('PUT', `/products/${seg(productId)}/documents/${seg(type)}/draft`, body);
370
426
  }
371
427
 
372
428
  async publishProductDocument(productId: string, type: string) {
373
- return this.request<{ data: TaigaDocument; warnings?: { unknownPolicyCitations?: string[] } }>(
429
+ return this.request<PublishProductDocumentResponse>(
374
430
  'POST',
375
431
  `/products/${seg(productId)}/documents/${seg(type)}/publish`
376
432
  );
@@ -378,22 +434,25 @@ export class TaigaClient {
378
434
 
379
435
  /** Mark the published version reviewed by the caller, as the Documents page shows it. */
380
436
  async markProductDocumentReviewed(productId: string, type: string) {
381
- return this.request<{ data: unknown }>('POST', `/products/${seg(productId)}/documents/${seg(type)}/review`);
437
+ return this.request<One<DocumentReviewState>>('POST', `/products/${seg(productId)}/documents/${seg(type)}/review`);
382
438
  }
383
439
 
384
440
  /** Generate remaining: every missing and outdated document, in chain order. */
385
441
  async generateRemainingDocuments(productId: string) {
386
- return this.request<{ data: unknown }>('POST', `/products/${seg(productId)}/documents/generate-all`);
442
+ return this.request<One<{ jobIds: string[] }>>('POST', `/products/${seg(productId)}/documents/generate-all`);
387
443
  }
388
444
 
389
445
  /** Cancel a Generate remaining run still in flight. */
390
446
  async stopGeneratingDocuments(productId: string) {
391
- return this.request<{ data: unknown }>('POST', `/products/${seg(productId)}/documents/generate-all/stop`);
447
+ return this.request<One<{ cancelledJobIds: string[] }>>(
448
+ 'POST',
449
+ `/products/${seg(productId)}/documents/generate-all/stop`
450
+ );
392
451
  }
393
452
 
394
453
  async getProductDocument(productId: string, type: string, origin?: string) {
395
454
  const qs = origin ? `?${new URLSearchParams({ origin })}` : '';
396
- return this.request<{ data: unknown }>('GET', `/products/${seg(productId)}/documents/${seg(type)}${qs}`);
455
+ return this.request<One<ProductDocument>>('GET', `/products/${seg(productId)}/documents/${seg(type)}${qs}`);
397
456
  }
398
457
 
399
458
  // ---------------------------------------------------------------------------
@@ -424,19 +483,16 @@ export class TaigaClient {
424
483
  if (opts?.limit) params.set('limit', String(opts.limit));
425
484
  if (opts?.cursor) params.set('cursor', opts.cursor);
426
485
  const qs = params.toString();
427
- return this.request<{ data: unknown[]; nextCursor: string | null }>(
428
- 'GET',
429
- `/products/${seg(productId)}/backlog${qs ? `?${qs}` : ''}`
430
- );
486
+ return this.request<Page<BacklogItem>>('GET', `/products/${seg(productId)}/backlog${qs ? `?${qs}` : ''}`);
431
487
  }
432
488
 
433
489
  async getBacklogItem(itemId: string, signal?: AbortSignal) {
434
- return this.request<{ data: unknown }>('GET', `/backlog/${seg(itemId)}`, undefined, { signal });
490
+ return this.request<One<BacklogItem>>('GET', `/backlog/${seg(itemId)}`, undefined, { signal });
435
491
  }
436
492
 
437
493
  /** The initiative's history, oldest first: every turn event and audited move, each with who did it. */
438
494
  async listBacklogItemActivity(itemId: string) {
439
- return this.request<{ data: unknown[] }>('GET', `/backlog/${seg(itemId)}/activity`);
495
+ return this.request<{ data: ActivityEvent[] }>('GET', `/backlog/${seg(itemId)}/activity`);
440
496
  }
441
497
 
442
498
  /** A person's move on the line board: the column dropped into, and what to follow there. */
@@ -449,34 +505,34 @@ export class TaigaClient {
449
505
  force?: boolean | undefined;
450
506
  }
451
507
  ) {
452
- return this.request<{ data: unknown }>('PUT', `/backlog/${seg(itemId)}/state`, body);
508
+ return this.request<One<BacklogItem>>('PUT', `/backlog/${seg(itemId)}/state`, body);
453
509
  }
454
510
 
455
511
  /** Answer the needs-human flag: plan again, resume the run, or clear a refused start. */
456
512
  async resumeInitiative(itemId: string) {
457
- return this.request<{ data: unknown }>('POST', `/backlog/${seg(itemId)}/resume`);
513
+ return this.request<One<BacklogItem>>('POST', `/backlog/${seg(itemId)}/resume`);
458
514
  }
459
515
 
460
516
  /** The initiative's own review gate: true builds a landed plan at once, false stops for a person, null follows the product. */
461
517
  async setAutoBuild(itemId: string, autoBuild: boolean | null) {
462
- return this.request<{ data: unknown }>('PUT', `/backlog/${seg(itemId)}/auto-build`, { autoBuild });
518
+ return this.request<One<BacklogItem>>('PUT', `/backlog/${seg(itemId)}/auto-build`, { autoBuild });
463
519
  }
464
520
 
465
521
  /** The product's live queue, in order. */
466
522
  async listQueue(productId: string) {
467
- return this.request<{ data: unknown[] }>('GET', `/products/${seg(productId)}/queue`);
523
+ return this.request<{ data: QueueEntry[] }>('GET', `/products/${seg(productId)}/queue`);
468
524
  }
469
525
 
470
526
  async getActivePlan(itemId: string) {
471
- return this.request<{ data: unknown }>('GET', `/backlog/${seg(itemId)}/plans/active`);
527
+ return this.request<One<BacklogItemPlanWithSteps>>('GET', `/backlog/${seg(itemId)}/plans/active`);
472
528
  }
473
529
 
474
530
  async approvePlan(planId: string) {
475
- return this.request<{ data: unknown }>('POST', `/plans/${seg(planId)}/approve`);
531
+ return this.request<One<BacklogItem>>('POST', `/plans/${seg(planId)}/approve`);
476
532
  }
477
533
 
478
534
  async rejectPlan(planId: string, body: { comment: string }) {
479
- return this.request<{ data: unknown }>('POST', `/plans/${seg(planId)}/reject`, body);
535
+ return this.request<One<BacklogItem>>('POST', `/plans/${seg(planId)}/reject`, body);
480
536
  }
481
537
 
482
538
  // ---------------------------------------------------------------------------
@@ -492,10 +548,7 @@ export class TaigaClient {
492
548
  if (opts?.limit) params.set('limit', String(opts.limit));
493
549
  if (opts?.cursor) params.set('cursor', opts.cursor);
494
550
  const qs = params.toString();
495
- return this.request<{ data: unknown[]; nextCursor: string | null }>(
496
- 'GET',
497
- `/products/${seg(productId)}/builds${qs ? `?${qs}` : ''}`
498
- );
551
+ return this.request<Page<BuildRunWithContext>>('GET', `/products/${seg(productId)}/builds${qs ? `?${qs}` : ''}`);
499
552
  }
500
553
 
501
554
  /** The runs of every product the key can see, newest first; `active` narrows to the ones executing now. */
@@ -510,19 +563,19 @@ export class TaigaClient {
510
563
  if (opts?.limit) params.set('limit', String(opts.limit));
511
564
  if (opts?.cursor) params.set('cursor', opts.cursor);
512
565
  const qs = params.toString();
513
- return this.request<{ data: unknown[]; nextCursor: string | null }>('GET', `/builds${qs ? `?${qs}` : ''}`);
566
+ return this.request<Page<BuildRunWithContext>>('GET', `/builds${qs ? `?${qs}` : ''}`);
514
567
  }
515
568
 
516
569
  async getBuildRun(id: string, signal?: AbortSignal) {
517
- return this.request<{ data: unknown }>('GET', `/builds/${seg(id)}`, undefined, { signal });
570
+ return this.request<One<BuildRunWithContext>>('GET', `/builds/${seg(id)}`, undefined, { signal });
518
571
  }
519
572
 
520
573
  async stopBuildRun(id: string) {
521
- return this.request<{ data: unknown }>('POST', `/builds/${seg(id)}/stop`);
574
+ return this.request<One<BuildRun>>('POST', `/builds/${seg(id)}/stop`);
522
575
  }
523
576
 
524
577
  async listBuildRunSteps(id: string) {
525
- return this.request<{ data: unknown[] }>('GET', `/builds/${seg(id)}/steps`);
578
+ return this.request<{ data: BuildRunStep[] }>('GET', `/builds/${seg(id)}/steps`);
526
579
  }
527
580
 
528
581
  // ---------------------------------------------------------------------------
@@ -530,11 +583,11 @@ export class TaigaClient {
530
583
  // ---------------------------------------------------------------------------
531
584
 
532
585
  async getMaintenanceSummary(productId: string) {
533
- return this.request<{ data: unknown }>('GET', `/products/${seg(productId)}/maintenance/summary`);
586
+ return this.request<One<MaintenanceSummary>>('GET', `/products/${seg(productId)}/maintenance/summary`);
534
587
  }
535
588
 
536
589
  async listMaintenanceSweeps(productId: string, opts?: { limit?: number | undefined }) {
537
- return this.request<{ data: unknown[]; nextCursor: string | null }>(
590
+ return this.request<Page<MaintenanceRun>>(
538
591
  'GET',
539
592
  `/products/${seg(productId)}/maintenance/runs${query({ limit: opts?.limit })}`
540
593
  );
@@ -550,14 +603,14 @@ export class TaigaClient {
550
603
  cursor?: string | undefined;
551
604
  }
552
605
  ) {
553
- return this.request<{ data: unknown[]; nextCursor: string | null; totalCount?: number }>(
606
+ return this.request<Page<MaintenanceFinding> & { totalCount?: number }>(
554
607
  'GET',
555
608
  `/products/${seg(productId)}/maintenance/findings${query({ ...opts })}`
556
609
  );
557
610
  }
558
611
 
559
612
  async getFinding(id: string) {
560
- return this.request<{ data: unknown }>('GET', `/maintenance/findings/${seg(id)}`);
613
+ return this.request<One<MaintenanceFindingDetail>>('GET', `/maintenance/findings/${seg(id)}`);
561
614
  }
562
615
 
563
616
  async updateFinding(
@@ -569,11 +622,11 @@ export class TaigaClient {
569
622
  ignoreExpiresAt?: string | null | undefined;
570
623
  }
571
624
  ) {
572
- return this.request<{ data: unknown }>('PATCH', `/maintenance/findings/${seg(id)}`, body);
625
+ return this.request<One<MaintenanceFinding>>('PATCH', `/maintenance/findings/${seg(id)}`, body);
573
626
  }
574
627
 
575
628
  async remediateFinding(productId: string, findingId: string) {
576
- return this.request<{ data: unknown }>(
629
+ return this.request<One<{ finding: MaintenanceFinding; backlogItemId: string }>>(
577
630
  'POST',
578
631
  `/products/${seg(productId)}/maintenance/findings/${seg(findingId)}/remediate`,
579
632
  {}
@@ -589,15 +642,19 @@ export class TaigaClient {
589
642
  productRepositoryId?: string | null | undefined;
590
643
  }
591
644
  ) {
592
- return this.request<{ data: unknown }>('POST', `/products/${seg(productId)}/maintenance/packages/remediate`, body);
645
+ return this.request<One<{ backlogItemId: string; findingIds: string[] }>>(
646
+ 'POST',
647
+ `/products/${seg(productId)}/maintenance/packages/remediate`,
648
+ body
649
+ );
593
650
  }
594
651
 
595
652
  async getPolicyStanding(productId: string) {
596
- return this.request<{ data: unknown }>('GET', `/products/${seg(productId)}/maintenance/policies`);
653
+ return this.request<One<PolicyConformance>>('GET', `/products/${seg(productId)}/maintenance/policies`);
597
654
  }
598
655
 
599
656
  async remediatePolicyControl(productId: string, controlId: string) {
600
- return this.request<{ data: unknown }>(
657
+ return this.request<One<PolicyRemediationResult>>(
601
658
  'POST',
602
659
  `/products/${seg(productId)}/maintenance/policies/controls/${seg(controlId)}/remediate`,
603
660
  {}
@@ -605,7 +662,7 @@ export class TaigaClient {
605
662
  }
606
663
 
607
664
  async remediatePolicyViolation(productId: string, divergenceId: string) {
608
- return this.request<{ data: unknown }>(
665
+ return this.request<One<PolicyRemediationResult>>(
609
666
  'POST',
610
667
  `/products/${seg(productId)}/maintenance/policies/divergences/${seg(divergenceId)}/remediate`,
611
668
  {}
@@ -626,17 +683,14 @@ export class TaigaClient {
626
683
  }) {
627
684
  const { productId, ...rest } = opts;
628
685
  const path = productId ? `/products/${seg(productId)}/inbox` : '/inbox';
629
- return this.request<{ data: unknown[]; nextCursor: string | null; totalCount?: number }>(
630
- 'GET',
631
- `${path}${query({ ...rest })}`
632
- );
686
+ return this.request<Page<InboxItem> & { totalCount?: number }>('GET', `${path}${query({ ...rest })}`);
633
687
  }
634
688
 
635
689
  async updateInboxItem(
636
690
  id: string,
637
691
  body: { status?: 'in_progress' | 'done' | 'dismissed' | undefined; snoozedUntil?: string | null | undefined }
638
692
  ) {
639
- return this.request<{ data: unknown }>('PATCH', `/inbox/${seg(id)}`, body);
693
+ return this.request<One<InboxItem>>('PATCH', `/inbox/${seg(id)}`, body);
640
694
  }
641
695
 
642
696
  async listSetupTasks(opts: {
@@ -648,10 +702,7 @@ export class TaigaClient {
648
702
  }) {
649
703
  const { productId, ...rest } = opts;
650
704
  const path = productId ? `/products/${seg(productId)}/setup-tasks` : '/setup-tasks';
651
- return this.request<{ data: unknown[]; nextCursor: string | null; totalCount?: number }>(
652
- 'GET',
653
- `${path}${query({ ...rest })}`
654
- );
705
+ return this.request<Page<SetupTaskListEntry> & { totalCount?: number }>('GET', `${path}${query({ ...rest })}`);
655
706
  }
656
707
 
657
708
  // ---------------------------------------------------------------------------
@@ -659,59 +710,53 @@ export class TaigaClient {
659
710
  // ---------------------------------------------------------------------------
660
711
 
661
712
  async listEnvironments(productId: string, opts?: { limit?: number | undefined; cursor?: string | undefined }) {
662
- return this.request<{ data: unknown[]; nextCursor: string | null }>(
663
- 'GET',
664
- `/products/${seg(productId)}/environments${query({ ...opts })}`
665
- );
713
+ return this.request<Page<Environment>>('GET', `/products/${seg(productId)}/environments${query({ ...opts })}`);
666
714
  }
667
715
 
668
716
  async createEnvironment(productId: string, body: { name: string; type: string }) {
669
- return this.request<{ data: unknown }>('POST', `/products/${seg(productId)}/environments`, body);
717
+ return this.request<One<Environment>>('POST', `/products/${seg(productId)}/environments`, body);
670
718
  }
671
719
 
672
720
  async updateEnvironment(
673
721
  id: string,
674
722
  body: { name?: string | undefined; type?: string | undefined; url?: string | null | undefined }
675
723
  ) {
676
- return this.request<{ data: unknown }>('PATCH', `/environments/${seg(id)}`, body);
724
+ return this.request<One<Environment>>('PATCH', `/environments/${seg(id)}`, body);
677
725
  }
678
726
 
679
727
  async getCurrentDeployments(productId: string) {
680
- return this.request<{ data: unknown }>('GET', `/products/${seg(productId)}/deployments/current`);
728
+ return this.request<One<CurrentDeployments>>('GET', `/products/${seg(productId)}/deployments/current`);
681
729
  }
682
730
 
683
731
  async listDeployments(
684
732
  productId: string,
685
733
  opts?: { environmentId?: string | undefined; limit?: number | undefined; cursor?: string | undefined }
686
734
  ) {
687
- return this.request<{ data: unknown[]; nextCursor: string | null }>(
688
- 'GET',
689
- `/products/${seg(productId)}/deployments${query({ ...opts })}`
690
- );
735
+ return this.request<Page<Deployment>>('GET', `/products/${seg(productId)}/deployments${query({ ...opts })}`);
691
736
  }
692
737
 
693
738
  async getDeploymentSyncStatus(productId: string) {
694
- return this.request<{ data: unknown }>('GET', `/products/${seg(productId)}/deployments/sync-status`);
739
+ return this.request<One<DeploymentSyncStatusView>>('GET', `/products/${seg(productId)}/deployments/sync-status`);
695
740
  }
696
741
 
697
742
  async getDeliveryMetrics(productId: string, environmentId: string, windowDays: number) {
698
- return this.request<{ data: unknown }>(
743
+ return this.request<One<DeploymentInsights>>(
699
744
  'GET',
700
745
  `/products/${seg(productId)}/deployments/insights${query({ environmentId, windowDays })}`
701
746
  );
702
747
  }
703
748
 
704
749
  async getMonitoringHealth(productId: string) {
705
- return this.request<{ data: unknown[] }>('GET', `/products/${seg(productId)}/monitoring/health`);
750
+ return this.request<{ data: EnvironmentHealth[] }>('GET', `/products/${seg(productId)}/monitoring/health`);
706
751
  }
707
752
 
708
- async getMonitoringSignal(
753
+ async getMonitoringSignal<S extends keyof MonitoringSignals>(
709
754
  productId: string,
710
- signal: 'uptime' | 'rum' | 'client-health' | 'history',
755
+ signal: S,
711
756
  environmentId: string,
712
757
  windowDays: number
713
758
  ) {
714
- return this.request<{ data: unknown }>(
759
+ return this.request<One<MonitoringSignals[S]>>(
715
760
  'GET',
716
761
  `/products/${seg(productId)}/monitoring/${signal}${query({ environmentId, windowDays })}`
717
762
  );
@@ -729,21 +774,18 @@ export class TaigaClient {
729
774
  }) {
730
775
  // The route reads the literal 'null' as "this level only, not below it".
731
776
  const scope = (value: string | null | undefined) => (value === null ? 'null' : value);
732
- return this.request<{ data: unknown[]; nextCursor: string | null }>(
777
+ return this.request<Page<CustomDocumentDto>>(
733
778
  'GET',
734
779
  `/custom-documents${query({ ...opts, factoryId: scope(opts.factoryId), productId: scope(opts.productId) })}`
735
780
  );
736
781
  }
737
782
 
738
783
  async getKnowledge(id: string) {
739
- return this.request<{ data: unknown }>('GET', `/custom-documents/${seg(id)}`);
784
+ return this.request<One<CustomDocumentDto>>('GET', `/custom-documents/${seg(id)}`);
740
785
  }
741
786
 
742
787
  async getKnowledgeDownload(id: string) {
743
- return this.request<{ data: { url?: unknown } & Record<string, unknown> }>(
744
- 'GET',
745
- `/custom-documents/${seg(id)}/download`
746
- );
788
+ return this.request<One<CustomDocumentDownloadView>>('GET', `/custom-documents/${seg(id)}/download`);
747
789
  }
748
790
 
749
791
  /** A text file filed as knowledge at one level: the organization, a factory, or a product in its factory. */
@@ -760,15 +802,15 @@ export class TaigaClient {
760
802
  if (file.factoryId) form.set('factoryId', file.factoryId);
761
803
  if (file.productId) form.set('productId', file.productId);
762
804
  form.set('file', new Blob([file.content], { type: file.mime }), file.fileName);
763
- return this.request<{ data: unknown; deduplicated?: boolean }>('POST', '/custom-documents', form);
805
+ return this.request<{ data: CustomDocumentDto; deduplicated: boolean }>('POST', '/custom-documents', form);
764
806
  }
765
807
 
766
808
  async retryKnowledge(id: string) {
767
- return this.request<{ data: unknown }>('POST', `/custom-documents/${seg(id)}/retry`);
809
+ return this.request<One<CustomDocumentDto>>('POST', `/custom-documents/${seg(id)}/retry`);
768
810
  }
769
811
 
770
812
  async deleteKnowledge(id: string) {
771
- return this.request<{ data?: unknown } | null>('DELETE', `/custom-documents/${seg(id)}`);
813
+ return this.request<null>('DELETE', `/custom-documents/${seg(id)}`);
772
814
  }
773
815
 
774
816
  // ---------------------------------------------------------------------------
@@ -787,11 +829,11 @@ export class TaigaClient {
787
829
  emitDesignStandards?: boolean | null | undefined;
788
830
  }
789
831
  ) {
790
- return this.request<{ data: unknown }>('PATCH', `/products/${seg(productId)}`, body);
832
+ return this.request<One<Product>>('PATCH', `/products/${seg(productId)}`, body);
791
833
  }
792
834
 
793
835
  async updateBacklogItem(itemId: string, body: { autoMerge?: boolean | null | undefined }) {
794
- return this.request<{ data: unknown }>('PATCH', `/backlog/${seg(itemId)}`, body);
836
+ return this.request<One<BacklogItem>>('PATCH', `/backlog/${seg(itemId)}`, body);
795
837
  }
796
838
 
797
839
  // ---------------------------------------------------------------------------
@@ -799,7 +841,7 @@ export class TaigaClient {
799
841
  // ---------------------------------------------------------------------------
800
842
 
801
843
  async getFactory(id: string) {
802
- return this.request<{ data: unknown }>('GET', `/factories/${seg(id)}`);
844
+ return this.request<One<Factory>>('GET', `/factories/${seg(id)}`);
803
845
  }
804
846
 
805
847
  async listFactories(opts?: { limit?: number | undefined; cursor?: string | undefined }) {
@@ -807,15 +849,15 @@ export class TaigaClient {
807
849
  if (opts?.limit) params.set('limit', String(opts.limit));
808
850
  if (opts?.cursor) params.set('cursor', opts.cursor);
809
851
  const qs = params.toString();
810
- return this.request<{ data: unknown[]; nextCursor: string | null }>('GET', `/factories${qs ? `?${qs}` : ''}`);
852
+ return this.request<Page<Factory>>('GET', `/factories${qs ? `?${qs}` : ''}`);
811
853
  }
812
854
 
813
855
  async createFactory(body: { name: string }) {
814
- return this.request<{ data: unknown }>('POST', '/factories', body);
856
+ return this.request<One<Factory>>('POST', '/factories', body);
815
857
  }
816
858
 
817
859
  async updateFactory(factoryId: string, body: { name?: string | undefined }) {
818
- return this.request<{ data: unknown }>('PATCH', `/factories/${seg(factoryId)}`, body);
860
+ return this.request<One<Factory>>('PATCH', `/factories/${seg(factoryId)}`, body);
819
861
  }
820
862
 
821
863
  // ---------------------------------------------------------------------------
@@ -830,23 +872,26 @@ export class TaigaClient {
830
872
  ownsDeployment?: boolean | null | undefined;
831
873
  deploymentNotes?: string | undefined;
832
874
  }) {
833
- return this.request<{ data: unknown }>('POST', '/products', body);
875
+ return this.request<One<Product>>('POST', '/products', body);
834
876
  }
835
877
 
836
878
  async previewProductMove(productId: string, factoryId: string) {
837
- return this.request<{ data: unknown }>('GET', `/products/${seg(productId)}/factory/preview${query({ factoryId })}`);
879
+ return this.request<One<FactoryMovePreview>>(
880
+ 'GET',
881
+ `/products/${seg(productId)}/factory/preview${query({ factoryId })}`
882
+ );
838
883
  }
839
884
 
840
885
  async moveProduct(productId: string, body: { factoryId: string; stopLine?: boolean | undefined }) {
841
- return this.request<Record<string, unknown>>('PUT', `/products/${seg(productId)}/factory`, body);
886
+ return this.request<MoveProductResult>('PUT', `/products/${seg(productId)}/factory`, body);
842
887
  }
843
888
 
844
889
  async startImport(productId: string) {
845
- return this.request<{ data: { jobId: string } }>('POST', `/products/${seg(productId)}/brownfield-import`);
890
+ return this.request<One<{ jobId: string }>>('POST', `/products/${seg(productId)}/brownfield-import`);
846
891
  }
847
892
 
848
893
  async refreshRealityDocuments(productId: string, docTypes?: readonly string[] | undefined) {
849
- return this.request<{ data: { jobId: string } }>(
894
+ return this.request<One<{ jobId: string }>>(
850
895
  'POST',
851
896
  `/products/${seg(productId)}/documents/refresh-reality`,
852
897
  docTypes ? { docTypes } : undefined
@@ -854,11 +899,11 @@ export class TaigaClient {
854
899
  }
855
900
 
856
901
  async completeSetup(productId: string) {
857
- return this.request<{ data: unknown }>('POST', `/products/${seg(productId)}/setup/complete`);
902
+ return this.request<One<Product>>('POST', `/products/${seg(productId)}/setup/complete`);
858
903
  }
859
904
 
860
905
  async reopenSetup(productId: string) {
861
- return this.request<{ data: unknown }>('POST', `/products/${seg(productId)}/setup/reopen`);
906
+ return this.request<One<Product>>('POST', `/products/${seg(productId)}/setup/reopen`);
862
907
  }
863
908
 
864
909
  // ---------------------------------------------------------------------------
@@ -867,10 +912,10 @@ export class TaigaClient {
867
912
 
868
913
  /** Every connected GitHub account, page by page: a later account must be findable by its id too. */
869
914
  async listGithubInstallations() {
870
- const data: unknown[] = [];
915
+ const data: WithoutTenant<GithubInstallation>[] = [];
871
916
  let cursor: string | undefined;
872
917
  do {
873
- const result = await this.request<{ data: unknown[]; nextCursor: string | null }>(
918
+ const result = await this.request<Page<GithubInstallation>>(
874
919
  'GET',
875
920
  `/github/installations${query({ limit: 100, cursor })}`
876
921
  );
@@ -884,7 +929,7 @@ export class TaigaClient {
884
929
  installationId: string,
885
930
  opts: { q?: string | undefined; limit?: number | undefined; cursor?: string | undefined }
886
931
  ) {
887
- return this.request<{ data: unknown[]; nextCursor: string | null }>(
932
+ return this.request<Page<GithubRepository>>(
888
933
  'GET',
889
934
  `/github/installations/${seg(installationId)}/repositories${query({ ...opts })}`
890
935
  );
@@ -894,7 +939,7 @@ export class TaigaClient {
894
939
  productId: string,
895
940
  body: { githubRepositoryId: string; replacesRepositoryId?: string | undefined }
896
941
  ) {
897
- return this.request<{ data: unknown }>('POST', `/products/${seg(productId)}/repositories`, body);
942
+ return this.request<One<ProductRepository>>('POST', `/products/${seg(productId)}/repositories`, body);
898
943
  }
899
944
 
900
945
  async updateRepository(
@@ -902,7 +947,7 @@ export class TaigaClient {
902
947
  repositoryId: string,
903
948
  body: { baseBranch?: string | null | undefined; label?: string | null | undefined }
904
949
  ) {
905
- return this.request<{ data: unknown }>(
950
+ return this.request<One<ProductRepositoryWithProtection>>(
906
951
  'PATCH',
907
952
  `/products/${seg(productId)}/repositories/${seg(repositoryId)}`,
908
953
  body
@@ -910,7 +955,7 @@ export class TaigaClient {
910
955
  }
911
956
 
912
957
  async listRepositoryBranches(productId: string, repositoryId: string) {
913
- return this.request<{ data: unknown[]; truncated: boolean }>(
958
+ return this.request<{ data: RepositoryBranch[]; truncated: boolean }>(
914
959
  'GET',
915
960
  `/products/${seg(productId)}/repositories/${seg(repositoryId)}/branches`
916
961
  );