---
name: update-listing
description: Update an existing short-term rental listing when amenities are added, swapped, or removed. Triggers on "update [property] listing", "add new amenities to [property]", "refresh the [property] listing", "[property] has new amenities", "the listing needs updating", "listing update for [property]", "we added new amenities to [property]", "[property] description needs updating", or any request to refresh a vacation-rental listing because something changed at the property. Also triggers on "refresh voice profile", "redo voice intake", or "the voice is off" to re-capture the operator's voice profile. NOT for creating brand-new listings from scratch.
---

# Update an STR listing description

Refresh a short-term rental listing when amenities change. Applies the standard STR listing template, produces a word-level diff, and packages a Word doc for team handoff.

## When to use this skill

Use this skill when the user is updating an *existing* listing because something changed at the property - new pool installed, fire pit added, hot tub removed, amenity upgraded, etc. The user already has listing copy; the job is to refresh it.

Do NOT use this skill for:
- Brand-new listings being written from scratch (no "before" version exists)
- Channel-specific copy variants (Airbnb vs VRBO tweaks)
- General copywriting requests that aren't tied to a property update

## Workflow

Follow these steps in order. Each step is a checkpoint with the user - don't skip ahead.

### Step 0: Operator voice intake (one-time setup, gates the rest)

The skill writes listing copy that has to sound like the operator's brand, not like the plugin author's. Step 0 captures the operator's voice once and reuses it on every subsequent run.

1. **Read** `references/operator-voice-profile.md`.
2. **If the file is populated** (the `captured_on:` field is set to a real date and at least one example listing has a "Full text" block), skip ahead to Step 1 silently. No prompt, no confirmation - the profile is already locked in.
3. **If the file is missing, empty, or still contains the placeholder stub** (`captured_on: <NOT YET CAPTURED>`), run the intake:
   a. Ask the operator for their **company website** as a URL. Note this is optional - they can press enter to skip if their company has no public site yet.
   b. Ask for **up to 3 examples of listings they consider "perfect"** for their operation. Accept any of: pasted text in chat, file paths (.docx / .md / .txt / .pdf), or public listing URLs (Airbnb / VRBO / direct site). Minimum 1 example; encourage 3.
   c. If a website URL was provided, use WebFetch to load it. Pull 3-5 short verbatim quotes from hero copy, About page, or value-prop sections that signal voice and brand. Discard nav, footer, legal, cookie-banner, and chat-widget text.
   d. For each example listing, read it in full and summarize the voice signals in 4-6 bullets: opener style, sentence rhythm, amenity-framing patterns, distinctive phrases, area-section structure, and sign-off cadence.
   e. Synthesize 3-5 composite voice notes across all examples: what always shows up, what never shows up, what tone register to hit.
   f. **Write** `references/operator-voice-profile.md` using the structure shown in that file's stub header. Set `captured_on:` to today's date.
   g. Show the operator the populated profile inline and ask: "This is what I'll use as your voice anchor going forward. Look right?" If they push back, edit per their feedback and re-show. Don't proceed until they confirm.
4. Once the profile is written and confirmed (or already populated and silently reused), proceed to Step 1.

**Refresh trigger:** if the operator says "refresh voice profile", "redo voice intake", "the voice is off", "re-capture brand voice", or similar, treat as a forced Step 0 even when the profile is populated. Overwrite the file with the new capture.

**Edge cases:**
- **No website:** skip the WebFetch step. Profile is built from example listings only.
- **0 examples and no website:** block. Don't fall through to template defaults - that's the failure mode this step exists to prevent. Explain that the skill needs at least one example listing to anchor voice.
- **WebFetch fails or page is JS-heavy:** skip the website signals, proceed with example listings only. Note the failure in the profile so the operator knows.
- **1 or 2 examples instead of 3:** that's fine - "up to 3" already implies flexibility.

### Step 1: Gather the current listing

Ask the user how they'd like to provide the existing listing description. Offer:
- Paste it directly into chat
- Upload a file (.docx, .md, .txt, PDF)
- Pull from a known location (Google Doc link, Notion page, etc.)

Once you have it, confirm the property name and identify which template fields it currently covers (Headline, Short Description, The Space with area sub-sections, Guest Access, Other Things to Note, Location Description).

If any required field is missing from the current listing, flag it and ask how to handle it (fill from template defaults, leave blank, ask owner).

### Step 2: Gather the new amenities

Ask the user to list the new amenities and - critically - which *area of the property* each one belongs in. The standard areas are:
- Living & Game Room
- Kitchen & Dining
- Sleeping Arrangements (and which bedroom)
- Bathrooms
- Backyard
- Other (e.g., garage, gym, dock)

If an amenity replaces or upgrades an existing one (e.g., wood-burning fire pit replacing a propane fire pit), confirm whether the old one is being removed or kept alongside.

### Step 3: Apply the template

**Voice anchor first:** before writing any prose, re-read `references/operator-voice-profile.md`. Match its cadence, opener patterns, sentence rhythm, and phrasing register. The template's structure stays as-is; the prose voice does not.

Read `references/template.md` for the full field structure. Update each affected section:

1. **Headline (50 char max)** - Rewrite if a new amenity is headline-worthy (cowboy pool, hot tub, game room). Always count characters; cap at 50.
2. **Short Description / About This Space** - Update the intro paragraph and the bulleted feature list (the "Backyard Oasis" line, etc.).
3. **The Space - long intro paragraph** - Weave new amenities into the narrative, especially the backyard sentence which is usually the most loaded.
4. **The Space - area sub-sections** - Update the affected area's intro line + checkmark amenity list. Reorder so the newest/most impactful amenities are at the top of the list.
5. **House Note (under Other Things to Note)** - Update any amenity-specific instructions (e.g., "Firewood for the fire pit" → "Firewood for the Solo Stove fire pit").
6. **Location Description - Tranquil Retreat paragraph** - Update if the new amenity adds a sensory hook (e.g., "cooling off in the cowboy pool").
7. **Keywords (bottom of Location Description)** - Add new amenity keywords for SEO.

Also do a consistency pass: check bedroom numbering in the Bathrooms section matches the Sleeping Arrangements numbering. Check that nothing the user said was removed still appears anywhere in the listing (search the full text for it).

### Step 4: Show updated copy in chat

Re-check `references/operator-voice-profile.md` if you drifted while writing. Then present the full updated listing in chat, section by section. The user wants to be able to copy-paste from chat into their working Google Doc.

### Step 5: Render the visual diff

**Primary path - script-generated HTML:**

1. Build a `diff_config.json` in the current working directory:
   ```json
   {
     "propertyName": "<property name>",
     "date": "<today's date>",
     "sections": [
       { "title": "1. Headline", "type": "prose", "old": "<old text>", "new": "<new text>" },
       { "title": "2. Short Description - bullets", "type": "bullets", "old": ["..."], "new": ["..."] }
     ]
   }
   ```
   Include one entry per changed section. Use `type: "prose"` for freeform text and `type: "bullets"` for amenity lists where each line is added/removed atomically. See `references/diff-format.md` for the color spec and sections to always cover.

2. Run the diff builder:
   ```bash
   node ${CLAUDE_PLUGIN_ROOT}/scripts/build_diff.js /path/to/diff_config.json "<Property Name> - Diff.html"
   ```
   The script runs a Myers word-level diff (prose) or line-level diff (bullets) algorithmically - unchanged words reliably render green, changed words red. A successful run means the HTML is validated and saved to the current working directory.

3. Delete `diff_config.json` after the HTML is written.

**Inline widget (Cowork only):** if `mcp__visualize__show_widget` is available, also render the diff inline using the widget template from `references/diff-format.md` (use CSS variables, not hex). Skip silently if the tool is missing - the HTML file is enough.

Cover every section that changed. Don't skip "minor" changes - bedroom numbering fixes, typo corrections, etc.

### Step 6: Build the team-handoff Word doc

Generate a JSON config matching the schema in `scripts/build_doc.js`, then run:

```bash
node ${CLAUDE_PLUGIN_ROOT}/scripts/build_doc.js /path/to/config.json /path/to/output.docx
```

The script produces a polished Word doc with:
- Title block (property name + date)
- "What changed in this update" bulleted summary
- Each field as its own H3 section with a gray "where it goes" callout and the ready-to-paste copy

Save the `.docx` to the current working directory as `<Property Name> - Listing Update.docx`. The build script itself validates the output (size > 1 KB, valid ZIP signature) and exits non-zero if anything is off, so a successful run means the file is good.

## Output expectations

The user expects three deliverables every time:
1. Updated listing copy in chat (in the operator's voice per `references/operator-voice-profile.md`)
2. Word-level visual diff saved as `<Property Name> - Diff.html` in the current working directory (plus inline widget render if available)
3. `<Property Name> - Listing Update.docx` saved to the current working directory

If the user pushes back on the diff format, the docx structure, the template, or the captured voice, update the relevant reference file (`references/diff-format.md`, `references/template.md`, or `references/operator-voice-profile.md`) so the change sticks for next time.

## See also

- `references/operator-voice-profile.md` - captured operator voice anchor, populated by Step 0
- `references/template.md` - full field structure with formatting conventions
- `references/diff-format.md` - exact visual diff specification
- `references/example.md` - generic worked example showing the full workflow
- `${CLAUDE_PLUGIN_ROOT}/scripts/build_doc.js` - parameterized docx builder (JSON config schema documented in the file header)
- `${CLAUDE_PLUGIN_ROOT}/scripts/build_diff.js` - algorithmic diff builder (config schema and usage documented in the file header)
