Metadata-Version: 2.5
Name: bookai-cli
Version: 0.8.1
Summary: Command-line client for the bookai group-sales back office API
Requires-Python: >=3.9
Requires-Dist: click<9,>=8
Requires-Dist: httpx<1,>=0.27
Requires-Dist: keyring>=24
Requires-Dist: rich>=13
Provides-Extra: dev
Requires-Dist: pytest<9,>=8; extra == 'dev'
Requires-Dist: respx<1,>=0.21; extra == 'dev'
Description-Content-Type: text/markdown

# bookai-cli

Command-line client for the bookai group-sales back office API — manage venue
pricing, offerings and their showings, and view confirmed orders from the
terminal or a script, instead of clicking through the admin web app.

This is a thin HTTP client: it talks only to the public, authenticated JSON
API and contains no business logic or backend code.

## Install

```bash
pip install bookai-cli
```

## Authenticate

```bash
bookai login
```

Opens your browser to authorize this machine, then saves the key locally
(your OS keychain, or a `0600` file under your user config dir if no keychain
is available) — no copy-pasting a raw key required. **The environment you
just logged into also becomes your default** for every later command that
doesn't explicitly pass `--env`/`--base-url` (see
[Environments](#environments) below) — run `bookai login` again any time to
re-authorize, switch accounts, or switch which environment is your default.

Check what's currently active any time with:

```bash
bookai whoami
```

For scripting/CI, skip `login` and use a key directly instead — mint one from
the back office's **API Keys** page and set it as an environment variable:

```bash
export GROUPSALES_API_KEY=gsk_...
```

`--api-key` (or `GROUPSALES_API_KEY`) always takes priority over a
`bookai login`-saved key when both are present.

`bookai logout` removes the locally saved key (add `--all` to clear every
environment at once). This only forgets the key on this machine — it stays
valid until you revoke it from the back office's API Keys page.

## Environments

This project runs three environments:

| `--env`   | Base URL                       |
|-----------|---------------------------------|
| `prod`    | `https://b2b.bookai.now`           |
| `sandbox` | `https://b2b-sandbox.bookai.now`   |
| `local`   | `http://localhost:8001`            |

**You don't need to pass `--env` on every command.** `bookai login` sets the
environment you just authorized as your default (saved to a small local
config file, separate from the key itself) — so once you've run `bookai
--env sandbox login`, every later `bookai venues list` / `bookai pricing ...`
targets sandbox automatically, with no flags needed. Run `bookai whoami` any
time to check which environment and key are currently active.

`--env <name>` (or `--base-url <url>`/`GROUPSALES_BASE_URL` for a fully
custom URL not in the table above) overrides your default **for that one
command only** — it doesn't change what `bookai login` set as your default.
`bookai login` saves a separate key per base URL, so logging into sandbox and
prod don't clobber each other; switching your default just means running
`bookai login` again against the other one.

Before your first `bookai login`, the default is `prod` if nothing else is
set.

Global options (`--api-key`, `--base-url`, `--env`, `--json`) go **before**
the subcommand: `bookai --json venues list`, not `bookai venues list --json`.

## Interactive shell

Run `bookai` with no command to get a prompt — commands run without the
leading `bookai`, and your environment/key from that shell's startup (or from
a `login`/`--env` typed mid-session) stays in effect line to line:

```
$ bookai
bookai interactive shell -- commands run without the leading `bookai` (e.g. `venues list`).
Type `help` for the command list, `whoami` for your current env/key, `exit` to quit.

bookai (sandbox)> venues list
...
bookai (sandbox)> exit
```

`exit`/`quit`/Ctrl-D leaves the shell. This is purely a convenience layer —
every line is dispatched through the exact same commands as one-shot usage,
so anything in [Usage](#usage) below works here too.

## Usage

```bash
bookai venues list
bookai venues create                            # guided prompts -- creates a brand-new venue + your account, no key needed yet
bookai venues import <meetings page URL> -o draft.json   # read your website into a draft (writes nothing)
bookai venues import --apply draft.json          # import the edited draft: spaces, events, brand name, logo

bookai contacts list <venue_id>
bookai contacts get <venue_id> <contact_id>
bookai contacts add <venue_id> --first-name Jamie --last-name Rivera --email jamie@example.com --role Owner
bookai contacts update <venue_id> <contact_id> --first-name Jamie --last-name Rivera --role "Events Manager"
bookai contacts delete <venue_id> <contact_id>

bookai pricing list <venue_id>
bookai pricing get <venue_id> <rule_id>
bookai pricing set <venue_id> --min-group 10 --max-group 50 --min-price 20 --max-price 30
bookai pricing set <venue_id> --min-group 10 --max-group 50 --min-price 20 --max-price 30 \
    --source acme-isv --external-id acme-rule-42   # tag a rule as synced from an external system
bookai pricing update <venue_id> <rule_id> --min-group 10 --max-group 50 --min-price 18 --max-price 28
bookai pricing delete <venue_id> <rule_id>

bookai offerings list
bookai offerings show <offering_id>
bookai offerings extend <offering_id> --to 2027-03-31    # run it for longer
bookai offerings extend <offering_id> --by 3m            # ...or just push the end date out

bookai offerings slots list <offering_id>
bookai offerings slots add <offering_id> --date 2026-11-19 --time 19:00 --capacity 400
bookai offerings slots add <offering_id> --date 2026-11-19 --time 19:00 --capacity 400 \
    --duration 4 --repeat-weekly 3                       # lay out a four-week run in one go
bookai offerings slots set <offering_id> <slot_id> --time 14:00    # move it, keeping its length
bookai offerings slots set <offering_id> <slot_id> --capacity 250
bookai offerings slots rm <offering_id> <slot_id>

bookai orders list
bookai orders list --page 2

bookai conversations list                             # recent conversations and their pipeline stage
bookai conversations delete <id>                      # with its quotes and offers; asks first (--yes skips)

bookai customers list                                 # individuals and organizations, lead or booked
bookai customers list --stage lead --kind organization --sort name --order asc
bookai customers show <customer_id>                   # contact, stage, notes, orders and value
bookai customers add --email pat@acme.com --first-name Pat --notes "May trip, 40 people"
bookai customers add --email old@school.org --booked  # someone who has booked with you before
bookai customers update <customer_id> --phone "(615) 555-0100" --consent
bookai customers delete <customer_id>                 # a lead added by mistake; asks first (--yes skips)
bookai customers import leads.csv                     # CSV or Excel: previews, writes nothing
bookai customers import leads.csv --apply             # writes the valid rows, lists the rest
bookai customers export --stage booked --format xlsx  # in the columns import reads
bookai customers template                             # an empty file with the import's columns
bookai customers quote <customer_id> --offering <id> --size 40 --date 2026-11-10
bookai customers send-link <customer_id> --quote <quote_id>   # emails them a link into your booking chat
bookai customers links <customer_id>                  # sent, opened, expired or revoked
bookai customers revoke-link <customer_id> <link_id>

bookai organizations list                             # schools, companies, nonprofits that booked with you
bookai organizations list --type school --registration outdated
bookai organizations show <organization_id>           # registration answers, members, bookings

bookai forms list                                     # one registration form per organization type
bookai forms show school                              # the live questions (--draft for the draft)
bookai forms publish school                           # shows what changes and who it affects, then asks

bookai search acme                                    # every record at your venue: guests, deals, setup
bookai search where do coaches park                   # matches meaning, not just exact words
bookai search jane@school.org --type offer --type quote
bookai --json search "school trip" --limit 5          # the API's response, for scripts

bookai ask which offers need my approval               # ask in plain words; it looks it up
bookai ask -c and which of those are over 50 people    # follow up on your last conversation
bookai ask                                             # a conversation, one question per line
bookai ask --history                                   # your recent conversations
```

### Ask

`bookai ask` puts a question to the back office's Ask agent, the same one behind
the **Ask** panel in the web app. It searches and reads your venue's records
(guests and their conversations, offers, quotes, orders, offerings and showings,
policies, pricing, payment terms, contacts), shows each lookup as it runs, and
answers with numbered links to the records it used. It can only read. Nothing
it does changes anything.

- `-c` / `--continue` follows up on your last conversation in that environment,
  and `--session ID` follows up on any of them (IDs from `--history`).
- With no question it opens a conversation in the terminal: one question per
  line, and an empty line or Ctrl-D finishes. A question can also come on stdin:
  `echo "who went quiet after a quote?" | bookai ask`.
- `--json` prints the final answer with its citations and steps, for scripts.
- Quote questions containing `?` or `!` if your shell expands them.

### Showings

`offerings slots` manages the individual showings of a timed offering — the
dates and times people actually book. `--repeat-weekly` is there because a
season is usually a repeating shape rather than a list of one-off dates:
`--repeat-weekly 3` creates the showing plus three weekly copies, four in
total.

The API keeps you from doing the things that would quietly break bookings, and
the CLI reports its reason rather than swallowing it:

- a showing with seats already sold can't be removed, and its capacity can't
  drop below what's sold
- a showing outside the offering's run is refused, rather than sitting on the
  calendar looking bookable — extend the run first
- narrowing a run past showings that already exist is refused for the same
  reason: they'd stay on the calendar while quietly dropping out of
  availability

`offerings extend` is exactly that "extend the run first" step. It's a plain
update of the offering's end date — `--to` for an exact date, `--by 3m` (also
`6w`, `30d`) to push it out from wherever it currently ends.

### Import from your website

`bookai venues import` reads your venue's own meetings or events page into a
draft of its brand name, logo, event spaces (size, capacity, setups) and dated
events, and saves it as JSON. Nothing is written to your venue yet. Edit the
file, then apply it:

- `include` picks which spaces and events to import. A page listing more than
  25 spaces includes the 25 largest.
- `base_price_per_person` must be set on each included item, or passed once
  with `--default-price`. A website never states group rates, so the import
  never guesses one. Spaces are priced per guest.
- `branding.logo_url` is the logo it will use, and `logo_candidates` lists the
  others it found. Set `branding.apply` to `false` to leave your branding alone.
- Items are bookable for a year from today unless the item or
  `--valid-from`/`--valid-to` says otherwise.

Applying again updates what an earlier apply imported instead of duplicating it,
so the same draft can be rehearsed on `--env sandbox` and then applied to
production. If a site blocks automated reads, save the page from your browser
and pass it with `--html page.html` (the URL is still needed so links resolve).
Pages with no structured room data are read by an AI extractor. Check its
figures against the site before applying, and use `--no-ai` to turn it off.

`bookai venues create` is the one command that works with no API key at all —
it's how a brand-new venue's very first account gets created in the first
place (same as signing up in the browser). It asks for the venue's details
and your own name/email/password one at a time, confirms before submitting,
then tells you to run `bookai login` next.

Add `--json` before the command for machine-readable output instead of a table
(`bookai --json orders list`) — useful for piping into `jq` or scripting in CI.

## Development

```bash
pip install -e ".[dev]"
pytest
```

Tests are fully offline (HTTP is mocked via `respx`) — no server or database
required.
