Adds c_msg_purge to coord.py + SKILL.md doc. Deletes coordination messages older than a
date cutoff via DELETE /api/coord/messages/{id}. Safety: --before is required (can't wipe
the store by accident), DRY-RUN by default (previews; --yes to actually delete), optional
--to scopes to one recipient session, paginates over the API's 1000-row limit cap, logs
partial failures. Replaces the ad-hoc curl loop used to purge 208 stale messages this session.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- bitdefender gz.py: add "missing name" to _EXPECTED_ERROR_MARKERS — closes the last gap in
Howard's errorlog suppression ("Missing name 'X' in 'options' object" validation errors were
still logged). Verified all 10 real spam messages now suppressed; genuine errors still log.
- memory feedback_submodule_autosync_discipline: capture the recurring auto-synced-submodule
rule (worktree or push-by-SHA + ls-remote verify; assert HEAD==origin/main before audits;
never checkout-- shared files). Recurred on Howard-Home x3 + GURU-5070 this session.
- CLAUDE.md CORE Windows bullet: promote the two top recurring mechanical traps (/tmp path
mismatch, curl.exe/plink quote-stripping) to always-loaded hard rules so they stop repeating.
Lint of errorlog.md: bitdefender expected-validation spam was ~70% of entries (Howard's
suppression now complete); fabb3421/Mail.Send drift closed earlier this session; wiki-compile
lock-release doc already fixed (entries predate the fix).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PROBLEM: Broadcast messages were never being marked as read on the server,
only tracked in a local gitignored seen-file. This caused them to re-appear
in every new session or on different machines.
ROOT CAUSE: check-messages.sh lines 101-104 had a flawed assumption that
broadcasts share a single read_at field that would "clobber" other machines'
unread state. This was wrong - the API supports per-session read tracking.
FIX:
- check-messages.sh now marks broadcasts as read on the server (like personal
messages), in addition to tracking them in the local seen-file
- Updated comments to reflect correct behavior
- coord SKILL.md now documents auto-mark-read behavior and reply workflow
- Manually marked all 39 accumulated unread broadcasts as read
IMPACT: Broadcast messages will now be properly marked as read and won't
keep appearing across sessions. Fixes user complaint about answered questions
(pfSense cred-path, fabb3421, etc.) continuing to show up.
Logged to errorlog.md as --correction.
Found during the RMM-TEST-MACHINE full-function test (live tenant):
- assignPolicy: assigning a policyId REQUIRES inheritFromAbove:false in the same
call, else the API rejects with a misleading "inheritFromAbove should not be
used with policyId" error. Fixed assign_policy to always send it; dropped the
wrong --inherit-from-above flag.
- isolate/unisolate: the API takes a SINGLE endpointId per call, NOT an
endpointIds array (errored "not expected"). Client now loops per endpoint.
unisolate fails while the isolate task is in progress — wait + retry.
- api-reference updated with the live-verified shapes.
Full function test PASSED on RMM-TEST-MACHINE: install(offline kit/SYSTEM) ->
enroll -> move(ZZ-RMM-TEST) -> assign-policy(GPS Base, applied) -> set-label ->
scan -> reconfigure -> isolate -> unisolate -> quarantine/blocklist read ->
managed uninstall(deleteEndpoint). selftest 75/75.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Completed Companies module for bitdefender GravityZone Public API
- Implemented: getCompanyDetails, getCompanyDetailsByUser, createCompany, suspendCompany, activateCompany, deleteCompany
- Discovered updateCompany and getCompaniesList not available; companies retrieved via network inventory
- Company types: 0=Partner, 1=Customer; createCompany accepts nested licenseSubscription via JSON passthrough
- All write operations require --confirm; raw also restricts createCompany/suspendCompany/activateCompany
- selftest 49 -> 55 passing
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Completed Accounts module for bitdefender skill (GravityZone Public API)
- Added 5 methods: getAccountDetails, createAccount, updateAccount, deleteAccount, configureNotificationsSettings
- Write methods require --confirm; raw also gates createAccount/updateAccount/configureNotificationsSettings
- Param shapes validated against official docs and safe validation probes
- configureNotificationsSettings is a setter with no required param; warning documented against empty payload on live tenant
- selftest 42 -> 49 passing
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- assign_policy: add inheritFromAbove option; mark VERIFIED via official docs
(policyId/targetIds/forcePolicyInheritance/inheritFromAbove; not applied to
ENFORCED-policy targets).
- setPushEventSettings: documented serviceType (splunk/cef/jsonRPC), TLS 1.2+
receiver requirement, subscribeToEventTypes event-flag map; webhook receiver
pattern noted.
- api-reference.md: cite GravityZone Support Center as authoritative source.
- add references/BUILDOUT.md — master checklist to implement every API method
module-by-module; seeded with current done/todo/dead state.
- memory: reference_gravityzone_support (+ index).
selftest 42/42.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-verified the live tenant's full API scope and wrapped the modules the key
allows but the skill didn't expose. New CLI subcommands:
- assign-policy (gated) — apply an existing policy to endpoints/groups
(param shape policyId+targetIds verified live)
- reports, accounts, notif-settings, scan-tasks — read
- push-settings / push-stats / push-set (gated) — push event service
(status param verified; needs a receiver URL to enable)
Corrections from live probing:
- policies are NOT shallow: getPolicyDetails returns the FULL granular config.
Removed the false "shallow" warning; documented read+assign, console-only authoring.
- raw now gates assignPolicy + setPushEventSettings.
- documented dead modules (patchmanagement/phasr/maintenancewindows/integrations,
incidents.getIncidentsList) and unconfigured-push handled cleanly (rc0, no errorlog).
selftest 29/29 -> 42/42, all green against the live tenant.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mail.Send is NOT an open decision or a 'blocked' item: the Exchange Operator
tier (b43e7342) already holds Graph Mail.Send + Mail.ReadWrite +
MailboxSettings.ReadWrite (the suite's IR victim-notification mail path).
/mailbox (ACG own-mail) separately uses the dedicated ComputerGuru Mailbox app
1873b1b0. The deleted fabb3421/Claude-MSP-Access app is now referenced only as
DELETED/do-not-use across all live surfaces.
Corrected: remediation-tool gotchas.md (removed 'suite has no mail scopes /
mailbox BLOCKED / decision-not-executed'), commands/mailbox.md (header +
Attribution no longer name the deleted app as active), feedback memory
(promoted 'suite has Mail.Send — settled' to a headline), breach-report
template, .grok mirrors, credentials.md, CATALOG_SHARED_DATA.md, and wiki
(internal-infrastructure, glaztech, dataforth). Removed dead plaintext secret
for the deleted app from CATALOG_SHARED_DATA.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
/autotask exists nowhere as a Claude command (no .claude/commands/autotask.md in
the repo; only a Grok skill by that name). It was a GURU-5070-specific artifact in
the provisional manifest and produced a spurious RED on every other machine. Removing
it clears the false FAIL fleet-wide.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codifies the scan-first/data-driven workflow proven on Cascades (where the baked-in
non-DFS bias picked the congested channels and a data-driven DFS plan halved 5GHz retry):
- NEW survey-report.py: rolls survey-collect JSON into the fleet per-channel/per-band-group
measured busy% table + cleanest/dirtiest ranking + a suggested clean 40MHz palette. The
decision-driver that was missing (we built it by hand).
- channel-plan.sh: na palette is now DATA-DRIVEN, not hardcoded non-DFS. Adds --channels
(explicit palette) + --dfs ok|avoid|only; default considers ALL 40MHz primaries and lets
measured busy% choose. Adds load-balancing + a local-search pass -> strong co-channel to 0.
- survey-collect.sh: per-AP "cleanest" report no longer pre-filters out DFS (DFS is usually
cleanest here); marks DFS with *, points at survey-report.
- SKILL.md: documents the mandatory scan -> survey-report -> channel-plan --channels -> apply
-> validate order + the Cascades lesson.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
While using the new 3-retry gemini path for live VPN research, two bugs surfaced:
- emit_or_fail checked auth_failed INSIDE the retry loop; a benign mid-run token-refresh line
matched the over-broad auth regex (bare login|credential|authenticat|oauth|401) and aborted the
retries with a false "auth error" - even though `gemini -p` auth tested fine. Moved auth-classify
to AFTER the retries (it only picks the final error message now) and tightened auth_failed to real
signatures (invalid_grant, not authenticated, login with google, token expired, ...).
- Added quota_exhausted() + a QUOTA FALLBACK: the pinned strong model (gemini-3.1-pro-preview) hit
"exhausted your capacity on this model" mid-session; emit_or_fail now retries once on the default
(lighter) model by stripping -m (separate quota). Validated: capped pro run -> fell back -> 2.9KB answer.
CT_THOUGHTS Thought 2 Resolution updated with both. (Search-bot reliability hardening continues.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mike's must-fix. Diagnosed from RAW output of failing queries (not guessed):
- grok xsearch = TIMEOUT: grok-4.20-multi-agent web_search runs past budget on multi-part queries
(286s/280s, rc=124, still searching - 183 thoughts, only progress-noise text); buffered json => total loss.
- gemini search = INTERMITTENT empty turn (a clean re-run gave a real 2.6KB answer in 122s); the wrapper
retried only once, so two empties in a row failed spuriously.
Fixes:
- ask-gemini.sh emit_or_fail: retry up to 3x with 3s/6s backoff (was 1).
- ask-grok.sh xsearch: --output-format streaming-json (salvage partials) + AUTO-FALLBACK to
ask-gemini.sh search when grok doesn't finish (rc!=0 or empty). Validated e2e: grok timed out
(rc=124) -> fell back -> gemini returned a real sourced answer (UniFi Teleport invite-link API).
grok's own multi-agent timeout is an xAI-side limitation; the fallback makes xsearch reliable regardless.
Docs: grok SKILL.md xsearch row + CT_THOUGHTS Thought 2 Resolution.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Earlier "no usable Teleport API" was wrong (probed /rest/teleport, /stat/teleport, /v1/teleport).
Gemini research + live verification: Teleport config lives at /api/s/<site>/rest/setting/teleport
(GET/PUT, also under /get/setting key 'teleport') - reachable via the connector. Brooklyn confirmed
enabled, subnet 192.168.1.1/24. Invite generate/revoke is reportedly POST /api/s/<site>/cmd/teleport
{"cmd":"generate-invite"|"revoke-invite"} (untested - it creates a live VPN access link; gate as a
write). Invites are WiFiman-app-only. Proxy path is /v1/connector/consoles/{id}/proxy/... (Gemini's
/v1/hosts/{id}/proxy form 404s). Doc updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
neighbor-collect.sh: add `--console <name> [--site <short>]` so the AP name/BSSID/IP map can come
from the cloud connector (/v1/connector/.../stat/device) instead of a UOS direct-login -- lets the
disable-analysis collector run against ANY console we have AP-VLAN reach to (the AP SSH harvest of
/proc/ui_neighbor is unchanged and still needs L3 reach). UOS path untouched. Validated against
Cascades via connector: source=CONNECTOR, built 77-mac + 450-bssid map for the 75 online APs.
This completes the hybrid (don't-lose-functionality): connector for airtime everywhere + neighbor-
collect (any source) for the SNR matrix -> NEIGHBOR_JSON -> optimize-radios disables on remote sites.
Documented (references/site-manager-api.md): the neighbor-collect --console flow, and the gateway
VPN/Teleport reach -- connector reaches /rest/networkconf (VPN servers: wireguard-server/openvpn-
server, site-to-site) read+writable in principle (gate writes like gw-control); Teleport has no
usable API (v1/ea/teleport 404, per-console /teleport 403).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Validated the cloud-connector analysis against a KNOWN entity (Cascades, normally UOS-Mongo).
The connector reaches the self-hosted "UOS Server" host; Cascades is its site `va6iba3v`.
Two fixes from the validation:
- rf-analyze.py: pass macs:[<all uap macs>] to /stat/report/*.ap. The UniFi report endpoint
returns only a small DEFAULT subset otherwise -- Cascades came back as 10 of 77 APs until the
MAC list was supplied. Now profiles all 75 (uaps with 2.4 radios), matching the UOS path.
- model-rank.sh / optimize-radios.sh: --console now accepts --site <name> (internal short name
from /api/self/sites) for multi-site controllers like the UOS Server (Cascades = va6iba3v).
Result lines up with the known UOS-Mongo figures: 75 APs, 2.4GHz util 65-90% / interf 53-78% /
~1 client each, all power-down, 0 disables (roam graph absent via connector -> same coverage-safe
degradation; disables still need NEIGHBOR_JSON). Apples-to-apples confirmed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both analyses now accept `--console "<name>"` and run against the UniFi cloud connector
instead of the UOS Mongo server, so RF airtime tuning works on standalone/non-UOS consoles
(e.g. Brooklyn/Skybar). The UOS Mongo path is unchanged.
- New shared analyzer scripts/rf-analyze.py: pulls per-AP/band airtime history via the
connector POST /stat/report/hourly.ap (SAME schema as ace_stat.stat_hourly) + /stat/device
for names/zones, derives cu_interf = cu_total - cu_self_rx - cu_self_tx, and runs the SAME
model-rank ranking and optimize-radios greedy power-down/disable logic (ported faithfully).
- Roam graph (/stat/event) is usually empty on small/stationary sites -> graceful degrade:
model-rank ranks by airtime pressure; optimize-radios returns power-down candidates + 0
disables (coverage-safe). NEIGHBOR_JSON (SNR matrix) still enables disables, as on UOS.
- model-rank.sh / optimize-radios.sh: added the `--console` route (resolves the key from
vault services/unifi-site-manager, execs rf-analyze.py). Validated on Brooklyn/Skybar:
2.4GHz saturated (Yoga AP cu 63%/interf 55%), 5GHz idle (1-5%) - the expected pain-band split.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New backend reaching ANY of the ~36 ACG UniFi consoles remotely via api.ui.com with the
account key (vault services/unifi-site-manager) - no UOS server, no LAN/VPN. Mapped the API
surface empirically (key live), corroborated by grok+gemini web search:
- Tier 1 (Site Manager): fleet/devices/sites/isp commands - inventory, site health (counts,
IPS, ISP/ASN), and WAN/ISP time-series (latency/throughput/downtime).
- Tier 2 (CLOUD CONNECTOR -> console LOCAL Network API = UOS PARITY): the `net` command proxies
/v1/connector/consoles/<id>/proxy/network/api/s/<site>/stat/{device,sta}, returning the SAME
ace_stat depth as the UOS Mongo path - per-radio cu_total airtime/channel/bw/tx_power/num_sta/
satisfaction and per-client rssi/signal/noise/satisfaction/rates. Verified live on Brooklyn/
Skybar (standalone UDM, WAN-firewalled): `net brooklyn radios` + `net brooklyn clients` work.
This achieves parity with (and broader coverage than) the UOS server for non-UOS consoles.
Added references/site-manager-api.md (full catalog + 3 tiers), a Plane 3 note in SKILL.md, and
updated the reference memory. Read-only; POST actions (device restart, client block) exist, not wired.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- apply-wlan.sh: wlan_bands token was "6e" but this controller stores "6g" (verified live on Cascades
Guest SSID) -> setting 6 GHz membership would have failed. Fixed band values + option names (5g6g/6g/all).
- Cascades 2.4 runbook: folded in Phase 5 (5 GHz: width 80->40 on 76 radios; channel plan with the
DFS decision flagged -- DFS empirically clean here, so including clean-DFS gives ~20 channels vs ~5
non-DFS-only for 77 APs) and Phase 6 (6 GHz: root cause = production SSID CSCNet not on 6 GHz [bands
2g,5g only]; add 6g + enable bss-transition; band-steering already on). Per Howard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Upstream description ("You MUST use this before any creative work...") would
auto-fire the brainstorming skill on routine feature/code work. Rewrote the
frontmatter description to invoke ONLY when the user explicitly asks to
brainstorm/design. Methodology body (incl. HARD-GATE) unchanged. Noted in SOURCE.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the ComposioHQ/awesome-claude-skills list. Checked licenses BEFORE copying:
- threat-hunting-with-sigma-rules: repo is gone (GitHub 404) -- not harvested.
- forensics (mhattingpete): repo restructured, those skills no longer exist -- not harvested.
- pdf / mcp-builder (Anthropic official): LICENSE.txt FORBIDS copying out of the
Service / derivatives / redistribution -- NOT harvestable into this repo (install via
the official Claude Code marketplace instead if wanted).
- obra/superpowers: MIT -> the only legally harvestable set; imported with attribution.
Imported (each with its own MIT LICENSE copy + SOURCE.md provenance, commit a21956e48c13,
ASCII-normalized to house style, no emojis):
- using-git-worktrees
- test-driven-development (+ testing-anti-patterns.md)
- root-cause-tracing (+ find-polluter.sh helper, emojis -> ASCII markers)
- brainstorming (methodology only; upstream visual websocket server intentionally omitted)
Faithful imports -- content not reworded beyond ASCII typography/emoji normalization.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audited the Gemini wrapper against the CLI's bundled help/README (gemini 0.45.2),
same pass as the grok skill. Unlike grok, found NO functional bug:
- All flags correct and real: -p, --skip-trust, -o json, --approval-mode plan|yolo,
--include-directories, -m (verified against `gemini --help`).
- JSON schema {session_id, response, stats} -> .response confirmed via live probe.
- Pinned model gemini-3.1-pro-preview STILL VALID (live PONG); the GA-looking
gemini-3.1-pro and gemini-3-pro both ModelNotFoundError -> keep the -preview suffix.
- Default text model is gemini-3.1-flash-lite (by design; verify/review/search/image
pin pro). No thought-suppression flag exists in the CLI, so the gresponse() reasoning
-leak scrub stays (justified, signature-gated, byte-exact otherwise).
- Live `search` re-validated end-to-end through the wrapper (58s, grounded sources).
Only change: version 0.45.1 -> 0.45.2 in SKILL.md + wrapper header, and refreshed the
verified-date notes with the 2026-06-17 re-validation findings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root-caused the long-standing `ask-grok.sh xsearch` "no result (stopReason=)"
failure by reading Grok's bundled docs (~/.grok/docs/user-guide + README) instead
of probing:
- web_search runs a SEPARATE multi-agent model (grok-4.20-multi-agent), so the
wrapper's blanket --no-subagents strangled it -> indefinite hang, 0 bytes. Scoped
--no-subagents OFF xsearch; use --yolo (documented headless tool-run posture).
- xsearch prompt mandated X/Twitter search on every call (slow multi-agent) and the
budget was 240s -> still timed out. Now web-primary (X only when relevant), 300s.
Validated end-to-end through the wrapper: 23s, correct answer + 3 sources.
Model: pin -m grok-build (xAI flagship, 512k, the documented default) for the
reasoning modes (text/verify/review*) so quality is deterministic and not at the
mercy of the runtime default (this machine drifted to grok-composer-2.5-fast, a fast
Cursor coding model). xsearch + image/video keep the runtime default. Validated text
mode on grok-build (13s).
Doc accuracy (SKILL.md): corrected the model facts (default, the separate web_search
model, --effort unsupported on grok-build per supports_reasoning_effort:false);
documented the xsearch subagent exception. Fixed a stale in-script comment claiming
--rules/--disallowed-tools "tripped the CLI" (both are valid headless flags).
memory: add feedback_interview_ai_read_docs (read bundled docs / interview the model
before probing) + index; errorlog correction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DECISION (Mike, 2026-06-16): drop the RESTAPI package — VPN + SSH shell reads the same data and makes
changes. Confirmed Cascades pfSense is Plus 25.07-RELEASE (current; the "too old" premise was wrong) and
admin SSH = real shell (no menu). The upgrade/package blocker is moot; compat layer is off hold.
- NEW scripts/pfsense-ssh.sh: audit (version/WAN-media/gateway-events/DHCP-exhaustion/states/DNS/load/NIC),
dhcp (pool utilization + no-free-leases), run "<cmd>" (arbitrary, incl changes; operator-gated). Cred
from clients/<slug>/pfsense-firewall; system OpenSSH via askpass. Validated live on Cascades.
- audit report: added "pfSense health check (2026-06-16)" — DHCP NOT exhausted (192.168.0.0/22 pool 270/507,
0 no-free-leases), DNS up, dual-WAN stable (no gateway flaps), states/load healthy => gateway is NOT a
WiFi factor; the 2.4 GHz RF work is the sole fix. (Minor: igc3/WAN2 I225 2.5G counter quirk, not a fault.)
- ROADMAP §E + SKILL.md updated to the SSH backend decision; REST pfsense-backend.sh kept dormant/optional.
- Remaining: named gated CONTROL verbs over SSH (easyrule block-ips, pf/fw toggles) + optional gw-* dispatch.
- Closed obsolete coord todo (upgrade-pfSense-for-RESTAPI).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Howard caught a real hazard: coverage-thin was mesh-blind. At Cascades, 2nd Floor Atrium is the
wireless-mesh PARENT for CC Bridge + salon (backhaul ch36/5GHz), and 206 U7 Pro carries 108. The tool
had listed 2nd Floor Atrium / CC Bridge / 206 as 2.4 disable targets. Although the backhaul is 5GHz
(so a 2.4-radio disable wouldn't drop it), touching infra APs that feed others is needless risk.
Fix: fetch live uplink topology (stat/device); build the mesh set = wireless-uplink APs UNION their
parents; exclude them from disable (kept as coverers if their 2.4 is on); print MESH-PROTECTED line.
Falls back with a clear WARNING if no controller cred. Cascades now auto-excludes 108/206/2nd Atrium/
CC Bridge/salon; resilient plan 34->33. Also verified SSIDs are not AP-pinned (broadcasting_aps off),
so no client is orphaned by a radio disable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Answers "which 2.4 radios can we turn OFF given over-coverage, based on AP proximity." Greedy
dominating-set on the AP-to-AP 2.4 SNR layer: disables radios whose area stays covered by a nearby
ACTIVE-2.4 neighbor, maximizing interference-airtime removed without opening a 2.4 hole. Caps per-zone,
guards coverer capacity, flags single-coverer (low-resilience) disables, reports co-channel before/after.
Why separate from optimize-radios: optimize uses band-AGNOSTIC physical adjacency, so it counts an AP
whose ng radio is DISABLED as a "coverer" via its 5/6 GHz (observed: it proposed disabling 127/229/330/428
"covered by 128" — but 128's 2.4 is already disabled => those would be 2.4 holes). coverage-thin uses the
2.4 SNR layer specifically and only counts neighbors whose 2.4 stays ON.
Cascades (live): aggressive MINCOV=1 -> disable 36/76; resilient MINCOV=2 -> disable 34/76 with >=2 active
2.4 coverers each; co-channel ch6 28->13, ch11 25->13, ch1 20->13; ~2400 interference-airtime pts removed.
Read-only; needs NEIGHBOR_JSON. SKILL.md step 3b.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds `radio-usage.sh <site> <band> --ap "<AP name>"`: lists the devices on one AP's band by merging
live clients (stat/sta) with recent association events (wifi_connectivity_event, band-aware), enriched
from ace.user identity. Tags each device steerable vs legacy:
- from events: DUAL (also seen on 5/6 GHz -> steerable) vs NG-ONLY (2.4-only -> legacy/IoT)
- fallback when no event in the (short ~1d) retention window: randomized MAC = modern phone/laptop
(likely 5G/steerable) vs fixed vendor OUI = likely IoT/legacy.
Decision value: steerable -> fix via band-steering/min-RSSI; a legacy/IoT device present argues AGAINST
disabling that 2.4 radio. Needs controller cred for the live BSSID (vap_table) map; honest about the
short event retention. Validated live on Cascades (347, Dining Room).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Answers "is this 2.4 radio actually used?" from accumulated controller stats (ace_stat.stat_daily,
~77d). Reports per-AP time-avg concurrent users (<radio>-num_sta_avg) + peak station snapshot
(<band>-num_sta), distinguishing avg~0/peak>0 (takes bursts -> POWER-DOWN) from peak==0 (genuinely
unused -> disable-safe). With NEIGHBOR_JSON it crosses low-use APs against the AP-to-AP SNR matrix to
emit a defensible safe-to-disable shortlist (low-use AP + strong overlapping neighbor with headroom),
noting mutual-coverage conflicts and deferring conflict-free selection to optimize-radios.
Validated live on Cascades: of 76 APs only 1 has peak==0 over 77d (the offline AP 108); every other
2.4 radio takes real client bursts (peaks 5-58) at very low avg (12 APs <0.5 concurrent). I.e. the
usage history independently CONFIRMS the conservative power-down-not-disable call. Read-only (Mongo
plane). Uses var-assignment to avoid the legacy-mongo REPL echo. SKILL.md documents it as step 2b.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full verification of the skill against Cascades (live):
- All 19 scripts syntax-clean.
- Controller-side read-only validated live: sites, audit-site, switch-audit, live-stats, model-rank,
optimize-radios, monitor-run, gw-audit. Dry-run apply paths validated: apply-radio, apply-wlan,
client-control, device-control. AP-side mechanism validated: SSH auth + /proc/ui_neighbor read on a
sample AP; full neighbor-collect (74-AP SNR sweep) -> channel-plan end-to-end produced a 1/6/11 plan.
Fixes:
- optimize-radios.sh: the `for(k in prof)` loop's numeric completion value was REPL-echoed by the legacy
mongo shell (stray "94.56..." line in output). Terminated the loop body with `void 0` to suppress it.
- ASCII-clean printed output (CLAUDE.md no-non-ASCII): replaced em-dashes / Unicode arrows / § that
reached stdout and rendered as `?`/mojibake on the Windows console, across optimize-radios,
neighbor-collect, survey-collect, dfs-check, audit-site, sites, monitor-run, apply-radio, apply-wlan,
pfsense-backend. (Comment-only non-ASCII left as-is; never printed.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>