@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.
- package/dist/ai-tools.d.ts +571 -0
- package/dist/ai-tools.d.ts.map +1 -0
- package/dist/ai-tools.js +696 -0
- package/dist/ai-tools.js.map +1 -0
- package/dist/auth-forms.d.ts +24 -0
- package/dist/auth-forms.d.ts.map +1 -0
- package/dist/auth-forms.js +27 -0
- package/dist/auth-forms.js.map +1 -0
- package/dist/cap-failures.d.ts +17 -0
- package/dist/cap-failures.d.ts.map +1 -0
- package/dist/cap-failures.js +58 -0
- package/dist/cap-failures.js.map +1 -0
- package/dist/content.d.ts +111 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +137 -0
- package/dist/content.js.map +1 -0
- package/dist/context.d.ts +40 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +3 -0
- package/dist/context.js.map +1 -0
- package/dist/i18n.d.ts +49 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +154 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +56 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +84 -0
- package/dist/logger.js.map +1 -0
- package/dist/media.d.ts +143 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +168 -0
- package/dist/media.js.map +1 -0
- package/dist/preview-compose.d.ts +84 -0
- package/dist/preview-compose.d.ts.map +1 -0
- package/dist/preview-compose.js +385 -0
- package/dist/preview-compose.js.map +1 -0
- package/dist/preview-scanner.d.ts +44 -0
- package/dist/preview-scanner.d.ts.map +1 -0
- package/dist/preview-scanner.js +177 -0
- package/dist/preview-scanner.js.map +1 -0
- package/dist/result.d.ts +21 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +14 -0
- package/dist/result.js.map +1 -0
- package/dist/seo.d.ts +128 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +176 -0
- package/dist/seo.js.map +1 -0
- package/dist/skills.d.ts +88 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +127 -0
- package/dist/skills.js.map +1 -0
- package/dist/snapshots.d.ts +54 -0
- package/dist/snapshots.d.ts.map +1 -0
- package/dist/snapshots.js +59 -0
- package/dist/snapshots.js.map +1 -0
- package/dist/structured-sets.d.ts +116 -0
- package/dist/structured-sets.d.ts.map +1 -0
- package/dist/structured-sets.js +154 -0
- package/dist/structured-sets.js.map +1 -0
- package/dist/subagents.d.ts +123 -0
- package/dist/subagents.d.ts.map +1 -0
- package/dist/subagents.js +202 -0
- package/dist/subagents.js.map +1 -0
- package/dist/translation.d.ts +127 -0
- package/dist/translation.d.ts.map +1 -0
- package/dist/translation.js +208 -0
- package/dist/translation.js.map +1 -0
- package/dist/version.d.ts +46 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +46 -0
- package/dist/version.js.map +1 -0
- package/package.json +38 -0
package/dist/ai-tools.js
ADDED
|
@@ -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
|