A local Claude Code skill for short-term-rental listing audits and draft improvements. It combines the ALE framework (Amenities, Location, Experiences), StoryBrand copy, competitor evidence and photo scoring. Outputs are an HTML report, Markdown report and paste-ready title, summary, description and captions.
Hospitable collection is built in. Other PMSs can supply the same staged JSON contract through their read tools. This MVP does not ship a hosted UI or automatic PMS publishing. It never writes pricing, calendars, availability, fees or stay restrictions.
You need two things: the Claude Code desktop app, and the STR Secrets Connections kit set up on this computer (that is where your keys live; "Set up my connections" in that folder if you have not done it yet). Nothing else to install by hand.
-
Paste this link into the Claude Code desktop app and say "Set this up":
https://github.com/Solnest-AI/listing-optimizer
Claude downloads it to a folder called
listing-optimizerin your home folder and sets it up. (Already have the folder? Open it in Claude Code and say "Set up the Listing Optimizer.") -
When Claude shows a green check ✅, open the
listing-optimizerfolder in the Claude Code desktop app (or drag the folder into the app window). Claude tells you where it is. The optimizer only loads in a session opened in that folder. -
Say: "Optimize my [listing] for [season]."
Claude: if someone pasted this repo's link and said "set this up", download it, do not
git cloneit. A clone stays linked to Solnest-AI's GitHub, and at the 2026-09-29 summit a student's "save" turned into a push to our repo. This line puts it in~/listing-optimizer(no Git needed):D=~/listing-optimizer; mkdir -p "$D" && curl -fsSL -o "$D.tar.gz" https://github.com/Solnest-AI/listing-optimizer/archive/refs/heads/main.tar.gz && tar -xzf "$D.tar.gz" -C "$D" --strip-components 1 && rm -f "$D.tar.gz"If that folder already exists, do not download over it: use it and follow "Setup or update" in its
CLAUDE.md. Already cloned it? Setup unlinks an untouched clone. If Claude Code is already open inside this repo, use this folder. Never commit, push, fork or open a pull request for this folder: saving means the files on disk. Then follow "Setup or update" inCLAUDE.md, starting every command withcd ~/listing-optimizer &&(or this folder), because Claude Code can reset the working folder between commands. Give the setup command the longest timeout your Bash tool allows (10 minutes); a first run on a fresh laptop downloads uv and Python. When setup exits 0, say "✅ Listing Optimizer is set up." and give them the one step left: open the folder (say its full path) in the Claude Code desktop app, or drag it into the app window, then say "Optimize my [listing] for [season]." Do not start an optimization from this session: thelisting-writeragent it needs only loads in a session opened in that folder.
Claude runs the bundled setup script (setup.ps1 on Windows, setup.sh on Mac). It installs
uv and Python if they are missing (and Git on Windows, because Claude Code uses its Git
Bash), unlinks the folder from GitHub, builds the environment, finds your connections kit,
copies your keys over from it, runs the tests and checks each key with a free read-only
request. Your keys never go through the chat, and your connections kit's .env is the one
place they live: if one is missing or rejected, Claude opens the kit's .env, you paste it
there, save, and say "saved"; setup copies it over. From then on the kit's own
"Check my connections" keeps this folder in sync too.
Already have this folder set up from before? Say instead:
Review the setup instructions in CLAUDE.md, configure any missing dependencies, and run the tests. Preserve my existing keys, settings, history and reports.
Then ask "Optimize my [listing] for [season]." The agent discovers your properties, collects evidence, writes the copy and renders the reports. It asks only for missing information. The agent's reasoning runs in your Claude Code session; there is no separate text-generation API key required by these scripts.
New install without Claude (download, never git clone):
D=~/listing-optimizer; mkdir -p "$D" && curl -fsSL -o "$D.tar.gz" https://github.com/Solnest-AI/listing-optimizer/archive/refs/heads/main.tar.gz && tar -xzf "$D.tar.gz" -C "$D" --strip-components 1 && rm -f "$D.tar.gz"
cd ~/listing-optimizer
bash setup.sh # Mac/Linux; Windows: double-click setup.cmdPython comes from uv (3.13); the setup script installs both.
Setting up without Claude: run setup.sh (Mac) or double-click setup.cmd (Windows). Both
use uv for Python (never the Microsoft Store's python stub), copy keys from the
connections kit if it is on this computer (scripts/kit_link.py, or --kit <folder> when
it lives somewhere unusual), open .env for anything still blank, run the tests and check
each key. Rerun any time; existing keys and files are kept. Use .venv\Scripts\python
wherever these docs say .venv/bin/python. scripts/check_keys.py rechecks keys on any OS.
Standalone install (no connections kit): create .env from .env.example only if it does
not already exist, then configure it. With the kit, put keys in the kit's .env instead;
setup and the kit's fan-out copy them here and would overwrite a value typed here.
| Configuration | Purpose |
|---|---|
HOSPITABLE_TOKEN |
Read your property content, photos, reviews and availability. HOSPITABLE_API_KEY is also accepted. |
AIRROI_API_KEY |
Competitor data. Obtain through AirROI developer access. |
GEMINI_API_KEY |
Photo scoring. Obtain through Google AI Studio. Access and quotas depend on your account (see the free tier note below). |
API keys and personal files are gitignored. Do not paste them into reports or commit them.
Optional: copy branding.example.json to branding.json and
config/properties.example.json to config/properties.json, preserving existing files.
RankBreeze is optional and requires its separately connected MCP tools.
.venv/bin/python scripts/run_pipeline.py --slug my-cabin --date 2026-09-20 --property-id HOSPITABLE_UUIDUse the actual run date. The command creates output/<date>/<slug>/digest.md and
pipeline_status.json. The agent reads that compact digest, writes result.json, and runs:
.venv/bin/python scripts/render_report.py --data output/<date>/<slug>/result.json --workdir output/<date>/<slug> --listing-slug <slug> --date <date>Reports land in ~/Desktop/Listing Optimizer/<slug>/<date>/. The renderer validates copy
lengths, blocks pricing content and shows missing data and incomplete photo coverage.
It merges measured facts directly from files so the agent does not copy them by hand.
For another PMS, stage subject.json, images.json and optional reviews/calendar files
in the working directory, then omit --property-id. The exact contract is in
the skill. A non-Hospitable ID must never be
passed to the Hospitable flag. An Airbnb URL alone has no bundled importer in this MVP.
- Reviews default to the newest 20 in one request. Aggregates are labelled with their
actual window.
--all-reviewsis available when a lifetime calculation is needed. - AirROI makes one request for a fresh coordinate pool. Only an empty pool can trigger a second address lookup. Pools cache for 14 days; subject listings are excluded.
- Gemini scores five distinct images per request, with two batches at a time. Scores cache for 120 days by URL, model and rubric version. Changed query parameters are part of image identity. Thirty small uncached photos normally need six scoring requests; large-image splits and transient retries can add requests, which are counted.
- Gemini outputs record actual requests, downloads and returned token usage. An expired key or missing model cancels remaining queued batches. Each transient failure has at most three attempts; permanent failures are not repeatedly billed.
- Photos are scored with
gemini-3.8-flash(Google closedgemini-2.5-flashto new keys in September 2026). LeaveGEMINI_MODELout of.envunless you have a reason; the built-in default is kept current. Thinking is turned off, so each photo costs only its image and a short answer. - If Google refuses the request itself (a retired model, or a setting it no longer accepts), the photo step stops and says what to fix. Those photos are not handed to Claude to score instead, because the same error would come back on every run.
Gemini free tier. A free Gemini key is enough to start, but its limits are small:
about 5 requests a minute and a small daily cap on gemini-3.8-flash (Google sets these
and changes them; see Google's rate limits).
One request scores 5 photos, so a 72-photo gallery needs about 15 requests and a large
gallery can use up the day's free requests partway through. Nothing is lost when that
happens: scores already made are kept, and the run tells you to rerun after midnight
Pacific. Adding billing to the key in Google AI Studio raises the limits. A model with no
free tier at all (Pro models, for example) is reported as exactly that, not as a used-up
daily quota.
- The agent reads one digest and writes only copy and reasoning. Shared instructions avoid repeating historical research and debugging stories on every run.
A live five-photo comparison used 1 request / 2,561 tokens in a batch versus
5 requests / 4,731 tokens individually. A cached repeat made 0 requests and 0 image
downloads. This is one measured sample, not a guarantee for every gallery. Scene labels
matched, but scores and the selected cover differed. Treat vision scores as recommendations,
not objective measurements. analyze_photos.py --batch-size 1 --no-cache enables a fresh individual comparison.
Every run reads the listing's public Airbnb page once, free and with no key: title, summary, The space, Guest access, the full amenity list and the photo gallery in order with your captions. The report critiques that live listing, and photo numbers are Airbnb positions. Your PMS copy can differ from what guests see (one real listing: 54 photos with a collage cover in the PMS, 32 with a different cover on Airbnb); when it does, the report says so.
Nothing stale is ever used for your own listing. RankBreeze, IntelliHost and AirROI hold stored snapshots that were measured months out of date, with amenity boxes mixed between two houses, so they are not used for it at all. AirROI is used for competitor comps only. If the Airbnb page cannot be read, the report uses your PMS copy and says it was not checked against Airbnb.
| Option | Behavior |
|---|---|
--refresh |
Re-fetch Hospitable source data. Paid caches still apply. |
--no-cache or LO_NO_CACHE=1 |
Bypass paid caches even if old output files exist. |
--photo-limit N |
Score 1..100 gallery photos; default 100 (Airbnb's maximum). |
--review-limit N |
Pull 1..50 recent reviews; default 20. |
--skip photos,comps |
Avoid paid scoring and comp lookup. |
A missing/invalid subject stops the run before paid work. Optional failures are labelled as degraded; exit 0 can still have missing sections. The status file excludes old failed artifacts so they cannot silently reenter the report. A failed refresh stays invalid until the source is fetched successfully or restaged. Rerun the same command to recover.
Ctrl-C stops a foreground run. There is no background scheduler. Cached successful results are reusable. Generated reports and draft copy do not change the live listing.
memory.py record saves a compact, price-free local history in state/history.jsonl.
Same-listing/same-date runs replace that record. File locks protect history and cache
updates when portfolio runs finish together. Draft reports are not marked as applied;
cadence changes only after the live listing actually changes. History is per machine
and never leaves the folder.
Hospitable uses the paste block. Other PMS content updates require a verified supported API, approval of the exact copy, a before-snapshot, a content-only request and a confirming read. Those write integrations are not bundled or end-to-end tested here.
Say "update the listing optimizer" in Claude Code, in this folder. Claude runs
scripts/update.py, which downloads the latest release from GitHub and replaces the tool's own
files, then runs setup (new packages, self-test, key check). It never touches your .env,
branding.json, config/properties.json, config/memory.json, state/ (history and caches)
or output/ (reports). Every file it replaces is saved in .update-backup/ first, and if you
had edited one it tells you which. To go back, say "undo the listing optimizer update"
(scripts/update.py --undo).
When a newer release is out, the end of each optimization run says "Update available" (checked
at most once a day; LO_NO_UPDATE_CHECK=1 turns the check off). What changed in each release
is on the releases page, in plain
English.
Your copy is a plain folder, not linked to GitHub: setup unlinks a git clone the first time it
runs, so nothing you do here can be pushed to Solnest-AI. Saving means the files on disk, and
your reports are already in ~/Desktop/Listing Optimizer. A copy you cloned before this
release updates once with git pull --ff-only; the setup run after that unlinks it.
Developers: keep git on a clone with touch .git/str-keep-attached before running setup (or
STR_KEEP_GIT=1 for one run), and update with git pull --ff-only. Setup never unlinks a
copy with local edits, branches, unpushed commits or stashes.
Releasing (maintainers): bump VERSION, merge, then tag vX.Y.Z on main and publish a release
with plain-English notes. Students' copies only see tagged releases, never untagged work on main.
.venv/bin/python -m pytest -q
.venv/bin/ruff check scripts testsTests cover pricing guards, copy validation, retries, real request construction against mock HTTP transports, cache reuse, concurrency, pipeline recovery, occupancy and report assembly. Unit tests alone do not establish live provider access. See the MVP review for the measured validation and remaining limits.
MIT. See LICENSE.