@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.
- package/LICENSE +201 -0
- package/dist/agent/agent-context.d.ts +19 -0
- package/dist/agent/agent-context.js +67 -0
- package/dist/agent/agent-logger.d.ts +5 -0
- package/dist/agent/agent-logger.js +22 -0
- package/dist/agent/agent-loop-openai.d.ts +8 -0
- package/dist/agent/agent-loop-openai.js +172 -0
- package/dist/agent/agent-loop.d.ts +56 -0
- package/dist/agent/agent-loop.js +167 -0
- package/dist/agent/agent-provider.d.ts +28 -0
- package/dist/agent/agent-provider.js +63 -0
- package/dist/agent/agent-tools.d.ts +28 -0
- package/dist/agent/agent-tools.js +899 -0
- package/dist/agent/context/editing-guidelines.md +46 -0
- package/dist/agent/context/role.md +39 -0
- package/dist/agent/integration-prompt.d.ts +9 -0
- package/dist/agent/integration-prompt.js +154 -0
- package/dist/agent/sites-agent-context.d.ts +12 -0
- package/dist/agent/sites-agent-context.js +316 -0
- package/dist/agent/sites-agent-shared.d.ts +161 -0
- package/dist/agent/sites-agent-shared.js +1101 -0
- package/dist/agent/sites-agent-tools.d.ts +18 -0
- package/dist/agent/sites-agent-tools.js +1227 -0
- package/dist/chat/anthropic-cache.d.ts +20 -0
- package/dist/chat/anthropic-cache.js +54 -0
- package/dist/chat/anthropic-planner.d.ts +98 -0
- package/dist/chat/anthropic-planner.js +1012 -0
- package/dist/chat/changelog-coverage-validator.d.ts +37 -0
- package/dist/chat/changelog-coverage-validator.js +215 -0
- package/dist/chat/chat-pipeline-context.d.ts +211 -0
- package/dist/chat/chat-pipeline-context.js +249 -0
- package/dist/chat/chat-pipeline-deterministic.d.ts +61 -0
- package/dist/chat/chat-pipeline-deterministic.js +407 -0
- package/dist/chat/chat-pipeline-image.d.ts +86 -0
- package/dist/chat/chat-pipeline-image.js +897 -0
- package/dist/chat/chat-pipeline-shared.d.ts +69 -0
- package/dist/chat/chat-pipeline-shared.js +212 -0
- package/dist/chat/chat-pipeline-translation.d.ts +27 -0
- package/dist/chat/chat-pipeline-translation.js +417 -0
- package/dist/chat/chat-pipeline-ui.d.ts +14 -0
- package/dist/chat/chat-pipeline-ui.js +244 -0
- package/dist/chat/chat-pipeline.d.ts +99 -0
- package/dist/chat/chat-pipeline.js +3999 -0
- package/dist/chat/decomposer.d.ts +21 -0
- package/dist/chat/decomposer.js +65 -0
- package/dist/chat/gemini-planner.d.ts +70 -0
- package/dist/chat/gemini-planner.js +541 -0
- package/dist/chat/hallucination-validator.d.ts +36 -0
- package/dist/chat/hallucination-validator.js +110 -0
- package/dist/chat/locale-strings.d.ts +47 -0
- package/dist/chat/locale-strings.js +100 -0
- package/dist/chat/plan-json-schema.d.ts +133 -0
- package/dist/chat/plan-json-schema.js +112 -0
- package/dist/chat/planner-types.d.ts +120 -0
- package/dist/chat/planner-types.js +66 -0
- package/dist/chat/planner.d.ts +148 -0
- package/dist/chat/planner.js +1361 -0
- package/dist/chat/prompts.d.ts +67 -0
- package/dist/chat/prompts.js +356 -0
- package/dist/chat/provider-routing.d.ts +14 -0
- package/dist/chat/provider-routing.js +27 -0
- package/dist/chat/variation-pipeline.d.ts +135 -0
- package/dist/chat/variation-pipeline.js +837 -0
- package/dist/chat/vision-alt-generator.d.ts +35 -0
- package/dist/chat/vision-alt-generator.js +152 -0
- package/dist/cms/adapter.d.ts +62 -0
- package/dist/cms/adapter.js +1 -0
- package/dist/cms/bootstrap.d.ts +17 -0
- package/dist/cms/bootstrap.js +85 -0
- package/dist/cms/editor-api-adapter.d.ts +23 -0
- package/dist/cms/editor-api-adapter.js +71 -0
- package/dist/cms/index.d.ts +4 -0
- package/dist/cms/index.js +3 -0
- package/dist/cms/json-file-adapter.d.ts +11 -0
- package/dist/cms/json-file-adapter.js +62 -0
- package/dist/demo-mode.d.ts +59 -0
- package/dist/demo-mode.js +201 -0
- package/dist/errors.d.ts +67 -0
- package/dist/errors.js +129 -0
- package/dist/http/chat-stream-resumable.d.ts +108 -0
- package/dist/http/chat-stream-resumable.js +290 -0
- package/dist/http/chat-stream.d.ts +99 -0
- package/dist/http/chat-stream.js +92 -0
- package/dist/image/gdrive-client.d.ts +22 -0
- package/dist/image/gdrive-client.js +215 -0
- package/dist/image/image-helpers.d.ts +95 -0
- package/dist/image/image-helpers.js +488 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/jira/jira-approval.d.ts +22 -0
- package/dist/jira/jira-approval.js +51 -0
- package/dist/jira/jira-client.d.ts +44 -0
- package/dist/jira/jira-client.js +313 -0
- package/dist/jira/jira-poller.d.ts +46 -0
- package/dist/jira/jira-poller.js +184 -0
- package/dist/jira/jira-processor.d.ts +103 -0
- package/dist/jira/jira-processor.js +1085 -0
- package/dist/jira/jira-types.d.ts +117 -0
- package/dist/jira/jira-types.js +38 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.js +28 -0
- package/dist/migration/mcp-server-stdio.d.ts +8 -0
- package/dist/migration/mcp-server-stdio.js +672 -0
- package/dist/migration/migration-prompt.d.ts +7 -0
- package/dist/migration/migration-prompt.js +197 -0
- package/dist/migration/migration-tools.d.ts +17 -0
- package/dist/migration/migration-tools.js +159 -0
- package/dist/migration/scrape-cache.d.ts +9 -0
- package/dist/migration/scrape-cache.js +19 -0
- package/dist/nlp/deterministic-planner-context.d.ts +141 -0
- package/dist/nlp/deterministic-planner-context.js +362 -0
- package/dist/nlp/deterministic-planner-pages.d.ts +26 -0
- package/dist/nlp/deterministic-planner-pages.js +170 -0
- package/dist/nlp/deterministic-planner-patches.d.ts +80 -0
- package/dist/nlp/deterministic-planner-patches.js +508 -0
- package/dist/nlp/deterministic-planner-refs.d.ts +33 -0
- package/dist/nlp/deterministic-planner-refs.js +164 -0
- package/dist/nlp/deterministic-planner-suggestions.d.ts +49 -0
- package/dist/nlp/deterministic-planner-suggestions.js +579 -0
- package/dist/nlp/deterministic-planner.d.ts +85 -0
- package/dist/nlp/deterministic-planner.js +1631 -0
- package/dist/nlp/intent-detection.d.ts +309 -0
- package/dist/nlp/intent-detection.js +730 -0
- package/dist/nlp/intent-helpers.d.ts +15 -0
- package/dist/nlp/intent-helpers.js +243 -0
- package/dist/nlp/intent-patterns.d.ts +40 -0
- package/dist/nlp/intent-patterns.js +223 -0
- package/dist/nlp/plan-normalizer.d.ts +41 -0
- package/dist/nlp/plan-normalizer.js +1537 -0
- package/dist/ops/destructive-action-gate.d.ts +44 -0
- package/dist/ops/destructive-action-gate.js +90 -0
- package/dist/ops/ops-engine.d.ts +151 -0
- package/dist/ops/ops-engine.js +1394 -0
- package/dist/publish/diff-engine.d.ts +18 -0
- package/dist/publish/diff-engine.js +305 -0
- package/dist/publish/publish-helpers.d.ts +87 -0
- package/dist/publish/publish-helpers.js +521 -0
- package/dist/publish/publish-target-registry.d.ts +7 -0
- package/dist/publish/publish-target-registry.js +61 -0
- package/dist/publish/publish-target.d.ts +81 -0
- package/dist/publish/publish-target.js +1 -0
- package/dist/publish/targets/deploy-hook.d.ts +13 -0
- package/dist/publish/targets/deploy-hook.js +123 -0
- package/dist/publish/targets/git.d.ts +13 -0
- package/dist/publish/targets/git.js +55 -0
- package/dist/publish/targets/site-contract.d.ts +19 -0
- package/dist/publish/targets/site-contract.js +124 -0
- package/dist/state/content-source.d.ts +17 -0
- package/dist/state/content-source.js +1 -0
- package/dist/state/in-memory-content-source.d.ts +27 -0
- package/dist/state/in-memory-content-source.js +51 -0
- package/dist/state/session-lock.d.ts +13 -0
- package/dist/state/session-lock.js +29 -0
- package/dist/state/session-state.d.ts +310 -0
- package/dist/state/session-state.js +1083 -0
- package/dist/state/sqlite-store-singleton.d.ts +31 -0
- package/dist/state/sqlite-store-singleton.js +170 -0
- package/dist/state/sqlite-store.d.ts +135 -0
- package/dist/state/sqlite-store.js +421 -0
- package/dist/telemetry/chat-telemetry.d.ts +105 -0
- package/dist/telemetry/chat-telemetry.js +247 -0
- package/dist/telemetry/eval-candidate-store.d.ts +50 -0
- package/dist/telemetry/eval-candidate-store.js +120 -0
- package/dist/telemetry/feedback-store.d.ts +34 -0
- package/dist/telemetry/feedback-store.js +76 -0
- package/dist/telemetry/jira-telemetry.d.ts +57 -0
- package/dist/telemetry/jira-telemetry.js +68 -0
- package/dist/telemetry/migration-telemetry.d.ts +35 -0
- package/dist/telemetry/migration-telemetry.js +40 -0
- package/dist/telemetry/usage.d.ts +24 -0
- package/dist/telemetry/usage.js +80 -0
- package/dist/tools/builtin-registrations.d.ts +12 -0
- package/dist/tools/builtin-registrations.js +33 -0
- package/dist/tools/builtins/gdrive-browse.d.ts +3 -0
- package/dist/tools/builtins/gdrive-browse.js +68 -0
- package/dist/tools/builtins/image-generate.d.ts +3 -0
- package/dist/tools/builtins/image-generate.js +211 -0
- package/dist/tools/builtins/unsplash-get-by-id.d.ts +23 -0
- package/dist/tools/builtins/unsplash-get-by-id.js +119 -0
- package/dist/tools/builtins/unsplash-search.d.ts +3 -0
- package/dist/tools/builtins/unsplash-search.js +74 -0
- package/dist/tools/executor.d.ts +23 -0
- package/dist/tools/executor.js +169 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.js +5 -0
- package/dist/tools/registry.d.ts +21 -0
- package/dist/tools/registry.js +75 -0
- package/dist/tools/runtime.d.ts +27 -0
- package/dist/tools/runtime.js +48 -0
- package/dist/tools/schema-validator.d.ts +24 -0
- package/dist/tools/schema-validator.js +88 -0
- package/dist/tools/types.d.ts +86 -0
- package/dist/tools/types.js +1 -0
- package/dist/variation-images.d.ts +19 -0
- package/dist/variation-images.js +12 -0
- 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
|
+
};
|