@bettercms-ai/mcp 0.80.1 → 0.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +148 -14
- package/dist/index.js.map +1 -1
- package/dist/skills/bettercms/SKILL.md +5 -4
- package/dist/skills/bettercms/references/frameworks.md +2 -2
- package/dist/skills/bettercms/references/getting-started.md +23 -1
- package/dist/skills/bettercms/references/integrations.md +189 -4
- package/dist/skills/bettercms/references/other-frameworks.md +3 -1
- package/dist/skills/bettercms/references/publishing.md +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2915,6 +2915,11 @@ var LISTING_TAIL = /* @__PURE__ */ new Set([
|
|
|
2915
2915
|
"delete_integration",
|
|
2916
2916
|
"test_integration_fetch",
|
|
2917
2917
|
"run_integration_sync",
|
|
2918
|
+
"run_integration_function",
|
|
2919
|
+
"search_integration_reference",
|
|
2920
|
+
"start_integration_signin",
|
|
2921
|
+
"save_integration_template",
|
|
2922
|
+
"install_integration_template",
|
|
2918
2923
|
// The ABM campaign lane — also hidden from the default listing (ABM_TOOLS below).
|
|
2919
2924
|
"research_account",
|
|
2920
2925
|
"build_abm_page",
|
|
@@ -2999,12 +3004,17 @@ var NEW_TOOL_TEXT = {
|
|
|
2999
3004
|
editor_setup: "Make the live build editable: derives pages, collections, forms, bindings and components from the live release (skill bindings). Run it after an import or a rebuild, before the conversion brief. Idempotent: `ready` when it already ran for the live sha, else it starts (`running`; poll with `check: true`). `state`: never or stale (run it), running, ready, failed. 409 = nothing is live yet: deploy first. Then get_next_steps.",
|
|
3000
3005
|
transform_image: "Edit an existing Media Library image with an instruction (e.g. 'remove the background', 'make it dusk'); the result is saved as a NEW asset and the original is kept (5 AI credits). Returns the new asset (id, url, altText).",
|
|
3001
3006
|
write_alt_text: "Alt text for a Media Library image (mediaId) or an https image URL (url), written from its pixels (1 AI credit). A suggestion: save it with update_media.",
|
|
3002
|
-
list_integrations: "List the project's custom integrations (any vendor with a public API
|
|
3003
|
-
get_integration: "One integration (`integration`: id or slug)
|
|
3004
|
-
create_integration: "Declare a custom integration
|
|
3005
|
-
update_integration: "Change an integration (`integration`: id or slug); keys
|
|
3007
|
+
list_integrations: "List the project's custom integrations (any vendor with a public API) and the workspace's integration `templates`. Each has its hosts, secret NAMES with `secretsPresent` (never a value), syncs, hooks, endpoints, functions and `approved`. Read bettercms://skill/integrations first.",
|
|
3008
|
+
get_integration: "One integration (`integration`: id or slug): `hookUrls`, `endpointUrls`, `oauthStatus`, its last sync runs, write-backs (`pushes`), function runs, webhook deliveries and vendor calls (the call log).",
|
|
3009
|
+
create_integration: "Declare a custom integration. Shapes: bettercms://skill/integrations. Secret VALUES never pass through here (the user sets env vars; list_env_vars shows names). Until an admin approves it, every vendor call, sync, webhook, endpoint and function is refused: give the user `approval.approveUrl` and `approval.changes` in plain words. You cannot approve it.",
|
|
3010
|
+
update_integration: "Change an integration (`integration`: id or slug); parameters as create_integration, keys left out are kept, lists replace the stored ones whole. A change to hosts, secrets, inject, allowPost, auth, oauth, hooks, endpoints, references or functions withdraws its approval (`approval.approveUrl`); name, syncs, settings and panel never need it.",
|
|
3006
3011
|
delete_integration: "Delete an integration and its sync runs. Entries it synced stay (their provenance still names it); the env vars stay.",
|
|
3007
3012
|
run_integration_sync: "Run one of an integration's syncs now (`integration`, `sync`: its key): every page of the vendor list is fetched and each item is created or updated as a DRAFT entry, matched on its externalKey, so a re-run only changes what changed (`unchanged` counts the rest); `publish: true` on the sync publishes what changed. Answers with the run: queued, running, done (counts: created, updated, unchanged, failed, pages, items) or failed (error). `waitSeconds` (\u2264 20) waits for it to finish; else follow it with get_integration. A sync with `schedule` 'hourly' or 'daily' also runs on its own.",
|
|
3013
|
+
run_integration_function: "Run an integration's function (bettercms/functions/<name>.ts, trigger 'manual') with `input`. Its fetch() reaches only the integration's approved hosts and the server adds the secrets: the code never sees them. `waitSeconds` (\u2264 20) answers with output, logs or error.",
|
|
3014
|
+
search_integration_reference: "Search an integration's live reference (`reference`: its key) at the vendor: up to 20 {id, title, image}. Store the pick in a json field (skill \xA712); the site resolves it live.",
|
|
3015
|
+
start_integration_signin: "Start the vendor sign-in of an integration with `oauth`: a `url` the USER opens to sign in with their own OAuth app (register `redirectUri` in it first). Tokens stay on the server.",
|
|
3016
|
+
save_integration_template: "Save an integration as a workspace template: its shape, never a secret value or its settings, with copies of the collections its syncs write into.",
|
|
3017
|
+
install_integration_template: "Install a workspace template on this project: missing collections are created and the integration waits for approval (`approval.approveUrl`); `secretsNeeded` lists the env vars to set.",
|
|
3008
3018
|
test_integration_fetch: "Call the vendor once through the server, as the integration: an approved host only, with the integration's `inject` secrets added server-side and every secret redacted from the answer. The request itself takes no {{...}} templates, and the token endpoint cannot be called. Use it to read the response shape before writing a sync: `items`, `externalKey` and `map` are JSON Pointers into it. Bodies over 1 MB come back truncated. 409 integration_not_approved: an admin must approve it first."
|
|
3009
3019
|
};
|
|
3010
3020
|
var DELETE_PAGE_RUNTIME_BUNDLE = " Or, with no pageId, `runtimeBundle`: 'report' lists the duplicate pages derived from a server app's own bundle (`app/` folders); 'apply' retires them and their empty folders.";
|
|
@@ -3063,9 +3073,17 @@ var INTEGRATION_PARAM_TEXT = {
|
|
|
3063
3073
|
secrets: "template name (UPPER_SNAKE) -> project env var KEY, e.g. {TOKEN: 'SHOPIFY_ADMIN_TOKEN'}; never a value",
|
|
3064
3074
|
inject: "where the secrets go on every vendor request: {headers?: {name: template}, query?: {name: template}}, templates {{secret.NAME}} or {{token}}",
|
|
3065
3075
|
allowPost: "true lets requests other than GET through (vendor writes, POST search APIs such as Apollo's); GET only when omitted",
|
|
3066
|
-
auth: "optional token exchange
|
|
3076
|
+
auth: "optional token exchange (skill \xA75); its result is {{token}}",
|
|
3067
3077
|
settings: "non-secret JSON the panel reads and writes (store id, toggles); at most 16 KB",
|
|
3068
|
-
syncs: "[{key, contentModelId, request
|
|
3078
|
+
syncs: "vendor list -> collection [{key, contentModelId, request, items, externalKey, map, item?, writeBack?, publish?, schedule?}] (skill \xA76; two-way \xA715)",
|
|
3079
|
+
hooks: "vendor webhooks [{key, verify, action}] (skill \xA79); the vendor posts to `hookUrls`",
|
|
3080
|
+
endpoints: "public reads for the site [{key, request, params, pick, cacheSeconds?}] (skill \xA711); at `endpointUrls`",
|
|
3081
|
+
references: "live references [{key, label, search, lookup, items, id, title}] (skill \xA712)",
|
|
3082
|
+
functions: "[{name, triggers?, cpuMs?}]: bettercms/functions/<name>.ts (skill \xA710)",
|
|
3083
|
+
oauth: "sign-in with the customer's own OAuth app {authorizeUrl, tokenUrl, scopes, clientId, clientSecret} (secret NAMES; skill \xA713)",
|
|
3084
|
+
alertAfter: "tell admins after this many failed runs in a row (0 = never; 3 when omitted)",
|
|
3085
|
+
reference: "the reference's key (from the integration's references)",
|
|
3086
|
+
template: "the template's slug (from list_integrations `templates`)",
|
|
3069
3087
|
panel: "the custom panel's slug: bettercms/panels/<panel>.tsx in the site repo; null for the stock page",
|
|
3070
3088
|
url: "the full https URL on an allowed host (no templates in the URL)",
|
|
3071
3089
|
sync: "the sync's key (from the integration's syncs)",
|
|
@@ -3097,6 +3115,18 @@ var SLOT_MULTIPLE_NOTE = "'slot' with config.multiple holds a list [{componentId
|
|
|
3097
3115
|
var PROP_V2_NOTE = "Props take the fields' V2 keys (\xA714): 'object' = ONE {child: value} from config.fields, \u226410 levels (field_depth_exceeded); datetime|slug|email|phone|json|location|tags|rating|code|reference|multi-reference as that field (a reference names contentModelId, or polymorphic + allowedModelSlugs; page reads hydrate its entries); helpMore, advanced, recommendedMax, openMode, config.scale, ui.layout rows|chips|gallery, ui.preview.fallback, showIf notEmpty|empty. Send helpText: null for a prop whose /kit schema has no description (omitted, the platform writes one).";
|
|
3098
3116
|
var LAYOUT_LOCK_NOTE = "A Global field with `overridable:false` + `lockReason` (add-field `field`, update-field `patch`) refuses page overrides (LAYOUT_FIELD_NOT_OVERRIDABLE); get_layout scope page lists stored ones as `shadowed`.";
|
|
3099
3117
|
var AUTHORING_DESCRIPTION = "Record which authoring architecture this site uses: 'components' (the default: reusable section components placed as blocks, which editors add, reorder and swap; on a site whose pages were DERIVED it keeps every field, value and binding, then run the componentize lane) or 'fields' (a typed field schema per page, for a blog, catalogue or directory). A deploy, a promote and every editor_setup record 'components' when nothing is set, so call this only to change it. Never ask the user: record it, say so in your final report, and tell them they can switch it in the dashboard.";
|
|
3118
|
+
var DELIVERY_KEY_DESCRIPTION = "Create a read-only delivery key for this project's own front end and return it once (it is never shown again: write it straight into the front end's .env under the name its SDK config reads, e.g. BCMS_API_KEY or BETTERCMS_API_KEY, never into chat or a commit). Published content needs no key (the SDK reads it by BCMS_PROJECT_ID), so create one only when the front end's config asks for a key. `scope: \"published\"` = content:read, no expiry; `scope: \"draft\"` also reads drafts and expires within the hour (a preview build's credential; editors' previews use the dashboard's preview link instead). Listed and revocable in Project \u2192 Settings \u2192 API keys like any other key. Requires artifact:write.";
|
|
3119
|
+
var DELIVERY_KEY_PARAMS = {
|
|
3120
|
+
name: "what it is for, shown in the key list (e.g. 'Front end (Vercel)')",
|
|
3121
|
+
scope: "published (content:read, the default) or draft (also drafts; expires within the hour)"
|
|
3122
|
+
};
|
|
3123
|
+
var FRONT_END_DESCRIPTION = 'Record where this project\'s own front end lives, so editors open drafts on it ("Preview on site") and a publish reaches it: `siteUrl` (its deployed origin), `framework` (next or astro: which preview route the SDK mounts), `draftPath` (Next only, when not /api/draft) and `revalidation` (a front end that revalidates instead of rebuilding). Call it after the front end is deployed; `npx @bettercms-ai/cli@latest deploy --to <host>` already registers the rebuild hook. Read the current values from get_project. Fields you omit are left as they are; null clears one. Requires artifact:write.';
|
|
3124
|
+
var FRONT_END_PARAMS = {
|
|
3125
|
+
siteUrl: "the deployed front end's origin, https://\u2026 (null clears it)",
|
|
3126
|
+
framework: "next or astro",
|
|
3127
|
+
draftPath: "Next only: the draft-mode route when not /api/draft (null clears it)",
|
|
3128
|
+
revalidation: "a front end that revalidates instead of rebuilding: { webhookUrl (https), webhookSecret (16+ chars, also stored as BETTERCMS_WEBHOOK_SECRET on the front end) } (null clears it)"
|
|
3129
|
+
};
|
|
3100
3130
|
var MEDIA_DEFAULT_NOTE = "Leave the default (the CDN) unless the owner asked otherwise; never ask.";
|
|
3101
3131
|
var REBUILD_DESCRIPTION = "Rebuild from the connected repository, as the dashboard's Rebuild does. Use it when a get_next_steps fix names it, or after a push that started no build; then get_deploy_status { waitSeconds: 50 } until live, and editor_setup. 409 NOT_QUEUED: no repository (deploy_project the working tree) or publishing held.";
|
|
3102
3132
|
var withWait = (path, waitSeconds) => typeof waitSeconds === "number" && waitSeconds > 0 ? `${path}?wait=${Math.min(Math.round(waitSeconds), 50)}` : path;
|
|
@@ -3359,6 +3389,7 @@ var uiObject = z.strictObject({
|
|
|
3359
3389
|
placeholder: z.string().min(1).max(120).optional().describe("ghost text, a field of any type. A prop uses its own `placeholder`."),
|
|
3360
3390
|
synced: z.strictObject({ from: z.string().min(1).max(64) }).optional().describe("the value comes from this global; edits apply everywhere."),
|
|
3361
3391
|
valueShape: z.enum(["string", "object"]).optional().describe("a link / image / button writes a plain string or the object."),
|
|
3392
|
+
reference: z.object({ integration: z.string(), key: z.string() }).strict().optional().describe("a json field holding a live vendor item: the integration's slug and reference key."),
|
|
3362
3393
|
access: z.strictObject({ view: z.literal("schema.view").optional(), edit: z.literal("schema.manage").optional() }).optional().describe("who sees (view: developer-only) and who changes (edit) it, gated in the editor. The enforced lock is readOnly:'developer'."),
|
|
3363
3394
|
hidden: z.boolean().optional().describe("true: never shown in the form (a system value). A conditional hide is showIf."),
|
|
3364
3395
|
blocks: z.record(z.string(), z.strictObject({ icon: dockIconArg.optional(), preview: z.record(z.string(), z.string()).optional() })).optional().describe("modular only: per block slug, {icon?, preview?} for its rows."),
|
|
@@ -3551,8 +3582,17 @@ var integrationShape = {
|
|
|
3551
3582
|
auth: z.record(z.string(), z.unknown()).nullable().optional().describe(INTEGRATION_PARAM_TEXT.auth),
|
|
3552
3583
|
settings: z.record(z.string(), z.unknown()).optional().describe(INTEGRATION_PARAM_TEXT.settings),
|
|
3553
3584
|
syncs: z.array(z.record(z.string(), z.unknown())).max(20).optional().describe(INTEGRATION_PARAM_TEXT.syncs),
|
|
3554
|
-
panel: z.string().max(48).nullable().optional().describe(INTEGRATION_PARAM_TEXT.panel)
|
|
3585
|
+
panel: z.string().max(48).nullable().optional().describe(INTEGRATION_PARAM_TEXT.panel),
|
|
3586
|
+
hooks: z.array(z.record(z.string(), z.unknown())).max(20).optional().describe(INTEGRATION_PARAM_TEXT.hooks),
|
|
3587
|
+
endpoints: z.array(z.record(z.string(), z.unknown())).max(20).optional().describe(INTEGRATION_PARAM_TEXT.endpoints),
|
|
3588
|
+
references: z.array(z.record(z.string(), z.unknown())).max(10).optional().describe(INTEGRATION_PARAM_TEXT.references),
|
|
3589
|
+
functions: z.array(z.record(z.string(), z.unknown())).max(20).optional().describe(INTEGRATION_PARAM_TEXT.functions),
|
|
3590
|
+
oauth: z.record(z.string(), z.unknown()).nullable().optional().describe(INTEGRATION_PARAM_TEXT.oauth),
|
|
3591
|
+
alertAfter: z.number().int().min(0).max(50).optional().describe(INTEGRATION_PARAM_TEXT.alertAfter)
|
|
3555
3592
|
};
|
|
3593
|
+
var integrationShapeBare = Object.fromEntries(
|
|
3594
|
+
Object.entries(integrationShape).map(([k, v]) => [k, v.describe(void 0)])
|
|
3595
|
+
);
|
|
3556
3596
|
var DRY_RUN_SUMMARY = "Dry run: nothing was written.";
|
|
3557
3597
|
var dryRunOf = async (client, method, path, body, headers = {}) => ok(DRY_RUN_SUMMARY, await client.fetchJSON(client.url(path), { method, body: JSON.stringify({ ...body, dryRun: true }), ...withHeaders(headers) }));
|
|
3558
3598
|
function toField(f) {
|
|
@@ -3666,7 +3706,7 @@ var GIT_TEXT = {
|
|
|
3666
3706
|
var HOSTING_TEXT = {
|
|
3667
3707
|
list_env_vars: "The project's environment variables by NAME and scope (never a value), and `missing`: the ones the repository's .env.example declares that the project does not set. A site missing them builds and serves, then renders empty or crashes in the browser. Resolve `missing` by running `npx @bettercms-ai/cli@latest env push` in the repository: it uploads those values from the user's own .env files straight to BetterCMS, so they never pass through this conversation. Never read, print or ask the user to paste a value. When the values do not exist locally because the site reads its content from another CMS, move that content into BetterCMS instead (the bettercms skill's external-content reference). BetterCMS's own BCMS_* identifiers are supplied to every build and never need setting.",
|
|
3668
3708
|
list_hosting_connections: "The Vercel, Netlify and Cloudflare accounts connected to this workspace (id, provider, accountLabel, status; never a token), plus `connectUrl`, the dashboard page where a person connects one. Call it before deploy_to_host. If the provider the user wants has no connection with status `ok`, give the user `connectUrl` to connect it in their browser and stop until they have: you never handle OAuth or a host token yourself.",
|
|
3669
|
-
deploy_to_host: "Deploy this project to the user's own Vercel or Netlify account through their connection. BetterCMS builds the site and the host serves that build (skill publishing). The first call creates the site on the host and makes it the project's host; a later call for the same provider redeploys that site (`redeployed: true`). `connectionId` (from list_hosting_connections) defaults to the newest working one, `siteName` to the project's handle. A refusal carries a `code` and a `fixUrl` (not_connected, reconnect, pick_existing_site, grant_required, ADMIN_REQUIRED): each is fixed by the user at `fixUrl`, so relay it and stop. `deploymentQueued: false` means the project has no live build yet: deploy_project first, and that release reaches the host on its own. Never run the Vercel or Netlify CLI while a connection exists. Then poll get_host_deploy_status. Needs artifact:write, like deploy_project.",
|
|
3709
|
+
deploy_to_host: "Deploy this project to the user's own Vercel or Netlify account through their connection. BetterCMS builds the site and the host serves that build (skill publishing). The first call creates the site on the host and makes it the project's host; a later call for the same provider redeploys that site (`redeployed: true`). `connectionId` (from list_hosting_connections) defaults to the newest working one, `siteName` to the project's handle. A refusal carries a `code` and a `fixUrl` (not_connected, reconnect, pick_existing_site, grant_required, ADMIN_REQUIRED): each is fixed by the user at `fixUrl`, so relay it and stop. `deploymentQueued: false` means the project has no live build yet: deploy_project first, and that release reaches the host on its own. Never run the Vercel or Netlify CLI while a connection exists. A headless project is not deployed here: its own front end deploys with `npx @bettercms-ai/cli@latest deploy --to <host>`. Then poll get_host_deploy_status. Needs artifact:write, like deploy_project.",
|
|
3670
3710
|
get_host_deploy_status: "The connected project's recent deployments to Vercel, Netlify or Cloudflare, newest first: state (queued|deploying|live|failed), providerUrl, error, sha and targetName. Poll it after deploy_to_host until the newest one is `live`, whose `providerUrl` is the live URL to hand the user, or `failed`, whose `error` you read back to them."
|
|
3671
3711
|
};
|
|
3672
3712
|
async function builtinCopyId(client, builtinId) {
|
|
@@ -4251,7 +4291,7 @@ function buildToolDefs(deps) {
|
|
|
4251
4291
|
propUi: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe(PROP_UI_ARG_NOTE),
|
|
4252
4292
|
propGroup: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe("prop key \u2192 its ui.group ({id, label?, kind?, icon?, collapsed?}), null takes it out"),
|
|
4253
4293
|
propTab: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe("prop key \u2192 its ui.tab ({id, label?, icon?, order?}), the V2 schema group it sits in. The FIRST top-level prop in a tab defines {id, label, icon?, order?}, later ones send {id}; \u22648 tabs; one group's members share one tab. null takes it out"),
|
|
4254
|
-
propLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe("prop key \u2192 label"),
|
|
4294
|
+
propLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe("prop key \u2192 label: one or two words of meaning (playbook \xA72, Naming)"),
|
|
4255
4295
|
propHelpText: z.record(z.string(), z.string().max(200).nullable()).optional().describe("prop key \u2192 help text; null = no help on purpose (stays empty, never refilled)"),
|
|
4256
4296
|
override: z.boolean().optional().describe(OVERRIDE_NOTE),
|
|
4257
4297
|
ifMatch: z.number().int().min(0).optional().describe(COMPONENT_IF_MATCH_NOTE),
|
|
@@ -4770,6 +4810,25 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
4770
4810
|
z.object({ preference: z.enum(AUTHORING_CHOICES).describe("components or fields") }).shape,
|
|
4771
4811
|
async (c, a) => ok("Recorded the authoring architecture.", await data(c, "PATCH", `/management/projects/current/authoring-preference`, { preference: a.preference }))
|
|
4772
4812
|
),
|
|
4813
|
+
def(
|
|
4814
|
+
"create_delivery_key",
|
|
4815
|
+
"Create a read-only delivery key for the front end",
|
|
4816
|
+
DELIVERY_KEY_DESCRIPTION,
|
|
4817
|
+
z.object({ name: z.string().min(1).describe(DELIVERY_KEY_PARAMS.name), scope: z.enum(["published", "draft"]).optional().describe(DELIVERY_KEY_PARAMS.scope) }).shape,
|
|
4818
|
+
async (c, a) => ok("Created the delivery key. It is shown once: write it into the front end's .env now.", await data(c, "POST", `/management/projects/current/delivery-keys`, a))
|
|
4819
|
+
),
|
|
4820
|
+
def(
|
|
4821
|
+
"set_front_end",
|
|
4822
|
+
"Record where the project's own front end lives",
|
|
4823
|
+
FRONT_END_DESCRIPTION,
|
|
4824
|
+
z.object({
|
|
4825
|
+
siteUrl: z.string().nullable().optional().describe(FRONT_END_PARAMS.siteUrl),
|
|
4826
|
+
framework: z.enum(["next", "astro"]).optional().describe(FRONT_END_PARAMS.framework),
|
|
4827
|
+
draftPath: z.string().nullable().optional().describe(FRONT_END_PARAMS.draftPath),
|
|
4828
|
+
revalidation: z.object({ webhookUrl: z.string(), webhookSecret: z.string() }).nullable().optional().describe(FRONT_END_PARAMS.revalidation)
|
|
4829
|
+
}).shape,
|
|
4830
|
+
async (c, a) => ok("Recorded the front end.", await data(c, "PATCH", `/management/projects/current/front-end`, a))
|
|
4831
|
+
),
|
|
4773
4832
|
def(
|
|
4774
4833
|
"set_binding_mode",
|
|
4775
4834
|
"Set how the site's bindings are resolved",
|
|
@@ -4831,7 +4890,7 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
4831
4890
|
fieldUi: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe(FIELD_UI_ARG_NOTE),
|
|
4832
4891
|
fieldFieldset: z.record(z.string(), z.string().min(1).nullable()).optional().describe("path \u2192 fieldset id, null takes it out"),
|
|
4833
4892
|
fieldOrder: z.strictObject({ parentPath: z.string().min(1).optional(), order: z.array(z.string().min(1)).min(1).max(200) }).optional().describe("the full new order of one parent's child keys (top level without parentPath). An array with both zones needs 'KEY.zones.nonRepeatable' or 'KEY.zones.repeatable'; its bare key is a 400"),
|
|
4834
|
-
fieldLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe("path \u2192 label"),
|
|
4893
|
+
fieldLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe("path \u2192 label: one or two words of meaning (playbook \xA72, Naming)"),
|
|
4835
4894
|
fieldHelpText: z.record(z.string(), z.string().max(500).nullable()).optional().describe("path \u2192 help text, null clears it"),
|
|
4836
4895
|
override: z.boolean().optional().describe(OVERRIDE_NOTE),
|
|
4837
4896
|
ifMatch: z.number().int().min(0).optional().describe(MODEL_IF_MATCH_NOTE),
|
|
@@ -5227,7 +5286,8 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
5227
5286
|
"List custom integrations",
|
|
5228
5287
|
NEW_TOOL_TEXT.list_integrations,
|
|
5229
5288
|
z.object({}).shape,
|
|
5230
|
-
|
|
5289
|
+
// The whole body: the workspace's `templates` ride beside `data`.
|
|
5290
|
+
async (c) => ok("Integrations.", await c.fetchJSON(c.url(`/management/integrations`), { method: "GET" }))
|
|
5231
5291
|
),
|
|
5232
5292
|
def(
|
|
5233
5293
|
"get_integration",
|
|
@@ -5255,9 +5315,9 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
5255
5315
|
NEW_TOOL_TEXT.update_integration,
|
|
5256
5316
|
z.object({
|
|
5257
5317
|
integration: z.string().min(1).describe(INTEGRATION_PARAM_TEXT.integration),
|
|
5258
|
-
name: z.string().min(1).max(120).optional()
|
|
5259
|
-
vendor: z.string().max(60).nullable().optional()
|
|
5260
|
-
...
|
|
5318
|
+
name: z.string().min(1).max(120).optional(),
|
|
5319
|
+
vendor: z.string().max(60).nullable().optional(),
|
|
5320
|
+
...integrationShapeBare
|
|
5261
5321
|
}).shape,
|
|
5262
5322
|
async (c, a) => {
|
|
5263
5323
|
const { integration, ...body } = a;
|
|
@@ -5282,6 +5342,66 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
5282
5342
|
}).shape,
|
|
5283
5343
|
async (c, a) => ok("Sync run.", await data(c, "POST", withWait(`/management/integrations/${s(a.integration)}/syncs/${s(a.sync)}/run`, a.waitSeconds)))
|
|
5284
5344
|
),
|
|
5345
|
+
def(
|
|
5346
|
+
"run_integration_function",
|
|
5347
|
+
"Run an integration's function",
|
|
5348
|
+
NEW_TOOL_TEXT.run_integration_function,
|
|
5349
|
+
z.object({
|
|
5350
|
+
integration: z.string().min(1).describe(INTEGRATION_PARAM_TEXT.integration),
|
|
5351
|
+
name: z.string().min(1).describe("the function's name"),
|
|
5352
|
+
input: z.unknown().optional().describe("the function's `event` (JSON)"),
|
|
5353
|
+
waitSeconds: z.number().min(0).max(50).optional().describe(PARAM_TEXT.waitSeconds)
|
|
5354
|
+
}).shape,
|
|
5355
|
+
async (c, a) => ok("Function run.", await data(c, "POST", withWait(`/management/integrations/${s(a.integration)}/functions/${s(a.name)}/run`, a.waitSeconds), { input: a.input }))
|
|
5356
|
+
),
|
|
5357
|
+
def(
|
|
5358
|
+
"search_integration_reference",
|
|
5359
|
+
"Search an integration's live reference",
|
|
5360
|
+
NEW_TOOL_TEXT.search_integration_reference,
|
|
5361
|
+
z.object({
|
|
5362
|
+
integration: z.string().min(1).describe(INTEGRATION_PARAM_TEXT.integration),
|
|
5363
|
+
reference: z.string().min(1).describe(INTEGRATION_PARAM_TEXT.reference),
|
|
5364
|
+
query: z.string().max(200).optional().describe("what to search for")
|
|
5365
|
+
}).shape,
|
|
5366
|
+
async (c, a) => ok("Vendor items.", await data(c, "POST", `/management/integrations/${s(a.integration)}/references/${s(a.reference)}/search`, { query: a.query ?? "" }))
|
|
5367
|
+
),
|
|
5368
|
+
def(
|
|
5369
|
+
"start_integration_signin",
|
|
5370
|
+
"Start an integration's vendor sign-in",
|
|
5371
|
+
NEW_TOOL_TEXT.start_integration_signin,
|
|
5372
|
+
z.object({ integration: z.string().min(1).describe(INTEGRATION_PARAM_TEXT.integration) }).shape,
|
|
5373
|
+
async (c, a) => ok("Sign-in URL: give it to the user to open.", await data(c, "POST", `/management/integrations/${s(a.integration)}/oauth/start`))
|
|
5374
|
+
),
|
|
5375
|
+
def(
|
|
5376
|
+
"save_integration_template",
|
|
5377
|
+
"Save an integration as a template",
|
|
5378
|
+
NEW_TOOL_TEXT.save_integration_template,
|
|
5379
|
+
z.object({
|
|
5380
|
+
integration: z.string().min(1).describe(INTEGRATION_PARAM_TEXT.integration),
|
|
5381
|
+
slug: z.string().min(1).max(48).optional().describe(INTEGRATION_PARAM_TEXT.slug),
|
|
5382
|
+
name: z.string().min(1).max(120).optional().describe("a label, 1-120 chars"),
|
|
5383
|
+
description: z.string().max(500).optional().describe("what it connects, for the people installing it")
|
|
5384
|
+
}).shape,
|
|
5385
|
+
async (c, a) => {
|
|
5386
|
+
const { integration, ...body } = a;
|
|
5387
|
+
return ok("Template saved.", await data(c, "POST", `/management/integrations/${s(integration)}/template`, body));
|
|
5388
|
+
}
|
|
5389
|
+
),
|
|
5390
|
+
def(
|
|
5391
|
+
"install_integration_template",
|
|
5392
|
+
"Install an integration template",
|
|
5393
|
+
NEW_TOOL_TEXT.install_integration_template,
|
|
5394
|
+
z.object({
|
|
5395
|
+
template: z.string().min(1).describe(INTEGRATION_PARAM_TEXT.template),
|
|
5396
|
+
slug: z.string().min(1).max(48).optional().describe(INTEGRATION_PARAM_TEXT.slug),
|
|
5397
|
+
name: z.string().min(1).max(120).optional().describe("a label, 1-120 chars")
|
|
5398
|
+
}).shape,
|
|
5399
|
+
// The whole body: `approval`, `secretsNeeded` and `collectionsCreated` ride beside `data`.
|
|
5400
|
+
async (c, a) => {
|
|
5401
|
+
const { template, ...body } = a;
|
|
5402
|
+
return ok("Template installed.", await c.fetchJSON(c.url(`/management/integrations/templates/${s(template)}/install`), { method: "POST", body: JSON.stringify(body) }));
|
|
5403
|
+
}
|
|
5404
|
+
),
|
|
5285
5405
|
def(
|
|
5286
5406
|
"test_integration_fetch",
|
|
5287
5407
|
"Call a vendor through an integration",
|
|
@@ -7273,6 +7393,20 @@ from the builtin blueprints \`list_components\` returns (\`builtin:*\`) instead
|
|
|
7273
7393
|
JSON. On an IMPORTED site, never materialize a builtin: read the repo's own components first
|
|
7274
7394
|
(\`src/components\`, its sections folder) and make ONE CMS component per source component, named
|
|
7275
7395
|
as the source names it \u2014 the site's Pricing section is "Pricing", not "Section 2" or "Simple".
|
|
7396
|
+
|
|
7397
|
+
**Naming: one or two words of MEANING, BEM-shaped, in Portable Text's words.** You choose every
|
|
7398
|
+
name, from what the page holds. The BLOCK is the section: what it IS (Hero, Pricing, FAQ), else
|
|
7399
|
+
the meaning words of its own heading ("We've spent years learning three timbers" \u2192 "Three
|
|
7400
|
+
Timbers", "Get in touch" \u2192 "Get In Touch"). A LIST is named by its own title, the heading or
|
|
7401
|
+
caption over it ("Our services" \u2192 "Services"), else by what its rows are (Plans, Questions,
|
|
7402
|
+
Steps, Links), never by its first row's copy. A list with its own title is a block inside the
|
|
7403
|
+
block: "About \u203A Services", its columns bare roles read under it (Title, Description). A field is
|
|
7404
|
+
its block plus its ELEMENT, said the way Portable Text titles its types (heading, paragraph,
|
|
7405
|
+
quote, list item, link, image, button) with a modifier only to tell two apart ("Hero headline",
|
|
7406
|
+
"Footer link 2 URL"). Never body copy, never a sentence. Rename anything to say its meaning better
|
|
7407
|
+
\u2014 \`propLabel\`, \`fieldLabel\`, or \`data-bcms-section-label\` / \`data-bcms-label\` in the source \u2014
|
|
7408
|
+
within the bound; a name you give is yours and is never re-derived.
|
|
7409
|
+
|
|
7276
7410
|
On a DERIVED site run \`get_componentize_plan\` \u2192 \`componentize_sections\` FIRST \u2014 it does most
|
|
7277
7411
|
of this in one call, and once \`components\` is recorded each \`editor_setup\` of a derived site
|
|
7278
7412
|
runs it in bind mode and publishes the components, so expect ALREADY_COMPONENTIZED \u2014 the placements it wrote are
|