@caelo-cms/shared 0.2.2

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.
Files changed (77) hide show
  1. package/dist/ai-tools.d.ts +571 -0
  2. package/dist/ai-tools.d.ts.map +1 -0
  3. package/dist/ai-tools.js +696 -0
  4. package/dist/ai-tools.js.map +1 -0
  5. package/dist/auth-forms.d.ts +24 -0
  6. package/dist/auth-forms.d.ts.map +1 -0
  7. package/dist/auth-forms.js +27 -0
  8. package/dist/auth-forms.js.map +1 -0
  9. package/dist/cap-failures.d.ts +17 -0
  10. package/dist/cap-failures.d.ts.map +1 -0
  11. package/dist/cap-failures.js +58 -0
  12. package/dist/cap-failures.js.map +1 -0
  13. package/dist/content.d.ts +111 -0
  14. package/dist/content.d.ts.map +1 -0
  15. package/dist/content.js +137 -0
  16. package/dist/content.js.map +1 -0
  17. package/dist/context.d.ts +40 -0
  18. package/dist/context.d.ts.map +1 -0
  19. package/dist/context.js +3 -0
  20. package/dist/context.js.map +1 -0
  21. package/dist/i18n.d.ts +49 -0
  22. package/dist/i18n.d.ts.map +1 -0
  23. package/dist/i18n.js +154 -0
  24. package/dist/i18n.js.map +1 -0
  25. package/dist/index.d.ts +20 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +21 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/logger.d.ts +56 -0
  30. package/dist/logger.d.ts.map +1 -0
  31. package/dist/logger.js +84 -0
  32. package/dist/logger.js.map +1 -0
  33. package/dist/media.d.ts +143 -0
  34. package/dist/media.d.ts.map +1 -0
  35. package/dist/media.js +168 -0
  36. package/dist/media.js.map +1 -0
  37. package/dist/preview-compose.d.ts +84 -0
  38. package/dist/preview-compose.d.ts.map +1 -0
  39. package/dist/preview-compose.js +385 -0
  40. package/dist/preview-compose.js.map +1 -0
  41. package/dist/preview-scanner.d.ts +44 -0
  42. package/dist/preview-scanner.d.ts.map +1 -0
  43. package/dist/preview-scanner.js +177 -0
  44. package/dist/preview-scanner.js.map +1 -0
  45. package/dist/result.d.ts +21 -0
  46. package/dist/result.d.ts.map +1 -0
  47. package/dist/result.js +14 -0
  48. package/dist/result.js.map +1 -0
  49. package/dist/seo.d.ts +128 -0
  50. package/dist/seo.d.ts.map +1 -0
  51. package/dist/seo.js +176 -0
  52. package/dist/seo.js.map +1 -0
  53. package/dist/skills.d.ts +88 -0
  54. package/dist/skills.d.ts.map +1 -0
  55. package/dist/skills.js +127 -0
  56. package/dist/skills.js.map +1 -0
  57. package/dist/snapshots.d.ts +54 -0
  58. package/dist/snapshots.d.ts.map +1 -0
  59. package/dist/snapshots.js +59 -0
  60. package/dist/snapshots.js.map +1 -0
  61. package/dist/structured-sets.d.ts +116 -0
  62. package/dist/structured-sets.d.ts.map +1 -0
  63. package/dist/structured-sets.js +154 -0
  64. package/dist/structured-sets.js.map +1 -0
  65. package/dist/subagents.d.ts +123 -0
  66. package/dist/subagents.d.ts.map +1 -0
  67. package/dist/subagents.js +202 -0
  68. package/dist/subagents.js.map +1 -0
  69. package/dist/translation.d.ts +127 -0
  70. package/dist/translation.d.ts.map +1 -0
  71. package/dist/translation.js +208 -0
  72. package/dist/translation.js.map +1 -0
  73. package/dist/version.d.ts +46 -0
  74. package/dist/version.d.ts.map +1 -0
  75. package/dist/version.js +46 -0
  76. package/dist/version.js.map +1 -0
  77. package/package.json +38 -0
@@ -0,0 +1,696 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ /**
3
+ * Zod schemas for the AI tools shipped in P5. Lives in @caelo-cms/shared so
4
+ * the provider abstraction (which streams tool-call args from the LLM)
5
+ * and the tool dispatcher (which validates + invokes the handler)
6
+ * import from a single source.
7
+ *
8
+ * `.strict()` on every input — the LLM occasionally hallucinates fields
9
+ * and we want a typed rejection at the Validator boundary, not silent
10
+ * silent drops in the handler.
11
+ */
12
+ import { z } from "zod";
13
+ import { MODULE_CSS_MAX, MODULE_HTML_MAX, MODULE_JS_MAX } from "./content.js";
14
+ export const editModuleToolInput = z
15
+ .object({
16
+ moduleId: z.string().uuid(),
17
+ displayName: z.string().min(1).max(128).optional(),
18
+ html: z.string().max(MODULE_HTML_MAX).optional(),
19
+ css: z.string().max(MODULE_CSS_MAX).optional(),
20
+ js: z.string().max(MODULE_JS_MAX).optional(),
21
+ })
22
+ .strict();
23
+ export const siteMemoryProposeToolInput = z
24
+ .object({
25
+ slot: z.enum(["purpose", "brand-voice", "tone", "banned-phrases", "instructions", "glossary"]),
26
+ body: z.string().min(1).max(4000),
27
+ rationale: z.string().min(1).max(1000),
28
+ })
29
+ .strict();
30
+ /**
31
+ * The set of tools shipped in P5. Other phases extend by adding a new
32
+ * entry; the dispatcher walks this map at registration time.
33
+ */
34
+ /**
35
+ * P6.7.3 — `add_module_to_page` AI tool. Creates a new module and
36
+ * inserts it into a target page's block at the requested position. The
37
+ * AI passes html (and optionally css/js) and a sluggable displayName;
38
+ * the tool generates a unique slug.
39
+ */
40
+ export const addModuleToPageToolInput = z
41
+ .object({
42
+ pageId: z.string().uuid(),
43
+ blockName: z.string().min(1).max(80),
44
+ /** "top" | "bottom" | a 0-based integer index. */
45
+ position: z.union([z.enum(["top", "bottom"]), z.number().int().min(0).max(1000)]),
46
+ displayName: z.string().min(1).max(128),
47
+ html: z.string().min(1).max(50_000),
48
+ css: z.string().max(50_000).optional(),
49
+ js: z.string().max(50_000).optional(),
50
+ })
51
+ .strict();
52
+ /**
53
+ * P6.7.3 — `add_module_to_template` AI tool. Same shape as
54
+ * `add_module_to_page` but fans the new module out to every page using
55
+ * the target template, inserting at the same block + position. Used
56
+ * for "site-wide" content (a global footer, a header banner, etc.).
57
+ */
58
+ export const addModuleToTemplateToolInput = z
59
+ .object({
60
+ templateId: z.string().uuid(),
61
+ blockName: z.string().min(1).max(80),
62
+ position: z.union([z.enum(["top", "bottom"]), z.number().int().min(0).max(1000)]),
63
+ displayName: z.string().min(1).max(128),
64
+ html: z.string().min(1).max(50_000),
65
+ css: z.string().max(50_000).optional(),
66
+ js: z.string().max(50_000).optional(),
67
+ })
68
+ .strict();
69
+ export const AI_TOOLS = [
70
+ "edit_module",
71
+ "site_memory_propose",
72
+ "add_module_to_page",
73
+ "add_module_to_template",
74
+ "create_page",
75
+ "rename_page",
76
+ "set_page_title",
77
+ "change_page_slug",
78
+ "delete_page",
79
+ "remove_module_from_page",
80
+ "set_structured_set",
81
+ "update_theme",
82
+ "add_module_to_layout",
83
+ "remove_module_from_layout",
84
+ "set_template_layout",
85
+ "create_layout",
86
+ "set_site_defaults",
87
+ "duplicate_page",
88
+ "change_template",
89
+ "move_module",
90
+ "reorder_module",
91
+ "set_nav_menu",
92
+ ];
93
+ /** Chat ops input shapes — used by the SvelteKit form actions. */
94
+ export const chatCreateSessionInput = z
95
+ .object({
96
+ title: z.string().min(1).max(200).optional(),
97
+ /** P6.7.4 — bind the new chat to one page (live-edit surface). */
98
+ pageId: z.string().uuid().optional(),
99
+ /** P6.7.4 — bind the new chat to one template (template editor). */
100
+ templateId: z.string().uuid().optional(),
101
+ /** P10.5 — when set, this is an ephemeral subagent session; sidebar filters it out. */
102
+ subagentRole: z.string().min(1).max(120).optional(),
103
+ /** P10.5 — parent chat session id for subagent attribution (audit trail). */
104
+ parentChatSessionId: z.string().uuid().nullable().optional(),
105
+ })
106
+ .strict();
107
+ export const chatSendMessageInput = z
108
+ .object({
109
+ chatSessionId: z.string().uuid(),
110
+ content: z.string().min(1).max(8000),
111
+ /** Element-reference chips appended to the message. */
112
+ chips: z
113
+ .array(z
114
+ .object({
115
+ moduleId: z.string().uuid(),
116
+ selector: z.string().min(1).max(500),
117
+ label: z.string().min(1).max(200),
118
+ })
119
+ .strict())
120
+ .default([]),
121
+ /**
122
+ * P6.7.3 — the active /edit page id, threaded so the chat-runner can
123
+ * compose a Current-page volatile chunk in the system prompt and so
124
+ * tools that operate on a page (add_module_to_page) know the target
125
+ * without a chip. Optional because the standalone chat editor at
126
+ * /content/chat doesn't have a page context.
127
+ */
128
+ activePageId: z.string().uuid().optional(),
129
+ })
130
+ .strict();
131
+ export const chatRenameSessionInput = z
132
+ .object({
133
+ chatSessionId: z.string().uuid(),
134
+ title: z.string().min(1).max(200),
135
+ })
136
+ .strict();
137
+ export const chatPublishInput = z
138
+ .object({
139
+ chatSessionId: z.string().uuid(),
140
+ /**
141
+ * Optional partial-publish filter (P5.2 #5). When present, only
142
+ * entities listed here are merged into main; the rest stay on the
143
+ * branch for a later publish. Empty array means "publish nothing"
144
+ * and is rejected; omit the field to publish everything.
145
+ */
146
+ entities: z
147
+ .array(z
148
+ .object({
149
+ kind: z.enum(["module", "template", "page", "pageLayout"]),
150
+ entityId: z.string().uuid(),
151
+ })
152
+ .strict())
153
+ .min(1)
154
+ .optional(),
155
+ })
156
+ .strict();
157
+ export const aiMemorySetInput = z
158
+ .object({
159
+ slot: z.enum(["purpose", "brand-voice", "tone", "banned-phrases", "instructions", "glossary"]),
160
+ body: z.string().max(4000),
161
+ })
162
+ .strict();
163
+ export const aiMemoryReviewInput = z
164
+ .object({
165
+ proposalId: z.string().uuid(),
166
+ decision: z.enum(["accept", "reject"]),
167
+ })
168
+ .strict();
169
+ export const aiProvidersSetInput = z
170
+ .object({
171
+ name: z.enum(["anthropic", "openai", "google", "local-openai-compat"]),
172
+ displayName: z.string().min(1).max(100),
173
+ config: z.record(z.string(), z.unknown()).default({}),
174
+ isActive: z.boolean().default(true),
175
+ /**
176
+ * Optional plaintext API key. When present the op encrypts it with the
177
+ * project KEK and persists ciphertext + IV + KEK fingerprint. When
178
+ * absent the existing stored key (if any) is preserved untouched —
179
+ * lets the Owner edit displayName / model / baseUrl without re-pasting.
180
+ * Audit logs `apiKeyChanged: boolean`, never the value.
181
+ */
182
+ apiKey: z.string().min(1).max(500).optional(),
183
+ })
184
+ .strict();
185
+ /**
186
+ * P19 — `compose_from_import` AI tool input. Wraps
187
+ * `imports.compose_from_run`. Single transaction synthesis: aggregates
188
+ * theme tokens, creates one template bound to the default layout,
189
+ * materialises every staged import_pages row into a draft page +
190
+ * modules. Idempotent — pages already accepted skip cleanly.
191
+ */
192
+ export const composeFromImportToolInput = z
193
+ .object({
194
+ runId: z.string().uuid(),
195
+ templateSlug: z
196
+ .string()
197
+ .min(1)
198
+ .max(120)
199
+ .regex(/^[a-z0-9][a-z0-9-]*$/, "lowercase letters/digits/hyphens, leading non-hyphen")
200
+ .optional(),
201
+ includeImportPageIds: z.array(z.string().uuid()).optional(),
202
+ })
203
+ .strict();
204
+ /**
205
+ * Input for `ai_providers.clear_key` — Owner-only NULLs the encrypted
206
+ * triplet so the resolver falls back to the env-var path for that
207
+ * provider (or returns null if no env is set, which surfaces the
208
+ * "configure AI provider" UI banner).
209
+ */
210
+ export const aiProvidersClearKeyInput = z
211
+ .object({
212
+ name: z.enum(["anthropic", "openai", "google", "local-openai-compat"]),
213
+ })
214
+ .strict();
215
+ /**
216
+ * P6.7.5 — page-lifecycle tools. Three identifiers, three independent
217
+ * tools so the AI never silently substitutes one for another.
218
+ */
219
+ const slugInputSchema = z
220
+ .string()
221
+ .min(1)
222
+ .max(120)
223
+ .regex(/^[a-z0-9][a-z0-9-]*$/, "lowercase letters/digits/hyphens, leading non-hyphen");
224
+ export const createPageToolInput = z
225
+ .object({
226
+ name: z.string().min(1).max(256),
227
+ title: z.string().min(1).max(256),
228
+ slug: slugInputSchema,
229
+ locale: z.string().min(2).max(10).default("en"),
230
+ /**
231
+ * Optional. When omitted, the underlying `pages.create` op resolves
232
+ * to `site_defaults.default_template_id` (P6.7.6). The AI should pass
233
+ * a UUID from `## Site defaults` / `## Templates → layouts` only when
234
+ * the user asks for a non-default template.
235
+ */
236
+ templateId: z.string().uuid().optional(),
237
+ status: z.enum(["draft", "published"]).default("draft"),
238
+ })
239
+ .strict();
240
+ /**
241
+ * P18 AI-completeness — `create_template` AI tool input. Wraps
242
+ * `templates.create` (widened to AI in this pass per CLAUDE.md §11
243
+ * default-AI-allowed scope). `layoutId` is optional; the op resolves to
244
+ * `site_defaults.default_layout_id` when omitted.
245
+ */
246
+ export const createTemplateToolInput = z
247
+ .object({
248
+ slug: slugInputSchema,
249
+ displayName: z.string().min(1).max(256),
250
+ html: z.string().min(1).max(2_000_000),
251
+ css: z.string().max(2_000_000).default(""),
252
+ layoutId: z.string().uuid().optional(),
253
+ })
254
+ .strict();
255
+ export const renamePageToolInput = z
256
+ .object({
257
+ pageId: z.string().uuid(),
258
+ newName: z.string().min(1).max(256),
259
+ })
260
+ .strict();
261
+ export const setPageTitleToolInput = z
262
+ .object({
263
+ pageId: z.string().uuid(),
264
+ newTitle: z.string().min(1).max(256),
265
+ })
266
+ .strict();
267
+ export const changePageSlugToolInput = z
268
+ .object({
269
+ pageId: z.string().uuid(),
270
+ newSlug: slugInputSchema,
271
+ /**
272
+ * `auto` (default): create a 301 from the old slug → new slug.
273
+ * `skip`: only choose when the user explicitly says they don't
274
+ * want existing inbound links to redirect.
275
+ */
276
+ redirectFromOld: z.enum(["auto", "skip"]).default("auto"),
277
+ })
278
+ .strict();
279
+ export const deletePageToolInput = z
280
+ .object({
281
+ pageId: z.string().uuid(),
282
+ /** '404' returns a not-found; 'redirect' creates a 301 to redirectTo. */
283
+ disposition: z.enum(["404", "redirect"]),
284
+ redirectTo: z.string().min(1).max(500).optional(),
285
+ })
286
+ .strict()
287
+ .refine((v) => v.disposition === "404" || (v.redirectTo !== undefined && v.redirectTo.length > 0), {
288
+ message: "redirectTo is required when disposition='redirect'",
289
+ path: ["redirectTo"],
290
+ });
291
+ export const removeModuleFromPageToolInput = z
292
+ .object({
293
+ pageId: z.string().uuid(),
294
+ moduleId: z.string().uuid(),
295
+ })
296
+ .strict();
297
+ export const setStructuredSetToolInput = z
298
+ .object({
299
+ kind: z.enum(["nav-menu", "taxonomy", "theme", "tags", "link-list"]),
300
+ slug: slugInputSchema,
301
+ displayName: z.string().min(1).max(200),
302
+ items: z.array(z.unknown()),
303
+ })
304
+ .strict();
305
+ export const updateThemeToolInput = z
306
+ .object({
307
+ /** Map of token name to value. Merges into the existing theme/site set. */
308
+ tokens: z.record(z.string(), z.string()),
309
+ })
310
+ .strict();
311
+ /**
312
+ * P6.7.6 — layout-layer tools. Layouts are site-wide chrome (header /
313
+ * footer / nav) that wraps every page on every template bound to the
314
+ * layout. `add_module_to_layout` reaches every page across the site
315
+ * with one call; `set_template_layout` re-points a template's chrome.
316
+ * `create_layout` and `set_site_defaults` are Owner-only at the op
317
+ * level — AI calls reject with ActorScopeRejected and the chat surfaces
318
+ * the permission requirement.
319
+ */
320
+ export const addModuleToLayoutToolInput = z
321
+ .object({
322
+ layoutSlug: slugInputSchema,
323
+ blockName: z.string().min(1).max(80),
324
+ position: z.union([z.enum(["top", "bottom"]), z.number().int().min(0).max(1000)]),
325
+ displayName: z.string().min(1).max(128),
326
+ html: z.string().min(1).max(50_000),
327
+ css: z.string().max(50_000).optional(),
328
+ js: z.string().max(50_000).optional(),
329
+ })
330
+ .strict();
331
+ export const removeModuleFromLayoutToolInput = z
332
+ .object({
333
+ layoutSlug: slugInputSchema,
334
+ moduleId: z.string().uuid(),
335
+ })
336
+ .strict();
337
+ export const setTemplateLayoutToolInput = z
338
+ .object({
339
+ templateId: z.string().uuid(),
340
+ layoutSlug: slugInputSchema,
341
+ })
342
+ .strict();
343
+ export const createLayoutToolInput = z
344
+ .object({
345
+ slug: slugInputSchema,
346
+ displayName: z.string().min(1).max(200),
347
+ html: z.string().min(1).max(50_000),
348
+ css: z.string().max(50_000).optional(),
349
+ blocks: z
350
+ .array(z
351
+ .object({
352
+ name: z.string().min(1).max(80),
353
+ displayName: z.string().min(1).max(200),
354
+ position: z.number().int().min(0).max(1000),
355
+ })
356
+ .strict())
357
+ .min(1)
358
+ .max(20),
359
+ })
360
+ .strict();
361
+ export const setSiteDefaultsToolInput = z
362
+ .object({
363
+ defaultLayoutSlug: slugInputSchema.optional(),
364
+ defaultTemplateSlug: slugInputSchema.optional(),
365
+ })
366
+ .strict()
367
+ .refine((v) => v.defaultLayoutSlug !== undefined || v.defaultTemplateSlug !== undefined, {
368
+ message: "must provide at least one of defaultLayoutSlug, defaultTemplateSlug",
369
+ });
370
+ /**
371
+ * P6.7.7 — content-ops follow-ups: clone a page, swap a page's
372
+ * template, reorder modules within a block, move a module across
373
+ * blocks. All wrap existing or new ops; the tool layer captures the
374
+ * user-facing intent (which the system prompt steers the AI toward).
375
+ */
376
+ export const duplicatePageToolInput = z
377
+ .object({
378
+ sourcePageId: z.string().uuid(),
379
+ newSlug: slugInputSchema,
380
+ newName: z.string().min(1).max(256).optional(),
381
+ newTitle: z.string().min(1).max(256).optional(),
382
+ targetTemplateId: z.string().uuid().optional(),
383
+ locale: z.string().min(2).max(10).optional(),
384
+ })
385
+ .strict();
386
+ export const changeTemplateToolInput = z
387
+ .object({
388
+ pageId: z.string().uuid(),
389
+ newTemplateId: z.string().uuid(),
390
+ /**
391
+ * `drop` discards modules in blocks that don't exist on the new
392
+ * template. `preserve-as-block` reroutes them to a named block on
393
+ * the new template (must exist). The AI should ASK the user when
394
+ * the diff would drop modules; only pass `drop` after explicit
395
+ * confirmation.
396
+ */
397
+ orphanDisposition: z
398
+ .discriminatedUnion("kind", [
399
+ z.object({ kind: z.literal("drop") }).strict(),
400
+ z.object({ kind: z.literal("preserve-as-block"), blockName: slugInputSchema }).strict(),
401
+ ])
402
+ .default({ kind: "drop" }),
403
+ })
404
+ .strict();
405
+ export const moveModuleToolInput = z
406
+ .object({
407
+ pageId: z.string().uuid(),
408
+ moduleId: z.string().uuid(),
409
+ toBlockName: z.string().min(1).max(80),
410
+ /** "top" | "bottom" | a 0-based index inside the destination block. */
411
+ position: z.union([z.enum(["top", "bottom"]), z.number().int().min(0).max(1000)]),
412
+ })
413
+ .strict();
414
+ export const reorderModuleToolInput = z
415
+ .object({
416
+ pageId: z.string().uuid(),
417
+ moduleId: z.string().uuid(),
418
+ /**
419
+ * `up` / `down` shift one slot. An integer is an absolute 0-based
420
+ * target position within the same block.
421
+ */
422
+ direction: z.union([z.enum(["up", "down"]), z.number().int().min(0).max(1000)]),
423
+ })
424
+ .strict();
425
+ /**
426
+ * Convenience wrapper over `set_structured_set` for the `nav-menu`
427
+ * kind specifically. Users say "edit the menu", not "set the
428
+ * structured set kind=nav-menu" — this maps natural language to the
429
+ * right tool. Items shape matches `navMenuItem` from
430
+ * @caelo-cms/shared/structured-sets.
431
+ */
432
+ export const setNavMenuToolInput = z
433
+ .object({
434
+ slug: slugInputSchema,
435
+ displayName: z.string().min(1).max(200),
436
+ items: z.array(z.unknown()),
437
+ })
438
+ .strict();
439
+ /**
440
+ * P7 — `find_media`. Searches the media library by alt-text /
441
+ * filename / mime. Returns up to `limit` matches with the WebP-800
442
+ * URL pre-resolved (or `orig` for non-image kinds). The system prompt
443
+ * already lists recent + frequently-used media; this tool covers the
444
+ * "search for an image of a sunlit office" case where the asset isn't
445
+ * in the recent slice.
446
+ */
447
+ export const findMediaToolInput = z
448
+ .object({
449
+ query: z.string().max(256).optional(),
450
+ mime: z
451
+ .enum([
452
+ "image/jpeg",
453
+ "image/png",
454
+ "image/webp",
455
+ "image/avif",
456
+ "image/gif",
457
+ "image/svg+xml",
458
+ "application/pdf",
459
+ "video/mp4",
460
+ ])
461
+ .optional(),
462
+ limit: z.number().int().min(1).max(50).default(15),
463
+ })
464
+ .strict();
465
+ /**
466
+ * P7 — `set_media_alt`. AI may improve a11y on an existing asset
467
+ * without a human round-trip (e.g. when the editor uploaded an image
468
+ * with the default alt and the AI reads its content). Updates only
469
+ * the alt field on `media_assets`.
470
+ */
471
+ export const setMediaAltToolInput = z
472
+ .object({
473
+ assetId: z.string().uuid(),
474
+ alt: z.string().max(2048),
475
+ })
476
+ .strict();
477
+ /**
478
+ * P8 — `set_page_seo`. Manual / panel writes to the per-page SEO
479
+ * sidecar. AI calls this only on explicit user intent
480
+ * ("set the home meta description to ..."). Doesn't bump fingerprints
481
+ * (autofilled_at / optimized_at).
482
+ */
483
+ export const setPageSeoToolInput = z
484
+ .object({
485
+ pageId: z.string().uuid(),
486
+ metaDescription: z.string().max(320).optional(),
487
+ ogImageAssetId: z.string().uuid().nullable().optional(),
488
+ canonicalUrl: z.string().max(2048).nullable().optional(),
489
+ noindex: z.boolean().optional(),
490
+ changefreq: z
491
+ .enum(["always", "hourly", "daily", "weekly", "monthly", "yearly", "never"])
492
+ .optional(),
493
+ priority: z.number().min(0).max(1).optional(),
494
+ })
495
+ .strict();
496
+ /**
497
+ * P8 — `autofill_page_seo`. Fill-once. Refuses when the page's SEO
498
+ * was already autofilled. Triggered by the `seo-autofill` skill on
499
+ * the first-publish path.
500
+ */
501
+ export const autofillPageSeoToolInput = z
502
+ .object({
503
+ pageId: z.string().uuid(),
504
+ metaDescription: z.string().min(1).max(320),
505
+ ogImageAssetId: z.string().uuid().nullable().optional(),
506
+ })
507
+ .strict();
508
+ /**
509
+ * P8 — `optimize_page_seo`. Explicit re-optimization. Always allowed.
510
+ * Takes optional context (keyword analysis / intent shifts / branding
511
+ * changes). The AI can call this across N pages in one chat turn —
512
+ * the resulting changes batch into one Publish-pill confirm.
513
+ */
514
+ export const optimizePageSeoToolInput = z
515
+ .object({
516
+ pageId: z.string().uuid(),
517
+ metaDescription: z.string().min(1).max(320),
518
+ ogImageAssetId: z.string().uuid().nullable().optional(),
519
+ context: z.string().max(4000).optional(),
520
+ })
521
+ .strict();
522
+ /**
523
+ * P8 AI-first review pass — bulk variants. Per CLAUDE.md §11, bulk
524
+ * tools save round-trips when the AI knows it has N changes to make
525
+ * in a single turn. The handlers run inside one transaction so the
526
+ * batch is all-or-nothing.
527
+ */
528
+ export const findRedirectsToolInput = z
529
+ .object({
530
+ query: z.string().max(500).optional(),
531
+ statusCode: z
532
+ .union([z.literal(301), z.literal(302), z.literal(307), z.literal(308), z.literal(410)])
533
+ .optional(),
534
+ limit: z.number().int().min(1).max(200).default(50),
535
+ })
536
+ .strict();
537
+ export const bulkCreateRedirectsToolInput = z
538
+ .object({
539
+ redirects: z
540
+ .array(z
541
+ .object({
542
+ fromPath: z.string().min(1).max(500),
543
+ toPath: z.string().min(1).max(500),
544
+ statusCode: z
545
+ .union([
546
+ z.literal(301),
547
+ z.literal(302),
548
+ z.literal(307),
549
+ z.literal(308),
550
+ z.literal(410),
551
+ ])
552
+ .default(301),
553
+ })
554
+ .strict())
555
+ .min(1)
556
+ .max(500),
557
+ upsert: z.boolean().default(false),
558
+ })
559
+ .strict();
560
+ export const bulkDeleteRedirectsToolInput = z
561
+ .object({
562
+ redirectIds: z.array(z.string().uuid()).max(500).optional(),
563
+ fromPaths: z.array(z.string().min(1).max(500)).max(500).optional(),
564
+ matches: z.string().min(1).max(500).optional(),
565
+ })
566
+ .strict();
567
+ export const bulkOptimizeSeoToolInput = z
568
+ .object({
569
+ updates: z
570
+ .array(z
571
+ .object({
572
+ pageId: z.string().uuid(),
573
+ metaDescription: z.string().min(1).max(320),
574
+ ogImageAssetId: z.string().uuid().nullable().optional(),
575
+ })
576
+ .strict())
577
+ .min(1)
578
+ .max(200),
579
+ context: z.string().max(4000).optional(),
580
+ })
581
+ .strict();
582
+ /**
583
+ * P9 — locales propose tool schemas. Per CLAUDE.md §11.A all four
584
+ * write paths are TWO-STEP (AI proposes → Owner clicks Approve);
585
+ * the AI cannot bypass the click.
586
+ */
587
+ const localeCodeToolSchema = z
588
+ .string()
589
+ .min(2)
590
+ .max(10)
591
+ .regex(/^[a-z]{2,3}(-[A-Za-z]{2,4})?$/, "BCP-47 like 'en' or 'de-AT'");
592
+ const urlStrategyToolSchema = z.enum(["none", "subdirectory", "subdomain", "domain"]);
593
+ export const proposeAddLocaleToolInput = z
594
+ .object({
595
+ code: localeCodeToolSchema,
596
+ displayName: z.string().min(1).max(120),
597
+ urlStrategy: urlStrategyToolSchema.default("subdirectory"),
598
+ urlHost: z.string().min(1).max(253).nullable().optional(),
599
+ })
600
+ .strict();
601
+ export const proposeRemoveLocaleToolInput = z.object({ code: localeCodeToolSchema }).strict();
602
+ export const proposeSetDefaultLocaleToolInput = z.object({ code: localeCodeToolSchema }).strict();
603
+ export const proposeUpdateLocaleStrategyToolInput = z
604
+ .object({
605
+ code: localeCodeToolSchema,
606
+ urlStrategy: urlStrategyToolSchema,
607
+ urlHost: z.string().min(1).max(253).nullable().optional(),
608
+ })
609
+ .strict();
610
+ /**
611
+ * P10 — translation tool inputs. `translate_page` auto-dispatches
612
+ * Mode 1 / Mode 2 based on the variant's existing status — the AI
613
+ * sees one verb regardless of state. `start_translation_job` queues
614
+ * a bulk run.
615
+ */
616
+ export const translatePageToolInput = z
617
+ .object({
618
+ pageId: z.string().uuid(),
619
+ targetLocale: localeCodeToolSchema,
620
+ })
621
+ .strict();
622
+ const translationJobScopeTool = z.discriminatedUnion("kind", [
623
+ z.object({ kind: z.literal("all-stale") }).strict(),
624
+ z.object({ kind: z.literal("page"), pageId: z.string().uuid() }).strict(),
625
+ z.object({ kind: z.literal("locale"), code: localeCodeToolSchema }).strict(),
626
+ z
627
+ .object({
628
+ kind: z.literal("pages"),
629
+ pageIds: z.array(z.string().uuid()).min(1).max(500),
630
+ })
631
+ .strict(),
632
+ ]);
633
+ export const startTranslationJobToolInput = z
634
+ .object({
635
+ scope: translationJobScopeTool,
636
+ capMicrocents: z.number().int().nonnegative().nullable().optional(),
637
+ })
638
+ .strict();
639
+ /**
640
+ * P10A — `propose_skill`. AI drafts a new skill body (or revision) and
641
+ * queues it for Owner review. Per CLAUDE.md §2: skills augment the AI's
642
+ * own system prompt, so site-wide activation requires explicit Owner
643
+ * confirmation. The proposal lands in skill_proposals; Owner accepts
644
+ * (creates `skills` row at status='awaiting_activation') and then
645
+ * activates separately.
646
+ */
647
+ export const proposeSkillToolInput = z
648
+ .object({
649
+ slug: z
650
+ .string()
651
+ .min(1)
652
+ .max(120)
653
+ .regex(/^[a-z0-9-]+$/, "lowercase letters/digits/hyphens"),
654
+ displayName: z.string().min(1).max(200),
655
+ description: z.string().max(1000).default(""),
656
+ body: z.string().min(1).max(20000),
657
+ rationale: z.string().min(1).max(1000),
658
+ allowlistedTools: z.array(z.string().min(1).max(120)).default([]),
659
+ hints: z
660
+ .object({
661
+ keywords: z.array(z.string().min(1).max(80)).default([]),
662
+ chipTrigger: z.boolean().default(false),
663
+ alwaysOn: z.boolean().default(false),
664
+ })
665
+ .strict()
666
+ .default({ keywords: [], chipTrigger: false, alwaysOn: false }),
667
+ })
668
+ .strict();
669
+ /**
670
+ * P11 — `submit_plugin`. AI submits a Tier 2 plugin for validation +
671
+ * Owner approval. CLAUDE.md §2 invariant: AI submits, human Owner
672
+ * activates. Tier 1 plugins ship via human PR + signed release; the AI
673
+ * tool surface cannot promote — the manifest field `tier` is forced
674
+ * to 2 by the handler.
675
+ */
676
+ export const submitPluginToolInput = z
677
+ .object({
678
+ slug: z
679
+ .string()
680
+ .min(1)
681
+ .max(120)
682
+ .regex(/^[a-z][a-z0-9-]*$/, "lowercase, dash-separated"),
683
+ version: z
684
+ .string()
685
+ .min(1)
686
+ .max(40)
687
+ .regex(/^\d+\.\d+\.\d+(-[a-z0-9.]+)?$/, "semver"),
688
+ /** Manifest object as written by the plugin author. Tier-1 fields
689
+ * (`requestedCapabilities`, `workers`, `tools`) and `tier: 1`
690
+ * are rejected by the validator. */
691
+ manifest: z.record(z.string(), z.unknown()),
692
+ /** Full source code of the plugin's compiled JS module. */
693
+ source: z.string().min(1).max(200_000),
694
+ })
695
+ .strict();
696
+ //# sourceMappingURL=ai-tools.js.map