@headband/cli 0.2.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +566 -70
- package/dist/index.js +5250 -1698
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# @headband/cli
|
|
2
2
|
|
|
3
|
-
Command-line tools for [Headband](https://headband.dev): project setup,
|
|
4
|
-
API
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Command-line tools for [Headband](https://headband.dev): project setup, login,
|
|
4
|
+
branches, API keys, indexes, documents, search, tasks, dedicated engines, schema
|
|
5
|
+
migrations and Sync. Every command works for a person at a terminal and for a
|
|
6
|
+
script or coding agent with no terminal at all.
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
9
|
npx @headband/cli --help
|
|
@@ -11,85 +11,349 @@ npx @headband/cli init
|
|
|
11
11
|
npx @headband/cli login
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## Contents
|
|
15
|
+
|
|
16
|
+
- [Global options](#global-options)
|
|
17
|
+
- [Authentication](#authentication)
|
|
18
|
+
- [Scripts and agents](#scripts-and-agents)
|
|
19
|
+
- [Exit codes](#exit-codes)
|
|
20
|
+
- [Command reference](#command-reference)
|
|
21
|
+
- [What `init` creates](#what-init-creates)
|
|
22
|
+
|
|
23
|
+
## Global options
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
--token <key> API key for this call. Wins over HEADBAND_API_KEY and stored logins.
|
|
27
|
+
--profile <name> Stored login to use (else HEADBAND_PROFILE, else the current profile)
|
|
28
|
+
--url <url> Headband URL (else HEADBAND_URL, else the profile's, else https://headband.dev)
|
|
29
|
+
--branch <name> Work on this branch for one call, in the project from --project <id>,
|
|
30
|
+
else the checkout's (see Branches). A branch id (br_...) is sent as it is.
|
|
31
|
+
Without --branch or a checkout, calls reach the primary branches.
|
|
32
|
+
--json Print one JSON document on stdout; errors print one JSON object on stderr
|
|
33
|
+
--yes, -y Answer yes to confirmations (required when stdin is not a terminal)
|
|
34
|
+
--help, -h Show help for the CLI or a command
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Authentication
|
|
38
|
+
|
|
39
|
+
### Interactive login
|
|
15
40
|
|
|
16
41
|
```bash
|
|
17
|
-
--url <url>
|
|
18
|
-
--
|
|
19
|
-
|
|
20
|
-
--
|
|
42
|
+
headband login [--url <url>] [--profile <name>] [--json]
|
|
43
|
+
headband logout [--profile <name>] [--yes] [--no-revoke] [--json]
|
|
44
|
+
headband whoami [--json]
|
|
45
|
+
headband profiles list [--json]
|
|
46
|
+
headband profiles use <name>
|
|
21
47
|
```
|
|
22
48
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
49
|
+
`login` runs Headband's device flow: it prints a code and a link, opens your
|
|
50
|
+
browser, and waits while you approve it in the dashboard. It saves the key it
|
|
51
|
+
receives as a named profile and makes that profile current. The name defaults
|
|
52
|
+
to `default` for headband.dev and to the host name for any other URL, such as
|
|
53
|
+
`localhost-3000`. The full key is never printed; output shows only its prefix.
|
|
27
54
|
|
|
28
|
-
|
|
55
|
+
Log in to several Headband URLs or organizations and switch between them:
|
|
29
56
|
|
|
30
57
|
```bash
|
|
31
|
-
headband
|
|
58
|
+
headband login --profile prod
|
|
59
|
+
headband login --url http://localhost:3000 --profile local
|
|
60
|
+
headband profiles list
|
|
61
|
+
headband profiles use prod
|
|
62
|
+
headband indexes list --profile local # one command, without switching
|
|
32
63
|
```
|
|
33
64
|
|
|
34
|
-
|
|
65
|
+
`logout` revokes the profile's key on the server, then removes it from this
|
|
66
|
+
machine. It asks first at a terminal. `--yes` revokes without asking.
|
|
67
|
+
`--no-revoke` removes the local copy and leaves the key working. If the key is
|
|
68
|
+
already gone on the server, logout still succeeds; any other failure keeps the
|
|
69
|
+
local login so you can retry.
|
|
70
|
+
|
|
71
|
+
Credentials live in `~/.config/headband/credentials.json`, or
|
|
72
|
+
`$XDG_CONFIG_HOME/headband/credentials.json`, with file mode `0600`. A file
|
|
73
|
+
written by CLI 0.2.0 or earlier (profiles keyed by URL) still works: its
|
|
74
|
+
profiles get names on first read, and the file is saved in the new format the
|
|
75
|
+
next time the CLI writes it.
|
|
76
|
+
|
|
77
|
+
### Tokens and environment variables
|
|
78
|
+
|
|
79
|
+
A script, CI job or agent does not need to log in. Give it a key:
|
|
35
80
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
6. Create `headband.config.ts` with your settings
|
|
42
|
-
7. Create an example `<HeadbandSearch />` component
|
|
81
|
+
| Variable | Flag | Meaning |
|
|
82
|
+
| ------------------ | ----------- | ------------------------------------------------------ |
|
|
83
|
+
| `HEADBAND_API_KEY` | `--token` | The API key to send. Used instead of any stored login. |
|
|
84
|
+
| `HEADBAND_URL` | `--url` | The Headband URL. |
|
|
85
|
+
| `HEADBAND_PROFILE` | `--profile` | Which stored login to use. |
|
|
43
86
|
|
|
44
|
-
|
|
87
|
+
Precedence, highest first:
|
|
88
|
+
|
|
89
|
+
- **Key:** `--token`, then `HEADBAND_API_KEY`, then the stored profile.
|
|
90
|
+
- **URL:** `--url`, then `HEADBAND_URL`, then the profile's URL, then
|
|
91
|
+
`https://headband.dev`. With a token, the profile's URL counts only when you
|
|
92
|
+
name the profile with `--profile`.
|
|
93
|
+
- **Profile:** `--profile`, then `HEADBAND_PROFILE`, then the current profile,
|
|
94
|
+
then the only stored profile when there is one.
|
|
95
|
+
|
|
96
|
+
A stored key is only ever sent to the URL it was issued for. `--url` with no
|
|
97
|
+
`--profile` picks the stored profile for that URL; `--url` with a `--profile`
|
|
98
|
+
for a different URL is a usage error. A `--profile` name that does not exist is
|
|
99
|
+
an error, never a silent fallback.
|
|
45
100
|
|
|
46
101
|
```bash
|
|
47
|
-
|
|
48
|
-
|
|
102
|
+
export HEADBAND_API_KEY=hb_adm_...
|
|
103
|
+
export HEADBAND_URL=https://headband.dev
|
|
104
|
+
headband indexes list --json
|
|
49
105
|
```
|
|
50
106
|
|
|
51
|
-
|
|
107
|
+
Which key to use: an admin key from `headband keys create <name> --type admin`
|
|
108
|
+
covers indexes, documents, search, engines and Sync. Managing keys, projects,
|
|
109
|
+
branches and schema state needs a key minted by `headband login`, because only
|
|
110
|
+
those carry `keys.manage`, `projects.manage`, `branches.manage` and
|
|
111
|
+
`schema.manage`.
|
|
112
|
+
|
|
113
|
+
## Scripts and agents
|
|
114
|
+
|
|
115
|
+
The CLI never waits for input it cannot get.
|
|
116
|
+
|
|
117
|
+
- **No hanging prompts.** A command that would ask for confirmation fails at
|
|
118
|
+
once with exit code `2` and code `confirmation_required` when stdin is not a
|
|
119
|
+
terminal. Pass `--yes` to confirm. `headband init` does the same without
|
|
120
|
+
`--yes`.
|
|
121
|
+
- **One JSON document.** With `--json`, stdout carries exactly one JSON
|
|
122
|
+
document and progress lines go to stderr, so `headband ... --json | jq` is
|
|
123
|
+
safe. `docs export` is the exception by design: its output is the documents.
|
|
124
|
+
- **JSON errors.** With `--json`, a failure prints one line to stderr:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{ "error": "Index not found", "code": "index_not_found", "status": 404, "exitCode": 5 }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`code` is the API's error code when there is one, else a CLI code such as
|
|
131
|
+
`usage_error`, `not_authenticated`, `confirmation_required` or
|
|
132
|
+
`network_error`. `status` is the HTTP status, or `null` when no request was
|
|
133
|
+
made.
|
|
134
|
+
|
|
135
|
+
- **Rate limits.** A `429` whose `Retry-After` is 10 seconds or less is
|
|
136
|
+
retried up to twice after that wait (CLI 0.6.0). If it still fails, the
|
|
137
|
+
command exits `7` and the message keeps the server's hint, such as
|
|
138
|
+
"Retry after 1s.". A write where some documents were rejected names the
|
|
139
|
+
first rejected document and the engine's reason.
|
|
140
|
+
- **Branch on exit codes**, not on message text. See the table below.
|
|
141
|
+
|
|
142
|
+
## Exit codes
|
|
143
|
+
|
|
144
|
+
| Code | Meaning | Typical cause |
|
|
145
|
+
| ----- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
146
|
+
| `0` | Success | |
|
|
147
|
+
| `1` | Error | Anything not listed below; also `schema diff` when changes are pending, `sync reconcile` when drift is left |
|
|
148
|
+
| `2` | Usage | Unknown command or option, missing or malformed value, HTTP 400/413/422, confirmation needed without a terminal |
|
|
149
|
+
| `3` | Authentication | No key found, a `--profile` that does not exist, HTTP 401 |
|
|
150
|
+
| `4` | Permission | HTTP 403, or 402 (the plan does not allow it) |
|
|
151
|
+
| `5` | Not found | HTTP 404 or 410, `profiles use` with an unknown name |
|
|
152
|
+
| `6` | Conflict | HTTP 409, such as deleting a project that still has indexes |
|
|
153
|
+
| `7` | Rate limited | HTTP 429 |
|
|
154
|
+
| `8` | Server or engine | HTTP 5xx, or the URL could not be reached |
|
|
155
|
+
| `130` | Interrupted | Ctrl+C |
|
|
156
|
+
|
|
157
|
+
## Command reference
|
|
158
|
+
|
|
159
|
+
### Setup
|
|
52
160
|
|
|
53
161
|
```bash
|
|
54
|
-
headband
|
|
55
|
-
headband logout [--url <url>] [--yes]
|
|
56
|
-
headband whoami [--url <url>] [--json]
|
|
162
|
+
headband init [--yes]
|
|
57
163
|
```
|
|
58
164
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
165
|
+
Asks for your Headband URL, a browser-safe search key (`hb_src_...` only) and
|
|
166
|
+
a default index, installs `@headband/react`, and writes `headband.config.ts`
|
|
167
|
+
plus an example `<HeadbandSearch />` component. `--yes` skips the questions and
|
|
168
|
+
uses defaults. Running `headband` with no command runs `init`.
|
|
63
169
|
|
|
64
170
|
### Projects
|
|
65
171
|
|
|
66
172
|
```bash
|
|
67
173
|
headband projects list [--json]
|
|
68
|
-
headband projects create <name> --engine <meilisearch|typesense|elasticsearch>
|
|
174
|
+
headband projects create <name> --engine <meilisearch|typesense|elasticsearch|visor> [--json]
|
|
175
|
+
headband projects delete <id> [--yes] [--json]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`projects delete` refuses a project that still has indexes (exit `6`, code
|
|
179
|
+
`project_not_empty`). Delete its indexes first. Deleting a project
|
|
180
|
+
also deletes the API keys scoped to it.
|
|
181
|
+
|
|
182
|
+
### Branches
|
|
183
|
+
|
|
184
|
+
A branch is one environment of a project: its primary branch `main`
|
|
185
|
+
(production), plus others such as `staging` or `preview/pr-123`. Each branch
|
|
186
|
+
has its own indexes and its own keys.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
headband branch list [--project <id>] [--json]
|
|
190
|
+
headband branch create <name> --empty [--parent <name>] [--expires-in <2h|7d>] [--project <id>] [--json]
|
|
191
|
+
headband branch delete <name> [--project <id>] [--yes] [--json]
|
|
192
|
+
headband branch protect <name> [--project <id>] [--json]
|
|
193
|
+
headband branch unprotect <name> [--project <id>] [--json]
|
|
194
|
+
headband branch expire <name> (--in <2h|7d> | --at <iso> | --clear) [--project <id>] [--json]
|
|
195
|
+
headband branch checkout <name> [--project <id>] [--json]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- The project comes from `--project`, else the directory's checkout (below).
|
|
199
|
+
With neither, the command is a usage error.
|
|
200
|
+
- `create` makes an empty branch. Without `--empty` it fails with exit `6`
|
|
201
|
+
(code `fork_not_available`): copying a parent's data into a new branch
|
|
202
|
+
arrives with bucket storage. Until branches other than main open on the
|
|
203
|
+
server, every `create` fails with exit `6` (code `branches_not_open`),
|
|
204
|
+
`--empty` or not. `--parent` records the branch it comes from
|
|
205
|
+
(default: the primary branch). `--expires-in` deletes the branch on its own
|
|
206
|
+
after that long.
|
|
207
|
+
- `delete` removes the branch with its indexes, keys and engines. It asks
|
|
208
|
+
first. The primary branch, a protected branch, a branch with live child
|
|
209
|
+
branches and a branch with Sync-owned indexes are refused (exit `6`).
|
|
210
|
+
- `protect` blocks delete and expiry; `unprotect` lifts it. The primary
|
|
211
|
+
branch (`main`) serves production and is always protected: `unprotect`,
|
|
212
|
+
`expire` and `delete` on it are refused (exit `6`, code `branch_is_primary`).
|
|
213
|
+
- `expire` takes exactly one of `--in <duration>`, `--at <ISO time>` (in the
|
|
214
|
+
future) or `--clear`.
|
|
215
|
+
- Durations are a number and a unit: `s`, `m`, `h`, `d` or `w`, such as `30m`,
|
|
216
|
+
`2h` or `7d`.
|
|
217
|
+
- Creating, deleting, protecting and expiring need the key from
|
|
218
|
+
`headband login`. `branch list` works with any admin key that reaches the
|
|
219
|
+
project.
|
|
220
|
+
|
|
221
|
+
**Working on a branch.** `checkout` makes a directory work on one branch:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
headband branch checkout staging --project <project id>
|
|
225
|
+
headband indexes list # staging's indexes
|
|
226
|
+
headband docs push products.json --index products
|
|
227
|
+
headband search products shoes # searches staging
|
|
69
228
|
```
|
|
70
229
|
|
|
230
|
+
`checkout` writes `.headband/project.json`: `{ "orgId", "projectId", "branch" }`,
|
|
231
|
+
with file mode `0600`. It holds no key. Every later command in that directory,
|
|
232
|
+
or in any directory below it, sends `Headband-Branch: <project id>/<branch>`
|
|
233
|
+
with your login key. That key reaches every branch of your organization, so no
|
|
234
|
+
key is minted. Checking out `main` writes the file too. `checkout` needs a
|
|
235
|
+
stored login (`headband login`), because the file records your login's
|
|
236
|
+
organization. `--token` and `HEADBAND_API_KEY` take the place of a stored
|
|
237
|
+
login, so unset them to check out.
|
|
238
|
+
|
|
239
|
+
Like git, the CLI uses the nearest `.headband/project.json` in the working
|
|
240
|
+
directory or a parent, up to your home directory (or the filesystem root,
|
|
241
|
+
outside it). `checkout` rewrites that nearest file, or writes one in the
|
|
242
|
+
working directory when there is none. As in git, a file another user owns, or
|
|
243
|
+
one in a `.headband` directory another user owns, is an error: every command
|
|
244
|
+
that would send `Headband-Branch` stops and names the file, and the search goes
|
|
245
|
+
no further. So a file planted in a shared directory such as `/tmp` never steers
|
|
246
|
+
your commands. A symlink is judged by its own owner, as in git, so another
|
|
247
|
+
user's link to your `.headband` is theirs. `checkout` then writes your own file
|
|
248
|
+
in the working directory, and never rewrites the other user's. When the other
|
|
249
|
+
user's file is in the working directory itself, remove it before you check out
|
|
250
|
+
there.
|
|
251
|
+
|
|
252
|
+
A checkout never falls back to production silently. Every command that would
|
|
253
|
+
send `Headband-Branch` stops with an error when the nearest
|
|
254
|
+
`.headband/project.json` cannot be read, holds a value that is not a valid
|
|
255
|
+
project id, branch name or branch id, or was written for another organization
|
|
256
|
+
than your stored login's. Run `headband branch checkout <name>` again to rewrite
|
|
257
|
+
the file, or delete it to work on production. A token (`--token`,
|
|
258
|
+
`HEADBAND_API_KEY`) names no organization, so the organization check applies
|
|
259
|
+
only to stored logins.
|
|
260
|
+
|
|
261
|
+
For one command, `--project <id> --branch <name>` picks another branch, and
|
|
262
|
+
`--branch <name>` alone takes the checkout's project. A branch id
|
|
263
|
+
(`--branch br_...`) names its own project, so it is sent as it is and needs no
|
|
264
|
+
`--project`. With no checkout and no `--branch`, commands reach each project's
|
|
265
|
+
primary branch, as before. A branch name with no project at all is a usage
|
|
266
|
+
error, so a name is never matched across projects by accident.
|
|
267
|
+
|
|
268
|
+
`--project` without `--branch` is a usage error, because these commands send no
|
|
269
|
+
project of their own and would ignore it. The exception is `keys create`, which
|
|
270
|
+
sends the project in its body (see Keys).
|
|
271
|
+
|
|
272
|
+
Coming with bucket storage: `branch create` without `--empty` (a copy of its
|
|
273
|
+
parent), `reset`, `rollback`, `restore` and `set-primary`.
|
|
274
|
+
|
|
71
275
|
### Keys
|
|
72
276
|
|
|
73
277
|
```bash
|
|
74
278
|
headband keys list [--json]
|
|
75
|
-
headband keys create <name> --type <admin|search> [--project <id>] [--
|
|
76
|
-
|
|
279
|
+
headband keys create <name> --type <admin|search> [--project <id>] [--branch <name>]
|
|
280
|
+
[--instances <id,id>] [--permissions <a,b,...>] [--expires <30d|ISO date>] [--json]
|
|
281
|
+
headband keys revoke <id> [--yes] [--json]
|
|
77
282
|
```
|
|
78
283
|
|
|
284
|
+
A key belongs to one branch and reaches only that branch's indexes, for an app
|
|
285
|
+
such as a preview deployment. `keys create` picks the branch in this order:
|
|
286
|
+
|
|
287
|
+
1. `--branch <name>`, in the project from `--project`, else the checkout's. A
|
|
288
|
+
branch id (`--branch br_...`) needs no project.
|
|
289
|
+
2. The checked-out branch, unless `--project` names another project.
|
|
290
|
+
3. The primary branch of `--project`.
|
|
291
|
+
|
|
292
|
+
With none of these, `--type admin` mints an org key, which reaches every branch
|
|
293
|
+
of every project, and `--type search` is refused (exit `2`, code
|
|
294
|
+
`branch_required`), because search keys ship in browsers. A key on the primary
|
|
295
|
+
branch is a live key (`hb_adm_live_`, `hb_src_live_`); a key on any other
|
|
296
|
+
branch is a test key (`hb_adm_test_`, `hb_src_test_`). `keys create` and
|
|
297
|
+
`keys list` show each key's branch and environment.
|
|
298
|
+
|
|
79
299
|
`keys create` is the only command that prints a raw API key. Store it when it is
|
|
80
|
-
shown
|
|
300
|
+
shown: Headband returns raw keys only once.
|
|
301
|
+
|
|
302
|
+
`--instances <id,id>` also limits the key to those dedicated engines. It takes
|
|
303
|
+
one comma-separated list of 1 to 50 ids (from `headband instances list`);
|
|
304
|
+
repeating the flag does not add engines, only the last one counts. An org key then reaches only them, on every
|
|
305
|
+
branch. A branch key reaches its branch's engines among them, and every id must
|
|
306
|
+
sit on that branch. Any other index answers as if it did not exist, and
|
|
307
|
+
endpoints that are not about those engines refuse the key. The list is fixed:
|
|
308
|
+
to change it, create a new key and revoke the old one. `keys list` and
|
|
309
|
+
`keys create` show each key's engines: `all` when it is not limited, the engine
|
|
310
|
+
ids when it is, `none` when its list reaches nothing, and `unknown` when the
|
|
311
|
+
API did not report them (never shown as `all`).
|
|
312
|
+
|
|
313
|
+
`--permissions` limits the new key to the permissions you list. You can grant
|
|
314
|
+
only permissions your own key holds, and only ones that fit the key type:
|
|
315
|
+
|
|
316
|
+
| Type | Grantable permissions |
|
|
317
|
+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
318
|
+
| `search` | `search`, `analytics.write` |
|
|
319
|
+
| `admin` | `search`, `documents.read`, `documents.add`, `documents.delete`, `indexes.create`, `indexes.delete`, `settings.update`, `analytics.write`, `connectors.write`, `instances.manage`, `sync.manage` |
|
|
320
|
+
|
|
321
|
+
Anything else is refused with exit `4` and the names it refused. Without
|
|
322
|
+
`--permissions`, the key gets the type's defaults, cut down to what your key
|
|
323
|
+
holds. `keys.manage`, `projects.manage`, `branches.manage` and
|
|
324
|
+
`schema.manage` are never grantable here; only `headband login` mints them.
|
|
325
|
+
|
|
326
|
+
`--expires` sets when the key stops working: a duration from now (`12h`,
|
|
327
|
+
`30d`, `2w`, `1y`) or an ISO date (`2027-01-31`). It must be in the future and
|
|
328
|
+
at most one year away. Without it, the key does not expire.
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
headband keys create ci-reader --type admin --permissions search,documents.read --expires 30d --json
|
|
332
|
+
```
|
|
81
333
|
|
|
82
334
|
### Indexes
|
|
83
335
|
|
|
84
336
|
```bash
|
|
85
337
|
headband indexes list [--json]
|
|
86
338
|
headband indexes create <uid> [--primary-key <pk>] [--engine <engine>] [--instance <id>] [--json]
|
|
87
|
-
headband indexes delete <uid> [--yes]
|
|
88
|
-
headband indexes settings get <uid>
|
|
89
|
-
headband indexes settings set <uid> --file <settings.json>
|
|
90
|
-
headband indexes stats <uid>
|
|
339
|
+
headband indexes delete <uid> [--yes] [--json]
|
|
340
|
+
headband indexes settings get <uid>
|
|
341
|
+
headband indexes settings set <uid> --file <settings.json>
|
|
342
|
+
headband indexes stats <uid>
|
|
91
343
|
```
|
|
92
344
|
|
|
345
|
+
`index` is an alias of `indexes` for every command here: `headband index list`
|
|
346
|
+
and `headband index switch-engine <uid>` work the same.
|
|
347
|
+
|
|
348
|
+
`settings get`, `settings set` and `stats` always print JSON.
|
|
349
|
+
|
|
350
|
+
`indexes create` prints the project and branch the index went to (CLI 0.6.0).
|
|
351
|
+
With the key from `headband login` and no `--project`, `--branch` or
|
|
352
|
+
checkout, that is the org's Default project, main branch.
|
|
353
|
+
|
|
354
|
+
Deleting an index that a `schema apply` reindex moved to `<uid>__v2` also
|
|
355
|
+
removes that build's own index row, so nothing is left behind.
|
|
356
|
+
|
|
93
357
|
`--instance <id>` creates the index on one of your dedicated engines (ids from
|
|
94
358
|
`headband instances list`). The index takes that engine's type, so `--engine` is
|
|
95
359
|
optional; if you pass it, it must match.
|
|
@@ -98,58 +362,289 @@ On the engine, the index has a longer name (`engineIndexUid`, such as
|
|
|
98
362
|
`hbo_<org>_products`). `indexes create` and `indexes list` show it. Use that name,
|
|
99
363
|
not the uid, with the engine's own client on the instance endpoint.
|
|
100
364
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Dedicated engines are private Meilisearch, Elasticsearch or Typesense servers for
|
|
104
|
-
your organization, reachable with each engine's own client. They are rolling out
|
|
105
|
-
gradually. Until your organization has access, these commands say so.
|
|
365
|
+
#### Change an index's engine
|
|
106
366
|
|
|
107
367
|
```bash
|
|
108
|
-
headband
|
|
109
|
-
headband
|
|
110
|
-
headband instances show <id> [--json]
|
|
111
|
-
headband instances wake <id> [--json]
|
|
112
|
-
headband instances park <id> [--json]
|
|
113
|
-
headband instances delete <id> [--yes]
|
|
114
|
-
headband instances connect <id> [--client <node|curl>]
|
|
368
|
+
headband indexes switch-engine <uid> --engine <engine> [--instance <id>] [--accept-drops] [--declare-key] [--dry-run] [--wait] [--json]
|
|
369
|
+
headband indexes switch-engine <uid> --status [--json]
|
|
115
370
|
```
|
|
116
371
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
372
|
+
Moves the index to another engine. The
|
|
373
|
+
uid, keys and URLs stay the same, and searches keep working until the switch.
|
|
374
|
+
Writes are held for about a minute at the end (they answer 503 with `Retry-After`).
|
|
375
|
+
|
|
376
|
+
- `--dry-run` shows how many documents would be copied, where the new index goes,
|
|
377
|
+
and which settings would not carry over. It changes nothing.
|
|
378
|
+
- A setting the new engine cannot apply stops the switch with exit code 6 and a
|
|
379
|
+
list of what would not carry over, with a reason for each. `--accept-drops`
|
|
380
|
+
switches anyway.
|
|
381
|
+
- `--wait` follows the switch until it ends and prints each phase. Without it the
|
|
382
|
+
command returns once the switch is queued; `--status` shows how it went.
|
|
383
|
+
- `--declare-key` is for a Visor index over 1000 documents whose primary key is
|
|
384
|
+
not filterable and sortable: the key is declared while the index is read, which
|
|
385
|
+
rebuilds it.
|
|
124
386
|
|
|
125
387
|
### Documents
|
|
126
388
|
|
|
127
389
|
```bash
|
|
128
|
-
headband docs push <file.json|file.jsonl> --index <uid>
|
|
129
|
-
headband docs
|
|
390
|
+
headband docs push <file.json|file.jsonl> --index <uid> [--json]
|
|
391
|
+
headband docs export --index <uid> [--out <file>] [--format <jsonl|json>] [--batch <n>] [--json]
|
|
392
|
+
headband docs delete <docId>... --index <uid> [--json]
|
|
130
393
|
```
|
|
131
394
|
|
|
132
|
-
`docs
|
|
395
|
+
`docs delete` removes one or more documents by id, in one call to
|
|
396
|
+
`DELETE /v1/documents?index=<uid>`.
|
|
397
|
+
|
|
398
|
+
`docs push` accepts a JSON array or a JSONL file and sends documents to
|
|
133
399
|
`/v1/bulk` in batches of 1000.
|
|
134
400
|
|
|
401
|
+
`docs export` walks `GET /v1/documents` by cursor and writes every document in
|
|
402
|
+
the index, one page in memory at a time:
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
headband docs export --index products > products.jsonl
|
|
406
|
+
headband docs export --index products --out products.json
|
|
407
|
+
headband docs export --index products | head -5
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
- Without `--out`, documents stream to stdout and nothing else is printed
|
|
411
|
+
there. Progress for `--out` goes to stderr.
|
|
412
|
+
- `--format` is `jsonl` (one document per line, the default) or `json` (one
|
|
413
|
+
array). With `--out`, a `.json` file name picks `json`.
|
|
414
|
+
- `--out` writes to `<file>.partial` and renames it when the export finishes,
|
|
415
|
+
so a failed export never leaves a truncated file under the real name. If the
|
|
416
|
+
export stops on an error after some documents were written (CLI 0.6.0), they
|
|
417
|
+
stay in `<file>.partial`, as a whole JSONL file or a closed JSON array, and
|
|
418
|
+
stderr says how many. `--json` with `--out` prints a summary:
|
|
419
|
+
`{index, out, format, exported}`.
|
|
420
|
+
- `--batch` is the page size. The default is 1000, or 250 for an index on
|
|
421
|
+
Typesense, which refuses larger pages. The server may answer fewer a page.
|
|
422
|
+
- The cursor reaches every document on every engine: Visor past its first
|
|
423
|
+
1000 and Elasticsearch past its first 10,000. A server without cursors is
|
|
424
|
+
paged by offset, as before CLI 0.5.0.
|
|
425
|
+
- A Visor index created before 2026-10-21 with more than 1000 documents may
|
|
426
|
+
not have its primary key declared filterable and sortable. The server then
|
|
427
|
+
refuses the walk (exit `2`, code `feature_not_supported`), and the CLI prints
|
|
428
|
+
the fix: `headband indexes settings get <uid> > settings.json`, add the key
|
|
429
|
+
to the lists it names, `headband indexes settings set <uid> --file
|
|
430
|
+
settings.json` (Visor rebuilds the index), then export again. New Visor
|
|
431
|
+
indexes declare the key on their own.
|
|
432
|
+
- Export needs an admin key with `documents.read`.
|
|
433
|
+
|
|
135
434
|
### Search
|
|
136
435
|
|
|
137
436
|
```bash
|
|
138
|
-
headband search <index> <query> [--limit N] [--filter <expr>] [--json]
|
|
437
|
+
headband search <index> [<query>] [--limit N] [--filter <expr>] [--mode <instant|retrieval>] [--json]
|
|
139
438
|
```
|
|
140
439
|
|
|
440
|
+
Without a query, or with `""`, search browses the index (CLI 0.6.0): every
|
|
441
|
+
document matches, and `--filter` and `--limit` pick what comes back.
|
|
442
|
+
|
|
443
|
+
`--mode` picks Visor's ranking: `instant` (the default, typo-tolerant,
|
|
444
|
+
as-you-type) or `retrieval` (BM25F, for longer queries). Indexes on the other
|
|
445
|
+
engines ignore it.
|
|
446
|
+
|
|
141
447
|
Plain output shows a compact table. `--json` prints the raw API response.
|
|
142
448
|
|
|
143
449
|
### Tasks
|
|
144
450
|
|
|
145
451
|
```bash
|
|
146
|
-
headband tasks list [--json]
|
|
147
|
-
headband tasks get <id> [--json]
|
|
452
|
+
headband tasks list [--index <uid>] [--limit <n>] [--json]
|
|
453
|
+
headband tasks get <id> [--index <uid>] [--json]
|
|
148
454
|
```
|
|
149
455
|
|
|
150
|
-
|
|
456
|
+
`tasks list` prints a table, or the raw response with `--json`. `tasks get`
|
|
457
|
+
prints JSON.
|
|
458
|
+
|
|
459
|
+
Tasks live on the engine that holds the index. The key `headband login`
|
|
460
|
+
creates is org-wide, so it must name the index with `--index`. So must a key
|
|
461
|
+
of a branch other than production. A project key of the production branch may
|
|
462
|
+
leave it out.
|
|
463
|
+
|
|
464
|
+
### Engines
|
|
465
|
+
|
|
466
|
+
Dedicated engines are private Meilisearch, Elasticsearch, Typesense or Visor
|
|
467
|
+
servers for your organization, reachable with each engine's own client (Visor over
|
|
468
|
+
its HTTP API). They are rolling out gradually. Until your organization has access,
|
|
469
|
+
these commands say so. Visor is offered in a region where a dedicated engine host
|
|
470
|
+
runs it; elsewhere `instances create visor` answers `engine_not_available`.
|
|
471
|
+
|
|
472
|
+
```bash
|
|
473
|
+
headband instances list [--json]
|
|
474
|
+
headband instances create <meilisearch|elasticsearch|typesense|visor> [--name <name>]
|
|
475
|
+
[--lane <serverless|always_on>] [--memory <512m|1g|...>]
|
|
476
|
+
[--region <region>] [--idle-timeout <seconds>] [--json]
|
|
477
|
+
headband instances show <id> [--json]
|
|
478
|
+
headband instances update <id> [--name <name>] [--lane <serverless|always_on>]
|
|
479
|
+
[--memory <512m|1g|...>] [--idle-timeout <seconds>] [--json]
|
|
480
|
+
headband instances wake <id> [--json]
|
|
481
|
+
headband instances park <id> [--json]
|
|
482
|
+
headband instances wakes <id> [--limit <n>] [--json]
|
|
483
|
+
headband instances delete <id> [--yes] [--json]
|
|
484
|
+
headband instances connect <id> [--client <node|curl>] [--json]
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
`update` changes only the settings you pass; `--size` is an alias for
|
|
488
|
+
`--memory`. The engine and region cannot change. A resize applies in the
|
|
489
|
+
background: `instances show` reports the state as "applying changes" until it
|
|
490
|
+
is done. Always-on engines need a paid plan (exit `4` otherwise).
|
|
491
|
+
|
|
492
|
+
`wakes` lists recent wake-ups, newest first: when each was requested, how it
|
|
493
|
+
woke, the outcome and how long it took to be ready. `--limit` takes up to 200;
|
|
494
|
+
the default is 50.
|
|
495
|
+
|
|
496
|
+
`connect` prints the endpoint and a snippet for the engine's own client.
|
|
497
|
+
Authenticate with an organization-wide key, a branch key of the engine's branch, or a key limited to that engine. The snippets create an index and add
|
|
498
|
+
a document, so run them with an admin key (`headband keys create <name> --type
|
|
499
|
+
admin`); a search key can only search. A key of another branch does not work on the engine. Serverless engines are available for Meilisearch, and for Typesense
|
|
500
|
+
in early access; Elasticsearch runs always on. If a command says the key cannot
|
|
501
|
+
manage engines, run `headband login` again.
|
|
502
|
+
|
|
503
|
+
### Schema migrations
|
|
504
|
+
|
|
505
|
+
For projects that define their indexes in `headband.config.ts` with
|
|
506
|
+
`@headband/sync`. These commands read that file from the current directory:
|
|
507
|
+
its default export is `defineConfig({ url, projectId?, indexes })`, and `apply`
|
|
508
|
+
also needs a named `adapter` export, such as `drizzleAdapter(...)` from
|
|
509
|
+
`@headband/drizzle`, in the same file. When the adapter is exported, the
|
|
510
|
+
config is checked against your tables first, and every misspelled table,
|
|
511
|
+
field, relation or settings attribute is listed with its index.
|
|
512
|
+
`headband schema <command> --help` describes each command.
|
|
513
|
+
|
|
514
|
+
```bash
|
|
515
|
+
headband schema diff [--allow-destructive] [--json]
|
|
516
|
+
headband schema generate [--slug <slug>] [--dir <dir>] [--allow-destructive] [--json]
|
|
517
|
+
headband schema apply [--project <id>] [--allow-destructive] [--dir <dir>] [--yes] [--json]
|
|
518
|
+
headband schema status [--dir <dir>] [--json]
|
|
519
|
+
headband schema forget <index> [--yes] [--json]
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
- `diff` compares the config with the schema state on the server. Like
|
|
523
|
+
`git diff --exit-code`, it exits `1` when changes are pending and `0` when
|
|
524
|
+
in sync. `--json` prints `{inSync, ops}`.
|
|
525
|
+
- `generate` writes a migration file for the pending changes into `--dir`
|
|
526
|
+
(default `headband/migrations`). `--json` prints `{path}`, with `null` when
|
|
527
|
+
there was nothing to generate.
|
|
528
|
+
- `apply` runs the migration files the server does not have yet, in order. A
|
|
529
|
+
new index is created and backfilled from every source row (rows
|
|
530
|
+
`shouldIndex` rejects are left out). A schema change builds a new index
|
|
531
|
+
beside the live one, backfills it and switches the name over. Removing an
|
|
532
|
+
index needs `--allow-destructive`. `--json` prints
|
|
533
|
+
`{appliedFiles, appliedOps, projectId}`.
|
|
534
|
+
- `apply` recognises migrations the server already has from its schema state,
|
|
535
|
+
so a fresh checkout or CI applies without `headband/migrations/.applied.json`.
|
|
536
|
+
That file is only a local cache: commit it or ignore it.
|
|
537
|
+
- `status` shows each index's applied version and any drift, including
|
|
538
|
+
`index missing` for an index deleted outside `apply`. `--json` prints
|
|
539
|
+
`{indexes, localApplied, localHead}`.
|
|
540
|
+
- `forget` removes the recorded schema state of an index deleted outside
|
|
541
|
+
`apply`, so the next `generate` plans it as new. `headband indexes delete`
|
|
542
|
+
clears it for you.
|
|
543
|
+
|
|
544
|
+
Schema commands need a key with `schema.manage`, which only `headband login`
|
|
545
|
+
mints. That key is org-wide: new indexes go to the project named by
|
|
546
|
+
`--project <id>` or the config's `projectId`, else to your org's Default
|
|
547
|
+
project. Your app's own project admin key reaches only its project's indexes,
|
|
548
|
+
so name that project.
|
|
549
|
+
|
|
550
|
+
The URL comes from `--url`, else `HEADBAND_URL`, else the config's `url`, else
|
|
551
|
+
your login's.
|
|
552
|
+
|
|
553
|
+
### Sync
|
|
554
|
+
|
|
555
|
+
Sync keeps search indexes in step with a Postgres database. There are two ways
|
|
556
|
+
to run it.
|
|
557
|
+
|
|
558
|
+
**Managed Sync** runs on Headband. You register a source database and define
|
|
559
|
+
synced indexes; Headband reads the database's changes and keeps the indexes
|
|
560
|
+
current.
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
headband sync source add <name> [--db-url <url>] [--tls-mode verify-full|require] [--ca-file <pem>] [--json]
|
|
564
|
+
headband sync source add <name> --kind batadb --batadb-project <id> --batadb-branch <id> --batadb-compute <id> --accept-broad-role
|
|
565
|
+
headband sync source list [--json]
|
|
566
|
+
headband sync source show <id> [--json]
|
|
567
|
+
headband sync source test <id> [--json]
|
|
568
|
+
headband sync source setup-sql <id> [--provider generic|rds|batadb] [--json]
|
|
569
|
+
headband sync source setup <id> [--owner-url <url>] [--provider generic|rds|batadb] [--yes] [--json]
|
|
570
|
+
headband sync source set-ca <id> (--ca-file <pem> | --none) [--tls-mode verify-full|require] [--json]
|
|
571
|
+
headband sync source remove <id> [--yes] [--json]
|
|
572
|
+
|
|
573
|
+
headband sync index create -f <file.sync.json> [--json]
|
|
574
|
+
headband sync index list [--source <id>] [--instance <id>] [--json]
|
|
575
|
+
headband sync index show <id> [--version <n>] [--json]
|
|
576
|
+
headband sync index update <id> -f <file.sync.json> [--rebuild] [--json]
|
|
577
|
+
headband sync index validate -f <file.sync.json> [--index <id>] [--json]
|
|
578
|
+
headband sync index preview <id> [--ids <id,id,...>] [-f <file.sync.json>] [--json]
|
|
579
|
+
headband sync index rebuild|pause|resume|check <id> [--yes] [--json]
|
|
580
|
+
headband sync index remove <id> [--drop-index] [--yes] [--json]
|
|
581
|
+
|
|
582
|
+
headband sync status [--json]
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
Keep connection strings out of shell history: the source URL can come from
|
|
586
|
+
`HEADBAND_SYNC_DB_URL`, the owner URL from `HEADBAND_SYNC_OWNER_URL`, and a
|
|
587
|
+
BataDB API key from `HEADBAND_BATADB_API_KEY` only. `sync source setup` runs
|
|
588
|
+
Headband's setup SQL on your machine as the tables' owner, in one transaction;
|
|
589
|
+
the owner URL never leaves your machine. Run `headband sync source --help` and
|
|
590
|
+
`headband sync index --help` for the details.
|
|
591
|
+
|
|
592
|
+
Managed Sync is in early access: until your organization has it, these
|
|
593
|
+
commands say "Sync is not enabled for this organization yet."
|
|
594
|
+
|
|
595
|
+
**Self-hosted Sync** runs a worker in your own infrastructure. Your app writes
|
|
596
|
+
changes to an outbox table, and the worker pushes them to Headband. The
|
|
597
|
+
`@headband/drizzle` README walks through the whole setup.
|
|
598
|
+
|
|
599
|
+
```bash
|
|
600
|
+
headband sync init [--table <name>] [--write] [--apply] [--json]
|
|
601
|
+
headband sync worker [--loop] [--budget-ms <ms>] [--batch-size <n>] [--reconcile-every <n>] [--project <id>] [--json]
|
|
602
|
+
headband sync worker --scaffold docker [--force] [--json]
|
|
603
|
+
headband sync reconcile [--fix] [--json]
|
|
604
|
+
headband sync retry-dead-letters [--json]
|
|
605
|
+
headband sync status [--json]
|
|
606
|
+
```
|
|
151
607
|
|
|
152
|
-
|
|
608
|
+
- `init` prints the outbox table's DDL; `--write` also saves it to
|
|
609
|
+
`headband/outbox.sql`, and `--apply` runs it against `DATABASE_URL` (it is
|
|
610
|
+
`CREATE TABLE IF NOT EXISTS`). `--json` prints `{ddl, path, applied}`.
|
|
611
|
+
- `worker` drains the outbox. Without `--budget-ms` it loops until stopped
|
|
612
|
+
(Ctrl+C or SIGTERM finish the current batch first), so `--loop` changes
|
|
613
|
+
nothing on its own. With `--budget-ms` it stops after that long, or earlier
|
|
614
|
+
once the outbox is empty; add `--loop` to keep waiting for changes until the
|
|
615
|
+
budget ends. `--json` then prints the run report. Each batch that drained
|
|
616
|
+
rows prints one line to stderr (CLI 0.6.0). In a loop,
|
|
617
|
+
`--json` sends progress to stderr and prints the report when the worker
|
|
618
|
+
stops. A row that fails is printed with its model, ids and error; a batch
|
|
619
|
+
that fails is retried row by row, so one bad row never fails the others.
|
|
620
|
+
Rows that keep failing move to the dead letters. Each invocation is recorded
|
|
621
|
+
as one run under the connector `orm-sync` (`GET /v1/connectors/runs`); with
|
|
622
|
+
the org-wide login key that needs `--project <id>` or the config's
|
|
623
|
+
`projectId`. `--reconcile-every <n>` runs `reconcile` after every n-th batch.
|
|
624
|
+
- `worker --scaffold docker` writes `Dockerfile.headband-sync`, which runs
|
|
625
|
+
`npx --yes @headband/cli@<version> sync worker --loop`, and prints a compose
|
|
626
|
+
service for it. The container needs `DATABASE_URL` and `HEADBAND_API_KEY`.
|
|
627
|
+
- `reconcile` compares the source rows that belong in each index (every row
|
|
628
|
+
`shouldIndex` keeps) with the documents in it, and reports how many are
|
|
629
|
+
missing and how many are extra (counts, not ids). `--fix` pushes the missing
|
|
630
|
+
and deletes the extra. It exits
|
|
631
|
+
`1` when drift is left. `--json` prints `{indexes, drift}`. The index's ids
|
|
632
|
+
are read by cursor, so a Visor index past 1000 documents is compared id by
|
|
633
|
+
id too.
|
|
634
|
+
- `retry-dead-letters` requeues rows that failed too many times. `--json`
|
|
635
|
+
prints `{requeued}`.
|
|
636
|
+
- `status`, beside a `headband.config.ts`, shows the outbox's pending rows,
|
|
637
|
+
dead letters and oldest pending row. Elsewhere it shows managed Sync.
|
|
638
|
+
|
|
639
|
+
The worker, `retry-dead-letters`, `status` and `init --apply` read the outbox
|
|
640
|
+
database URL from `DATABASE_URL`, falling back to `HEADBAND_DATABASE_URL`. The
|
|
641
|
+
worker and `reconcile` need `headband.config.ts` to export an `adapter`. A
|
|
642
|
+
missing outbox table is reported by name, with how to create it.
|
|
643
|
+
|
|
644
|
+
## What `init` creates
|
|
645
|
+
|
|
646
|
+
**`headband.config.ts`**: search configuration that reads from the
|
|
647
|
+
`NEXT_PUBLIC_HEADBAND_KEY` env var with a fallback:
|
|
153
648
|
|
|
154
649
|
```typescript
|
|
155
650
|
export const headbandConfig = {
|
|
@@ -164,7 +659,8 @@ by a browser component, so the CLI only accepts scoped `hb_src_` search keys.
|
|
|
164
659
|
Keep admin keys in server-only environments for indexing, settings, and sync
|
|
165
660
|
jobs.
|
|
166
661
|
|
|
167
|
-
**`components/headband-search.tsx`** (or `src/components/` if your project uses
|
|
662
|
+
**`components/headband-search.tsx`** (or `src/components/` if your project uses
|
|
663
|
+
a `src/` directory): a ready-to-use search component:
|
|
168
664
|
|
|
169
665
|
```tsx
|
|
170
666
|
"use client";
|