@taiga-ai/mcp-server 0.25.0-rc.474 → 0.26.0-dev.479
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 +111 -0
- package/dist/index.js +292 -115
- package/package.json +15 -2
- package/scripts/build.mjs +0 -37
- package/scripts/prepare-publish.cjs +0 -49
- package/src/client.test.ts +0 -424
- package/src/client.ts +0 -977
- package/src/config.test.ts +0 -24
- package/src/config.ts +0 -28
- package/src/index.ts +0 -45
- package/src/modern.integration.test.ts +0 -153
- package/src/server.integration.test.ts +0 -9214
- package/src/server.ts +0 -18
- package/src/tools/annotations.ts +0 -38
- package/src/tools/autonomy.ts +0 -86
- package/src/tools/backlogs.ts +0 -802
- package/src/tools/builds.ts +0 -668
- package/src/tools/catalogue.test.ts +0 -170
- package/src/tools/catalogue.ts +0 -223
- package/src/tools/context.ts +0 -513
- package/src/tools/documents.ts +0 -375
- package/src/tools/errors.ts +0 -172
- package/src/tools/hints.ts +0 -99
- package/src/tools/inbox.ts +0 -546
- package/src/tools/index.ts +0 -239
- package/src/tools/inputs.ts +0 -47
- package/src/tools/instructions.ts +0 -41
- package/src/tools/jobs.ts +0 -90
- package/src/tools/knowledge.ts +0 -249
- package/src/tools/maintenance.ts +0 -547
- package/src/tools/markdown.test.ts +0 -249
- package/src/tools/markdown.ts +0 -151
- package/src/tools/observe.ts +0 -311
- package/src/tools/product-documents.ts +0 -184
- package/src/tools/products.ts +0 -529
- package/src/tools/prompts.ts +0 -265
- package/src/tools/queue.ts +0 -250
- package/src/tools/readiness.ts +0 -177
- package/src/tools/remote.test.ts +0 -44
- package/src/tools/remote.ts +0 -107
- package/src/tools/repositories.ts +0 -360
- package/src/tools/request-prompt.test.ts +0 -32
- package/src/tools/request-prompt.ts +0 -28
- package/src/tools/resources.ts +0 -217
- package/src/tools/sections.test.ts +0 -143
- package/src/tools/sections.ts +0 -287
- package/src/tools/setup.ts +0 -455
- package/src/tools/summarize.test.ts +0 -20
- package/src/tools/summarize.ts +0 -96
- package/src/tools/wait.ts +0 -490
- package/src/version.test.ts +0 -10
- package/src/version.ts +0 -14
- package/taiga-ai-mcp-server-0.25.0-rc.474.tgz +0 -0
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -9
package/src/client.ts
DELETED
|
@@ -1,977 +0,0 @@
|
|
|
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
|
-
PolicyRemediationInput,
|
|
35
|
-
PolicyRemediationResult,
|
|
36
|
-
Product,
|
|
37
|
-
ProductDocument,
|
|
38
|
-
ProductDocumentListItem,
|
|
39
|
-
ProductRepository,
|
|
40
|
-
ProductRepositoryWithProtection,
|
|
41
|
-
PublishProductDocumentResponse,
|
|
42
|
-
QueueEntry,
|
|
43
|
-
RepositoryBranch,
|
|
44
|
-
RumSummary,
|
|
45
|
-
SetupTaskListEntry,
|
|
46
|
-
TenantDocument,
|
|
47
|
-
UptimeReport,
|
|
48
|
-
CreateFactoryInput,
|
|
49
|
-
CreateProductInput,
|
|
50
|
-
} from '@taiga/types';
|
|
51
|
-
import { SERVER_VERSION } from './version.js';
|
|
52
|
-
|
|
53
|
-
/** A response type as the tools see it: `withoutTenant` has removed every `tenantId`, at any depth. */
|
|
54
|
-
export type WithoutTenant<T> = T extends readonly (infer Item)[]
|
|
55
|
-
? WithoutTenant<Item>[]
|
|
56
|
-
: T extends object
|
|
57
|
-
? { [K in keyof T as K extends 'tenantId' ? never : K]: WithoutTenant<T[K]> }
|
|
58
|
-
: T;
|
|
59
|
-
|
|
60
|
-
/** What each monitoring signal route returns. */
|
|
61
|
-
interface MonitoringSignals {
|
|
62
|
-
uptime: UptimeReport;
|
|
63
|
-
rum: RumSummary;
|
|
64
|
-
'client-health': ClientHealthSummary;
|
|
65
|
-
history: MonitoringHistory;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** The API's single-resource envelope. */
|
|
69
|
-
type One<T> = { data: T };
|
|
70
|
-
/** The API's paginated envelope. */
|
|
71
|
-
type Page<T> = { data: T[]; nextCursor: string | null };
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* The same value without any `tenantId`, at any depth. Build run and step
|
|
75
|
-
* rows carry one; the key is scoped to one tenant already, so the id is
|
|
76
|
-
* never something the model acts on. Applied to every response here, once,
|
|
77
|
-
* so no tool, resource or prompt can hand it over by forgetting to; only
|
|
78
|
-
* the key's own introspection keeps it, typed and never handed over.
|
|
79
|
-
*
|
|
80
|
-
* An explicit stack rather than recursion: a document body a person stored
|
|
81
|
-
* can nest as deep as JSON.parse allows, and a walk must not overflow where
|
|
82
|
-
* the parse did not. Every key is defined as a data property, so an own
|
|
83
|
-
* "__proto__" key stays a key instead of becoming the copy's prototype.
|
|
84
|
-
*/
|
|
85
|
-
export function withoutTenant<T>(value: T): WithoutTenant<T> {
|
|
86
|
-
const root: { out?: unknown } = {};
|
|
87
|
-
// Siblings keep their order and nested values are copied in full: see the loop.
|
|
88
|
-
const pending: { source: unknown; target: object; key: string | number }[] = [
|
|
89
|
-
{ source: value, target: root, key: 'out' },
|
|
90
|
-
];
|
|
91
|
-
while (pending.length > 0) {
|
|
92
|
-
const { source, target, key } = pending.pop()!;
|
|
93
|
-
let copy: unknown = source;
|
|
94
|
-
// Children are pushed last-first so they pop first-first: key order is
|
|
95
|
-
// meaning in a section map (the order sections render in) and must survive.
|
|
96
|
-
if (Array.isArray(source)) {
|
|
97
|
-
copy = new Array(source.length);
|
|
98
|
-
for (let index = source.length - 1; index >= 0; index -= 1) {
|
|
99
|
-
pending.push({ source: source[index], target: copy as object, key: index });
|
|
100
|
-
}
|
|
101
|
-
} else if (source !== null && typeof source === 'object') {
|
|
102
|
-
copy = {};
|
|
103
|
-
const entries = Object.entries(source as Record<string, unknown>).filter(([inner]) => inner !== 'tenantId');
|
|
104
|
-
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
105
|
-
pending.push({ source: entries[index]![1], target: copy as object, key: entries[index]![0] });
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
Object.defineProperty(target, key, { value: copy, enumerable: true, writable: true, configurable: true });
|
|
109
|
-
}
|
|
110
|
-
return root.out as WithoutTenant<T>;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export class TaigaApiError extends Error {
|
|
114
|
-
readonly status: number;
|
|
115
|
-
readonly code: string | undefined;
|
|
116
|
-
readonly apiMessage: string | undefined;
|
|
117
|
-
/** A refusal's structured reason, when the API sent one: `{ reason, ... }` (a 409's discriminator and its data). */
|
|
118
|
-
readonly details: Record<string, unknown> | undefined;
|
|
119
|
-
|
|
120
|
-
constructor(method: string, path: string, status: number, body: string) {
|
|
121
|
-
const parsed = parseErrorBody(body);
|
|
122
|
-
super(`Taiga API ${method} ${path} failed (${status}): ${parsed.message ?? body}`);
|
|
123
|
-
this.name = 'TaigaApiError';
|
|
124
|
-
this.status = status;
|
|
125
|
-
this.code = parsed.code;
|
|
126
|
-
this.apiMessage = parsed.message;
|
|
127
|
-
this.details = parsed.details;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* One path segment. Every id and type a tool passes is validated at the
|
|
133
|
-
* tool boundary; this is the second wall. Encoding keeps a slash inside the
|
|
134
|
-
* segment, and the dot segments are refused outright: encodeURIComponent
|
|
135
|
-
* leaves "." and ".." as they are, and the URL parser in fetch would then
|
|
136
|
-
* collapse `/documents/../publish` into a different route.
|
|
137
|
-
*/
|
|
138
|
-
function seg(value: string): string {
|
|
139
|
-
if (value === '' || value === '.' || value === '..') {
|
|
140
|
-
throw new TypeError(`Invalid path segment: ${JSON.stringify(value)}`);
|
|
141
|
-
}
|
|
142
|
-
return encodeURIComponent(value);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* A query string from the defined values only, prefixed with "?" when there
|
|
147
|
-
* is one. Every value goes through URLSearchParams, so nothing a tool passes
|
|
148
|
-
* can add a parameter of its own.
|
|
149
|
-
*/
|
|
150
|
-
function query(params: Record<string, string | number | boolean | null | undefined>): string {
|
|
151
|
-
const search = new URLSearchParams();
|
|
152
|
-
for (const [key, value] of Object.entries(params)) {
|
|
153
|
-
if (value !== undefined && value !== null && value !== '') search.set(key, String(value));
|
|
154
|
-
}
|
|
155
|
-
const text = search.toString();
|
|
156
|
-
return text ? `?${text}` : '';
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function parseErrorBody(body: string): { code?: string; message?: string; details?: Record<string, unknown> } {
|
|
160
|
-
try {
|
|
161
|
-
const json = JSON.parse(body) as { code?: unknown; message?: unknown; details?: unknown };
|
|
162
|
-
const message = typeof json.message === 'string' ? json.message : undefined;
|
|
163
|
-
// A 400 from route validation says only "Request validation failed";
|
|
164
|
-
// the field and the reason are in `details`, and the model needs them.
|
|
165
|
-
const details = Array.isArray(json.details)
|
|
166
|
-
? json.details
|
|
167
|
-
.map((d: { instancePath?: unknown; message?: unknown }) =>
|
|
168
|
-
[d.instancePath, d.message].filter((x) => typeof x === 'string' && x).join(' ')
|
|
169
|
-
)
|
|
170
|
-
.filter(Boolean)
|
|
171
|
-
: [];
|
|
172
|
-
// A service refusal names its gate in an object (`reason`, and what it
|
|
173
|
-
// found, such as the documents still missing); the model needs both.
|
|
174
|
-
const structured =
|
|
175
|
-
typeof json.details === 'object' && json.details !== null && !Array.isArray(json.details)
|
|
176
|
-
? (json.details as Record<string, unknown>)
|
|
177
|
-
: undefined;
|
|
178
|
-
return {
|
|
179
|
-
...(typeof json.code === 'string' ? { code: json.code } : {}),
|
|
180
|
-
...(message ? { message: details.length ? `${message}: ${details.join('; ')}` : message } : {}),
|
|
181
|
-
...(structured ? { details: structured } : {}),
|
|
182
|
-
};
|
|
183
|
-
} catch {
|
|
184
|
-
return {};
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* A tenant, factory or product document as the tools read it. `data` holds
|
|
190
|
-
* the content; policies, instructions and product documents keep it under
|
|
191
|
-
* `sections` (see tools/sections.ts). Only tenant and factory documents have a
|
|
192
|
-
* `name`; a product document is named by its type.
|
|
193
|
-
*/
|
|
194
|
-
export type TaigaDocument = WithoutTenant<TenantDocument | FactoryDocument | ProductDocument>;
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* HTTP client for the Taiga API.
|
|
198
|
-
* Used by the MCP server to proxy tool calls to the backend.
|
|
199
|
-
*/
|
|
200
|
-
export class TaigaClient {
|
|
201
|
-
private baseUrl: string;
|
|
202
|
-
private apiKey: string;
|
|
203
|
-
private introspection: ApiKeyIntrospection | null = null;
|
|
204
|
-
|
|
205
|
-
constructor(opts: { baseUrl: string; apiKey: string }) {
|
|
206
|
-
this.baseUrl = opts.baseUrl.replace(/\/+$/, '');
|
|
207
|
-
this.apiKey = opts.apiKey;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* One API call, its body typed as the route's response. The body is not
|
|
212
|
-
* validated: the server trusts the API, and `T` names what the route
|
|
213
|
-
* returns. Every `tenantId` is removed unless the caller keeps it.
|
|
214
|
-
*/
|
|
215
|
-
private request<T>(
|
|
216
|
-
method: string,
|
|
217
|
-
path: string,
|
|
218
|
-
body: unknown,
|
|
219
|
-
options: { keepTenant: true; signal?: AbortSignal | undefined }
|
|
220
|
-
): Promise<T>;
|
|
221
|
-
private request<T>(
|
|
222
|
-
method: string,
|
|
223
|
-
path: string,
|
|
224
|
-
body?: unknown,
|
|
225
|
-
options?: { signal?: AbortSignal | undefined }
|
|
226
|
-
): Promise<WithoutTenant<T>>;
|
|
227
|
-
private async request<T>(
|
|
228
|
-
method: string,
|
|
229
|
-
path: string,
|
|
230
|
-
body?: unknown,
|
|
231
|
-
options: { keepTenant?: true; signal?: AbortSignal | undefined } = {}
|
|
232
|
-
): Promise<T | WithoutTenant<T>> {
|
|
233
|
-
const url = `${this.baseUrl}/v1${path}`;
|
|
234
|
-
const headers: Record<string, string> = {
|
|
235
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
236
|
-
'User-Agent': `taiga-ai-mcp-server/${SERVER_VERSION}`,
|
|
237
|
-
};
|
|
238
|
-
// Only with a body: Fastify refuses an empty body under a JSON
|
|
239
|
-
// content type, which is what a bodiless POST (publish) would send. A
|
|
240
|
-
// form (a file upload) sets its own multipart type with its boundary.
|
|
241
|
-
const form = body instanceof FormData;
|
|
242
|
-
if (body !== undefined && !form) headers['Content-Type'] = 'application/json';
|
|
243
|
-
|
|
244
|
-
const response = await fetch(url, {
|
|
245
|
-
method,
|
|
246
|
-
headers,
|
|
247
|
-
body: body === undefined ? null : form ? body : JSON.stringify(body),
|
|
248
|
-
// A caller that must stop waiting (a wait's deadline, a cancelled call) ends the read too.
|
|
249
|
-
...(options.signal ? { signal: options.signal } : {}),
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
if (!response.ok) {
|
|
253
|
-
const text = await response.text().catch(() => '');
|
|
254
|
-
throw new TaigaApiError(method, path, response.status, text);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// A delete answers 204 with no body: there is nothing to parse.
|
|
258
|
-
if (response.status === 204) return null as T;
|
|
259
|
-
const parsed = (await response.json()) as T;
|
|
260
|
-
return options.keepTenant ? parsed : withoutTenant(parsed);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// ---------------------------------------------------------------------------
|
|
264
|
-
// Auth
|
|
265
|
-
// ---------------------------------------------------------------------------
|
|
266
|
-
|
|
267
|
-
async introspect(): Promise<ApiKeyIntrospection> {
|
|
268
|
-
// The key's own identity keeps its tenant: typed so, and never handed to the model.
|
|
269
|
-
const result = await this.request<{ data: ApiKeyIntrospection }>('GET', '/auth/introspect', undefined, {
|
|
270
|
-
keepTenant: true,
|
|
271
|
-
});
|
|
272
|
-
this.introspection = result.data;
|
|
273
|
-
return result.data;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
getIntrospection(): ApiKeyIntrospection | null {
|
|
277
|
-
return this.introspection;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
hasCapability(capability: string): boolean {
|
|
281
|
-
if (!this.introspection) return false;
|
|
282
|
-
return this.introspection.capabilities.includes(capability);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// ---------------------------------------------------------------------------
|
|
286
|
-
// Products
|
|
287
|
-
// ---------------------------------------------------------------------------
|
|
288
|
-
|
|
289
|
-
async listProducts(opts?: {
|
|
290
|
-
factoryId?: string | undefined;
|
|
291
|
-
status?: string | undefined;
|
|
292
|
-
slug?: string | undefined;
|
|
293
|
-
repository?: string | undefined;
|
|
294
|
-
repositoryHost?: string | undefined;
|
|
295
|
-
limit?: number | undefined;
|
|
296
|
-
cursor?: string | undefined;
|
|
297
|
-
}) {
|
|
298
|
-
const params = new URLSearchParams();
|
|
299
|
-
if (opts?.factoryId) params.set('factoryId', opts.factoryId);
|
|
300
|
-
if (opts?.status) params.set('status', opts.status);
|
|
301
|
-
if (opts?.slug) params.set('slug', opts.slug);
|
|
302
|
-
if (opts?.repository) params.set('repository', opts.repository);
|
|
303
|
-
if (opts?.repositoryHost) params.set('repositoryHost', opts.repositoryHost);
|
|
304
|
-
if (opts?.limit) params.set('limit', String(opts.limit));
|
|
305
|
-
if (opts?.cursor) params.set('cursor', opts.cursor);
|
|
306
|
-
const qs = params.toString();
|
|
307
|
-
return this.request<Page<Product>>('GET', `/products${qs ? `?${qs}` : ''}`);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
async getProduct(id: string, signal?: AbortSignal) {
|
|
311
|
-
return this.request<One<Product>>('GET', `/products/${seg(id)}`, undefined, { signal });
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
async listProductRepositories(productId: string) {
|
|
315
|
-
return this.request<Page<ProductRepository>>('GET', `/products/${seg(productId)}/repositories`);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// ---------------------------------------------------------------------------
|
|
319
|
-
// Documents (tenant-level policies)
|
|
320
|
-
// ---------------------------------------------------------------------------
|
|
321
|
-
|
|
322
|
-
async listTenantDocuments(opts?: { limit?: number | undefined; cursor?: string | undefined }) {
|
|
323
|
-
const params = new URLSearchParams();
|
|
324
|
-
if (opts?.limit) params.set('limit', String(opts.limit));
|
|
325
|
-
if (opts?.cursor) params.set('cursor', opts.cursor);
|
|
326
|
-
const qs = params.toString();
|
|
327
|
-
return this.request<Page<TenantDocument>>('GET', `/tenants/current/documents${qs ? `?${qs}` : ''}`);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
async getTenantDocument(type: string) {
|
|
331
|
-
return this.request<One<TenantDocument>>('GET', `/tenants/current/documents/${seg(type)}`);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/** The shared draft for a document type, or `{ data: null }` when none exists. */
|
|
335
|
-
async getTenantDocumentDraft(type: string) {
|
|
336
|
-
return this.request<One<TenantDocument | null>>('GET', `/tenants/current/documents/${seg(type)}/draft`);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
async saveTenantDocumentDraft(type: string, body: { data: Record<string, unknown> }) {
|
|
340
|
-
return this.request<One<TenantDocument>>('PUT', `/tenants/current/documents/${seg(type)}/draft`, body);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
async publishTenantDocument(type: string) {
|
|
344
|
-
return this.request<One<TenantDocument>>('POST', `/tenants/current/documents/${seg(type)}/publish`);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// ---------------------------------------------------------------------------
|
|
348
|
-
// Factory Documents
|
|
349
|
-
// ---------------------------------------------------------------------------
|
|
350
|
-
|
|
351
|
-
async getFactoryDocument(factoryId: string, type: string) {
|
|
352
|
-
return this.request<One<FactoryDocument>>('GET', `/factories/${seg(factoryId)}/documents/${seg(type)}`);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/** The shared draft for a factory document type, or `{ data: null }` when none exists. */
|
|
356
|
-
async getFactoryDocumentDraft(factoryId: string, type: string) {
|
|
357
|
-
return this.request<One<FactoryDocument | null>>(
|
|
358
|
-
'GET',
|
|
359
|
-
`/factories/${seg(factoryId)}/documents/${seg(type)}/draft`
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
async saveFactoryDocumentDraft(factoryId: string, type: string, body: { data: Record<string, unknown> }) {
|
|
364
|
-
return this.request<One<FactoryDocument>>('PUT', `/factories/${seg(factoryId)}/documents/${seg(type)}/draft`, body);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
async publishFactoryDocument(factoryId: string, type: string) {
|
|
368
|
-
return this.request<One<FactoryDocument>>('POST', `/factories/${seg(factoryId)}/documents/${seg(type)}/publish`);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// ---------------------------------------------------------------------------
|
|
372
|
-
// Product Documents
|
|
373
|
-
// ---------------------------------------------------------------------------
|
|
374
|
-
|
|
375
|
-
async listProductDocuments(productId: string, opts?: { origin?: string | undefined; limit?: number | undefined }) {
|
|
376
|
-
const params = new URLSearchParams();
|
|
377
|
-
if (opts?.origin) params.set('origin', opts.origin);
|
|
378
|
-
if (opts?.limit) params.set('limit', String(opts.limit));
|
|
379
|
-
const qs = params.toString();
|
|
380
|
-
return this.request<Page<ProductDocumentListItem>>(
|
|
381
|
-
'GET',
|
|
382
|
-
`/products/${seg(productId)}/documents${qs ? `?${qs}` : ''}`
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Stop or start the product's line. Stopping halts pulls and builds and
|
|
388
|
-
* stops the plan and build in flight; starting picks the interrupted work
|
|
389
|
-
* back up.
|
|
390
|
-
*/
|
|
391
|
-
async setLineStopped(productId: string, stopped: boolean) {
|
|
392
|
-
return this.request<One<Product>>('PUT', `/products/${seg(productId)}/line`, { stopped });
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// ---------------------------------------------------------------------------
|
|
396
|
-
// Agent jobs
|
|
397
|
-
// ---------------------------------------------------------------------------
|
|
398
|
-
|
|
399
|
-
/** Ask an agent for work: the job is queued and runs in the background. */
|
|
400
|
-
async createAgentJob(body: {
|
|
401
|
-
scope: 'product' | 'tenant';
|
|
402
|
-
type: AgentJobType;
|
|
403
|
-
targetId: string;
|
|
404
|
-
documentType?: string | undefined;
|
|
405
|
-
metadata?: Record<string, unknown> | undefined;
|
|
406
|
-
}) {
|
|
407
|
-
return this.request<One<AgentJob>>('POST', '/agent-jobs', body);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
async getAgentJob(id: string, signal?: AbortSignal) {
|
|
411
|
-
return this.request<One<AgentJob>>('GET', `/agent-jobs/${seg(id)}`, undefined, { signal });
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/** The plan-side draft of a product document, or `{ data: null }` when none exists. */
|
|
415
|
-
async getProductDocumentDraft(productId: string, type: string) {
|
|
416
|
-
return this.request<One<ProductDocument | null>>('GET', `/products/${seg(productId)}/documents/${seg(type)}/draft`);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
async saveProductDocumentDraft(productId: string, type: string, body: { data: Record<string, unknown> }) {
|
|
420
|
-
return this.request<One<ProductDocument>>('PUT', `/products/${seg(productId)}/documents/${seg(type)}/draft`, body);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
async publishProductDocument(productId: string, type: string) {
|
|
424
|
-
return this.request<PublishProductDocumentResponse>(
|
|
425
|
-
'POST',
|
|
426
|
-
`/products/${seg(productId)}/documents/${seg(type)}/publish`
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/** Mark the published version reviewed by the caller, as the Documents page shows it. */
|
|
431
|
-
async markProductDocumentReviewed(productId: string, type: string) {
|
|
432
|
-
return this.request<One<DocumentReviewState>>('POST', `/products/${seg(productId)}/documents/${seg(type)}/review`);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/** Generate remaining: every missing and outdated document, in chain order. */
|
|
436
|
-
async generateRemainingDocuments(productId: string) {
|
|
437
|
-
return this.request<One<{ jobIds: string[] }>>('POST', `/products/${seg(productId)}/documents/generate-all`);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/** Cancel a Generate remaining run still in flight. */
|
|
441
|
-
async stopGeneratingDocuments(productId: string) {
|
|
442
|
-
return this.request<One<{ cancelledJobIds: string[] }>>(
|
|
443
|
-
'POST',
|
|
444
|
-
`/products/${seg(productId)}/documents/generate-all/stop`
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
async getProductDocument(productId: string, type: string, origin?: string) {
|
|
449
|
-
const qs = origin ? `?${new URLSearchParams({ origin })}` : '';
|
|
450
|
-
return this.request<One<ProductDocument>>('GET', `/products/${seg(productId)}/documents/${seg(type)}${qs}`);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// ---------------------------------------------------------------------------
|
|
454
|
-
// Factory Documents
|
|
455
|
-
// ---------------------------------------------------------------------------
|
|
456
|
-
|
|
457
|
-
// ---------------------------------------------------------------------------
|
|
458
|
-
// Backlog
|
|
459
|
-
// ---------------------------------------------------------------------------
|
|
460
|
-
|
|
461
|
-
async listBacklogItems(
|
|
462
|
-
productId: string,
|
|
463
|
-
opts?: {
|
|
464
|
-
status?: string | undefined;
|
|
465
|
-
planningStatus?: string | undefined;
|
|
466
|
-
category?: string | undefined;
|
|
467
|
-
includeArchived?: boolean | undefined;
|
|
468
|
-
limit?: number | undefined;
|
|
469
|
-
cursor?: string | undefined;
|
|
470
|
-
}
|
|
471
|
-
) {
|
|
472
|
-
const params = new URLSearchParams();
|
|
473
|
-
if (opts?.status) params.set('status', opts.status);
|
|
474
|
-
if (opts?.planningStatus) params.set('planningStatus', opts.planningStatus);
|
|
475
|
-
if (opts?.category) params.set('category', opts.category);
|
|
476
|
-
// The route reads the literal strings 'true' and 'false'.
|
|
477
|
-
if (opts?.includeArchived) params.set('includeArchived', 'true');
|
|
478
|
-
if (opts?.limit) params.set('limit', String(opts.limit));
|
|
479
|
-
if (opts?.cursor) params.set('cursor', opts.cursor);
|
|
480
|
-
const qs = params.toString();
|
|
481
|
-
return this.request<Page<BacklogItem>>('GET', `/products/${seg(productId)}/backlog${qs ? `?${qs}` : ''}`);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
async getBacklogItem(itemId: string, signal?: AbortSignal) {
|
|
485
|
-
return this.request<One<BacklogItem>>('GET', `/backlog/${seg(itemId)}`, undefined, { signal });
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/** The initiative's history, oldest first: every turn event and audited move, each with who did it. */
|
|
489
|
-
async listBacklogItemActivity(itemId: string) {
|
|
490
|
-
return this.request<{ data: ActivityEvent[] }>('GET', `/backlog/${seg(itemId)}/activity`);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/** A person's move on the line board: the column dropped into, and what to follow there. */
|
|
494
|
-
async transitionInitiative(
|
|
495
|
-
itemId: string,
|
|
496
|
-
body: {
|
|
497
|
-
state: string;
|
|
498
|
-
afterId?: string | null | undefined;
|
|
499
|
-
archived?: boolean | undefined;
|
|
500
|
-
force?: boolean | undefined;
|
|
501
|
-
}
|
|
502
|
-
) {
|
|
503
|
-
return this.request<One<BacklogItem>>('PUT', `/backlog/${seg(itemId)}/state`, body);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/** Answer the needs-human flag: plan again, resume the run, or clear a refused start. */
|
|
507
|
-
async resumeInitiative(itemId: string) {
|
|
508
|
-
return this.request<One<BacklogItem>>('POST', `/backlog/${seg(itemId)}/resume`);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/** The initiative's own review gate: true builds a landed plan at once, false stops for a person, null follows the product. */
|
|
512
|
-
async setAutoBuild(itemId: string, autoBuild: boolean | null) {
|
|
513
|
-
return this.request<One<BacklogItem>>('PUT', `/backlog/${seg(itemId)}/auto-build`, { autoBuild });
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/** The product's live queue, in order. */
|
|
517
|
-
async listQueue(productId: string) {
|
|
518
|
-
return this.request<{ data: QueueEntry[] }>('GET', `/products/${seg(productId)}/queue`);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
async getActivePlan(itemId: string) {
|
|
522
|
-
return this.request<One<BacklogItemPlanWithSteps>>('GET', `/backlog/${seg(itemId)}/plans/active`);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
async approvePlan(planId: string) {
|
|
526
|
-
return this.request<One<BacklogItem>>('POST', `/plans/${seg(planId)}/approve`);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
async rejectPlan(planId: string, body: { comment: string }) {
|
|
530
|
-
return this.request<One<BacklogItem>>('POST', `/plans/${seg(planId)}/reject`, body);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// ---------------------------------------------------------------------------
|
|
534
|
-
// Build runs
|
|
535
|
-
// ---------------------------------------------------------------------------
|
|
536
|
-
|
|
537
|
-
async listBuildRuns(
|
|
538
|
-
productId: string,
|
|
539
|
-
opts?: { backlogItemId?: string | undefined; limit?: number | undefined; cursor?: string | undefined }
|
|
540
|
-
) {
|
|
541
|
-
const params = new URLSearchParams();
|
|
542
|
-
if (opts?.backlogItemId) params.set('backlogItemId', opts.backlogItemId);
|
|
543
|
-
if (opts?.limit) params.set('limit', String(opts.limit));
|
|
544
|
-
if (opts?.cursor) params.set('cursor', opts.cursor);
|
|
545
|
-
const qs = params.toString();
|
|
546
|
-
return this.request<Page<BuildRunWithContext>>('GET', `/products/${seg(productId)}/builds${qs ? `?${qs}` : ''}`);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/** The runs of every product the key can see, newest first; `active` narrows to the ones executing now. */
|
|
550
|
-
async listBuildRunActivity(opts?: {
|
|
551
|
-
active?: boolean | undefined;
|
|
552
|
-
limit?: number | undefined;
|
|
553
|
-
cursor?: string | undefined;
|
|
554
|
-
}) {
|
|
555
|
-
const params = new URLSearchParams();
|
|
556
|
-
// The route reads the literal strings 'true' and 'false'.
|
|
557
|
-
if (opts?.active) params.set('active', 'true');
|
|
558
|
-
if (opts?.limit) params.set('limit', String(opts.limit));
|
|
559
|
-
if (opts?.cursor) params.set('cursor', opts.cursor);
|
|
560
|
-
const qs = params.toString();
|
|
561
|
-
return this.request<Page<BuildRunWithContext>>('GET', `/builds${qs ? `?${qs}` : ''}`);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
async getBuildRun(id: string, signal?: AbortSignal) {
|
|
565
|
-
return this.request<One<BuildRunWithContext>>('GET', `/builds/${seg(id)}`, undefined, { signal });
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
async stopBuildRun(id: string) {
|
|
569
|
-
return this.request<One<BuildRun>>('POST', `/builds/${seg(id)}/stop`);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
async listBuildRunSteps(id: string) {
|
|
573
|
-
return this.request<{ data: BuildRunStep[] }>('GET', `/builds/${seg(id)}/steps`);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// ---------------------------------------------------------------------------
|
|
577
|
-
// Maintaining
|
|
578
|
-
// ---------------------------------------------------------------------------
|
|
579
|
-
|
|
580
|
-
async getMaintenanceSummary(productId: string) {
|
|
581
|
-
return this.request<One<MaintenanceSummary>>('GET', `/products/${seg(productId)}/maintenance/summary`);
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
async listMaintenanceSweeps(productId: string, opts?: { limit?: number | undefined }) {
|
|
585
|
-
return this.request<Page<MaintenanceRun>>(
|
|
586
|
-
'GET',
|
|
587
|
-
`/products/${seg(productId)}/maintenance/runs${query({ limit: opts?.limit })}`
|
|
588
|
-
);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
async listFindings(
|
|
592
|
-
productId: string,
|
|
593
|
-
opts?: {
|
|
594
|
-
status?: string | undefined;
|
|
595
|
-
severity?: string | undefined;
|
|
596
|
-
kind?: string | undefined;
|
|
597
|
-
limit?: number | undefined;
|
|
598
|
-
cursor?: string | undefined;
|
|
599
|
-
}
|
|
600
|
-
) {
|
|
601
|
-
return this.request<Page<MaintenanceFinding> & { totalCount?: number }>(
|
|
602
|
-
'GET',
|
|
603
|
-
`/products/${seg(productId)}/maintenance/findings${query({ ...opts })}`
|
|
604
|
-
);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
async getFinding(id: string) {
|
|
608
|
-
return this.request<One<MaintenanceFindingDetail>>('GET', `/maintenance/findings/${seg(id)}`);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
async updateFinding(
|
|
612
|
-
id: string,
|
|
613
|
-
body: {
|
|
614
|
-
status: 'open' | 'ignored';
|
|
615
|
-
ignoreReason?: string | undefined;
|
|
616
|
-
ignoreNote?: string | null | undefined;
|
|
617
|
-
ignoreExpiresAt?: string | null | undefined;
|
|
618
|
-
}
|
|
619
|
-
) {
|
|
620
|
-
return this.request<One<MaintenanceFinding>>('PATCH', `/maintenance/findings/${seg(id)}`, body);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
async remediateFinding(productId: string, findingId: string) {
|
|
624
|
-
return this.request<One<{ finding: MaintenanceFinding; backlogItemId: string }>>(
|
|
625
|
-
'POST',
|
|
626
|
-
`/products/${seg(productId)}/maintenance/findings/${seg(findingId)}/remediate`,
|
|
627
|
-
{}
|
|
628
|
-
);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
async remediatePackage(
|
|
632
|
-
productId: string,
|
|
633
|
-
body: {
|
|
634
|
-
ecosystem: string;
|
|
635
|
-
packageName: string;
|
|
636
|
-
currentVersion?: string | null | undefined;
|
|
637
|
-
productRepositoryId?: string | null | undefined;
|
|
638
|
-
}
|
|
639
|
-
) {
|
|
640
|
-
return this.request<One<{ backlogItemId: string; findingIds: string[] }>>(
|
|
641
|
-
'POST',
|
|
642
|
-
`/products/${seg(productId)}/maintenance/packages/remediate`,
|
|
643
|
-
body
|
|
644
|
-
);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
async getPolicyStanding(productId: string) {
|
|
648
|
-
return this.request<One<PolicyConformance>>('GET', `/products/${seg(productId)}/maintenance/policies`);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
async remediatePolicyControl(productId: string, controlId: string, body: PolicyRemediationInput = {}) {
|
|
652
|
-
return this.request<One<PolicyRemediationResult>>(
|
|
653
|
-
'POST',
|
|
654
|
-
`/products/${seg(productId)}/maintenance/policies/controls/${seg(controlId)}/remediate`,
|
|
655
|
-
body
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
async remediatePolicyViolation(productId: string, divergenceId: string, body: PolicyRemediationInput = {}) {
|
|
660
|
-
return this.request<One<PolicyRemediationResult>>(
|
|
661
|
-
'POST',
|
|
662
|
-
`/products/${seg(productId)}/maintenance/policies/divergences/${seg(divergenceId)}/remediate`,
|
|
663
|
-
body
|
|
664
|
-
);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
// ---------------------------------------------------------------------------
|
|
668
|
-
// Action Required
|
|
669
|
-
// ---------------------------------------------------------------------------
|
|
670
|
-
|
|
671
|
-
async listInbox(opts: {
|
|
672
|
-
productId?: string | undefined;
|
|
673
|
-
category?: string | undefined;
|
|
674
|
-
resolved?: boolean | undefined;
|
|
675
|
-
withTotal?: boolean | undefined;
|
|
676
|
-
limit?: number | undefined;
|
|
677
|
-
cursor?: string | undefined;
|
|
678
|
-
}) {
|
|
679
|
-
const { productId, ...rest } = opts;
|
|
680
|
-
const path = productId ? `/products/${seg(productId)}/inbox` : '/inbox';
|
|
681
|
-
return this.request<Page<InboxItem> & { totalCount?: number }>('GET', `${path}${query({ ...rest })}`);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
async updateInboxItem(
|
|
685
|
-
id: string,
|
|
686
|
-
body: { status?: 'in_progress' | 'done' | 'dismissed' | undefined; snoozedUntil?: string | null | undefined }
|
|
687
|
-
) {
|
|
688
|
-
return this.request<One<InboxItem>>('PATCH', `/inbox/${seg(id)}`, body);
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
async listSetupTasks(opts: {
|
|
692
|
-
productId?: string | undefined;
|
|
693
|
-
openOnly?: boolean | undefined;
|
|
694
|
-
withTotal?: boolean | undefined;
|
|
695
|
-
limit?: number | undefined;
|
|
696
|
-
cursor?: string | undefined;
|
|
697
|
-
}) {
|
|
698
|
-
const { productId, ...rest } = opts;
|
|
699
|
-
const path = productId ? `/products/${seg(productId)}/setup-tasks` : '/setup-tasks';
|
|
700
|
-
return this.request<Page<SetupTaskListEntry> & { totalCount?: number }>('GET', `${path}${query({ ...rest })}`);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// ---------------------------------------------------------------------------
|
|
704
|
-
// Deployments, environments and monitoring (observed, never driven)
|
|
705
|
-
// ---------------------------------------------------------------------------
|
|
706
|
-
|
|
707
|
-
async listEnvironments(productId: string, opts?: { limit?: number | undefined; cursor?: string | undefined }) {
|
|
708
|
-
return this.request<Page<Environment>>('GET', `/products/${seg(productId)}/environments${query({ ...opts })}`);
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
async createEnvironment(productId: string, body: { name: string; type: string }) {
|
|
712
|
-
return this.request<One<Environment>>('POST', `/products/${seg(productId)}/environments`, body);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
async updateEnvironment(
|
|
716
|
-
id: string,
|
|
717
|
-
body: { name?: string | undefined; type?: string | undefined; url?: string | null | undefined }
|
|
718
|
-
) {
|
|
719
|
-
return this.request<One<Environment>>('PATCH', `/environments/${seg(id)}`, body);
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
async getCurrentDeployments(productId: string) {
|
|
723
|
-
return this.request<One<CurrentDeployments>>('GET', `/products/${seg(productId)}/deployments/current`);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
async listDeployments(
|
|
727
|
-
productId: string,
|
|
728
|
-
opts?: { environmentId?: string | undefined; limit?: number | undefined; cursor?: string | undefined }
|
|
729
|
-
) {
|
|
730
|
-
return this.request<Page<Deployment>>('GET', `/products/${seg(productId)}/deployments${query({ ...opts })}`);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
async getDeploymentSyncStatus(productId: string) {
|
|
734
|
-
return this.request<One<DeploymentSyncStatusView>>('GET', `/products/${seg(productId)}/deployments/sync-status`);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
async getDeliveryMetrics(productId: string, environmentId: string, windowDays: number) {
|
|
738
|
-
return this.request<One<DeploymentInsights>>(
|
|
739
|
-
'GET',
|
|
740
|
-
`/products/${seg(productId)}/deployments/insights${query({ environmentId, windowDays })}`
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
async getMonitoringHealth(productId: string) {
|
|
745
|
-
return this.request<{ data: EnvironmentHealth[] }>('GET', `/products/${seg(productId)}/monitoring/health`);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
async getMonitoringSignal<S extends keyof MonitoringSignals>(
|
|
749
|
-
productId: string,
|
|
750
|
-
signal: S,
|
|
751
|
-
environmentId: string,
|
|
752
|
-
windowDays: number
|
|
753
|
-
) {
|
|
754
|
-
return this.request<One<MonitoringSignals[S]>>(
|
|
755
|
-
'GET',
|
|
756
|
-
`/products/${seg(productId)}/monitoring/${signal}${query({ environmentId, windowDays })}`
|
|
757
|
-
);
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// ---------------------------------------------------------------------------
|
|
761
|
-
// Knowledge
|
|
762
|
-
// ---------------------------------------------------------------------------
|
|
763
|
-
|
|
764
|
-
async listKnowledge(opts: {
|
|
765
|
-
factoryId?: string | null | undefined;
|
|
766
|
-
productId?: string | null | undefined;
|
|
767
|
-
limit?: number | undefined;
|
|
768
|
-
cursor?: string | undefined;
|
|
769
|
-
}) {
|
|
770
|
-
// The route reads the literal 'null' as "this level only, not below it".
|
|
771
|
-
const scope = (value: string | null | undefined) => (value === null ? 'null' : value);
|
|
772
|
-
return this.request<Page<CustomDocumentDto>>(
|
|
773
|
-
'GET',
|
|
774
|
-
`/custom-documents${query({ ...opts, factoryId: scope(opts.factoryId), productId: scope(opts.productId) })}`
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
async getKnowledge(id: string) {
|
|
779
|
-
return this.request<One<CustomDocumentDto>>('GET', `/custom-documents/${seg(id)}`);
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
async getKnowledgeDownload(id: string) {
|
|
783
|
-
return this.request<One<CustomDocumentDownloadView>>('GET', `/custom-documents/${seg(id)}/download`);
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
/** A text file filed as knowledge at one level: the organization, a factory, or a product in its factory. */
|
|
787
|
-
async addKnowledge(file: {
|
|
788
|
-
fileName: string;
|
|
789
|
-
mime: 'text/markdown' | 'text/plain';
|
|
790
|
-
content: string;
|
|
791
|
-
factoryId?: string | undefined;
|
|
792
|
-
productId?: string | undefined;
|
|
793
|
-
}) {
|
|
794
|
-
const form = new FormData();
|
|
795
|
-
// Fields before the file: the route reads them in any order, but the
|
|
796
|
-
// order a person would read them in costs nothing.
|
|
797
|
-
if (file.factoryId) form.set('factoryId', file.factoryId);
|
|
798
|
-
if (file.productId) form.set('productId', file.productId);
|
|
799
|
-
form.set('file', new Blob([file.content], { type: file.mime }), file.fileName);
|
|
800
|
-
return this.request<{ data: CustomDocumentDto; deduplicated: boolean }>('POST', '/custom-documents', form);
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
async retryKnowledge(id: string) {
|
|
804
|
-
return this.request<One<CustomDocumentDto>>('POST', `/custom-documents/${seg(id)}/retry`);
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
async deleteKnowledge(id: string) {
|
|
808
|
-
return this.request<null>('DELETE', `/custom-documents/${seg(id)}`);
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
// ---------------------------------------------------------------------------
|
|
812
|
-
// Autonomy
|
|
813
|
-
// ---------------------------------------------------------------------------
|
|
814
|
-
|
|
815
|
-
async updateProduct(
|
|
816
|
-
productId: string,
|
|
817
|
-
body: {
|
|
818
|
-
autoMerge?: boolean | null | undefined;
|
|
819
|
-
autoBuild?: boolean | undefined;
|
|
820
|
-
name?: string | undefined;
|
|
821
|
-
ownsInfrastructure?: boolean | null | undefined;
|
|
822
|
-
ownsDeployment?: boolean | null | undefined;
|
|
823
|
-
deploymentNotes?: string | null | undefined;
|
|
824
|
-
emitDesignStandards?: boolean | null | undefined;
|
|
825
|
-
}
|
|
826
|
-
) {
|
|
827
|
-
return this.request<One<Product>>('PATCH', `/products/${seg(productId)}`, body);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
async updateBacklogItem(itemId: string, body: { autoMerge?: boolean | null | undefined }) {
|
|
831
|
-
return this.request<One<BacklogItem>>('PATCH', `/backlog/${seg(itemId)}`, body);
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// ---------------------------------------------------------------------------
|
|
835
|
-
// Factories
|
|
836
|
-
// ---------------------------------------------------------------------------
|
|
837
|
-
|
|
838
|
-
async getFactory(id: string) {
|
|
839
|
-
return this.request<One<Factory>>('GET', `/factories/${seg(id)}`);
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
async listFactories(opts?: { limit?: number | undefined; cursor?: string | undefined }) {
|
|
843
|
-
const params = new URLSearchParams();
|
|
844
|
-
if (opts?.limit) params.set('limit', String(opts.limit));
|
|
845
|
-
if (opts?.cursor) params.set('cursor', opts.cursor);
|
|
846
|
-
const qs = params.toString();
|
|
847
|
-
return this.request<Page<Factory>>('GET', `/factories${qs ? `?${qs}` : ''}`);
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
async createFactory(body: CreateFactoryInput) {
|
|
851
|
-
return this.request<One<Factory>>('POST', '/factories', body);
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
async updateFactory(factoryId: string, body: { name?: string | undefined }) {
|
|
855
|
-
return this.request<One<Factory>>('PATCH', `/factories/${seg(factoryId)}`, body);
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
// ---------------------------------------------------------------------------
|
|
859
|
-
// Setting a product up
|
|
860
|
-
// ---------------------------------------------------------------------------
|
|
861
|
-
|
|
862
|
-
async createProduct(
|
|
863
|
-
body: Pick<
|
|
864
|
-
CreateProductInput,
|
|
865
|
-
| 'name'
|
|
866
|
-
| 'factoryId'
|
|
867
|
-
| 'projectType'
|
|
868
|
-
| 'ownsInfrastructure'
|
|
869
|
-
| 'ownsDeployment'
|
|
870
|
-
| 'deploymentNotes'
|
|
871
|
-
| 'refuseDuplicateName'
|
|
872
|
-
>
|
|
873
|
-
) {
|
|
874
|
-
return this.request<One<Product>>('POST', '/products', body);
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
async previewProductMove(productId: string, factoryId: string) {
|
|
878
|
-
return this.request<One<FactoryMovePreview>>(
|
|
879
|
-
'GET',
|
|
880
|
-
`/products/${seg(productId)}/factory/preview${query({ factoryId })}`
|
|
881
|
-
);
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
async moveProduct(productId: string, body: { factoryId: string; stopLine?: boolean | undefined }) {
|
|
885
|
-
return this.request<MoveProductResult>('PUT', `/products/${seg(productId)}/factory`, body);
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
async startImport(productId: string) {
|
|
889
|
-
return this.request<One<{ jobId: string }>>('POST', `/products/${seg(productId)}/brownfield-import`);
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
async refreshRealityDocuments(productId: string, docTypes?: readonly string[] | undefined) {
|
|
893
|
-
return this.request<One<{ jobId: string }>>(
|
|
894
|
-
'POST',
|
|
895
|
-
`/products/${seg(productId)}/documents/refresh-reality`,
|
|
896
|
-
docTypes ? { docTypes } : undefined
|
|
897
|
-
);
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
/** What still blocks finishing setup: required documents unpublished, required checks unstamped (PRO-2800). */
|
|
901
|
-
async getProductSetupBlockers(productId: string) {
|
|
902
|
-
return this.request<{
|
|
903
|
-
data: {
|
|
904
|
-
inSetup: boolean;
|
|
905
|
-
missingDocuments: string[];
|
|
906
|
-
openChecks: { templateName: string; itemKey: string; criterion: string }[];
|
|
907
|
-
};
|
|
908
|
-
}>('GET', `/products/${seg(productId)}/setup`);
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
async completeSetup(productId: string) {
|
|
912
|
-
return this.request<One<Product>>('POST', `/products/${seg(productId)}/setup/complete`);
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
async reopenSetup(productId: string) {
|
|
916
|
-
return this.request<One<Product>>('POST', `/products/${seg(productId)}/setup/reopen`);
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
// ---------------------------------------------------------------------------
|
|
920
|
-
// Repositories
|
|
921
|
-
// ---------------------------------------------------------------------------
|
|
922
|
-
|
|
923
|
-
/** Every connected GitHub account, page by page: a later account must be findable by its id too. */
|
|
924
|
-
async listGithubInstallations() {
|
|
925
|
-
const data: WithoutTenant<GithubInstallation>[] = [];
|
|
926
|
-
let cursor: string | undefined;
|
|
927
|
-
do {
|
|
928
|
-
const result = await this.request<Page<GithubInstallation>>(
|
|
929
|
-
'GET',
|
|
930
|
-
`/github/installations${query({ limit: 100, cursor })}`
|
|
931
|
-
);
|
|
932
|
-
data.push(...result.data);
|
|
933
|
-
cursor = result.nextCursor ?? undefined;
|
|
934
|
-
} while (cursor);
|
|
935
|
-
return { data };
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
async listInstallationRepositories(
|
|
939
|
-
installationId: string,
|
|
940
|
-
opts: { q?: string | undefined; limit?: number | undefined; cursor?: string | undefined }
|
|
941
|
-
) {
|
|
942
|
-
return this.request<Page<GithubRepository>>(
|
|
943
|
-
'GET',
|
|
944
|
-
`/github/installations/${seg(installationId)}/repositories${query({ ...opts })}`
|
|
945
|
-
);
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
async linkRepository(
|
|
949
|
-
productId: string,
|
|
950
|
-
body: { githubRepositoryId: string; replacesRepositoryId?: string | undefined }
|
|
951
|
-
) {
|
|
952
|
-
return this.request<One<ProductRepository>>('POST', `/products/${seg(productId)}/repositories`, body);
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
async updateRepository(
|
|
956
|
-
productId: string,
|
|
957
|
-
repositoryId: string,
|
|
958
|
-
body: { baseBranch?: string | null | undefined; label?: string | null | undefined }
|
|
959
|
-
) {
|
|
960
|
-
return this.request<One<ProductRepositoryWithProtection>>(
|
|
961
|
-
'PATCH',
|
|
962
|
-
`/products/${seg(productId)}/repositories/${seg(repositoryId)}`,
|
|
963
|
-
body
|
|
964
|
-
);
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
async listRepositoryBranches(productId: string, repositoryId: string) {
|
|
968
|
-
return this.request<{ data: RepositoryBranch[]; truncated: boolean }>(
|
|
969
|
-
'GET',
|
|
970
|
-
`/products/${seg(productId)}/repositories/${seg(repositoryId)}/branches`
|
|
971
|
-
);
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
async unlinkRepository(productId: string, repositoryId: string) {
|
|
975
|
-
return this.request<null>('DELETE', `/products/${seg(productId)}/repositories/${seg(repositoryId)}`);
|
|
976
|
-
}
|
|
977
|
-
}
|