@clipform/mcp-server 1.48.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -71,7 +71,8 @@ Your MCP client lists these automatically on connect (via `tools/list`). Full re
71
71
  | `clipform_add_node` | Add a new node to an existing form. |
72
72
  | `clipform_update_node` | Update one or more existing nodes' text, type, config, or options. |
73
73
  | `clipform_delete_node` | Delete a node from a form. |
74
- | `clipform_upload_node_media` | Upload media for one or more nodes. |
74
+ | `clipform_upload_media_asset` | Put one or more media files into your workspace media library (max 10, uploaded sequentially). |
75
+ | `clipform_attach_node_media` | Attach an existing workspace media asset (from clipform_upload_media_asset) to one or more nodes (max 10). |
75
76
  | `clipform_get_node_media` | Get the media attached to a node, including processing status. |
76
77
  | `clipform_delete_node_media` | Remove media from a node. |
77
78
  | `clipform_set_logic` | Set routing logic on one or more nodes. |
@@ -84,8 +85,10 @@ Your MCP client lists these automatically on connect (via `tools/list`). Full re
84
85
  | `clipform_render_composition` | Render a specialised video composition to MP4 or PNG - custom animated visuals that clipform_generate_video can't provide, such as geography animations or designed motion graphics. |
85
86
  | `clipform_search_music` | Search for royalty-free music tracks and ambient sounds. |
86
87
  | `clipform_list_compositions` | Browse available video compositions and their expected props schemas. |
88
+ | `clipform_list_video_templates` | Browse available video templates - curated Scene arrangements (bed + overlay + sane defaults) that render through the Scene composition from a small controls object. |
89
+ | `clipform_render_video_template` | Render a curated video template (a pre-arranged Scene: bed + overlay + sane defaults) to MP4 or PNG from a small controls object, instead of hand-assembling Scene layers. |
87
90
  | `clipform_list_assets` | List available creative assets (sound effects, animations, fonts) for video compositions. |
88
- | `clipform_check_render` | Check the status of render jobs started by clipform_generate_video or clipform_render_composition. |
91
+ | `clipform_check_render` | Check the status of render jobs started by clipform_generate_video, clipform_render_video_template, or clipform_render_composition. |
89
92
  | `clipform_fetch_boundary` | Fetch a GeoJSON boundary polygon for a country, city, or region. |
90
93
  | `clipform_get_guide` | Retrieve craft knowledge for building a specific form type. |
91
94
  | `clipform_get_workflow` | Retrieve a step-by-step build workflow for creating a specific form type. |
@@ -3,7 +3,7 @@ import {
3
3
  FORM_TYPE_KEYS,
4
4
  callApi,
5
5
  getSessionContext
6
- } from "./chunk-4CY5EJCR.js";
6
+ } from "./chunk-ET43M6YO.js";
7
7
 
8
8
  // src/lib/guides.ts
9
9
  var guidesPromise = null;
@@ -57,7 +57,7 @@ var GUIDE_DESCRIPTIONS = {
57
57
  var QUIZ_VARIANT_DESCRIPTIONS = {
58
58
  "personality": "Addendum for personality quizzes - category design, option weighting, outcome writing, no right/wrong answers",
59
59
  "comprehension": "Addendum for YouTube comprehension quizzes - extracting questions from transcripts, distractor design, audience adaptation",
60
- "composition": "Addendum for composition quizzes - guess-and-reveal formats built from rendered compositions: mechanic selection, the clue/reveal node pair, difficulty design"
60
+ "composition": "Addendum for composition quizzes - guess formats built from rendered compositions: mechanic selection, the clue clip + answer feedback default (with an optional reveal payoff node), difficulty design"
61
61
  };
62
62
  function getGuideUri(type, variant) {
63
63
  if (type === "quiz" && variant) {
@@ -136,4 +136,4 @@ export {
136
136
  getGuideUri,
137
137
  registerResources
138
138
  };
139
- //# sourceMappingURL=chunk-WUORO245.js.map
139
+ //# sourceMappingURL=chunk-DDBQDDOK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/guides.ts","../src/resources.ts"],"sourcesContent":["import { callApi } from \"./api-client.js\";\n\n/**\n * Craft guide bodies live server-side (#470) - the npm package is a shim\n * that fetches them at runtime instead of bundling them in the tarball.\n * One fetch per process (all ~30KB of guides in a single response), cached;\n * a failed fetch is NOT cached so the next read retries.\n */\n\nexport interface RemoteGuide {\n type: string;\n variant: string | null;\n uri: string;\n mimeType: string;\n text: string;\n}\n\nlet guidesPromise: Promise<Map<string, RemoteGuide>> | null = null;\n\nasync function loadGuides(): Promise<Map<string, RemoteGuide>> {\n const result = await callApi(\"/internal/mcp/guides\", { method: \"GET\" });\n if (!result.ok) {\n throw new Error(`Failed to load guides: ${result.error}`);\n }\n const guides = (result.data as { guides?: RemoteGuide[] }).guides ?? [];\n return new Map(guides.map((g) => [g.uri, g]));\n}\n\n/** Fetch a guide body by its clipform:// URI. Returns null when unavailable. */\nexport async function fetchGuideText(uri: string): Promise<string | null> {\n if (!guidesPromise) {\n guidesPromise = loadGuides();\n }\n try {\n const guides = await guidesPromise;\n return guides.get(uri)?.text ?? null;\n } catch (err) {\n guidesPromise = null; // don't cache failures - retry on the next read\n // Loud, not silent: a misconfigured API_URL/key otherwise degrades every\n // guide to the stub with nothing in the logs.\n console.error(`[guides] Failed to fetch ${uri}: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n}\n\n/** Shown in place of a guide when the API is unreachable or unauthenticated. */\nexport function guideFallbackText(uri: string): string {\n return [\n `# Guide unavailable`,\n ``,\n `The craft guide (${uri}) could not be loaded from the Clipform API.`,\n `Guides are served at runtime and need a reachable API with a valid key -`,\n `check API_URL and CLIPFORM_API_KEY, then try again.`,\n ``,\n `General principles in the meantime: write narration for the ear (short,`,\n `conversational), never reveal answers in narration or media, and keep`,\n `forms tight - every question must earn its place.`,\n ].join(\"\\n\");\n}\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getSessionContext } from \"./lib/session-context.js\";\nimport { FORM_TYPE_KEYS, FORM_TYPES, ALL_VARIANTS } from \"@vid-master/config\";\n\nexport const GUIDE_TYPES = FORM_TYPE_KEYS as readonly string[] as readonly [string, ...string[]];\nexport type GuideType = (typeof FORM_TYPE_KEYS)[number];\n\nexport const QUIZ_VARIANTS = ALL_VARIANTS as readonly string[] as readonly [string, ...string[]];\nexport type QuizVariant = (typeof ALL_VARIANTS)[number];\n// Guide BODIES are not bundled here (#470): the npm tarball ships only this\n// registration metadata. Text is fetched from the API at runtime - see\n// lib/guides.ts (cached per process, stub fallback when unreachable).\nimport { fetchGuideText, guideFallbackText } from \"./lib/guides.js\";\n\nconst GUIDE_DESCRIPTIONS: Record<GuideType, string> = {\n \"quiz\": \"Craft knowledge for writing engaging quizzes - difficulty curves, question psychology, narration style, scoring\",\n \"survey\": \"Craft knowledge for feedback surveys, NPS, and research forms - brevity, rating scales, respondent fatigue\",\n \"interview\": \"Craft knowledge for building interview forms - warm-up pacing, open questions, consent, video responses\",\n \"funnel\": \"Craft knowledge for lead qualification funnels - planned feature, conditional routing coming soon\",\n \"testimonial\": \"Craft knowledge for collecting testimonials and customer stories on video - storytelling prompts, comfort techniques, consent\",\n \"application\": \"Craft knowledge for application and evaluation forms - multi-section structure, video responses for behavioural questions, screening\",\n \"booking\": \"Craft knowledge for event registration and booking forms - minimal friction, video welcome, confirmation flow\",\n};\n\nconst QUIZ_VARIANT_DESCRIPTIONS: Record<QuizVariant, string> = {\n \"personality\": \"Addendum for personality quizzes - category design, option weighting, outcome writing, no right/wrong answers\",\n \"comprehension\": \"Addendum for YouTube comprehension quizzes - extracting questions from transcripts, distractor design, audience adaptation\",\n \"composition\": \"Addendum for composition quizzes - guess formats built from rendered compositions: mechanic selection, the clue clip + answer feedback default (with an optional reveal payoff node), difficulty design\",\n};\n\nexport function getGuideUri(type: GuideType, variant?: QuizVariant): string {\n if (type === \"quiz\" && variant) {\n return `clipform://guides/quiz/${variant}`;\n }\n return `clipform://guides/${type}`;\n}\n\nasync function readGuide(uri: string): Promise<string> {\n return (await fetchGuideText(uri)) ?? guideFallbackText(uri);\n}\n\nexport function registerResources(server: McpServer) {\n for (const type of GUIDE_TYPES) {\n server.registerResource(\n `guide-${type}`,\n getGuideUri(type),\n {\n description: GUIDE_DESCRIPTIONS[type],\n mimeType: \"text/markdown\",\n annotations: { audience: [\"assistant\" as const], priority: 0.8 },\n },\n async () => ({\n contents: [{\n uri: getGuideUri(type),\n mimeType: \"text/markdown\",\n text: await readGuide(getGuideUri(type)),\n }],\n }),\n );\n }\n\n for (const variant of QUIZ_VARIANTS) {\n server.registerResource(\n `guide-quiz-${variant}`,\n getGuideUri(\"quiz\", variant),\n {\n description: QUIZ_VARIANT_DESCRIPTIONS[variant],\n mimeType: \"text/markdown\",\n annotations: { audience: [\"assistant\" as const], priority: 0.8 },\n },\n async () => ({\n contents: [{\n uri: getGuideUri(\"quiz\", variant),\n mimeType: \"text/markdown\",\n text: await readGuide(getGuideUri(\"quiz\", variant)),\n }],\n }),\n );\n }\n\n server.registerResource(\n \"context-session\",\n \"clipform://context/session\",\n {\n description:\n \"Current session info: auth mode, workspace, plan tier, node limits, feature flags. Read this before planning content to know your constraints.\",\n mimeType: \"text/markdown\",\n annotations: { audience: [\"assistant\"], priority: 1.0 },\n },\n async () => {\n const text = await getSessionContext();\n return {\n contents: [\n {\n uri: \"clipform://context/session\",\n mimeType: \"text/markdown\",\n text: text || \"Session context unavailable - API may not be reachable.\",\n },\n ],\n };\n }\n );\n}\n"],"mappings":"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","names":[]}
@@ -112,8 +112,8 @@ var FEATURES = {
112
112
  status: "live",
113
113
  category: "node-types"
114
114
  },
115
- node_contact: {
116
- name: "Contact form",
115
+ node_details: {
116
+ name: "Details",
117
117
  description: "Structured fields (name, email, phone, company) with consent checkboxes",
118
118
  enabled: true,
119
119
  status: "live",
@@ -195,8 +195,9 @@ var FEATURES = {
195
195
  node_file_download: {
196
196
  name: "File download",
197
197
  description: "Provide files for respondents to download",
198
- enabled: false,
199
- status: "planned",
198
+ enabled: true,
199
+ status: "live",
200
+ wentLiveAt: "2026-07-12",
200
201
  category: "node-types"
201
202
  },
202
203
  node_shopping: {
@@ -871,12 +872,12 @@ var NODE_TYPES = {
871
872
  },
872
873
  output_schema: null
873
874
  },
874
- contact: {
875
- label: "Contact Form",
876
- shorthand: "Contact",
875
+ details: {
876
+ label: "Details",
877
+ shorthand: "Details",
877
878
  icon: "User",
878
879
  color: "#CFFAFE",
879
- description: "Collect standardized contact information (name, email, phone, company)",
880
+ description: "Collect several fields on one screen - name, email, phone, address, date, and more",
880
881
  category: "input",
881
882
  sort_order: 5,
882
883
  has_options: false,
@@ -896,9 +897,9 @@ var NODE_TYPES = {
896
897
  { id: "first_name", type: "first_name", label: "First Name", enabled: true, required: true },
897
898
  { id: "email", type: "email", label: "Email", enabled: true, required: true }
898
899
  ],
899
- consent_items: [
900
- { id: "default-consent", name: "Privacy policy", label: "I agree to the privacy policy and terms of service", order: 0, type: "consent" }
901
- ]
900
+ // No agreement out of the box - the builder adds one via "Add agreement"
901
+ // when they actually need consent, so a fresh node ships no orphan checkbox. (#1378)
902
+ consent_items: []
902
903
  },
903
904
  config_schema: {
904
905
  type: "object",
@@ -913,7 +914,7 @@ var NODE_TYPES = {
913
914
  required: ["id", "required"],
914
915
  properties: {
915
916
  id: { type: "string" },
916
- type: { enum: ["text", "textarea", "email", "tel", "url"], type: "string" },
917
+ type: { enum: ["text", "textarea", "email", "tel", "url", "date", "number"], type: "string" },
917
918
  label: { type: "string" },
918
919
  order: { type: "number" },
919
920
  required: { type: "boolean", default: true },
@@ -976,7 +977,7 @@ var NODE_TYPES = {
976
977
  type: "object",
977
978
  required: ["node_type", "fields"],
978
979
  properties: {
979
- node_type: { type: "string", const: "contact" },
980
+ node_type: { type: "string", const: "details" },
980
981
  fields: {
981
982
  type: "array",
982
983
  items: {
@@ -1090,13 +1091,14 @@ var NODE_TYPES = {
1090
1091
  show_nav_bar: true,
1091
1092
  loading: "lazy",
1092
1093
  supports_prompt: false,
1093
- supports_media: false,
1094
+ supports_media: true,
1094
1095
  is_system: false,
1095
1096
  show_in_results: true,
1096
1097
  default_config: {
1097
1098
  paper_color: "#FAF9F6",
1098
1099
  stroke_colors: ["#1F2937", "#EF4444", "#3B82F6", "#22C55E", "#FACC15"],
1099
- stroke_widths: [3, 6, 12]
1100
+ stroke_widths: [3, 6, 12],
1101
+ background: { fit: "contain", opacity: 1 }
1100
1102
  },
1101
1103
  config_schema: {
1102
1104
  type: "object",
@@ -1119,6 +1121,19 @@ var NODE_TYPES = {
1119
1121
  label: "Pen thicknesses",
1120
1122
  default: [3, 6, 12],
1121
1123
  description: "Stroke widths (px) the respondent can choose"
1124
+ },
1125
+ // The reference image itself is the node's attached media (media_id,
1126
+ // via the same media_asset/create-media.ts pipeline every other
1127
+ // supports_media node uses) - not a config field, to avoid a
1128
+ // duplicated asset ref. This only tunes its display/bake.
1129
+ background: {
1130
+ type: "object",
1131
+ label: "Background image",
1132
+ description: "Display settings for the optional reference image behind the drawing canvas",
1133
+ properties: {
1134
+ fit: { type: "string", enum: ["cover", "contain"], default: "contain", label: "Fit", description: "contain letterboxes the image, cover crops it to fill the canvas" },
1135
+ opacity: { type: "number", minimum: 0, maximum: 1, default: 1, label: "Opacity", description: "Background image opacity, from 0 (invisible) to 1 (fully opaque)" }
1136
+ }
1122
1137
  }
1123
1138
  }
1124
1139
  },
@@ -1178,6 +1193,13 @@ var NODE_TYPES = {
1178
1193
  supports_media: false,
1179
1194
  is_system: false,
1180
1195
  show_in_results: true,
1196
+ // Config keys that reference a workspace-scoped connected account (Stripe/etc
1197
+ // via workspace_integrations). The SoT for save_form to strip on cross-workspace
1198
+ // copy (#2064 part 2 - migration, not yet wired). Standing convention for any
1199
+ // future OAuth/connected-account node (booking, shopify), not a payment special
1200
+ // case - the publish gate's own showcase waiver (graph-validation.js) still
1201
+ // names workspace_integration_id directly since it's the only per-type check today.
1202
+ connection_config_keys: ["workspace_integration_id"],
1181
1203
  default_config: { amount: null, currency: "usd", provider: "stripe" },
1182
1204
  config_schema: {
1183
1205
  type: "object",
@@ -1353,7 +1375,7 @@ var NODE_TYPES = {
1353
1375
  color: "#FFEDD5",
1354
1376
  description: "Redirect users to an external URL",
1355
1377
  category: "end",
1356
- sort_order: 101,
1378
+ sort_order: 997,
1357
1379
  has_options: false,
1358
1380
  is_terminal: true,
1359
1381
  show_nav_bar: false,
@@ -1383,7 +1405,7 @@ var NODE_TYPES = {
1383
1405
  color: "#FFEDD5",
1384
1406
  description: "Display a list of links for users to choose from",
1385
1407
  category: "end",
1386
- sort_order: 102,
1408
+ sort_order: 998,
1387
1409
  has_options: false,
1388
1410
  is_terminal: true,
1389
1411
  show_nav_bar: false,
@@ -1428,7 +1450,7 @@ var NODE_TYPES = {
1428
1450
  color: "#E9D5FF",
1429
1451
  description: "Provide a file for respondents to download",
1430
1452
  category: "action",
1431
- sort_order: 103,
1453
+ sort_order: 9,
1432
1454
  has_options: false,
1433
1455
  is_terminal: false,
1434
1456
  show_nav_bar: true,
@@ -1446,7 +1468,8 @@ var NODE_TYPES = {
1446
1468
  items: {
1447
1469
  type: "object",
1448
1470
  properties: {
1449
- file_name: { type: "string", label: "Display file name", required: true },
1471
+ file_name: { type: "string", label: "Original file name", required: true },
1472
+ display_name: { type: "string", label: "Friendly name shown to respondents" },
1450
1473
  file_path: { type: "string", label: "File path in storage", required: true },
1451
1474
  file_size: { type: "number", label: "File size in bytes" },
1452
1475
  mime_type: { type: "string", label: "MIME type" }
@@ -1454,7 +1477,7 @@ var NODE_TYPES = {
1454
1477
  },
1455
1478
  label: "Downloadable files"
1456
1479
  },
1457
- button_text: { type: "string", label: "Button text", default: "Download Files" },
1480
+ button_text: { type: "string", label: "Continue button text", default: "Continue" },
1458
1481
  description: { type: "string", label: "Description text" }
1459
1482
  }
1460
1483
  },
@@ -1477,7 +1500,28 @@ var NODE_TYPES = {
1477
1500
  }
1478
1501
  }
1479
1502
  },
1480
- output_schema: null
1503
+ output_schema: {
1504
+ type: "object",
1505
+ required: ["node_type"],
1506
+ properties: {
1507
+ node_type: { type: "string", const: "file_download" },
1508
+ files: {
1509
+ type: "array",
1510
+ description: "The files the respondent downloaded, each a canonical media object with a fresh signed link.",
1511
+ items: {
1512
+ type: "object",
1513
+ properties: {
1514
+ filename: { type: "string", description: "Original filename" },
1515
+ content_type: { type: "string", description: "File MIME type" },
1516
+ downloaded_at: { type: "string", format: "date-time", description: "Timestamp when download was initiated" },
1517
+ url: { type: "string", format: "uri", description: "Direct, time-limited signed link (~48h, supports HTTP range)" },
1518
+ download_url: { type: "string", format: "uri", description: "Same link with a forced download (Content-Disposition: attachment)" },
1519
+ expires_at: { type: "string", format: "date-time", description: "When url/download_url stop working" }
1520
+ }
1521
+ }
1522
+ }
1523
+ }
1524
+ }
1481
1525
  },
1482
1526
  shopping: {
1483
1527
  label: "Shopping",
@@ -1622,6 +1666,9 @@ for (const [type, def] of Object.entries(NODE_TYPES)) {
1622
1666
  var NODE_TYPES_LIST = Object.entries(NODE_TYPES).map(([type, def]) => ({ type, ...def })).sort((a, b) => a.sort_order - b.sort_order);
1623
1667
  var ACTIVE_NODE_TYPES = Object.entries(NODE_TYPES).filter(([, def]) => def.is_active && !def.is_system).map(([type]) => type);
1624
1668
  var NODE_TYPE_KEYS = Object.keys(NODE_TYPES);
1669
+ var CONNECTION_CONFIG_KEYS = Object.fromEntries(
1670
+ Object.entries(NODE_TYPES).filter(([, def]) => Array.isArray(def.connection_config_keys) && def.connection_config_keys.length > 0).map(([type, def]) => [type, def.connection_config_keys])
1671
+ );
1625
1672
  var RESPONSE_SCHEMAS = Object.fromEntries(
1626
1673
  Object.entries(NODE_TYPES).map(([k, v]) => [k, v.response_schema])
1627
1674
  );
@@ -1681,7 +1728,7 @@ var FORM_TYPES = {
1681
1728
  ],
1682
1729
  composition: [
1683
1730
  { key: "topic", question: "What topic? (flags, movies, geography, music...)", fallback: "General trivia", mode: "all" },
1684
- { key: "question_count", question: "How many questions? (composition quizzes use ~2 nodes per question - 5-6 is the sweet spot)", fallback: "5", mode: "all" }
1731
+ { key: "question_count", question: "How many questions? (composition quizzes use ~1 node per question, plus one more for any optional reveal beat - 5-6 is the sweet spot)", fallback: "5", mode: "all" }
1685
1732
  ]
1686
1733
  },
1687
1734
  variant_guide_uris: {
@@ -1692,7 +1739,7 @@ var FORM_TYPES = {
1692
1739
  variant_descriptions: {
1693
1740
  personality: "Addendum for personality quizzes - category design, option weighting, outcome writing, no right/wrong answers",
1694
1741
  comprehension: "Addendum for YouTube comprehension quizzes - extracting questions from transcripts, distractor design, audience adaptation",
1695
- composition: "Addendum for composition quizzes - guess-and-reveal formats built from rendered compositions: mechanic selection, the clue/reveal node pair, difficulty design"
1742
+ composition: "Addendum for composition quizzes - guess formats built from rendered compositions: mechanic selection, the clue clip + answer feedback default (with an optional reveal payoff node), difficulty design"
1696
1743
  },
1697
1744
  prompt: { name: "create-quiz", title: "Create a Quiz", description: "Build a scored knowledge quiz with narrated video questions" }
1698
1745
  },
@@ -1725,7 +1772,7 @@ var FORM_TYPES = {
1725
1772
  aliases: ["case-study", "callout"],
1726
1773
  variants: [],
1727
1774
  required_features: [],
1728
- required_node_types: ["open", "contact", "end_screen"],
1775
+ required_node_types: ["open", "details", "end_screen"],
1729
1776
  default_media_style: "video",
1730
1777
  guide_uri: "clipform://guides/interview",
1731
1778
  guide_description: "Craft knowledge for building interview forms - warm-up pacing, open questions, consent, video responses",
@@ -1747,7 +1794,7 @@ var FORM_TYPES = {
1747
1794
  aliases: ["lead-gen", "qualification", "lead-magnet"],
1748
1795
  variants: [],
1749
1796
  required_features: ["branching", "funnel"],
1750
- required_node_types: ["choice", "contact", "end_screen"],
1797
+ required_node_types: ["choice", "details", "end_screen"],
1751
1798
  default_media_style: "text",
1752
1799
  guide_uri: "clipform://guides/funnel",
1753
1800
  guide_description: "Craft knowledge for lead qualification funnels - planned feature, conditional routing coming soon",
@@ -1765,7 +1812,7 @@ var FORM_TYPES = {
1765
1812
  aliases: ["story", "review"],
1766
1813
  variants: [],
1767
1814
  required_features: [],
1768
- required_node_types: ["open", "contact", "end_screen"],
1815
+ required_node_types: ["open", "details", "end_screen"],
1769
1816
  default_media_style: "video",
1770
1817
  guide_uri: "clipform://guides/testimonial",
1771
1818
  guide_description: "Craft knowledge for collecting testimonials and customer stories on video - storytelling prompts, comfort techniques, consent",
@@ -1786,7 +1833,7 @@ var FORM_TYPES = {
1786
1833
  aliases: ["job-application", "admission", "enrollment", "grant"],
1787
1834
  variants: [],
1788
1835
  required_features: [],
1789
- required_node_types: ["open", "choice", "contact", "end_screen"],
1836
+ required_node_types: ["open", "choice", "details", "end_screen"],
1790
1837
  default_media_style: "video",
1791
1838
  guide_uri: "clipform://guides/application",
1792
1839
  guide_description: "Craft knowledge for application and evaluation forms - multi-section structure, video responses for behavioural questions, screening",
@@ -1806,7 +1853,7 @@ var FORM_TYPES = {
1806
1853
  aliases: ["registration", "signup", "event", "rsvp", "workshop"],
1807
1854
  variants: [],
1808
1855
  required_features: [],
1809
- required_node_types: ["contact", "choice", "end_screen"],
1856
+ required_node_types: ["details", "choice", "end_screen"],
1810
1857
  default_media_style: "text",
1811
1858
  guide_uri: "clipform://guides/booking",
1812
1859
  guide_description: "Craft knowledge for event registration and booking forms - minimal friction, video welcome, confirmation flow",
@@ -1943,7 +1990,8 @@ var BUSINESS = {
1943
1990
  email: {
1944
1991
  support: "support@clipform.io",
1945
1992
  sales: "sales@clipform.io",
1946
- hello: "hello@clipform.io"
1993
+ hello: "hello@clipform.io",
1994
+ founder: "andy@clipform.io"
1947
1995
  },
1948
1996
  urls: getUrls()
1949
1997
  };
@@ -2001,37 +2049,33 @@ var SLIDESHOW_TRANSITIONS = [
2001
2049
  ];
2002
2050
  var COMPOSITION_REGISTRY = [
2003
2051
  // Beds (full-bleed backgrounds)
2004
- { id: "MediaSlideshow", tier: "public", kind: "bed", scene_layer: true },
2052
+ { id: "Slideshow", tier: "public", kind: "bed", scene_layer: true },
2005
2053
  { id: "Map", tier: "public", kind: "bed", scene_layer: true },
2006
2054
  { id: "Grid", tier: "public", kind: "bed", scene_layer: true },
2007
- { id: "ObscuredReveal", tier: "public", kind: "bed" },
2008
- { id: "Timeline", tier: "public", kind: "bed" },
2009
- { id: "NumberLine", tier: "public", kind: "bed" },
2010
- { id: "CountdownRing", tier: "public", kind: "bed" },
2011
- { id: "CountrySilhouetteClip", tier: "public", kind: "bed", scene_layer: true },
2012
- { id: "FlagReveal", tier: "public", kind: "bed" },
2013
- { id: "ColorCards", tier: "public", kind: "bed", scene_layer: true },
2014
- { id: "TitleCard", tier: "public", kind: "bed" },
2015
- { id: "ParallaxImage", tier: "public", kind: "bed" },
2016
- { id: "Spin3D", tier: "labs", kind: "bed", scene_layer: true },
2055
+ { id: "Timeline", tier: "labs", kind: "bed" },
2056
+ { id: "CountrySilhouetteClip", tier: "labs", kind: "bed", scene_layer: true },
2017
2057
  // Overlays (assets layered on a bed)
2018
- { id: "TextReveal", tier: "public", kind: "overlay", scene_layer: true },
2019
- { id: "EmojiPuzzle", tier: "public", kind: "overlay", scene_layer: true },
2020
- { id: "BeforeAfter", tier: "public", kind: "overlay", scene_layer: true },
2021
- { id: "StatCounter", tier: "labs", kind: "overlay", scene_layer: true },
2022
- { id: "ListReveal", tier: "labs", kind: "overlay", scene_layer: true },
2058
+ { id: "Text", tier: "labs", kind: "overlay", scene_layer: true },
2059
+ { id: "Rebus", tier: "labs", kind: "overlay", scene_layer: true },
2023
2060
  // Container
2024
2061
  { id: "Scene", tier: "labs", kind: "container" },
2025
2062
  // Social marketing templates (gated)
2026
- { id: "ShortFormQuiz", tier: "social", kind: "template" },
2027
- { id: "ScorecardQuiz", tier: "social", kind: "template" },
2028
- { id: "ScorecardGK", tier: "social", kind: "template" },
2029
- { id: "CountrySilhouetteQuiz", tier: "social", kind: "template" }
2063
+ { id: "ShortFormQuiz", tier: "social", kind: "video" },
2064
+ { id: "ScorecardQuiz", tier: "social", kind: "video" },
2065
+ { id: "ScorecardGK", tier: "social", kind: "video" },
2066
+ { id: "CountrySilhouetteQuiz", tier: "social", kind: "video" }
2030
2067
  ];
2031
2068
  var compositionIdsByTier = (tier) => COMPOSITION_REGISTRY.filter((c) => c.tier === tier).map((c) => c.id);
2032
2069
  var PUBLIC_COMPOSITION_IDS = compositionIdsByTier("public");
2033
2070
  var LABS_COMPOSITION_IDS = compositionIdsByTier("labs");
2034
2071
  var SOCIAL_COMPOSITION_IDS = compositionIdsByTier("social");
2072
+ function exposedCompositionIds(includeNonPublic) {
2073
+ return [
2074
+ ...PUBLIC_COMPOSITION_IDS,
2075
+ ...includeNonPublic ? LABS_COMPOSITION_IDS : [],
2076
+ ...includeNonPublic ? SOCIAL_COMPOSITION_IDS : []
2077
+ ];
2078
+ }
2035
2079
  var SCENE_LAYER_COMPOSITION_IDS = COMPOSITION_REGISTRY.filter((c) => c.scene_layer).map((c) => c.id);
2036
2080
  var MCP_TOOL_TIERS = {};
2037
2081
 
@@ -2257,9 +2301,7 @@ export {
2257
2301
  KEN_BURNS_PRESETS,
2258
2302
  KEN_BURNS_FIT_MODES,
2259
2303
  SLIDESHOW_TRANSITIONS,
2260
- PUBLIC_COMPOSITION_IDS,
2261
- LABS_COMPOSITION_IDS,
2262
- SOCIAL_COMPOSITION_IDS,
2304
+ exposedCompositionIds,
2263
2305
  MCP_TOOL_TIERS,
2264
2306
  setMcpVersion,
2265
2307
  callApi,
@@ -2269,4 +2311,4 @@ export {
2269
2311
  getSessionContextWithAuth,
2270
2312
  getSessionContext
2271
2313
  };
2272
- //# sourceMappingURL=chunk-4CY5EJCR.js.map
2314
+ //# sourceMappingURL=chunk-ET43M6YO.js.map