Turn photos of your stuff into ready-to-paste Facebook Marketplace listings.
Drop in a few photos → Claude identifies the item, writes the listing, and researches what it actually sells for → the app lays out every Marketplace field with a copy button. You paste into Facebook by hand. There's also a dashboard with earnings graphs and a GitHub-style selling-streak heatmap.
Local-only, runs in Docker, authenticates with your own Claude subscription.
The listing workspace — drop in photos, get every Facebook field with a copy button, plus price research that shows its working. A bundle is itemized line by line, so the estimate is something you can check rather than a number you have to trust:
Your listings at a glance — every status chip carries its count, so you can see where the work is instead of clicking through empty filters. Search covers titles, brands, models and tags:
Or as rows, sorted by price, days listed, or title — each one expanding in place to show the estimate, the retail anchor and the renewal history:
Dashboard — total earned, monthly trend, listings by status, a GitHub-style selling-streak heatmap, and what the agent spent per listing (shown with sample data):
Facebook has no public API for person-to-person Marketplace listings. The Catalog/Commerce API only reaches Marketplace for approved partner verticals (vehicles, real estate, jobs), and browser automation against the listing form violates Meta's terms and risks your account.
So Fleabook doesn't try to post for you. Posting was never the slow part — typing the form takes thirty seconds. Deciding what to type — what the thing is, how to describe it, what it's worth — takes ten minutes. That is the part this automates. The last step is a copy-paste you do yourself, which keeps it entirely within Facebook's rules.
- Photo → listing in one click. Identify + price run as a single job; the whole thing finishes server-side even if you close the tab.
- Real pricing research. Searches the web for used comparables, falls back to retail-minus-depreciation when comps are thin, and shows its reasoning and sources.
- Seller-voice descriptions. Written as the owner, not a photo caption — no "appears to be", no invented flaws, no made-up backstory.
- Context box. Tell Claude what the photos can't show ("the white one is sealed", a purchase link, "keep it short") and it feeds both identify and pricing.
- Copy-per-field UI mirroring Facebook's form exactly, with live character counters.
- Configurable meetup note appended to every description (set once in Settings).
- EXIF/GPS stripped from every upload — a photo taken at home won't leak your address.
- Dashboard: total earned, monthly earnings, listings-by-status, and a GitHub-style daily-earnings heatmap.
docker compose up -d --buildThen open http://127.0.0.1:5180.
The port is bound to 127.0.0.1 on purpose — this app has no auth of its own and spends your
Claude subscription on every agent run, so it should not be exposed to a network.
npm install
npm run devWindows runs the app natively — no WSL, no Docker. Download
the latest release and run
Fleabook-Setup.exe. It installs for you only, under %LOCALAPPDATA%, so Windows never asks
for an administrator password, and it adds Start-menu and desktop shortcuts plus an entry in
Settings → Apps. Fleabook then opens in its own window — not a browser tab. A portable zip
is in the same release for anyone who would rather not install anything; unzip it and
double-click Fleabook.exe. Note that GitHub's green Code → Download ZIP button gives you
the source, not the app — the executable only comes from a release.
The window is drawn with the Microsoft Edge WebView2 Runtime, which ships with Windows 11 and is on most Windows 10 machines; the installer adds it if it is missing. Neither build is code-signed, so the first run shows SmartScreen's "Windows protected your PC" — More info → Run anyway.
Listings and photos live in %LOCALAPPDATA%\Fleabook\data, not in the program folder, so
upgrading or uninstalling never touches them (the uninstaller asks separately, and defaults to
keeping them).
Connect it to Claude from Settings, which handles both options without a command prompt: Sign in with Claude for a subscription (a browser page opens; paste the code it returns), or an API key in the box below it. Note that signing in to the separate Claude desktop app does not sign in Fleabook — the two keep credentials in different places, so the in-app sign-in is required even on a machine where that app is already logged in.
To build the package yourself, from Linux or Windows:
node scripts/build-windows.mjsThat produces dist-windows/Fleabook-Setup.exe, the unpacked dist-windows/Fleabook/, and a
zip beside them (~160 MB compressed; most of it is the Claude Code binary the Agent SDK ships).
It needs Go and makensis (apt install nsis) on the build machine, plus network access to
fetch the pinned Node runtime. --no-installer skips the NSIS step. It does not touch the
Docker build.
Windows Docker Desktop also works if you prefer it — docker compose up -d --build is
unchanged — but set CLAUDE_DIR first, because Compose expands ${HOME} and Windows doesn't
set it:
echo CLAUDE_DIR=%USERPROFILE%\.claude> .envFleabook uses the Claude Agent SDK, which
authenticates with the credentials the Claude Code CLI already stored — no API key needed
if you're logged in with claude. Under Docker, that's why ~/.claude is bind-mounted.
Two things to understand about that mount:
- It's read-write, and it's the whole directory. OAuth token refresh rewrites
.credentials.json, and bind-mounting that single file breaks when the process replaces it via rename. So the directory is mounted, which means the container can write to your entire Claude Code config, conversation history included. If that's more access than you want, run on the host instead (npm run dev) — the SDK finds the same credentials natively. - It's for personal use. Anthropic does not permit third-party developers to offer
claude.ai login for their products. A tool you run on your own machine for your own listings
isn't that — but if you ever share or expose it, switch to an API key: set
ANTHROPIC_API_KEY(uncomment it indocker-compose.yml) and the same code path uses it.
An Anthropic API key works instead, and can be entered in Settings rather than as an environment variable — there is no compose file to edit in the Windows build. Settings also explains where to get one. The key is stored in the app's own SQLite database in plain text, alongside the meetup note; that database is local and never leaves the machine, but it is worth knowing before you put a key in it.
src/lib/server/auth.ts resolves the three sources in order, first hit wins:
ANTHROPIC_API_KEYin the environment — the operator's override. It outranks the database deliberately, so a deployment can't be repointed at someone else's billing from the web UI.- A key saved in Settings.
- Neither — the SDK falls back to the Claude Code OAuth credentials.
That resolution happens in one place and produces one environment object. The agent code does not branch on which source won.
Two agent stages, deliberately separate, run back-to-back as one server-side job:
- Identify (~15s, no web access) reads the photos and fills in title, description, category, condition, and flaws. Catching a misidentification here is cheap.
- Price (3–5 minutes, searches the web) finds real used comparables, falls back to retail-minus-depreciation when comps are thin, and reports its basis, confidence, reasoning, and the URLs it opened.
Progress streams over SSE. The work is not tied to the browser — close the tab mid-run and the result still lands in the database. Fire a listing, move to the next, come back later.
Web search reaches asking prices far more easily than completed sales (eBay's sold data is mostly behind JS and login), and asking prices run high. The agent adjusts for that, but the number is a starting point to negotiate from, not gospel — which is why the range, the reasoning, and the sources sit right next to it, and the price field stays editable.
- Meetup note (Settings): a line appended to every description — where/how to meet. The agent is told never to write meetup/pickup/payment language itself, so this is the single source of it.
- Port / origin: default
127.0.0.1:5180. Behind a reverse proxy, add your hostname's origin tocsrf.trustedOriginsinvite.config.ts(see the comment there) or SvelteKit's CSRF check will reject uploads.
src/
routes/ SvelteKit pages + API routes
dashboard/ earnings dashboard
listing/[id]/ the workhorse: edit + copy fields, run the agent
lib/
server/
db.ts sqlite via node:sqlite (no native dependency)
images.ts sharp: resize, thumbnail, EXIF/GPS strip
categories.ts curated Facebook category enum
depreciation.ts retention curves, passed into the pricing prompt
dashboard.ts earnings aggregation + sample-data seeding
agent/
run.ts query() wrapper: isolation, tool locking, JSON validate + retry
identify.ts stage 1 prompt + schema
price.ts stage 2 prompt + schema
sse.ts progress streaming that survives client disconnect
scripts/
smoke-agent.mjs verifies auth + settingSources isolation
dump-messages.mjs prints raw Agent SDK message shapes
SvelteKit 2 + Svelte 5, adapter-node. SQLite via Node's built-in node:sqlite (no native
build). sharp for images, zod for validating agent output, @anthropic-ai/claude-agent-sdk
for the model. Charts are hand-rolled inline SVG/CSS — no charting dependency.
settingSources: []is load-bearing. Without it the agent inherits~/.claude/CLAUDE.mdand any globally installed skills, and writes listing copy in that voice — a stylized global skill makes descriptions sound like it instead of like a normal seller. Runnode scripts/smoke-agent.mjsto confirm isolation after changing agent options or upgrading the SDK.- Origin handling is split across two files, and both halves matter.
docker-compose.ymlsetsHOST_HEADER/PROTOCOL_HEADERso the origin is derived from request headers (covers a proxy);vite.config.tsalso lists direct origins undercsrf.trustedOrigins(covers direct port access, wherePROTOCOL_HEADERis absent and adapter-node otherwise assumeshttps). Change the port and both files need updating. BODY_SIZE_LIMITdefaults to 512K in adapter-node, which rejects a single phone photo.docker-compose.ymlraises it to 128M. Nothing that large is stored — uploads are downscaled to 1600px on arrival.- Agent restarts orphan in-flight runs. A listing frozen at
identifying/pricingafter a restart is reset to a recoverable state on the next boot — no permanent zombies. - Progress notes depend on an undocumented SDK detail — tool calls arrive as
tool_usecontent blocks nested insideassistantmessages. If notes go quiet after an SDK upgrade, runnode scripts/dump-messages.mjs <photo-dir>and check whether the shape moved.
MIT — see LICENSE.
Made for flea-market flippers. Not affiliated with Facebook or Meta.



