@nuxtseo/protocol 0.1.4 → 0.2.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/dist/v1/sites.js CHANGED
@@ -93,9 +93,239 @@ export const sitesListOperation = defineOperation({
93
93
  }],
94
94
  },
95
95
  });
96
+ // ---------------------------------------------------------------------------
97
+ // Site status — the flagship read behind the `site_status` MCP tool. One call
98
+ // answers "what is wrong with this Site and what should I do first": the
99
+ // assessment verdict, the single ranked Next Action, the worklist counts, the
100
+ // recent changes, and the Receipts for work already verified.
101
+ // ---------------------------------------------------------------------------
102
+ export const siteStatusParamsSchema = schema.strictObject({
103
+ siteId: schema.string().min(1),
104
+ });
105
+ export const siteAssessmentSchema = defineResponseObject({
106
+ reachStage: schema.string().nullable(),
107
+ healthStage: schema.string().nullable(),
108
+ headlineAxis: schema.string().nullable(),
109
+ summary: schema.string(),
110
+ recommendation: schema.string(),
111
+ assessedAt: schema.string().datetime().nullable(),
112
+ }, {
113
+ reachStage: schema.string().nullable(),
114
+ healthStage: schema.string().nullable(),
115
+ headlineAxis: schema.string().nullable(),
116
+ summary: schema.string(),
117
+ recommendation: schema.string(),
118
+ assessedAt: schema.string().datetime().nullable(),
119
+ });
120
+ /**
121
+ * The one ranked thing to do next. `kind` says what it is: `connect` asks for a
122
+ * missing data source, `action` points at a specific open Action by id, and
123
+ * `dimension` names an area with no single backing Action yet. Only `action`
124
+ * carries an `actionId` a caller can resolve.
125
+ */
126
+ export const siteNextActionSchema = defineResponseObject({
127
+ kind: schema.enum(['connect', 'action', 'dimension']),
128
+ label: schema.string(),
129
+ detail: schema.string().nullable(),
130
+ focus: schema.string().nullable(),
131
+ dimension: schema.string().nullable(),
132
+ urgent: schema.boolean(),
133
+ /** Organic traffic at stake, the ranking magnitude. GLOSSARY: Impact. */
134
+ impact: schema.number().nullable(),
135
+ actionId: schema.string().nullable(),
136
+ citedProofKey: schema.string().nullable(),
137
+ }, {
138
+ kind: schema.enum(['connect', 'action', 'dimension']),
139
+ label: schema.string(),
140
+ detail: schema.string().nullable(),
141
+ focus: schema.string().nullable(),
142
+ dimension: schema.string().nullable(),
143
+ urgent: schema.boolean(),
144
+ impact: schema.number().nullable(),
145
+ actionId: schema.string().nullable(),
146
+ citedProofKey: schema.string().nullable(),
147
+ });
148
+ export const siteStatusChangeSchema = defineResponseObject({
149
+ kind: schema.string(),
150
+ source: schema.string().nullable(),
151
+ label: schema.string(),
152
+ direction: schema.enum(['up', 'down', 'flat']),
153
+ value: schema.string().nullable(),
154
+ deltaPct: schema.number().nullable(),
155
+ window: schema.string().nullable(),
156
+ meaning: schema.string().nullable(),
157
+ }, {
158
+ kind: schema.string(),
159
+ source: schema.string().nullable(),
160
+ label: schema.string(),
161
+ direction: schema.enum(['up', 'down', 'flat']),
162
+ value: schema.string().nullable(),
163
+ deltaPct: schema.number().nullable(),
164
+ window: schema.string().nullable(),
165
+ meaning: schema.string().nullable(),
166
+ });
167
+ export const siteStatusReceiptSchema = defineResponseObject({
168
+ actionId: schema.string().min(1),
169
+ title: schema.string(),
170
+ outcome: schema.string(),
171
+ observedAt: schema.string(),
172
+ note: schema.string().nullable(),
173
+ }, {
174
+ actionId: schema.string().min(1),
175
+ title: schema.string(),
176
+ outcome: schema.string(),
177
+ observedAt: schema.string(),
178
+ note: schema.string().nullable(),
179
+ });
180
+ const siteStatusCountsShape = {
181
+ open: schema.number().int().nonnegative(),
182
+ inProgress: schema.number().int().nonnegative(),
183
+ shipped: schema.number().int().nonnegative(),
184
+ landed: schema.number().int().nonnegative(),
185
+ noEffect: schema.number().int().nonnegative(),
186
+ };
187
+ /**
188
+ * `dataQuality.status` is not decoration. `degraded` means the assessment ran
189
+ * with one or more proofs unavailable, so the verdict is real but incomplete;
190
+ * `unavailable` means it has not run at all. Read it before quoting the
191
+ * assessment as the Site's state.
192
+ */
193
+ const siteStatusDataQualityShape = {
194
+ status: schema.enum(['complete', 'degraded', 'unavailable']),
195
+ reason: schema.string().nullable(),
196
+ unavailableProofs: schema.array(schema.string()),
197
+ };
198
+ export const siteStatusDataSchema = defineResponseObject({
199
+ site: schema.string(),
200
+ available: schema.boolean(),
201
+ message: schema.string().nullable(),
202
+ assessment: siteAssessmentSchema.producer.nullable(),
203
+ nextAction: siteNextActionSchema.producer.nullable(),
204
+ dataQuality: schema.strictObject(siteStatusDataQualityShape),
205
+ openCount: schema.number().int().nonnegative(),
206
+ verifiedCount: schema.number().int().nonnegative(),
207
+ counts: schema.strictObject(siteStatusCountsShape),
208
+ degradedSteps: schema.array(schema.string()),
209
+ changes: schema.array(siteStatusChangeSchema.producer),
210
+ receipts: schema.array(siteStatusReceiptSchema.producer),
211
+ }, {
212
+ site: schema.string(),
213
+ available: schema.boolean(),
214
+ message: schema.string().nullable(),
215
+ assessment: siteAssessmentSchema.client.nullable(),
216
+ nextAction: siteNextActionSchema.client.nullable(),
217
+ dataQuality: schema.looseObject(siteStatusDataQualityShape),
218
+ openCount: schema.number().int().nonnegative(),
219
+ verifiedCount: schema.number().int().nonnegative(),
220
+ counts: schema.looseObject(siteStatusCountsShape),
221
+ degradedSteps: schema.array(schema.string()),
222
+ changes: schema.array(siteStatusChangeSchema.client),
223
+ receipts: schema.array(siteStatusReceiptSchema.client),
224
+ });
225
+ export const siteStatusResponseSchema = defineSuccessEnvelope(siteStatusDataSchema);
226
+ export const siteStatusOperation = defineOperation({
227
+ id: 'sites.status',
228
+ method: 'GET',
229
+ path: '/api/v1/sites/{siteId}/status',
230
+ visibility: 'public',
231
+ semantics: { kind: 'query', sideEffects: 'none', retry: 'idempotent', readConsistency: 'primary' },
232
+ auth: {
233
+ credentials: ['session', 'api_token'],
234
+ scopes: ['sites:read'],
235
+ minimumRole: 'viewer',
236
+ ownership: 'site',
237
+ },
238
+ request: { params: siteStatusParamsSchema, query: null, headers: null, body: null },
239
+ responses: { 200: siteStatusResponseSchema },
240
+ errors: [
241
+ 'invalid_request',
242
+ 'unauthorized',
243
+ 'auth_expired',
244
+ 'forbidden',
245
+ // The Site monitoring fence this read enforces denies with these two:
246
+ // `entitlement_required` when the Site has no Pro Site access, `conflict` when
247
+ // it is archived or its collection is paused.
248
+ 'entitlement_required',
249
+ 'conflict',
250
+ 'not_found',
251
+ 'rate_limited',
252
+ 'contract_violation',
253
+ 'internal_error',
254
+ ],
255
+ errorResponse: publicV1ErrorEnvelope,
256
+ resources: { reads: [{ type: 'site.actions', idFrom: 'params.siteId' }], changes: [] },
257
+ policy: {
258
+ rate: publicV1StandardRatePolicy,
259
+ idempotency: { _tag: 'none' },
260
+ entitlement: null,
261
+ meter: null,
262
+ },
263
+ lifecycle: { introduced: '1.2.0' },
264
+ docs: {
265
+ summary: 'Get Site status',
266
+ description: 'Returns the current assessment for one Site: the verdict, the single ranked Next Action, open and verified counts, the pipeline steps that degraded on the last run, recent changes, and recent Receipts. `available: false` means the first assessment has not run yet. Start here before listing Actions.',
267
+ tags: ['Sites'],
268
+ examples: [{
269
+ request: { params: { siteId: 'site_01JXYZ' } },
270
+ response: {
271
+ status: 200,
272
+ body: {
273
+ data: {
274
+ site: 'https://example.com',
275
+ available: true,
276
+ message: null,
277
+ assessment: {
278
+ reachStage: 'growing',
279
+ healthStage: 'at-risk',
280
+ headlineAxis: 'health',
281
+ summary: 'Indexing is slipping on the docs template.',
282
+ recommendation: 'Fix the canonical tag on /docs pages.',
283
+ assessedAt: '2026-09-04T02:00:00.000Z',
284
+ },
285
+ nextAction: {
286
+ kind: 'action',
287
+ label: 'Fix indexing',
288
+ detail: 'Repair the canonical tag on 42 /docs pages',
289
+ focus: 'protect',
290
+ dimension: 'indexing',
291
+ urgent: true,
292
+ impact: 1840,
293
+ actionId: 'act_01JXYZ',
294
+ citedProofKey: 'gsc.indexState',
295
+ },
296
+ dataQuality: { status: 'complete', reason: null, unavailableProofs: [] },
297
+ openCount: 7,
298
+ verifiedCount: 3,
299
+ counts: { open: 5, inProgress: 2, shipped: 3, landed: 0, noEffect: 0 },
300
+ degradedSteps: [],
301
+ changes: [{
302
+ kind: 'indexing',
303
+ source: 'gsc',
304
+ label: 'Not-indexed pages rose to 42',
305
+ direction: 'up',
306
+ value: '42 pages',
307
+ deltaPct: 18.5,
308
+ window: '28d',
309
+ meaning: 'Google is declining more of this Site than last month.',
310
+ }],
311
+ receipts: [{
312
+ actionId: 'act_01JXYA',
313
+ title: 'Fixed: broken internal links in /guides',
314
+ outcome: 'verified',
315
+ observedAt: '2026-08-30',
316
+ note: null,
317
+ }],
318
+ },
319
+ meta: { requestId: 'req_01JXYZ', version: '1.0' },
320
+ },
321
+ },
322
+ }],
323
+ },
324
+ });
96
325
  export const sitesV1Registry = defineRegistry({
97
326
  feature: 'sites',
98
327
  operations: {
99
328
  list: sitesListOperation,
329
+ status: siteStatusOperation,
100
330
  },
101
331
  });