@se-studio/contentful-cms 3.14.2 → 3.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +5 -1
  3. package/dist/commands/asset.d.ts.map +1 -1
  4. package/dist/commands/asset.js +310 -3
  5. package/dist/commands/asset.js.map +1 -1
  6. package/dist/contentful/asset-concepts.d.ts +53 -0
  7. package/dist/contentful/asset-concepts.d.ts.map +1 -0
  8. package/dist/contentful/asset-concepts.js +137 -0
  9. package/dist/contentful/asset-concepts.js.map +1 -0
  10. package/dist/contentful/asset-replace.d.ts +29 -0
  11. package/dist/contentful/asset-replace.d.ts.map +1 -0
  12. package/dist/contentful/asset-replace.js +57 -0
  13. package/dist/contentful/asset-replace.js.map +1 -0
  14. package/dist/contentful/assets.d.ts +11 -0
  15. package/dist/contentful/assets.d.ts.map +1 -1
  16. package/dist/contentful/assets.js +48 -2
  17. package/dist/contentful/assets.js.map +1 -1
  18. package/dist/contentful/client.d.ts +9 -0
  19. package/dist/contentful/client.d.ts.map +1 -1
  20. package/dist/contentful/client.js +27 -0
  21. package/dist/contentful/client.js.map +1 -1
  22. package/dist/contentful/cma-user-agent.d.ts +1 -1
  23. package/dist/contentful/media-dimensions.d.ts +7 -0
  24. package/dist/contentful/media-dimensions.d.ts.map +1 -0
  25. package/dist/contentful/media-dimensions.js +169 -0
  26. package/dist/contentful/media-dimensions.js.map +1 -0
  27. package/dist/contentful/upload-limits.d.ts +10 -4
  28. package/dist/contentful/upload-limits.d.ts.map +1 -1
  29. package/dist/contentful/upload-limits.js +14 -7
  30. package/dist/contentful/upload-limits.js.map +1 -1
  31. package/dist/editor-pack/generate.js +2 -2
  32. package/dist/editor-pack/generate.js.map +1 -1
  33. package/dist/generated/hosted-guide.d.ts +1 -1
  34. package/dist/generated/hosted-guide.d.ts.map +1 -1
  35. package/dist/generated/hosted-guide.js +1 -1
  36. package/dist/generated/hosted-guide.js.map +1 -1
  37. package/dist/generated/version.d.ts +1 -1
  38. package/dist/generated/version.js +1 -1
  39. package/dist/mcp/tools/cms-edit.d.ts +1 -1
  40. package/dist/mcp/tools/cms-edit.d.ts.map +1 -1
  41. package/dist/mcp/tools/cms-edit.js +1 -1
  42. package/dist/mcp/tools/media.d.ts.map +1 -1
  43. package/dist/mcp/tools/media.js +3 -3
  44. package/dist/mcp/tools/media.js.map +1 -1
  45. package/dist/mcp/tools/staged-upload.d.ts.map +1 -1
  46. package/dist/mcp/tools/staged-upload.js +4 -3
  47. package/dist/mcp/tools/staged-upload.js.map +1 -1
  48. package/dist/staged-upload/direct-intake.d.ts +23 -0
  49. package/dist/staged-upload/direct-intake.d.ts.map +1 -0
  50. package/dist/staged-upload/direct-intake.js +71 -0
  51. package/dist/staged-upload/direct-intake.js.map +1 -0
  52. package/dist/staged-upload/intake-policy.d.ts +13 -0
  53. package/dist/staged-upload/intake-policy.d.ts.map +1 -0
  54. package/dist/staged-upload/intake-policy.js +37 -0
  55. package/dist/staged-upload/intake-policy.js.map +1 -0
  56. package/dist/staged-upload/request.d.ts +5 -1
  57. package/dist/staged-upload/request.d.ts.map +1 -1
  58. package/dist/staged-upload/request.js +31 -2
  59. package/dist/staged-upload/request.js.map +1 -1
  60. package/dist/staged-upload/types.d.ts +4 -0
  61. package/dist/staged-upload/types.d.ts.map +1 -1
  62. package/docs/editor-tasks/media-pipeline-prepare.md +12 -2
  63. package/docs/editor-tasks/media-reuse-and-upload.md +40 -8
  64. package/docs/editor-tasks/taxonomy.md +2 -0
  65. package/docs/mcp/hosted-guide.md +8 -6
  66. package/package.json +2 -1
  67. package/skills/core/SKILL.md +11 -5
  68. package/skills/editor-tasks/SKILL.md +1 -1
@@ -1,7 +1,7 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { HostedMcpInfo } from '../guide.js';
3
3
  import type { RunCmsEditFn } from '../types.js';
4
- export declare const TOOL_DESCRIPTION = "Run any cms-edit command. Saves create Contentful drafts. Publish, unpublish, archive, and revalidate are separate plan commands and run only after the user says yes.\n\nUse hosted MCP only for content. Do not run a local cms-edit binary, --file, or stdin for pages.\n\nJSON-first (preferred for a whole page):\n [\"schema\", \"component\"] then [\"schema\", \"types\", \"component\"] \u2014 field ids and enums for THIS space\n [\"help\", \"create-from-json\"] or cms_edit_help {topic:\"create-from-json\"}\n Hosted payloads over 8192 bytes MUST use staged upload (kind=json), not --json / --json-base64.\n Filesystem client: write JSON on disk, cms_edit_request_staged_upload kind=json, curl, then\n [\"create\", \"from-json\", \"--staged\", \"<id>\", \"--dry-run\", \"--strict\"]\n Tiny JSON only: [\"create\", \"from-json\", \"--json\", \"{...}\", \"--dry-run\", \"--strict\"]\n Then the same command without --dry-run. That writes drafts. Do not publish them in the same step.\n Edit existing: [\"open\", \"--page-slug\", \"/x\"] \u2192 [\"export\", \"from-json\"] \u2192 staged json \u2192 [\"apply\", \"from-json\", \"--staged\", \"<id>\"]\n Tiny patches only: set / rtf / add. Long Text (rawHtml/customCss/customJs): kind=text then set --staged.\n Object JSON, rtf --content, batch run, person/taxonomy imports: --staged when over 8k (kind=json or kind=text).\n\nUse cms_edit_version (no args) to check the installed version.\nUse cms_edit_help to read the getting-started guide or playbooks (including create-from-json).\n Topic \"workflow\" is the playbook; use topic \"guide/workflow\" for the short hosted-guide section.\nOutput: command JSON, then a last line {\"cma_requests\":N,\"cpa_requests\":N} (parse the last line for CMA counts; JSON.parse the command body separately).\nUse cms_edit_customer to read site-specific playbooks (articles, routing, blog-tag-matrix, etc.).\nIf Integrations only exposes cms_edit, use [\"customer\", \"index\"] or [\"customer\", \"resource=index\"] instead.\nRead the cms-edit://guide resource for the full guide.\n\nWorkflow (patches): open \u2192 snapshot \u2192 read/set/rtf/add/remove \u2192 diff \u2192 save\nSaves create drafts. To go live: publish plan \u2192 if the plan asks all vs mine, ask the user and publish choose \u2192 publish apply only after the user says yes.\nThe Contentful role must allow publish. AI Editor cannot. Same plan-then-yes rule for unpublish, archive, and revalidate.\n\nCRITICAL \u2014 sessions and open:\n set/rtf field edits are staged until save. Plain open while dirty HARD-FAILS (does not wipe silently).\n [\"open\", \"--resume\"] \u2014 re-print current session; keeps all staged edits (no Contentful refetch)\n [\"open\", \"--overwrite\", \"--confirm-discard\", \"--id\", \"<id>\"] \u2014 abandon staged work and load a new entry\n [\"discard\"] then open \u2014 also valid abandon path\n Never re-open the same article mid-edit without save/discard \u2014 use --resume if you only need the tree again.\n\nCommon command sequences:\n [\"open\", \"--page-slug\", \"/\"] \u2014 homepage (CMS slug index; /index is equivalent)\n [\"open\", \"--page-slug\", \"/pricing\"] \u2014 load a page by slug (skips template menu/footer)\n [\"open\", \"--include-chrome\", \"--page-slug\", \"/pricing\"] \u2014 also fetch template nav chrome\n [\"open\", \"--id\", \"<entry-id>\"] \u2014 load by Contentful entry ID\n [\"open\", \"--resume\"] \u2014 keep staged work; re-print snapshot\n [\"snapshot\"] \u2014 print content tree with @refs\n [\"read\", \"@c0\"] \u2014 read all fields of a component (requires open session)\n [\"read\", \"@c0\", \"heading\"] \u2014 read a specific field (requires open session)\n [\"read\", \"@root\"] \u2014 read root entry fields (requires open session)\n [\"read\", \"<entry-id>\", \"--id\"] \u2014 read entry by ID (no session)\n [\"read\", \"<entry-id>\", \"--id\", \"heading\"] \u2014 read one field by entry ID (no session)\n [\"peek\", \"--page-slug\", \"/pricing\"] \u2014 preview tree + root fields (no session)\n [\"peek\", \"--id\", \"<entry-id>\"] \u2014 preview by entry ID with rootFields in JSON\n [\"set\", \"@c0\", \"heading\", \"New heading\"] \u2014 set a scalar field\n [\"set\", \"<entry-id>\", \"autoplay\", \"true\"] \u2014 mid-session scalar by ID (e.g. media; no re-open)\n [\"set\", \"<entry-id>:autoplay=true\"] \u2014 same, batch-shaped token on page session\n [\"set\", \"@c1\", \"visual\", \"<media-id>\", \"--link\"] \u2014 single Entry link field only\n [\"set\", \"@c2\", \"contents\", \"<id1>,<id2>\", \"--links\"] \u2014 array of links (full replace); never --link on arrays\n [\"rtf\", \"@c0\", \"body\", \"--content\", \"# H1\\n\\nBody.\"] \u2014 set rich text (preferred for AI)\n [\"rtf\", \"@c0\", \"body\", \"--base64\", \"<encoded>\"] \u2014 set rich text via base64 (no filesystem)\n [\"set\", \"@c1\", \"rawHtml\", \"--staged\", \"<id>\"] \u2014 long Text from staged upload (kind=text)\n [\"read\", \"@c0\", \"--hash\"] \u2014 byte lengths + sha256 (no bodies)\n [\"diff\"] \u2014 pending changes; long Text is bytes/sha256/head only\n [\"save\"] \u2014 write drafts to Contentful\n [\"discard\"] \u2014 discard unsaved changes\n [\"links\", \"set\", \"@c0\", \"0\", \"variant\", \"link\"] \u2014 set scalar field on link at index 0\n [\"batch\", \"set\", \"<entry-id>:variant=link\"] \u2014 scalar by ID (OK mid-page session; then save)\n [\"batch\", \"diff\"] \u2014 show pending batch edits\n [\"batch\", \"save\"] \u2014 save all batch edits as drafts\n [\"health\"] \u2014 verify config and connectivity\n [\"diagnose\"] \u2014 session/MCP diagnostics (store, userId, active session)\n [\"version\"] \u2014 print installed cms-edit version\n [\"guide\"] \u2014 print getting-started guide\n [\"guide\", \"workflow\"] \u2014 print a guide section\n [\"customer\", \"index\"] \u2014 list site editor playbooks\n [\"customer\", \"articles\"] \u2014 read articles playbook\n [\"customer\", \"resource=index\"] \u2014 same as index (Integrations fallback)\n [\"list\", \"--type\", \"article\", \"--sort\", \"date\", \"-n\", \"10\"] \u2014 recent articles by publication date\n [\"list\", \"--type\", \"article\", \"--sort\", \"date\"] \u2014 list articles (preview index includes drafts)\n [\"peek\", \"--article-slug\", \"<slug>\"] \u2014 preview draft or published article (CMA)\n [\"preview\", \"url\", \"/pricing\"] \u2014 staging page URL (open in browser MCP)\n [\"preview\", \"urls\", \"/pricing\"] \u2014 all preview targets; slug wins over leftover session root\n [\"nav\", \"open\", \"--nav-id\", \"<id>\"] \u2014 persist a navigation session (not --id)\n [\"nav\", \"open\", \"--nav-name\", \"Main menu\"] \u2014 same by navigation name\n [\"preview\", \"showcase\", \"--component\", \"HeroSimple\"] \u2014 component showcase URL (mock data)\n [\"preview\", \"showcase\", \"--collection\", \"CardGrid\", \"--param\", \"backgroundColour=Navy\"]\n\nDiscovery notes:\n Visual checks: use preview url / preview showcase, then open the returned URL with a browser MCP (cms-edit does not capture screenshots).\n peek/open/read --id load draft content via CMA \u2014 NOT published-only.\n Only list --published / resolve --published use the published-only Delivery API index.\n Articles: use --article-slug (not --page-slug). search --type article sorts by publication date.\n\nSessions persist between MCP calls (Redis on hosted, disk locally). MCP auto-uses session \"_mcp\" unless you pass --session.\nUse --session <name> before the subcommand to isolate concurrent workflows, e.g. [\"--session\", \"my-work\", \"open\", \"--page-slug\", \"/\"].\nUse --space <name> to target a specific Contentful space (local MCP only \u2014 omit for hosted Integrations).\nStateless read (no open required): [\"read\", \"<entry-id>\", \"--id\"] or [\"read\", \"<entry-id>\", \"--id\", \"heading\"].\npeek is one-shot (no session). open / nav open persist. preview urls /slug follows that slug even if _mcp is another page (JSON may include sessionRootIgnored).\nBefore set on a nested nav item (often a shared header CTA): ask whether the change is global or page-specific. Page-specific \u2192 new NavigationItem + page.menu (or a cloned menu), do not mutate the shared entry.\nTemplate menu is site-wide; page.menu overrides one page. nav clone is shallow for nested navigationItems.\nOutput is always JSON (CMS_EDIT_JSON=1 is forced by the MCP server).\n\n\nRich text tips:\n --content \"<markdown>\" preferred named flag for inline content (hosted: max 8192 bytes)\n --base64 \"<encoded>\" fully environment-agnostic, no filesystem needed\n --staged \"<id>\" kind=text (or kind=json with --rtf-json)\n\nJSON bulk input (create/apply from-json, batch run, taxonomy/person/article-tags imports, set object fields, rtf patch):\n --json \"<string>\" inline JSON (hosted: max 8192 bytes)\n --json-base64 \"<b64>\" base64-encoded JSON (hosted: max 8192 decoded bytes)\n --staged \"<id>\" after cms_edit_request_staged_upload kind=json\n\nAsset upload (hosted MCP):\n Raster/video: cms_edit_request_staged_upload \u2192 curl \u2192 cms_edit_media prepare with stagedUploadIds \u2192 wait \u2192 catalog \u2192 import (video: import only; overrides[].alt required). Never asset upload --staged/--url/--base64 for jpeg/png/webp/gif/mp4. Never ffmpeg/convert GIF or PNG locally \u2014 leave the original file; the pipeline encodes (animated GIF \u2192 gif-converted MP4, mute-loop Media, not featuredImage).\n SVG/PDF/Lottie: [\"staged-upload\", \"request\", ...] then curl, then [\"asset\", \"upload\", \"--staged\", \"<id>\", ...].\n Long Text / large page JSON: cms_edit_request_staged_upload kind=text|json \u2192 curl \u2192 set --staged or create from-json --staged.\n Pipeline ready **image** URLs may use import or asset upload --url on a Blob pack host.";
4
+ export declare const TOOL_DESCRIPTION = "Run any cms-edit command. Saves create Contentful drafts. Publish, unpublish, archive, and revalidate are separate plan commands and run only after the user says yes.\n\nUse hosted MCP only for content. Do not run a local cms-edit binary, --file, or stdin for pages.\n\nJSON-first (preferred for a whole page):\n [\"schema\", \"component\"] then [\"schema\", \"types\", \"component\"] \u2014 field ids and enums for THIS space\n [\"help\", \"create-from-json\"] or cms_edit_help {topic:\"create-from-json\"}\n Hosted payloads over 8192 bytes MUST use staged upload (kind=json), not --json / --json-base64.\n Filesystem client: write JSON on disk, cms_edit_request_staged_upload kind=json, curl, then\n [\"create\", \"from-json\", \"--staged\", \"<id>\", \"--dry-run\", \"--strict\"]\n Tiny JSON only: [\"create\", \"from-json\", \"--json\", \"{...}\", \"--dry-run\", \"--strict\"]\n Then the same command without --dry-run. That writes drafts. Do not publish them in the same step.\n Edit existing: [\"open\", \"--page-slug\", \"/x\"] \u2192 [\"export\", \"from-json\"] \u2192 staged json \u2192 [\"apply\", \"from-json\", \"--staged\", \"<id>\"]\n Tiny patches only: set / rtf / add. Long Text (rawHtml/customCss/customJs): kind=text then set --staged.\n Object JSON, rtf --content, batch run, person/taxonomy imports: --staged when over 8k (kind=json or kind=text).\n\nUse cms_edit_version (no args) to check the installed version.\nUse cms_edit_help to read the getting-started guide or playbooks (including create-from-json).\n Topic \"workflow\" is the playbook; use topic \"guide/workflow\" for the short hosted-guide section.\nOutput: command JSON, then a last line {\"cma_requests\":N,\"cpa_requests\":N} (parse the last line for CMA counts; JSON.parse the command body separately).\nUse cms_edit_customer to read site-specific playbooks (articles, routing, blog-tag-matrix, etc.).\nIf Integrations only exposes cms_edit, use [\"customer\", \"index\"] or [\"customer\", \"resource=index\"] instead.\nRead the cms-edit://guide resource for the full guide.\n\nWorkflow (patches): open \u2192 snapshot \u2192 read/set/rtf/add/remove \u2192 diff \u2192 save\nSaves create drafts. To go live: publish plan \u2192 if the plan asks all vs mine, ask the user and publish choose \u2192 publish apply only after the user says yes.\nThe Contentful role must allow publish. AI Editor cannot. Same plan-then-yes rule for unpublish, archive, and revalidate.\n\nCRITICAL \u2014 sessions and open:\n set/rtf field edits are staged until save. Plain open while dirty HARD-FAILS (does not wipe silently).\n [\"open\", \"--resume\"] \u2014 re-print current session; keeps all staged edits (no Contentful refetch)\n [\"open\", \"--overwrite\", \"--confirm-discard\", \"--id\", \"<id>\"] \u2014 abandon staged work and load a new entry\n [\"discard\"] then open \u2014 also valid abandon path\n Never re-open the same article mid-edit without save/discard \u2014 use --resume if you only need the tree again.\n\nCommon command sequences:\n [\"open\", \"--page-slug\", \"/\"] \u2014 homepage (CMS slug index; /index is equivalent)\n [\"open\", \"--page-slug\", \"/pricing\"] \u2014 load a page by slug (skips template menu/footer)\n [\"open\", \"--include-chrome\", \"--page-slug\", \"/pricing\"] \u2014 also fetch template nav chrome\n [\"open\", \"--id\", \"<entry-id>\"] \u2014 load by Contentful entry ID\n [\"open\", \"--resume\"] \u2014 keep staged work; re-print snapshot\n [\"snapshot\"] \u2014 print content tree with @refs\n [\"read\", \"@c0\"] \u2014 read all fields of a component (requires open session)\n [\"read\", \"@c0\", \"heading\"] \u2014 read a specific field (requires open session)\n [\"read\", \"@root\"] \u2014 read root entry fields (requires open session)\n [\"read\", \"<entry-id>\", \"--id\"] \u2014 read entry by ID (no session)\n [\"read\", \"<entry-id>\", \"--id\", \"heading\"] \u2014 read one field by entry ID (no session)\n [\"peek\", \"--page-slug\", \"/pricing\"] \u2014 preview tree + root fields (no session)\n [\"peek\", \"--id\", \"<entry-id>\"] \u2014 preview by entry ID with rootFields in JSON\n [\"set\", \"@c0\", \"heading\", \"New heading\"] \u2014 set a scalar field\n [\"set\", \"<entry-id>\", \"autoplay\", \"true\"] \u2014 mid-session scalar by ID (e.g. media; no re-open)\n [\"set\", \"<entry-id>:autoplay=true\"] \u2014 same, batch-shaped token on page session\n [\"set\", \"@c1\", \"visual\", \"<media-id>\", \"--link\"] \u2014 single Entry link field only\n [\"set\", \"@c2\", \"contents\", \"<id1>,<id2>\", \"--links\"] \u2014 array of links (full replace); never --link on arrays\n [\"rtf\", \"@c0\", \"body\", \"--content\", \"# H1\\n\\nBody.\"] \u2014 set rich text (preferred for AI)\n [\"rtf\", \"@c0\", \"body\", \"--base64\", \"<encoded>\"] \u2014 set rich text via base64 (no filesystem)\n [\"set\", \"@c1\", \"rawHtml\", \"--staged\", \"<id>\"] \u2014 long Text from staged upload (kind=text)\n [\"read\", \"@c0\", \"--hash\"] \u2014 byte lengths + sha256 (no bodies)\n [\"diff\"] \u2014 pending changes; long Text is bytes/sha256/head only\n [\"save\"] \u2014 write drafts to Contentful\n [\"discard\"] \u2014 discard unsaved changes\n [\"links\", \"set\", \"@c0\", \"0\", \"variant\", \"link\"] \u2014 set scalar field on link at index 0\n [\"batch\", \"set\", \"<entry-id>:variant=link\"] \u2014 scalar by ID (OK mid-page session; then save)\n [\"batch\", \"diff\"] \u2014 show pending batch edits\n [\"batch\", \"save\"] \u2014 save all batch edits as drafts\n [\"health\"] \u2014 verify config and connectivity\n [\"diagnose\"] \u2014 session/MCP diagnostics (store, userId, active session)\n [\"version\"] \u2014 print installed cms-edit version\n [\"guide\"] \u2014 print getting-started guide\n [\"guide\", \"workflow\"] \u2014 print a guide section\n [\"customer\", \"index\"] \u2014 list site editor playbooks\n [\"customer\", \"articles\"] \u2014 read articles playbook\n [\"customer\", \"resource=index\"] \u2014 same as index (Integrations fallback)\n [\"list\", \"--type\", \"article\", \"--sort\", \"date\", \"-n\", \"10\"] \u2014 recent articles by publication date\n [\"list\", \"--type\", \"article\", \"--sort\", \"date\"] \u2014 list articles (preview index includes drafts)\n [\"peek\", \"--article-slug\", \"<slug>\"] \u2014 preview draft or published article (CMA)\n [\"preview\", \"url\", \"/pricing\"] \u2014 staging page URL (open in browser MCP)\n [\"preview\", \"urls\", \"/pricing\"] \u2014 all preview targets; slug wins over leftover session root\n [\"nav\", \"open\", \"--nav-id\", \"<id>\"] \u2014 persist a navigation session (not --id)\n [\"nav\", \"open\", \"--nav-name\", \"Main menu\"] \u2014 same by navigation name\n [\"preview\", \"showcase\", \"--component\", \"HeroSimple\"] \u2014 component showcase URL (mock data)\n [\"preview\", \"showcase\", \"--collection\", \"CardGrid\", \"--param\", \"backgroundColour=Navy\"]\n\nDiscovery notes:\n Visual checks: use preview url / preview showcase, then open the returned URL with a browser MCP (cms-edit does not capture screenshots).\n peek/open/read --id load draft content via CMA \u2014 NOT published-only.\n Only list --published / resolve --published use the published-only Delivery API index.\n Articles: use --article-slug (not --page-slug). search --type article sorts by publication date.\n\nSessions persist between MCP calls (Redis on hosted, disk locally). MCP auto-uses session \"_mcp\" unless you pass --session.\nUse --session <name> before the subcommand to isolate concurrent workflows, e.g. [\"--session\", \"my-work\", \"open\", \"--page-slug\", \"/\"].\nUse --space <name> to target a specific Contentful space (local MCP only \u2014 omit for hosted Integrations).\nStateless read (no open required): [\"read\", \"<entry-id>\", \"--id\"] or [\"read\", \"<entry-id>\", \"--id\", \"heading\"].\npeek is one-shot (no session). open / nav open persist. preview urls /slug follows that slug even if _mcp is another page (JSON may include sessionRootIgnored).\nBefore set on a nested nav item (often a shared header CTA): ask whether the change is global or page-specific. Page-specific \u2192 new NavigationItem + page.menu (or a cloned menu), do not mutate the shared entry.\nTemplate menu is site-wide; page.menu overrides one page. nav clone is shallow for nested navigationItems.\nOutput is always JSON (CMS_EDIT_JSON=1 is forced by the MCP server).\n\n\nRich text tips:\n --content \"<markdown>\" preferred named flag for inline content (hosted: max 8192 bytes)\n --base64 \"<encoded>\" fully environment-agnostic, no filesystem needed\n --staged \"<id>\" kind=text (or kind=json with --rtf-json)\n\nJSON bulk input (create/apply from-json, batch run, taxonomy/person/article-tags imports, set object fields, rtf patch):\n --json \"<string>\" inline JSON (hosted: max 8192 bytes)\n --json-base64 \"<b64>\" base64-encoded JSON (hosted: max 8192 decoded bytes)\n --staged \"<id>\" after cms_edit_request_staged_upload kind=json\n\nAsset upload (hosted MCP):\n Raster/video: cms_edit_request_staged_upload \u2192 curl \u2192 cms_edit_media prepare (stagedUploadIds, or httpsUrls when intake is direct) \u2192 wait \u2192 catalog \u2192 import (video: import only; overrides[].alt required). Video and an image master over 4MB return a presigned PUT (100MB) and must not POST through the function. Never asset upload --staged/--url/--base64 for jpeg/png/webp/gif/mp4. Never ffmpeg/convert GIF or PNG locally \u2014 leave the original file; the pipeline encodes (animated GIF \u2192 gif-converted MP4, mute-loop Media, not featuredImage). Replace an existing id with asset replace --url of the processed \u2026/packs/\u2026/ready/\u2026 file (dry-run first). That keeps the id and the file name. Alt is asset set-description. Title is asset set-title. Concepts are asset concept assign, not taxonomy-from-json.\n SVG/PDF/Lottie: [\"staged-upload\", \"request\", ...] then curl, then [\"asset\", \"upload\", \"--staged\", \"<id>\", ...].\n Long Text / large page JSON: cms_edit_request_staged_upload kind=text|json \u2192 curl \u2192 set --staged or create from-json --staged.\n Pipeline ready **image** URLs may use import or asset upload --url on a Blob pack host.";
5
5
  export declare function registerCmsEditTools(server: McpServer, runCmsEdit: RunCmsEditFn, hosted?: HostedMcpInfo): void;
6
6
  export declare function getMcpServerInfo(hosted?: HostedMcpInfo): {
7
7
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"cms-edit.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/cms-edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAOjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,gBAAgB,g6VAiH6D,CAAC;AAE3F,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,UAAU,EAAE,YAAY,EACxB,MAAM,CAAC,EAAE,aAAa,GACrB,IAAI,CAmEN;AAED,wBAAgB,gBAAgB,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAE1F"}
1
+ {"version":3,"file":"cms-edit.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/cms-edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAOjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,gBAAgB,2zWAiH6D,CAAC;AAE3F,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,UAAU,EAAE,YAAY,EACxB,MAAM,CAAC,EAAE,aAAa,GACrB,IAAI,CAmEN;AAED,wBAAgB,gBAAgB,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAE1F"}
@@ -113,7 +113,7 @@ JSON bulk input (create/apply from-json, batch run, taxonomy/person/article-tags
113
113
  --staged "<id>" after cms_edit_request_staged_upload kind=json
114
114
 
115
115
  Asset upload (hosted MCP):
116
- Raster/video: cms_edit_request_staged_upload → curl → cms_edit_media prepare with stagedUploadIds → wait → catalog → import (video: import only; overrides[].alt required). Never asset upload --staged/--url/--base64 for jpeg/png/webp/gif/mp4. Never ffmpeg/convert GIF or PNG locally — leave the original file; the pipeline encodes (animated GIF → gif-converted MP4, mute-loop Media, not featuredImage).
116
+ Raster/video: cms_edit_request_staged_upload → curl → cms_edit_media prepare (stagedUploadIds, or httpsUrls when intake is direct) → wait → catalog → import (video: import only; overrides[].alt required). Video and an image master over 4MB return a presigned PUT (100MB) and must not POST through the function. Never asset upload --staged/--url/--base64 for jpeg/png/webp/gif/mp4. Never ffmpeg/convert GIF or PNG locally — leave the original file; the pipeline encodes (animated GIF → gif-converted MP4, mute-loop Media, not featuredImage). Replace an existing id with asset replace --url of the processed …/packs/…/ready/… file (dry-run first). That keeps the id and the file name. Alt is asset set-description. Title is asset set-title. Concepts are asset concept assign, not taxonomy-from-json.
117
117
  SVG/PDF/Lottie: ["staged-upload", "request", ...] then curl, then ["asset", "upload", "--staged", "<id>", ...].
118
118
  Long Text / large page JSON: cms_edit_request_staged_upload kind=text|json → curl → set --staged or create from-json --staged.
119
119
  Pipeline ready **image** URLs may use import or asset upload --url on a Blob pack host.`;
@@ -1 +1 @@
1
- {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/media.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAczE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA4CjD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAqJlF"}
1
+ {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/media.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAczE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA4CjD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAyJlF"}
@@ -14,7 +14,7 @@ Raster images (jpeg/png/webp/gif) must be encoded here before Contentful. Video
14
14
  If catalog/status gaps say "staged GET failed 405", stop. That is a cms-edit host version lag (GET /api/staged-upload missing). Do not retry prepare, do not fall back to the Contentful web UI.
15
15
 
16
16
  Actions:
17
- - prepare: start a pack job (returns jobId). Source is **one of**: driveFolderUrl, stagedUploadIds (after curl POST to staged uploadUrl), and/or httpsUrls. Optional sourceNames/excludeSourceNames filter **before encode**. Omit sourceNames = entire Drive folder (slow for large packs).
17
+ - prepare: start a pack job (returns jobId). Source is **one of**: driveFolderUrl, stagedUploadIds (image at or under 4MB, after curl POST to uploadUrl), or httpsUrls (video, or an image over 4MB, after the presigned PUT — do not POST that file through the function). Optional sourceNames/excludeSourceNames filter **before encode**. Omit sourceNames = entire Drive folder (slow for large packs).
18
18
  - status: poll job status / gap counts
19
19
  - catalog: list ready assets (readyUrl, previewUrl) for naming/alt
20
20
  - wait: poll until terminal or timeout (~25s default)
@@ -53,11 +53,11 @@ export function registerMediaTools(server, hosted) {
53
53
  stagedUploadIds: z
54
54
  .array(z.string())
55
55
  .optional()
56
- .describe('Staged upload IDs after curl POST to uploadUrl (prepare)'),
56
+ .describe('Staged upload IDs after a function POST (image at or under 4MB). Video uses httpsUrls.'),
57
57
  httpsUrls: z
58
58
  .array(z.string())
59
59
  .optional()
60
- .describe('Public HTTPS file URLs on pipeline-allowed hosts (prepare)'),
60
+ .describe('Public HTTPS URLs (prepare). Video and image masters over 4MB: the presigned intake URL.'),
61
61
  jobId: z.string().optional().describe('Job id from prepare (status/catalog/wait/import)'),
62
62
  profile: z.string().optional().describe('Pipeline profile (default case-study-v1)'),
63
63
  ttlHours: z.number().int().min(1).max(24).optional().describe('Pack TTL hours (1–24)'),
@@ -1 +1 @@
1
- {"version":3,"file":"media.js","sourceRoot":"","sources":["../../../src/mcp/tools/media.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAEL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,WAAW,EACX,SAAS,GACV,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAEpE,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;mLA0B0J,CAAC;AAEpL,SAAS,kBAAkB,CAAC,MAAsB;IAChD,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,uBAAuB,EAAE,CAAC;IACzC,OAAO;QACL,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,EAAE;QACnC,UAAU,EAAE,OAAO,EAAE,UAAU,IAAI,MAAM,EAAE,UAAU,IAAI,SAAS;QAClE,MAAM;QACN,QAAQ,EAAE,OAAO,EAAE,qBAAqB,IAAI,IAAI;QAChD,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI;QACvD,iBAAiB,EAAE,OAAO,EAAE,iBAAiB,IAAI,IAAI;KACtD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAiB,EAAE,MAAsB;IAC1E,MAAM,CAAC,IAAI,CACT,gBAAgB,EAChB,2BAA2B,CAAC,gBAAgB,EAAE,MAAM,CAAC,EACrD;QACE,MAAM,EAAE,CAAC;aACN,IAAI,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;aACxD,QAAQ,CAAC,uBAAuB,CAAC;QACpC,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;QACnF,eAAe,EAAE,CAAC;aACf,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,QAAQ,EAAE;aACV,QAAQ,CAAC,0DAA0D,CAAC;QACvE,SAAS,EAAE,CAAC;aACT,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,QAAQ,EAAE;aACV,QAAQ,CAAC,4DAA4D,CAAC;QACzE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kDAAkD,CAAC;QACzF,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0CAA0C,CAAC;QACnF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;QACtF,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;QACnF,cAAc,EAAE,CAAC;aACd,MAAM,EAAE;aACR,GAAG,EAAE;aACL,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,EAAE,CAAC;aACP,QAAQ,EAAE;aACV,QAAQ,CAAC,gDAAgD,CAAC;QAC7D,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;QAChF,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;QACvF,SAAS,EAAE,CAAC;aACT,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;YACP,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;YACtB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC5B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC1B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SAChC,CAAC,CACH;aACA,QAAQ,EAAE;aACV,QAAQ,CAAC,qDAAqD,CAAC;QAClE,WAAW,EAAE,CAAC;aACX,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,QAAQ,EAAE;aACV,QAAQ,CACP,yIAAyI,CAC1I;QACH,kBAAkB,EAAE,CAAC;aAClB,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,QAAQ,EAAE;aACV,QAAQ,CAAC,kDAAkD,CAAC;QAC/D,iBAAiB,EAAE,CAAC;aACjB,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,QAAQ,CAAC,uDAAuD,CAAC;KACrE,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,MAA+B,CAAC;QAEpC,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;YACtB,KAAK,SAAS;gBACZ,MAAM,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE;oBAC/B,cAAc,EAAE,MAAM,CAAC,cAAc;oBACrC,eAAe,EAAE,MAAM,CAAC,eAAe;oBACvC,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;oBACzB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,WAAW,EAAE,MAAM,CAAC,WAAW;oBAC/B,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;iBAC9C,CAAC,CAAC;gBACH,MAAM;YACR,KAAK,QAAQ;gBACX,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;oBAClB,MAAM,GAAG;wBACP,EAAE,EAAE,KAAK;wBACT,KAAK,EAAE,qCAAqC;wBAC5C,IAAI,EAAE,eAAe;qBACtB,CAAC;oBACF,MAAM;gBACR,CAAC;gBACD,MAAM,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;gBACzD,MAAM;YACR,KAAK,SAAS;gBACZ,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;oBAClB,MAAM,GAAG;wBACP,EAAE,EAAE,KAAK;wBACT,KAAK,EAAE,sCAAsC;wBAC7C,IAAI,EAAE,eAAe;qBACtB,CAAC;oBACF,MAAM;gBACR,CAAC;gBACD,MAAM,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC1D,MAAM;YACR,KAAK,MAAM;gBACT,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;oBAClB,MAAM,GAAG;wBACP,EAAE,EAAE,KAAK;wBACT,KAAK,EAAE,mCAAmC;wBAC1C,IAAI,EAAE,eAAe;qBACtB,CAAC;oBACF,MAAM;gBACR,CAAC;gBACD,MAAM,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;oBAC5B,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,cAAc,EAAE,MAAM,CAAC,cAAc;iBACtC,CAAC,CAAC;gBACH,MAAM;YACR,KAAK,QAAQ;gBACX,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;oBAClB,MAAM,GAAG;wBACP,EAAE,EAAE,KAAK;wBACT,KAAK,EAAE,qCAAqC;wBAC5C,IAAI,EAAE,eAAe;qBACtB,CAAC;oBACF,MAAM;gBACR,CAAC;gBACD,MAAM,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE;oBAC9B,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;oBAC/B,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;oBAC7C,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;iBAC5C,CAAC,CAAC;gBACH,MAAM;YACR;gBACE,MAAM,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QAC9E,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;QACxC,4FAA4F;QAC5F,UAAU,CAAC,qBAAqB,EAAE;YAChC,UAAU;YACV,IAAI,EAAE,UAAU,IAAI,MAAM;YAC1B,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,KAAK;YAC1B,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC;SACrB,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"media.js","sourceRoot":"","sources":["../../../src/mcp/tools/media.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"staged-upload.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/staged-upload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAyBjD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CA8EzF"}
1
+ {"version":3,"file":"staged-upload.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/staged-upload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA0BjD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CA8EzF"}
@@ -5,8 +5,9 @@ import { prefixHostedToolDescription } from '../hosted-metadata.js';
5
5
  const TOOL_DESCRIPTION = `Request a presigned staged upload URL. Pass kind: asset (default), text, or json. Keeps large bytes out of MCP tool args.
6
6
 
7
7
  kind=asset (default) — binary/media:
8
- Raster/video: intake only. After curl, cms_edit_media action=prepare with stagedUploadIds. Do not asset upload --staged those MIME types.
9
- SVG, PDF, Lottie: curl then cms_edit consumeArgs (asset upload --staged).
8
+ Video, and an image master over 4MB: intake is direct (presigned PUT, up to 100MB). The response has intake "direct" and a publicUrl. After curl, cms_edit_media prepare with that https URL (consumeArgs). The stored Contentful asset is the processed pack file, not this source.
9
+ Image at or under 4MB: curl the function URL, then cms_edit_media prepare with stagedUploadIds.
10
+ SVG, PDF, Lottie, and this tool's JSON/text kinds: 4MB function POST. SVG/PDF/Lottie then asset upload --staged.
10
11
 
11
12
  kind=text — HtmlComponent / long Text fields (text/html, text/css, text/javascript, text/plain):
12
13
  After curl: cms_edit ["set", "@cN", "customJs", "--staged", "<uploadId>"] (insert ref + field; consumeArgs is a stub).
@@ -17,7 +18,7 @@ kind=json — page/article JSON (application/json only; Lottie still uses kind=a
17
18
 
18
19
  Workflow:
19
20
  1. Call this tool with fileName, mimeType, kind (asset|text|json, default asset), optional byteLength
20
- 2. Run the returned curlCommand (POST multipart file to uploadUrl)
21
+ 2. Run the returned curlCommand (function route: POST multipart; direct intake: PUT --data-binary)
21
22
  3. Follow consumeArgs (for text, add @ref and fieldId before --staged)
22
23
  4. For pipeline jobs: wait → catalog → import with overrides[].alt, then link and save
23
24
 
@@ -1 +1 @@
1
- {"version":3,"file":"staged-upload.js","sourceRoot":"","sources":["../../../src/mcp/tools/staged-upload.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAE3E,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAEpE,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;+DAoBsC,CAAC;AAEhE,MAAM,UAAU,yBAAyB,CAAC,MAAiB,EAAE,MAAsB;IACjF,MAAM,CAAC,IAAI,CACT,gCAAgC,EAChC,2BAA2B,CAAC,gBAAgB,EAAE,MAAM,CAAC,EACrD;QACE,IAAI,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,qGAAqG,CACtG;QACH,QAAQ,EAAE,CAAC;aACR,MAAM,EAAE;aACR,QAAQ,CAAC,+DAA+D,CAAC;QAC5E,QAAQ,EAAE,CAAC;aACR,MAAM,EAAE;aACR,QAAQ,CAAC,+DAA+D,CAAC;QAC5E,UAAU,EAAE,CAAC;aACV,MAAM,EAAE;aACR,GAAG,EAAE;aACL,QAAQ,EAAE;aACV,QAAQ,EAAE;aACV,QAAQ,CAAC,yDAAyD,CAAC;QACtE,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,gEAAgE,CAAC;KAC9E,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE;QACxD,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC;QACrC,MAAM,KAAK,GAAG,OAAO,EAAE,iBAAiB,CAAC;QAEzC,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACtB,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,EAAE,EAAE,KAAK;4BACT,KAAK,EACH,iFAAiF;yBACpF,CAAC;qBACH;iBACF;aACF,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,yBAAyB,CAAC,KAAK,EAAE;gBACpD,MAAM;gBACN,QAAQ;gBACR,QAAQ;gBACR,UAAU;gBACV,KAAK;gBACL,IAAI;aACL,CAAC,CAAC;YACH,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC;qBAC9C;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;qBACpD;iBACF;aACF,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"staged-upload.js","sourceRoot":"","sources":["../../../src/mcp/tools/staged-upload.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAE3E,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAEpE,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;+DAqBsC,CAAC;AAEhE,MAAM,UAAU,yBAAyB,CAAC,MAAiB,EAAE,MAAsB;IACjF,MAAM,CAAC,IAAI,CACT,gCAAgC,EAChC,2BAA2B,CAAC,gBAAgB,EAAE,MAAM,CAAC,EACrD;QACE,IAAI,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,qGAAqG,CACtG;QACH,QAAQ,EAAE,CAAC;aACR,MAAM,EAAE;aACR,QAAQ,CAAC,+DAA+D,CAAC;QAC5E,QAAQ,EAAE,CAAC;aACR,MAAM,EAAE;aACR,QAAQ,CAAC,+DAA+D,CAAC;QAC5E,UAAU,EAAE,CAAC;aACV,MAAM,EAAE;aACR,GAAG,EAAE;aACL,QAAQ,EAAE;aACV,QAAQ,EAAE;aACV,QAAQ,CAAC,yDAAyD,CAAC;QACtE,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,gEAAgE,CAAC;KAC9E,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE;QACxD,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC;QACrC,MAAM,KAAK,GAAG,OAAO,EAAE,iBAAiB,CAAC;QAEzC,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACtB,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,EAAE,EAAE,KAAK;4BACT,KAAK,EACH,iFAAiF;yBACpF,CAAC;qBACH;iBACF;aACF,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,yBAAyB,CAAC,KAAK,EAAE;gBACpD,MAAM;gBACN,QAAQ;gBACR,QAAQ;gBACR,UAAU;gBACV,KAAK;gBACL,IAAI;aACL,CAAC,CAAC;YACH,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC;qBAC9C;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;qBACpD;iBACF;aACF,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ export interface DirectIntakeParams {
2
+ fileName: string;
3
+ mimeType: string;
4
+ byteLength?: number;
5
+ }
6
+ export interface DirectIntakeResult {
7
+ uploadId: string;
8
+ presignedUrl: string;
9
+ publicUrl: string;
10
+ expiresAt: number;
11
+ maxBytes: number;
12
+ }
13
+ /** Blob pathname for a source that the pipeline fetches. Not a Contentful file name. */
14
+ export declare function intakePathname(uploadId: string, fileName: string): string;
15
+ /** Public URL the media pipeline can GET after the presigned PUT. */
16
+ export declare function publicBlobUrl(storeId: string, pathname: string): string;
17
+ export declare function directIntakeCurl(presignedUrl: string, mimeType: string, fileName: string): string;
18
+ /**
19
+ * Presigned PUT straight to blob storage. The Vercel function never sees the body.
20
+ * Requires `BLOB_READ_WRITE_TOKEN` (or Vercel OIDC plus a blob store) on the cms-edit host.
21
+ */
22
+ export declare function createDirectIntakeUpload(params: DirectIntakeParams): Promise<DirectIntakeResult>;
23
+ //# sourceMappingURL=direct-intake.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"direct-intake.d.ts","sourceRoot":"","sources":["../../src/staged-upload/direct-intake.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAMD,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIzE;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMvE;AAED,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGjG;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,kBAAkB,CAAC,CA4C7B"}
@@ -0,0 +1,71 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { issueSignedToken, parseStoreIdFromDelegationToken, presignUrl } from '@vercel/blob';
3
+ import { DIRECT_INTAKE_MAX_BYTES } from '../contentful/upload-limits.js';
4
+ import { STAGED_UPLOAD_TTL_SECONDS } from './limits.js';
5
+ import { normalizeMimeType } from './mime.js';
6
+ function shellQuote(value) {
7
+ return `'${value.replace(/'/g, `'\\''`)}'`;
8
+ }
9
+ /** Blob pathname for a source that the pipeline fetches. Not a Contentful file name. */
10
+ export function intakePathname(uploadId, fileName) {
11
+ const base = fileName.split(/[/\\]/u).pop() ?? 'upload.bin';
12
+ const safe = base.replace(/[^a-zA-Z0-9._-]/gu, '_').slice(0, 180);
13
+ return `intake/${uploadId}/${safe || 'upload.bin'}`;
14
+ }
15
+ /** Public URL the media pipeline can GET after the presigned PUT. */
16
+ export function publicBlobUrl(storeId, pathname) {
17
+ const path = pathname
18
+ .split('/')
19
+ .map((segment) => encodeURIComponent(segment))
20
+ .join('/');
21
+ return `https://${storeId}.public.blob.vercel-storage.com/${path}`;
22
+ }
23
+ export function directIntakeCurl(presignedUrl, mimeType, fileName) {
24
+ const mime = normalizeMimeType(mimeType) || 'application/octet-stream';
25
+ return `curl -f -X PUT -H ${shellQuote(`Content-Type: ${mime}`)} --data-binary ${shellQuote(`@/path/to/${fileName}`)} ${shellQuote(presignedUrl)}`;
26
+ }
27
+ /**
28
+ * Presigned PUT straight to blob storage. The Vercel function never sees the body.
29
+ * Requires `BLOB_READ_WRITE_TOKEN` (or Vercel OIDC plus a blob store) on the cms-edit host.
30
+ */
31
+ export async function createDirectIntakeUpload(params) {
32
+ const uploadId = randomUUID().replace(/-/g, '');
33
+ const pathname = intakePathname(uploadId, params.fileName);
34
+ const mime = normalizeMimeType(params.mimeType);
35
+ const maxBytes = params.byteLength !== undefined && params.byteLength > 0
36
+ ? Math.min(params.byteLength, DIRECT_INTAKE_MAX_BYTES)
37
+ : DIRECT_INTAKE_MAX_BYTES;
38
+ const validUntil = Date.now() + STAGED_UPLOAD_TTL_SECONDS * 1000;
39
+ let signed;
40
+ try {
41
+ signed = await issueSignedToken({
42
+ pathname,
43
+ operations: ['put'],
44
+ allowedContentTypes: mime ? [mime] : undefined,
45
+ maximumSizeInBytes: maxBytes,
46
+ validUntil,
47
+ });
48
+ }
49
+ catch (err) {
50
+ const message = err instanceof Error ? err.message : String(err);
51
+ throw new Error(`Direct intake could not sign a blob upload (${message}). Set BLOB_READ_WRITE_TOKEN on the cms-edit host. JSON, text, SVG, PDF, and Lottie stay on the 4MB staged POST.`);
52
+ }
53
+ const presigned = await presignUrl(signed, {
54
+ operation: 'put',
55
+ pathname,
56
+ access: 'public',
57
+ allowedContentTypes: mime ? [mime] : undefined,
58
+ maximumSizeInBytes: maxBytes,
59
+ addRandomSuffix: false,
60
+ validUntil,
61
+ });
62
+ const storeId = parseStoreIdFromDelegationToken(signed.delegationToken);
63
+ return {
64
+ uploadId,
65
+ presignedUrl: presigned.presignedUrl,
66
+ publicUrl: publicBlobUrl(storeId, pathname),
67
+ expiresAt: Math.floor(validUntil / 1000),
68
+ maxBytes: DIRECT_INTAKE_MAX_BYTES,
69
+ };
70
+ }
71
+ //# sourceMappingURL=direct-intake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"direct-intake.js","sourceRoot":"","sources":["../../src/staged-upload/direct-intake.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { type StagedUploadKind } from './mime.js';
2
+ /** `function` POSTs through the Vercel route. `direct` is a presigned blob PUT. */
3
+ export type IntakeRoute = 'function' | 'direct';
4
+ export declare function intakeRoute(params: {
5
+ kind: StagedUploadKind;
6
+ mimeType: string;
7
+ fileName: string;
8
+ byteLength?: number;
9
+ }): IntakeRoute;
10
+ export declare function intakeMaxBytes(route: IntakeRoute): number;
11
+ /** Refuse a declared size the chosen route cannot accept. Missing size is checked at upload time. */
12
+ export declare function assertIntakeByteLength(route: IntakeRoute, byteLength: number | undefined): void;
13
+ //# sourceMappingURL=intake-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intake-policy.d.ts","sourceRoot":"","sources":["../../src/staged-upload/intake-policy.ts"],"names":[],"mappings":"AAGA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAErE,mFAAmF;AACnF,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEhD,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,WAAW,CAed;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAEzD;AAED,qGAAqG;AACrG,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAc/F"}
@@ -0,0 +1,37 @@
1
+ import { isVideoUpload, requiresPipelineEncode } from '../contentful/pipeline-encode-gate.js';
2
+ import { DIRECT_INTAKE_MAX_BYTES } from '../contentful/upload-limits.js';
3
+ import { HOSTED_STAGED_MAX_BYTES } from './limits.js';
4
+ import { normalizeMimeType } from './mime.js';
5
+ export function intakeRoute(params) {
6
+ if (params.kind !== 'asset') {
7
+ return 'function';
8
+ }
9
+ const mime = normalizeMimeType(params.mimeType);
10
+ if (isVideoUpload(mime, params.fileName)) {
11
+ return 'direct';
12
+ }
13
+ if (!requiresPipelineEncode(mime, params.fileName)) {
14
+ return 'function';
15
+ }
16
+ if (params.byteLength !== undefined && params.byteLength > HOSTED_STAGED_MAX_BYTES) {
17
+ return 'direct';
18
+ }
19
+ return 'function';
20
+ }
21
+ export function intakeMaxBytes(route) {
22
+ return route === 'direct' ? DIRECT_INTAKE_MAX_BYTES : HOSTED_STAGED_MAX_BYTES;
23
+ }
24
+ /** Refuse a declared size the chosen route cannot accept. Missing size is checked at upload time. */
25
+ export function assertIntakeByteLength(route, byteLength) {
26
+ if (byteLength === undefined) {
27
+ return;
28
+ }
29
+ const max = intakeMaxBytes(route);
30
+ if (byteLength > max) {
31
+ const label = route === 'direct'
32
+ ? `${String(DIRECT_INTAKE_MAX_BYTES / (1024 * 1024))}MB direct intake`
33
+ : `${String(HOSTED_STAGED_MAX_BYTES / (1024 * 1024))}MB hosted staged upload`;
34
+ throw new Error(`Declared byteLength ${String(byteLength)} exceeds the ${label} limit (${String(max)} bytes).`);
35
+ }
36
+ }
37
+ //# sourceMappingURL=intake-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intake-policy.js","sourceRoot":"","sources":["../../src/staged-upload/intake-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC9F,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAyB,MAAM,WAAW,CAAC;AAKrE,MAAM,UAAU,WAAW,CAAC,MAK3B;IACC,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC5B,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,MAAM,IAAI,GAAG,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAChD,IAAI,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzC,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,IAAI,CAAC,sBAAsB,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnD,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,IAAI,MAAM,CAAC,UAAU,GAAG,uBAAuB,EAAE,CAAC;QACnF,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAkB;IAC/C,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,uBAAuB,CAAC;AAChF,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,sBAAsB,CAAC,KAAkB,EAAE,UAA8B;IACvF,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,UAAU,GAAG,GAAG,EAAE,CAAC;QACrB,MAAM,KAAK,GACT,KAAK,KAAK,QAAQ;YAChB,CAAC,CAAC,GAAG,MAAM,CAAC,uBAAuB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,kBAAkB;YACtE,CAAC,CAAC,GAAG,MAAM,CAAC,uBAAuB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,yBAAyB,CAAC;QAClF,MAAM,IAAI,KAAK,CACb,uBAAuB,MAAM,CAAC,UAAU,CAAC,gBAAgB,KAAK,WAAW,MAAM,CAAC,GAAG,CAAC,UAAU,CAC/F,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { type DirectIntakeParams, type DirectIntakeResult } from './direct-intake.js';
1
2
  import { type StagedUploadKind } from './mime.js';
2
3
  import type { StagedUploadStore } from './store.js';
3
4
  import type { StagedUploadRequestResult } from './types.js';
@@ -9,5 +10,8 @@ export interface CreateStagedUploadRequestParams {
9
10
  title?: string;
10
11
  kind?: StagedUploadKind | string;
11
12
  }
12
- export declare function createStagedUploadRequest(store: StagedUploadStore, params: CreateStagedUploadRequestParams): Promise<StagedUploadRequestResult>;
13
+ export interface StagedUploadDeps {
14
+ createDirectIntake?: (params: DirectIntakeParams) => Promise<DirectIntakeResult>;
15
+ }
16
+ export declare function createStagedUploadRequest(store: StagedUploadStore, params: CreateStagedUploadRequestParams, deps?: StagedUploadDeps): Promise<StagedUploadRequestResult>;
13
17
  //# sourceMappingURL=request.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/staged-upload/request.ts"],"names":[],"mappings":"AAIA,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAAC;CAClC;AAgCD,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,+BAA+B,GACtC,OAAO,CAAC,yBAAyB,CAAC,CA6BpC"}
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/staged-upload/request.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EAExB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAkD,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAAC;CAClC;AAMD,MAAM,WAAW,gBAAgB;IAC/B,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;CAClF;AA4BD,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,+BAA+B,EACvC,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,yBAAyB,CAAC,CA0DpC"}
@@ -1,8 +1,10 @@
1
1
  import { requiresPipelineEncode } from '../contentful/pipeline-encode-gate.js';
2
2
  import { getPublicOrigin } from '../hosted/oauth/config.js';
3
+ import { createDirectIntakeUpload, directIntakeCurl, } from './direct-intake.js';
3
4
  import { buildStagedUploadUrl, getUploadHmacSecret } from './hmac.js';
5
+ import { assertIntakeByteLength, intakeRoute } from './intake-policy.js';
4
6
  import { hostedStagedMaxBytes, STAGED_UPLOAD_TTL_SECONDS } from './limits.js';
5
- import { parseStagedUploadKind } from './mime.js';
7
+ import { isAllowedStagedMimeType, parseStagedUploadKind } from './mime.js';
6
8
  function shellQuote(value) {
7
9
  return `'${value.replace(/'/g, `'\\''`)}'`;
8
10
  }
@@ -27,8 +29,34 @@ function buildConsumeArgs(kind, uploadId, params) {
27
29
  ...(params.title ? ['--title', params.title] : []),
28
30
  ];
29
31
  }
30
- export async function createStagedUploadRequest(store, params) {
32
+ export async function createStagedUploadRequest(store, params, deps = {}) {
31
33
  const kind = parseStagedUploadKind(typeof params.kind === 'string' ? params.kind : undefined);
34
+ if (!isAllowedStagedMimeType(params.mimeType, kind)) {
35
+ throw new Error(`MIME type not allowed for staged upload (${kind}): ${params.mimeType}`);
36
+ }
37
+ const route = intakeRoute({
38
+ kind,
39
+ mimeType: params.mimeType,
40
+ fileName: params.fileName,
41
+ byteLength: params.byteLength,
42
+ });
43
+ assertIntakeByteLength(route, params.byteLength);
44
+ if (route === 'direct') {
45
+ await store.assertRateLimit(params.userId);
46
+ const createDirect = deps.createDirectIntake ?? createDirectIntakeUpload;
47
+ const direct = await createDirect(params);
48
+ return {
49
+ uploadId: direct.uploadId,
50
+ uploadUrl: direct.presignedUrl,
51
+ expiresIn: STAGED_UPLOAD_TTL_SECONDS,
52
+ expiresAt: direct.expiresAt,
53
+ maxBytes: direct.maxBytes,
54
+ curlCommand: directIntakeCurl(direct.presignedUrl, params.mimeType, params.fileName),
55
+ consumeArgs: ['media', 'prepare', '--https-urls', direct.publicUrl],
56
+ intake: 'direct',
57
+ publicUrl: direct.publicUrl,
58
+ };
59
+ }
32
60
  const uploadId = await store.createPending({
33
61
  userId: params.userId,
34
62
  fileName: params.fileName,
@@ -50,6 +78,7 @@ export async function createStagedUploadRequest(store, params) {
50
78
  maxBytes: hostedStagedMaxBytes(),
51
79
  curlCommand,
52
80
  consumeArgs,
81
+ intake: 'function',
53
82
  };
54
83
  }
55
84
  //# sourceMappingURL=request.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/staged-upload/request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,qBAAqB,EAAyB,MAAM,WAAW,CAAC;AAazE,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AAC7C,CAAC;AAED,SAAS,gBAAgB,CACvB,IAAsB,EACtB,QAAgB,EAChB,MAAuC;IAEvC,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC;IACD,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,CAAC,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,sBAAsB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;QAC7D,CAAC,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,CAAC;QAChD,CAAC,CAAC;YACE,OAAO;YACP,QAAQ;YACR,UAAU;YACV,QAAQ;YACR,QAAQ;YACR,MAAM,CAAC,QAAQ;YACf,aAAa;YACb,MAAM,CAAC,QAAQ;YACf,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SACnD,CAAC;AACR,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,KAAwB,EACxB,MAAuC;IAEvC,MAAM,IAAI,GAAG,qBAAqB,CAAC,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAE9F,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,aAAa,CAAC;QACzC,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,IAAI;QACJ,UAAU,EAAE,MAAM,CAAC,UAAU;KAC9B,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,mBAAmB,EAAE,CAAC;IACrC,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,yBAAyB,CAAC;IACtE,MAAM,SAAS,GAAG,oBAAoB,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IAErF,MAAM,WAAW,GAAG,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAE7D,MAAM,WAAW,GAAG,sBAAsB,UAAU,CAAC,kBAAkB,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;IAErH,OAAO;QACL,QAAQ;QACR,SAAS;QACT,SAAS,EAAE,yBAAyB;QACpC,SAAS,EAAE,GAAG;QACd,QAAQ,EAAE,oBAAoB,EAAE;QAChC,WAAW;QACX,WAAW;KACZ,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/staged-upload/request.ts"],"names":[],"mappings":"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"}
@@ -20,6 +20,10 @@ export interface StagedUploadRequestResult {
20
20
  maxBytes: number;
21
21
  curlCommand: string;
22
22
  consumeArgs: string[];
23
+ /** `function` POSTs to the cms-edit route. `direct` PUTs to blob storage. */
24
+ intake: 'function' | 'direct';
25
+ /** Set when intake is `direct`. Pass this URL to media prepare after the PUT. */
26
+ publicUrl?: string;
23
27
  }
24
28
  export interface StagedUploadConsumeResult {
25
29
  buffer: Buffer;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/staged-upload/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElD,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,gBAAgB,CAAC;CACxB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/staged-upload/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElD,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,6EAA6E;IAC7E,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC9B,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,gBAAgB,CAAC;CACxB"}
@@ -34,7 +34,7 @@ Normalize a **folder of designer masters** into short-lived **public HTTPS URLs*
34
34
  | Situation | Action |
35
35
  |-----------|--------|
36
36
  | Drive folder of masters | This task with `driveFolderUrl` |
37
- | Staged files (curl after `cms_edit_request_staged_upload`) | This task with `stagedUploadIds` — one prepare for the batch |
37
+ | Staged files (curl after `cms_edit_request_staged_upload`) | This task with `stagedUploadIds` when `intake` is `function`, or `httpsUrls` / `--https-urls` when `intake` is `direct` |
38
38
  | Public HTTPS on allowed hosts | This task with `httpsUrls` |
39
39
  | Single SVG/PDF/Lottie | `task-media-reuse-and-upload` asset upload --staged |
40
40
  | Prior prepare already reached `succeeded` + catalog | **Reuse that jobId** — do not re-prepare |
@@ -61,7 +61,7 @@ Drive:
61
61
  }
62
62
  ```
63
63
 
64
- Staged (after curl POST to each `uploadUrl`):
64
+ Function POST (image at or under 4MB, after curl POST to `uploadUrl`):
65
65
 
66
66
  ```json
67
67
  {
@@ -70,6 +70,15 @@ Staged (after curl POST to each `uploadUrl`):
70
70
  }
71
71
  ```
72
72
 
73
+ Direct (video, or an image over 4MB, after the presigned PUT). Do not POST that file through the function:
74
+
75
+ ```json
76
+ {
77
+ "action": "prepare",
78
+ "httpsUrls": ["https://….public.blob.vercel-storage.com/intake/…/file.mp4"]
79
+ }
80
+ ```
81
+
73
82
  CLI:
74
83
 
75
84
  ```bash
@@ -77,6 +86,7 @@ cms-edit media prepare --drive-folder "https://drive.google.com/drive/folders/
77
86
  --source-names "BusinessCards copy.jpg,ADB_Macbook_HomeLoop.mp4"
78
87
 
79
88
  cms-edit media prepare --staged-ids "id1,id2"
89
+ cms-edit media prepare --https-urls "https://….public.blob.vercel-storage.com/intake/…/file.mp4"
80
90
  ```
81
91
 
82
92
  Expect `{ ok: true, jobId, status: "queued", pollHint }`.
@@ -37,10 +37,42 @@
37
37
  Encode via **`task-media-pipeline-prepare`**. Intake can be:
38
38
 
39
39
  1. A **Drive folder**, or
40
- 2. **Staged upload** (4 MB): `cms_edit_request_staged_upload` → curl → `cms_edit_media` prepare with `stagedUploadIds` (see returned `consumeArgs`), or
40
+ 2. **Staged upload**: `cms_edit_request_staged_upload` → curl → follow `consumeArgs`, or
41
41
  3. **httpsUrls** on pipeline-allowed hosts
42
42
 
43
- Then wait → catalog → **import** with `overrides[].alt`. Do **not** `asset upload --staged/--base64` for jpeg/png/webp/gif/mp4. Do **not** `asset upload --url` for **video** (import only). Raster `--url` only for pipeline ready image hosts.
43
+ Then wait → catalog → **import** with `overrides[].alt` for a **new** asset. Do **not** `asset upload --staged/--base64` for jpeg/png/webp/gif/mp4. Do **not** `asset upload --url` for **video** (import only). Raster `--url` only for pipeline ready image hosts.
44
+
45
+ **Search before upload still applies.** `asset search` (title or file name) first. **Search by concept** is how an editor finds a subject and a tone, including files no page uses yet:
46
+
47
+ ```
48
+ cms_edit ["asset", "search", "--concept", "<subject-concept-id>", "--concept-all", "<tone-concept-id>"]
49
+ ```
50
+
51
+ `--concept` matches any listed id. Repeat `--concept-all` when several concepts are required. The result is id, title, file name, description, concepts. Concept search reads Contentful. It does not use the local index.
52
+
53
+ **Size.** Video source intake is **100MB** via direct-to-storage. An image master over **4MB** uses the same path. JSON, text, SVG, PDF, and Lottie stay on the **4MB** function POST. Pass `byteLength` for an image that might be over 4MB so the tool returns a presigned PUT (`intake: "direct"`) instead of the function URL. Run the curl, then `consumeArgs` (`media prepare --https-urls` of the public intake URL). The asset stored in Contentful is the **processed** file from the pack (`…/packs/…/ready/…`), not the 100MB source. Do not re-encode every asset. Size targets come from the site sizing standard. `asset review` still flags a stored video over **8MB**.
54
+
55
+ **Replace the file on an existing id** when pages already point at it. A second upload leaves those links on the old id.
56
+
57
+ ```
58
+ cms_edit ["asset", "replace", "<asset-id>", "--url", "<processed-ready-url>", "--dry-run"]
59
+ cms_edit ["asset", "replace", "<asset-id>", "--url", "<processed-ready-url>"]
60
+ ```
61
+
62
+ Replace keeps the id and the current file name. Dry-run returns id, file name, old dimensions, new dimensions, and writes nothing. The write is a draft. There is no publish flag. It refuses a replace that would turn an image into a non-image, or the reverse. SVG, PDF, and Lottie may use `--staged` or `--url`. Raster and video must use the processed pack URL.
63
+
64
+ **Title.** `asset set-title <asset-id> "<title>"` sets the asset title. Draft. It does not change the file name. Description stays `asset set-description`, written from the picture.
65
+
66
+ **Concepts are not blog tags.** `taxonomy-from-json` stays the blog importer (`tag` / `tagType`). Do not turn `taxonomyImport` on. Asset concepts are Contentful `metadata.concepts`, assigned by concept id after a person creates the scheme in the Taxonomy manager. The same organisation concepts show in every space. Files do not cross spaces. cms-edit does not create the scheme.
67
+
68
+ ```
69
+ cms_edit ["asset", "concept", "assign", "<asset-id>", "<concept-id>"]
70
+ cms_edit ["asset", "concept", "unassign", "<asset-id>", "<concept-id>"]
71
+ ```
72
+
73
+ A missing concept fails with the id. Do not invent one.
74
+
75
+ **Do not emotion-score headshots, logos, icons, or illustrations.** Tone concepts are for lifestyle photos only. Alt text is still the asset description, one sentence from looking at the picture, not a keyword list and not the file name.
44
76
 
45
77
  Animated GIFs become **MP4** (`gif-converted`) — attach as mute-loop Media / video visual, not `featuredImage`. **Do not** convert the GIF (or PNG) with local ffmpeg first; prepare the original file. If prepare gaps say `staged GET failed 405`, stop and report a cms-edit host bump — do not retry and do not use the Contentful web UI. If they say `staged GET failed 404`, re-stage (POST) and prepare once — the GET route exists.
46
78
 
@@ -48,19 +80,19 @@ Animated GIFs become **MP4** (`gif-converted`) — attach as mute-loop Media / v
48
80
 
49
81
  These may skip the pipeline. On hosted MCP use staged upload then `asset upload --staged`.
50
82
 
51
- ### Hosted MCP (SVG/PDF/Lottie only)
83
+ ### Hosted MCP
52
84
 
53
85
  **Always use staged upload** for ad-hoc binaries. Do not pass base64 in `cms_edit` args — hosted MCP rejects `asset upload --base64`.
54
86
 
55
- 1. `cms_edit_request_staged_upload` with `fileName`, `mimeType`, optional `byteLength`, optional `title`
56
- 2. Run the returned `curlCommand` in a shell (POST file to `uploadUrl`)
57
- 3. Follow `consumeArgs`: **media prepare** for photos/video; **asset upload --staged** for SVG/PDF/Lottie
87
+ 1. `cms_edit_request_staged_upload` with `fileName`, `mimeType`, `byteLength` when the file may be over 4MB, optional `title`
88
+ 2. Run the returned `curlCommand`. `intake: "direct"` (video, or an image over 4MB) is a PUT of the file. `intake: "function"` is a POST to `uploadUrl`. Do not POST a video through the function.
89
+ 3. Follow `consumeArgs`. Direct intake: **media prepare** with `--https-urls` / `httpsUrls`. Function intake of an image at or under 4MB: **media prepare** with `stagedUploadIds`. SVG/PDF/Lottie: **asset upload --staged**.
58
90
 
59
91
  Rules:
60
92
 
61
93
  - Staged upload keeps binary **out of MCP args**
62
- - Hosted limit: **4 MB** per file; **1000 upload URLs per user per hour**; TTL **60 minutes**
63
- - Raster/video staged files stay in Redis until pipeline fetches them (GET); they are not written to CMA
94
+ - Function POST (JSON, text, SVG, PDF, Lottie, and an image at or under 4MB): **4 MB**. Video, and an image master over 4MB: **100MB** presigned PUT to storage, then the media pipeline. **1000 upload URLs per user per hour**; TTL **60 minutes**
95
+ - Direct intake bytes are not stored in Redis. An image at or under 4MB stays in Redis until the pipeline fetches it (GET). Neither path writes the source to CMA
64
96
  - Upload/import alone does **not** attach the asset — follow with **link + save** (below)
65
97
  - Use `--if-exists-by-filename` (with `--file-name`) to avoid duplicate assets when re-running
66
98
 
@@ -1,5 +1,7 @@
1
1
  # Capability: Taxonomy setup
2
2
 
3
+ Blog `tag` / `tagType` / `articleType` entries. This is not Contentful asset concepts (`metadata.concepts`). Do not use `taxonomy-from-json` to label assets. Asset concepts are assigned with `asset concept assign` after a person creates the scheme in the Taxonomy manager.
4
+
3
5
  ## Intent phrases
4
6
 
5
7
  - create tags