@avocadostudio-ai/orchestrator-core 0.1.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 (196) hide show
  1. package/LICENSE +201 -0
  2. package/dist/agent/agent-context.d.ts +19 -0
  3. package/dist/agent/agent-context.js +67 -0
  4. package/dist/agent/agent-logger.d.ts +5 -0
  5. package/dist/agent/agent-logger.js +22 -0
  6. package/dist/agent/agent-loop-openai.d.ts +8 -0
  7. package/dist/agent/agent-loop-openai.js +172 -0
  8. package/dist/agent/agent-loop.d.ts +56 -0
  9. package/dist/agent/agent-loop.js +167 -0
  10. package/dist/agent/agent-provider.d.ts +28 -0
  11. package/dist/agent/agent-provider.js +63 -0
  12. package/dist/agent/agent-tools.d.ts +28 -0
  13. package/dist/agent/agent-tools.js +899 -0
  14. package/dist/agent/context/editing-guidelines.md +46 -0
  15. package/dist/agent/context/role.md +39 -0
  16. package/dist/agent/integration-prompt.d.ts +9 -0
  17. package/dist/agent/integration-prompt.js +154 -0
  18. package/dist/agent/sites-agent-context.d.ts +12 -0
  19. package/dist/agent/sites-agent-context.js +316 -0
  20. package/dist/agent/sites-agent-shared.d.ts +161 -0
  21. package/dist/agent/sites-agent-shared.js +1101 -0
  22. package/dist/agent/sites-agent-tools.d.ts +18 -0
  23. package/dist/agent/sites-agent-tools.js +1227 -0
  24. package/dist/chat/anthropic-cache.d.ts +20 -0
  25. package/dist/chat/anthropic-cache.js +54 -0
  26. package/dist/chat/anthropic-planner.d.ts +98 -0
  27. package/dist/chat/anthropic-planner.js +1012 -0
  28. package/dist/chat/changelog-coverage-validator.d.ts +37 -0
  29. package/dist/chat/changelog-coverage-validator.js +215 -0
  30. package/dist/chat/chat-pipeline-context.d.ts +211 -0
  31. package/dist/chat/chat-pipeline-context.js +249 -0
  32. package/dist/chat/chat-pipeline-deterministic.d.ts +61 -0
  33. package/dist/chat/chat-pipeline-deterministic.js +407 -0
  34. package/dist/chat/chat-pipeline-image.d.ts +86 -0
  35. package/dist/chat/chat-pipeline-image.js +897 -0
  36. package/dist/chat/chat-pipeline-shared.d.ts +69 -0
  37. package/dist/chat/chat-pipeline-shared.js +212 -0
  38. package/dist/chat/chat-pipeline-translation.d.ts +27 -0
  39. package/dist/chat/chat-pipeline-translation.js +417 -0
  40. package/dist/chat/chat-pipeline-ui.d.ts +14 -0
  41. package/dist/chat/chat-pipeline-ui.js +244 -0
  42. package/dist/chat/chat-pipeline.d.ts +99 -0
  43. package/dist/chat/chat-pipeline.js +3999 -0
  44. package/dist/chat/decomposer.d.ts +21 -0
  45. package/dist/chat/decomposer.js +65 -0
  46. package/dist/chat/gemini-planner.d.ts +70 -0
  47. package/dist/chat/gemini-planner.js +541 -0
  48. package/dist/chat/hallucination-validator.d.ts +36 -0
  49. package/dist/chat/hallucination-validator.js +110 -0
  50. package/dist/chat/locale-strings.d.ts +47 -0
  51. package/dist/chat/locale-strings.js +100 -0
  52. package/dist/chat/plan-json-schema.d.ts +133 -0
  53. package/dist/chat/plan-json-schema.js +112 -0
  54. package/dist/chat/planner-types.d.ts +120 -0
  55. package/dist/chat/planner-types.js +66 -0
  56. package/dist/chat/planner.d.ts +148 -0
  57. package/dist/chat/planner.js +1361 -0
  58. package/dist/chat/prompts.d.ts +67 -0
  59. package/dist/chat/prompts.js +356 -0
  60. package/dist/chat/provider-routing.d.ts +14 -0
  61. package/dist/chat/provider-routing.js +27 -0
  62. package/dist/chat/variation-pipeline.d.ts +135 -0
  63. package/dist/chat/variation-pipeline.js +837 -0
  64. package/dist/chat/vision-alt-generator.d.ts +35 -0
  65. package/dist/chat/vision-alt-generator.js +152 -0
  66. package/dist/cms/adapter.d.ts +62 -0
  67. package/dist/cms/adapter.js +1 -0
  68. package/dist/cms/bootstrap.d.ts +17 -0
  69. package/dist/cms/bootstrap.js +85 -0
  70. package/dist/cms/editor-api-adapter.d.ts +23 -0
  71. package/dist/cms/editor-api-adapter.js +71 -0
  72. package/dist/cms/index.d.ts +4 -0
  73. package/dist/cms/index.js +3 -0
  74. package/dist/cms/json-file-adapter.d.ts +11 -0
  75. package/dist/cms/json-file-adapter.js +62 -0
  76. package/dist/demo-mode.d.ts +59 -0
  77. package/dist/demo-mode.js +201 -0
  78. package/dist/errors.d.ts +67 -0
  79. package/dist/errors.js +129 -0
  80. package/dist/http/chat-stream-resumable.d.ts +108 -0
  81. package/dist/http/chat-stream-resumable.js +290 -0
  82. package/dist/http/chat-stream.d.ts +99 -0
  83. package/dist/http/chat-stream.js +92 -0
  84. package/dist/image/gdrive-client.d.ts +22 -0
  85. package/dist/image/gdrive-client.js +215 -0
  86. package/dist/image/image-helpers.d.ts +95 -0
  87. package/dist/image/image-helpers.js +488 -0
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.js +1 -0
  90. package/dist/jira/jira-approval.d.ts +22 -0
  91. package/dist/jira/jira-approval.js +51 -0
  92. package/dist/jira/jira-client.d.ts +44 -0
  93. package/dist/jira/jira-client.js +313 -0
  94. package/dist/jira/jira-poller.d.ts +46 -0
  95. package/dist/jira/jira-poller.js +184 -0
  96. package/dist/jira/jira-processor.d.ts +103 -0
  97. package/dist/jira/jira-processor.js +1085 -0
  98. package/dist/jira/jira-types.d.ts +117 -0
  99. package/dist/jira/jira-types.js +38 -0
  100. package/dist/logger.d.ts +12 -0
  101. package/dist/logger.js +28 -0
  102. package/dist/migration/mcp-server-stdio.d.ts +8 -0
  103. package/dist/migration/mcp-server-stdio.js +672 -0
  104. package/dist/migration/migration-prompt.d.ts +7 -0
  105. package/dist/migration/migration-prompt.js +197 -0
  106. package/dist/migration/migration-tools.d.ts +17 -0
  107. package/dist/migration/migration-tools.js +159 -0
  108. package/dist/migration/scrape-cache.d.ts +9 -0
  109. package/dist/migration/scrape-cache.js +19 -0
  110. package/dist/nlp/deterministic-planner-context.d.ts +141 -0
  111. package/dist/nlp/deterministic-planner-context.js +362 -0
  112. package/dist/nlp/deterministic-planner-pages.d.ts +26 -0
  113. package/dist/nlp/deterministic-planner-pages.js +170 -0
  114. package/dist/nlp/deterministic-planner-patches.d.ts +80 -0
  115. package/dist/nlp/deterministic-planner-patches.js +508 -0
  116. package/dist/nlp/deterministic-planner-refs.d.ts +33 -0
  117. package/dist/nlp/deterministic-planner-refs.js +164 -0
  118. package/dist/nlp/deterministic-planner-suggestions.d.ts +49 -0
  119. package/dist/nlp/deterministic-planner-suggestions.js +579 -0
  120. package/dist/nlp/deterministic-planner.d.ts +85 -0
  121. package/dist/nlp/deterministic-planner.js +1631 -0
  122. package/dist/nlp/intent-detection.d.ts +309 -0
  123. package/dist/nlp/intent-detection.js +730 -0
  124. package/dist/nlp/intent-helpers.d.ts +15 -0
  125. package/dist/nlp/intent-helpers.js +243 -0
  126. package/dist/nlp/intent-patterns.d.ts +40 -0
  127. package/dist/nlp/intent-patterns.js +223 -0
  128. package/dist/nlp/plan-normalizer.d.ts +41 -0
  129. package/dist/nlp/plan-normalizer.js +1537 -0
  130. package/dist/ops/destructive-action-gate.d.ts +44 -0
  131. package/dist/ops/destructive-action-gate.js +90 -0
  132. package/dist/ops/ops-engine.d.ts +151 -0
  133. package/dist/ops/ops-engine.js +1394 -0
  134. package/dist/publish/diff-engine.d.ts +18 -0
  135. package/dist/publish/diff-engine.js +305 -0
  136. package/dist/publish/publish-helpers.d.ts +87 -0
  137. package/dist/publish/publish-helpers.js +521 -0
  138. package/dist/publish/publish-target-registry.d.ts +7 -0
  139. package/dist/publish/publish-target-registry.js +61 -0
  140. package/dist/publish/publish-target.d.ts +81 -0
  141. package/dist/publish/publish-target.js +1 -0
  142. package/dist/publish/targets/deploy-hook.d.ts +13 -0
  143. package/dist/publish/targets/deploy-hook.js +123 -0
  144. package/dist/publish/targets/git.d.ts +13 -0
  145. package/dist/publish/targets/git.js +55 -0
  146. package/dist/publish/targets/site-contract.d.ts +19 -0
  147. package/dist/publish/targets/site-contract.js +124 -0
  148. package/dist/state/content-source.d.ts +17 -0
  149. package/dist/state/content-source.js +1 -0
  150. package/dist/state/in-memory-content-source.d.ts +27 -0
  151. package/dist/state/in-memory-content-source.js +51 -0
  152. package/dist/state/session-lock.d.ts +13 -0
  153. package/dist/state/session-lock.js +29 -0
  154. package/dist/state/session-state.d.ts +310 -0
  155. package/dist/state/session-state.js +1083 -0
  156. package/dist/state/sqlite-store-singleton.d.ts +31 -0
  157. package/dist/state/sqlite-store-singleton.js +170 -0
  158. package/dist/state/sqlite-store.d.ts +135 -0
  159. package/dist/state/sqlite-store.js +421 -0
  160. package/dist/telemetry/chat-telemetry.d.ts +105 -0
  161. package/dist/telemetry/chat-telemetry.js +247 -0
  162. package/dist/telemetry/eval-candidate-store.d.ts +50 -0
  163. package/dist/telemetry/eval-candidate-store.js +120 -0
  164. package/dist/telemetry/feedback-store.d.ts +34 -0
  165. package/dist/telemetry/feedback-store.js +76 -0
  166. package/dist/telemetry/jira-telemetry.d.ts +57 -0
  167. package/dist/telemetry/jira-telemetry.js +68 -0
  168. package/dist/telemetry/migration-telemetry.d.ts +35 -0
  169. package/dist/telemetry/migration-telemetry.js +40 -0
  170. package/dist/telemetry/usage.d.ts +24 -0
  171. package/dist/telemetry/usage.js +80 -0
  172. package/dist/tools/builtin-registrations.d.ts +12 -0
  173. package/dist/tools/builtin-registrations.js +33 -0
  174. package/dist/tools/builtins/gdrive-browse.d.ts +3 -0
  175. package/dist/tools/builtins/gdrive-browse.js +68 -0
  176. package/dist/tools/builtins/image-generate.d.ts +3 -0
  177. package/dist/tools/builtins/image-generate.js +211 -0
  178. package/dist/tools/builtins/unsplash-get-by-id.d.ts +23 -0
  179. package/dist/tools/builtins/unsplash-get-by-id.js +119 -0
  180. package/dist/tools/builtins/unsplash-search.d.ts +3 -0
  181. package/dist/tools/builtins/unsplash-search.js +74 -0
  182. package/dist/tools/executor.d.ts +23 -0
  183. package/dist/tools/executor.js +169 -0
  184. package/dist/tools/index.d.ts +5 -0
  185. package/dist/tools/index.js +5 -0
  186. package/dist/tools/registry.d.ts +21 -0
  187. package/dist/tools/registry.js +75 -0
  188. package/dist/tools/runtime.d.ts +27 -0
  189. package/dist/tools/runtime.js +48 -0
  190. package/dist/tools/schema-validator.d.ts +24 -0
  191. package/dist/tools/schema-validator.js +88 -0
  192. package/dist/tools/types.d.ts +86 -0
  193. package/dist/tools/types.js +1 -0
  194. package/dist/variation-images.d.ts +19 -0
  195. package/dist/variation-images.js +12 -0
  196. package/package.json +78 -0
@@ -0,0 +1,7 @@
1
+ /**
2
+ * System prompt for the migration agent (Claude Agent SDK version).
3
+ *
4
+ * Claude uses built-in Write/Edit/Bash tools to create block files directly,
5
+ * plus custom MCP tools for web scraping, design token extraction, and theming.
6
+ */
7
+ export declare function buildMigrationSystemPrompt(): string;
@@ -0,0 +1,197 @@
1
+ /**
2
+ * System prompt for the migration agent (Claude Agent SDK version).
3
+ *
4
+ * Claude uses built-in Write/Edit/Bash tools to create block files directly,
5
+ * plus custom MCP tools for web scraping, design token extraction, and theming.
6
+ */
7
+ import { getAllBlockMeta } from "@avocadostudio-ai/shared";
8
+ export function buildMigrationSystemPrompt() {
9
+ // Build block catalog from registry
10
+ const allMeta = getAllBlockMeta();
11
+ const blockCatalog = Object.entries(allMeta)
12
+ .map(([type, meta]) => {
13
+ const category = meta.category ?? "content";
14
+ const chrome = meta.chrome ? " (chrome — structurally pinned)" : "";
15
+ const fields = Object.entries(meta.fields)
16
+ .map(([key, f]) => ` ${key}: ${f.kind}${f.label ? ` — ${f.label}` : ""}`)
17
+ .join("\n");
18
+ const lists = meta.listFields
19
+ ? Object.entries(meta.listFields)
20
+ .map(([key, l]) => {
21
+ const itemFields = Object.entries(l.itemFields)
22
+ .map(([k, f]) => ` ${k}: ${f.kind}${f.label ? ` — ${f.label}` : ""}`)
23
+ .join("\n");
24
+ return ` ${key} (list)${l.label ? ` — ${l.label}` : ""}:\n${itemFields}`;
25
+ })
26
+ .join("\n")
27
+ : "";
28
+ return ` ${type} [${category}]${chrome}\n${fields}${lists ? "\n" + lists : ""}`;
29
+ })
30
+ .join("\n\n");
31
+ return `You are a website migration agent. Your job is to analyze an external website and recreate it using a block-based site editor.
32
+
33
+ You have both **custom migration tools** (for web scraping and design analysis) and **built-in file tools** (Read, Write, Edit, Bash, Glob) for creating block code directly.
34
+
35
+ ## Workflow
36
+
37
+ Follow these steps in order:
38
+
39
+ 1. **Generate section specs** — Use \`generate_page_specs\` to scrape the source page and get detailed section-by-section specs with exact computed CSS styles, DOM structure, verbatim content, and design notes. This also returns design tokens and theme variables.
40
+
41
+ 2. **Analyze specs & plan** — Review each section spec:
42
+ - \`structure.pattern\` tells you the layout (e.g. "3-column grid of 4 items", "side-by-side layout")
43
+ - \`structure.interactionModel\` tells you if it's static, accordion, tabs, carousel, or scroll-driven
44
+ - \`styles\` gives you exact computed CSS for container, heading, body text, repeated items, and CTAs
45
+ - \`content\` has the verbatim text, images, and links
46
+ - \`suggestedBlockType\` is a heuristic hint — override it if the spec data suggests a better fit
47
+
48
+ 3. **Map to blocks or create custom** — For each section:
49
+ - If it clearly matches an existing block type → use it with props derived from \`content\`
50
+ - If the layout is unique or doesn't map well → **create a custom block** using the Write tool. Use the exact CSS from \`styles.container\`, \`styles.repeatedItem\`, etc. to reproduce the visual design faithfully.
51
+
52
+ 4. **Download key images** — Use \`download_remote_image\` for hero images, logos, and key visuals found in spec \`content.images\`.
53
+
54
+ 5. **Apply theme** — Use \`apply_theme\` with the \`themeVariables\` returned by \`generate_page_specs\`.
55
+
56
+ 6. **Summary** — Summarize what was migrated, which blocks were used or created, and any gaps.
57
+
58
+ ## Available Block Types
59
+
60
+ ${blockCatalog}
61
+
62
+ ## Section Spec Reference
63
+
64
+ Each section spec from \`generate_page_specs\` contains:
65
+
66
+ \`\`\`
67
+ content:
68
+ headings[] — h1-h6 text, verbatim
69
+ paragraphs[] — paragraph text
70
+ images[] — src, alt, isBackground
71
+ links[] — href, text
72
+ lists[][] — list items
73
+
74
+ structure:
75
+ pattern — "3-column grid of 4 items", "side-by-side layout", etc.
76
+ repeatCount — number of repeated child elements
77
+ repeatSignature — tag structure (e.g. "img + h3 + p + a")
78
+ elementCount — total DOM elements
79
+ interactionModel — static | accordion | tabs | carousel | scroll-driven
80
+
81
+ styles:
82
+ container — section root computed CSS (backgroundColor, display, gap, padding, etc.)
83
+ heading — first heading computed CSS (fontSize, fontWeight, fontFamily, color, etc.)
84
+ bodyText — first paragraph computed CSS
85
+ repeatedItem — first repeated child computed CSS (if repeatCount > 0)
86
+ cta — first button/link with background computed CSS
87
+
88
+ designNotes:
89
+ backgroundColor, textColor, headingFont, headingSize, layout,
90
+ hasGradient, hasShadow, borderRadius
91
+ \`\`\`
92
+
93
+ When creating custom blocks, use the exact CSS values from \`styles\` to match the source design.
94
+ The \`styles.repeatedItem\` + \`structure.repeatSignature\` define the fields a custom block needs.
95
+
96
+ **Important:** If a section doesn't map well to any existing block, create a new custom block rather than forcing a bad match. Use the computed CSS values from the spec to reproduce the visual design.
97
+
98
+ ## Creating Custom Blocks (Write Tool)
99
+
100
+ When a section needs a custom block type, create it by writing files to \`apps/site/blocks/{kebab-name}/\`. Each block needs 3 files:
101
+
102
+ ### 1. Schema file: \`apps/site/blocks/{kebab-name}/schema.ts\`
103
+
104
+ \`\`\`typescript
105
+ import { z } from "zod"
106
+ import { registerBlock } from "@avocadostudio-ai/shared"
107
+
108
+ registerBlock("PascalName", {
109
+ schema: z.object({
110
+ title: z.string().min(1),
111
+ description: z.string().optional(),
112
+ // Use z.array(z.object({...})).min(1) for list fields
113
+ }),
114
+ meta: {
115
+ displayName: "Pascal Name",
116
+ description: "Block description.",
117
+ category: "content", // content | media | layout | conversion
118
+ fields: {
119
+ title: { kind: "text", label: "Title" },
120
+ description: { kind: "text", label: "Description", multiline: true },
121
+ },
122
+ // For list fields:
123
+ // listFields: {
124
+ // items: { label: "Items", itemFields: { title: { kind: "text", label: "Title" } } }
125
+ // }
126
+ }
127
+ })
128
+
129
+ export function pascalNameDefaultProps(): Record<string, unknown> {
130
+ return { title: "Default title", description: "Default description" }
131
+ }
132
+ \`\`\`
133
+
134
+ ### 2. Renderer file: \`apps/site/blocks/{kebab-name}/renderer.tsx\`
135
+
136
+ \`\`\`tsx
137
+ import type { JSX } from "react"
138
+
139
+ export function PascalName(props: Record<string, unknown>): JSX.Element {
140
+ const title = String(props.title ?? "")
141
+ return (
142
+ <section className="kebab-name">
143
+ <div className="section__inner">
144
+ <h2 data-editable-target="title" data-editable-target-label="title" data-editable-label="title">
145
+ {title}
146
+ </h2>
147
+ </div>
148
+ </section>
149
+ )
150
+ }
151
+ \`\`\`
152
+
153
+ ### 3. Styles file: \`apps/site/blocks/{kebab-name}/styles.css\`
154
+
155
+ Use CSS variables from the design system:
156
+ - Colors: \`--brand\`, \`--heading\`, \`--body\`, \`--body-secondary\`, \`--text-200\`, \`--section-bg\`, \`--surface\`, \`--border\`
157
+ - Use BEM-style class names: \`.kebab-name\`, \`.kebab-name__element\`
158
+ - Include responsive breakpoint: \`@media (max-width: 900px)\`
159
+
160
+ ### 4. Update manifest: \`apps/site/blocks/index.ts\`
161
+
162
+ After creating block files, update or create the manifest:
163
+
164
+ \`\`\`typescript
165
+ // Auto-generated by migration agent — do not edit manually
166
+ import "./pricing-table/schema.ts"
167
+ import "./team-grid/schema.ts"
168
+ export { PricingTable } from "./pricing-table/renderer.tsx"
169
+ export { TeamGrid } from "./team-grid/renderer.tsx"
170
+ \`\`\`
171
+
172
+ ### 5. Verify
173
+
174
+ After writing block files, run \`pnpm typecheck\` via Bash to verify the generated code compiles. If there are errors, use the Edit tool to fix them.
175
+
176
+ ## Reading Reference Blocks
177
+
178
+ Before creating custom blocks, read an existing block as a reference pattern:
179
+ - Schema: \`packages/shared/src/blocks/quote.ts\` or \`packages/shared/src/blocks/feature-grid.ts\` (for list-based blocks)
180
+ - Renderer: \`packages/blocks/src/blocks/quote/renderer.tsx\` or \`packages/blocks/src/blocks/feature-grid/renderer.tsx\`
181
+ - Styles: \`packages/blocks/src/blocks/quote/styles.css\`
182
+
183
+ ## Image Handling
184
+
185
+ - Download important images (hero backgrounds, logos, product images) using \`download_remote_image\`.
186
+ - Use the returned \`localUrl\` in block props instead of the original remote URL.
187
+ - Skip small decorative images and icons.
188
+
189
+ ## Theme Matching
190
+
191
+ After analyzing the source site's design tokens, use \`apply_theme\` to set CSS variable overrides. Focus on:
192
+ - Brand/accent color → \`--brand\`
193
+ - Background colors → \`--bg-0\`, \`--bg-100\`
194
+ - Text colors → \`--text-100\`, \`--heading\`, \`--body\`
195
+ - Font families (if distinctive)
196
+ `;
197
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Migration tools for the sites-agent — higher-level alternatives to scrape_url
3
+ * that include computed style extraction and section spec generation.
4
+ *
5
+ * These give the LLM rich, block-type-agnostic data so it can decide:
6
+ * use an existing block type OR spawn block-coder for a custom one.
7
+ */
8
+ import { z } from "zod";
9
+ export declare const MIGRATION_DEBUG_DIR: string;
10
+ /** Save a base64 screenshot to disk for post-run analysis. */
11
+ export declare function saveScreenshot(label: string, base64: string, url: string): Promise<string>;
12
+ /** Save full scrape debug data (specs, tokens, nav) to JSON for analysis. */
13
+ export declare function saveScrapeDebug(url: string, data: Record<string, unknown>): Promise<string>;
14
+ /** Returns all migration tools for the sites-agent MCP server. */
15
+ export declare function createMigrationTools(): import("@anthropic-ai/claude-agent-sdk").SdkMcpToolDefinition<{
16
+ url: z.ZodString;
17
+ }>[];
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Migration tools for the sites-agent — higher-level alternatives to scrape_url
3
+ * that include computed style extraction and section spec generation.
4
+ *
5
+ * These give the LLM rich, block-type-agnostic data so it can decide:
6
+ * use an existing block type OR spawn block-coder for a custom one.
7
+ */
8
+ import { tool } from "@anthropic-ai/claude-agent-sdk";
9
+ import { z } from "zod";
10
+ import { writeFile, mkdir } from "node:fs/promises";
11
+ import { resolve } from "node:path";
12
+ import { homedir } from "node:os";
13
+ import { scrapeFullPage, buildPageSpecs, extractDesignTokens, mapToThemeVariables, augmentThemeFromComputedStyles, } from "@avocadostudio-ai/migration-sdk";
14
+ import { getCachedScrape, setCachedScrape } from "./scrape-cache.js";
15
+ export const MIGRATION_DEBUG_DIR = resolve(homedir(), ".data/migration-debug");
16
+ function debugFilename(url, suffix) {
17
+ const slug = url.replace(/https?:\/\//, "").replace(/[^a-z0-9]+/gi, "-").slice(0, 60);
18
+ const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
19
+ return `${ts}_${slug}_${suffix}`;
20
+ }
21
+ /** Save a base64 screenshot to disk for post-run analysis. */
22
+ export async function saveScreenshot(label, base64, url) {
23
+ await mkdir(MIGRATION_DEBUG_DIR, { recursive: true });
24
+ const filepath = resolve(MIGRATION_DEBUG_DIR, debugFilename(url, `${label}.jpg`));
25
+ await writeFile(filepath, Buffer.from(base64, "base64"));
26
+ return filepath;
27
+ }
28
+ /** Save full scrape debug data (specs, tokens, nav) to JSON for analysis. */
29
+ export async function saveScrapeDebug(url, data) {
30
+ await mkdir(MIGRATION_DEBUG_DIR, { recursive: true });
31
+ const filepath = resolve(MIGRATION_DEBUG_DIR, debugFilename(url, "specs.json"));
32
+ await writeFile(filepath, JSON.stringify(data, null, 2), "utf-8");
33
+ return filepath;
34
+ }
35
+ /**
36
+ * Primary migration tool: scrape a URL and generate block-type-agnostic
37
+ * section specs with exact computed CSS styles, DOM structure, content,
38
+ * and design notes.
39
+ */
40
+ const generatePageSpecsTool = tool("generate_page_specs", `Scrape a URL and generate detailed section specs with exact computed CSS styles, DOM structure, content, and design notes.
41
+ Returns block-type-agnostic specs — use them to decide which sections map to existing blocks and which need custom blocks via block-coder.
42
+
43
+ Each spec contains:
44
+ - content: verbatim text, images, links from the source section
45
+ - structure: layout pattern, repeat count/signature, interaction model (static/accordion/tabs/carousel)
46
+ - styles: exact computed CSS for container, heading, body text, repeated items, CTA buttons
47
+ - designNotes: colors, fonts, layout summary, gradients, shadows
48
+ - suggestedBlockType: heuristic hint (not authoritative — you decide)
49
+
50
+ Also returns designTokens (colors, fonts, radii) and themeVariables (CSS custom property mapping).`, { url: z.string().url().describe("The URL to scrape and analyze") }, async ({ url }) => {
51
+ try {
52
+ const cached = !!getCachedScrape(url);
53
+ const t0 = Date.now();
54
+ let scrape = getCachedScrape(url);
55
+ if (!scrape) {
56
+ console.log(`[generate_page_specs] Scraping ${url}...`);
57
+ scrape = await scrapeFullPage(url);
58
+ setCachedScrape(url, scrape);
59
+ }
60
+ const scrapeMs = Date.now() - t0;
61
+ const specs = buildPageSpecs(scrape);
62
+ const tokens = extractDesignTokens(scrape.content.css, scrape.resolvedCssVars);
63
+ let theme = mapToThemeVariables(tokens);
64
+ // Augment theme with actual computed CSS values from section specs.
65
+ // Computed styles (getComputedStyle) are far more reliable than CSS regex,
66
+ // especially for CMS sites using CSS variables, inline styles, or shadow DOM.
67
+ const sectionStyleData = specs
68
+ .filter(s => Object.keys(s.styles.container).length > 0)
69
+ .map(s => s.styles);
70
+ if (sectionStyleData.length > 0) {
71
+ // Collect hover states from interaction sweep for brand-hover extraction
72
+ const hoverStates = scrape.interactionStates
73
+ ?.flatMap(is => is.states)
74
+ .filter(s => s.trigger === "hover")
75
+ .map(s => ({ triggerTarget: s.triggerTarget, changedStyles: s.changedStyles }));
76
+ theme = augmentThemeFromComputedStyles(theme, sectionStyleData, hoverStates);
77
+ }
78
+ // Override fonts with computed values (more reliable than CSS regex)
79
+ if (scrape.computedFonts) {
80
+ if (scrape.computedFonts.heading)
81
+ theme["--font-heading"] = scrape.computedFonts.heading + ", sans-serif";
82
+ if (scrape.computedFonts.body)
83
+ theme["--font-body"] = scrape.computedFonts.body + ", sans-serif";
84
+ }
85
+ const totalMs = Date.now() - t0;
86
+ // Log summary
87
+ console.log(`[generate_page_specs] ${url} → ${specs.length} sections, ` +
88
+ `${tokens.colors.length} colors, ${tokens.fonts.length} fonts | ` +
89
+ `${cached ? "cached" : `scraped in ${scrapeMs}ms`} | total ${totalMs}ms`);
90
+ for (const spec of specs) {
91
+ const blockHint = spec.suggestedBlockType ?? "unknown";
92
+ const conf = spec.suggestedConfidence.toFixed(2);
93
+ const pattern = spec.structure.pattern;
94
+ const repeat = spec.structure.repeatCount > 0 ? ` (${spec.structure.repeatCount}x ${spec.structure.repeatSignature ?? "?"})` : "";
95
+ const headings = spec.content.headings.map(h => h.text).join(", ").slice(0, 80);
96
+ console.log(` [${spec.sectionIndex}] ${blockHint} (${conf}) — ${pattern}${repeat}` +
97
+ (headings ? ` — "${headings}"` : ""));
98
+ }
99
+ // Save debug artifacts to ~/.data/migration-debug/
100
+ const debugData = {
101
+ url,
102
+ scrapedAt: new Date().toISOString(),
103
+ scrapeMs,
104
+ totalMs,
105
+ cached,
106
+ pageTitle: scrape.content.title,
107
+ pageDescription: scrape.content.metaDescription,
108
+ sectionCount: specs.length,
109
+ visualSectionCount: scrape.visualSections?.length ?? 0,
110
+ specs,
111
+ designTokens: tokens,
112
+ themeVariables: theme,
113
+ nav: scrape.nav,
114
+ embeds: scrape.embeds,
115
+ videos: scrape.videos,
116
+ imageCompositions: scrape.imageCompositions?.length ? scrape.imageCompositions : undefined,
117
+ computedFonts: scrape.computedFonts,
118
+ };
119
+ // Fire-and-forget debug artifact saves — non-critical, don't block tool return
120
+ const debugWrites = [];
121
+ if (scrape.screenshot)
122
+ debugWrites.push(saveScreenshot("desktop", scrape.screenshot.base64, url));
123
+ if (scrape.mobileScreenshot)
124
+ debugWrites.push(saveScreenshot("mobile", scrape.mobileScreenshot.base64, url));
125
+ debugWrites.push(saveScrapeDebug(url, debugData));
126
+ Promise.all(debugWrites)
127
+ .then(files => console.log(`[generate_page_specs] Debug artifacts saved: ${files.map(f => f.split("/").pop()).join(", ")}`))
128
+ .catch(e => console.warn(`[generate_page_specs] Failed to save debug artifacts: ${e instanceof Error ? e.message : String(e)}`));
129
+ const result = {
130
+ pageTitle: scrape.content.title,
131
+ pageDescription: scrape.content.metaDescription,
132
+ sectionCount: specs.length,
133
+ specs,
134
+ designTokens: tokens,
135
+ themeVariables: theme,
136
+ nav: scrape.nav,
137
+ };
138
+ const content = [
139
+ { type: "text", text: JSON.stringify(result, null, 2) },
140
+ ];
141
+ // Include screenshots so the LLM can visually analyze sections
142
+ if (scrape.screenshot)
143
+ content.push({ type: "image", data: scrape.screenshot.base64, mimeType: "image/jpeg" });
144
+ if (scrape.mobileScreenshot)
145
+ content.push({ type: "image", data: scrape.mobileScreenshot.base64, mimeType: "image/jpeg" });
146
+ return { content };
147
+ }
148
+ catch (e) {
149
+ console.error(`[generate_page_specs] ERROR for ${url}:`, e instanceof Error ? e.message : String(e));
150
+ return {
151
+ content: [{ type: "text", text: `Error generating page specs: ${e instanceof Error ? e.message : String(e)}` }],
152
+ isError: true,
153
+ };
154
+ }
155
+ }, { annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true } });
156
+ /** Returns all migration tools for the sites-agent MCP server. */
157
+ export function createMigrationTools() {
158
+ return [generatePageSpecsTool];
159
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared scrape cache — avoids redundant Playwright launches (~5-15s each)
3
+ * when both scrape_url and generate_page_specs tools are called for the same URL.
4
+ *
5
+ * Capped at 10 entries with LRU eviction — each FullPageScrape can be several MB.
6
+ */
7
+ import type { FullPageScrape } from "@avocadostudio-ai/migration-sdk";
8
+ export declare function getCachedScrape(url: string): FullPageScrape | undefined;
9
+ export declare function setCachedScrape(url: string, scrape: FullPageScrape): void;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shared scrape cache — avoids redundant Playwright launches (~5-15s each)
3
+ * when both scrape_url and generate_page_specs tools are called for the same URL.
4
+ *
5
+ * Capped at 10 entries with LRU eviction — each FullPageScrape can be several MB.
6
+ */
7
+ const MAX_ENTRIES = 10;
8
+ const cache = new Map();
9
+ export function getCachedScrape(url) {
10
+ return cache.get(url);
11
+ }
12
+ export function setCachedScrape(url, scrape) {
13
+ if (cache.size >= MAX_ENTRIES) {
14
+ const oldest = cache.keys().next().value;
15
+ if (oldest)
16
+ cache.delete(oldest);
17
+ }
18
+ cache.set(url, scrape);
19
+ }
@@ -0,0 +1,141 @@
1
+ import type { PageDoc } from "@avocadostudio-ai/shared";
2
+ import type { ChatAttachment } from "./intent-detection.ts";
3
+ export declare function readPathValue(root: unknown, path: string): unknown;
4
+ /**
5
+ * Given block props and an editablePath targeting an alt-text field,
6
+ * resolve the companion image URL for vision-powered alt text generation.
7
+ *
8
+ * Examples:
9
+ * - "imageAlt" → props.imageUrl
10
+ * - "cards[2].imageAlt" → props.cards[2].imageUrl
11
+ * - "items[0].image.alt" → props.items[0].image.src
12
+ */
13
+ export declare function resolveImageUrlForAltField(blockProps: Record<string, unknown>, editablePath: string): string | undefined;
14
+ /**
15
+ * Fetch an image URL and return base64 + media type.
16
+ * For localhost URLs the AI APIs can't reach, we fetch locally.
17
+ * For remote URLs, returns null so the caller can use the URL directly.
18
+ */
19
+ export declare function fetchImageAsBase64(url: string): Promise<{
20
+ base64: string;
21
+ mediaType: string;
22
+ } | null>;
23
+ export type ResolvedLlmAttachment = {
24
+ kind: "image" | "pdf";
25
+ mediaType: string;
26
+ base64: string;
27
+ name?: string;
28
+ };
29
+ /**
30
+ * Fetch each chat attachment's bytes and return base64 + media type for the
31
+ * planner to embed as native multimodal content blocks. We always base64 the
32
+ * bytes (rather than pass a URL) so all three providers behave uniformly —
33
+ * OpenAI's `file` part requires inline data, and dev URLs (localhost) aren't
34
+ * reachable by the model APIs anyway. URLs are SSRF-allowlisted, fetches are
35
+ * bounded by a timeout and a byte cap, and anything that fails is skipped.
36
+ */
37
+ export declare function resolveAttachmentsForLlm(attachments: ChatAttachment[] | undefined): Promise<ResolvedLlmAttachment[]>;
38
+ export declare function selectedBlockSnapshot(args: {
39
+ currentPage: PageDoc;
40
+ activeBlockId?: string;
41
+ activeEditablePath?: string;
42
+ }): {
43
+ id: string;
44
+ type: import("@avocadostudio-ai/shared").BlockType;
45
+ props: Record<string, unknown>;
46
+ selectedEditablePath: string | null;
47
+ selectedEditableValue: {} | null;
48
+ } | null;
49
+ export declare function arrayPropLengths(props: Record<string, unknown>): Record<string, {
50
+ length: number;
51
+ labels?: string[];
52
+ }>;
53
+ export declare function pageIntentSummary(args: {
54
+ slug: string;
55
+ currentPage: PageDoc;
56
+ }): string;
57
+ export declare function plannerContextPack(args: {
58
+ session: string;
59
+ slug: string;
60
+ message: string;
61
+ currentPage: PageDoc;
62
+ activeBlockId?: string;
63
+ activeBlockType?: string;
64
+ activeEditablePath?: string;
65
+ includeFullProps?: boolean;
66
+ attachments?: ChatAttachment[];
67
+ }): {
68
+ selected: {
69
+ blockId: string | null;
70
+ blockType: string | null;
71
+ editablePath: string | null;
72
+ block: {
73
+ id: string;
74
+ type: import("@avocadostudio-ai/shared").BlockType;
75
+ props: Record<string, unknown>;
76
+ selectedEditablePath: string | null;
77
+ selectedEditableValue: {} | null;
78
+ } | null;
79
+ imageUrlForVision: string | null;
80
+ };
81
+ neighbors: {
82
+ previous: {
83
+ id: string;
84
+ type: import("@avocadostudio-ai/shared").BlockType;
85
+ } | null;
86
+ next: {
87
+ id: string;
88
+ type: import("@avocadostudio-ai/shared").BlockType;
89
+ } | null;
90
+ };
91
+ pageOutline: {
92
+ id: string;
93
+ type: import("@avocadostudio-ai/shared").BlockType;
94
+ props: Record<string, unknown>;
95
+ arrayProps: Record<string, {
96
+ length: number;
97
+ labels?: string[];
98
+ }>;
99
+ }[];
100
+ pageMeta: import("@avocadostudio-ai/shared").PageMeta | null;
101
+ pageIntent: string;
102
+ recentSuccessfulEdits: {
103
+ at: string;
104
+ summary: string;
105
+ ops: ("update_props" | "create_page" | "add_block" | "remove_block" | "move_block" | "duplicate_block" | "add_item" | "update_item" | "remove_item" | "move_item" | "reorder_items" | "reorder_blocks" | "rename_page" | "remove_page" | "move_page" | "duplicate_page" | "update_page_meta" | "update_site_config" | "update_theme")[];
106
+ }[];
107
+ resolvedReferences: {
108
+ target: {
109
+ id: string;
110
+ type: import("@avocadostudio-ai/shared").BlockType;
111
+ } | null;
112
+ anchor: {
113
+ id: string;
114
+ type: import("@avocadostudio-ai/shared").BlockType;
115
+ } | null;
116
+ mentionedBlocks: {
117
+ id: string;
118
+ type: import("@avocadostudio-ai/shared").BlockType;
119
+ reason: string;
120
+ }[];
121
+ };
122
+ siteConfig: {
123
+ name: string | null;
124
+ logo: string | null;
125
+ navLabels: Record<string, string>;
126
+ navGroups: Record<string, string[]>;
127
+ defaultNavLabels: Record<string, string>;
128
+ editPath: string;
129
+ };
130
+ attachments?: {
131
+ id: string;
132
+ kind: "image" | "pdf";
133
+ url: string;
134
+ mimeType: string;
135
+ name?: string | undefined;
136
+ bytes?: number | undefined;
137
+ }[] | undefined;
138
+ route: string;
139
+ pageRoutes: string[];
140
+ blockCount: number;
141
+ };