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 @@ -5,7 +5,7 @@
First version of `meili`, the Meilisearch CLI built for coding agents.

- Commands derive from the routes of the Meilisearch API and of the Management API, verb first and resource plural: `meili get health`, `meili get indexes`, `meili get indexes --id movies`.
- 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`.
- Documents: `meili get documents --index <index>`, narrowed by `--ids` or `--filter`; `meili add 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.
Expand Down
6 changes: 3 additions & 3 deletions .claude/skills/add-command/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ argument-hint: <command, for example: get stats>

You ship exactly one command in this change: `meili $ARGUMENTS`, with every route of its verb and resource. One file for the command, one test file, one integration test, the regenerated manifest, one changeset line. Nothing else, unless a shared piece of `src/lib/` is missing for this kind of command (say so in the report).

When `$ARGUMENTS` names several commands of the same resource, `get create update delete documents`, rule 10 of `CLAUDE.md` allows them in one change: run every step below once per command, and ship them complete or not at all. Commands of different resources stay in different changes.
When `$ARGUMENTS` names several commands of the same resource, `get add update delete documents`, rule 10 of `CLAUDE.md` allows them in one change: run every step below once per command, and ship them complete or not at all. Commands of different resources stay in different changes.

Work through the steps in order. Every check below is enforced by `pnpm test` through `test/contract/`, so a shortcut only moves the failure later.

Expand Down Expand Up @@ -50,7 +50,7 @@ The command id is the file path. `meili get stats` gives:
| integration test | `test/integration/meilisearch/get-stats.test.ts` |
| snapshot (generated) | `test/commands/get/__snapshots__/stats.test.ts.snap` |

The class name is the command id in PascalCase (`GetStats`). The first command of a new verb (`create`, `update`, `delete`, `cancel`, ...) also needs the topic description in `package.json`, under `oclif.topics.<verb>.description`, otherwise `meili --help` does not list the verb. A verb-only command (`search`, `export`) is a file at the root of `src/commands/`.
The class name is the command id in PascalCase (`GetStats`). The first command of a new verb (`add`, `update`, `delete`, `cancel`, ...) also needs the topic description in `package.json`, under `oclif.topics.<verb>.description`, otherwise `meili --help` does not list the verb. A verb-only command (`search`, `export`) is a file at the root of `src/commands/`.

Find the SDK method in `node_modules/meilisearch/dist/meilisearch.d.ts` or `node_modules/meilisearch/dist/indexes.d.ts`. Prefer the variant that returns the API body unchanged: `getRawIndexes` over `getIndexes`, `getRawIndex` over `getIndex`. If the SDK has no method for a route, stop and ask: ADR 0003 forbids hand-written HTTP.

Expand All @@ -60,7 +60,7 @@ Find the SDK method in `node_modules/meilisearch/dist/meilisearch.d.ts` or `node
|---|---|---|
| read, one route, one result | `src/commands/get/health.ts` | `await this.output(result)` |
| read with a list route and a unit route | `src/commands/get/indexes.ts` | `outputList({...})` or `output(...)` by route |
| write returning a task (the response is a task) | `src/commands/create/documents.ts` | `await this.outputTask(task, flags)` |
| write returning a task (the response is a task) | `src/commands/add/documents.ts` | `await this.outputTask(task, flags)` |
| write with several routes, one per selector | `src/commands/delete/documents.ts` | `await this.outputTask(task, flags)` |
| offline (`schema`) | `src/commands/schema.ts` | `await this.output(result)` |

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Package: `@meilisearch/cli` on npm. Binary: `meili`.
7. Errors are structured. Use `MeiliCliError` from `src/lib/errors.ts` with a code and an exit code from the table in `docs/contract/output.md`. Never `throw new Error("...")`.
8. Secrets never appear in output, logs or debug traces. Use the redaction helpers in `src/lib/config.ts`.
9. No new dependency without a one line justification in the pull request. Prefer the standard library.
10. One command per pull request, or the commands of one resource when they ship together and complete: the `get`, `create`, `update` and `delete` of `documents` are one change. One file per command, covering every route of its verb and resource. Every command has a snapshot test.
10. One command per pull request, or the commands of one resource when they ship together and complete: the `get`, `add`, `update` and `delete` of `documents` are one change. One file per command, covering every route of its verb and resource. Every command has a snapshot test.
11. Every command declares the API it uses with `static api = "meilisearch" | "management" | "none"`. `BaseCommand` resolves credentials and builds the client. Never resolve credentials or build a client in a command.
12. Code and docs change together. If your change touches a command name, a flag, an environment variable, an error code, an exit code or a file path that appears in `docs/`, update that doc in the same pull request. The contract tests in `test/contract/` fail on most of these drifts, so a red `pnpm test` after a docs-free change is expected, not a flaky test.
13. Never edit an accepted ADR to match the code. If your change contradicts an ADR, stop and ask, or write a new ADR that supersedes the old one and mark the old one `superseded by ADR NNNN`. The history of decisions stays readable.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export MEILI_PROJECT_API_KEY=masterKey # only if the instance has a master key
meili get health
```

**Management API**, for the commands that manage Meilisearch Cloud, `meili get projects` or `meili create teams` (not shipped yet):
**Management API**, for the commands that manage Meilisearch Cloud, `meili get projects` or `meili add teams` (not shipped yet):

```sh
export MEILI_MANAGEMENT_TOKEN=... # a personal access token from the Meilisearch Cloud dashboard
Expand Down
5 changes: 3 additions & 2 deletions docs/adr/0003-commands-derive-from-openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ The grammar matters as much as the derivation. Coding agents have a strong prior
## Decision

- The OpenAPI documents are the reference for what a command is named and what it takes. `docs/contract/commands.md` states the naming rules, not the commands. `src/lib/api-naming.ts` implements the rules and `test/contract/api.test.ts` enforces them.
- The grammar is `meili <verb> <resources> [flags]`. The verb comes from the HTTP method, `get`, `create`, `update`, `delete`, or from a trailing literal of the route taken from a closed list, `cancel`, `compact`, `swap`. The resource is the last literal segment of the path, plural. The most typed commands, `search`, `similar`, `facet-search`, `multi-search`, `export`, carry no resource word, as recorded exceptions. Every value is a named flag: no positional argument, so an agent names what it passes and never has to know an order.
- The grammar is `meili <verb> <resources> [flags]`. The verb comes from the HTTP method, `get`, `add`, `update`, `delete`, or from a trailing literal of the route taken from a closed list, `cancel`, `compact`, `swap`. The resource is the last literal segment of the path, plural. The most typed commands, `search`, `similar`, `facet-search`, `multi-search`, `export`, carry no resource word, as recorded exceptions. Every value is a named flag: no positional argument, so an agent names what it passes and never has to know an order.
- `POST` is `add`, not `create`. Both words read well on `indexes` and on `keys`; only one of them reads well on `documents`, the most typed write command, which the API itself calls `documents.add`. `add` is also three letters and the word an agent reaches for first after `get`. The price is that the CLI's verb no longer echoes the `indexes.create` and `keys.create` key actions of the other resources: a command declares its key actions in `static keyActions`, read from the document, so no name is derived from the other.
Comment thread
curquiza marked this conversation as resolved.
- One command covers every route that shares its verb and resource, and the flags select the route: `get indexes` lists or, with `--id`, gets one; `delete documents` deletes by `--ids`, by `--filter` or everything with `--all`. Flags that call for two routes at once are a usage error. The command declares its routes, `static routes`, and the contract test checks that each is reachable by a distinct set of flags.
- Identifiers: the resource's own identifier is `--id`, or `--ids` when the list route filters by identifiers, in which case the unit route is not implemented and `--id` is an alias. A parent in the path is a flag named after its collection, `--index`. A query or body parameter that carries identifiers is named after the resource it identifies, `--ids` for the command's own resource, `--batch-ids` or `--task-ids` for another, whatever the API spelling, which stays accepted as an alias. Every other body field keeps its API name.
- The documents are downloaded, never committed. `pnpm openapi` fetches the Meilisearch API document from the latest release, or from the release in `MEILISEARCH_VERSION`, and the management document with a GitHub token that reads the private repository (`gh auth login` on a workstation, `MEILI_BOT_GH_PAT` in CI). A missing document fails the contract tests: nobody writes a command without seeing the route.
Expand All @@ -28,7 +29,7 @@ The grammar matters as much as the derivation. Coding agents have a strong prior
- Adding a command is one pull request: the command with all its routes, its tests, the manifest, a changeset, and an exception only if the rules give a wrong name. Fifty-odd commands cover the ninety-odd stable routes of the two APIs.
- `meili get --help` lists every resource on one screen. The agent learns one verb set and one flag convention, not a tree.
- The command tree follows the APIs. `pnpm api:coverage` prints every route with its command name and whether it exists.
- Some derived names read oddly to a human: `create indexes --id movies` creates one index, `create dumps` creates one dump. One spelling, always plural, beats two spellings an agent has to choose between. On a typo, the `cli_usage` error carries a hint with the closest existing command, "Did you mean `meili delete indexes`?", computed from the manifest; the CLI suggests, it never asks.
- Some derived names read oddly to a human: `add indexes --id movies` adds one index, `add dumps` takes one dump. One spelling, always plural, beats two spellings an agent has to choose between. On a typo, the `cli_usage` error carries a hint with the closest existing command, "Did you mean `meili delete indexes`?", computed from the manifest; the CLI suggests, it never asks.
- A command that filters a list instead of calling a unit route, `get tasks --ids 42`, returns the list envelope and the agent reads `results[0]`. In exchange the agent never has to know whether a route exists for one item.
- `BaseCommand` owns a small dispatcher: which route the given flags select, which flags conflict, which are ignored. Commands stay declarative: routes, flags, one call.
- Types come from the SDK and the generator, so `--help` descriptions and output shapes cannot drift from the API without a compile error. An undocumented parameter is a documentation bug to fix upstream before the flag exists in the CLI.
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0004-two-apis-one-credential-set-per-command.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ A command needs exactly one of the two. If the CLI resolved both on every call,
- `BaseCommand` owns credential resolution and client construction. It resolves only the declared API, builds only that client, and produces errors that name only that API's sources.
- The resolution order is the same for both APIs and has two levels: the flag, then the environment variable. A flag and its variable share their name, `MEILI_` plus the flag name in upper snake case (CLAUDE.md, rule 14). The sources are documented in `docs/contract/config.md`.
- Connections come from flags and environment variables. A config file is not decided; if one comes, it comes with its own ADR.
- Management commands carry no prefix. The resource says which API a command talks to, `get projects`, `create teams`, and `static api` says which credentials it needs. No resource name collides between the two documents today; a collision would be an exception in `commands.md`.
- Management commands carry no prefix. The resource says which API a command talks to, `get projects`, `add teams`, and `static api` says which credentials it needs. No resource name collides between the two documents today; a collision would be an exception in `commands.md`.

## Consequences

Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0007-delete-commands-require-yes.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Destructive, in `output.md`, names the verb `delete` and nothing else: what the
## Consequences

- A `delete` command without `--yes` fails like any command missing a required flag: `cli_usage`, exit 2, before the credentials are read and before any network call. No new error code, nothing new for an agent to parse, and `--help` and `meili schema` show `--yes` as required.
- The rule about verbs lives in the contract test, not in the binary: `test/contract/commands.test.ts` checks that every command under `src/commands/delete/` declares the flag and that no other command does. A `delete` command that forgets the group, or a `create` command that invents a confirmation of its own, turns the suite red before review. What the binary knows is narrower and enough: a required flag that oclif refuses to do without. Keying the refusal on the command's verb at runtime was considered and dropped: the verb comes from `this.id`, which oclif fills from the manifest, so it is empty when a test runs a command class directly, and the rule would then hold in the binary but not in the tests.
- The rule about verbs lives in the contract test, not in the binary: `test/contract/commands.test.ts` checks that every command under `src/commands/delete/` declares the flag and that no other command does. A `delete` command that forgets the group, or an `add` command that invents a confirmation of its own, turns the suite red before review. What the binary knows is narrower and enough: a required flag that oclif refuses to do without. Keying the refusal on the command's verb at runtime was considered and dropped: the verb comes from `this.id`, which oclif fills from the manifest, so it is empty when a test runs a command class directly, and the rule would then hold in the binary but not in the tests.
- The selector rule is unchanged: `delete documents` still refuses to run without `--ids`, `--filter` or `--all`.
- Every command line that deletes, in the documentation, in the examples and in the tests, carries `--yes`.
- This is a breaking change for the `delete` commands: a major version once the CLI is released, a minor one before 1.0.
Expand Down
Loading
Loading