This file is the maintainer guide for humans and for coding agents. Read it fully before you change anything. The detailed contracts live in docs/contract/. The reasons behind the decisions live in docs/adr/. When this file and a contract file disagree, the contract file wins.
meili is a command line tool that talks to two APIs:
- the Meilisearch engine API (indexes, documents, search, settings, tasks, keys) on any instance, local or remote
- the Meilisearch management API, which manages Meilisearch Cloud (organizations, teams, projects, resources, regions)
meili is built for coding agents only. Every design choice favors the agent: no interaction, structured output, stable errors, self-description. Human comfort features are out of scope.
Package: @meilisearch/cli on npm. Binary: meili.
- Data goes to stdout. Everything else (logs, progress, warnings, debug) goes to stderr. Never print a message like "Done" on stdout.
- Only
src/lib/output.tswrites to stdout. Commands never callconsole.log,process.stdout.writeorthis.logdirectly. - Only the generated management API client and
meilisearch-jsmake HTTP calls. Never writefetchin a command. - Never prompt when stdin is not a TTY. Destructive commands require
--yesin that case and exit with code 7 without it. - Never open
$EDITOR. Every input comes from a flag, an environment variable, a file (--file) or stdin (--file -). - Command and flag names follow the API names. Do not invent synonyms.
indexUidin the API is<index>as an argument and--index-uidas a flag. - Errors are structured. Use
MeiliCliErrorfromsrc/lib/errors.tswith a code and an exit code from the table indocs/contract/output.md. Neverthrow new Error("..."). - Secrets never appear in output, logs or debug traces. Use the redaction helpers in
src/lib/config.ts. - No new dependency without a one line justification in the pull request. Prefer the standard library.
- One command per pull request. One file per command. Every command has a snapshot test.
- Every command declares the API it uses with
static api = "engine" | "management" | "none".BaseCommandresolves credentials and builds the client. Never resolve credentials or build a client in a command. - Code and docs change together. If your change touches a command name, an argument, 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 intest/contract/fail on most of these drifts, so a redpnpm testafter a docs-free change is expected, not a flaky test. - 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.
- TypeScript, strict mode,
noUncheckedIndexedAccesson. Node 22 or newer. - oclif v5 (
@oclif/core5) for the command tree, topic separator is a space (meili index list). See ADR 0005. meilisearch-jsfor the engine API.@hey-api/openapi-tsgenerates the management API client from the spec committed atopenapi/management.yamlintosrc/lib/clients/management/generated/. Never edit generated files. Runpnpm gen:managementto refresh them. In code, commands, flags and environment variables,managementis the name for everything that touches the management API.Cloudonly appears in prose, as the product name.- vitest for tests, with snapshots.
mswmocks HTTP in unit tests. Integration tests run against a local Meilisearch and a local management API, seedocs/contract/testing.md. - Biome for lint and format.
- changesets for versioning and the changelog.
- pnpm as the package manager.
Explore the tree yourself. These are the rules that are not visible from the tree:
src/commands/holds one file per command. The file path is the command path:src/commands/management/project/list.tsismeili management project list. oclif derives the command tree from it, so moving a file renames a command.src/lib/output.ts,src/lib/errors.tsandsrc/lib/config.tsimplement the contract indocs/contract/. They change only through a contract pull request.src/lib/clients/management/generated/is generated fromopenapi/management.yaml. Never edit it.test/commands/mirrorssrc/commands/, one test file per command.docs/contract/is what the CLI promises.docs/adr/is why..claude/skills/holds the procedures agents follow.
Use the skill: /add-command <command path>, for example /add-command index stats. If the skill is not available yet, follow these steps by hand.
- Read
docs/contract/commands.mdand find the command. If it is not there, stop and ask. Do not add commands that are not in the contract. - Copy the closest existing command file.
src/commands/index/get.tsis the reference for a read command.src/commands/index/create.tsis the reference for a write command that returns a task. - Fill
description,examples(at least two, one of them with--output json),argsandflags. Reuse the shared flags fromBaseCommand(waitFlags,paginationFlags,bodyInputFlags). - Call the client. Read commands end with
await this.output(result). Write commands end withawait this.outputTask(task, flags). - Map errors. Do nothing for API errors,
BaseCommandmaps them. Add ahintonly when you know the next command the user should run. - Create
test/commands/<same path>.test.tsfrom the sibling test. It must snapshot the--helpoutput and the JSON output for one success case and one error case, withmswmocks. - Add one integration test in
test/integration/engine/ortest/integration/management/. Both APIs run locally: Meilisearch from thedocker composefile of this repository, the management API from themeilisearch-cloudrepository (seedocs/contract/testing.mdfor how CI starts it and which seeded token it uses). Unit tests still mock HTTP withmsw; integration tests hit the real thing. - Run
pnpm gen:manifestsooclif.manifest.jsonandmeili schemaknow the command. - Add a changeset:
pnpm changesetwith typeminorfor a new command. - Run the "Before you finish" list.
Run these commands and fix every failure before you open a pull request or say you are done:
pnpm lint
pnpm typecheck
pnpm test
Update snapshots (pnpm test -u) only when you changed the interface on purpose. A snapshot diff on a command you did not touch is a bug, not something to accept.
Then read the diff of your pull request once, from the point of view of docs/: does every name, flag, variable and code you added or renamed appear in the right contract file? The contract tests catch the mechanical part. The meaning is on you: a new behavior that docs/contract/ does not describe is a contract change and needs its own pull request first.
- Do not add a command, flag or output field that is not in
docs/contract/. Propose the change in the contract first, in its own pull request. - Do not add "human friendly" text to the JSON output. The table output is for humans.
- Do not wrap API responses. Return the API body as it is. See
docs/contract/output.md. - Do not catch errors to print them yourself. Let
BaseCommandrender them. - Do not add interactive features (menus, TUI, spinners on stdout). A spinner on stderr when stderr is a TTY is fine.
- Do not change
src/lib/output.ts,src/lib/errors.tsorsrc/lib/config.tsin a command pull request. These files carry the contract and change through their own pull request. - Do not touch
src/lib/clients/management/generated/. Fix the OpenAPI spec or the generator config instead. - Do not edit an accepted ADR. Write a new one.
docs/contract/commands.md: the full command tree, with the API route behind each commanddocs/contract/output.md: output modes, error format, exit codes, async tasks, paginationdocs/contract/config.md: connection resolution, environment variables, config file, secretsopenapi/management.yaml: the management API spec, source of the generated clientdocs/contract/testing.md: test levels, how the two local APIs start, what runs in pull request CIdocs/adr/: the decisions and the reasons