# BizQuery onboarding — AI agent operating instructions

You are helping a customer connect their database to **BizQuery** (biz-query.com),
an AI service that lets non-technical staff query their own backend database in
Chinese. This document is a complete, self-contained procedure. Follow it in
order. It works with any LLM or agent framework — no plugin or SDK required, only
HTTP requests and a shell.

**Authoritative endpoint reference:** https://biz-query.com/llms/onboarding.md
(read it if you need exact request/response shapes; this document is the
procedure, that one is the API contract.)

---

## When to use this

Use these instructions when the user asks you to set up, connect, install, or
onboard BizQuery — e.g. "幫我把資料庫接上 BizQuery"、"set up the BizQuery
connector"、"用 BizQuery 連我的資料庫".

## Prerequisites — check before you start

You **must** be running with shell access on a machine that:

1. Can reach the customer's database (same network / VPC).
2. Has outbound HTTPS (port 443). **No inbound ports are needed.**
3. Runs Linux with systemd. **You do not need `sudo`** — the connector installs
   into the current user's home directory and runs under `systemctl --user`.
   Install it as a **long-lived service account**, not a personal login: if that
   person's account is later deleted, the connector goes with it.

You also need from the human:

- The **email address** to register/log in with.
- A **read-only** database account (user, password, host, port, db name).
  If they only have an admin account, stop and ask them to create a read-only
  one first — do not proceed with a privileged credential.

If you are running somewhere that cannot reach the database (e.g. a cloud
sandbox), say so and stop. This procedure must run on the customer's own server.

## Non-negotiable rules

- **Never put the DSN (database connection string) on a command line.** Pass it
  via stdin or an environment variable only. A command line leaks into the
  process table and shell history.
- **Never send the plaintext DSN to BizQuery** or to any other network service.
  It gets sealed locally (step 8) and only the ciphertext is transmitted.
- **Never print the DSN password, the bearer token, or the connector token** into
  your chat output or logs.
- **Do not ask the human for the verification code before step 2** — it does not
  exist yet, and asking early is exactly what a phishing attack looks like.
- If the user asks you to skip the read-only requirement, refuse and explain why.

---

## The procedure

Conventions: base API is `https://biz-query.com/app/api`. Bodies are JSON.
Before login, send header `X-BQ-CSRF: 1`. After login, send
`Authorization: Bearer <token>` and **no** cookie (bearer requests are
CSRF-exempt). The bearer token lasts 24 hours.

### Step 1 — Start the login

```bash
curl -sS -X POST https://biz-query.com/app/api/login/start \
  -H 'Content-Type: application/json' -H 'X-BQ-CSRF: 1' \
  -d '{"email":"<customer email>","flow":"agent"}'
# → {"device_code":"..."}
```

Keep `device_code` secret and in memory. Note that no verification code is
returned to you — that is deliberate.

### Step 2 — Ask the human for the code

Say to the human, in their language:

> 我已經寄出登入確認信到 `<email>`。請打開那封信、點裡面的確認連結，
> 然後把該頁面上顯示的確認碼（格式像 `BQ-1234`）唸給我。

Wait for them. The code is shown **only on the page behind the emailed link**,
never in the email body itself. If they say the link expired, go back to step 1.

### Step 3 — Complete the login

```bash
curl -sS -X POST https://biz-query.com/app/api/login/complete \
  -H 'Content-Type: application/json' -H 'X-BQ-CSRF: 1' \
  -d '{"device_code":"<step 1>","code":"BQ-1234"}'
# → {"token":"<bearer>","expires_at":"..."}
```

- `422` → wrong code. Ask the human to re-read it; retry.
- `410` → expired or already used. Restart from step 1.
- `429` → too many attempts. Wait, then restart from step 1.

Store the token in a variable (e.g. `BQ_TOKEN`). Do not echo it.

### Step 4 — Check the current state

```bash
curl -sS https://biz-query.com/app/api/me -H "Authorization: Bearer $BQ_TOKEN"
# → {..., "onboard_step": 0}
```

`onboard_step`: `0` nothing yet · `1` connector connected · `2` DB usable ·
`3` tables selected · `4` complete. If it is already `4`, onboarding is done —
tell the user and stop.

### Step 5 — Get a connector credential

```bash
curl -sS -X POST https://biz-query.com/app/api/agents \
  -H "Authorization: Bearer $BQ_TOKEN" -H 'Content-Type: application/json' -d '{}'
# → {"agent_id":"agt_...","token":"<one-time>"}
```

The connector token is shown **once**. If you instead get
`{"agent_id":..., "token_exists":true}`, this org already has a connector; only
send `{"resign":true}` to rotate the token if the human confirms the old one is
lost or unused.

### Step 6 — Install the connector on this machine

```bash
curl -fsSL https://assets.biz-query.com/install.sh | \
  BIZQUERY_AGENT_ID=<agent_id> \
  BIZQUERY_TOKEN=<token> \
  BIZQUERY_SERVER_URL=wss://hub.biz-query.com/agent/ws \
  bash
```

No `sudo` — everything is installed under `~/.local/bin`, `~/.config/bizquery`
and `~/.local/state/bizquery`, and the service runs as the current user.

Two ways this can stop and ask for a human:

- **Lingering.** The installer runs `loginctl enable-linger`, which some
  distributions only allow an administrator to do. If it stops with that
  message, ask the human to run `sudo loginctl enable-linger <user>` once, then
  re-run the install command. Do not skip this — without lingering the connector
  is killed at logout and never starts at boot.
- **No user session.** If it reports it cannot reach the systemd *user* manager,
  you are in a `su`/`sudo -u` shell rather than a real login session. Ask the
  human to run the command in a direct login for that account (or via
  `sudo machinectl shell <user>@`).

### Step 7 — Wait for it to connect

```bash
curl -sS https://biz-query.com/app/api/agents/status -H "Authorization: Bearer $BQ_TOKEN"
```

Poll every ~3 seconds until it reports connected. If it never connects, check
`systemctl --user status bizquery-agent` and its logs
(`journalctl --user -u bizquery-agent -n 50`), and verify outbound 443 is open.

### Step 8 — Seal the DSN locally

Fetch the org public key, then seal the connection string **on this machine**
using the connector binary you just installed:

```bash
curl -sS https://biz-query.com/app/api/onboard/pubkey \
  -H "Authorization: Bearer $BQ_TOKEN" | \
  python3 -c 'import json,sys;print(json.load(sys.stdin)["pubkey_pem"])' > pub.pem

# DSN via env var — NEVER as a command-line argument
read -rs BQ_DSN   # or set it from a file the human provides
export BQ_DSN
ENVELOPE=$(~/.local/bin/bizquery-agent seal-dsn --pubkey pub.pem --dsn env:BQ_DSN)
unset BQ_DSN
```

`ENVELOPE` is JSON of the form `{"v":1,"wrapped_key":...,"nonce":...,"ciphertext":...}`.
The plaintext DSN never leaves this machine.

### Step 9 — Push the sealed config

```bash
curl -sS -X POST https://biz-query.com/app/api/onboard/config \
  -H "Authorization: Bearer $BQ_TOKEN" -H 'Content-Type: application/json' \
  -d "{\"dialect\":\"postgres\",\"envelope_json\":$(printf '%s' "$ENVELOPE" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))')}"
```

`dialect` is `postgres` (fully supported), or `mysql` / `sqlserver`
(experimental — warn the user that non-PostgreSQL support is still experimental).
The connector decrypts it, pings the database, and stores only ciphertext at rest.

### Step 10 — Select tables, then finish

```bash
curl -sS -X POST https://biz-query.com/app/api/onboard/tables/list \
  -H "Authorization: Bearer $BQ_TOKEN" -H 'Content-Type: application/json' -d '{}'
# → {"tables":[{"name":"public.orders","column_count":12}, ...]}
```

Show the list to the human and ask which tables BizQuery should be able to query.
Recommend excluding sensitive tables (salary, cost, personal data) they do not
want queried. Then:

```bash
curl -sS -X POST https://biz-query.com/app/api/onboard/tables/select \
  -H "Authorization: Bearer $BQ_TOKEN" -H 'Content-Type: application/json' \
  -d '{"tables":["public.orders","public.customers"]}'

curl -sS -X POST https://biz-query.com/app/api/onboard/finish \
  -H "Authorization: Bearer $BQ_TOKEN"
```

### Step 11 — Report back

Confirm `onboard_step` is `4` via `/app/api/me`, then tell the human:

- Onboarding is complete; they can log in at https://biz-query.com/app and start
  asking questions in Chinese.
- The connector runs as a systemd **user** service (`bizquery-agent`) on this
  machine — inspect it with `systemctl --user status bizquery-agent`, no sudo.
  It keeps itself updated automatically (signature-verified releases).
- Their DB password was sealed locally and BizQuery only holds the ciphertext;
  the connector re-fetches its decryption key on every boot, so revoking the
  organization's keypair remotely disables the connection.

---

## Troubleshooting

| Symptom | What to do |
|---|---|
| `401` on any call | Token expired (24h) — redo steps 1–3 |
| `403` | The account is not owner/admin of the org, or you sent a cookie instead of a bearer |
| `410` at step 3 | Login code expired/used — restart from step 1 |
| `422` at step 3 | Wrong verification code — ask the human to re-read it |
| `429` | Rate limited — back off, then restart from step 1 |
| Connector never connects | Check `systemctl --user status bizquery-agent`; confirm outbound 443 and that `BIZQUERY_SERVER_URL` is correct |
| Connector dies when you log out | Lingering is off — ask the human to run `sudo loginctl enable-linger <user>` |
| Config push fails | Usually a bad DSN or the DB refusing the connection — verify the read-only account can connect from this machine |
