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
1 change: 1 addition & 0 deletions .changeset/first-version.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ First version of `meili`, the Meilisearch CLI built for coding agents.
- Tasks: `meili get tasks`, narrowed by `--ids`, `--statuses`, `--types`, `--index-ids`, `--batch-ids`, `--canceled-by` and the date filters of the route, paging with `--limit` and `--from` and following the `next` cursor with `--all`; `meili cancel tasks` and `meili delete tasks --yes`, which take the same filters and answer a task.
- Search: `meili search --index <index> --q <query>`, on `POST /indexes/{index_uid}/search`, with every non-experimental search parameter of the route as a flag, from `--filter` and `--sort` to `--hybrid` and `--vector`; `--data` or `--file` carries a whole search body, which a flag of the same field overrides. `meili multi-search --queries '<json>'`, on `POST /multi-search`, runs several queries in one request, each naming its index, answered side by side or merged into one ranked list with `--federation`.
- Settings: `meili get settings --index <index>`, which returns every setting with its current value or its default; `meili update settings --index <index>`, with one flag per setting, from `--searchable-attributes` and `--stop-words` to `--typo-tolerance` and `--embedders`, where a value of `null` resets that one setting and `--data` or `--file` carries a whole settings body; `meili delete settings --index <index> --yes`, which resets them all.
- Statistics: `meili get stats`, on `GET /stats` and `GET /indexes/{index_uid}/stats`, returns the counts and the sizes of the whole instance or those of one index with `--index`, with `--size-format human` and `--show-internal-database-sizes`.
- `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 set` and `config unset` manage the file.
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/add-command/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Then fill the class:
- `static keyActions = ["indexes.get"]`: the key actions the routes require, the specific one of each. Read the `security` of each route in the OpenAPI document, `GET /indexes` carries `["indexes.get", "indexes.*", "*"]`, and keep the first entry, the most specific one. Leave it out when every route declares `security: []` (`GET /health`) and on the Management API, whose personal access token carries no scope. `test/contract/api.test.ts` checks it against the document, so a wrong action fails the tests, it does not reach an agent.
- `static override description`: one sentence, ends with a period. It says what each flag combination does: "List the indexes of the instance, or get one with --id."
- `static override examples`: at least two, one of them with `--output json`, and one per route. Use `<%= config.bin %> <%= command.id %>`.
- `static override flags`: the path flags first (`index: Flags.string({ required: true, ... })` for a parent, `id: Flags.string(...)` for the resource itself, required only when every route takes it), then the query parameters and body fields in kebab-case of the API name, plus the shared groups. Identifier parameters are `id`, `ids`, `batch-ids`, `index-ids`, `task-ids` by what they identify, with the API spelling as `aliases: ["uids"]`; `id` is an alias of `ids` when the command has no unit route. Flags that call for different routes are `exclusive` of each other (`ids`, `filter`, `all` on `delete documents`), so that giving two is a usage error. Reuse the shared groups by spreading them: `BaseCommand.paginationFlags` (`--limit`, `--offset`, `--all`), `BaseCommand.waitFlags` (`--wait`, `--timeout`), `BaseCommand.bodyInputFlags` (`--file`, `--data`), `BaseCommand.confirmFlags` (`--yes`) on every command of the verb `delete`. A route that paginates with `from` (tasks, batches) needs its own group, added to `BaseCommand` with the first such command. Never give a flag a function as `default`: oclif would then load the class to render `--help`.
- `static override flags`: the path flags first (`index: Flags.string(...)` for a parent, `id: Flags.string(...)` for the resource itself, each with `required: true` only when every route of the command takes it), then the query parameters and body fields in kebab-case of the API name, plus the shared groups. Identifier parameters are `id`, `ids`, `batch-ids`, `index-ids`, `task-ids` by what they identify, with the API spelling as `aliases: ["uids"]`; `id` is an alias of `ids` when the command has no unit route. Flags that call for different routes are `exclusive` of each other (`ids`, `filter`, `all` on `delete documents`), so that giving two is a usage error. Reuse the shared groups by spreading them: `BaseCommand.paginationFlags` (`--limit`, `--offset`, `--all`), `BaseCommand.waitFlags` (`--wait`, `--timeout`), `BaseCommand.bodyInputFlags` (`--file`, `--data`), `BaseCommand.confirmFlags` (`--yes`) on every command of the verb `delete`. A route that paginates with `from` (tasks, batches) needs its own group, added to `BaseCommand` with the first such command. Never give a flag a function as `default`: oclif would then load the class to render `--help`.
- Value types (rule 7): `Flags.boolean` for a boolean, `Flags.integer` for an integer, `Flags.string` for a string, a comma-separated `Flags.string` for an array of scalars, a JSON-text `Flags.string` for an object or a nested array. The value `null` sends a JSON null.
- `run()`: `const { flags } = await this.parse(ClassName)`, then one branch per route, selected by the flags (`if (flags.id !== undefined)`), one call on `this.meilisearch` and one of the `output*` calls per branch. A flag of the list route given with `--id` is simply not sent. For lists, pass the query built from the flags, `{ limit: flags.limit, offset: flags.offset }` (the SDK drops undefined values), and `nextOffsetQuery` from `src/lib/pagination.ts`.
- Errors: do nothing. `BaseCommand.catch` renders API errors as they are. Throw `MeiliCliError` only for CLI-side validation, with a `hint` only when you know the next command to run.
Expand Down Expand Up @@ -135,7 +135,7 @@ So that a red `pnpm test` reads as a message, not as flakiness:
- every command has a description and two examples, one with `--output json`; every flag has a description, and a flag with a value a `helpValue` or options
- every command in `src/commands/` has its test file in `test/commands/`
- every path, `pnpm` script and `BaseCommand` member the guides cite exists; a path cited before it exists is listed as planned in `test/contract/docs.test.ts`, and the test asks to remove it from the list once it exists
- every command exposes the global flags of `commands.md`; a parent path parameter is a required flag; no command has a positional argument
- every command exposes the global flags of `commands.md`; a parent path parameter is a required flag when every route of the command takes it; no command has a positional argument
- the `meili schema` index is a snapshot, and so is one command in full
- a variable that doubles a flag is named `MEILI_` plus the flag name (rule 14)
- no `MEILI_*` literal in `src/` outside the table of `config.md`
Expand Down
2 changes: 1 addition & 1 deletion docs/contract/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Every API command declares its routes, `static routes = ["GET /indexes", "GET /i
1. **Verb.** The HTTP method gives it: `GET` is `get`, `POST` is `add`, `PUT` and `PATCH` are `update`, `DELETE` is `delete`. A trailing literal segment that is an action replaces it: `POST /tasks/cancel` gives `cancel`, `POST /indexes/{index_uid}/compact` gives `compact`, `POST /indexes/{index_uid}/documents/delete` gives `delete`. The literal verbs are a closed list: `delete`, `cancel`, `compact`, `swap`, `search`, `similar`, `facet-search`, `multi-search`, `export`. Adding one is a contract change.
2. **Resource.** The last literal segment of the path that names a resource, plural, in kebab-case of the API name, without the `/api/v2` prefix of the Management API: `indexes`, `documents`, `team-members`, `stats`, `health`. A singleton keeps its name: `get health`, `get version`, `get stats`. Five commands have no resource word, because they are the most typed: `search`, `similar`, `facet-search`, `multi-search`, `export`. They are listed in the exceptions.
3. **Several routes, one command.** Every route with the same verb and resource belongs to one command: `GET /indexes` and `GET /indexes/{index_uid}` are `get indexes`, `GET /stats` and `GET /indexes/{index_uid}/stats` are `get stats`, the three deletion routes of documents are `delete documents`. The flags choose the route: first the route whose path parameters are exactly the identifier flags given, then the route whose parameters cover the other flags given. Two flags that call for different routes, `--ids` and `--filter` on `delete documents` for example, are a usage error, `cli_usage`, exit 2, before any network call. A flag that belongs to the list route only, `--limit` or `--offset`, is ignored and not sent when `--id` selects the unit route.
4. **Identifiers.** The path parameter of the resource itself is `--id` when the unit route is implemented: `get indexes --id movies`, `delete keys --id <uid>`, `get projects --id 343`. When the list route can filter by identifiers, the unit route is not implemented and the list takes `--ids`, with `--id` accepted as an alias: `get documents --index movies --ids 42` calls the fetch route, `get tasks --ids 42` filters `GET /tasks`, `get batches --ids 7` filters `GET /batches`. The path parameter of a parent resource is a flag named after that collection, singular: `--index movies`. The verb-only commands take the index the same way: `search --index movies`.
4. **Identifiers.** The path parameter of the resource itself is `--id` when the unit route is implemented: `get indexes --id movies`, `delete keys --id <uid>`, `get projects --id 343`. When the list route can filter by identifiers, the unit route is not implemented and the list takes `--ids`, with `--id` accepted as an alias: `get documents --index movies --ids 42` calls the fetch route, `get tasks --ids 42` filters `GET /tasks`, `get batches --ids 7` filters `GET /batches`. The path parameter of a parent resource is a flag named after that collection, singular: `--index movies`, required when every route of the command takes it and optional when one of them does not: `--index` is required on `get settings` and optional on `get stats`, whose other route is `GET /stats`. The verb-only commands take the index the same way: `search --index movies`.
5. **Identifiers are named by what they identify.** A query or body parameter that carries identifiers, `uid`, `uids`, `batchUids`, `indexUids`, is named after the resource it identifies: the command's own resource gives `--id` or `--ids`, another resource gives `--<resource>-ids`. So `add indexes --id movies` and `add keys --id <uuid>` carry `uid`; `get tasks --ids 1,2 --batch-ids 7 --index-ids movies` carries `uids`, `batchUids` and `indexUids`; `get batches --ids 7 --task-ids 1,2` carries `batchUids` and `uids`, because bare `uids` are task identifiers everywhere. The API spellings, `--uid`, `--uids`, `--batch-uids`, `--index-uids`, are accepted as aliases, absent from `--help` and `meili schema`. One exception: on `update indexes`, `--id` is the index itself, so the new `uid` is `--new-id`, alias `--uid`. Every other body field keeps its API name, `--primary-key`, `--name`.
6. **Other flags.** The query parameters and the first-level fields of the JSON body, in kebab-case of the API name: `retrieveVectors` is `--retrieve-vectors`, `teamId` is `--team-id`, `primaryKey` is `--primary-key`. When a body has a single object property named after the resource, `{"project": {"name": …}}` in the Management API, the fields of that object are the flags. The only flags that do not come from a route are the global flags and the CLI-owned flags: `--wait`, `--timeout`, `--all`, `--file`, `--data`, `--yes`.
7. **Values.** A scalar is passed as it is. A boolean is a bare flag, `--retrieve-vectors`, unless the route also reads `false` from it, as the settings do: it then takes its value, `--facet-search false`, because a bare flag says neither `false` nor `null`. An array of scalars is comma-separated, `--fields id,title`, as the API's own GET syntax. An object or a nested array is JSON text, `--typo-tolerance '{"enabled":false}'`. The value `null` sends a JSON null, `--expires-at null`, `--stop-words null`.
Expand Down
213 changes: 213 additions & 0 deletions oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3894,6 +3894,219 @@
"settings.js"
]
},
"get:stats": {
"aliases": [],
"args": {},
"description": "Get the statistics of the whole instance, or those of one index with --index.",
"examples": [
"<%= config.bin %> <%= command.id %>",
"<%= config.bin %> <%= command.id %> --output json",
"<%= config.bin %> <%= command.id %> --index movies",
"<%= config.bin %> <%= command.id %> --index movies --size-format human --show-internal-database-sizes"
],
"flags": {
"output": {
"char": "o",
"description": "json (default): one document, the API body as it is, indented in a terminal, compact when piped. ndjson: one item per line, list commands only, streams with --all.",
"name": "output",
"hasDynamicHelp": false,
"helpValue": "<mode>",
"multiple": false,
"options": [
"json",
"ndjson"
],
"type": "option"
},
"debug": {
"description": "Print every HTTP request and response on stderr, secrets redacted.",
"name": "debug",
"allowNo": false,
"type": "boolean"
},
"project-url": {
"description": "Project URL, the Meilisearch instance to talk to.",
"name": "project-url",
"hasDynamicHelp": false,
"helpValue": "<url>",
"multiple": false,
"type": "option"
},
"project-api-key": {
"description": "Project API key. `-` reads it from stdin.",
"name": "project-api-key",
"hasDynamicHelp": false,
"helpValue": "<key>",
"multiple": false,
"type": "option"
},
"project": {
"description": "A project of the config file. Cannot be combined with --project-url. Required when the file has several projects.",
"name": "project",
"hasDynamicHelp": false,
"helpValue": "<name>",
"multiple": false,
"type": "option"
},
"index": {
"description": "The index to read the statistics of. Without it, the whole instance.",
"name": "index",
"hasDynamicHelp": false,
"helpValue": "<index>",
"multiple": false,
"type": "option"
},
"show-internal-database-sizes": {
"description": "Add internalDatabaseSizes to the response, the size of each internal database.",
"name": "show-internal-database-sizes",
"allowNo": false,
"type": "boolean"
},
"size-format": {
"description": "How the sizes are written: raw (default), a number of bytes, or human, a string with its unit.",
"name": "size-format",
"hasDynamicHelp": false,
"multiple": false,
"options": [
"raw",
"human"
],
"type": "option"
}
},
"hasDynamicHelp": false,
"hiddenAliases": [],
"id": "get:stats",
"pluginAlias": "@meilisearch/cli",
"pluginName": "@meilisearch/cli",
"pluginType": "core",
"strict": true,
"enableJsonFlag": false,
"api": "meilisearch",
"routes": [
"GET /stats",
"GET /indexes/{index_uid}/stats"
],
"keyActions": [
"stats.get"
],
"connectionFlags": {
"project-url": {
"description": "Project URL, the Meilisearch instance to talk to.",
"helpValue": "<url>",
"input": [],
"multiple": false,
"type": "option"
},
"project-api-key": {
"description": "Project API key. `-` reads it from stdin.",
"helpValue": "<key>",
"input": [],
"multiple": false,
"type": "option"
},
"project": {
"description": "A project of the config file. Cannot be combined with --project-url. Required when the file has several projects.",
"helpValue": "<name>",
"input": [],
"multiple": false,
"type": "option"
}
},
"waitFlags": {
"wait": {
"description": "Poll the task until it reaches a final status, then print the final task.",
"allowNo": false,
"type": "boolean"
},
"timeout": {
"description": "Maximum wait time, for example 30s, 2m. Default 60s. Only with --wait.",
"helpValue": "<duration>",
"dependsOn": [
"wait"
],
"input": [],
"multiple": false,
"type": "option"
}
},
"paginationFlags": {
"limit": {
"description": "Passed to the API.",
"helpValue": "<n>",
"input": [],
"multiple": false,
"type": "option"
},
"offset": {
"description": "Passed to the API.",
"helpValue": "<n>",
"input": [],
"multiple": false,
"type": "option"
},
"all": {
"description": "Fetch every page. Streams with ndjson, buffers into one array with json.",
"allowNo": false,
"type": "boolean"
}
},
"fromPaginationFlags": {
"limit": {
"description": "Passed to the API.",
"helpValue": "<n>",
"input": [],
"multiple": false,
"type": "option"
},
"from": {
"description": "Passed to the API: the uid the page starts at, going down.",
"helpValue": "<uid>",
"input": [],
"multiple": false,
"type": "option"
},
"all": {
"description": "Fetch every page. Streams with ndjson, buffers into one array with json.",
"allowNo": false,
"type": "boolean"
}
},
"bodyInputFlags": {
"file": {
"description": "Read the body from a file. `-` reads from stdin.",
"helpValue": "<path>",
"exclusive": [
"data"
],
"input": [],
"multiple": false,
"type": "option"
},
"data": {
"description": "Inline JSON body.",
"helpValue": "<json>",
"input": [],
"multiple": false,
"type": "option"
}
},
"confirmFlags": {
"yes": {
"required": true,
"description": "Confirm the deletion. Nothing is sent to the API without it.",
"allowNo": false,
"type": "boolean"
}
},
"isESM": true,
"relativePath": [
"dist",
"commands",
"get",
"stats.js"
]
},
"get:tasks": {
"aliases": [],
"args": {},
Expand Down
Loading
Loading