Releases: hyzyla/kibana-agent
Release list
v0.7.4
Three fixes for results that looked like answers but were not: an empty aggregation with no warning, a confident zero-hint after a broken request, and a --sort value that failed far away from the mistake.
An aggregation that returns nothing while documents matched is now explained. search used to print {"total":3667,...,"buckets":[]} with a clean stderr. That happens when the matching documents do not carry the field — a must_not clause on a field also matches every document that lacks it, so must_not level=info selects exactly the unparsed startup banners that have no level. The CLI now runs one _count with exists on the field and says what it found:
Warning: aggregation 'lvl' is empty: none of the 3,667 matching documents has 'logs.level.keyword'. Read them with -f to see what they hold, or add {"exists":{"field":"logs.level.keyword"}} to -q.
Shard failures name their reason, and no zero-hint follows them. --sort asc on a wide pattern produced 45 of 93 shards failed, results are partial, followed by 0 hits, but the last 14d holds 58,897,422 documents. The filter is the problem, not the window. The filter was match_all. The other 48 shards were skipped by the date pre-filter, so no shard ran the sort at all, and the note blamed the wrong thing. The warning now ends with the first failure reason Elasticsearch gave, and the zero-diagnosis is skipped when shards failed:
Warning: 45 of 93 shards failed, so the result is incomplete: No mapping found for [asc] in order to sort on
--sort is checked before the request. A bare order such as asc was sent as a sort on a field named "asc" and died with ES 400: all shards failed. It now fails at once with the value to use: Use --sort @timestamp:asc to sort by the time field. A bad order names both valid ones, and the sort field is checked against the mapping the way -q and --aggs fields already were, with suggestions for a near miss. --sort <field> with no order still sorts desc.
v0.7.3
Makes context describe the indices you actually query, and describe them in a way that is usable on a wide index.
Profile notes now choose which patterns context describes. It profiles five patterns in full; before, those were the first five in alphabetical order, which on a real cluster meant five indices nobody had asked about while the ones named in the notes got nothing. Patterns named in the notes come first now, and the remaining slots fall back to the alphabetical order. Writing a note therefore improves the next context, so op_set_notes also drops the cached context — otherwise a new note would not take effect until the cache expired.
A wide mapping is summarised by namespace. A narrow index is still listed in full. A wide one used to show the first 60 fields alphabetically, which on a 1,439-field index meant a slice that stopped inside one namespace and hid everything else:
"logs.message": "text +keyword",
"logs.exception.*": "6 fields",
"logs.ctx.*": "1307 fields",
"logs.nginx.*": "63 fields",
"…": "1439 fields in total — run: fields <pattern> '<glob>'"A key ending in .* is a namespace, not a field — it says where to look, and fields <pattern> 'logs.ctx.*user*' lists what is inside. text +keyword says the field is analyzed and has a .keyword sibling, which is the question you ask before choosing between match_phrase and term.
An index whose fields share no prefix has no shape to show, so it falls back to a sample that leads with whatever namespaces do exist.
Measured on one cluster: the same five patterns went from a 60-field alphabetical slice each to 26–61 namespace entries each, and the whole context payload dropped from 12,865 to 7,780 bytes while covering the indices the notes name.
The bundled agent skill and agent-help describe both changes.
v0.7.2
Four fixes for output that was wrong or larger than it needed to be.
total is now marked when it is only a floor. Elasticsearch stops counting at 10,000 and reports "relation": "gte", which the CLI discarded. search and histogram now add "total_is_lower_bound": true and warn on stderr; context adds "docs_is_lower_bound" per pattern. Reading relation costs nothing — track_total_hits: true would have forced a full count on every search.
Warning: total=10000 is a floor, not the real count: Elasticsearch stops counting at 10,000.
#{"total":10000,"n":3,"total_is_lower_bound":true}
Use count, or sum the histogram buckets, for an exact number.
context is smaller. Index names ending in a sequence (-000397) never collapsed into a prefix, only dated ones did. Both collapse now, and anything left is grouped by common prefix when six or more names are siblings. On one cluster: 241 prefixes became 38, and the whole payload went from 13.3KB to 9.1KB.
The cache carries the package version. A payload written by another version is dropped instead of being served. Without this, a cache written before a shape change survives the upgrade and produces output that looks plausible and is wrong.
--expand-json makes tracebacks readable. Applications often log a whole JSON document into one string field, so an exception arrives escaped several layers deep. The flag parses those strings back into objects and splits multi-line strings into a list of lines. Available on search and tail, and as expand_json on the MCP tools.
kibana-agent search 'logs-*' --last 1h -n 1 -q '<query>' \
--expand-json --format pretty --max-source-len 6000The bundled agent skill and agent-help now cover the 10,000 cap and --expand-json.
v0.7.1
Adds kibana-agent cred-cache-ttl to control how long 1Password / keychain credentials stay cached.
kibana-agent cred-cache-ttl # show the value and where it comes from
kibana-agent cred-cache-ttl 3600 # cache for one hour
kibana-agent cred-cache-ttl 0 # disable caching, authenticate every call
kibana-agent cred-cache-ttl unset # drop the override, use the defaultPrecedence is KIBANA_AGENT_CRED_CACHE_TTL → config file → 24h default. Setting 0 stops the cache being read and written, so every call re-authenticates — useful when a Touch ID prompt per call is preferable to a credential sitting in the keyring.
The default is unchanged at 24h.
v0.7.0
Makes the CLI explain itself, so an agent stops reporting a wrong query as "no results".
Fixes
-fnow returns nested fields. It compared dotted paths against the flat top level, so everya.bselection was silently dropped and only@timestampcame back.rawprints the response body. Compact format iterated the response'shitsdict and printed its keys instead of the documents.- Caches are keyed per profile. Both the data cache and the keyring credential cache used the active profile, so
--profile Xcould read Y's data — or authenticate as Y. - An ES error returned with HTTP 200 is now a hard failure. It used to format as an empty result set, so an auth failure read as "no matching documents".
- Network and timeout errors give a clear message instead of a traceback.
Query hints — search, count, and histogram check the request against the mapping and explain empty results, all on stderr (--no-hints to disable):
- unknown field, with the closest matching names
term/termson an analyzed text field, naming the.keywordsibling- aggregation on a text field
--time-fieldabsent from the mapping, naming the date field the index really uses- index pattern matching no index
0 hits, but the last 1h holds N documents— distinguishes a wrong filter from an empty window- partial shard failures, and documents cut at
--max-source-len
Profile notes — kibana-agent profile note <profile> key=value stores what the cluster cannot tell you, such as which index serves which application. Notes appear first in context, live in the config file, and survive cache-clear. MCP clients get the remember_fact tool.
Richer context — now reports profile scope, ES version, and per pattern the detected time_field, first/last document time, document counts, and field counts. Wide mappings are capped with a pointer to fields <pattern> '<glob>'.
Also
cache-clearis scoped to the active profile, with--profile,--all,--creds-only,--data-only. Previously it wiped every profile's cache and all credentials.--time-fieldonsearch,count, andhistogram;mappingandfieldsnow list.keywordsubfields.- Alias and context cache TTLs raised from 1h to 24h.
v0.6.0
What's new
- MCP server —
kibana-agent mcpstarts an MCP stdio server exposing all read-only operations as tools (search, count, histogram, context, mapping, fields, aliases, tail, raw, discover URL, list profiles). Works with Claude Code, Claude Desktop, Cursor, and any MCP client. - Env-var bootstrap — set
KIBANA_URL+KIBANA_USERNAME/KIBANA_PASSWORD(or*_OP_REFfor 1Password) to use without creating a profile. Useful for MCP client configs and CI. - Refactored internals — business logic extracted into
client.pywith typed exceptions; CLI and MCP server both call the sameop_*functions. - Local dev stack —
docker-compose.ymlwith ES + Kibana + sample data for testing.
MCP setup (Claude Code)
claude mcp add kibana-agent -- kibana-agent mcpOr with env-var credentials:
{
"mcpServers": {
"kibana-agent": {
"command": "kibana-agent",
"args": ["mcp"],
"env": {
"KIBANA_URL": "https://kibana.example.com",
"KIBANA_USERNAME": "...",
"KIBANA_PASSWORD": "..."
}
}
}
}Full Changelog: v0.5.0...v0.6.0
v0.5.0
Linux/Windows support for the keychain auth type via the keyring library.
- Linux → Secret Service (GNOME Keyring / KWallet / KeePassXC)
- Windows → Credential Locker
- macOS → still uses
/usr/bin/securitysubprocess, unchanged (existing profiles keep working with zero migration; no per-item auth popups)
See jaraco/keyring#605 and jaraco/keyring#619 for the macOS ACL limitation that drove the platform split.
v0.4.0
What's New
- Kibana Space support — profiles now accept
--spaceto route API calls and Discover URLs through a specific Kibana space (/s/<space>/...) - Default index pattern — set
--indexon a profile so you can omit the index argument onsearch,count,tail,histogram,mapping,fields, anddiscover - Restrict flags —
--restrict-spaceand--restrict-indexlock a profile to its configured space/index, preventing accidental cross-space or cross-index queries - Profile display —
profile listnow shows space and index info with restriction status - Tests — added unit tests for
_space_prefixand_resolve_index
Usage
# Create a profile with space and default index
kibana-agent profile create prd --url https://kibana.example.com \
--auth 1password \
--op-username "op://vault/item/username" \
--op-password "op://vault/item/password" \
--space backend --index "logs-*" --restrict-index --use
# Index argument is now optional when profile has a default
kibana-agent search
kibana-agent count --last 1h
kibana-agent tailv0.3.0
What's new
- KQL support —
--kqlflag onsearch,count,histogram, andtailcommands. Uses a built-in recursive-descent parser that translates Kibana Query Language to Elasticsearch DSL.
kibana-agent search 'my-logs-*' --kql "level:ERROR and app:web"
kibana-agent count 'my-logs-*' --kql "status:(500 or 502)"
kibana-agent histogram 'my-logs-*' --kql "not level:DEBUG" --last 6hSupports: field matching, phrases, wildcards, exists, ranges (>, >=, <, <=), boolean operators (and/or/not), implicit AND, grouping, value lists, and nested queries.
v0.2.0
What's changed
- Fix rison encoder — now spec-compliant per rison spec; correctly quotes
@,*,:and strings starting with-or digits - Fix discover URLs — removed unsupported
indexparam for modern Kibana - Code quality — added ruff (lint + format), mypy (strict), renamed all cryptic variable names to descriptive ones, removed decorative comments, added type annotations