@se-studio/contentful-cms 3.10.1 → 3.10.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.
@@ -1,2 +1,2 @@
1
- export declare const HOSTED_GUIDE_TEMPLATE = "# cms-edit MCP guide\n\nUse this guide when editing Contentful draft content through a hosted MCP connector (Claude Desktop Integrations + OAuth, or Grok Build).\n\n{{SITE_INTRO}}\n\n## Safety rules\n\ncms-edit **cannot and will not**:\n\n- Publish or unpublish entries\n- Archive or delete published entries\n\nAll `save` operations create **drafts only**. A human must review and publish in Contentful.\n\n**Assets:** cms-edit **can upload** draft assets via **staged upload** (hosted) or `--url` (public HTTPS last resort). **`--base64` is not supported on hosted MCP.** Always **search and reuse existing assets first** (`index sync`, then `asset search` or `list --type media`). Use `--if-exists-by-filename` when uploading to avoid duplicates.\n\n## MCP tools\n\n| Tool | Purpose |\n|------|---------|\n| `cms_edit` | Run any cms-edit command via an `args` array |\n| `cms_edit_request_staged_upload` | Presigned URL for binaries. Raster/video: then `cms_edit_media` prepare. SVG/PDF/Lottie: then `asset upload --staged` |\n| `cms_edit_version` | Check the installed cms-edit version (no session required) |\n| `cms_edit_help` | Guide topics (`overview`, `workflow`, `commands`, `troubleshooting`, `hosted`) **or** playbooks (`create-from-json`, `fields-component`, \u2026) |\n\n## Customer editor pack (read first)\n\nWhen a site editor pack is deployed, these MCP resources provide **site-specific** context. Read them before creating or editing content:\n\n| Resource | Purpose |\n|----------|---------|\n| `cms-edit://customer/index` | List of all customer resources |\n| `cms-edit://customer/tasks-index` | Intent \u2192 task playbook map (filtered for this site) |\n| `cms-edit://customer/capabilities` | Machine-readable list of what this site supports |\n| `cms-edit://customer/checklist` | First-session onboarding steps |\n| `cms-edit://customer/overview` | Site name, space, staging URL, handoff rules |\n| `cms-edit://customer/routing` | Article/tag URL rules for this site |\n| `cms-edit://customer/production-site` | Production URL and how to fetch page markdown (do not guess `.md` URLs) |\n| `cms-edit://customer/components-index` | Component types and when to use each |\n| `cms-edit://customer/collections-index` | Collection types |\n| `cms-edit://customer/brand` | Brand voice and terminology (if configured) |\n| `cms-edit://customer/articles` | Article/publication playbook (if configured) |\n| `cms-edit://customer/defaults` | Featured image rules and article types (if configured) |\n| `cms-edit://customer/site-facts` | Optional canonical business facts (if configured) |\n| `cms-edit://customer/task-*` | Capability playbooks (e.g. `task-media-reuse-and-upload`) |\n\n**Workflow:** read `tasks-index` \u2192 `capabilities` \u2192 `checklist` \u2192 `overview` \u2192 `routing` \u2192 `production-site` \u2192 pick a `task-*` playbook for your goal \u2192 `components-index` \u2192 then use `cms_edit` commands.\n\n## MCP prompts\n\n| Prompt | Purpose |\n|--------|---------|\n| `cms_edit_verify` | Check version and connectivity |\n| `cms_edit_explore` | List site content and open a page |\n| `cms_edit_edit_page` | Edit a page through diff \u2192 save |\n| `cms_edit_edit_article` | Edit an article through diff \u2192 save |\n| `cms_edit_index_sync` | Build/refresh local index (assets, media, templates, taxonomy) |\n| `cms_edit_create_page` | Create a page with template selection |\n| `cms_edit_create_article` | Create an article (requires article type; featured image rules) |\n| `cms_edit_create_from_doc` | Create a page from a freeform document (infer \u2192 confirm \u2192 dry-run) |\n| `cms_edit_create_article_from_doc` | Create an article from a freeform document |\n| `cms_edit_taxonomy` | Import taxonomy entries (`create \u2026 --if-not-exists`) |\n| `cms_edit_bulk_seo` | Update SEO meta descriptions (read `task-seo-descriptions`) |\n| `cms_edit_bulk_find_replace` | Bulk find-replace in rich text across entries |\n| `cms_edit_bulk_article_tags` | Update tags on one or more articles |\n| `cms_edit_import_authors` | Create person/author entries from a list or sheet |\n| `cms_edit_media` | Find existing assets or upload new ones (reuse-first) |\n\nRead the full guide anytime via the `cms-edit://guide` resource or `cms_edit_help`.\n\n## End-to-end QA prompt\n\nFor release QA after cms-edit or editor-pack upgrades, use a **copy-paste Claude Desktop test script** (phases: connector \u2192 customer resources \u2192 discovery \u2192 session \u2192 schema \u2192 media \u2192 dry-run writes \u2192 negatives). See [e2e-test-prompt.md](./e2e-test-prompt.md) to author one per customer site. Example: Brightline `docs/cms-edit/MCP-E2E-TEST.md`.\n\n## Core workflow\n\nStandard editing flow: **open \u2192 snapshot \u2192 read \u2192 set/rtf \u2192 diff \u2192 save**\n\nEach step is a separate `cms_edit` call. Sessions persist in Redis between calls (hosted) or on disk (local). MCP auto-uses session `_mcp`.\n\n### 1. Open a page\n\n```\n[\"open\", \"--page-slug\", \"/\"]\n[\"open\", \"--page-slug\", \"/pricing\"]\n[\"open\", \"--id\", \"<entry-id>\"]\n[\"open\", \"--include-chrome\", \"--page-slug\", \"/pricing\"]\n```\n\nDefault `open` / `peek` fetch the page or article tree and the template entry, but **not** template menu/footer/navigation. Edit nav with `[\"nav\", \"open\", \"--nav-id\", \"<id>\"]` or `--nav-name` (not `--id`). Use `--include-chrome` only when you need those trees. Template `menu` is site-wide; `page.menu` overrides one page. Nested nav items are often shared \u2014 ask global vs page-specific before `set`.\n\n**Homepage (all SE Studio sites):** Contentful stores the marketing homepage slug as `index`. The live site URL is `/`. In cms-edit, `--page-slug /` and `--page-slug /index` are equivalent (both resolve to `index`). Read `cms-edit://customer/routing` for site-specific exceptions (e.g. Pedestal uses `home`).\n\n### 2. Snapshot \u2014 see the content tree\n\n```\n[\"snapshot\"]\n```\n\nOutput shows `@c0`, `@c1`, \u2026 refs for each component. Use these refs in `read`, `set`, and `rtf`.\n\n### 3. Read fields\n\n```\n[\"read\", \"@c1\"]\n[\"read\", \"@c1\", \"heading\"]\n```\n\n### 4. Edit fields\n\n```\n[\"set\", \"@c1\", \"heading\", \"New heading text\"]\n[\"rtf\", \"@c1\", \"body\", \"--content\", \"# Title\\n\\nParagraph text.\"]\n```\n\nFor rich text, prefer `--content` with inline markdown. Use `--base64` when special characters cause issues.\n\n### 5. Review and save\n\n```\n[\"diff\"]\n[\"save\"]\n```\n\nAlways run `diff` before `save`. To abandon changes: `[\"discard\"]`.\n\n## When to use Contentful UI\n\n| Task | Where |\n|------|-------|\n| Edit copy, add sections, create pages/articles | **Agent + cms-edit** (drafts) |\n| Upload new images and files | **Agent + cms-edit** \u2014 search/reuse first; raster/video via `cms_edit_media` import |\n| Publish approved drafts | **Contentful UI** |\n| Edit copy directly in Contentful | **Avoid** \u2014 use cms-edit instead |\n\ncms-edit saves **drafts only**. A colleague must **publish in Contentful** for changes to go live.\n\n## Asset upload (hosted MCP)\n\nHosted connectors have **no server filesystem** and **reject `asset upload --base64`**. Raster/video: staged intake then **media pipeline**. SVG/PDF/Lottie: staged then `asset upload --staged`.\n\n| Source | When | Example |\n|--------|------|---------|\n| **Staged + pipeline** | jpeg/png/webp/gif/mp4 | `cms_edit_media` prepare `stagedUploadIds` \u2192 import (required for video) |\n| **`--url`** | Pipeline-ready **images** only, or SVG/PDF | `[\"asset\", \"upload\", \"--url\", \"https://\u2026.blob.vercel-storage.com/ready.jpg\"]` |\n| **Staged consume** | SVG/PDF/Lottie | `asset upload --staged` |\n\n**After upload:** note the returned asset `id`, attach to the entry (`asset set @root media <id>` for person portraits), then `diff` \u2192 `save`. Upload alone does not update entries.\n\n**PNG** when transparency matters.\n\nFull workflow: `cms-edit://customer/task-media-reuse-and-upload`.\n\n### Common pitfalls (agents)\n\n| Issue | Do this |\n|-------|---------|\n| Singular Media on a component (`visual`) | `set @cN visual <mediaId> --link` \u2014 **not** `add \u2026 --target visual` |\n| Shared Drive folder has extra files | Import with `sourceNames` or `onlyWithOverrides` after catalog |\n| Prepare says no media / empty folder | Share folder **Viewer** with `media-pipeline-reader@se-media-pipeline.iam.gserviceaccount.com`, then re-prepare once |\n| MCP tool times out ~180s | `read` / `diff` / re-`catalog` before retrying the same write \u2014 server may already have saved |\n| `preview urls` has no Live Preview URL | Use staging + classic entry editor; row includes `unavailableReason` when CMA preview fails |\n| Reuse-first import matches old fileNames | dry-run first; set `overrides[].fileName` for unique names when duplication is required |\n\n### Staged upload\n\nKeeps binary **out of MCP tool args**. The agent uploads via `curl`, then passes only a short `uploadId`.\n\n1. `cms_edit_request_staged_upload` with `fileName`, `mimeType`, optional `byteLength`, optional `title`\n2. Run the returned `curlCommand` in a shell (POST multipart to `uploadUrl`)\n3. Follow `consumeArgs`: **media prepare** (`cms_edit_media` / `media prepare --staged-ids`) for jpeg/png/webp/gif/mp4; **asset upload --staged** for SVG/PDF/Lottie\n4. For pipeline jobs: wait \u2192 catalog \u2192 **import** (required for video). Then link and save\n\nHosted limit: **4 MB** per staged file (Vercel body limit). Rate limit: **1000 upload URLs per user per hour**. TTL: **60 minutes**. Raster/video staged files are intake (GET by the pipeline); they are not consumed into CMA.\n\n## Featured images (articles)\n\nRead `cms-edit://customer/defaults` when present. Otherwise:\n\n1. **Custom** \u2014 set `featuredImage` or `featuredImageAssetFilename` on the article when the editor provides a specific image\n2. **Article type** \u2014 if omitted, cms-edit inherits the linked **articleType** `featuredImage`\n3. **Site default** \u2014 from `editor.defaults` in the site config (also in `defaults.json` in the editor pack)\n\nRun `[\"index\", \"sync\"]` before resolving assets by filename.\n\n## JSON payloads (filesystem vs no filesystem)\n\nHosted MCP cannot see your disk. Same commands either way:\n\n- **Filesystem client:** write `page.json`, then `cms_edit [\"create\", \"from-json\", \"--json-base64\", \"<base64 of file>\", \"--dry-run\", \"--strict\"]`\n- **No filesystem (Desktop):** put the JSON in `--json` on the tool call\n\nNever use a local `cms-edit` binary, `--file`, or stdin for content.\n\n## Create content\n\n### Create a page\n\n```\n[\"schema\", \"component\"]\n[\"help\", \"create-from-json\"]\n[\"create\", \"from-json\", \"--json\", \"{...}\", \"--dry-run\", \"--strict\"]\n[\"create\", \"from-json\", \"--json\", \"{...}\"]\n```\n\nUse `\"components\"` (not `\"content\"`) and nested `\"type\"` (not `\"componentType\"`). Peek a live entry or `schema <ct>` before authoring field ids.\n\nEmpty `components` fails validation. Incremental `add`/`set` is for tiny patches only.\n\n### Create an article\n\nEvery article requires an **articleType**:\n\n```\n[\"index\", \"sync\"]\n[\"list\", \"--type\", \"articleType\"]\n[\"create\", \"article\", \"--slug\", \"resources/blog/my-post\", \"--title\", \"My Post\", \"--article-type-slug\", \"resources/blog\"]\n```\n\nOr `create from-json` with `\"type\": \"article\"` and `articleTypeSlug` / `articleTypeId`.\n\nUse MCP prompt `cms_edit_create_article` for guided creation.\n\n## Common sequences\n\n### List all pages\n\n```\n[\"list\", \"--type\", \"page\"]\n```\n\n### Refresh local index (assets, media, templates, taxonomy)\n\nRun before asset filename search, media-by-filename list, or fast template/taxonomy lookup:\n\n```\n[\"index\", \"sync\"]\n[\"index\", \"status\"]\n[\"list\", \"--type\", \"template\"]\n```\n\nAsset search and media-by-filename list **require** the index. Template/taxonomy list prefers the index when built.\n\n### Explore the homepage\n\n```\n[\"open\", \"--page-slug\", \"/\"]\n[\"snapshot\"]\n[\"read\", \"@c1\"]\n```\n\n### Edit copy on a page\n\n```\n[\"open\", \"--page-slug\", \"{{EXAMPLE_SLUG}}\"]\n[\"snapshot\"]\n[\"read\", \"@c1\", \"heading\"]\n[\"set\", \"@c1\", \"heading\", \"Updated heading\"]\n[\"diff\"]\n[\"save\"]\n```\n\n## Field discovery\n\n```\n[\"schema\", \"component\"]\n[\"list\", \"--type\", \"page\"]\n[\"search\", \"pricing\"]\n```\n\n## Grok Build setup\n\nGrok connects to the same hosted MCP URL as Claude. Add to `~/.grok/config.toml`:\n\n```toml\n[mcp_servers.cms-edit-brightline]\nurl = \"https://brightline.content.se.studio/api/mcp\"\nenabled = true\n```\n\nReplace the server name and host with your site's deployment. The URL **must** end with `/api/mcp` (not the site root).\n\n**Connect:**\n\n1. In Grok, run `/mcps` and select your cms-edit server\n2. Press **`i`** to initiate OAuth (not just enable the toggle)\n3. Sign in with Contentful when the browser opens\n4. Verify with `grok mcp doctor <server-name>` or call `cms_edit_version`\n\nOAuth is two steps: Grok obtains an MCP token, then you sign in with Contentful to link your account. If the browser flashes and closes immediately, reconnect after upgrading cms-edit (loopback redirect handling).\n\n## Hosted connector rules\n\n{{HOSTED_RULES}}\n\n## Session tips\n\n- After `open`, subsequent MCP commands share session `_mcp` automatically (stored in Redis on hosted).\n- Use `--session <name>` only when juggling multiple pages \u2014 pass it **before** the subcommand on every call: `[\"--session\", \"my-work\", \"open\", \"--page-slug\", \"/\"]`.\n- Snapshot JSON includes `sessionName` and `sessionKey` for debugging.\n- If you see \"No active session\", check `/api/health` (Redis must be ok), then run `open` again. Set `LOG_CMS_SESSION=1` on Vercel and search runtime logs for `[cms-edit:session]` (`redis save ok` / `redis load hit` / `requireSession miss`).\n\n### peek vs open vs read\n\n| Goal | MCP args |\n|------|----------|\n| Edit workflow (session) | `[\"open\", \"--page-slug\", \"/\"]` then `[\"read\", \"@c0\"]` |\n| Preview tree + fields, no session | `[\"peek\", \"--page-slug\", \"/pricing\"]` or `[\"peek\", \"--id\", \"<entry-id>\"]` (JSON includes `rootFields`) |\n| Read fields by entry ID, no session | `[\"read\", \"<entry-id>\", \"--id\"]` or `[\"read\", \"<entry-id>\", \"--id\", \"heading\"]` |\n| Debug session persistence | `[\"diagnose\"]` \u2014 reports store, userId, active session key |\n\n- **`peek` is one-shot** \u2014 prints tree and **root field values** (`rootFields` in JSON). No session is created; do not chain `read @ref` afterward.\n- **`open` / `nav open` persist** the session. `preview urls /slug` follows that slug even if leftover `_mcp` is another page (JSON may include `sessionRootIgnored`).\n- **Use flat lookup flags** for `open` and `peek` \u2014 same as CLI (`--page-slug`, `--id`, etc.). See `help lookup`. `nav open` uses `--nav-id` or `--nav-name` only.\n- **`read @root`** only works after `open` (uses the active session). The hint in peek output is for terminal users who already ran `open`.\n- **Do not invent flags** \u2014 there is no `--read`, `--then`, or chained peek+read syntax. One command per `cms_edit` call.\n\n## Troubleshooting\n\n| Problem | What to do |\n|---------|------------|\n| **401 / connection failed** | Reconnect: Claude \u2192 Settings \u2192 Integrations, or Grok `/mcps` \u2192 press `i`. Complete Contentful sign-in. If Grok says \u201CFailed to get tools after auth\u201D or `auth required`, check Vercel `[cms-edit:oauth] auth_failure`. `invalid_refresh_token` is a rotated/missing MCP refresh UUID (fixed in **3.6.1+** by not rotating). `missing_contentful_token` / `invalid_contentful_token` means the CMA cache is gone \u2014 **3.5.3+** returns `invalid_token` so Grok re-runs Contentful sign-in. If it still loops, delete that server\u2019s entry from `~/.grok/mcp_credentials.json` and press `i` again. Set `LOG_CMS_OAUTH=1` for connect success traces. |\n| **No cms_edit tool** | Confirm the integration is connected under Settings \u2192 Integrations |\n| **No active session** | Do not pass `--space` on hosted. Check `/api/health` \u2014 `redis` must be ok. MCP uses `_mcp` session automatically. Manual session: `[\"--session\", \"name\", \"open\", \"--page-slug\", \"/\"]`. Fallback: `[\"read\", \"<entry-id>\", \"--id\"]` |\n| **Wrong space** | Your Contentful account must have access to this deployment's space |\n| **Check version** | Call `cms_edit_version` or `curl /api/health` and read `cmsEdit` |\n\n## Articles\n\n**Recent articles** (by publication `date`, not last-updated):\n\n```json\n[\"list\", \"--type\", \"article\", \"--sort\", \"date\", \"-n\", \"10\"]\n```\n\nAlternative: `[\"search\", \"<keyword>\", \"--type\", \"article\"]` (also sorts by date).\n\n**Draft articles** \u2014 `peek` and `open` load drafts via CMA (not published-only). To discover drafts:\n\n```json\n[\"list\", \"--type\", \"article\", \"--sort\", \"date\"]\n[\"search\", \"<title>\", \"--type\", \"article\"]\n[\"list\", \"--type\", \"article\", \"--slug\", \"<slug>\"]\n```\n\nThen open: `[\"peek\", \"--article-slug\", \"<slug>\"]` or `[\"open\", \"--id\", \"<entry-id>\"]`. Use `--article-slug`, not `--page-slug`.\n\n**Published-only** is opt-in: `list --published` and `resolve --published` use the Delivery API index and omit never-published drafts.\n\nAfter upgrading cms-edit, run `[\"index\", \"sync\"]` once so the local index includes article `date` values (schema v4).\n\n**Index freshness on hosted MCP:** The catalog index is shared via Redis (gzipped SQLite blob). `index status` on a cold instance may show **not built** until `list`, `open`, or another index command runs \u2014 that is normal. Committed writes (`save`, `create page`, `asset upload`, \u2026) mark the index dirty; the next discovery command refreshes it. In-session `set` edits do not trigger a sync.\n\n## Command reference (cms_edit args)\n\n**Discovery:** `list --type <type>`, `search <query>`, `schema <contentType>`, `index sync`, `index status`\n\n**Reading:** `open --page-slug <slug>`, `open --id <id>`, `peek --page-slug <slug>`, `peek --id <id>`, `snapshot`, `read <ref>`, `read <ref> <field>`, `read <id> --id [field]`\n\n**Diagnostics:** `diagnose` \u2014 session store, userId, active session (after open/read issues)\n\n**Editing:** `set <ref> <field> <value>`, `rtf <ref> <field> --content \"<md>\"`, `diff`, `save`, `discard`\n\n**Creating:** `create from-json --json \u2026 --dry-run --strict`, `export from-json`, `apply from-json --json \u2026`, `create page` / `create article` for empty shells, `add` for tiny inserts\n\n**Other:** `health`, `version`, `guide [topic]`\n";
1
+ export declare const HOSTED_GUIDE_TEMPLATE = "# cms-edit MCP guide\n\nUse this guide when editing Contentful draft content through a hosted MCP connector (Claude Desktop Integrations + OAuth, or Grok Build).\n\n{{SITE_INTRO}}\n\n## Safety rules\n\ncms-edit **cannot and will not**:\n\n- Publish or unpublish entries\n- Archive or delete published entries\n\nAll `save` operations create **drafts only**. A human must review and publish in Contentful.\n\n**Assets:** cms-edit **can upload** draft assets via **staged upload** (hosted) or `--url` (public HTTPS last resort). **`--base64` is not supported on hosted MCP.** Always **search and reuse existing assets first** (`index sync`, then `asset search` or `list --type media`). Use `--if-exists-by-filename` when uploading to avoid duplicates.\n\n## MCP tools\n\n| Tool | Purpose |\n|------|---------|\n| `cms_edit` | Run any cms-edit command via an `args` array |\n| `cms_edit_request_staged_upload` | Presigned URL for binaries. Raster/video: then `cms_edit_media` prepare. SVG/PDF/Lottie: then `asset upload --staged` |\n| `cms_edit_version` | Check the installed cms-edit version (no session required) |\n| `cms_edit_help` | Guide topics (`overview`, `workflow`, `commands`, `troubleshooting`, `hosted`) **or** playbooks (`create-from-json`, `fields-html-component`, `fields-component`, \u2026) |\n\n## Customer editor pack (read first)\n\nWhen a site editor pack is deployed, these MCP resources provide **site-specific** context. Read them before creating or editing content:\n\n| Resource | Purpose |\n|----------|---------|\n| `cms-edit://customer/index` | List of all customer resources |\n| `cms-edit://customer/tasks-index` | Intent \u2192 task playbook map (filtered for this site) |\n| `cms-edit://customer/capabilities` | Machine-readable list of what this site supports |\n| `cms-edit://customer/checklist` | First-session onboarding steps |\n| `cms-edit://customer/overview` | Site name, space, staging URL, handoff rules |\n| `cms-edit://customer/routing` | Article/tag URL rules for this site |\n| `cms-edit://customer/production-site` | Production URL and how to fetch page markdown (do not guess `.md` URLs) |\n| `cms-edit://customer/components-index` | Component types and when to use each |\n| `cms-edit://customer/collections-index` | Collection types |\n| `cms-edit://customer/brand` | Brand voice and terminology (if configured) |\n| `cms-edit://customer/articles` | Article/publication playbook (if configured) |\n| `cms-edit://customer/defaults` | Featured image rules and article types (if configured) |\n| `cms-edit://customer/site-facts` | Optional canonical business facts (if configured) |\n| `cms-edit://customer/task-*` | Capability playbooks (e.g. `task-media-reuse-and-upload`) |\n\n**Workflow:** read `tasks-index` \u2192 `capabilities` \u2192 `checklist` \u2192 `overview` \u2192 `routing` \u2192 `production-site` \u2192 pick a `task-*` playbook for your goal \u2192 `components-index` \u2192 then use `cms_edit` commands.\n\n## MCP prompts\n\n| Prompt | Purpose |\n|--------|---------|\n| `cms_edit_verify` | Check version and connectivity |\n| `cms_edit_explore` | List site content and open a page |\n| `cms_edit_edit_page` | Edit a page through diff \u2192 save |\n| `cms_edit_edit_article` | Edit an article through diff \u2192 save |\n| `cms_edit_index_sync` | Build/refresh local index (assets, media, templates, taxonomy) |\n| `cms_edit_create_page` | Create a page with template selection |\n| `cms_edit_create_article` | Create an article (requires article type; featured image rules) |\n| `cms_edit_create_from_doc` | Create a page from a freeform document (infer \u2192 confirm \u2192 dry-run) |\n| `cms_edit_create_article_from_doc` | Create an article from a freeform document |\n| `cms_edit_taxonomy` | Import taxonomy entries (`create \u2026 --if-not-exists`) |\n| `cms_edit_bulk_seo` | Update SEO meta descriptions (read `task-seo-descriptions`) |\n| `cms_edit_bulk_find_replace` | Bulk find-replace in rich text across entries |\n| `cms_edit_bulk_article_tags` | Update tags on one or more articles |\n| `cms_edit_import_authors` | Create person/author entries from a list or sheet |\n| `cms_edit_media` | Find existing assets or upload new ones (reuse-first) |\n\nRead the full guide anytime via the `cms-edit://guide` resource or `cms_edit_help`.\n\n## End-to-end QA prompt\n\nFor release QA after cms-edit or editor-pack upgrades, use a **copy-paste Claude Desktop test script** (phases: connector \u2192 customer resources \u2192 discovery \u2192 session \u2192 schema \u2192 media \u2192 dry-run writes \u2192 negatives). See [e2e-test-prompt.md](./e2e-test-prompt.md) to author one per customer site. Example: Brightline `docs/cms-edit/MCP-E2E-TEST.md`.\n\n## Core workflow\n\nStandard editing flow: **open \u2192 snapshot \u2192 read \u2192 set/rtf \u2192 diff \u2192 save**\n\nEach step is a separate `cms_edit` call. Sessions persist in Redis between calls (hosted) or on disk (local). MCP auto-uses session `_mcp`.\n\n### 1. Open a page\n\n```\n[\"open\", \"--page-slug\", \"/\"]\n[\"open\", \"--page-slug\", \"/pricing\"]\n[\"open\", \"--id\", \"<entry-id>\"]\n[\"open\", \"--include-chrome\", \"--page-slug\", \"/pricing\"]\n```\n\nDefault `open` / `peek` fetch the page or article tree and the template entry, but **not** template menu/footer/navigation. Edit nav with `[\"nav\", \"open\", \"--nav-id\", \"<id>\"]` or `--nav-name` (not `--id`). Use `--include-chrome` only when you need those trees. Template `menu` is site-wide; `page.menu` overrides one page. Nested nav items are often shared \u2014 ask global vs page-specific before `set`.\n\n**Homepage (all SE Studio sites):** Contentful stores the marketing homepage slug as `index`. The live site URL is `/`. In cms-edit, `--page-slug /` and `--page-slug /index` are equivalent (both resolve to `index`). Read `cms-edit://customer/routing` for site-specific exceptions (e.g. Pedestal uses `home`).\n\n### 2. Snapshot \u2014 see the content tree\n\n```\n[\"snapshot\"]\n```\n\nOutput shows `@c0`, `@c1`, \u2026 refs for each component. Use these refs in `read`, `set`, and `rtf`.\n\n### 3. Read fields\n\n```\n[\"read\", \"@c1\"]\n[\"read\", \"@c1\", \"heading\"]\n```\n\n### 4. Edit fields\n\n```\n[\"set\", \"@c1\", \"heading\", \"New heading text\"]\n[\"rtf\", \"@c1\", \"body\", \"--content\", \"# Title\\n\\nParagraph text.\"]\n```\n\nFor rich text, prefer `--content` with inline markdown. Use `--base64` when special characters cause issues.\n\n### 5. Review and save\n\n```\n[\"diff\"]\n[\"save\"]\n```\n\nAlways run `diff` before `save`. To abandon changes: `[\"discard\"]`.\n\n## When to use Contentful UI\n\n| Task | Where |\n|------|-------|\n| Edit copy, add sections, create pages/articles | **Agent + cms-edit** (drafts) |\n| Upload new images and files | **Agent + cms-edit** \u2014 search/reuse first; raster/video via `cms_edit_media` import |\n| Publish approved drafts | **Contentful UI** |\n| Edit copy directly in Contentful | **Avoid** \u2014 use cms-edit instead |\n\ncms-edit saves **drafts only**. A colleague must **publish in Contentful** for changes to go live.\n\n## Asset upload (hosted MCP)\n\nHosted connectors have **no server filesystem** and **reject `asset upload --base64`**. Raster/video: staged intake then **media pipeline**. SVG/PDF/Lottie: staged then `asset upload --staged`.\n\n| Source | When | Example |\n|--------|------|---------|\n| **Staged + pipeline** | jpeg/png/webp/gif/mp4 | `cms_edit_media` prepare `stagedUploadIds` \u2192 import (required for video) |\n| **`--url`** | Pipeline-ready **images** only, or SVG/PDF | `[\"asset\", \"upload\", \"--url\", \"https://\u2026.blob.vercel-storage.com/ready.jpg\"]` |\n| **Staged consume** | SVG/PDF/Lottie | `asset upload --staged` |\n\n**After upload:** note the returned asset `id`, attach to the entry (`asset set @root media <id>` for person portraits), then `diff` \u2192 `save`. Upload alone does not update entries.\n\n**PNG** when transparency matters.\n\nFull workflow: `cms-edit://customer/task-media-reuse-and-upload`.\n\n### Common pitfalls (agents)\n\n| Issue | Do this |\n|-------|---------|\n| Singular Media on a component (`visual`) | `set @cN visual <mediaId> --link` \u2014 **not** `add \u2026 --target visual` |\n| Shared Drive folder has extra files | Import with `sourceNames` or `onlyWithOverrides` after catalog |\n| Prepare says no media / empty folder | Share folder **Viewer** with `media-pipeline-reader@se-media-pipeline.iam.gserviceaccount.com`, then re-prepare once |\n| MCP tool times out ~180s | `read` / `diff` / re-`catalog` before retrying the same write \u2014 server may already have saved |\n| `preview urls` has no Live Preview URL | Use staging + classic entry editor; row includes `unavailableReason` when CMA preview fails |\n| Reuse-first import matches old fileNames | dry-run first; set `overrides[].fileName` for unique names when duplication is required |\n\n### Staged upload\n\nKeeps binary **out of MCP tool args**. The agent uploads via `curl`, then passes only a short `uploadId`.\n\n1. `cms_edit_request_staged_upload` with `fileName`, `mimeType`, optional `byteLength`, optional `title`\n2. Run the returned `curlCommand` in a shell (POST multipart to `uploadUrl`)\n3. Follow `consumeArgs`: **media prepare** (`cms_edit_media` / `media prepare --staged-ids`) for jpeg/png/webp/gif/mp4; **asset upload --staged** for SVG/PDF/Lottie\n4. For pipeline jobs: wait \u2192 catalog \u2192 **import** (required for video). Then link and save\n\nHosted limit: **4 MB** per staged file (Vercel body limit). Rate limit: **1000 upload URLs per user per hour**. TTL: **60 minutes**. Raster/video staged files are intake (GET by the pipeline); they are not consumed into CMA.\n\n## Featured images (articles)\n\nRead `cms-edit://customer/defaults` when present. Otherwise:\n\n1. **Custom** \u2014 set `featuredImage` or `featuredImageAssetFilename` on the article when the editor provides a specific image\n2. **Article type** \u2014 if omitted, cms-edit inherits the linked **articleType** `featuredImage`\n3. **Site default** \u2014 from `editor.defaults` in the site config (also in `defaults.json` in the editor pack)\n\nRun `[\"index\", \"sync\"]` before resolving assets by filename.\n\n## JSON payloads (filesystem vs no filesystem)\n\nHosted MCP cannot see your disk. Same commands either way:\n\n- **Filesystem client:** write `page.json`, then `cms_edit [\"create\", \"from-json\", \"--json-base64\", \"<base64 of file>\", \"--dry-run\", \"--strict\"]`\n- **No filesystem (Desktop):** put the JSON in `--json` on the tool call\n\nNever use a local `cms-edit` binary, `--file`, or stdin for content.\n\n## Create content\n\n### Create a page\n\n```\n[\"schema\", \"component\"]\n[\"help\", \"create-from-json\"]\n[\"create\", \"from-json\", \"--json\", \"{...}\", \"--dry-run\", \"--strict\"]\n[\"create\", \"from-json\", \"--json\", \"{...}\"]\n```\n\nUse `\"components\"` (not `\"content\"`) and nested `\"type\"` (not `\"componentType\"`). Peek a live entry or `schema <ct>` before authoring field ids.\n\nEmpty `components` fails validation. Incremental `add`/`set` is for tiny patches only.\n\n### Create an article\n\nEvery article requires an **articleType**:\n\n```\n[\"index\", \"sync\"]\n[\"list\", \"--type\", \"articleType\"]\n[\"create\", \"article\", \"--slug\", \"resources/blog/my-post\", \"--title\", \"My Post\", \"--article-type-slug\", \"resources/blog\"]\n```\n\nOr `create from-json` with `\"type\": \"article\"` and `articleTypeSlug` / `articleTypeId`.\n\nUse MCP prompt `cms_edit_create_article` for guided creation.\n\n## Common sequences\n\n### List all pages\n\n```\n[\"list\", \"--type\", \"page\"]\n```\n\n### Refresh local index (assets, media, templates, taxonomy)\n\nRun before asset filename search, media-by-filename list, or fast template/taxonomy lookup:\n\n```\n[\"index\", \"sync\"]\n[\"index\", \"status\"]\n[\"list\", \"--type\", \"template\"]\n```\n\nAsset search and media-by-filename list **require** the index. Template/taxonomy list prefers the index when built.\n\n### Explore the homepage\n\n```\n[\"open\", \"--page-slug\", \"/\"]\n[\"snapshot\"]\n[\"read\", \"@c1\"]\n```\n\n### Edit copy on a page\n\n```\n[\"open\", \"--page-slug\", \"{{EXAMPLE_SLUG}}\"]\n[\"snapshot\"]\n[\"read\", \"@c1\", \"heading\"]\n[\"set\", \"@c1\", \"heading\", \"Updated heading\"]\n[\"diff\"]\n[\"save\"]\n```\n\n## Field discovery\n\n```\n[\"schema\", \"component\"]\n[\"list\", \"--type\", \"page\"]\n[\"search\", \"pricing\"]\n```\n\n## Grok Build setup\n\nGrok connects to the same hosted MCP URL as Claude. Add to `~/.grok/config.toml`:\n\n```toml\n[mcp_servers.cms-edit-brightline]\nurl = \"https://brightline.content.se.studio/api/mcp\"\nenabled = true\n```\n\nReplace the server name and host with your site's deployment. The URL **must** end with `/api/mcp` (not the site root).\n\n**Connect:**\n\n1. In Grok, run `/mcps` and select your cms-edit server\n2. Press **`i`** to initiate OAuth (not just enable the toggle)\n3. Sign in with Contentful when the browser opens\n4. Verify with `grok mcp doctor <server-name>` or call `cms_edit_version`\n\nOAuth is two steps: Grok obtains an MCP token, then you sign in with Contentful to link your account. If the browser flashes and closes immediately, reconnect after upgrading cms-edit (loopback redirect handling).\n\n## Hosted connector rules\n\n{{HOSTED_RULES}}\n\n## Session tips\n\n- After `open`, subsequent MCP commands share session `_mcp` automatically (stored in Redis on hosted).\n- Use `--session <name>` only when juggling multiple pages \u2014 pass it **before** the subcommand on every call: `[\"--session\", \"my-work\", \"open\", \"--page-slug\", \"/\"]`.\n- Snapshot JSON includes `sessionName` and `sessionKey` for debugging.\n- If you see \"No active session\", check `/api/health` (Redis must be ok), then run `open` again. Set `LOG_CMS_SESSION=1` on Vercel and search runtime logs for `[cms-edit:session]` (`redis save ok` / `redis load hit` / `requireSession miss`).\n\n### peek vs open vs read\n\n| Goal | MCP args |\n|------|----------|\n| Edit workflow (session) | `[\"open\", \"--page-slug\", \"/\"]` then `[\"read\", \"@c0\"]` |\n| Preview tree + fields, no session | `[\"peek\", \"--page-slug\", \"/pricing\"]` or `[\"peek\", \"--id\", \"<entry-id>\"]` (JSON includes `rootFields`) |\n| Read fields by entry ID, no session | `[\"read\", \"<entry-id>\", \"--id\"]` or `[\"read\", \"<entry-id>\", \"--id\", \"heading\"]` |\n| Debug session persistence | `[\"diagnose\"]` \u2014 reports store, userId, active session key |\n\n- **`peek` is one-shot** \u2014 prints tree and **root field values** (`rootFields` in JSON). No session is created; do not chain `read @ref` afterward.\n- **`open` / `nav open` persist** the session. `preview urls /slug` follows that slug even if leftover `_mcp` is another page (JSON may include `sessionRootIgnored`).\n- **Use flat lookup flags** for `open` and `peek` \u2014 same as CLI (`--page-slug`, `--id`, etc.). See `help lookup`. `nav open` uses `--nav-id` or `--nav-name` only.\n- **`read @root`** only works after `open` (uses the active session). The hint in peek output is for terminal users who already ran `open`.\n- **Do not invent flags** \u2014 there is no `--read`, `--then`, or chained peek+read syntax. One command per `cms_edit` call.\n\n## Troubleshooting\n\n| Problem | What to do |\n|---------|------------|\n| **401 / connection failed** | Reconnect: Claude \u2192 Settings \u2192 Integrations, or Grok `/mcps` \u2192 press `i`. Complete Contentful sign-in. If Grok says \u201CFailed to get tools after auth\u201D or `auth required`, check Vercel `[cms-edit:oauth] auth_failure`. `invalid_refresh_token` is a rotated/missing MCP refresh UUID (fixed in **3.6.1+** by not rotating). `missing_contentful_token` / `invalid_contentful_token` means the CMA cache is gone \u2014 **3.5.3+** returns `invalid_token` so Grok re-runs Contentful sign-in. If it still loops, delete that server\u2019s entry from `~/.grok/mcp_credentials.json` and press `i` again. Set `LOG_CMS_OAUTH=1` for connect success traces. |\n| **No cms_edit tool** | Confirm the integration is connected under Settings \u2192 Integrations |\n| **No active session** | Do not pass `--space` on hosted. Check `/api/health` \u2014 `redis` must be ok. MCP uses `_mcp` session automatically. Manual session: `[\"--session\", \"name\", \"open\", \"--page-slug\", \"/\"]`. Fallback: `[\"read\", \"<entry-id>\", \"--id\"]` |\n| **Wrong space** | Your Contentful account must have access to this deployment's space |\n| **Check version** | Call `cms_edit_version` or `curl /api/health` and read `cmsEdit` |\n\n## Articles\n\n**Recent articles** (by publication `date`, not last-updated):\n\n```json\n[\"list\", \"--type\", \"article\", \"--sort\", \"date\", \"-n\", \"10\"]\n```\n\nAlternative: `[\"search\", \"<keyword>\", \"--type\", \"article\"]` (also sorts by date).\n\n**Draft articles** \u2014 `peek` and `open` load drafts via CMA (not published-only). To discover drafts:\n\n```json\n[\"list\", \"--type\", \"article\", \"--sort\", \"date\"]\n[\"search\", \"<title>\", \"--type\", \"article\"]\n[\"list\", \"--type\", \"article\", \"--slug\", \"<slug>\"]\n```\n\nThen open: `[\"peek\", \"--article-slug\", \"<slug>\"]` or `[\"open\", \"--id\", \"<entry-id>\"]`. Use `--article-slug`, not `--page-slug`.\n\n**Published-only** is opt-in: `list --published` and `resolve --published` use the Delivery API index and omit never-published drafts.\n\nAfter upgrading cms-edit, run `[\"index\", \"sync\"]` once so the local index includes article `date` values (schema v4).\n\n**Index freshness on hosted MCP:** The catalog index is shared via Redis (gzipped SQLite blob). `index status` on a cold instance may show **not built** until `list`, `open`, or another index command runs \u2014 that is normal. Committed writes (`save`, `create page`, `asset upload`, \u2026) mark the index dirty; the next discovery command refreshes it. In-session `set` edits do not trigger a sync.\n\n## Command reference (cms_edit args)\n\n**Discovery:** `list --type <type>`, `search <query>`, `schema <contentType>`, `index sync`, `index status`\n\n**Reading:** `open --page-slug <slug>`, `open --id <id>`, `peek --page-slug <slug>`, `peek --id <id>`, `snapshot`, `read <ref>`, `read <ref> <field>`, `read <id> --id [field]`\n\n**Diagnostics:** `diagnose` \u2014 session store, userId, active session (after open/read issues)\n\n**Editing:** `set <ref> <field> <value>`, `rtf <ref> <field> --content \"<md>\"`, `diff`, `save`, `discard`\n\n**Creating:** `create from-json --json \u2026 --dry-run --strict`, `export from-json`, `apply from-json --json \u2026`, `create page` / `create article` for empty shells, `add` for tiny inserts\n\n**Other:** `health`, `version`, `guide [topic]`\n";
2
2
  //# sourceMappingURL=hosted-guide.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hosted-guide.d.ts","sourceRoot":"","sources":["../../src/generated/hosted-guide.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,qBAAqB,k6kBAAumkB,CAAC"}
1
+ {"version":3,"file":"hosted-guide.d.ts","sourceRoot":"","sources":["../../src/generated/hosted-guide.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,qBAAqB,27kBAAgokB,CAAC"}
@@ -1,3 +1,3 @@
1
1
  // Generated by scripts/generate-embedded-assets.mjs — do not edit manually.
2
- export const HOSTED_GUIDE_TEMPLATE = "# cms-edit MCP guide\n\nUse this guide when editing Contentful draft content through a hosted MCP connector (Claude Desktop Integrations + OAuth, or Grok Build).\n\n{{SITE_INTRO}}\n\n## Safety rules\n\ncms-edit **cannot and will not**:\n\n- Publish or unpublish entries\n- Archive or delete published entries\n\nAll `save` operations create **drafts only**. A human must review and publish in Contentful.\n\n**Assets:** cms-edit **can upload** draft assets via **staged upload** (hosted) or `--url` (public HTTPS last resort). **`--base64` is not supported on hosted MCP.** Always **search and reuse existing assets first** (`index sync`, then `asset search` or `list --type media`). Use `--if-exists-by-filename` when uploading to avoid duplicates.\n\n## MCP tools\n\n| Tool | Purpose |\n|------|---------|\n| `cms_edit` | Run any cms-edit command via an `args` array |\n| `cms_edit_request_staged_upload` | Presigned URL for binaries. Raster/video: then `cms_edit_media` prepare. SVG/PDF/Lottie: then `asset upload --staged` |\n| `cms_edit_version` | Check the installed cms-edit version (no session required) |\n| `cms_edit_help` | Guide topics (`overview`, `workflow`, `commands`, `troubleshooting`, `hosted`) **or** playbooks (`create-from-json`, `fields-component`, …) |\n\n## Customer editor pack (read first)\n\nWhen a site editor pack is deployed, these MCP resources provide **site-specific** context. Read them before creating or editing content:\n\n| Resource | Purpose |\n|----------|---------|\n| `cms-edit://customer/index` | List of all customer resources |\n| `cms-edit://customer/tasks-index` | Intent → task playbook map (filtered for this site) |\n| `cms-edit://customer/capabilities` | Machine-readable list of what this site supports |\n| `cms-edit://customer/checklist` | First-session onboarding steps |\n| `cms-edit://customer/overview` | Site name, space, staging URL, handoff rules |\n| `cms-edit://customer/routing` | Article/tag URL rules for this site |\n| `cms-edit://customer/production-site` | Production URL and how to fetch page markdown (do not guess `.md` URLs) |\n| `cms-edit://customer/components-index` | Component types and when to use each |\n| `cms-edit://customer/collections-index` | Collection types |\n| `cms-edit://customer/brand` | Brand voice and terminology (if configured) |\n| `cms-edit://customer/articles` | Article/publication playbook (if configured) |\n| `cms-edit://customer/defaults` | Featured image rules and article types (if configured) |\n| `cms-edit://customer/site-facts` | Optional canonical business facts (if configured) |\n| `cms-edit://customer/task-*` | Capability playbooks (e.g. `task-media-reuse-and-upload`) |\n\n**Workflow:** read `tasks-index` → `capabilities` → `checklist` → `overview` → `routing` → `production-site` → pick a `task-*` playbook for your goal → `components-index` → then use `cms_edit` commands.\n\n## MCP prompts\n\n| Prompt | Purpose |\n|--------|---------|\n| `cms_edit_verify` | Check version and connectivity |\n| `cms_edit_explore` | List site content and open a page |\n| `cms_edit_edit_page` | Edit a page through diff → save |\n| `cms_edit_edit_article` | Edit an article through diff → save |\n| `cms_edit_index_sync` | Build/refresh local index (assets, media, templates, taxonomy) |\n| `cms_edit_create_page` | Create a page with template selection |\n| `cms_edit_create_article` | Create an article (requires article type; featured image rules) |\n| `cms_edit_create_from_doc` | Create a page from a freeform document (infer → confirm → dry-run) |\n| `cms_edit_create_article_from_doc` | Create an article from a freeform document |\n| `cms_edit_taxonomy` | Import taxonomy entries (`create … --if-not-exists`) |\n| `cms_edit_bulk_seo` | Update SEO meta descriptions (read `task-seo-descriptions`) |\n| `cms_edit_bulk_find_replace` | Bulk find-replace in rich text across entries |\n| `cms_edit_bulk_article_tags` | Update tags on one or more articles |\n| `cms_edit_import_authors` | Create person/author entries from a list or sheet |\n| `cms_edit_media` | Find existing assets or upload new ones (reuse-first) |\n\nRead the full guide anytime via the `cms-edit://guide` resource or `cms_edit_help`.\n\n## End-to-end QA prompt\n\nFor release QA after cms-edit or editor-pack upgrades, use a **copy-paste Claude Desktop test script** (phases: connector → customer resources → discovery → session → schema → media → dry-run writes → negatives). See [e2e-test-prompt.md](./e2e-test-prompt.md) to author one per customer site. Example: Brightline `docs/cms-edit/MCP-E2E-TEST.md`.\n\n## Core workflow\n\nStandard editing flow: **open → snapshot → read → set/rtf → diff → save**\n\nEach step is a separate `cms_edit` call. Sessions persist in Redis between calls (hosted) or on disk (local). MCP auto-uses session `_mcp`.\n\n### 1. Open a page\n\n```\n[\"open\", \"--page-slug\", \"/\"]\n[\"open\", \"--page-slug\", \"/pricing\"]\n[\"open\", \"--id\", \"<entry-id>\"]\n[\"open\", \"--include-chrome\", \"--page-slug\", \"/pricing\"]\n```\n\nDefault `open` / `peek` fetch the page or article tree and the template entry, but **not** template menu/footer/navigation. Edit nav with `[\"nav\", \"open\", \"--nav-id\", \"<id>\"]` or `--nav-name` (not `--id`). Use `--include-chrome` only when you need those trees. Template `menu` is site-wide; `page.menu` overrides one page. Nested nav items are often shared — ask global vs page-specific before `set`.\n\n**Homepage (all SE Studio sites):** Contentful stores the marketing homepage slug as `index`. The live site URL is `/`. In cms-edit, `--page-slug /` and `--page-slug /index` are equivalent (both resolve to `index`). Read `cms-edit://customer/routing` for site-specific exceptions (e.g. Pedestal uses `home`).\n\n### 2. Snapshot — see the content tree\n\n```\n[\"snapshot\"]\n```\n\nOutput shows `@c0`, `@c1`, … refs for each component. Use these refs in `read`, `set`, and `rtf`.\n\n### 3. Read fields\n\n```\n[\"read\", \"@c1\"]\n[\"read\", \"@c1\", \"heading\"]\n```\n\n### 4. Edit fields\n\n```\n[\"set\", \"@c1\", \"heading\", \"New heading text\"]\n[\"rtf\", \"@c1\", \"body\", \"--content\", \"# Title\\n\\nParagraph text.\"]\n```\n\nFor rich text, prefer `--content` with inline markdown. Use `--base64` when special characters cause issues.\n\n### 5. Review and save\n\n```\n[\"diff\"]\n[\"save\"]\n```\n\nAlways run `diff` before `save`. To abandon changes: `[\"discard\"]`.\n\n## When to use Contentful UI\n\n| Task | Where |\n|------|-------|\n| Edit copy, add sections, create pages/articles | **Agent + cms-edit** (drafts) |\n| Upload new images and files | **Agent + cms-edit** — search/reuse first; raster/video via `cms_edit_media` import |\n| Publish approved drafts | **Contentful UI** |\n| Edit copy directly in Contentful | **Avoid** — use cms-edit instead |\n\ncms-edit saves **drafts only**. A colleague must **publish in Contentful** for changes to go live.\n\n## Asset upload (hosted MCP)\n\nHosted connectors have **no server filesystem** and **reject `asset upload --base64`**. Raster/video: staged intake then **media pipeline**. SVG/PDF/Lottie: staged then `asset upload --staged`.\n\n| Source | When | Example |\n|--------|------|---------|\n| **Staged + pipeline** | jpeg/png/webp/gif/mp4 | `cms_edit_media` prepare `stagedUploadIds` → import (required for video) |\n| **`--url`** | Pipeline-ready **images** only, or SVG/PDF | `[\"asset\", \"upload\", \"--url\", \"https://….blob.vercel-storage.com/ready.jpg\"]` |\n| **Staged consume** | SVG/PDF/Lottie | `asset upload --staged` |\n\n**After upload:** note the returned asset `id`, attach to the entry (`asset set @root media <id>` for person portraits), then `diff` → `save`. Upload alone does not update entries.\n\n**PNG** when transparency matters.\n\nFull workflow: `cms-edit://customer/task-media-reuse-and-upload`.\n\n### Common pitfalls (agents)\n\n| Issue | Do this |\n|-------|---------|\n| Singular Media on a component (`visual`) | `set @cN visual <mediaId> --link` — **not** `add … --target visual` |\n| Shared Drive folder has extra files | Import with `sourceNames` or `onlyWithOverrides` after catalog |\n| Prepare says no media / empty folder | Share folder **Viewer** with `media-pipeline-reader@se-media-pipeline.iam.gserviceaccount.com`, then re-prepare once |\n| MCP tool times out ~180s | `read` / `diff` / re-`catalog` before retrying the same write — server may already have saved |\n| `preview urls` has no Live Preview URL | Use staging + classic entry editor; row includes `unavailableReason` when CMA preview fails |\n| Reuse-first import matches old fileNames | dry-run first; set `overrides[].fileName` for unique names when duplication is required |\n\n### Staged upload\n\nKeeps binary **out of MCP tool args**. The agent uploads via `curl`, then passes only a short `uploadId`.\n\n1. `cms_edit_request_staged_upload` with `fileName`, `mimeType`, optional `byteLength`, optional `title`\n2. Run the returned `curlCommand` in a shell (POST multipart to `uploadUrl`)\n3. Follow `consumeArgs`: **media prepare** (`cms_edit_media` / `media prepare --staged-ids`) for jpeg/png/webp/gif/mp4; **asset upload --staged** for SVG/PDF/Lottie\n4. For pipeline jobs: wait → catalog → **import** (required for video). Then link and save\n\nHosted limit: **4 MB** per staged file (Vercel body limit). Rate limit: **1000 upload URLs per user per hour**. TTL: **60 minutes**. Raster/video staged files are intake (GET by the pipeline); they are not consumed into CMA.\n\n## Featured images (articles)\n\nRead `cms-edit://customer/defaults` when present. Otherwise:\n\n1. **Custom** — set `featuredImage` or `featuredImageAssetFilename` on the article when the editor provides a specific image\n2. **Article type** — if omitted, cms-edit inherits the linked **articleType** `featuredImage`\n3. **Site default** — from `editor.defaults` in the site config (also in `defaults.json` in the editor pack)\n\nRun `[\"index\", \"sync\"]` before resolving assets by filename.\n\n## JSON payloads (filesystem vs no filesystem)\n\nHosted MCP cannot see your disk. Same commands either way:\n\n- **Filesystem client:** write `page.json`, then `cms_edit [\"create\", \"from-json\", \"--json-base64\", \"<base64 of file>\", \"--dry-run\", \"--strict\"]`\n- **No filesystem (Desktop):** put the JSON in `--json` on the tool call\n\nNever use a local `cms-edit` binary, `--file`, or stdin for content.\n\n## Create content\n\n### Create a page\n\n```\n[\"schema\", \"component\"]\n[\"help\", \"create-from-json\"]\n[\"create\", \"from-json\", \"--json\", \"{...}\", \"--dry-run\", \"--strict\"]\n[\"create\", \"from-json\", \"--json\", \"{...}\"]\n```\n\nUse `\"components\"` (not `\"content\"`) and nested `\"type\"` (not `\"componentType\"`). Peek a live entry or `schema <ct>` before authoring field ids.\n\nEmpty `components` fails validation. Incremental `add`/`set` is for tiny patches only.\n\n### Create an article\n\nEvery article requires an **articleType**:\n\n```\n[\"index\", \"sync\"]\n[\"list\", \"--type\", \"articleType\"]\n[\"create\", \"article\", \"--slug\", \"resources/blog/my-post\", \"--title\", \"My Post\", \"--article-type-slug\", \"resources/blog\"]\n```\n\nOr `create from-json` with `\"type\": \"article\"` and `articleTypeSlug` / `articleTypeId`.\n\nUse MCP prompt `cms_edit_create_article` for guided creation.\n\n## Common sequences\n\n### List all pages\n\n```\n[\"list\", \"--type\", \"page\"]\n```\n\n### Refresh local index (assets, media, templates, taxonomy)\n\nRun before asset filename search, media-by-filename list, or fast template/taxonomy lookup:\n\n```\n[\"index\", \"sync\"]\n[\"index\", \"status\"]\n[\"list\", \"--type\", \"template\"]\n```\n\nAsset search and media-by-filename list **require** the index. Template/taxonomy list prefers the index when built.\n\n### Explore the homepage\n\n```\n[\"open\", \"--page-slug\", \"/\"]\n[\"snapshot\"]\n[\"read\", \"@c1\"]\n```\n\n### Edit copy on a page\n\n```\n[\"open\", \"--page-slug\", \"{{EXAMPLE_SLUG}}\"]\n[\"snapshot\"]\n[\"read\", \"@c1\", \"heading\"]\n[\"set\", \"@c1\", \"heading\", \"Updated heading\"]\n[\"diff\"]\n[\"save\"]\n```\n\n## Field discovery\n\n```\n[\"schema\", \"component\"]\n[\"list\", \"--type\", \"page\"]\n[\"search\", \"pricing\"]\n```\n\n## Grok Build setup\n\nGrok connects to the same hosted MCP URL as Claude. Add to `~/.grok/config.toml`:\n\n```toml\n[mcp_servers.cms-edit-brightline]\nurl = \"https://brightline.content.se.studio/api/mcp\"\nenabled = true\n```\n\nReplace the server name and host with your site's deployment. The URL **must** end with `/api/mcp` (not the site root).\n\n**Connect:**\n\n1. In Grok, run `/mcps` and select your cms-edit server\n2. Press **`i`** to initiate OAuth (not just enable the toggle)\n3. Sign in with Contentful when the browser opens\n4. Verify with `grok mcp doctor <server-name>` or call `cms_edit_version`\n\nOAuth is two steps: Grok obtains an MCP token, then you sign in with Contentful to link your account. If the browser flashes and closes immediately, reconnect after upgrading cms-edit (loopback redirect handling).\n\n## Hosted connector rules\n\n{{HOSTED_RULES}}\n\n## Session tips\n\n- After `open`, subsequent MCP commands share session `_mcp` automatically (stored in Redis on hosted).\n- Use `--session <name>` only when juggling multiple pages — pass it **before** the subcommand on every call: `[\"--session\", \"my-work\", \"open\", \"--page-slug\", \"/\"]`.\n- Snapshot JSON includes `sessionName` and `sessionKey` for debugging.\n- If you see \"No active session\", check `/api/health` (Redis must be ok), then run `open` again. Set `LOG_CMS_SESSION=1` on Vercel and search runtime logs for `[cms-edit:session]` (`redis save ok` / `redis load hit` / `requireSession miss`).\n\n### peek vs open vs read\n\n| Goal | MCP args |\n|------|----------|\n| Edit workflow (session) | `[\"open\", \"--page-slug\", \"/\"]` then `[\"read\", \"@c0\"]` |\n| Preview tree + fields, no session | `[\"peek\", \"--page-slug\", \"/pricing\"]` or `[\"peek\", \"--id\", \"<entry-id>\"]` (JSON includes `rootFields`) |\n| Read fields by entry ID, no session | `[\"read\", \"<entry-id>\", \"--id\"]` or `[\"read\", \"<entry-id>\", \"--id\", \"heading\"]` |\n| Debug session persistence | `[\"diagnose\"]` — reports store, userId, active session key |\n\n- **`peek` is one-shot** — prints tree and **root field values** (`rootFields` in JSON). No session is created; do not chain `read @ref` afterward.\n- **`open` / `nav open` persist** the session. `preview urls /slug` follows that slug even if leftover `_mcp` is another page (JSON may include `sessionRootIgnored`).\n- **Use flat lookup flags** for `open` and `peek` — same as CLI (`--page-slug`, `--id`, etc.). See `help lookup`. `nav open` uses `--nav-id` or `--nav-name` only.\n- **`read @root`** only works after `open` (uses the active session). The hint in peek output is for terminal users who already ran `open`.\n- **Do not invent flags** — there is no `--read`, `--then`, or chained peek+read syntax. One command per `cms_edit` call.\n\n## Troubleshooting\n\n| Problem | What to do |\n|---------|------------|\n| **401 / connection failed** | Reconnect: Claude → Settings → Integrations, or Grok `/mcps` → press `i`. Complete Contentful sign-in. If Grok says “Failed to get tools after auth” or `auth required`, check Vercel `[cms-edit:oauth] auth_failure`. `invalid_refresh_token` is a rotated/missing MCP refresh UUID (fixed in **3.6.1+** by not rotating). `missing_contentful_token` / `invalid_contentful_token` means the CMA cache is gone — **3.5.3+** returns `invalid_token` so Grok re-runs Contentful sign-in. If it still loops, delete that server’s entry from `~/.grok/mcp_credentials.json` and press `i` again. Set `LOG_CMS_OAUTH=1` for connect success traces. |\n| **No cms_edit tool** | Confirm the integration is connected under Settings → Integrations |\n| **No active session** | Do not pass `--space` on hosted. Check `/api/health` — `redis` must be ok. MCP uses `_mcp` session automatically. Manual session: `[\"--session\", \"name\", \"open\", \"--page-slug\", \"/\"]`. Fallback: `[\"read\", \"<entry-id>\", \"--id\"]` |\n| **Wrong space** | Your Contentful account must have access to this deployment's space |\n| **Check version** | Call `cms_edit_version` or `curl /api/health` and read `cmsEdit` |\n\n## Articles\n\n**Recent articles** (by publication `date`, not last-updated):\n\n```json\n[\"list\", \"--type\", \"article\", \"--sort\", \"date\", \"-n\", \"10\"]\n```\n\nAlternative: `[\"search\", \"<keyword>\", \"--type\", \"article\"]` (also sorts by date).\n\n**Draft articles** — `peek` and `open` load drafts via CMA (not published-only). To discover drafts:\n\n```json\n[\"list\", \"--type\", \"article\", \"--sort\", \"date\"]\n[\"search\", \"<title>\", \"--type\", \"article\"]\n[\"list\", \"--type\", \"article\", \"--slug\", \"<slug>\"]\n```\n\nThen open: `[\"peek\", \"--article-slug\", \"<slug>\"]` or `[\"open\", \"--id\", \"<entry-id>\"]`. Use `--article-slug`, not `--page-slug`.\n\n**Published-only** is opt-in: `list --published` and `resolve --published` use the Delivery API index and omit never-published drafts.\n\nAfter upgrading cms-edit, run `[\"index\", \"sync\"]` once so the local index includes article `date` values (schema v4).\n\n**Index freshness on hosted MCP:** The catalog index is shared via Redis (gzipped SQLite blob). `index status` on a cold instance may show **not built** until `list`, `open`, or another index command runs — that is normal. Committed writes (`save`, `create page`, `asset upload`, …) mark the index dirty; the next discovery command refreshes it. In-session `set` edits do not trigger a sync.\n\n## Command reference (cms_edit args)\n\n**Discovery:** `list --type <type>`, `search <query>`, `schema <contentType>`, `index sync`, `index status`\n\n**Reading:** `open --page-slug <slug>`, `open --id <id>`, `peek --page-slug <slug>`, `peek --id <id>`, `snapshot`, `read <ref>`, `read <ref> <field>`, `read <id> --id [field]`\n\n**Diagnostics:** `diagnose` — session store, userId, active session (after open/read issues)\n\n**Editing:** `set <ref> <field> <value>`, `rtf <ref> <field> --content \"<md>\"`, `diff`, `save`, `discard`\n\n**Creating:** `create from-json --json … --dry-run --strict`, `export from-json`, `apply from-json --json …`, `create page` / `create article` for empty shells, `add` for tiny inserts\n\n**Other:** `health`, `version`, `guide [topic]`\n";
2
+ export const HOSTED_GUIDE_TEMPLATE = "# cms-edit MCP guide\n\nUse this guide when editing Contentful draft content through a hosted MCP connector (Claude Desktop Integrations + OAuth, or Grok Build).\n\n{{SITE_INTRO}}\n\n## Safety rules\n\ncms-edit **cannot and will not**:\n\n- Publish or unpublish entries\n- Archive or delete published entries\n\nAll `save` operations create **drafts only**. A human must review and publish in Contentful.\n\n**Assets:** cms-edit **can upload** draft assets via **staged upload** (hosted) or `--url` (public HTTPS last resort). **`--base64` is not supported on hosted MCP.** Always **search and reuse existing assets first** (`index sync`, then `asset search` or `list --type media`). Use `--if-exists-by-filename` when uploading to avoid duplicates.\n\n## MCP tools\n\n| Tool | Purpose |\n|------|---------|\n| `cms_edit` | Run any cms-edit command via an `args` array |\n| `cms_edit_request_staged_upload` | Presigned URL for binaries. Raster/video: then `cms_edit_media` prepare. SVG/PDF/Lottie: then `asset upload --staged` |\n| `cms_edit_version` | Check the installed cms-edit version (no session required) |\n| `cms_edit_help` | Guide topics (`overview`, `workflow`, `commands`, `troubleshooting`, `hosted`) **or** playbooks (`create-from-json`, `fields-html-component`, `fields-component`, …) |\n\n## Customer editor pack (read first)\n\nWhen a site editor pack is deployed, these MCP resources provide **site-specific** context. Read them before creating or editing content:\n\n| Resource | Purpose |\n|----------|---------|\n| `cms-edit://customer/index` | List of all customer resources |\n| `cms-edit://customer/tasks-index` | Intent → task playbook map (filtered for this site) |\n| `cms-edit://customer/capabilities` | Machine-readable list of what this site supports |\n| `cms-edit://customer/checklist` | First-session onboarding steps |\n| `cms-edit://customer/overview` | Site name, space, staging URL, handoff rules |\n| `cms-edit://customer/routing` | Article/tag URL rules for this site |\n| `cms-edit://customer/production-site` | Production URL and how to fetch page markdown (do not guess `.md` URLs) |\n| `cms-edit://customer/components-index` | Component types and when to use each |\n| `cms-edit://customer/collections-index` | Collection types |\n| `cms-edit://customer/brand` | Brand voice and terminology (if configured) |\n| `cms-edit://customer/articles` | Article/publication playbook (if configured) |\n| `cms-edit://customer/defaults` | Featured image rules and article types (if configured) |\n| `cms-edit://customer/site-facts` | Optional canonical business facts (if configured) |\n| `cms-edit://customer/task-*` | Capability playbooks (e.g. `task-media-reuse-and-upload`) |\n\n**Workflow:** read `tasks-index` → `capabilities` → `checklist` → `overview` → `routing` → `production-site` → pick a `task-*` playbook for your goal → `components-index` → then use `cms_edit` commands.\n\n## MCP prompts\n\n| Prompt | Purpose |\n|--------|---------|\n| `cms_edit_verify` | Check version and connectivity |\n| `cms_edit_explore` | List site content and open a page |\n| `cms_edit_edit_page` | Edit a page through diff → save |\n| `cms_edit_edit_article` | Edit an article through diff → save |\n| `cms_edit_index_sync` | Build/refresh local index (assets, media, templates, taxonomy) |\n| `cms_edit_create_page` | Create a page with template selection |\n| `cms_edit_create_article` | Create an article (requires article type; featured image rules) |\n| `cms_edit_create_from_doc` | Create a page from a freeform document (infer → confirm → dry-run) |\n| `cms_edit_create_article_from_doc` | Create an article from a freeform document |\n| `cms_edit_taxonomy` | Import taxonomy entries (`create … --if-not-exists`) |\n| `cms_edit_bulk_seo` | Update SEO meta descriptions (read `task-seo-descriptions`) |\n| `cms_edit_bulk_find_replace` | Bulk find-replace in rich text across entries |\n| `cms_edit_bulk_article_tags` | Update tags on one or more articles |\n| `cms_edit_import_authors` | Create person/author entries from a list or sheet |\n| `cms_edit_media` | Find existing assets or upload new ones (reuse-first) |\n\nRead the full guide anytime via the `cms-edit://guide` resource or `cms_edit_help`.\n\n## End-to-end QA prompt\n\nFor release QA after cms-edit or editor-pack upgrades, use a **copy-paste Claude Desktop test script** (phases: connector → customer resources → discovery → session → schema → media → dry-run writes → negatives). See [e2e-test-prompt.md](./e2e-test-prompt.md) to author one per customer site. Example: Brightline `docs/cms-edit/MCP-E2E-TEST.md`.\n\n## Core workflow\n\nStandard editing flow: **open → snapshot → read → set/rtf → diff → save**\n\nEach step is a separate `cms_edit` call. Sessions persist in Redis between calls (hosted) or on disk (local). MCP auto-uses session `_mcp`.\n\n### 1. Open a page\n\n```\n[\"open\", \"--page-slug\", \"/\"]\n[\"open\", \"--page-slug\", \"/pricing\"]\n[\"open\", \"--id\", \"<entry-id>\"]\n[\"open\", \"--include-chrome\", \"--page-slug\", \"/pricing\"]\n```\n\nDefault `open` / `peek` fetch the page or article tree and the template entry, but **not** template menu/footer/navigation. Edit nav with `[\"nav\", \"open\", \"--nav-id\", \"<id>\"]` or `--nav-name` (not `--id`). Use `--include-chrome` only when you need those trees. Template `menu` is site-wide; `page.menu` overrides one page. Nested nav items are often shared — ask global vs page-specific before `set`.\n\n**Homepage (all SE Studio sites):** Contentful stores the marketing homepage slug as `index`. The live site URL is `/`. In cms-edit, `--page-slug /` and `--page-slug /index` are equivalent (both resolve to `index`). Read `cms-edit://customer/routing` for site-specific exceptions (e.g. Pedestal uses `home`).\n\n### 2. Snapshot — see the content tree\n\n```\n[\"snapshot\"]\n```\n\nOutput shows `@c0`, `@c1`, … refs for each component. Use these refs in `read`, `set`, and `rtf`.\n\n### 3. Read fields\n\n```\n[\"read\", \"@c1\"]\n[\"read\", \"@c1\", \"heading\"]\n```\n\n### 4. Edit fields\n\n```\n[\"set\", \"@c1\", \"heading\", \"New heading text\"]\n[\"rtf\", \"@c1\", \"body\", \"--content\", \"# Title\\n\\nParagraph text.\"]\n```\n\nFor rich text, prefer `--content` with inline markdown. Use `--base64` when special characters cause issues.\n\n### 5. Review and save\n\n```\n[\"diff\"]\n[\"save\"]\n```\n\nAlways run `diff` before `save`. To abandon changes: `[\"discard\"]`.\n\n## When to use Contentful UI\n\n| Task | Where |\n|------|-------|\n| Edit copy, add sections, create pages/articles | **Agent + cms-edit** (drafts) |\n| Upload new images and files | **Agent + cms-edit** — search/reuse first; raster/video via `cms_edit_media` import |\n| Publish approved drafts | **Contentful UI** |\n| Edit copy directly in Contentful | **Avoid** — use cms-edit instead |\n\ncms-edit saves **drafts only**. A colleague must **publish in Contentful** for changes to go live.\n\n## Asset upload (hosted MCP)\n\nHosted connectors have **no server filesystem** and **reject `asset upload --base64`**. Raster/video: staged intake then **media pipeline**. SVG/PDF/Lottie: staged then `asset upload --staged`.\n\n| Source | When | Example |\n|--------|------|---------|\n| **Staged + pipeline** | jpeg/png/webp/gif/mp4 | `cms_edit_media` prepare `stagedUploadIds` → import (required for video) |\n| **`--url`** | Pipeline-ready **images** only, or SVG/PDF | `[\"asset\", \"upload\", \"--url\", \"https://….blob.vercel-storage.com/ready.jpg\"]` |\n| **Staged consume** | SVG/PDF/Lottie | `asset upload --staged` |\n\n**After upload:** note the returned asset `id`, attach to the entry (`asset set @root media <id>` for person portraits), then `diff` → `save`. Upload alone does not update entries.\n\n**PNG** when transparency matters.\n\nFull workflow: `cms-edit://customer/task-media-reuse-and-upload`.\n\n### Common pitfalls (agents)\n\n| Issue | Do this |\n|-------|---------|\n| Singular Media on a component (`visual`) | `set @cN visual <mediaId> --link` — **not** `add … --target visual` |\n| Shared Drive folder has extra files | Import with `sourceNames` or `onlyWithOverrides` after catalog |\n| Prepare says no media / empty folder | Share folder **Viewer** with `media-pipeline-reader@se-media-pipeline.iam.gserviceaccount.com`, then re-prepare once |\n| MCP tool times out ~180s | `read` / `diff` / re-`catalog` before retrying the same write — server may already have saved |\n| `preview urls` has no Live Preview URL | Use staging + classic entry editor; row includes `unavailableReason` when CMA preview fails |\n| Reuse-first import matches old fileNames | dry-run first; set `overrides[].fileName` for unique names when duplication is required |\n\n### Staged upload\n\nKeeps binary **out of MCP tool args**. The agent uploads via `curl`, then passes only a short `uploadId`.\n\n1. `cms_edit_request_staged_upload` with `fileName`, `mimeType`, optional `byteLength`, optional `title`\n2. Run the returned `curlCommand` in a shell (POST multipart to `uploadUrl`)\n3. Follow `consumeArgs`: **media prepare** (`cms_edit_media` / `media prepare --staged-ids`) for jpeg/png/webp/gif/mp4; **asset upload --staged** for SVG/PDF/Lottie\n4. For pipeline jobs: wait → catalog → **import** (required for video). Then link and save\n\nHosted limit: **4 MB** per staged file (Vercel body limit). Rate limit: **1000 upload URLs per user per hour**. TTL: **60 minutes**. Raster/video staged files are intake (GET by the pipeline); they are not consumed into CMA.\n\n## Featured images (articles)\n\nRead `cms-edit://customer/defaults` when present. Otherwise:\n\n1. **Custom** — set `featuredImage` or `featuredImageAssetFilename` on the article when the editor provides a specific image\n2. **Article type** — if omitted, cms-edit inherits the linked **articleType** `featuredImage`\n3. **Site default** — from `editor.defaults` in the site config (also in `defaults.json` in the editor pack)\n\nRun `[\"index\", \"sync\"]` before resolving assets by filename.\n\n## JSON payloads (filesystem vs no filesystem)\n\nHosted MCP cannot see your disk. Same commands either way:\n\n- **Filesystem client:** write `page.json`, then `cms_edit [\"create\", \"from-json\", \"--json-base64\", \"<base64 of file>\", \"--dry-run\", \"--strict\"]`\n- **No filesystem (Desktop):** put the JSON in `--json` on the tool call\n\nNever use a local `cms-edit` binary, `--file`, or stdin for content.\n\n## Create content\n\n### Create a page\n\n```\n[\"schema\", \"component\"]\n[\"help\", \"create-from-json\"]\n[\"create\", \"from-json\", \"--json\", \"{...}\", \"--dry-run\", \"--strict\"]\n[\"create\", \"from-json\", \"--json\", \"{...}\"]\n```\n\nUse `\"components\"` (not `\"content\"`) and nested `\"type\"` (not `\"componentType\"`). Peek a live entry or `schema <ct>` before authoring field ids.\n\nEmpty `components` fails validation. Incremental `add`/`set` is for tiny patches only.\n\n### Create an article\n\nEvery article requires an **articleType**:\n\n```\n[\"index\", \"sync\"]\n[\"list\", \"--type\", \"articleType\"]\n[\"create\", \"article\", \"--slug\", \"resources/blog/my-post\", \"--title\", \"My Post\", \"--article-type-slug\", \"resources/blog\"]\n```\n\nOr `create from-json` with `\"type\": \"article\"` and `articleTypeSlug` / `articleTypeId`.\n\nUse MCP prompt `cms_edit_create_article` for guided creation.\n\n## Common sequences\n\n### List all pages\n\n```\n[\"list\", \"--type\", \"page\"]\n```\n\n### Refresh local index (assets, media, templates, taxonomy)\n\nRun before asset filename search, media-by-filename list, or fast template/taxonomy lookup:\n\n```\n[\"index\", \"sync\"]\n[\"index\", \"status\"]\n[\"list\", \"--type\", \"template\"]\n```\n\nAsset search and media-by-filename list **require** the index. Template/taxonomy list prefers the index when built.\n\n### Explore the homepage\n\n```\n[\"open\", \"--page-slug\", \"/\"]\n[\"snapshot\"]\n[\"read\", \"@c1\"]\n```\n\n### Edit copy on a page\n\n```\n[\"open\", \"--page-slug\", \"{{EXAMPLE_SLUG}}\"]\n[\"snapshot\"]\n[\"read\", \"@c1\", \"heading\"]\n[\"set\", \"@c1\", \"heading\", \"Updated heading\"]\n[\"diff\"]\n[\"save\"]\n```\n\n## Field discovery\n\n```\n[\"schema\", \"component\"]\n[\"list\", \"--type\", \"page\"]\n[\"search\", \"pricing\"]\n```\n\n## Grok Build setup\n\nGrok connects to the same hosted MCP URL as Claude. Add to `~/.grok/config.toml`:\n\n```toml\n[mcp_servers.cms-edit-brightline]\nurl = \"https://brightline.content.se.studio/api/mcp\"\nenabled = true\n```\n\nReplace the server name and host with your site's deployment. The URL **must** end with `/api/mcp` (not the site root).\n\n**Connect:**\n\n1. In Grok, run `/mcps` and select your cms-edit server\n2. Press **`i`** to initiate OAuth (not just enable the toggle)\n3. Sign in with Contentful when the browser opens\n4. Verify with `grok mcp doctor <server-name>` or call `cms_edit_version`\n\nOAuth is two steps: Grok obtains an MCP token, then you sign in with Contentful to link your account. If the browser flashes and closes immediately, reconnect after upgrading cms-edit (loopback redirect handling).\n\n## Hosted connector rules\n\n{{HOSTED_RULES}}\n\n## Session tips\n\n- After `open`, subsequent MCP commands share session `_mcp` automatically (stored in Redis on hosted).\n- Use `--session <name>` only when juggling multiple pages — pass it **before** the subcommand on every call: `[\"--session\", \"my-work\", \"open\", \"--page-slug\", \"/\"]`.\n- Snapshot JSON includes `sessionName` and `sessionKey` for debugging.\n- If you see \"No active session\", check `/api/health` (Redis must be ok), then run `open` again. Set `LOG_CMS_SESSION=1` on Vercel and search runtime logs for `[cms-edit:session]` (`redis save ok` / `redis load hit` / `requireSession miss`).\n\n### peek vs open vs read\n\n| Goal | MCP args |\n|------|----------|\n| Edit workflow (session) | `[\"open\", \"--page-slug\", \"/\"]` then `[\"read\", \"@c0\"]` |\n| Preview tree + fields, no session | `[\"peek\", \"--page-slug\", \"/pricing\"]` or `[\"peek\", \"--id\", \"<entry-id>\"]` (JSON includes `rootFields`) |\n| Read fields by entry ID, no session | `[\"read\", \"<entry-id>\", \"--id\"]` or `[\"read\", \"<entry-id>\", \"--id\", \"heading\"]` |\n| Debug session persistence | `[\"diagnose\"]` — reports store, userId, active session key |\n\n- **`peek` is one-shot** — prints tree and **root field values** (`rootFields` in JSON). No session is created; do not chain `read @ref` afterward.\n- **`open` / `nav open` persist** the session. `preview urls /slug` follows that slug even if leftover `_mcp` is another page (JSON may include `sessionRootIgnored`).\n- **Use flat lookup flags** for `open` and `peek` — same as CLI (`--page-slug`, `--id`, etc.). See `help lookup`. `nav open` uses `--nav-id` or `--nav-name` only.\n- **`read @root`** only works after `open` (uses the active session). The hint in peek output is for terminal users who already ran `open`.\n- **Do not invent flags** — there is no `--read`, `--then`, or chained peek+read syntax. One command per `cms_edit` call.\n\n## Troubleshooting\n\n| Problem | What to do |\n|---------|------------|\n| **401 / connection failed** | Reconnect: Claude → Settings → Integrations, or Grok `/mcps` → press `i`. Complete Contentful sign-in. If Grok says “Failed to get tools after auth” or `auth required`, check Vercel `[cms-edit:oauth] auth_failure`. `invalid_refresh_token` is a rotated/missing MCP refresh UUID (fixed in **3.6.1+** by not rotating). `missing_contentful_token` / `invalid_contentful_token` means the CMA cache is gone — **3.5.3+** returns `invalid_token` so Grok re-runs Contentful sign-in. If it still loops, delete that server’s entry from `~/.grok/mcp_credentials.json` and press `i` again. Set `LOG_CMS_OAUTH=1` for connect success traces. |\n| **No cms_edit tool** | Confirm the integration is connected under Settings → Integrations |\n| **No active session** | Do not pass `--space` on hosted. Check `/api/health` — `redis` must be ok. MCP uses `_mcp` session automatically. Manual session: `[\"--session\", \"name\", \"open\", \"--page-slug\", \"/\"]`. Fallback: `[\"read\", \"<entry-id>\", \"--id\"]` |\n| **Wrong space** | Your Contentful account must have access to this deployment's space |\n| **Check version** | Call `cms_edit_version` or `curl /api/health` and read `cmsEdit` |\n\n## Articles\n\n**Recent articles** (by publication `date`, not last-updated):\n\n```json\n[\"list\", \"--type\", \"article\", \"--sort\", \"date\", \"-n\", \"10\"]\n```\n\nAlternative: `[\"search\", \"<keyword>\", \"--type\", \"article\"]` (also sorts by date).\n\n**Draft articles** — `peek` and `open` load drafts via CMA (not published-only). To discover drafts:\n\n```json\n[\"list\", \"--type\", \"article\", \"--sort\", \"date\"]\n[\"search\", \"<title>\", \"--type\", \"article\"]\n[\"list\", \"--type\", \"article\", \"--slug\", \"<slug>\"]\n```\n\nThen open: `[\"peek\", \"--article-slug\", \"<slug>\"]` or `[\"open\", \"--id\", \"<entry-id>\"]`. Use `--article-slug`, not `--page-slug`.\n\n**Published-only** is opt-in: `list --published` and `resolve --published` use the Delivery API index and omit never-published drafts.\n\nAfter upgrading cms-edit, run `[\"index\", \"sync\"]` once so the local index includes article `date` values (schema v4).\n\n**Index freshness on hosted MCP:** The catalog index is shared via Redis (gzipped SQLite blob). `index status` on a cold instance may show **not built** until `list`, `open`, or another index command runs — that is normal. Committed writes (`save`, `create page`, `asset upload`, …) mark the index dirty; the next discovery command refreshes it. In-session `set` edits do not trigger a sync.\n\n## Command reference (cms_edit args)\n\n**Discovery:** `list --type <type>`, `search <query>`, `schema <contentType>`, `index sync`, `index status`\n\n**Reading:** `open --page-slug <slug>`, `open --id <id>`, `peek --page-slug <slug>`, `peek --id <id>`, `snapshot`, `read <ref>`, `read <ref> <field>`, `read <id> --id [field]`\n\n**Diagnostics:** `diagnose` — session store, userId, active session (after open/read issues)\n\n**Editing:** `set <ref> <field> <value>`, `rtf <ref> <field> --content \"<md>\"`, `diff`, `save`, `discard`\n\n**Creating:** `create from-json --json … --dry-run --strict`, `export from-json`, `apply from-json --json …`, `create page` / `create article` for empty shells, `add` for tiny inserts\n\n**Other:** `health`, `version`, `guide [topic]`\n";
3
3
  //# sourceMappingURL=hosted-guide.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hosted-guide.js","sourceRoot":"","sources":["../../src/generated/hosted-guide.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,CAAC,MAAM,qBAAqB,GAAG,omkBAAomkB,CAAC"}
1
+ {"version":3,"file":"hosted-guide.js","sourceRoot":"","sources":["../../src/generated/hosted-guide.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,CAAC,MAAM,qBAAqB,GAAG,6nkBAA6nkB,CAAC"}
@@ -1,3 +1,3 @@
1
1
  export declare const CMS_EDIT_PACKAGE_NAME = "@se-studio/contentful-cms";
2
- export declare const CMS_EDIT_VERSION = "3.10.1";
2
+ export declare const CMS_EDIT_VERSION = "3.10.2";
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -1,4 +1,4 @@
1
1
  // Generated by scripts/generate-embedded-assets.mjs — do not edit manually.
2
2
  export const CMS_EDIT_PACKAGE_NAME = "@se-studio/contentful-cms";
3
- export const CMS_EDIT_VERSION = "3.10.1";
3
+ export const CMS_EDIT_VERSION = "3.10.2";
4
4
  //# sourceMappingURL=version.js.map
@@ -16,7 +16,7 @@
16
16
 
17
17
  - Fetch package materials (Google Drive MCP, paste, Figma link, zip, production ref, **or designer HTML**)
18
18
  - Read `cms-edit://customer/components-index`, `routing`, and for articles **`articles`**
19
- - **Designer HTML / interactive prototype:** if `cms-edit://customer/task-create-html-component` (or another site HtmlComponent playbook) exists, **switch to that playbook** and stop this one. Otherwise follow the HtmlComponent fork in Phase 2 — do not flatten custom CSS/JS/scroll into registered cards.
19
+ - **Designer HTML / interactive prototype / vendor JS:** switch to `cms-edit://customer/task-create-html-component` and stop this one. Do not flatten custom CSS/JS/scroll into registered cards. Libraries go on `externalScripts`, never `<script src>` or `createElement('script')`.
20
20
  - **Non-trivial packages:** complete **`task-source-readiness-review`** first (inventory → beat sheet → verdict → light human yes)
21
21
 
22
22
  ## Steps
@@ -44,7 +44,7 @@
44
44
  - If the source is custom HTML/JS, sticky/scroll, diagrams, or a private layout shell, and this space has `htmlComponent`, map that beat to **`htmlComponent`** — not cards/lists that only match the copy.
45
45
  - Mixed pages are expected. Merge consecutive custom sections that share CSS/JS/sticky into **one** HtmlComponent. Do not emit one HtmlComponent per `<section>`.
46
46
  - Prefer **source order** over CMS convenience (do not invent side-by-side pairs unless the source is a true pair).
47
- 3. **Propose plan** (can reuse the soft-proof): slug, title, template/article type, ordered component list (registered **and** `htmlComponent` where needed), gap/placeholder list, **asset map** — **ask user to confirm** with a light **yes** if not already confirmed. For HtmlComponent beats: extract `data:` media first; run `schema htmlComponent` for this space’s field IDs (`html` vs `htmlContent`).
47
+ 3. **Propose plan** (can reuse the soft-proof): slug, title, template/article type, ordered component list (registered **and** `htmlComponent` where needed), gap/placeholder list, **asset map** — **ask user to confirm** with a light **yes** if not already confirmed. For HtmlComponent beats: extract `data:` media first; run `schema htmlComponent` for this space’s field IDs (`html` vs `htmlContent`); list vendor URLs for `externalScripts` (see `task-create-html-component`).
48
48
  4. For pages: `list --type template` — pick template; check template `preContent` / `postContent` so heroes are not duplicated.
49
49
  5. For articles: resolve `articleType`; apply site SEO and lab rules from `articles` playbook.
50
50
  6. Build `create from-json` payload **or** prefer incremental `open` → `add` / `set` / `rtf` for large multi-block work. Link visuals using the pack map from Phase 1.
@@ -81,4 +81,4 @@ Use MCP prompts `cms_edit_create_from_doc` or `cms_edit_create_article_from_doc`
81
81
  - `task-create-page`
82
82
  - `task-media-reuse-and-upload`
83
83
  - `task-preview-verify`
84
- - Site HtmlComponent playbook when present (`cms-edit://customer/task-create-html-component`)
84
+ - `cms-edit://customer/task-create-html-component`
@@ -0,0 +1,135 @@
1
+ # Capability: Create from designer HTML (HtmlComponent)
2
+
3
+ ## Intent phrases
4
+
5
+ - build from designer HTML
6
+ - HTML dump
7
+ - create from HTML prototype
8
+ - import HTML landing page
9
+ - HtmlComponent from HTML
10
+ - custom HTML page
11
+ - load HtmlComponent external scripts
12
+
13
+ ## Requires capabilities
14
+
15
+ `pages` or `articles`
16
+
17
+ ## Prerequisites
18
+
19
+ - Read `cms-edit://customer/pages` (or `articles`) and `cms-edit://customer/components-index`
20
+ - `cms_edit ["schema", "htmlComponent"]` — use this space’s field IDs (`rawHtml` vs `htmlContent`, `customJs` vs `jsContent`). **`externalScripts` is the same field id on every space.**
21
+ - Designer HTML / prototype available (file, paste, or Drive). **Do not** paste a multi-megabyte dump with `data:` URIs into Contentful.
22
+ - For ordinary Google Doc / Figma-to-registered-stack briefs with no custom CSS/JS, use `task-create-from-document` instead.
23
+
24
+ ## When to use this playbook
25
+
26
+ Use this when the source is a **designer HTML dump**, interactive prototype, or a block that needs a **third-party JS library**. Do not flatten custom CSS/JS/scroll into registered cards because the copy looks like a card grid.
27
+
28
+ ## Per-section decision
29
+
30
+ For **each** source section, ask: do **layout and interaction** match a registered type?
31
+
32
+ | Fit | Action |
33
+ |-----|--------|
34
+ | Strong (same pattern) | Use the registered type. |
35
+ | Medium (copy matches, chrome differs) | Ask the human: site chrome vs keep HtmlComponent. |
36
+ | Weak / none | HtmlComponent. Custom sticky scroll, diagrams, widgets, vendor JS. |
37
+
38
+ Mock **nav and footer** in the HTML: strip. The page **template** supplies chrome.
39
+
40
+ ## Split HtmlComponents (mixed pages)
41
+
42
+ Mixed pages are expected: registered types **plus** one or more `htmlComponent` entries.
43
+
44
+ **Do split** when a section matches a registered type, remaining HTML would exceed field limits, or adjacent custom blocks **do not share** JS/CSS/sticky state.
45
+
46
+ **Do not split** when one behaviour spans the markup (sticky rail + stacked cards) or several sections share one stylesheet / SVG sprite / library.
47
+
48
+ Rules:
49
+
50
+ 1. Registered match → registered type.
51
+ 2. Merge consecutive custom sections that share CSS/JS into **one** HtmlComponent.
52
+ 3. Start a new HtmlComponent only at a clean boundary (no shared script, no sticky parent).
53
+ 4. Never one HtmlComponent per `<section>` by default.
54
+
55
+ ## External scripts (non-negotiable)
56
+
57
+ Put third-party libraries in **`externalScripts`**, not `<script src>` in HTML and not `createElement('script')` inside `customJs`.
58
+
59
+ Allowed values:
60
+
61
+ - Same-origin paths under `/html-components/` that end in `.js` (no `..`, no query, no hash). Prefer files in the site repo at `public/html-components/<feature>/….js`.
62
+ - HTTPS URLs whose hostname is already listed on the site’s `CmsRendererConfig.htmlComponentExternalScriptHosts`. If the site sets CSP, that host must also be in `script-src`.
63
+
64
+ If the host is **not** allowlisted, **stop** — do not invent a CDN load and do not patch renderer config without a human.
65
+
66
+ Rejected (renderer drops these; preview/dev may warn): `http:`, `javascript:`, protocol-relative `//…`, path `..`, query/hash on same-origin paths.
67
+
68
+ Max **10** URLs per entry. Duplicate URLs on one page share one `next/script` id. Externals load with `scriptStrategy` (default `afterInteractive`). **`customJs` runs only after every listed src is ready**; if any src fails, inline JS is not injected.
69
+
70
+ ### How to set `externalScripts`
71
+
72
+ It is an **Array of Symbol**, not a string. Scalar `set` is rejected.
73
+
74
+ ```text
75
+ cms_edit ["set", "@cN", "externalScripts", "--json", "[\"/html-components/feature/lib.js\"]"]
76
+ ```
77
+
78
+ On `create from-json`, put the same array under `fields.externalScripts`. Run `schema htmlComponent` first — unknown keys fail (`html` vs `htmlContent`).
79
+
80
+ Then set HTML/CSS/JS with `--content` / `--content-base64` on `rawHtml`/`customCss`/`customJs` (or this space’s dual-write ids).
81
+
82
+ ## Media first (mandatory)
83
+
84
+ 1. Extract binary media from `data:` URIs and local files. Upload via `task-media-reuse-and-upload` (search/reuse first).
85
+ 2. Rewrite HTML `src` / `poster` to Contentful or CDN URLs. Never leave base64 in the field.
86
+ 3. `markdownContent` = user-visible copy and structure. Omit layout chrome and JS behaviour.
87
+
88
+ ## HtmlComponent authoring
89
+
90
+ 1. Follow the site HTML style guide (`docs/cms-guidelines/html-component-style-guide.md` when available).
91
+ 2. Scope all JS queries to injected `hcRoot`.
92
+ 3. `isHero` — `true` if this block contains the page `<h1>`. `fullWidth` — as needed for full-bleed sections.
93
+ 4. Set `cmsLabel` immediately (never leave “New Generic”).
94
+ 5. Dual-write legacy + core field IDs when `schema htmlComponent` shows both.
95
+ 6. Draft only — never publish unless the human explicitly asks.
96
+
97
+ ## Steps
98
+
99
+ 1. Inventory sections. For each: registered / ask / HtmlComponent. Merge custom blocks that share CSS/JS.
100
+ 2. List vendor libraries. Copy same-origin files into `public/html-components/<feature>/` or confirm the HTTPS host is already allowlisted.
101
+ 3. Post a soft-proof (ordered stack + `externalScripts` URLs) and get a light human **yes**.
102
+ 4. Media pipeline: extract, upload, rewrite URLs.
103
+ 5. Create or open the page/article. Add registered components where decided.
104
+ 6. For each HtmlComponent: `add htmlComponent` (or `create from-json --dry-run --strict` with this space’s field keys).
105
+ 7. Set `externalScripts` with `--json`. Set HTML/CSS/JS. Set `cmsLabel`.
106
+ 8. `diff` → `save` (draft).
107
+ 9. `cms_edit ["preview", "urls", "<slug>"]` — follow `task-preview-verify`. Confirm libraries load (no `createElement('script')`, no dropped-URL warnings).
108
+
109
+ ## Confirmation gates
110
+
111
+ 1. Soft-proof (mixed stack + script URLs) + human yes
112
+ 2. Media extracted (no `data:` in saved HTML)
113
+ 3. `schema htmlComponent` field IDs used
114
+ 4. Libraries only via `externalScripts`
115
+ 5. `diff` before `save`
116
+
117
+ ## Out of scope
118
+
119
+ - Publish
120
+ - Flattening custom interaction into cards/lists because copy looks similar
121
+ - One HtmlComponent per `<section>` by default
122
+ - Rewriting source copy
123
+ - Pasting the raw designer file into HTML
124
+ - Adding a new CDN host to `htmlComponentExternalScriptHosts` without a human
125
+ - Scalar `set` of a single URL string onto `externalScripts`
126
+
127
+ ## Related resources
128
+
129
+ - `cms-edit://customer/pages`
130
+ - `cms-edit://customer/task-create-from-document`
131
+ - `cms-edit://customer/task-create-page`
132
+ - `cms-edit://customer/task-media-reuse-and-upload`
133
+ - `cms-edit://customer/task-preview-verify`
134
+ - `cms-edit://customer/task-publish-handoff`
135
+ - `cms_edit ["help", "fields-html-component"]`
@@ -45,11 +45,11 @@ If title, order, or hero asset **conflict** and the user has not decided → **N
45
45
  |---|------|-------------------|-----------------------------------|----------------------|
46
46
  | 1 | … | … | … | … |
47
47
 
48
- 3. **Build beat sheet** — ordered narrative + visual beats. For each beat: use a **registered** component/collection type when **layout and interaction** match (`components-index` / site playbook). If the source is designer HTML, custom CSS/JS, sticky/scroll, or a diagram that no registered type can reproduce, map to **`htmlComponent`** when that type exists in the space (and follow a site `task-create-html-component` playbook if present). Mixed stacks are valid. Merge consecutive custom HTML beats that share CSS/JS. Prefer **source order** over CMS convenience.
48
+ 3. **Build beat sheet** — ordered narrative + visual beats. For each beat: use a **registered** component/collection type when **layout and interaction** match (`components-index` / site playbook). If the source is designer HTML, custom CSS/JS, sticky/scroll, or a diagram that no registered type can reproduce, map to **`htmlComponent`** when that type exists in the space and follow **`task-create-html-component`**. Mixed stacks are valid. Merge consecutive custom HTML beats that share CSS/JS. Prefer **source order** over CMS convenience. Vendor libraries belong on `externalScripts`.
49
49
  4. **Score verdict** using the threshold below.
50
50
  5. **Post one-screen soft-proof** — numbered final `content` (or page stack) order + gap list + verdict.
51
51
  6. **Ask for a light human confirm** — accept **yes** / **go** / **proceed** (or equivalent). Do not build on silence if the package is non-trivial.
52
- 7. On **GO** or **GO-WITH-GAPS** + yes → hand off to `task-create-html-component` (if the site has it and the source is designer HTML), otherwise `task-create-from-document` / `task-create-article` / `task-create-page` with the soft-proof as the plan.
52
+ 7. On **GO** or **GO-WITH-GAPS** + yes → hand off to `task-create-html-component` when the source is designer HTML / vendor JS, otherwise `task-create-from-document` / `task-create-article` / `task-create-page` with the soft-proof as the plan.
53
53
  8. On **NO-GO** → stop; list what is blocking (do not invent structure to unblock).
54
54
 
55
55
  ## Gap threshold
@@ -114,4 +114,4 @@ Copy: unchanged from source (list any human decisions only)
114
114
  - `task-media-pipeline-prepare`
115
115
  - `task-media-reuse-and-upload`
116
116
  - `task-preview-verify`
117
- - Site `task-create-html-component` when present
117
+ - `task-create-html-component`
@@ -17,7 +17,7 @@ cms_edit ["create", "from-json", "--json", "{...}", "--dry-run", "--strict"]
17
17
  - Nested tree key is **`components`**, not `content` (`content` is a page field — empty page).
18
18
  - Discriminator is **`type`**, not `componentType` / `collectionType`.
19
19
  - **`components` must be non-empty**.
20
- - Unknown nested keys fail (`html` vs `htmlContent` — run `schema htmlComponent` for this space).
20
+ - Unknown nested keys fail (`html` vs `htmlContent` — run `schema htmlComponent` for this space). Put vendor libraries on `fields.externalScripts` (string array), not inside HTML.
21
21
  - CTA text is **`label`**, not `linkText`.
22
22
  - Quoted `set @c1:field="value"` is rejected (quotes would be stored). Use `["set", "@c1", "field", "value"]`.
23
23
 
@@ -403,3 +403,29 @@ Once created:
403
403
  3. Publish in the Contentful web app when ready.
404
404
 
405
405
  The tool prints the Contentful web app URL for the new entry after creation.
406
+
407
+ ---
408
+
409
+ ## HtmlComponent example
410
+
411
+ Run `schema htmlComponent` first. Field ids below are the core names; some spaces use `htmlContent` / `cssContent` / `jsContent` instead (or dual-write both). Vendor libraries belong on `externalScripts`, not inside HTML.
412
+
413
+ ```json
414
+ {
415
+ "type": "page",
416
+ "slug": "/lab/html-widget",
417
+ "title": "HTML widget",
418
+ "components": [
419
+ {
420
+ "contentType": "htmlComponent",
421
+ "cmsLabel": "Widget — jsPDF demo",
422
+ "fields": {
423
+ "rawHtml": "<div class=\"widget\"><button type=\"button\" class=\"export\">Export</button></div>",
424
+ "customJs": "hcRoot.querySelector('.export').addEventListener('click', function () { window.jspdf.jsPDF(); });",
425
+ "externalScripts": ["/html-components/widget/jspdf.umd.min.js"],
426
+ "markdownContent": "Export widget"
427
+ }
428
+ }
429
+ ]
430
+ }
431
+ ```
@@ -0,0 +1,78 @@
1
+ # cms-edit — HtmlComponent Field Reference
2
+
3
+ An `htmlComponent` entry is developer-authored HTML, optional scoped CSS, optional inline JS, and optional **external script URLs**. It is not a registered `component` variant.
4
+
5
+ Run `cms-edit schema htmlComponent` for this space’s field IDs. Some spaces dual-write legacy names (`htmlContent` / `cssContent` / `jsContent`) plus core ids (`rawHtml` / `customCss` / `customJs`). **`externalScripts` is the same id on every space.** Unknown JSON keys fail (`html` vs `htmlContent`).
6
+
7
+ See `cms-edit://customer/task-create-html-component` for the full create playbook.
8
+
9
+ ---
10
+
11
+ ## Fields
12
+
13
+ | Field | Type | Required | Description |
14
+ |---|---|---|---|
15
+ | `cmsLabel` | Symbol | Yes | Internal label. Set immediately. |
16
+ | `rawHtml` / `htmlContent` | Text | Yes | Markup. No `<script src>`. |
17
+ | `customCss` / `cssContent` | Text | No | Auto-scoped to `[data-hc-id]`. |
18
+ | `customJs` / `jsContent` | Text | No | Inline JS via `next/script`. Scope queries to injected `hcRoot`. |
19
+ | `externalScripts` | Array of Symbol | No | Allowlisted script URLs loaded **before** `customJs`. Max 10. |
20
+ | `markdownContent` | Text | No | Plain text for search / markdown export. |
21
+ | `scriptStrategy` | Symbol | No | `afterInteractive` (default), `lazyOnload`, `beforeInteractive`. Applies to externals and inline JS. |
22
+ | `isHero` | Boolean | No | `true` when the HTML contains the page `<h1>`. |
23
+ | `fullWidth` | Boolean | No | Viewport-breakout layout when the site renderer supports it. |
24
+ | `excludeFromSearch` | Boolean | No | Skip search even if `markdownContent` is set. |
25
+ | `loadScriptOnce` | Boolean | No | Dedupe inline script id per entry (default true). External URLs already share one `next/script` id per URL. |
26
+
27
+ ---
28
+
29
+ ## `externalScripts` allowlist
30
+
31
+ Always allowed: same-origin paths matching `/html-components/` + a `.js` filename (no `..`, no query, no hash). Prefer files in the site repo at `public/html-components/<feature>/….js`.
32
+
33
+ HTTPS URLs are allowed only when the hostname is in `CmsRendererConfig.htmlComponentExternalScriptHosts`. If the site has CSP, that host must also be in `script-src`. If the host is not allowlisted, **stop** — do not invent a CDN load.
34
+
35
+ Rejected: `http:`, `javascript:`, `//…`, path traversal, query/hash on same-origin paths. Disallowed URLs are not loaded (preview/dev may warn). Duplicates are dropped at render. `customJs` waits until every listed src is ready; a failed src skips inline JS.
36
+
37
+ Do **not** put libraries in HTML `<script>` tags or `createElement('script')` inside `customJs`.
38
+
39
+ ---
40
+
41
+ ## Setting fields
42
+
43
+ HTML/CSS/JS (long text):
44
+
45
+ ```
46
+ cms_edit ["set", "@cN", "rawHtml", "--content-base64", "<b64>"]
47
+ ```
48
+
49
+ `externalScripts` is an array — **not** a scalar string. Scalar `set` is rejected.
50
+
51
+ ```
52
+ cms_edit ["set", "@cN", "externalScripts", "--json", "[\"/html-components/feature/lib.js\"]"]
53
+ ```
54
+
55
+ On `create from-json`:
56
+
57
+ ```json
58
+ {
59
+ "contentType": "htmlComponent",
60
+ "cmsLabel": "Pricing grid",
61
+ "fields": {
62
+ "rawHtml": "<div class=\"hc-root\">…</div>",
63
+ "customJs": "hcRoot.querySelector('.buy')",
64
+ "externalScripts": ["/html-components/pricing-grid/lib.js"],
65
+ "markdownContent": "Pricing grid"
66
+ }
67
+ }
68
+ ```
69
+
70
+ Use this space’s field ids from `schema htmlComponent` (`rawHtml` vs `htmlContent`). Dual-write both when the schema shows both.
71
+
72
+ ---
73
+
74
+ ## Related
75
+
76
+ - `cms-edit://customer/task-create-html-component`
77
+ - `cms-edit help create-from-json`
78
+ - `cms-edit schema htmlComponent`
@@ -10,7 +10,7 @@ This page lists the content types managed by `cms-edit` and links to their field
10
10
  |---|---|---|
11
11
  | `page` | Web pages with a slug and content tree | `cms-edit help fields-page` |
12
12
  | `article` | Blog posts and news items | `cms-edit help fields-article` |
13
- | `component` | Individual content sections (hero, text block, card, etc.) | `cms-edit help fields-component` |
13
+ | `htmlComponent` | Developer-authored HTML / CSS / JS blocks | `cms-edit help fields-html-component` |
14
14
  | `collection` | Groups of components (card grids, timelines, etc.) | `cms-edit help fields-collection` |
15
15
  | `link` | CTA button entries | Managed via `cms-edit links` |
16
16
  | `template` | Reusable page shell (header, footer slots) | Used via `templateId` in `create from-json` |
@@ -57,4 +57,5 @@ Run `cms-edit snapshot` to see current refs.
57
57
  - `cms-edit help fields-page` — page entry fields
58
58
  - `cms-edit help fields-article` — article entry fields
59
59
  - `cms-edit help fields-component` — component entry fields
60
+ - `cms-edit help fields-html-component` — HtmlComponent (raw HTML, `externalScripts`)
60
61
  - `cms-edit help fields-collection` — collection entry fields
@@ -29,6 +29,7 @@ Available topics for `cms-edit help <topic>`:
29
29
  | `fields-page` | All fields on a `page` entry |
30
30
  | `fields-article` | All fields on an `article` entry |
31
31
  | `fields-component` | All fields on a `component` entry |
32
+ | `fields-html-component` | HtmlComponent fields including `externalScripts` |
32
33
  | `fields-collection` | All fields on a `collection` entry |
33
34
  | `fields-taxonomy` | All fields on `tagType`, `articleType`, and `tag` entries |
34
35
  | `index-sync` | Build and use the local content index (CDA sync) |
@@ -22,7 +22,7 @@ All `save` operations create **drafts only**. A human must review and publish in
22
22
  | `cms_edit` | Run any cms-edit command via an `args` array |
23
23
  | `cms_edit_request_staged_upload` | Presigned URL for binaries. Raster/video: then `cms_edit_media` prepare. SVG/PDF/Lottie: then `asset upload --staged` |
24
24
  | `cms_edit_version` | Check the installed cms-edit version (no session required) |
25
- | `cms_edit_help` | Guide topics (`overview`, `workflow`, `commands`, `troubleshooting`, `hosted`) **or** playbooks (`create-from-json`, `fields-component`, …) |
25
+ | `cms_edit_help` | Guide topics (`overview`, `workflow`, `commands`, `troubleshooting`, `hosted`) **or** playbooks (`create-from-json`, `fields-html-component`, `fields-component`, …) |
26
26
 
27
27
  ## Customer editor pack (read first)
28
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@se-studio/contentful-cms",
3
- "version": "3.10.1",
3
+ "version": "3.10.2",
4
4
  "description": "CLI tool for AI agents to read and edit Contentful draft content without publish permissions",
5
5
  "repository": {
6
6
  "type": "git",