A small, scriptable CLI to read, search, health-check, and safely administer Splunk Enterprise over its documented REST API — built for AI CLI agents and humans alike.
Splunk is a platform that collects and searches machine data (logs, metrics,
events). Administering it usually means clicking through its web UI or calling
its REST API by hand. This tool wraps that API in one predictable command,
splunk, so a person — or an AI agent — can inspect a Splunk server, run
searches, and make carefully guarded changes from a terminal or a script. Reads
are always safe; anything that changes the server previews first, asks before
acting, and leaves an audit trail.
uv venv
uv pip install -e ".[dev]" # editable install with test deps
splunk --helpRequires Python 3.10+. A Nix dev shell is also provided: nix develop (or direnv allow) gives you a shell with Python, uv, and ruff ready.
Authenticate with environment variables (the token is never passed as a flag):
export SPLUNK_URL="https://your-search-head:8089"
export SPLUNK_TOKEN="<a Splunk authentication token>" # a JWT; the primary path
# optional: a Splunk session key is accepted instead, as SPLUNK_SESSION_KEY
# fallback: SPLUNK_USERNAME + SPLUNK_PASSWORD log in for a session key
# (discouraged — prefer a token; used by CI against Docker Splunk)
# optional:
export SPLUNK_CA_BUNDLE="/path/to/ca.pem" # custom CA for on-prem
export SPLUNK_VERIFY="true" # TLS verification (default true)TLS precedence: setting
SPLUNK_CA_BUNDLEalways enables verification against that CA and takes precedence overSPLUNK_VERIFY. To turn verification off entirely (e.g. a self-signed lab cert), leaveSPLUNK_CA_BUNDLEunset and setSPLUNK_VERIFY=false.
You can also authenticate with a session key instead of a token (splunk auth login
mints one; set SPLUNK_SESSION_KEY), and keep per-target settings in a config-file
profile chosen with --profile / SPLUNK_PROFILE (precedence: flag > env > profile).
Commands (singular-noun → verb):
splunk server info # connectivity, identity, version
splunk api get /services/data/indexes # GET-only raw escape hatch (any read endpoint)
splunk index list # list indexes
splunk index get main # one index
splunk search run --query 'index=_internal | stats count by sourcetype' --earliest -1h
splunk search run --query 'index=main' --export --max-rows 5000 # bounded stream from the export endpoint
splunk search list # running / finished search jobs
splunk search get <sid> # one job by SID
splunk search cancel <sid> # cancel a job (gated write)
splunk saved-search list --app my_app # saved searches in an app
splunk saved-search create nightly --search 'index=main | stats count' --app my_app --cron '0 2 * * *'
splunk health check # native health; exits 5 if warn/fail
splunk index create payments --max-gb 50 --frozen-secs 7776000 # gated writeMany admin resources — user, role, monitor-input, hec-token, macro,
eventtype, kvstore-collection, app, and more — are generated CRUD groups. Run
splunk --help to list them and splunk <group> --help for each.
Output is TTY-adaptive: a human-readable table on a terminal, JSON when piped or with
--output json. stdout is pure data; diagnostics and prompts go to stderr, so this is safe:
splunk index list --output json | jq '.data[] | select(.disabled) | .name'Most search and knowledge objects live in a namespace — an owner plus an
app (/servicesNS/<owner>/<app>/...). Two universal options set it:
splunk saved-search list --app my_app --owner nobody # narrow a read
splunk saved-search create nightly --search '...' --app my_app # writes require an app
export SPLUNK_APP=my_app SPLUNK_OWNER=nobody # or set defaults onceReads default to the - wildcard (every owner and app). Writes require an
explicit app and never fall back to the default search app, so an object is
never created somewhere you did not intend.
Every write — the index lifecycle, saved-search create/update/delete,
search cancel, and all generated-group mutations — is safe by default:
splunk index create payments --dry-run # preview the request; sends nothing
splunk index create payments # prompts for confirmation on a TTY
splunk index create payments --yes # --yes is required when non-interactiveA non-interactive write without --yes fails fast (exit 2) rather than hanging.
Each applied write is appended to a local audit log: $VCT_SPLUNK_AUDIT if
set, else $XDG_STATE_HOME/vct-splunk/audit.log, else
~/.local/state/vct-splunk/audit.log.
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | API / transport / operation error |
| 2 | usage or config error (e.g. write refused without --yes) |
| 3 | authentication error (401/403) |
| 4 | not found (404) |
| 5 | health check succeeded but some finding is warn/fail |
The JSON output — {"data": ..., "meta": ...} on success, {"error": {"code", "message"}} on failure — and the exit codes above are a stable, additive-only
contract: fields and codes may be added over time, never renamed or removed, so
scripts, AI agents, and a backend service can depend on them. Remote Splunk result
text is treated as data only and never drives a write.
The primary, certified target is Splunk Enterprise on-prem, using your own credentials against the documented REST API. It does not use, bundle, or proxy any Splunk-distributed app.
Splunk Cloud is supported transparently. When SPLUNK_URL is a
*.splunkcloud.com host, the CLI deduces the Cloud backend and the same flat
commands read via the Cloud ACS API instead of splunkd — you never pick a
backend. ACS reads need a SPLUNK_ACS_TOKEN (the stack is derived from the URL).
The ACS origin defaults to the commercial endpoint https://admin.splunk.com;
set SPLUNK_ACS_BASE_URL=https://admin.splunkcloudgc.com for FedRAMP stacks.
Cloud coverage is read-only and not yet certified against a live stack, so an
operation it can't serve stops with a clean unsupported_backend error (exit 4)
rather than guessing. splunk inspect reports the deduced backend and what it
supports for anyone who wants to know. An MCP wrapper is a planned follow-up.
.venv/bin/python -m pytest # unit tests (mocked HTTP)
SPLUNK_INTEGRATION_TEST=true .venv/bin/python -m pytest -m integration # against a live/Docker SplunkCI runs the integration suite against a Dockerized splunk/splunk on an x86 runner,
where KV Store (and its bundled MongoDB) runs natively. On Apple Silicon that MongoDB
needs an AVX instruction the emulated image lacks, so boot the local container with KV
Store disabled (server.conf [kvstore] disabled = true via the image's
default.yml / SPLUNK_DEFAULTS_URL). Everything except KV Store works locally; the
KV-Store-dependent checks run only in CI.
The package separates a Click-free core from a thin CLI shell:
src/vct_splunk/core/— plain functions and typed errors; never imports Click.src/vct_splunk/commands/— Click adapters (one module per command group) plus the sharedcontextandoutputhelpers.
Keep modules small and single-purpose. Tests mirror this layout under tests/unit/
and tests/integration/. See CONTRIBUTING.md for setup and the
pre-PR checks, and AGENTS.md for the architecture and conventions.
More at docs.jacobpevans.com.