@rubytech/create-maxy-code 0.1.111 → 0.1.113

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 (54) hide show
  1. package/dist/__tests__/brew-install.test.js +10 -0
  2. package/dist/__tests__/brew-resolve.test.js +35 -0
  3. package/dist/__tests__/launchd-plist.test.js +46 -0
  4. package/dist/__tests__/macos-darwin-branch.test.js +85 -0
  5. package/dist/brew-install.js +5 -0
  6. package/dist/index.js +26 -6
  7. package/package.json +1 -1
  8. package/payload/platform/plugins/cloudflare/references/manual-setup.md +105 -0
  9. package/payload/platform/plugins/docs/references/deployment.md +22 -4
  10. package/payload/platform/plugins/memory/mcp/dist/index.js +35 -0
  11. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  12. package/payload/platform/plugins/memory/mcp/dist/tools/image-fetch.d.ts +15 -0
  13. package/payload/platform/plugins/memory/mcp/dist/tools/image-fetch.d.ts.map +1 -0
  14. package/payload/platform/plugins/memory/mcp/dist/tools/image-fetch.js +64 -0
  15. package/payload/platform/plugins/memory/mcp/dist/tools/image-fetch.js.map +1 -0
  16. package/payload/platform/plugins/memory/references/schema-estate-agent.md +76 -1
  17. package/payload/platform/plugins/venture-studio/PLUGIN.md +13 -4
  18. package/payload/platform/plugins/venture-studio/bin/scaffold.sh +15 -3
  19. package/payload/platform/plugins/venture-studio/skills/brand-pack/SKILL.md +1 -1
  20. package/payload/platform/plugins/venture-studio/skills/investor-data-room/SKILL.md +27 -14
  21. package/payload/platform/plugins/venture-studio/skills/investor-data-room/references/business-plan-template.md +7 -4
  22. package/payload/platform/plugins/venture-studio/skills/investor-data-room/references/data-room-structure.md +16 -5
  23. package/payload/platform/plugins/venture-studio/skills/investor-data-room/references/deck-blueprint-template.md +7 -6
  24. package/payload/platform/plugins/venture-studio/skills/office-hours/SKILL.md +8 -9
  25. package/payload/platform/plugins/venture-studio/skills/prototype-host/SKILL.md +179 -0
  26. package/payload/platform/plugins/venture-studio/skills/prototype-host/references/cloudflared-ingress-edit.md +81 -0
  27. package/payload/platform/plugins/venture-studio/skills/prototype-host/references/scaffold-frameworks.md +60 -0
  28. package/payload/platform/plugins/venture-studio/skills/prototype-host/references/systemd-user-service.md +104 -0
  29. package/payload/platform/plugins/venture-studio/skills/zero-to-prototype/SKILL.md +4 -0
  30. package/payload/platform/templates/agents/public/IDENTITY.md +11 -1
  31. package/payload/premium-plugins/real-agent/BUNDLE.md +3 -2
  32. package/payload/premium-plugins/real-agent/agents/listing-curator.md +152 -0
  33. package/payload/premium-plugins/real-agent/plugins/brochures/skills/a4-print-documents/SKILL.md +1 -1
  34. package/payload/premium-plugins/real-agent/plugins/brochures/skills/brand-design/SKILL.md +5 -11
  35. package/payload/premium-plugins/real-agent/plugins/brochures/skills/make-brochure/SKILL.md +7 -7
  36. package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-extract/SKILL.md +31 -39
  37. package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-market-report/SKILL.md +4 -4
  38. package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-socials/SKILL.md +3 -2
  39. package/payload/premium-plugins/real-agent/plugins/buyers/.claude-plugin/plugin.json +1 -1
  40. package/payload/premium-plugins/real-agent/plugins/buyers/PLUGIN.md +3 -2
  41. package/payload/premium-plugins/real-agent/plugins/buyers/skills/property-recommender/SKILL.md +96 -0
  42. package/payload/premium-plugins/venture-studio/PLUGIN.md +13 -4
  43. package/payload/premium-plugins/venture-studio/bin/scaffold.sh +15 -3
  44. package/payload/premium-plugins/venture-studio/skills/brand-pack/SKILL.md +1 -1
  45. package/payload/premium-plugins/venture-studio/skills/investor-data-room/SKILL.md +27 -14
  46. package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/business-plan-template.md +7 -4
  47. package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/data-room-structure.md +16 -5
  48. package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/deck-blueprint-template.md +7 -6
  49. package/payload/premium-plugins/venture-studio/skills/office-hours/SKILL.md +8 -9
  50. package/payload/premium-plugins/venture-studio/skills/prototype-host/SKILL.md +179 -0
  51. package/payload/premium-plugins/venture-studio/skills/prototype-host/references/cloudflared-ingress-edit.md +81 -0
  52. package/payload/premium-plugins/venture-studio/skills/prototype-host/references/scaffold-frameworks.md +60 -0
  53. package/payload/premium-plugins/venture-studio/skills/prototype-host/references/systemd-user-service.md +104 -0
  54. package/payload/premium-plugins/venture-studio/skills/zero-to-prototype/SKILL.md +4 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: property-extract
3
- description: Use when a live property listing URL on an estate-agent website (Muvin, or any site using the Loop CRM CDN at b-cdn.net) needs to be turned into a local property assets directory containing photos, floorplans, EPC documents, and structured metadata that the property-brochure skill can consume. Trigger phrases include "extract property from <url>", "get property details from <url>", "scrape this listing", "pull all the photos from <listing>", "stage assets for the brochure", or any request that names a live property URL and asks for the photos, floorplans, and details to be saved locally.
3
+ description: Use when a live property listing URL on any UK estate-agent website needs to be turned into a local property assets directory containing photos, floorplans, EPC documents, and structured metadata that the property-brochure skill can consume. Trigger phrases include "extract property from <url>", "get property details from <url>", "scrape this listing", "pull all the photos from <listing>", "stage assets for the brochure", or any request that names a live property URL and asks for the photos, floorplans, and details to be saved locally.
4
4
  allowed-tools:
5
5
  - Bash
6
6
  - Read
@@ -15,7 +15,7 @@ allowed-tools:
15
15
 
16
16
  Turn one publicly-accessible property listing URL into a self-contained property assets directory that `property-brochure` can consume without re-visiting the source site.
17
17
 
18
- The reference output this skill targets is `/Users/neo/estate-agents/muvin/properties/henham-road-debden-green-hamperden-end-cb11-3lz-949931/` read that directory once before generating to anchor the standard.
18
+ The contract is **portal-agnostic**: the output shape is the same whether the source is an agent's own site (Muvin / Loop CRM tenants, custom builds) or a portal (Rightmove, Zoopla, OnTheMarket, OpenRent). The static-HTML probe is the cheap default; when it returns no asset URLs or the data is tab-bound, fall back to a headless browser. See **Process — high level**.
19
19
 
20
20
  ## Outcome contract
21
21
 
@@ -34,7 +34,7 @@ Every photograph and floorplan referenced by the listing must be downloaded. `ep
34
34
 
35
35
  Do **not** pre-create a `brochure/` slot. `property-brochure` writes its deliverable directly to `<property_dir>/output/` — see `property-brochure/SKILL.md → Reference standard`. An empty `brochure/` next to `output/` is the legacy layout and a defect.
36
36
 
37
- `<property-slug>` comes from the URL path segment (e.g. `henham-road-debden-green-hamperden-end-cb11-3lz`). `<listing-id>` is the numeric ID, also from the URL. Concatenating them keeps the folder both human-readable and unique even when the same street has multiple listings.
37
+ `<property-slug>` comes from the URL path segment (e.g. `<street-locality-postcode>` for Loop-CRM-style URLs, or whatever slug component the source portal exposes). `<listing-id>` is the numeric ID, also from the URL. Concatenating them keeps the folder both human-readable and unique even when the same street has multiple listings.
38
38
 
39
39
  Anything else (raw HTML dumps, `/tmp` files, intermediate URL lists) must be cleaned up before reporting completion.
40
40
 
@@ -42,7 +42,7 @@ Anything else (raw HTML dumps, `/tmp` files, intermediate URL lists) must be cle
42
42
 
43
43
  Required: a property listing URL, fully-formed and publicly accessible. If only a postcode or property name is given, decline and ask for the URL — listings are uniquely identified by their numeric IDs, and there is no reliable way to infer one from a description.
44
44
 
45
- Optional: an output directory. The default is `<brand_dir>/properties/`, where `<brand_dir>` is the brand workspace for the agent that owns the listing. For a Muvin URL with the existing brand pack at `/Users/neo/estate-agents/muvin/`, that resolves to `/Users/neo/estate-agents/muvin/properties/`. If the brand workspace cannot be located, fall back to `./properties/` in the caller's current working directory.
45
+ Optional: an output directory. The default is `<brand_dir>/properties/`, where `<brand_dir>` is the brand workspace for the agent that owns the listing. If a brand pack already exists at `<brand_dir>/DESIGN.md`, properties nest under `<brand_dir>/properties/`. If the brand workspace cannot be located, fall back to `./properties/` in the caller's current working directory.
46
46
 
47
47
  The property directory **must** sit inside its brand workspace at `<brand_dir>/properties/<property_slug>-<listing_id>/` — never as a sibling of the brand workspace, never inside another property's folder. This is the contract `make-brochure` and `property-brochure` read against.
48
48
 
@@ -50,14 +50,15 @@ The property directory **must** sit inside its brand workspace at `<brand_dir>/p
50
50
 
51
51
  | In scope | Out of scope |
52
52
  |---|---|
53
- | Muvin listings (`muvin.co.uk/property/...`) and any other agent fronting the Loop CRM CDN (`*.b-cdn.net/propertyimages/...`, `*.b-cdn.net/floorplans/...`) | Listings on agency-specific platforms (Rightmove, Zoopla, OnTheMarket, OpenRent)those have separate page structures and image hosts |
54
- | All photos, floorplans, drone shots, AI-staged renders | Stripping AI-generated images from the photo set keep them all and flag in metadata |
53
+ | Download every CDN-hosted image, floorplan and EPC asset referenced by the listing page | Stripping AI-generated images from the photo setkeep them all and flag in metadata |
54
+ | Extract description copy verbatim from whichever description container the page exposes | Paraphrasing or rewriting the description the brochure step owns voice |
55
+ | Capture a screenshot of any map element by heuristic (provider-agnostic — Google Maps iframe, Mapbox canvas, static-map `<img>`, custom embed) | Synthesising a map when the listing has none (no postcode-centroid renders, no AI maps) |
55
56
  | Property description, key features, price, specs, agent contact | Walkthrough video transcription (the property-brochure skill handles that separately if the user supplies one) |
56
57
  | EPC rating as a value, EPC certificate PDF if linked from the page | EPC certificate retrieval from the EPC Register — that's a separate lookup; if the listing does not link the certificate, leave `epc/` empty and record `"epc_rating": "TBC"` (or the band shown) in `property.json` |
57
58
  | Council tax band as shown on the page | Council tax band verification against VOA — out of scope |
58
59
  | Material Information visible on the listing page (tenure, council tax, EPC) | Full Part A/B/C compliance gathering — that's the brochure assembly step's job |
59
60
 
60
- If the page is JS-rendered and `curl` returns no asset URLs, fall back to a headless-browser fetch (Chrome DevTools MCP or Playwright). If even that fails, stop and report `STATUS: BLOCKED` — do not synthesise an asset list.
61
+ The skill is portal-agnostic. The Loop-CRM CDN pattern (`*.b-cdn.net/propertyimages/...`, `*.b-cdn.net/floorplans/...`) is one common example that the static-HTML probe handles cheaply; agency-specific platforms (Rightmove, Zoopla, OnTheMarket, OpenRent, custom builds) JS-render most data and require the Playwright fallback documented in **Process — high level** step 4b. When the static probe returns no asset URLs or critical fields are tab-bound, escalate to the headless-browser fallback as a first-class path, not a conditional one. If even that returns nothing, stop and report `STATUS: BLOCKED` — do not synthesise an asset list.
61
62
 
62
63
  ## Artifact contracts
63
64
 
@@ -67,12 +68,9 @@ The three subfolders (`images/`, `floorplans/`, `epc/`) are **mandatory and name
67
68
 
68
69
  ### `images/`
69
70
 
70
- Every URL on the page matching `b-cdn.net/propertyimages/<tenant-uuid>/<listing-id>/` is an image asset. Two CDN buckets are in active use as of April 2026:
71
+ Every URL on the page pointing at an image asset is in scope. On Loop-CRM tenants the pattern is `b-cdn.net/propertyimages/<tenant-uuid>/<listing-id>/` two example buckets in active use as of April 2026 are `loop-app.b-cdn.net` (older 2024-vintage uploads) and `loopcrm.b-cdn.net` (newer 2026 additions including drone shots, AI-staged renders, screenshots). Other portals serve from their own hosts (e.g. Rightmove's `media.rightmove.co.uk`); the rule is the same — grep every image URL the page emits, deduplicate, download all of them.
71
72
 
72
- - `loop-app.b-cdn.net` older photos (2024-vintage uploads)
73
- - `loopcrm.b-cdn.net` — newer 2026 additions (drone shots, AI-staged renders, screenshots)
74
-
75
- Download all of them. Preserve the original filenames — they encode upload timestamps as Unix epochs (e.g. `DSC01495_1727361503898.jpg` → 26 Sept 2024) which are useful for ordering and de-duplication later.
73
+ Preserve the original filenames — many tenants encode upload timestamps as Unix epochs in the filename (e.g. `DSC01495_1727361503898.jpg` 26 Sept 2024) which are useful for ordering and de-duplication later. Where filenames carry no timestamp, fall back to download order.
76
74
 
77
75
  Image categories observable from filename prefixes:
78
76
 
@@ -88,7 +86,7 @@ Record the count per prefix in `property.json` under `media.image_breakdown` —
88
86
 
89
87
  #### Curator-friendly rename — "<Property Name> N.jpg"
90
88
 
91
- After downloading, **also rename the images to `<Property Name> N.jpg`** (1-indexed, ordered by upload timestamp parsed from the original filename) so a human curator can browse `images/` in Finder/Preview and intuitively pick which photo goes in which slot. The downstream `property-brochure` slot map references `images/<property-slug>-NN.webp`, where `NN` matches the curator-chosen `N` 1:1 — so renaming source images to that order at extract time makes the entire pipeline traceable: the curator sees `Sparrows Farm 22.jpg` in Finder, the brochure HTML references `images/sparrows-farm-22.webp`, and a search across either resolves to the same photograph.
89
+ After downloading, **also rename the images to `<Property Name> N.jpg`** (1-indexed, ordered by upload timestamp parsed from the original filename) so a human curator can browse `images/` in Finder/Preview and intuitively pick which photo goes in which slot. The downstream `property-brochure` slot map references `images/<property-slug>-NN.webp`, where `NN` matches the curator-chosen `N` 1:1 — so renaming source images to that order at extract time makes the entire pipeline traceable: the curator sees `<Property Name> 22.jpg` in Finder, the brochure HTML references `images/<property-slug>-22.webp`, and a search across either resolves to the same photograph.
92
90
 
93
91
  The rename is **not** a replacement of the original CDN-named files — those are preserved as-is for timestamp ordering and de-duplication. The renamed files are the same bytes (or a `cp`/`ln` of the originals), placed alongside, with the new `<Property Name> N.jpg` filenames.
94
92
 
@@ -105,15 +103,15 @@ for src in $(ls -1 | grep -E '^(DSC|DJI|ChatGPT-Image|Screenshot|IMG|iPhone|What
105
103
  done
106
104
  ```
107
105
 
108
- The `<Property Name>` is taken from `property.json -> property_name` (see *Property name vs address* below). If the property has no distinct name (uncommon at this register), use the first part of the address (e.g. "Henham Road").
106
+ The `<Property Name>` is taken from `property.json -> property_name` (see *Property name vs address* below). If the property has no distinct name (uncommon at this register), use the first part of the address (the street name).
109
107
 
110
108
  Record the original-to-curator-name mapping in `property.json -> media.image_curator_map` so the rename is reversible:
111
109
 
112
110
  ```json
113
111
  "media": {
114
112
  "image_curator_map": {
115
- "Sparrows Farm 1.jpg": "DJI_0125_1777122816019.jpg",
116
- "Sparrows Farm 2.jpg": "DJI_0115_1777122829462.jpg",
113
+ "The Old Rectory 1.jpg": "DJI_0125_1777122816019.jpg",
114
+ "The Old Rectory 2.jpg": "DJI_0115_1777122829462.jpg",
117
115
  "...": "..."
118
116
  }
119
117
  }
@@ -121,7 +119,7 @@ Record the original-to-curator-name mapping in `property.json -> media.image_cur
121
119
 
122
120
  ### `floorplans/`
123
121
 
124
- URLs matching `b-cdn.net/floorplans/<tenant-uuid>/<listing-id>/`. Listings typically ship one floorplan per floor (so 1–3 files for a UK home). Preserve original filenames.
122
+ Every URL on the page pointing at a floorplan asset is in scope. On Loop-CRM tenants the pattern is `b-cdn.net/floorplans/<tenant-uuid>/<listing-id>/`; other portals serve from their own hosts. Listings typically ship one floorplan per floor (so 1–3 files for a UK home). Preserve original filenames.
125
123
 
126
124
  ### `epc/`
127
125
 
@@ -131,7 +129,7 @@ If the page links a PDF whose URL contains `epc` or whose link text mentions "EP
131
129
 
132
130
  **Maps are extracted, never synthesized.** If the listing doesn't supply one, the brochure ships without a map. No fallback to OSM static maps, no postcode-geocoded re-render via postcodes.io, no AI-generated map artwork. Postcode centroids can mislead about exact location, and the brochure should reflect what the agency actually publishes — not what the pipeline could imagine.
133
131
 
134
- The capture is **provider-agnostic by design.** Estate agents embed maps in many ways: Google Maps iframes (Loop CRM / Muvin), Mapbox or Leaflet `<div>`+canvas instantiations (custom builds), `<img>` static-map renders (Rightmove-style), Apple Maps embeds, custom-class wrappers — and that list will only grow. Don't hard-code a single CMS's markup. Instead: **find a map element by heuristic, screenshot it, save the bytes.** The provider's brand and tile style come along for the ride.
132
+ The capture is **provider-agnostic by design.** Estate agents embed maps in many ways: Google Maps iframes (common on Loop-CRM tenants), Mapbox or Leaflet `<div>`+canvas instantiations (custom builds), `<img>` static-map renders (used by some portal-style listings), Apple Maps embeds, custom-class wrappers — and that list will only grow. Don't hard-code a single CMS's markup. Instead: **find a map element by heuristic, screenshot it, save the bytes.** The provider's brand and tile style come along for the ride.
135
133
 
136
134
  **Detect.** Render the listing page in a real browser (Playwright). Try these heuristics in order, taking the first non-empty hit:
137
135
 
@@ -166,21 +164,21 @@ The rule, in one line: **extract or omit**.
166
164
 
167
165
  ### Property name vs address — they are not the same field
168
166
 
169
- At the super-premium register, many properties have a **distinct name** that is separate from the postal address: *Sparrows Farm*, *The Old Rectory*, *Beacon House*. The brochure's cover hero is the property name; the subtitle is the address. The two must be captured separately at extraction time.
167
+ At the super-premium register, many properties have a **distinct name** that is separate from the postal address: *The Old Rectory*, *Mill House*, *Beacon House*. The brochure's cover hero is the property name; the subtitle is the address. The two must be captured separately at extraction time.
170
168
 
171
169
  Detect the property name by:
172
170
 
173
- 1. Looking for "<Name>, <street>" patterns in the listing's title or H1 (e.g. "Sparrows Farm, Henham Road").
174
- 2. Looking for a name that is *not* a numbered street address (numbered addresses like "12 Henham Road" don't have a separate name).
175
- 3. Cross-checking with a Sprift / Land Registry report — if the title is registered as "Sparrows Farm at Henham Road", the name is *Sparrows Farm*.
171
+ 1. Looking for "<Name>, <street>" patterns in the listing's title or H1 (e.g. "The Old Rectory, Mill Lane").
172
+ 2. Looking for a name that is *not* a numbered street address (numbered addresses like "12 Mill Lane" don't have a separate name).
173
+ 3. Cross-checking with a Sprift / Land Registry report — if the title is registered as "<Name> at <street>", that's the property name.
176
174
 
177
175
  Record both in `property.json`:
178
176
 
179
177
  ```json
180
- "property_name": "Sparrows Farm", // or null if the property has no distinct name
178
+ "property_name": "The Old Rectory", // or null if the property has no distinct name
181
179
  "address": {
182
- "line_1": "Henham Road",
183
- "locality": "Hamperden End",
180
+ "line_1": "Mill Lane",
181
+ "locality": "<Locality>",
184
182
  "...": "..."
185
183
  }
186
184
  ```
@@ -309,29 +307,24 @@ Capture rules:
309
307
 
310
308
  ## Image preview safety
311
309
 
312
- **Do not `Read` any downloaded image whose longest edge exceeds 2000px** — it breaks the session by overrunning the multimodal encoding budget. Most DSLR (`DSC*`) and drone (`DJI_*`) photos from a Muvin listing are 6000–8000px and will trigger this. Measure with `sips -g pixelWidth -g pixelHeight <file>` before any `Read`, or skip the visual check entirely (filename, byte-size, and `sips` dimensions cover most "did the download work?" questions). See `make-brochure` for the full safety workflow including the `sips -Z 2000` downscale recipe.
310
+ **Do not `Read` any downloaded image whose longest edge exceeds 2000px** — it breaks the session by overrunning the multimodal encoding budget. Most DSLR (`DSC*`) and drone (`DJI_*`) photos from any premium listing are 6000–8000px and will trigger this. Measure with `sips -g pixelWidth -g pixelHeight <file>` before any `Read`, or skip the visual check entirely (filename, byte-size, and `sips` dimensions cover most "did the download work?" questions). See `make-brochure` for the full safety workflow including the `sips -Z 2000` downscale recipe.
313
311
 
314
312
  ## Process — high level
315
313
 
316
314
  The skill orchestrates four capabilities, each a **what** not a **how**:
317
315
 
318
- 1. **Probe.** Fetch the URL once with `WebFetch` to get a summarised view (price, address, agent, image count). This is a cheap sanity check — if the summary disagrees with what you extract from raw HTML, the page is likely JS-rendered and you need a headless browser.
319
- 2. **Extract.** Pull the raw HTML and grep all `b-cdn.net/propertyimages/...` and `b-cdn.net/floorplans/...` URLs. Deduplicate. Also grep PDF URLs containing `epc`. Extract the `<div class="section__description">` block for the description text. (Map detection is provider-agnostic and happens in step 4 below — don't try to parse map markup at this static-HTML stage.)
316
+ 1. **Probe.** Fetch the URL once with `WebFetch` to get a summarised view (price, address, agent, image count). This is a cheap sanity check — if the summary disagrees with what you extract from raw HTML, the page is JS-rendered and you need a headless browser.
317
+ 2. **Extract — tiered.** Try the static-HTML path first because it's cheap: pull the raw HTML and grep every image-asset URL (Loop-CRM tenants use `b-cdn.net/propertyimages/...` and `b-cdn.net/floorplans/...`; other portals serve from their own hosts), deduplicate, grep PDF URLs containing `epc`, extract any visible description container. **If the static probe returns zero image URLs, or the probed summary in step 1 reports content the static HTML does not contain (EPC band, floorplan thumbnail, video URL), escalate to the Playwright fallback in step 4b** — that's the first-class path for portals and tabbed UIs, not a conditional one. (Map detection is provider-agnostic and happens in step 4 below — don't try to parse map markup at this static-HTML stage.)
320
318
  3. **Stage.** Create the four-folder structure under `<output-dir>/<slug>-<listing-id>/`. Download images and floorplans in parallel (`xargs -n1 -P8 curl -O -L`) — there are routinely 40–60 images per listing and serial downloads are slow.
321
319
  4. **Capture map** (conditional, provider-agnostic). Drive Playwright to the listing URL and apply the heuristic detector under **Map — extract or omit**: try iframe-by-provider, then class/id-by-keyword, then static-map images, then visual fallback. On the first hit, scroll into view, wait for tiles, screenshot the **element** (not the page), convert to `map.webp` at ~1500 px wide. If no heuristic finds a map, skip — **extract or omit**, no fallback synthesis.
322
- 4b. **Traverse tabbed UIs (conditional, provider-agnostic).** Modern listing pages from Loop CRM tenants, Rightmove, Zoopla and others render top-level data in tabs — *Details / Floorplan / EPC / Virtual Tour / Map View / Book Viewing* — and the static HTML returned by `WebFetch` or `curl` often does not include the inactive-tab content. If the probe in step 1 came back with data the listing visibly shows (EPC band, floorplan thumbnail, video URL) but the static-HTML extract in step 2 does not, the data is almost certainly tab-bound. Drive the Playwright session opened in step 4 (or open one if step 4 skipped) and walk the tab list: detect elements with `role="tablist"`, `role="tab"`, or class/id substrings `tab` containing the keywords `EPC`, `Floor plan`, `Map`, `Virtual tour`, `Brochure`. For each tab found, `page.click(<selector>)`, `page.waitForLoadState('networkidle')`, then harvest the panel content. For the EPC tab specifically: scrape the certificate PDF/PNG link and the displayed current/potential scores into `specifications.epc_rating`, `specifications.epc_current_score`, `specifications.epc_potential_score`. For other tabs: extract what each contract field needs and skip the rest. The traversal is principled — never hard-code a single agency's tab markup as the sole detector; the same listing surface ships with different tab implementations across CMS tenants.
320
+ 4b. **Traverse tabbed UIs and JS-rendered portals (first-class for non-static hosts).** Modern listing pages from Loop CRM tenants, Rightmove, Zoopla, OnTheMarket and others render top-level data in tabs — *Details / Floorplan / EPC / Virtual Tour / Map View / Book Viewing* — and the static HTML returned by `WebFetch` or `curl` often does not include the inactive-tab content. Whenever step 2's static probe came up short, drive a Playwright session against the listing URL and walk the tab list: detect elements with `role="tablist"`, `role="tab"`, or class/id substrings `tab` containing the keywords `EPC`, `Floor plan`, `Map`, `Virtual tour`, `Brochure`. For each tab found, `page.click(<selector>)`, `page.waitForLoadState('networkidle')`, then harvest the panel content. For the EPC tab specifically: scrape the certificate PDF/PNG link and the displayed current/potential scores into `specifications.epc_rating`, `specifications.epc_current_score`, `specifications.epc_potential_score`. For other tabs: extract what each contract field needs and skip the rest. The traversal is principled — never hard-code a single agency's or portal's tab markup as the sole detector; the same listing surface ships with different tab implementations across CMS tenants and portal layouts.
323
321
  5. **Assemble.** Write `property.json` and `description.md`. Run a final consistency pass: image count in JSON matches files on disk, EPC rating in JSON matches the description.md line, agent contact in JSON matches the description.md header. `media.map` is populated iff a map screenshot landed on disk; `address.geo` is populated iff coordinates were parseable from a recognised URL pattern (and may legitimately be `null` even when `media.map` is set — see the schema notes).
324
322
 
325
- The skill does not prescribe which HTTP tool to use. `curl` is fine for the static HTML pages Muvin serves. If a future agent ships a JS-rendered listing page, switch to Chrome DevTools MCP or Playwright — the contract on the output is unchanged.
323
+ The skill does not prescribe which HTTP tool to use. `curl` is fine for static-HTML hosts (most Loop-CRM tenants serve listing data inline); switch to Chrome DevTools MCP or Playwright for JS-rendered listing pages and portals — the contract on the output is unchanged.
326
324
 
327
325
  ## Reference standard
328
326
 
329
- Before producing output, read these files to anchor the format:
330
-
331
- - `/Users/neo/estate-agents/muvin/properties/henham-road-debden-green-hamperden-end-cb11-3lz-949931/property.json` — the JSON schema in practice.
332
- - `/Users/neo/estate-agents/muvin/properties/henham-road-debden-green-hamperden-end-cb11-3lz-949931/description.md` — the prose voice and structure.
333
-
334
- A delivered package that diverges substantially in shape from the reference is wrong, even if the content is accurate.
327
+ The format contract is defined by `property.json` (schema above) and `description.md` (structure above). If the caller has a prior conforming property pack on disk (under any `<brand_dir>/properties/<slug>-<id>/`), read its `property.json` and `description.md` to anchor the format. A delivered package that diverges substantially in shape from those is wrong, even if the content is accurate.
335
328
 
336
329
  ## Common mistakes
337
330
 
@@ -343,8 +336,8 @@ A delivered package that diverges substantially in shape from the reference is w
343
336
  | Inventing values for unknown specifications | `null` means "not on the listing". A guess at tenure or EPC introduces silent error into downstream Material Information compliance. |
344
337
  | Including the agency's marketing boilerplate ("WOULD YOU LIKE TO VIEW", "CAN WE HELP YOU TOO") in `description.md` | The brochure has its own back page. Including it would duplicate text the brand designer is already authoring. |
345
338
  | Skipping `floorplans/` because the listing only has one | One floorplan still goes in `floorplans/`, not `images/`. The folder layout is a contract — `property-brochure` looks for floorplans in exactly one place. |
346
- | Saving `/tmp/muvin_property.html` or other working files inside the output directory | The directory is the deliverable. Working files get cleaned up. |
347
- | Hardcoding the tenant UUID | The UUID identifies the agency on Loop CRM. Different Muvin offices can have different tenant IDs. Extract it from the actual URLs on the page. |
339
+ | Saving `/tmp/listing_raw.html` or other working files inside the output directory | The directory is the deliverable. Working files get cleaned up. |
340
+ | Hardcoding the tenant UUID | On Loop-CRM tenants the UUID identifies the agency; different offices have different tenant IDs. Extract it from the actual URLs on the page rather than assuming a single value. |
348
341
  | Treating "EPC: TBC" as a missing field | "TBC" is information — the agent is telling you the assessment hasn't been done. Record it verbatim. |
349
342
  | Recording "EPC: TBC" when the listing's EPC tab actually shows a band but the static HTML did not | Modern listings tab-hide data behind a JS tab switcher; `WebFetch` / `curl` see only the active tab. If the listing visibly carries EPC content the static HTML lacks, drive Playwright per step 4b and harvest from the tab panel. "TBC" recorded from a missed-tab extract is a silent data-loss defect, not a faithful record. |
350
343
  | Synthesizing a map when the listing has none | The rule is **extract or omit**. Postcode-centroid renders, OSM static maps, and AI-generated map artwork are all forbidden. If the heuristic detector returns nothing, write `media.map: null` and move on. |
@@ -369,6 +362,5 @@ If any of those are not true, report `DONE_WITH_CONCERNS` and list each gap.
369
362
  ## When NOT to use this skill
370
363
 
371
364
  - The user wants a brochure produced from photos they already have on disk → skip extraction, go straight to `property-brochure`.
372
- - The listing is on Rightmove, Zoopla, OnTheMarket, or another non-Loop-CRM platform → the URL parsing and CDN paths differ. Either extend this skill with a new branch or route to a platform-specific extractor.
373
365
  - The user wants a quick price/specs lookup, not the full asset bundle → use `WebFetch` directly. The folder structure is overkill.
374
366
  - The user wants market data, comparables, or transaction history → that's a different research task; this skill is asset staging, not analysis.
@@ -19,9 +19,9 @@ The report has its own typographic identity — a luxury serif stack pinned in t
19
19
 
20
20
  ## Inputs (never invented)
21
21
 
22
- - **`design_tokens_path`** — path to a `DESIGN.md` that tokenises the agent's brand (`muvin.co.uk` `/estate-agents/muvin/DESIGN.md`). Read the canonical hex values directly; never substitute "close" colours. **Only colour tokens are consumed** — the report imports `primary` (e.g. teal-900), `primary_dark`, `accent` (e.g. gold-500), `paper`, `paper_banded`, and `rule`. Any typography, spacing, or radius tokens in the agent's DESIGN.md are *ignored by design*.
23
- - **`logo_light_path`** / **`logo_dark_path`** — PNG/SVG. Light = white-on-dark version (for the teal-toned cover); dark = full-colour version (for body-page headers). When supplied, the renderer emits an `<img>` for the logo; when omitted, it falls back to a Cormorant-Garamond text wordmark using `brand.name` + `brand.tagline`. The text-wordmark fallback is acceptable for brands whose logo is essentially a serif-typeset name (e.g. *muvin*); for richer marks, supply the files.
24
- - **`property_urls`** — list of up to 10 source URLs (Muvin, Rightmove, Zoopla, etc.). Each must resolve to a single property listing page. Reject more than 10; the layout breaks at >10 cards.
22
+ - **`design_tokens_path`** — path to a `DESIGN.md` that tokenises the agent's brand. Resolve it from `brand_source` at runtime: when `brand_source` is a URL, the path is `<brand_dir>/DESIGN.md` for whatever brand directory the orchestrator created (or already had on disk); when `brand_source` is a directory path, the file is at `<brand_source>/DESIGN.md`. Read the canonical hex values directly; never substitute "close" colours. **Only colour tokens are consumed** — the report imports `primary` (e.g. teal-900), `primary_dark`, `accent` (e.g. gold-500), `paper`, `paper_banded`, and `rule`. Any typography, spacing, or radius tokens in the agent's DESIGN.md are *ignored by design*.
23
+ - **`logo_light_path`** / **`logo_dark_path`** — PNG/SVG. Light = white-on-dark version (for the teal-toned cover); dark = full-colour version (for body-page headers). When supplied, the renderer emits an `<img>` for the logo; when omitted, it falls back to a Cormorant-Garamond text wordmark using `brand.name` + `brand.tagline`. The text-wordmark fallback is acceptable for brands whose logo is essentially a serif-typeset name; for richer marks, supply the files.
24
+ - **`property_urls`** — list of up to 10 source URLs from any UK estate-agent or portal site (Loop-CRM tenants, Rightmove, Zoopla, OnTheMarket, etc.). Each must resolve to a single property listing page. Reject more than 10; the layout breaks at >10 cards.
25
25
  - **`market_data`** — flat dict of numbers (average asking, average sold, £/sqft, YoY growth, demand rating, days-on-market, property-type distribution, sample size, period). Source from a market intelligence API (PropertyData / Land Registry / ONS) or paste from an existing report. **The period is the *generated* date, not whatever appears in the URL slug** — the Market Trak `oct-2025` slug describes a snapshot from May 2026 (Oct 2025 was the trailing window). Use the report-page's stated `Generated` value.
26
26
 
27
27
  ## How the skill renders
@@ -90,7 +90,7 @@ The disclaimer block on the final page must name:
90
90
  - **Logo invisible in preview.** The IDE preview panel can't always resolve relative `img/` paths or large base64 data URIs. The deliverable PDF is the source of truth; viewing the HTML in a real browser (or via `python3 -m http.server`) is the second source of truth. Don't burn iterations chasing preview-panel artefacts.
91
91
  - **Date drift from URL slug.** A Market Trak URL like `…-oct-2025?…` does not mean the report is October 2025. Always trust the `Generated:` field on the source page.
92
92
  - **Page 3 / page 4 overflow.** The default Cormorant Garamond + Lora stack is slightly taller-set than a sans default. KPI tile padding and panel padding need to come down (~5 mm not 7 mm) for serif typography to fit alongside the photo strip on page 3 and two listing cards on page 4.
93
- - **Property-socials skipped.** A report that drops raw `loop-app.b-cdn.net/propertyimages/...jpg` straight into a tile breaks the brand contract — the tiles have no overlay, no scrim, no address copy. Always run the OG pipeline; if it can't run, surface the gap, don't substitute.
93
+ - **Property-socials skipped.** A report that drops the raw listing-CDN image straight into a tile (whether `loop-app.b-cdn.net/propertyimages/...jpg`, `media.rightmove.co.uk/...`, or any other host) breaks the brand contract — the tiles have no overlay, no scrim, no address copy. Always run the OG pipeline; if it can't run, surface the gap, don't substitute.
94
94
  - **Anchor links dead in PDF.** Chrome `--print-to-pdf` preserves `<a href>` as live PDF annotations. Verify by opening the PDF in Preview and clicking a listing — if the link is dead, the HTML wrapped a `<div>` instead of an `<a>`.
95
95
 
96
96
  ## Out of scope
@@ -19,8 +19,9 @@ shots — it does not re-design anything.
19
19
  that page. Do not pick new colours.
20
20
  - **Copy** — `property.json` is canonical for address, price, qualifier,
21
21
  bedrooms/baths/receptions, agent name + phone. The page's
22
- `<meta name="description">` is the sub-headline. The brochure folder's
23
- display name (e.g. *Sparrows Farm*) is the headline.
22
+ `<meta name="description">` is the sub-headline. The property's display
23
+ name (`property.json -> property_name`, or the first address line if the
24
+ property has no distinct name) is the headline.
24
25
  - **Hero images** — `images/<slug>-hero-1-main.webp`, `hero-2-kitchen.webp`,
25
26
  `hero-3-garden.webp` are the curated set. Use them by name; do not pick
26
27
  numbered shots from the raw set unless the curated heroes are missing.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "real-agent-buyers",
3
- "description": "Full buyer lifecycle — enquiry handling, qualification, viewing management, feedback collection, and educational guides for buyers and sellers.",
3
+ "description": "Full buyer lifecycle — enquiry handling, qualification, viewing management, feedback collection, educational guides, and the public-chat property recommender that surfaces current listings as rendered cards on visitor buyer intent.",
4
4
  "version": "0.1.0",
5
5
  "author": {
6
6
  "name": "Rubytech LLC"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: real-agent-buyers
3
- description: "Full buyer lifecycle — enquiry handling, qualification, viewing management, feedback collection, and educational guides for buyers and sellers."
3
+ description: "Full buyer lifecycle — enquiry handling, qualification, viewing management, feedback collection, educational guides, and the public-chat property recommender that surfaces current listings as rendered cards on visitor buyer intent."
4
4
  tools: []
5
5
  always: false
6
6
  metadata: {"platform":{"optional":true,"embed":["public","admin"]}}
@@ -8,7 +8,7 @@ metadata: {"platform":{"optional":true,"embed":["public","admin"]}}
8
8
 
9
9
  # Real Agent — Buyer Management
10
10
 
11
- Five skills covering the complete buyer lifecycle from initial enquiry through viewing management and educational content.
11
+ Six skills covering the complete buyer lifecycle from initial enquiry through viewing management and educational content, plus the public-chat property recommender that fires on visitor buyer intent.
12
12
 
13
13
  ## When to Activate
14
14
 
@@ -21,6 +21,7 @@ The user is handling buyer enquiries, qualifying applicants, managing viewings,
21
21
  | `buyer-management` | Full buyer lifecycle — enquiry, qualification, viewing, feedback, offer |
22
22
  | `buyer-feedback` | Post-viewing feedback collection via conversational check-in |
23
23
  | `property-enquiry` | Structured intake for new buyer enquiries — requirements, finances, timeline |
24
+ | `property-recommender` | Public-chat skill — render a property card (image + blurb + listing URL) on buyer intent, sourced from `:Listing` and `:ImageObject` via memory-search |
24
25
  | `viewing-management` | Viewing lifecycle — booking, confirming, rescheduling, reminders |
25
26
  | `buyer-seller-guides` | Educational guides for buyers and sellers — first-time, upsizing, downsizing, probate |
26
27
 
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: property-recommender
3
+ description: "Recommend a current property to a visitor as a rendered card (image + blurb + clickable listing URL). Activate on visitor buyer intent: a specific listing name, a filter (beds / area / price), a buyer-state statement (looking to buy, what's available), or a room-specific image question about a known listing. Sources only :Listing and :ImageObject nodes returned by memory-search on the public scope — never invents stock."
4
+ ---
5
+
6
+ # Property Recommender
7
+
8
+ You handle the moment a visitor on the public chat shows buyer intent. Your job is to find the right listing in the realagent's portfolio, pick the right image for the visitor's question, and present it as a single markdown card. You never invent a property. You never embed an image from outside the graph. You never recommend more than the visitor asked for.
9
+
10
+ ## Outcome contract
11
+
12
+ For every turn this skill fires on, the reply contains either:
13
+
14
+ - **One property card** in the exact markdown shape below, or
15
+ - **A plain-English no-match sentence** when memory-search returns no `:Listing` with `scope: "public"` that fits the visitor's intent.
16
+
17
+ Never both. Never two cards in one reply. Never a card plus a "would you also like…?" follow-up. The visitor asks for more if they want more.
18
+
19
+ The card shape:
20
+
21
+ ```markdown
22
+ ![<image description>](<imageUrl>)
23
+
24
+ **<displayName>** — <priceQualifier> £<priceFormatted>
25
+
26
+ <blurb>
27
+
28
+ [View full details](<pageUrl>)
29
+ ```
30
+
31
+ - `<image description>` is the picked `:ImageObject.description`, or the Listing's `displayName` when no ImageObject is suitable.
32
+ - `<imageUrl>` is the picked `:ImageObject.url`, or the Listing's `heroImageUrl` as fallback.
33
+ - `<priceQualifier>` is rendered only when present: `Guide` / `Offers over` / `OIEO` / `POA` / `Auction`. When `priceQualifier` is null, omit the qualifier.
34
+ - `<priceFormatted>` is the integer pounds with thousand-separators (`695,000`). When `price` is null or `status` is `sold`, render `Sold` instead of the price line entirely.
35
+ - `<blurb>` is the Listing's curator-written `blurb` field, used verbatim. Never paraphrase. Never extend.
36
+ - `<pageUrl>` is the Listing's `pageUrl`. The link text is always exactly `View full details`.
37
+
38
+ The card is one message. Nothing precedes it. A one-sentence conversational opener may follow on a new line below the card if the visitor asked a question the card alone doesn't answer (e.g. they asked "is this still available?" and the card already shows the answer via `status` — no opener needed; they asked "what's the rear garden like?" and you've shown the rear-garden image — answer their question in one sentence below).
39
+
40
+ ## Triggers — when this skill fires
41
+
42
+ The skill fires on these visitor intents:
43
+
44
+ - **Specific listing name** — visitor names a property by address, road, or curator slug (e.g. "Griffin House", "the cottage on Upper Church Street", "Chepstow NP16").
45
+ - **Filter intent** — visitor names criteria (`4-bed`, `under £600k`, `in Monmouthshire`, `cottage`, `detached`) and asks what matches.
46
+ - **Buyer-state statement** — visitor says they're looking to buy, asks what's available, asks what's on the market, asks for current stock.
47
+ - **Room/feature question on a known listing** — visitor has just been shown a listing and asks to see the kitchen, the garden, the bathroom, the floorplan, the EPC. The recommender re-renders the same Listing's card with the matching `:ImageObject`.
48
+
49
+ The skill does **not** fire on:
50
+
51
+ - Greetings and small talk ("hi", "how are you", "good morning").
52
+ - Generic agency questions ("what areas do you cover?", "what are your fees?", "who runs the business?").
53
+ - Off-topic or hostile messages.
54
+ - Questions about a property type the realagent doesn't list (commercial, overseas) unless the visitor explicitly asks "do you list X".
55
+
56
+ When in doubt, do not fire. Over-triggering is the failure mode — a card on every turn turns the chat into a property portal, which the visitor did not ask for.
57
+
58
+ ## How to find the right Listing
59
+
60
+ Call `memory-search` with the visitor's intent text as the query and `labels: ["Listing"]` as the filter. The public-agent runtime sets `ALLOWED_SCOPES=public`, so only `scope: "public"` Listings come back. Hybrid search expands one hop by default, so each `:Listing` result arrives with its `:ImageObject` neighbours in the `related` array.
61
+
62
+ Pick exactly one Listing:
63
+
64
+ - **Specific name match** — when the visitor named a property, the top hit on `displayName` / `slug` / `addressLine` / `postcode` is the answer. Confidence is unambiguous.
65
+ - **Filter match** — when the visitor named criteria, pick the highest-ranked Listing whose `status` is `for-sale` or `under-offer` (skip `sold` and `withdrawn` unless the visitor explicitly asks about sold stock) and whose structured fields match the filters. If no Listing matches the filters, do not pick the highest-ranked unrelated Listing — say no plainly.
66
+ - **General "what's available"** — pick the highest-ranked `for-sale` Listing. The visitor pulls more by asking.
67
+
68
+ If memory-search returns zero `:Listing` results in the public scope, do not fabricate. Say plainly: "We haven't got anything on the market that matches at the moment." Vary the wording for tone but never invent stock. Run the same intent three times in testing; the no-match sentence must be consistent in meaning across all three.
69
+
70
+ ## How to pick the image
71
+
72
+ The picked Listing arrives with its `:ImageObject` neighbours in `related`. Each ImageObject carries `url`, `description`, `tags`, and `heroIndex`. Pick one:
73
+
74
+ - **Room-specific question** — when the visitor asked about a specific room or feature, pick the ImageObject whose `tags` array contains the matching label. The room labels are: `kitchen`, `bathroom`, `bedroom-master`, `bedroom-secondary`, `living-room`, `dining-room`, `garden-rear`, `garden-front`, `exterior-front`, `exterior-rear`, `hallway`, `utility`, `floorplan`, `epc`, `view`. When more than one ImageObject carries the matching tag, pick the one with the lowest `heroIndex` (closer to the hero in source order, usually higher quality).
75
+ - **No matching tag** — when the visitor asked for a room (e.g. "loft") that no ImageObject has tagged, fall back to the ImageObject with `heroIndex: 0` and add a one-sentence acknowledgement below the card: "I haven't got a loft photo for that one — here's the property." Plain, honest, no invented detail.
76
+ - **General recommendation** — pick the ImageObject with `heroIndex: 0` (the hero). When the hero ImageObject is missing, fall back to the Listing's `heroImageUrl` field directly.
77
+
78
+ The image embed always uses the picked image's `description` as the alt text. Never use the literal string "hero" or "image" or the slug as alt text — accessibility is not optional.
79
+
80
+ ## Out of scope
81
+
82
+ - Fetching Loop CRM property data directly. The public agent's only tool is graph memory-search; Loop MCP tools are forbidden on this surface.
83
+ - Capturing visitor contact details on card click-through. Lead capture is a separate skill / task.
84
+ - Comparing two listings in one reply ("compare A and B"). One card per turn. The visitor asks about each in turn.
85
+ - Off-market or pre-marketing pipeline listings. Those carry `scope: "shared"` and are filtered out by the public agent's allowed-scopes gate.
86
+ - Generating images. The agent only embeds URLs returned as `:ImageObject.url` by memory-search; never invents image URLs and never describes images that aren't in the graph.
87
+
88
+ ## Observability
89
+
90
+ Emit one structured log line per fire:
91
+
92
+ ```
93
+ [property-recommended] sessionKey=<sk> trigger=<short-label> listingSlug=<slug> imagePick=<hero|tag:<tag>|fallback> imageUrl=<url>
94
+ ```
95
+
96
+ `trigger` is a 1-3 word label naming why the skill fired (`specific-name` / `filter-beds-area` / `buyer-state` / `room-kitchen`). `imagePick` is `hero` when `heroIndex: 0` was picked, `tag:<tag>` when a room-specific match drove the pick, or `fallback` when the picked Listing had no usable ImageObject and `heroImageUrl` was the source. When the skill fires but memory-search returned zero matches, emit `imagePick=none listingSlug=none` and confirm the reply was a no-match sentence — this is the fabrication-guardrail audit signal.
@@ -4,6 +4,8 @@ description: "Founding-a-business agent. Drives an operator from raw idea to fun
4
4
  requires:
5
5
  - projects
6
6
  - work
7
+ - cloudflare
8
+ - deep-research
7
9
  metadata: {"platform":{"always":false,"embed":["admin"],"pluginKey":"venture-studio","optional":true,"recommended":true}}
8
10
  ---
9
11
 
@@ -34,7 +36,7 @@ If either check fails, run the scaffolding script (see below) before the skill f
34
36
 
35
37
  ### Scaffolding script
36
38
 
37
- `bin/scaffold.sh` is the deterministic enforcement layer. It creates the Project + eight artefact Tasks (via the work plugin's `project-create-cli` bridge) and materialises the ten-section directory tree. Graph write first, directories second; idempotent re-run if any partial state lands.
39
+ `bin/scaffold.sh` is the deterministic enforcement layer. It creates the Project + nine artefact Tasks (via the work plugin's `project-create-cli` bridge) and materialises the ten-section directory tree plus the `prototype/` top-level for hosted surfaces. Graph write first, directories second; idempotent re-run if any partial state lands.
38
40
 
39
41
  ```bash
40
42
  ACCOUNT_ID="${ACCOUNT_ID}" \
@@ -60,10 +62,11 @@ The script's three effects:
60
62
 
61
63
  1. **Scaffolds the data room.** Materialises the ten-section directory tree under `<project-root>/.docs/data-room/`. Every artefact has a slot to land in before any artefact is produced.
62
64
  2. **Creates the `Project` node.** Tier `full` (data-room work is multi-phase). Project name is the business's working name; description references the data-room root path.
63
- 3. **Enumerates the eight artefact Tasks** in section order — one transaction, atomic with the Project. The work-item list the script pre-seeds:
65
+ 3. **Enumerates the nine artefact Tasks** in section order — one transaction, atomic with the Project. The work-item list the script pre-seeds:
64
66
  - **Stage 1 — Office-hours design doc** → produces `01-narrative/office-hours-design.md` (skill: `office-hours`)
65
- - **Brand pack** → produces brand identity files into `06-product-ip/brand/` (skill: `brand-pack`)
66
67
  - **Stage 2 — Wedge validation + landing page + PRD** → produces `01-narrative/{PMF, LANDING, PRD}.md` (skill: `zero-to-prototype`)
68
+ - **Brand pack** → produces brand guidelines + summary + color palette + typography system + tone-of-voice (voice/messaging/copy) + logo guidelines into `06-product-ip/brand/` (skill: `brand-pack`)
69
+ - **Prototype host** → public URL for the landing page and/or wedge prototype via cloudflared ingress + systemd-managed dev server (skill: `prototype-host`)
67
70
  - **Stage 3 — Business plan** → produces `01-narrative/business-plan.md` (skill: `investor-data-room` Stage 3)
68
71
  - **Stage 3b — Term sheet** → produces `html/prospectus/term_sheet.html` (skill: `investor-data-room` Stage 3b)
69
72
  - **Stage 4 — Deck blueprint** → produces `01-narrative/deck-blueprint.md` (skill: `investor-data-room` Stage 4)
@@ -81,6 +84,7 @@ The following intent phrases route to specific skills. These calls are gated by
81
84
  | "I want to think this through" / "office hours" / "is this worth building" | `office-hours` |
82
85
  | "let's brand this" / "tokens for the brand" / "logo and palette" | `brand-pack` |
83
86
  | "validate this idea" / "let's find PMF" / "draft the landing page" / "write the PRD" | `zero-to-prototype` |
87
+ | "host the landing" / "host the prototype" / "deploy this" / "give me a public URL" | `prototype-host` |
84
88
  | "build me a data room" / "scaffold the data room" | `investor-data-room` (Stage 2) |
85
89
  | "draft the business plan" | `investor-data-room` (Stage 3) |
86
90
  | "draft the term sheet" | `investor-data-room` (Stage 3b) |
@@ -96,15 +100,20 @@ When the operator's intent matches a row but the scaffold does not yet exist, ru
96
100
  - If a section reveals a new artefact the operator wants (e.g. a competitor-claims-rebuttal in `08-market/`), `work-create` a new Task under the same Project, then proceed.
97
101
  - The Project's health signal (green/amber/red) is computed from Task state. Use `project-get` at the start of each turn to surface what's outstanding.
98
102
 
103
+ ## Research routing
104
+
105
+ Every structured-research task — market sizing (TAM/SAM/SOM + CAGR), compliance research (DMCC / ICO ADM / EU AI Act), competitor scans, comparable rounds and exit comparables, named-acquirer enumeration — routes through the `research-assistant` specialist with the five `deep-research` skills (`deep-research`, `book-mirror`, `strategic-reading`, `academic-verify`, `data-research`). Dispatch the specialist; do not call `WebSearch` or `WebFetch` directly from a venture-studio skill. The specialist returns structured findings with citations the data-room artefacts can quote verbatim.
106
+
99
107
  ## Tone
100
108
 
101
109
  Founder-facing. Direct, evidence-based, no flattery. You are the chief of staff for a one-person founding team — your job is to keep every artefact tracked and every deadline visible, while the operator focuses on the parts only they can do (customer conversations, founder signal, vision).
102
110
 
103
111
  ## References
104
112
 
105
- The four skills shipped under this plugin carry their own SKILL.md detail:
113
+ The five skills shipped under this plugin carry their own SKILL.md detail:
106
114
 
107
115
  - `skills/office-hours/SKILL.md` — YC office-hours forcing questions in startup + builder modes; outputs the design-doc substrate every later artefact derives from.
108
116
  - `skills/brand-pack/SKILL.md` — brand identity (palette, typography, tone, logo) tokenised for the data-room artefacts to consume.
109
117
  - `skills/zero-to-prototype/SKILL.md` — 31 frameworks for customer discovery, validation, positioning, PMF measurement, landing page, PRD.
118
+ - `skills/prototype-host/SKILL.md` — scaffolds the landing page or wedge prototype, allocates a port, edits the brand's cloudflared ingress, runs the dev server under a systemd-user unit, and verifies the public URL returns HTTP 200.
110
119
  - `skills/investor-data-room/SKILL.md` — full seed-raise pack: data room scaffold, business plan, term sheet, deck blueprint, prospectus, A4 print pipeline.
@@ -2,7 +2,7 @@
2
2
  # Venture-studio data-room scaffold — deterministic enforcement of the
3
3
  # scaffold-first principle (Task 286 + feedback_doctrine_paragraph_is_not_a_gate).
4
4
  #
5
- # Creates exactly one Project (with eight pre-seeded artefact Tasks) in
5
+ # Creates exactly one Project (with nine pre-seeded artefact Tasks) in
6
6
  # the graph, then materialises the ten-section data-room directory tree
7
7
  # on disk. The venture-studio agent's downstream skills are gated on
8
8
  # both signals existing — see venture-studio/PLUGIN.md.
@@ -53,9 +53,14 @@ fi
53
53
 
54
54
  DATA_ROOM="${PROJECT_ROOT}/.docs/data-room"
55
55
 
56
- # Eight pre-seeded artefact tasks — order and names match
56
+ # Nine pre-seeded artefact tasks — order and names match
57
57
  # premium-plugins/venture-studio/PLUGIN.md § First-conversation routing.
58
58
  # Section order is preserved so the agent can walk them sequentially.
59
+ # Prototype-host is slotted between Stage 2 (wedge + landing copy) and
60
+ # Stage 3 (business plan): brand-pack and wedge are hard prerequisites
61
+ # (the skill consumes 06-product-ip/brand/ tokens and 01-narrative/LANDING.md);
62
+ # hosting before business plan keeps the founder's wedge-validation loop
63
+ # (live URL) ahead of investor-pack work.
59
64
  PAYLOAD=$(cat <<JSON
60
65
  {
61
66
  "accountId": "${ACCOUNT_ID}",
@@ -64,8 +69,9 @@ PAYLOAD=$(cat <<JSON
64
69
  "tier": "full",
65
70
  "workItems": [
66
71
  {"name": "Stage 1 — Office-hours design doc", "description": "Produces 01-narrative/office-hours-design.md via the office-hours skill."},
67
- {"name": "Brand pack", "description": "Produces brand identity (palette, typography, logo) into 06-product-ip/brand/ via the brand-pack skill."},
68
72
  {"name": "Stage 2 — Wedge validation + landing page + PRD", "description": "Produces 01-narrative/{PMF,LANDING,PRD}.md via the zero-to-prototype skill."},
73
+ {"name": "Brand pack", "description": "Produces brand guidelines + summary + color palette + typography system + tone-of-voice (voice/messaging/copy) + logo guidelines into 06-product-ip/brand/ via the brand-pack skill."},
74
+ {"name": "Prototype host", "description": "Public URL for the landing page and/or wedge prototype via cloudflared ingress + systemd-managed dev server (skill: prototype-host)."},
69
75
  {"name": "Stage 3 — Business plan", "description": "Produces 01-narrative/business-plan.md via investor-data-room Stage 3."},
70
76
  {"name": "Stage 3b — Term sheet", "description": "Produces html/prospectus/term_sheet.html via investor-data-room Stage 3b."},
71
77
  {"name": "Stage 4 — Deck blueprint", "description": "Produces 01-narrative/deck-blueprint.md via investor-data-room Stage 4."},
@@ -101,4 +107,10 @@ for section in \
101
107
  mkdir -p "${DATA_ROOM}/${section}"
102
108
  done
103
109
 
110
+ # Hosted-prototype source roots live alongside the numbered sections under
111
+ # data-room/. The prototype-host skill populates this with one subdir per
112
+ # surface (landing/, prototype/, etc.); creating the parent here so the
113
+ # directory exists before the first invocation.
114
+ mkdir -p "${DATA_ROOM}/prototype"
115
+
104
116
  echo "scaffold.sh: data-room scaffolded at ${DATA_ROOM}"
@@ -39,7 +39,7 @@ Before generating anything, gather the following. Ask all questions in a single
39
39
  - Website URL (fetch it if provided — extract existing colors, fonts, tone)
40
40
  - Inspiration brands (brands they admire aesthetically)
41
41
 
42
- If the user provides a URL, use the fetch tool to read it and extract: dominant colors (check CSS or visual description), font choices, tone of copy, and any existing brand signals. Pre-fill your recommendations based on this research.
42
+ If the user provides a URL — their own site, a competitor brand, or an inspiration brand — dispatch the `research-assistant` specialist (with `deep-research` for site copy + visual scan, `WebFetch` available through the specialist) rather than calling `fetch` or `WebFetch` directly from this skill. The specialist returns: dominant colors (CSS or visual description), font choices, tone of copy, and any existing brand signals. Pre-fill your recommendations from the specialist's structured findings.
43
43
 
44
44
  ---
45
45