@seldonframe/mcp 1.10.1 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "name": "@seldonframe/mcp",
3
- "version": "1.10.1",
3
+ "version": "1.12.0",
4
+ "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.12.0: COMPOSITE-TREE PRIMITIVES — operators can now manifest ANY landing-page section by description (comparison columns, pricing tiers, 'how it works' steps, stats rows, side-by-side image+text, custom CTAs) without server-side type-per-block work. The agent's LLM composes a tree from 12 low-level primitives; the server validates + renders. WHY: typed blocks (hero, services, about, faq, cta) require dedicated SKILL.md + Zod schema + renderer mapping per type — fine for high-stakes surfaces, too rigid for the long tail of operator requests. WHAT SHIPS: (1) New SectionComposite section type — holds a recursive CompositeNode tree. (2) 12 primitive node kinds: containers (section / row / col / card), content leaves (heading / text / image / list / button / stat), special (embed / divider / spacer). embed.ref ∈ {services, faq, testimonials, hours, phone} pulls workspace-data into the section without re-typing. (3) Zod schema + structural validators: tree depth ≤ 4, children-per-container caps (section ≤ 8, row ≤ 4, card ≤ 8, list ≤ 12), heading levels descend without skipping, all text fields trim + length-cap. (4) Theme-aware renderer (~3 KB shared CSS, emitted once per page). Mobile-responsive by default (rows stack on narrow viewports). All operator/LLM-supplied text HTML-escaped on emit (no XSS). External nav buttons get target=_blank rel=noopener. (5) Voice scanner — scans all text-bearing fields against soul.voice.avoidWords; returns warnings (not errors) for the agent to self-correct on retry. (6) 2 new MCP tools: add_composite_section({ workspace_id, tree, position? }) and update_composite_section({ workspace_id, index, tree }). (7) get_block_skill('composite') serves a hand-written SKILL.md with the primitive vocabulary, validation rules, voice guidance, and 7 worked patterns (COMPARISON, STATS, HOW-IT-WORKS, SIDE-BY-SIDE, CALL-CTA, FEATURES-GRID, EMBED-DRIVEN-SERVICES). (8) 45 new unit tests (27 validation + 18 render). DESIGN PRINCIPLES: thin harness (server only validates + renders; no LLM judgment), fat skill (ONE skill file with vocabulary + patterns; agent's LLM does composition), antifragile (as LLMs improve at composing trees, validation_warnings rate drops without harness changes), permissive-on-read/strict-on-write (old persisted trees keep rendering even if schema evolves; new writes must validate). Test count: 2452 (was 2407). Backward compatible. NO migrations, NO env vars, NO new deps. Existing typed blocks (hero/services/etc.) untouched. v1.13 candidate: form primitives (intake field add/move/delete/update). v1.14: composite trees on the customer portal surface with customer-data embed refs.",
5
+ "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.11.0: STRUCTURAL PRIMITIVES — three new tools that cut landing-page restructuring time from minutes to seconds, with index-based addressing that handles duplicate section types. WHY: v1.10's reorder_landing_sections used type-based addressing ('hero', 'services-grid', 'faq', ...) which breaks when types repeat (smoke test 2026-05-05: a workspace had two 'services-grid' sections — one grid-3 services, one stats — and the reorder API correctly refused as ambiguous, but the agent had no way to disambiguate without fetching + PowerShell-parsing landing_pages.blueprintJson, which took 8+ minutes). WHAT SHIPS: (1) get_landing_structure({workspace_id}) — returns ordered sections with index (0..N-1) + type + 1-line preview ('Vancouver's Trusted HVAC Family — Same-Day Service' for hero, '3 services (grid-3)' vs 'stats — 4 numbers' for services-grid duplicates). Cheap one-DB-read. Replaces the v1.10 fetch-and-parse workflow. (2) move_section({workspace_id, from_index, to_index}) — atomic single-section move with splice semantics (section at from_index is removed, then inserted at to_index in the result). Handles duplicate types because index is unambiguous. Existing reorder_landing_sections stays as the bulk path for clean cases. (3) delete_section({workspace_id, index}) — atomic single-section remove. Refuses to leave 0 sections (minimum is 1). Use to clean up unintended duplicates from v2 generation artifacts. (4) 22 new unit tests covering applyMove (forward / backward / no-op / immutability / out-of-range), applyDelete (boundary indices / minimum-1-section guard / immutability), and derivePreview (each section type emits a useful 1-liner; services-grid grid-3 vs stats are distinguishable; long text truncates). DESIGN PRINCIPLES: thin harness (server only does mechanical reorder/delete + re-render; no LLM judgment), fat skill (agent's LLM picks WHICH section based on operator intent + preview text), antifragile (as LLMs improve at intent-mapping, output quality rises with zero harness changes). Test count: 2407 (was 2385). Time-to-restructure: 8 min → ~3 sec. NO migrations, NO env vars, NO new deps. Backward compatible. v1.12 candidate: add_section + new section primitives (comparison block, pricing-table, gallery) — separate larger work.",
4
6
  "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.10.1: HOTFIX — upload_workspace_image now accepts image_url + local_file_path, eliminating the agent-side base64 round-trip that v1.10.0 forced. WHY: v1.10.0's image_data_b64-only design made the agent base64-encode bytes into the JSON tool argument; the encoded string had to fit in the agent's tool-input token budget (~16 KB base64 ≈ ~12 KB raw). A 100 KB logo forced the agent into manual resize loops (verified 2026-05-05 smoke test: 7m 31s for one Cloudinary URL upload — 4 resize iterations before bytes fit). WHAT SHIPS: (1) image_url body field — SF backend fetches the URL directly via fetch + streamed read with running byte counter (5 MB cap, 5s timeout). SSRF guards: HTTPS-only, loopback / RFC1918 private / link-local IPv4 (incl. 169.254.169.254 cloud metadata) / IPv6 ::1 rejected. file_name + content_type auto-derived from URL path extension; falls back to response Content-Type header for extensionless URLs. (2) local_file_path MCP-client branch — reads the file in the npm-package process (which runs on the operator's machine, no agent token budget involved), base64-encodes for transport to the SF backend. file_name + content_type auto-derived from path extension. (3) image_data_b64 unchanged — kept as a backward-compatible fallback for callers that already have bytes in hand. (4) Tool descriptor explicitly ranks the three sources (image_url > local_file_path > image_data_b64) so the agent picks correctly. (5) 18 new unit tests covering URL validation (HTTPS-only enforcement, loopback / RFC1918 / link-local rejection, public IPv4 acceptance, malformed URL rejection), content-type derivation from URL extension (handles query strings), and file-name basename extraction. Test count: 2385 (was 2367). Time-to-upload-a-logo: ~7 min → ~3 sec (>99% reduction). NO migrations, NO new env vars, NO new dependencies. Backward compatible.",
5
7
  "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.10.0: TIER 2 CUSTOMIZE TOOLS — three new operator-driven block-customization tools that respect the thin-harness/fat-skill principle (server only assembles + persists; the IDE agent's LLM does all creative work, so quality compounds as models improve with zero MCP changes). (1) regenerate_block({ workspace_id, block_name, new_instructions? }) — bundles current_props + workspace_summary (business name, industry, services, voice from organizations.soul) + brain_patterns (cross-workspace patterns for this vertical) + customization_history + the operator's new_instructions, returns next_step prose telling the agent: fetch SKILL.md, generate new props, call persist_block with customization: { prompt: <new_instructions> }. Handles first_generation (block never persisted) AND regenerate (iterate from current_props) cases with the same flow. New endpoint GET /api/v1/workspace/v2/blocks/[name]/regenerate. (2) reorder_landing_sections({ workspace_id, new_order: string[] }) — purely mechanical reorder of Blueprint.landing.sections; new_order is the full ordered array of section types ([\"hero\", \"services-grid\", \"about\", \"mid-cta\", \"faq\"]); validates the multiset matches current sections (no add/remove); rejects empty new_order, duplicates, missing types, unknown types. New endpoint POST /api/v1/workspace/v2/landing/reorder. Operator says \"move FAQ to bottom\" → agent computes new_order → server reorders + re-renders. (3) upload_workspace_image({ workspace_id, slot, file_name, content_type, image_data_b64 }) — uploads a base64-encoded image to Vercel Blob and applies the URL to one of two slots: 'logo' (organizations.theme.logoUrl, surfaces in header/footer/og-image) or 'hero_background' (Blueprint.landing.sections[hero].imageUrl + landing re-render). 5 MB max, allowed types image/png|jpeg|webp|svg+xml|gif. Sanitized blob path with random uuid suffix so re-uploads don't overwrite previous URLs. New endpoint POST /api/v1/workspace/v2/images. WHAT SHIPS BESIDES THE TOOLS: 22 new unit tests (6 regenerate context assembly, 6 reorder validation, 11 upload validation + path sanitization). Pure-function-first design — DB integration left for the v1.10+ harness; what's tested here is the security-relevant + correctness-relevant logic that lives outside any DB. Test count: 2366 (was 2344; +22). NO migrations, NO env vars, NO new dependencies (Vercel Blob already used for portal documents). Backend redeploy auto-triggers via main-branch push. v1.11+ candidates: customize_theme advanced presets (palette generation from logo / industry-aware mood), additional image slots (og_image, gallery), section delete/insert (currently reorder is the only structural change supported).",
6
8
  "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.9.0: BEHAVIORAL CONTRACT TESTS — kills the handler-execution-state-drift bug class structurally. Three bugs in 7 days hit the same shape: a server-side function `throw new Error()`s for an edge user/workspace state (no users row, no Stripe customer, free tier, empty form_fields), and the throw cascades through the SSR boundary as 'This page couldn't load.' WHAT SHIPS: (1) static analysis test (tests/unit/contract-no-uncaught-throws.spec.ts) that scans designated directories — lib/billing/**, lib/auth/**, lib/page-blocks/persist.ts, app/(dashboard)/** — for `throw new Error()` patterns. Every throw must EITHER be wrapped in try/catch OR carry a `// contract:throw-ok: <reason>` annotation. CI fails if any throw is unannotated. (2) 34 existing throws audited and annotated with semantic reasons (Stripe API errors, programmer-error sentinels, deployment config, form-submit error UX, etc.). Going forward, every NEW throw added to scoped directories must be annotated explicitly. (3) Behavioral runtime test (tests/unit/contract-booking-form-fields.spec.ts) for the v1.4.2 bug: 5 tests asserting mergeBookingFormFields always prepends standard fullName + email regardless of LLM input — preserves order, dedupes redundant LLM-supplied standards, sets required=true. mergeBookingFormFields exported from lib/page-blocks/persist.ts for testability. (4) docs/CONTRACTS.md documenting the rule, annotation pattern, scoped directories, when NOT to throw, how to add a new contract test. The MCP package itself didn't change in v1.9.0 — version bumps coupled with backend so they redeploy together. Test count: 2344 (was 2338; +1 static check + 5 booking contract tests). v1.10 candidate: integration test harness so we can write contract tests for the v1.7.3 + v1.8.1 fixes (currently documented in CONTRACTS.md but not yet runtime-asserted).",
package/src/tools.js CHANGED
@@ -2723,6 +2723,172 @@ export const TOOLS = [
2723
2723
  },
2724
2724
  },
2725
2725
 
2726
+ {
2727
+ name: "get_landing_structure",
2728
+ description:
2729
+ "Read the workspace's landing-page section list with INDEX as the addressing primitive. Returns each section's index (0..N-1, top-to-bottom on the rendered page), type ('hero', 'services-grid', 'about', 'faq', 'mid-cta', 'trust-strip', 'footer', etc.), and a 1-line preview ('Vancouver's Trusted HVAC Family — Same-Day Service' for hero, '3 services (grid-3)' vs 'stats — 4 numbers' for services-grid duplicates). " +
2730
+ "Use this BEFORE move_section / delete_section so you know which index to target. The preview disambiguates duplicate types (e.g. when a workspace has TWO services-grid sections — one with services, one with stats). " +
2731
+ "v1.11+ replaces the v1.10 workflow where the agent had to fetch landing_pages.blueprintJson manually and parse it client-side. Cheap server-side (one DB read).",
2732
+ inputSchema: obj(
2733
+ { workspace_id: str("Workspace id.") },
2734
+ ["workspace_id"],
2735
+ ),
2736
+ handler: async (args) => {
2737
+ const ws = args.workspace_id;
2738
+ const result = await api("GET", "/workspace/v2/landing/structure", {
2739
+ workspace_id: ws,
2740
+ });
2741
+ return result;
2742
+ },
2743
+ },
2744
+
2745
+ {
2746
+ name: "move_section",
2747
+ description:
2748
+ "Move ONE landing-page section atomically. Identifies sections by INDEX (run get_landing_structure first to find which index to move). Splice semantics: the section at from_index is removed, then inserted at to_index in the resulting array — so to_index is the section's NEW position in the result. " +
2749
+ "Examples: 'put hero below FAQ' → from_index=<hero index>, to_index=<faq's current index>. 'Move services to the top' → from_index=<services index>, to_index=0. " +
2750
+ "Handles duplicate types correctly (the case reorder_landing_sections refused) — index identity is unambiguous even when two services-grid or two mid-cta sections exist. " +
2751
+ "Use reorder_landing_sections instead when you want to express the entire new order at once AND types are unique. Use move_section for single-step moves OR when types repeat.",
2752
+ inputSchema: obj(
2753
+ {
2754
+ workspace_id: str("Workspace id."),
2755
+ from_index: {
2756
+ type: "integer",
2757
+ description:
2758
+ "0-based index of the section to move (from the get_landing_structure response).",
2759
+ minimum: 0,
2760
+ },
2761
+ to_index: {
2762
+ type: "integer",
2763
+ description:
2764
+ "0-based index where the section should END UP in the result. Splice semantics — equivalent to: remove from from_index, then insert at to_index.",
2765
+ minimum: 0,
2766
+ },
2767
+ },
2768
+ ["workspace_id", "from_index", "to_index"],
2769
+ ),
2770
+ handler: async (args) => {
2771
+ const ws = args.workspace_id;
2772
+ const result = await api("POST", "/workspace/v2/landing/move-section", {
2773
+ body: {
2774
+ workspace_id: ws,
2775
+ from_index: args.from_index,
2776
+ to_index: args.to_index,
2777
+ },
2778
+ workspace_id: ws,
2779
+ });
2780
+ return result;
2781
+ },
2782
+ },
2783
+
2784
+ {
2785
+ name: "delete_section",
2786
+ description:
2787
+ "Remove ONE landing-page section atomically. Identifies the section by INDEX (run get_landing_structure first). Refuses to leave 0 sections — minimum is 1 — so you can't accidentally wipe the page. " +
2788
+ "Use when the workspace has a duplicate section type (e.g. two services-grid sections, one of which was an unintended generation artifact) and the operator wants the duplicate gone. Disambiguate WHICH duplicate via the preview text from get_landing_structure (e.g. 'stats — 4 numbers' vs '3 services (grid-3)'). " +
2789
+ "For content edits, use update_landing_section. For replacing a section's content, use persist_block. delete_section is structural — it removes the section from the page entirely.",
2790
+ inputSchema: obj(
2791
+ {
2792
+ workspace_id: str("Workspace id."),
2793
+ index: {
2794
+ type: "integer",
2795
+ description:
2796
+ "0-based index of the section to delete (from get_landing_structure response).",
2797
+ minimum: 0,
2798
+ },
2799
+ },
2800
+ ["workspace_id", "index"],
2801
+ ),
2802
+ handler: async (args) => {
2803
+ const ws = args.workspace_id;
2804
+ const result = await api("POST", "/workspace/v2/landing/delete-section", {
2805
+ body: { workspace_id: ws, index: args.index },
2806
+ workspace_id: ws,
2807
+ });
2808
+ return result;
2809
+ },
2810
+ },
2811
+
2812
+ {
2813
+ name: "add_composite_section",
2814
+ description:
2815
+ "Add a CUSTOM landing-page section by composing low-level primitives (heading / text / image / list / button / card / row / col / stat / embed / divider / spacer) into a tree. Use this when the operator asks for a section type that doesn't fit hero/services/about/faq/mid-cta — e.g. 'a 2-column comparison of us vs DIY', 'a pricing tier section', 'a how-it-works in 4 steps', 'a stats row with 4 numbers', 'a side-by-side image + bullet-list'. " +
2816
+ "The agent's job: read operator intent + the workspace soul (voice, services, brand) and emit a `tree` JSON object. Server validates (Zod schema, depth ≤ 4, children-per-container caps, heading-level descent), voice-scans against soul.voice.avoidWords (warnings, not errors), then renders + persists. " +
2817
+ "Tree root MUST be kind=section. Leaves can include kind=embed with ref ∈ {services, faq, testimonials, hours, phone} to pull workspace-data into the section without re-typing it. Phone embed renders as a tel: link. " +
2818
+ "Pattern library (typical compositions): COMPARISON = section { row{cols:2, [card{heading,list-check}, card{variant:muted, heading,list-x}]}}. STATS = section { row{cols:4, [stat,stat,stat,stat]}}. HOW-IT-WORKS = section { row{cols:4, [card{heading,text}, card{heading,text}, card{heading,text}, card{heading,text}]}}. SIDE-BY-SIDE = section { row{cols:2, [col{image}, col{heading,text,button}]}}. " +
2819
+ "Returns the new section's index, the full sections list with previews, validation_warnings (voice violations the agent should fix on retry), and the public_url. Use position to insert at a specific index (default: append).",
2820
+ inputSchema: obj(
2821
+ {
2822
+ workspace_id: str("Workspace id."),
2823
+ tree: {
2824
+ type: "object",
2825
+ description:
2826
+ "Composite tree root. MUST be kind=section with optional eyebrow/headline/subhead and a children array. See the COMPOSITE_BLOCK_SKILL.md (fetch via get_block_skill('composite')) for the full primitive vocabulary, validation rules, and worked patterns.",
2827
+ additionalProperties: true,
2828
+ },
2829
+ position: {
2830
+ type: "integer",
2831
+ description:
2832
+ "Optional 0-based insert position. Default: appended at the end. Use get_landing_structure to find the right slot first.",
2833
+ minimum: 0,
2834
+ },
2835
+ },
2836
+ ["workspace_id", "tree"],
2837
+ ),
2838
+ handler: async (args) => {
2839
+ const ws = args.workspace_id;
2840
+ const result = await api("POST", "/workspace/v2/landing/composite", {
2841
+ body: {
2842
+ workspace_id: ws,
2843
+ op: "add",
2844
+ tree: args.tree,
2845
+ position: args.position,
2846
+ },
2847
+ workspace_id: ws,
2848
+ });
2849
+ return result;
2850
+ },
2851
+ },
2852
+
2853
+ {
2854
+ name: "update_composite_section",
2855
+ description:
2856
+ "Replace the tree of an EXISTING composite section. Use when the operator asks to refine a custom section you previously created ('shorten the comparison', 'add another stat', 'make the cards muted'). Index must point at a section of type=composite — for typed sections (hero, services, faq, etc.) use update_landing_section, regenerate_block, or persist_block. " +
2857
+ "First call get_landing_structure to find the right index — composite sections show preview text starting with 'composite — <headline>'. Then generate the new tree (typically by reading current_props equivalent — for composite sections this means fetching the existing tree, mutating, and submitting; today the simplest path is to regenerate from scratch using operator instructions + soul). " +
2858
+ "Same validation + voice-scan as add_composite_section. Returns the same payload with index unchanged.",
2859
+ inputSchema: obj(
2860
+ {
2861
+ workspace_id: str("Workspace id."),
2862
+ index: {
2863
+ type: "integer",
2864
+ description:
2865
+ "0-based index of the composite section to update (from get_landing_structure).",
2866
+ minimum: 0,
2867
+ },
2868
+ tree: {
2869
+ type: "object",
2870
+ description:
2871
+ "Replacement tree. Same shape as add_composite_section — kind=section root with children.",
2872
+ additionalProperties: true,
2873
+ },
2874
+ },
2875
+ ["workspace_id", "index", "tree"],
2876
+ ),
2877
+ handler: async (args) => {
2878
+ const ws = args.workspace_id;
2879
+ const result = await api("POST", "/workspace/v2/landing/composite", {
2880
+ body: {
2881
+ workspace_id: ws,
2882
+ op: "update",
2883
+ tree: args.tree,
2884
+ index: args.index,
2885
+ },
2886
+ workspace_id: ws,
2887
+ });
2888
+ return result;
2889
+ },
2890
+ },
2891
+
2726
2892
  {
2727
2893
  name: "reorder_landing_sections",
2728
2894
  description:
package/src/welcome.js CHANGED
@@ -8,7 +8,7 @@
8
8
  // stripped. `create_full_workspace` is the only workspace-creation
9
9
  // path mentioned anywhere in this briefing.
10
10
 
11
- export const VERSION = "1.10.1";
11
+ export const VERSION = "1.12.0";
12
12
 
13
13
  export const WELCOME_MARKDOWN = `# SeldonFrame — create a real Business OS in one conversation
14
14
 
@@ -190,10 +190,28 @@ further natural-language requests ("change the headline to …",
190
190
  block re-generation ("make the hero punchier", "rewrite the FAQ to
191
191
  be less salesy"). Server only assembles context; YOUR LLM does the
192
192
  generation, then call persist_block with \`customization\`.
193
- - **\`reorder_landing_sections\`** (v1.10+) — reorder landing-page
194
- sections without changing content. Pass the full ordered array of
195
- section types; multiset must equal current. For content edits use
196
- update_landing_section; for regeneration use regenerate_block.
193
+ - **\`get_landing_structure\`** (v1.11+) — read the workspace's landing
194
+ section list with INDEX as the addressing primitive + 1-line preview
195
+ per section. Use BEFORE move/delete to find the right index;
196
+ preview text disambiguates duplicate types ('3 services' vs
197
+ 'stats — 4 numbers').
198
+ - **\`move_section\`** (v1.11+) — atomic single-section move by index.
199
+ Splice semantics: from_index → to_index in result. Works even when
200
+ section types repeat (the case reorder_landing_sections refuses).
201
+ - **\`delete_section\`** (v1.11+) — atomic single-section remove by
202
+ index. Refuses to leave 0 sections. Use to clean up unintended
203
+ duplicates.
204
+ - **\`reorder_landing_sections\`** (v1.10+) — bulk reorder by section
205
+ type when types are unique. Pass the full ordered type array. For
206
+ duplicate-type cases use move_section instead.
207
+ - **\`add_composite_section\`** (v1.12+) — manifest ANY landing block
208
+ (comparison, pricing, "how it works," stats, side-by-side, custom
209
+ CTAs) by composing a tree from 12 low-level primitives. Server
210
+ validates + renders. \`get_block_skill('composite')\` returns the
211
+ primitive vocabulary + worked patterns.
212
+ - **\`update_composite_section\`** (v1.12+) — replace the tree of an
213
+ existing composite section by index. Use to refine ('shorten the
214
+ comparison', 'add another stat', 'make the cards muted').
197
215
  - **\`upload_workspace_image\`** (v1.10+, fast path in v1.10.1+) — set
198
216
  the workspace logo (slot=logo → organizations.theme.logoUrl) or hero
199
217
  background (slot=hero_background → Blueprint.landing hero imageUrl
@@ -262,4 +280,4 @@ admin dashboard. Pre-fills their email automatically.
262
280
  <https://seldonframe.com> · **Discord:** <https://discord.gg/sbVUu976NW>
263
281
  `;
264
282
 
265
- export const FIRST_CALL_BANNER = `🚀 SeldonFrame v1.10.1 is connected. PREFERRED workspace creation: create_workspace_v2 → IN PARALLEL for all 7 recommended_blocks (hero, services, about, faq, cta, booking, intake): get_block_skill + persist_block → complete_workspace_v2 → finalize_workspace({ workspace_id, email }). The v2 flow puts YOUR LLM in charge of every operator-facing surface using one SKILL.md per block. Each block's prop schema is server-validated. Run blocks in PARALLEL (Promise.all) — sequential takes 60+ seconds. v1.10+ TIER 2 CUSTOMIZE TOOLS: regenerate_block (re-do one block with operator instructions; thin-harness — server bundles context, your LLM generates), reorder_landing_sections (purely mechanical reorder by section type), upload_workspace_image (set logo/hero_background; v1.10.1+ accepts image_url or local_file_path — DON'T base64 unless you have to, the encoded string eats your tool-call token budget). Every URL is real. NEVER create local files. Skipping finalize_workspace leaves the operator with no admin login.`;
283
+ export const FIRST_CALL_BANNER = `🚀 SeldonFrame v1.12.0 is connected. PREFERRED workspace creation: create_workspace_v2 → IN PARALLEL for all 7 recommended_blocks (hero, services, about, faq, cta, booking, intake): get_block_skill + persist_block → complete_workspace_v2 → finalize_workspace({ workspace_id, email }). The v2 flow puts YOUR LLM in charge of every operator-facing surface using one SKILL.md per block. Each block's prop schema is server-validated. Run blocks in PARALLEL (Promise.all) — sequential takes 60+ seconds. v1.10+ TIER 2 CUSTOMIZE TOOLS: regenerate_block (re-do one block with operator instructions; thin-harness — server bundles context, your LLM generates), upload_workspace_image (set logo/hero_background; v1.10.1+ accepts image_url or local_file_path — DON'T base64 unless you have to, the encoded string eats your tool-call token budget). v1.11+ STRUCTURAL PRIMITIVES: get_landing_structure → move_section → delete_section. INDEX-based, handle duplicate section types, atomic. v1.12+ COMPOSITE TREES: add_composite_section / update_composite_section — manifest ANY block (comparison, pricing, "how it works," stats, custom CTAs) from 12 low-level primitives. Server validates + renders; YOUR LLM composes. Read the SKILL.md via get_block_skill('composite') before composing. Every URL is real. NEVER create local files. Skipping finalize_workspace leaves the operator with no admin login.`;