# Cirdan [![PyPI](https://img.shields.io/pypi/v/cirdanops)](https://pypi.org/project/cirdanops/) [![npm](https://img.shields.io/npm/v/%40cirdanops%2Fcli?logo=npm)](https://www.npmjs.com/package/@cirdanops/cli) [![npm installs](https://img.shields.io/npm/dt/%40cirdanops%2Fcli?logo=npm&label=npm%20installs)](https://www.npmjs.com/package/@cirdanops/cli) [![Python](https://img.shields.io/pypi/pyversions/cirdanops)](https://pypi.org/project/cirdanops/) [![Downloads](https://static.pepy.tech/badge/cirdanops)](https://pepy.tech/projects/cirdanops) [![CI](https://github.com/adanb13/cirdan/actions/workflows/ci.yml/badge.svg)](https://github.com/adanb13/cirdan/actions/workflows/ci.yml) [![License](https://img.shields.io/github/license/adanb13/cirdan)](LICENSE) [![Stars](https://img.shields.io/github/stars/adanb13/cirdan?style=social)](https://github.com/adanb13/cirdan/stargazers) **Cirdan is an on-call software repair agent.** A production alert fires — Grafana, Alertmanager, or Slack. Cirdan resolves the affected service and its repo from the explicit `services:` registry in `cirdan.yaml`, reproduces the failure in an isolated runner (Docker Compose or Kubernetes), has a coding agent fix the code — your own logged-in Claude Code / Codex CLI, or an API provider — re-verifies the fix against the pinned reproduction probe and the repo's test suite, and opens a verified pull request. When policy allows, it merges, deploys, and verifies production recovery, stopping at `manual_required` with full evidence when recovery fails. ``` alert ─▶ resolve service + repo (the services: registry) ─▶ reproduce in an isolated runner ─▶ coding agent fixes the code ─▶ verify (pinned repro probe + sandboxed test suite) ─▶ verified PR (@cirdan review loop; CI checks watched) ─▶ optional merge + deploy ─▶ verify recovery in production ``` The job it does: when an alert fires at 3am for a code-shaped failure, a verified fix PR (or a completed deploy plus recovery confirmation) is waiting instead of a page. Cirdan is deliberately **not** an infrastructure mapper, a topology explorer, an observability dashboard, a generic agent launcher, or a ChatOps bot. It repairs the services you register — nothing is discovered, and nothing is repaired implicitly. ## Quickstart ```bash pipx install "cirdanops[all]" # or uv / pip / npm / brew / curl / docker — see Install cirdan setup # opens the local, loopback-only setup UI ``` `cirdan setup` walks one short path and shows a readiness banner that flips to **"Ready to repair"** only when this install can actually fix something: 1. **Choose a pathway** — *Use the CLI* (the tools already logged in on this machine) or *Use the API* (managed credentials + a GitHub App). See [Two ways to run](#two-ways-to-run-use-the-cli--use-the-api). 2. **Connect an alert source** — a Grafana webhook (+ HMAC), native Alertmanager intake, or Slack. 3. **Select repair repositories** — in *Source Code Connection*, Cirdan lists the repos your `gh` login can push to; pick which ones it may repair. Only selected repositories are ever repaired. 4. **Prove routing** — the *Alert routing* step synthesizes a sample alert (or takes a real payload you paste) and must show it resolving to a selected repository **and** routing to a code-repair pipeline. 5. **Start the daemon** and post a demo alert: ```bash cirdand serve --http curl -X POST localhost:8090/v1/ingest/grafana -d @examples/demo/alert.firing.json ``` The runnable demo in [`examples/demo/`](examples/demo/) wires this end to end. The same checklist is available headlessly — `cirdan status --checks` prints the identical table and exits 1 until the install is ready. Useful setup flags: - `cirdan setup --terminal` — the text step flow instead of the UI - `cirdan setup --all` — run every step without prompting (scripts / CI) - `cirdan setup --system` — machine-level scope in `~/.cirdan` instead of a repo ## Install **From PyPI (recommended):** ```bash uv tool install "cirdanops[all]" # or pipx install "cirdanops[all]" pip install "cirdanops[all]" ``` **Without Python — npm / Homebrew / curl:** ```bash npx -y @cirdanops/cli serve-mcp # run the MCP server, zero install npm install -g @cirdanops/cli # or put the `cirdan` command on your PATH brew install adanb13/tap/cirdan # macOS / Linux (Homebrew) curl -LsSf https://raw.githubusercontent.com/adanb13/cirdan/main/packaging/install.sh | sh # Windows: irm https://raw.githubusercontent.com/adanb13/cirdan/main/packaging/install.ps1 | iex ``` These ship a self-contained binary — no Python 3.11+ required. Supported: macOS (arm64), Linux x64/arm64 (glibc), Windows x64. On Intel Macs, Alpine/musl, or other platforms, use the PyPI install above. **Then set it up in one command** (see [Quickstart](#quickstart)): ```bash cirdan setup # inside a repo # or, machine-level scope in ~/.cirdan: cirdan setup --system ``` Both open the same guided setup: choose a pathway, connect an alert source + GitHub, select repair repositories, prove routing, and start the always-on daemon. Targeted installs: `cirdanops[mcp]`, `cirdanops[api]`, `cirdanops[slack]`, `cirdanops[agent]`, `cirdanops[github-app]`, `cirdanops[secrets]`, or combinations like `cirdanops[api,mcp]`. The sandbox runners need no extras — they use the `docker`/`kubectl` CLIs already on your PATH. **Prerequisites by platform** (Python 3.11+ and `uv` or `pipx`): ```bash # macOS brew install python@3.12 uv # Windows winget install astral-sh.uv # Ubuntu / Debian sudo apt install python3.12 python3-pip pipx # or get uv: curl -LsSf https://astral.sh/uv/install.sh | sh ``` **Docker (no Python needed):** ```bash # run the always-on repair daemon: alert receiver + isolated repair runner, # using the host's Docker for sandboxes docker run -d \ -v "$PWD":/workspace:ro \ -v /var/run/docker.sock:/var/run/docker.sock \ -p 127.0.0.1:8090:8090 \ ghcr.io/adanb13/cirdan ``` (or `docker compose up` with the provided [docker-compose.yml](docker-compose.yml)) **From source (latest unreleased):** ```bash pip install "cirdanops[all] @ git+https://github.com/adanb13/cirdan.git" ``` Two commands are installed: | Command | What it is | |---|---| | `cirdan` | Human/agent CLI | | `cirdand` | Long-running Always ON daemon | ### Local development (from a clone) Contributors work from a clone with an **editable** virtualenv, so changes to the working tree take effect immediately — no reinstall between edits. `make install` creates `.venv` and installs the package (distribution name `cirdanops`) editable with the `[all,dev]` extras — every runtime feature plus the test toolchain: ```bash git clone https://github.com/adanb13/cirdan.git cd cirdan make install # ≈ python3 -m venv .venv && .venv/bin/pip install -e ".[all,dev]" make test # .venv/bin/python -m pytest tests/ -q ``` Requires Python 3.11+. The manual two-line equivalent works on a standard Python; `make install` additionally handles the pip-less-venv case (it falls back to `python3 -m venv --without-pip` and bootstraps pip into the venv). The editable install puts both console scripts in `.venv/bin`, running working-tree code with no rebuild: ```bash .venv/bin/cirdan setup # or: .venv/bin/python -m cirdan.cli.main setup .venv/bin/cirdand serve --http .venv/bin/python -m pytest tests/test_slack_events.py -q # a single test module ``` Common Make targets (`make help` lists them all): | target | what it does | |---|---| | `make install` | create `.venv` + editable install with `[all,dev]` | | `make test` | run the full pytest suite | | `make build` | build the sdist + wheel into `dist/` | | `make setup-wipe DIR= [KEY=1]` | reset a project's onboarding so `cirdan setup` restarts from step 0 (`KEY=1` also drops the global master key) | | `make demo-up` … `make demo-clean` | build and drive the runnable stack in [`examples/demo/`](examples/demo/) | > On a headless box with no OS keyring, storing credentials during `cirdan setup` > needs a master key in the environment first: > `export CIRDAN_MASTER_KEY=$(.venv/bin/python -c 'import os,base64;print(base64.b64encode(os.urandom(32)).decode())')`. ## Two ways to run: Use the CLI / Use the API The first setup screen picks a deployment posture (`deployment_mode:` in `cirdan.yaml`), which gates everything after it: **Use the CLI** (`cli`, the default) — Cirdan runs with the tools already logged in on this machine. GitHub writes go through your `gh auth login`; the coding agent is a detected, logged-in Claude Code or Codex CLI, spawned headless inside the isolated repair workspace; the sandbox runner is your local Docker (or kubectl). The happy path needs **no stored keys and no GitHub App** — an env-var API key and a GitHub App remain available behind explicitly labeled "Advanced" toggles. **Use the API** (`api`) — headless and managed, for shared/enterprise deployments. Provider API keys are stored encrypted (`cirdan secret`; AES-256-GCM), GitHub writes go through a GitHub App (guided manifest onboarding) with short-lived per-repo installation tokens, and the enterprise security profile is derived automatically: inherited `gh` auth and host-spawned CLI agents are refused at config load. Both pathways end at the same **readiness contract** — the setup UI banner and `cirdan status --checks` render the same table: repair repositories selected (each with a resolvable repo) · an alert source connected · **a routing proof** (a sample or live alert demonstrably resolved to a selected repository and routed to a code pipeline) · `remediation.mode` ∈ {develop, deliver} · a live-probed coding agent · a verified GitHub write path including access to each selected repo · `git` on PATH · a working sandbox runner · the daemon running. Every red row shows the exact fix command or the setup step that resolves it. Legacy configs load forever: `deployment_mode: single-user` reads as `cli`, `business` as `api`, and the file text migrates on the next setup save. ## Configuration `cirdan setup` writes `cirdan.yaml` for you; everything is also hand-editable — see [`cirdan.yaml.example`](cirdan.yaml.example). The heart of the file is the **`services:` registry**: the explicit list of services Cirdan may repair, how alerts resolve to them, and where their code lives. ```yaml services: - name: checkout-api # canonical id (lowercase, node-id-safe) repo: acme/checkout # "org/name" — the PR / repair target aliases: [checkout, checkout-svc] # extra alert-label names for this service match_labels: {team: payments} # optional exact label matchers (first match wins) deploy: # how it runs in prod + how the runner rebuilds it kind: compose # compose | k8s | helm compose_service: checkout-api # defaults to the entry name compose_files: [docker-compose.yml] deploy_workflow: deploy.yml # per-service GitHub Actions deploy (optional) telemetry_service: checkout # Loki/PromQL service label; defaults to name - name: payments-api repo: acme/payments source_root: ~/src/payments # local checkout (cli pathway only) deploy: kind: k8s namespace: prod workload: payments-api # kubernetes_kind defaults to Deployment ``` Alerts resolve to a service by an explicit `cirdan_node` label, then `match_labels`, then a ladder of common labels (`service`, `service_name`, `app`, `job`, `deployment`, `statefulset`, `pod`, `container`, the host of `instance`, `annotations.service`, `alertname`) matched against names + aliases. There is **no implicit default**: an alert that matches nothing opens a triage incident and never auto-repairs "the only registered repo". **Routing policy.** Every alert source flows through one first-match-wins policy: ```yaml routing_policy: rules: - when: {service: checkout-api, severity: [high, critical]} pipeline: full_remediation # may merge/deploy — only under mode: deliver + the auto_* gates - when: {source: slack} pipeline: triage_only ``` With no matching rule, an alert that resolves to a registered service with a resolvable repo defaults to `reproduce_and_pr_only` at qualifying severity (`responder.severities`, default high/critical); everything else fails safe to `triage_only`. Pipelines, by decreasing depth: `full_remediation` · `reproduce_and_pr_only` · `lightweight_pr_only` · `triage_only`. **Safety gates** (under `source_hosts.github` and `remediation`): | gate | default | what it does | |---|---|---| | `require_verified_pr` | `true` | a PR opens only when the failure was reproduced (baseline probe failed) AND the fix verified (post-fix probe passed) | | `require_tests` | `false` | additionally require a passing sandboxed repo-test run before any PR | | `auto_merge` | `false` | enable GitHub-**native** auto-merge on Cirdan PRs — branch protection and reviewers still gate the real merge; Cirdan never force-merges | | `auto_deploy` | `false` | master switch for delivery: deploy the merged fix and re-verify in production | | `require_checks` | `true` | block deploy dispatch until the merge commit's CI checks are green (block-only: it can stop a deploy, never start one) | | `remediation.mode` | `develop` | ceiling on what any pipeline may do; `deliver` unlocks merge/deploy | A failed deploy or failed production verification stops the delivery at **`manual_required`**, with the failure evidence notified and audited — Cirdan stops at manual intervention (automatic rollback is deliberately deferred). ## Commands ```bash cirdan setup # guided setup: loopback UI (default) or --terminal cirdan install --project # scripted variant: hook agents, register MCP, arm responder, start daemon cirdan incidents # detection pass + list incidents cirdan explain # evidence-backed explanation cirdan respond # write the incident brief + invoke the responder once under a mode cirdan remediate # durable fix: isolated workspace → reproduce → fix → verify → PR cirdan deliver # manually drive an in-flight delivery (merge → deploy → prod-verify) cirdan watch # foreground event + incident stream cirdan status [--checks] # daemon status; --checks runs the full readiness checklist cirdan stop # stop the daemon for this scope cirdan secret set|get|list|delete # encrypted credential store (provider keys, App key) cirdan slack-app # emit the importable Slack app manifest for the configured intake cirdan preview-intake # dry-parse a sample payload; --source/--service runs the routing proof cirdan serve-mcp # MCP server (stdio; --transport streamable-http) ``` ## Always ON ```bash cirdand serve # ingest alerts, detect incidents, repair, deliver — forever cirdand serve --http # + the HTTP API (alert ingestion endpoints) cirdand serve --http --mcp # + MCP mounted at /mcp ``` The daemon runs supervised loops — access refresh, alert ingestion + incident detection, delivery, PR-checks watching, `@cirdan` comment handling, and the Slack inbox (plus the Socket Mode client and webhook wakers, as configured). A crashing loop logs and restarts; it never takes the daemon down. One instance per scope: a second `cirdand serve` (or `cirdan watch`) against the same scope is refused with the running pid. `cirdan status` / `cirdan stop` (also available on `cirdand`) inspect and shut it down. ## Agent integration Cirdan is also a tool surface for your coding agents: the same engine behind the CLI is exposed over MCP with **exactly six read/evidence tools** — `list_incidents`, `explain_incident`, `get_recent_errors`, `get_logs`, `get_state`, and `get_audit`. `get_logs` and `get_state` take a **registered service name** (or alias) from `services:` and resolve the live container/pod themselves. Everything returned is redacted. ```bash cirdan install --project # full guided setup (recommended) cirdan setup # re-run the guided setup anytime cirdan install --platform claude # .claude/skills/cirdan/SKILL.md + CLAUDE.md + .mcp.json cirdan install --platform codex # AGENTS.md + .codex/cirdan.md cirdan install --platform cursor # .cursor/rules/cirdan.mdc + .cursor/mcp.json cirdan install --platform gemini # GEMINI.md cirdan install --platform vscode # .github/copilot-instructions.md + .vscode/mcp.json cirdan install --platform windsurf # .windsurf/rules/cirdan.md (MCP: global config) cirdan install --platform roo # .roo/rules/cirdan.md + .roo/mcp.json cirdan install --platform cline # .clinerules/cirdan.md (MCP: editor UI) cirdan install --platform opencode # AGENTS.md + opencode.json cirdan install --platform goose # .goosehints (MCP: global config) cirdan install --platform generic # .agents/skills/cirdan/SKILL.md + AGENTS.md ``` `cirdan install --project` is a guided setup that leaves Cirdan running the repair loop, not just documented: 1. **detects the agents on your machine** (claude/codex/cursor/gemini/vscode/windsurf/opencode/goose config or CLIs) and writes instruction files for exactly those (plus generic `AGENTS.md`) 2. **registers the MCP server** in `.mcp.json` 3. **arms the incident responder** so daemon alerts route to your agent 4. **starts the always-on daemon** (detached; `cirdand status` / `cirdand stop`) Steps 3–4 are prompted (default yes) or driven by flags for scripts: `--responder/--no-responder`, `--daemon/--no-daemon`, `--all-platforms`. Re-run any of it later with `cirdan setup`, which shows each step's current state and only proposes what's missing. Installs are idempotent and never touch content outside Cirdan's own marker block. **Supported agents.** First-class auto-registration (native instruction file + MCP where the agent supports a writable config) covers Claude Code, Codex, Cursor, Gemini, VS Code (+ GitHub Copilot), Windsurf, Roo Code, Cline, opencode, and Goose. Any other agent works through the generic `AGENTS.md` + `.agents/skills/cirdan/SKILL.md`. Auto-detection finds claude/codex/cursor/gemini/vscode/windsurf/opencode/goose; pass `--platform roo` or `--platform cline` explicitly (they're VS Code extensions with no CLI to detect, and Cline keeps MCP config in editor storage, so add that one through its UI). For any MCP client not listed, register Cirdan by hand — no Python required: ```jsonc // npx form — zero install { "command": "npx", "args": ["-y", "@cirdanops/cli", "serve-mcp"] } // or, if `cirdan` is already on PATH (pip / npm -g / brew / curl install): { "command": "cirdan", "args": ["serve-mcp"] } ``` ## HTTP API With the `[api]` extra, `cirdand serve --http` exposes `/health`, `/incidents`, `/incidents/{id}`, `/incidents/{id}/explain`, `/audit`, the alert intake under `/v1/ingest/{grafana|alertmanager|…}` (plus `/v1/ingest/slack/events` and a signature-verified `/v1/ingest/github` webhook waker), `/v1/slack/interactions`, a minimal OTLP/HTTP JSON receiver at `/v1/logs`, and the setup UI + API under `/setup`. Add `--mcp` to mount the MCP server at `/mcp`. State-changing routes are guarded — see [SECURITY.md](SECURITY.md). ## Upgrading Nothing happens automatically — PyPI is pull-only, so an installed version keeps working as-is until you upgrade. New installs always get the latest release. Upgrading is one command, matching however you installed: | Installed via | Upgrade command | |---|---| | `uv tool install` | `uv tool upgrade cirdanops` | | `pipx install` | `pipx upgrade cirdanops` | | `pip install` | `pip install -U cirdanops` | | npm (`-g`) | `npm install -g @cirdanops/cli@latest` (or just `npx @cirdanops/cli@latest …`) | | Homebrew | `brew upgrade cirdan` | | curl installer | re-run the install script (it fetches the latest) | | Docker | `docker pull ghcr.io/adanb13/cirdan:latest` (then restart the container) | Two operational notes: existing `cirdan-out/` artifacts and the SQLite store are compatible across releases so far (a schema-version key + migration will land before 1.0). And the CLI prints a single dim line on stderr when a newer release exists (checked at most once a day, interactive terminals only) — silence it with `CIRDAN_NO_UPDATE_CHECK=1`. ### Upgrading from the mapping releases (≤ 0.9) Cirdan committed to a single product: the repair workflow. The infrastructure-mapping surface — the graph/exploration commands, runtime profiles, and their artifacts — was removed. Removed commands and what replaces them: | Removed command | What replaces it | |---|---| | `map`, `graph`, `enrich`, `fingerprint` | the explicit `services:` registry in `cirdan.yaml` — you declare what Cirdan may repair; nothing is discovered | | `query`, `show` | `cirdan incidents`, `cirdan explain `, and the MCP evidence tools (`get_logs`, `get_state`, `get_recent_errors`) | | `access` | `cirdan status --checks` — the readiness checklist | Old configs keep loading: `adapters:`, `enrich:`, and `profile:` keys are accepted and ignored (`profile` warns once — Cirdan always runs the repair workflow), and `deployment_mode: single-user`/`business` normalize to `cli`/`api`. The `[map]` and `[terraform]` extras are gone from packaging. Old artifacts (`infra.graph.json`, `INFRA_REPORT.md`, `infra.html`, `fingerprint.json`, …) simply stop being written — Cirdan never deletes your files. ## Security Cirdan mirrors the access of the process it runs in and never escalates; everything it writes is redacted and audited. The two postures draw different trust boundaries: "Use the CLI" runs your own logged-in tools with your own permissions, while "Use the API" enforces the enterprise profile (GitHub App + stored keys, no host CLIs). Details — including network behavior and why PRs can only ever target the configured repo — in [SECURITY.md](SECURITY.md). ## License Apache-2.0