Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/first-version.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ First version of `meili`, the Meilisearch CLI built for coding agents.
- Documents: `meili get documents --index <index>`, narrowed by `--ids` or `--filter`; `meili create documents --index <index> --file <path>` and `meili update documents --index <index> --file <path>`, which read the documents from a file, from stdin with `--file -` or from `--data`; `meili delete documents --index <index> (--ids <ids> | --filter <filter> | --all) --yes`.
- `meili schema` prints the index of the command tree as JSON, one entry per command with its routes, and `meili schema --command "get indexes"` prints one command in full, with its key actions and whether it returns a task.
- Connection through `--project-url` and `--project-api-key`, or `MEILI_PROJECT_URL` and `MEILI_PROJECT_API_KEY`; `MEILI_MANAGEMENT_TOKEN` and `MEILI_MANAGEMENT_URL` for the Management API.
- Several projects in a config file, `~/.config/meili/config.json` or `MEILI_CONFIG`, chosen with `--project` on the command line and never deduced; `meili config list`, `config add` and `config remove` manage the file.
- Several projects in a config file, `~/.config/meili/config.json` or `MEILI_CONFIG`, chosen with `--project` on the command line and never deduced; `meili config list`, `config set` and `config unset` manage the file.
- `meili auth login` logs in to Meilisearch Cloud with a browser, through the OAuth2 authorization code flow with PKCE on a loopback redirect, and stores the session in `credentials.json` next to the config file, mode `600`; the management commands refresh it on their own, `meili auth status` says whether that session is still accepted by the server, `meili auth refresh` rotates it on demand, and `meili auth logout` revokes it and forgets it.
- A command of the verb `delete` requires `--yes` on the command line: without it the command refuses to run, `cli_usage` and exit 2, before any network call. The CLI still never asks a question.
- `json` output by default, `ndjson` for lists, `--all` to follow the pagination, `--wait` and `--timeout` for asynchronous tasks.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The URL, the key and the token also exist as flags with the same name: `--projec
meili get indexes --project prod
```

With several projects in the file, `--project` is required: the CLI never guesses, and it refuses to run when the file and the `MEILI_PROJECT_URL` variable both define a project. `MEILI_CONFIG` names another file. For a coding agent, give it its own file through `MEILI_CONFIG`, read-only: the file is shared state, and an agent must not be able to change where another one's commands go. The file holds your keys: keep it readable by you alone, the CLI warns on stderr otherwise, and an agent changes nothing in it, neither a project nor its permissions, unless you asked for it explicitly. `meili config list`, `meili config add` and `meili config remove` manage the file.
With several projects in the file, `--project` is required: the CLI never guesses, and it refuses to run when the file and the `MEILI_PROJECT_URL` variable both define a project. `MEILI_CONFIG` names another file. For a coding agent, give it its own file through `MEILI_CONFIG`, read-only: the file is shared state, and an agent must not be able to change where another one's commands go. The file holds your keys: keep it readable by you alone, the CLI warns on stderr otherwise, and an agent changes nothing in it, neither a project nor its permissions, unless you asked for it explicitly. `meili config list`, `meili config set` and `meili config unset` manage the file.

## Documentation

Expand Down
4 changes: 2 additions & 2 deletions docs/adr/0005-named-projects-config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Alternatives considered:

- **A URL and its key travel together.** With `--project-url`, the key comes from `--project-api-key` or from nowhere, never from the environment or the file: a configured key is never sent to a URL it was not configured with. `--project-api-key` alone, or with `--project`, replaces the key of the configured project: an explicit key for a configured URL is the way to hand an agent a restricted key.

- **Three commands without an API manage the file**, for humans: `config list` prints the path and the projects with keys masked; `config add` creates the file in mode `600`, validates the URL, reads the key from stdin with `--project-api-key -` to keep it out of the shell history, and refuses a name that already exists; `config remove` deletes an entry. They are a convenience, not a protection.
- **Three commands without an API manage the file**, for humans: `config list` prints the path and the projects with keys masked; `config set` creates the file in mode `600`, validates the URL, reads the key from stdin with `--project-api-key -` to keep it out of the shell history, and refuses a name that already exists; `config unset` deletes an entry. They are a convenience, not a protection.
Comment thread
curquiza marked this conversation as resolved.

- **The file is shared state.** An agent gets its own file through `MEILI_CONFIG`, prepared by a human and mounted read-only in its sandbox, never the human's file. An agent changes nothing in the file, neither a project nor its permissions, unless the human asked for it explicitly. `--help`, `meili schema` and the README say so, because those are what an agent reads.

Expand All @@ -46,7 +46,7 @@ Alternatives considered:

- An agent works on two projects in one session, `--project staging` then `--project prod`, with no key on any command line and no state that another process can switch under it.
- A forgotten variable cannot choose the target silently: the environment pair next to a file is an error, not a precedence. The price is that a script which exports the pair on a machine that has a file must point `MEILI_CONFIG` at a file that does not exist, or unset the pair.
- What the CLI cannot protect against: another writer of the same file. Removing `staging` and adding it back with other credentials redirects every later `--project staging` of whoever shares the file, and a process with a file tool does not need `config add` to do it. Isolation, one file per agent, is the protection; `config add` refusing an existing name only prevents a replacement by mistake.
- What the CLI cannot protect against: another writer of the same file. Unsetting `staging` and setting it again with other credentials redirects every later `--project staging` of whoever shares the file, and a process with a file tool does not need `config set` to do it. Isolation, one file per agent, is the protection; `config set` refusing an existing name only prevents a replacement by mistake.
- `config.md` changes one promise: the CLI reads keys from the config file, and from nowhere else on disk, and warns when the file is readable by others. `output.md` gains the warning format.
- Unit and integration tests point `MEILI_CONFIG` at a file that does not exist, so a developer's own file never leaks into a test.
- `config.md` gains the file format and the resolution rules, `commands.md` the `--project` global flag and the three `config` commands.
4 changes: 2 additions & 2 deletions docs/adr/0008-schema-has-two-zoom-levels.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ The output is also missing what an agent asks first. The routes a command covers

`meili schema` keeps its name and gains a second zoom level, selected by a flag. There is no third level and no full dump: the two levels cover every need, and `oclif.manifest.json` is still there for whoever wants everything.

- `meili schema` prints the index: the global flags once, then one entry per command with its `id`, its `description` and its `routes`. The description is its first paragraph: `config add` and `config remove` carry a warning paragraph that is 17 % of today's index and belongs in the detail. About 12 kB at sixty-three commands.
- `meili schema` prints the index: the global flags once, then one entry per command with its `id`, its `description` and its `routes`. The description is its first paragraph: `config set` and `config unset` carry a warning paragraph that is 17 % of today's index and belongs in the detail. About 12 kB at sixty-three commands.
- `meili schema --command "get indexes"` prints one command in full: `id`, `description`, `api`, `routes`, `async`, `keyActions`, the `flags` the command declares itself and its `examples`.
- An unknown `--command` is a usage error, `cli_usage`, exit 2, with a hint that names the closest command, the suggestion `errors.ts` already computes for an unknown command, or `meili schema` when nothing is close.
- A field that would say nothing is absent rather than empty or false, the convention `routes` and `keyActions` already follow: no `async` on a command that returns no task, no `required` on an optional flag. That is 7.6 % of a detail call.

"The flags the command declares itself" is read from the command class, where oclif keeps `flags` and `baseFlags` apart, and not inferred from the manifest, which merges them. The distinction matters because `meili config add` redefines `--project` and `--project-url` as required with its own descriptions: dropping global flags by name would hide exactly the two flags that command needs. Comparing the rendered flag against `BaseCommand.baseFlags` would work today but compares two separately built artifacts, the live `src` object against the manifest built from `dist`, so a stale manifest would make every untouched global flag look redefined. The class answers the question directly.
"The flags the command declares itself" is read from the command class, where oclif keeps `flags` and `baseFlags` apart, and not inferred from the manifest, which merges them. The distinction matters because `meili config set` redefines `--project` and `--project-url` as required with its own descriptions: dropping global flags by name would hide exactly the two flags that command needs. Comparing the rendered flag against `BaseCommand.baseFlags` would work today but compares two separately built artifacts, the live `src` object against the manifest built from `dist`, so a stale manifest would make every untouched global flag look redefined. The class answers the question directly.

Three fields are new, and none of them is metadata to maintain by hand twice:

Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0009-oauth-login-for-the-management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Alternatives considered:

- **Four commands without an API, `auth login`, `auth status`, `auth refresh` and `auth logout`**, in the table of `commands.md`. They call neither API of the contract: the authorization server is described by no OpenAPI document here, so no route derives their name. They sit next to the `config` commands, and like them they are for humans.

- **`auth login` runs the authorization code flow with PKCE**, as a native application does (RFC 8252). It starts a server bound to `127.0.0.1` on a free port, prints the consent URL on stderr, opens it in a browser unless `--no-browser`, waits for the redirect, checks the `state` it sent, and exchanges the code with its verifier. It writes nothing on stdout: what it produced is a file, as for `config add`. `auth status` is where the data is. The browser is a convenience: the URL is always printed, so a session over SSH works by opening it elsewhere.
- **`auth login` runs the authorization code flow with PKCE**, as a native application does (RFC 8252). It starts a server bound to `127.0.0.1` on a free port, prints the consent URL on stderr, opens it in a browser unless `--no-browser`, waits for the redirect, checks the `state` it sent, and exchanges the code with its verifier. It writes nothing on stdout: what it produced is a file, as for `config set`. `auth status` is where the data is. The browser is a convenience: the URL is always printed, so a session over SSH works by opening it elsewhere.

- **It is the one command that waits for a human**, and it says so in `commands.md`. It never prompts and never reads stdin: it waits for an HTTP request that the human causes, for five minutes, then `cli_timeout` and exit 5. Everything else the CLI does stays non-interactive, and an agent that must not wait uses `MEILI_MANAGEMENT_TOKEN`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Status: accepted. Date: 2026-10-21.

`--project-url`, `--project-api-key` and `--project` were global: oclif put them on every command through `BaseCommand.baseFlags`, and `commands.md` listed them in a table that said "these flags work on every command". That was true of the commands written at the time, which all talked to a Meilisearch project.

It stopped being true with the `auth` commands of ADR 0009, which talk to the authorization server of Meilisearch Cloud and resolve no project at all. `meili auth login --help` advertised three flags it ignores entirely, and so did `schema`, `config list` and `config remove`. An agent learns this CLI by reading `meili schema`, and it was being told that four commands take a project they do nothing with. A flag that does nothing is worse than a missing one: nothing fails, the agent just believes something false.
It stopped being true with the `auth` commands of ADR 0009, which talk to the authorization server of Meilisearch Cloud and resolve no project at all. `meili auth login --help` advertised three flags it ignores entirely, and so did `schema`, `config list` and `config unset`. An agent learns this CLI by reading `meili schema`, and it was being told that four commands take a project they do nothing with. A flag that does nothing is worse than a missing one: nothing fails, the agent just believes something false.

ADR 0005 decided how a project is chosen and ADR 0008 how `meili schema` prints a command. Neither decided that the three flags belong to every command: 0005 names `--project` a "global flag" once, in a consequence that records where the documentation went, and 0008 counts "five global flags" in a context that describes the CLI as it was. Those two sentences are the only trace of the old arrangement, and they are history, not decisions.

Expand All @@ -24,13 +24,13 @@ Alternatives considered:

- **The group sits in a command's own `flags`, not in `baseFlags`.** ADR 0008 prints the detail of a command from the flags its class declares, so a connection in `baseFlags` would be invisible to `meili schema --command "get indexes"`, which is where an agent looks to learn how to reach a project. The index keeps printing the global flags once, now two of them.

- **`meili config add` declares its own three.** It writes a project into the config file rather than connecting to one: `--project` is the name to add, `--project-url` the URL to store, `--project-api-key` the key, with `-` reading stdin out of the shell history. That also makes the rule testable without a list of exceptions: the connection group is on the commands whose `static api` is `meilisearch`, and on `config add`, which says so in its own flags.
- **`meili config set` declares its own three.** It writes a project into the config file rather than connecting to one: `--project` is the name to set, `--project-url` the URL to store, `--project-api-key` the key, with `-` reading stdin out of the shell history. That also makes the rule testable without a list of exceptions: the connection group is on the commands whose `static api` is `meilisearch`, and on `config set`, which says so in its own flags.

- **A contract test holds it**: a command declares the connection flags if and only if it reaches a project. It keys on `--project-url`, because `config remove --project` names an entry of the file, not a connection, and is allowed to keep that name.
- **A contract test holds it**: a command declares the connection flags if and only if it reaches a project. It keys on `--project-url`, because `config unset --project` names an entry of the file, not a connection, and is allowed to keep that name.

## Consequences

- `meili auth`, `meili schema`, `config list` and `config remove` stop advertising three flags they ignore. `meili get indexes --help` is unchanged: oclif renders a leaf command's base flags and own flags in the same list.
- `meili auth`, `meili schema`, `config list` and `config unset` stop advertising three flags they ignore. `meili get indexes --help` is unchanged: oclif renders a leaf command's base flags and own flags in the same list.
- `meili schema` prints two global flags instead of five, and the connection appears in the detail of the commands that take it, which is where it is usable.
- Nothing changes in how a project is resolved: ADR 0005 holds entirely, and `config.md` is untouched.
- Two sentences of accepted ADRs now describe a world where the three flags were global: the consequence line of ADR 0005 and the context and one example of ADR 0008. They are not edited, and neither ADR is superseded, since this one changes no decision of theirs: it decides which commands carry the flags, which neither had decided.
Expand Down
Loading
Loading