Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Ops Unifi

DSecurity

OPS on-demand: This skill should be used when the user asks to \"unifi\", \"cameras\", or \"/ops:ops-unifi\"

192 stars
0 votes
0 copies
0 views
Added 9/3/2026
ai-agentsrustgoshellbashapisecurity

Works with

claude codecliapimcp

Security Analysis

D50/100
criticalAccesses system keychains or credential stores
criticalModifies startup scripts or system services for persistence
criticalModifies startup scripts or system services for persistence
criticalSends environment variables or credentials to an external URL

Scanned 9/3/2026

Install to Claude Code

$npx -y skills add Lifecycle-Innovations-Limited/claude-ops --skill ops-unifi --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ops Unifi?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Ops Unifi
[![Security: D — Skills Directory](https://www.skillsdirectory.com/api/skills/lifecycle-innovations-limited-ops-unifi/badge)](https://www.skillsdirectory.com/skills/lifecycle-innovations-limited-ops-unifi)

More formats (shields.io, HTML) on the badges page.

Download with Pro
Files
SKILL.md
---
name: ops-unifi
description: "OPS on-demand: This skill should be used when the user asks to \"unifi\", \"cameras\", or \"/ops:ops-unifi\""
argument-hint: '[status|sites|devices|clients|isp|sdwan|protect|snapshot|restart|block|predict|setup]'
allowed-tools:
  - Bash
  - Read
  - Write
  - Grep
  - Glob
  - Agent
  - TeamCreate
  - SendMessage
  - AskUserQuestion
  - WebFetch
  - WebSearch
effort: medium
maxTurns: 30
context: fork
---

# OPS ► UNIFI — Network Command Center (UniFi OS)

Load `ops-rules` before acting. Public repo (no personal data). Outbound: one draft → one approval → one send. If `AskUserQuestion` / `Workflow` are missing, follow Rule 10 in `ops-rules` (Hermes: numbered options / two-turn Telegram card; `delegate_task`).

Control Ubiquiti UniFi infrastructure across **all three official APIs**:

1. **Site Manager API** (cloud, `api.ui.com`) — fleet-wide oversight: every console, site, device, and ISP/WAN health metric across all your UniFi deployments in one call. Read-first (write endpoints roll out per-key).
2. **Network Integration API** (local, `https://<gateway>/proxy/network/integration/v1`) — per-console control: list/restart devices, list/block clients, manage hotspot vouchers, read live statistics.
3. **Protect Integration API** (local, `https://<host>/proxy/protect/integration/v1`) — cameras, NVR, sensors, lights, chimes, viewers: list, snapshot, stream, and patch device settings; real-time WebSocket event stream.

This skill is **curl-native** (works headless, no SDK dependency) — consistent with the rest of `/ops:*`. If you want a richer natural-language surface, an optional community MCP can be enabled (see **Optional MCP path** at the end); the skill never depends on it.

## Runtime Context

Before executing, load available context:

1. **Preferences**: Read `${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json`
   - `timezone` — display all timestamps in user's timezone
   - `home_network.unifi_site_manager_api_key` — cloud API key from unifi.ui.com (read-only multi-site)
   - `home_network.unifi_local_gateway_url` — e.g. `https://192.168.1.1` (UniFi OS console LAN address)
   - `home_network.unifi_local_api_key` — Network Integration API key (generated in the Network app)
   - `home_network.unifi_protect_url` — Protect host (often same as gateway); defaults to gateway URL
   - `home_network.unifi_protect_api_key` — Protect Integration API key (defaults to local key if unset — one UniFi OS key often works for both)

2. **Daemon health**: Read `${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/daemon-health.json`
   - If `action_needed` is not null → surface it before running any UniFi operations
   - On any auth/connectivity failure in this skill, write `action_needed` back to daemon-health.json

3. **Secrets**: Resolve UniFi credentials via userConfig → env vars → Doppler → keychain (see Phase 1 below)

## Phase 1 — Resolve credentials

Resolve UniFi credentials in this order (userConfig → env → Doppler → keychain). The three surfaces are independent — any subset may be configured; the skill degrades gracefully and only runs the surfaces it has keys for.

```bash
PLUGIN_DATA_DIR="${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}"
PREFS_PATH="${PLUGIN_DATA_DIR}/preferences.json"

# Capture shell-exported protect host before prefs load overwrites UNIFI_PROTECT_URL
_ENV_UNIFI_PROTECT_URL="${UNIFI_PROTECT_URL:-}"

# 1. Plugin userConfig (preferences.json → home_network.*)
UNIFI_SM_KEY=$(jq -r '.home_network.unifi_site_manager_api_key // empty' "$PREFS_PATH" 2>/dev/null)
UNIFI_LOCAL_URL=$(jq -r '.home_network.unifi_local_gateway_url // empty' "$PREFS_PATH" 2>/dev/null)
UNIFI_LOCAL_KEY=$(jq -r '.home_network.unifi_local_api_key // empty' "$PREFS_PATH" 2>/dev/null)
UNIFI_PROTECT_URL=$(jq -r '.home_network.unifi_protect_url // empty' "$PREFS_PATH" 2>/dev/null)
UNIFI_PROTECT_KEY=$(jq -r '.home_network.unifi_protect_api_key // empty' "$PREFS_PATH" 2>/dev/null)

# 2. Environment variables (override userConfig if set)
[ -n "$UNIFI_SM_KEY" ]      || UNIFI_SM_KEY="${UNIFI_SITE_MANAGER_API_KEY:-${UNIFI_SM_KEY:-}}"
[ -n "$UNIFI_LOCAL_URL" ]   || UNIFI_LOCAL_URL="${UNIFI_LOCAL_GATEWAY_URL:-${UNIFI_LOCAL_URL:-}}"
[ -n "$UNIFI_LOCAL_KEY" ]   || UNIFI_LOCAL_KEY="${UNIFI_LOCAL_API_KEY:-${UNIFI_LOCAL_KEY:-}}"
[ -n "$UNIFI_PROTECT_URL" ] || UNIFI_PROTECT_URL="${_ENV_UNIFI_PROTECT_URL:-}"
[ -n "$UNIFI_PROTECT_KEY" ] || UNIFI_PROTECT_KEY="${UNIFI_PROTECT_API_KEY:-${UNIFI_PROTECT_KEY:-}}"

# 3. Doppler fallback (project: unifi)
if command -v doppler &>/dev/null; then
  [ -z "$UNIFI_SM_KEY" ]    && UNIFI_SM_KEY=$(doppler secrets get UNIFI_SITE_MANAGER_API_KEY --project unifi --plain 2>/dev/null)
  [ -z "$UNIFI_LOCAL_URL" ] && UNIFI_LOCAL_URL=$(doppler secrets get UNIFI_LOCAL_GATEWAY_URL --project unifi --plain 2>/dev/null)
  [ -z "$UNIFI_LOCAL_KEY" ] && UNIFI_LOCAL_KEY=$(doppler secrets get UNIFI_LOCAL_API_KEY --project unifi --plain 2>/dev/null)
  [ -z "$UNIFI_PROTECT_KEY" ] && UNIFI_PROTECT_KEY=$(doppler secrets get UNIFI_PROTECT_API_KEY --project unifi --plain 2>/dev/null)
fi

# 4. Keychain fallback (macOS)
[ -z "$UNIFI_SM_KEY" ]    && UNIFI_SM_KEY=$(security find-generic-password -s "unifi-site-manager-key" -w 2>/dev/null)
[ -z "$UNIFI_LOCAL_KEY" ] && UNIFI_LOCAL_KEY=$(security find-generic-password -s "unifi-local-key" -w 2>/dev/null)
[ -z "$UNIFI_PROTECT_KEY" ] && UNIFI_PROTECT_KEY=$(security find-generic-password -s "unifi-protect-key" -w 2>/dev/null)

# 5. Sensible defaults — Protect commonly shares host + key with the Network console
[ -z "$UNIFI_PROTECT_URL" ] && UNIFI_PROTECT_URL="$UNIFI_LOCAL_URL"
[ -z "$UNIFI_PROTECT_KEY" ] && UNIFI_PROTECT_KEY="$UNIFI_LOCAL_KEY"
```

Unless the argument is `setup`, `configure`, `init`, or `token`, if **none** of (`UNIFI_SM_KEY`), (`UNIFI_LOCAL_URL` + `UNIFI_LOCAL_KEY`), nor (`UNIFI_PROTECT_URL` + `UNIFI_PROTECT_KEY`) is resolvable, tell the user and exit gracefully:

```
No UniFi credentials configured. Run /ops:setup --section network to configure your UniFi Site Manager and/or local console API keys.
```

Write `action_needed: "configure_unifi"` to `${PLUGIN_DATA_DIR}/daemon-health.json` and exit.

Set helpers used by all phases below:

```bash
SM_BASE="https://api.ui.com"
NET_BASE="${UNIFI_LOCAL_URL}/proxy/network/integration/v1"
PRO_BASE="${UNIFI_PROTECT_URL}/proxy/protect/integration/v1"

# Site Manager (cloud) call
sm_call() {  # sm_call <path> [method] [body]
  [ -z "$UNIFI_SM_KEY" ] && { echo '{"error":"no site-manager key"}'; return 1; }
  curl -s --max-time 15 -X "${2:-GET}" \
    -H "X-API-Key: ${UNIFI_SM_KEY}" -H "Accept: application/json" -H "Content-Type: application/json" \
    ${3:+--data "$3"} "${SM_BASE}$1"
}

# Network Integration (local) call — self-signed cert ⇒ -k
net_call() {  # net_call <path> [method] [body]
  [ -z "$UNIFI_LOCAL_URL" ] || [ -z "$UNIFI_LOCAL_KEY" ] && { echo '{"error":"no local network creds"}'; return 1; }
  curl -sk --max-time 12 -X "${2:-GET}" \
    -H "X-API-Key: ${UNIFI_LOCAL_KEY}" -H "Accept: application/json" -H "Content-Type: application/json" \
    ${3:+--data "$3"} "${NET_BASE}$1"
}

# Protect Integration (local) call — self-signed cert ⇒ -k
pro_call() {  # pro_call <path> [method] [body]
  [ -z "$UNIFI_PROTECT_URL" ] || [ -z "$UNIFI_PROTECT_KEY" ] && { echo '{"error":"no protect creds"}'; return 1; }
  curl -sk --max-time 12 -X "${2:-GET}" \
    -H "X-API-Key: ${UNIFI_PROTECT_KEY}" -H "Accept: application/json" -H "Content-Type: application/json" \
    ${3:+--data "$3"} "${PRO_BASE}$1"
}
```

---

## Phase 2 — Route by argument

| Input                                       | Action                                           |
| ------------------------------------------- | ------------------------------------------------ |
| (empty)                                     | Cross-surface status dashboard                   |
| status, dashboard                           | Cross-surface status dashboard                   |
| sites, hosts, consoles                      | Site Manager: hosts + sites                      |
| devices, device, aps, switches, gateway     | Network: devices per site                        |
| clients, who, wifi, online                  | Network: clients                                 |
| isp, wan, internet, uptime, latency         | Site Manager: ISP/WAN metrics                    |
| sdwan, sd-wan                               | Site Manager: SD-WAN configs                     |
| protect, cameras, camera, nvr, surveillance | Protect: cameras + NVR                           |
| snapshot <camera>                           | Protect: camera snapshot                         |
| restart, reboot <device>                    | Network: device restart (CONFIRM — Rule 5)       |
| block <client> / unblock <client>           | Network: client block/unblock (CONFIRM — Rule 5) |
| voucher [create\|list\|revoke]              | Network: hotspot vouchers                        |
| predict, insights, anomaly, health          | Predict / insights (cross-surface anomaly scan)  |
| setup, configure, init, token               | Setup flow                                       |

Pick the first site automatically when a surface needs a `siteId` and only one site exists; otherwise present sites via `AskUserQuestion` (max 4, Rule 1) and let the user choose.

---

## STATUS (default — empty argument)

One-screen network dashboard. Probe Site Manager (hosts/devices/ISP), Network (devices/clients), and Protect (cameras) in parallel — separate Bash calls or the Agent Team in **Agent Teams support** below — then render.

```bash
# Site Manager fleet view (skip surface when key missing)
[ -n "$UNIFI_SM_KEY" ] && sm_call "/v1/hosts" | jq '{hosts: ([.data // [] | .[] | {name:.reportedState.hostname, model:.reportedState.hardware.shortname, state:.reportedState.state}])}'
[ -n "$UNIFI_SM_KEY" ] && sm_call "/v1/devices" | jq '{fleet_devices: ([.data // [] | length])}'

# Local network (skip surface when creds missing)
if [ -n "$UNIFI_LOCAL_URL" ] && [ -n "$UNIFI_LOCAL_KEY" ]; then
  SITE=$(net_call "/sites" | jq -r '.data[0].id // .data[0].internalReference // empty')
  net_call "/sites/${SITE}/devices" | jq '{net_devices: (.data|length), online: ([.data[]|select(.state=="ONLINE")]|length)}'
  net_call "/sites/${SITE}/clients" | jq '{clients: (.data|length)}'
fi

# Protect (skip surface when creds missing)
[ -n "$UNIFI_PROTECT_URL" ] && [ -n "$UNIFI_PROTECT_KEY" ] && pro_call "/cameras" | jq '{cameras: length, recording: ([.[]|select(.isRecording==true)]|length), offline: ([.[]|select(.state!="CONNECTED")]|length)}'
```

Desktop render:

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► UNIFI — [host-name] — [timestamp]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CONSOLE      [model]  ([fw])   state: [online]
SURFACES     site-manager [✓]  network [✓]  protect [✓]

NETWORK      [N online] / [N devices]   ([N] offline)
CLIENTS      [N connected]   (wired [N] · wifi [N])
WAN / ISP    [status]  latency [Nms]  uptime [N.NN%] (24h)

PROTECT      [N cameras]  [N recording]  [N offline]

ALERTS       [N]
  [device] offline / [WAN] degraded / [camera] disconnected   (if any)

──────────────────────────────────────────────────────
 [s] sites   [d] devices   [c] clients   [i] isp
 [p] protect   [x] predict   [setup] credentials
──────────────────────────────────────────────────────
```

Mobile mode (`$SSH_CONNECTION` set or `$OPS_MOBILE=1`): plain text, 5–8 lines, no banners.

```
unifi: [N]/[N] net devices online · [N] clients.
wan: [status] · latency [N]ms · uptime [N.NN]%.
protect: [N] cams ([N] recording, [N] offline).
alerts: [N].
next: /ops-unifi devices | isp | protect | predict
```

If any surface key is missing, show that surface as `[—]` and skip its calls (never error the whole dashboard on one missing key). If a device/WAN/camera alert is critical, surface it at the top and suggest piping to `/ops:ops-comms` (Rule 6 — stage draft, never auto-send).

---

## SITES / HOSTS

Site Manager fleet inventory — every console and site across the account.

```bash
sm_call "/v1/hosts" | jq -r '.data[]? | "• \(.reportedState.hostname // .id)  \(.reportedState.hardware.shortname // "?")  \(.reportedState.state // "?")  ip=\(.ipAddress // "?")"'
sm_call "/v1/sites" | jq -r '.data[]? | "  site: \(.meta.name // .name // .siteId)  devices=\(.statistics.counts.totalDevice // "?")  clients=\(.statistics.counts.totalClient // "?")"'
```

Render grouped by host → its sites, with model/fw/state and device+client counts. Footer offers `[d] drill into a site's devices`.

---

## DEVICES

Local Network device inventory for a site — APs, switches, gateways with live state.

```bash
SITE="${CHOSEN_SITE}"
net_call "/sites/${SITE}/devices" | jq -r '.data[]? | "• \(.name)  \(.model // .shortname)  \(.state)  fw=\(.firmwareVersion // "?")  uplink=\(.uplink.type // "?")"'
# Drill: latest stats for one device
net_call "/sites/${SITE}/devices/${DEVICE_ID}/statistics/latest" | jq '{cpu:.cpuUtilizationPct, mem:.memoryUtilizationPct, rxMbps:(.uplink.rxRateBps/1e6), txMbps:(.uplink.txRateBps/1e6), uptime_s:.uptimeSec}'
```

Filter from argument: `devices aps` → `type=="uap"`/class AP; `devices switches` → `usw`; `devices gateway` → `ugw`/`udm`. Render grouped by type, flag any `state != "ONLINE"`. Offer `[a] restart a device` (→ goes through the confirmed RESTART path below).

---

## CLIENTS

Connected clients on a site, wired + wireless.

```bash
SITE="${CHOSEN_SITE}"
net_call "/sites/${SITE}/clients" | jq -r '.data[]? | "• \(.name // .hostname // .mac)  \(.ipAddress // "?")  \(if .uplinkDeviceId then "wifi@"+(.connectedToName//"?") else "wired" end)  rx=\(.rxBytes // 0)"'
```

Sort by usage; flag clients with weak signal (`.signal < -70`) or high retries. Footer offers `[b] block a client` / `[u] unblock` (confirmed path below).

---

## ISP / WAN

Site Manager ISP metrics — WAN latency, packet loss, downtime, throughput. The backbone of **predict** mode.

```bash
# 1h-granularity metrics for the trailing window
sm_call "/v1/isp-metrics/1h" | jq '{
  sites: [.data[]? | {
    site: (.siteId // .meta.name),
    avgLatencyMs: (.metrics.latencyAvgMs // null),
    maxLatencyMs: (.metrics.latencyMaxMs // null),
    packetLossPct: (.metrics.packetLossPct // null),
    downtimeSec: (.metrics.downtimeSec // 0),
    rxMbps: (.metrics.download.avgMbps // null),
    txMbps: (.metrics.upload.avgMbps // null)
  }]
}'
```

Render per-site WAN health. Flag: `downtimeSec > 0` (outage in window), `packetLossPct > 1`, `avgLatencyMs > 1.5×` the site's trailing baseline. If any flagged → suggest **predict** mode for the full picture and offer to broadcast (Rule 6).

Mobile: `wan [site]: [status] · lat [N]ms · loss [N]% · down [N]s`.

---

## SD-WAN

```bash
sm_call "/v1/sd-wan-configs" | jq -r '.data[]? | "• \(.name)  type=\(.type // "?")  status=\(.deploymentStatus // "?")"'
# Detail + status for one
sm_call "/v1/sd-wan-configs/${CFG_ID}/status" | jq '.'
```

Render config list with deployment status; flag any not `ACTIVE`/`DEPLOYED`.

---

## PROTECT (cameras / NVR)

```bash
pro_call "/meta/info" | jq '{nvr:.name, version:.version, mac:.mac}'
pro_call "/cameras" | jq -r '.[]? | "• \(.name)  \(.type // .modelKey)  state=\(.state)  rec=\(.isRecording)  fw=\(.firmwareVersion // "?")"'
```

Render cameras grouped by state; flag `state != "CONNECTED"` and any with `isRecording == false` that are expected to record. Footer offers `[snapshot]` and `[live]` (WebSocket watch).

### Snapshot

```bash
CAM_ID="${CHOSEN_CAM}"
pro_call "/cameras/${CAM_ID}/snapshot?highQuality=true" > "/tmp/unifi-snap-${CAM_ID}.jpg"
echo "saved /tmp/unifi-snap-${CAM_ID}.jpg ($(wc -c < /tmp/unifi-snap-${CAM_ID}.jpg) bytes)"
```

Report the saved path; the snapshot is binary JPEG — do not inline it.

### Camera settings (PATCH) — CONFIRM (Rule 5)

`PATCH /cameras/{id}` with partial JSON (e.g. toggle recording, mic sensitivity) is state-changing. Show the diff and require `AskUserQuestion` confirmation before firing.

---

## RESTART / BLOCK — confirmed state-changing actions

All of the following REQUIRE `AskUserQuestion` confirmation (Rule 5) showing the exact target before executing, and are appended to the audit log.

```bash
# Restart a device
net_call "/sites/${SITE}/devices/${DEVICE_ID}/actions" POST '{"action":"RESTART"}'

# Block / unblock a client
net_call "/sites/${SITE}/clients/${CLIENT_ID}/actions" POST '{"action":"BLOCK"}'
net_call "/sites/${SITE}/clients/${CLIENT_ID}/actions" POST '{"action":"UNBLOCK"}'
```

After firing, re-read the target once to confirm the new state and report it.

---

## VOUCHER (hotspot)

```bash
net_call "/sites/${SITE}/vouchers" | jq -r '.data[]? | "• \(.code)  \(.durationMinutes)min  used=\(.usedCount)/\(.quota)  expires=\(.expiresAt // "—")"'
# Create (CONFIRM — creates a credential)
net_call "/sites/${SITE}/vouchers" POST '{"count":1,"durationMinutes":1440,"quota":1,"name":"ops"}'
# Revoke (CONFIRM — destructive)
net_call "/sites/${SITE}/vouchers/${VOUCHER_ID}" DELETE
```

Create + revoke require `AskUserQuestion` confirmation.

---

## PREDICT / INSIGHTS (`predict`, `insights`, `anomaly`, `health`)

Cross-surface anomaly scan that surfaces problems _before_ they become outages. Pull ISP metrics (Site Manager), device stats (Network), and camera state (Protect), then score against simple thresholds and trailing baselines. Read-only — never changes state.

Signals scored:

1. **WAN degradation** — `downtimeSec > 0` in the trailing window, `packetLossPct > 1`, or `avgLatencyMs > 1.5×` the site's 24h baseline → _WAN trending unhealthy_.
2. **AP/switch flapping** — any device with `state` toggling or `uptimeSec` reset within the window (recent reboot), or CPU/mem `> 85%` sustained → _device under stress_.
3. **Client RF health** — share of wireless clients with `signal < -72 dBm` or high retry rate > 20% → _coverage/interference risk in [zone/AP]_.
4. **Uplink saturation** — any uplink at `> 90%` of negotiated speed sustained → _capacity ceiling near_.
5. **Protect** — any camera `state != CONNECTED`, NVR storage `> 90%`, or expected-recording camera not recording → _surveillance gap_.

```bash
SITE=$(net_call "/sites" | jq -r '.data[0].id // .data[0].internalReference // empty')
# Pull the three inputs in parallel (or via the Agent Team)
sm_call "/v1/isp-metrics/1h"            > /tmp/unifi_isp.json &
net_call "/sites/${SITE}/devices"       > /tmp/unifi_dev.json &
net_call "/sites/${SITE}/clients"       > /tmp/unifi_cli.json &
pro_call "/cameras"                     > /tmp/unifi_cam.json &
wait
# Score locally with jq (thresholds above) and rank findings CRITICAL→LOW.
```

Render:

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► UNIFI ► PREDICT — [host] — [timestamp]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

WAN          [healthy | degrading | down]
  ⚠ [site] latency 48ms → trending 2.1× baseline, loss 1.4%

DEVICES      [N] under stress
  ⚠ [AP name] CPU 91% sustained 20m — consider load-balance / reboot

CLIENTS      [N]% wireless below -72 dBm near [AP]
UPLINKS      [N] approaching capacity
PROTECT      [N] surveillance gaps

VERDICT      [all clear | watch | act now]

──────────────────────────────────────────────────────
 Actions:
 a) Restart the flagged device
 b) Broadcast WAN/outage risk to /ops:ops-comms
 c) Drill into a finding
 d) Re-run after [N] min
──────────────────────────────────────────────────────
```

Cross-channel: if VERDICT is `act now` (active WAN outage, NVR full, or a security camera offline), suggest piping to `/ops:ops-comms` (Rule 6 — stage draft, never auto-send) and writing the finding to `daemon-health.json` for `/ops:ops-fires`.

Mobile:

```
unifi predict: [verdict].
wan: [site] [status] (lat [N]ms, loss [N]%).
devices: [N] stressed · clients: [N]% weak RF.
protect: [N] gaps.
next: /ops-unifi devices | isp
```

---

## SETUP FLOW (`setup`, `configure`, `init`, `token`)

Delegate to the central setup wizard with the network section:

```
/ops:setup --section network
```

If the wizard is unavailable, run inline discovery (background per Rule 4):

```bash
# 1. Env vars
printenv UNIFI_SITE_MANAGER_API_KEY UNIFI_LOCAL_GATEWAY_URL UNIFI_LOCAL_API_KEY UNIFI_PROTECT_URL UNIFI_PROTECT_API_KEY 2>/dev/null
# 2. Shell profiles
grep -hE 'UNIFI_' ~/.zshrc ~/.bashrc ~/.envrc ~/.mcp-secrets.env 2>/dev/null | grep -v '^#'
# 3. Doppler
doppler secrets --project unifi --config prd --json 2>/dev/null | jq -r 'to_entries[] | select(.key|test("UNIFI";"i")) | "\(.key)=(present)"'
# 4. Keychain
security find-generic-password -s "unifi-site-manager-key" 2>/dev/null
# 5. Discover the console on the LAN (mDNS / common gateway IPs)
for ip in 192.168.1.1 192.168.0.1 10.0.0.1; do curl -sk --max-time 2 "https://$ip" -o /dev/null -w "$ip → %{http_code}\n" 2>/dev/null; done
```

Instruct the user where to mint each key (Rule 3 — never silently skip):

1. **Site Manager (cloud)**: unifi.ui.com → Settings → Control Plane → Integrations → Create API Key. Header `X-API-Key`.
2. **Network (local)**: Network app → Settings → Control Plane → Integrations → Create API Key. UniFi OS consoles only.
3. **Protect (local)**: Protect → Settings → Control Plane → Integrations → Create API Key (or reuse the OS key).

Verify each acquired key before writing it to `preferences.json` under `home_network.*`:

```bash
# Site Manager
sm_call "/v1/hosts" | jq -e '.data' >/dev/null && echo "site-manager OK"
# Network
net_call "/sites" | jq -e '.data' >/dev/null && echo "network OK"
# Protect
pro_call "/meta/info" | jq -e '.version' >/dev/null && echo "protect OK"
```

After each surface passes its smoke test, merge verified values into `$PREFS_PATH` (omit empty fields — keep existing prefs for skipped surfaces):

```bash
mkdir -p "$(dirname "$PREFS_PATH")"
jq --arg sm "${UNIFI_SM_KEY:-}" \
   --arg url "${UNIFI_LOCAL_URL:-}" \
   --arg lk "${UNIFI_LOCAL_KEY:-}" \
   --arg pu "${UNIFI_PROTECT_URL:-}" \
   --arg pk "${UNIFI_PROTECT_KEY:-}" \
   '.home_network = ((.home_network // {}) + {
     unifi_site_manager_api_key: (if $sm != "" then $sm else (.home_network.unifi_site_manager_api_key // "") end),
     unifi_local_gateway_url: (if $url != "" then $url else (.home_network.unifi_local_gateway_url // "") end),
     unifi_local_api_key: (if $lk != "" then $lk else (.home_network.unifi_local_api_key // "") end),
     unifi_protect_url: (if $pu != "" then $pu else (.home_network.unifi_protect_url // "") end),
     unifi_protect_api_key: (if $pk != "" then $pk else (.home_network.unifi_protect_api_key // "") end)
   })' \
   "$PREFS_PATH" > "$PREFS_PATH.tmp" && mv "$PREFS_PATH.tmp" "$PREFS_PATH"
```

Clear `action_needed` in `daemon-health.json` when at least one surface is configured.

401/403 → key invalid; re-prompt via `AskUserQuestion` (`[Paste new key]`, `[Skip this surface]`).

---

## Agent Teams support

If `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` is set, use **Agent Teams** for parallel surface probing:

```
TeamCreate("unifi-team")
Agent(team_name="unifi-team", name="fleet-scanner",   prompt="Site Manager: list hosts + sites + fleet device count + ISP metrics, return structured JSON")
Agent(team_name="unifi-team", name="network-scanner", prompt="Network Integration API for the chosen site: devices (online/offline) + clients + per-device latest stats, return structured JSON")
Agent(team_name="unifi-team", name="protect-scanner", prompt="Protect Integration API: cameras + NVR state + recording status + storage, return structured JSON")
```

If the flag is NOT set, dispatch `ops:unifi-agent` as standard fire-and-forget subagents per surface (site-manager, network, protect).

---

## Phase 3 — Cross-channel integration

After the main output, evaluate cross-channel triggers (suggestions only — never auto-execute Rule-5/Rule-6 actions):

1. **WAN outage / security-camera offline → comms** — stage a `/ops:ops-comms` draft (WhatsApp/Telegram) to emergency contacts; Rule 6 per-message approval.
2. **Predict verdict = act now → fires** — write the finding to `daemon-health.json` under `action_needed`; suggest `/ops:ops-fires`.
3. **Recurring device offline → status** — if a device has been offline across consecutive scans, note it for the next `/ops:ops-go` briefing.
4. **Both Site Manager and local unreachable → status** — write `action_needed: "unifi_unreachable"` to daemon-health and exit with a banner.

---

## Phase 4 — Error handling

| Failure                            | Behavior                                                                                                                            |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Site Manager 401                   | Cloud key expired/invalid. Tell user to regenerate at unifi.ui.com.                                                                 |
| Site Manager 429                   | Rate-limited. Back off; note "site-manager throttled" and use cached/local data.                                                    |
| Local connection refused / timeout | Console unreachable on LAN (off-site or wrong IP). Fall back to Site Manager for read-only fleet view; note `transport=cloud-only`. |
| Local 401                          | Network/Protect key invalid. Tell user to regenerate in the app.                                                                    |
| Protect 404 on integration paths   | Protect Integration API not enabled / older firmware. Note "protect integration unavailable".                                       |
| All surfaces fail                  | Report which failed; write `action_needed: "unifi_unreachable"` to daemon-health; exit with banner.                                 |
| jq missing                         | Print raw JSON, suggest installing jq.                                                                                              |
| No credentials at all              | Exit gracefully with `/ops:setup --section network`.                                                                                |

Audit every state-changing call (device RESTART, client BLOCK/UNBLOCK, camera PATCH, voucher create/revoke) to `${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/ops-unifi-audit.log` with timestamp, action, target id, and result.

---

## Optional MCP path

The skill is fully self-sufficient via curl. If you want a richer natural-language tool surface, community UniFi MCP servers exist (no official Ubiquiti MCP as of 2026):

- `ry-ops/unifi-mcp-server` — local consoles **and** cloud Site Manager in one server.
- `sirkirby/unifi-mcp` — Network (stable, ~169 tools), Protect, Access.
- `DataKnifeAI/unifi-network-mcp` + `unifi-protect-mcp` — split Network / Protect servers.

To use one, register it via the on-demand MCP registry (`~/.claude/mcp-ondemand.json` → `mcp-toggle.sh on unifi`) and restart Claude Code. Treat these as untrusted third-party code — security-review before install. This skill does not require any of them.

---

## Output style

- Terse-direct. Plain text in mobile mode (Rule 7). Tables only on desktop.
- Always show the hotkey footer (`[s] sites [d] devices [c] clients [i] isp [p] protect [x] predict`) on desktop.
- Use `AskUserQuestion` (max 4 options, Rule 1) for any state-changing action and for site selection when ambiguous.
- Never auto-send messages — always stage drafts per Rule 6.
- Local calls always use `-k` (self-signed cert) and a short `--max-time`; degrade to Site Manager read-only when the console is off-LAN.

## Additional resources

CLI detail: `references/cli.md`.

Attribution

Lifecycle-Innovations-LimitedLifecycle-Innovations-Limited
View sourceMore from Lifecycle-Innovations-Limited →
SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Related Skills

Caveman

Ultra-compressed communication mode that cuts output tokens while keeping technical accuracy. Levels: lite, full, ultra and the wenyan variants. Use for /caveman, "caveman mode", "talk like caveman", "be brief" or "less tokens".

1066601 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

686011 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3351 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

651 votes

math-skill

A comprehensive mathematical reasoning skill for AI assistants — handles arithmetic to research-level problems with rigorous step-by-step reasoning, systematic verification, and transparent uncertainty handling

381 votes
View all in ai-agents →