# 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
```
