{"owner":"netalertx","repo":"NetAlertX","hasSkills":true,"totalSkillsCount":135,"totalTokensCount":49181,"categories":["anthropic-skill","copilot-instructions","project-spec","root-instruction","plugin-manifest"],"hasMcp":true,"mcpConfig":{"mcpServers":{"NetAlertX":{"command":"npx","args":["-y","@modelcontextprotocol/server-NetAlertX"]}}},"found":[".gemini/skills/plugin-development/plugin-skill.md",".gemini/skills/mcp-activation/SKILL.md",".gemini/skills/initiative-start/reasearch-skill.md",".gemini/skills/testing-workflow/SKILL.md",".gemini/skills/devcontainer-management/SKILL.md",".github/skills/testing-workflow/SKILL.md",".github/skills/skills-overview/SKILL.md",".github/skills/sample-data/SKILL.md",".gemini/skills/project-navigation/SKILL.md",".gemini/skills/settings/SKILL.md",".github/skills/settings-management/SKILL.md",".gemini/skills/skills-index/SKILL.md",".github/skills/project-navigation/SKILL.md",".github/skills/plugin-run-development/SKILL.md",".github/skills/mcp-activation/SKILL.md",".github/skills/docker-prune/SKILL.md",".github/skills/docker-build/SKILL.md",".github/skills/devcontainer-setup/SKILL.md",".github/skills/devcontainer-services/SKILL.md",".github/skills/devcontainer-configs/SKILL.md",".github/skills/database-reset/SKILL.md",".github/skills/database-patterns/SKILL.md",".github/skills/code-standards/SKILL.md",".github/skills/authentication/SKILL.md",".github/skills/api-development/SKILL.md",".github/copilot-instructions.md","docs/API.md","front/llms.txt","server/plugins/README.md","server/plugins/pihole_scan/config.json","server/plugins/pihole_scan/README.md","server/plugins/pihole_api_scan/config.json","server/plugins/pihole_api_scan/README.md","server/plugins/omada_sdn_openapi/config.json","server/plugins/omada_sdn_openapi/README.md","server/plugins/omada_sdn_imp/config.json","server/plugins/omada_sdn_imp/README.md","server/plugins/nslookup_scan/config.json","server/plugins/nslookup_scan/README.md","server/plugins/notification_processing/config.json","server/plugins/notification_processing/README.md","server/plugins/nmap_scan/config.json","server/plugins/nmap_scan/README.md","server/plugins/nmap_dev_scan/config.json","server/plugins/nmap_dev_scan/README.md","server/plugins/newdev_template/config.json","server/plugins/newdev_template/README.md","server/plugins/nbtscan_scan/config.json","server/plugins/nbtscan_scan/README.md","server/plugins/mikrotik_scan/config.json","server/plugins/mikrotik_scan/README.md","server/plugins/maintenance/config.json","server/plugins/maintenance/README.md","server/plugins/luci_import/config.json","server/plugins/luci_import/README.md","server/plugins/kea_api/config.json","server/plugins/kea_api/README.md","server/plugins/ipneigh/config.json","server/plugins/ipneigh/README.md","server/plugins/internet_speedtest/config.json","server/plugins/internet_speedtest/README.md","server/plugins/internet_ip/config.json","server/plugins/internet_ip/README.md","server/plugins/icmp_scan/config.json","server/plugins/icmp_scan/README.md","server/plugins/fritzbox/config.json","server/plugins/fritzbox/README.md","server/plugins/freebox/config.json","server/plugins/freebox/README.md","server/plugins/dig_scan/config.json","server/plugins/dig_scan/README.md","server/plugins/dhcp_servers/config.json","server/plugins/dhcp_servers/README.md","server/plugins/dhcp_leases/ASUS_ROUTERS.md","server/plugins/dhcp_leases/config.json","server/plugins/dhcp_leases/README.md","server/plugins/ddns_update/config.json","server/plugins/ddns_update/README.md","server/plugins/ddns_update/README_DE.md","server/plugins/db_cleanup/config.json","server/plugins/db_cleanup/README.md","server/plugins/custom_props/config.json","server/plugins/custom_props/README.md","server/plugins/csv_backup/config.json","server/plugins/csv_backup/README.md","server/plugins/avahi_scan/config.json","server/plugins/avahi_scan/README.md","server/plugins/asuswrt_import/config.json","server/plugins/asuswrt_import/README.md","server/plugins/arp_scan/config.json","server/plugins/arp_scan/README.md","server/plugins/adguard_import/config.json","server/plugins/adguard_import/README.md","server/plugins/adguard_export/config.json","server/plugins/adguard_export/README.md","server/plugins/_publisher_webhook/config.json","server/plugins/_publisher_webhook/README.md","server/plugins/_publisher_telegram/config.json","server/plugins/_publisher_telegram/README.md","server/plugins/_publisher_pushsafer/config.json","server/plugins/_publisher_pushsafer/README.md","server/plugins/_publisher_pushover/config.json","server/plugins/_publisher_pushover/README.md","server/plugins/_publisher_ntfy/config.json","server/plugins/_publisher_ntfy/README.md","server/plugins/_publisher_mqtt/config.json","server/plugins/_publisher_mqtt/README.md","server/plugins/_publisher_email/config.json","server/plugins/_publisher_email/README.md","server/plugins/_publisher_apprise/config.json","server/plugins/_publisher_apprise/README.md","server/plugins/__template/config.json","server/plugins/__template/README.md","server/plugins/rest_import/config.json","server/plugins/rest_import/README.md","server/plugins/set_password/config.json","server/plugins/set_password/README.md","server/plugins/snmp_discovery/config.json","server/plugins/snmp_discovery/README.md","server/plugins/sync/config.json","server/plugins/sync/README.md","server/plugins/ui_settings/config.json","server/plugins/ui_settings/README.md","server/plugins/unifi_api_import/config.json","server/plugins/unifi_api_import/README.md","server/plugins/unifi_import/config.json","server/plugins/unifi_import/README.md","server/plugins/vendor_update/config.json","server/plugins/vendor_update/README.md","server/plugins/wake_on_lan/config.json","server/plugins/wake_on_lan/README.md","server/plugins/website_monitor/config.json","server/plugins/website_monitor/README.md","server/plugins/workflows/config.json","server/plugins/workflows/README.md"],"skills":{".gemini/skills/plugin-development/plugin-skill.md":"---\nname: netalertx-plugin-development\ndescription: Create and run NetAlertX plugins. Use this when asked to create plugin, run plugin, test plugin, or develop plugin functionality.\n---\n\n# Plugin Development\n\n## Expected Workflow\n\n1. Read this skill and `docs/PLUGINS_DEV.md` for full context.\n2. Find or create the plugin in `server/plugins/<code_name>/`.\n3. Read the plugin's `config.json` and `script.py` to understand its functionality.\n4. Run: `python3 server/plugins/<code_name>/script.py`\n5. Retrieve the result from `/tmp/log/plugins/last_result.<PREF>.log` quickly — the backend deletes it after processing.\n\n## Run a Plugin Manually\n\n```bash\npython3 server/plugins/<code_name>/script.py\n```\n\nEnsure `sys.path` includes `/app/server/plugins` and `/app/server` (as in the template).\n\n## Plugin Structure\n\n```text\nserver/plugins/<code_name>/\n├── config.json      # Manifest with settings\n├── script.py        # Main script\n└── ...\n```\n\n## Plugin Prefix Rules\n\n- Uppercase letters only (no underscores, no numbers)\n- Must be unique across all plugins\n- Short but readable (e.g., `ARPSCAN`, `MQTT`, `UNIFI`)\n\n## Settings Pattern\n\n- `<PREF>_RUN`: execution phase\n- `<PREF>_RUN_SCHD`: cron-like schedule\n- `<PREF>_CMD`: script path\n- `<PREF>_RUN_TIMEOUT`: timeout in seconds — **this is enforced by the core plugin runner as the whole script's kill-timeout** (`server/plugin.py` passes it straight to `subprocess(..., timeout=...)`). It is not a safe per-HTTP-call timeout — don't reuse it for individual network calls in a loop, or one slow call can burn the whole budget and get the process killed before it writes its result file.\n- `<PREF>_WATCH`: columns to watch for changes\n- `<PREF>_IMPORT_ON`: optional — gates whether this run's rows get promoted into `CurrentScan` (only relevant if `mapped_to_table: \"CurrentScan\"`). See `docs/PLUGINS_IMPORT_BEHAVIOR.md` for the related per-row `scanCreatesDevice`/`scanNotificationMode`/`scanPresence` columns.\n- **`dataType` and `default_value` must agree.** `dataType: \"array\"`/`\"object\"` needs a real JSON literal for `default_value` (`'[\"default\"]'`), not a bare string (`\"default\"`). `setting_value_to_python_type()` (`server/helper.py`) `json.loads()`s the default at runtime; a bare string fails silently — logged, and `[]` is returned instead of your default (e.g. `devParentRelType`, `UI_theme`, `UI_TOPOLOGY_ORDER`). If `elementOptions` already sets `multiple`/`orderable: \"false\"`, the setting is scalar — use `dataType: \"string\"` instead.\n\n## Data Contract\n\nScripts write to `/tmp/log/plugins/last_result.<PREF>.log` using `plugin_helper.py`:\n\n```python\nfrom plugin_helper import Plugin_Objects\n\nplugin_objects = Plugin_Objects()\nplugin_objects.add_object(...)  # During processing\nplugin_objects.write_result_file()  # Exactly once at end\n```\n\n**Important:** The backend processes and deletes the result file almost immediately. Retrieve it quickly if inspecting output.\n\nEvery mapped field (`objectPrimaryId`/`objectSecondaryId`/`watchedValue1-4`/`extra`/`helpVal1-4`) is HTML/control-char-stripped by default before it's persisted: plugin output is untrusted (network responses, device-reported names, etc.). `foreignKey` is always sanitized too, unconditionally. Only opt a column out (`\"allow_raw_text\": true`) if it's a display-only type (`textarea_readonly`); see `docs/PLUGINS_DEV.md#field-sanitization`.\n\n## Execution Phases\n\n| Phase | Trigger |\n|-------|---------|\n| `once` | Once at startup |\n| `schedule` | On cron schedule |\n| `always_after_scan` | After every scan |\n| `before_name_updates` | Before name resolution |\n| `on_new_device` | When new device detected |\n| `on_notification` | When notification triggered |\n\n## `all_plugins` Caching\n\n`all_plugins` is loaded once (`initialise.py`'s `get_plugins_configs()`) and reused as the same object until a reload - but `update_api()` (`server/api.py`), which takes it as a parameter, runs on every scan cycle and plugin completion, not just on reload.\n\n- If you write a function that derives something from `all_plugins` and gets called from `update_api()` or another frequent call site, cache the result instead of recomputing every call - retain a reference to the last-seen `all_plugins` list and compare it to the current one with `is`, not by `id(all_plugins)` alone (once the old list is garbage collected, Python can reuse that id for an unrelated object, wrongly serving a stale cached result for what is actually a new reload)\n- Pattern: `server/db/plugin_field_views.py`'s `get_all_device_field_views()`\n\n## Plugin Formats\n\n| Format | Purpose | Phase |\n|--------|---------|-------|\n| publisher | Send notifications | `on_notification` |\n| dev scanner | Create/manage devices | `schedule` |\n| name discovery | Discover device names | `before_name_updates` |\n| importer | Import from services | `schedule` |\n| system | Core functionality | `schedule` |\n\n## Before Opening a PR\n\nCheck the plugin against the [Conventions Checklist](../../../docs/PLUGINS_DEV.md#conventions-checklist) in `docs/PLUGINS_DEV.md` — `RUN` default, schedule precedent, `RUN_TIMEOUT` semantics, reusing core settings, description length, the multi-instance settings pattern, `allow_raw_text` restricted to display-only column types, and `scanSourcePlugin`'s static value matching `unique_prefix` exactly. Most plugin PR review comments trace back to one of these.\n\nIf the plugin needs a new system package or Python dependency, mirroring it into the root `Dockerfile`/`requirements.txt` alone is not enough: see the Conventions Checklist's build-target-mirroring bullet for `.devcontainer/Dockerfile` (regenerate via `.devcontainer/scripts/generate-configs.sh`, don't hand-edit it), `Dockerfile.debian`, and `install/ubuntu24`/`install/proxmox`'s own `requirements.txt` files.\n\n## Starting Point\n\nCopy from `server/plugins/__template` and customize. Read `docs/PLUGINS_DEV.md` for the full development guide.\n\n\n",".gemini/skills/mcp-activation/SKILL.md":"---\nname: mcp-activation\ndescription: Enables live interaction with the NetAlertX runtime. This skill configures the Model Context Protocol (MCP) connection, granting full API access for debugging, troubleshooting, and real-time operations including database queries, network scans, and device management.\n---\n\n# MCP Activation Skill\n\nThis skill configures the NetAlertX development environment to expose the Model Context Protocol (MCP) server to AI agents.\n\n## Why use this?\n\nBy default, agents only have access to the static codebase (files). To perform dynamic actions—such as:\n- **Querying the database** (e.g., getting device lists, events)\n- **Triggering actions** (e.g., network scans, Wake-on-LAN)\n- **Validating runtime state** (e.g., checking if a fix actually works)\n\n...you need access to the **MCP Server** running inside the container. This skill sets up the necessary authentication tokens and connection configs to bridge your agent to that live server.\n\n## Prerequisites\n\n1.  **Devcontainer:** You must be connected to the NetAlertX devcontainer.\n2.  **Server Running:** The backend server must be running (to generate `app.conf` with the API token).\n\n## Activation Steps\n\n1.  **Activate Devcontainer Skill:**\n    If you are not already inside the container, activate the management skill:\n    ```text\n    activate_skill(\"devcontainer-management\")\n    ```\n\n2.  **Generate Configurations:**\n    Run the configuration generation script *inside* the container. This script extracts the API Token and creates the necessary settings files (`.gemini/settings.json` and `.vscode/mcp.json`).\n\n    ```bash\n    # Run inside the container\n    /workspaces/NetAlertX/.devcontainer/scripts/generate-configs.sh\n    ```\n\n3.  **Apply Changes:**\n\n    *   **For Gemini CLI:**\n        The agent session must be **restarted** to load the new `.gemini/settings.json`.\n        > \"I have generated the MCP configuration. Please **restart this session** to activate the `netalertx-devcontainer` tools.\"\n\n    *   **For VS Code (GitHub Copilot / Cline):**\n        The VS Code window must be **reloaded** to pick up the new `.vscode/mcp.json`.\n        > \"I have generated the MCP configuration. Please run **'Developer: Reload Window'** in VS Code to activate the MCP server.\"\n\n## Verification\n\nAfter restarting, you should see new tools available (e.g., `netalertx-devcontainer__get_devices`).\n",".gemini/skills/initiative-start/reasearch-skill.md":"### 🧠 NAX Research Skill — Specification\n\nThis defines a **Research Skill module for NAX (NetAlertX)** focused on safe, structured analysis before any implementation work.\n\n---\n\n## 1. Purpose\n\nEnsure all work begins with **documentation-first understanding**, **PRD validation**, and **conflict detection**, before any planning or coding.\n\n---\n\n## 2. Core Workflow\n\n### Step 1 — Documentation First\n\n* Always begin by reading relevant repository documentation.\n\n* Priority order:\n\n  1. `/CONTRIBUTING.md`\n  2. `/README.md`\n  3. `/.github/skills/code-standards/SKILL.md`\n  4. `/.github/skills/database-patterns/SKILL.md` — read when the feature touches the `Devices` table or requires audit/history logging\n  5. `/docs/**`\n  6. Related module/code context if referenced\n\n* Extract:\n\n  * Architecture expectations\n  * Coding standards\n  * Plugin or module conventions\n  * Existing workflows or constraints\n\n---\n\n### Step 2 — PRD Check\n\n* If a PRD (Product Requirements Document) is NOT provided:\n\n  * Explicitly request it before proceeding further\n  * Do not assume requirements\n\n* If PRD is provided:\n\n  * Parse and restate key requirements internally\n  * Identify scope boundaries\n\n---\n\n### Step 3 — Clarification Gate\n\nIf anything is unclear:\n\n* Stop immediately\n* Ask targeted clarifying questions\n* Do NOT propose solutions yet\n\n---\n\n### Step 4 — Codebase Cross-Check\n\n* Compare PRD + documentation against existing codebase\n\n* Identify:\n\n  * Conflicting behavior\n  * Outdated patterns\n  * Duplicate logic\n  * Breaking assumptions\n  * Plugin or API mismatches\n\n* **If the feature writes to the `Devices` table:**\n\n  * Load `/.github/skills/database-patterns/SKILL.md`\n  * Audit all write paths listed there — confirm which are affected\n  * Determine if a SQLite trigger or Python hook is the right approach\n  * Check if `*Source` fields already solve attribution requirements\n\n* Clearly report inconsistencies before proceeding\n\n---\n\n### Step 5 — Planning Requirement (Strict)\n\nBefore any implementation:\n\n* Produce a structured plan including:\n\n  * Approach overview\n  * Files/modules affected\n  * Dependencies\n  * Risk areas\n  * Migration considerations (if any)\n\n* Explicitly label:\n\n  > “WAITING FOR USER CONFIRMATION”\n\n---\n\n### Step 6 — Implementation Gate (Hard Rule)\n\n* Do NOT start implementation until user explicitly confirms the plan\n* No partial coding, no early patches, no assumptions\n\n---\n\n## 3. Behavioral Constraints\n\n* Always prioritize correctness over speed\n* Never skip PRD validation\n* Never proceed past ambiguity\n* Never implement without approval\n* Always surface contradictions in source material\n* Always prefer asking questions over guessing\n\n---\n\n## 4. Output Style Rules\n\n* Be structured and technical\n* Avoid unnecessary verbosity\n* Separate:\n\n  * Findings\n  * Risks\n  * Questions\n  * Plan\n* No hidden assumptions\n\n---\n\n## 5. Summary Flow\n\n```\nDocs → PRD → Clarify → Codebase Check → Plan → User Approval → Implement\n```\n\n",".gemini/skills/testing-workflow/SKILL.md":"---\nname: testing-workflow\ndescription: Read before running tests. Detailed instructions for single, standard unit tests (fast), full suites (slow), handling authentication, and obtaining the API Token. Tests must be run when a job is complete.\n---\n\n# Testing Workflow\nAfter code is developed, tests must be run to ensure the integrity of the final result.\n\n**Crucial:** Tests MUST be run inside the container to access the correct runtime environment (DB, Config, Dependencies).\n\n## 0. Pre-requisites: Environment Check\n\nBefore running any tests, verify you are inside the development container:\n\n```bash\nls -d /workspaces/NetAlertX\n```\n\n**IF** this directory does not exist, you are likely on the host machine. You **MUST** immediately activate the `devcontainer-management` skill to enter the container or run commands inside it.\n\n```text\nactivate_skill(\"devcontainer-management\")\n```\n\n## 1. Full Test Suite (MANDATORY DEFAULT)\n\nUnless the user **explicitly** requests \"fast\" or \"quick\" tests, you **MUST** run the full test suite. **Do not** optimize for time. Comprehensive coverage is the priority over speed.\n\n```bash\ncd /workspaces/NetAlertX; pytest test/\n```\n\n## 2. Fast Unit Tests (Conditional)\n\n**ONLY** use this if the user explicitly asks for \"fast tests\", \"quick tests\", or \"unit tests only\". This **excludes** slow tests marked with `docker` or `feature_complete`.\n\n```bash\ncd /workspaces/NetAlertX; pytest test/ -m 'not docker and not feature_complete'\n```\n\n## 3. Running Specific Tests\n\nTo run a specific file or folder:\n\n```bash\ncd /workspaces/NetAlertX; pytest test/<path_to_test>\n```\n\n*Example:*\n```bash\ncd /workspaces/NetAlertX; pytest test/api_endpoints/test_mcp_extended_endpoints.py\n```\n\n## Authentication & Environment Reset\n\nAuthentication tokens are required to perform certain operations such as manual testing or crafting expressions to work with the web APIs. After making code changes, you MUST reset the environment to ensure the new code is running and verify you have the latest `API_TOKEN`.\n\n1. **Reset Environment:** Run the setup script inside the container.\n   ```bash\n   bash /workspaces/NetAlertX/.devcontainer/scripts/setup.sh\n   ```\n2. **Wait for Stabilization:** Wait at least 5 seconds for services (nginx, python server, etc.) to start.\n   ```bash\n   sleep 5\n   ```\n3. **Obtain Token:** Retrieve the current token from the container.\n   ```bash\n   python3 -c \"from helper import get_setting_value; print(get_setting_value('API_TOKEN'))\"\n   ```\n\nThe retrieved token MUST be used in all subsequent API or test calls requiring authentication.\n\n### Troubleshooting\n\nIf tests fail with 403 Forbidden or empty tokens:\n1. Verify server is running and use the setup script (`/workspaces/NetAlertX/.devcontainer/scripts/setup.sh`) if required.\n2. Verify `app.conf` inside the container: `cat /data/config/app.conf`\n\n## Check for Pre-Existing Failures\n\nBefore attributing failures to your changes, check what was already broken:\n\n```bash\ncd /workspaces/NetAlertX; pytest test/ --tb=no -q 2>&1 | tail -20\n```\n\nDo not fix pre-existing failures unless that is the explicit goal.\n\n## PYTHONPATH\n\nThe test environment is pre-configured with:\n- `/app` — primary location where Python runs in production\n- `/app/server` — symlink to `/workspaces/NetAlertX/server`\n- `/app/server/plugins` — symlink to `/workspaces/NetAlertX/server/plugins`\n- `/opt/venv/lib/pythonX.Y/site-packages`\n- `/workspaces/NetAlertX/test`\n- `/workspaces/NetAlertX/server`\n- `/workspaces/NetAlertX`\n- `/usr/lib/pythonX.Y/site-packages`\n\n## Docker Test Image\n\nIf the Dockerfile or dependencies changed, rebuild the test image before running:\n\n```bash\ndocker buildx build -t netalertx-test .\n```\n\nTakes ~30 seconds; ~90 seconds if the venv stage changed.\n\n## Pitfall: `sys.modules` Stubbing Leaks Across Test Files\n\nSome plugin tests (e.g. `test/plugins/test_ntfy_custom_headers.py`) stub NetAlertX\nmodules (`conf`, `helper`, `models.notification_instance`, etc.) via\n`sys.modules[name] = fake_module` so the plugin script can be imported standalone,\noutside the container. Because `sys.modules` is a single process-wide cache shared\nby the whole pytest session, a fake module inserted by one test file silently\nshadows the real module for every other test file collected afterwards — pytest\nimports all test files during collection, before any test runs, so this can happen\nregardless of alphabetical/directory order.\n\nSymptom: `AttributeError: <module 'models.notification_instance'> does not have\nthe attribute 'get_setting_value'` (or similar) in an unrelated test file, where\nthe module repr has no `from '<path>'` suffix — a giveaway that a stub, not the\nreal module, was resolved.\n\nFix pattern: track which module names your stub actually inserted, and pop them\nback out of `sys.modules` immediately after the one-time import that needed them\n(the already-imported script keeps its bound names regardless):\n\n```python\n_stubbed_module_names = []\n\ndef _stub(name, **attrs):\n    if name not in sys.modules:\n        mod = types.ModuleType(name)\n        for k, v in attrs.items():\n            setattr(mod, k, v)\n        sys.modules[name] = mod\n        _stubbed_module_names.append(name)\n\n# ... _stub(...) calls, then the one-time import ...\nimport ntfy\n\nfor _name in _stubbed_module_names:\n    sys.modules.pop(_name, None)\n```",".gemini/skills/devcontainer-management/SKILL.md":"---\nname: devcontainer-management\ndescription: Guide for identifying, managing, and running commands within the NetAlertX development container. Use this when asked to run commands, testing, setup scripts, or troubleshoot container issues.\n---\n\n# Devcontainer Management\n\nWhen starting a session or performing tasks requiring the runtime environment, you must identify and use the active development container.\n\n## Finding the Container\n\nRun `docker ps` to list running containers. Look for an image name containing `vsc-netalertx` or similar.\n\n```bash\ndocker ps --format \"table {{.ID}}\\t{{.Image}}\\t{{.Status}}\\t{{.Names}}\" | grep netalertx\n```\n\n- **If no container is found:** Inform the user. You cannot run integration tests or backend logic without it.\n- **If multiple containers are found:** Ask the user to clarify which one to use (e.g., provide the Container ID).\n\n## Running Commands in the Container\n\nPrefix commands with `docker exec <CONTAINER_ID>` to run them inside the environment. Use the scripts in `/services/` to control backend and other processes.\n\n```bash\ndocker exec <CONTAINER_ID> bash /workspaces/NetAlertX/.devcontainer/scripts/setup.sh\n```\n\n*Note: This script wipes `/tmp` ramdisks, ensures `/data`, `/data/config`, `/data/db` exist, and restarts services (python server, cron, php-fpm, nginx) by symlinking and running `/entrypoint.sh` (`install/production-filesystem/entrypoint.d/`). By default it does **not** delete existing database or config content - `entrypoint.d/25-first-run-db.sh` and `20-first-run-config.sh` only wipe them when `ALWAYS_FRESH_INSTALL=true` is set in the environment (default: unset/`false`, so content is preserved).*\n\n```\n",".github/skills/testing-workflow/SKILL.md":"---\nname: netalertx-testing-workflow\ndescription: Run and debug tests in the NetAlertX devcontainer. Use this when asked to run tests, check test failures, debug failing tests, or execute pytest.\n---\n\n# Testing Workflow\n\n## Pre-Flight Check (MANDATORY)\n\nBefore running any tests, always check for existing failures first:\n\n1. Use the `testFailure` tool to gather current failure information\n2. Review the failures to understand what's already broken\n3. Only then proceed with test execution\n\n## Running Tests\n\nUse VS Code's testing interface or the `runTests` tool with appropriate parameters:\n\n- To run all tests: invoke runTests without file filter\n- To run specific test file: invoke runTests with the test file path\n- To run failed tests only: invoke runTests with `--lf` flag\n\n## Test Location\n\nTests live in `test/` directory. App code is under `server/`.\n\nPYTHONPATH is preconfigured to include the following which should meet all needs:\n- `/app` # the primary location where python runs in the production system\n- `/app/server` # symbolic link to /wprkspaces/NetAlertX/server\n- `/app/server/plugins` # symbolic link to /workspaces/NetAlertX/server/plugins\n- `/opt/venv/lib/pythonX.Y/site-packages`\n- `/workspaces/NetAlertX/test`\n- `/workspaces/NetAlertX/server`\n- `/workspaces/NetAlertX`\n- `/usr/lib/pythonX.Y/site-packages`\n\n## Authentication in Tests\n\nRetrieve `API_TOKEN` using Python (not shell):\n\n```python\nfrom helper import get_setting_value\ntoken = get_setting_value(\"API_TOKEN\")\n```\n\n## Troubleshooting 403 Forbidden\n\n1. Ensure backend is running (use devcontainer-services skill)\n2. Verify config loaded: `get_setting_value(\"API_TOKEN\")` returns non-empty\n3. Re-run startup if needed (use devcontainer-setup skill)\n\n## Docker Test Image\n\nIf container changes affect tests, rebuild the test image first:\n\n```bash\ndocker buildx build -t netalertx-test .\n```\n\nThis takes ~30 seconds unless venv stage changes (~90s).\n\n## Pitfall: `sys.modules` Stubbing Leaks Across Test Files\n\nSome plugin tests (e.g. `test/plugins/test_ntfy_custom_headers.py`) stub NetAlertX\nmodules (`conf`, `helper`, `models.notification_instance`, etc.) via\n`sys.modules[name] = fake_module` so the plugin script can be imported standalone,\noutside the container. Because `sys.modules` is a single process-wide cache shared\nby the whole pytest session, a fake module inserted by one test file silently\nshadows the real module for every other test file collected afterwards — pytest\nimports all test files during collection, before any test runs, so this can happen\nregardless of alphabetical/directory order.\n\nSymptom: `AttributeError: <module 'models.notification_instance'> does not have\nthe attribute 'get_setting_value'` (or similar) in an unrelated test file, where\nthe module repr has no `from '<path>'` suffix — a giveaway that a stub, not the\nreal module, was resolved.\n\nFix pattern: track which module names your stub actually inserted, and pop them\nback out of `sys.modules` immediately after the one-time import that needed them\n(the already-imported script keeps its bound names regardless):\n\n```python\n_stubbed_module_names = []\n\ndef _stub(name, **attrs):\n    if name not in sys.modules:\n        mod = types.ModuleType(name)\n        for k, v in attrs.items():\n            setattr(mod, k, v)\n        sys.modules[name] = mod\n        _stubbed_module_names.append(name)\n\n# ... _stub(...) calls, then the one-time import ...\nimport ntfy\n\nfor _name in _stubbed_module_names:\n    sys.modules.pop(_name, None)\n```\n\nReproduce cross-file pollution locally by running the suspect file together with\nthe affected one in a single pytest invocation (order matters less than you'd\nthink — collection happens for all files first):\n\n```bash\npytest test/plugins/test_ntfy_custom_headers.py test/backend/test_notification_templates.py -v\n```\n",".github/skills/skills-overview/SKILL.md":"---\nname: skills-overview\ndescription: Index of all available skills across GitHub Copilot (.github/skills/), Gemini CLI (.gemini/skills/), and Claude Code (.claude/skills/). Load this to find the right skill for a task, or to locate the counterpart skill in another assistant's tree.\n---\n\n# Skills Index — Cross-Reference\n\nThree AI assistants are configured for this project, each with their own skill directory:\n\n- **GitHub Copilot** → `.github/skills/`\n- **Gemini CLI** → `.gemini/skills/`\n- **Claude Code** → `.claude/skills/` (mirrors most, not all, of the shared skills below — see the table's Claude Skill column for which)\n\nSkills with the same purpose exist in more than one, sometimes under different names and with different depth. This index maps them so you can find the richer version when needed. A CI check (`scripts/check_skill_pairs.py`, run as `check-skill-pairs` in `.github/workflows/code-checks.yml`) flags PRs that touch some but not all files in a mirrored group - non-blocking, since some divergence is intentional.\n\n---\n\n## Shared Skills (exist in more than one tree)\n\n| Topic | Copilot Skill | Gemini Skill | Claude Skill | Notes |\n|-------|--------------|--------------|--------------|-------|\n| Testing | `testing-workflow` | `testing-workflow` | `testing-workflow` | All three cover the full-suite-by-default rule, PYTHONPATH, auth/token retrieval, and the `sys.modules` stubbing pitfall |\n| Settings & config | `settings-management` | `settings` | — | Gemini version is more comprehensive (22-point guide + PR checklist); Copilot version covers `ccd()` and `get_setting_value()` usage |\n| MCP activation | `mcp-activation` | `mcp-activation` | — | Copilot version covers VS Code window reload; Gemini version covers Gemini CLI session restart |\n| Project navigation | `project-navigation` | `project-navigation` | — | Copilot version has full path tables and env vars; Gemini version is a brief reference |\n| Plugin dev | `plugin-run-development` | `plugin-development` | `plugin-development` | All three cover data contract, phases, formats, the `RUN_TIMEOUT` kill-timer gotcha (`timeoutMultiplier`/`per_item_timeout()`), and a pre-PR pointer to the Conventions Checklist in `docs/PLUGINS_DEV.md` |\n| Plugin README docs | `plugin-readme` | `plugin-readme` | `plugin-readme` | All three cover README structure, the \"don't re-document settings\" rule, the `docs.netalertx.com` cross-linking convention, and common defects (template leftovers, copy-paste errors) found during a full-repo audit |\n| Devcontainer | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | `devcontainer-management` | — | Copilot splits into 3 focused skills; Gemini combines into one (uses `docker exec`). `devcontainer-configs` also covers Dockerfile generation (root `Dockerfile` → `generate-dockerfile.sh` → `.devcontainer/Dockerfile`, source files not present at build time) - mined from real review comments on PR #1184/#1230. |\n| Install scripts | `install-scripts` | `install-scripts` | `install-scripts` | `install/*` conventions mined from real review comments (#1214/#1230/#1235): uninstall must only remove NetAlertX's own files, never system packages; `check-*.sh` scripts intentionally differ on fail-fast vs. warning-only; `/back` is legacy, not the active code path. |\n| PR review | `pr-analysis` | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist; the standing rule that a repeated review comment becomes a skill update (sourced from jokob's own real PR comments, #1739/#1744); a readability-over-cleverness code-style note from the same source. |\n| Logging | `logging-standards` | `logging-standards` | — | `mylog` levels, message format, what not to log |\n| Scan pipeline internals | `scan-pipeline` | `scan-pipeline` | `scan-pipeline` | `process_scan()` call order and why it's load-bearing, `CurrentScan`/`Events`/`Sessions`/`DevicesView` relationships, how a session actually closes (no `close_session()` exists), and the `FIELD_SPECS` field-write authority mechanism. Complements `database-patterns` (Devices write-path/`*Source` attribution) rather than duplicating it. |\n| Database patterns | `database-patterns` | `database-patterns` | `database-patterns` | Devices table write-path inventory, the `FIELD_SOURCE_MAP`/`*Source` attribution system in `server/db/authoritative_handler.py`, SQLite trigger vs. Python-hook tradeoffs, and event-sourced vs. snapshot audit logging. |\n| PRD writing | `prd-writing` | `prd-writing` | `prd-writing` | Methodology for writing a design doc: challenge the idea, verify every claim against actual code, trace every downstream consumer of a new mechanism, evaluate performance impact against the real schema/indexes, record rejected alternatives and open-issue decisions explicitly, final-check pass before done. |\n| UX/frontend design | `ux-design-patterns` | `ux-design-patterns` | `ux-design-patterns` | Don't invent new UX behavior/visual patterns unless a PRD calls for it - search `front/` for an existing pattern first and reuse it. Priority order for design tradeoffs when several options are reasonable: existing behavior > intuitiveness > information density > usability > utility > uniqueness > industry practices > generic UI. |\n| Skill hygiene | `skill-hygiene` | `skill-hygiene` | `skill-hygiene` | Read before writing/editing any SKILL.md, or any research/audit doc in `.gemini/internal-docs/research/`. Two standing rules: state current behavior only (no \"Correction:\", no \"as of <date>\", no \"caught in review\" narration - that trail belongs in PRDs), and prefer plain, short wording. Includes the grep sweep to run before calling a doc clean. |\n| Plugin review | `plugin-review` | `plugin-review` | `plugin-review` | Reviewer-facing complement to Plugin dev above: no raw SQL in a plugin script - use an existing/new `server/models/*.py` method, not GraphQL (which no plugin reaches). Named exception list for 5 pre-existing core/infra plugins with legitimate direct DB access, a collation-checking step, and a worked example from a real PR (#1788, DOCKERDISC). |\n\n---\n\n## Copilot-Only Skills\n\nNo Gemini equivalent yet:\n\n| Skill | Purpose |\n|-------|---------|\n| `api-development` | Creating REST API endpoints |\n| `authentication` | API tokens and 401/403 debugging |\n| `code-standards` | Coding conventions and style rules |\n| `database-reset` | Wipe and regenerate the database and config |\n| `docker-build` | Build Docker images for testing or production |\n| `docker-prune` | Clean unused Docker resources (destructive — requires confirmation) |\n| `sample-data` | Load synthetic device data into the devcontainer |\n\n---\n\n## Gemini-Only Skills\n\nNo Copilot equivalent yet:\n\n| Skill | Purpose |\n|-------|---------|\n| `initiative-start` | Research methodology and structured approach for new tasks |\n\n---\n\n## Adding a New Skill\n\nWhen adding a skill, create it in **both** directories to keep both AI systems current:\n\n- `.github/skills/<name>/SKILL.md` — add an entry to the skills table in `.github/copilot-instructions.md`\n- `.gemini/skills/<name>/SKILL.md` — auto-discovered by Gemini CLI via YAML frontmatter\n\nKeep the body content identical between both files. Only the frontmatter `name`/`description` may differ slightly to match each system's discovery heuristics.\n\nIf the skill is high-value enough to also mirror to Claude Code, add `.claude/skills/<name>/SKILL.md` too, and add the group to `GROUPS` in `scripts/check_skill_pairs.py` so drift gets flagged. Claude Code has no `activate_skill()`/`testFailure`/`runTests`/`report_progress` equivalents - adapt any such tool references to plain `Bash` commands instead of copying them verbatim.\n",".github/skills/sample-data/SKILL.md":"---\nname: netalertx-sample-data\ndescription: Load synthetic device data into the devcontainer. Use this when asked to load sample devices, seed data, import test devices, populate database, or generate test data.\n---\n\n# Sample Data Loading\n\nGenerates synthetic device inventory and imports it via the `/devices/import` API endpoint.\n\n## Command\n\n```bash\ncd /workspaces/NetAlertX/.devcontainer/scripts\n./load-devices.sh\n```\n\n## Environment\n\n- `CSV_PATH`: defaults to `/tmp/netalertx-devices.csv`\n\n## Prerequisites\n\n- Backend must be running\n- API must be accessible\n\n## What It Does\n\n1. Generates synthetic device records (MAC addresses, IPs, names, vendors)\n2. Creates CSV file at `$CSV_PATH`\n3. POSTs to `/devices/import` endpoint\n4. Devices appear in database and UI\n",".gemini/skills/project-navigation/SKILL.md":"---\nname: project-navigation\ndescription: Reference for the NetAlertX codebase structure, key file paths, and configuration locations. Use this when exploring the codebase or looking for specific components like the backend entry point, frontend files, or database location.\n---\n\n# Project Navigation\n\n## Key Paths\n\n| Component | Path |\n|-----------|------|\n| Workspace root | `/workspaces/NetAlertX` |\n| Backend entry | `server/__main__.py` |\n| API server | `server/api_server/api_server_start.py` |\n| Plugin system | `server/plugin.py` |\n| Initialization | `server/initialise.py` |\n| Frontend | `front/` |\n| Frontend JS | `front/js/common.js` |\n| Frontend PHP | `front/php/server/*.php` |\n| Plugins | `server/plugins/` |\n| Plugin template | `server/plugins/__template` |\n| Database helpers | `server/db/db_helper.py` |\n| Device model | `server/models/device_instance.py` |\n| Messaging | `server/messaging/` |\n| Workflows | `server/workflows/` |\n\n## Architecture\n\nNetAlertX uses a frontend–backend architecture: the frontend runs on **PHP + Nginx** (see `front/`), the backend is implemented in **Python** (see `server/`), and scheduled tasks are managed by a **supercronic** scheduler.\n\n## Runtime Paths\n\n| Data | Path |\n|------|------|\n| Config (runtime) | `/data/config/app.conf` |\n| Config (default) | `back/app.conf` |\n| Database | `/data/db/app.db` |\n| API JSON cache | `/tmp/api/*.json` |\n| Logs | `/tmp/log/` |\n| Plugin logs | `/tmp/log/plugins/` |\n\n## Environment Variables\n\nUse these instead of hardcoding paths:\n- `NETALERTX_DB`\n- `NETALERTX_LOG`\n- `NETALERTX_CONFIG`\n- `NETALERTX_DATA`\n- `NETALERTX_APP`\n\n## Documentation\n\n| Topic | Path |\n|-------|------|\n| Plugin development | `docs/PLUGINS_DEV.md` |\n| System settings | `docs/SETTINGS_SYSTEM.md` |\n| API docs | `docs/API_*.md` |\n| Debug guides | `docs/DEBUG_*.md` |\n",".gemini/skills/settings/SKILL.md":"---\nname: nax-settings-development\ndescription: Best practices for creating and maintaining NetAlertX settings (config.json), including UI, validation, localization, runtime behavior, and implementation consistency.\n---\n\n# NetAlertX Settings Development\n\nThis skill defines the conventions and best practices for adding or modifying settings in NetAlertX.\n\nThe goal is for every setting to be:\n\n- Declarative\n- Self-documenting\n- Automatically rendered by the Settings UI\n- Fully localized\n- Backwards compatible\n- Easy to maintain\n\n---\n\n## Quick Reference\n\n**Read a setting (backend):**\n```python\nfrom helper import get_setting_value\nvalue = get_setting_value('SETTING_NAME')\n```\nNever read `app.conf` directly. Always use `get_setting_value()`.\n\n**Read a setting (frontend):**\n```javascript\ngetSetting(\"SETTING_NAME\")\n```\n\n**Add a core setting** — use `ccd()` in `server/initialise.py`:\n```python\nccd('SETTING_NAME', 'default_value', 'description')\n```\n\n**Add a plugin setting** — define in the plugin's `config.json` under the `settings` key.\n\n| File | Purpose |\n|------|---------|\n| `/data/config/app.conf` | Runtime config (source of truth) |\n| `back/app.conf` | Default config (template) |\n\nUse `APP_CONF_OVERRIDE` for settings that must be set before startup.\n\n---\n\n# 1. Follow existing patterns\n\nBefore creating a new setting:\n\n- Search for similar settings.\n- Reuse existing naming conventions.\n- Reuse existing setting types.\n- Reuse existing validation patterns.\n- Follow the style already used by similar plugins.\n\nAvoid introducing new setting types unless absolutely necessary.\n\n---\n\n# 2. Setting naming\n\nSetting keys use uppercase snake case.\n\nGood:\n\n```text\nUI_DEV_SECTIONS\nSCAN_INTERVAL\nMQTT_HOST\n```\n\nAvoid:\n\n```text\nuiDevSections\nscanInterval\nScanInterval\n```\n\nNames should clearly describe the purpose.\n\n---\n\n# 3. Categories\n\nPlace settings into the most appropriate category.\n\nKeep related settings together.\n\nAvoid creating new categories unless there is a clear need.\n\n---\n\n# 4. Choose the correct type\n\nUse the simplest type that correctly models the value.\n\nCommon types include:\n\n- string\n- integer\n- float\n- boolean\n- password\n- select\n- array\n- textarea\n\nAvoid encoding structured JSON inside string settings.\n\n---\n\n# 5. Validation\n\nAlways define validation whenever appropriate.\n\nExamples include:\n\n- minimum values\n- maximum values\n- regex validation\n- allowed options\n\nReject invalid configuration rather than silently accepting it.\n\n---\n\n# 6. Defaults\n\nProvide sensible defaults that work for a fresh installation.\n\nAvoid defaults that require external services or additional configuration.\n\nA default value should exist in exactly one place.\n\nUse the `default_value` defined in `config.json`.\n\nDo not duplicate the same default value elsewhere in Python or JavaScript.\n\n---\n\n# 7. Names and descriptions\n\nSetting names should be concise.\n\nExamples:\n\n- Scan interval\n- MQTT Host\n- Hide device sections\n\nDescriptions should explain:\n\n- what the setting does\n- when it should be used\n- important side effects\n\nAvoid implementation details.\n\nGood:\n\n> Interval between network scans in seconds.\n\nBad:\n\n> Calls scheduler.py every X seconds.\n\n---\n\n# 8. Boolean settings\n\nBoolean settings should read naturally.\n\nPrefer verbs such as:\n\n- Enable\n- Disable\n- Require\n- Allow\n- Ignore\n- Show\n- Hide\n\nExamples:\n\n- Enable MQTT\n- Require NICs Online\n- Hide Offline Devices\n\n---\n\n# 9. Select settings\n\nWhen users must choose from predefined values, use a select setting.\n\nNever require users to remember internal values.\n\nEach option should have a meaningful label.\n\n---\n\n# 10. Array settings\n\nUse arrays only when multiple independent values are expected.\n\nExamples:\n\n- ignored MAC addresses\n- subnet lists\n- plugin lists\n\n---\n\n# 11. Localization\n\nEvery setting must have localized language strings.\n\nIf the language strings are **not** defined directly inside `config.json`, they must exist in `en_us.json`.\n\nFor example, for:\n\n```text\nUI_DEV_SECTIONS\n```\n\nadd:\n\n```json\n\"UI_DEV_SECTIONS_name\": \"Hide device sections\",\n\"UI_DEV_SECTIONS_description\": \"Select which UI elements to hide on the Devices page.\"\n```\n\nWhen using external language files, `config.json` must reference:\n\n```json\n\"name\": [\n  {\n    \"string\": \"_GLOBAL_LANG_FILES_\"\n  }\n]\n```\n\nThis tells NetAlertX to resolve the display name from the language files.\n\n---\n\n# 12. Source of truth\n\n`app.conf` is the source of truth.\n\nKeep the following in mind:\n\n- User-defined values always come from `app.conf`.\n- Default values from `config.json` are only used when the setting is missing from `app.conf`.\n- Never assume settings are permanently stored in the database.\n\n---\n\n# 13. Plugin-first settings\n\nWhenever possible, define new settings inside a plugin's `config.json`.\n\nAvoid adding hardcoded application settings unless there is a compelling architectural reason.\n\nPlugin settings are the preferred and future-proof approach.\n\n---\n\n# 14. Runtime lifecycle\n\nUnderstand how settings flow through the application.\n\n```text\nconfig.json\n        │\n        │ default values\n        ▼\napp.conf (source of truth)\n        │\n        ▼\nSettings database table\n        │\n        ▼\ntable_settings.json API\n        │\n        ▼\nFrontend (getSetting())\n```\n\nThe database and API are runtime representations only.\n\nThey are regenerated from `app.conf` during initialization.\n\n---\n\n# 15. Access\n\nCode should never read `app.conf` directly.\n\nIn the Frontend, always retrieve settings through:\n\n```javascript\ngetSetting(\"SETTING_NAME\")\n```\n\nAnd in the backend, always retrieve settings through:\n\n```python\nget_setting_value(\"SETTING_NAME\")\n```\n\nThis guarantees the value comes from the generated settings API.\n\n---\n\n# 16. Metadata\n\nEach setting automatically has a corresponding `__metadata` entry generated in `app.conf`.\n\nDo not manually create or modify metadata entries unless working on the settings framework itself.\n\n---\n\n# 17. Backend\n\nBackend code should:\n\n- use the existing configuration helpers\n- avoid duplicated parsing logic\n- gracefully handle missing values\n- avoid hardcoded defaults\n\n---\n\n# 18. Frontend\n\nA correctly defined setting should render automatically in the existing Settings UI.\n\nAvoid writing custom JavaScript unless absolutely necessary.\n\n---\n\n# 19. Prefer configuration over code\n\nIf a behaviour can reasonably be controlled through an existing setting type (boolean, select, integer, array, etc.), introduce a setting instead of hardcoding special-case logic.\n\nConfiguration is preferred over implementation-specific behaviour whenever practical.\n\n---\n\n# 20. Backwards compatibility\n\nAvoid:\n\n- renaming settings\n- changing data types\n- changing semantics\n\nIf unavoidable:\n\n- provide migration logic\n- preserve compatibility where possible\n\n---\n\n# 21. Documentation\n\nEvery user-facing setting should eventually be documented.\n\nInclude:\n\n- purpose\n- accepted values\n- default value\n- examples when useful\n\n---\n\n# 22. Pull request checklist\n\nBefore submitting a PR, verify:\n\n- [ ] Setting follows existing conventions.\n- [ ] Correct category used.\n- [ ] Appropriate type selected.\n- [ ] Validation defined.\n- [ ] Sensible default provided.\n- [ ] Default defined only once.\n- [ ] Name is concise.\n- [ ] Description is clear.\n- [ ] Localization strings added.\n- [ ] `_GLOBAL_LANG_FILES_` used where appropriate.\n- [ ] Plugin-first approach followed where applicable.\n- [ ] Frontend renders automatically.\n- [ ] Backend uses existing configuration helpers.\n- [ ] No unnecessary frontend code added.\n- [ ] No duplicated parsing logic.\n- [ ] Backwards compatibility maintained.\n- [ ] Documentation updated if required.\n\n---\n\n# References\n\n- https://docs.netalertx.com/PLUGINS_DEV_SETTINGS/\n- https://docs.netalertx.com/SETTINGS_SYSTEM/\n- https://docs.netalertx.com/PLUGINS_OVERVIEW/",".github/skills/settings-management/SKILL.md":"---\nname: netalertx-settings-management\ndescription: Manage NetAlertX configuration settings. Use this when asked to add setting, read config, get_setting_value, ccd, or configure options.\n---\n\n# Settings Management\n\n## Quick Reference\n\n**Read a setting (backend):**\n```python\nfrom helper import get_setting_value\nvalue = get_setting_value('SETTING_NAME')\n```\nNever hardcode ports, secrets, or configuration values. Always use `get_setting_value()`.\n\n**Read a setting (frontend):**\n```javascript\ngetSetting(\"SETTING_NAME\")\n```\n\n**Add a core setting** — use `ccd()` in `server/initialise.py`:\n```python\nccd('SETTING_NAME', 'default_value', 'description')\n```\n\n**Add a plugin setting** — define in the plugin's `config.json` under the `settings` key.\n\n| File | Purpose |\n|------|---------|\n| `/data/config/app.conf` | Runtime config (source of truth, modified by app) |\n| `back/app.conf` | Default config (template) |\n\nUse `APP_CONF_OVERRIDE` for settings that must be set before startup.\n\n---\n\n# Development Guide\n\nThe goal is for every setting to be:\n\n- Declarative and self-documenting\n- Automatically rendered by the Settings UI\n- Fully localized\n- Backwards compatible\n- Easy to maintain\n\n---\n\n## 1. Follow existing patterns\n\nBefore creating a new setting, search for similar ones. Reuse existing naming conventions, types, and validation patterns. Follow the style already used by similar plugins. Avoid introducing new setting types unless absolutely necessary.\n\n---\n\n## 2. Setting naming\n\nSetting keys use uppercase snake case: `UI_DEV_SECTIONS`, `SCAN_INTERVAL`, `MQTT_HOST`.\nNames should clearly describe the purpose.\n\n---\n\n## 3. Categories\n\nPlace settings into the most appropriate category. Keep related settings together. Avoid creating new categories unless there is a clear need.\n\n---\n\n## 4. Choose the correct type\n\nUse the simplest type that correctly models the value: `string`, `integer`, `float`, `boolean`, `password`, `select`, `array`, `textarea`. Avoid encoding structured JSON inside string settings.\n\n---\n\n## 5. Validation\n\nAlways define validation whenever appropriate (min/max values, regex, allowed options). Reject invalid configuration rather than silently accepting it.\n\n---\n\n## 6. Defaults\n\nProvide sensible defaults that work for a fresh installation. The `default_value` in `config.json` is the single source of truth — do not duplicate it in Python or JavaScript.\n\n---\n\n## 7. Names and descriptions\n\nNames should be concise. Descriptions should explain what the setting does, when to use it, and any important side effects — not implementation details.\n\n---\n\n## 8. Boolean settings\n\nPrefer verbs: Enable, Disable, Require, Allow, Ignore, Show, Hide. Examples: *Enable MQTT*, *Require NICs Online*, *Hide Offline Devices*.\n\n---\n\n## 9. Select settings\n\nUse when users must choose from predefined values. Each option must have a meaningful label — never require users to remember internal values.\n\n---\n\n## 10. Array settings\n\nUse only when multiple independent values are expected (e.g., ignored MACs, subnet lists, plugin lists).\n\n---\n\n## 11. Localization\n\nEvery setting must have localized language strings. If not defined directly in `config.json`, they must exist in `en_us.json`.\n\nExample for `UI_DEV_SECTIONS`:\n```json\n\"UI_DEV_SECTIONS_name\": \"Hide device sections\",\n\"UI_DEV_SECTIONS_description\": \"Select which UI elements to hide on the Devices page.\"\n```\n\nWhen using external language files, `config.json` must reference:\n```json\n\"name\": [{ \"string\": \"_GLOBAL_LANG_FILES_\" }]\n```\n\n---\n\n## 12. Source of truth\n\n`app.conf` is the source of truth. User-defined values always come from `app.conf`; defaults from `config.json` apply only when a setting is absent. Never read `app.conf` directly — always go through `get_setting_value()`.\n\n---\n\n## 13. Plugin-first settings\n\nWhenever possible, define new settings inside a plugin's `config.json`. Avoid adding hardcoded application settings unless there is a compelling architectural reason.\n\n---\n\n## 14. Runtime lifecycle\n\n```text\nconfig.json (defaults)\n    → app.conf (source of truth)\n    → Settings database table\n    → table_settings.json API\n    → Frontend getSetting() / Backend get_setting_value()\n```\n\nThe database and API are runtime representations only, regenerated from `app.conf` at init.\n\n---\n\n## 15. Backwards compatibility\n\nAvoid renaming settings, changing data types, or changing semantics. If unavoidable, provide migration logic and preserve compatibility where possible.\n\n---\n\n## 16. Pull request checklist\n\n- [ ] Follows existing naming conventions (uppercase snake case)\n- [ ] Correct category used\n- [ ] Appropriate type selected, validation defined\n- [ ] Sensible default — defined only once (in `config.json`)\n- [ ] Name concise, description clear (no implementation details)\n- [ ] Localization strings added; `_GLOBAL_LANG_FILES_` used where appropriate\n- [ ] Plugin-first approach followed where applicable\n- [ ] Frontend renders automatically without custom JavaScript\n- [ ] Backend uses `get_setting_value()` — no direct `app.conf` reads\n- [ ] Backwards compatibility maintained\n- [ ] Documentation updated if required\n\n---\n\n## References\n\n- https://docs.netalertx.com/PLUGINS_DEV_SETTINGS/\n- https://docs.netalertx.com/SETTINGS_SYSTEM/\n",".gemini/skills/skills-index/SKILL.md":"---\nname: skills-index\ndescription: Index of all available skills across Gemini CLI (.gemini/skills/), GitHub Copilot (.github/skills/), and Claude Code (.claude/skills/). Load this to find the right skill for a task, or to locate the counterpart skill in another assistant's tree.\n---\n\n# Skills Index — Cross-Reference\n\nThree AI assistants are configured for this project, each with their own skill directory:\n\n- **Gemini CLI** → `.gemini/skills/`\n- **GitHub Copilot** → `.github/skills/`\n- **Claude Code** → `.claude/skills/` (mirrors most, not all, of the shared skills below — see the table's Claude Skill column for which)\n\nSkills with the same purpose exist in more than one, sometimes under different names and with different depth. This index maps them so you can find the richer version when needed. A CI check (`scripts/check_skill_pairs.py`, run as `check-skill-pairs` in `.github/workflows/code-checks.yml`) flags PRs that touch some but not all files in a mirrored group - non-blocking, since some divergence is intentional.\n\n---\n\n## Shared Skills (exist in more than one tree)\n\n| Topic | Gemini Skill | Copilot Skill | Claude Skill | Notes |\n|-------|-------------|--------------|--------------|-------|\n| Testing | `testing-workflow` | `testing-workflow` | `testing-workflow` | All three cover the full-suite-by-default rule, PYTHONPATH, auth/token retrieval, and the `sys.modules` stubbing pitfall |\n| Settings & config | `settings` | `settings-management` | — | Gemini version is more comprehensive (22-point guide + PR checklist); Copilot version covers `ccd()` and `get_setting_value()` usage |\n| MCP activation | `mcp-activation` | `mcp-activation` | — | Gemini version covers Gemini CLI session restart; Copilot version covers VS Code window reload |\n| Project navigation | `project-navigation` | `project-navigation` | — | Copilot version has full path tables and env vars; Gemini version is a brief reference |\n| Plugin dev | `plugin-development` | `plugin-run-development` | `plugin-development` | All three cover data contract, phases, formats, the `RUN_TIMEOUT` kill-timer gotcha (`timeoutMultiplier`/`per_item_timeout()`), and a pre-PR pointer to the Conventions Checklist in `docs/PLUGINS_DEV.md` |\n| Plugin README docs | `plugin-readme` | `plugin-readme` | `plugin-readme` | All three cover README structure, the \"don't re-document settings\" rule, the `docs.netalertx.com` cross-linking convention, and common defects (template leftovers, copy-paste errors) found during a full-repo audit |\n| Devcontainer | `devcontainer-management` | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | — | Gemini combines into one (uses `docker exec`); Copilot splits into 3 focused skills. `devcontainer-configs` also covers Dockerfile generation (root `Dockerfile` → `generate-dockerfile.sh` → `.devcontainer/Dockerfile`, source files not present at build time) - mined from real review comments on PR #1184/#1230. |\n| Install scripts | `install-scripts` | `install-scripts` | `install-scripts` | `install/*` conventions mined from real review comments (#1214/#1230/#1235): uninstall must only remove NetAlertX's own files, never system packages; `check-*.sh` scripts intentionally differ on fail-fast vs. warning-only; `/back` is legacy, not the active code path. |\n| PR review | `pr-analysis` | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist; the standing rule that a repeated review comment becomes a skill update (sourced from jokob's own real PR comments, #1739/#1744); a readability-over-cleverness code-style note from the same source. |\n| Logging | `logging-standards` | `logging-standards` | — | `mylog` levels, message format, what not to log |\n| Scan pipeline internals | `scan-pipeline` | `scan-pipeline` | `scan-pipeline` | `process_scan()` call order and why it's load-bearing, `CurrentScan`/`Events`/`Sessions`/`DevicesView` relationships, how a session actually closes (no `close_session()` exists), and the `FIELD_SPECS` field-write authority mechanism. Complements `database-patterns` (Devices write-path/`*Source` attribution) rather than duplicating it. |\n| Database patterns | `database-patterns` | `database-patterns` | `database-patterns` | Devices table write-path inventory, the `FIELD_SOURCE_MAP`/`*Source` attribution system in `server/db/authoritative_handler.py`, SQLite trigger vs. Python-hook tradeoffs, and event-sourced vs. snapshot audit logging. |\n| PRD writing | `prd-writing` | `prd-writing` | `prd-writing` | Methodology for writing a design doc: challenge the idea, verify every claim against actual code, trace every downstream consumer of a new mechanism, evaluate performance impact against the real schema/indexes, record rejected alternatives and open-issue decisions explicitly, final-check pass before done. |\n| UX/frontend design | `ux-design-patterns` | `ux-design-patterns` | `ux-design-patterns` | Don't invent new UX behavior/visual patterns unless a PRD calls for it - search `front/` for an existing pattern first and reuse it. Priority order for design tradeoffs when several options are reasonable: existing behavior > intuitiveness > information density > usability > utility > uniqueness > industry practices > generic UI. |\n| Skill hygiene | `skill-hygiene` | `skill-hygiene` | `skill-hygiene` | Read before writing/editing any SKILL.md, or any research/audit doc in `.gemini/internal-docs/research/`. Two standing rules: state current behavior only (no \"Correction:\", no \"as of <date>\", no \"caught in review\" narration - that trail belongs in PRDs), and prefer plain, short wording. Includes the grep sweep to run before calling a doc clean. |\n| Plugin review | `plugin-review` | `plugin-review` | `plugin-review` | Reviewer-facing complement to Plugin dev above: no raw SQL in a plugin script - use an existing/new `server/models/*.py` method, not GraphQL (which no plugin reaches). Named exception list for 5 pre-existing core/infra plugins with legitimate direct DB access, a collation-checking step, and a worked example from a real PR (#1788, DOCKERDISC). |\n\n---\n\n## Copilot-Only Skills\n\nNo Gemini equivalent yet:\n\n| Copilot Skill | Purpose |\n|--------------|---------|\n| `api-development` | Creating REST API endpoints |\n| `authentication` | API tokens and 401/403 debugging |\n| `code-standards` | Coding conventions and style rules |\n| `database-reset` | Wipe and regenerate the database and config |\n| `docker-build` | Build Docker images for testing or production |\n| `docker-prune` | Clean unused Docker resources (destructive — requires confirmation) |\n| `sample-data` | Load synthetic device data into the devcontainer |\n\n---\n\n## Gemini-Only Skills\n\nNo Copilot equivalent yet:\n\n| Gemini Skill | Purpose |\n|-------------|---------|\n| `initiative-start` | Research methodology and structured approach for new tasks |\n\n---\n\n## Adding a New Skill\n\nWhen adding a skill, create it in **both** directories to keep both AI systems current:\n\n- `.gemini/skills/<name>/SKILL.md` — auto-discovered by Gemini CLI via YAML frontmatter\n- `.github/skills/<name>/SKILL.md` — add an entry to the skills table in `.github/copilot-instructions.md`\n\nKeep the body content identical between both files. Only the frontmatter `name`/`description` may differ slightly to match each system's discovery heuristics.\n\nIf the skill is high-value enough to also mirror to Claude Code, add `.claude/skills/<name>/SKILL.md` too, and add the group to `GROUPS` in `scripts/check_skill_pairs.py` so drift gets flagged. Claude Code has no `activate_skill()`/`testFailure`/`runTests`/`report_progress` equivalents - adapt any such tool references to plain `Bash` commands instead of copying them verbatim.\n",".github/skills/project-navigation/SKILL.md":"# project-navigation\n\nPath: `.github/skills/project-navigation/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/project-navigation/SKILL.md)",".github/skills/plugin-run-development/SKILL.md":"# plugin-run-development\n\nPath: `.github/skills/plugin-run-development/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/plugin-run-development/SKILL.md)",".github/skills/mcp-activation/SKILL.md":"# mcp-activation\n\nPath: `.github/skills/mcp-activation/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/mcp-activation/SKILL.md)",".github/skills/docker-prune/SKILL.md":"# docker-prune\n\nPath: `.github/skills/docker-prune/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/docker-prune/SKILL.md)",".github/skills/docker-build/SKILL.md":"# docker-build\n\nPath: `.github/skills/docker-build/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/docker-build/SKILL.md)",".github/skills/devcontainer-setup/SKILL.md":"# devcontainer-setup\n\nPath: `.github/skills/devcontainer-setup/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-setup/SKILL.md)",".github/skills/devcontainer-services/SKILL.md":"# devcontainer-services\n\nPath: `.github/skills/devcontainer-services/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-services/SKILL.md)",".github/skills/devcontainer-configs/SKILL.md":"# devcontainer-configs\n\nPath: `.github/skills/devcontainer-configs/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-configs/SKILL.md)",".github/skills/database-reset/SKILL.md":"# database-reset\n\nPath: `.github/skills/database-reset/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/database-reset/SKILL.md)",".github/skills/database-patterns/SKILL.md":"# database-patterns\n\nPath: `.github/skills/database-patterns/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/database-patterns/SKILL.md)",".github/skills/code-standards/SKILL.md":"# code-standards\n\nPath: `.github/skills/code-standards/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/code-standards/SKILL.md)",".github/skills/authentication/SKILL.md":"# authentication\n\nPath: `.github/skills/authentication/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/authentication/SKILL.md)",".github/skills/api-development/SKILL.md":"# api-development\n\nPath: `.github/skills/api-development/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/api-development/SKILL.md)",".github/copilot-instructions.md":"# GitHub Copilot Instructions\n\nPath: `.github/copilot-instructions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/copilot-instructions.md)","docs/API.md":"# docs - API Interface Contract & Specifications\n\nPath: `docs/API.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/docs/API.md)","front/llms.txt":"# front - LLM Index & Context Digest\n\nPath: `front/llms.txt`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/front/llms.txt)","server/plugins/README.md":"# plugins Documentation\n\nPath: `server/plugins/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/README.md)","server/plugins/pihole_scan/config.json":"# Pihole_scan Skill\n\nPath: `server/plugins/pihole_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_scan/config.json)","server/plugins/pihole_scan/README.md":"# pihole_scan Documentation\n\nPath: `server/plugins/pihole_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_scan/README.md)","server/plugins/pihole_api_scan/config.json":"# Pihole_api_scan Skill\n\nPath: `server/plugins/pihole_api_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_api_scan/config.json)","server/plugins/pihole_api_scan/README.md":"# pihole_api_scan Documentation\n\nPath: `server/plugins/pihole_api_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_api_scan/README.md)","server/plugins/omada_sdn_openapi/config.json":"# Omada_sdn_openapi Skill\n\nPath: `server/plugins/omada_sdn_openapi/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_openapi/config.json)","server/plugins/omada_sdn_openapi/README.md":"# omada_sdn_openapi Documentation\n\nPath: `server/plugins/omada_sdn_openapi/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_openapi/README.md)","server/plugins/omada_sdn_imp/config.json":"# Omada_sdn_imp Skill\n\nPath: `server/plugins/omada_sdn_imp/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_imp/config.json)","server/plugins/omada_sdn_imp/README.md":"# omada_sdn_imp Documentation\n\nPath: `server/plugins/omada_sdn_imp/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_imp/README.md)","server/plugins/nslookup_scan/config.json":"# Nslookup_scan Skill\n\nPath: `server/plugins/nslookup_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nslookup_scan/config.json)","server/plugins/nslookup_scan/README.md":"# nslookup_scan Documentation\n\nPath: `server/plugins/nslookup_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nslookup_scan/README.md)","server/plugins/notification_processing/config.json":"# Notification_processing Skill\n\nPath: `server/plugins/notification_processing/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/notification_processing/config.json)","server/plugins/notification_processing/README.md":"# notification_processing Documentation\n\nPath: `server/plugins/notification_processing/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/notification_processing/README.md)","server/plugins/nmap_scan/config.json":"# Nmap_scan Skill\n\nPath: `server/plugins/nmap_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_scan/config.json)","server/plugins/nmap_scan/README.md":"# nmap_scan Documentation\n\nPath: `server/plugins/nmap_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_scan/README.md)","server/plugins/nmap_dev_scan/config.json":"# Nmap_dev_scan Skill\n\nPath: `server/plugins/nmap_dev_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_dev_scan/config.json)","server/plugins/nmap_dev_scan/README.md":"# nmap_dev_scan Documentation\n\nPath: `server/plugins/nmap_dev_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_dev_scan/README.md)","server/plugins/newdev_template/config.json":"# Newdev_template Skill\n\nPath: `server/plugins/newdev_template/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/newdev_template/config.json)","server/plugins/newdev_template/README.md":"# newdev_template Documentation\n\nPath: `server/plugins/newdev_template/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/newdev_template/README.md)","server/plugins/nbtscan_scan/config.json":"# Nbtscan_scan Skill\n\nPath: `server/plugins/nbtscan_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nbtscan_scan/config.json)","server/plugins/nbtscan_scan/README.md":"# nbtscan_scan Documentation\n\nPath: `server/plugins/nbtscan_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nbtscan_scan/README.md)","server/plugins/mikrotik_scan/config.json":"# Mikrotik_scan Skill\n\nPath: `server/plugins/mikrotik_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/mikrotik_scan/config.json)","server/plugins/mikrotik_scan/README.md":"# mikrotik_scan Documentation\n\nPath: `server/plugins/mikrotik_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/mikrotik_scan/README.md)","server/plugins/maintenance/config.json":"# Maintenance Skill\n\nPath: `server/plugins/maintenance/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/maintenance/config.json)","server/plugins/maintenance/README.md":"# maintenance Documentation\n\nPath: `server/plugins/maintenance/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/maintenance/README.md)","server/plugins/luci_import/config.json":"# Luci_import Skill\n\nPath: `server/plugins/luci_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/luci_import/config.json)","server/plugins/luci_import/README.md":"# luci_import Documentation\n\nPath: `server/plugins/luci_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/luci_import/README.md)","server/plugins/kea_api/config.json":"# Kea_api Skill\n\nPath: `server/plugins/kea_api/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/kea_api/config.json)","server/plugins/kea_api/README.md":"# kea_api Documentation\n\nPath: `server/plugins/kea_api/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/kea_api/README.md)","server/plugins/ipneigh/config.json":"# Ipneigh Skill\n\nPath: `server/plugins/ipneigh/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ipneigh/config.json)","server/plugins/ipneigh/README.md":"# ipneigh Documentation\n\nPath: `server/plugins/ipneigh/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ipneigh/README.md)","server/plugins/internet_speedtest/config.json":"# Internet_speedtest Skill\n\nPath: `server/plugins/internet_speedtest/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_speedtest/config.json)","server/plugins/internet_speedtest/README.md":"# internet_speedtest Documentation\n\nPath: `server/plugins/internet_speedtest/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_speedtest/README.md)","server/plugins/internet_ip/config.json":"# Internet_ip Skill\n\nPath: `server/plugins/internet_ip/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_ip/config.json)","server/plugins/internet_ip/README.md":"# internet_ip Documentation\n\nPath: `server/plugins/internet_ip/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_ip/README.md)","server/plugins/icmp_scan/config.json":"# Icmp_scan Skill\n\nPath: `server/plugins/icmp_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/icmp_scan/config.json)","server/plugins/icmp_scan/README.md":"# icmp_scan Documentation\n\nPath: `server/plugins/icmp_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/icmp_scan/README.md)","server/plugins/fritzbox/config.json":"# Fritzbox Skill\n\nPath: `server/plugins/fritzbox/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/fritzbox/config.json)","server/plugins/fritzbox/README.md":"# fritzbox Documentation\n\nPath: `server/plugins/fritzbox/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/fritzbox/README.md)","server/plugins/freebox/config.json":"# Freebox Skill\n\nPath: `server/plugins/freebox/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/freebox/config.json)","server/plugins/freebox/README.md":"# freebox Documentation\n\nPath: `server/plugins/freebox/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/freebox/README.md)","server/plugins/dig_scan/config.json":"# Dig_scan Skill\n\nPath: `server/plugins/dig_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dig_scan/config.json)","server/plugins/dig_scan/README.md":"# dig_scan Documentation\n\nPath: `server/plugins/dig_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dig_scan/README.md)","server/plugins/dhcp_servers/config.json":"# Dhcp_servers Skill\n\nPath: `server/plugins/dhcp_servers/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_servers/config.json)","server/plugins/dhcp_servers/README.md":"# dhcp_servers Documentation\n\nPath: `server/plugins/dhcp_servers/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_servers/README.md)","server/plugins/dhcp_leases/ASUS_ROUTERS.md":"# Dhcp_leases Skill\n\nPath: `server/plugins/dhcp_leases/ASUS_ROUTERS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_leases/ASUS_ROUTERS.md)","server/plugins/dhcp_leases/config.json":"# Dhcp_leases Skill\n\nPath: `server/plugins/dhcp_leases/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_leases/config.json)","server/plugins/dhcp_leases/README.md":"# dhcp_leases Documentation\n\nPath: `server/plugins/dhcp_leases/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_leases/README.md)","server/plugins/ddns_update/config.json":"# Ddns_update Skill\n\nPath: `server/plugins/ddns_update/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ddns_update/config.json)","server/plugins/ddns_update/README.md":"# ddns_update Documentation\n\nPath: `server/plugins/ddns_update/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ddns_update/README.md)","server/plugins/ddns_update/README_DE.md":"# Ddns_update Skill\n\nPath: `server/plugins/ddns_update/README_DE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ddns_update/README_DE.md)","server/plugins/db_cleanup/config.json":"# Db_cleanup Skill\n\nPath: `server/plugins/db_cleanup/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/db_cleanup/config.json)","server/plugins/db_cleanup/README.md":"# db_cleanup Documentation\n\nPath: `server/plugins/db_cleanup/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/db_cleanup/README.md)","server/plugins/custom_props/config.json":"# Custom_props Skill\n\nPath: `server/plugins/custom_props/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/custom_props/config.json)","server/plugins/custom_props/README.md":"# custom_props Documentation\n\nPath: `server/plugins/custom_props/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/custom_props/README.md)","server/plugins/csv_backup/config.json":"# Csv_backup Skill\n\nPath: `server/plugins/csv_backup/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/csv_backup/config.json)","server/plugins/csv_backup/README.md":"# csv_backup Documentation\n\nPath: `server/plugins/csv_backup/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/csv_backup/README.md)","server/plugins/avahi_scan/config.json":"# Avahi_scan Skill\n\nPath: `server/plugins/avahi_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/avahi_scan/config.json)","server/plugins/avahi_scan/README.md":"# avahi_scan Documentation\n\nPath: `server/plugins/avahi_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/avahi_scan/README.md)","server/plugins/asuswrt_import/config.json":"# Asuswrt_import Skill\n\nPath: `server/plugins/asuswrt_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/asuswrt_import/config.json)","server/plugins/asuswrt_import/README.md":"# asuswrt_import Documentation\n\nPath: `server/plugins/asuswrt_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/asuswrt_import/README.md)","server/plugins/arp_scan/config.json":"# Arp_scan Skill\n\nPath: `server/plugins/arp_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/arp_scan/config.json)","server/plugins/arp_scan/README.md":"# arp_scan Documentation\n\nPath: `server/plugins/arp_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/arp_scan/README.md)","server/plugins/adguard_import/config.json":"# Adguard_import Skill\n\nPath: `server/plugins/adguard_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_import/config.json)","server/plugins/adguard_import/README.md":"# adguard_import Documentation\n\nPath: `server/plugins/adguard_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_import/README.md)","server/plugins/adguard_export/config.json":"# Adguard_export Skill\n\nPath: `server/plugins/adguard_export/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_export/config.json)","server/plugins/adguard_export/README.md":"# adguard_export Documentation\n\nPath: `server/plugins/adguard_export/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_export/README.md)","server/plugins/_publisher_webhook/config.json":"# _publisher_webhook Skill\n\nPath: `server/plugins/_publisher_webhook/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_webhook/config.json)","server/plugins/_publisher_webhook/README.md":"# _publisher_webhook Documentation\n\nPath: `server/plugins/_publisher_webhook/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_webhook/README.md)","server/plugins/_publisher_telegram/config.json":"# _publisher_telegram Skill\n\nPath: `server/plugins/_publisher_telegram/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_telegram/config.json)","server/plugins/_publisher_telegram/README.md":"# _publisher_telegram Documentation\n\nPath: `server/plugins/_publisher_telegram/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_telegram/README.md)","server/plugins/_publisher_pushsafer/config.json":"# _publisher_pushsafer Skill\n\nPath: `server/plugins/_publisher_pushsafer/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushsafer/config.json)","server/plugins/_publisher_pushsafer/README.md":"# _publisher_pushsafer Documentation\n\nPath: `server/plugins/_publisher_pushsafer/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushsafer/README.md)","server/plugins/_publisher_pushover/config.json":"# _publisher_pushover Skill\n\nPath: `server/plugins/_publisher_pushover/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushover/config.json)","server/plugins/_publisher_pushover/README.md":"# _publisher_pushover Documentation\n\nPath: `server/plugins/_publisher_pushover/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushover/README.md)","server/plugins/_publisher_ntfy/config.json":"# _publisher_ntfy Skill\n\nPath: `server/plugins/_publisher_ntfy/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_ntfy/config.json)","server/plugins/_publisher_ntfy/README.md":"# _publisher_ntfy Documentation\n\nPath: `server/plugins/_publisher_ntfy/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_ntfy/README.md)","server/plugins/_publisher_mqtt/config.json":"# _publisher_mqtt Skill\n\nPath: `server/plugins/_publisher_mqtt/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_mqtt/config.json)","server/plugins/_publisher_mqtt/README.md":"# _publisher_mqtt Documentation\n\nPath: `server/plugins/_publisher_mqtt/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_mqtt/README.md)","server/plugins/_publisher_email/config.json":"# _publisher_email Skill\n\nPath: `server/plugins/_publisher_email/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_email/config.json)","server/plugins/_publisher_email/README.md":"# _publisher_email Documentation\n\nPath: `server/plugins/_publisher_email/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_email/README.md)","server/plugins/_publisher_apprise/config.json":"# _publisher_apprise Skill\n\nPath: `server/plugins/_publisher_apprise/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_apprise/config.json)","server/plugins/_publisher_apprise/README.md":"# _publisher_apprise Documentation\n\nPath: `server/plugins/_publisher_apprise/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_apprise/README.md)","server/plugins/__template/config.json":"# __template Skill\n\nPath: `server/plugins/__template/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/__template/config.json)","server/plugins/__template/README.md":"# __template Documentation\n\nPath: `server/plugins/__template/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/__template/README.md)","server/plugins/rest_import/config.json":"# Rest_import Skill\n\nPath: `server/plugins/rest_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/rest_import/config.json)","server/plugins/rest_import/README.md":"# rest_import Documentation\n\nPath: `server/plugins/rest_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/rest_import/README.md)","server/plugins/set_password/config.json":"# Set_password Skill\n\nPath: `server/plugins/set_password/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/set_password/config.json)","server/plugins/set_password/README.md":"# set_password Documentation\n\nPath: `server/plugins/set_password/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/set_password/README.md)","server/plugins/snmp_discovery/config.json":"# Snmp_discovery Skill\n\nPath: `server/plugins/snmp_discovery/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/snmp_discovery/config.json)","server/plugins/snmp_discovery/README.md":"# snmp_discovery Documentation\n\nPath: `server/plugins/snmp_discovery/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/snmp_discovery/README.md)","server/plugins/sync/config.json":"# Sync Skill\n\nPath: `server/plugins/sync/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/sync/config.json)","server/plugins/sync/README.md":"# sync Documentation\n\nPath: `server/plugins/sync/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/sync/README.md)","server/plugins/ui_settings/config.json":"# Ui_settings Skill\n\nPath: `server/plugins/ui_settings/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ui_settings/config.json)","server/plugins/ui_settings/README.md":"# ui_settings Documentation\n\nPath: `server/plugins/ui_settings/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ui_settings/README.md)","server/plugins/unifi_api_import/config.json":"# Unifi_api_import Skill\n\nPath: `server/plugins/unifi_api_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_api_import/config.json)","server/plugins/unifi_api_import/README.md":"# unifi_api_import Documentation\n\nPath: `server/plugins/unifi_api_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_api_import/README.md)","server/plugins/unifi_import/config.json":"# Unifi_import Skill\n\nPath: `server/plugins/unifi_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_import/config.json)","server/plugins/unifi_import/README.md":"# unifi_import Documentation\n\nPath: `server/plugins/unifi_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_import/README.md)","server/plugins/vendor_update/config.json":"# Vendor_update Skill\n\nPath: `server/plugins/vendor_update/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/vendor_update/config.json)","server/plugins/vendor_update/README.md":"# vendor_update Documentation\n\nPath: `server/plugins/vendor_update/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/vendor_update/README.md)","server/plugins/wake_on_lan/config.json":"# Wake_on_lan Skill\n\nPath: `server/plugins/wake_on_lan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/wake_on_lan/config.json)","server/plugins/wake_on_lan/README.md":"# wake_on_lan Documentation\n\nPath: `server/plugins/wake_on_lan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/wake_on_lan/README.md)","server/plugins/website_monitor/config.json":"# Website_monitor Skill\n\nPath: `server/plugins/website_monitor/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/website_monitor/config.json)","server/plugins/website_monitor/README.md":"# website_monitor Documentation\n\nPath: `server/plugins/website_monitor/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/website_monitor/README.md)","server/plugins/workflows/config.json":"# Workflows Skill\n\nPath: `server/plugins/workflows/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/workflows/config.json)","server/plugins/workflows/README.md":"# workflows Documentation\n\nPath: `server/plugins/workflows/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/workflows/README.md)"},"files":{".gemini/skills/plugin-development/plugin-skill.md":"---\nname: netalertx-plugin-development\ndescription: Create and run NetAlertX plugins. Use this when asked to create plugin, run plugin, test plugin, or develop plugin functionality.\n---\n\n# Plugin Development\n\n## Expected Workflow\n\n1. Read this skill and `docs/PLUGINS_DEV.md` for full context.\n2. Find or create the plugin in `server/plugins/<code_name>/`.\n3. Read the plugin's `config.json` and `script.py` to understand its functionality.\n4. Run: `python3 server/plugins/<code_name>/script.py`\n5. Retrieve the result from `/tmp/log/plugins/last_result.<PREF>.log` quickly — the backend deletes it after processing.\n\n## Run a Plugin Manually\n\n```bash\npython3 server/plugins/<code_name>/script.py\n```\n\nEnsure `sys.path` includes `/app/server/plugins` and `/app/server` (as in the template).\n\n## Plugin Structure\n\n```text\nserver/plugins/<code_name>/\n├── config.json      # Manifest with settings\n├── script.py        # Main script\n└── ...\n```\n\n## Plugin Prefix Rules\n\n- Uppercase letters only (no underscores, no numbers)\n- Must be unique across all plugins\n- Short but readable (e.g., `ARPSCAN`, `MQTT`, `UNIFI`)\n\n## Settings Pattern\n\n- `<PREF>_RUN`: execution phase\n- `<PREF>_RUN_SCHD`: cron-like schedule\n- `<PREF>_CMD`: script path\n- `<PREF>_RUN_TIMEOUT`: timeout in seconds — **this is enforced by the core plugin runner as the whole script's kill-timeout** (`server/plugin.py` passes it straight to `subprocess(..., timeout=...)`). It is not a safe per-HTTP-call timeout — don't reuse it for individual network calls in a loop, or one slow call can burn the whole budget and get the process killed before it writes its result file.\n- `<PREF>_WATCH`: columns to watch for changes\n- `<PREF>_IMPORT_ON`: optional — gates whether this run's rows get promoted into `CurrentScan` (only relevant if `mapped_to_table: \"CurrentScan\"`). See `docs/PLUGINS_IMPORT_BEHAVIOR.md` for the related per-row `scanCreatesDevice`/`scanNotificationMode`/`scanPresence` columns.\n- **`dataType` and `default_value` must agree.** `dataType: \"array\"`/`\"object\"` needs a real JSON literal for `default_value` (`'[\"default\"]'`), not a bare string (`\"default\"`). `setting_value_to_python_type()` (`server/helper.py`) `json.loads()`s the default at runtime; a bare string fails silently — logged, and `[]` is returned instead of your default (e.g. `devParentRelType`, `UI_theme`, `UI_TOPOLOGY_ORDER`). If `elementOptions` already sets `multiple`/`orderable: \"false\"`, the setting is scalar — use `dataType: \"string\"` instead.\n\n## Data Contract\n\nScripts write to `/tmp/log/plugins/last_result.<PREF>.log` using `plugin_helper.py`:\n\n```python\nfrom plugin_helper import Plugin_Objects\n\nplugin_objects = Plugin_Objects()\nplugin_objects.add_object(...)  # During processing\nplugin_objects.write_result_file()  # Exactly once at end\n```\n\n**Important:** The backend processes and deletes the result file almost immediately. Retrieve it quickly if inspecting output.\n\nEvery mapped field (`objectPrimaryId`/`objectSecondaryId`/`watchedValue1-4`/`extra`/`helpVal1-4`) is HTML/control-char-stripped by default before it's persisted: plugin output is untrusted (network responses, device-reported names, etc.). `foreignKey` is always sanitized too, unconditionally. Only opt a column out (`\"allow_raw_text\": true`) if it's a display-only type (`textarea_readonly`); see `docs/PLUGINS_DEV.md#field-sanitization`.\n\n## Execution Phases\n\n| Phase | Trigger |\n|-------|---------|\n| `once` | Once at startup |\n| `schedule` | On cron schedule |\n| `always_after_scan` | After every scan |\n| `before_name_updates` | Before name resolution |\n| `on_new_device` | When new device detected |\n| `on_notification` | When notification triggered |\n\n## `all_plugins` Caching\n\n`all_plugins` is loaded once (`initialise.py`'s `get_plugins_configs()`) and reused as the same object until a reload - but `update_api()` (`server/api.py`), which takes it as a parameter, runs on every scan cycle and plugin completion, not just on reload.\n\n- If you write a function that derives something from `all_plugins` and gets called from `update_api()` or another frequent call site, cache the result instead of recomputing every call - retain a reference to the last-seen `all_plugins` list and compare it to the current one with `is`, not by `id(all_plugins)` alone (once the old list is garbage collected, Python can reuse that id for an unrelated object, wrongly serving a stale cached result for what is actually a new reload)\n- Pattern: `server/db/plugin_field_views.py`'s `get_all_device_field_views()`\n\n## Plugin Formats\n\n| Format | Purpose | Phase |\n|--------|---------|-------|\n| publisher | Send notifications | `on_notification` |\n| dev scanner | Create/manage devices | `schedule` |\n| name discovery | Discover device names | `before_name_updates` |\n| importer | Import from services | `schedule` |\n| system | Core functionality | `schedule` |\n\n## Before Opening a PR\n\nCheck the plugin against the [Conventions Checklist](../../../docs/PLUGINS_DEV.md#conventions-checklist) in `docs/PLUGINS_DEV.md` — `RUN` default, schedule precedent, `RUN_TIMEOUT` semantics, reusing core settings, description length, the multi-instance settings pattern, `allow_raw_text` restricted to display-only column types, and `scanSourcePlugin`'s static value matching `unique_prefix` exactly. Most plugin PR review comments trace back to one of these.\n\nIf the plugin needs a new system package or Python dependency, mirroring it into the root `Dockerfile`/`requirements.txt` alone is not enough: see the Conventions Checklist's build-target-mirroring bullet for `.devcontainer/Dockerfile` (regenerate via `.devcontainer/scripts/generate-configs.sh`, don't hand-edit it), `Dockerfile.debian`, and `install/ubuntu24`/`install/proxmox`'s own `requirements.txt` files.\n\n## Starting Point\n\nCopy from `server/plugins/__template` and customize. Read `docs/PLUGINS_DEV.md` for the full development guide.\n\n\n",".gemini/skills/mcp-activation/SKILL.md":"---\nname: mcp-activation\ndescription: Enables live interaction with the NetAlertX runtime. This skill configures the Model Context Protocol (MCP) connection, granting full API access for debugging, troubleshooting, and real-time operations including database queries, network scans, and device management.\n---\n\n# MCP Activation Skill\n\nThis skill configures the NetAlertX development environment to expose the Model Context Protocol (MCP) server to AI agents.\n\n## Why use this?\n\nBy default, agents only have access to the static codebase (files). To perform dynamic actions—such as:\n- **Querying the database** (e.g., getting device lists, events)\n- **Triggering actions** (e.g., network scans, Wake-on-LAN)\n- **Validating runtime state** (e.g., checking if a fix actually works)\n\n...you need access to the **MCP Server** running inside the container. This skill sets up the necessary authentication tokens and connection configs to bridge your agent to that live server.\n\n## Prerequisites\n\n1.  **Devcontainer:** You must be connected to the NetAlertX devcontainer.\n2.  **Server Running:** The backend server must be running (to generate `app.conf` with the API token).\n\n## Activation Steps\n\n1.  **Activate Devcontainer Skill:**\n    If you are not already inside the container, activate the management skill:\n    ```text\n    activate_skill(\"devcontainer-management\")\n    ```\n\n2.  **Generate Configurations:**\n    Run the configuration generation script *inside* the container. This script extracts the API Token and creates the necessary settings files (`.gemini/settings.json` and `.vscode/mcp.json`).\n\n    ```bash\n    # Run inside the container\n    /workspaces/NetAlertX/.devcontainer/scripts/generate-configs.sh\n    ```\n\n3.  **Apply Changes:**\n\n    *   **For Gemini CLI:**\n        The agent session must be **restarted** to load the new `.gemini/settings.json`.\n        > \"I have generated the MCP configuration. Please **restart this session** to activate the `netalertx-devcontainer` tools.\"\n\n    *   **For VS Code (GitHub Copilot / Cline):**\n        The VS Code window must be **reloaded** to pick up the new `.vscode/mcp.json`.\n        > \"I have generated the MCP configuration. Please run **'Developer: Reload Window'** in VS Code to activate the MCP server.\"\n\n## Verification\n\nAfter restarting, you should see new tools available (e.g., `netalertx-devcontainer__get_devices`).\n",".gemini/skills/initiative-start/reasearch-skill.md":"### 🧠 NAX Research Skill — Specification\n\nThis defines a **Research Skill module for NAX (NetAlertX)** focused on safe, structured analysis before any implementation work.\n\n---\n\n## 1. Purpose\n\nEnsure all work begins with **documentation-first understanding**, **PRD validation**, and **conflict detection**, before any planning or coding.\n\n---\n\n## 2. Core Workflow\n\n### Step 1 — Documentation First\n\n* Always begin by reading relevant repository documentation.\n\n* Priority order:\n\n  1. `/CONTRIBUTING.md`\n  2. `/README.md`\n  3. `/.github/skills/code-standards/SKILL.md`\n  4. `/.github/skills/database-patterns/SKILL.md` — read when the feature touches the `Devices` table or requires audit/history logging\n  5. `/docs/**`\n  6. Related module/code context if referenced\n\n* Extract:\n\n  * Architecture expectations\n  * Coding standards\n  * Plugin or module conventions\n  * Existing workflows or constraints\n\n---\n\n### Step 2 — PRD Check\n\n* If a PRD (Product Requirements Document) is NOT provided:\n\n  * Explicitly request it before proceeding further\n  * Do not assume requirements\n\n* If PRD is provided:\n\n  * Parse and restate key requirements internally\n  * Identify scope boundaries\n\n---\n\n### Step 3 — Clarification Gate\n\nIf anything is unclear:\n\n* Stop immediately\n* Ask targeted clarifying questions\n* Do NOT propose solutions yet\n\n---\n\n### Step 4 — Codebase Cross-Check\n\n* Compare PRD + documentation against existing codebase\n\n* Identify:\n\n  * Conflicting behavior\n  * Outdated patterns\n  * Duplicate logic\n  * Breaking assumptions\n  * Plugin or API mismatches\n\n* **If the feature writes to the `Devices` table:**\n\n  * Load `/.github/skills/database-patterns/SKILL.md`\n  * Audit all write paths listed there — confirm which are affected\n  * Determine if a SQLite trigger or Python hook is the right approach\n  * Check if `*Source` fields already solve attribution requirements\n\n* Clearly report inconsistencies before proceeding\n\n---\n\n### Step 5 — Planning Requirement (Strict)\n\nBefore any implementation:\n\n* Produce a structured plan including:\n\n  * Approach overview\n  * Files/modules affected\n  * Dependencies\n  * Risk areas\n  * Migration considerations (if any)\n\n* Explicitly label:\n\n  > “WAITING FOR USER CONFIRMATION”\n\n---\n\n### Step 6 — Implementation Gate (Hard Rule)\n\n* Do NOT start implementation until user explicitly confirms the plan\n* No partial coding, no early patches, no assumptions\n\n---\n\n## 3. Behavioral Constraints\n\n* Always prioritize correctness over speed\n* Never skip PRD validation\n* Never proceed past ambiguity\n* Never implement without approval\n* Always surface contradictions in source material\n* Always prefer asking questions over guessing\n\n---\n\n## 4. Output Style Rules\n\n* Be structured and technical\n* Avoid unnecessary verbosity\n* Separate:\n\n  * Findings\n  * Risks\n  * Questions\n  * Plan\n* No hidden assumptions\n\n---\n\n## 5. Summary Flow\n\n```\nDocs → PRD → Clarify → Codebase Check → Plan → User Approval → Implement\n```\n\n",".gemini/skills/testing-workflow/SKILL.md":"---\nname: testing-workflow\ndescription: Read before running tests. Detailed instructions for single, standard unit tests (fast), full suites (slow), handling authentication, and obtaining the API Token. Tests must be run when a job is complete.\n---\n\n# Testing Workflow\nAfter code is developed, tests must be run to ensure the integrity of the final result.\n\n**Crucial:** Tests MUST be run inside the container to access the correct runtime environment (DB, Config, Dependencies).\n\n## 0. Pre-requisites: Environment Check\n\nBefore running any tests, verify you are inside the development container:\n\n```bash\nls -d /workspaces/NetAlertX\n```\n\n**IF** this directory does not exist, you are likely on the host machine. You **MUST** immediately activate the `devcontainer-management` skill to enter the container or run commands inside it.\n\n```text\nactivate_skill(\"devcontainer-management\")\n```\n\n## 1. Full Test Suite (MANDATORY DEFAULT)\n\nUnless the user **explicitly** requests \"fast\" or \"quick\" tests, you **MUST** run the full test suite. **Do not** optimize for time. Comprehensive coverage is the priority over speed.\n\n```bash\ncd /workspaces/NetAlertX; pytest test/\n```\n\n## 2. Fast Unit Tests (Conditional)\n\n**ONLY** use this if the user explicitly asks for \"fast tests\", \"quick tests\", or \"unit tests only\". This **excludes** slow tests marked with `docker` or `feature_complete`.\n\n```bash\ncd /workspaces/NetAlertX; pytest test/ -m 'not docker and not feature_complete'\n```\n\n## 3. Running Specific Tests\n\nTo run a specific file or folder:\n\n```bash\ncd /workspaces/NetAlertX; pytest test/<path_to_test>\n```\n\n*Example:*\n```bash\ncd /workspaces/NetAlertX; pytest test/api_endpoints/test_mcp_extended_endpoints.py\n```\n\n## Authentication & Environment Reset\n\nAuthentication tokens are required to perform certain operations such as manual testing or crafting expressions to work with the web APIs. After making code changes, you MUST reset the environment to ensure the new code is running and verify you have the latest `API_TOKEN`.\n\n1. **Reset Environment:** Run the setup script inside the container.\n   ```bash\n   bash /workspaces/NetAlertX/.devcontainer/scripts/setup.sh\n   ```\n2. **Wait for Stabilization:** Wait at least 5 seconds for services (nginx, python server, etc.) to start.\n   ```bash\n   sleep 5\n   ```\n3. **Obtain Token:** Retrieve the current token from the container.\n   ```bash\n   python3 -c \"from helper import get_setting_value; print(get_setting_value('API_TOKEN'))\"\n   ```\n\nThe retrieved token MUST be used in all subsequent API or test calls requiring authentication.\n\n### Troubleshooting\n\nIf tests fail with 403 Forbidden or empty tokens:\n1. Verify server is running and use the setup script (`/workspaces/NetAlertX/.devcontainer/scripts/setup.sh`) if required.\n2. Verify `app.conf` inside the container: `cat /data/config/app.conf`\n\n## Check for Pre-Existing Failures\n\nBefore attributing failures to your changes, check what was already broken:\n\n```bash\ncd /workspaces/NetAlertX; pytest test/ --tb=no -q 2>&1 | tail -20\n```\n\nDo not fix pre-existing failures unless that is the explicit goal.\n\n## PYTHONPATH\n\nThe test environment is pre-configured with:\n- `/app` — primary location where Python runs in production\n- `/app/server` — symlink to `/workspaces/NetAlertX/server`\n- `/app/server/plugins` — symlink to `/workspaces/NetAlertX/server/plugins`\n- `/opt/venv/lib/pythonX.Y/site-packages`\n- `/workspaces/NetAlertX/test`\n- `/workspaces/NetAlertX/server`\n- `/workspaces/NetAlertX`\n- `/usr/lib/pythonX.Y/site-packages`\n\n## Docker Test Image\n\nIf the Dockerfile or dependencies changed, rebuild the test image before running:\n\n```bash\ndocker buildx build -t netalertx-test .\n```\n\nTakes ~30 seconds; ~90 seconds if the venv stage changed.\n\n## Pitfall: `sys.modules` Stubbing Leaks Across Test Files\n\nSome plugin tests (e.g. `test/plugins/test_ntfy_custom_headers.py`) stub NetAlertX\nmodules (`conf`, `helper`, `models.notification_instance`, etc.) via\n`sys.modules[name] = fake_module` so the plugin script can be imported standalone,\noutside the container. Because `sys.modules` is a single process-wide cache shared\nby the whole pytest session, a fake module inserted by one test file silently\nshadows the real module for every other test file collected afterwards — pytest\nimports all test files during collection, before any test runs, so this can happen\nregardless of alphabetical/directory order.\n\nSymptom: `AttributeError: <module 'models.notification_instance'> does not have\nthe attribute 'get_setting_value'` (or similar) in an unrelated test file, where\nthe module repr has no `from '<path>'` suffix — a giveaway that a stub, not the\nreal module, was resolved.\n\nFix pattern: track which module names your stub actually inserted, and pop them\nback out of `sys.modules` immediately after the one-time import that needed them\n(the already-imported script keeps its bound names regardless):\n\n```python\n_stubbed_module_names = []\n\ndef _stub(name, **attrs):\n    if name not in sys.modules:\n        mod = types.ModuleType(name)\n        for k, v in attrs.items():\n            setattr(mod, k, v)\n        sys.modules[name] = mod\n        _stubbed_module_names.append(name)\n\n# ... _stub(...) calls, then the one-time import ...\nimport ntfy\n\nfor _name in _stubbed_module_names:\n    sys.modules.pop(_name, None)\n```",".gemini/skills/devcontainer-management/SKILL.md":"---\nname: devcontainer-management\ndescription: Guide for identifying, managing, and running commands within the NetAlertX development container. Use this when asked to run commands, testing, setup scripts, or troubleshoot container issues.\n---\n\n# Devcontainer Management\n\nWhen starting a session or performing tasks requiring the runtime environment, you must identify and use the active development container.\n\n## Finding the Container\n\nRun `docker ps` to list running containers. Look for an image name containing `vsc-netalertx` or similar.\n\n```bash\ndocker ps --format \"table {{.ID}}\\t{{.Image}}\\t{{.Status}}\\t{{.Names}}\" | grep netalertx\n```\n\n- **If no container is found:** Inform the user. You cannot run integration tests or backend logic without it.\n- **If multiple containers are found:** Ask the user to clarify which one to use (e.g., provide the Container ID).\n\n## Running Commands in the Container\n\nPrefix commands with `docker exec <CONTAINER_ID>` to run them inside the environment. Use the scripts in `/services/` to control backend and other processes.\n\n```bash\ndocker exec <CONTAINER_ID> bash /workspaces/NetAlertX/.devcontainer/scripts/setup.sh\n```\n\n*Note: This script wipes `/tmp` ramdisks, ensures `/data`, `/data/config`, `/data/db` exist, and restarts services (python server, cron, php-fpm, nginx) by symlinking and running `/entrypoint.sh` (`install/production-filesystem/entrypoint.d/`). By default it does **not** delete existing database or config content - `entrypoint.d/25-first-run-db.sh` and `20-first-run-config.sh` only wipe them when `ALWAYS_FRESH_INSTALL=true` is set in the environment (default: unset/`false`, so content is preserved).*\n\n```\n",".github/skills/testing-workflow/SKILL.md":"---\nname: netalertx-testing-workflow\ndescription: Run and debug tests in the NetAlertX devcontainer. Use this when asked to run tests, check test failures, debug failing tests, or execute pytest.\n---\n\n# Testing Workflow\n\n## Pre-Flight Check (MANDATORY)\n\nBefore running any tests, always check for existing failures first:\n\n1. Use the `testFailure` tool to gather current failure information\n2. Review the failures to understand what's already broken\n3. Only then proceed with test execution\n\n## Running Tests\n\nUse VS Code's testing interface or the `runTests` tool with appropriate parameters:\n\n- To run all tests: invoke runTests without file filter\n- To run specific test file: invoke runTests with the test file path\n- To run failed tests only: invoke runTests with `--lf` flag\n\n## Test Location\n\nTests live in `test/` directory. App code is under `server/`.\n\nPYTHONPATH is preconfigured to include the following which should meet all needs:\n- `/app` # the primary location where python runs in the production system\n- `/app/server` # symbolic link to /wprkspaces/NetAlertX/server\n- `/app/server/plugins` # symbolic link to /workspaces/NetAlertX/server/plugins\n- `/opt/venv/lib/pythonX.Y/site-packages`\n- `/workspaces/NetAlertX/test`\n- `/workspaces/NetAlertX/server`\n- `/workspaces/NetAlertX`\n- `/usr/lib/pythonX.Y/site-packages`\n\n## Authentication in Tests\n\nRetrieve `API_TOKEN` using Python (not shell):\n\n```python\nfrom helper import get_setting_value\ntoken = get_setting_value(\"API_TOKEN\")\n```\n\n## Troubleshooting 403 Forbidden\n\n1. Ensure backend is running (use devcontainer-services skill)\n2. Verify config loaded: `get_setting_value(\"API_TOKEN\")` returns non-empty\n3. Re-run startup if needed (use devcontainer-setup skill)\n\n## Docker Test Image\n\nIf container changes affect tests, rebuild the test image first:\n\n```bash\ndocker buildx build -t netalertx-test .\n```\n\nThis takes ~30 seconds unless venv stage changes (~90s).\n\n## Pitfall: `sys.modules` Stubbing Leaks Across Test Files\n\nSome plugin tests (e.g. `test/plugins/test_ntfy_custom_headers.py`) stub NetAlertX\nmodules (`conf`, `helper`, `models.notification_instance`, etc.) via\n`sys.modules[name] = fake_module` so the plugin script can be imported standalone,\noutside the container. Because `sys.modules` is a single process-wide cache shared\nby the whole pytest session, a fake module inserted by one test file silently\nshadows the real module for every other test file collected afterwards — pytest\nimports all test files during collection, before any test runs, so this can happen\nregardless of alphabetical/directory order.\n\nSymptom: `AttributeError: <module 'models.notification_instance'> does not have\nthe attribute 'get_setting_value'` (or similar) in an unrelated test file, where\nthe module repr has no `from '<path>'` suffix — a giveaway that a stub, not the\nreal module, was resolved.\n\nFix pattern: track which module names your stub actually inserted, and pop them\nback out of `sys.modules` immediately after the one-time import that needed them\n(the already-imported script keeps its bound names regardless):\n\n```python\n_stubbed_module_names = []\n\ndef _stub(name, **attrs):\n    if name not in sys.modules:\n        mod = types.ModuleType(name)\n        for k, v in attrs.items():\n            setattr(mod, k, v)\n        sys.modules[name] = mod\n        _stubbed_module_names.append(name)\n\n# ... _stub(...) calls, then the one-time import ...\nimport ntfy\n\nfor _name in _stubbed_module_names:\n    sys.modules.pop(_name, None)\n```\n\nReproduce cross-file pollution locally by running the suspect file together with\nthe affected one in a single pytest invocation (order matters less than you'd\nthink — collection happens for all files first):\n\n```bash\npytest test/plugins/test_ntfy_custom_headers.py test/backend/test_notification_templates.py -v\n```\n",".github/skills/skills-overview/SKILL.md":"---\nname: skills-overview\ndescription: Index of all available skills across GitHub Copilot (.github/skills/), Gemini CLI (.gemini/skills/), and Claude Code (.claude/skills/). Load this to find the right skill for a task, or to locate the counterpart skill in another assistant's tree.\n---\n\n# Skills Index — Cross-Reference\n\nThree AI assistants are configured for this project, each with their own skill directory:\n\n- **GitHub Copilot** → `.github/skills/`\n- **Gemini CLI** → `.gemini/skills/`\n- **Claude Code** → `.claude/skills/` (mirrors most, not all, of the shared skills below — see the table's Claude Skill column for which)\n\nSkills with the same purpose exist in more than one, sometimes under different names and with different depth. This index maps them so you can find the richer version when needed. A CI check (`scripts/check_skill_pairs.py`, run as `check-skill-pairs` in `.github/workflows/code-checks.yml`) flags PRs that touch some but not all files in a mirrored group - non-blocking, since some divergence is intentional.\n\n---\n\n## Shared Skills (exist in more than one tree)\n\n| Topic | Copilot Skill | Gemini Skill | Claude Skill | Notes |\n|-------|--------------|--------------|--------------|-------|\n| Testing | `testing-workflow` | `testing-workflow` | `testing-workflow` | All three cover the full-suite-by-default rule, PYTHONPATH, auth/token retrieval, and the `sys.modules` stubbing pitfall |\n| Settings & config | `settings-management` | `settings` | — | Gemini version is more comprehensive (22-point guide + PR checklist); Copilot version covers `ccd()` and `get_setting_value()` usage |\n| MCP activation | `mcp-activation` | `mcp-activation` | — | Copilot version covers VS Code window reload; Gemini version covers Gemini CLI session restart |\n| Project navigation | `project-navigation` | `project-navigation` | — | Copilot version has full path tables and env vars; Gemini version is a brief reference |\n| Plugin dev | `plugin-run-development` | `plugin-development` | `plugin-development` | All three cover data contract, phases, formats, the `RUN_TIMEOUT` kill-timer gotcha (`timeoutMultiplier`/`per_item_timeout()`), and a pre-PR pointer to the Conventions Checklist in `docs/PLUGINS_DEV.md` |\n| Plugin README docs | `plugin-readme` | `plugin-readme` | `plugin-readme` | All three cover README structure, the \"don't re-document settings\" rule, the `docs.netalertx.com` cross-linking convention, and common defects (template leftovers, copy-paste errors) found during a full-repo audit |\n| Devcontainer | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | `devcontainer-management` | — | Copilot splits into 3 focused skills; Gemini combines into one (uses `docker exec`). `devcontainer-configs` also covers Dockerfile generation (root `Dockerfile` → `generate-dockerfile.sh` → `.devcontainer/Dockerfile`, source files not present at build time) - mined from real review comments on PR #1184/#1230. |\n| Install scripts | `install-scripts` | `install-scripts` | `install-scripts` | `install/*` conventions mined from real review comments (#1214/#1230/#1235): uninstall must only remove NetAlertX's own files, never system packages; `check-*.sh` scripts intentionally differ on fail-fast vs. warning-only; `/back` is legacy, not the active code path. |\n| PR review | `pr-analysis` | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist; the standing rule that a repeated review comment becomes a skill update (sourced from jokob's own real PR comments, #1739/#1744); a readability-over-cleverness code-style note from the same source. |\n| Logging | `logging-standards` | `logging-standards` | — | `mylog` levels, message format, what not to log |\n| Scan pipeline internals | `scan-pipeline` | `scan-pipeline` | `scan-pipeline` | `process_scan()` call order and why it's load-bearing, `CurrentScan`/`Events`/`Sessions`/`DevicesView` relationships, how a session actually closes (no `close_session()` exists), and the `FIELD_SPECS` field-write authority mechanism. Complements `database-patterns` (Devices write-path/`*Source` attribution) rather than duplicating it. |\n| Database patterns | `database-patterns` | `database-patterns` | `database-patterns` | Devices table write-path inventory, the `FIELD_SOURCE_MAP`/`*Source` attribution system in `server/db/authoritative_handler.py`, SQLite trigger vs. Python-hook tradeoffs, and event-sourced vs. snapshot audit logging. |\n| PRD writing | `prd-writing` | `prd-writing` | `prd-writing` | Methodology for writing a design doc: challenge the idea, verify every claim against actual code, trace every downstream consumer of a new mechanism, evaluate performance impact against the real schema/indexes, record rejected alternatives and open-issue decisions explicitly, final-check pass before done. |\n| UX/frontend design | `ux-design-patterns` | `ux-design-patterns` | `ux-design-patterns` | Don't invent new UX behavior/visual patterns unless a PRD calls for it - search `front/` for an existing pattern first and reuse it. Priority order for design tradeoffs when several options are reasonable: existing behavior > intuitiveness > information density > usability > utility > uniqueness > industry practices > generic UI. |\n| Skill hygiene | `skill-hygiene` | `skill-hygiene` | `skill-hygiene` | Read before writing/editing any SKILL.md, or any research/audit doc in `.gemini/internal-docs/research/`. Two standing rules: state current behavior only (no \"Correction:\", no \"as of <date>\", no \"caught in review\" narration - that trail belongs in PRDs), and prefer plain, short wording. Includes the grep sweep to run before calling a doc clean. |\n| Plugin review | `plugin-review` | `plugin-review` | `plugin-review` | Reviewer-facing complement to Plugin dev above: no raw SQL in a plugin script - use an existing/new `server/models/*.py` method, not GraphQL (which no plugin reaches). Named exception list for 5 pre-existing core/infra plugins with legitimate direct DB access, a collation-checking step, and a worked example from a real PR (#1788, DOCKERDISC). |\n\n---\n\n## Copilot-Only Skills\n\nNo Gemini equivalent yet:\n\n| Skill | Purpose |\n|-------|---------|\n| `api-development` | Creating REST API endpoints |\n| `authentication` | API tokens and 401/403 debugging |\n| `code-standards` | Coding conventions and style rules |\n| `database-reset` | Wipe and regenerate the database and config |\n| `docker-build` | Build Docker images for testing or production |\n| `docker-prune` | Clean unused Docker resources (destructive — requires confirmation) |\n| `sample-data` | Load synthetic device data into the devcontainer |\n\n---\n\n## Gemini-Only Skills\n\nNo Copilot equivalent yet:\n\n| Skill | Purpose |\n|-------|---------|\n| `initiative-start` | Research methodology and structured approach for new tasks |\n\n---\n\n## Adding a New Skill\n\nWhen adding a skill, create it in **both** directories to keep both AI systems current:\n\n- `.github/skills/<name>/SKILL.md` — add an entry to the skills table in `.github/copilot-instructions.md`\n- `.gemini/skills/<name>/SKILL.md` — auto-discovered by Gemini CLI via YAML frontmatter\n\nKeep the body content identical between both files. Only the frontmatter `name`/`description` may differ slightly to match each system's discovery heuristics.\n\nIf the skill is high-value enough to also mirror to Claude Code, add `.claude/skills/<name>/SKILL.md` too, and add the group to `GROUPS` in `scripts/check_skill_pairs.py` so drift gets flagged. Claude Code has no `activate_skill()`/`testFailure`/`runTests`/`report_progress` equivalents - adapt any such tool references to plain `Bash` commands instead of copying them verbatim.\n",".github/skills/sample-data/SKILL.md":"---\nname: netalertx-sample-data\ndescription: Load synthetic device data into the devcontainer. Use this when asked to load sample devices, seed data, import test devices, populate database, or generate test data.\n---\n\n# Sample Data Loading\n\nGenerates synthetic device inventory and imports it via the `/devices/import` API endpoint.\n\n## Command\n\n```bash\ncd /workspaces/NetAlertX/.devcontainer/scripts\n./load-devices.sh\n```\n\n## Environment\n\n- `CSV_PATH`: defaults to `/tmp/netalertx-devices.csv`\n\n## Prerequisites\n\n- Backend must be running\n- API must be accessible\n\n## What It Does\n\n1. Generates synthetic device records (MAC addresses, IPs, names, vendors)\n2. Creates CSV file at `$CSV_PATH`\n3. POSTs to `/devices/import` endpoint\n4. Devices appear in database and UI\n",".gemini/skills/project-navigation/SKILL.md":"---\nname: project-navigation\ndescription: Reference for the NetAlertX codebase structure, key file paths, and configuration locations. Use this when exploring the codebase or looking for specific components like the backend entry point, frontend files, or database location.\n---\n\n# Project Navigation\n\n## Key Paths\n\n| Component | Path |\n|-----------|------|\n| Workspace root | `/workspaces/NetAlertX` |\n| Backend entry | `server/__main__.py` |\n| API server | `server/api_server/api_server_start.py` |\n| Plugin system | `server/plugin.py` |\n| Initialization | `server/initialise.py` |\n| Frontend | `front/` |\n| Frontend JS | `front/js/common.js` |\n| Frontend PHP | `front/php/server/*.php` |\n| Plugins | `server/plugins/` |\n| Plugin template | `server/plugins/__template` |\n| Database helpers | `server/db/db_helper.py` |\n| Device model | `server/models/device_instance.py` |\n| Messaging | `server/messaging/` |\n| Workflows | `server/workflows/` |\n\n## Architecture\n\nNetAlertX uses a frontend–backend architecture: the frontend runs on **PHP + Nginx** (see `front/`), the backend is implemented in **Python** (see `server/`), and scheduled tasks are managed by a **supercronic** scheduler.\n\n## Runtime Paths\n\n| Data | Path |\n|------|------|\n| Config (runtime) | `/data/config/app.conf` |\n| Config (default) | `back/app.conf` |\n| Database | `/data/db/app.db` |\n| API JSON cache | `/tmp/api/*.json` |\n| Logs | `/tmp/log/` |\n| Plugin logs | `/tmp/log/plugins/` |\n\n## Environment Variables\n\nUse these instead of hardcoding paths:\n- `NETALERTX_DB`\n- `NETALERTX_LOG`\n- `NETALERTX_CONFIG`\n- `NETALERTX_DATA`\n- `NETALERTX_APP`\n\n## Documentation\n\n| Topic | Path |\n|-------|------|\n| Plugin development | `docs/PLUGINS_DEV.md` |\n| System settings | `docs/SETTINGS_SYSTEM.md` |\n| API docs | `docs/API_*.md` |\n| Debug guides | `docs/DEBUG_*.md` |\n",".gemini/skills/settings/SKILL.md":"---\nname: nax-settings-development\ndescription: Best practices for creating and maintaining NetAlertX settings (config.json), including UI, validation, localization, runtime behavior, and implementation consistency.\n---\n\n# NetAlertX Settings Development\n\nThis skill defines the conventions and best practices for adding or modifying settings in NetAlertX.\n\nThe goal is for every setting to be:\n\n- Declarative\n- Self-documenting\n- Automatically rendered by the Settings UI\n- Fully localized\n- Backwards compatible\n- Easy to maintain\n\n---\n\n## Quick Reference\n\n**Read a setting (backend):**\n```python\nfrom helper import get_setting_value\nvalue = get_setting_value('SETTING_NAME')\n```\nNever read `app.conf` directly. Always use `get_setting_value()`.\n\n**Read a setting (frontend):**\n```javascript\ngetSetting(\"SETTING_NAME\")\n```\n\n**Add a core setting** — use `ccd()` in `server/initialise.py`:\n```python\nccd('SETTING_NAME', 'default_value', 'description')\n```\n\n**Add a plugin setting** — define in the plugin's `config.json` under the `settings` key.\n\n| File | Purpose |\n|------|---------|\n| `/data/config/app.conf` | Runtime config (source of truth) |\n| `back/app.conf` | Default config (template) |\n\nUse `APP_CONF_OVERRIDE` for settings that must be set before startup.\n\n---\n\n# 1. Follow existing patterns\n\nBefore creating a new setting:\n\n- Search for similar settings.\n- Reuse existing naming conventions.\n- Reuse existing setting types.\n- Reuse existing validation patterns.\n- Follow the style already used by similar plugins.\n\nAvoid introducing new setting types unless absolutely necessary.\n\n---\n\n# 2. Setting naming\n\nSetting keys use uppercase snake case.\n\nGood:\n\n```text\nUI_DEV_SECTIONS\nSCAN_INTERVAL\nMQTT_HOST\n```\n\nAvoid:\n\n```text\nuiDevSections\nscanInterval\nScanInterval\n```\n\nNames should clearly describe the purpose.\n\n---\n\n# 3. Categories\n\nPlace settings into the most appropriate category.\n\nKeep related settings together.\n\nAvoid creating new categories unless there is a clear need.\n\n---\n\n# 4. Choose the correct type\n\nUse the simplest type that correctly models the value.\n\nCommon types include:\n\n- string\n- integer\n- float\n- boolean\n- password\n- select\n- array\n- textarea\n\nAvoid encoding structured JSON inside string settings.\n\n---\n\n# 5. Validation\n\nAlways define validation whenever appropriate.\n\nExamples include:\n\n- minimum values\n- maximum values\n- regex validation\n- allowed options\n\nReject invalid configuration rather than silently accepting it.\n\n---\n\n# 6. Defaults\n\nProvide sensible defaults that work for a fresh installation.\n\nAvoid defaults that require external services or additional configuration.\n\nA default value should exist in exactly one place.\n\nUse the `default_value` defined in `config.json`.\n\nDo not duplicate the same default value elsewhere in Python or JavaScript.\n\n---\n\n# 7. Names and descriptions\n\nSetting names should be concise.\n\nExamples:\n\n- Scan interval\n- MQTT Host\n- Hide device sections\n\nDescriptions should explain:\n\n- what the setting does\n- when it should be used\n- important side effects\n\nAvoid implementation details.\n\nGood:\n\n> Interval between network scans in seconds.\n\nBad:\n\n> Calls scheduler.py every X seconds.\n\n---\n\n# 8. Boolean settings\n\nBoolean settings should read naturally.\n\nPrefer verbs such as:\n\n- Enable\n- Disable\n- Require\n- Allow\n- Ignore\n- Show\n- Hide\n\nExamples:\n\n- Enable MQTT\n- Require NICs Online\n- Hide Offline Devices\n\n---\n\n# 9. Select settings\n\nWhen users must choose from predefined values, use a select setting.\n\nNever require users to remember internal values.\n\nEach option should have a meaningful label.\n\n---\n\n# 10. Array settings\n\nUse arrays only when multiple independent values are expected.\n\nExamples:\n\n- ignored MAC addresses\n- subnet lists\n- plugin lists\n\n---\n\n# 11. Localization\n\nEvery setting must have localized language strings.\n\nIf the language strings are **not** defined directly inside `config.json`, they must exist in `en_us.json`.\n\nFor example, for:\n\n```text\nUI_DEV_SECTIONS\n```\n\nadd:\n\n```json\n\"UI_DEV_SECTIONS_name\": \"Hide device sections\",\n\"UI_DEV_SECTIONS_description\": \"Select which UI elements to hide on the Devices page.\"\n```\n\nWhen using external language files, `config.json` must reference:\n\n```json\n\"name\": [\n  {\n    \"string\": \"_GLOBAL_LANG_FILES_\"\n  }\n]\n```\n\nThis tells NetAlertX to resolve the display name from the language files.\n\n---\n\n# 12. Source of truth\n\n`app.conf` is the source of truth.\n\nKeep the following in mind:\n\n- User-defined values always come from `app.conf`.\n- Default values from `config.json` are only used when the setting is missing from `app.conf`.\n- Never assume settings are permanently stored in the database.\n\n---\n\n# 13. Plugin-first settings\n\nWhenever possible, define new settings inside a plugin's `config.json`.\n\nAvoid adding hardcoded application settings unless there is a compelling architectural reason.\n\nPlugin settings are the preferred and future-proof approach.\n\n---\n\n# 14. Runtime lifecycle\n\nUnderstand how settings flow through the application.\n\n```text\nconfig.json\n        │\n        │ default values\n        ▼\napp.conf (source of truth)\n        │\n        ▼\nSettings database table\n        │\n        ▼\ntable_settings.json API\n        │\n        ▼\nFrontend (getSetting())\n```\n\nThe database and API are runtime representations only.\n\nThey are regenerated from `app.conf` during initialization.\n\n---\n\n# 15. Access\n\nCode should never read `app.conf` directly.\n\nIn the Frontend, always retrieve settings through:\n\n```javascript\ngetSetting(\"SETTING_NAME\")\n```\n\nAnd in the backend, always retrieve settings through:\n\n```python\nget_setting_value(\"SETTING_NAME\")\n```\n\nThis guarantees the value comes from the generated settings API.\n\n---\n\n# 16. Metadata\n\nEach setting automatically has a corresponding `__metadata` entry generated in `app.conf`.\n\nDo not manually create or modify metadata entries unless working on the settings framework itself.\n\n---\n\n# 17. Backend\n\nBackend code should:\n\n- use the existing configuration helpers\n- avoid duplicated parsing logic\n- gracefully handle missing values\n- avoid hardcoded defaults\n\n---\n\n# 18. Frontend\n\nA correctly defined setting should render automatically in the existing Settings UI.\n\nAvoid writing custom JavaScript unless absolutely necessary.\n\n---\n\n# 19. Prefer configuration over code\n\nIf a behaviour can reasonably be controlled through an existing setting type (boolean, select, integer, array, etc.), introduce a setting instead of hardcoding special-case logic.\n\nConfiguration is preferred over implementation-specific behaviour whenever practical.\n\n---\n\n# 20. Backwards compatibility\n\nAvoid:\n\n- renaming settings\n- changing data types\n- changing semantics\n\nIf unavoidable:\n\n- provide migration logic\n- preserve compatibility where possible\n\n---\n\n# 21. Documentation\n\nEvery user-facing setting should eventually be documented.\n\nInclude:\n\n- purpose\n- accepted values\n- default value\n- examples when useful\n\n---\n\n# 22. Pull request checklist\n\nBefore submitting a PR, verify:\n\n- [ ] Setting follows existing conventions.\n- [ ] Correct category used.\n- [ ] Appropriate type selected.\n- [ ] Validation defined.\n- [ ] Sensible default provided.\n- [ ] Default defined only once.\n- [ ] Name is concise.\n- [ ] Description is clear.\n- [ ] Localization strings added.\n- [ ] `_GLOBAL_LANG_FILES_` used where appropriate.\n- [ ] Plugin-first approach followed where applicable.\n- [ ] Frontend renders automatically.\n- [ ] Backend uses existing configuration helpers.\n- [ ] No unnecessary frontend code added.\n- [ ] No duplicated parsing logic.\n- [ ] Backwards compatibility maintained.\n- [ ] Documentation updated if required.\n\n---\n\n# References\n\n- https://docs.netalertx.com/PLUGINS_DEV_SETTINGS/\n- https://docs.netalertx.com/SETTINGS_SYSTEM/\n- https://docs.netalertx.com/PLUGINS_OVERVIEW/",".github/skills/settings-management/SKILL.md":"---\nname: netalertx-settings-management\ndescription: Manage NetAlertX configuration settings. Use this when asked to add setting, read config, get_setting_value, ccd, or configure options.\n---\n\n# Settings Management\n\n## Quick Reference\n\n**Read a setting (backend):**\n```python\nfrom helper import get_setting_value\nvalue = get_setting_value('SETTING_NAME')\n```\nNever hardcode ports, secrets, or configuration values. Always use `get_setting_value()`.\n\n**Read a setting (frontend):**\n```javascript\ngetSetting(\"SETTING_NAME\")\n```\n\n**Add a core setting** — use `ccd()` in `server/initialise.py`:\n```python\nccd('SETTING_NAME', 'default_value', 'description')\n```\n\n**Add a plugin setting** — define in the plugin's `config.json` under the `settings` key.\n\n| File | Purpose |\n|------|---------|\n| `/data/config/app.conf` | Runtime config (source of truth, modified by app) |\n| `back/app.conf` | Default config (template) |\n\nUse `APP_CONF_OVERRIDE` for settings that must be set before startup.\n\n---\n\n# Development Guide\n\nThe goal is for every setting to be:\n\n- Declarative and self-documenting\n- Automatically rendered by the Settings UI\n- Fully localized\n- Backwards compatible\n- Easy to maintain\n\n---\n\n## 1. Follow existing patterns\n\nBefore creating a new setting, search for similar ones. Reuse existing naming conventions, types, and validation patterns. Follow the style already used by similar plugins. Avoid introducing new setting types unless absolutely necessary.\n\n---\n\n## 2. Setting naming\n\nSetting keys use uppercase snake case: `UI_DEV_SECTIONS`, `SCAN_INTERVAL`, `MQTT_HOST`.\nNames should clearly describe the purpose.\n\n---\n\n## 3. Categories\n\nPlace settings into the most appropriate category. Keep related settings together. Avoid creating new categories unless there is a clear need.\n\n---\n\n## 4. Choose the correct type\n\nUse the simplest type that correctly models the value: `string`, `integer`, `float`, `boolean`, `password`, `select`, `array`, `textarea`. Avoid encoding structured JSON inside string settings.\n\n---\n\n## 5. Validation\n\nAlways define validation whenever appropriate (min/max values, regex, allowed options). Reject invalid configuration rather than silently accepting it.\n\n---\n\n## 6. Defaults\n\nProvide sensible defaults that work for a fresh installation. The `default_value` in `config.json` is the single source of truth — do not duplicate it in Python or JavaScript.\n\n---\n\n## 7. Names and descriptions\n\nNames should be concise. Descriptions should explain what the setting does, when to use it, and any important side effects — not implementation details.\n\n---\n\n## 8. Boolean settings\n\nPrefer verbs: Enable, Disable, Require, Allow, Ignore, Show, Hide. Examples: *Enable MQTT*, *Require NICs Online*, *Hide Offline Devices*.\n\n---\n\n## 9. Select settings\n\nUse when users must choose from predefined values. Each option must have a meaningful label — never require users to remember internal values.\n\n---\n\n## 10. Array settings\n\nUse only when multiple independent values are expected (e.g., ignored MACs, subnet lists, plugin lists).\n\n---\n\n## 11. Localization\n\nEvery setting must have localized language strings. If not defined directly in `config.json`, they must exist in `en_us.json`.\n\nExample for `UI_DEV_SECTIONS`:\n```json\n\"UI_DEV_SECTIONS_name\": \"Hide device sections\",\n\"UI_DEV_SECTIONS_description\": \"Select which UI elements to hide on the Devices page.\"\n```\n\nWhen using external language files, `config.json` must reference:\n```json\n\"name\": [{ \"string\": \"_GLOBAL_LANG_FILES_\" }]\n```\n\n---\n\n## 12. Source of truth\n\n`app.conf` is the source of truth. User-defined values always come from `app.conf`; defaults from `config.json` apply only when a setting is absent. Never read `app.conf` directly — always go through `get_setting_value()`.\n\n---\n\n## 13. Plugin-first settings\n\nWhenever possible, define new settings inside a plugin's `config.json`. Avoid adding hardcoded application settings unless there is a compelling architectural reason.\n\n---\n\n## 14. Runtime lifecycle\n\n```text\nconfig.json (defaults)\n    → app.conf (source of truth)\n    → Settings database table\n    → table_settings.json API\n    → Frontend getSetting() / Backend get_setting_value()\n```\n\nThe database and API are runtime representations only, regenerated from `app.conf` at init.\n\n---\n\n## 15. Backwards compatibility\n\nAvoid renaming settings, changing data types, or changing semantics. If unavoidable, provide migration logic and preserve compatibility where possible.\n\n---\n\n## 16. Pull request checklist\n\n- [ ] Follows existing naming conventions (uppercase snake case)\n- [ ] Correct category used\n- [ ] Appropriate type selected, validation defined\n- [ ] Sensible default — defined only once (in `config.json`)\n- [ ] Name concise, description clear (no implementation details)\n- [ ] Localization strings added; `_GLOBAL_LANG_FILES_` used where appropriate\n- [ ] Plugin-first approach followed where applicable\n- [ ] Frontend renders automatically without custom JavaScript\n- [ ] Backend uses `get_setting_value()` — no direct `app.conf` reads\n- [ ] Backwards compatibility maintained\n- [ ] Documentation updated if required\n\n---\n\n## References\n\n- https://docs.netalertx.com/PLUGINS_DEV_SETTINGS/\n- https://docs.netalertx.com/SETTINGS_SYSTEM/\n",".gemini/skills/skills-index/SKILL.md":"---\nname: skills-index\ndescription: Index of all available skills across Gemini CLI (.gemini/skills/), GitHub Copilot (.github/skills/), and Claude Code (.claude/skills/). Load this to find the right skill for a task, or to locate the counterpart skill in another assistant's tree.\n---\n\n# Skills Index — Cross-Reference\n\nThree AI assistants are configured for this project, each with their own skill directory:\n\n- **Gemini CLI** → `.gemini/skills/`\n- **GitHub Copilot** → `.github/skills/`\n- **Claude Code** → `.claude/skills/` (mirrors most, not all, of the shared skills below — see the table's Claude Skill column for which)\n\nSkills with the same purpose exist in more than one, sometimes under different names and with different depth. This index maps them so you can find the richer version when needed. A CI check (`scripts/check_skill_pairs.py`, run as `check-skill-pairs` in `.github/workflows/code-checks.yml`) flags PRs that touch some but not all files in a mirrored group - non-blocking, since some divergence is intentional.\n\n---\n\n## Shared Skills (exist in more than one tree)\n\n| Topic | Gemini Skill | Copilot Skill | Claude Skill | Notes |\n|-------|-------------|--------------|--------------|-------|\n| Testing | `testing-workflow` | `testing-workflow` | `testing-workflow` | All three cover the full-suite-by-default rule, PYTHONPATH, auth/token retrieval, and the `sys.modules` stubbing pitfall |\n| Settings & config | `settings` | `settings-management` | — | Gemini version is more comprehensive (22-point guide + PR checklist); Copilot version covers `ccd()` and `get_setting_value()` usage |\n| MCP activation | `mcp-activation` | `mcp-activation` | — | Gemini version covers Gemini CLI session restart; Copilot version covers VS Code window reload |\n| Project navigation | `project-navigation` | `project-navigation` | — | Copilot version has full path tables and env vars; Gemini version is a brief reference |\n| Plugin dev | `plugin-development` | `plugin-run-development` | `plugin-development` | All three cover data contract, phases, formats, the `RUN_TIMEOUT` kill-timer gotcha (`timeoutMultiplier`/`per_item_timeout()`), and a pre-PR pointer to the Conventions Checklist in `docs/PLUGINS_DEV.md` |\n| Plugin README docs | `plugin-readme` | `plugin-readme` | `plugin-readme` | All three cover README structure, the \"don't re-document settings\" rule, the `docs.netalertx.com` cross-linking convention, and common defects (template leftovers, copy-paste errors) found during a full-repo audit |\n| Devcontainer | `devcontainer-management` | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | — | Gemini combines into one (uses `docker exec`); Copilot splits into 3 focused skills. `devcontainer-configs` also covers Dockerfile generation (root `Dockerfile` → `generate-dockerfile.sh` → `.devcontainer/Dockerfile`, source files not present at build time) - mined from real review comments on PR #1184/#1230. |\n| Install scripts | `install-scripts` | `install-scripts` | `install-scripts` | `install/*` conventions mined from real review comments (#1214/#1230/#1235): uninstall must only remove NetAlertX's own files, never system packages; `check-*.sh` scripts intentionally differ on fail-fast vs. warning-only; `/back` is legacy, not the active code path. |\n| PR review | `pr-analysis` | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist; the standing rule that a repeated review comment becomes a skill update (sourced from jokob's own real PR comments, #1739/#1744); a readability-over-cleverness code-style note from the same source. |\n| Logging | `logging-standards` | `logging-standards` | — | `mylog` levels, message format, what not to log |\n| Scan pipeline internals | `scan-pipeline` | `scan-pipeline` | `scan-pipeline` | `process_scan()` call order and why it's load-bearing, `CurrentScan`/`Events`/`Sessions`/`DevicesView` relationships, how a session actually closes (no `close_session()` exists), and the `FIELD_SPECS` field-write authority mechanism. Complements `database-patterns` (Devices write-path/`*Source` attribution) rather than duplicating it. |\n| Database patterns | `database-patterns` | `database-patterns` | `database-patterns` | Devices table write-path inventory, the `FIELD_SOURCE_MAP`/`*Source` attribution system in `server/db/authoritative_handler.py`, SQLite trigger vs. Python-hook tradeoffs, and event-sourced vs. snapshot audit logging. |\n| PRD writing | `prd-writing` | `prd-writing` | `prd-writing` | Methodology for writing a design doc: challenge the idea, verify every claim against actual code, trace every downstream consumer of a new mechanism, evaluate performance impact against the real schema/indexes, record rejected alternatives and open-issue decisions explicitly, final-check pass before done. |\n| UX/frontend design | `ux-design-patterns` | `ux-design-patterns` | `ux-design-patterns` | Don't invent new UX behavior/visual patterns unless a PRD calls for it - search `front/` for an existing pattern first and reuse it. Priority order for design tradeoffs when several options are reasonable: existing behavior > intuitiveness > information density > usability > utility > uniqueness > industry practices > generic UI. |\n| Skill hygiene | `skill-hygiene` | `skill-hygiene` | `skill-hygiene` | Read before writing/editing any SKILL.md, or any research/audit doc in `.gemini/internal-docs/research/`. Two standing rules: state current behavior only (no \"Correction:\", no \"as of <date>\", no \"caught in review\" narration - that trail belongs in PRDs), and prefer plain, short wording. Includes the grep sweep to run before calling a doc clean. |\n| Plugin review | `plugin-review` | `plugin-review` | `plugin-review` | Reviewer-facing complement to Plugin dev above: no raw SQL in a plugin script - use an existing/new `server/models/*.py` method, not GraphQL (which no plugin reaches). Named exception list for 5 pre-existing core/infra plugins with legitimate direct DB access, a collation-checking step, and a worked example from a real PR (#1788, DOCKERDISC). |\n\n---\n\n## Copilot-Only Skills\n\nNo Gemini equivalent yet:\n\n| Copilot Skill | Purpose |\n|--------------|---------|\n| `api-development` | Creating REST API endpoints |\n| `authentication` | API tokens and 401/403 debugging |\n| `code-standards` | Coding conventions and style rules |\n| `database-reset` | Wipe and regenerate the database and config |\n| `docker-build` | Build Docker images for testing or production |\n| `docker-prune` | Clean unused Docker resources (destructive — requires confirmation) |\n| `sample-data` | Load synthetic device data into the devcontainer |\n\n---\n\n## Gemini-Only Skills\n\nNo Copilot equivalent yet:\n\n| Gemini Skill | Purpose |\n|-------------|---------|\n| `initiative-start` | Research methodology and structured approach for new tasks |\n\n---\n\n## Adding a New Skill\n\nWhen adding a skill, create it in **both** directories to keep both AI systems current:\n\n- `.gemini/skills/<name>/SKILL.md` — auto-discovered by Gemini CLI via YAML frontmatter\n- `.github/skills/<name>/SKILL.md` — add an entry to the skills table in `.github/copilot-instructions.md`\n\nKeep the body content identical between both files. Only the frontmatter `name`/`description` may differ slightly to match each system's discovery heuristics.\n\nIf the skill is high-value enough to also mirror to Claude Code, add `.claude/skills/<name>/SKILL.md` too, and add the group to `GROUPS` in `scripts/check_skill_pairs.py` so drift gets flagged. Claude Code has no `activate_skill()`/`testFailure`/`runTests`/`report_progress` equivalents - adapt any such tool references to plain `Bash` commands instead of copying them verbatim.\n",".github/skills/project-navigation/SKILL.md":"# project-navigation\n\nPath: `.github/skills/project-navigation/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/project-navigation/SKILL.md)",".github/skills/plugin-run-development/SKILL.md":"# plugin-run-development\n\nPath: `.github/skills/plugin-run-development/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/plugin-run-development/SKILL.md)",".github/skills/mcp-activation/SKILL.md":"# mcp-activation\n\nPath: `.github/skills/mcp-activation/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/mcp-activation/SKILL.md)",".github/skills/docker-prune/SKILL.md":"# docker-prune\n\nPath: `.github/skills/docker-prune/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/docker-prune/SKILL.md)",".github/skills/docker-build/SKILL.md":"# docker-build\n\nPath: `.github/skills/docker-build/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/docker-build/SKILL.md)",".github/skills/devcontainer-setup/SKILL.md":"# devcontainer-setup\n\nPath: `.github/skills/devcontainer-setup/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-setup/SKILL.md)",".github/skills/devcontainer-services/SKILL.md":"# devcontainer-services\n\nPath: `.github/skills/devcontainer-services/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-services/SKILL.md)",".github/skills/devcontainer-configs/SKILL.md":"# devcontainer-configs\n\nPath: `.github/skills/devcontainer-configs/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-configs/SKILL.md)",".github/skills/database-reset/SKILL.md":"# database-reset\n\nPath: `.github/skills/database-reset/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/database-reset/SKILL.md)",".github/skills/database-patterns/SKILL.md":"# database-patterns\n\nPath: `.github/skills/database-patterns/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/database-patterns/SKILL.md)",".github/skills/code-standards/SKILL.md":"# code-standards\n\nPath: `.github/skills/code-standards/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/code-standards/SKILL.md)",".github/skills/authentication/SKILL.md":"# authentication\n\nPath: `.github/skills/authentication/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/authentication/SKILL.md)",".github/skills/api-development/SKILL.md":"# api-development\n\nPath: `.github/skills/api-development/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/api-development/SKILL.md)",".github/copilot-instructions.md":"# GitHub Copilot Instructions\n\nPath: `.github/copilot-instructions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/copilot-instructions.md)","docs/API.md":"# docs - API Interface Contract & Specifications\n\nPath: `docs/API.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/docs/API.md)","front/llms.txt":"# front - LLM Index & Context Digest\n\nPath: `front/llms.txt`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/front/llms.txt)","server/plugins/README.md":"# plugins Documentation\n\nPath: `server/plugins/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/README.md)","server/plugins/pihole_scan/config.json":"# Pihole_scan Skill\n\nPath: `server/plugins/pihole_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_scan/config.json)","server/plugins/pihole_scan/README.md":"# pihole_scan Documentation\n\nPath: `server/plugins/pihole_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_scan/README.md)","server/plugins/pihole_api_scan/config.json":"# Pihole_api_scan Skill\n\nPath: `server/plugins/pihole_api_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_api_scan/config.json)","server/plugins/pihole_api_scan/README.md":"# pihole_api_scan Documentation\n\nPath: `server/plugins/pihole_api_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_api_scan/README.md)","server/plugins/omada_sdn_openapi/config.json":"# Omada_sdn_openapi Skill\n\nPath: `server/plugins/omada_sdn_openapi/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_openapi/config.json)","server/plugins/omada_sdn_openapi/README.md":"# omada_sdn_openapi Documentation\n\nPath: `server/plugins/omada_sdn_openapi/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_openapi/README.md)","server/plugins/omada_sdn_imp/config.json":"# Omada_sdn_imp Skill\n\nPath: `server/plugins/omada_sdn_imp/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_imp/config.json)","server/plugins/omada_sdn_imp/README.md":"# omada_sdn_imp Documentation\n\nPath: `server/plugins/omada_sdn_imp/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_imp/README.md)","server/plugins/nslookup_scan/config.json":"# Nslookup_scan Skill\n\nPath: `server/plugins/nslookup_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nslookup_scan/config.json)","server/plugins/nslookup_scan/README.md":"# nslookup_scan Documentation\n\nPath: `server/plugins/nslookup_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nslookup_scan/README.md)","server/plugins/notification_processing/config.json":"# Notification_processing Skill\n\nPath: `server/plugins/notification_processing/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/notification_processing/config.json)","server/plugins/notification_processing/README.md":"# notification_processing Documentation\n\nPath: `server/plugins/notification_processing/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/notification_processing/README.md)","server/plugins/nmap_scan/config.json":"# Nmap_scan Skill\n\nPath: `server/plugins/nmap_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_scan/config.json)","server/plugins/nmap_scan/README.md":"# nmap_scan Documentation\n\nPath: `server/plugins/nmap_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_scan/README.md)","server/plugins/nmap_dev_scan/config.json":"# Nmap_dev_scan Skill\n\nPath: `server/plugins/nmap_dev_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_dev_scan/config.json)","server/plugins/nmap_dev_scan/README.md":"# nmap_dev_scan Documentation\n\nPath: `server/plugins/nmap_dev_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_dev_scan/README.md)","server/plugins/newdev_template/config.json":"# Newdev_template Skill\n\nPath: `server/plugins/newdev_template/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/newdev_template/config.json)","server/plugins/newdev_template/README.md":"# newdev_template Documentation\n\nPath: `server/plugins/newdev_template/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/newdev_template/README.md)","server/plugins/nbtscan_scan/config.json":"# Nbtscan_scan Skill\n\nPath: `server/plugins/nbtscan_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nbtscan_scan/config.json)","server/plugins/nbtscan_scan/README.md":"# nbtscan_scan Documentation\n\nPath: `server/plugins/nbtscan_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nbtscan_scan/README.md)","server/plugins/mikrotik_scan/config.json":"# Mikrotik_scan Skill\n\nPath: `server/plugins/mikrotik_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/mikrotik_scan/config.json)","server/plugins/mikrotik_scan/README.md":"# mikrotik_scan Documentation\n\nPath: `server/plugins/mikrotik_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/mikrotik_scan/README.md)","server/plugins/maintenance/config.json":"# Maintenance Skill\n\nPath: `server/plugins/maintenance/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/maintenance/config.json)","server/plugins/maintenance/README.md":"# maintenance Documentation\n\nPath: `server/plugins/maintenance/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/maintenance/README.md)","server/plugins/luci_import/config.json":"# Luci_import Skill\n\nPath: `server/plugins/luci_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/luci_import/config.json)","server/plugins/luci_import/README.md":"# luci_import Documentation\n\nPath: `server/plugins/luci_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/luci_import/README.md)","server/plugins/kea_api/config.json":"# Kea_api Skill\n\nPath: `server/plugins/kea_api/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/kea_api/config.json)","server/plugins/kea_api/README.md":"# kea_api Documentation\n\nPath: `server/plugins/kea_api/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/kea_api/README.md)","server/plugins/ipneigh/config.json":"# Ipneigh Skill\n\nPath: `server/plugins/ipneigh/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ipneigh/config.json)","server/plugins/ipneigh/README.md":"# ipneigh Documentation\n\nPath: `server/plugins/ipneigh/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ipneigh/README.md)","server/plugins/internet_speedtest/config.json":"# Internet_speedtest Skill\n\nPath: `server/plugins/internet_speedtest/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_speedtest/config.json)","server/plugins/internet_speedtest/README.md":"# internet_speedtest Documentation\n\nPath: `server/plugins/internet_speedtest/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_speedtest/README.md)","server/plugins/internet_ip/config.json":"# Internet_ip Skill\n\nPath: `server/plugins/internet_ip/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_ip/config.json)","server/plugins/internet_ip/README.md":"# internet_ip Documentation\n\nPath: `server/plugins/internet_ip/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_ip/README.md)","server/plugins/icmp_scan/config.json":"# Icmp_scan Skill\n\nPath: `server/plugins/icmp_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/icmp_scan/config.json)","server/plugins/icmp_scan/README.md":"# icmp_scan Documentation\n\nPath: `server/plugins/icmp_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/icmp_scan/README.md)","server/plugins/fritzbox/config.json":"# Fritzbox Skill\n\nPath: `server/plugins/fritzbox/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/fritzbox/config.json)","server/plugins/fritzbox/README.md":"# fritzbox Documentation\n\nPath: `server/plugins/fritzbox/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/fritzbox/README.md)","server/plugins/freebox/config.json":"# Freebox Skill\n\nPath: `server/plugins/freebox/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/freebox/config.json)","server/plugins/freebox/README.md":"# freebox Documentation\n\nPath: `server/plugins/freebox/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/freebox/README.md)","server/plugins/dig_scan/config.json":"# Dig_scan Skill\n\nPath: `server/plugins/dig_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dig_scan/config.json)","server/plugins/dig_scan/README.md":"# dig_scan Documentation\n\nPath: `server/plugins/dig_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dig_scan/README.md)","server/plugins/dhcp_servers/config.json":"# Dhcp_servers Skill\n\nPath: `server/plugins/dhcp_servers/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_servers/config.json)","server/plugins/dhcp_servers/README.md":"# dhcp_servers Documentation\n\nPath: `server/plugins/dhcp_servers/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_servers/README.md)","server/plugins/dhcp_leases/ASUS_ROUTERS.md":"# Dhcp_leases Skill\n\nPath: `server/plugins/dhcp_leases/ASUS_ROUTERS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_leases/ASUS_ROUTERS.md)","server/plugins/dhcp_leases/config.json":"# Dhcp_leases Skill\n\nPath: `server/plugins/dhcp_leases/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_leases/config.json)","server/plugins/dhcp_leases/README.md":"# dhcp_leases Documentation\n\nPath: `server/plugins/dhcp_leases/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_leases/README.md)","server/plugins/ddns_update/config.json":"# Ddns_update Skill\n\nPath: `server/plugins/ddns_update/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ddns_update/config.json)","server/plugins/ddns_update/README.md":"# ddns_update Documentation\n\nPath: `server/plugins/ddns_update/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ddns_update/README.md)","server/plugins/ddns_update/README_DE.md":"# Ddns_update Skill\n\nPath: `server/plugins/ddns_update/README_DE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ddns_update/README_DE.md)","server/plugins/db_cleanup/config.json":"# Db_cleanup Skill\n\nPath: `server/plugins/db_cleanup/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/db_cleanup/config.json)","server/plugins/db_cleanup/README.md":"# db_cleanup Documentation\n\nPath: `server/plugins/db_cleanup/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/db_cleanup/README.md)","server/plugins/custom_props/config.json":"# Custom_props Skill\n\nPath: `server/plugins/custom_props/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/custom_props/config.json)","server/plugins/custom_props/README.md":"# custom_props Documentation\n\nPath: `server/plugins/custom_props/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/custom_props/README.md)","server/plugins/csv_backup/config.json":"# Csv_backup Skill\n\nPath: `server/plugins/csv_backup/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/csv_backup/config.json)","server/plugins/csv_backup/README.md":"# csv_backup Documentation\n\nPath: `server/plugins/csv_backup/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/csv_backup/README.md)","server/plugins/avahi_scan/config.json":"# Avahi_scan Skill\n\nPath: `server/plugins/avahi_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/avahi_scan/config.json)","server/plugins/avahi_scan/README.md":"# avahi_scan Documentation\n\nPath: `server/plugins/avahi_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/avahi_scan/README.md)","server/plugins/asuswrt_import/config.json":"# Asuswrt_import Skill\n\nPath: `server/plugins/asuswrt_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/asuswrt_import/config.json)","server/plugins/asuswrt_import/README.md":"# asuswrt_import Documentation\n\nPath: `server/plugins/asuswrt_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/asuswrt_import/README.md)","server/plugins/arp_scan/config.json":"# Arp_scan Skill\n\nPath: `server/plugins/arp_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/arp_scan/config.json)","server/plugins/arp_scan/README.md":"# arp_scan Documentation\n\nPath: `server/plugins/arp_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/arp_scan/README.md)","server/plugins/adguard_import/config.json":"# Adguard_import Skill\n\nPath: `server/plugins/adguard_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_import/config.json)","server/plugins/adguard_import/README.md":"# adguard_import Documentation\n\nPath: `server/plugins/adguard_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_import/README.md)","server/plugins/adguard_export/config.json":"# Adguard_export Skill\n\nPath: `server/plugins/adguard_export/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_export/config.json)","server/plugins/adguard_export/README.md":"# adguard_export Documentation\n\nPath: `server/plugins/adguard_export/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_export/README.md)","server/plugins/_publisher_webhook/config.json":"# _publisher_webhook Skill\n\nPath: `server/plugins/_publisher_webhook/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_webhook/config.json)","server/plugins/_publisher_webhook/README.md":"# _publisher_webhook Documentation\n\nPath: `server/plugins/_publisher_webhook/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_webhook/README.md)","server/plugins/_publisher_telegram/config.json":"# _publisher_telegram Skill\n\nPath: `server/plugins/_publisher_telegram/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_telegram/config.json)","server/plugins/_publisher_telegram/README.md":"# _publisher_telegram Documentation\n\nPath: `server/plugins/_publisher_telegram/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_telegram/README.md)","server/plugins/_publisher_pushsafer/config.json":"# _publisher_pushsafer Skill\n\nPath: `server/plugins/_publisher_pushsafer/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushsafer/config.json)","server/plugins/_publisher_pushsafer/README.md":"# _publisher_pushsafer Documentation\n\nPath: `server/plugins/_publisher_pushsafer/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushsafer/README.md)","server/plugins/_publisher_pushover/config.json":"# _publisher_pushover Skill\n\nPath: `server/plugins/_publisher_pushover/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushover/config.json)","server/plugins/_publisher_pushover/README.md":"# _publisher_pushover Documentation\n\nPath: `server/plugins/_publisher_pushover/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushover/README.md)","server/plugins/_publisher_ntfy/config.json":"# _publisher_ntfy Skill\n\nPath: `server/plugins/_publisher_ntfy/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_ntfy/config.json)","server/plugins/_publisher_ntfy/README.md":"# _publisher_ntfy Documentation\n\nPath: `server/plugins/_publisher_ntfy/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_ntfy/README.md)","server/plugins/_publisher_mqtt/config.json":"# _publisher_mqtt Skill\n\nPath: `server/plugins/_publisher_mqtt/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_mqtt/config.json)","server/plugins/_publisher_mqtt/README.md":"# _publisher_mqtt Documentation\n\nPath: `server/plugins/_publisher_mqtt/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_mqtt/README.md)","server/plugins/_publisher_email/config.json":"# _publisher_email Skill\n\nPath: `server/plugins/_publisher_email/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_email/config.json)","server/plugins/_publisher_email/README.md":"# _publisher_email Documentation\n\nPath: `server/plugins/_publisher_email/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_email/README.md)","server/plugins/_publisher_apprise/config.json":"# _publisher_apprise Skill\n\nPath: `server/plugins/_publisher_apprise/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_apprise/config.json)","server/plugins/_publisher_apprise/README.md":"# _publisher_apprise Documentation\n\nPath: `server/plugins/_publisher_apprise/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_apprise/README.md)","server/plugins/__template/config.json":"# __template Skill\n\nPath: `server/plugins/__template/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/__template/config.json)","server/plugins/__template/README.md":"# __template Documentation\n\nPath: `server/plugins/__template/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/__template/README.md)","server/plugins/rest_import/config.json":"# Rest_import Skill\n\nPath: `server/plugins/rest_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/rest_import/config.json)","server/plugins/rest_import/README.md":"# rest_import Documentation\n\nPath: `server/plugins/rest_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/rest_import/README.md)","server/plugins/set_password/config.json":"# Set_password Skill\n\nPath: `server/plugins/set_password/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/set_password/config.json)","server/plugins/set_password/README.md":"# set_password Documentation\n\nPath: `server/plugins/set_password/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/set_password/README.md)","server/plugins/snmp_discovery/config.json":"# Snmp_discovery Skill\n\nPath: `server/plugins/snmp_discovery/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/snmp_discovery/config.json)","server/plugins/snmp_discovery/README.md":"# snmp_discovery Documentation\n\nPath: `server/plugins/snmp_discovery/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/snmp_discovery/README.md)","server/plugins/sync/config.json":"# Sync Skill\n\nPath: `server/plugins/sync/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/sync/config.json)","server/plugins/sync/README.md":"# sync Documentation\n\nPath: `server/plugins/sync/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/sync/README.md)","server/plugins/ui_settings/config.json":"# Ui_settings Skill\n\nPath: `server/plugins/ui_settings/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ui_settings/config.json)","server/plugins/ui_settings/README.md":"# ui_settings Documentation\n\nPath: `server/plugins/ui_settings/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ui_settings/README.md)","server/plugins/unifi_api_import/config.json":"# Unifi_api_import Skill\n\nPath: `server/plugins/unifi_api_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_api_import/config.json)","server/plugins/unifi_api_import/README.md":"# unifi_api_import Documentation\n\nPath: `server/plugins/unifi_api_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_api_import/README.md)","server/plugins/unifi_import/config.json":"# Unifi_import Skill\n\nPath: `server/plugins/unifi_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_import/config.json)","server/plugins/unifi_import/README.md":"# unifi_import Documentation\n\nPath: `server/plugins/unifi_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_import/README.md)","server/plugins/vendor_update/config.json":"# Vendor_update Skill\n\nPath: `server/plugins/vendor_update/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/vendor_update/config.json)","server/plugins/vendor_update/README.md":"# vendor_update Documentation\n\nPath: `server/plugins/vendor_update/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/vendor_update/README.md)","server/plugins/wake_on_lan/config.json":"# Wake_on_lan Skill\n\nPath: `server/plugins/wake_on_lan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/wake_on_lan/config.json)","server/plugins/wake_on_lan/README.md":"# wake_on_lan Documentation\n\nPath: `server/plugins/wake_on_lan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/wake_on_lan/README.md)","server/plugins/website_monitor/config.json":"# Website_monitor Skill\n\nPath: `server/plugins/website_monitor/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/website_monitor/config.json)","server/plugins/website_monitor/README.md":"# website_monitor Documentation\n\nPath: `server/plugins/website_monitor/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/website_monitor/README.md)","server/plugins/workflows/config.json":"# Workflows Skill\n\nPath: `server/plugins/workflows/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/workflows/config.json)","server/plugins/workflows/README.md":"# workflows Documentation\n\nPath: `server/plugins/workflows/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/workflows/README.md)"},"items":[{"name":"llms.txt","path":"front/llms.txt","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/front/llms.txt","title":"front - LLM Index & Context Digest","category":"root-instruction","format":"text","content":"# front - LLM Index & Context Digest\n\nPath: `front/llms.txt`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/front/llms.txt)","tokens":300,"sizeBytes":165},{"name":"API.md","path":"docs/API.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/docs/API.md","title":"docs - API Interface Contract & Specifications","category":"project-spec","format":"markdown","content":"# docs - API Interface Contract & Specifications\n\nPath: `docs/API.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/docs/API.md)","tokens":300,"sizeBytes":171},{"name":"SKILL.md","path":".gemini/skills/devcontainer-management/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.gemini/skills/devcontainer-management/SKILL.md","title":"devcontainer-management","category":"anthropic-skill","format":"markdown","content":"---\nname: devcontainer-management\ndescription: Guide for identifying, managing, and running commands within the NetAlertX development container. Use this when asked to run commands, testing, setup scripts, or troubleshoot container issues.\n---\n\n# Devcontainer Management\n\nWhen starting a session or performing tasks requiring the runtime environment, you must identify and use the active development container.\n\n## Finding the Container\n\nRun `docker ps` to list running containers. Look for an image name containing `vsc-netalertx` or similar.\n\n```bash\ndocker ps --format \"table {{.ID}}\\t{{.Image}}\\t{{.Status}}\\t{{.Names}}\" | grep netalertx\n```\n\n- **If no container is found:** Inform the user. You cannot run integration tests or backend logic without it.\n- **If multiple containers are found:** Ask the user to clarify which one to use (e.g., provide the Container ID).\n\n## Running Commands in the Container\n\nPrefix commands with `docker exec <CONTAINER_ID>` to run them inside the environment. Use the scripts in `/services/` to control backend and other processes.\n\n```bash\ndocker exec <CONTAINER_ID> bash /workspaces/NetAlertX/.devcontainer/scripts/setup.sh\n```\n\n*Note: This script wipes `/tmp` ramdisks, ensures `/data`, `/data/config`, `/data/db` exist, and restarts services (python server, cron, php-fpm, nginx) by symlinking and running `/entrypoint.sh` (`install/production-filesystem/entrypoint.d/`). By default it does **not** delete existing database or config content - `entrypoint.d/25-first-run-db.sh` and `20-first-run-config.sh` only wipe them when `ALWAYS_FRESH_INSTALL=true` is set in the environment (default: unset/`false`, so content is preserved).*\n\n```\n","frontmatter":{"name":"devcontainer-management","description":"Guide for identifying, managing, and running commands within the NetAlertX development container. Use this when asked to run commands, testing, setup scripts, or troubleshoot container issues."},"isInternal":false,"tokens":391,"sizeBytes":1680},{"name":"reasearch-skill.md","path":".gemini/skills/initiative-start/reasearch-skill.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.gemini/skills/initiative-start/reasearch-skill.md","title":"Initiative-start Skill","category":"anthropic-skill","format":"markdown","content":"### 🧠 NAX Research Skill — Specification\n\nThis defines a **Research Skill module for NAX (NetAlertX)** focused on safe, structured analysis before any implementation work.\n\n---\n\n## 1. Purpose\n\nEnsure all work begins with **documentation-first understanding**, **PRD validation**, and **conflict detection**, before any planning or coding.\n\n---\n\n## 2. Core Workflow\n\n### Step 1 — Documentation First\n\n* Always begin by reading relevant repository documentation.\n\n* Priority order:\n\n  1. `/CONTRIBUTING.md`\n  2. `/README.md`\n  3. `/.github/skills/code-standards/SKILL.md`\n  4. `/.github/skills/database-patterns/SKILL.md` — read when the feature touches the `Devices` table or requires audit/history logging\n  5. `/docs/**`\n  6. Related module/code context if referenced\n\n* Extract:\n\n  * Architecture expectations\n  * Coding standards\n  * Plugin or module conventions\n  * Existing workflows or constraints\n\n---\n\n### Step 2 — PRD Check\n\n* If a PRD (Product Requirements Document) is NOT provided:\n\n  * Explicitly request it before proceeding further\n  * Do not assume requirements\n\n* If PRD is provided:\n\n  * Parse and restate key requirements internally\n  * Identify scope boundaries\n\n---\n\n### Step 3 — Clarification Gate\n\nIf anything is unclear:\n\n* Stop immediately\n* Ask targeted clarifying questions\n* Do NOT propose solutions yet\n\n---\n\n### Step 4 — Codebase Cross-Check\n\n* Compare PRD + documentation against existing codebase\n\n* Identify:\n\n  * Conflicting behavior\n  * Outdated patterns\n  * Duplicate logic\n  * Breaking assumptions\n  * Plugin or API mismatches\n\n* **If the feature writes to the `Devices` table:**\n\n  * Load `/.github/skills/database-patterns/SKILL.md`\n  * Audit all write paths listed there — confirm which are affected\n  * Determine if a SQLite trigger or Python hook is the right approach\n  * Check if `*Source` fields already solve attribution requirements\n\n* Clearly report inconsistencies before proceeding\n\n---\n\n### Step 5 — Planning Requirement (Strict)\n\nBefore any implementation:\n\n* Produce a structured plan including:\n\n  * Approach overview\n  * Files/modules affected\n  * Dependencies\n  * Risk areas\n  * Migration considerations (if any)\n\n* Explicitly label:\n\n  > “WAITING FOR USER CONFIRMATION”\n\n---\n\n### Step 6 — Implementation Gate (Hard Rule)\n\n* Do NOT start implementation until user explicitly confirms the plan\n* No partial coding, no early patches, no assumptions\n\n---\n\n## 3. Behavioral Constraints\n\n* Always prioritize correctness over speed\n* Never skip PRD validation\n* Never proceed past ambiguity\n* Never implement without approval\n* Always surface contradictions in source material\n* Always prefer asking questions over guessing\n\n---\n\n## 4. Output Style Rules\n\n* Be structured and technical\n* Avoid unnecessary verbosity\n* Separate:\n\n  * Findings\n  * Risks\n  * Questions\n  * Plan\n* No hidden assumptions\n\n---\n\n## 5. Summary Flow\n\n```\nDocs → PRD → Clarify → Codebase Check → Plan → User Approval → Implement\n```\n\n","isInternal":false,"tokens":679,"sizeBytes":2994},{"name":"SKILL.md","path":".gemini/skills/mcp-activation/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.gemini/skills/mcp-activation/SKILL.md","title":"mcp-activation","category":"anthropic-skill","format":"markdown","content":"---\nname: mcp-activation\ndescription: Enables live interaction with the NetAlertX runtime. This skill configures the Model Context Protocol (MCP) connection, granting full API access for debugging, troubleshooting, and real-time operations including database queries, network scans, and device management.\n---\n\n# MCP Activation Skill\n\nThis skill configures the NetAlertX development environment to expose the Model Context Protocol (MCP) server to AI agents.\n\n## Why use this?\n\nBy default, agents only have access to the static codebase (files). To perform dynamic actions—such as:\n- **Querying the database** (e.g., getting device lists, events)\n- **Triggering actions** (e.g., network scans, Wake-on-LAN)\n- **Validating runtime state** (e.g., checking if a fix actually works)\n\n...you need access to the **MCP Server** running inside the container. This skill sets up the necessary authentication tokens and connection configs to bridge your agent to that live server.\n\n## Prerequisites\n\n1.  **Devcontainer:** You must be connected to the NetAlertX devcontainer.\n2.  **Server Running:** The backend server must be running (to generate `app.conf` with the API token).\n\n## Activation Steps\n\n1.  **Activate Devcontainer Skill:**\n    If you are not already inside the container, activate the management skill:\n    ```text\n    activate_skill(\"devcontainer-management\")\n    ```\n\n2.  **Generate Configurations:**\n    Run the configuration generation script *inside* the container. This script extracts the API Token and creates the necessary settings files (`.gemini/settings.json` and `.vscode/mcp.json`).\n\n    ```bash\n    # Run inside the container\n    /workspaces/NetAlertX/.devcontainer/scripts/generate-configs.sh\n    ```\n\n3.  **Apply Changes:**\n\n    *   **For Gemini CLI:**\n        The agent session must be **restarted** to load the new `.gemini/settings.json`.\n        > \"I have generated the MCP configuration. Please **restart this session** to activate the `netalertx-devcontainer` tools.\"\n\n    *   **For VS Code (GitHub Copilot / Cline):**\n        The VS Code window must be **reloaded** to pick up the new `.vscode/mcp.json`.\n        > \"I have generated the MCP configuration. Please run **'Developer: Reload Window'** in VS Code to activate the MCP server.\"\n\n## Verification\n\nAfter restarting, you should see new tools available (e.g., `netalertx-devcontainer__get_devices`).\n","frontmatter":{"name":"mcp-activation","description":"Enables live interaction with the NetAlertX runtime. This skill configures the Model Context Protocol (MCP) connection, granting full API access for debugging, troubleshooting, and real-time operations including database queries, network scans, and device management."},"isInternal":false,"tokens":539,"sizeBytes":2387},{"name":"plugin-skill.md","path":".gemini/skills/plugin-development/plugin-skill.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.gemini/skills/plugin-development/plugin-skill.md","title":"netalertx-plugin-development","category":"anthropic-skill","format":"markdown","content":"---\nname: netalertx-plugin-development\ndescription: Create and run NetAlertX plugins. Use this when asked to create plugin, run plugin, test plugin, or develop plugin functionality.\n---\n\n# Plugin Development\n\n## Expected Workflow\n\n1. Read this skill and `docs/PLUGINS_DEV.md` for full context.\n2. Find or create the plugin in `server/plugins/<code_name>/`.\n3. Read the plugin's `config.json` and `script.py` to understand its functionality.\n4. Run: `python3 server/plugins/<code_name>/script.py`\n5. Retrieve the result from `/tmp/log/plugins/last_result.<PREF>.log` quickly — the backend deletes it after processing.\n\n## Run a Plugin Manually\n\n```bash\npython3 server/plugins/<code_name>/script.py\n```\n\nEnsure `sys.path` includes `/app/server/plugins` and `/app/server` (as in the template).\n\n## Plugin Structure\n\n```text\nserver/plugins/<code_name>/\n├── config.json      # Manifest with settings\n├── script.py        # Main script\n└── ...\n```\n\n## Plugin Prefix Rules\n\n- Uppercase letters only (no underscores, no numbers)\n- Must be unique across all plugins\n- Short but readable (e.g., `ARPSCAN`, `MQTT`, `UNIFI`)\n\n## Settings Pattern\n\n- `<PREF>_RUN`: execution phase\n- `<PREF>_RUN_SCHD`: cron-like schedule\n- `<PREF>_CMD`: script path\n- `<PREF>_RUN_TIMEOUT`: timeout in seconds — **this is enforced by the core plugin runner as the whole script's kill-timeout** (`server/plugin.py` passes it straight to `subprocess(..., timeout=...)`). It is not a safe per-HTTP-call timeout — don't reuse it for individual network calls in a loop, or one slow call can burn the whole budget and get the process killed before it writes its result file.\n- `<PREF>_WATCH`: columns to watch for changes\n- `<PREF>_IMPORT_ON`: optional — gates whether this run's rows get promoted into `CurrentScan` (only relevant if `mapped_to_table: \"CurrentScan\"`). See `docs/PLUGINS_IMPORT_BEHAVIOR.md` for the related per-row `scanCreatesDevice`/`scanNotificationMode`/`scanPresence` columns.\n- **`dataType` and `default_value` must agree.** `dataType: \"array\"`/`\"object\"` needs a real JSON literal for `default_value` (`'[\"default\"]'`), not a bare string (`\"default\"`). `setting_value_to_python_type()` (`server/helper.py`) `json.loads()`s the default at runtime; a bare string fails silently — logged, and `[]` is returned instead of your default (e.g. `devParentRelType`, `UI_theme`, `UI_TOPOLOGY_ORDER`). If `elementOptions` already sets `multiple`/`orderable: \"false\"`, the setting is scalar — use `dataType: \"string\"` instead.\n\n## Data Contract\n\nScripts write to `/tmp/log/plugins/last_result.<PREF>.log` using `plugin_helper.py`:\n\n```python\nfrom plugin_helper import Plugin_Objects\n\nplugin_objects = Plugin_Objects()\nplugin_objects.add_object(...)  # During processing\nplugin_objects.write_result_file()  # Exactly once at end\n```\n\n**Important:** The backend processes and deletes the result file almost immediately. Retrieve it quickly if inspecting output.\n\nEvery mapped field (`objectPrimaryId`/`objectSecondaryId`/`watchedValue1-4`/`extra`/`helpVal1-4`) is HTML/control-char-stripped by default before it's persisted: plugin output is untrusted (network responses, device-reported names, etc.). `foreignKey` is always sanitized too, unconditionally. Only opt a column out (`\"allow_raw_text\": true`) if it's a display-only type (`textarea_readonly`); see `docs/PLUGINS_DEV.md#field-sanitization`.\n\n## Execution Phases\n\n| Phase | Trigger |\n|-------|---------|\n| `once` | Once at startup |\n| `schedule` | On cron schedule |\n| `always_after_scan` | After every scan |\n| `before_name_updates` | Before name resolution |\n| `on_new_device` | When new device detected |\n| `on_notification` | When notification triggered |\n\n## `all_plugins` Caching\n\n`all_plugins` is loaded once (`initialise.py`'s `get_plugins_configs()`) and reused as the same object until a reload - but `update_api()` (`server/api.py`), which takes it as a parameter, runs on every scan cycle and plugin completion, not just on reload.\n\n- If you write a function that derives something from `all_plugins` and gets called from `update_api()` or another frequent call site, cache the result instead of recomputing every call - retain a reference to the last-seen `all_plugins` list and compare it to the current one with `is`, not by `id(all_plugins)` alone (once the old list is garbage collected, Python can reuse that id for an unrelated object, wrongly serving a stale cached result for what is actually a new reload)\n- Pattern: `server/db/plugin_field_views.py`'s `get_all_device_field_views()`\n\n## Plugin Formats\n\n| Format | Purpose | Phase |\n|--------|---------|-------|\n| publisher | Send notifications | `on_notification` |\n| dev scanner | Create/manage devices | `schedule` |\n| name discovery | Discover device names | `before_name_updates` |\n| importer | Import from services | `schedule` |\n| system | Core functionality | `schedule` |\n\n## Before Opening a PR\n\nCheck the plugin against the [Conventions Checklist](../../../docs/PLUGINS_DEV.md#conventions-checklist) in `docs/PLUGINS_DEV.md` — `RUN` default, schedule precedent, `RUN_TIMEOUT` semantics, reusing core settings, description length, the multi-instance settings pattern, `allow_raw_text` restricted to display-only column types, and `scanSourcePlugin`'s static value matching `unique_prefix` exactly. Most plugin PR review comments trace back to one of these.\n\nIf the plugin needs a new system package or Python dependency, mirroring it into the root `Dockerfile`/`requirements.txt` alone is not enough: see the Conventions Checklist's build-target-mirroring bullet for `.devcontainer/Dockerfile` (regenerate via `.devcontainer/scripts/generate-configs.sh`, don't hand-edit it), `Dockerfile.debian`, and `install/ubuntu24`/`install/proxmox`'s own `requirements.txt` files.\n\n## Starting Point\n\nCopy from `server/plugins/__template` and customize. Read `docs/PLUGINS_DEV.md` for the full development guide.\n\n\n","frontmatter":{"name":"netalertx-plugin-development","description":"Create and run NetAlertX plugins. Use this when asked to create plugin, run plugin, test plugin, or develop plugin functionality."},"isInternal":false,"tokens":1435,"sizeBytes":5951},{"name":"SKILL.md","path":".gemini/skills/project-navigation/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.gemini/skills/project-navigation/SKILL.md","title":"project-navigation","category":"anthropic-skill","format":"markdown","content":"---\nname: project-navigation\ndescription: Reference for the NetAlertX codebase structure, key file paths, and configuration locations. Use this when exploring the codebase or looking for specific components like the backend entry point, frontend files, or database location.\n---\n\n# Project Navigation\n\n## Key Paths\n\n| Component | Path |\n|-----------|------|\n| Workspace root | `/workspaces/NetAlertX` |\n| Backend entry | `server/__main__.py` |\n| API server | `server/api_server/api_server_start.py` |\n| Plugin system | `server/plugin.py` |\n| Initialization | `server/initialise.py` |\n| Frontend | `front/` |\n| Frontend JS | `front/js/common.js` |\n| Frontend PHP | `front/php/server/*.php` |\n| Plugins | `server/plugins/` |\n| Plugin template | `server/plugins/__template` |\n| Database helpers | `server/db/db_helper.py` |\n| Device model | `server/models/device_instance.py` |\n| Messaging | `server/messaging/` |\n| Workflows | `server/workflows/` |\n\n## Architecture\n\nNetAlertX uses a frontend–backend architecture: the frontend runs on **PHP + Nginx** (see `front/`), the backend is implemented in **Python** (see `server/`), and scheduled tasks are managed by a **supercronic** scheduler.\n\n## Runtime Paths\n\n| Data | Path |\n|------|------|\n| Config (runtime) | `/data/config/app.conf` |\n| Config (default) | `back/app.conf` |\n| Database | `/data/db/app.db` |\n| API JSON cache | `/tmp/api/*.json` |\n| Logs | `/tmp/log/` |\n| Plugin logs | `/tmp/log/plugins/` |\n\n## Environment Variables\n\nUse these instead of hardcoding paths:\n- `NETALERTX_DB`\n- `NETALERTX_LOG`\n- `NETALERTX_CONFIG`\n- `NETALERTX_DATA`\n- `NETALERTX_APP`\n\n## Documentation\n\n| Topic | Path |\n|-------|------|\n| Plugin development | `docs/PLUGINS_DEV.md` |\n| System settings | `docs/SETTINGS_SYSTEM.md` |\n| API docs | `docs/API_*.md` |\n| Debug guides | `docs/DEBUG_*.md` |\n","frontmatter":{"name":"project-navigation","description":"Reference for the NetAlertX codebase structure, key file paths, and configuration locations. Use this when exploring the codebase or looking for specific components like the backend entry point, frontend files, or database location."},"isInternal":false,"tokens":485,"sizeBytes":1835},{"name":"SKILL.md","path":".gemini/skills/settings/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.gemini/skills/settings/SKILL.md","title":"nax-settings-development","category":"anthropic-skill","format":"markdown","content":"---\nname: nax-settings-development\ndescription: Best practices for creating and maintaining NetAlertX settings (config.json), including UI, validation, localization, runtime behavior, and implementation consistency.\n---\n\n# NetAlertX Settings Development\n\nThis skill defines the conventions and best practices for adding or modifying settings in NetAlertX.\n\nThe goal is for every setting to be:\n\n- Declarative\n- Self-documenting\n- Automatically rendered by the Settings UI\n- Fully localized\n- Backwards compatible\n- Easy to maintain\n\n---\n\n## Quick Reference\n\n**Read a setting (backend):**\n```python\nfrom helper import get_setting_value\nvalue = get_setting_value('SETTING_NAME')\n```\nNever read `app.conf` directly. Always use `get_setting_value()`.\n\n**Read a setting (frontend):**\n```javascript\ngetSetting(\"SETTING_NAME\")\n```\n\n**Add a core setting** — use `ccd()` in `server/initialise.py`:\n```python\nccd('SETTING_NAME', 'default_value', 'description')\n```\n\n**Add a plugin setting** — define in the plugin's `config.json` under the `settings` key.\n\n| File | Purpose |\n|------|---------|\n| `/data/config/app.conf` | Runtime config (source of truth) |\n| `back/app.conf` | Default config (template) |\n\nUse `APP_CONF_OVERRIDE` for settings that must be set before startup.\n\n---\n\n# 1. Follow existing patterns\n\nBefore creating a new setting:\n\n- Search for similar settings.\n- Reuse existing naming conventions.\n- Reuse existing setting types.\n- Reuse existing validation patterns.\n- Follow the style already used by similar plugins.\n\nAvoid introducing new setting types unless absolutely necessary.\n\n---\n\n# 2. Setting naming\n\nSetting keys use uppercase snake case.\n\nGood:\n\n```text\nUI_DEV_SECTIONS\nSCAN_INTERVAL\nMQTT_HOST\n```\n\nAvoid:\n\n```text\nuiDevSections\nscanInterval\nScanInterval\n```\n\nNames should clearly describe the purpose.\n\n---\n\n# 3. Categories\n\nPlace settings into the most appropriate category.\n\nKeep related settings together.\n\nAvoid creating new categories unless there is a clear need.\n\n---\n\n# 4. Choose the correct type\n\nUse the simplest type that correctly models the value.\n\nCommon types include:\n\n- string\n- integer\n- float\n- boolean\n- password\n- select\n- array\n- textarea\n\nAvoid encoding structured JSON inside string settings.\n\n---\n\n# 5. Validation\n\nAlways define validation whenever appropriate.\n\nExamples include:\n\n- minimum values\n- maximum values\n- regex validation\n- allowed options\n\nReject invalid configuration rather than silently accepting it.\n\n---\n\n# 6. Defaults\n\nProvide sensible defaults that work for a fresh installation.\n\nAvoid defaults that require external services or additional configuration.\n\nA default value should exist in exactly one place.\n\nUse the `default_value` defined in `config.json`.\n\nDo not duplicate the same default value elsewhere in Python or JavaScript.\n\n---\n\n# 7. Names and descriptions\n\nSetting names should be concise.\n\nExamples:\n\n- Scan interval\n- MQTT Host\n- Hide device sections\n\nDescriptions should explain:\n\n- what the setting does\n- when it should be used\n- important side effects\n\nAvoid implementation details.\n\nGood:\n\n> Interval between network scans in seconds.\n\nBad:\n\n> Calls scheduler.py every X seconds.\n\n---\n\n# 8. Boolean settings\n\nBoolean settings should read naturally.\n\nPrefer verbs such as:\n\n- Enable\n- Disable\n- Require\n- Allow\n- Ignore\n- Show\n- Hide\n\nExamples:\n\n- Enable MQTT\n- Require NICs Online\n- Hide Offline Devices\n\n---\n\n# 9. Select settings\n\nWhen users must choose from predefined values, use a select setting.\n\nNever require users to remember internal values.\n\nEach option should have a meaningful label.\n\n---\n\n# 10. Array settings\n\nUse arrays only when multiple independent values are expected.\n\nExamples:\n\n- ignored MAC addresses\n- subnet lists\n- plugin lists\n\n---\n\n# 11. Localization\n\nEvery setting must have localized language strings.\n\nIf the language strings are **not** defined directly inside `config.json`, they must exist in `en_us.json`.\n\nFor example, for:\n\n```text\nUI_DEV_SECTIONS\n```\n\nadd:\n\n```json\n\"UI_DEV_SECTIONS_name\": \"Hide device sections\",\n\"UI_DEV_SECTIONS_description\": \"Select which UI elements to hide on the Devices page.\"\n```\n\nWhen using external language files, `config.json` must reference:\n\n```json\n\"name\": [\n  {\n    \"string\": \"_GLOBAL_LANG_FILES_\"\n  }\n]\n```\n\nThis tells NetAlertX to resolve the display name from the language files.\n\n---\n\n# 12. Source of truth\n\n`app.conf` is the source of truth.\n\nKeep the following in mind:\n\n- User-defined values always come from `app.conf`.\n- Default values from `config.json` are only used when the setting is missing from `app.conf`.\n- Never assume settings are permanently stored in the database.\n\n---\n\n# 13. Plugin-first settings\n\nWhenever possible, define new settings inside a plugin's `config.json`.\n\nAvoid adding hardcoded application settings unless there is a compelling architectural reason.\n\nPlugin settings are the preferred and future-proof approach.\n\n---\n\n# 14. Runtime lifecycle\n\nUnderstand how settings flow through the application.\n\n```text\nconfig.json\n        │\n        │ default values\n        ▼\napp.conf (source of truth)\n        │\n        ▼\nSettings database table\n        │\n        ▼\ntable_settings.json API\n        │\n        ▼\nFrontend (getSetting())\n```\n\nThe database and API are runtime representations only.\n\nThey are regenerated from `app.conf` during initialization.\n\n---\n\n# 15. Access\n\nCode should never read `app.conf` directly.\n\nIn the Frontend, always retrieve settings through:\n\n```javascript\ngetSetting(\"SETTING_NAME\")\n```\n\nAnd in the backend, always retrieve settings through:\n\n```python\nget_setting_value(\"SETTING_NAME\")\n```\n\nThis guarantees the value comes from the generated settings API.\n\n---\n\n# 16. Metadata\n\nEach setting automatically has a corresponding `__metadata` entry generated in `app.conf`.\n\nDo not manually create or modify metadata entries unless working on the settings framework itself.\n\n---\n\n# 17. Backend\n\nBackend code should:\n\n- use the existing configuration helpers\n- avoid duplicated parsing logic\n- gracefully handle missing values\n- avoid hardcoded defaults\n\n---\n\n# 18. Frontend\n\nA correctly defined setting should render automatically in the existing Settings UI.\n\nAvoid writing custom JavaScript unless absolutely necessary.\n\n---\n\n# 19. Prefer configuration over code\n\nIf a behaviour can reasonably be controlled through an existing setting type (boolean, select, integer, array, etc.), introduce a setting instead of hardcoding special-case logic.\n\nConfiguration is preferred over implementation-specific behaviour whenever practical.\n\n---\n\n# 20. Backwards compatibility\n\nAvoid:\n\n- renaming settings\n- changing data types\n- changing semantics\n\nIf unavoidable:\n\n- provide migration logic\n- preserve compatibility where possible\n\n---\n\n# 21. Documentation\n\nEvery user-facing setting should eventually be documented.\n\nInclude:\n\n- purpose\n- accepted values\n- default value\n- examples when useful\n\n---\n\n# 22. Pull request checklist\n\nBefore submitting a PR, verify:\n\n- [ ] Setting follows existing conventions.\n- [ ] Correct category used.\n- [ ] Appropriate type selected.\n- [ ] Validation defined.\n- [ ] Sensible default provided.\n- [ ] Default defined only once.\n- [ ] Name is concise.\n- [ ] Description is clear.\n- [ ] Localization strings added.\n- [ ] `_GLOBAL_LANG_FILES_` used where appropriate.\n- [ ] Plugin-first approach followed where applicable.\n- [ ] Frontend renders automatically.\n- [ ] Backend uses existing configuration helpers.\n- [ ] No unnecessary frontend code added.\n- [ ] No duplicated parsing logic.\n- [ ] Backwards compatibility maintained.\n- [ ] Documentation updated if required.\n\n---\n\n# References\n\n- https://docs.netalertx.com/PLUGINS_DEV_SETTINGS/\n- https://docs.netalertx.com/SETTINGS_SYSTEM/\n- https://docs.netalertx.com/PLUGINS_OVERVIEW/","frontmatter":{"name":"nax-settings-development","description":"Best practices for creating and maintaining NetAlertX settings (config.json), including UI, validation, localization, runtime behavior, and implementation consistency."},"isInternal":false,"tokens":1658,"sizeBytes":7793},{"name":"SKILL.md","path":".gemini/skills/skills-index/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.gemini/skills/skills-index/SKILL.md","title":"skills-index","category":"anthropic-skill","format":"markdown","content":"---\nname: skills-index\ndescription: Index of all available skills across Gemini CLI (.gemini/skills/), GitHub Copilot (.github/skills/), and Claude Code (.claude/skills/). Load this to find the right skill for a task, or to locate the counterpart skill in another assistant's tree.\n---\n\n# Skills Index — Cross-Reference\n\nThree AI assistants are configured for this project, each with their own skill directory:\n\n- **Gemini CLI** → `.gemini/skills/`\n- **GitHub Copilot** → `.github/skills/`\n- **Claude Code** → `.claude/skills/` (mirrors most, not all, of the shared skills below — see the table's Claude Skill column for which)\n\nSkills with the same purpose exist in more than one, sometimes under different names and with different depth. This index maps them so you can find the richer version when needed. A CI check (`scripts/check_skill_pairs.py`, run as `check-skill-pairs` in `.github/workflows/code-checks.yml`) flags PRs that touch some but not all files in a mirrored group - non-blocking, since some divergence is intentional.\n\n---\n\n## Shared Skills (exist in more than one tree)\n\n| Topic | Gemini Skill | Copilot Skill | Claude Skill | Notes |\n|-------|-------------|--------------|--------------|-------|\n| Testing | `testing-workflow` | `testing-workflow` | `testing-workflow` | All three cover the full-suite-by-default rule, PYTHONPATH, auth/token retrieval, and the `sys.modules` stubbing pitfall |\n| Settings & config | `settings` | `settings-management` | — | Gemini version is more comprehensive (22-point guide + PR checklist); Copilot version covers `ccd()` and `get_setting_value()` usage |\n| MCP activation | `mcp-activation` | `mcp-activation` | — | Gemini version covers Gemini CLI session restart; Copilot version covers VS Code window reload |\n| Project navigation | `project-navigation` | `project-navigation` | — | Copilot version has full path tables and env vars; Gemini version is a brief reference |\n| Plugin dev | `plugin-development` | `plugin-run-development` | `plugin-development` | All three cover data contract, phases, formats, the `RUN_TIMEOUT` kill-timer gotcha (`timeoutMultiplier`/`per_item_timeout()`), and a pre-PR pointer to the Conventions Checklist in `docs/PLUGINS_DEV.md` |\n| Plugin README docs | `plugin-readme` | `plugin-readme` | `plugin-readme` | All three cover README structure, the \"don't re-document settings\" rule, the `docs.netalertx.com` cross-linking convention, and common defects (template leftovers, copy-paste errors) found during a full-repo audit |\n| Devcontainer | `devcontainer-management` | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | — | Gemini combines into one (uses `docker exec`); Copilot splits into 3 focused skills. `devcontainer-configs` also covers Dockerfile generation (root `Dockerfile` → `generate-dockerfile.sh` → `.devcontainer/Dockerfile`, source files not present at build time) - mined from real review comments on PR #1184/#1230. |\n| Install scripts | `install-scripts` | `install-scripts` | `install-scripts` | `install/*` conventions mined from real review comments (#1214/#1230/#1235): uninstall must only remove NetAlertX's own files, never system packages; `check-*.sh` scripts intentionally differ on fail-fast vs. warning-only; `/back` is legacy, not the active code path. |\n| PR review | `pr-analysis` | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist; the standing rule that a repeated review comment becomes a skill update (sourced from jokob's own real PR comments, #1739/#1744); a readability-over-cleverness code-style note from the same source. |\n| Logging | `logging-standards` | `logging-standards` | — | `mylog` levels, message format, what not to log |\n| Scan pipeline internals | `scan-pipeline` | `scan-pipeline` | `scan-pipeline` | `process_scan()` call order and why it's load-bearing, `CurrentScan`/`Events`/`Sessions`/`DevicesView` relationships, how a session actually closes (no `close_session()` exists), and the `FIELD_SPECS` field-write authority mechanism. Complements `database-patterns` (Devices write-path/`*Source` attribution) rather than duplicating it. |\n| Database patterns | `database-patterns` | `database-patterns` | `database-patterns` | Devices table write-path inventory, the `FIELD_SOURCE_MAP`/`*Source` attribution system in `server/db/authoritative_handler.py`, SQLite trigger vs. Python-hook tradeoffs, and event-sourced vs. snapshot audit logging. |\n| PRD writing | `prd-writing` | `prd-writing` | `prd-writing` | Methodology for writing a design doc: challenge the idea, verify every claim against actual code, trace every downstream consumer of a new mechanism, evaluate performance impact against the real schema/indexes, record rejected alternatives and open-issue decisions explicitly, final-check pass before done. |\n| UX/frontend design | `ux-design-patterns` | `ux-design-patterns` | `ux-design-patterns` | Don't invent new UX behavior/visual patterns unless a PRD calls for it - search `front/` for an existing pattern first and reuse it. Priority order for design tradeoffs when several options are reasonable: existing behavior > intuitiveness > information density > usability > utility > uniqueness > industry practices > generic UI. |\n| Skill hygiene | `skill-hygiene` | `skill-hygiene` | `skill-hygiene` | Read before writing/editing any SKILL.md, or any research/audit doc in `.gemini/internal-docs/research/`. Two standing rules: state current behavior only (no \"Correction:\", no \"as of <date>\", no \"caught in review\" narration - that trail belongs in PRDs), and prefer plain, short wording. Includes the grep sweep to run before calling a doc clean. |\n| Plugin review | `plugin-review` | `plugin-review` | `plugin-review` | Reviewer-facing complement to Plugin dev above: no raw SQL in a plugin script - use an existing/new `server/models/*.py` method, not GraphQL (which no plugin reaches). Named exception list for 5 pre-existing core/infra plugins with legitimate direct DB access, a collation-checking step, and a worked example from a real PR (#1788, DOCKERDISC). |\n\n---\n\n## Copilot-Only Skills\n\nNo Gemini equivalent yet:\n\n| Copilot Skill | Purpose |\n|--------------|---------|\n| `api-development` | Creating REST API endpoints |\n| `authentication` | API tokens and 401/403 debugging |\n| `code-standards` | Coding conventions and style rules |\n| `database-reset` | Wipe and regenerate the database and config |\n| `docker-build` | Build Docker images for testing or production |\n| `docker-prune` | Clean unused Docker resources (destructive — requires confirmation) |\n| `sample-data` | Load synthetic device data into the devcontainer |\n\n---\n\n## Gemini-Only Skills\n\nNo Copilot equivalent yet:\n\n| Gemini Skill | Purpose |\n|-------------|---------|\n| `initiative-start` | Research methodology and structured approach for new tasks |\n\n---\n\n## Adding a New Skill\n\nWhen adding a skill, create it in **both** directories to keep both AI systems current:\n\n- `.gemini/skills/<name>/SKILL.md` — auto-discovered by Gemini CLI via YAML frontmatter\n- `.github/skills/<name>/SKILL.md` — add an entry to the skills table in `.github/copilot-instructions.md`\n\nKeep the body content identical between both files. Only the frontmatter `name`/`description` may differ slightly to match each system's discovery heuristics.\n\nIf the skill is high-value enough to also mirror to Claude Code, add `.claude/skills/<name>/SKILL.md` too, and add the group to `GROUPS` in `scripts/check_skill_pairs.py` so drift gets flagged. Claude Code has no `activate_skill()`/`testFailure`/`runTests`/`report_progress` equivalents - adapt any such tool references to plain `Bash` commands instead of copying them verbatim.\n","frontmatter":{"name":"skills-index","description":"Index of all available skills across Gemini CLI (.gemini/skills/), GitHub Copilot (.github/skills/), and Claude Code (.claude/skills/). Load this to find the right skill for a task, or to locate the counterpart skill in another assistant's tree."},"isInternal":false,"tokens":1817,"sizeBytes":7772},{"name":"SKILL.md","path":".gemini/skills/testing-workflow/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.gemini/skills/testing-workflow/SKILL.md","title":"testing-workflow","category":"anthropic-skill","format":"markdown","content":"---\nname: testing-workflow\ndescription: Read before running tests. Detailed instructions for single, standard unit tests (fast), full suites (slow), handling authentication, and obtaining the API Token. Tests must be run when a job is complete.\n---\n\n# Testing Workflow\nAfter code is developed, tests must be run to ensure the integrity of the final result.\n\n**Crucial:** Tests MUST be run inside the container to access the correct runtime environment (DB, Config, Dependencies).\n\n## 0. Pre-requisites: Environment Check\n\nBefore running any tests, verify you are inside the development container:\n\n```bash\nls -d /workspaces/NetAlertX\n```\n\n**IF** this directory does not exist, you are likely on the host machine. You **MUST** immediately activate the `devcontainer-management` skill to enter the container or run commands inside it.\n\n```text\nactivate_skill(\"devcontainer-management\")\n```\n\n## 1. Full Test Suite (MANDATORY DEFAULT)\n\nUnless the user **explicitly** requests \"fast\" or \"quick\" tests, you **MUST** run the full test suite. **Do not** optimize for time. Comprehensive coverage is the priority over speed.\n\n```bash\ncd /workspaces/NetAlertX; pytest test/\n```\n\n## 2. Fast Unit Tests (Conditional)\n\n**ONLY** use this if the user explicitly asks for \"fast tests\", \"quick tests\", or \"unit tests only\". This **excludes** slow tests marked with `docker` or `feature_complete`.\n\n```bash\ncd /workspaces/NetAlertX; pytest test/ -m 'not docker and not feature_complete'\n```\n\n## 3. Running Specific Tests\n\nTo run a specific file or folder:\n\n```bash\ncd /workspaces/NetAlertX; pytest test/<path_to_test>\n```\n\n*Example:*\n```bash\ncd /workspaces/NetAlertX; pytest test/api_endpoints/test_mcp_extended_endpoints.py\n```\n\n## Authentication & Environment Reset\n\nAuthentication tokens are required to perform certain operations such as manual testing or crafting expressions to work with the web APIs. After making code changes, you MUST reset the environment to ensure the new code is running and verify you have the latest `API_TOKEN`.\n\n1. **Reset Environment:** Run the setup script inside the container.\n   ```bash\n   bash /workspaces/NetAlertX/.devcontainer/scripts/setup.sh\n   ```\n2. **Wait for Stabilization:** Wait at least 5 seconds for services (nginx, python server, etc.) to start.\n   ```bash\n   sleep 5\n   ```\n3. **Obtain Token:** Retrieve the current token from the container.\n   ```bash\n   python3 -c \"from helper import get_setting_value; print(get_setting_value('API_TOKEN'))\"\n   ```\n\nThe retrieved token MUST be used in all subsequent API or test calls requiring authentication.\n\n### Troubleshooting\n\nIf tests fail with 403 Forbidden or empty tokens:\n1. Verify server is running and use the setup script (`/workspaces/NetAlertX/.devcontainer/scripts/setup.sh`) if required.\n2. Verify `app.conf` inside the container: `cat /data/config/app.conf`\n\n## Check for Pre-Existing Failures\n\nBefore attributing failures to your changes, check what was already broken:\n\n```bash\ncd /workspaces/NetAlertX; pytest test/ --tb=no -q 2>&1 | tail -20\n```\n\nDo not fix pre-existing failures unless that is the explicit goal.\n\n## PYTHONPATH\n\nThe test environment is pre-configured with:\n- `/app` — primary location where Python runs in production\n- `/app/server` — symlink to `/workspaces/NetAlertX/server`\n- `/app/server/plugins` — symlink to `/workspaces/NetAlertX/server/plugins`\n- `/opt/venv/lib/pythonX.Y/site-packages`\n- `/workspaces/NetAlertX/test`\n- `/workspaces/NetAlertX/server`\n- `/workspaces/NetAlertX`\n- `/usr/lib/pythonX.Y/site-packages`\n\n## Docker Test Image\n\nIf the Dockerfile or dependencies changed, rebuild the test image before running:\n\n```bash\ndocker buildx build -t netalertx-test .\n```\n\nTakes ~30 seconds; ~90 seconds if the venv stage changed.\n\n## Pitfall: `sys.modules` Stubbing Leaks Across Test Files\n\nSome plugin tests (e.g. `test/plugins/test_ntfy_custom_headers.py`) stub NetAlertX\nmodules (`conf`, `helper`, `models.notification_instance`, etc.) via\n`sys.modules[name] = fake_module` so the plugin script can be imported standalone,\noutside the container. Because `sys.modules` is a single process-wide cache shared\nby the whole pytest session, a fake module inserted by one test file silently\nshadows the real module for every other test file collected afterwards — pytest\nimports all test files during collection, before any test runs, so this can happen\nregardless of alphabetical/directory order.\n\nSymptom: `AttributeError: <module 'models.notification_instance'> does not have\nthe attribute 'get_setting_value'` (or similar) in an unrelated test file, where\nthe module repr has no `from '<path>'` suffix — a giveaway that a stub, not the\nreal module, was resolved.\n\nFix pattern: track which module names your stub actually inserted, and pop them\nback out of `sys.modules` immediately after the one-time import that needed them\n(the already-imported script keeps its bound names regardless):\n\n```python\n_stubbed_module_names = []\n\ndef _stub(name, **attrs):\n    if name not in sys.modules:\n        mod = types.ModuleType(name)\n        for k, v in attrs.items():\n            setattr(mod, k, v)\n        sys.modules[name] = mod\n        _stubbed_module_names.append(name)\n\n# ... _stub(...) calls, then the one-time import ...\nimport ntfy\n\nfor _name in _stubbed_module_names:\n    sys.modules.pop(_name, None)\n```","frontmatter":{"name":"testing-workflow","description":"Read before running tests. Detailed instructions for single, standard unit tests (fast), full suites (slow), handling authentication, and obtaining the API Token. Tests must be run when a job is complete."},"isInternal":false,"tokens":1257,"sizeBytes":5337},{"name":"SKILL.md","path":".github/skills/api-development/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/api-development/SKILL.md","title":"api-development","category":"anthropic-skill","format":"markdown","content":"# api-development\n\nPath: `.github/skills/api-development/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/api-development/SKILL.md)","tokens":300,"sizeBytes":196},{"name":"SKILL.md","path":".github/skills/authentication/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/authentication/SKILL.md","title":"authentication","category":"anthropic-skill","format":"markdown","content":"# authentication\n\nPath: `.github/skills/authentication/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/authentication/SKILL.md)","tokens":300,"sizeBytes":193},{"name":"SKILL.md","path":".github/skills/code-standards/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/code-standards/SKILL.md","title":"code-standards","category":"anthropic-skill","format":"markdown","content":"# code-standards\n\nPath: `.github/skills/code-standards/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/code-standards/SKILL.md)","tokens":300,"sizeBytes":193},{"name":"SKILL.md","path":".github/skills/database-patterns/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/database-patterns/SKILL.md","title":"database-patterns","category":"anthropic-skill","format":"markdown","content":"# database-patterns\n\nPath: `.github/skills/database-patterns/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/database-patterns/SKILL.md)","tokens":300,"sizeBytes":202},{"name":"SKILL.md","path":".github/skills/database-reset/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/database-reset/SKILL.md","title":"database-reset","category":"anthropic-skill","format":"markdown","content":"# database-reset\n\nPath: `.github/skills/database-reset/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/database-reset/SKILL.md)","tokens":300,"sizeBytes":193},{"name":"SKILL.md","path":".github/skills/devcontainer-configs/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-configs/SKILL.md","title":"devcontainer-configs","category":"anthropic-skill","format":"markdown","content":"# devcontainer-configs\n\nPath: `.github/skills/devcontainer-configs/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-configs/SKILL.md)","tokens":300,"sizeBytes":211},{"name":"SKILL.md","path":".github/skills/devcontainer-services/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-services/SKILL.md","title":"devcontainer-services","category":"anthropic-skill","format":"markdown","content":"# devcontainer-services\n\nPath: `.github/skills/devcontainer-services/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-services/SKILL.md)","tokens":300,"sizeBytes":214},{"name":"SKILL.md","path":".github/skills/devcontainer-setup/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-setup/SKILL.md","title":"devcontainer-setup","category":"anthropic-skill","format":"markdown","content":"# devcontainer-setup\n\nPath: `.github/skills/devcontainer-setup/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-setup/SKILL.md)","tokens":300,"sizeBytes":205},{"name":"SKILL.md","path":".github/skills/docker-build/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/docker-build/SKILL.md","title":"docker-build","category":"anthropic-skill","format":"markdown","content":"# docker-build\n\nPath: `.github/skills/docker-build/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/docker-build/SKILL.md)","tokens":300,"sizeBytes":187},{"name":"SKILL.md","path":".github/skills/docker-prune/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/docker-prune/SKILL.md","title":"docker-prune","category":"anthropic-skill","format":"markdown","content":"# docker-prune\n\nPath: `.github/skills/docker-prune/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/docker-prune/SKILL.md)","tokens":300,"sizeBytes":187},{"name":"SKILL.md","path":".github/skills/mcp-activation/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/mcp-activation/SKILL.md","title":"mcp-activation","category":"anthropic-skill","format":"markdown","content":"# mcp-activation\n\nPath: `.github/skills/mcp-activation/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/mcp-activation/SKILL.md)","tokens":300,"sizeBytes":193},{"name":"SKILL.md","path":".github/skills/plugin-run-development/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/plugin-run-development/SKILL.md","title":"plugin-run-development","category":"anthropic-skill","format":"markdown","content":"# plugin-run-development\n\nPath: `.github/skills/plugin-run-development/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/plugin-run-development/SKILL.md)","tokens":300,"sizeBytes":217},{"name":"SKILL.md","path":".github/skills/project-navigation/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/project-navigation/SKILL.md","title":"project-navigation","category":"anthropic-skill","format":"markdown","content":"# project-navigation\n\nPath: `.github/skills/project-navigation/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/project-navigation/SKILL.md)","tokens":300,"sizeBytes":205},{"name":"SKILL.md","path":".github/skills/sample-data/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/sample-data/SKILL.md","title":"netalertx-sample-data","category":"anthropic-skill","format":"markdown","content":"---\nname: netalertx-sample-data\ndescription: Load synthetic device data into the devcontainer. Use this when asked to load sample devices, seed data, import test devices, populate database, or generate test data.\n---\n\n# Sample Data Loading\n\nGenerates synthetic device inventory and imports it via the `/devices/import` API endpoint.\n\n## Command\n\n```bash\ncd /workspaces/NetAlertX/.devcontainer/scripts\n./load-devices.sh\n```\n\n## Environment\n\n- `CSV_PATH`: defaults to `/tmp/netalertx-devices.csv`\n\n## Prerequisites\n\n- Backend must be running\n- API must be accessible\n\n## What It Does\n\n1. Generates synthetic device records (MAC addresses, IPs, names, vendors)\n2. Creates CSV file at `$CSV_PATH`\n3. POSTs to `/devices/import` endpoint\n4. Devices appear in database and UI\n","frontmatter":{"name":"netalertx-sample-data","description":"Load synthetic device data into the devcontainer. Use this when asked to load sample devices, seed data, import test devices, populate database, or generate test data."},"isInternal":false,"tokens":180,"sizeBytes":769},{"name":"SKILL.md","path":".github/skills/settings-management/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/settings-management/SKILL.md","title":"netalertx-settings-management","category":"anthropic-skill","format":"markdown","content":"---\nname: netalertx-settings-management\ndescription: Manage NetAlertX configuration settings. Use this when asked to add setting, read config, get_setting_value, ccd, or configure options.\n---\n\n# Settings Management\n\n## Quick Reference\n\n**Read a setting (backend):**\n```python\nfrom helper import get_setting_value\nvalue = get_setting_value('SETTING_NAME')\n```\nNever hardcode ports, secrets, or configuration values. Always use `get_setting_value()`.\n\n**Read a setting (frontend):**\n```javascript\ngetSetting(\"SETTING_NAME\")\n```\n\n**Add a core setting** — use `ccd()` in `server/initialise.py`:\n```python\nccd('SETTING_NAME', 'default_value', 'description')\n```\n\n**Add a plugin setting** — define in the plugin's `config.json` under the `settings` key.\n\n| File | Purpose |\n|------|---------|\n| `/data/config/app.conf` | Runtime config (source of truth, modified by app) |\n| `back/app.conf` | Default config (template) |\n\nUse `APP_CONF_OVERRIDE` for settings that must be set before startup.\n\n---\n\n# Development Guide\n\nThe goal is for every setting to be:\n\n- Declarative and self-documenting\n- Automatically rendered by the Settings UI\n- Fully localized\n- Backwards compatible\n- Easy to maintain\n\n---\n\n## 1. Follow existing patterns\n\nBefore creating a new setting, search for similar ones. Reuse existing naming conventions, types, and validation patterns. Follow the style already used by similar plugins. Avoid introducing new setting types unless absolutely necessary.\n\n---\n\n## 2. Setting naming\n\nSetting keys use uppercase snake case: `UI_DEV_SECTIONS`, `SCAN_INTERVAL`, `MQTT_HOST`.\nNames should clearly describe the purpose.\n\n---\n\n## 3. Categories\n\nPlace settings into the most appropriate category. Keep related settings together. Avoid creating new categories unless there is a clear need.\n\n---\n\n## 4. Choose the correct type\n\nUse the simplest type that correctly models the value: `string`, `integer`, `float`, `boolean`, `password`, `select`, `array`, `textarea`. Avoid encoding structured JSON inside string settings.\n\n---\n\n## 5. Validation\n\nAlways define validation whenever appropriate (min/max values, regex, allowed options). Reject invalid configuration rather than silently accepting it.\n\n---\n\n## 6. Defaults\n\nProvide sensible defaults that work for a fresh installation. The `default_value` in `config.json` is the single source of truth — do not duplicate it in Python or JavaScript.\n\n---\n\n## 7. Names and descriptions\n\nNames should be concise. Descriptions should explain what the setting does, when to use it, and any important side effects — not implementation details.\n\n---\n\n## 8. Boolean settings\n\nPrefer verbs: Enable, Disable, Require, Allow, Ignore, Show, Hide. Examples: *Enable MQTT*, *Require NICs Online*, *Hide Offline Devices*.\n\n---\n\n## 9. Select settings\n\nUse when users must choose from predefined values. Each option must have a meaningful label — never require users to remember internal values.\n\n---\n\n## 10. Array settings\n\nUse only when multiple independent values are expected (e.g., ignored MACs, subnet lists, plugin lists).\n\n---\n\n## 11. Localization\n\nEvery setting must have localized language strings. If not defined directly in `config.json`, they must exist in `en_us.json`.\n\nExample for `UI_DEV_SECTIONS`:\n```json\n\"UI_DEV_SECTIONS_name\": \"Hide device sections\",\n\"UI_DEV_SECTIONS_description\": \"Select which UI elements to hide on the Devices page.\"\n```\n\nWhen using external language files, `config.json` must reference:\n```json\n\"name\": [{ \"string\": \"_GLOBAL_LANG_FILES_\" }]\n```\n\n---\n\n## 12. Source of truth\n\n`app.conf` is the source of truth. User-defined values always come from `app.conf`; defaults from `config.json` apply only when a setting is absent. Never read `app.conf` directly — always go through `get_setting_value()`.\n\n---\n\n## 13. Plugin-first settings\n\nWhenever possible, define new settings inside a plugin's `config.json`. Avoid adding hardcoded application settings unless there is a compelling architectural reason.\n\n---\n\n## 14. Runtime lifecycle\n\n```text\nconfig.json (defaults)\n    → app.conf (source of truth)\n    → Settings database table\n    → table_settings.json API\n    → Frontend getSetting() / Backend get_setting_value()\n```\n\nThe database and API are runtime representations only, regenerated from `app.conf` at init.\n\n---\n\n## 15. Backwards compatibility\n\nAvoid renaming settings, changing data types, or changing semantics. If unavoidable, provide migration logic and preserve compatibility where possible.\n\n---\n\n## 16. Pull request checklist\n\n- [ ] Follows existing naming conventions (uppercase snake case)\n- [ ] Correct category used\n- [ ] Appropriate type selected, validation defined\n- [ ] Sensible default — defined only once (in `config.json`)\n- [ ] Name concise, description clear (no implementation details)\n- [ ] Localization strings added; `_GLOBAL_LANG_FILES_` used where appropriate\n- [ ] Plugin-first approach followed where applicable\n- [ ] Frontend renders automatically without custom JavaScript\n- [ ] Backend uses `get_setting_value()` — no direct `app.conf` reads\n- [ ] Backwards compatibility maintained\n- [ ] Documentation updated if required\n\n---\n\n## References\n\n- https://docs.netalertx.com/PLUGINS_DEV_SETTINGS/\n- https://docs.netalertx.com/SETTINGS_SYSTEM/\n","frontmatter":{"name":"netalertx-settings-management","description":"Manage NetAlertX configuration settings. Use this when asked to add setting, read config, get_setting_value, ccd, or configure options."},"isInternal":false,"tokens":1148,"sizeBytes":5276},{"name":"SKILL.md","path":".github/skills/skills-overview/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/skills-overview/SKILL.md","title":"skills-overview","category":"anthropic-skill","format":"markdown","content":"---\nname: skills-overview\ndescription: Index of all available skills across GitHub Copilot (.github/skills/), Gemini CLI (.gemini/skills/), and Claude Code (.claude/skills/). Load this to find the right skill for a task, or to locate the counterpart skill in another assistant's tree.\n---\n\n# Skills Index — Cross-Reference\n\nThree AI assistants are configured for this project, each with their own skill directory:\n\n- **GitHub Copilot** → `.github/skills/`\n- **Gemini CLI** → `.gemini/skills/`\n- **Claude Code** → `.claude/skills/` (mirrors most, not all, of the shared skills below — see the table's Claude Skill column for which)\n\nSkills with the same purpose exist in more than one, sometimes under different names and with different depth. This index maps them so you can find the richer version when needed. A CI check (`scripts/check_skill_pairs.py`, run as `check-skill-pairs` in `.github/workflows/code-checks.yml`) flags PRs that touch some but not all files in a mirrored group - non-blocking, since some divergence is intentional.\n\n---\n\n## Shared Skills (exist in more than one tree)\n\n| Topic | Copilot Skill | Gemini Skill | Claude Skill | Notes |\n|-------|--------------|--------------|--------------|-------|\n| Testing | `testing-workflow` | `testing-workflow` | `testing-workflow` | All three cover the full-suite-by-default rule, PYTHONPATH, auth/token retrieval, and the `sys.modules` stubbing pitfall |\n| Settings & config | `settings-management` | `settings` | — | Gemini version is more comprehensive (22-point guide + PR checklist); Copilot version covers `ccd()` and `get_setting_value()` usage |\n| MCP activation | `mcp-activation` | `mcp-activation` | — | Copilot version covers VS Code window reload; Gemini version covers Gemini CLI session restart |\n| Project navigation | `project-navigation` | `project-navigation` | — | Copilot version has full path tables and env vars; Gemini version is a brief reference |\n| Plugin dev | `plugin-run-development` | `plugin-development` | `plugin-development` | All three cover data contract, phases, formats, the `RUN_TIMEOUT` kill-timer gotcha (`timeoutMultiplier`/`per_item_timeout()`), and a pre-PR pointer to the Conventions Checklist in `docs/PLUGINS_DEV.md` |\n| Plugin README docs | `plugin-readme` | `plugin-readme` | `plugin-readme` | All three cover README structure, the \"don't re-document settings\" rule, the `docs.netalertx.com` cross-linking convention, and common defects (template leftovers, copy-paste errors) found during a full-repo audit |\n| Devcontainer | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | `devcontainer-management` | — | Copilot splits into 3 focused skills; Gemini combines into one (uses `docker exec`). `devcontainer-configs` also covers Dockerfile generation (root `Dockerfile` → `generate-dockerfile.sh` → `.devcontainer/Dockerfile`, source files not present at build time) - mined from real review comments on PR #1184/#1230. |\n| Install scripts | `install-scripts` | `install-scripts` | `install-scripts` | `install/*` conventions mined from real review comments (#1214/#1230/#1235): uninstall must only remove NetAlertX's own files, never system packages; `check-*.sh` scripts intentionally differ on fail-fast vs. warning-only; `/back` is legacy, not the active code path. |\n| PR review | `pr-analysis` | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist; the standing rule that a repeated review comment becomes a skill update (sourced from jokob's own real PR comments, #1739/#1744); a readability-over-cleverness code-style note from the same source. |\n| Logging | `logging-standards` | `logging-standards` | — | `mylog` levels, message format, what not to log |\n| Scan pipeline internals | `scan-pipeline` | `scan-pipeline` | `scan-pipeline` | `process_scan()` call order and why it's load-bearing, `CurrentScan`/`Events`/`Sessions`/`DevicesView` relationships, how a session actually closes (no `close_session()` exists), and the `FIELD_SPECS` field-write authority mechanism. Complements `database-patterns` (Devices write-path/`*Source` attribution) rather than duplicating it. |\n| Database patterns | `database-patterns` | `database-patterns` | `database-patterns` | Devices table write-path inventory, the `FIELD_SOURCE_MAP`/`*Source` attribution system in `server/db/authoritative_handler.py`, SQLite trigger vs. Python-hook tradeoffs, and event-sourced vs. snapshot audit logging. |\n| PRD writing | `prd-writing` | `prd-writing` | `prd-writing` | Methodology for writing a design doc: challenge the idea, verify every claim against actual code, trace every downstream consumer of a new mechanism, evaluate performance impact against the real schema/indexes, record rejected alternatives and open-issue decisions explicitly, final-check pass before done. |\n| UX/frontend design | `ux-design-patterns` | `ux-design-patterns` | `ux-design-patterns` | Don't invent new UX behavior/visual patterns unless a PRD calls for it - search `front/` for an existing pattern first and reuse it. Priority order for design tradeoffs when several options are reasonable: existing behavior > intuitiveness > information density > usability > utility > uniqueness > industry practices > generic UI. |\n| Skill hygiene | `skill-hygiene` | `skill-hygiene` | `skill-hygiene` | Read before writing/editing any SKILL.md, or any research/audit doc in `.gemini/internal-docs/research/`. Two standing rules: state current behavior only (no \"Correction:\", no \"as of <date>\", no \"caught in review\" narration - that trail belongs in PRDs), and prefer plain, short wording. Includes the grep sweep to run before calling a doc clean. |\n| Plugin review | `plugin-review` | `plugin-review` | `plugin-review` | Reviewer-facing complement to Plugin dev above: no raw SQL in a plugin script - use an existing/new `server/models/*.py` method, not GraphQL (which no plugin reaches). Named exception list for 5 pre-existing core/infra plugins with legitimate direct DB access, a collation-checking step, and a worked example from a real PR (#1788, DOCKERDISC). |\n\n---\n\n## Copilot-Only Skills\n\nNo Gemini equivalent yet:\n\n| Skill | Purpose |\n|-------|---------|\n| `api-development` | Creating REST API endpoints |\n| `authentication` | API tokens and 401/403 debugging |\n| `code-standards` | Coding conventions and style rules |\n| `database-reset` | Wipe and regenerate the database and config |\n| `docker-build` | Build Docker images for testing or production |\n| `docker-prune` | Clean unused Docker resources (destructive — requires confirmation) |\n| `sample-data` | Load synthetic device data into the devcontainer |\n\n---\n\n## Gemini-Only Skills\n\nNo Copilot equivalent yet:\n\n| Skill | Purpose |\n|-------|---------|\n| `initiative-start` | Research methodology and structured approach for new tasks |\n\n---\n\n## Adding a New Skill\n\nWhen adding a skill, create it in **both** directories to keep both AI systems current:\n\n- `.github/skills/<name>/SKILL.md` — add an entry to the skills table in `.github/copilot-instructions.md`\n- `.gemini/skills/<name>/SKILL.md` — auto-discovered by Gemini CLI via YAML frontmatter\n\nKeep the body content identical between both files. Only the frontmatter `name`/`description` may differ slightly to match each system's discovery heuristics.\n\nIf the skill is high-value enough to also mirror to Claude Code, add `.claude/skills/<name>/SKILL.md` too, and add the group to `GROUPS` in `scripts/check_skill_pairs.py` so drift gets flagged. Claude Code has no `activate_skill()`/`testFailure`/`runTests`/`report_progress` equivalents - adapt any such tool references to plain `Bash` commands instead of copying them verbatim.\n","frontmatter":{"name":"skills-overview","description":"Index of all available skills across GitHub Copilot (.github/skills/), Gemini CLI (.gemini/skills/), and Claude Code (.claude/skills/). Load this to find the right skill for a task, or to locate the counterpart skill in another assistant's tree."},"isInternal":false,"tokens":1814,"sizeBytes":7748},{"name":"SKILL.md","path":".github/skills/testing-workflow/SKILL.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/testing-workflow/SKILL.md","title":"netalertx-testing-workflow","category":"anthropic-skill","format":"markdown","content":"---\nname: netalertx-testing-workflow\ndescription: Run and debug tests in the NetAlertX devcontainer. Use this when asked to run tests, check test failures, debug failing tests, or execute pytest.\n---\n\n# Testing Workflow\n\n## Pre-Flight Check (MANDATORY)\n\nBefore running any tests, always check for existing failures first:\n\n1. Use the `testFailure` tool to gather current failure information\n2. Review the failures to understand what's already broken\n3. Only then proceed with test execution\n\n## Running Tests\n\nUse VS Code's testing interface or the `runTests` tool with appropriate parameters:\n\n- To run all tests: invoke runTests without file filter\n- To run specific test file: invoke runTests with the test file path\n- To run failed tests only: invoke runTests with `--lf` flag\n\n## Test Location\n\nTests live in `test/` directory. App code is under `server/`.\n\nPYTHONPATH is preconfigured to include the following which should meet all needs:\n- `/app` # the primary location where python runs in the production system\n- `/app/server` # symbolic link to /wprkspaces/NetAlertX/server\n- `/app/server/plugins` # symbolic link to /workspaces/NetAlertX/server/plugins\n- `/opt/venv/lib/pythonX.Y/site-packages`\n- `/workspaces/NetAlertX/test`\n- `/workspaces/NetAlertX/server`\n- `/workspaces/NetAlertX`\n- `/usr/lib/pythonX.Y/site-packages`\n\n## Authentication in Tests\n\nRetrieve `API_TOKEN` using Python (not shell):\n\n```python\nfrom helper import get_setting_value\ntoken = get_setting_value(\"API_TOKEN\")\n```\n\n## Troubleshooting 403 Forbidden\n\n1. Ensure backend is running (use devcontainer-services skill)\n2. Verify config loaded: `get_setting_value(\"API_TOKEN\")` returns non-empty\n3. Re-run startup if needed (use devcontainer-setup skill)\n\n## Docker Test Image\n\nIf container changes affect tests, rebuild the test image first:\n\n```bash\ndocker buildx build -t netalertx-test .\n```\n\nThis takes ~30 seconds unless venv stage changes (~90s).\n\n## Pitfall: `sys.modules` Stubbing Leaks Across Test Files\n\nSome plugin tests (e.g. `test/plugins/test_ntfy_custom_headers.py`) stub NetAlertX\nmodules (`conf`, `helper`, `models.notification_instance`, etc.) via\n`sys.modules[name] = fake_module` so the plugin script can be imported standalone,\noutside the container. Because `sys.modules` is a single process-wide cache shared\nby the whole pytest session, a fake module inserted by one test file silently\nshadows the real module for every other test file collected afterwards — pytest\nimports all test files during collection, before any test runs, so this can happen\nregardless of alphabetical/directory order.\n\nSymptom: `AttributeError: <module 'models.notification_instance'> does not have\nthe attribute 'get_setting_value'` (or similar) in an unrelated test file, where\nthe module repr has no `from '<path>'` suffix — a giveaway that a stub, not the\nreal module, was resolved.\n\nFix pattern: track which module names your stub actually inserted, and pop them\nback out of `sys.modules` immediately after the one-time import that needed them\n(the already-imported script keeps its bound names regardless):\n\n```python\n_stubbed_module_names = []\n\ndef _stub(name, **attrs):\n    if name not in sys.modules:\n        mod = types.ModuleType(name)\n        for k, v in attrs.items():\n            setattr(mod, k, v)\n        sys.modules[name] = mod\n        _stubbed_module_names.append(name)\n\n# ... _stub(...) calls, then the one-time import ...\nimport ntfy\n\nfor _name in _stubbed_module_names:\n    sys.modules.pop(_name, None)\n```\n\nReproduce cross-file pollution locally by running the suspect file together with\nthe affected one in a single pytest invocation (order matters less than you'd\nthink — collection happens for all files first):\n\n```bash\npytest test/plugins/test_ntfy_custom_headers.py test/backend/test_notification_templates.py -v\n```\n","frontmatter":{"name":"netalertx-testing-workflow","description":"Run and debug tests in the NetAlertX devcontainer. Use this when asked to run tests, check test failures, debug failing tests, or execute pytest."},"isInternal":false,"tokens":878,"sizeBytes":3829},{"name":"config.json","path":"server/plugins/__template/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/__template/config.json","title":"__template Skill","category":"anthropic-skill","format":"json","content":"# __template Skill\n\nPath: `server/plugins/__template/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/__template/config.json)","tokens":300,"sizeBytes":193},{"name":"config.json","path":"server/plugins/_publisher_apprise/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_apprise/config.json","title":"_publisher_apprise Skill","category":"anthropic-skill","format":"json","content":"# _publisher_apprise Skill\n\nPath: `server/plugins/_publisher_apprise/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_apprise/config.json)","tokens":300,"sizeBytes":217},{"name":"config.json","path":"server/plugins/_publisher_email/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_email/config.json","title":"_publisher_email Skill","category":"anthropic-skill","format":"json","content":"# _publisher_email Skill\n\nPath: `server/plugins/_publisher_email/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_email/config.json)","tokens":300,"sizeBytes":211},{"name":"config.json","path":"server/plugins/_publisher_mqtt/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_mqtt/config.json","title":"_publisher_mqtt Skill","category":"anthropic-skill","format":"json","content":"# _publisher_mqtt Skill\n\nPath: `server/plugins/_publisher_mqtt/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_mqtt/config.json)","tokens":300,"sizeBytes":208},{"name":"config.json","path":"server/plugins/_publisher_ntfy/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_ntfy/config.json","title":"_publisher_ntfy Skill","category":"anthropic-skill","format":"json","content":"# _publisher_ntfy Skill\n\nPath: `server/plugins/_publisher_ntfy/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_ntfy/config.json)","tokens":300,"sizeBytes":208},{"name":"config.json","path":"server/plugins/_publisher_pushover/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushover/config.json","title":"_publisher_pushover Skill","category":"anthropic-skill","format":"json","content":"# _publisher_pushover Skill\n\nPath: `server/plugins/_publisher_pushover/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushover/config.json)","tokens":300,"sizeBytes":220},{"name":"config.json","path":"server/plugins/_publisher_pushsafer/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushsafer/config.json","title":"_publisher_pushsafer Skill","category":"anthropic-skill","format":"json","content":"# _publisher_pushsafer Skill\n\nPath: `server/plugins/_publisher_pushsafer/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushsafer/config.json)","tokens":300,"sizeBytes":223},{"name":"config.json","path":"server/plugins/_publisher_telegram/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_telegram/config.json","title":"_publisher_telegram Skill","category":"anthropic-skill","format":"json","content":"# _publisher_telegram Skill\n\nPath: `server/plugins/_publisher_telegram/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_telegram/config.json)","tokens":300,"sizeBytes":220},{"name":"config.json","path":"server/plugins/_publisher_webhook/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_webhook/config.json","title":"_publisher_webhook Skill","category":"anthropic-skill","format":"json","content":"# _publisher_webhook Skill\n\nPath: `server/plugins/_publisher_webhook/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_webhook/config.json)","tokens":300,"sizeBytes":217},{"name":"config.json","path":"server/plugins/adguard_export/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_export/config.json","title":"Adguard_export Skill","category":"anthropic-skill","format":"json","content":"# Adguard_export Skill\n\nPath: `server/plugins/adguard_export/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_export/config.json)","tokens":300,"sizeBytes":205},{"name":"config.json","path":"server/plugins/adguard_import/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_import/config.json","title":"Adguard_import Skill","category":"anthropic-skill","format":"json","content":"# Adguard_import Skill\n\nPath: `server/plugins/adguard_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_import/config.json)","tokens":300,"sizeBytes":205},{"name":"config.json","path":"server/plugins/arp_scan/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/arp_scan/config.json","title":"Arp_scan Skill","category":"anthropic-skill","format":"json","content":"# Arp_scan Skill\n\nPath: `server/plugins/arp_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/arp_scan/config.json)","tokens":300,"sizeBytes":187},{"name":"config.json","path":"server/plugins/asuswrt_import/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/asuswrt_import/config.json","title":"Asuswrt_import Skill","category":"anthropic-skill","format":"json","content":"# Asuswrt_import Skill\n\nPath: `server/plugins/asuswrt_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/asuswrt_import/config.json)","tokens":300,"sizeBytes":205},{"name":"config.json","path":"server/plugins/avahi_scan/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/avahi_scan/config.json","title":"Avahi_scan Skill","category":"anthropic-skill","format":"json","content":"# Avahi_scan Skill\n\nPath: `server/plugins/avahi_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/avahi_scan/config.json)","tokens":300,"sizeBytes":193},{"name":"config.json","path":"server/plugins/csv_backup/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/csv_backup/config.json","title":"Csv_backup Skill","category":"anthropic-skill","format":"json","content":"# Csv_backup Skill\n\nPath: `server/plugins/csv_backup/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/csv_backup/config.json)","tokens":300,"sizeBytes":193},{"name":"config.json","path":"server/plugins/custom_props/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/custom_props/config.json","title":"Custom_props Skill","category":"anthropic-skill","format":"json","content":"# Custom_props Skill\n\nPath: `server/plugins/custom_props/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/custom_props/config.json)","tokens":300,"sizeBytes":199},{"name":"config.json","path":"server/plugins/db_cleanup/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/db_cleanup/config.json","title":"Db_cleanup Skill","category":"anthropic-skill","format":"json","content":"# Db_cleanup Skill\n\nPath: `server/plugins/db_cleanup/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/db_cleanup/config.json)","tokens":300,"sizeBytes":193},{"name":"config.json","path":"server/plugins/ddns_update/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ddns_update/config.json","title":"Ddns_update Skill","category":"anthropic-skill","format":"json","content":"# Ddns_update Skill\n\nPath: `server/plugins/ddns_update/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ddns_update/config.json)","tokens":300,"sizeBytes":196},{"name":"README_DE.md","path":"server/plugins/ddns_update/README_DE.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ddns_update/README_DE.md","title":"Ddns_update Skill","category":"anthropic-skill","format":"markdown","content":"# Ddns_update Skill\n\nPath: `server/plugins/ddns_update/README_DE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ddns_update/README_DE.md)","tokens":300,"sizeBytes":198},{"name":"ASUS_ROUTERS.md","path":"server/plugins/dhcp_leases/ASUS_ROUTERS.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_leases/ASUS_ROUTERS.md","title":"Dhcp_leases Skill","category":"anthropic-skill","format":"markdown","content":"# Dhcp_leases Skill\n\nPath: `server/plugins/dhcp_leases/ASUS_ROUTERS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_leases/ASUS_ROUTERS.md)","tokens":300,"sizeBytes":204},{"name":"config.json","path":"server/plugins/dhcp_leases/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_leases/config.json","title":"Dhcp_leases Skill","category":"anthropic-skill","format":"json","content":"# Dhcp_leases Skill\n\nPath: `server/plugins/dhcp_leases/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_leases/config.json)","tokens":300,"sizeBytes":196},{"name":"config.json","path":"server/plugins/dhcp_servers/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_servers/config.json","title":"Dhcp_servers Skill","category":"anthropic-skill","format":"json","content":"# Dhcp_servers Skill\n\nPath: `server/plugins/dhcp_servers/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_servers/config.json)","tokens":300,"sizeBytes":199},{"name":"config.json","path":"server/plugins/dig_scan/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dig_scan/config.json","title":"Dig_scan Skill","category":"anthropic-skill","format":"json","content":"# Dig_scan Skill\n\nPath: `server/plugins/dig_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dig_scan/config.json)","tokens":300,"sizeBytes":187},{"name":"config.json","path":"server/plugins/freebox/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/freebox/config.json","title":"Freebox Skill","category":"anthropic-skill","format":"json","content":"# Freebox Skill\n\nPath: `server/plugins/freebox/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/freebox/config.json)","tokens":300,"sizeBytes":184},{"name":"config.json","path":"server/plugins/fritzbox/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/fritzbox/config.json","title":"Fritzbox Skill","category":"anthropic-skill","format":"json","content":"# Fritzbox Skill\n\nPath: `server/plugins/fritzbox/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/fritzbox/config.json)","tokens":300,"sizeBytes":187},{"name":"config.json","path":"server/plugins/icmp_scan/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/icmp_scan/config.json","title":"Icmp_scan Skill","category":"anthropic-skill","format":"json","content":"# Icmp_scan Skill\n\nPath: `server/plugins/icmp_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/icmp_scan/config.json)","tokens":300,"sizeBytes":190},{"name":"config.json","path":"server/plugins/internet_ip/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_ip/config.json","title":"Internet_ip Skill","category":"anthropic-skill","format":"json","content":"# Internet_ip Skill\n\nPath: `server/plugins/internet_ip/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_ip/config.json)","tokens":300,"sizeBytes":196},{"name":"config.json","path":"server/plugins/internet_speedtest/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_speedtest/config.json","title":"Internet_speedtest Skill","category":"anthropic-skill","format":"json","content":"# Internet_speedtest Skill\n\nPath: `server/plugins/internet_speedtest/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_speedtest/config.json)","tokens":300,"sizeBytes":217},{"name":"config.json","path":"server/plugins/ipneigh/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ipneigh/config.json","title":"Ipneigh Skill","category":"anthropic-skill","format":"json","content":"# Ipneigh Skill\n\nPath: `server/plugins/ipneigh/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ipneigh/config.json)","tokens":300,"sizeBytes":184},{"name":"config.json","path":"server/plugins/kea_api/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/kea_api/config.json","title":"Kea_api Skill","category":"anthropic-skill","format":"json","content":"# Kea_api Skill\n\nPath: `server/plugins/kea_api/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/kea_api/config.json)","tokens":300,"sizeBytes":184},{"name":"config.json","path":"server/plugins/luci_import/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/luci_import/config.json","title":"Luci_import Skill","category":"anthropic-skill","format":"json","content":"# Luci_import Skill\n\nPath: `server/plugins/luci_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/luci_import/config.json)","tokens":300,"sizeBytes":196},{"name":"config.json","path":"server/plugins/maintenance/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/maintenance/config.json","title":"Maintenance Skill","category":"anthropic-skill","format":"json","content":"# Maintenance Skill\n\nPath: `server/plugins/maintenance/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/maintenance/config.json)","tokens":300,"sizeBytes":196},{"name":"config.json","path":"server/plugins/mikrotik_scan/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/mikrotik_scan/config.json","title":"Mikrotik_scan Skill","category":"anthropic-skill","format":"json","content":"# Mikrotik_scan Skill\n\nPath: `server/plugins/mikrotik_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/mikrotik_scan/config.json)","tokens":300,"sizeBytes":202},{"name":"config.json","path":"server/plugins/nbtscan_scan/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nbtscan_scan/config.json","title":"Nbtscan_scan Skill","category":"anthropic-skill","format":"json","content":"# Nbtscan_scan Skill\n\nPath: `server/plugins/nbtscan_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nbtscan_scan/config.json)","tokens":300,"sizeBytes":199},{"name":"config.json","path":"server/plugins/newdev_template/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/newdev_template/config.json","title":"Newdev_template Skill","category":"anthropic-skill","format":"json","content":"# Newdev_template Skill\n\nPath: `server/plugins/newdev_template/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/newdev_template/config.json)","tokens":300,"sizeBytes":208},{"name":"config.json","path":"server/plugins/nmap_dev_scan/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_dev_scan/config.json","title":"Nmap_dev_scan Skill","category":"anthropic-skill","format":"json","content":"# Nmap_dev_scan Skill\n\nPath: `server/plugins/nmap_dev_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_dev_scan/config.json)","tokens":300,"sizeBytes":202},{"name":"config.json","path":"server/plugins/nmap_scan/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_scan/config.json","title":"Nmap_scan Skill","category":"anthropic-skill","format":"json","content":"# Nmap_scan Skill\n\nPath: `server/plugins/nmap_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_scan/config.json)","tokens":300,"sizeBytes":190},{"name":"config.json","path":"server/plugins/notification_processing/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/notification_processing/config.json","title":"Notification_processing Skill","category":"anthropic-skill","format":"json","content":"# Notification_processing Skill\n\nPath: `server/plugins/notification_processing/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/notification_processing/config.json)","tokens":300,"sizeBytes":232},{"name":"config.json","path":"server/plugins/nslookup_scan/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nslookup_scan/config.json","title":"Nslookup_scan Skill","category":"anthropic-skill","format":"json","content":"# Nslookup_scan Skill\n\nPath: `server/plugins/nslookup_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nslookup_scan/config.json)","tokens":300,"sizeBytes":202},{"name":"config.json","path":"server/plugins/omada_sdn_imp/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_imp/config.json","title":"Omada_sdn_imp Skill","category":"anthropic-skill","format":"json","content":"# Omada_sdn_imp Skill\n\nPath: `server/plugins/omada_sdn_imp/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_imp/config.json)","tokens":300,"sizeBytes":202},{"name":"config.json","path":"server/plugins/omada_sdn_openapi/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_openapi/config.json","title":"Omada_sdn_openapi Skill","category":"anthropic-skill","format":"json","content":"# Omada_sdn_openapi Skill\n\nPath: `server/plugins/omada_sdn_openapi/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_openapi/config.json)","tokens":300,"sizeBytes":214},{"name":"config.json","path":"server/plugins/pihole_api_scan/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_api_scan/config.json","title":"Pihole_api_scan Skill","category":"anthropic-skill","format":"json","content":"# Pihole_api_scan Skill\n\nPath: `server/plugins/pihole_api_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_api_scan/config.json)","tokens":300,"sizeBytes":208},{"name":"config.json","path":"server/plugins/pihole_scan/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_scan/config.json","title":"Pihole_scan Skill","category":"anthropic-skill","format":"json","content":"# Pihole_scan Skill\n\nPath: `server/plugins/pihole_scan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_scan/config.json)","tokens":300,"sizeBytes":196},{"name":"config.json","path":"server/plugins/rest_import/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/rest_import/config.json","title":"Rest_import Skill","category":"anthropic-skill","format":"json","content":"# Rest_import Skill\n\nPath: `server/plugins/rest_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/rest_import/config.json)","tokens":300,"sizeBytes":196},{"name":"config.json","path":"server/plugins/set_password/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/set_password/config.json","title":"Set_password Skill","category":"anthropic-skill","format":"json","content":"# Set_password Skill\n\nPath: `server/plugins/set_password/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/set_password/config.json)","tokens":300,"sizeBytes":199},{"name":"config.json","path":"server/plugins/snmp_discovery/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/snmp_discovery/config.json","title":"Snmp_discovery Skill","category":"anthropic-skill","format":"json","content":"# Snmp_discovery Skill\n\nPath: `server/plugins/snmp_discovery/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/snmp_discovery/config.json)","tokens":300,"sizeBytes":205},{"name":"config.json","path":"server/plugins/sync/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/sync/config.json","title":"Sync Skill","category":"anthropic-skill","format":"json","content":"# Sync Skill\n\nPath: `server/plugins/sync/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/sync/config.json)","tokens":300,"sizeBytes":175},{"name":"config.json","path":"server/plugins/ui_settings/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ui_settings/config.json","title":"Ui_settings Skill","category":"anthropic-skill","format":"json","content":"# Ui_settings Skill\n\nPath: `server/plugins/ui_settings/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ui_settings/config.json)","tokens":300,"sizeBytes":196},{"name":"config.json","path":"server/plugins/unifi_api_import/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_api_import/config.json","title":"Unifi_api_import Skill","category":"anthropic-skill","format":"json","content":"# Unifi_api_import Skill\n\nPath: `server/plugins/unifi_api_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_api_import/config.json)","tokens":300,"sizeBytes":211},{"name":"config.json","path":"server/plugins/unifi_import/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_import/config.json","title":"Unifi_import Skill","category":"anthropic-skill","format":"json","content":"# Unifi_import Skill\n\nPath: `server/plugins/unifi_import/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_import/config.json)","tokens":300,"sizeBytes":199},{"name":"config.json","path":"server/plugins/vendor_update/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/vendor_update/config.json","title":"Vendor_update Skill","category":"anthropic-skill","format":"json","content":"# Vendor_update Skill\n\nPath: `server/plugins/vendor_update/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/vendor_update/config.json)","tokens":300,"sizeBytes":202},{"name":"config.json","path":"server/plugins/wake_on_lan/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/wake_on_lan/config.json","title":"Wake_on_lan Skill","category":"anthropic-skill","format":"json","content":"# Wake_on_lan Skill\n\nPath: `server/plugins/wake_on_lan/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/wake_on_lan/config.json)","tokens":300,"sizeBytes":196},{"name":"config.json","path":"server/plugins/website_monitor/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/website_monitor/config.json","title":"Website_monitor Skill","category":"anthropic-skill","format":"json","content":"# Website_monitor Skill\n\nPath: `server/plugins/website_monitor/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/website_monitor/config.json)","tokens":300,"sizeBytes":208},{"name":"config.json","path":"server/plugins/workflows/config.json","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/workflows/config.json","title":"Workflows Skill","category":"anthropic-skill","format":"json","content":"# Workflows Skill\n\nPath: `server/plugins/workflows/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/workflows/config.json)","tokens":300,"sizeBytes":190},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/copilot-instructions.md","title":"GitHub Copilot Instructions","category":"copilot-instructions","format":"markdown","content":"# GitHub Copilot Instructions\n\nPath: `.github/copilot-instructions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/copilot-instructions.md)","tokens":300,"sizeBytes":192},{"name":"README.md","path":"server/plugins/__template/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/__template/README.md","title":"__template Documentation","category":"plugin-manifest","format":"markdown","content":"# __template Documentation\n\nPath: `server/plugins/__template/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/__template/README.md)","tokens":300,"sizeBytes":197},{"name":"README.md","path":"server/plugins/_publisher_apprise/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_apprise/README.md","title":"_publisher_apprise Documentation","category":"plugin-manifest","format":"markdown","content":"# _publisher_apprise Documentation\n\nPath: `server/plugins/_publisher_apprise/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_apprise/README.md)","tokens":300,"sizeBytes":221},{"name":"README.md","path":"server/plugins/_publisher_email/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_email/README.md","title":"_publisher_email Documentation","category":"plugin-manifest","format":"markdown","content":"# _publisher_email Documentation\n\nPath: `server/plugins/_publisher_email/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_email/README.md)","tokens":300,"sizeBytes":215},{"name":"README.md","path":"server/plugins/_publisher_mqtt/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_mqtt/README.md","title":"_publisher_mqtt Documentation","category":"plugin-manifest","format":"markdown","content":"# _publisher_mqtt Documentation\n\nPath: `server/plugins/_publisher_mqtt/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_mqtt/README.md)","tokens":300,"sizeBytes":212},{"name":"README.md","path":"server/plugins/_publisher_ntfy/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_ntfy/README.md","title":"_publisher_ntfy Documentation","category":"plugin-manifest","format":"markdown","content":"# _publisher_ntfy Documentation\n\nPath: `server/plugins/_publisher_ntfy/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_ntfy/README.md)","tokens":300,"sizeBytes":212},{"name":"README.md","path":"server/plugins/_publisher_pushover/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushover/README.md","title":"_publisher_pushover Documentation","category":"plugin-manifest","format":"markdown","content":"# _publisher_pushover Documentation\n\nPath: `server/plugins/_publisher_pushover/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushover/README.md)","tokens":300,"sizeBytes":224},{"name":"README.md","path":"server/plugins/_publisher_pushsafer/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushsafer/README.md","title":"_publisher_pushsafer Documentation","category":"plugin-manifest","format":"markdown","content":"# _publisher_pushsafer Documentation\n\nPath: `server/plugins/_publisher_pushsafer/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_pushsafer/README.md)","tokens":300,"sizeBytes":227},{"name":"README.md","path":"server/plugins/_publisher_telegram/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_telegram/README.md","title":"_publisher_telegram Documentation","category":"plugin-manifest","format":"markdown","content":"# _publisher_telegram Documentation\n\nPath: `server/plugins/_publisher_telegram/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_telegram/README.md)","tokens":300,"sizeBytes":224},{"name":"README.md","path":"server/plugins/_publisher_webhook/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_webhook/README.md","title":"_publisher_webhook Documentation","category":"plugin-manifest","format":"markdown","content":"# _publisher_webhook Documentation\n\nPath: `server/plugins/_publisher_webhook/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_webhook/README.md)","tokens":300,"sizeBytes":221},{"name":"README.md","path":"server/plugins/adguard_export/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_export/README.md","title":"adguard_export Documentation","category":"plugin-manifest","format":"markdown","content":"# adguard_export Documentation\n\nPath: `server/plugins/adguard_export/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_export/README.md)","tokens":300,"sizeBytes":209},{"name":"README.md","path":"server/plugins/adguard_import/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_import/README.md","title":"adguard_import Documentation","category":"plugin-manifest","format":"markdown","content":"# adguard_import Documentation\n\nPath: `server/plugins/adguard_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/adguard_import/README.md)","tokens":300,"sizeBytes":209},{"name":"README.md","path":"server/plugins/arp_scan/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/arp_scan/README.md","title":"arp_scan Documentation","category":"plugin-manifest","format":"markdown","content":"# arp_scan Documentation\n\nPath: `server/plugins/arp_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/arp_scan/README.md)","tokens":300,"sizeBytes":191},{"name":"README.md","path":"server/plugins/asuswrt_import/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/asuswrt_import/README.md","title":"asuswrt_import Documentation","category":"plugin-manifest","format":"markdown","content":"# asuswrt_import Documentation\n\nPath: `server/plugins/asuswrt_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/asuswrt_import/README.md)","tokens":300,"sizeBytes":209},{"name":"README.md","path":"server/plugins/avahi_scan/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/avahi_scan/README.md","title":"avahi_scan Documentation","category":"plugin-manifest","format":"markdown","content":"# avahi_scan Documentation\n\nPath: `server/plugins/avahi_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/avahi_scan/README.md)","tokens":300,"sizeBytes":197},{"name":"README.md","path":"server/plugins/csv_backup/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/csv_backup/README.md","title":"csv_backup Documentation","category":"plugin-manifest","format":"markdown","content":"# csv_backup Documentation\n\nPath: `server/plugins/csv_backup/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/csv_backup/README.md)","tokens":300,"sizeBytes":197},{"name":"README.md","path":"server/plugins/custom_props/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/custom_props/README.md","title":"custom_props Documentation","category":"plugin-manifest","format":"markdown","content":"# custom_props Documentation\n\nPath: `server/plugins/custom_props/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/custom_props/README.md)","tokens":300,"sizeBytes":203},{"name":"README.md","path":"server/plugins/db_cleanup/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/db_cleanup/README.md","title":"db_cleanup Documentation","category":"plugin-manifest","format":"markdown","content":"# db_cleanup Documentation\n\nPath: `server/plugins/db_cleanup/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/db_cleanup/README.md)","tokens":300,"sizeBytes":197},{"name":"README.md","path":"server/plugins/ddns_update/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ddns_update/README.md","title":"ddns_update Documentation","category":"plugin-manifest","format":"markdown","content":"# ddns_update Documentation\n\nPath: `server/plugins/ddns_update/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ddns_update/README.md)","tokens":300,"sizeBytes":200},{"name":"README.md","path":"server/plugins/dhcp_leases/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_leases/README.md","title":"dhcp_leases Documentation","category":"plugin-manifest","format":"markdown","content":"# dhcp_leases Documentation\n\nPath: `server/plugins/dhcp_leases/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_leases/README.md)","tokens":300,"sizeBytes":200},{"name":"README.md","path":"server/plugins/dhcp_servers/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_servers/README.md","title":"dhcp_servers Documentation","category":"plugin-manifest","format":"markdown","content":"# dhcp_servers Documentation\n\nPath: `server/plugins/dhcp_servers/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dhcp_servers/README.md)","tokens":300,"sizeBytes":203},{"name":"README.md","path":"server/plugins/dig_scan/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dig_scan/README.md","title":"dig_scan Documentation","category":"plugin-manifest","format":"markdown","content":"# dig_scan Documentation\n\nPath: `server/plugins/dig_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/dig_scan/README.md)","tokens":300,"sizeBytes":191},{"name":"README.md","path":"server/plugins/freebox/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/freebox/README.md","title":"freebox Documentation","category":"plugin-manifest","format":"markdown","content":"# freebox Documentation\n\nPath: `server/plugins/freebox/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/freebox/README.md)","tokens":300,"sizeBytes":188},{"name":"README.md","path":"server/plugins/fritzbox/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/fritzbox/README.md","title":"fritzbox Documentation","category":"plugin-manifest","format":"markdown","content":"# fritzbox Documentation\n\nPath: `server/plugins/fritzbox/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/fritzbox/README.md)","tokens":300,"sizeBytes":191},{"name":"README.md","path":"server/plugins/icmp_scan/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/icmp_scan/README.md","title":"icmp_scan Documentation","category":"plugin-manifest","format":"markdown","content":"# icmp_scan Documentation\n\nPath: `server/plugins/icmp_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/icmp_scan/README.md)","tokens":300,"sizeBytes":194},{"name":"README.md","path":"server/plugins/internet_ip/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_ip/README.md","title":"internet_ip Documentation","category":"plugin-manifest","format":"markdown","content":"# internet_ip Documentation\n\nPath: `server/plugins/internet_ip/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_ip/README.md)","tokens":300,"sizeBytes":200},{"name":"README.md","path":"server/plugins/internet_speedtest/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_speedtest/README.md","title":"internet_speedtest Documentation","category":"plugin-manifest","format":"markdown","content":"# internet_speedtest Documentation\n\nPath: `server/plugins/internet_speedtest/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/internet_speedtest/README.md)","tokens":300,"sizeBytes":221},{"name":"README.md","path":"server/plugins/ipneigh/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ipneigh/README.md","title":"ipneigh Documentation","category":"plugin-manifest","format":"markdown","content":"# ipneigh Documentation\n\nPath: `server/plugins/ipneigh/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ipneigh/README.md)","tokens":300,"sizeBytes":188},{"name":"README.md","path":"server/plugins/kea_api/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/kea_api/README.md","title":"kea_api Documentation","category":"plugin-manifest","format":"markdown","content":"# kea_api Documentation\n\nPath: `server/plugins/kea_api/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/kea_api/README.md)","tokens":300,"sizeBytes":188},{"name":"README.md","path":"server/plugins/luci_import/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/luci_import/README.md","title":"luci_import Documentation","category":"plugin-manifest","format":"markdown","content":"# luci_import Documentation\n\nPath: `server/plugins/luci_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/luci_import/README.md)","tokens":300,"sizeBytes":200},{"name":"README.md","path":"server/plugins/maintenance/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/maintenance/README.md","title":"maintenance Documentation","category":"plugin-manifest","format":"markdown","content":"# maintenance Documentation\n\nPath: `server/plugins/maintenance/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/maintenance/README.md)","tokens":300,"sizeBytes":200},{"name":"README.md","path":"server/plugins/mikrotik_scan/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/mikrotik_scan/README.md","title":"mikrotik_scan Documentation","category":"plugin-manifest","format":"markdown","content":"# mikrotik_scan Documentation\n\nPath: `server/plugins/mikrotik_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/mikrotik_scan/README.md)","tokens":300,"sizeBytes":206},{"name":"README.md","path":"server/plugins/nbtscan_scan/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nbtscan_scan/README.md","title":"nbtscan_scan Documentation","category":"plugin-manifest","format":"markdown","content":"# nbtscan_scan Documentation\n\nPath: `server/plugins/nbtscan_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nbtscan_scan/README.md)","tokens":300,"sizeBytes":203},{"name":"README.md","path":"server/plugins/newdev_template/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/newdev_template/README.md","title":"newdev_template Documentation","category":"plugin-manifest","format":"markdown","content":"# newdev_template Documentation\n\nPath: `server/plugins/newdev_template/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/newdev_template/README.md)","tokens":300,"sizeBytes":212},{"name":"README.md","path":"server/plugins/nmap_dev_scan/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_dev_scan/README.md","title":"nmap_dev_scan Documentation","category":"plugin-manifest","format":"markdown","content":"# nmap_dev_scan Documentation\n\nPath: `server/plugins/nmap_dev_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_dev_scan/README.md)","tokens":300,"sizeBytes":206},{"name":"README.md","path":"server/plugins/nmap_scan/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_scan/README.md","title":"nmap_scan Documentation","category":"plugin-manifest","format":"markdown","content":"# nmap_scan Documentation\n\nPath: `server/plugins/nmap_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nmap_scan/README.md)","tokens":300,"sizeBytes":194},{"name":"README.md","path":"server/plugins/notification_processing/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/notification_processing/README.md","title":"notification_processing Documentation","category":"plugin-manifest","format":"markdown","content":"# notification_processing Documentation\n\nPath: `server/plugins/notification_processing/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/notification_processing/README.md)","tokens":300,"sizeBytes":236},{"name":"README.md","path":"server/plugins/nslookup_scan/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nslookup_scan/README.md","title":"nslookup_scan Documentation","category":"plugin-manifest","format":"markdown","content":"# nslookup_scan Documentation\n\nPath: `server/plugins/nslookup_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/nslookup_scan/README.md)","tokens":300,"sizeBytes":206},{"name":"README.md","path":"server/plugins/omada_sdn_imp/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_imp/README.md","title":"omada_sdn_imp Documentation","category":"plugin-manifest","format":"markdown","content":"# omada_sdn_imp Documentation\n\nPath: `server/plugins/omada_sdn_imp/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_imp/README.md)","tokens":300,"sizeBytes":206},{"name":"README.md","path":"server/plugins/omada_sdn_openapi/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_openapi/README.md","title":"omada_sdn_openapi Documentation","category":"plugin-manifest","format":"markdown","content":"# omada_sdn_openapi Documentation\n\nPath: `server/plugins/omada_sdn_openapi/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/omada_sdn_openapi/README.md)","tokens":300,"sizeBytes":218},{"name":"README.md","path":"server/plugins/pihole_api_scan/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_api_scan/README.md","title":"pihole_api_scan Documentation","category":"plugin-manifest","format":"markdown","content":"# pihole_api_scan Documentation\n\nPath: `server/plugins/pihole_api_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_api_scan/README.md)","tokens":300,"sizeBytes":212},{"name":"README.md","path":"server/plugins/pihole_scan/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_scan/README.md","title":"pihole_scan Documentation","category":"plugin-manifest","format":"markdown","content":"# pihole_scan Documentation\n\nPath: `server/plugins/pihole_scan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/pihole_scan/README.md)","tokens":300,"sizeBytes":200},{"name":"README.md","path":"server/plugins/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/README.md","title":"plugins Documentation","category":"plugin-manifest","format":"markdown","content":"# plugins Documentation\n\nPath: `server/plugins/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/README.md)","tokens":300,"sizeBytes":172},{"name":"README.md","path":"server/plugins/rest_import/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/rest_import/README.md","title":"rest_import Documentation","category":"plugin-manifest","format":"markdown","content":"# rest_import Documentation\n\nPath: `server/plugins/rest_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/rest_import/README.md)","tokens":300,"sizeBytes":200},{"name":"README.md","path":"server/plugins/set_password/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/set_password/README.md","title":"set_password Documentation","category":"plugin-manifest","format":"markdown","content":"# set_password Documentation\n\nPath: `server/plugins/set_password/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/set_password/README.md)","tokens":300,"sizeBytes":203},{"name":"README.md","path":"server/plugins/snmp_discovery/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/snmp_discovery/README.md","title":"snmp_discovery Documentation","category":"plugin-manifest","format":"markdown","content":"# snmp_discovery Documentation\n\nPath: `server/plugins/snmp_discovery/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/snmp_discovery/README.md)","tokens":300,"sizeBytes":209},{"name":"README.md","path":"server/plugins/sync/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/sync/README.md","title":"sync Documentation","category":"plugin-manifest","format":"markdown","content":"# sync Documentation\n\nPath: `server/plugins/sync/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/sync/README.md)","tokens":300,"sizeBytes":179},{"name":"README.md","path":"server/plugins/ui_settings/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ui_settings/README.md","title":"ui_settings Documentation","category":"plugin-manifest","format":"markdown","content":"# ui_settings Documentation\n\nPath: `server/plugins/ui_settings/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/ui_settings/README.md)","tokens":300,"sizeBytes":200},{"name":"README.md","path":"server/plugins/unifi_api_import/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_api_import/README.md","title":"unifi_api_import Documentation","category":"plugin-manifest","format":"markdown","content":"# unifi_api_import Documentation\n\nPath: `server/plugins/unifi_api_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_api_import/README.md)","tokens":300,"sizeBytes":215},{"name":"README.md","path":"server/plugins/unifi_import/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_import/README.md","title":"unifi_import Documentation","category":"plugin-manifest","format":"markdown","content":"# unifi_import Documentation\n\nPath: `server/plugins/unifi_import/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/unifi_import/README.md)","tokens":300,"sizeBytes":203},{"name":"README.md","path":"server/plugins/vendor_update/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/vendor_update/README.md","title":"vendor_update Documentation","category":"plugin-manifest","format":"markdown","content":"# vendor_update Documentation\n\nPath: `server/plugins/vendor_update/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/vendor_update/README.md)","tokens":300,"sizeBytes":206},{"name":"README.md","path":"server/plugins/wake_on_lan/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/wake_on_lan/README.md","title":"wake_on_lan Documentation","category":"plugin-manifest","format":"markdown","content":"# wake_on_lan Documentation\n\nPath: `server/plugins/wake_on_lan/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/wake_on_lan/README.md)","tokens":300,"sizeBytes":200},{"name":"README.md","path":"server/plugins/website_monitor/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/website_monitor/README.md","title":"website_monitor Documentation","category":"plugin-manifest","format":"markdown","content":"# website_monitor Documentation\n\nPath: `server/plugins/website_monitor/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/website_monitor/README.md)","tokens":300,"sizeBytes":212},{"name":"README.md","path":"server/plugins/workflows/README.md","rawUrl":"https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/workflows/README.md","title":"workflows Documentation","category":"plugin-manifest","format":"markdown","content":"# workflows Documentation\n\nPath: `server/plugins/workflows/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/workflows/README.md)","tokens":300,"sizeBytes":194}],"systemPromptSnippet":"<yakaai_skills repo=\"netalertx/NetAlertX\">\n<!-- File: front/llms.txt (Tokens: ~300 | Category: root-instruction) -->\n# front - LLM Index & Context Digest\n\nPath: `front/llms.txt`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/front/llms.txt)\n\n<!-- File: docs/API.md (Tokens: ~300 | Category: project-spec) -->\n# docs - API Interface Contract & Specifications\n\nPath: `docs/API.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/docs/API.md)\n\n<!-- File: .gemini/skills/devcontainer-management/SKILL.md (Tokens: ~391 | Category: anthropic-skill) -->\n---\nname: devcontainer-management\ndescription: Guide for identifying, managing, and running commands within the NetAlertX development container. Use this when asked to run commands, testing, setup scripts, or troubleshoot container issues.\n---\n\n# Devcontainer Management\n\nWhen starting a session or performing tasks requiring the runtime environment, you must identify and use the active development container.\n\n## Finding the Container\n\nRun `docker ps` to list running containers. Look for an image name containing `vsc-netalertx` or similar.\n\n```bash\ndocker ps --format \"table {{.ID}}\\t{{.Image}}\\t{{.Status}}\\t{{.Names}}\" | grep netalertx\n```\n\n- **If no container is found:** Inform the user. You cannot run integration tests or backend logic without it.\n- **If multiple containers are found:** Ask the user to clarify which one to use (e.g., provide the Container ID).\n\n## Running Commands in the Container\n\nPrefix commands with `docker exec <CONTAINER_ID>` to run them inside the environment. Use the scripts in `/services/` to control backend and other processes.\n\n```bash\ndocker exec <CONTAINER_ID> bash /workspaces/NetAlertX/.devcontainer/scripts/setup.sh\n```\n\n*Note: This script wipes `/tmp` ramdisks, ensures `/data`, `/data/config`, `/data/db` exist, and restarts services (python server, cron, php-fpm, nginx) by symlinking and running `/entrypoint.sh` (`install/production-filesystem/entrypoint.d/`). By default it does **not** delete existing database or config content - `entrypoint.d/25-first-run-db.sh` and `20-first-run-config.sh` only wipe them when `ALWAYS_FRESH_INSTALL=true` is set in the environment (default: unset/`false`, so content is preserved).*\n\n```\n\n<!-- File: .gemini/skills/initiative-start/reasearch-skill.md (Tokens: ~679 | Category: anthropic-skill) -->\n### 🧠 NAX Research Skill — Specification\n\nThis defines a **Research Skill module for NAX (NetAlertX)** focused on safe, structured analysis before any implementation work.\n\n---\n\n## 1. Purpose\n\nEnsure all work begins with **documentation-first understanding**, **PRD validation**, and **conflict detection**, before any planning or coding.\n\n---\n\n## 2. Core Workflow\n\n### Step 1 — Documentation First\n\n* Always begin by reading relevant repository documentation.\n\n* Priority order:\n\n  1. `/CONTRIBUTING.md`\n  2. `/README.md`\n  3. `/.github/skills/code-standards/SKILL.md`\n  4. `/.github/skills/database-patterns/SKILL.md` — read when the feature touches the `Devices` table or requires audit/history logging\n  5. `/docs/**`\n  6. Related module/code context if referenced\n\n* Extract:\n\n  * Architecture expectations\n  * Coding standards\n  * Plugin or module conventions\n  * Existing workflows or constraints\n\n---\n\n### Step 2 — PRD Check\n\n* If a PRD (Product Requirements Document) is NOT provided:\n\n  * Explicitly request it before proceeding further\n  * Do not assume requirements\n\n* If PRD is provided:\n\n  * Parse and restate key requirements internally\n  * Identify scope boundaries\n\n---\n\n### Step 3 — Clarification Gate\n\nIf anything is unclear:\n\n* Stop immediately\n* Ask targeted clarifying questions\n* Do NOT propose solutions yet\n\n---\n\n### Step 4 — Codebase Cross-Check\n\n* Compare PRD + documentation against existing codebase\n\n* Identify:\n\n  * Conflicting behavior\n  * Outdated patterns\n  * Duplicate logic\n  * Breaking assumptions\n  * Plugin or API mismatches\n\n* **If the feature writes to the `Devices` table:**\n\n  * Load `/.github/skills/database-patterns/SKILL.md`\n  * Audit all write paths listed there — confirm which are affected\n  * Determine if a SQLite trigger or Python hook is the right approach\n  * Check if `*Source` fields already solve attribution requirements\n\n* Clearly report inconsistencies before proceeding\n\n---\n\n### Step 5 — Planning Requirement (Strict)\n\nBefore any implementation:\n\n* Produce a structured plan including:\n\n  * Approach overview\n  * Files/modules affected\n  * Dependencies\n  * Risk areas\n  * Migration considerations (if any)\n\n* Explicitly label:\n\n  > “WAITING FOR USER CONFIRMATION”\n\n---\n\n### Step 6 — Implementation Gate (Hard Rule)\n\n* Do NOT start implementation until user explicitly confirms the plan\n* No partial coding, no early patches, no assumptions\n\n---\n\n## 3. Behavioral Constraints\n\n* Always prioritize correctness over speed\n* Never skip PRD validation\n* Never proceed past ambiguity\n* Never implement without approval\n* Always surface contradictions in source material\n* Always prefer asking questions over guessing\n\n---\n\n## 4. Output Style Rules\n\n* Be structured and technical\n* Avoid unnecessary verbosity\n* Separate:\n\n  * Findings\n  * Risks\n  * Questions\n  * Plan\n* No hidden assumptions\n\n---\n\n## 5. Summary Flow\n\n```\nDocs → PRD → Clarify → Codebase Check → Plan → User Approval → Implement\n```\n\n<!-- File: .gemini/skills/mcp-activation/SKILL.md (Tokens: ~539 | Category: anthropic-skill) -->\n---\nname: mcp-activation\ndescription: Enables live interaction with the NetAlertX runtime. This skill configures the Model Context Protocol (MCP) connection, granting full API access for debugging, troubleshooting, and real-time operations including database queries, network scans, and device management.\n---\n\n# MCP Activation Skill\n\nThis skill configures the NetAlertX development environment to expose the Model Context Protocol (MCP) server to AI agents.\n\n## Why use this?\n\nBy default, agents only have access to the static codebase (files). To perform dynamic actions—such as:\n- **Querying the database** (e.g., getting device lists, events)\n- **Triggering actions** (e.g., network scans, Wake-on-LAN)\n- **Validating runtime state** (e.g., checking if a fix actually works)\n\n...you need access to the **MCP Server** running inside the container. This skill sets up the necessary authentication tokens and connection configs to bridge your agent to that live server.\n\n## Prerequisites\n\n1.  **Devcontainer:** You must be connected to the NetAlertX devcontainer.\n2.  **Server Running:** The backend server must be running (to generate `app.conf` with the API token).\n\n## Activation Steps\n\n1.  **Activate Devcontainer Skill:**\n    If you are not already inside the container, activate the management skill:\n    ```text\n    activate_skill(\"devcontainer-management\")\n    ```\n\n2.  **Generate Configurations:**\n    Run the configuration generation script *inside* the container. This script extracts the API Token and creates the necessary settings files (`.gemini/settings.json` and `.vscode/mcp.json`).\n\n    ```bash\n    # Run inside the container\n    /workspaces/NetAlertX/.devcontainer/scripts/generate-configs.sh\n    ```\n\n3.  **Apply Changes:**\n\n    *   **For Gemini CLI:**\n        The agent session must be **restarted** to load the new `.gemini/settings.json`.\n        > \"I have generated the MCP configuration. Please **restart this session** to activate the `netalertx-devcontainer` tools.\"\n\n    *   **For VS Code (GitHub Copilot / Cline):**\n        The VS Code window must be **reloaded** to pick up the new `.vscode/mcp.json`.\n        > \"I have generated the MCP configuration. Please run **'Developer: Reload Window'** in VS Code to activate the MCP server.\"\n\n## Verification\n\nAfter restarting, you should see new tools available (e.g., `netalertx-devcontainer__get_devices`).\n\n<!-- File: .gemini/skills/plugin-development/plugin-skill.md (Tokens: ~1435 | Category: anthropic-skill) -->\n---\nname: netalertx-plugin-development\ndescription: Create and run NetAlertX plugins. Use this when asked to create plugin, run plugin, test plugin, or develop plugin functionality.\n---\n\n# Plugin Development\n\n## Expected Workflow\n\n1. Read this skill and `docs/PLUGINS_DEV.md` for full context.\n2. Find or create the plugin in `server/plugins/<code_name>/`.\n3. Read the plugin's `config.json` and `script.py` to understand its functionality.\n4. Run: `python3 server/plugins/<code_name>/script.py`\n5. Retrieve the result from `/tmp/log/plugins/last_result.<PREF>.log` quickly — the backend deletes it after processing.\n\n## Run a Plugin Manually\n\n```bash\npython3 server/plugins/<code_name>/script.py\n```\n\nEnsure `sys.path` includes `/app/server/plugins` and `/app/server` (as in the template).\n\n## Plugin Structure\n\n```text\nserver/plugins/<code_name>/\n├── config.json      # Manifest with settings\n├── script.py        # Main script\n└── ...\n```\n\n## Plugin Prefix Rules\n\n- Uppercase letters only (no underscores, no numbers)\n- Must be unique across all plugins\n- Short but readable (e.g., `ARPSCAN`, `MQTT`, `UNIFI`)\n\n## Settings Pattern\n\n- `<PREF>_RUN`: execution phase\n- `<PREF>_RUN_SCHD`: cron-like schedule\n- `<PREF>_CMD`: script path\n- `<PREF>_RUN_TIMEOUT`: timeout in seconds — **this is enforced by the core plugin runner as the whole script's kill-timeout** (`server/plugin.py` passes it straight to `subprocess(..., timeout=...)`). It is not a safe per-HTTP-call timeout — don't reuse it for individual network calls in a loop, or one slow call can burn the whole budget and get the process killed before it writes its result file.\n- `<PREF>_WATCH`: columns to watch for changes\n- `<PREF>_IMPORT_ON`: optional — gates whether this run's rows get promoted into `CurrentScan` (only relevant if `mapped_to_table: \"CurrentScan\"`). See `docs/PLUGINS_IMPORT_BEHAVIOR.md` for the related per-row `scanCreatesDevice`/`scanNotificationMode`/`scanPresence` columns.\n- **`dataType` and `default_value` must agree.** `dataType: \"array\"`/`\"object\"` needs a real JSON literal for `default_value` (`'[\"default\"]'`), not a bare string (`\"default\"`). `setting_value_to_python_type()` (`server/helper.py`) `json.loads()`s the default at runtime; a bare string fails silently — logged, and `[]` is returned instead of your default (e.g. `devParentRelType`, `UI_theme`, `UI_TOPOLOGY_ORDER`). If `elementOptions` already sets `multiple`/`orderable: \"false\"`, the setting is scalar — use `dataType: \"string\"` instead.\n\n## Data Contract\n\nScripts write to `/tmp/log/plugins/last_result.<PREF>.log` using `plugin_helper.py`:\n\n```python\nfrom plugin_helper import Plugin_Objects\n\nplugin_objects = Plugin_Objects()\nplugin_objects.add_object(...)  # During processing\nplugin_objects.write_result_file()  # Exactly once at end\n```\n\n**Important:** The backend processes and deletes the result file almost immediately. Retrieve it quickly if inspecting output.\n\nEvery mapped field (`objectPrimaryId`/`objectSecondaryId`/`watchedValue1-4`/`extra`/`helpVal1-4`) is HTML/control-char-stripped by default before it's persisted: plugin output is untrusted (network responses, device-reported names, etc.). `foreignKey` is always sanitized too, unconditionally. Only opt a column out (`\"allow_raw_text\": true`) if it's a display-only type (`textarea_readonly`); see `docs/PLUGINS_DEV.md#field-sanitization`.\n\n## Execution Phases\n\n| Phase | Trigger |\n|-------|---------|\n| `once` | Once at startup |\n| `schedule` | On cron schedule |\n| `always_after_scan` | After every scan |\n| `before_name_updates` | Before name resolution |\n| `on_new_device` | When new device detected |\n| `on_notification` | When notification triggered |\n\n## `all_plugins` Caching\n\n`all_plugins` is loaded once (`initialise.py`'s `get_plugins_configs()`) and reused as the same object until a reload - but `update_api()` (`server/api.py`), which takes it as a parameter, runs on every scan cycle and plugin completion, not just on reload.\n\n- If you write a function that derives something from `all_plugins` and gets called from `update_api()` or another frequent call site, cache the result instead of recomputing every call - retain a reference to the last-seen `all_plugins` list and compare it to the current one with `is`, not by `id(all_plugins)` alone (once the old list is garbage collected, Python can reuse that id for an unrelated object, wrongly serving a stale cached result for what is actually a new reload)\n- Pattern: `server/db/plugin_field_views.py`'s `get_all_device_field_views()`\n\n## Plugin Formats\n\n| Format | Purpose | Phase |\n|--------|---------|-------|\n| publisher | Send notifications | `on_notification` |\n| dev scanner | Create/manage devices | `schedule` |\n| name discovery | Discover device names | `before_name_updates` |\n| importer | Import from services | `schedule` |\n| system | Core functionality | `schedule` |\n\n## Before Opening a PR\n\nCheck the plugin against the [Conventions Checklist](../../../docs/PLUGINS_DEV.md#conventions-checklist) in `docs/PLUGINS_DEV.md` — `RUN` default, schedule precedent, `RUN_TIMEOUT` semantics, reusing core settings, description length, the multi-instance settings pattern, `allow_raw_text` restricted to display-only column types, and `scanSourcePlugin`'s static value matching `unique_prefix` exactly. Most plugin PR review comments trace back to one of these.\n\nIf the plugin needs a new system package or Python dependency, mirroring it into the root `Dockerfile`/`requirements.txt` alone is not enough: see the Conventions Checklist's build-target-mirroring bullet for `.devcontainer/Dockerfile` (regenerate via `.devcontainer/scripts/generate-configs.sh`, don't hand-edit it), `Dockerfile.debian`, and `install/ubuntu24`/`install/proxmox`'s own `requirements.txt` files.\n\n## Starting Point\n\nCopy from `server/plugins/__template` and customize. Read `docs/PLUGINS_DEV.md` for the full development guide.\n\n<!-- File: .gemini/skills/project-navigation/SKILL.md (Tokens: ~485 | Category: anthropic-skill) -->\n---\nname: project-navigation\ndescription: Reference for the NetAlertX codebase structure, key file paths, and configuration locations. Use this when exploring the codebase or looking for specific components like the backend entry point, frontend files, or database location.\n---\n\n# Project Navigation\n\n## Key Paths\n\n| Component | Path |\n|-----------|------|\n| Workspace root | `/workspaces/NetAlertX` |\n| Backend entry | `server/__main__.py` |\n| API server | `server/api_server/api_server_start.py` |\n| Plugin system | `server/plugin.py` |\n| Initialization | `server/initialise.py` |\n| Frontend | `front/` |\n| Frontend JS | `front/js/common.js` |\n| Frontend PHP | `front/php/server/*.php` |\n| Plugins | `server/plugins/` |\n| Plugin template | `server/plugins/__template` |\n| Database helpers | `server/db/db_helper.py` |\n| Device model | `server/models/device_instance.py` |\n| Messaging | `server/messaging/` |\n| Workflows | `server/workflows/` |\n\n## Architecture\n\nNetAlertX uses a frontend–backend architecture: the frontend runs on **PHP + Nginx** (see `front/`), the backend is implemented in **Python** (see `server/`), and scheduled tasks are managed by a **supercronic** scheduler.\n\n## Runtime Paths\n\n| Data | Path |\n|------|------|\n| Config (runtime) | `/data/config/app.conf` |\n| Config (default) | `back/app.conf` |\n| Database | `/data/db/app.db` |\n| API JSON cache | `/tmp/api/*.json` |\n| Logs | `/tmp/log/` |\n| Plugin logs | `/tmp/log/plugins/` |\n\n## Environment Variables\n\nUse these instead of hardcoding paths:\n- `NETALERTX_DB`\n- `NETALERTX_LOG`\n- `NETALERTX_CONFIG`\n- `NETALERTX_DATA`\n- `NETALERTX_APP`\n\n## Documentation\n\n| Topic | Path |\n|-------|------|\n| Plugin development | `docs/PLUGINS_DEV.md` |\n| System settings | `docs/SETTINGS_SYSTEM.md` |\n| API docs | `docs/API_*.md` |\n| Debug guides | `docs/DEBUG_*.md` |\n\n<!-- File: .gemini/skills/settings/SKILL.md (Tokens: ~1658 | Category: anthropic-skill) -->\n---\nname: nax-settings-development\ndescription: Best practices for creating and maintaining NetAlertX settings (config.json), including UI, validation, localization, runtime behavior, and implementation consistency.\n---\n\n# NetAlertX Settings Development\n\nThis skill defines the conventions and best practices for adding or modifying settings in NetAlertX.\n\nThe goal is for every setting to be:\n\n- Declarative\n- Self-documenting\n- Automatically rendered by the Settings UI\n- Fully localized\n- Backwards compatible\n- Easy to maintain\n\n---\n\n## Quick Reference\n\n**Read a setting (backend):**\n```python\nfrom helper import get_setting_value\nvalue = get_setting_value('SETTING_NAME')\n```\nNever read `app.conf` directly. Always use `get_setting_value()`.\n\n**Read a setting (frontend):**\n```javascript\ngetSetting(\"SETTING_NAME\")\n```\n\n**Add a core setting** — use `ccd()` in `server/initialise.py`:\n```python\nccd('SETTING_NAME', 'default_value', 'description')\n```\n\n**Add a plugin setting** — define in the plugin's `config.json` under the `settings` key.\n\n| File | Purpose |\n|------|---------|\n| `/data/config/app.conf` | Runtime config (source of truth) |\n| `back/app.conf` | Default config (template) |\n\nUse `APP_CONF_OVERRIDE` for settings that must be set before startup.\n\n---\n\n# 1. Follow existing patterns\n\nBefore creating a new setting:\n\n- Search for similar settings.\n- Reuse existing naming conventions.\n- Reuse existing setting types.\n- Reuse existing validation patterns.\n- Follow the style already used by similar plugins.\n\nAvoid introducing new setting types unless absolutely necessary.\n\n---\n\n# 2. Setting naming\n\nSetting keys use uppercase snake case.\n\nGood:\n\n```text\nUI_DEV_SECTIONS\nSCAN_INTERVAL\nMQTT_HOST\n```\n\nAvoid:\n\n```text\nuiDevSections\nscanInterval\nScanInterval\n```\n\nNames should clearly describe the purpose.\n\n---\n\n# 3. Categories\n\nPlace settings into the most appropriate category.\n\nKeep related settings together.\n\nAvoid creating new categories unless there is a clear need.\n\n---\n\n# 4. Choose the correct type\n\nUse the simplest type that correctly models the value.\n\nCommon types include:\n\n- string\n- integer\n- float\n- boolean\n- password\n- select\n- array\n- textarea\n\nAvoid encoding structured JSON inside string settings.\n\n---\n\n# 5. Validation\n\nAlways define validation whenever appropriate.\n\nExamples include:\n\n- minimum values\n- maximum values\n- regex validation\n- allowed options\n\nReject invalid configuration rather than silently accepting it.\n\n---\n\n# 6. Defaults\n\nProvide sensible defaults that work for a fresh installation.\n\nAvoid defaults that require external services or additional configuration.\n\nA default value should exist in exactly one place.\n\nUse the `default_value` defined in `config.json`.\n\nDo not duplicate the same default value elsewhere in Python or JavaScript.\n\n---\n\n# 7. Names and descriptions\n\nSetting names should be concise.\n\nExamples:\n\n- Scan interval\n- MQTT Host\n- Hide device sections\n\nDescriptions should explain:\n\n- what the setting does\n- when it should be used\n- important side effects\n\nAvoid implementation details.\n\nGood:\n\n> Interval between network scans in seconds.\n\nBad:\n\n> Calls scheduler.py every X seconds.\n\n---\n\n# 8. Boolean settings\n\nBoolean settings should read naturally.\n\nPrefer verbs such as:\n\n- Enable\n- Disable\n- Require\n- Allow\n- Ignore\n- Show\n- Hide\n\nExamples:\n\n- Enable MQTT\n- Require NICs Online\n- Hide Offline Devices\n\n---\n\n# 9. Select settings\n\nWhen users must choose from predefined values, use a select setting.\n\nNever require users to remember internal values.\n\nEach option should have a meaningful label.\n\n---\n\n# 10. Array settings\n\nUse arrays only when multiple independent values are expected.\n\nExamples:\n\n- ignored MAC addresses\n- subnet lists\n- plugin lists\n\n---\n\n# 11. Localization\n\nEvery setting must have localized language strings.\n\nIf the language strings are **not** defined directly inside `config.json`, they must exist in `en_us.json`.\n\nFor example, for:\n\n```text\nUI_DEV_SECTIONS\n```\n\nadd:\n\n```json\n\"UI_DEV_SECTIONS_name\": \"Hide device sections\",\n\"UI_DEV_SECTIONS_description\": \"Select which UI elements to hide on the Devices page.\"\n```\n\nWhen using external language files, `config.json` must reference:\n\n```json\n\"name\": [\n  {\n    \"string\": \"_GLOBAL_LANG_FILES_\"\n  }\n]\n```\n\nThis tells NetAlertX to resolve the display name from the language files.\n\n---\n\n# 12. Source of truth\n\n`app.conf` is the source of truth.\n\nKeep the following in mind:\n\n- User-defined values always come from `app.conf`.\n- Default values from `config.json` are only used when the setting is missing from `app.conf`.\n- Never assume settings are permanently stored in the database.\n\n---\n\n# 13. Plugin-first settings\n\nWhenever possible, define new settings inside a plugin's `config.json`.\n\nAvoid adding hardcoded application settings unless there is a compelling architectural reason.\n\nPlugin settings are the preferred and future-proof approach.\n\n---\n\n# 14. Runtime lifecycle\n\nUnderstand how settings flow through the application.\n\n```text\nconfig.json\n        │\n        │ default values\n        ▼\napp.conf (source of truth)\n        │\n        ▼\nSettings database table\n        │\n        ▼\ntable_settings.json API\n        │\n        ▼\nFrontend (getSetting())\n```\n\nThe database and API are runtime representations only.\n\nThey are regenerated from `app.conf` during initialization.\n\n---\n\n# 15. Access\n\nCode should never read `app.conf` directly.\n\nIn the Frontend, always retrieve settings through:\n\n```javascript\ngetSetting(\"SETTING_NAME\")\n```\n\nAnd in the backend, always retrieve settings through:\n\n```python\nget_setting_value(\"SETTING_NAME\")\n```\n\nThis guarantees the value comes from the generated settings API.\n\n---\n\n# 16. Metadata\n\nEach setting automatically has a corresponding `__metadata` entry generated in `app.conf`.\n\nDo not manually create or modify metadata entries unless working on the settings framework itself.\n\n---\n\n# 17. Backend\n\nBackend code should:\n\n- use the existing configuration helpers\n- avoid duplicated parsing logic\n- gracefully handle missing values\n- avoid hardcoded defaults\n\n---\n\n# 18. Frontend\n\nA correctly defined setting should render automatically in the existing Settings UI.\n\nAvoid writing custom JavaScript unless absolutely necessary.\n\n---\n\n# 19. Prefer configuration over code\n\nIf a behaviour can reasonably be controlled through an existing setting type (boolean, select, integer, array, etc.), introduce a setting instead of hardcoding special-case logic.\n\nConfiguration is preferred over implementation-specific behaviour whenever practical.\n\n---\n\n# 20. Backwards compatibility\n\nAvoid:\n\n- renaming settings\n- changing data types\n- changing semantics\n\nIf unavoidable:\n\n- provide migration logic\n- preserve compatibility where possible\n\n---\n\n# 21. Documentation\n\nEvery user-facing setting should eventually be documented.\n\nInclude:\n\n- purpose\n- accepted values\n- default value\n- examples when useful\n\n---\n\n# 22. Pull request checklist\n\nBefore submitting a PR, verify:\n\n- [ ] Setting follows existing conventions.\n- [ ] Correct category used.\n- [ ] Appropriate type selected.\n- [ ] Validation defined.\n- [ ] Sensible default provided.\n- [ ] Default defined only once.\n- [ ] Name is concise.\n- [ ] Description is clear.\n- [ ] Localization strings added.\n- [ ] `_GLOBAL_LANG_FILES_` used where appropriate.\n- [ ] Plugin-first approach followed where applicable.\n- [ ] Frontend renders automatically.\n- [ ] Backend uses existing configuration helpers.\n- [ ] No unnecessary frontend code added.\n- [ ] No duplicated parsing logic.\n- [ ] Backwards compatibility maintained.\n- [ ] Documentation updated if required.\n\n---\n\n# References\n\n- https://docs.netalertx.com/PLUGINS_DEV_SETTINGS/\n- https://docs.netalertx.com/SETTINGS_SYSTEM/\n- https://docs.netalertx.com/PLUGINS_OVERVIEW/\n\n<!-- File: .gemini/skills/skills-index/SKILL.md (Tokens: ~1817 | Category: anthropic-skill) -->\n---\nname: skills-index\ndescription: Index of all available skills across Gemini CLI (.gemini/skills/), GitHub Copilot (.github/skills/), and Claude Code (.claude/skills/). Load this to find the right skill for a task, or to locate the counterpart skill in another assistant's tree.\n---\n\n# Skills Index — Cross-Reference\n\nThree AI assistants are configured for this project, each with their own skill directory:\n\n- **Gemini CLI** → `.gemini/skills/`\n- **GitHub Copilot** → `.github/skills/`\n- **Claude Code** → `.claude/skills/` (mirrors most, not all, of the shared skills below — see the table's Claude Skill column for which)\n\nSkills with the same purpose exist in more than one, sometimes under different names and with different depth. This index maps them so you can find the richer version when needed. A CI check (`scripts/check_skill_pairs.py`, run as `check-skill-pairs` in `.github/workflows/code-checks.yml`) flags PRs that touch some but not all files in a mirrored group - non-blocking, since some divergence is intentional.\n\n---\n\n## Shared Skills (exist in more than one tree)\n\n| Topic | Gemini Skill | Copilot Skill | Claude Skill | Notes |\n|-------|-------------|--------------|--------------|-------|\n| Testing | `testing-workflow` | `testing-workflow` | `testing-workflow` | All three cover the full-suite-by-default rule, PYTHONPATH, auth/token retrieval, and the `sys.modules` stubbing pitfall |\n| Settings & config | `settings` | `settings-management` | — | Gemini version is more comprehensive (22-point guide + PR checklist); Copilot version covers `ccd()` and `get_setting_value()` usage |\n| MCP activation | `mcp-activation` | `mcp-activation` | — | Gemini version covers Gemini CLI session restart; Copilot version covers VS Code window reload |\n| Project navigation | `project-navigation` | `project-navigation` | — | Copilot version has full path tables and env vars; Gemini version is a brief reference |\n| Plugin dev | `plugin-development` | `plugin-run-development` | `plugin-development` | All three cover data contract, phases, formats, the `RUN_TIMEOUT` kill-timer gotcha (`timeoutMultiplier`/`per_item_timeout()`), and a pre-PR pointer to the Conventions Checklist in `docs/PLUGINS_DEV.md` |\n| Plugin README docs | `plugin-readme` | `plugin-readme` | `plugin-readme` | All three cover README structure, the \"don't re-document settings\" rule, the `docs.netalertx.com` cross-linking convention, and common defects (template leftovers, copy-paste errors) found during a full-repo audit |\n| Devcontainer | `devcontainer-management` | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | — | Gemini combines into one (uses `docker exec`); Copilot splits into 3 focused skills. `devcontainer-configs` also covers Dockerfile generation (root `Dockerfile` → `generate-dockerfile.sh` → `.devcontainer/Dockerfile`, source files not present at build time) - mined from real review comments on PR #1184/#1230. |\n| Install scripts | `install-scripts` | `install-scripts` | `install-scripts` | `install/*` conventions mined from real review comments (#1214/#1230/#1235): uninstall must only remove NetAlertX's own files, never system packages; `check-*.sh` scripts intentionally differ on fail-fast vs. warning-only; `/back` is legacy, not the active code path. |\n| PR review | `pr-analysis` | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist; the standing rule that a repeated review comment becomes a skill update (sourced from jokob's own real PR comments, #1739/#1744); a readability-over-cleverness code-style note from the same source. |\n| Logging | `logging-standards` | `logging-standards` | — | `mylog` levels, message format, what not to log |\n| Scan pipeline internals | `scan-pipeline` | `scan-pipeline` | `scan-pipeline` | `process_scan()` call order and why it's load-bearing, `CurrentScan`/`Events`/`Sessions`/`DevicesView` relationships, how a session actually closes (no `close_session()` exists), and the `FIELD_SPECS` field-write authority mechanism. Complements `database-patterns` (Devices write-path/`*Source` attribution) rather than duplicating it. |\n| Database patterns | `database-patterns` | `database-patterns` | `database-patterns` | Devices table write-path inventory, the `FIELD_SOURCE_MAP`/`*Source` attribution system in `server/db/authoritative_handler.py`, SQLite trigger vs. Python-hook tradeoffs, and event-sourced vs. snapshot audit logging. |\n| PRD writing | `prd-writing` | `prd-writing` | `prd-writing` | Methodology for writing a design doc: challenge the idea, verify every claim against actual code, trace every downstream consumer of a new mechanism, evaluate performance impact against the real schema/indexes, record rejected alternatives and open-issue decisions explicitly, final-check pass before done. |\n| UX/frontend design | `ux-design-patterns` | `ux-design-patterns` | `ux-design-patterns` | Don't invent new UX behavior/visual patterns unless a PRD calls for it - search `front/` for an existing pattern first and reuse it. Priority order for design tradeoffs when several options are reasonable: existing behavior > intuitiveness > information density > usability > utility > uniqueness > industry practices > generic UI. |\n| Skill hygiene | `skill-hygiene` | `skill-hygiene` | `skill-hygiene` | Read before writing/editing any SKILL.md, or any research/audit doc in `.gemini/internal-docs/research/`. Two standing rules: state current behavior only (no \"Correction:\", no \"as of <date>\", no \"caught in review\" narration - that trail belongs in PRDs), and prefer plain, short wording. Includes the grep sweep to run before calling a doc clean. |\n| Plugin review | `plugin-review` | `plugin-review` | `plugin-review` | Reviewer-facing complement to Plugin dev above: no raw SQL in a plugin script - use an existing/new `server/models/*.py` method, not GraphQL (which no plugin reaches). Named exception list for 5 pre-existing core/infra plugins with legitimate direct DB access, a collation-checking step, and a worked example from a real PR (#1788, DOCKERDISC). |\n\n---\n\n## Copilot-Only Skills\n\nNo Gemini equivalent yet:\n\n| Copilot Skill | Purpose |\n|--------------|---------|\n| `api-development` | Creating REST API endpoints |\n| `authentication` | API tokens and 401/403 debugging |\n| `code-standards` | Coding conventions and style rules |\n| `database-reset` | Wipe and regenerate the database and config |\n| `docker-build` | Build Docker images for testing or production |\n| `docker-prune` | Clean unused Docker resources (destructive — requires confirmation) |\n| `sample-data` | Load synthetic device data into the devcontainer |\n\n---\n\n## Gemini-Only Skills\n\nNo Copilot equivalent yet:\n\n| Gemini Skill | Purpose |\n|-------------|---------|\n| `initiative-start` | Research methodology and structured approach for new tasks |\n\n---\n\n## Adding a New Skill\n\nWhen adding a skill, create it in **both** directories to keep both AI systems current:\n\n- `.gemini/skills/<name>/SKILL.md` — auto-discovered by Gemini CLI via YAML frontmatter\n- `.github/skills/<name>/SKILL.md` — add an entry to the skills table in `.github/copilot-instructions.md`\n\nKeep the body content identical between both files. Only the frontmatter `name`/`description` may differ slightly to match each system's discovery heuristics.\n\nIf the skill is high-value enough to also mirror to Claude Code, add `.claude/skills/<name>/SKILL.md` too, and add the group to `GROUPS` in `scripts/check_skill_pairs.py` so drift gets flagged. Claude Code has no `activate_skill()`/`testFailure`/`runTests`/`report_progress` equivalents - adapt any such tool references to plain `Bash` commands instead of copying them verbatim.\n\n<!-- File: .gemini/skills/testing-workflow/SKILL.md (Tokens: ~1257 | Category: anthropic-skill) -->\n---\nname: testing-workflow\ndescription: Read before running tests. Detailed instructions for single, standard unit tests (fast), full suites (slow), handling authentication, and obtaining the API Token. Tests must be run when a job is complete.\n---\n\n# Testing Workflow\nAfter code is developed, tests must be run to ensure the integrity of the final result.\n\n**Crucial:** Tests MUST be run inside the container to access the correct runtime environment (DB, Config, Dependencies).\n\n## 0. Pre-requisites: Environment Check\n\nBefore running any tests, verify you are inside the development container:\n\n```bash\nls -d /workspaces/NetAlertX\n```\n\n**IF** this directory does not exist, you are likely on the host machine. You **MUST** immediately activate the `devcontainer-management` skill to enter the container or run commands inside it.\n\n```text\nactivate_skill(\"devcontainer-management\")\n```\n\n## 1. Full Test Suite (MANDATORY DEFAULT)\n\nUnless the user **explicitly** requests \"fast\" or \"quick\" tests, you **MUST** run the full test suite. **Do not** optimize for time. Comprehensive coverage is the priority over speed.\n\n```bash\ncd /workspaces/NetAlertX; pytest test/\n```\n\n## 2. Fast Unit Tests (Conditional)\n\n**ONLY** use this if the user explicitly asks for \"fast tests\", \"quick tests\", or \"unit tests only\". This **excludes** slow tests marked with `docker` or `feature_complete`.\n\n```bash\ncd /workspaces/NetAlertX; pytest test/ -m 'not docker and not feature_complete'\n```\n\n## 3. Running Specific Tests\n\nTo run a specific file or folder:\n\n```bash\ncd /workspaces/NetAlertX; pytest test/<path_to_test>\n```\n\n*Example:*\n```bash\ncd /workspaces/NetAlertX; pytest test/api_endpoints/test_mcp_extended_endpoints.py\n```\n\n## Authentication & Environment Reset\n\nAuthentication tokens are required to perform certain operations such as manual testing or crafting expressions to work with the web APIs. After making code changes, you MUST reset the environment to ensure the new code is running and verify you have the latest `API_TOKEN`.\n\n1. **Reset Environment:** Run the setup script inside the container.\n   ```bash\n   bash /workspaces/NetAlertX/.devcontainer/scripts/setup.sh\n   ```\n2. **Wait for Stabilization:** Wait at least 5 seconds for services (nginx, python server, etc.) to start.\n   ```bash\n   sleep 5\n   ```\n3. **Obtain Token:** Retrieve the current token from the container.\n   ```bash\n   python3 -c \"from helper import get_setting_value; print(get_setting_value('API_TOKEN'))\"\n   ```\n\nThe retrieved token MUST be used in all subsequent API or test calls requiring authentication.\n\n### Troubleshooting\n\nIf tests fail with 403 Forbidden or empty tokens:\n1. Verify server is running and use the setup script (`/workspaces/NetAlertX/.devcontainer/scripts/setup.sh`) if required.\n2. Verify `app.conf` inside the container: `cat /data/config/app.conf`\n\n## Check for Pre-Existing Failures\n\nBefore attributing failures to your changes, check what was already broken:\n\n```bash\ncd /workspaces/NetAlertX; pytest test/ --tb=no -q 2>&1 | tail -20\n```\n\nDo not fix pre-existing failures unless that is the explicit goal.\n\n## PYTHONPATH\n\nThe test environment is pre-configured with:\n- `/app` — primary location where Python runs in production\n- `/app/server` — symlink to `/workspaces/NetAlertX/server`\n- `/app/server/plugins` — symlink to `/workspaces/NetAlertX/server/plugins`\n- `/opt/venv/lib/pythonX.Y/site-packages`\n- `/workspaces/NetAlertX/test`\n- `/workspaces/NetAlertX/server`\n- `/workspaces/NetAlertX`\n- `/usr/lib/pythonX.Y/site-packages`\n\n## Docker Test Image\n\nIf the Dockerfile or dependencies changed, rebuild the test image before running:\n\n```bash\ndocker buildx build -t netalertx-test .\n```\n\nTakes ~30 seconds; ~90 seconds if the venv stage changed.\n\n## Pitfall: `sys.modules` Stubbing Leaks Across Test Files\n\nSome plugin tests (e.g. `test/plugins/test_ntfy_custom_headers.py`) stub NetAlertX\nmodules (`conf`, `helper`, `models.notification_instance`, etc.) via\n`sys.modules[name] = fake_module` so the plugin script can be imported standalone,\noutside the container. Because `sys.modules` is a single process-wide cache shared\nby the whole pytest session, a fake module inserted by one test file silently\nshadows the real module for every other test file collected afterwards — pytest\nimports all test files during collection, before any test runs, so this can happen\nregardless of alphabetical/directory order.\n\nSymptom: `AttributeError: <module 'models.notification_instance'> does not have\nthe attribute 'get_setting_value'` (or similar) in an unrelated test file, where\nthe module repr has no `from '<path>'` suffix — a giveaway that a stub, not the\nreal module, was resolved.\n\nFix pattern: track which module names your stub actually inserted, and pop them\nback out of `sys.modules` immediately after the one-time import that needed them\n(the already-imported script keeps its bound names regardless):\n\n```python\n_stubbed_module_names = []\n\ndef _stub(name, **attrs):\n    if name not in sys.modules:\n        mod = types.ModuleType(name)\n        for k, v in attrs.items():\n            setattr(mod, k, v)\n        sys.modules[name] = mod\n        _stubbed_module_names.append(name)\n\n# ... _stub(...) calls, then the one-time import ...\nimport ntfy\n\nfor _name in _stubbed_module_names:\n    sys.modules.pop(_name, None)\n```\n\n<!-- File: .github/skills/api-development/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# api-development\n\nPath: `.github/skills/api-development/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/api-development/SKILL.md)\n\n<!-- File: .github/skills/authentication/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# authentication\n\nPath: `.github/skills/authentication/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/authentication/SKILL.md)\n\n<!-- File: .github/skills/code-standards/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# code-standards\n\nPath: `.github/skills/code-standards/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/code-standards/SKILL.md)\n\n<!-- File: .github/skills/database-patterns/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# database-patterns\n\nPath: `.github/skills/database-patterns/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/database-patterns/SKILL.md)\n\n<!-- File: .github/skills/database-reset/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# database-reset\n\nPath: `.github/skills/database-reset/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/database-reset/SKILL.md)\n\n<!-- File: .github/skills/devcontainer-configs/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# devcontainer-configs\n\nPath: `.github/skills/devcontainer-configs/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-configs/SKILL.md)\n\n<!-- File: .github/skills/devcontainer-services/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# devcontainer-services\n\nPath: `.github/skills/devcontainer-services/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-services/SKILL.md)\n\n<!-- File: .github/skills/devcontainer-setup/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# devcontainer-setup\n\nPath: `.github/skills/devcontainer-setup/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/devcontainer-setup/SKILL.md)\n\n<!-- File: .github/skills/docker-build/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# docker-build\n\nPath: `.github/skills/docker-build/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/docker-build/SKILL.md)\n\n<!-- File: .github/skills/docker-prune/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# docker-prune\n\nPath: `.github/skills/docker-prune/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/docker-prune/SKILL.md)\n\n<!-- File: .github/skills/mcp-activation/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# mcp-activation\n\nPath: `.github/skills/mcp-activation/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/mcp-activation/SKILL.md)\n\n<!-- File: .github/skills/plugin-run-development/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# plugin-run-development\n\nPath: `.github/skills/plugin-run-development/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/plugin-run-development/SKILL.md)\n\n<!-- File: .github/skills/project-navigation/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# project-navigation\n\nPath: `.github/skills/project-navigation/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/.github/skills/project-navigation/SKILL.md)\n\n<!-- File: .github/skills/sample-data/SKILL.md (Tokens: ~180 | Category: anthropic-skill) -->\n---\nname: netalertx-sample-data\ndescription: Load synthetic device data into the devcontainer. Use this when asked to load sample devices, seed data, import test devices, populate database, or generate test data.\n---\n\n# Sample Data Loading\n\nGenerates synthetic device inventory and imports it via the `/devices/import` API endpoint.\n\n## Command\n\n```bash\ncd /workspaces/NetAlertX/.devcontainer/scripts\n./load-devices.sh\n```\n\n## Environment\n\n- `CSV_PATH`: defaults to `/tmp/netalertx-devices.csv`\n\n## Prerequisites\n\n- Backend must be running\n- API must be accessible\n\n## What It Does\n\n1. Generates synthetic device records (MAC addresses, IPs, names, vendors)\n2. Creates CSV file at `$CSV_PATH`\n3. POSTs to `/devices/import` endpoint\n4. Devices appear in database and UI\n\n<!-- File: .github/skills/settings-management/SKILL.md (Tokens: ~1148 | Category: anthropic-skill) -->\n---\nname: netalertx-settings-management\ndescription: Manage NetAlertX configuration settings. Use this when asked to add setting, read config, get_setting_value, ccd, or configure options.\n---\n\n# Settings Management\n\n## Quick Reference\n\n**Read a setting (backend):**\n```python\nfrom helper import get_setting_value\nvalue = get_setting_value('SETTING_NAME')\n```\nNever hardcode ports, secrets, or configuration values. Always use `get_setting_value()`.\n\n**Read a setting (frontend):**\n```javascript\ngetSetting(\"SETTING_NAME\")\n```\n\n**Add a core setting** — use `ccd()` in `server/initialise.py`:\n```python\nccd('SETTING_NAME', 'default_value', 'description')\n```\n\n**Add a plugin setting** — define in the plugin's `config.json` under the `settings` key.\n\n| File | Purpose |\n|------|---------|\n| `/data/config/app.conf` | Runtime config (source of truth, modified by app) |\n| `back/app.conf` | Default config (template) |\n\nUse `APP_CONF_OVERRIDE` for settings that must be set before startup.\n\n---\n\n# Development Guide\n\nThe goal is for every setting to be:\n\n- Declarative and self-documenting\n- Automatically rendered by the Settings UI\n- Fully localized\n- Backwards compatible\n- Easy to maintain\n\n---\n\n## 1. Follow existing patterns\n\nBefore creating a new setting, search for similar ones. Reuse existing naming conventions, types, and validation patterns. Follow the style already used by similar plugins. Avoid introducing new setting types unless absolutely necessary.\n\n---\n\n## 2. Setting naming\n\nSetting keys use uppercase snake case: `UI_DEV_SECTIONS`, `SCAN_INTERVAL`, `MQTT_HOST`.\nNames should clearly describe the purpose.\n\n---\n\n## 3. Categories\n\nPlace settings into the most appropriate category. Keep related settings together. Avoid creating new categories unless there is a clear need.\n\n---\n\n## 4. Choose the correct type\n\nUse the simplest type that correctly models the value: `string`, `integer`, `float`, `boolean`, `password`, `select`, `array`, `textarea`. Avoid encoding structured JSON inside string settings.\n\n---\n\n## 5. Validation\n\nAlways define validation whenever appropriate (min/max values, regex, allowed options). Reject invalid configuration rather than silently accepting it.\n\n---\n\n## 6. Defaults\n\nProvide sensible defaults that work for a fresh installation. The `default_value` in `config.json` is the single source of truth — do not duplicate it in Python or JavaScript.\n\n---\n\n## 7. Names and descriptions\n\nNames should be concise. Descriptions should explain what the setting does, when to use it, and any important side effects — not implementation details.\n\n---\n\n## 8. Boolean settings\n\nPrefer verbs: Enable, Disable, Require, Allow, Ignore, Show, Hide. Examples: *Enable MQTT*, *Require NICs Online*, *Hide Offline Devices*.\n\n---\n\n## 9. Select settings\n\nUse when users must choose from predefined values. Each option must have a meaningful label — never require users to remember internal values.\n\n---\n\n## 10. Array settings\n\nUse only when multiple independent values are expected (e.g., ignored MACs, subnet lists, plugin lists).\n\n---\n\n## 11. Localization\n\nEvery setting must have localized language strings. If not defined directly in `config.json`, they must exist in `en_us.json`.\n\nExample for `UI_DEV_SECTIONS`:\n```json\n\"UI_DEV_SECTIONS_name\": \"Hide device sections\",\n\"UI_DEV_SECTIONS_description\": \"Select which UI elements to hide on the Devices page.\"\n```\n\nWhen using external language files, `config.json` must reference:\n```json\n\"name\": [{ \"string\": \"_GLOBAL_LANG_FILES_\" }]\n```\n\n---\n\n## 12. Source of truth\n\n`app.conf` is the source of truth. User-defined values always come from `app.conf`; defaults from `config.json` apply only when a setting is absent. Never read `app.conf` directly — always go through `get_setting_value()`.\n\n---\n\n## 13. Plugin-first settings\n\nWhenever possible, define new settings inside a plugin's `config.json`. Avoid adding hardcoded application settings unless there is a compelling architectural reason.\n\n---\n\n## 14. Runtime lifecycle\n\n```text\nconfig.json (defaults)\n    → app.conf (source of truth)\n    → Settings database table\n    → table_settings.json API\n    → Frontend getSetting() / Backend get_setting_value()\n```\n\nThe database and API are runtime representations only, regenerated from `app.conf` at init.\n\n---\n\n## 15. Backwards compatibility\n\nAvoid renaming settings, changing data types, or changing semantics. If unavoidable, provide migration logic and preserve compatibility where possible.\n\n---\n\n## 16. Pull request checklist\n\n- [ ] Follows existing naming conventions (uppercase snake case)\n- [ ] Correct category used\n- [ ] Appropriate type selected, validation defined\n- [ ] Sensible default — defined only once (in `config.json`)\n- [ ] Name concise, description clear (no implementation details)\n- [ ] Localization strings added; `_GLOBAL_LANG_FILES_` used where appropriate\n- [ ] Plugin-first approach followed where applicable\n- [ ] Frontend renders automatically without custom JavaScript\n- [ ] Backend uses `get_setting_value()` — no direct `app.conf` reads\n- [ ] Backwards compatibility maintained\n- [ ] Documentation updated if required\n\n---\n\n## References\n\n- https://docs.netalertx.com/PLUGINS_DEV_SETTINGS/\n- https://docs.netalertx.com/SETTINGS_SYSTEM/\n\n<!-- File: .github/skills/skills-overview/SKILL.md (Tokens: ~1814 | Category: anthropic-skill) -->\n---\nname: skills-overview\ndescription: Index of all available skills across GitHub Copilot (.github/skills/), Gemini CLI (.gemini/skills/), and Claude Code (.claude/skills/). Load this to find the right skill for a task, or to locate the counterpart skill in another assistant's tree.\n---\n\n# Skills Index — Cross-Reference\n\nThree AI assistants are configured for this project, each with their own skill directory:\n\n- **GitHub Copilot** → `.github/skills/`\n- **Gemini CLI** → `.gemini/skills/`\n- **Claude Code** → `.claude/skills/` (mirrors most, not all, of the shared skills below — see the table's Claude Skill column for which)\n\nSkills with the same purpose exist in more than one, sometimes under different names and with different depth. This index maps them so you can find the richer version when needed. A CI check (`scripts/check_skill_pairs.py`, run as `check-skill-pairs` in `.github/workflows/code-checks.yml`) flags PRs that touch some but not all files in a mirrored group - non-blocking, since some divergence is intentional.\n\n---\n\n## Shared Skills (exist in more than one tree)\n\n| Topic | Copilot Skill | Gemini Skill | Claude Skill | Notes |\n|-------|--------------|--------------|--------------|-------|\n| Testing | `testing-workflow` | `testing-workflow` | `testing-workflow` | All three cover the full-suite-by-default rule, PYTHONPATH, auth/token retrieval, and the `sys.modules` stubbing pitfall |\n| Settings & config | `settings-management` | `settings` | — | Gemini version is more comprehensive (22-point guide + PR checklist); Copilot version covers `ccd()` and `get_setting_value()` usage |\n| MCP activation | `mcp-activation` | `mcp-activation` | — | Copilot version covers VS Code window reload; Gemini version covers Gemini CLI session restart |\n| Project navigation | `project-navigation` | `project-navigation` | — | Copilot version has full path tables and env vars; Gemini version is a brief reference |\n| Plugin dev | `plugin-run-development` | `plugin-development` | `plugin-development` | All three cover data contract, phases, formats, the `RUN_TIMEOUT` kill-timer gotcha (`timeoutMultiplier`/`per_item_timeout()`), and a pre-PR pointer to the Conventions Checklist in `docs/PLUGINS_DEV.md` |\n| Plugin README docs | `plugin-readme` | `plugin-readme` | `plugin-readme` | All three cover README structure, the \"don't re-document settings\" rule, the `docs.netalertx.com` cross-linking convention, and common defects (template leftovers, copy-paste errors) found during a full-repo audit |\n| Devcontainer | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | `devcontainer-management` | — | Copilot splits into 3 focused skills; Gemini combines into one (uses `docker exec`). `devcontainer-configs` also covers Dockerfile generation (root `Dockerfile` → `generate-dockerfile.sh` → `.devcontainer/Dockerfile`, source files not present at build time) - mined from real review comments on PR #1184/#1230. |\n| Install scripts | `install-scripts` | `install-scripts` | `install-scripts` | `install/*` conventions mined from real review comments (#1214/#1230/#1235): uninstall must only remove NetAlertX's own files, never system packages; `check-*.sh` scripts intentionally differ on fail-fast vs. warning-only; `/back` is legacy, not the active code path. |\n| PR review | `pr-analysis` | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist; the standing rule that a repeated review comment becomes a skill update (sourced from jokob's own real PR comments, #1739/#1744); a readability-over-cleverness code-style note from the same source. |\n| Logging | `logging-standards` | `logging-standards` | — | `mylog` levels, message format, what not to log |\n| Scan pipeline internals | `scan-pipeline` | `scan-pipeline` | `scan-pipeline` | `process_scan()` call order and why it's load-bearing, `CurrentScan`/`Events`/`Sessions`/`DevicesView` relationships, how a session actually closes (no `close_session()` exists), and the `FIELD_SPECS` field-write authority mechanism. Complements `database-patterns` (Devices write-path/`*Source` attribution) rather than duplicating it. |\n| Database patterns | `database-patterns` | `database-patterns` | `database-patterns` | Devices table write-path inventory, the `FIELD_SOURCE_MAP`/`*Source` attribution system in `server/db/authoritative_handler.py`, SQLite trigger vs. Python-hook tradeoffs, and event-sourced vs. snapshot audit logging. |\n| PRD writing | `prd-writing` | `prd-writing` | `prd-writing` | Methodology for writing a design doc: challenge the idea, verify every claim against actual code, trace every downstream consumer of a new mechanism, evaluate performance impact against the real schema/indexes, record rejected alternatives and open-issue decisions explicitly, final-check pass before done. |\n| UX/frontend design | `ux-design-patterns` | `ux-design-patterns` | `ux-design-patterns` | Don't invent new UX behavior/visual patterns unless a PRD calls for it - search `front/` for an existing pattern first and reuse it. Priority order for design tradeoffs when several options are reasonable: existing behavior > intuitiveness > information density > usability > utility > uniqueness > industry practices > generic UI. |\n| Skill hygiene | `skill-hygiene` | `skill-hygiene` | `skill-hygiene` | Read before writing/editing any SKILL.md, or any research/audit doc in `.gemini/internal-docs/research/`. Two standing rules: state current behavior only (no \"Correction:\", no \"as of <date>\", no \"caught in review\" narration - that trail belongs in PRDs), and prefer plain, short wording. Includes the grep sweep to run before calling a doc clean. |\n| Plugin review | `plugin-review` | `plugin-review` | `plugin-review` | Reviewer-facing complement to Plugin dev above: no raw SQL in a plugin script - use an existing/new `server/models/*.py` method, not GraphQL (which no plugin reaches). Named exception list for 5 pre-existing core/infra plugins with legitimate direct DB access, a collation-checking step, and a worked example from a real PR (#1788, DOCKERDISC). |\n\n---\n\n## Copilot-Only Skills\n\nNo Gemini equivalent yet:\n\n| Skill | Purpose |\n|-------|---------|\n| `api-development` | Creating REST API endpoints |\n| `authentication` | API tokens and 401/403 debugging |\n| `code-standards` | Coding conventions and style rules |\n| `database-reset` | Wipe and regenerate the database and config |\n| `docker-build` | Build Docker images for testing or production |\n| `docker-prune` | Clean unused Docker resources (destructive — requires confirmation) |\n| `sample-data` | Load synthetic device data into the devcontainer |\n\n---\n\n## Gemini-Only Skills\n\nNo Copilot equivalent yet:\n\n| Skill | Purpose |\n|-------|---------|\n| `initiative-start` | Research methodology and structured approach for new tasks |\n\n---\n\n## Adding a New Skill\n\nWhen adding a skill, create it in **both** directories to keep both AI systems current:\n\n- `.github/skills/<name>/SKILL.md` — add an entry to the skills table in `.github/copilot-instructions.md`\n- `.gemini/skills/<name>/SKILL.md` — auto-discovered by Gemini CLI via YAML frontmatter\n\nKeep the body content identical between both files. Only the frontmatter `name`/`description` may differ slightly to match each system's discovery heuristics.\n\nIf the skill is high-value enough to also mirror to Claude Code, add `.claude/skills/<name>/SKILL.md` too, and add the group to `GROUPS` in `scripts/check_skill_pairs.py` so drift gets flagged. Claude Code has no `activate_skill()`/`testFailure`/`runTests`/`report_progress` equivalents - adapt any such tool references to plain `Bash` commands instead of copying them verbatim.\n\n<!-- File: .github/skills/testing-workflow/SKILL.md (Tokens: ~878 | Category: anthropic-skill) -->\n---\nname: netalertx-testing-workflow\ndescription: Run and debug tests in the NetAlertX devcontainer. Use this when asked to run tests, check test failures, debug failing tests, or execute pytest.\n---\n\n# Testing Workflow\n\n## Pre-Flight Check (MANDATORY)\n\nBefore running any tests, always check for existing failures first:\n\n1. Use the `testFailure` tool to gather current failure information\n2. Review the failures to understand what's already broken\n3. Only then proceed with test execution\n\n## Running Tests\n\nUse VS Code's testing interface or the `runTests` tool with appropriate parameters:\n\n- To run all tests: invoke runTests without file filter\n- To run specific test file: invoke runTests with the test file path\n- To run failed tests only: invoke runTests with `--lf` flag\n\n## Test Location\n\nTests live in `test/` directory. App code is under `server/`.\n\nPYTHONPATH is preconfigured to include the following which should meet all needs:\n- `/app` # the primary location where python runs in the production system\n- `/app/server` # symbolic link to /wprkspaces/NetAlertX/server\n- `/app/server/plugins` # symbolic link to /workspaces/NetAlertX/server/plugins\n- `/opt/venv/lib/pythonX.Y/site-packages`\n- `/workspaces/NetAlertX/test`\n- `/workspaces/NetAlertX/server`\n- `/workspaces/NetAlertX`\n- `/usr/lib/pythonX.Y/site-packages`\n\n## Authentication in Tests\n\nRetrieve `API_TOKEN` using Python (not shell):\n\n```python\nfrom helper import get_setting_value\ntoken = get_setting_value(\"API_TOKEN\")\n```\n\n## Troubleshooting 403 Forbidden\n\n1. Ensure backend is running (use devcontainer-services skill)\n2. Verify config loaded: `get_setting_value(\"API_TOKEN\")` returns non-empty\n3. Re-run startup if needed (use devcontainer-setup skill)\n\n## Docker Test Image\n\nIf container changes affect tests, rebuild the test image first:\n\n```bash\ndocker buildx build -t netalertx-test .\n```\n\nThis takes ~30 seconds unless venv stage changes (~90s).\n\n## Pitfall: `sys.modules` Stubbing Leaks Across Test Files\n\nSome plugin tests (e.g. `test/plugins/test_ntfy_custom_headers.py`) stub NetAlertX\nmodules (`conf`, `helper`, `models.notification_instance`, etc.) via\n`sys.modules[name] = fake_module` so the plugin script can be imported standalone,\noutside the container. Because `sys.modules` is a single process-wide cache shared\nby the whole pytest session, a fake module inserted by one test file silently\nshadows the real module for every other test file collected afterwards — pytest\nimports all test files during collection, before any test runs, so this can happen\nregardless of alphabetical/directory order.\n\nSymptom: `AttributeError: <module 'models.notification_instance'> does not have\nthe attribute 'get_setting_value'` (or similar) in an unrelated test file, where\nthe module repr has no `from '<path>'` suffix — a giveaway that a stub, not the\nreal module, was resolved.\n\nFix pattern: track which module names your stub actually inserted, and pop them\nback out of `sys.modules` immediately after the one-time import that needed them\n(the already-imported script keeps its bound names regardless):\n\n```python\n_stubbed_module_names = []\n\ndef _stub(name, **attrs):\n    if name not in sys.modules:\n        mod = types.ModuleType(name)\n        for k, v in attrs.items():\n            setattr(mod, k, v)\n        sys.modules[name] = mod\n        _stubbed_module_names.append(name)\n\n# ... _stub(...) calls, then the one-time import ...\nimport ntfy\n\nfor _name in _stubbed_module_names:\n    sys.modules.pop(_name, None)\n```\n\nReproduce cross-file pollution locally by running the suspect file together with\nthe affected one in a single pytest invocation (order matters less than you'd\nthink — collection happens for all files first):\n\n```bash\npytest test/plugins/test_ntfy_custom_headers.py test/backend/test_notification_templates.py -v\n```\n\n<!-- File: server/plugins/__template/config.json (Tokens: ~300 | Category: anthropic-skill) -->\n# __template Skill\n\nPath: `server/plugins/__template/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/__template/config.json)\n\n<!-- File: server/plugins/_publisher_apprise/config.json (Tokens: ~300 | Category: anthropic-skill) -->\n# _publisher_apprise Skill\n\nPath: `server/plugins/_publisher_apprise/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_apprise/config.json)\n\n<!-- File: server/plugins/_publisher_email/config.json (Tokens: ~300 | Category: anthropic-skill) -->\n# _publisher_email Skill\n\nPath: `server/plugins/_publisher_email/config.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/netalertx/NetAlertX/HEAD/server/plugins/_publisher_email/config.json)\n\n<!-- ... +105 more modular skills available via /api/skills/netalertx/NetAlertX?path={path} -->\n</yakaai_skills>"}