@typeroll/mcp-server 0.7.4 → 0.7.7
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/dist/index.js +16 -1
- package/dist/install-skills.js +130 -0
- package/dist/tools/pages.js +22 -6
- package/dist/tools/partials.js +9 -4
- package/dist/tools/search.js +6 -3
- package/dist/tools/settings.js +6 -1
- package/package.json +3 -3
- package/skills/README.md +23 -5
- package/skills/tr-blog.md +177 -0
- package/skills/tr-brand.md +169 -0
- package/skills/tr-forms.md +243 -0
- package/skills/tr-import-url.md +173 -0
- package/skills/tr-new-site.md +198 -0
- package/skills/tr-seo.md +179 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tr-brand
|
|
3
|
+
description: Use when the user asks to create a brand identity, design system, or visual style for a site. Triggers on "create a brand", "design the look", "choose colors", "pick fonts", "make it look like [reference]", or "rebrand the site". Produces a cohesive palette, typography scale, and CSS custom properties applied to an existing site.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Design a brand identity for a Typeroll site
|
|
7
|
+
|
|
8
|
+
This skill turns a brief (or a reference URL/screenshot) into a complete
|
|
9
|
+
visual design system applied to the site's settings and partials.
|
|
10
|
+
|
|
11
|
+
## Preconditions
|
|
12
|
+
|
|
13
|
+
- Site exists and MCP is configured.
|
|
14
|
+
- You have at least one of: industry, mood words, reference URL, existing
|
|
15
|
+
logo colors, or competitor sites to contrast with.
|
|
16
|
+
|
|
17
|
+
## Step 1 — Gather context
|
|
18
|
+
|
|
19
|
+
Ask (or infer from the brief):
|
|
20
|
+
|
|
21
|
+
1. **Industry + audience.** Law firm → formal, trust. Café → warm, approachable.
|
|
22
|
+
Tech startup → clean, modern. Interior design → refined, editorial.
|
|
23
|
+
2. **Mood words.** 3–5 adjectives the brand should feel: "minimal, Nordic,
|
|
24
|
+
calm" or "bold, energetic, playful".
|
|
25
|
+
3. **Reference.** A URL, a screenshot, or a competitor they like (and what
|
|
26
|
+
they want to be different from it).
|
|
27
|
+
4. **Must-keep.** Existing logo color? Legal industry color conventions?
|
|
28
|
+
|
|
29
|
+
If the user provided a URL, fetch it and note the dominant colors,
|
|
30
|
+
typeface categories, and layout density.
|
|
31
|
+
|
|
32
|
+
## Step 2 — Build the palette
|
|
33
|
+
|
|
34
|
+
A Typeroll site uses 7 color tokens:
|
|
35
|
+
|
|
36
|
+
| Token | Role | Design rule |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `primary` | Brand identity. CTA buttons, active nav, links. | High contrast on `background`. |
|
|
39
|
+
| `secondary` | Header, footer, darker sections. | Darker or more neutral than primary. |
|
|
40
|
+
| `accent` | Highlights, price tags, badges, hover states. | High-energy complement. |
|
|
41
|
+
| `background` | Page background. | Near-white for light themes, near-black for dark. |
|
|
42
|
+
| `surface` | Cards, input boxes, code blocks. | Slightly off from `background`. |
|
|
43
|
+
| `text` | Body copy. | ≥4.5:1 contrast ratio on `background`. |
|
|
44
|
+
| `text_light` | Secondary labels, captions, placeholders. | ≥3:1 on `background`. |
|
|
45
|
+
|
|
46
|
+
**Palette recipes by mood:**
|
|
47
|
+
|
|
48
|
+
*Nordic / minimal:*
|
|
49
|
+
```
|
|
50
|
+
primary: #1f2a30 secondary: #142027 accent: #c9b89a
|
|
51
|
+
background: #faf8f4 surface: #f2ede5 text: #1f2a30 text_light: #7a7265
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
*Warm / artisan:*
|
|
55
|
+
```
|
|
56
|
+
primary: #3d2b1f secondary: #2a1d14 accent: #c8860a
|
|
57
|
+
background: #fdf6ee surface: #f7ede0 text: #1a1008 text_light: #8a7060
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
*Modern / tech:*
|
|
61
|
+
```
|
|
62
|
+
primary: #2563eb secondary: #1e293b accent: #f59e0b
|
|
63
|
+
background: #ffffff surface: #f8fafc text: #0f172a text_light: #64748b
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
*Editorial / dark:*
|
|
67
|
+
```
|
|
68
|
+
primary: #e2c08d secondary: #0f0f0f accent: #e2c08d
|
|
69
|
+
background: #0f0f0f surface: #1a1a1a text: #f5f5f0 text_light: #a0a090
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Check WCAG contrast ratios mentally: text on background must be ≥4.5:1.
|
|
73
|
+
The online tool `https://webaim.org/resources/contrastchecker/` is useful
|
|
74
|
+
but not accessible during a tool call — reason about perceived contrast
|
|
75
|
+
instead (light grey on white = bad; dark grey on white = fine).
|
|
76
|
+
|
|
77
|
+
## Step 3 — Choose typefaces
|
|
78
|
+
|
|
79
|
+
Pick from high-quality Google Fonts pairings:
|
|
80
|
+
|
|
81
|
+
| Heading | Body | Mood |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| Cormorant Garamond | Raleway | Luxury, editorial |
|
|
84
|
+
| Playfair Display | Source Sans 3 | Classic, readable |
|
|
85
|
+
| DM Serif Display | DM Sans | Contemporary, clean |
|
|
86
|
+
| Fraunces | Mulish | Artisan, craft |
|
|
87
|
+
| Syne | Inter | Bold, modern |
|
|
88
|
+
| Plus Jakarta Sans | Plus Jakarta Sans | Clean, versatile |
|
|
89
|
+
| Libre Baskerville | Libre Franklin | Traditional, trustworthy |
|
|
90
|
+
|
|
91
|
+
Same font for heading and body is fine if it has enough weight variation
|
|
92
|
+
(Inter at 700 + 400 works well).
|
|
93
|
+
|
|
94
|
+
`size_base` should be 16 for most sites; 17–18 for text-heavy editorial
|
|
95
|
+
sites; 15 for dense dashboards.
|
|
96
|
+
|
|
97
|
+
## Step 4 — Apply to the site
|
|
98
|
+
|
|
99
|
+
One call sets everything:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
update_site_settings {
|
|
103
|
+
"colors": { ...all 7 tokens },
|
|
104
|
+
"fonts": { "heading": "...", "body": "...", "size_base": 16 },
|
|
105
|
+
"custom_css": "/* optional: utility classes or @keyframes */"
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Read back to confirm: `read_site_settings`.
|
|
110
|
+
|
|
111
|
+
## Step 5 — Update partials to use the new palette
|
|
112
|
+
|
|
113
|
+
Partials that hardcoded hex colors need updating. Fetch the header:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
read_partial partial_id="header"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
If it has hardcoded colors, replace them with CSS variable references
|
|
120
|
+
(`var(--color-primary)`) and call `replace_partial`:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
replace_partial partial_id="header" html_content="<updated HTML>"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Same for footer.
|
|
127
|
+
|
|
128
|
+
## Step 6 — Custom CSS for advanced tokens (optional)
|
|
129
|
+
|
|
130
|
+
If the brand needs things beyond the 7 base tokens — e.g. a gradient,
|
|
131
|
+
a special border radius, or a branded highlight color — add them via
|
|
132
|
+
`custom_css`:
|
|
133
|
+
|
|
134
|
+
```css
|
|
135
|
+
:root {
|
|
136
|
+
--brand-gradient: linear-gradient(135deg, var(--color-primary), var(--color-accent));
|
|
137
|
+
--radius-brand: 2px; /* sharp corners for formal brands */
|
|
138
|
+
--letter-spacing-display: -0.03em; /* tight tracking for display headings */
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Then reference `var(--brand-gradient)` etc. in page HTML and partials.
|
|
143
|
+
|
|
144
|
+
## Step 7 — Preview
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
get_preview_link
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Open in browser. Check:
|
|
151
|
+
- Colors render as intended (not "undefined" or missing)
|
|
152
|
+
- Fonts load (Google Fonts link is in `<head>`)
|
|
153
|
+
- Nav text is readable against header background
|
|
154
|
+
- Body text has sufficient contrast
|
|
155
|
+
|
|
156
|
+
## Pitfalls
|
|
157
|
+
|
|
158
|
+
- **Don't set colors without checking the header contrast.** If `primary`
|
|
159
|
+
is light, white nav text becomes unreadable. Either darken `primary` or
|
|
160
|
+
make the header use `secondary`.
|
|
161
|
+
- **Custom_css is global.** Rules here apply to every page. Keep it to
|
|
162
|
+
`:root {}` token additions and truly global utilities. Page-specific
|
|
163
|
+
styles go in the page's HTML `<style>` block.
|
|
164
|
+
- **Google Fonts load time.** Two different font families is fine; three
|
|
165
|
+
adds measurable LCP impact. Stick to two families with variable-font
|
|
166
|
+
versions when possible.
|
|
167
|
+
- **Dark themes need dark surface too.** Setting `background: #0f0f0f`
|
|
168
|
+
but leaving `surface: #f8fafc` (white) breaks every card/input. Always
|
|
169
|
+
update all 7 tokens as a set.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tr-forms
|
|
3
|
+
description: Use when the user wants to add a contact form, booking form, or any web form to their Typeroll site. Triggers on "lägg till formulär", "contact form", "add a form", "let visitors message us", "booking form", "formulär", or similar. Covers both creating the form definition and embedding the HTML widget on a page.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add a form to a Typeroll site
|
|
7
|
+
|
|
8
|
+
Typeroll forms are server-backed: submissions go to
|
|
9
|
+
`/api/forms/submit` (HMAC-signed, rate-limited, honeypot-protected)
|
|
10
|
+
and are stored in Firestore. The user sees them in the portal's
|
|
11
|
+
Submissions inbox. No third-party service needed.
|
|
12
|
+
|
|
13
|
+
## Preconditions
|
|
14
|
+
|
|
15
|
+
- Site exists and MCP is configured.
|
|
16
|
+
- You know what fields the form needs.
|
|
17
|
+
|
|
18
|
+
## Recipe
|
|
19
|
+
|
|
20
|
+
### 1. Create the form definition
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
create_form {
|
|
24
|
+
"id": "kontakt",
|
|
25
|
+
"name": "Kontaktformulär",
|
|
26
|
+
"fields": [
|
|
27
|
+
{"name":"name", "label":"Namn", "type":"text", "required":true},
|
|
28
|
+
{"name":"email", "label":"E-post", "type":"email", "required":true},
|
|
29
|
+
{"name":"phone", "label":"Telefon", "type":"text"},
|
|
30
|
+
{"name":"message", "label":"Meddelande", "type":"textarea", "required":true},
|
|
31
|
+
{"name":"subject", "label":"Ämne", "type":"select",
|
|
32
|
+
"options":["Prisförfrågan","Samarbete","Övrigt"]}
|
|
33
|
+
],
|
|
34
|
+
"success_message": "Tack! Vi återkommer inom 24 timmar.",
|
|
35
|
+
"recipient_email": "hej@acme.se"
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Field types:** `text`, `email`, `tel`, `url`, `number`, `textarea`,
|
|
40
|
+
`select`, `radio`, `checkbox`.
|
|
41
|
+
|
|
42
|
+
**Field name rules:** Lowercase ASCII only: `[a-z][a-z0-9_-]*`.
|
|
43
|
+
- `besokt` not `besökt` (`ö→o`)
|
|
44
|
+
- `foretag` not `företag` (`ö→o`, `ä→a`)
|
|
45
|
+
- `meddelande` not `Meddelande` (the `name` must be lowercase; `label` can be anything)
|
|
46
|
+
|
|
47
|
+
### 2. Get the signed embed token
|
|
48
|
+
|
|
49
|
+
The form's submit button needs a signed token that proves it was
|
|
50
|
+
generated by the platform. Fetch it:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
read_form form_id="kontakt"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The response includes `submit_token` (a short-lived HMAC). Use this in
|
|
57
|
+
the HTML.
|
|
58
|
+
|
|
59
|
+
Actually, the embed HTML is best generated by reading the form and
|
|
60
|
+
building it manually — the form's `id` is all you need for the action URL.
|
|
61
|
+
|
|
62
|
+
### 3. Embed the form on a page
|
|
63
|
+
|
|
64
|
+
Typeroll forms submit to the platform's endpoint. The HTML:
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<section class="contact-section">
|
|
68
|
+
<div class="contact-container">
|
|
69
|
+
<h2>Kontakta oss</h2>
|
|
70
|
+
<p>Fyll i formuläret så återkommer vi inom 24 timmar.</p>
|
|
71
|
+
|
|
72
|
+
<form class="contact-form"
|
|
73
|
+
action="https://app.typeroll.com/api/forms/submit"
|
|
74
|
+
method="POST">
|
|
75
|
+
<!-- Hidden fields — required by the platform -->
|
|
76
|
+
<input type="hidden" name="_form_id" value="kontakt">
|
|
77
|
+
<input type="hidden" name="_site_id" value="YOUR_SITE_ID">
|
|
78
|
+
<input type="hidden" name="_token" value="SIGNED_TOKEN">
|
|
79
|
+
<!-- Honeypot — must stay empty, bots fill it -->
|
|
80
|
+
<input type="text" name="_hp" style="display:none" tabindex="-1" autocomplete="off">
|
|
81
|
+
|
|
82
|
+
<div class="form-group">
|
|
83
|
+
<label for="name">Namn *</label>
|
|
84
|
+
<input type="text" id="name" name="name" required>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div class="form-group">
|
|
88
|
+
<label for="email">E-post *</label>
|
|
89
|
+
<input type="email" id="email" name="email" required>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div class="form-group">
|
|
93
|
+
<label for="message">Meddelande *</label>
|
|
94
|
+
<textarea id="message" name="message" rows="5" required></textarea>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div class="form-group">
|
|
98
|
+
<label for="subject">Ämne</label>
|
|
99
|
+
<select id="subject" name="subject">
|
|
100
|
+
<option value="Prisförfrågan">Prisförfrågan</option>
|
|
101
|
+
<option value="Samarbete">Samarbete</option>
|
|
102
|
+
<option value="Övrigt">Övrigt</option>
|
|
103
|
+
</select>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<button type="submit" class="btn-primary">Skicka meddelande</button>
|
|
107
|
+
</form>
|
|
108
|
+
</div>
|
|
109
|
+
</section>
|
|
110
|
+
|
|
111
|
+
<style>
|
|
112
|
+
.contact-section{padding:4rem 2rem}
|
|
113
|
+
.contact-container{max-width:600px;margin:0 auto}
|
|
114
|
+
.form-group{margin-bottom:1.5rem}
|
|
115
|
+
.form-group label{display:block;font-weight:600;margin-bottom:0.4rem;font-size:0.9rem}
|
|
116
|
+
.form-group input,.form-group textarea,.form-group select{
|
|
117
|
+
width:100%;padding:0.75rem 1rem;border:1px solid var(--color-surface);
|
|
118
|
+
border-radius:0.375rem;font-family:inherit;font-size:1rem;
|
|
119
|
+
background:var(--color-surface);color:var(--color-text)
|
|
120
|
+
}
|
|
121
|
+
.form-group textarea{resize:vertical}
|
|
122
|
+
.btn-primary{
|
|
123
|
+
background:var(--color-primary);color:#fff;border:none;
|
|
124
|
+
padding:0.875rem 2rem;border-radius:0.375rem;font-size:1rem;
|
|
125
|
+
font-weight:600;cursor:pointer;width:100%
|
|
126
|
+
}
|
|
127
|
+
.btn-primary:hover{opacity:0.9}
|
|
128
|
+
</style>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Replace:**
|
|
132
|
+
- `YOUR_SITE_ID` → the site's actual id (from `get_site`)
|
|
133
|
+
- `SIGNED_TOKEN` → fetch via `read_form form_id="kontakt"` → `submit_token`
|
|
134
|
+
|
|
135
|
+
### 4. Add success/error handling (optional JS)
|
|
136
|
+
|
|
137
|
+
To show inline feedback without a page reload:
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<script>
|
|
141
|
+
(function(){
|
|
142
|
+
const form = document.querySelector('.contact-form');
|
|
143
|
+
if(!form) return;
|
|
144
|
+
form.addEventListener('submit', async(e) => {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
const btn = form.querySelector('[type=submit]');
|
|
147
|
+
btn.disabled = true;
|
|
148
|
+
btn.textContent = 'Skickar…';
|
|
149
|
+
try {
|
|
150
|
+
const res = await fetch(form.action, {method:'POST', body: new FormData(form)});
|
|
151
|
+
const data = await res.json();
|
|
152
|
+
if(data.ok) {
|
|
153
|
+
form.innerHTML = '<p class="form-success">Tack! Vi återkommer inom 24 timmar.</p>';
|
|
154
|
+
} else {
|
|
155
|
+
btn.disabled = false;
|
|
156
|
+
btn.textContent = 'Skicka meddelande';
|
|
157
|
+
alert('Något gick fel: ' + (data.error || 'okänt fel'));
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
btn.disabled = false;
|
|
161
|
+
btn.textContent = 'Skicka meddelande';
|
|
162
|
+
alert('Nätverksfel — försök igen.');
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
})();
|
|
166
|
+
</script>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 5. Update the contact page with the form HTML
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
update_page page_id="kontakt" patch={
|
|
173
|
+
"html_content": "<full page HTML including the form section>"
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 6. Verify
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
read_form form_id="kontakt"
|
|
181
|
+
list_forms
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Confirm the form appears and fields match what you embedded.
|
|
185
|
+
|
|
186
|
+
### 7. Deploy
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
trigger_deploy
|
|
190
|
+
get_deploy_status job_id=<id>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
After deploy, test by submitting the live form. Submissions appear in the
|
|
194
|
+
portal at `/app/sites/{siteId}/forms/kontakt/submissions`.
|
|
195
|
+
|
|
196
|
+
## Common form patterns
|
|
197
|
+
|
|
198
|
+
### Booking / appointment request
|
|
199
|
+
```json
|
|
200
|
+
{"fields": [
|
|
201
|
+
{"name":"name", "type":"text", "label":"Namn", "required":true},
|
|
202
|
+
{"name":"email", "type":"email", "label":"E-post", "required":true},
|
|
203
|
+
{"name":"date", "type":"text", "label":"Önskat datum (YYYY-MM-DD)"},
|
|
204
|
+
{"name":"time", "type":"select","label":"Tid", "options":["09:00","10:00","11:00","14:00","15:00"]},
|
|
205
|
+
{"name":"notes", "type":"textarea","label":"Kommentar"}
|
|
206
|
+
]}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Newsletter signup (minimal)
|
|
210
|
+
```json
|
|
211
|
+
{"fields": [
|
|
212
|
+
{"name":"email", "type":"email", "label":"E-postadress", "required":true}
|
|
213
|
+
]}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Job application
|
|
217
|
+
```json
|
|
218
|
+
{"fields": [
|
|
219
|
+
{"name":"name", "type":"text", "label":"Namn", "required":true},
|
|
220
|
+
{"name":"email", "type":"email", "label":"E-post", "required":true},
|
|
221
|
+
{"name":"role", "type":"select","label":"Roll", "options":["Designer","Projektledare","Övrigt"]},
|
|
222
|
+
{"name":"experience", "type":"textarea","label":"Berätta om dig själv"},
|
|
223
|
+
{"name":"portfolio", "type":"url", "label":"Portfolio-URL"}
|
|
224
|
+
]}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Pitfalls
|
|
228
|
+
|
|
229
|
+
- **Tokens expire.** The `submit_token` in the form embed is short-lived
|
|
230
|
+
(24h by default). For forms on long-cached static pages, fetch a fresh
|
|
231
|
+
token via `read_form` and rebuild the page HTML, then redeploy.
|
|
232
|
+
Alternatively, fetch the token client-side via a small `<script>` that
|
|
233
|
+
calls the portal's token endpoint before submit.
|
|
234
|
+
- **Field names must be ASCII.** `message` not `meddelande`... wait,
|
|
235
|
+
`meddelande` is ASCII. `foretag` not `företag`. Only Swedish/Nordic
|
|
236
|
+
special characters (å, ä, ö) cause problems.
|
|
237
|
+
- **Don't use the same form_id on two different forms.** IDs must be
|
|
238
|
+
unique per site — use descriptive names: `kontakt`, `boka`, `nyhetsbrev`.
|
|
239
|
+
- **Honeypot must be invisible.** `_hp` field must have `display:none`.
|
|
240
|
+
If it's visible and a real user fills it, their submission is rejected.
|
|
241
|
+
- **Recipient email is display-only in phase 1.** The portal stores
|
|
242
|
+
submissions; email delivery to `recipient_email` depends on the platform's
|
|
243
|
+
email configuration. Confirm with the customer that they'll check the inbox.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tr-import-url
|
|
3
|
+
description: Use when the user wants to import or migrate content from a non-WordPress website — a Squarespace site, a Wix site, a static HTML site, a Webflow export, or any URL the user points at. Also triggers on "copy content from", "rebuild this site", "import from Squarespace/Wix/Webflow", or "make it look like this site". For WordPress sources use tr-migrate-wp instead.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Import content from a non-WordPress site
|
|
7
|
+
|
|
8
|
+
## When to use this vs tr-migrate-wp
|
|
9
|
+
|
|
10
|
+
| Source | Use |
|
|
11
|
+
|---|---|
|
|
12
|
+
| WordPress with `/wp-json` accessible | `tr-migrate-wp` |
|
|
13
|
+
| WordPress with REST disabled | This skill (scrape HTML) |
|
|
14
|
+
| Squarespace, Wix, Webflow, static HTML | This skill |
|
|
15
|
+
| CSV / spreadsheet data | This skill (skip scraping, just parse) |
|
|
16
|
+
| Any URL the user points at | This skill |
|
|
17
|
+
|
|
18
|
+
## Preconditions
|
|
19
|
+
|
|
20
|
+
- Target Typeroll site exists with working header/footer.
|
|
21
|
+
- Source URL(s) accessible (check with a quick `fetch`; if blocked, mention
|
|
22
|
+
it and ask the user for an HTML export or screenshot).
|
|
23
|
+
|
|
24
|
+
## Recipe
|
|
25
|
+
|
|
26
|
+
### 1. Inventory the source site
|
|
27
|
+
|
|
28
|
+
Fetch the homepage and build a URL list:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
fetch <source-url> # root HTML
|
|
32
|
+
fetch <source-url>/sitemap.xml # XML sitemap if it exists
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Parse `<a href>` links to discover internal pages. Build a list:
|
|
36
|
+
- Homepage
|
|
37
|
+
- Top-level pages (About, Services, Contact, etc.)
|
|
38
|
+
- Any sub-pages that look important
|
|
39
|
+
|
|
40
|
+
Avoid: pagination URLs, session URLs, `/wp-admin`, `/cdn-cgi/`, query strings.
|
|
41
|
+
|
|
42
|
+
### 2. Learn the target's design
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
read_site_settings
|
|
46
|
+
read_partial partial_id="header"
|
|
47
|
+
list_pages limit=5
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The goal is to understand what CSS variables, class names, and structural
|
|
51
|
+
conventions the target site uses so the imported content looks native.
|
|
52
|
+
|
|
53
|
+
### 3. Fetch and clean each source page
|
|
54
|
+
|
|
55
|
+
For each URL:
|
|
56
|
+
|
|
57
|
+
**a. Fetch the HTML.**
|
|
58
|
+
```
|
|
59
|
+
fetch <page-url>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If the site returns a bot-block (Cloudflare, 403, or clearly JS-only
|
|
63
|
+
SPA output), note it. Tell the user: "This page blocked direct fetching.
|
|
64
|
+
Can you provide the page source or an HTML export?"
|
|
65
|
+
|
|
66
|
+
**b. Extract the main content.**
|
|
67
|
+
|
|
68
|
+
Discard: nav, header, footer, cookie banners, chat widgets, scripts.
|
|
69
|
+
Keep: `<main>`, `<article>`, the largest content region.
|
|
70
|
+
|
|
71
|
+
Clean the HTML:
|
|
72
|
+
- Strip platform-specific classes: `sqsrte-*`, `wf-*`, `et_*`,
|
|
73
|
+
`elementor-*`, `fl-*`, `divi-*`, `vc_*`
|
|
74
|
+
- Remove empty `<div>`, `<span>`, `<section>` wrappers (no class, no content)
|
|
75
|
+
- Unwrap redundant nesting: `<div><p>text</p></div>` → `<p>text</p>`
|
|
76
|
+
- Keep: `<h1>`–`<h6>`, `<p>`, `<ul>`, `<ol>`, `<img>`, `<a>`, `<table>`,
|
|
77
|
+
`<blockquote>`, `<figure>`, `<figcaption>`, `<strong>`, `<em>`
|
|
78
|
+
- Fix headings: ensure exactly one `<h1>` per page (the page title)
|
|
79
|
+
|
|
80
|
+
**c. Transfer images.** For each `<img src>`:
|
|
81
|
+
```
|
|
82
|
+
upload_media_from_url url="<source-img-url>" alt="..."
|
|
83
|
+
```
|
|
84
|
+
Replace the src with the returned CDN URL. Skip tracking pixels
|
|
85
|
+
(1×1 images), decorative SVGs that are just icons, and anything
|
|
86
|
+
that 404s.
|
|
87
|
+
|
|
88
|
+
**d. Adapt to the target's design.**
|
|
89
|
+
Replace source-specific CSS classes with target conventions.
|
|
90
|
+
Use `var(--color-*)` for colors, `var(--font-*)` for type.
|
|
91
|
+
|
|
92
|
+
### 4. Create pages as drafts
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
create_page title="Om oss" slug="om-oss"
|
|
96
|
+
html_content="<cleaned, adapted HTML>"
|
|
97
|
+
content_mode="html" status="draft"
|
|
98
|
+
seo_title="Om oss — Acme"
|
|
99
|
+
seo_description="..."
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Always draft first. The user signs off before publishing.
|
|
103
|
+
|
|
104
|
+
### 5. Handle redirects
|
|
105
|
+
|
|
106
|
+
If the source URLs differ from the target slugs, create redirects:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
create_redirect from_path="/about" to_path="/om-oss"
|
|
110
|
+
create_redirect from_path="/services.html" to_path="/tjanster"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 6. Preview with the user
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
get_preview_link
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Walk through every imported page with the user. Common issues:
|
|
120
|
+
- Heading hierarchy wrong (two H1s, or H3 used where H2 belongs)
|
|
121
|
+
- Images missing alt text
|
|
122
|
+
- Squarespace column layouts that don't work without their grid system
|
|
123
|
+
- Embedded forms or maps that need re-setup
|
|
124
|
+
|
|
125
|
+
### 7. Publish + deploy
|
|
126
|
+
|
|
127
|
+
After approval:
|
|
128
|
+
```
|
|
129
|
+
batch_update_pages updates=[
|
|
130
|
+
{page_id: "om-oss", patch: {status: "published"}},
|
|
131
|
+
{page_id: "tjanster", patch: {status: "published"}}
|
|
132
|
+
]
|
|
133
|
+
trigger_deploy
|
|
134
|
+
get_deploy_status job_id=<id>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Platform-specific notes
|
|
138
|
+
|
|
139
|
+
### Squarespace
|
|
140
|
+
- Main content is inside `.content-wrapper` or `[data-section-theme]` blocks
|
|
141
|
+
- Portfolio images are usually high-resolution originals in `/universal/images/`
|
|
142
|
+
- JSON-LD is Squarespace's own schema — strip it
|
|
143
|
+
- Gallery blocks → convert to CSS grid with inline `<img>` tags
|
|
144
|
+
|
|
145
|
+
### Wix
|
|
146
|
+
- Wix sites are React SPAs — `fetch` returns an empty shell
|
|
147
|
+
- Ask the user for the Wix site's "Export to HTML" (available in some plans)
|
|
148
|
+
or take screenshots for reference
|
|
149
|
+
- Best path: get content from the user (text + image files), rebuild clean
|
|
150
|
+
|
|
151
|
+
### Webflow
|
|
152
|
+
- Usually fetchable; clean output
|
|
153
|
+
- Classes like `w-container`, `w-row`, `w-col-*` can be stripped
|
|
154
|
+
- Webflow CMS items are server-rendered — they appear in the HTML
|
|
155
|
+
|
|
156
|
+
### Static HTML / old sites
|
|
157
|
+
- Often the cleanest import. Fetch, strip nav/footer, keep body.
|
|
158
|
+
- Watch for table-based layouts (pre-2010 sites) — convert to CSS grid
|
|
159
|
+
|
|
160
|
+
## Pitfalls
|
|
161
|
+
|
|
162
|
+
- **Don't import `<style>` blocks from the source site.** They reference
|
|
163
|
+
external fonts, resets, and classes that don't exist in the target.
|
|
164
|
+
Strip all `<style>` tags from source HTML and rewrite styles in the
|
|
165
|
+
target's conventions.
|
|
166
|
+
- **Don't break the single-H1 rule.** Many source sites have no H1 or
|
|
167
|
+
several. Fix it.
|
|
168
|
+
- **Squarespace/Wix forms.** They won't work after import — the backend
|
|
169
|
+
is vendor-locked. Create a Typeroll form instead: `create_form`.
|
|
170
|
+
- **Analytics/tracking code.** If the source has GA4 or similar, don't
|
|
171
|
+
copy it into pages. Set it via `update_site_settings scripts_head="..."`.
|
|
172
|
+
- **Videos.** YouTube/Vimeo embeds are fine (`<iframe>` is allowed).
|
|
173
|
+
Hosted MP4s need re-uploading if the source URL won't persist.
|