Skill
Read-only, agent-friendly Elasticsearch log query CLI
npx -y skills add chenwei791129/es-log-cli --skill skillAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Query Elasticsearch logs in read-only mode. Use when you need to fetch, search, or filter logs from ES (beats → index alias or datastream), inspect a target's field mappings/types, or spot cross-index type conflicts, and require "never run destructive operations" plus "agent-friendly JSONL output". Triggers on situations like "query ES logs", "search elasticsearch logs", "fetch errors from app-logs", "what fields does this index have", "find mapping type conflicts".
SKILL.md
11.3 KB, ~2.9k tokens by cl100k_base, as published. Nobody here has run it
es-log
es-log is a read-only Elasticsearch log query CLI. It guarantees at the code level that only read requests are issued (no PUT/DELETE/PATCH, no _rollover/_reindex). Output defaults to JSONL suited for jq/pipe, and it uses tiered exit codes so programs can branch reliably.
When to use
- You need to fetch or search logs from Elasticsearch (alias or datastream).
- You need to inspect a target's fields and types before querying, or spot fields whose type diverges across a target's backing indices (a common cause of partial shard failures).
- You need a safe query entry point that cannot accidentally hit destructive endpoints.
- You need structured output consumable line-by-line by
jq, or a full payload with metadata/total.
Do not use for: any write/maintenance operation (intentionally not provided), live tail (--follow not yet supported), or ES 7.x (only 8.x+ is supported).
Prerequisites
es-logis installed onPATH.- The config file
~/.config/es-log/config.yamlexists (see README). Override with--configor$ES_LOG_CONFIG. - Every ES command must specify a context: use
--context/-c <name>or set$ES_LOG_CONTEXT. There is no hidden current-context.
Global flags
-c, --context <name>: select the context to connect to (or set$ES_LOG_CONTEXT).-o, --output <jsonl|json|table>: output format.searchdefaults tojsonl; other commands default tojson.--quiet: suppress warnings and noise so stdout carries only results.--config <path>: config file path.
Commands
es-log config get-contexts
List all context names in the config file (no context required).
es-log config get-contexts # JSON string array
es-log config get-contexts -o table
es-log config view
Print the resolved config. api-key/password are always redacted to *** (no context required, secrets not expanded).
es-log -c <ctx> ping
Hit GET /_cluster/health to verify connectivity and authentication. Exit 0 on success; exit 3 on connection/auth failure.
es-log -c <ctx> ls [aliases|datastreams]
List aliases and datastreams together; each row has name and type. ls aliases / ls datastreams lists a single type only. Datastream rows carry backing_indices_count; alias rows carry index_count.
es-log -c prod ls # combined view (JSON array)
es-log -c prod ls datastreams -o table
es-log -c <ctx> fields <target>
Run GET /<target>/_mapping and present it as a flattened list of field paths and types. Takes exactly one <target> (alias, datastream, comma-separated targets, or wildcard). Nested objects flatten to dotted paths (user.id), multi-fields become their own entries (message.keyword), and an object with no sub-properties is typed object.
When the target resolves to multiple backing indices and the same field has more than one type across them, that field is a type conflict (the common root cause of partial shard failures). Conflicts are surfaced in every format: table appends a ⚠ conflict marker, and json/jsonl set "conflict": true and add an "indices" per-index type breakdown. A field present in only some indices but with one consistent type is not a conflict. Defaults to json.
es-log -c prod fields app-logs # JSON array of {name, types, conflict}
es-log -c prod fields 'app-logs,web-logs' -o table
# Count fields whose type conflicts across indices
es-log -c prod fields 'app-logs,web-logs' | jq '[.[] | select(.conflict)] | length'
es-log -c <ctx> search -t <target> [flags]
Run POST /<target>/_search against <target> (an alias or datastream). The target can also be passed as a positional argument.
Key flags:
-q, --query <lucene>: Lucene query string (match_allwhen omitted).--since <dur>: relative time range, e.g.15m/1h/24h/7d. Mutually exclusive with--from/--to.--from <RFC3339> --to <RFC3339>: absolute time range.--timestamp-field <field>: timestamp field (default@timestamp), applied to both the range filter and the default sort.-n, --size <N>(alias--limit, default 50): fetch limit.--size 0fetches everything viasearch_after(auto-paginates beyondmax_result_window). AnNexceeding the window is truncated with a warning on stderr.--fields <a,b,c>: return only the specified_sourcefields.--sort <field:asc|desc>: sort order (default<timestamp-field>:desc).-i, --include <regex>/-e, --exclude <regex>(repeatable): client-side regex refinement against each hit's serialized_sourceJSON (independent of the-oformat).
Aggregation flags (run server-side aggregations on the same _search endpoint):
--terms <field>[:<size>]: terms bucketing on a keyword field (default bucket size 10, ordered bydoc_countdescending).--date-histogram <field>:<interval>: time bucketing. Interval suffixess/m/h/dmap tofixed_interval(any multiplier, e.g.5m,30s);w/M/ymap tocalendar_interval, which accepts only a multiplier of1(1w/1M/1y). Notemis minutes,Mis months.--metric <op>:<field>(repeatable): a metric aggregation whereopis one ofsum,avg,min,max,value_count.--cardinality <field>(repeatable): approximate distinct-value count.--aggs <json>: raw passthrough — the value is the contents of the Elasticsearchaggskey (a single JSON object). Use it for nested sub-aggregations, ordering by a sub-metric, and pipeline aggregations the structured flags cannot express.
Aggregation rules:
- Mutual exclusion: the structured flags (
--terms/--date-histogram/--metric/--cardinality) and--aggscannot be combined, and at most one bucketing flag (--termsxor--date-histogram) may be given. Violations (and a malformed--metricop, a non-positive--termssize, a missing/invalid--date-histograminterval, or an--aggsvalue that is not a JSON object) fail with exit code 2 before any request is issued. - Shared query/time:
--query,--since,--from/--to, and--timestamp-fieldbuild thequery/range portion and apply to both aggregation modes — you supply only the aggregation, es-log wraps it with the time range and Lucene query. - Default
size: 0: when any aggregation flag is present, the request returns no hits (buckets only).--size Noverrides this to also return N hits alongside the aggregations. Note this differs from a plainsearch, where--size 0means "fetch everything" — in aggregation mode--size 0(the default) means no hits, and there is no fetch-all behavior. - Bucket vs metric placement: with a bucketing flag, each
--metric/--cardinalitybecomes an in-bucket sub-aggregation; with no bucketing flag, each metric is a single top-level value. - Structured aggregations use fixed names: the bucket is
group, each metric is<op>_<field>(e.g.sum_bytes), and each cardinality iscardinality_<field>.
Lucene query examples
# Errors within the last hour
es-log -c prod search -t app-logs -q 'level:error' --since 1h
# Multiple conditions + field projection + client-side refinement
es-log -c prod search -t app-logs \
-q 'level:error AND service:checkout' \
--fields '@timestamp,level,message' \
--include 'timeout|deadline' --exclude 'healthcheck' \
--since 24h
# Fetch all matching results (auto-paginated)
es-log -c prod search -t app-logs -q 'status:500' --size 0 > errors.jsonl
# Absolute time range + custom timestamp field
es-log -c prod search -t metrics -q '*' \
--timestamp-field event.created \
--from 2026-06-01T00:00:00Z --to 2026-06-02T00:00:00Z
Aggregation examples
# Top services by hit count, with a metric sub-aggregation per bucket
es-log -c prod search -t app-logs -q 'level:error' --since 24h \
--terms service:10 --metric sum:bytes
# Traffic over time (5-minute fixed-interval buckets)
es-log -c prod search -t metrics --since 1h --date-histogram @timestamp:5m
# A single top-level metric (no bucketing) — one number back
es-log -c prod search -t app-logs --since 1h --metric value_count:status
# Raw passthrough: Top ISP by summed bytes, ordered by the sub-metric (B mode)
es-log -c prod search -t netflow --since 1h -o json \
--aggs '{"top_isp":{"terms":{"field":"isp","size":10,"order":{"bytes":"desc"}},"aggs":{"bytes":{"sum":{"field":"bytes"}}}}}'
Output formats
- jsonl (
searchdefault): one hit per line = raw_source, with no_id/_index/_scorewrapper. Best forjq -c,grep,head, and streaming. - json:
searchreturns{"total":N,"hits":[{"_id","_index","_score","_source"}]};lsreturns an array of row objects;fieldsreturns an array of{"name","types","conflict"}rows (a conflict row also carries"indices":{<index>:<type>}, omitted otherwise);config get-contextsreturns an array of name strings;config viewreturns the config object. - table: human-readable aligned columns.
fieldsrendersFIELD/TYPE, appending⚠ conflictto a divergent row.
--include/--exclude always match against the _source JSON, so the same pattern set yields identical filtering under both jsonl and json.
For aggregation queries the shapes are:
- json:
{"total":N,"aggregations":<block>,"hits":[...]}.aggregationsis the canonical-named block for structured mode or the caller-named block for raw mode;hitsis empty unless--size Nwas given. - jsonl: structured bucketing emits one flattened object per bucket —
{"key","doc_count","<metric>"...}with each metric reduced to its scalar value (date_histogram useskey_as_stringas the key); a structured top-level metric emits a single object of metric values; raw mode emits theaggregationsobject on one line. - table: structured bucketing renders aligned
key/doc_count/per-metric columns; a top-level metric renders a single row; raw mode prints theaggregationsJSON. Prefer-o jsonfor raw (--aggs) queries, whose arbitrary nesting cannot be flattened into rows.
Exit codes
| code | meaning |
|---|---|
| 0 | success |
| 2 | argument/config error (missing context, --since conflicting with --from/--to, missing target, unset secret ${ENV_VAR}, conflicting or malformed aggregation flags) |
| 3 | connection or authentication failure |
| 4 | target not found (ES 404 index_not_found) |
| 5 | incomplete results — search/aggregation got 200 OK but some shards failed (partial shard failure) |
Errors always go to stderr as plain text; on a hard failure (exit 2/3/4) stdout never contains result output. Exit 5 is different: the cluster responded 200 OK but reported failed shards, so the partial results are still written to stdout while a diagnostic naming the failed-shard count and reason goes to stderr — check the exit code before trusting the output. (fields never returns 5; it reads _mapping, not shards.)
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.