From 72417bf62e97481b6956568b8f31e977f26cf534 Mon Sep 17 00:00:00 2001 From: Rob Macrae Date: Sat, 18 Jul 2026 00:13:27 +0100 Subject: [PATCH 1/2] feat(desktop): seed curated starter templates on a fresh DB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Desktop starts with an empty DB, so the dashboard picker fell back to hardcoded placeholder cards that all created a blank dashboard (templates are otherwise a cloud-only, user-submitted feature). Seed three real starter templates β€” Agentic Coding (Claude Code terminal + intro note), Automation (shell + note), Documentation (note + todo) β€” from /init-db. Gated to desktop (SURFACE_TOKEN is set only by the desktop app) and seeded once per DB via a schema_migrations marker, so it's a no-op on cloud and a user who deletes them isn't re-seeded. Verified live: GET /templates returns the three and creating a dashboard from one populates the real blocks (not blank). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_014t8Ukp4NPWtqJ55X261wMZ --- controlplane/src/index.ts | 2 + controlplane/src/templates/handler.ts | 114 ++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/controlplane/src/index.ts b/controlplane/src/index.ts index 4f7e530a..15a1608c 100644 --- a/controlplane/src/index.ts +++ b/controlplane/src/index.ts @@ -915,6 +915,8 @@ async function handleRequest(request: Request, env: EnvWithBindings, ctx: Pick = [ + { + id: 'starter-agentic-coding', + name: 'Agentic Coding', + description: 'A Claude Code terminal, ready to build.', + category: 'coding', + items: [ + { + placeholderId: 'note', + type: 'note' as DashboardItemType, + content: + 'πŸ‘‹ Welcome to Orcabot\n\nThe terminal runs Claude Code β€” type what you want built and it writes the code for you.\n\nTip: add a GitHub or Google Drive block from the οΌ‹ menu and draw an edge to the terminal to give the agent your files.', + position: { x: 48, y: 48 }, + size: { width: 320, height: 220 }, + }, + { + placeholderId: 'claude', + type: 'terminal' as DashboardItemType, + content: JSON.stringify({ name: 'Claude Code', subagentIds: [], skillIds: [], agentic: true, bootCommand: 'claude' }), + position: { x: 420, y: 48 }, + size: { width: 560, height: 440 }, + }, + ], + }, + { + id: 'starter-automation', + name: 'Automation', + description: 'A shell terminal to wire into schedules and messaging.', + category: 'automation', + items: [ + { + placeholderId: 'note', + type: 'note' as DashboardItemType, + content: + 'βš™οΈ Automation starter\n\nThis is a plain shell terminal. Add a Schedule block from the οΌ‹ menu and connect it to run commands on a timer, or wire a messaging block to trigger the terminal on an incoming message.', + position: { x: 48, y: 48 }, + size: { width: 320, height: 220 }, + }, + { + placeholderId: 'shell', + type: 'terminal' as DashboardItemType, + content: JSON.stringify({ name: 'Shell', subagentIds: [], skillIds: [], agentic: false, bootCommand: '' }), + position: { x: 420, y: 48 }, + size: { width: 560, height: 440 }, + }, + ], + }, + { + id: 'starter-documentation', + name: 'Documentation', + description: 'Notes and a todo list to plan your work.', + category: 'documentation', + items: [ + { + placeholderId: 'title', + type: 'note' as DashboardItemType, + content: 'πŸ“ Project Notes\n\nJot ideas, links, and context here. Double-click to edit.', + position: { x: 48, y: 48 }, + size: { width: 320, height: 180 }, + }, + { + placeholderId: 'todo', + type: 'todo' as DashboardItemType, + content: '[]', + position: { x: 420, y: 48 }, + size: { width: 300, height: 260 }, + }, + ], + }, +]; + +/** + * Seed the curated starter templates into a fresh desktop DB. Desktop only + * (SURFACE_TOKEN is set only by the desktop app) and once per DB (a schema + * marker, so a user who deletes them isn't re-seeded). Called from /init-db. + */ +export async function seedStarterTemplates(env: Env): Promise { + if (!env.SURFACE_TOKEN) return; + await ensureTemplateColumns(env); + + const marker = 'seed_desktop_starter_templates_v1'; + const already = await env.DB.prepare( + `SELECT 1 FROM schema_migrations WHERE name = ?` + ).bind(marker).first(); + if (already) return; + + const now = new Date().toISOString(); + for (const t of STARTER_TEMPLATES) { + await env.DB.prepare( + `INSERT INTO dashboard_templates + (id, name, description, category, author_id, author_name, + items_json, edges_json, item_count, is_featured, status, created_at, updated_at) + VALUES (?, ?, ?, ?, 'orcabot', 'Orcabot', ?, '[]', ?, 1, 'approved', ?, ?) + ON CONFLICT(id) DO NOTHING` + ).bind( + t.id, t.name, t.description, t.category, + JSON.stringify(t.items), t.items.length, now, now + ).run(); + } + await env.DB.prepare( + `INSERT INTO schema_migrations (name) VALUES (?)` + ).bind(marker).run(); +} + /** * Dashboard template (summary for listing) */ From d6ff6d22bed76097506478874b235fa2c3930db6 Mon Sep 17 00:00:00 2001 From: Rob Macrae Date: Sat, 18 Jul 2026 00:36:42 +0100 Subject: [PATCH 2/2] feat(desktop): seed the three curated prod templates as starters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the hand-made starter templates with verbatim copies of the curated prod templates β€” Council of Experts, Code and Review, Agents Chess β€” pulled from the production D1. Their blocks/edges/viewport live in starter-templates.json; seedStarterTemplates now inserts from it (bumped to the v2 marker, which also drops the old hand-made starter ids so an already-seeded desktop DB is upgraded). Verified live: /init-db seeds the three; creating a dashboard from Council of Experts populates the real promptβ†’multi-LLM layout with edges + viewport. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_014t8Ukp4NPWtqJ55X261wMZ --- controlplane/src/templates/handler.ts | 102 +---- .../src/templates/starter-templates.json | 353 ++++++++++++++++++ 2 files changed, 370 insertions(+), 85 deletions(-) create mode 100644 controlplane/src/templates/starter-templates.json diff --git a/controlplane/src/templates/handler.ts b/controlplane/src/templates/handler.ts index 910c7f23..4a3e49d7 100644 --- a/controlplane/src/templates/handler.ts +++ b/controlplane/src/templates/handler.ts @@ -11,6 +11,7 @@ import type { Env } from '../types'; import { scrubItemContent, type DashboardItemType } from './scrubber'; +import starterTemplates from './starter-templates.json'; /** * Ensure the status and viewport_json columns exist on dashboard_templates. @@ -59,86 +60,9 @@ export interface TemplateEdge { targetHandle?: string; } -// Curated starter templates for a fresh desktop DB (the cloud has real -// user-submitted templates; desktop starts empty, so the picker showed only -// blank-dashboard placeholders). Note content is plain text; terminal content is -// the stringified terminal config the frontend uses. -const STARTER_TEMPLATES: Array<{ - id: string; - name: string; - description: string; - category: 'coding' | 'automation' | 'documentation'; - items: TemplateItem[]; -}> = [ - { - id: 'starter-agentic-coding', - name: 'Agentic Coding', - description: 'A Claude Code terminal, ready to build.', - category: 'coding', - items: [ - { - placeholderId: 'note', - type: 'note' as DashboardItemType, - content: - 'πŸ‘‹ Welcome to Orcabot\n\nThe terminal runs Claude Code β€” type what you want built and it writes the code for you.\n\nTip: add a GitHub or Google Drive block from the οΌ‹ menu and draw an edge to the terminal to give the agent your files.', - position: { x: 48, y: 48 }, - size: { width: 320, height: 220 }, - }, - { - placeholderId: 'claude', - type: 'terminal' as DashboardItemType, - content: JSON.stringify({ name: 'Claude Code', subagentIds: [], skillIds: [], agentic: true, bootCommand: 'claude' }), - position: { x: 420, y: 48 }, - size: { width: 560, height: 440 }, - }, - ], - }, - { - id: 'starter-automation', - name: 'Automation', - description: 'A shell terminal to wire into schedules and messaging.', - category: 'automation', - items: [ - { - placeholderId: 'note', - type: 'note' as DashboardItemType, - content: - 'βš™οΈ Automation starter\n\nThis is a plain shell terminal. Add a Schedule block from the οΌ‹ menu and connect it to run commands on a timer, or wire a messaging block to trigger the terminal on an incoming message.', - position: { x: 48, y: 48 }, - size: { width: 320, height: 220 }, - }, - { - placeholderId: 'shell', - type: 'terminal' as DashboardItemType, - content: JSON.stringify({ name: 'Shell', subagentIds: [], skillIds: [], agentic: false, bootCommand: '' }), - position: { x: 420, y: 48 }, - size: { width: 560, height: 440 }, - }, - ], - }, - { - id: 'starter-documentation', - name: 'Documentation', - description: 'Notes and a todo list to plan your work.', - category: 'documentation', - items: [ - { - placeholderId: 'title', - type: 'note' as DashboardItemType, - content: 'πŸ“ Project Notes\n\nJot ideas, links, and context here. Double-click to edit.', - position: { x: 48, y: 48 }, - size: { width: 320, height: 180 }, - }, - { - placeholderId: 'todo', - type: 'todo' as DashboardItemType, - content: '[]', - position: { x: 420, y: 48 }, - size: { width: 300, height: 260 }, - }, - ], - }, -]; +// Starter templates seeded into a fresh desktop DB β€” verbatim copies of the +// curated prod templates (the cloud has real user-submitted templates; desktop +// starts empty). Block/edge/viewport data lives in starter-templates.json. /** * Seed the curated starter templates into a fresh desktop DB. Desktop only @@ -149,23 +73,31 @@ export async function seedStarterTemplates(env: Env): Promise { if (!env.SURFACE_TOKEN) return; await ensureTemplateColumns(env); - const marker = 'seed_desktop_starter_templates_v1'; + const marker = 'seed_desktop_starter_templates_v2'; const already = await env.DB.prepare( `SELECT 1 FROM schema_migrations WHERE name = ?` ).bind(marker).first(); if (already) return; + // Drop the earlier hand-made starters, superseded by these prod copies. + await env.DB.prepare( + `DELETE FROM dashboard_templates WHERE id IN + ('starter-agentic-coding', 'starter-automation', 'starter-documentation')` + ).run(); + const now = new Date().toISOString(); - for (const t of STARTER_TEMPLATES) { + for (const t of starterTemplates) { await env.DB.prepare( `INSERT INTO dashboard_templates (id, name, description, category, author_id, author_name, - items_json, edges_json, item_count, is_featured, status, created_at, updated_at) - VALUES (?, ?, ?, ?, 'orcabot', 'Orcabot', ?, '[]', ?, 1, 'approved', ?, ?) + items_json, edges_json, viewport_json, item_count, is_featured, status, created_at, updated_at) + VALUES (?, ?, ?, ?, 'orcabot', 'Orcabot', ?, ?, ?, ?, 1, 'approved', ?, ?) ON CONFLICT(id) DO NOTHING` ).bind( t.id, t.name, t.description, t.category, - JSON.stringify(t.items), t.items.length, now, now + JSON.stringify(t.items), JSON.stringify(t.edges), + t.viewport ? JSON.stringify(t.viewport) : null, + t.itemCount, now, now ).run(); } await env.DB.prepare( diff --git a/controlplane/src/templates/starter-templates.json b/controlplane/src/templates/starter-templates.json new file mode 100644 index 00000000..8261e752 --- /dev/null +++ b/controlplane/src/templates/starter-templates.json @@ -0,0 +1,353 @@ +[ + { + "id": "starter-council-of-experts", + "name": "Council of Experts", + "description": "Send your prompt to multiple LLMs", + "category": "custom", + "items": [ + { + "placeholderId": "item_0", + "type": "prompt", + "content": "", + "position": { + "x": 784, + "y": 0 + }, + "size": { + "width": 392, + "height": 208 + } + }, + { + "placeholderId": "item_1", + "type": "terminal", + "content": "{\"name\":\"Gemini CLI\",\"agentic\":true,\"bootCommand\":\"gemini\",\"subagentIds\":[],\"skillIds\":[],\"mcpToolIds\":[]}", + "position": { + "x": 480, + "y": 272 + }, + "size": { + "width": 480, + "height": 500 + } + }, + { + "placeholderId": "item_2", + "type": "terminal", + "content": "{\"name\":\"Gemini CLI\",\"agentic\":true,\"bootCommand\":\"gemini\",\"subagentIds\":[],\"skillIds\":[],\"mcpToolIds\":[]}", + "position": { + "x": 1008, + "y": 272 + }, + "size": { + "width": 480, + "height": 500 + } + }, + { + "placeholderId": "item_3", + "type": "terminal", + "content": "{\"name\":\"Claude Code\",\"agentic\":true,\"bootCommand\":\"claude\",\"subagentIds\":[],\"skillIds\":[],\"mcpToolIds\":[]}", + "position": { + "x": 1552, + "y": 256 + }, + "size": { + "width": 480, + "height": 500 + } + }, + { + "placeholderId": "item_4", + "type": "terminal", + "content": "{\"name\":\"Codex\",\"agentic\":true,\"bootCommand\":\"codex\",\"subagentIds\":[],\"skillIds\":[],\"mcpToolIds\":[]}", + "position": { + "x": -64, + "y": 256 + }, + "size": { + "width": 480, + "height": 500 + } + } + ], + "edges": [ + { + "sourcePlaceholderId": "item_0", + "targetPlaceholderId": "item_1", + "sourceHandle": "bottom-out", + "targetHandle": "top-in" + }, + { + "sourcePlaceholderId": "item_0", + "targetPlaceholderId": "item_2", + "sourceHandle": "bottom-out", + "targetHandle": "top-in" + }, + { + "sourcePlaceholderId": "item_0", + "targetPlaceholderId": "item_3", + "sourceHandle": "bottom-out", + "targetHandle": "top-in" + }, + { + "sourcePlaceholderId": "item_0", + "targetPlaceholderId": "item_4", + "sourceHandle": "bottom-out", + "targetHandle": "top-in" + } + ], + "viewport": { + "x": 243.61111437832903, + "y": 166.49999816035023, + "zoom": 0.6944444444444445 + }, + "itemCount": 5 + }, + { + "id": "starter-code-and-review", + "name": "Code and Review", + "description": "Have one agent review the code of another", + "category": "coding", + "items": [ + { + "placeholderId": "item_0", + "type": "prompt", + "content": "Start work on new feature X", + "position": { + "x": 256, + "y": -176 + }, + "size": { + "width": 440, + "height": 144 + } + }, + { + "placeholderId": "item_1", + "type": "prompt", + "content": "Review the latest changes", + "position": { + "x": 864, + "y": 48 + }, + "size": { + "width": 312, + "height": 144 + } + }, + { + "placeholderId": "item_2", + "type": "browser", + "content": "", + "position": { + "x": 1312, + "y": 0 + }, + "size": { + "width": 680, + "height": 480 + } + }, + { + "placeholderId": "item_3", + "type": "terminal", + "content": "{\"name\":\"Terminal\",\"agentic\":false,\"bootCommand\":\"cd \\\"$HOME/github/robdmac/turtledash\\\" && exec bash\",\"subagentIds\":[],\"skillIds\":[],\"mcpToolIds\":[]}", + "position": { + "x": 1376, + "y": -208 + }, + "size": { + "width": 576, + "height": 200 + } + }, + { + "placeholderId": "item_4", + "type": "terminal", + "content": "{\"name\":\"Claude Code\",\"agentic\":true,\"bootCommand\":\"cd \\\"$HOME/github\\\" && claude\",\"subagentIds\":[],\"skillIds\":[],\"mcpToolIds\":[]}", + "position": { + "x": 240, + "y": 48 + }, + "size": { + "width": 496, + "height": 692 + } + }, + { + "placeholderId": "item_5", + "type": "terminal", + "content": "{\"name\":\"Codex\",\"agentic\":true,\"bootCommand\":\"codex\",\"subagentIds\":[],\"skillIds\":[],\"mcpToolIds\":[]}", + "position": { + "x": 784, + "y": 240 + }, + "size": { + "width": 480, + "height": 500 + } + } + ], + "edges": [ + { + "sourcePlaceholderId": "item_0", + "targetPlaceholderId": "item_4", + "sourceHandle": "bottom-out", + "targetHandle": "top-in" + }, + { + "sourcePlaceholderId": "item_1", + "targetPlaceholderId": "item_5", + "sourceHandle": "bottom-out", + "targetHandle": "top-in" + }, + { + "sourcePlaceholderId": "item_4", + "targetPlaceholderId": "item_1", + "sourceHandle": "right-out", + "targetHandle": "top-in" + }, + { + "sourcePlaceholderId": "item_5", + "targetPlaceholderId": "item_4", + "sourceHandle": "bottom-out", + "targetHandle": "left-in" + } + ], + "viewport": { + "x": 8.070002649095425, + "y": 253.78388832963515, + "zoom": 0.7925 + }, + "itemCount": 6 + }, + { + "id": "starter-agents-chess", + "name": "Agents Chess", + "description": "Comparing agents chess skills.", + "category": "custom", + "items": [ + { + "placeholderId": "item_0", + "type": "terminal", + "content": "{\"name\":\"Gemini CLI\",\"agentic\":true,\"bootCommand\":\"gemini\",\"subagentIds\":[],\"skillIds\":[],\"mcpToolIds\":[]}", + "position": { + "x": 896, + "y": 48 + }, + "size": { + "width": 480, + "height": 500 + }, + "metadata": { + "minimized": false, + "expandedSize": { + "width": 480, + "height": 500 + } + } + }, + { + "placeholderId": "item_1", + "type": "terminal", + "content": "{\"name\":\"Claude Code\",\"agentic\":true,\"bootCommand\":\"claude\",\"subagentIds\":[],\"skillIds\":[],\"mcpToolIds\":[]}", + "position": { + "x": 240, + "y": 48 + }, + "size": { + "width": 480, + "height": 500 + }, + "metadata": { + "minimized": false, + "expandedSize": { + "width": 480, + "height": 500 + } + } + }, + { + "placeholderId": "item_2", + "type": "prompt", + "content": "Lets play chess. You are white so you can go first...", + "position": { + "x": 256, + "y": -176 + }, + "size": { + "width": 440, + "height": 144 + } + }, + { + "placeholderId": "item_3", + "type": "note", + "content": "", + "position": { + "x": 240, + "y": 608 + }, + "size": { + "width": 472, + "height": 280 + } + }, + { + "placeholderId": "item_4", + "type": "note", + "content": "", + "position": { + "x": 896, + "y": 608 + }, + "size": { + "width": 472, + "height": 280 + }, + "metadata": { + "color": "blue" + } + } + ], + "edges": [ + { + "sourcePlaceholderId": "item_2", + "targetPlaceholderId": "item_1", + "sourceHandle": "bottom-out", + "targetHandle": "top-in" + }, + { + "sourcePlaceholderId": "item_1", + "targetPlaceholderId": "item_3", + "sourceHandle": "bottom-out", + "targetHandle": "top-in" + }, + { + "sourcePlaceholderId": "item_1", + "targetPlaceholderId": "item_0", + "sourceHandle": "bottom-out", + "targetHandle": "top-in" + }, + { + "sourcePlaceholderId": "item_0", + "targetPlaceholderId": "item_4", + "sourceHandle": "bottom-out", + "targetHandle": "top-in" + }, + { + "sourcePlaceholderId": "item_0", + "targetPlaceholderId": "item_1", + "sourceHandle": "bottom-out", + "targetHandle": "top-in" + } + ], + "viewport": { + "x": 209.1131436314363, + "y": 186.37127371273712, + "zoom": 0.6876693766937669 + }, + "itemCount": 5 + } +] \ No newline at end of file