@nuxtseo/protocol 0.1.5 → 0.2.1

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.
@@ -142,9 +142,253 @@ export const timelineEntriesOperation = defineOperation({
142
142
  }],
143
143
  },
144
144
  });
145
+ // ---------------------------------------------------------------------------
146
+ // Chart annotations — the date-anchored markers behind the `manage_annotations`
147
+ // MCP tool. An agent that ships a fix marks the day it shipped, so the next
148
+ // traffic or ranking move has a cause on the chart beside it.
149
+ // ---------------------------------------------------------------------------
150
+ /**
151
+ * A user-picked calendar day is a reporting-day bucket, never an instant
152
+ * (ADR-0082). It stays `YYYY-MM-DD` text so the marker lands on the chosen day
153
+ * in every timezone.
154
+ */
155
+ export const annotationDaySchema = schema.iso.date();
156
+ /** http(s) only: this value is rendered as a link in marker tooltips. */
157
+ export const annotationLinkSchema = schema.string().url().max(500).refine(value => /^https?:\/\//i.test(value), { message: 'only http(s) URLs are allowed' });
158
+ export const annotationParamsSchema = schema.strictObject({
159
+ siteId: schema.string().min(1),
160
+ annotationId: schema.string().min(1),
161
+ });
162
+ export const annotationCreateBodySchema = schema.strictObject({
163
+ date: annotationDaySchema,
164
+ title: schema.string().trim().min(1).max(120),
165
+ note: schema.string().trim().max(1000).nullish(),
166
+ url: annotationLinkSchema.nullish(),
167
+ });
168
+ export const annotationUpdateBodySchema = schema.strictObject({
169
+ date: annotationDaySchema.optional(),
170
+ title: schema.string().trim().min(1).max(120).optional(),
171
+ note: schema.string().trim().max(1000).nullish(),
172
+ url: annotationLinkSchema.nullish(),
173
+ });
174
+ export const annotationSchema = defineResponseObject({
175
+ id: schema.string().min(1),
176
+ date: schema.string().min(1),
177
+ title: schema.string(),
178
+ note: schema.string().nullable(),
179
+ url: schema.string().nullable(),
180
+ }, {
181
+ id: schema.string().min(1),
182
+ date: schema.string().min(1),
183
+ title: schema.string(),
184
+ note: schema.string().nullable(),
185
+ url: schema.string().nullable(),
186
+ });
187
+ export const annotationListDataSchema = defineResponseObject({
188
+ annotations: schema.array(annotationSchema.producer),
189
+ total: schema.number().int().nonnegative(),
190
+ limit: schema.number().int().positive(),
191
+ }, {
192
+ annotations: schema.array(annotationSchema.client),
193
+ total: schema.number().int().nonnegative(),
194
+ limit: schema.number().int().positive(),
195
+ });
196
+ export const annotationListResponseSchema = defineSuccessEnvelope(annotationListDataSchema);
197
+ export const annotationDataSchema = defineResponseObject({ annotation: annotationSchema.producer }, { annotation: annotationSchema.client });
198
+ export const annotationResponseSchema = defineSuccessEnvelope(annotationDataSchema);
199
+ export const annotationDeleteDataSchema = defineResponseObject({ annotationId: schema.string().min(1), deleted: schema.literal(true) }, { annotationId: schema.string().min(1), deleted: schema.literal(true) });
200
+ export const annotationDeleteResponseSchema = defineSuccessEnvelope(annotationDeleteDataSchema);
201
+ const annotationReadErrors = [
202
+ 'invalid_request',
203
+ 'unauthorized',
204
+ 'auth_expired',
205
+ 'forbidden',
206
+ 'not_found',
207
+ 'rate_limited',
208
+ 'contract_violation',
209
+ 'internal_error',
210
+ ];
211
+ const annotationWriteErrors = [
212
+ 'invalid_request',
213
+ 'unauthorized',
214
+ 'auth_expired',
215
+ 'forbidden',
216
+ 'not_found',
217
+ 'conflict',
218
+ 'quota_exhausted',
219
+ 'rate_limited',
220
+ 'contract_violation',
221
+ 'internal_error',
222
+ ];
223
+ const annotationWriteAuth = {
224
+ credentials: ['session', 'api_token'],
225
+ scopes: ['timeline:write'],
226
+ minimumRole: 'member',
227
+ ownership: 'site',
228
+ };
229
+ const annotationWritePolicy = {
230
+ rate: publicV1StandardRatePolicy,
231
+ idempotency: { _tag: 'none' },
232
+ entitlement: null,
233
+ meter: null,
234
+ };
235
+ const annotationWriteResources = {
236
+ reads: [{ type: 'site.timeline', idFrom: 'params.siteId' }],
237
+ changes: [{ type: 'site.timeline', idFrom: 'params.siteId' }],
238
+ };
239
+ export const annotationListOperation = defineOperation({
240
+ id: 'timeline.annotations.list',
241
+ method: 'GET',
242
+ path: '/api/v1/sites/{siteId}/timeline/annotations',
243
+ visibility: 'public',
244
+ semantics: { kind: 'query', sideEffects: 'none', retry: 'idempotent', readConsistency: 'primary' },
245
+ auth: {
246
+ credentials: ['session', 'api_token'],
247
+ scopes: ['timeline:read'],
248
+ minimumRole: 'viewer',
249
+ ownership: 'site',
250
+ },
251
+ request: { params: timelineSiteParamsSchema, query: null, headers: null, body: null },
252
+ responses: { 200: annotationListResponseSchema },
253
+ errors: annotationReadErrors,
254
+ errorResponse: publicV1ErrorEnvelope,
255
+ resources: { reads: [{ type: 'site.timeline', idFrom: 'params.siteId' }], changes: [] },
256
+ policy: {
257
+ rate: publicV1StandardRatePolicy,
258
+ idempotency: { _tag: 'none' },
259
+ entitlement: null,
260
+ meter: null,
261
+ },
262
+ lifecycle: { introduced: '1.2.0' },
263
+ docs: {
264
+ summary: 'List chart annotations',
265
+ description: 'Returns this Site own chart annotations, oldest day first. These are the markers a person or an agent placed; detected events and releases are read through the Timeline instead. `limit` is the per-Site cap, not a paging window.',
266
+ tags: ['Timeline'],
267
+ examples: [{
268
+ request: { params: { siteId: 'site_01JXYZ' } },
269
+ response: {
270
+ status: 200,
271
+ body: {
272
+ data: {
273
+ annotations: [{
274
+ id: 'ann_01JXYZ',
275
+ date: '2026-07-30',
276
+ title: 'Shipped v3.2',
277
+ note: 'Canonical tags repaired across /docs',
278
+ url: 'https://github.com/owner/repo/releases/tag/v3.2.0',
279
+ }],
280
+ total: 1,
281
+ limit: 100,
282
+ },
283
+ meta: { requestId: 'req_01JXYZ', version: '1.0' },
284
+ },
285
+ },
286
+ }],
287
+ },
288
+ });
289
+ export const annotationCreateOperation = defineOperation({
290
+ id: 'timeline.annotations.create',
291
+ method: 'POST',
292
+ path: '/api/v1/sites/{siteId}/timeline/annotations',
293
+ visibility: 'public',
294
+ semantics: { kind: 'mutation', sideEffects: 'state', retry: 'never', readConsistency: null },
295
+ auth: annotationWriteAuth,
296
+ request: { params: timelineSiteParamsSchema, query: null, headers: null, body: annotationCreateBodySchema },
297
+ responses: { 200: annotationResponseSchema },
298
+ errors: annotationWriteErrors,
299
+ errorResponse: publicV1ErrorEnvelope,
300
+ resources: annotationWriteResources,
301
+ policy: annotationWritePolicy,
302
+ lifecycle: { introduced: '1.2.0' },
303
+ docs: {
304
+ summary: 'Create a chart annotation',
305
+ description: 'Places one dated marker on this Site charts. Mark the day work shipped, so the next traffic or ranking move has a cause beside it. The Site has a fixed annotation cap; when it is reached the request is rejected and stale markers must be deleted first.',
306
+ tags: ['Timeline'],
307
+ examples: [{
308
+ request: {
309
+ params: { siteId: 'site_01JXYZ' },
310
+ body: { date: '2026-07-30', title: 'Shipped v3.2', note: null, url: null },
311
+ },
312
+ response: {
313
+ status: 200,
314
+ body: {
315
+ data: { annotation: { id: 'ann_01JXYZ', date: '2026-07-30', title: 'Shipped v3.2', note: null, url: null } },
316
+ meta: { requestId: 'req_01JXYZ', version: '1.0' },
317
+ },
318
+ },
319
+ }],
320
+ },
321
+ });
322
+ export const annotationUpdateOperation = defineOperation({
323
+ id: 'timeline.annotations.update',
324
+ method: 'PATCH',
325
+ path: '/api/v1/sites/{siteId}/timeline/annotations/{annotationId}',
326
+ visibility: 'public',
327
+ semantics: { kind: 'mutation', sideEffects: 'state', retry: 'never', readConsistency: null },
328
+ auth: annotationWriteAuth,
329
+ request: { params: annotationParamsSchema, query: null, headers: null, body: annotationUpdateBodySchema },
330
+ responses: { 200: annotationResponseSchema },
331
+ errors: annotationWriteErrors,
332
+ errorResponse: publicV1ErrorEnvelope,
333
+ resources: annotationWriteResources,
334
+ policy: annotationWritePolicy,
335
+ lifecycle: { introduced: '1.2.0' },
336
+ docs: {
337
+ summary: 'Update a chart annotation',
338
+ description: 'Changes the day, title, note, or link of one annotation. Send only the fields that change. Pass null for note or url to clear it.',
339
+ tags: ['Timeline'],
340
+ examples: [{
341
+ request: {
342
+ params: { siteId: 'site_01JXYZ', annotationId: 'ann_01JXYZ' },
343
+ body: { title: 'Shipped v3.2.1' },
344
+ },
345
+ response: {
346
+ status: 200,
347
+ body: {
348
+ data: { annotation: { id: 'ann_01JXYZ', date: '2026-07-30', title: 'Shipped v3.2.1', note: null, url: null } },
349
+ meta: { requestId: 'req_01JXYZ', version: '1.0' },
350
+ },
351
+ },
352
+ }],
353
+ },
354
+ });
355
+ export const annotationDeleteOperation = defineOperation({
356
+ id: 'timeline.annotations.delete',
357
+ method: 'DELETE',
358
+ path: '/api/v1/sites/{siteId}/timeline/annotations/{annotationId}',
359
+ visibility: 'public',
360
+ semantics: { kind: 'mutation', sideEffects: 'state', retry: 'never', readConsistency: null },
361
+ auth: annotationWriteAuth,
362
+ request: { params: annotationParamsSchema, query: null, headers: null, body: null },
363
+ responses: { 200: annotationDeleteResponseSchema },
364
+ errors: annotationWriteErrors,
365
+ errorResponse: publicV1ErrorEnvelope,
366
+ resources: annotationWriteResources,
367
+ policy: annotationWritePolicy,
368
+ lifecycle: { introduced: '1.2.0' },
369
+ docs: {
370
+ summary: 'Delete a chart annotation',
371
+ description: 'Removes one annotation permanently. Deleting frees a slot against the per-Site cap.',
372
+ tags: ['Timeline'],
373
+ examples: [{
374
+ request: { params: { siteId: 'site_01JXYZ', annotationId: 'ann_01JXYZ' } },
375
+ response: {
376
+ status: 200,
377
+ body: {
378
+ data: { annotationId: 'ann_01JXYZ', deleted: true },
379
+ meta: { requestId: 'req_01JXYZ', version: '1.0' },
380
+ },
381
+ },
382
+ }],
383
+ },
384
+ });
145
385
  export const timelineV1Registry = defineRegistry({
146
386
  feature: 'timeline',
147
387
  operations: {
148
388
  entries: timelineEntriesOperation,
389
+ listAnnotations: annotationListOperation,
390
+ createAnnotation: annotationCreateOperation,
391
+ updateAnnotation: annotationUpdateOperation,
392
+ deleteAnnotation: annotationDeleteOperation,
149
393
  },
150
394
  });