This file teaches an AI agent how to drive `greenops-scan` non-interactively, read its `--json` output, and turn it into a prioritized, high-impact remediation plan for a human (or for further automated action). It assumes the agent has valid AWS credentials available (env vars, `~/.aws/credentials` profile, or an assumed role) and Node.js available to run `npx`. `greenops-scan` is a free, read-only CLI that scans an AWS account for idle and over-provisioned resources (EC2, S3, ECS/Fargate, E...
Scanned 8/18/2026
Install via CLI
openskills install spidgorny/greenops-scan# SKILLS.md — Using `greenops-scan` as an agent
This file teaches an AI agent how to drive `greenops-scan` non-interactively, read its
`--json` output, and turn it into a prioritized, high-impact remediation plan for a
human (or for further automated action). It assumes the agent has valid AWS
credentials available (env vars, `~/.aws/credentials` profile, or an assumed role) and
Node.js available to run `npx`.
`greenops-scan` is a free, read-only CLI that scans an AWS account for idle and
over-provisioned resources (EC2, S3, ECS/Fargate, EKS, Lambda, RDS, ElastiCache,
CloudFront, EBS snapshots/AMIs, Bedrock, and VPC NAT Gateway/Endpoint waste) and reports
estimated monthly cost savings ($) and carbon savings (kg CO2) per finding. It never
mutates infrastructure itself — it only reports and suggests exact `aws` CLI commands to
run.
## TL;DR for an agent
```bash
npx greenops-scan --non-interactive --json \
--profile <profile> --region <region>[,<region>...] \
> scan.json
```
Then parse `scan.json`, sort `findings` by `monthlySavingsUsd` descending, and report the
top N. See [Finding the biggest-impact issues](#finding-the-biggest-impact-issues) below
for the exact algorithm.
---
## Table of contents
- [Running it non-interactively](#running-it-non-interactively)
- [All CLI switches](#all-cli-switches)
- [Environment variables](#environment-variables)
- [Exit codes](#exit-codes)
- [Output formats](#output-formats)
- [The `--json` schema](#the---json-schema)
- [Finding the biggest-impact issues](#finding-the-biggest-impact-issues)
- [Turning findings into action](#turning-findings-into-action)
- [Safety rules for an agent](#safety-rules-for-an-agent)
- [Recipes](#recipes)
---
## Running it non-interactively
By default `greenops-scan` is an interactive TUI (it prompts for provider, profile,
region). An agent should always pass `--non-interactive` (or set
`GREENOPS_NON_INTERACTIVE=1`) plus enough flags/env vars to fully resolve provider,
profile, and region — otherwise the process exits with `EXIT_USAGE` (2) instead of
hanging on a prompt.
Minimum viable non-interactive invocation:
```bash
npx greenops-scan --non-interactive --json --profile prod --region eu-west-1
```
If `--profile`/`--region` are omitted, the CLI falls back to `AWS_PROFILE` /
`AWS_REGION` / `AWS_DEFAULT_REGION` env vars. If none resolve, it exits with code 2 and
a clear error on stderr — check for this before assuming JSON was produced on stdout.
## All CLI switches
```
Usage:
npx greenops-scan [options]
Selection (skip interactive prompts):
-p, --provider <name> Cloud provider: aws, gcp [GREENOPS_PROVIDER]
--profile <name> Cloud profile/project name [GREENOPS_PROFILE, AWS_PROFILE]
-r, --region <codes> Region code(s), comma-separated [GREENOPS_REGION, AWS_REGION]
-m, --modules <names> Scanner modules to run, comma-separated [GREENOPS_MODULES]
--non-interactive Never prompt; fail if a value is missing [GREENOPS_NON_INTERACTIVE]
Output:
--format <format> human | json | fix-commands [GREENOPS_FORMAT]
--json Shorthand for --format json
--fix-commands Shorthand for --format fix-commands
-q, --quiet Suppress non-essential stderr output [GREENOPS_QUIET]
-v, --verbose Extra diagnostics on stderr [GREENOPS_VERBOSE]
--save-report Also write the JSON report file to disk (machine formats)
--no-pdf Skip PDF report generation
Filters:
-s, --severity <levels> Comma-separated: critical,high,medium,low [GREENOPS_SEVERITY]
--min-cost <amount> Minimum monthly savings (USD) to include [GREENOPS_MIN_COST]
Other:
--endpoint-url <url> Custom API endpoint (e.g. LocalStack) [GREENOPS_ENDPOINT_URL]
-h, --help Show this help message
```
Notes for agents:
- `--modules` accepts a comma-separated subset of module names (see
[module names](#module-names-for---modules) below) if you only want to scan, say,
`ec2,rds` to keep the run fast. Omit it to run every module the credentials allow.
- `--region` accepts multiple comma-separated codes (e.g. `eu-west-1,us-east-1`) — the
scan runs across all of them and every finding in the JSON output carries a `region`
field so you know where each resource lives.
- `--severity` and `--min-cost` are applied **before** you ever see the JSON — prefer
filtering there over re-implementing the filter yourself, since it keeps the reported
`summary` totals consistent with what you receive.
- `--save-report` writes the same JSON document to a timestamped file on disk in
addition to whatever `--format` prints to stdout — useful if you want a durable
artifact for later diffing between runs.
- `--fix-commands` (see [Output formats](#output-formats)) is the only way to get
runnable `aws` commands locally; the plain `--json` output omits `remediation` /
`remediationSafety` fields by default (this CLI is free and never gates them behind a
paid plan check, but the shared report builder still strips them for the machine
format — use `--fix-commands` or read `recommendation` for a plain-English fix).
### Module names for `--modules`
`ec2`, `s3`, `ecs`, `eks`, `lambda`, `rds`, `elasticache`, `cloudfront`,
`ebs-snapshots`, `bedrock`, `vpc-endpoints`.
## Environment variables
All flags have an env var equivalent so an agent running in CI/automation doesn't need
to construct a shell command with flags at all:
| Env var | Equivalent flag |
|---|---|
| `GREENOPS_PROVIDER` | `--provider` |
| `GREENOPS_PROFILE` / `AWS_PROFILE` | `--profile` |
| `GREENOPS_REGION` / `GREENOPS_REGIONS` / `AWS_REGION` / `AWS_DEFAULT_REGION` | `--region` |
| `GREENOPS_MODULES` | `--modules` |
| `GREENOPS_NON_INTERACTIVE` (`1`/`true`) | `--non-interactive` |
| `GREENOPS_FORMAT` | `--format` |
| `GREENOPS_QUIET` (`1`/`true`) | `--quiet` |
| `GREENOPS_VERBOSE` (`1`/`true`) | `--verbose` |
| `GREENOPS_SEVERITY` | `--severity` |
| `GREENOPS_MIN_COST` | `--min-cost` |
| `GREENOPS_ENDPOINT_URL` | `--endpoint-url` |
Explicit CLI flags always win over env vars when both are set.
## Exit codes
| Code | Meaning |
|---|---|
| `0` | Scan completed, **no findings** matched the filters |
| `1` | Scan completed, **findings present** (non-interactive runs only) |
| `2` | Usage/configuration error (e.g. couldn't resolve profile/region non-interactively) |
| `3` | Runtime error (bad credentials, missing permissions, unexpected failure) |
**Agent behavior:** treat exit code `1` as success-with-findings, not failure — check
`$?`/exit status but still parse stdout for the JSON report. Only exit codes `2` and `3`
mean the run itself is broken (fix invocation or credentials before retrying).
## Output formats
| `--format` | What it produces | When to use it |
|---|---|---|
| `human` (default) | Colorized table + summary, for a person watching a terminal | Never for agent parsing — use `--json` instead |
| `json` | The full machine-readable `ScanReportDocument` on stdout, no ANSI codes | Default choice for an agent — parse this to prioritize findings |
| `fix-commands` | A shell script (comments + runnable `aws`/`kubectl`/`terraform` commands) on stdout | When you specifically want ready-to-run remediation commands, e.g. to write to a `.sh` file for human review, or to extract per-finding commands programmatically |
## The `--json` schema
`--json` prints a single JSON document (`ScanReportDocument`) to stdout. Top-level
shape:
```jsonc
{
"schemaVersion": "1.0",
"timestamp": "2026-08-18T09:12:03.000Z",
"provider": "aws",
"account": "123456789012",
"accountAlias": "my-company-prod",
"region": "eu-west-1",
"regions": ["eu-west-1", "us-east-1"],
"findings": [
{
"module": "ecs",
"resourceId": "ORSCluster/ORSService",
"resourceType": "ECS Service",
"region": "eu-west-1",
"issue": "Over-provisioned: 2 task(s) (8 vCPU, 16384 MB each) — 30-day avg: CPU avg 0.1%, Mem avg 1.3%",
"severity": "critical",
"monthlySavingsUsd": 288.05,
"savingsConfidence": "medium",
"monthlyCarbonSavingsKg": 5.92,
"recommendation": "Right-size this Fargate service — reduce vCPU/memory to match observed utilization",
"metadata": { "avgCpuPercent": 0.1, "avgMemoryPercent": 1.3 }
// "remediation" / "remediationSafety" are stripped from plain --json output —
// use --fix-commands to get the runnable `aws` command for this finding.
}
],
"summary": {
"totalFindings": 2,
"totalMonthlySavingsUsd": 393.17,
"totalMonthlyCarbonSavingsKg": 8.14,
"totalAnnualSavingsUsd": 4718.04,
"totalAnnualCarbonSavingsKg": 97.69,
"moduleBreakdown": [
{ "module": "ecs", "findingCount": 1, "monthlySavingsUsd": 288.05, "monthlyCarbonSavingsKg": 5.92 },
{ "module": "rds", "findingCount": 1, "monthlySavingsUsd": 105.12, "monthlyCarbonSavingsKg": 2.22 }
]
},
"billing": {
"periodStart": "2026-07-01",
"periodEnd": "2026-07-31",
"monthlyCostUsd": 7710.00,
"annualCostUsd": 92520.00,
"monthlySavingsPercentage": 5.1,
"annualSavingsPercentage": 5.1,
"serviceCosts": [{ "service": "Amazon ECS", "monthlyCostUsd": 2100.00 }],
"moduleCosts": [{ "module": "ecs", "monthlyCostUsd": 2100.00, "confidence": "high" }]
},
"remediationHint": "Detailed remediation steps and safety checks are available with a paid GreenOps plan.",
"upgradeUrl": "https://greenops.cloud"
}
```
### Field reference
**Top level (`ScanReportDocument`)**
| Field | Type | Notes |
|---|---|---|
| `schemaVersion` | string | Only bumped on breaking changes — safe to pin automation against |
| `timestamp` | ISO 8601 string | When the scan ran |
| `provider` | `"aws" \| "gcp"` | |
| `account` | string | Account ID / project ID |
| `accountAlias` | string \| null | Human-friendly account alias if resolvable |
| `region` | string | Primary/first scanned region |
| `regions` | string[] | All regions covered |
| `findings` | `ScanFinding[]` | The list to prioritize — see below |
| `summary` | `ScanSummary` | Pre-aggregated totals — trust these over re-summing `findings` yourself unless you've applied additional filtering |
| `billing` | `BillingSummary` (optional) | Present only if AWS Cost Explorer access is available; ties findings back to real last-month spend |
| `remediationHint` / `upgradeUrl` | string (optional) | Informational only — ignore for automation logic |
**Each `ScanFinding`**
| Field | Type | Notes |
|---|---|---|
| `module` | string | One of the [module names](#module-names-for---modules) |
| `resourceId` | string | Resource identifier (instance ID, bucket name, cluster/service, VPC ID, etc.) |
| `resourceType` | string | e.g. `"ECS Service"`, `"RDS Instance"`, `"VPC"` |
| `region` | string | Set on every finding by the scan runner |
| `issue` | string | Human-readable description, often includes the raw metrics behind the finding |
| `severity` | `"critical" \| "high" \| "medium" \| "low"` | See [prioritization](#finding-the-biggest-impact-issues) — **do not rely on severity alone**, it does not always correlate with dollar impact |
| `monthlySavingsUsd` | number | **The primary field to sort on** for dollar-impact ranking |
| `savingsConfidence` | `"high" \| "medium" \| "low"` (optional) | Confidence of the savings estimate — e.g. VPC Gateway Endpoint findings report `"low"` because NAT Gateway traffic can't be split by destination service. Weight low-confidence findings down when ranking, or surface confidence alongside the number so a human can judge |
| `monthlyCarbonSavingsKg` | number | Carbon-impact equivalent of the same finding — use for a secondary/carbon-first ranking |
| `recommendation` | string | Plain-English fix, always present, safe to show even without `--fix-commands` |
| `remediation` | string (optional, `--fix-commands` only) | Raw `aws`/`kubectl`/`terraform` command(s), possibly multi-line |
| `remediationSafety` | object (optional, `--fix-commands` only) | `destructive: boolean`, `warning?`, `backupCommand?`, `context?: { profile, region, accountId }` |
| `metadata` | object (optional) | Module-specific extra data (e.g. `avgCpuPercent`, `vpcId`, `natGatewayIds`) — useful for building your own custom scoring beyond `monthlySavingsUsd` |
## Finding the biggest-impact issues
The single field to optimize for is **`monthlySavingsUsd`**, since it is already
normalized across every module (a small idle Lambda and a big idle RDS instance are
directly comparable in dollars). Recommended algorithm for an agent:
1. Run with `--json --non-interactive` and no `--min-cost`/`--severity` filter first, so
you see the full picture before deciding what's "big enough" to report.
2. Parse `findings`, drop any with `monthlySavingsUsd <= 0` (defensive — should not
normally occur).
3. Sort descending by `monthlySavingsUsd`.
4. Prefer surfacing **top 5–10** findings to a human rather than the entire list — this
is a "biggest bang for the buck" tool, not an exhaustive audit report.
5. When two findings have similar `monthlySavingsUsd`, break ties by:
- `savingsConfidence` (`high` > `medium` > `low`)
- `severity` (`critical` > `high` > `medium` > `low`)
- `monthlyCarbonSavingsKg` if the user has stated a sustainability/CSRD priority
6. Use `summary.moduleBreakdown` to say things like *"ECS is your single biggest cost
waste category this month at $X/mo across N findings"* — useful for a
module-level narrative on top of the resource-level list.
7. If `billing` is present, compute `finding.monthlySavingsUsd / billing.monthlyCostUsd`
to express each finding as "% of your total AWS bill" — this is often more persuasive
to a human than a raw dollar figure.
Example `jq` one-liner for the same ranking, if you'd rather shell out than parse JSON
yourself:
```bash
npx greenops-scan --non-interactive --json --profile prod --region eu-west-1 \
| jq '.findings | sort_by(-.monthlySavingsUsd) | .[:5]
| map({module, resourceId, issue, monthlySavingsUsd, savingsConfidence})'
```
## Turning findings into action
- For a **read-only report to a human**: summarize the top N findings from
`--json`, using `recommendation` for the plain-English fix. Do not fabricate a CLI
command from `recommendation` text — if you need the actual command, re-run with
`--fix-commands` (or `--format fix-commands`) and extract the matching block by
`module`/`resourceId`.
- For a **remediation script**: run with `--fix-commands`, which emits a shell-safe
script with `#`-prefixed context comments (severity, module, resource, estimated
savings, confidence, and a `DESTRUCTIVE` warning + optional backup command when
applicable) directly above each runnable command. Treat every line starting with `#`
as non-executable context, and only present/run the command lines.
- **Never auto-execute `--fix-commands` output without human review**, especially any
block preceded by a `DESTRUCTIVE` warning comment. Surface the warning and the
`backupCommand` (if present) to the human before they approve running it.
## Safety rules for an agent
1. `greenops-scan` itself is read-only — running the scan is always safe. The remediation
*commands it prints* are not always safe (e.g. resizing an RDS instance causes
downtime); treat `remediationSafety.destructive: true` as a hard requirement to get
human sign-off before executing.
2. Never embed real account IDs, resource names, or profile names from scan output in
public-facing text (docs, marketing, shared reports) without checking with the user
first — scan output is inherently full of a customer's real infrastructure detail.
3. Respect `--min-cost`/`--severity` if the user has told you what "worth mentioning"
means to them; otherwise default to showing the full picture and let the ranking in
[Finding the biggest-impact issues](#finding-the-biggest-impact-issues) do the
filtering for you.
4. If `billing` is absent from the JSON, don't assume Cost Explorer access failed
silently and invent numbers — just omit any "% of total bill" framing.
## Recipes
**Fastest useful invocation (single profile/region, top savings only):**
```bash
npx greenops-scan --non-interactive --json --profile prod --region eu-west-1 \
| jq '.findings | sort_by(-.monthlySavingsUsd) | .[:10]'
```
**Multi-region sweep, only material findings:**
```bash
npx greenops-scan --non-interactive --json \
--profile prod --region eu-west-1,us-east-1,ap-southeast-1 \
--severity high,critical --min-cost 50 \
| jq '.summary'
```
**Just the modules relevant to a specific investigation (e.g. after the NAT Gateway "$1,000 AWS bill" lesson):**
```bash
npx greenops-scan --non-interactive --json --profile prod --region eu-west-1 \
--modules vpc-endpoints,ec2
```
**Generate a reviewable fix script without running anything:**
```bash
npx greenops-scan --non-interactive --fix-commands --profile prod --region eu-west-1 \
> fixes.sh
# Show fixes.sh to a human; do not execute automatically.
```
**Persist a durable JSON artifact alongside stdout output (e.g. for diffing scans over time):**
```bash
npx greenops-scan --non-interactive --json --save-report \
--profile prod --region eu-west-1 > scan-$(date +%F).json
```
No comments yet. Be the first to comment!