@prenta/mcp-server 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +583 -0
- package/LICENSE +21 -0
- package/README.md +130 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +44 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +1348 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1019 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/instructions.d.ts +13 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +61 -0
- package/dist/instructions.js.map +1 -0
- package/dist/navigation.d.ts +76 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/navigation.js +228 -0
- package/dist/navigation.js.map +1 -0
- package/dist/resources.d.ts +28 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +88 -0
- package/dist/resources.js.map +1 -0
- package/dist/server.d.ts +25 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +90 -0
- package/dist/server.js.map +1 -0
- package/dist/tools.d.ts +25 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +1920 -0
- package/dist/tools.js.map +1 -0
- package/package.json +43 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,1348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin HTTP client for the Prenta CMS REST API. We deliberately don't depend
|
|
3
|
+
* on @prenta/core here — that would force the MCP server to bring
|
|
4
|
+
* in Prisma, all the server-side modules, and a Next.js runtime context. The
|
|
5
|
+
* MCP server only needs to translate MCP tool calls into HTTP requests.
|
|
6
|
+
*/
|
|
7
|
+
export interface PrentaClientOptions {
|
|
8
|
+
/** Base URL of the CMS, e.g. `https://maidpro.co`. The /api/cms path is appended automatically. */
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
/** API key (`act_sk_...`). */
|
|
11
|
+
apiKey: string;
|
|
12
|
+
/** Optional fetch implementation override (used in tests). */
|
|
13
|
+
fetchImpl?: typeof fetch;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Thrown by `PrentaClient` on non-2xx responses. Carries the HTTP
|
|
17
|
+
* `status` so callers (e.g. `list_collections`'s OpenAPI fallback) can
|
|
18
|
+
* branch on the status code rather than pattern-matching the error
|
|
19
|
+
* message — pattern matching was rejected by review on PR #155 because
|
|
20
|
+
* a real 404 might omit the literal substring "not found" and an
|
|
21
|
+
* unrelated 5xx response might include it.
|
|
22
|
+
*/
|
|
23
|
+
export declare class PrentaClientError extends Error {
|
|
24
|
+
readonly status: number;
|
|
25
|
+
constructor(message: string, status: number);
|
|
26
|
+
}
|
|
27
|
+
export declare class PrentaClient {
|
|
28
|
+
readonly baseUrl: string;
|
|
29
|
+
readonly apiKey: string;
|
|
30
|
+
private readonly fetchImpl;
|
|
31
|
+
constructor(opts: PrentaClientOptions);
|
|
32
|
+
private headers;
|
|
33
|
+
private request;
|
|
34
|
+
/**
|
|
35
|
+
* POST a `multipart/form-data` body. Distinct from `request()` because:
|
|
36
|
+
*
|
|
37
|
+
* - we MUST NOT set `Content-Type` (the fetch implementation appends the
|
|
38
|
+
* multipart boundary; if we override it the server can't parse the body),
|
|
39
|
+
* - we MUST NOT `JSON.stringify(form)` — a `FormData` is sent verbatim,
|
|
40
|
+
* - the upload handler in `cms-core` rejects requests without a valid
|
|
41
|
+
* `Content-Length` header, and Node 20+ / undici populate that header
|
|
42
|
+
* automatically when the body is a `FormData`.
|
|
43
|
+
*
|
|
44
|
+
* Used exclusively by `uploadMedia` today. Kept as a thin private helper so
|
|
45
|
+
* the failure mode (multipart parse error vs JSON parse error vs HTTP
|
|
46
|
+
* error) stays observable to the caller.
|
|
47
|
+
*/
|
|
48
|
+
private requestMultipart;
|
|
49
|
+
listDocuments(collection: string, query?: {
|
|
50
|
+
page?: number;
|
|
51
|
+
pageSize?: number;
|
|
52
|
+
status?: string;
|
|
53
|
+
search?: string;
|
|
54
|
+
slug?: string;
|
|
55
|
+
}): Promise<{
|
|
56
|
+
data: {
|
|
57
|
+
docs: any[];
|
|
58
|
+
total: number;
|
|
59
|
+
page: number;
|
|
60
|
+
pageSize: number;
|
|
61
|
+
};
|
|
62
|
+
}>;
|
|
63
|
+
getDocument(collection: string, id: string): Promise<{
|
|
64
|
+
data: any;
|
|
65
|
+
}>;
|
|
66
|
+
createDocument(collection: string, data: Record<string, unknown>): Promise<{
|
|
67
|
+
data: any;
|
|
68
|
+
}>;
|
|
69
|
+
updateDocument(collection: string, id: string, data: Record<string, unknown>): Promise<{
|
|
70
|
+
data: any;
|
|
71
|
+
}>;
|
|
72
|
+
deleteDocument(collection: string, id: string): Promise<{
|
|
73
|
+
data: {
|
|
74
|
+
deleted: boolean;
|
|
75
|
+
};
|
|
76
|
+
}>;
|
|
77
|
+
getGlobal(slug: string): Promise<{
|
|
78
|
+
data: any;
|
|
79
|
+
}>;
|
|
80
|
+
updateGlobal(slug: string, data: Record<string, unknown>): Promise<{
|
|
81
|
+
data: any;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* List media assets. The `GET /media` endpoint returns the asset array under
|
|
85
|
+
* `items`/`data` (and pagination fields), never `docs`. We normalize it into
|
|
86
|
+
* the same `{ data: { docs, total, page, pageSize, totalPages } }` envelope
|
|
87
|
+
* as `listDocuments` so the `list_media` MCP tool output is consistent with
|
|
88
|
+
* `list_documents` and an agent can read `docs` uniformly.
|
|
89
|
+
*/
|
|
90
|
+
listMedia(query?: {
|
|
91
|
+
page?: number;
|
|
92
|
+
pageSize?: number;
|
|
93
|
+
}): Promise<{
|
|
94
|
+
data: {
|
|
95
|
+
docs: any[];
|
|
96
|
+
total: number;
|
|
97
|
+
page: number;
|
|
98
|
+
pageSize: number;
|
|
99
|
+
totalPages: number;
|
|
100
|
+
};
|
|
101
|
+
}>;
|
|
102
|
+
/**
|
|
103
|
+
* Upload a binary asset to the CMS media library. Bytes are accepted as a
|
|
104
|
+
* `Uint8Array` / `Buffer` (the MCP tool wrapper handles base64 decoding
|
|
105
|
+
* from the JSON-RPC layer, but the client surface stays binary).
|
|
106
|
+
*
|
|
107
|
+
* The server enforces a 50 MB hard cap and validates magic bytes against
|
|
108
|
+
* the declared `mimeType` — uploading a `.exe` with `image/png` will be
|
|
109
|
+
* rejected with HTTP 415. SVGs are sanitised server-side. Pass
|
|
110
|
+
* `skipOptimize: true` to keep the original bytes; by default JPEG / PNG
|
|
111
|
+
* are recompressed to WebP.
|
|
112
|
+
*/
|
|
113
|
+
uploadMedia(input: {
|
|
114
|
+
filename: string;
|
|
115
|
+
mimeType: string;
|
|
116
|
+
bytes: Uint8Array | Buffer;
|
|
117
|
+
skipOptimize?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* When true, skip content-hash dedupe and always create a new record
|
|
120
|
+
* even if identical bytes already exist (#718).
|
|
121
|
+
*/
|
|
122
|
+
forceUpload?: boolean;
|
|
123
|
+
}): Promise<{
|
|
124
|
+
data: any;
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* Update media metadata. Only the supplied fields are touched. `alt` maps
|
|
128
|
+
* to the DB column `altText`; `focalX` / `focalY` (range 0..1) drive
|
|
129
|
+
* smart-crop on the front end. `caption`, `description`, and `focusKeyword`
|
|
130
|
+
* round out the fields `calculateMediaSeoScore` reads, so an agent can take
|
|
131
|
+
* an asset all the way to a passing media SEO grade.
|
|
132
|
+
*/
|
|
133
|
+
updateMedia(id: string, body: {
|
|
134
|
+
alt?: string;
|
|
135
|
+
title?: string;
|
|
136
|
+
filename?: string;
|
|
137
|
+
caption?: string;
|
|
138
|
+
description?: string;
|
|
139
|
+
focusKeyword?: string;
|
|
140
|
+
focalX?: number;
|
|
141
|
+
focalY?: number;
|
|
142
|
+
/** WCAG decorative image — empty alt is intentional (#714). */
|
|
143
|
+
isDecorative?: boolean;
|
|
144
|
+
}): Promise<{
|
|
145
|
+
data: any;
|
|
146
|
+
}>;
|
|
147
|
+
/**
|
|
148
|
+
* Vision-backed alt text for a media library image. Requires the AI plugin,
|
|
149
|
+
* a vision-capable provider, and `ai.features.mediaAltText` enabled.
|
|
150
|
+
* Returns a suggestion — call `update_media({ alt })` to publish it.
|
|
151
|
+
*/
|
|
152
|
+
generateMediaAlt(body: {
|
|
153
|
+
mediaId?: string;
|
|
154
|
+
imageUrl?: string;
|
|
155
|
+
pageTitle?: string;
|
|
156
|
+
surroundingText?: string;
|
|
157
|
+
purpose?: 'decorative' | 'informative' | 'functional';
|
|
158
|
+
}): Promise<{
|
|
159
|
+
data: {
|
|
160
|
+
alt: string;
|
|
161
|
+
title: string;
|
|
162
|
+
alternatives: string[];
|
|
163
|
+
isDecorative: boolean;
|
|
164
|
+
confidence: number;
|
|
165
|
+
usedVision: boolean;
|
|
166
|
+
};
|
|
167
|
+
}>;
|
|
168
|
+
/** Delete a media item AND its underlying blob storage object (best-effort). */
|
|
169
|
+
deleteMedia(id: string): Promise<{
|
|
170
|
+
data: {
|
|
171
|
+
success: boolean;
|
|
172
|
+
};
|
|
173
|
+
}>;
|
|
174
|
+
/**
|
|
175
|
+
* Bulk-update media metadata. Per-item patches (not a shared patch). Returns
|
|
176
|
+
* per-id success/failure so a large backfill can land partial results (#717).
|
|
177
|
+
*/
|
|
178
|
+
bulkUpdateMedia(items: Array<{
|
|
179
|
+
id: string;
|
|
180
|
+
alt?: string;
|
|
181
|
+
title?: string;
|
|
182
|
+
filename?: string;
|
|
183
|
+
caption?: string;
|
|
184
|
+
description?: string;
|
|
185
|
+
focusKeyword?: string;
|
|
186
|
+
focalX?: number;
|
|
187
|
+
focalY?: number;
|
|
188
|
+
isDecorative?: boolean;
|
|
189
|
+
}>): Promise<{
|
|
190
|
+
data: {
|
|
191
|
+
updated: number;
|
|
192
|
+
failed: number;
|
|
193
|
+
results: Array<{
|
|
194
|
+
id: string;
|
|
195
|
+
ok: true;
|
|
196
|
+
data: any;
|
|
197
|
+
} | {
|
|
198
|
+
id: string;
|
|
199
|
+
ok: false;
|
|
200
|
+
error: string;
|
|
201
|
+
status?: number;
|
|
202
|
+
}>;
|
|
203
|
+
};
|
|
204
|
+
}>;
|
|
205
|
+
/**
|
|
206
|
+
* Bulk-delete media items and their blobs (best-effort). Requires
|
|
207
|
+
* `confirm: true` at the MCP tool layer; the API also requires it.
|
|
208
|
+
*/
|
|
209
|
+
bulkDeleteMedia(ids: string[], confirm: true): Promise<{
|
|
210
|
+
data: {
|
|
211
|
+
deleted: number;
|
|
212
|
+
failed: number;
|
|
213
|
+
results: Array<{
|
|
214
|
+
id: string;
|
|
215
|
+
ok: true;
|
|
216
|
+
} | {
|
|
217
|
+
id: string;
|
|
218
|
+
ok: false;
|
|
219
|
+
error: string;
|
|
220
|
+
status?: number;
|
|
221
|
+
}>;
|
|
222
|
+
};
|
|
223
|
+
}>;
|
|
224
|
+
/**
|
|
225
|
+
* Recompress an existing image to WebP. Returns the updated media row plus
|
|
226
|
+
* an `optimization` summary (`{ originalSize, optimizedSize, savings, ... }`).
|
|
227
|
+
* Already-WebP files and non-images short-circuit with `alreadyOptimized: true`.
|
|
228
|
+
*/
|
|
229
|
+
optimizeMedia(id: string): Promise<{
|
|
230
|
+
data: any;
|
|
231
|
+
}>;
|
|
232
|
+
/**
|
|
233
|
+
* Move a media item into a folder, or out of every folder by passing
|
|
234
|
+
* `folderId: null`. The server validates that the target folder lives in
|
|
235
|
+
* the `media` scope.
|
|
236
|
+
*/
|
|
237
|
+
moveMediaToFolder(id: string, folderId: string | null): Promise<{
|
|
238
|
+
data: {
|
|
239
|
+
success: boolean;
|
|
240
|
+
};
|
|
241
|
+
}>;
|
|
242
|
+
/**
|
|
243
|
+
* List folders in a given scope as a nested tree (`{ ...folder, children: [...] }`).
|
|
244
|
+
* Scopes today: `'media'` for the media library, `'documents'` / `'collections'`
|
|
245
|
+
* for document folders. The `scope` query param is required by the server.
|
|
246
|
+
*/
|
|
247
|
+
listFolders(scope: string): Promise<{
|
|
248
|
+
data: any[];
|
|
249
|
+
}>;
|
|
250
|
+
/**
|
|
251
|
+
* Create a folder. If `parentId` is supplied, it must already exist AND live
|
|
252
|
+
* in the same scope — the server refuses cross-scope reparenting.
|
|
253
|
+
*/
|
|
254
|
+
createFolder(body: {
|
|
255
|
+
name: string;
|
|
256
|
+
scope: string;
|
|
257
|
+
parentId?: string | null;
|
|
258
|
+
}): Promise<{
|
|
259
|
+
data: any;
|
|
260
|
+
}>;
|
|
261
|
+
/**
|
|
262
|
+
* Update a folder: rename (`name`), reparent (`parentId`; pass `null` to
|
|
263
|
+
* promote to root), or reorder (`position`). The server prevents self-
|
|
264
|
+
* parenting and cross-scope reparenting.
|
|
265
|
+
*/
|
|
266
|
+
updateFolder(id: string, body: {
|
|
267
|
+
name?: string;
|
|
268
|
+
parentId?: string | null;
|
|
269
|
+
position?: number;
|
|
270
|
+
}): Promise<{
|
|
271
|
+
data: any;
|
|
272
|
+
}>;
|
|
273
|
+
/**
|
|
274
|
+
* Delete a folder. Refuses with HTTP 409 if the folder still contains
|
|
275
|
+
* documents, media, or sub-folders — the caller must move or delete the
|
|
276
|
+
* contents first.
|
|
277
|
+
*/
|
|
278
|
+
deleteFolder(id: string): Promise<{
|
|
279
|
+
data: {
|
|
280
|
+
success: boolean;
|
|
281
|
+
};
|
|
282
|
+
}>;
|
|
283
|
+
/**
|
|
284
|
+
* Move a document into a folder, or remove it from every folder by passing
|
|
285
|
+
* `folderId: null`. Server validates that the target folder is in the
|
|
286
|
+
* documents / collections scope.
|
|
287
|
+
*/
|
|
288
|
+
moveDocumentToFolder(id: string, folderId: string | null): Promise<{
|
|
289
|
+
data: {
|
|
290
|
+
success: boolean;
|
|
291
|
+
};
|
|
292
|
+
}>;
|
|
293
|
+
/**
|
|
294
|
+
* List immutable snapshots of a document, newest first. Each `Version`
|
|
295
|
+
* carries the full `data` payload at the time of write plus the user who
|
|
296
|
+
* made the change (joined via `relationLoadStrategy: 'join'` server-side).
|
|
297
|
+
*/
|
|
298
|
+
listVersions(collection: string, documentId: string, query?: {
|
|
299
|
+
page?: number;
|
|
300
|
+
pageSize?: number;
|
|
301
|
+
}): Promise<{
|
|
302
|
+
data: {
|
|
303
|
+
versions: any[];
|
|
304
|
+
total: number;
|
|
305
|
+
page: number;
|
|
306
|
+
pageSize: number;
|
|
307
|
+
totalPages: number;
|
|
308
|
+
};
|
|
309
|
+
}>;
|
|
310
|
+
/**
|
|
311
|
+
* Roll a document back to a prior version. The restore goes through the
|
|
312
|
+
* normal `updateDocument` path so it: (a) runs validation hooks, (b)
|
|
313
|
+
* creates a NEW version row capturing the restore (i.e. it never rewrites
|
|
314
|
+
* history), and (c) emits a `version_restored` event to `prenta_audit_logs`.
|
|
315
|
+
*/
|
|
316
|
+
restoreVersion(collection: string, documentId: string, versionId: string): Promise<{
|
|
317
|
+
data: any;
|
|
318
|
+
}>;
|
|
319
|
+
/**
|
|
320
|
+
* Schedule a document to publish and/or unpublish at a future timestamp.
|
|
321
|
+
* `scheduledAt` (publish) forces `status` → `SCHEDULED` so the cron picks
|
|
322
|
+
* it up; `scheduledUnpublishAt` leaves the current status alone. The
|
|
323
|
+
* server requires at least one of the two, both must be ISO 8601 strings
|
|
324
|
+
* AND in the future, and `scheduledUnpublishAt` must be after
|
|
325
|
+
* `scheduledAt` when both are provided.
|
|
326
|
+
*/
|
|
327
|
+
schedulePublish(collection: string, documentId: string, body: {
|
|
328
|
+
scheduledAt?: string | null;
|
|
329
|
+
scheduledUnpublishAt?: string | null;
|
|
330
|
+
}): Promise<{
|
|
331
|
+
data: {
|
|
332
|
+
id: string;
|
|
333
|
+
status: string;
|
|
334
|
+
scheduledAt: string | null;
|
|
335
|
+
scheduledUnpublishAt: string | null;
|
|
336
|
+
};
|
|
337
|
+
}>;
|
|
338
|
+
/**
|
|
339
|
+
* Cancel any pending publish + unpublish schedule for the document. If
|
|
340
|
+
* the document was in `SCHEDULED` (not yet published) the server drops
|
|
341
|
+
* its status back to `DRAFT`. Already-published documents stay live.
|
|
342
|
+
*/
|
|
343
|
+
unschedulePublish(collection: string, documentId: string): Promise<{
|
|
344
|
+
data: {
|
|
345
|
+
id: string;
|
|
346
|
+
status: string;
|
|
347
|
+
scheduledAt: null;
|
|
348
|
+
scheduledUnpublishAt: null;
|
|
349
|
+
};
|
|
350
|
+
}>;
|
|
351
|
+
/**
|
|
352
|
+
* Read the workflow state of a document. Returns the current stage
|
|
353
|
+
* (`DRAFT` | `IN_REVIEW` | `APPROVED` | `PUBLISHED` | `ARCHIVED`), the
|
|
354
|
+
* stages the *current* user is allowed to transition to next (derived
|
|
355
|
+
* from their role), plus the reviewer ID + note if set.
|
|
356
|
+
*/
|
|
357
|
+
getWorkflowState(collection: string, documentId: string): Promise<{
|
|
358
|
+
data: {
|
|
359
|
+
stage: string;
|
|
360
|
+
transitions: string[];
|
|
361
|
+
reviewerId: string | null;
|
|
362
|
+
reviewNote: string | null;
|
|
363
|
+
};
|
|
364
|
+
}>;
|
|
365
|
+
/**
|
|
366
|
+
* Move a document to a new workflow stage. The server validates that the
|
|
367
|
+
* transition is allowed from the current stage AND that the caller's
|
|
368
|
+
* role permits it; if either check fails it returns HTTP 400 with the
|
|
369
|
+
* reason in the error body.
|
|
370
|
+
*/
|
|
371
|
+
transitionWorkflow(collection: string, documentId: string, body: {
|
|
372
|
+
stage: string;
|
|
373
|
+
note?: string;
|
|
374
|
+
}): Promise<{
|
|
375
|
+
data: {
|
|
376
|
+
success: boolean;
|
|
377
|
+
};
|
|
378
|
+
}>;
|
|
379
|
+
/**
|
|
380
|
+
* List the comment thread attached to a document. By default returns only
|
|
381
|
+
* unresolved, non-deleted comments. `includeResolved` widens that to
|
|
382
|
+
* include resolved threads; `includeDeleted` is admin-only and surfaces
|
|
383
|
+
* soft-deleted moderation history (returns HTTP 403 for non-admin keys).
|
|
384
|
+
*/
|
|
385
|
+
listComments(documentId: string, query?: {
|
|
386
|
+
includeResolved?: boolean;
|
|
387
|
+
includeDeleted?: boolean;
|
|
388
|
+
pageSize?: number;
|
|
389
|
+
}): Promise<{
|
|
390
|
+
data: any[];
|
|
391
|
+
}>;
|
|
392
|
+
/**
|
|
393
|
+
* Create a comment on a document. `anchor` is optional and lets the
|
|
394
|
+
* caller pin the comment to a specific text range (used by the
|
|
395
|
+
* Prosemirror selection UI in the admin). `parentId` is set for thread
|
|
396
|
+
* replies. The server emits a `comment_created` event to
|
|
397
|
+
* `prenta_audit_logs` and fires a fire-and-forget notification dispatch.
|
|
398
|
+
*/
|
|
399
|
+
createComment(documentId: string, body: {
|
|
400
|
+
body: string;
|
|
401
|
+
anchor?: {
|
|
402
|
+
from: string;
|
|
403
|
+
to: string;
|
|
404
|
+
} | null;
|
|
405
|
+
parentId?: string | null;
|
|
406
|
+
}): Promise<{
|
|
407
|
+
data: any;
|
|
408
|
+
}>;
|
|
409
|
+
/**
|
|
410
|
+
* Run a deterministic accessibility audit over a page-builder tree.
|
|
411
|
+
* Detects missing alt text, heading-level skips, missing form labels,
|
|
412
|
+
* missing ARIA attributes, and undersized touch targets. Returns an
|
|
413
|
+
* array of `A11yIssue` entries — each carries `autoFixable: boolean`
|
|
414
|
+
* so the caller can pair this with `fixPageA11y` for one-click cleanup.
|
|
415
|
+
*
|
|
416
|
+
* Pass the tree exactly as returned by a document's `data.tree` field;
|
|
417
|
+
* no normalisation needed.
|
|
418
|
+
*/
|
|
419
|
+
auditPageA11y(tree: unknown): Promise<{
|
|
420
|
+
data: {
|
|
421
|
+
issues: Array<{
|
|
422
|
+
id: string;
|
|
423
|
+
blockId: string;
|
|
424
|
+
type: "missing-alt" | "heading-skip" | "missing-label" | "missing-aria" | "small-touch-target";
|
|
425
|
+
severity: "error" | "warning";
|
|
426
|
+
message: string;
|
|
427
|
+
autoFixable: boolean;
|
|
428
|
+
}>;
|
|
429
|
+
};
|
|
430
|
+
}>;
|
|
431
|
+
/**
|
|
432
|
+
* Auto-fix the accessibility issues marked `autoFixable: true` from
|
|
433
|
+
* `audit_page_a11y` and return the mutated tree plus a report
|
|
434
|
+
* (`fixedCount` / `remainingCount`). Non-auto-fixable issues are
|
|
435
|
+
* returned in `report.issues` for human follow-up. The handler emits a
|
|
436
|
+
* `settings_changed` audit event with the fix counts.
|
|
437
|
+
*/
|
|
438
|
+
fixPageA11y(tree: unknown): Promise<{
|
|
439
|
+
data: {
|
|
440
|
+
tree: unknown;
|
|
441
|
+
report: {
|
|
442
|
+
issues: Array<{
|
|
443
|
+
id: string;
|
|
444
|
+
blockId: string;
|
|
445
|
+
type: string;
|
|
446
|
+
severity: "error" | "warning";
|
|
447
|
+
message: string;
|
|
448
|
+
autoFixable: boolean;
|
|
449
|
+
}>;
|
|
450
|
+
fixedCount: number;
|
|
451
|
+
remainingCount: number;
|
|
452
|
+
};
|
|
453
|
+
};
|
|
454
|
+
}>;
|
|
455
|
+
/**
|
|
456
|
+
* Edit a comment body. Only the author can edit; the server returns a
|
|
457
|
+
* `forbidden` error otherwise. Resolved threads remain editable so a
|
|
458
|
+
* moderator can append context.
|
|
459
|
+
*/
|
|
460
|
+
editComment(id: string, body: string): Promise<{
|
|
461
|
+
data: any;
|
|
462
|
+
}>;
|
|
463
|
+
/** Mark a comment thread resolved. Requires write-role; emits `comment_resolved` audit event. */
|
|
464
|
+
resolveComment(id: string): Promise<{
|
|
465
|
+
data: any;
|
|
466
|
+
}>;
|
|
467
|
+
/** Re-open a previously resolved comment thread. Requires write-role. */
|
|
468
|
+
reopenComment(id: string): Promise<{
|
|
469
|
+
data: any;
|
|
470
|
+
}>;
|
|
471
|
+
/**
|
|
472
|
+
* Soft-delete a comment. Server returns `forbidden` unless the caller is
|
|
473
|
+
* the author OR an admin. Soft-deleted comments are admin-only viewable
|
|
474
|
+
* via `list_comments({ includeDeleted: true })`.
|
|
475
|
+
*/
|
|
476
|
+
deleteComment(id: string): Promise<{
|
|
477
|
+
data: any;
|
|
478
|
+
}>;
|
|
479
|
+
/** List all redirects, newest first. */
|
|
480
|
+
listRedirects(): Promise<{
|
|
481
|
+
data: any[];
|
|
482
|
+
}>;
|
|
483
|
+
/**
|
|
484
|
+
* Create a redirect. Admin-only. The server enforces an open-redirect
|
|
485
|
+
* defence — absolute destinations must point at a host that's either
|
|
486
|
+
* the configured `NEXT_PUBLIC_SITE_URL` or listed in
|
|
487
|
+
* `redirects.allowedExternalHosts`. Relative destinations (`/foo`) are
|
|
488
|
+
* always allowed. `statusCode` accepts 301 / 302 / 307 / 308 only and
|
|
489
|
+
* defaults to 301 on the server.
|
|
490
|
+
*/
|
|
491
|
+
createRedirect(body: {
|
|
492
|
+
source: string;
|
|
493
|
+
destination: string;
|
|
494
|
+
statusCode?: 301 | 302 | 307 | 308;
|
|
495
|
+
isRegex?: boolean;
|
|
496
|
+
notes?: string | null;
|
|
497
|
+
}): Promise<{
|
|
498
|
+
data: any;
|
|
499
|
+
}>;
|
|
500
|
+
/** Delete a redirect by ID. Admin-only. */
|
|
501
|
+
deleteRedirect(id: string): Promise<{
|
|
502
|
+
data: {
|
|
503
|
+
success: boolean;
|
|
504
|
+
};
|
|
505
|
+
}>;
|
|
506
|
+
/**
|
|
507
|
+
* List page templates. Admin-only. Optional `category` filter narrows
|
|
508
|
+
* to a single template category. `collection` reorders the result so
|
|
509
|
+
* the suggested templates for that collection bubble to the top
|
|
510
|
+
* (driven by collection presets) and annotates each row with
|
|
511
|
+
* `suggested` / `suggestionRank`. When `collection` is supplied the
|
|
512
|
+
* response also carries a top-level `suggestion` summary.
|
|
513
|
+
*/
|
|
514
|
+
listPageTemplates(query?: {
|
|
515
|
+
category?: string;
|
|
516
|
+
collection?: string;
|
|
517
|
+
}): Promise<{
|
|
518
|
+
data: any[];
|
|
519
|
+
suggestion?: any;
|
|
520
|
+
}>;
|
|
521
|
+
/** Fetch a single page template by ID. Admin-only. */
|
|
522
|
+
getPageTemplate(id: string): Promise<{
|
|
523
|
+
data: any;
|
|
524
|
+
}>;
|
|
525
|
+
/** List saved page-builder sections. Admin-only. Optional `category` filter. */
|
|
526
|
+
listSavedSections(query?: {
|
|
527
|
+
category?: string;
|
|
528
|
+
}): Promise<{
|
|
529
|
+
data: any[];
|
|
530
|
+
}>;
|
|
531
|
+
/**
|
|
532
|
+
* Run a site-wide SEO scan over every published document. **Expensive**
|
|
533
|
+
* — touches every published row. Returns
|
|
534
|
+
* `{ total, pagesWithIssues, totalProblems, issues[] }` where each
|
|
535
|
+
* issue carries `{ documentId, title, slug, problems[] }`. The audit
|
|
536
|
+
* recommended gating this behind an explicit confirmation arg; the
|
|
537
|
+
* tool wrapper enforces that, the client is intentionally a thin
|
|
538
|
+
* pass-through.
|
|
539
|
+
*/
|
|
540
|
+
seoScanRun(): Promise<{
|
|
541
|
+
data: any;
|
|
542
|
+
}>;
|
|
543
|
+
/**
|
|
544
|
+
* Aggregate SEO health for the dashboard — counts of missing meta
|
|
545
|
+
* descriptions / alt text plus a `topContent` array of recent
|
|
546
|
+
* published docs with light SEO scores. Cheap; safe to call on
|
|
547
|
+
* dashboard refresh.
|
|
548
|
+
*/
|
|
549
|
+
seoSummary(): Promise<{
|
|
550
|
+
data: any;
|
|
551
|
+
}>;
|
|
552
|
+
/**
|
|
553
|
+
* Aggregated Overview-tab payload: site score + grade + delta, four
|
|
554
|
+
* headline metrics, open-issue summary, priority-ranked quick wins, and
|
|
555
|
+
* (gated) search-performance rows. Cheap read; derived from the last
|
|
556
|
+
* completed audit run.
|
|
557
|
+
*/
|
|
558
|
+
seoOverview(): Promise<{
|
|
559
|
+
data: any;
|
|
560
|
+
}>;
|
|
561
|
+
/**
|
|
562
|
+
* Kick off a persistent SEO audit run. `scope` controls breadth:
|
|
563
|
+
* `full` (everything), `content`, `technical`, or `redirect`. Returns the
|
|
564
|
+
* created `SEOAuditRun` (status, issue counts, before/after site score).
|
|
565
|
+
* Heavier than `seoSummary` — persists issues for the Audit tab.
|
|
566
|
+
*/
|
|
567
|
+
runSeoAudit(scope?: 'full' | 'content' | 'technical' | 'redirect'): Promise<{
|
|
568
|
+
data: any;
|
|
569
|
+
}>;
|
|
570
|
+
/** List persisted SEO audit runs, newest first. */
|
|
571
|
+
listSeoAuditRuns(): Promise<{
|
|
572
|
+
data: {
|
|
573
|
+
runs: any[];
|
|
574
|
+
};
|
|
575
|
+
}>;
|
|
576
|
+
/**
|
|
577
|
+
* List SEO issues with optional filters. `severity` (critical/warning/info),
|
|
578
|
+
* `category` (content/technical/redirects/...), `status`
|
|
579
|
+
* (open/ignored/resolved/recurring), and `auditRunId` narrow the result.
|
|
580
|
+
*/
|
|
581
|
+
listSeoIssues(query?: {
|
|
582
|
+
severity?: string;
|
|
583
|
+
category?: string;
|
|
584
|
+
status?: string;
|
|
585
|
+
auditRunId?: string;
|
|
586
|
+
}): Promise<{
|
|
587
|
+
data: {
|
|
588
|
+
issues: any[];
|
|
589
|
+
};
|
|
590
|
+
}>;
|
|
591
|
+
/** Fetch a single SEO issue by ID. */
|
|
592
|
+
getSeoIssue(id: string): Promise<{
|
|
593
|
+
data: any;
|
|
594
|
+
}>;
|
|
595
|
+
/** Mark an SEO issue resolved / ignored / reopened. */
|
|
596
|
+
setSeoIssueStatus(id: string, action: 'resolve' | 'ignore' | 'reopen'): Promise<{
|
|
597
|
+
data: any;
|
|
598
|
+
}>;
|
|
599
|
+
/**
|
|
600
|
+
* Export a markdown SEO fix brief for dev-team handoff. Embeds cached AI
|
|
601
|
+
* content briefs and topic research when available.
|
|
602
|
+
*/
|
|
603
|
+
exportSeoFixBrief(query?: {
|
|
604
|
+
severity?: string;
|
|
605
|
+
status?: string;
|
|
606
|
+
auditRunId?: string;
|
|
607
|
+
manualOnly?: boolean;
|
|
608
|
+
includeContentBriefs?: boolean;
|
|
609
|
+
includeContentResearch?: boolean;
|
|
610
|
+
}): Promise<{
|
|
611
|
+
data: {
|
|
612
|
+
markdown: string;
|
|
613
|
+
filename: string;
|
|
614
|
+
issueCount: number;
|
|
615
|
+
};
|
|
616
|
+
}>;
|
|
617
|
+
/** Preview a deterministic or AI-assisted inline metadata/schema fix. */
|
|
618
|
+
suggestSeoIssueFix(id: string): Promise<{
|
|
619
|
+
data: {
|
|
620
|
+
suggestion: any;
|
|
621
|
+
};
|
|
622
|
+
}>;
|
|
623
|
+
/** Apply a previously suggested inline fix (requires matching fingerprint). */
|
|
624
|
+
applySeoIssueFix(id: string, fingerprint: string): Promise<{
|
|
625
|
+
data: any;
|
|
626
|
+
}>;
|
|
627
|
+
/** Load a cached AI content brief for a content/heading issue. */
|
|
628
|
+
getSeoIssueContentBrief(id: string): Promise<{
|
|
629
|
+
data: {
|
|
630
|
+
supported: boolean;
|
|
631
|
+
brief: any;
|
|
632
|
+
stale: boolean;
|
|
633
|
+
};
|
|
634
|
+
}>;
|
|
635
|
+
/** Generate (or regenerate) an AI content brief for manual content fixes. */
|
|
636
|
+
generateSeoIssueContentBrief(id: string): Promise<{
|
|
637
|
+
data: {
|
|
638
|
+
brief: any;
|
|
639
|
+
};
|
|
640
|
+
}>;
|
|
641
|
+
/**
|
|
642
|
+
* Run topic research (thin-content) then generate a content brief in one step.
|
|
643
|
+
* Optional competitor URLs are fetched SSRF-safe (max 3).
|
|
644
|
+
*/
|
|
645
|
+
researchAndBriefSeoIssue(id: string, competitorUrls?: string[]): Promise<{
|
|
646
|
+
data: {
|
|
647
|
+
research: any;
|
|
648
|
+
brief: any;
|
|
649
|
+
};
|
|
650
|
+
}>;
|
|
651
|
+
/** Batch-generate content briefs for open issues (skips fresh cached briefs). */
|
|
652
|
+
batchGenerateSeoContentBriefs(body?: {
|
|
653
|
+
severity?: string;
|
|
654
|
+
status?: string;
|
|
655
|
+
issueIds?: string[];
|
|
656
|
+
skipExisting?: boolean;
|
|
657
|
+
includeResearch?: boolean;
|
|
658
|
+
competitorUrls?: string[];
|
|
659
|
+
}): Promise<{
|
|
660
|
+
data: {
|
|
661
|
+
generated: number;
|
|
662
|
+
skipped: number;
|
|
663
|
+
failed: number;
|
|
664
|
+
results: Array<{
|
|
665
|
+
issueId: string;
|
|
666
|
+
status: string;
|
|
667
|
+
error?: string;
|
|
668
|
+
}>;
|
|
669
|
+
};
|
|
670
|
+
}>;
|
|
671
|
+
/** List redirect rules with hit counts + stale flags. */
|
|
672
|
+
listRedirectRules(): Promise<{
|
|
673
|
+
data: {
|
|
674
|
+
rules: any[];
|
|
675
|
+
};
|
|
676
|
+
}>;
|
|
677
|
+
/** Redirect overview counts (active / stale / 404s / chains). */
|
|
678
|
+
redirectOverview(): Promise<{
|
|
679
|
+
data: any;
|
|
680
|
+
}>;
|
|
681
|
+
/** Detected redirect chains and loops with safe-flatten suggestions. */
|
|
682
|
+
redirectChains(): Promise<{
|
|
683
|
+
data: {
|
|
684
|
+
chains: any[];
|
|
685
|
+
};
|
|
686
|
+
}>;
|
|
687
|
+
/** Flatten a redirect chain to a single hop (updates the first rule's destination). */
|
|
688
|
+
flattenRedirectChain(chainId: string): Promise<{
|
|
689
|
+
data: any;
|
|
690
|
+
}>;
|
|
691
|
+
/** AI-generated 404 recovery suggestions awaiting review. */
|
|
692
|
+
listRedirectSuggestions(): Promise<{
|
|
693
|
+
data: {
|
|
694
|
+
suggestions: any[];
|
|
695
|
+
};
|
|
696
|
+
}>;
|
|
697
|
+
getRedirectRecoverySettings(): Promise<{
|
|
698
|
+
data: any;
|
|
699
|
+
}>;
|
|
700
|
+
updateRedirectRecoverySettings(body: {
|
|
701
|
+
aiRerank?: boolean;
|
|
702
|
+
autoApply?: boolean;
|
|
703
|
+
autoApplyMinConfidence?: number;
|
|
704
|
+
}): Promise<{
|
|
705
|
+
data: any;
|
|
706
|
+
}>;
|
|
707
|
+
/** Scan tracked 404s and generate recovery redirect suggestions. */
|
|
708
|
+
generateRedirectRecoverySuggestions(): Promise<{
|
|
709
|
+
data: any;
|
|
710
|
+
}>;
|
|
711
|
+
acceptRedirectSuggestion(id: string): Promise<{
|
|
712
|
+
data: any;
|
|
713
|
+
}>;
|
|
714
|
+
dismissRedirectSuggestion(id: string): Promise<{
|
|
715
|
+
data: any;
|
|
716
|
+
}>;
|
|
717
|
+
exportRedirectsCsv(): Promise<{
|
|
718
|
+
data: {
|
|
719
|
+
csv: string;
|
|
720
|
+
};
|
|
721
|
+
}>;
|
|
722
|
+
importRedirectsCsv(csv: string): Promise<{
|
|
723
|
+
data: {
|
|
724
|
+
imported: number;
|
|
725
|
+
errors: string[];
|
|
726
|
+
};
|
|
727
|
+
}>;
|
|
728
|
+
updateRedirectRule(id: string, body: Record<string, unknown>): Promise<{
|
|
729
|
+
data: any;
|
|
730
|
+
}>;
|
|
731
|
+
/** Site-wide internal/external link graph snapshot (Links tab). */
|
|
732
|
+
seoLinkSnapshot(): Promise<{
|
|
733
|
+
data: any;
|
|
734
|
+
}>;
|
|
735
|
+
/** Broken / suspicious outbound link scan (bounded batch). */
|
|
736
|
+
seoLinkHealth(): Promise<{
|
|
737
|
+
data: any;
|
|
738
|
+
}>;
|
|
739
|
+
seoLinkSuggest(body: {
|
|
740
|
+
targetEntityId: string;
|
|
741
|
+
targetEntityType: 'page' | 'post';
|
|
742
|
+
limit?: number;
|
|
743
|
+
}): Promise<{
|
|
744
|
+
data: any;
|
|
745
|
+
}>;
|
|
746
|
+
/** Build or refresh the AI link index used for contextual suggestions. */
|
|
747
|
+
seoLinkAiIndex(): Promise<{
|
|
748
|
+
data: any;
|
|
749
|
+
}>;
|
|
750
|
+
getCrawlSettings(): Promise<{
|
|
751
|
+
data: any;
|
|
752
|
+
}>;
|
|
753
|
+
updateCrawlSettings(body: Record<string, unknown>): Promise<{
|
|
754
|
+
data: any;
|
|
755
|
+
}>;
|
|
756
|
+
getRobotsTxt(): Promise<{
|
|
757
|
+
data: any;
|
|
758
|
+
}>;
|
|
759
|
+
updateRobotsTxt(body: string): Promise<{
|
|
760
|
+
data: any;
|
|
761
|
+
}>;
|
|
762
|
+
getSitemapStatus(): Promise<{
|
|
763
|
+
data: any;
|
|
764
|
+
}>;
|
|
765
|
+
regenerateSitemap(): Promise<{
|
|
766
|
+
data: any;
|
|
767
|
+
}>;
|
|
768
|
+
listSeoContentRecords(): Promise<{
|
|
769
|
+
data: {
|
|
770
|
+
records: any[];
|
|
771
|
+
};
|
|
772
|
+
}>;
|
|
773
|
+
getSeoContentRecord(entityType: string, entityId: string): Promise<{
|
|
774
|
+
data: any;
|
|
775
|
+
}>;
|
|
776
|
+
updateSeoContentFields(entityType: string, entityId: string, body: Record<string, unknown>): Promise<{
|
|
777
|
+
data: any;
|
|
778
|
+
}>;
|
|
779
|
+
aiSeoGenerateField(body: {
|
|
780
|
+
field: string;
|
|
781
|
+
entityType: string;
|
|
782
|
+
entityId: string;
|
|
783
|
+
}): Promise<{
|
|
784
|
+
data: any;
|
|
785
|
+
}>;
|
|
786
|
+
aiSeoExplainIssue(issueId: string): Promise<{
|
|
787
|
+
data: any;
|
|
788
|
+
}>;
|
|
789
|
+
aiSeoGenerateSchema(body: {
|
|
790
|
+
entityType: string;
|
|
791
|
+
entityId: string;
|
|
792
|
+
schemaType: string;
|
|
793
|
+
}): Promise<{
|
|
794
|
+
data: any;
|
|
795
|
+
}>;
|
|
796
|
+
aiSeoSummarize(): Promise<{
|
|
797
|
+
data: any;
|
|
798
|
+
}>;
|
|
799
|
+
/**
|
|
800
|
+
* List submissions for a form. **PII-sensitive** — form submissions
|
|
801
|
+
* typically contain names, emails, free-form messages, and any other
|
|
802
|
+
* fields the form collects. Requires write-role on the API key AND
|
|
803
|
+
* an explicit `formId` (no enumeration). Paged.
|
|
804
|
+
*/
|
|
805
|
+
listFormSubmissions(formId: string, query?: {
|
|
806
|
+
page?: number;
|
|
807
|
+
pageSize?: number;
|
|
808
|
+
}): Promise<{
|
|
809
|
+
data: {
|
|
810
|
+
submissions: any[];
|
|
811
|
+
total: number;
|
|
812
|
+
page: number;
|
|
813
|
+
pageSize: number;
|
|
814
|
+
totalPages: number;
|
|
815
|
+
};
|
|
816
|
+
}>;
|
|
817
|
+
/**
|
|
818
|
+
* List entries from the submissions inbox (decrypted PII, attribution,
|
|
819
|
+
* read/star/archive state). `formId` is required at the tool boundary to
|
|
820
|
+
* prevent accidental cross-form enumeration. Returns
|
|
821
|
+
* `{ data: { entries, total, page, pageSize } }`.
|
|
822
|
+
*/
|
|
823
|
+
listFormEntries(query: {
|
|
824
|
+
formId: string;
|
|
825
|
+
page?: number;
|
|
826
|
+
pageSize?: number;
|
|
827
|
+
unread?: boolean;
|
|
828
|
+
starred?: boolean;
|
|
829
|
+
archived?: boolean;
|
|
830
|
+
spamStatus?: string;
|
|
831
|
+
}): Promise<{
|
|
832
|
+
data: {
|
|
833
|
+
entries: any[];
|
|
834
|
+
total: number;
|
|
835
|
+
page: number;
|
|
836
|
+
pageSize: number;
|
|
837
|
+
};
|
|
838
|
+
}>;
|
|
839
|
+
/**
|
|
840
|
+
* Read a single entry plus the schema version it was captured against.
|
|
841
|
+
* Returns `{ data: { entry, fields } }`. The server audits this read as
|
|
842
|
+
* `form.entry.viewed` because it exposes decrypted PII.
|
|
843
|
+
*/
|
|
844
|
+
getFormEntry(entryId: string): Promise<{
|
|
845
|
+
data: {
|
|
846
|
+
entry: any;
|
|
847
|
+
fields: any[];
|
|
848
|
+
};
|
|
849
|
+
}>;
|
|
850
|
+
/**
|
|
851
|
+
* List form definitions (NOT submissions). Returns
|
|
852
|
+
* `{ data: Form[], total, page, pageSize }` where each form carries its
|
|
853
|
+
* field schema plus the legacy `fieldCount` / `submissions` aliases. Use
|
|
854
|
+
* this to discover form IDs before reading entries or editing a schema.
|
|
855
|
+
*/
|
|
856
|
+
listForms(query?: {
|
|
857
|
+
page?: number;
|
|
858
|
+
pageSize?: number;
|
|
859
|
+
status?: string;
|
|
860
|
+
search?: string;
|
|
861
|
+
sortBy?: string;
|
|
862
|
+
sortDirection?: 'asc' | 'desc';
|
|
863
|
+
}): Promise<{
|
|
864
|
+
data: any[];
|
|
865
|
+
total: number;
|
|
866
|
+
page: number;
|
|
867
|
+
pageSize: number;
|
|
868
|
+
}>;
|
|
869
|
+
/** Fetch a single form definition by ID (metadata + field schema). */
|
|
870
|
+
getForm(id: string): Promise<{
|
|
871
|
+
data: any;
|
|
872
|
+
}>;
|
|
873
|
+
/**
|
|
874
|
+
* Create a form. Only `name` is required server-side; the slug is derived
|
|
875
|
+
* from `name` when omitted. `fields` is the initial field schema (see
|
|
876
|
+
* `saveFormSchema` for the field shape) — omit to start with an empty
|
|
877
|
+
* form and add the schema in a second call.
|
|
878
|
+
*/
|
|
879
|
+
createForm(body: {
|
|
880
|
+
name: string;
|
|
881
|
+
slug?: string;
|
|
882
|
+
description?: string;
|
|
883
|
+
status?: 'active' | 'draft' | 'archived';
|
|
884
|
+
fields?: Array<Record<string, unknown>>;
|
|
885
|
+
successMessage?: string;
|
|
886
|
+
redirectUrl?: string;
|
|
887
|
+
}): Promise<{
|
|
888
|
+
data: any;
|
|
889
|
+
}>;
|
|
890
|
+
/**
|
|
891
|
+
* Update a form's metadata. Only the supplied fields change. Note the
|
|
892
|
+
* field SCHEMA is NOT editable here — use `saveFormSchema` so each schema
|
|
893
|
+
* change is versioned.
|
|
894
|
+
*/
|
|
895
|
+
updateForm(id: string, body: {
|
|
896
|
+
name?: string;
|
|
897
|
+
description?: string;
|
|
898
|
+
status?: 'active' | 'draft' | 'archived';
|
|
899
|
+
successMessage?: string;
|
|
900
|
+
redirectUrl?: string;
|
|
901
|
+
}): Promise<{
|
|
902
|
+
data: any;
|
|
903
|
+
}>;
|
|
904
|
+
/**
|
|
905
|
+
* Delete a form. By default the server refuses (HTTP 409) when the form
|
|
906
|
+
* has submissions; pass `force: true` to delete it and its entries.
|
|
907
|
+
*/
|
|
908
|
+
deleteForm(id: string, force?: boolean): Promise<{
|
|
909
|
+
data: {
|
|
910
|
+
success: boolean;
|
|
911
|
+
};
|
|
912
|
+
}>;
|
|
913
|
+
/** Duplicate a form (new draft copy with a fresh slug). Returns the new form. */
|
|
914
|
+
duplicateForm(id: string): Promise<{
|
|
915
|
+
data: any;
|
|
916
|
+
}>;
|
|
917
|
+
/**
|
|
918
|
+
* Read a form's field schema plus its version history:
|
|
919
|
+
* `{ fields, activeVersionId, versions }`. Call before `saveFormSchema`
|
|
920
|
+
* to avoid clobbering the live schema.
|
|
921
|
+
*/
|
|
922
|
+
getFormSchema(id: string): Promise<{
|
|
923
|
+
data: {
|
|
924
|
+
fields: any[];
|
|
925
|
+
activeVersionId: string | null;
|
|
926
|
+
versions: any[];
|
|
927
|
+
};
|
|
928
|
+
}>;
|
|
929
|
+
/**
|
|
930
|
+
* Replace a form's field schema. Creates a NEW schema version (history is
|
|
931
|
+
* never rewritten) and points the form at it. `fields` is the full
|
|
932
|
+
* ordered field list — partial patches are not supported (read with
|
|
933
|
+
* `getFormSchema`, mutate, write back the whole array).
|
|
934
|
+
*/
|
|
935
|
+
saveFormSchema(id: string, fields: Array<Record<string, unknown>>, notes?: string): Promise<{
|
|
936
|
+
data: {
|
|
937
|
+
form: any;
|
|
938
|
+
version: any;
|
|
939
|
+
};
|
|
940
|
+
}>;
|
|
941
|
+
/**
|
|
942
|
+
* Update inbox state on a single submission entry (mark read/unread,
|
|
943
|
+
* star, archive). Does not touch the captured field values.
|
|
944
|
+
*/
|
|
945
|
+
updateFormEntry(entryId: string, patch: {
|
|
946
|
+
unread?: boolean;
|
|
947
|
+
starred?: boolean;
|
|
948
|
+
archived?: boolean;
|
|
949
|
+
}): Promise<{
|
|
950
|
+
data: any;
|
|
951
|
+
}>;
|
|
952
|
+
/** Apply the same inbox-state patch to many entries at once. Returns `{ updated }`. */
|
|
953
|
+
bulkUpdateFormEntries(ids: string[], patch: {
|
|
954
|
+
unread?: boolean;
|
|
955
|
+
starred?: boolean;
|
|
956
|
+
archived?: boolean;
|
|
957
|
+
}): Promise<{
|
|
958
|
+
data: {
|
|
959
|
+
updated: number;
|
|
960
|
+
};
|
|
961
|
+
}>;
|
|
962
|
+
/** Permanently delete a submission entry (and its decrypted PII). Destructive. */
|
|
963
|
+
deleteFormEntry(entryId: string): Promise<{
|
|
964
|
+
data: {
|
|
965
|
+
success: boolean;
|
|
966
|
+
};
|
|
967
|
+
}>;
|
|
968
|
+
/**
|
|
969
|
+
* Export every submission for a form as CSV. The endpoint returns a raw
|
|
970
|
+
* `text/csv` body (NOT a JSON envelope), so we read it as text rather
|
|
971
|
+
* than via `request()`. **Bulk PII egress** — the server records a
|
|
972
|
+
* `form.entries.exported` audit event. Returns the CSV text plus the
|
|
973
|
+
* server-suggested filename.
|
|
974
|
+
*/
|
|
975
|
+
exportFormEntries(formId: string): Promise<{
|
|
976
|
+
filename: string;
|
|
977
|
+
csv: string;
|
|
978
|
+
}>;
|
|
979
|
+
/**
|
|
980
|
+
* Read the deployment health of the CMS. Unauthenticated on the server
|
|
981
|
+
* side (still safe to call — exposes only operational status, no
|
|
982
|
+
* tenant data). Returns `{ status, version, env, models,
|
|
983
|
+
* databaseConnected, secretConfigured }`.
|
|
984
|
+
*
|
|
985
|
+
* Recommended as an agent's first call: a `degraded` / `unhealthy`
|
|
986
|
+
* status surfaces operator-level config problems early instead of
|
|
987
|
+
* surfacing as cryptic errors deep in a workflow.
|
|
988
|
+
*/
|
|
989
|
+
healthCheck(): Promise<{
|
|
990
|
+
data: any;
|
|
991
|
+
}>;
|
|
992
|
+
generatePage(input: {
|
|
993
|
+
prompt: string;
|
|
994
|
+
template?: string;
|
|
995
|
+
context?: string;
|
|
996
|
+
steps?: string[];
|
|
997
|
+
tone?: string;
|
|
998
|
+
}): Promise<{
|
|
999
|
+
data: {
|
|
1000
|
+
tree: any;
|
|
1001
|
+
steps: any[];
|
|
1002
|
+
totalTokensUsed: number;
|
|
1003
|
+
totalDurationMs: number;
|
|
1004
|
+
};
|
|
1005
|
+
}>;
|
|
1006
|
+
generateBlock(input: {
|
|
1007
|
+
blockType: string;
|
|
1008
|
+
variant?: string;
|
|
1009
|
+
pageContext?: unknown;
|
|
1010
|
+
tone?: string;
|
|
1011
|
+
}): Promise<{
|
|
1012
|
+
data: any;
|
|
1013
|
+
}>;
|
|
1014
|
+
/** One-shot: generate AI page AND persist as a draft (or published) document. */
|
|
1015
|
+
createPageFromPrompt(input: {
|
|
1016
|
+
prompt: string;
|
|
1017
|
+
template?: string;
|
|
1018
|
+
context?: string;
|
|
1019
|
+
steps?: string[];
|
|
1020
|
+
tone?: string;
|
|
1021
|
+
collection?: string;
|
|
1022
|
+
title?: string;
|
|
1023
|
+
slug?: string;
|
|
1024
|
+
status?: 'DRAFT' | 'PUBLISHED';
|
|
1025
|
+
publish?: boolean;
|
|
1026
|
+
}): Promise<{
|
|
1027
|
+
data: {
|
|
1028
|
+
document: any;
|
|
1029
|
+
generation: {
|
|
1030
|
+
steps: any[];
|
|
1031
|
+
totalTokensUsed: number;
|
|
1032
|
+
totalDurationMs: number;
|
|
1033
|
+
};
|
|
1034
|
+
};
|
|
1035
|
+
}>;
|
|
1036
|
+
/**
|
|
1037
|
+
* Generic AI content authoring for any non-page collection. Used by the
|
|
1038
|
+
* per-type MCP tools (create_blog_post, create_case_study, etc.) and by the
|
|
1039
|
+
* generic create_in_collection tool.
|
|
1040
|
+
*/
|
|
1041
|
+
createDocumentFromPrompt(collection: string, input: {
|
|
1042
|
+
prompt: string;
|
|
1043
|
+
context?: string;
|
|
1044
|
+
tone?: string;
|
|
1045
|
+
targetAudience?: string;
|
|
1046
|
+
title?: string;
|
|
1047
|
+
slug?: string;
|
|
1048
|
+
status?: 'DRAFT' | 'PUBLISHED';
|
|
1049
|
+
publish?: boolean;
|
|
1050
|
+
}): Promise<{
|
|
1051
|
+
data: {
|
|
1052
|
+
document: any;
|
|
1053
|
+
generation: {
|
|
1054
|
+
usage: {
|
|
1055
|
+
promptTokens: number;
|
|
1056
|
+
completionTokens: number;
|
|
1057
|
+
};
|
|
1058
|
+
durationMs: number;
|
|
1059
|
+
seo: {
|
|
1060
|
+
metaTitle?: string;
|
|
1061
|
+
metaDescription?: string;
|
|
1062
|
+
schemaType?: string;
|
|
1063
|
+
};
|
|
1064
|
+
};
|
|
1065
|
+
};
|
|
1066
|
+
}>;
|
|
1067
|
+
/**
|
|
1068
|
+
* Run the composite content audit (SEO + readability + a11y + freshness)
|
|
1069
|
+
* against a stored document or ad-hoc content. Deterministic — no LLM call,
|
|
1070
|
+
* safe to invoke on every save.
|
|
1071
|
+
*/
|
|
1072
|
+
auditDocument(input: {
|
|
1073
|
+
collection?: string;
|
|
1074
|
+
id?: string;
|
|
1075
|
+
title?: string;
|
|
1076
|
+
body?: string;
|
|
1077
|
+
metaTitle?: string;
|
|
1078
|
+
metaDescription?: string;
|
|
1079
|
+
targetKeyword?: string;
|
|
1080
|
+
url?: string;
|
|
1081
|
+
headings?: string[];
|
|
1082
|
+
images?: Array<{
|
|
1083
|
+
src: string;
|
|
1084
|
+
alt?: string;
|
|
1085
|
+
}>;
|
|
1086
|
+
internalLinks?: string[];
|
|
1087
|
+
externalLinks?: string[];
|
|
1088
|
+
publishedAt?: string;
|
|
1089
|
+
updatedAt?: string;
|
|
1090
|
+
contentType?: string;
|
|
1091
|
+
}): Promise<{
|
|
1092
|
+
data: any;
|
|
1093
|
+
}>;
|
|
1094
|
+
/**
|
|
1095
|
+
* Inline AI co-author. Wraps the plugin-ai writing assistant
|
|
1096
|
+
* (rewrite/expand/compress/proofread) behind a single HTTP surface.
|
|
1097
|
+
*/
|
|
1098
|
+
coauthor(input: {
|
|
1099
|
+
action: 'rewrite' | 'expand' | 'compress' | 'proofread';
|
|
1100
|
+
text: string;
|
|
1101
|
+
style?: string;
|
|
1102
|
+
tone?: string;
|
|
1103
|
+
instructions?: string;
|
|
1104
|
+
targetLength?: number;
|
|
1105
|
+
collectionSlug?: string;
|
|
1106
|
+
targetAudience?: string;
|
|
1107
|
+
}): Promise<{
|
|
1108
|
+
data: {
|
|
1109
|
+
action: string;
|
|
1110
|
+
text: string;
|
|
1111
|
+
changes?: unknown[];
|
|
1112
|
+
};
|
|
1113
|
+
}>;
|
|
1114
|
+
/**
|
|
1115
|
+
* Suggest internal links from a body of text against the published CMS
|
|
1116
|
+
* documents the API key can read. Optionally scope to a single collection.
|
|
1117
|
+
*/
|
|
1118
|
+
suggestInternalLinks(input: {
|
|
1119
|
+
content: string;
|
|
1120
|
+
collection?: string;
|
|
1121
|
+
limit?: number;
|
|
1122
|
+
excludeId?: string;
|
|
1123
|
+
}): Promise<{
|
|
1124
|
+
data: {
|
|
1125
|
+
suggestions: any[];
|
|
1126
|
+
candidatesScanned: number;
|
|
1127
|
+
};
|
|
1128
|
+
}>;
|
|
1129
|
+
/** Fetch the auto-generated OpenAPI spec, useful for tools listing collections + fields. */
|
|
1130
|
+
getOpenApi(): Promise<any>;
|
|
1131
|
+
/**
|
|
1132
|
+
* List configured collections via the dedicated `/collections`
|
|
1133
|
+
* discovery endpoint. Returns `{ data: CollectionMeta[] }` with one
|
|
1134
|
+
* entry per configured collection (`slug`, `labels`, `type`, `fields`,
|
|
1135
|
+
* `fieldCount`, `hidden`). Sourced directly from `getPrentaConfig()`
|
|
1136
|
+
* server-side, so it stays accurate even when OpenAPI spec generation
|
|
1137
|
+
* is missing routes. Newer endpoint — older CMS servers may return
|
|
1138
|
+
* 404; `list_collections` falls back to OpenAPI in that case.
|
|
1139
|
+
*/
|
|
1140
|
+
listCollections(): Promise<{
|
|
1141
|
+
data: Array<{
|
|
1142
|
+
slug: string;
|
|
1143
|
+
labels: {
|
|
1144
|
+
singular: string;
|
|
1145
|
+
plural: string;
|
|
1146
|
+
};
|
|
1147
|
+
type: string;
|
|
1148
|
+
urlPrefix: string;
|
|
1149
|
+
hidden: boolean;
|
|
1150
|
+
fieldCount: number;
|
|
1151
|
+
fields: Array<{
|
|
1152
|
+
name: string;
|
|
1153
|
+
type: string;
|
|
1154
|
+
required: boolean;
|
|
1155
|
+
relationTo?: string | string[];
|
|
1156
|
+
hasMany?: boolean;
|
|
1157
|
+
options?: Array<{
|
|
1158
|
+
label: string;
|
|
1159
|
+
value: string;
|
|
1160
|
+
}>;
|
|
1161
|
+
}>;
|
|
1162
|
+
}>;
|
|
1163
|
+
}>;
|
|
1164
|
+
/**
|
|
1165
|
+
* Lightweight user directory lookup used by the comment mention
|
|
1166
|
+
* picker. Returns up to 10 active users whose name OR email matches
|
|
1167
|
+
* `query` (case-insensitive `contains`). Empty / whitespace
|
|
1168
|
+
* queries return an empty array — the endpoint deliberately won't
|
|
1169
|
+
* dump the whole user table.
|
|
1170
|
+
*
|
|
1171
|
+
* Exposed via the `search_users` MCP tool so AI agents can resolve
|
|
1172
|
+
* `@<userId>` mentions when drafting comments.
|
|
1173
|
+
*/
|
|
1174
|
+
searchUsers(query: string): Promise<{
|
|
1175
|
+
data: Array<{
|
|
1176
|
+
id: string;
|
|
1177
|
+
name: string;
|
|
1178
|
+
email: string;
|
|
1179
|
+
}>;
|
|
1180
|
+
}>;
|
|
1181
|
+
/** Resolve a public URL path to its document. */
|
|
1182
|
+
resolveUrl(path: string): Promise<{
|
|
1183
|
+
data: any;
|
|
1184
|
+
}>;
|
|
1185
|
+
/**
|
|
1186
|
+
* Soft-check navigation item URLs against published documents + redirects.
|
|
1187
|
+
* Returns warnings only — never blocks save. Requires cms-core with
|
|
1188
|
+
* `POST /navigations/validate-targets`.
|
|
1189
|
+
*/
|
|
1190
|
+
validateNavigationTargets(items: unknown[]): Promise<{
|
|
1191
|
+
data: {
|
|
1192
|
+
warnings: string[];
|
|
1193
|
+
unresolved: Array<{
|
|
1194
|
+
itemPath: string;
|
|
1195
|
+
label: string;
|
|
1196
|
+
url: string;
|
|
1197
|
+
normalizedPath: string;
|
|
1198
|
+
}>;
|
|
1199
|
+
};
|
|
1200
|
+
}>;
|
|
1201
|
+
/** Run the deterministic SEO content analysis against a stored document. */
|
|
1202
|
+
seoAnalyzeDocument(documentId: string): Promise<any>;
|
|
1203
|
+
/** Compute a Flesch-Kincaid / grade-level readability score for a document body. */
|
|
1204
|
+
seoReadability(documentId: string): Promise<any>;
|
|
1205
|
+
/**
|
|
1206
|
+
* Suggest internal-link targets for a document. Title-keyword-based (extracts
|
|
1207
|
+
* up to 5 keywords > 3 chars from the doc title, matches them against
|
|
1208
|
+
* other PUBLISHED documents). Returns up to 10 suggestions. Distinct from
|
|
1209
|
+
* `suggestInternalLinks` above, which is content-based and freeform.
|
|
1210
|
+
*
|
|
1211
|
+
* `title` and `slug` are typed nullable because the underlying Prisma
|
|
1212
|
+
* `Document` schema makes them optional (`String?`) — the API forwards
|
|
1213
|
+
* the raw column values, so callers must handle nulls before rendering
|
|
1214
|
+
* anchor text or building target URLs.
|
|
1215
|
+
*/
|
|
1216
|
+
seoInternalLinks(documentId: string): Promise<{
|
|
1217
|
+
suggestions: Array<{
|
|
1218
|
+
id: string;
|
|
1219
|
+
title: string | null;
|
|
1220
|
+
slug: string | null;
|
|
1221
|
+
collection: string;
|
|
1222
|
+
}>;
|
|
1223
|
+
}>;
|
|
1224
|
+
/** Build the OG / Twitter / canonical meta-tag preview for a document. */
|
|
1225
|
+
seoMetaPreview(documentId: string): Promise<any>;
|
|
1226
|
+
/** Build the Schema.org JSON-LD graph for a document. */
|
|
1227
|
+
seoSchemaGraph(documentId: string): Promise<any>;
|
|
1228
|
+
/**
|
|
1229
|
+
* Discover the registered section types + their content/field schemas.
|
|
1230
|
+
* Each entry carries `id`, `name`, `description`, `category`, `scope`,
|
|
1231
|
+
* `fields` (the inspector schema), and `defaultContent` / `defaultSettings`.
|
|
1232
|
+
*
|
|
1233
|
+
* Pass `scope: 'page'` or `scope: 'post'` to list only the types usable on
|
|
1234
|
+
* that surface (so an agent building a post template doesn't pick a
|
|
1235
|
+
* page-only type, and vice versa).
|
|
1236
|
+
*/
|
|
1237
|
+
listSectionTypes(scope?: 'page' | 'post'): Promise<{
|
|
1238
|
+
data: any[];
|
|
1239
|
+
}>;
|
|
1240
|
+
/**
|
|
1241
|
+
* Validate section content against its type schema. Deterministic (no
|
|
1242
|
+
* LLM). Returns `{ valid, errors, warnings }` where each issue is
|
|
1243
|
+
* `{ sectionType, field?, message }`. An unknown `sectionType` is a hard
|
|
1244
|
+
* error (no registered renderer).
|
|
1245
|
+
*/
|
|
1246
|
+
validateSection(input: {
|
|
1247
|
+
sectionType: string;
|
|
1248
|
+
content: Record<string, unknown>;
|
|
1249
|
+
settings?: Record<string, unknown>;
|
|
1250
|
+
}): Promise<{
|
|
1251
|
+
data: {
|
|
1252
|
+
valid: boolean;
|
|
1253
|
+
errors: Array<{
|
|
1254
|
+
sectionType: string;
|
|
1255
|
+
field?: string;
|
|
1256
|
+
message: string;
|
|
1257
|
+
}>;
|
|
1258
|
+
warnings: Array<{
|
|
1259
|
+
sectionType: string;
|
|
1260
|
+
field?: string;
|
|
1261
|
+
message: string;
|
|
1262
|
+
}>;
|
|
1263
|
+
};
|
|
1264
|
+
}>;
|
|
1265
|
+
/** List a page's ordered sections (`data.sections`). */
|
|
1266
|
+
listPageSections(collection: string, id: string): Promise<{
|
|
1267
|
+
data: any[];
|
|
1268
|
+
}>;
|
|
1269
|
+
/**
|
|
1270
|
+
* Add a section to a page. The server seeds registry defaults for
|
|
1271
|
+
* `sectionType`, shallow-merges the supplied `content` / `settings`,
|
|
1272
|
+
* generates an id, inserts at `position` (default end), and persists.
|
|
1273
|
+
* Returns `{ section, sections, validation }`.
|
|
1274
|
+
*/
|
|
1275
|
+
addPageSection(collection: string, id: string, body: {
|
|
1276
|
+
sectionType: string;
|
|
1277
|
+
name?: string;
|
|
1278
|
+
internalLabel?: string;
|
|
1279
|
+
visible?: boolean;
|
|
1280
|
+
content?: Record<string, unknown>;
|
|
1281
|
+
settings?: Record<string, unknown>;
|
|
1282
|
+
position?: number;
|
|
1283
|
+
}): Promise<{
|
|
1284
|
+
data: {
|
|
1285
|
+
section: any;
|
|
1286
|
+
sections: any[];
|
|
1287
|
+
validation: any;
|
|
1288
|
+
};
|
|
1289
|
+
}>;
|
|
1290
|
+
/**
|
|
1291
|
+
* Update a section in place. Only supplied fields change; `content` /
|
|
1292
|
+
* `settings` are shallow-merged. Returns `{ section, sections, validation }`.
|
|
1293
|
+
*/
|
|
1294
|
+
updatePageSection(collection: string, id: string, sectionId: string, body: {
|
|
1295
|
+
content?: Record<string, unknown>;
|
|
1296
|
+
settings?: Record<string, unknown>;
|
|
1297
|
+
name?: string;
|
|
1298
|
+
internalLabel?: string;
|
|
1299
|
+
visible?: boolean;
|
|
1300
|
+
}): Promise<{
|
|
1301
|
+
data: {
|
|
1302
|
+
section: any;
|
|
1303
|
+
sections: any[];
|
|
1304
|
+
validation: any;
|
|
1305
|
+
};
|
|
1306
|
+
}>;
|
|
1307
|
+
/** Reorder a page's sections to an explicit id order. Returns the new array. */
|
|
1308
|
+
reorderPageSections(collection: string, id: string, orderedIds: string[]): Promise<{
|
|
1309
|
+
data: any[];
|
|
1310
|
+
}>;
|
|
1311
|
+
/** Remove a section from a page. Returns the remaining sections. */
|
|
1312
|
+
removePageSection(collection: string, id: string, sectionId: string): Promise<{
|
|
1313
|
+
data: {
|
|
1314
|
+
sections: any[];
|
|
1315
|
+
};
|
|
1316
|
+
}>;
|
|
1317
|
+
/**
|
|
1318
|
+
* Read a post type's template: `{ postType, docId, header, sections }`.
|
|
1319
|
+
* Returns an unsaved default (centered header, empty section list) when
|
|
1320
|
+
* the type has no saved template yet (`docId: null`).
|
|
1321
|
+
*/
|
|
1322
|
+
getPostTemplate(postType: string): Promise<{
|
|
1323
|
+
data: {
|
|
1324
|
+
postType: string;
|
|
1325
|
+
docId: string | null;
|
|
1326
|
+
header: any;
|
|
1327
|
+
sections: any[];
|
|
1328
|
+
};
|
|
1329
|
+
}>;
|
|
1330
|
+
/**
|
|
1331
|
+
* Create or replace a post type's template. `header` is coerced to a full
|
|
1332
|
+
* header config (missing keys filled with defaults) and `sections` are
|
|
1333
|
+
* coerced (unknown section types dropped) so the stored template always
|
|
1334
|
+
* renders. Use only `scope: 'post'` section types in `sections`.
|
|
1335
|
+
*/
|
|
1336
|
+
savePostTemplate(postType: string, body: {
|
|
1337
|
+
header?: Record<string, unknown>;
|
|
1338
|
+
sections?: any[];
|
|
1339
|
+
}): Promise<{
|
|
1340
|
+
data: {
|
|
1341
|
+
postType: string;
|
|
1342
|
+
docId: string | null;
|
|
1343
|
+
header: any;
|
|
1344
|
+
sections: any[];
|
|
1345
|
+
};
|
|
1346
|
+
}>;
|
|
1347
|
+
}
|
|
1348
|
+
//# sourceMappingURL=client.d.ts.map
|