Query CloudWatch OTLP metrics with PromQL — from your terminal.
CloudWatch exposes a Prometheus-compatible HTTP API for metrics ingested via
OpenTelemetry (OTLP), but there is no aws CLI command for it. To use it you
have to hand-sign an HTTP request with AWS SigV4 (service monitoring) against
https://monitoring.<region>.amazonaws.com/api/v1/.... That's fine for a one-off
curl, but painful for day-to-day exploration.
cwpromql fills that gap. It owns the signing, discovery, and rendering so you
can list metrics, run PromQL, and view charts and tables right in the terminal
— or emit JSON/CSV to pipe into jq and friends.
- How it works
- Install
- Authentication & IAM
- Quick start
- Command reference
- Output formats
- Watch mode
- Use with AI agents (SKILL.md)
- PromQL syntax notes
- Troubleshooting
- Project layout
- Development
- Roadmap
CloudWatch's PromQL API is Prometheus-compatible but authenticated with AWS
SigV4 (service name monitoring). cwpromql:
- Loads credentials via the standard AWS SDK for Go v2 chain (env vars, shared
config, SSO,
credential_process,--profile). - Signs each request with SigV4.
- Calls the Prometheus endpoints and renders the standard JSON envelope as a table, chart, or raw JSON/CSV.
| Endpoint | Used by | Purpose |
|---|---|---|
/api/v1/query |
query |
instant query (one value per series) |
/api/v1/query_range |
range |
time series over a window |
/api/v1/series |
series |
series matching a selector |
/api/v1/labels |
labels |
all label names |
/api/v1/label/<name>/values |
label-values, metrics |
values for a label |
No Go toolchain required. Grab the archive for your platform from the
latest release,
extract cwpromql, and put it on your PATH.
Prebuilt binaries are published for macOS and Linux (both x86_64 and arm64).
# Pick your platform: Darwin_arm64, Darwin_x86_64, Linux_arm64, Linux_x86_64
VERSION=0.1.0
PLATFORM=Darwin_arm64
curl -sSfL \
"https://github.com/lewinkedrs/cw-otel-cli/releases/download/v${VERSION}/cwpromql_${VERSION}_${PLATFORM}.tar.gz" \
| tar -xz cwpromql
# Move onto your PATH (e.g. ~/.local/bin or /usr/local/bin):
install -m 0755 cwpromql ~/.local/bin/cwpromql
cwpromql --versionEach release also ships a checksums.txt; verify with
sha256sum -c checksums.txt (macOS: shasum -a 256 -c checksums.txt).
macOS Gatekeeper may quarantine an unsigned download. If you see "cannot be opened because the developer cannot be verified", clear the quarantine flag:
xattr -d com.apple.quarantine ~/.local/bin/cwpromql.
Requires Go 1.26+.
git clone https://github.com/lewinkedrs/cw-otel-cli.git
cd cw-otel-cli
# Many corporate networks block the default Go module proxy (proxy.golang.org).
# If `go build` hangs on "dial tcp ... proxy.golang.org", fetch modules directly:
go env -w GOPROXY=direct GOSUMDB=off
# Build a local binary:
go build -o bin/cwpromql .
# …or install onto your PATH:
go install . # -> $(go env GOPATH)/bin/cwpromqlOr install a tagged version directly with the Go toolchain:
go install github.com/lewinkedrs/cw-otel-cli@v0.1.0If $(go env GOPATH)/bin isn't on your PATH, symlink the binary into a dir
that is (e.g. ~/.local/bin):
ln -sf "$(go env GOPATH)/bin/cwpromql" ~/.local/bin/cwpromqlAfter re-installing, if your shell still says "command not found", run
hash -r(bash) /rehash(zsh) or open a new terminal.
cwpromql uses your ambient AWS credentials — nothing to configure if the AWS
CLI already works. The region is resolved from your environment/profile
(AWS_REGION, AWS_DEFAULT_REGION, or the profile's configured region); use
the global flags to override the profile or region:
cwpromql --region us-west-2 --profile my-profile metricsThe calling identity needs these IAM actions (the ones behind the PromQL API):
cloudwatch:GetMetricData— instant and range queriescloudwatch:ListMetrics— series and label/metric discovery
Credentials are short-lived in many setups; if a call fails with a credentials
error, refresh them (e.g. ada credentials update …, aws sso login, etc.) and
retry.
# What metrics exist?
cwpromql metrics --filter http.server
# What values does a label take?
cwpromql label-values @resource.k8s.namespace.name
# Instant snapshot as a table
cwpromql query 'sum by ("@resource.k8s.namespace.name")({"up"})'
# Trend over the last hour as a chart
cwpromql range 'sum({"up"})' --since 1h
# Live, top-style refresh
cwpromql range 'sum({"up"})' --since 30m --watch 10sGlobal flags (apply to every command):
| Flag | Default | Meaning |
|---|---|---|
--region |
(from AWS env/profile) | AWS region; overrides AWS_REGION / profile |
--profile |
(chain) | AWS shared-config profile |
-o, --output |
per-command | table | chart | json | csv |
--limit |
0 (API max) |
max series to return |
--no-color |
false |
disable colored charts |
cwpromql metrics # all metric names
cwpromql metrics --filter http # case-insensitive substring filter
cwpromql metrics -o json | jq length # counthttp.server.active_requests
http.server.duration
http.server.request.body.size
http.server.request.duration
cwpromql labels
cwpromql labels --filter resource.k8s@resource.k8s.cluster.name
@resource.k8s.container.name
@resource.k8s.namespace.name
@resource.k8s.node.name
cwpromql label-values @resource.k8s.namespace.nameagent-observability amazon-cloudwatch claude-code
kube-system otel-demo prom-app-demo
Renders one row per series (default table).
cwpromql query '{"up"}'
cwpromql query 'sum by ("@resource.k8s.namespace.name")({"up"})'
cwpromql query '{"up"}' -o json | jq .SERIES VALUE
k8s.namespace.name=kube-system 6
k8s.namespace.name=otel-demo 2
k8s.namespace.name=prom-app-demo 2
6 series
Renders a colored multi-series ASCII chart (default chart).
| Flag | Default | Meaning |
|---|---|---|
--since |
1h |
look-back window (30m, 1h, 24h) |
--step |
auto | resolution step (60s); auto ≈ 240 points, 15s floor |
--watch |
off | re-render on an interval (10s); Ctrl+C to stop |
--height |
12 |
chart rows |
--width |
terminal | chart columns |
cwpromql range 'sum({"up"})' --since 1h
cwpromql range '{"up"}' --since 3h --step 60s
cwpromql range 'sum by ("@resource.k8s.namespace.name")({"up"})' --since 1h -o table-o table on a range shows a downsampled sparkline plus min/max/last per series:
SERIES TREND MIN MAX LAST
k8s.namespace.name=kube-system ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ 6.00 6.00 6.00
k8s.namespace.name=otel-demo ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ 2.00 2.00 2.00
Lists the series matching a selector over a time window.
cwpromql series '{"up"}' --since 1h
cwpromql series '{"claude_code.token.usage"}' --since 1h -o json | jq '.[0] | keys'Use -o json to see the full label set of each series — the default view
shows a shortened name.
-o |
query (instant) |
range |
|---|---|---|
| (unset) | table |
chart |
table |
series + value | sparkline + min/max/last |
chart |
— | colored ASCII line chart |
json |
Prometheus samples | Prometheus matrix |
csv |
series,value,timestamp | — |
Charts are colored by default; disable with --no-color (colors are ANSI escape
codes, so use --no-color when redirecting chart output to a file).
--watch <interval> turns range into a live, top-style dashboard: it clears
the screen and re-renders on the interval until you press Ctrl+C.
cwpromql range 'sum({"up"})' --since 30m --watch 10sThis repo ships a SKILL.md — a concise, agent-oriented guide that
teaches an AI coding assistant when and how to use cwpromql. It captures the
hard rules (OTLP metrics don't appear in aws cloudwatch list-metrics; never
hand-roll SigV4; every selector needs an exact metric-name matcher), the command
reference, the CloudWatch PromQL dialect (@resource. prefixes, delta counters),
worked examples, and common gotchas.
Why: without it, an agent asked to "check the GPU metrics" will often run
aws cloudwatch get-metric-data, find nothing, and wrongly conclude the metrics
are missing. SKILL.md redirects it to cwpromql and the right query syntax.
How to use it in your agent of choice — point the assistant at the file so it loads as context/rules:
- Kiro: copy or symlink it into your steering dir, e.g.
cp SKILL.md ~/.kiro/steering/cwpromql.md, or reference it from an existing steering file. - Claude Code: append its contents to
CLAUDE.md(or@importthe file) in your project or~/.claude/. - Codex: add it to your
AGENTS.md(Codex auto-loadsAGENTS.md), or paste the contents into the custom instructions. - Any other agent: paste the contents into the system prompt / custom
instructions, or just keep
SKILL.mdin the repo the agent has open — most file-aware agents will pick it up on their own.
The file is plain Markdown with no tool-specific syntax, so it drops into any of these without changes. Keep it updated alongside the CLI as commands evolve.
Metrics ingested via OTLP use dotted names and must be quoted inside braces:
{"http.server.duration_count"}
sum by ("@resource.service.name")({"http.server.duration_count"})
{"up","@resource.k8s.namespace.name"="otel-demo"}
Every selector must include an exact metric-name matcher. Regex-on-name
({__name__=~".+"}) or label-only selectors are rejected by CloudWatch with
HTTP 400. cwpromql catches this client-side with a clear message before
sending the request.
OTLP attributes are addressed by scope prefix:
| Scope | Attribute prefix | Example |
|---|---|---|
| Resource | @resource. |
@resource.service.name="cart" |
| Instrumentation | @instrumentation. |
@instrumentation.@name="cloudwatch.aws/ec2" |
| Datapoint | @datapoint. or bare |
type="input" |
| AWS-reserved | @aws. |
@aws.account="123456789012" |
Vended AWS metrics (once OTel enrichment is enabled) keep their CloudWatch names
and dimensions, e.g. {Invocations, FunctionName="my-api-handler"}.
dial tcp ... proxy.golang.org: i/o timeout during build
Your network blocks the Go module proxy. Run go env -w GOPROXY=direct GOSUMDB=off
and rebuild (fetches modules directly from GitHub).
command not found: cwpromql after install
$(go env GOPATH)/bin isn't on your PATH — symlink into ~/.local/bin (see
Install) — or your shell cached a miss: run hash -r / rehash.
Credentials error / HTTP 403
Refresh your AWS credentials and confirm the identity has cloudwatch:GetMetricData
and cloudwatch:ListMetrics. Check with aws sts get-caller-identity.
selector needs an exact metric name
Add a metric name to the selector, e.g. {"up"} or {__name__="up"}. See
PromQL syntax notes.
Empty results
The PromQL API only scans a 24-hour window; widen --since within that limit,
and confirm the metric exists with cwpromql metrics --filter <name>.
main.go entrypoint -> cli.Execute()
internal/awscfg/creds.go AWS default credential chain
internal/promql/ SigV4 client, typed vector/matrix decoding,
metric-name guard, unit + live tests
internal/cli/ cobra commands + table/chart/json/csv/sparkline rendering
make all # fmt-check + vet + test + build (see the Makefile for targets)
make build # -> ./bin/cwpromql
make cover # tests with a coverage summary
make live # live smoke test (needs AWS creds)Check the version of an installed binary:
cwpromql --versionShell completions (bash/zsh/fish/powershell) are generated by cobra:
cwpromql completion zsh > "${fpath[1]}/_cwpromql" # example for zshSee CONTRIBUTING.md for the full workflow.
Released under the MIT License.
- Query history file and saved queries.
- Label-value / metric-name autocomplete.
rate()/derived helpers and richer chart legends.series --by <label>grouping and full-label default view.
