Ops Unifi logo

Ops Unifi

Organization
Lifecycle-Innovations-Limited
ops-unifi

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

Overview

PublisherLifecycle-Innovations-Limited
Repositoryclaude-ops
Skill nameops-unifi
Stars
543
Forks
64
Bundled files
1
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 1 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by Lifecycle-Innovations-Limited on GitHub. Read the source before you install it.

Installation

Install the Ops Unifi AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/Lifecycle-Innovations-Limited/claude-ops/tree/main/claude-ops/skills/ops-unifi
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/Lifecycle-Innovations-Limited/claude-ops.git /tmp/claude-ops
mkdir -p .claude/skills
cp -r /tmp/claude-ops/claude-ops/skills/ops-unifi .claude/skills/ops-unifi
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Ops Unifi in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Ops Unifi on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Ops Unifi is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

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

InputAction
(empty)Cross-surface status dashboard
status, dashboardCross-surface status dashboard
sites, hosts, consolesSite Manager: hosts + sites
devices, device, aps, switches, gatewayNetwork: devices per site
clients, who, wifi, onlineNetwork: clients
isp, wan, internet, uptime, latencySite Manager: ISP/WAN metrics
sdwan, sd-wanSite Manager: SD-WAN configs
protect, cameras, camera, nvr, surveillanceProtect: cameras + NVR
snapshot Protect: camera snapshot
restart, reboot Network: device restart (CONFIRM — Rule 5)
block / unblock Network: client block/unblock (CONFIRM — Rule 5)
voucher [create|list|revoke]Network: hotspot vouchers
predict, insights, anomaly, healthPredict / insights (cross-surface anomaly scan)
setup, configure, init, tokenSetup 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

FailureBehavior
Site Manager 401Cloud key expired/invalid. Tell user to regenerate at unifi.ui.com.
Site Manager 429Rate-limited. Back off; note "site-manager throttled" and use cached/local data.
Local connection refused / timeoutConsole unreachable on LAN (off-site or wrong IP). Fall back to Site Manager for read-only fleet view; note transport=cloud-only.
Local 401Network/Protect key invalid. Tell user to regenerate in the app.
Protect 404 on integration pathsProtect Integration API not enabled / older firmware. Note "protect integration unavailable".
All surfaces failReport which failed; write action_needed: "unifi_unreachable" to daemon-health; exit with banner.
jq missingPrint raw JSON, suggest installing jq.
No credentials at allExit 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.

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Ops Unifi AI skill do?

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

Why use Ops Unifi on TypingMind?

Because you install it once and use it with any model. Ops Unifi is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Ops Unifi in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/Lifecycle-Innovations-Limited/claude-ops/tree/main/claude-ops/skills/ops-unifi. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Ops Unifi?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Ops Unifi?

As many as you like. As long as a model supports skills, you can use Ops Unifi with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Ops Unifi AI skill free?

Yes. It is published on GitHub by Lifecycle-Innovations-Limited under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇