ProjectOperations CLI · 2026
Elasticsearch CLI
A Go CLI for Elasticsearch clusters, indices, documents, Query DSL and SQL. Cluster health, index management and ad-hoc querying from one binary.
- Go
- Elasticsearch API
- Query DSL
- SQL
es cluster health --profile prodgreen 12 nodes 428 shards 0 unassigned es index list --profile prod -o json[{"index":"orders-2026.09","docs":1284122,"size":"3.1gb"}] es index delete orders-2024.01 --read-onlyblocked: read-only mode is on, no write was sentOperations CLI · Project
Elasticsearch CLI covers the operational work around a cluster, not only the searching. Health, nodes, shards, indices, aliases, templates, ingest pipelines, ILM policies, documents, Query DSL and SQL are all there. I built it because everything I did with Elasticsearch was a curl, and the curl was never the interesting part.
Working through raw HTTP has two problems. The first is friction: a URL, an auth header, a CA path and a JSON body retyped for every question. The second is worse. A read and a delete look identical at the shell, both a curl with a different method. Nothing stands between a tired operator and a dropped index.
Connecting to a cluster
Three credential methods are supported, and the client picks one from whichever values are present. Basic username and password, an Elasticsearch API key ID and secret, or a bearer token. Settings resolve in one order: flags, then environment variables, then the profile, then defaults. es login asks for the URL, the method, the credentials, a CA certificate and a TLS preference, tests the connection, then writes ~/.config/es-cli/config.yaml. --ca-cert takes a PEM file for a private CA and --insecure skips verification for a throwaway cluster. es config view prints the saved profile including credentials, which the documentation marks as sensitive output.
The command surface
| Group | Commands |
|---|---|
| Cluster | health, stats, settings, pending-tasks, allocation-explain |
| Indices | list, create, mappings, settings, rollover, reindex, alias, template |
| Documents | get, index, mget, bulk, delete |
| Search | query, sql, count, msearch, field-caps |
| Lifecycle | ilm create, explain, ingest simulate, node, shard |
Reads take -o table, -o json or -o yaml, with structured data on stdout and verbose logs on stderr. Writes are deliberately not uniform. Some return API data and some print only a success line, so the documented advice is to confirm a write with a read when a script needs a parseable result.
How safety is wired
Every command carries an annotation. mutates=true marks a write and interactive=true marks a prompt. --read-only, or ES_READ_ONLY=true, refuses anything marked as a write before a request leaves the process. Destructive commands prompt unless --confirm is passed, and --no-input removes prompts without approving anything, so a delete under --no-input fails rather than proceeds. --if-not-exists and --if-exists absorb the 409 and the 404 that make a rerun noisy. A test in cli-go/cmd/agent_safety_manifest_test.go walks every runnable command, hashes the list together with its annotations, and fails when that hash moves. It also fails any command whose verb reads as mutating but carries no annotation.
Choices and their cost
- Requests go straight to the REST API with no Elasticsearch SDK in between. Nothing hides a response or lags a server release. The cost is that response shapes are mine to keep current, and a major version change is my problem to check.
- No query language of my own. Query DSL bodies arrive through
-fas JSON or YAML, SQL through--queryor a.sqlfile, and bulk and multi-search take NDJSON exactly as the API does. Nothing has to be relearned. In return the CLI cannot help with a malformed query beyond repeating what the cluster said. search queryruns a match-all when-fis omitted, and merges--size,--fromand--sortinto the body when it is present. This is the one I would revisit. A flag that quietly rewrites a request body makes the request harder to reason about than passing the body alone.--insecureexists at all. A test cluster with a self-signed certificate is a real situation, and pretending otherwise pushes people toward worse workarounds. It sits in the docs next to--ca-certand a warning.
A provisioning run
export ES_URL=https://elasticsearch.example.com:9200
export ES_API_KEY_ID=key-id ES_API_KEY=key-secret
es cluster health -o json | jq -r .status
es index create app-events -f settings.yaml --if-not-exists
es ilm create logs-policy -f policy.json --if-not-exists
es document bulk -f seed.ndjson --index app-events -o json
es search query app-events -f query.json --size 20 --sort timestamp:desc -o json
Each step is safe to rerun, which is the point of the idempotency flags. The same script under ES_READ_ONLY=true stops at the first create rather than completing half of the work. That is also how the Reckon workspace hands the binary to an agent: read-only, no input, pointed at log indices.
Where it stops
It is an independent wrapper over the Elasticsearch HTTP API and not affiliated with Elastic. Coverage stops at cluster, index, document, search, ingest, ILM, node and shard work. Snapshots, security realms and roles, watcher and machine learning are absent. SQL depends on the server license, so a command can be present and still refused upstream. es node hot-threads returns plain text whatever -o says. Building needs Go 1.26 or later, against an API baseline checked at Elasticsearch 9.5.3. It is MIT licensed and ships one binary with es update for self replacement.
Next projectOperations CLI