{"id":51633706,"url":"https://github.com/rnwolfe/ufi","last_synced_at":"2026-07-13T11:31:08.505Z","repository":{"id":367573543,"uuid":"1281382794","full_name":"rnwolfe/ufi","owner":"rnwolfe","description":"An agent-friendly CLI for Ubiquiti UniFi Network — read-only by default, gated mutations, machine-discoverable, built to the Agent CLI Guidelines.","archived":false,"fork":false,"pushed_at":"2026-06-26T14:11:39.000Z","size":1492,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-26T16:09:37.305Z","etag":null,"topics":["agent-cli","ai-agents","cli","go","llm-tools","networking","ubiquiti","unifi"],"latest_commit_sha":null,"homepage":"https://ufi-two.vercel.app","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rnwolfe.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":"SUPPORT.md","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-26T13:56:30.000Z","updated_at":"2026-06-26T14:11:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rnwolfe/ufi","commit_stats":null,"previous_names":["rnwolfe/ufi"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/rnwolfe/ufi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnwolfe%2Fufi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnwolfe%2Fufi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnwolfe%2Fufi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnwolfe%2Fufi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rnwolfe","download_url":"https://codeload.github.com/rnwolfe/ufi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnwolfe%2Fufi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35421716,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-13T02:00:06.543Z","response_time":119,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["agent-cli","ai-agents","cli","go","llm-tools","networking","ubiquiti","unifi"],"created_at":"2026-07-13T11:31:07.884Z","updated_at":"2026-07-13T11:31:08.497Z","avatar_url":"https://github.com/rnwolfe.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# ufi\n\n**An agent-friendly CLI for Ubiquiti UniFi Network.**\n\nCapable UniFi CLIs exist — but they're built for humans, or speak the brittle reverse-engineered\ncontroller API. `ufi` is built for **AI agents** on Ubiquiti's **official** local Network\nIntegration API, with the [Agent CLI Guidelines](https://aclig.dev/) baked in: read-only by\ndefault, a single `--allow-mutations` gate (plus a reviewed-artifact `apply \u003chash\u003e` flow for\nconfig), machine-discoverable `schema`, structured errors + stable exit codes, bounded JSON, and\nprompt-injection fencing — all in one static binary.\n\n[![ci](https://github.com/rnwolfe/ufi/actions/workflows/ci.yml/badge.svg)](https://github.com/rnwolfe/ufi/actions/workflows/ci.yml)\n[![release](https://img.shields.io/github/v/release/rnwolfe/ufi?sort=semver)](https://github.com/rnwolfe/ufi/releases/latest)\n[![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)\n[![Agent CLI Guidelines v0.4.0: Full](https://aclig.dev/badge/agent-cli-guidelines-full.svg)](https://aclig.dev/conformance/)\n\n![ufi demo](./demo/ufi.gif)\n\n\u003c/div\u003e\n\n## Why ufi\n\n- **Read-only by default; mutations are gated.** Listing devices, clients, WiFi, firewall, DNS —\n  all safe. State-changing commands need an explicit `--allow-mutations`; a blocked one returns a\n  structured `MUTATION_BLOCKED` (exit 12), so an agent can tell \"ask permission\" from \"real error.\"\n- **High-stakes config is reviewed before it runs.** `network`/`firewall`/`acl`/`dns` writes don't\n  execute directly: `--dry-run` emits a plan + hash, and `ufi apply \u003chash\u003e` runs *exactly* that\n  plan — closing the time-of-check/time-of-use gap a blind confirmation opens.\n- **Built for agents, not just humans.** `--json` everywhere, `schema --json` (with a conformance\n  block), an embedded `agent` guide, `{error,code,remediation}` errors, a stable exit-code table,\n  and `--limit`/`--select`/`--cursor` so responses stay within an agent's context budget.\n- **Prompt-injection fenced.** A guest can name their device `Ignore previous instructions…`.\n  ufi wraps network-controlled names/notes `[UNTRUSTED_DATA_BEGIN]…[UNTRUSTED_DATA_END]` by default.\n- **Official API only.** Ubiquiti's versioned Integration API — no reverse-engineered endpoints,\n  so it stays stable as UniFi updates. Validated against UniFi Network 10.4.57.\n\n## Install\n\n```bash\n# Homebrew (macOS / Linux) — recommended\nbrew install rnwolfe/tap/ufi\n\n# Go (any platform; static, no CGO)\ngo install github.com/rnwolfe/ufi/cmd/ufi@latest\n\n# Shell script (macOS / Linux) — downloads a release binary, verifies its SHA-256, installs to ~/.local/bin\ncurl -fsSL https://uficli.sh/install.sh | sh\n```\n\nOr grab a prebuilt binary (linux/macOS/windows, amd64/arm64) from\n[Releases](https://github.com/rnwolfe/ufi/releases) — each ships with checksums, an SBOM, and\nbuild-provenance attestation.\n\n## Quickstart\n\nNo console needed for the first look — `ufi` describes itself offline:\n\n```bash\nufi --help\nufi agent                                  # the embedded agent guide (SKILL.md)\nufi schema | jq '{conformance, exit_codes}' # machine-readable contract\n```\n\nThen point it at your console. Generate an API key in the UniFi UI\n(**Settings → Control Plane → Integrations → Create API Key**), then:\n\n```bash\nexport UNIFI_HOST=https://192.168.1.1\nexport UNIFI_API_KEY=…          # or: printf %s \"$KEY\" | ufi auth login  (stored in the OS keyring)\n# self-signed console cert? add --insecure (or UNIFI_INSECURE=1)\n\nufi doctor --json                          # host / key / connectivity / TLS / perms\nufi device list --json --select id,name,model,state\nufi client list --json --limit 20\nufi device restart \u003cid\u003e --allow-mutations  # mutations are opt-in\n```\n\n## Authentication\n\n`X-API-KEY` — no OAuth, no session, no refresh. The key is read from **stdin** (`ufi auth login`)\nor the `UNIFI_API_KEY` env var, **never argv** (argv leaks to `ps`/`/proc`/shell history), and\nstored in the OS keyring with a `0600` file fallback on headless hosts.\n\n- `ufi auth login` — pipe the key on stdin; validates it against `GET /info` and stores it.\n- `ufi auth status --json` — reports which key is stored and whether it works (never prints it).\n- `ufi auth logout` — removes the **local** copy only (revoke on the console to invalidate it).\n- `ufi doctor --json` — full diagnostics with a fix for each failure.\n\nA UniFi API key is effectively **full-admin** (it bypasses per-admin RBAC) — treat it like a root\npassword. A missing/invalid key returns `AUTH_REQUIRED` (exit 4) naming the login command.\n\n## Commands\n\n```bash\nufi info                       # controller version\nufi site list                  # sites on the console\nufi device list|get|stats \u003cid\u003e # adopted devices + latest statistics\nufi device restart \u003cid\u003e            # (mutation) reboot a device\nufi device port-cycle \u003cid\u003e \u003cport\u003e  # (mutation) PoE power-cycle a switch port\nufi client list|get \u003cid\u003e       # connected clients\nufi client authorize|unauthorize \u003cid\u003e   # (mutation) guest access\nufi wifi list|get \u003cid\u003e         # WiFi broadcasts (SSIDs)\nufi voucher list                   # hotspot vouchers\nufi voucher create \u003cname\u003e --minutes N   # (mutation) generate vouchers\nufi voucher delete \u003cid\u003e                 # (mutation, idempotent)\nufi network|firewall|acl|dns|traffic-list …   # declarative config (preview → apply \u003chash\u003e)\nufi apply \u003chash\u003e               # (mutation) execute a previewed config plan\nufi auth login|status|logout|refresh\nufi doctor                     # diagnostics\nufi schema                     # machine-readable command tree, flags, exit codes, safety\nufi agent                      # print the embedded agent guide (SKILL.md)\nufi version [--check]          # installed version; --check pulls GitHub for the latest release\n```\n\n\u003e ufi never auto-updates. On the human path it prints a once-a-day upgrade hint to stderr\n\u003e (silent for agents; disable with `UFI_NO_UPDATE_CHECK=1`).\n\nGlobal flags: `--json`/`--format json|plain|tsv`, `--select a,b.c`, `--limit N`, `--cursor`/`--page`,\n`--allow-mutations`/`--write`, `--dry-run`, `--host`, `--site`, `--insecure`, `--no-fence`, `--no-input`.\n\n\u003e **Local-only for now.** The Site Manager **cloud** surface (`ufi cloud …`) is deferred — it\n\u003e returns `UNSUPPORTED` with a pointer to open an issue. Want it? [File one](https://github.com/rnwolfe/ufi/issues).\n\n## Exit codes\n\n`0` ok · `2` usage · `3` empty results · `4` auth required · `5` not found · `6` permission ·\n`7` rate limited · `8` retryable · `10` config · `11` unsupported (needs a console feature, e.g.\nZone-Based Firewall) · `12` mutation blocked · `13` input required · `130` cancelled.\nFull table: `ufi schema`.\n\n## Conformance\n\nufi conforms to the [Agent CLI Guidelines](https://aclig.dev/) **v0.4.0** at the **Full** level.\nThe contract version is machine-verifiable from the binary — `ufi schema` emits a `conformance`\nblock, so an agent can confirm the standard it was built against without trusting this README:\n\n```json\n{\n  \"conformance\": {\n    \"level\": \"Full\",\n    \"spec\": \"agent-cli-guidelines\",\n    \"version\": \"0.4.0\"\n  }\n}\n```\n\n## Safety\n\n- **Read-only by default**; every mutation is behind `--allow-mutations`, and declarative config\n  goes through the reviewed-artifact `apply \u003chash\u003e` flow.\n- **Untrusted text fenced** by default in agent mode (`--no-fence` to disable).\n- **Secrets** via stdin/env → OS keyring (`0600` file fallback); never argv; redacted in output.\n\nSee [SECURITY.md](./SECURITY.md) for the secret-handling threat model.\n\n## Contributing\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md) and [AGENTS.md](./AGENTS.md). Build/test:\n`go build ./... \u0026\u0026 go vet ./... \u0026\u0026 go test ./...`.\n\n## License\n\n[MIT](./LICENSE) © Ryan Wolfe\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frnwolfe%2Fufi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frnwolfe%2Fufi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frnwolfe%2Fufi/lists"}