feat(website): M5 — AI tools + starter templates

Two things:

1. AI tools (9) in the website module — writes go through the standard
   proposal flow, reads run auto during planning.
   - shared-ai/src/tools/schemas.ts: AI_TOOL_CATALOG entries with
     defaultPolicy propose/auto.
   - webapp modules/website/tools.ts: execute functions wired to the
     existing stores. ModuleTool[] registered in data/tools/init.ts.
   - Propose: create_website, apply_website_template, create_website_page,
     add_website_block, update_website_block, publish_website
   - Auto: list_websites, list_website_pages, list_website_blocks
   Server-side mana-tool-registry integration (mana-mcp, mana-ai) is
   a M5.x follow-up — webapp flow unblocks the missions-based use case.

2. Starter templates — clone into a fresh site with new UUIDs.
   - templates/types.ts: SiteTemplate shape with localId / parentLocalId
     so container→child references survive the clone.
   - 4 templates: portfolio (4 pages), personal-linktree (1 page, 6 CTAs),
     event (3 pages incl. RSVP form), blank (1 empty page). Deferred:
     smb-corporate + product-landing (need team/pricing/testimonials
     blocks, M6+).
   - sitesStore.applyTemplate: walks template, bulk-inserts new rows,
     remaps parent refs. Sets navConfig items from template pages.
   - TemplatePicker component + /website/new route. Replaces the old
     quick-create modal; ListView now links to /new. AppRegistry
     context-menu action points there too.

AiProposalInbox integration deferred — the component doesn't exist in
the webapp yet (the plan mentions it aspirationally). defaultPolicy
'propose' is already set so writes stage correctly once the UI catches
up.

Validation:
- pnpm run validate:all: 6/6 gates green
- pnpm run check (web): 0 errors, 0 warnings
- apps/api + packages/shared-ai type-check: green

Plan: docs/plans/website-builder.md (M5 shipped)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-23 15:14:45 +02:00
parent 3edf680ea0
commit 13efae8cd2
14 changed files with 1486 additions and 235 deletions

View file

@ -1518,6 +1518,159 @@ export const AI_TOOL_CATALOG: readonly ToolSchema[] = [
},
],
},
// ── Website ───────────────────────────────────────────────
{
name: 'create_website',
module: 'website',
description:
'Erstellt eine neue Website im aktiven Space mit einer Startseite. Gibt siteId und homePageId zurueck.',
defaultPolicy: 'propose',
parameters: [
{
name: 'name',
type: 'string',
description: 'Anzeigename der Website',
required: true,
},
{
name: 'slug',
type: 'string',
description: '2-40 Kleinbuchstaben/Zahlen/Bindestrich — wird Teil der URL (/s/{slug})',
required: true,
},
],
},
{
name: 'apply_website_template',
module: 'website',
description:
'Erstellt eine neue Website aus einem Template (portfolio, personal-linktree, event, blank). Kopiert alle Seiten und Bloecke mit neuen IDs.',
defaultPolicy: 'propose',
parameters: [
{
name: 'templateId',
type: 'string',
description: 'Template-Kennung',
required: true,
enum: ['portfolio', 'personal-linktree', 'event', 'blank'],
},
{ name: 'name', type: 'string', description: 'Website-Name', required: true },
{ name: 'slug', type: 'string', description: 'URL-Slug', required: true },
],
},
{
name: 'create_website_page',
module: 'website',
description:
'Fuegt einer existierenden Website eine neue Seite hinzu (z.B. /ueber-uns, /kontakt).',
defaultPolicy: 'propose',
parameters: [
{ name: 'siteId', type: 'string', description: 'ID der Website', required: true },
{
name: 'path',
type: 'string',
description: 'URL-Pfad mit fuehrendem Slash (z.B. /ueber-uns)',
required: true,
},
{ name: 'title', type: 'string', description: 'Seitentitel', required: true },
],
},
{
name: 'add_website_block',
module: 'website',
description:
'Fuegt einer Seite einen neuen Block hinzu. Block-Typen: hero, richText, cta, image, gallery, faq, form, moduleEmbed, columns, spacer. `props` ist ein JSON-Objekt mit den typ-spezifischen Feldern.',
defaultPolicy: 'propose',
parameters: [
{ name: 'pageId', type: 'string', description: 'ID der Zielseite', required: true },
{
name: 'type',
type: 'string',
description: 'Block-Typ',
required: true,
enum: [
'hero',
'richText',
'cta',
'image',
'gallery',
'faq',
'form',
'moduleEmbed',
'columns',
'spacer',
],
},
{
name: 'props',
type: 'object',
description:
'Typ-spezifische Eigenschaften. Leer = verwendet die Defaults. Beispiel fuer hero: { title, subtitle, ctaLabel, ctaHref }.',
required: false,
},
{
name: 'parentBlockId',
type: 'string',
description:
'Falls der Block in einem Container liegt (z.B. columns), die ID des Containers.',
required: false,
},
{
name: 'slotKey',
type: 'string',
description: 'Slot-Key innerhalb des Containers, z.B. col-0 / col-1.',
required: false,
},
],
},
{
name: 'update_website_block',
module: 'website',
description:
'Aktualisiert die props eines Blocks. `patch` ist ein JSON-Objekt mit nur den zu aendernden Feldern — alles andere bleibt unveraendert.',
defaultPolicy: 'propose',
parameters: [
{ name: 'blockId', type: 'string', description: 'ID des Blocks', required: true },
{
name: 'patch',
type: 'object',
description: 'Die zu aendernden props-Felder',
required: true,
},
],
},
{
name: 'publish_website',
module: 'website',
description:
'Veroeffentlicht die aktuelle Draft-Version der Website unter /s/{slug}. Vorher sollte der Inhalt vom Nutzer geprueft werden.',
defaultPolicy: 'propose',
parameters: [{ name: 'siteId', type: 'string', description: 'ID der Website', required: true }],
},
{
name: 'list_websites',
module: 'website',
description:
'Listet alle Websites im aktiven Space (id, slug, name, published-Status). Auto-Policy: laeuft waehrend Planning.',
defaultPolicy: 'auto',
parameters: [],
},
{
name: 'list_website_pages',
module: 'website',
description: 'Listet die Seiten einer Website (id, path, title, order). Auto-Policy.',
defaultPolicy: 'auto',
parameters: [{ name: 'siteId', type: 'string', description: 'ID der Website', required: true }],
},
{
name: 'list_website_blocks',
module: 'website',
description:
'Listet die Bloecke einer Seite (id, type, parentBlockId, order, props-Snapshot). Auto-Policy.',
defaultPolicy: 'auto',
parameters: [{ name: 'pageId', type: 'string', description: 'ID der Seite', required: true }],
},
];
// ═══════════════════════════════════════════════════════════════