leboncoin — manage your listings, publish/delete via CDP
Markdown + photos in a folder are the source of truth. You help write the copy
and fill the structured fields; the skill drives Chrome (via the Chrome DevTools
Protocol, on the user's real logged-in profile) to publish and delete ads.
It reuses the project's zero-bot-detection CDP core, so there are no automation
flags and DataDome sees a normal browser.
The core rule
- Never publish content that is not in
annonce.md. The CLI only mechanically
transfers what the markdown says — title, description, price, attributes, photos.
- Semi-auto is the default.
publish fills every field and uploads the photos,
then pauses for the human to review in the browser and click
« Déposer mon annonce » themselves. Only pass --yes if the user explicitly asks
for full-auto. The human owns the final click; that click also clears the DataDome
captcha at submit.
- These actions hit the user's real account. Confirm intent before publishing or
deleting.
delete asks for a y/N confirmation unless --yes.
- Verify with the screenshot; ask when unsure.
publish saves
annonces/<slug>/publish-preview.png of the prefilled form — Read that PNG to confirm
visually before the human submits. If required info is missing (the CLI prints
ask the user about → …), ask the user and fix annonce.md rather than publishing a blank.
The script (zero install — just node)
node scripts/leboncoin.mjs <command> [options]
| Command |
What it does |
login (alias auth) |
Open the account page, actively verify you're logged in (DOM probe, not just a redirect), save auth-state.png. --cookies-file <p> attaches an exported cookies.json (best-effort escape hatch, always re-verified); waits while you log in if needed. Run before publish/delete. |
new <slug> |
Scaffold annonces/<slug>/annonce.md + photos/. --notes/--price/--zipcode/--condition/--attributes prefill it. |
comparables <slug> |
Scrape similar live listings → comparables.json + comparables.md (price/keyword/attribute grounding). |
validate <slug> |
Structural gate: required fields, ≥1 photo, real description, status: draft. Exit ≠ 0 if invalid (warnings are advisory). |
inspect <slug> |
Read-only: open the live deposit form and write form-map.json (every field + required/optional + select options) + initial.png/html. Submits nothing. Use it to discover category-specific required fields. |
publish <slug> |
Fill the deposit form + upload photos via CDP, write form-map.json + push-readiness.json + a preview screenshot. Semi-auto; --diagnostic (field report + HTML, no submit), --strict, --shots (checkpoint + element + post-submit confirmation shots), --yes, --no-screenshot. |
edit <slug> |
Re-open the published ad's modify form, re-fill from annonce.md, screenshot; review and click « Enregistrer » yourself (--yes submits). |
renew / mark-sold / deactivate / reactivate <slug> |
Bump / mark sold (→ sold) / pause (→ paused) / put back online (→ published). Confirm unless --yes. |
delete <slug> |
Remove a published ad (confirms unless --yes). |
list / status |
Show local annonces and their state (draft/published/sold/paused/deleted). |
scrape |
The original read-only scraper (search results + ad details). |
Common flags: --annonces-dir <dir> (default ./annonces), --json, -h, -v.
Full reference: references/cli.md.
Workflow
- Check login —
login opens the account page, verifies the session (it Reads an account
marker, not just the URL), and saves ~/.lbc-scraper/auth-state.png. Read that PNG to
confirm the account is shown before publishing/deleting. If logged out, log in once in the
opened browser (the command waits), then continue. publish/delete/edit/… also pre-flight
this and stop early with login-required if the session is dead.
- Create —
new <slug> --title "<t>" --category "<c>" --notes "<rough description>".
--notes seeds the body; you can also pass --price, --zipcode, --condition,
--attributes "k=v,k2=v2". Drop the user's photos into annonces/<slug>/photos/.
- Write the copy — the user gives rough notes + photos. You improve the description
into the markdown body (honest, specific). This is your judgment, not the CLI's.
- Ask for what's missing — if the annonce lacks required facts (zipcode, exact
model/year, condition, price), ask the user and write the answers into
annonce.md.
Never publish blanks. See references/enrichment-playbook.md.
- Ground it —
comparables <slug>; read annonces/<slug>/comparables.md, then set
price/category/attributes from what comparable ads show.
- Validate —
validate <slug> until it passes (warnings are advisory).
- Preview & publish —
publish <slug>. The engine fills the form, uploads the photos,
and writes three artifacts next to the annonce:
publish-preview.png — Read it to verify the form visually.
form-map.json — every live field with its required/optional status (and why) + select
options. Read it to find mandatory fields the annonce didn't cover — often
category-specific (e.g. kilométrage, surface). Live required fields are folded into
the missing[] / ask the user about → … list.
push-readiness.json — the machine-readable can-we-push? verdict (ready, blockers[]).
Read it first.
For any required-but-empty field, ask the user, write the answer into annonce.md
(durable), and retry. (publish --diagnostic fills + saves everything without submitting;
inspect <slug> does the same read-only without filling.) Then tell the user: review the
prefilled form and click « Déposer mon annonce ». Use --yes only if they asked for full-auto.
On success the ad id/URL are written back, status becomes published, and (with --shots)
shots/30-confirmation.png is the visual proof it went live.
- Manage —
edit (fix a typo / change price-photos; review + save), renew (bump),
mark-sold, deactivate/reactivate (pause/resume), delete (uses the stored id). All
pre-flight the login check and confirm unless --yes.
Markdown schema
One folder per annonce; frontmatter holds structured fields, the body is the
description. Minimal valid draft:
---
title: "MacBook Air M1 2020 — 256 Go"
category: "Informatique"
price: 650
zipcode: "75012"
attributes:
brand: "Apple"
model: "MacBook Air M1"
photos: [] # empty = use every image in photos/, sorted
status: draft
---
The description body the agent writes/enriches.
publish adds leboncoin_id, leboncoin_url, published_at, sets status: published.
Full contract + state machine: references/markdown-schema.md.
Safety & captcha
- Never
--yes without explicit user consent. Default semi-auto is the guardrail.
- DataDome is solved by the human in the browser; the engine waits up to 5 min.
A captcha at submit means
--yes still needs a human there.
- Login: publish/delete/edit/manage need the
~/.lbc-scraper profile already logged in.
Run login first to verify (it Reads an account marker and saves auth-state.png); every
write action also pre-flights it and stops early with login-required. The reliable session is
the once-copied real-browser profile. --cookies-file (attach an exported cookies.json) is a
best-effort escape hatch only — DataDome binds the session to the device fingerprint and
validates the token server-side, so injected cookies often set yet still bounce to login; the
command always re-verifies and reports the probe result, never the set-count.
- ToS: automating posts/deletes on a real account may violate Leboncoin's terms and
risks account action. The semi-auto default + explicit
--yes opt-in are deliberate.
Details: references/captcha-and-safety.md.
Scrape & comparables (read-only)
scrape and comparables use the original CDP scraper (real first navigation +
Next.js data routes). Use them to research the market before pricing. comparables
is the grounding step that makes the enriched description and price defensible.
References
references/markdown-schema.md — frontmatter contract, folder layout, status state machine, attribute vocabulary.
references/cli.md — every command, flag, exit code, and the offline demo.
references/enrichment-playbook.md — how to write the copy, infer category, and price from comparables.md.
references/deposit-form-mapping.md — the logical-field → selectors.ts map and the maintenance runbook when Leboncoin changes.
references/captcha-and-safety.md — semi-auto vs --yes, DataDome behavior, the stealth profile, ToS notes.
1---2name: leboncoin3description: Prepare and manage Leboncoin listings and research comparable ads through the logged-in browser.4license: MIT5---67# leboncoin — manage your listings, publish/delete via CDP89Markdown + photos in a folder are the **source of truth**. You help write the copy10and fill the structured fields; the skill drives Chrome (via the Chrome DevTools11Protocol, on the user's real logged-in profile) to **publish** and **delete** ads.12It reuses the project's zero-bot-detection CDP core, so there are no automation13flags and DataDome sees a normal browser.1415## The core rule1617- **Never publish content that is not in `annonce.md`.** The CLI only mechanically18 transfers what the markdown says — title, description, price, attributes, photos.19- **Semi-auto is the default.** `publish` fills every field and uploads the photos,20 then *pauses* for the human to review in the browser and click21 « Déposer mon annonce » themselves. Only pass `--yes` if the user explicitly asks22 for full-auto. The human owns the final click; that click also clears the DataDome23 captcha at submit.24- **These actions hit the user's real account.** Confirm intent before publishing or25 deleting. `delete` asks for a y/N confirmation unless `--yes`.26- **Verify with the screenshot; ask when unsure.** `publish` saves27 `annonces/<slug>/publish-preview.png` of the prefilled form — **Read that PNG** to confirm28 visually before the human submits. If required info is missing (the CLI prints29 `ask the user about → …`), ask the user and fix `annonce.md` rather than publishing a blank.3031## The script (zero install — just `node`)3233```34node scripts/leboncoin.mjs <command> [options]35```3637| Command | What it does |38|---|---|39| `login` (alias `auth`) | Open the account page, **actively verify** you're logged in (DOM probe, not just a redirect), save `auth-state.png`. `--cookies-file <p>` attaches an exported `cookies.json` (best-effort escape hatch, always re-verified); waits while you log in if needed. **Run before publish/delete.** |40| `new <slug>` | Scaffold `annonces/<slug>/annonce.md` + `photos/`. `--notes`/`--price`/`--zipcode`/`--condition`/`--attributes` prefill it. |41| `comparables <slug>` | Scrape similar live listings → `comparables.json` + `comparables.md` (price/keyword/attribute grounding). |42| `validate <slug>` | Structural gate: required fields, ≥1 photo, real description, `status: draft`. Exit ≠ 0 if invalid (warnings are advisory). |43| `inspect <slug>` | **Read-only**: open the live deposit form and write `form-map.json` (every field + required/optional + select options) + `initial.png`/`html`. Submits nothing. Use it to discover category-specific required fields. |44| `publish <slug>` | Fill the deposit form + upload photos via CDP, write `form-map.json` + `push-readiness.json` + a preview screenshot. Semi-auto; `--diagnostic` (field report + HTML, no submit), `--strict`, `--shots` (checkpoint + element + post-submit confirmation shots), `--yes`, `--no-screenshot`. |45| `edit <slug>` | Re-open the published ad's modify form, re-fill from `annonce.md`, screenshot; review and click « Enregistrer » yourself (`--yes` submits). |46| `renew` / `mark-sold` / `deactivate` / `reactivate` `<slug>` | Bump / mark sold (→ `sold`) / pause (→ `paused`) / put back online (→ `published`). Confirm unless `--yes`. |47| `delete <slug>` | Remove a published ad (confirms unless `--yes`). |48| `list` / `status` | Show local annonces and their state (`draft`/`published`/`sold`/`paused`/`deleted`). |49| `scrape` | The original read-only scraper (search results + ad details). |5051Common flags: `--annonces-dir <dir>` (default `./annonces`), `--json`, `-h`, `-v`.52Full reference: `references/cli.md`.5354## Workflow55560. **Check login** — `login` opens the account page, verifies the session (it Reads an account57 marker, not just the URL), and saves `~/.lbc-scraper/auth-state.png`. **Read that PNG** to58 confirm the account is shown before publishing/deleting. If logged out, log in once in the59 opened browser (the command waits), then continue. `publish`/`delete`/`edit`/… also pre-flight60 this and stop early with `login-required` if the session is dead.611. **Create** — `new <slug> --title "<t>" --category "<c>" --notes "<rough description>"`.62 `--notes` seeds the body; you can also pass `--price`, `--zipcode`, `--condition`,63 `--attributes "k=v,k2=v2"`. Drop the user's photos into `annonces/<slug>/photos/`.642. **Write the copy** — the user gives rough notes + photos. You improve the description65 into the markdown body (honest, specific). This is *your* judgment, not the CLI's.663. **Ask for what's missing** — if the annonce lacks required facts (zipcode, exact67 model/year, condition, price), **ask the user** and write the answers into `annonce.md`.68 Never publish blanks. See `references/enrichment-playbook.md`.694. **Ground it** — `comparables <slug>`; read `annonces/<slug>/comparables.md`, then set70 `price`/`category`/`attributes` from what comparable ads show.715. **Validate** — `validate <slug>` until it passes (warnings are advisory).726. **Preview & publish** — `publish <slug>`. The engine fills the form, uploads the photos,73 and writes three artifacts next to the annonce:74 - `publish-preview.png` — **Read it** to verify the form visually.75 - `form-map.json` — every live field with its **required/optional** status (and why) + select76 options. **Read it** to find mandatory fields the annonce didn't cover — often77 **category-specific** (e.g. `kilométrage`, `surface`). Live required fields are folded into78 the `missing[]` / `ask the user about → …` list.79 - `push-readiness.json` — the machine-readable *can-we-push?* verdict (`ready`, `blockers[]`).80 **Read it first.**81 For any required-but-empty field, **ask the user**, write the answer into `annonce.md`82 (durable), and retry. (`publish --diagnostic` fills + saves everything without submitting;83 `inspect <slug>` does the same read-only without filling.) Then tell the user: *review the84 prefilled form and click « Déposer mon annonce »*. Use `--yes` only if they asked for full-auto.85 On success the ad id/URL are written back, `status` becomes `published`, and (with `--shots`)86 `shots/30-confirmation.png` is the visual proof it went live.877. **Manage** — `edit` (fix a typo / change price-photos; review + save), `renew` (bump),88 `mark-sold`, `deactivate`/`reactivate` (pause/resume), `delete` (uses the stored id). All89 pre-flight the login check and confirm unless `--yes`.9091## Markdown schema9293One folder per annonce; frontmatter holds structured fields, the body is the94description. Minimal valid draft:9596```markdown97---98title: "MacBook Air M1 2020 — 256 Go"99category: "Informatique"100price: 650101zipcode: "75012"102attributes:103 brand: "Apple"104 model: "MacBook Air M1"105photos: [] # empty = use every image in photos/, sorted106status: draft107---108The description body the agent writes/enriches.109```110111`publish` adds `leboncoin_id`, `leboncoin_url`, `published_at`, sets `status: published`.112Full contract + state machine: `references/markdown-schema.md`.113114## Safety & captcha115116- **Never `--yes` without explicit user consent.** Default semi-auto is the guardrail.117- **DataDome** is solved by the human in the browser; the engine waits up to 5 min.118 A captcha at submit means `--yes` still needs a human there.119- **Login**: publish/delete/edit/manage need the `~/.lbc-scraper` profile already logged in.120 Run `login` first to **verify** (it Reads an account marker and saves `auth-state.png`); every121 write action also pre-flights it and stops early with `login-required`. The reliable session is122 the once-copied real-browser profile. `--cookies-file` (attach an exported `cookies.json`) is a123 **best-effort escape hatch only** — DataDome binds the session to the device fingerprint and124 validates the token server-side, so injected cookies often set yet still bounce to login; the125 command always re-verifies and reports the *probe* result, never the set-count.126- **ToS**: automating posts/deletes on a real account may violate Leboncoin's terms and127 risks account action. The semi-auto default + explicit `--yes` opt-in are deliberate.128 Details: `references/captcha-and-safety.md`.129130## Scrape & comparables (read-only)131132`scrape` and `comparables` use the original CDP scraper (real first navigation +133Next.js data routes). Use them to research the market before pricing. `comparables`134is the grounding step that makes the enriched description and price defensible.135136## References137138- `references/markdown-schema.md` — frontmatter contract, folder layout, status state machine, attribute vocabulary.139- `references/cli.md` — every command, flag, exit code, and the offline `demo`.140- `references/enrichment-playbook.md` — how to write the copy, infer category, and price from `comparables.md`.141- `references/deposit-form-mapping.md` — the logical-field → `selectors.ts` map and the maintenance runbook when Leboncoin changes.142- `references/captcha-and-safety.md` — semi-auto vs `--yes`, DataDome behavior, the stealth profile, ToS notes.