{"id":28904775,"url":"https://github.com/ops0-ai/ops0-cli","last_synced_at":"2026-05-15T19:01:21.579Z","repository":{"id":298390414,"uuid":"999810335","full_name":"ops0-ai/ops0-cli","owner":"ops0-ai","description":"Stop your AI agent from shipping insecure IaC. ops0 CLI sits between Claude Code, Codex or Gemini and your cloud, scanning every .tf the agent writes and blocking destroy commands before they run.","archived":false,"fork":false,"pushed_at":"2026-05-13T05:07:07.000Z","size":8802,"stargazers_count":64,"open_issues_count":5,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-13T06:39:58.263Z","etag":null,"topics":["ai-agents","audit-log","claude-code","cli-tool","codex","devsecops","gemini-cli","golang-cli","governance","iac","mcp","opentofu","oxid","policy-as-code","terraform"],"latest_commit_sha":null,"homepage":"https://ops0.com","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ops0-ai.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-10T20:26:33.000Z","updated_at":"2026-05-13T05:07:12.000Z","dependencies_parsed_at":"2025-07-06T22:22:29.886Z","dependency_job_id":"b5e27ee8-0023-4e35-a551-45b1ae0d44cc","html_url":"https://github.com/ops0-ai/ops0-cli","commit_stats":null,"previous_names":["ops0-ai/ops0-cli"],"tags_count":36,"template":false,"template_full_name":null,"purl":"pkg:github/ops0-ai/ops0-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ops0-ai%2Fops0-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ops0-ai%2Fops0-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ops0-ai%2Fops0-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ops0-ai%2Fops0-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ops0-ai","download_url":"https://codeload.github.com/ops0-ai/ops0-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ops0-ai%2Fops0-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33075216,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-15T11:35:32.926Z","status":"ssl_error","status_checked_at":"2026-05-15T11:35:31.362Z","response_time":103,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","audit-log","claude-code","cli-tool","codex","devsecops","gemini-cli","golang-cli","governance","iac","mcp","opentofu","oxid","policy-as-code","terraform"],"created_at":"2025-06-21T13:02:16.705Z","updated_at":"2026-05-15T19:01:21.569Z","avatar_url":"https://github.com/ops0-ai.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# ops0 CLI\n\n**Policy, lint, vulnerability, and cost guardrails for AI coding agents.**\n\nSits in front of Claude Code, Codex and Gemini CLI. Every IaC edit the agent\nmakes gets validated, linted, policy-checked, security-scanned, and\ncost-estimated server-side, with failures surfaced back to the model as a\nfailed tool call. Destructive commands are blocked before they run.\n\n[![Latest Release](https://img.shields.io/github/v/release/ops0-ai/ops0-cli?display_name=tag\u0026sort=semver)](https://github.com/ops0-ai/ops0-cli/releases/latest)\n[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)\n[![Go Report Card](https://goreportcard.com/badge/github.com/ops0-ai/ops0-cli)](https://goreportcard.com/report/github.com/ops0-ai/ops0-cli)\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/ops0-ai/ops0-cli/main/install.sh | sh\n```\n\n[Quick start](#quick-start)\n· [What runs on every edit](#what-runs-on-every-edit)\n· [How agents trigger it](#how-agents-trigger-it)\n· [ops0-scan.md](#the-ops0-scanmd-report)\n· [FAQ](#faq)\n\n\u003c/div\u003e\n\n---\n\n## Why this exists\n\nWhen humans write infrastructure, policy gates fire on the PR. CI runs the\nscanner, someone gets paged, the PR sits in review for hours.\n\nWhen an **agent** writes infrastructure, that loop is broken. The agent\nwill happily generate a public S3 bucket, an open security group, or an\noversized EC2 fleet. By the time CI catches it, the agent has moved on.\n\n`ops0` sits in front of the agent. It runs your organization's checks\nbefore the agent considers an edit done, blocks destructive commands\nbefore they execute, tells the agent how much its IaC will cost per\nmonth, and gates the change against the project's budget.\n\n## What it does\n\n| | |\n|---|---|\n| **Validates IaC after every edit** | A `PostToolUse` hook runs `ops0 validate` against every `.tf` / `.tofu` / `.hcl` / `.tfvars` file the agent writes. Server-side pipeline catches everything in one call. |\n| **Blocks destroy commands** | A `PreToolUse` hook intercepts `terraform destroy`, `tofu destroy`, `oxid destroy` and the `-destroy` variants. Override with `OPS0_ALLOW_DESTROY=1`. |\n| **Enforces project budgets** | If the cost estimate exceeds a project budget set in the ops0 dashboard, the gate fails. Agent gets told to optimize. |\n| **Writes a fresh report file** | `ops0-scan.md` is rewritten at the repo root after every run, so the agent reads one file to know the current state across all stages. |\n| **Speaks MCP** | `ops0 mcp serve` exposes `list_policies` and `check_compliance` to any MCP-compatible agent. Registered automatically with Claude Code on `ops0 init`. |\n| **Multi-project aware** | Walks up from the edited file to find the nearest `.ops0/config.json`. One repo with ten subprojects each maps to its own ops0 project. |\n| **Audit trail** | Every failed lint finding, policy violation, vulnerability, blocked destroy, and budget overrun is recorded against your API key in `Settings → API Keys → Activity`. |\n| **Works everywhere** | Hooks install at both project-level and user-level, so they fire no matter which directory Claude Code opens at. |\n\n## Quick start\n\n```bash\n# 1. Install\ncurl -fsSL https://raw.githubusercontent.com/ops0-ai/ops0-cli/main/install.sh | sh\n\n# 2. Auth: get a key at https://brew.ops0.ai/settings?tab=api-keys\nops0 login --api-base https://brew.ops0.ai\n\n# 3. Bind a repo (or any subdir) to an ops0 project\ncd ~/work/my-terraform-repo\nops0 init --project=\u003cproject-id\u003e\n\n# 4. Open Claude Code (or Codex/Gemini) in the repo and write some Terraform.\n#    Every .tf edit triggers the full pipeline. Try `terraform destroy`\n#    and watch it get blocked.\n```\n\nVerify the wiring:\n\n```console\n$ ops0 policies list\nNAME                              CATEGORY      SEVERITY  DESCRIPTION\nno-public-s3                      security      high      S3 buckets must not be public\nrequire-encryption-at-rest        security      medium    All storage must use customer-managed keys\ntag-required-cost-center          tagging       low       Every resource must carry a cost-center tag\n\n$ ops0 validate .\nops0 validate . (4 files, 8.2s)\n\n✓ Configuration is valid\n\ntflint: 0 error(s), 2 warning(s), 0 notice(s)\n  [WARNING] terraform_required_providers: Missing version constraint for provider \"aws\"\n\nscan: 14 passed, 8 failed (0 parsing errors). Severity: 1C / 1H / 6M / 0L\n  [CRITICAL] no-public-s3: S3 bucket has public read access\n  [HIGH]     require-encryption: S3 bucket is missing default encryption\n  ...\n\ncost: $284.50 / month across 6 resource(s)\n  $148.92   aws_db_instance.app (aws_db_instance)\n  $89.71    aws_instance.api (aws_instance)\n  ...\n\nbudget: ✓ $284.50/mo within project limit of $500.00/mo.\n```\n\n## What runs on every edit\n\nA single `ops0 validate` call fans out to **five** server-side stages, in\nthis order. The CLI returns the merged result; failures gate the hook.\n\n| # | Stage | Catches | Fails the gate when... |\n|---|---|---|---|\n| 1 | **Syntax validation** | parse errors, undefined variables, wrong attribute types | `terraform validate` returns invalid |\n| 2 | **Lint** (provider-aware) | wrong instance types, deprecated args, missing version constraints | lint errors (warnings/notices report only) |\n| 3 | **Policies + vulnerabilities** | your org's compliance rules, security findings (public buckets, open SGs, IMDSv1, unencrypted volumes, missing tags, etc.) | any finding at or above `--scan-fail-on` (default `high`) |\n| 4 | **Cost estimate** | monthly cost of all priced resources | informational unless step 5 triggers |\n| 5 | **Project budget** | per-project monthly limit from the ops0 dashboard | `enabled` AND `exceeded` AND `Block Deployments on Exceed` is on |\n\nAll five run in one HTTPS call. With the server-side provider cache warm,\nthe round-trip is ~5-12s per edit depending on repo size.\n\n## How agents trigger it\n\n`ops0 init` writes hooks at both project- and user-level `.claude/settings.json`,\nso the gate fires regardless of which directory Claude Code opens at.\n\n```\n┌──────────────────────────────┐\n│   Claude Code / Codex /      │\n│   Gemini CLI writes a .tf    │\n└──────────────┬───────────────┘\n               │\n               │ PostToolUse hook (.claude/settings.json)\n               ▼\n   ┌─────────────────────────────────┐\n   │   ops0 validate \"$file\"         │\n   │   (walks up to find             │\n   │    .ops0/config.json)           │\n   └─────────────┬───────────────────┘\n                 │ HTTPS (API key)\n                 ▼\n   ┌─────────────────────────────────┐\n   │   ops0 platform                 │\n   │   - syntax validate             │\n   │   - lint                        │\n   │   - policies + vulnerabilities  │\n   │   - cost                        │\n   │   - project budget              │\n   └─────────────┬───────────────────┘\n                 │\n                 ▼\n   ┌─────────────────────────────────┐\n   │  exit 0 → agent moves on        │\n   │  exit ≠ 0 → hook fails →        │\n   │  agent gets stderr, retries     │\n   │                                 │\n   │  Either way: ops0-scan.md       │\n   │  is rewritten with the latest   │\n   │  state.                         │\n   └─────────────────────────────────┘\n```\n\nThe user never has to ask \"did you run the scan?\". The gate is mechanical.\n\n## Destructive command blocking\n\nWhen the agent tries to run `terraform destroy` / `tofu destroy` / `oxid destroy`\n(or any `-destroy` variant) via Bash, the `PreToolUse` hook fires **before**\nthe command runs:\n\n```\nagent calls Bash with `terraform destroy -auto-approve`\n     │\n     ▼  PreToolUse hook reads the command\n     ▼  matches the destroy pattern\n     ▼  POSTs an audit row to ops0\n     ▼  prints the block message to stderr\n     ▼  exit 2  →  Claude Code aborts the Bash call\n```\n\nTo intentionally tear something down (sandbox, dev env), prefix with the\noverride:\n\n```bash\nOPS0_ALLOW_DESTROY=1 terraform destroy\n```\n\nThe override is still logged to the audit trail.\n\n## The ops0-scan.md report\n\nAfter every `ops0 validate` run, the CLI rewrites a markdown file at the\nbound repo root:\n\n```\n\u003crepo\u003e/ops0-scan.md\n```\n\nIt contains:\n\n- Generated timestamp + CLI version\n- Summary table (validate / lint / policies / cost / budget — one row each)\n- terraform validate errors, if any\n- Lint findings table\n- Failed policy + vulnerability findings table, ranked by severity\n- Cost breakdown, top 20 resources by monthly cost\n- Budget verdict (within limit / over by $X / blocked)\n\nThe file is overwritten on every run, so it's always the current truth.\nRead it across turns without re-running validate. Don't hand-edit it\n(the next tool call will throw your changes away).\n\nDisable with `--no-report`, or move it with `--report path/to/file.md`.\n\n## Multi-project monorepos\n\nOne repo, ten subprojects, each with its own policies and budget? That\nworks:\n\n```\nmy-monorepo/\n├── prod/\n│   ├── .ops0/config.json     ← projectId: prod\n│   └── main.tf\n├── staging/\n│   ├── .ops0/config.json     ← projectId: staging\n│   └── main.tf\n└── shared/\n    ├── .ops0/config.json     ← projectId: shared\n    └── main.tf\n```\n\nWhen the hook fires on `prod/main.tf`, the CLI walks up from that file's\ndirectory to find `prod/.ops0/config.json` and runs `ops0 validate` against\nthe `prod` project. Same edit in `staging/main.tf` resolves to `staging`.\nEach subproject's policies, vulnerability checks, and budget apply\nindependently.\n\n## Integrations\n\n### Claude Code\n\n`ops0 init` does the wiring for you:\n\n```bash\nops0 init --project=\u003cproject-id\u003e\n```\n\nThat single command:\n\n- Writes `\u003ccwd\u003e/.ops0/config.json` to bind the directory to a project.\n- Installs `PostToolUse` (validate IaC), `PreToolUse` (block destroys),\n  and `Stop` (end-of-turn re-check) hooks in `\u003ccwd\u003e/.claude/settings.json`.\n- Installs the same hooks in `~/.claude/settings.json` so they fire whatever\n  directory you open Claude Code at. The user-level hook is gated on a\n  `.ops0/config.json` walk-up so unrelated repos aren't validated.\n- Appends a fenced governance section to `CLAUDE.md` so the agent reads\n  the rules before generating IaC, and knows to read `ops0-scan.md` for\n  the current state.\n- Runs `claude mcp add ops0 ops0 mcp serve` so the agent can call\n  `list_policies` and `check_compliance` natively.\n\nRe-run `ops0 init --force` after upgrading the CLI to refresh the hook\nscripts.\n\n### Codex / Gemini CLI / any MCP client\n\n```bash\nops0 mcp serve\n```\n\nRun it as a stdio MCP server. Tools exposed: `list_policies`,\n`check_compliance`, `whoami`. Wire it up via your client's MCP config.\n\n## Commands\n\n| Command                          | What it does                                                          |\n|----------------------------------|-----------------------------------------------------------------------|\n| `ops0 login`                     | Authenticate with an API key from the ops0 settings UI                |\n| `ops0 init`                      | Bind the current directory to a project, install hooks, register MCP  |\n| `ops0 policies list`             | List policies in scope for the current directory's project            |\n| `ops0 policies check [path]`     | Lightweight scan (policies + vulnerabilities only, no init/lint/cost) |\n| `ops0 validate [path]`           | Full pipeline: syntax + lint + policies + vulnerabilities + cost      |\n| `ops0 mcp serve`                 | Run the MCP server over stdio                                         |\n| `ops0 telemetry blocked-command` | Record a destroy attempt blocked by the PreToolUse hook               |\n| `ops0 version`                   | Print version info                                                    |\n\n### `ops0 validate` flags\n\n| Flag | Default | Purpose |\n|---|---|---|\n| `--format pretty\\|json` | `pretty` | Output format. JSON is for piping into other tools. |\n| `--iac-type terraform\\|opentofu\\|oxid` | `terraform` | Which IaC flavor to dispatch to. |\n| `--cloud aws\\|gcp\\|azure\\|oracle` | (auto) | Hint for the lint plugins. |\n| `--scan-fail-on critical\\|high\\|medium\\|low` | `high` | Severity threshold for the policy/vulnerability gate. |\n| `--fail-on-warning` | `false` | Also exit non-zero on lint warnings. |\n| `--report \u003cpath\u003e` | `\u003cbound-dir\u003e/ops0-scan.md` | Where to write the report. |\n| `--no-report` | `false` | Skip writing the report file. |\n\n## Config files\n\n| Path | Scope | Purpose |\n|---|---|---|\n| `~/.ops0/config.yaml` | User-wide | Credentials and defaults (`chmod 0600`) |\n| `\u003cdir\u003e/.ops0/config.json` | Per-directory | Project binding. Commit this to git. |\n| `\u003cdir\u003e/.claude/settings.json` | Per-directory | Project-level Claude Code hooks |\n| `~/.claude/settings.json` | User-wide | User-level Claude Code hooks (fire from any workspace) |\n| `\u003cdir\u003e/ops0-scan.md` | Per-directory | Auto-generated scan report. Read it; don't edit it. |\n\n## FAQ\n\n**Does it send my Terraform to the cloud?**\nYes, over HTTPS, scoped by your API key. Files live in a tempdir on the\nops0 platform for the duration of the scan and are not persisted.\n\n**What happens if my CI runs `terraform apply`?**\n`apply` is intentionally not blocked. Blocking every `apply` would be\nunworkable. The right defense for `apply` is plan-aware, and that's on\nthe roadmap. Today the focus is preventing the agent from writing bad\nIaC in the first place, and preventing it from tearing down what's there.\n\n**How do I let `terraform destroy` through for a planned tear-down?**\nPrefix the command with `OPS0_ALLOW_DESTROY=1`. The block still gets\nlogged to the audit trail.\n\n**What if my org doesn't have project budgets set?**\nBudget enforcement is opt-in. Without it, cost is still computed and\nreported in the validate output and in `ops0-scan.md`, but never blocks.\n\n**I deleted `.ops0/config.json`. What happens?**\nThe user-level hook walks up and finds nothing, so it exits 0. The\ndirectory is unbound. Nothing weird happens.\n\n**I have ten projects in one repo. Will the hook know which one?**\nYes. The CLI walks up from the edited file to find the nearest\n`.ops0/config.json` and uses that project's policies + budget.\n\n**Does this work with anything other than Terraform?**\nToday: Terraform, OpenTofu, and Oxid via the `.tf`, `.tofu`, `.hcl`,\n`.tf.json`, `.tfvars`, and `.tfvars.json` extensions. Kubernetes\nmanifests are next.\n\n**Can the agent skip the gate?**\nNo. The hook fires mechanically on every Edit/Write/MultiEdit of an IaC\nfile. The agent has no way to opt out — exiting non-zero from the hook\nfails the tool call as far as Claude Code is concerned. That's the whole\npoint.\n\n## Build from source\n\n```bash\ngit clone https://github.com/ops0-ai/ops0-cli \u0026\u0026 cd ops0-cli\ngo build -o ops0 ./cmd/ops0\nsudo install -m 0755 ops0 /usr/local/bin/ops0\n```\n\nRequires Go 1.22 or later.\n\n## Contributing\n\nIssues and PRs welcome. Guardrails:\n\n- `go vet ./...` and `go test ./...` before pushing.\n- Hook scripts must work on macOS bash 3.2 and Linux bash 4+.\n- New telemetry fields need a paired migration in the `config-master` repo.\n- Telemetry calls are best-effort. Never block the CLI on a failed report.\n\n## Star history\n\nIf this is useful to you, star it. It's the cheapest signal that helps\nother teams find it.\n\n## License\n\nApache 2.0. See [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fops0-ai%2Fops0-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fops0-ai%2Fops0-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fops0-ai%2Fops0-cli/lists"}