Aller au contenu

Référence de la CLI bo

bo est l’outil de développement du dépôt : base de données, serveurs, tests, captures. Chaque commande accepte --help et --json ; club, platform, stripe connect et stripe reconcile acceptent aussi --target staging (la commande tourne sur la machine de staging, voir Déploiement). Page générée : elle liste exactement les commandes du code.

Check the machine + show this worktree’s env (ports, db)

Option Type Rôle
--json boolean

Databases: one per worktree, on the shared local Postgres

Start the local Postgres and the Mailpit dev inbox (shared containers)

Create the worktree DB if needed and apply pending SQL migrations

Option Type Rôle
--json boolean Machine-readable output

Show this worktree’s database and pending migrations

Option Type Rôle
--json boolean Machine-readable output

Drop this worktree’s database and test databases (then bo db migrate + bo e2e setup)

Option Type Rôle
--json boolean Machine-readable output
--yes boolean

Interactive psql on this worktree’s database (–app for the RLS role)

Option Type Rôle
--app boolean connect as the RLS-subject app role

Print the connection URL (owner by default, –app for the RLS-subject role)

Option Type Rôle
--app boolean

The end-to-end dataset (specs, journeys, screenshots)

Reset this worktree’s bo and demo clubs to the e2e dataset (also the dev one): the bo preset plus named test people (known password), bookings, money. Local only

Option Type Rôle
--json boolean Machine-readable output

Write e2e/durations.json (seconds per spec, from the latest green bo ci main, or an e2e.yml run’s blob reports): the e2e pick balances shards by it

Option Type Rôle
--run string an e2e.yml run id whose blob reports are still kept (default: the latest green bo ci main, else main’s latest green e2e.yml run that has them)
--json boolean Machine-readable output

Run api, worker, mcp, desk, player on this worktree’s ports (–only api,desk). Ctrl-C or bo dev down stops.

Option Type Rôle
--only string comma-separated: api,worker,mcp,desk,player
--club string dev only: this club answers on the bare localhost too (MCP clients can OAuth over http)

Stop this worktree’s dev servers, including orphans left by a closed terminal

Option Type Rôle
--json boolean

Which dev servers listen on this worktree’s ports, with their URLs, plus the shared docker services

Option Type Rôle
--json boolean

Tests. bo test all is the pre-commit gate.

biome ci → typecheck → unit → e2e → site check, stop at first failure (CI: bo ci, the same steps on a merge tree, docs/review.md § 2)

biome ci: lint + format check, a warning fails it, read-only

tsc in every package (turbo, cached)

Vitest in packages and apps (domain, api auth, mcp OAuth vs this worktree’s test DB; then desk + player model/hook tests + stories in Chromium, after the DB suites, not alongside)

Option Type Rôle
--only string only these packages, comma-separated short names (e.g. desk,player)
--skip string every package but these (e.g. desk,player)
--at string run with the tests’ clock moved to this date, e.g. 2027-11-05 (JS clock only: Postgres now() is unchanged, #630)

Playwright against api + desk on this worktree’s ports (re-seeds first)

Option Type Rôle
--headed boolean
--shard string run one part of the spec files, e.g. 2/4 (Playwright –shard)
--only string only these spec files, comma-separated names (e.g. booking,money for tests/booking.spec.ts…)
--bail boolean stop at the first failed test

Local CI (replaces the GitHub workflows): the candidate (the PR squashed onto origin/main) in a throwaway worktree, fresh install + databases, ci.yml’s jobs, e2e (AI pick, cache, failed-first, shards, a red spec once more alone; main: full suite), a Claude Code review once per PR (OCR with a Console key); posts the gate + merge-ready statuses and a PR comment. A tree a green run tested today on the same main isn’t run again. One run at a time on this machine (FIFO, main first)

Argument Type Rôle
target string a PR number, or main (default: this branch’s PR)
Option Type Rôle
--review boolean review again even if this PR was reviewed already (after a fix or a rebase)
--no-review boolean skip the review
--no-cache boolean no cache: every picked e2e spec, even those that passed before on identical inputs, and every unit/stories suite (TURBO_FORCE)
--keep boolean keep the run’s worktrees and databases (debugging)
--json boolean Machine-readable output

What bo ci runs now, what waits, the merge queue, the last runs

Option Type Rôle
--json boolean Machine-readable output

Merge a PR through the local merge queue: its own bo ci green on its head, then it waits its turn; the queue squashes up to 4 queued PRs onto main as one candidate, bo ci tests the tip (a tree already tested isn’t run again) and main fast-forwards to it. Red: half the batch next time; a PR red alone leaves the queue with a comment. Closes each PR « Merged as », deletes its branch. No PR: drain what is queued. –withdraw: take the PR out of the queue. –ci-main: bo ci main after

Argument Type Rôle
pr integer the PR to merge (none: drain what is queued)
Option Type Rôle
--ci-main boolean run bo ci main once the queue is drained (the full suite, the daily net)
--withdraw boolean take the PR out of the merge queue instead (Ctrl-C on a waiting bo merge leaves it queued)
--json boolean Machine-readable output

Talk to this worktree’s api

Run a GraphQL query against this worktree’s api, signed in as the club owner (dev session)

Option Type Rôle
--query string
--as string acting user: owner | none | (a dev session, see bo auth login)
--club string
--json boolean

After a GraphQL schema change: write apps/api/schema.graphql and regenerate apps/{desk,player}/src/gql

Dev identities (Better Auth sessions on club hosts)

Dev session for a user on a club host: cookie for scripts + a sign-in URL for the browser

Argument Type Rôle
email string user to sign in as (default: list the club’s identities)
Option Type Rôle
--club string
--json boolean

Stripe helpers: test mode, nightly reconciliation by hand

stripe listen –latest → this worktree’s /webhooks/stripe (checkout + account.updated, test mode)

Make a club chargeable (–target staging: the box’s sandbox): attach (–account) or create the sandbox’s Standard account (audited when the club’s account changes), wait for the terms click

Option Type Rôle
--club string club slug
--account string attach this existing Standard account (acct_…) instead of creating one
--email string the email of an account this command creates (default: the club’s contact email); Stripe mails it
--replace boolean a new account for a club whose account never went live (not the shared BO_STRIPE_TEST_ACCOUNT); the old one is deleted from the sandbox once the new one is attached
--no-wait boolean print the terms link and stop (the row stays pending)
--json boolean

Compare each club’s Stripe charges with our card payments: record a charge of ours left unrecorded, keep any other difference (what the worker does nightly). –target staging

Option Type Rôle
--at string run as of this instant (ISO, e.g. 2026-12-01T06:00+01:00); default now
--club string one club (slug)
--days integer look back this many days (default 3, at most 90)
--json boolean

Design-pass tooling

Screenshot every route of an app (light + dark) into demos/ui// for the design pass

Option Type Rôle
--app string
--out string

Upload this branch’s shots (+ –before) to an unlisted gist and print the PR’s before/after grid

Option Type Rôle
--app string
--before string shots of the screens before the change (a bo ui shots --out dir)
--yes boolean confirm the upload: the gist is unlisted, anyone with its URL sees the shots

Storybook of an app (screens on a fake client + @booker/ui) on this worktree’s STORYBOOK_PORT (desk), STORYBOOK_PLAYER_PORT (player) or STORYBOOK_SITE_PORT (site); –app all runs the three in the desk’s sidebar; –build makes the static composed one (dist/design) for bo deploy design

Option Type Rôle
--app choice (choices: desk, player, site, all)
--build boolean build the static Storybook of the three apps, composed, into dist/design (what bo deploy design uploads); –app is ignored

Clubs (tenants). –target staging: on the box

Create a club: legal details (required), subdomain, one site with default opening hours, optional owner (audited, invited by email)

Argument Type Rôle
slug string subdomain: .localhost
Option Type Rôle
--name string default: the slug
--owner string owner’s email (user created if new); –target staging: owner.@ by default
--primary string #rrggbb
--accent string #rrggbb
--legal-name string raison sociale, e.g. « Bø Sports SAS »
--siret string 14 digits
--legal-address string siège social
--preset choice bo: Bø: 7 padel, 4 squash, 2 badminton · pickleball, its prices and formules · padel: Padel only: 4 indoor courts, 9h–22h, one price · tennis: Tennis only: 2 clay outside, 2 covered, hourly, a yearly membership · squash: Squash only: 3 courts, 45-min créneaux, a monthly unlimited · demo: the bo preset + ~90 fictitious players (@.example.test, no password), a week of past and coming bookings, money, subscriptions, a lesson (needs an owner) (choices: bo, padel, tennis, squash, demo)
--test-password boolean the owner starts with BO_TEST_ACCOUNT_PASSWORD (env file; local or staging only; a password it has is kept). Test accounts only
--json boolean

Invite a club’s owner by email (audited): the link sets a first password, never resets one; –test-password –reset replaces this account’s

Argument Type Rôle
slug string subdomain: .localhost
email string the owner’s email
Option Type Rôle
--test-password boolean the owner starts with BO_TEST_ACCOUNT_PASSWORD (env file; local or staging only; a password it has is kept). Test accounts only
--reset boolean with –test-password: replace the password this account already has (the account named here only)
--json boolean

Set up a club without courts from a preset: site, hours, sports, courts, prices, formules (audited); demo also fills it with fictitious players and activity

Argument Type Rôle
slug string subdomain: .localhost
Option Type Rôle
--preset choice bo: Bø: 7 padel, 4 squash, 2 badminton · pickleball, its prices and formules · padel: Padel only: 4 indoor courts, 9h–22h, one price · tennis: Tennis only: 2 clay outside, 2 covered, hourly, a yearly membership · squash: Squash only: 3 courts, 45-min créneaux, a monthly unlimited · demo: the bo preset + ~90 fictitious players (@.example.test, no password), a week of past and coming bookings, money, subscriptions, a lesson (needs an owner) (choices: bo, padel, tennis, squash, demo)
--json boolean

One account per desk role besides the owner (staff, coach), members without an invitation and signing in with BO_TEST_ACCOUNT_PASSWORD (audited; local or staging only). Test accounts only

Argument Type Rôle
slug string subdomain: .localhost
Option Type Rôle
--reset boolean replace the password these accounts already have
--json boolean

Platform admin (no UI yet). –target staging: on the box

List every club with member and verified custom-domain counts

Option Type Rôle
--json boolean

Change a user’s sign-in email, for every club they belong to (audited): password, sessions and memberships stay, the new address is unverified, pending invitations follow

Argument Type Rôle
from string the user’s current email
to string the new email (must be free)
Option Type Rôle
--json boolean

Make an existing user a platform admin: every club’s desk with the owner’s rights (audited); –test-password for a test admin

Argument Type Rôle
email string an existing user’s email (signed in once, or invited and claimed); with –test-password, made if new
Option Type Rôle
--test-password boolean a test admin (staging’s « comptes de test » page): created if new, signs in with BO_TEST_ADMIN_PASSWORD (local or staging only; never an existing account without a password or a club member’s; –reset replaces its password)
--reset boolean with –test-password: replace the password this account already has (the account named here only)
--json boolean

Feedback notes from the clubs (the desk’s toolbar; the player app’s form comes with RBD-197; COLLECT_FEEDBACK). –target staging: on the box

Feedback notes sent from the desks and player apps, newest first (open ones by default; audited)

Option Type Rôle
--status choice open (default), handled or all (choices: open, handled, all)
--club string one club’s slug
--limit integer
--json boolean

One Feedback note, ready to paste to an agent: who, when, URL, release, each element pointed at with its test id and comment

Argument Type Rôle
id string the note’s id (bo feedback list)
Option Type Rôle
--json boolean

Mark a Feedback note handled, once: it leaves bo feedback list

Argument Type Rôle
id string the note’s id (bo feedback list)
Option Type Rôle
--json boolean

File a Feedback note as a Linear issue now, once (a note sent while filing was off, or whose job gave up; audited)

Argument Type Rôle
id string the note’s id (bo feedback list)
Option Type Rôle
--json boolean

Import a CSV/XLSX export (preview; –apply to write) through the api as the club owner. docs/import.md

Argument Type Rôle
entity choice what the file holds (recommended order: resources tariffs memberships entitlements wallets bookings)
file string CSV or XLSX export of the previous software
Option Type Rôle
--apply boolean write to the club (default: preview only)
--template boolean print the CSV template of and exit
--source string previous software: the column mapping is saved per source
--map string column mapping overrides: “lastName=Nom client,email=Mail” (field keys: –template)
--club string
--as string acting owner: owner (the club’s) |
--json boolean

Email templates (react-email, packages/emails): preview and export

react-email preview server on this worktree’s EMAILS_PORT: every message, Bø + demo, fr + en

Static HTML + text + 360 px light/dark shots of every message into demos/emails//

Option Type Rôle
--out string output dir (default demos/emails/)
--json boolean

The Mailpit dev inbox (everything bo dev and e2e send)

Latest message in the Mailpit dev inbox (–to, –since, –subject, –wait)

Option Type Rôle
--to string recipient email or phone
--since string ignore messages before this ISO time
--tag string only what one worktree sent (its db name, e.g. booker_main)
--subject string only messages whose subject contains this text
--wait integer seconds to wait for a matching message (the worker sends async)
--json boolean

The public site + docs (apps/site, Astro + Starlight, ADR-0013)

Astro dev server for apps/site on this worktree’s SITE_PORT

Static build of the site into apps/site/dist (shots/journeys if generated)

Option Type Rôle
--json boolean

Site gate: docs coverage, astro check, build, internal links, axe + smoke on the built pages

Option Type Rôle
--json boolean

bo ui shots for desk + player and the journeys, into apps/site/src/generated (needs bo dev)

Option Type Rôle
--json boolean
--part choice only one part: desk shots, player shots or the journeys (choices: desk, player, journeys)

Run the site’s journeys on the real app (e2e spec) and capture light/dark stills + a webm per journey

Option Type Rôle
--only string one journey id (e.g. reserver-un-terrain)
--json boolean

Run site.yml on main (captures, build, links, Netlify deploy when SITE_PUBLISH is on); CI never runs it on its own

Option Type Rôle
--json boolean

Put a changelog video on the site’s media bucket (R2): web-sized H.264, a poster, a content-hashed name; prints the entry’s frontmatter

Argument Type Rôle
file string the rendered video (mp4, mov…)
Option Type Rôle
--name string the slug, kebab-case: court-sponsors → changelog/court-sponsors-.mp4
--dry-run boolean encode and name only, upload nothing (files kept in the temp folder printed)
--json boolean

Write the site’s CLI reference page from the bo command tree

Option Type Rôle
--check boolean fail if the committed page differs (CI) instead of writing it
--json boolean

Subscriptions (ADR-0012): the renewal engine by hand, the support list

Run the renewal engine once (what the worker does every 15 min): renew, collect, retry, suspend, notices

Option Type Rôle
--at string run as of this instant (ISO, e.g. 2026-12-01T06:00+01:00); default now
--club string one club (slug)
--simulate-card choice dev: answer off-session card charges locally instead of calling Stripe (nothing leaves the machine) (choices: succeed, decline, requires_action)
--json boolean

Support: a club’s subscriptions (default bo), filtered by –status

Option Type Rôle
--club string one club (slug)
--status choice all · active · unpaid · suspended · ending (choices: all, active, unpaid, suspended, ending)
--json boolean

Money jobs by hand: check the money rules, recover paid checkouts

Check every club’s money rules (the worker does it nightly and records a failing club); exits non-zero on a violation

Option Type Rôle
--club string one club (slug)
--json boolean

Record hosted checkouts paid at Stripe whose webhook never came (what the worker does every 15 min)

Option Type Rôle
--at string run as of this instant (ISO, e.g. 2026-12-01T06:00+01:00); default now
--club string one club (slug)
--json boolean

The deployed environment: one box, Docker Compose over ssh (ADR-0016)

Create (or find) the box, its firewall, backups and the and *. DNS; generate the deploy secrets. Idempotent

Option Type Rôle
--json boolean Machine-readable output

Build this checkout on the box, apply migrations, restart what changed, wait for the api’s health, then remove our other images (keeps this release and the previous one) and the build cache past 4gb; under 20% free disk, prunes before building

Option Type Rôle
--json boolean Machine-readable output

Free the box’s disk: remove our images except the running release and the newest other one (to roll back to), dangling images, and the build cache past 4gb

Option Type Rôle
--json boolean Machine-readable output

Upload the public site to (the base domain itself): the local apps/site/dist, or –run <id|latest> for a site.yml run’s artefact (with captures)

Option Type Rôle
--json boolean Machine-readable output
--run string A site.yml run (an id, or latest: main’s last successful one): its site artefact instead of the local apps/site/dist

Upload the composed Storybook (design system, desk, player, site) to design.: the local dist/design (bo ui storybook –build; none: the last green bo ci main’s), or –run <id|latest> for a dispatched ci.yml run’s artefact

Option Type Rôle
--json boolean Machine-readable output
--run string A ci.yml run (an id, or latest: main’s last successful one): its design artefact instead of the local dist/design

Register the Stripe webhook endpoints (platform + Connect) on api., API version pinned. Idempotent

Option Type Rôle
--json boolean Machine-readable output
--recreate boolean Delete and create both endpoints (new secrets), e.g. after a leaked secret

Brevo for real sends: the sender of BO_EMAIL_FROM, the email and SMS delivery webhooks on api. (one token, written, never shown); checks the SMS sender and credits. Idempotent

Option Type Rôle
--json boolean Machine-readable output
--check boolean Report only: change nothing, in Brevo or in the file
--recreate boolean A new token on both webhooks: a leaked token, or the api answering 401 to Brevo

The box’s services, their images, the api’s health, its free disk (warns under 20%); fails unless the firewall lets in 22/80/443 only and only the edge publishes ports

Option Type Rôle
--json boolean Machine-readable output

A service’s logs on the box (–follow, –tail )

Argument Type Rôle
service string api · worker · mcp · edge · postgres (default: all)
Option Type Rôle
--follow boolean
--tail integer

psql on the box’s database, as the owner