@headband/cli 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +334 -67
  2. package/dist/index.js +2136 -401
  3. 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, device login,
4
- API key management, index operations, document pushes, search, and task inspection.
5
-
6
- ## Usage
3
+ Command-line tools for [Headband](https://headband.dev): project setup, login,
4
+ 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,213 @@ npx @headband/cli init
11
11
  npx @headband/cli login
12
12
  ```
13
13
 
14
- Global flags:
14
+ ## Contents
15
15
 
16
- ```bash
17
- --url <url> Select a Headband profile URL
18
- --json Print JSON for supported list/get commands
19
- --yes Skip confirmation prompts
20
- --help Show help
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
+ --json Print one JSON document on stdout; errors print one JSON object on stderr
30
+ --yes, -y Answer yes to confirmations (required when stdin is not a terminal)
31
+ --help, -h Show help for the CLI or a command
21
32
  ```
22
33
 
23
- Credentials are stored in `~/.config/headband/credentials.json` or
24
- `$XDG_CONFIG_HOME/headband/credentials.json` with file mode `0600`.
34
+ ## Authentication
25
35
 
26
- ## Commands
36
+ ### Interactive login
27
37
 
28
- ### Setup
38
+ ```bash
39
+ headband login [--url <url>] [--profile <name>] [--json]
40
+ headband logout [--profile <name>] [--yes] [--no-revoke] [--json]
41
+ headband whoami [--json]
42
+ headband profiles list [--json]
43
+ headband profiles use <name>
44
+ ```
45
+
46
+ `login` runs Headband's device flow: it prints a code and a link, opens your
47
+ browser, and waits while you approve it in the dashboard. It saves the key it
48
+ receives as a named profile and makes that profile current. The name defaults
49
+ to `default` for headband.dev and to the host name for any other URL, such as
50
+ `localhost-3000`. The full key is never printed; output shows only its prefix.
51
+
52
+ Log in to several Headband URLs or organizations and switch between them:
29
53
 
30
54
  ```bash
31
- headband init [--yes]
55
+ headband login --profile prod
56
+ headband login --url http://localhost:3000 --profile local
57
+ headband profiles list
58
+ headband profiles use prod
59
+ headband indexes list --profile local # one command, without switching
32
60
  ```
33
61
 
34
- This will:
62
+ `logout` revokes the profile's key on the server, then removes it from this
63
+ machine. It asks first at a terminal. `--yes` revokes without asking.
64
+ `--no-revoke` removes the local copy and leaves the key working. If the key is
65
+ already gone on the server, logout still succeeds; any other failure keeps the
66
+ local login so you can retry.
35
67
 
36
- 1. Ask for your Headband URL (default: `https://headband.dev`)
37
- 2. Ask for your browser-safe search API key (`hb_src_...` only)
38
- 3. Ask for your default search index (e.g. `products`, `movies`)
39
- 4. Detect your package manager (npm, pnpm, yarn, or bun)
40
- 5. Install `@headband/react`
41
- 6. Create `headband.config.ts` with your settings
42
- 7. Create an example `<HeadbandSearch />` component
68
+ Credentials live in `~/.config/headband/credentials.json`, or
69
+ `$XDG_CONFIG_HOME/headband/credentials.json`, with file mode `0600`. A file
70
+ written by CLI 0.2.0 or earlier (profiles keyed by URL) still works: its
71
+ profiles get names on first read, and the file is saved in the new format the
72
+ next time the CLI writes it.
43
73
 
44
- ## Options
74
+ ### Tokens and environment variables
75
+
76
+ A script, CI job or agent does not need to log in. Give it a key:
77
+
78
+ | Variable | Flag | Meaning |
79
+ | ------------------ | ----------- | ------------------------------------------------------ |
80
+ | `HEADBAND_API_KEY` | `--token` | The API key to send. Used instead of any stored login. |
81
+ | `HEADBAND_URL` | `--url` | The Headband URL. |
82
+ | `HEADBAND_PROFILE` | `--profile` | Which stored login to use. |
83
+
84
+ Precedence, highest first:
85
+
86
+ - **Key:** `--token`, then `HEADBAND_API_KEY`, then the stored profile.
87
+ - **URL:** `--url`, then `HEADBAND_URL`, then the profile's URL, then
88
+ `https://headband.dev`. With a token, the profile's URL counts only when you
89
+ name the profile with `--profile`.
90
+ - **Profile:** `--profile`, then `HEADBAND_PROFILE`, then the current profile,
91
+ then the only stored profile when there is one.
92
+
93
+ A stored key is only ever sent to the URL it was issued for. `--url` with no
94
+ `--profile` picks the stored profile for that URL; `--url` with a `--profile`
95
+ for a different URL is a usage error. A `--profile` name that does not exist is
96
+ an error, never a silent fallback.
45
97
 
46
98
  ```bash
47
- npx @headband/cli init --yes # Skip prompts, use defaults
48
- npx @headband/cli --help # Show help
99
+ export HEADBAND_API_KEY=hb_adm_...
100
+ export HEADBAND_URL=https://headband.dev
101
+ headband indexes list --json
49
102
  ```
50
103
 
51
- ### Auth
104
+ Which key to use: an admin key from `headband keys create <name> --type admin`
105
+ covers indexes, documents, search, engines and Sync. Managing keys, projects
106
+ and schema state needs a key minted by `headband login`, because only those
107
+ carry `keys.manage`, `projects.manage` and `schema.manage`.
108
+
109
+ ## Scripts and agents
110
+
111
+ The CLI never waits for input it cannot get.
112
+
113
+ - **No hanging prompts.** A command that would ask for confirmation fails at
114
+ once with exit code `2` and code `confirmation_required` when stdin is not a
115
+ terminal. Pass `--yes` to confirm. `headband init` does the same without
116
+ `--yes`.
117
+ - **One JSON document.** With `--json`, stdout carries exactly one JSON
118
+ document and progress lines go to stderr, so `headband ... --json | jq` is
119
+ safe. `docs export` is the exception by design: its output is the documents.
120
+ - **JSON errors.** With `--json`, a failure prints one line to stderr:
121
+
122
+ ```json
123
+ { "error": "Index not found", "code": "index_not_found", "status": 404, "exitCode": 5 }
124
+ ```
125
+
126
+ `code` is the API's error code when there is one, else a CLI code such as
127
+ `usage_error`, `not_authenticated`, `confirmation_required` or
128
+ `network_error`. `status` is the HTTP status, or `null` when no request was
129
+ made.
130
+
131
+ - **Branch on exit codes**, not on message text. See the table below.
132
+
133
+ ## Exit codes
134
+
135
+ | Code | Meaning | Typical cause |
136
+ | ----- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
137
+ | `0` | Success | |
138
+ | `1` | Error | Anything not listed below; also `schema diff` when changes are pending |
139
+ | `2` | Usage | Unknown command or option, missing or malformed value, HTTP 400/413/422, confirmation needed without a terminal |
140
+ | `3` | Authentication | No key found, a `--profile` that does not exist, HTTP 401 |
141
+ | `4` | Permission | HTTP 403, or 402 (the plan does not allow it) |
142
+ | `5` | Not found | HTTP 404 or 410, `profiles use` with an unknown name |
143
+ | `6` | Conflict | HTTP 409, such as deleting a project that still has indexes |
144
+ | `7` | Rate limited | HTTP 429 |
145
+ | `8` | Server or engine | HTTP 5xx, or the URL could not be reached |
146
+ | `130` | Interrupted | Ctrl+C |
147
+
148
+ ## Command reference
149
+
150
+ ### Setup
52
151
 
53
152
  ```bash
54
- headband login [--url <url>]
55
- headband logout [--url <url>] [--yes]
56
- headband whoami [--url <url>] [--json]
153
+ headband init [--yes]
57
154
  ```
58
155
 
59
- `login` uses Headband's device flow and stores only the API key profile locally.
60
- The full key is never printed during login; success output shows only the key
61
- prefix. `logout` removes local credentials and can offer to revoke the CLI key
62
- server-side first.
156
+ Asks for your Headband URL, a browser-safe search key (`hb_src_...` only) and
157
+ a default index, installs `@headband/react`, and writes `headband.config.ts`
158
+ plus an example `<HeadbandSearch />` component. `--yes` skips the questions and
159
+ uses defaults. Running `headband` with no command runs `init`.
63
160
 
64
161
  ### Projects
65
162
 
66
163
  ```bash
67
164
  headband projects list [--json]
68
- headband projects create <name> --engine <meilisearch|typesense|elasticsearch>
165
+ headband projects create <name> --engine <meilisearch|typesense|elasticsearch|visor> [--json]
166
+ headband projects delete <id> [--force] [--yes] [--json]
69
167
  ```
70
168
 
169
+ `projects delete` refuses a project that still has indexes (exit `6`, code
170
+ `project_has_indexes`). `--force` deletes the project's indexes on its shared
171
+ engine along with it. Even with `--force`, a project with an index on a
172
+ dedicated engine, or an index being moved to one, is refused: delete or move
173
+ those indexes first. Deleting a project also deletes the API keys scoped to it.
174
+
71
175
  ### Keys
72
176
 
73
177
  ```bash
74
178
  headband keys list [--json]
75
- headband keys create <name> --type <admin|search> [--project <id>] [--json]
76
- headband keys revoke <id> [--yes]
179
+ headband keys create <name> --type <admin|search> [--project <id>]
180
+ [--permissions <a,b,...>] [--expires <30d|ISO date>] [--json]
181
+ headband keys revoke <id> [--yes] [--json]
77
182
  ```
78
183
 
79
184
  `keys create` is the only command that prints a raw API key. Store it when it is
80
- shown because Headband returns raw keys only once.
185
+ shown: Headband returns raw keys only once.
186
+
187
+ `--permissions` limits the new key to the permissions you list. You can grant
188
+ only permissions your own key holds, and only ones that fit the key type:
189
+
190
+ | Type | Grantable permissions |
191
+ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
192
+ | `search` | `search`, `analytics.write` |
193
+ | `admin` | `search`, `documents.read`, `documents.add`, `documents.delete`, `indexes.create`, `indexes.delete`, `settings.update`, `analytics.write`, `connectors.write`, `instances.manage`, `sync.manage` |
194
+
195
+ Anything else is refused with exit `4` and the names it refused. Without
196
+ `--permissions`, the key gets the type's defaults, cut down to what your key
197
+ holds. `keys.manage`, `projects.manage` and `schema.manage` are never
198
+ grantable here; only `headband login` mints them.
199
+
200
+ `--expires` sets when the key stops working: a duration from now (`12h`,
201
+ `30d`, `2w`, `1y`) or an ISO date (`2027-01-31`). It must be in the future and
202
+ at most one year away. Without it, the key does not expire.
203
+
204
+ ```bash
205
+ headband keys create ci-reader --type admin --permissions search,documents.read --expires 30d --json
206
+ ```
81
207
 
82
208
  ### Indexes
83
209
 
84
210
  ```bash
85
211
  headband indexes list [--json]
86
212
  headband indexes create <uid> [--primary-key <pk>] [--engine <engine>] [--instance <id>] [--json]
87
- headband indexes delete <uid> [--yes]
88
- headband indexes settings get <uid> [--json]
89
- headband indexes settings set <uid> --file <settings.json> [--json]
90
- headband indexes stats <uid> [--json]
213
+ headband indexes delete <uid> [--yes] [--json]
214
+ headband indexes settings get <uid>
215
+ headband indexes settings set <uid> --file <settings.json>
216
+ headband indexes stats <uid>
91
217
  ```
92
218
 
219
+ `settings get`, `settings set` and `stats` always print JSON.
220
+
93
221
  `--instance <id>` creates the index on one of your dedicated engines (ids from
94
222
  `headband instances list`). The index takes that engine's type, so `--engine` is
95
223
  optional; if you pass it, it must match.
@@ -98,6 +226,57 @@ On the engine, the index has a longer name (`engineIndexUid`, such as
98
226
  `hbo_<org>_products`). `indexes create` and `indexes list` show it. Use that name,
99
227
  not the uid, with the engine's own client on the instance endpoint.
100
228
 
229
+ ### Documents
230
+
231
+ ```bash
232
+ headband docs push <file.json|file.jsonl> --index <uid> [--json]
233
+ headband docs export --index <uid> [--out <file>] [--format <jsonl|json>] [--batch <n>] [--json]
234
+ headband docs delete <docId> --index <uid> [--json]
235
+ ```
236
+
237
+ `docs push` accepts a JSON array or a JSONL file and sends documents to
238
+ `/v1/bulk` in batches of 1000.
239
+
240
+ `docs export` pages through `GET /v1/documents` and writes every document in
241
+ the index, one page in memory at a time:
242
+
243
+ ```bash
244
+ headband docs export --index products > products.jsonl
245
+ headband docs export --index products --out products.json
246
+ headband docs export --index products | head -5
247
+ ```
248
+
249
+ - Without `--out`, documents stream to stdout and nothing else is printed
250
+ there. Progress for `--out` goes to stderr.
251
+ - `--format` is `jsonl` (one document per line, the default) or `json` (one
252
+ array). With `--out`, a `.json` file name picks `json`.
253
+ - `--out` writes to `<file>.partial` and renames it when the export finishes,
254
+ so a failed export never leaves a truncated file under the real name.
255
+ `--json` with `--out` prints a summary: `{index, out, format, exported}`.
256
+ - `--batch` is the page size. The default is 1000, or 250 for an index on
257
+ Typesense, which refuses larger pages.
258
+ - Elasticsearch serves at most the first 10,000 documents by paging, so a
259
+ larger Elasticsearch index fails partway with exit `8`.
260
+ - Export needs an admin key with `documents.read`.
261
+
262
+ ### Search
263
+
264
+ ```bash
265
+ headband search <index> <query> [--limit N] [--filter <expr>] [--json]
266
+ ```
267
+
268
+ Plain output shows a compact table. `--json` prints the raw API response.
269
+
270
+ ### Tasks
271
+
272
+ ```bash
273
+ headband tasks list [--json]
274
+ headband tasks get <id>
275
+ ```
276
+
277
+ `tasks list` prints a table, or the raw response with `--json`. `tasks get`
278
+ prints JSON.
279
+
101
280
  ### Engines
102
281
 
103
282
  Dedicated engines are private Meilisearch, Elasticsearch or Typesense servers for
@@ -106,50 +285,137 @@ gradually. Until your organization has access, these commands say so.
106
285
 
107
286
  ```bash
108
287
  headband instances list [--json]
109
- headband instances create <meilisearch|elasticsearch|typesense> [--name <name>] [--lane <serverless|always_on>] [--memory <512m|1g|...>] [--region <region>] [--idle-timeout <seconds>] [--json]
288
+ headband instances create <meilisearch|elasticsearch|typesense|visor> [--name <name>]
289
+ [--lane <serverless|always_on>] [--memory <512m|1g|...>]
290
+ [--region <region>] [--idle-timeout <seconds>] [--json]
110
291
  headband instances show <id> [--json]
292
+ headband instances update <id> [--name <name>] [--lane <serverless|always_on>]
293
+ [--memory <512m|1g|...>] [--idle-timeout <seconds>] [--json]
111
294
  headband instances wake <id> [--json]
112
295
  headband instances park <id> [--json]
113
- headband instances delete <id> [--yes]
114
- headband instances connect <id> [--client <node|curl>]
296
+ headband instances wakes <id> [--limit <n>] [--json]
297
+ headband instances delete <id> [--yes] [--json]
298
+ headband instances connect <id> [--client <node|curl>] [--json]
115
299
  ```
116
300
 
117
- `connect` prints the endpoint and a snippet for the engine's own client. Authenticate
118
- with an organization-wide key. The snippets create an index and add a document, so
119
- run them with an admin key (`headband keys create <name> --type admin`); a search
120
- key can only search.
121
- Project keys do not work on engine endpoints. Serverless engines are available for
122
- Meilisearch only for now. If a command says the key cannot manage engines, run
123
- `headband login` again.
301
+ `update` changes only the settings you pass; `--size` is an alias for
302
+ `--memory`. The engine and region cannot change. A resize applies in the
303
+ background: `instances show` reports the state as "applying changes" until it
304
+ is done. Always-on engines need a paid plan (exit `4` otherwise).
124
305
 
125
- ### Documents
306
+ `wakes` lists recent wake-ups, newest first: when each was requested, how it
307
+ woke, the outcome and how long it took to be ready. `--limit` takes up to 200;
308
+ the default is 50.
309
+
310
+ `connect` prints the endpoint and a snippet for the engine's own client.
311
+ Authenticate with an organization-wide key. The snippets create an index and add
312
+ a document, so run them with an admin key (`headband keys create <name> --type
313
+ admin`); a search key can only search. Project keys do not work on engine
314
+ endpoints. Serverless engines are available for Meilisearch, and for Typesense
315
+ in early access; Elasticsearch runs always on. If a command says the key cannot
316
+ manage engines, run `headband login` again.
317
+
318
+ ### Schema migrations
319
+
320
+ For projects that define their indexes in `headband.config.ts` with
321
+ `@headband/sync`. These commands read that file from the current directory.
126
322
 
127
323
  ```bash
128
- headband docs push <file.json|file.jsonl> --index <uid>
129
- headband docs delete <docId> --index <uid>
324
+ headband schema diff [--allow-destructive] [--json]
325
+ headband schema generate [--slug <slug>] [--dir <dir>] [--allow-destructive] [--json]
326
+ headband schema apply [--allow-destructive] [--dir <dir>] [--yes] [--json]
327
+ headband schema status [--dir <dir>] [--json]
130
328
  ```
131
329
 
132
- `docs push` accepts a JSON array or JSONL file and sends documents to
133
- `/v1/bulk` in batches of 1000.
330
+ - `diff` compares the config with the schema state on the server. Like
331
+ `git diff --exit-code`, it exits `1` when changes are pending and `0` when
332
+ in sync. `--json` prints `{inSync, ops}`.
333
+ - `generate` writes a migration file for the pending changes into `--dir`
334
+ (default `headband/migrations`). `--json` prints `{path}`, with `null` when
335
+ there was nothing to generate.
336
+ - `apply` runs the migration files not yet applied, in order, backfilling
337
+ documents through your config's `adapter` when a change needs a reindex.
338
+ Removing an index needs `--allow-destructive`. `--json` prints
339
+ `{appliedFiles, appliedOps}`.
340
+ - `status` shows each index's applied version and any drift. `--json` prints
341
+ `{indexes, localApplied, localHead}`.
134
342
 
135
- ### Search
343
+ Schema commands need a key with `schema.manage`, which only `headband login`
344
+ mints.
345
+
346
+ ### Sync
347
+
348
+ Sync keeps search indexes in step with a Postgres database. There are two ways
349
+ to run it.
350
+
351
+ **Managed Sync** runs on Headband. You register a source database and define
352
+ synced indexes; Headband reads the database's changes and keeps the indexes
353
+ current.
136
354
 
137
355
  ```bash
138
- headband search <index> <query> [--limit N] [--filter <expr>] [--json]
356
+ headband sync source add <name> [--db-url <url>] [--tls-mode verify-full|require] [--ca-file <pem>] [--json]
357
+ headband sync source add <name> --kind batadb --batadb-project <id> --batadb-branch <id> --batadb-compute <id> --accept-broad-role
358
+ headband sync source list [--json]
359
+ headband sync source show <id> [--json]
360
+ headband sync source test <id> [--json]
361
+ headband sync source setup-sql <id> [--provider generic|rds|batadb] [--json]
362
+ headband sync source setup <id> [--owner-url <url>] [--provider generic|rds|batadb] [--yes] [--json]
363
+ headband sync source set-ca <id> (--ca-file <pem> | --none) [--tls-mode verify-full|require] [--json]
364
+ headband sync source remove <id> [--yes] [--json]
365
+
366
+ headband sync index create -f <file.sync.json> [--json]
367
+ headband sync index list [--source <id>] [--instance <id>] [--json]
368
+ headband sync index show <id> [--version <n>] [--json]
369
+ headband sync index update <id> -f <file.sync.json> [--rebuild] [--json]
370
+ headband sync index validate -f <file.sync.json> [--index <id>] [--json]
371
+ headband sync index preview <id> [--ids <id,id,...>] [-f <file.sync.json>] [--json]
372
+ headband sync index rebuild|pause|resume|check <id> [--yes] [--json]
373
+ headband sync index remove <id> [--drop-index] [--yes] [--json]
374
+
375
+ headband sync status [--json]
139
376
  ```
140
377
 
141
- Plain output shows a compact table. `--json` prints the raw API response.
378
+ Keep connection strings out of shell history: the source URL can come from
379
+ `HEADBAND_SYNC_DB_URL`, the owner URL from `HEADBAND_SYNC_OWNER_URL`, and a
380
+ BataDB API key from `HEADBAND_BATADB_API_KEY` only. `sync source setup` runs
381
+ Headband's setup SQL on your machine as the tables' owner, in one transaction;
382
+ the owner URL never leaves your machine. Run `headband sync source --help` and
383
+ `headband sync index --help` for the details.
142
384
 
143
- ### Tasks
385
+ **Self-hosted Sync** runs a worker in your own infrastructure. Your app writes
386
+ changes to an outbox table, and the worker pushes them to Headband.
144
387
 
145
388
  ```bash
146
- headband tasks list [--json]
147
- headband tasks get <id> [--json]
389
+ headband sync init [--table <name>] [--write] [--json]
390
+ headband sync worker [--loop] [--budget-ms <ms>] [--batch-size <n>] [--reconcile-every <n>] [--json]
391
+ headband sync worker --scaffold docker [--force] [--json]
392
+ headband sync reconcile [--fix] [--json]
393
+ headband sync retry-dead-letters [--json]
148
394
  ```
149
395
 
150
- ## What gets created
151
-
152
- **`headband.config.ts`** -- Search configuration that reads from `NEXT_PUBLIC_HEADBAND_KEY` env var with a fallback:
396
+ - `init` prints the outbox table's DDL; `--write` also saves it to
397
+ `headband/outbox.sql`. `--json` prints `{ddl, path}`.
398
+ - `worker` drains the outbox. Without `--budget-ms` it loops until stopped
399
+ (Ctrl+C or SIGTERM finish the current batch first). With `--budget-ms` it
400
+ stops after that long; `--json` then prints the run report. In a loop,
401
+ `--json` sends progress to stderr and prints the report when the worker
402
+ stops.
403
+ - `worker --scaffold docker` writes `Dockerfile.headband-sync` and prints a
404
+ compose service for it.
405
+ - `reconcile` compares each index's document count with its source table and
406
+ reports drift; `--fix` resends a drifted index in full. `--json` prints
407
+ `{indexes}`.
408
+ - `retry-dead-letters` requeues rows that failed too many times. `--json`
409
+ prints `{requeued}`.
410
+
411
+ The worker and `retry-dead-letters` read the outbox database URL from
412
+ `DATABASE_URL`, falling back to `HEADBAND_DATABASE_URL`. The worker and
413
+ `reconcile` need `headband.config.ts` to export an `adapter`.
414
+
415
+ ## What `init` creates
416
+
417
+ **`headband.config.ts`**: search configuration that reads from the
418
+ `NEXT_PUBLIC_HEADBAND_KEY` env var with a fallback:
153
419
 
154
420
  ```typescript
155
421
  export const headbandConfig = {
@@ -164,7 +430,8 @@ by a browser component, so the CLI only accepts scoped `hb_src_` search keys.
164
430
  Keep admin keys in server-only environments for indexing, settings, and sync
165
431
  jobs.
166
432
 
167
- **`components/headband-search.tsx`** (or `src/components/` if your project uses a `src/` directory) -- A ready-to-use search component:
433
+ **`components/headband-search.tsx`** (or `src/components/` if your project uses
434
+ a `src/` directory): a ready-to-use search component:
168
435
 
169
436
  ```tsx
170
437
  "use client";