@typeroll/mcp-server 0.45.43 → 0.45.44
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/AGENTS.md +14 -15
- package/README.md +4 -3
- package/dist/bundled-content.js +7 -7
- package/dist/tools/content-types.js +2 -2
- package/dist/tools/forms.js +16 -3
- package/dist/tools/helpers.js +7 -0
- package/dist/tools/page-blocks.js +5 -33
- package/dist/tools/pages.js +10 -3
- package/dist/tools/working-copy.js +5 -3
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/tr-content-write.md +3 -3
- package/skills/tr-forms.md +20 -6
- package/skills/tr-import-url.md +2 -2
- package/skills/tr-migrate-wp.md +2 -2
- package/skills/tr-new-site.md +2 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { ok, withErrorBoundary, versionParam } from './helpers.js';
|
|
2
|
+
import { ok, withErrorBoundary, versionParam, writeAuthority } from './helpers.js';
|
|
3
3
|
const schema = z.array(z.object({ name: z.string(), label: z.string(), type: z.enum(['text', 'textarea', 'richtext', 'image', 'file', 'color', 'select', 'multiselect', 'boolean', 'number', 'url', 'email', 'date', 'datetime', 'list', 'list_simple', 'array', 'object', 'page_ref', 'page_ref_list']) }).passthrough());
|
|
4
4
|
const fields = {
|
|
5
5
|
label_singular: z.string().optional(), label_plural: z.string().optional(), fields: schema.optional(),
|
|
@@ -14,7 +14,7 @@ const fields = {
|
|
|
14
14
|
export const contentTypeTools = [
|
|
15
15
|
{ name: 'page_completeness', description: 'Report missing, unverified and stale fields for Pages of one content type, with per-field counts and worst-first Page IDs. Read-only; agent-writable fields only by default.', inputSchema: { content_type: z.string(), limit: z.number().int().min(1).max(200).optional(), stale_after_days: z.number().min(0).optional(), agent_writable_only: z.boolean().optional(), version: versionParam },
|
|
16
16
|
handler: withErrorBoundary(async (args, { client, siteId }) => { const { content_type, ...query } = args; return ok(await client.get(siteId, `content-types/${encodeURIComponent(content_type)}/completeness`, query)); }) },
|
|
17
|
-
{ name: 'change_page_content_type', description: 'Change a saved Page to another content type. Preserves its identity, block body, status and existing URL. Save or discard its working copy first. If supplied, fields replaces the complete custom fields object; omitted fields are kept and must be valid for the new type. Snapshots a revision before changing.', inputSchema: { page_id: z.string(), content_type: z.string(), fields: z.record(z.unknown()).optional(), version: versionParam },
|
|
17
|
+
{ name: 'change_page_content_type', description: 'Change a saved Page to another content type. Preserves its identity, block body, status and existing URL. Save or discard its working copy first. If supplied, fields replaces the complete custom fields object; omitted fields are kept and must be valid for the new type. Snapshots a revision before changing.', inputSchema: { page_id: z.string(), content_type: z.string(), fields: z.record(z.unknown()).optional(), ...writeAuthority, version: versionParam },
|
|
18
18
|
handler: withErrorBoundary(async (args, { client, siteId }) => { const { page_id, version, ...body } = args; return ok(await client.post(siteId, `pages/${encodeURIComponent(page_id)}/content-type`, body, { version })); }) },
|
|
19
19
|
{ name: 'list_content_types', description: 'List the field schemas, URL patterns, sorting, allowed templates and default templates for Pages. All content is a Page; use list_pages with content_type to filter.', inputSchema: { version: versionParam },
|
|
20
20
|
handler: withErrorBoundary(async (args, { client, siteId }) => ok(await client.get(siteId, 'content-types', { version: args.version }))) },
|
package/dist/tools/forms.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// Forms — agents can create/update/delete forms,
|
|
1
|
+
// Forms — agents can create/update/delete forms, manage their email
|
|
2
|
+
// notifications and webhooks, and read submissions, like the portal.
|
|
2
3
|
// The customer-facing submit endpoint is HMAC-signed and lives at
|
|
3
4
|
// /api/forms/submit. Agents place forms by reference: core/form in block mode,
|
|
4
5
|
// or <x-form id="…" /> in HTML mode. Preview/build expands either reference
|
|
@@ -33,6 +34,16 @@ const targetSchema = z.object({
|
|
|
33
34
|
context_params: z.array(z.string()).max(8).optional().describe('Explicit URL query keys forwarded to the app hydration endpoint; reserved security keys are rejected.'),
|
|
34
35
|
installation_id: z.string(), path: z.string(), hydrate: z.boolean().optional(), session_param: z.string().optional(),
|
|
35
36
|
}).optional().describe('Admin-only: bind to a declared POST route of an enabled app installation. Read its authenticated guide first.');
|
|
37
|
+
// Actions run after a successful submission: email notifications, signed
|
|
38
|
+
// webhooks and app-provided action types. Same validation as the portal's
|
|
39
|
+
// Forms editor; admin permission required, as in the portal.
|
|
40
|
+
const actionSchema = z.object({
|
|
41
|
+
id: z.string().optional().describe('Keep an existing action\'s id to update it in place (webhooks keep their stored secret).'),
|
|
42
|
+
type: z.string().describe('"email", "webhook", or an action type provided by an installed app.'),
|
|
43
|
+
config: z.record(z.string(), z.unknown()).describe('email: { to, subject, body, cc?, bcc?, reply_to?, include_all?, format?: "html"|"text" } — to/subject/body may use {{field}} placeholders, e.g. to: "{{email}}" for a confirmation to the visitor. ' +
|
|
44
|
+
'webhook: { url (https), fields: ["name","email"] (only these values are sent), secret (signing secret; send "••••••••" to keep the stored one) }.'),
|
|
45
|
+
});
|
|
46
|
+
const actionsDescription = 'The complete list of actions after a submission (replaces the current list): email notifications, webhooks, app actions. Read the form first and send back the actions you keep. Requires admin permission, like the portal.';
|
|
36
47
|
export const formTools = [
|
|
37
48
|
{
|
|
38
49
|
name: 'list_forms',
|
|
@@ -44,7 +55,7 @@ export const formTools = [
|
|
|
44
55
|
},
|
|
45
56
|
{
|
|
46
57
|
name: 'read_form',
|
|
47
|
-
description: 'Read one form by id, including fields/steps, submit_text, and
|
|
58
|
+
description: 'Read one form by id, including fields/steps, submit_text, success_message and, for admins, its actions (email notifications and webhooks; secrets masked). Place it with a `core/form` block using data.form_id on a block-mode page, or `<x-form id="…" />` in HTML mode. Both references are expanded server-side with validation, signed token, initial state, and the shared runtime.',
|
|
48
59
|
inputSchema: { form_id: z.string() },
|
|
49
60
|
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
50
61
|
const res = await client.get(siteId, `forms/${encodeURIComponent(args.form_id)}`);
|
|
@@ -62,6 +73,7 @@ export const formTools = [
|
|
|
62
73
|
submit_text: z.string().optional(),
|
|
63
74
|
success_message: z.string().optional().describe('Shown after the final step. May contain basic HTML (links, emphasis); it is sanitized.'),
|
|
64
75
|
success_redirect_url: z.string().optional().describe('After the final step, send the visitor to this absolute http(s) URL or root-relative path (for example a booking or thank-you page) instead of showing success_message. Pass an empty string to clear.'),
|
|
76
|
+
actions: z.array(actionSchema).optional().describe(actionsDescription),
|
|
65
77
|
target: targetSchema,
|
|
66
78
|
},
|
|
67
79
|
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
@@ -74,7 +86,7 @@ export const formTools = [
|
|
|
74
86
|
},
|
|
75
87
|
{
|
|
76
88
|
name: 'update_form',
|
|
77
|
-
description: 'Patch a form. Provide only what you want to change. Passing `steps` replaces the whole step list; passing `fields` (sugar for simple forms) replaces the whole step list with ONE static step built from them. The existing form_id is immutable.',
|
|
89
|
+
description: 'Patch a form. Provide only what you want to change. Passing `steps` replaces the whole step list; passing `fields` (sugar for simple forms) replaces the whole step list with ONE static step built from them. Passing `actions` replaces the email notifications and webhooks (admin). The existing form_id is immutable.',
|
|
78
90
|
inputSchema: {
|
|
79
91
|
form_id: z.string(),
|
|
80
92
|
patch: z.object({
|
|
@@ -84,6 +96,7 @@ export const formTools = [
|
|
|
84
96
|
submit_text: z.string().optional(),
|
|
85
97
|
success_message: z.string().optional().describe('Shown after the final step. May contain basic HTML (links, emphasis); it is sanitized.'),
|
|
86
98
|
success_redirect_url: z.string().optional().describe('After the final step, send the visitor to this absolute http(s) URL or root-relative path (for example a booking or thank-you page) instead of showing success_message. Pass an empty string to clear.'),
|
|
99
|
+
actions: z.array(actionSchema).optional().describe(actionsDescription),
|
|
87
100
|
target: targetSchema,
|
|
88
101
|
}),
|
|
89
102
|
},
|
package/dist/tools/helpers.js
CHANGED
|
@@ -53,6 +53,13 @@ export function withErrorBoundary(handler) {
|
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
/** Shared zod fragment for the optional `version` parameter. */
|
|
56
|
+
/**
|
|
57
|
+
* MCP writes Page field values with the same authority as an editor in the
|
|
58
|
+
* portal. Only a value the listed business set itself needs a stated reason.
|
|
59
|
+
*/
|
|
60
|
+
export const writeAuthority = {
|
|
61
|
+
override_reason: z.string().max(500).optional().describe('Why a value the listed business set itself (through its edit link) is being replaced. Required only for those values, as in the portal; the write otherwise returns 409 naming the field.'),
|
|
62
|
+
};
|
|
56
63
|
export const versionParam = z
|
|
57
64
|
.string()
|
|
58
65
|
.optional()
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
// Page-block mutation tools
|
|
2
|
-
//
|
|
3
|
-
// (override with `version`).
|
|
1
|
+
// Page-block mutation tools: get/add/update/move/remove and friends. All
|
|
2
|
+
// target the active version (override with `version`).
|
|
4
3
|
//
|
|
5
|
-
// The AI agent uses these to author block-mode pages directly.
|
|
6
|
-
// HTML
|
|
7
|
-
// It never writes. A person accepts that preview in the page editor.
|
|
4
|
+
// The AI agent uses these to author block-mode pages directly. Typeroll does
|
|
5
|
+
// not convert HTML into blocks; block pages are built with blocks.
|
|
8
6
|
import { z } from 'zod';
|
|
9
7
|
import { ok, withErrorBoundary, versionParam } from './helpers.js';
|
|
10
8
|
const blockSchema = z.object({
|
|
@@ -193,7 +191,7 @@ export const pageBlockTools = [
|
|
|
193
191
|
},
|
|
194
192
|
{
|
|
195
193
|
name: 'set_page_mode',
|
|
196
|
-
description: 'Safely switch a page\'s content_mode between "blocks" and "html". Always snapshots a revision before the flip so the previous state is restorable. HTML → blocks does not convert or rewrite the body. Blocks → HTML drops the block tree (the revision retains it). Prefer this over `update_page patch={content_mode}` because that bypasses snapshotting.
|
|
194
|
+
description: 'Safely switch a page\'s content_mode between "blocks" and "html". Always snapshots a revision before the flip so the previous state is restorable. HTML → blocks does not convert or rewrite the body. Blocks → HTML drops the block tree (the revision retains it). Prefer this over `update_page patch={content_mode}` because that bypasses snapshotting. Typeroll does not convert HTML into blocks: after switching to blocks, build the content with blocks.',
|
|
197
195
|
inputSchema: {
|
|
198
196
|
page_id: z.string(),
|
|
199
197
|
to: z.enum(['blocks', 'html']).describe('Target mode.'),
|
|
@@ -204,30 +202,4 @@ export const pageBlockTools = [
|
|
|
204
202
|
return ok(res);
|
|
205
203
|
}),
|
|
206
204
|
},
|
|
207
|
-
{
|
|
208
|
-
name: 'convert_page_to_blocks',
|
|
209
|
-
description: 'Preview a heuristic conversion of a page\'s html_content into a block tree. Always returns the proposal, including unconverted text, classes and markup, and does not write. A person accepts the preview in the page editor. This tool cannot change content_mode or replace the body.',
|
|
210
|
-
inputSchema: {
|
|
211
|
-
page_id: z.string(),
|
|
212
|
-
version: versionParam,
|
|
213
|
-
},
|
|
214
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
215
|
-
const res = await client.post(siteId, `pages/${encodeURIComponent(args.page_id)}/blocks/convert`, { dry_run: true }, v(args.version));
|
|
216
|
-
return ok(res);
|
|
217
|
-
}),
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
name: 'convert_prose_block',
|
|
221
|
-
description: 'Turn one text block (core/prose) into separate blocks: headings (a classed label above a heading becomes its eyebrow), paragraphs, lists, images and buttons. Use it when a text block holds headings, classed HTML or media that should be editable and styled with named styles. Call without `accept` to preview (nothing is written); review `converted`, `notes` and `unconverted`, then call again with `accept` set to the preview\'s `fingerprint`. Writes the page draft (working copy) like other block edits. Pages only.',
|
|
222
|
-
inputSchema: {
|
|
223
|
-
page_id: z.string(),
|
|
224
|
-
block_id: z.string(),
|
|
225
|
-
accept: z.string().optional().describe('The fingerprint from a preview of this same block. Omit to preview.'),
|
|
226
|
-
version: versionParam,
|
|
227
|
-
},
|
|
228
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
229
|
-
const res = await client.post(siteId, `pages/${encodeURIComponent(args.page_id)}/blocks/convert-prose`, { block_id: args.block_id, ...(args.accept ? { accept: args.accept } : {}) }, v(args.version));
|
|
230
|
-
return ok(res);
|
|
231
|
-
}),
|
|
232
|
-
},
|
|
233
205
|
];
|
package/dist/tools/pages.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// Pages tools. Read + write + batch + blocks-view + preview.
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { ok, withErrorBoundary, versionParam } from './helpers.js';
|
|
3
|
+
import { ok, withErrorBoundary, versionParam, writeAuthority } from './helpers.js';
|
|
4
4
|
const answerSources = z.record(z.object({ source_url: z.string().url().optional(), import_run_id: z.string().max(200).optional() }).strict()).optional().describe('Evidence per schema leaf path. Actor and timestamp are always assigned by the server; unchanged answers are not confirmed.');
|
|
5
|
+
function authorityBody(args) {
|
|
6
|
+
return args.override_reason ? { override_reason: args.override_reason } : {};
|
|
7
|
+
}
|
|
5
8
|
function v(version) {
|
|
6
9
|
return version ? { version } : undefined;
|
|
7
10
|
}
|
|
@@ -108,6 +111,7 @@ export const pageTools = [
|
|
|
108
111
|
template: z.string().nullable().optional().describe('Allowed Page template ID; null uses the content type default.'),
|
|
109
112
|
image_sizes_default: z.string().optional().describe('Per-page default `sizes` for responsive images (e.g. "(max-width: 640px) 360px, 560px"). Overrides the site setting; a per-<img> `sizes` attr still wins. Set when this page\'s images render narrower than the generic default so the browser stops over-fetching.'),
|
|
110
113
|
custom_css: z.string().optional().describe('Per-page CSS, injected into <head> as a <style> AFTER the site-level custom_css (so it overrides site styling). This is the RIGHT home for page-specific styling — page metadata, not content. Put a page\'s <style> here instead of stuffing it into a core/html block (which is opaque and un-editable in the visual editor). Syntax errors refuse the write; target s-<style> classes or block custom_class rather than [data-block]/.block-* platform markup.'),
|
|
114
|
+
...writeAuthority,
|
|
111
115
|
version: versionParam,
|
|
112
116
|
},
|
|
113
117
|
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
@@ -168,11 +172,12 @@ export const pageTools = [
|
|
|
168
172
|
})
|
|
169
173
|
.passthrough(),
|
|
170
174
|
answer_sources: answerSources,
|
|
175
|
+
...writeAuthority,
|
|
171
176
|
save: z.boolean().optional().describe('Also SAVE (commit) the draft in the same call — use for pre-approved or batch changes. Without it, changes stay in the unsaved draft until commit_working_copy.'),
|
|
172
177
|
version: versionParam,
|
|
173
178
|
},
|
|
174
179
|
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
175
|
-
const res = await client.patch(siteId, `pages/${encodeURIComponent(args.page_id)}`, { ...args.patch, ...(args.answer_sources ? { answer_sources: args.answer_sources } : {}), ...(args.save ? { save: true } : {}) }, v(args.version));
|
|
180
|
+
const res = await client.patch(siteId, `pages/${encodeURIComponent(args.page_id)}`, { ...args.patch, ...(args.answer_sources ? { answer_sources: args.answer_sources } : {}), ...authorityBody(args), ...(args.save ? { save: true } : {}) }, v(args.version));
|
|
176
181
|
return ok(res);
|
|
177
182
|
}),
|
|
178
183
|
},
|
|
@@ -183,11 +188,12 @@ export const pageTools = [
|
|
|
183
188
|
page_id: z.string(),
|
|
184
189
|
page: z.object({ title: z.string().min(1) }).passthrough(),
|
|
185
190
|
answer_sources: answerSources,
|
|
191
|
+
...writeAuthority,
|
|
186
192
|
save: z.boolean().optional().describe('Also SAVE (commit) the draft in the same call — use for pre-approved or batch changes. Without it, changes stay in the unsaved draft until commit_working_copy.'),
|
|
187
193
|
version: versionParam,
|
|
188
194
|
},
|
|
189
195
|
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
190
|
-
const res = await client.put(siteId, `pages/${encodeURIComponent(args.page_id)}`, { ...args.page, ...(args.answer_sources ? { answer_sources: args.answer_sources } : {}), ...(args.save ? { save: true } : {}) }, v(args.version));
|
|
196
|
+
const res = await client.put(siteId, `pages/${encodeURIComponent(args.page_id)}`, { ...args.page, ...(args.answer_sources ? { answer_sources: args.answer_sources } : {}), ...authorityBody(args), ...(args.save ? { save: true } : {}) }, v(args.version));
|
|
191
197
|
return ok(res);
|
|
192
198
|
}),
|
|
193
199
|
},
|
|
@@ -201,6 +207,7 @@ export const pageTools = [
|
|
|
201
207
|
patch: z.record(z.unknown()),
|
|
202
208
|
save: z.boolean().optional(),
|
|
203
209
|
answer_sources: answerSources,
|
|
210
|
+
...writeAuthority,
|
|
204
211
|
}))
|
|
205
212
|
.min(1)
|
|
206
213
|
.max(200),
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// The portal editor shows any agent-written draft as "Unsaved changes", so
|
|
14
14
|
// the human can also Save or Discard it from the UI.
|
|
15
15
|
import { z } from 'zod';
|
|
16
|
-
import { ok, withErrorBoundary, versionParam } from './helpers.js';
|
|
16
|
+
import { ok, withErrorBoundary, versionParam, writeAuthority } from './helpers.js';
|
|
17
17
|
function v(version) {
|
|
18
18
|
return version ? { version } : undefined;
|
|
19
19
|
}
|
|
@@ -42,10 +42,11 @@ export const workingCopyTools = [
|
|
|
42
42
|
description: 'SAVE a doc\'s unsaved draft: promote it onto the saved doc through the canonical write path (revision snapshot, SEO transform, redirect hygiene) and delete the draft. Identical to the Save button in the portal editor. Returns { committed, seo_warnings, auto_redirects, retired_redirects }. Never changes publish status. Deploys only ship saved content, so commit before trigger_deploy. Equivalent shortcut: save:true on the write tools.',
|
|
43
43
|
inputSchema: {
|
|
44
44
|
target: wcTargetSchema,
|
|
45
|
+
...writeAuthority,
|
|
45
46
|
version: versionParam,
|
|
46
47
|
},
|
|
47
48
|
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
48
|
-
const res = await client.post(siteId, wcPath(args.target), {}, v(args.version));
|
|
49
|
+
const res = await client.post(siteId, wcPath(args.target), { ...(args.override_reason ? { override_reason: args.override_reason } : {}) }, v(args.version));
|
|
49
50
|
return ok(res);
|
|
50
51
|
}),
|
|
51
52
|
},
|
|
@@ -109,10 +110,11 @@ export const workingCopyTools = [
|
|
|
109
110
|
page_id: z.string(),
|
|
110
111
|
revision_id: z.string(),
|
|
111
112
|
save: z.boolean().optional().describe('Save immediately (default false: leave it as an unsaved draft to preview).'),
|
|
113
|
+
...writeAuthority,
|
|
112
114
|
version: versionParam,
|
|
113
115
|
},
|
|
114
116
|
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
115
|
-
const res = await client.post(siteId, `pages/${encodeURIComponent(args.page_id)}/revisions/${encodeURIComponent(args.revision_id)}/restore`, { save: args.save === true }, v(args.version));
|
|
117
|
+
const res = await client.post(siteId, `pages/${encodeURIComponent(args.page_id)}/revisions/${encodeURIComponent(args.revision_id)}/restore`, { save: args.save === true, ...(args.override_reason ? { override_reason: args.override_reason } : {}) }, v(args.version));
|
|
116
118
|
return ok(res);
|
|
117
119
|
}),
|
|
118
120
|
},
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -45,9 +45,9 @@ setting one-off values; ask first when it changes the site's look.
|
|
|
45
45
|
A small label above a heading is the heading block's `eyebrow`, and a line
|
|
46
46
|
below it is its `subtitle` (render version 2), each with its own style field.
|
|
47
47
|
Never write them as separate text blocks with classed HTML. In text blocks,
|
|
48
|
-
style whole paragraphs with site text styles (`<p class="s-lead">`)
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
style whole paragraphs with site text styles (`<p class="s-lead">`). Content
|
|
49
|
+
that needs headings with parts, images or buttons belongs in their own blocks,
|
|
50
|
+
not in a text block's HTML.
|
|
51
51
|
|
|
52
52
|
`list_block_templates` and `list_partials` too. Start a common section from a
|
|
53
53
|
block template (`insert_block_template`, then edit the copy). Use a global
|
package/skills/tr-forms.md
CHANGED
|
@@ -6,8 +6,8 @@ description: Use when the user wants to add a contact form, newsletter signup, b
|
|
|
6
6
|
# Add a form to a Typeroll site
|
|
7
7
|
|
|
8
8
|
Typeroll forms are server-backed. The platform renders the form shell, accepts
|
|
9
|
-
signed submissions, validates declared fields, stores submissions, and
|
|
10
|
-
|
|
9
|
+
signed submissions, validates declared fields, stores submissions, and runs
|
|
10
|
+
email and webhook actions configured in the portal or through MCP/API.
|
|
11
11
|
|
|
12
12
|
## Choose the placement
|
|
13
13
|
|
|
@@ -97,9 +97,23 @@ actions in the form editor:
|
|
|
97
97
|
`X-Typeroll-Signature`, sends an idempotency key, retries transient failures,
|
|
98
98
|
and stores delivery status.
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
Agents manage the same actions with `read_form` (actions with secrets masked)
|
|
101
|
+
and `update_form patch={ actions: [...] }`, with admin permission as in the
|
|
102
|
+
portal. `actions` replaces the whole list: read the form, change what you need
|
|
103
|
+
and send the list back. Examples:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
update_form form_id=contact patch={ actions: [
|
|
107
|
+
{ type: "email", config: { to: "hello@example.com", subject: "New lead: {{name}}",
|
|
108
|
+
body: "<p>{{message}}</p>", include_all: true, reply_to: "{{email}}" } },
|
|
109
|
+
{ type: "email", config: { to: "{{email}}", subject: "Thanks, {{name}}",
|
|
110
|
+
body: "<p>We will get back to you within a day.</p>" } }
|
|
111
|
+
] }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
A webhook needs `url` (https), an explicit `fields` allowlist and a signing
|
|
115
|
+
`secret`; send the masked value back to keep a stored secret. `list_form_submissions`
|
|
116
|
+
reads what visitors sent.
|
|
103
117
|
|
|
104
118
|
## Verify
|
|
105
119
|
|
|
@@ -143,7 +157,7 @@ Booking request:
|
|
|
143
157
|
|
|
144
158
|
- Do not hand-write a form, token, honeypot, or submit script.
|
|
145
159
|
- Do not put a raw `<script>` in page HTML; the sanitizer removes it.
|
|
146
|
-
- Do not
|
|
160
|
+
- Do not drop existing actions by accident: `actions` replaces the list.
|
|
147
161
|
- Do not send every submitted field to a webhook by default; choose the
|
|
148
162
|
smallest allowlist the external register needs.
|
|
149
163
|
- `submit_token` is stable until the platform rotates its form-signing secret;
|
package/skills/tr-import-url.md
CHANGED
|
@@ -61,8 +61,8 @@ small label directly above a heading is an eyebrow style, not a heading of its
|
|
|
61
61
|
own: put it in the heading block's `eyebrow` field (and a line under the
|
|
62
62
|
heading in `subtitle`). Never carry classed raw HTML into text blocks to keep a
|
|
63
63
|
look: the editor cannot edit it, and the next render version may not keep it.
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
Build such sections with heading, text, image and button blocks; Typeroll does
|
|
65
|
+
not convert HTML into blocks.
|
|
66
66
|
|
|
67
67
|
### 2. Measure the source and protect existing target work
|
|
68
68
|
|
package/skills/tr-migrate-wp.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tr-migrate-wp
|
|
3
|
-
description: Use when the user asks to migrate a WordPress site to Typeroll, mentions wp-json, or names a WP source URL. Walks the WP REST API, preserves cleaned HTML and shared references, transfers media, sets redirects, and leaves everything as drafts for human review.
|
|
3
|
+
description: Use when the user asks to migrate a WordPress site to Typeroll, mentions wp-json, or names a WP source URL. Walks the WP REST API, preserves cleaned HTML and shared references, transfers media, sets redirects, and leaves everything as drafts for human review. Typeroll does not convert HTML into blocks.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Migrate from WordPress to Typeroll
|
|
@@ -145,7 +145,7 @@ c. Migrate referenced images with `upload_media_from_url`. The customer's
|
|
|
145
145
|
media reference only after verification. Stop and report a failed required
|
|
146
146
|
transfer; do not silently hotlink the WordPress source.
|
|
147
147
|
|
|
148
|
-
The managed importer stores cleaned HTML
|
|
148
|
+
The managed importer stores cleaned HTML. Typeroll does not convert HTML into blocks, and `set_page_mode` never rewrites HTML. Pages move to blocks only when someone rebuilds them with blocks.
|
|
149
149
|
|
|
150
150
|
d. Preserve exact text, headings, anchors, links, media and semantic structure with native blocks first. Read the
|
|
151
151
|
available block types and map headings, prose, images, buttons and layout
|
package/skills/tr-new-site.md
CHANGED
|
@@ -359,9 +359,8 @@ For migrated legacy pages or a hand-crafted one-off, `content_mode:
|
|
|
359
359
|
negative-margin escape (`margin-left: calc(50% - 50vw); width: 100vw`)
|
|
360
360
|
— never combine with `overflow-x: clip` on a wrapper.
|
|
361
361
|
- `set_page_mode` flips a page between modes and does not convert HTML.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
preview in the page editor.
|
|
362
|
+
Typeroll has no HTML-to-blocks conversion: to move a page to blocks,
|
|
363
|
+
build its content with blocks.
|
|
365
364
|
|
|
366
365
|
## Pitfalls
|
|
367
366
|
|