{"owner":"nicolargo","repo":"glances","hasSkills":true,"totalSkillsCount":9,"totalTokensCount":5816,"categories":["claude-rule","anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md",".claude/skills/review.md",".claude/skills/debug.md",".claude/skills/docker.md",".claude/skills/new-plugin.md",".claude/skills/lint.md",".claude/skills/new-export.md",".claude/skills/test.md",".claude/skills/webui.md"],"skills":{"CLAUDE.md":"# CLAUDE.md — Glances Maintainer Context\n\nBehavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.\n\n## 1. Think Before Coding\n\n**Don't assume. Don't hide confusion. Surface tradeoffs.**\n\nBefore implementing:\n\n- State your assumptions explicitly. If uncertain, ask.\n- If multiple interpretations exist, present them - don't pick silently.\n- If a simpler approach exists, say so. Push back when warranted.\n- If something is unclear, stop. Name what's confusing. Ask.\n\n## 2. Simplicity First\n\n**Minimum code that solves the problem. Nothing speculative.**\n\n- No features beyond what was asked.\n- No abstractions for single-use code.\n- No \"flexibility\" or \"configurability\" that wasn't requested.\n- No error handling for impossible scenarios.\n- If you write 200 lines and it could be 50, rewrite it.\n\nAsk yourself: \"Would a senior engineer say this is overcomplicated?\" If yes, simplify.\n\n## 3. Surgical Changes\n\n**Touch only what you must. Clean up only your own mess.**\n\nWhen editing existing code:\n\n- Don't \"improve\" adjacent code, comments, or formatting.\n- Don't refactor things that aren't broken.\n- Match existing style, even if you'd do it differently.\n- If you notice unrelated dead code, mention it - don't delete it.\n\nWhen your changes create orphans:\n\n- Remove imports/variables/functions that YOUR changes made unused.\n- Don't remove pre-existing dead code unless asked.\n\nThe test: Every changed line should trace directly to the user's request.\n\n## 4. Goal-Driven Execution\n\n**Define success criteria. Loop until verified.**\n\nTransform tasks into verifiable goals:\n\n- \"Add validation\" → \"Write tests for invalid inputs, then make them pass\"\n- \"Fix the bug\" → \"Write a test that reproduces it, then make it pass\"\n- \"Refactor X\" → \"Ensure tests pass before and after\"\n\nFor multi-step tasks, state a brief plan:\n\n```md\n1. [Step] → verify: [check]\n2. [Step] → verify: [check]\n3. [Step] → verify: [check]\n```\n\nStrong success criteria let you loop independently. Weak criteria (\"make it work\") require constant clarification.\n\n## Additionnal context\n\n### Tech stack\n\n| Layer | Technology |\n| --- | --- |\n| Backend | Python, psutil, FastAPI (REST API), curses (TUI) |\n| Frontend | Vue.js 3, plain CSS (design tokens) |\n| Export plugins | InfluxDB, MongoDB, MQTT, DuckDB, and others |\n| Infrastructure | GitHub Actions, Helm/Kubernetes, Snap (snapcraft) |\n| LLM abstraction | LiteLLM (multi-provider) |\n| GPU/NPU monitoring | pynvml, sysfs/debugfs |\n\n### Code principles\n\n#### Exception handling for Snap confinement\n\nWrap the `open()` call inside `try/except`, not just the `read()`. Snap's strict\nconfinement blocks host file access at the open stage, not the read stage.\n\n### Security\n\nGlances runs unauthenticated by default — this is intentional and documented.\nMost users deploy on private networks for personal use. See global CLAUDE.md\nfor the general OSS security philosophy (4 rules).\n\n#### Sensitive endpoints\n\n- `/api/5/config` and `/api/5/args` — never expose credentials in plain text\n  (InfluxDB passwords, MongoDB tokens, MQTT passphrases, SSL key paths, etc.)\n  for unauthenticated access. `/api/5/config` serves `as_dict_secure()` when\n  `[outputs] password` is set, and only `as_dict_public()` otherwise;\n  `/api/5/args` is always redacted (`glances/routes.py`).\n- CORS: `allow_origins=[\"*\"]` + `allow_credentials=True` is invalid per the\n  CORS spec and reflected by Starlette. Default: `allow_credentials=False`.\n- DNS rebinding: `TrustedHostMiddleware` via `webui_allowed_hosts` (optional).\n  The MCP endpoint is already protected via `mcp_allowed_hosts` +\n  `TransportSecuritySettings` in `glances/outputs/glances_mcp.py`.\n\n### Contribution management\n\n#### Before merging a PR\n\n- [ ] The code is actively used (no dead code)\n- [ ] Existing tests pass\n- [ ] New behaviour is covered by tests\n- [ ] New configuration keys are documented\n- [ ] Breaking changes are identified and documented\n- [ ] The PR targets the correct branch\n- [ ] Pre-commit hooks pass (`make pre-commit` — supersedes `make lint && make format`)\n\n#### Glances-specific output formats\n\n| Deliverable | Format |\n| --- | --- |\n| Changelog entry | `.rst` file following the `NEWS.rst` format |\n| Helm Chart | `.tgz` archive or structured directory |\n\n### UI / TUI\n\n#### Web UI\n\nStack: Vue.js 3 + plain CSS (design tokens in\n`glances/outputs/static/css/glances.css`).\n\nDesign principles:\n\n- Strict typographic consistency across all plugins (size, weight, opacity,\n  letter-spacing).\n- Pixel-perfect sparkline alignment (CSS grid, fixed row heights).\n- Footer = vertical alert list (up to 10 entries), not a single horizontal row.\n- No gauges — prefer sparklines with an inline current value.\n\n#### TUI (curses)\n\n- 256-colour system with automatic fallback for limited terminals.\n- Lightweight hierarchical separators for information density.\n\n---\n\n**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.\n",".claude/skills/review.md":"# Skill: Review Code Changes\n\nReview the current changes (staged or unstaged) against Glances project guidelines.\n\n## Instructions\n\n1. Get the diff to review:\n   - If the user provides a PR number, fetch it with `gh pr diff <number>`\n   - Otherwise, use `git diff` (unstaged) and `git diff --cached` (staged)\n   - If a branch is specified, use `git diff <base>...<branch>`\n\n2. Check each item on this checklist:\n\n### Correctness\n- [ ] No dead code introduced (unused functions, imports, variables)\n- [ ] Exception handling wraps the right operation (e.g., `open()` not `read()` for Snap confinement)\n- [ ] No silent exception swallowing (`except: pass` or bare `except`)\n- [ ] No O(n²) patterns where O(n) is possible (linear search in a loop → use dict/set)\n- [ ] No `list.pop(0)` for queues → suggest `collections.deque(maxlen=N)`\n\n### Security\n- [ ] No credentials/secrets exposed in plain text\n- [ ] No new endpoints without considering auth implications\n- [ ] Sensitive config keys filtered from API responses\n- [ ] No command injection, XSS, or SQL injection vectors\n\n### Compatibility\n- [ ] Default behaviour unchanged (or breaking change explicitly documented)\n- [ ] Cross-platform considerations (Linux/macOS/Windows)\n- [ ] Configuration keys documented in `conf/glances.conf`\n- [ ] PR targets the right branch: `develop` for Glances v5, `support/glancesv4` for a v4 bugfix or security fix\n\n### Style\n- [ ] Code passes `make pre-commit` (supersedes `make format && make lint`)\n- [ ] No unnecessary comments, docstrings, or type annotations on unchanged code\n- [ ] Plugin/export conventions followed (class naming, auto-discovery, fields_description)\n\n3. Report findings grouped by severity:\n   - **Blocking** — must fix before merge\n   - **Suggestion** — improvement, not required\n   - **Nit** — style/cosmetic, optional\n\n4. Use the tone from CLAUDE.md: acknowledge quality, label problems as \"blocking concern\", invite collaboration.\n",".claude/skills/debug.md":"# Skill: Debug a Glances Issue\n\nHelp diagnose and fix a bug in Glances.\n\n## Instructions\n\n1. **Understand the issue**: Ask for or identify:\n   - Error message or traceback\n   - Glances version and OS\n   - How to reproduce (standalone, web server, client/server, Docker?)\n   - Relevant config section from `glances.conf`\n\n2. **Locate the code path**: Based on the mode and plugin involved:\n   - Startup / CLI: `glances/main.py`\n   - Standalone TUI: `glances/outputs/glances_curses.py` (`Tui`) → `glances/outputs/curses_renderer.py`\n   - Web server: `glances/webserver.py` → `glances/routes.py`\n   - WebUI: `glances/outputs/static/js/`\n   - Client/server: `glances/client.py` (`-c`, over the REST API) / `glances/webserver.py` (`-s`)\n   - Scheduler / alerts: `glances/scheduler.py` / `glances/alerts.py`\n   - Plugin issue: `glances/plugins/<name>/model.py` (TUI layout: `render_curses.py`)\n   - Export issue: `glances/exports/glances_<name>/export.py`\n   - Process list: `glances/processes.py`\n   - Configuration: `glances/config.py`\n\n3. **Run Glances in debug mode** to get detailed logs:\n   ```bash\n   .venv-uv/bin/uv run python -m glances -d 2>&1 | tail -100\n   ```\n\n4. **Reproduce with tests** when possible:\n   ```bash\n   .venv-uv/bin/uv run pytest tests/test_plugin_<name>.py -v -k \"test_name\" 2>&1\n   ```\n\n5. **Fix and verify**:\n   - Apply surgical, atomic edits\n   - Run the relevant test suite\n   - Run `make format && make lint`\n   - Verify no regressions with `make test`\n\n## Common pitfalls\n\n- **psutil version differences**: Some psutil APIs behave differently across OS — always check `glances/globals.py` for platform flags (`LINUX`, `MACOS`, `WINDOWS`, `BSD`)\n- **Plugin update cycle**: a plugin has no payload before its first update (`GET /api/5/<plugin>` → `200 null`), and `rate: True` fields are absent on the first cycle — handle gracefully\n- **Snap confinement**: `PermissionError` at `open()`, not at `read()` — wrap the right call\n- **Docker socket access**: Requires mounting `/var/run/docker.sock` and proper permissions\n",".claude/skills/docker.md":"# Skill: Build and Run Docker Images\n\nBuild, run, and troubleshoot Glances Docker images locally.\n\n## Instructions\n\n### Build images\n```bash\nmake docker                    # Build all images (Alpine + Ubuntu, full + minimal)\nmake docker-alpine-full        # Alpine full image\nmake docker-alpine-minimal     # Alpine minimal image\nmake docker-ubuntu-full        # Ubuntu full image\nmake docker-ubuntu-minimal     # Ubuntu minimal image\n```\n\nRequires Docker Buildx (`apt install docker-buildx` on Ubuntu).\n\n### Run containers\n```bash\nmake run-docker-alpine-full    # Run Alpine full in console mode\nmake run-docker-ubuntu-full    # Run Ubuntu full in console mode\n```\n\nDefault run options: `--rm --pid host --network host` with Docker/Podman socket mounts.\n\n### Security scan\n```bash\nmake trivy-docker              # Run Trivy on all local images\n```\n\n### Dockerfiles\nLocated in `docker-files/`:\n- `alpine.Dockerfile` — multi-stage: `minimal` and `full` targets\n- `ubuntu.Dockerfile` — multi-stage: `minimal` and `full` targets\n\n### Key considerations\n- Prefer `SYS_PTRACE` capability over `privileged: true`\n- Mount Docker socket read-only: `-v /var/run/docker.sock:/var/run/docker.sock:ro`\n- For Kubernetes: use DaemonSet (one pod per node) for system monitoring\n- The images run Glances v5 (`python -m glances ${GLANCES_OPT}`); in server mode (`-w`) bind to `0.0.0.0` (`bind_address` in `docker-compose/glances.conf`, or `-B 0.0.0.0`), v5 defaults to loopback\n- CI builds Docker images on `develop` (the `dev` image) and on tags only (see `.github/workflows/build_docker.yml`); a pre-release tag (`v5.0.0b1`, `rc`) never moves `latest`\n\n### Troubleshooting\n- **Socket permission errors**: Ensure the user is in the `docker` group or use Podman\n- **Multi-arch builds**: CI uses QEMU for cross-platform (ARM64) — local builds are native only by default\n- **Build timeouts**: Multi-arch builds can take 20-40 min; CI timeout is 60 min\n",".claude/skills/new-plugin.md":"# Skill: Create a New Plugin\n\nScaffold a new Glances monitoring plugin following project conventions.\n\n## Instructions\n\nAsk the user for:\n1. **Plugin name** (lowercase, e.g., `battery`)\n2. **What it monitors** (brief description)\n3. **Data source** (psutil, sysfs, external library, etc.)\n\nThen create the plugin following this structure:\n\n### File structure\n\n```\nglances/plugins/<name>/\n    __init__.py         # Package marker (docstring only)\n    model.py            # Plugin implementation (class PluginModel)\n    render_curses.py    # OPTIONAL — per-plugin TUI layout\n```\n\nNo registration needed — `discover_plugins()` (`glances/main.py`) imports\nevery `glances.plugins.<name>.model`. Its place in the TUI comes from the\nslot tuples in `glances/outputs/curses_renderer.py` (`LEFT_SLOT`,\n`RIGHT_SLOT`, …). The WebUI needs `glances/outputs/static/js/Plugin<Name>.vue`\nand one entry in `glances/outputs/static/js/plugins/index.js`.\n\n### Template\n\n`model.py` must follow this pattern (see `SKILL-plugin.md` for the full\ncontract and `glances/plugins/uptime/model.py` for a short example):\n\n```python\n#\n# Glances - An eye on your system\n#\n# SPDX-FileCopyrightText: 2026 Nicolas Hennion <nicolas@nicolargo.com>\n#\n# SPDX-License-Identifier: LGPL-3.0-only\n#\n\n\"\"\"Glances — <name> plugin (scalar).\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nfrom typing import Any, ClassVar\n\nimport psutil\n\nfrom glances.plugins.plugin.base import GlancesPluginBase\n\n\nclass PluginModel(GlancesPluginBase[dict]):\n    \"\"\"<Name> plugin (scalar).\"\"\"\n\n    plugin_name: ClassVar[str] = \"<name>\"\n    IS_COLLECTION: ClassVar[bool] = False\n\n    fields_description: ClassVar[dict[str, dict[str, Any]]] = {\n        \"field_name\": {\n            \"description\": \"Human-readable description.\",\n            \"unit\": \"percent\",  # percent, number, bytes, bytespers, seconds, string, ...\n            \"watched\": True,\n            \"default_thresholds\": {\"careful\": 50.0, \"warning\": 70.0, \"critical\": 90.0},\n        },\n    }\n\n    async def _grab_stats(self) -> dict:\n        \"\"\"Collect the raw stats. Blocking calls go through asyncio.to_thread().\"\"\"\n        value = await asyncio.to_thread(psutil.cpu_percent)\n        return {\"field_name\": value}\n```\n\n### Key conventions\n\n- Class name: `PluginModel` (exactly), in `glances/plugins/<name>/model.py`\n- Inherit from `GlancesPluginBase[dict]` (scalar) or `GlancesPluginBase[list]`\n  (collection, `IS_COLLECTION = True`, exactly one field with `primary_key: True`)\n- `_grab_stats()` is the only mandatory hook; never override `update()`\n- `fields_description` declares metadata for each field (unit, thresholds,\n  `rate`, `watched`, `exportable`); undeclared fields are stripped\n- Keep `model.py` free of curses / UI code — layout goes in `render_curses.py`\n- No SNMP: SNMP is not ported to v5\n\n### After creation\n\n1. Create a test file: `tests/test_plugin_<name>.py` (and\n   `tests/test_plugin_<name>_render_curses.py` with a `render_curses.py`)\n2. Run `make test-plugins` to verify\n3. Add configuration section in `conf/glances.conf` if needed\n",".claude/skills/lint.md":"# Skill: Lint and Format\n\nRun code formatting and linting on the Glances codebase.\n\n## Instructions\n\n1. Run formatting first, then linting:\n   ```bash\n   make format && make lint\n   ```\n2. If there are remaining issues that `--fix` could not resolve, list them and suggest manual fixes.\n3. Optionally, if the user asks for a full check, run all pre-commit hooks:\n   ```bash\n   make pre-commit\n   ```\n\n## Tools used\n\n- **Ruff** for both formatting and linting (configured in `pyproject.toml`)\n- **Pre-commit hooks** include: ruff, gitleaks (secret detection), shellcheck, and others\n\n## Notes\n\n- The virtualenv must already exist (`.venv-uv/`). If not, run `make venv-dev` first.\n- Never change ruff configuration without explicit approval — linting rules are shared across all contributors.\n",".claude/skills/new-export.md":"# Skill: Create a New Export Module\n\nScaffold a new Glances export module following project conventions.\n\n## Instructions\n\nAsk the user for:\n1. **Export name** (lowercase, e.g., `clickhouse`)\n2. **Target system** (database, message queue, file format, etc.)\n3. **Required Python library** (e.g., `clickhouse-driver`)\n\nThen create the export following this structure:\n\n### File structure\n\n```\nglances/exports/glances_<name>/\n    __init__.py    # Package marker (docstring only)\n    export.py      # Export implementation\n```\n\nNo registration needed — `discover_exporters()` (`glances/main.py`) imports\n`glances.exports.glances_<name>.export` when the user passes\n`--export <name>`.\n\n### Template\n\nThe `export.py` module must follow this pattern (see\n`glances/exports/glances_statsd/export.py` for a complete short example and\n`SKILL-exporter.md` for the contract):\n\n```python\n#\n# Glances - An eye on your system\n#\n# SPDX-FileCopyrightText: 2026 Nicolas Hennion <nicolas@nicolargo.com>\n#\n# SPDX-License-Identifier: LGPL-3.0-only\n#\n\n\"\"\"Glances — <Name> export module.\"\"\"\n\nfrom __future__ import annotations\n\nimport sys\nfrom typing import TYPE_CHECKING, Any\n\nfrom glances.exports.export_base import GlancesExportBase\nfrom glances.logger import logger\n\nif TYPE_CHECKING:\n    import argparse\n\n    from glances.config import GlancesConfig\n\n\nclass Export(GlancesExportBase):\n    \"\"\"Send Glances stats to <Name>.\"\"\"\n\n    export_name = \"<name>\"\n\n    def __init__(self, config: GlancesConfig, args: argparse.Namespace | None = None) -> None:\n        super().__init__(config, args)\n        # Load host, port (mandatory) and options from the [<name>] section\n        if not self.load_conf(\"<name>\", mandatories=(\"host\", \"port\"), options=()):\n            logger.critical(\"Missing <name> config\")\n            sys.exit(2)\n        self.client = self.init()\n\n    def init(self) -> Any:\n        \"\"\"Connect to the target system. Import the client library lazily.\"\"\"\n        try:\n            from <library> import Client\n        except ImportError as e:\n            logger.critical(\"Export <name> needs the <library> library (%s)\", e)\n            sys.exit(2)\n        logger.info(\"Stats will be exported to <Name> %s:%s\", self.host, self.port)\n        return Client(self.host, int(self.port))\n\n    def export(self, name: str, columns: list[str], points: list[Any]) -> None:\n        \"\"\"Export one plugin's stats (blocking I/O is fine: runs in a worker thread).\n\n        Args:\n            name: Plugin name (e.g., 'cpu', 'mem')\n            columns: List of field names\n            points: List of field values (same order as columns)\n        \"\"\"\n        try:\n            self.client.send(name, dict(zip(columns, points)))\n        except Exception as e:\n            logger.warning(\"Cannot export %s stats to <Name> (%s)\", name, e)\n\n    def exit(self) -> None:\n        super().exit()  # FIRST: waits for an in-flight update()\n        self.client.close()\n```\n\n### Key conventions\n\n- Directory: `glances/exports/glances_<name>/`, implementation in `export.py`\n- Class must be named `Export` (exactly) and inherit from `GlancesExportBase`\n- `export_name` matches the directory suffix and the `--export` token\n- Configuration is loaded from the `[<name>]` section in `glances.conf` with\n  `self.load_conf()`; a missing section or mandatory key is fatal\n- `update()` (base class) calls `plugin.get_export()` then\n  `export(name, columns, points)` for each plugin, once per `[export] refresh`\n- Never read the `StatsStore` directly; never interpolate monitored data\n  into a query (CVE-2026-32611 pattern)\n- Name sensitive config keys so they match `GlancesConfig.SECRET_KEYS` (`password`,\n  `token`, …) and `as_dict_secure()` redacts them\n\n### After creation\n\n1. Add the dependency to `pyproject.toml` under the appropriate optional group\n2. Add a `[<name>]` section in `conf/glances.conf` with all config keys (commented out)\n3. Add unit tests `tests/test_export_<name>.py` (fakes in `tests/export_fakes.py`)\n   and, optionally, an integration script `tests/test_export_<name>.sh`\n4. Run `make test` to verify no regressions\n",".claude/skills/test.md":"# Skill: Run Tests\n\nRun the appropriate Glances test suite based on the current changes or user request.\n\n## Instructions\n\n1. First, check which files have been modified using `git diff --name-only` (staged + unstaged).\n2. Determine which test suite(s) to run based on the changed files:\n   - Changes in `glances/plugins/` → `make test-plugins` and/or specific `pytest tests/test_plugin_<name>.py`\n   - Changes in `glances/webserver.py` or `glances/routes.py` → `make test-restful`\n   - Changes in `glances/outputs/glances_mcp.py` or `glances/outputs/mcp_adapter.py` → `pytest tests/test_mcp_adapter.py`\n   - Changes in `glances/outputs/static/` → `make test-js` and `make test-webui`\n   - Changes in `glances/exports/` → `pytest tests/test_export_*.py`; `make test-exports` for the integration scripts (requires Docker)\n   - Changes in `glances/client.py` → `pytest tests/test_client.py`\n   - Changes in core files (`glances/*.py`) → the matching `tests/test_<module>.py`, then `make test`\n   - If unsure or broad changes → `make test` (runs all tests)\n3. If the user specifies a test target (e.g., \"run core tests\"), use that directly.\n4. Run the tests using the Makefile targets. The virtualenv must already exist (`.venv-uv/`).\n5. If tests fail, analyze the output and suggest fixes.\n\n## Available test commands\n\n```bash\nmake test              # All tests\nmake test-core         # Core unit tests\nmake test-plugins      # Plugin tests\nmake test-api          # API unit tests\nmake test-restful      # REST API tests (routes + webserver)\nmake test-webui        # WebUI tests (browser tests need Playwright + Chromium)\nmake test-js           # WebUI JavaScript unit tests (Node.js)\nmake test-exports      # All export integration tests (needs Docker)\nmake memory-leak       # Memory leak profiling\n\n# Single test file or specific test:\n.venv-uv/bin/uv run pytest tests/test_core.py\n.venv-uv/bin/uv run pytest tests/test_core.py::TestGlances::test_016_subsample\n```\n",".claude/skills/webui.md":"# Skill: Build and Work with the WebUI\n\nBuild, update, or troubleshoot the Glances Vue.js WebUI.\n\n## Instructions\n\n### Build the WebUI\n```bash\nmake webui\n```\nThis runs `npm ci && npm run build` in `glances/outputs/static/`.\n\n### Development workflow\n1. Source files are in `glances/outputs/static/` (`js/*.vue`, `js/*.js`,\n   `css/glances.css`, `templates/index.html` and `browser.html`)\n2. Built output goes to `glances/outputs/static/public/` (`glances.js`,\n   `browser.js`; entries `js/app.js` and `js/browser_app.js`)\n3. Stack: Vue.js 3 + plain CSS (design tokens) — see `SKILL-webui.md`\n4. Test: `make test-js` (JavaScript unit tests) and `make test-webui`\n\n### Never commit the build artifacts\n\n`glances/outputs/static/public/glances.js` (and the other files under\n`glances/outputs/static/public/`) is generated by `make webui`. It is rebuilt\nand committed automatically at release time by the CI\n(see `.github/workflows/webui.yml`).\n\n**Never include `glances/outputs/static/public/glances.js` in a pull request.**\nCommit only the sources under `glances/outputs/static/`; a regenerated bundle\nin a PR creates huge unreviewable diffs and systematic merge conflicts.\nIf a local build dirtied it, restore it before committing:\n\n```bash\ngit checkout -- glances/outputs/static/public/\n```\n\n### Audit and update dependencies\n```bash\nmake webui-audit          # Run npm audit\nmake webui-audit-fix      # Fix audit issues and rebuild\nmake webui-update         # Update all JS dependencies and rebuild\n```\n\n### Run with WebUI\n```bash\nmake run-webserver        # Start Glances web server on port 61208\n```\nThen open http://localhost:61208 in a browser.\n\n### Design principles (from CLAUDE.md)\n- Strict typographic consistency across all plugins\n- Pixel-perfect sparkline alignment (CSS grid, fixed row heights)\n- Footer = vertical alert list (up to 10 entries)\n- No gauges — prefer sparklines with inline current value\n\n### Troubleshooting\n- If `npm ci` fails, check Node.js version (LTS required, currently 24.x in CI)\n- If build artifacts are stale, delete `glances/outputs/static/public/` and rebuild\n- The CI builds WebUI on `develop` branch only (see `.github/workflows/webui.yml`)\n"},"files":{"CLAUDE.md":"# CLAUDE.md — Glances Maintainer Context\n\nBehavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.\n\n## 1. Think Before Coding\n\n**Don't assume. Don't hide confusion. Surface tradeoffs.**\n\nBefore implementing:\n\n- State your assumptions explicitly. If uncertain, ask.\n- If multiple interpretations exist, present them - don't pick silently.\n- If a simpler approach exists, say so. Push back when warranted.\n- If something is unclear, stop. Name what's confusing. Ask.\n\n## 2. Simplicity First\n\n**Minimum code that solves the problem. Nothing speculative.**\n\n- No features beyond what was asked.\n- No abstractions for single-use code.\n- No \"flexibility\" or \"configurability\" that wasn't requested.\n- No error handling for impossible scenarios.\n- If you write 200 lines and it could be 50, rewrite it.\n\nAsk yourself: \"Would a senior engineer say this is overcomplicated?\" If yes, simplify.\n\n## 3. Surgical Changes\n\n**Touch only what you must. Clean up only your own mess.**\n\nWhen editing existing code:\n\n- Don't \"improve\" adjacent code, comments, or formatting.\n- Don't refactor things that aren't broken.\n- Match existing style, even if you'd do it differently.\n- If you notice unrelated dead code, mention it - don't delete it.\n\nWhen your changes create orphans:\n\n- Remove imports/variables/functions that YOUR changes made unused.\n- Don't remove pre-existing dead code unless asked.\n\nThe test: Every changed line should trace directly to the user's request.\n\n## 4. Goal-Driven Execution\n\n**Define success criteria. Loop until verified.**\n\nTransform tasks into verifiable goals:\n\n- \"Add validation\" → \"Write tests for invalid inputs, then make them pass\"\n- \"Fix the bug\" → \"Write a test that reproduces it, then make it pass\"\n- \"Refactor X\" → \"Ensure tests pass before and after\"\n\nFor multi-step tasks, state a brief plan:\n\n```md\n1. [Step] → verify: [check]\n2. [Step] → verify: [check]\n3. [Step] → verify: [check]\n```\n\nStrong success criteria let you loop independently. Weak criteria (\"make it work\") require constant clarification.\n\n## Additionnal context\n\n### Tech stack\n\n| Layer | Technology |\n| --- | --- |\n| Backend | Python, psutil, FastAPI (REST API), curses (TUI) |\n| Frontend | Vue.js 3, plain CSS (design tokens) |\n| Export plugins | InfluxDB, MongoDB, MQTT, DuckDB, and others |\n| Infrastructure | GitHub Actions, Helm/Kubernetes, Snap (snapcraft) |\n| LLM abstraction | LiteLLM (multi-provider) |\n| GPU/NPU monitoring | pynvml, sysfs/debugfs |\n\n### Code principles\n\n#### Exception handling for Snap confinement\n\nWrap the `open()` call inside `try/except`, not just the `read()`. Snap's strict\nconfinement blocks host file access at the open stage, not the read stage.\n\n### Security\n\nGlances runs unauthenticated by default — this is intentional and documented.\nMost users deploy on private networks for personal use. See global CLAUDE.md\nfor the general OSS security philosophy (4 rules).\n\n#### Sensitive endpoints\n\n- `/api/5/config` and `/api/5/args` — never expose credentials in plain text\n  (InfluxDB passwords, MongoDB tokens, MQTT passphrases, SSL key paths, etc.)\n  for unauthenticated access. `/api/5/config` serves `as_dict_secure()` when\n  `[outputs] password` is set, and only `as_dict_public()` otherwise;\n  `/api/5/args` is always redacted (`glances/routes.py`).\n- CORS: `allow_origins=[\"*\"]` + `allow_credentials=True` is invalid per the\n  CORS spec and reflected by Starlette. Default: `allow_credentials=False`.\n- DNS rebinding: `TrustedHostMiddleware` via `webui_allowed_hosts` (optional).\n  The MCP endpoint is already protected via `mcp_allowed_hosts` +\n  `TransportSecuritySettings` in `glances/outputs/glances_mcp.py`.\n\n### Contribution management\n\n#### Before merging a PR\n\n- [ ] The code is actively used (no dead code)\n- [ ] Existing tests pass\n- [ ] New behaviour is covered by tests\n- [ ] New configuration keys are documented\n- [ ] Breaking changes are identified and documented\n- [ ] The PR targets the correct branch\n- [ ] Pre-commit hooks pass (`make pre-commit` — supersedes `make lint && make format`)\n\n#### Glances-specific output formats\n\n| Deliverable | Format |\n| --- | --- |\n| Changelog entry | `.rst` file following the `NEWS.rst` format |\n| Helm Chart | `.tgz` archive or structured directory |\n\n### UI / TUI\n\n#### Web UI\n\nStack: Vue.js 3 + plain CSS (design tokens in\n`glances/outputs/static/css/glances.css`).\n\nDesign principles:\n\n- Strict typographic consistency across all plugins (size, weight, opacity,\n  letter-spacing).\n- Pixel-perfect sparkline alignment (CSS grid, fixed row heights).\n- Footer = vertical alert list (up to 10 entries), not a single horizontal row.\n- No gauges — prefer sparklines with an inline current value.\n\n#### TUI (curses)\n\n- 256-colour system with automatic fallback for limited terminals.\n- Lightweight hierarchical separators for information density.\n\n---\n\n**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.\n",".claude/skills/review.md":"# Skill: Review Code Changes\n\nReview the current changes (staged or unstaged) against Glances project guidelines.\n\n## Instructions\n\n1. Get the diff to review:\n   - If the user provides a PR number, fetch it with `gh pr diff <number>`\n   - Otherwise, use `git diff` (unstaged) and `git diff --cached` (staged)\n   - If a branch is specified, use `git diff <base>...<branch>`\n\n2. Check each item on this checklist:\n\n### Correctness\n- [ ] No dead code introduced (unused functions, imports, variables)\n- [ ] Exception handling wraps the right operation (e.g., `open()` not `read()` for Snap confinement)\n- [ ] No silent exception swallowing (`except: pass` or bare `except`)\n- [ ] No O(n²) patterns where O(n) is possible (linear search in a loop → use dict/set)\n- [ ] No `list.pop(0)` for queues → suggest `collections.deque(maxlen=N)`\n\n### Security\n- [ ] No credentials/secrets exposed in plain text\n- [ ] No new endpoints without considering auth implications\n- [ ] Sensitive config keys filtered from API responses\n- [ ] No command injection, XSS, or SQL injection vectors\n\n### Compatibility\n- [ ] Default behaviour unchanged (or breaking change explicitly documented)\n- [ ] Cross-platform considerations (Linux/macOS/Windows)\n- [ ] Configuration keys documented in `conf/glances.conf`\n- [ ] PR targets the right branch: `develop` for Glances v5, `support/glancesv4` for a v4 bugfix or security fix\n\n### Style\n- [ ] Code passes `make pre-commit` (supersedes `make format && make lint`)\n- [ ] No unnecessary comments, docstrings, or type annotations on unchanged code\n- [ ] Plugin/export conventions followed (class naming, auto-discovery, fields_description)\n\n3. Report findings grouped by severity:\n   - **Blocking** — must fix before merge\n   - **Suggestion** — improvement, not required\n   - **Nit** — style/cosmetic, optional\n\n4. Use the tone from CLAUDE.md: acknowledge quality, label problems as \"blocking concern\", invite collaboration.\n",".claude/skills/debug.md":"# Skill: Debug a Glances Issue\n\nHelp diagnose and fix a bug in Glances.\n\n## Instructions\n\n1. **Understand the issue**: Ask for or identify:\n   - Error message or traceback\n   - Glances version and OS\n   - How to reproduce (standalone, web server, client/server, Docker?)\n   - Relevant config section from `glances.conf`\n\n2. **Locate the code path**: Based on the mode and plugin involved:\n   - Startup / CLI: `glances/main.py`\n   - Standalone TUI: `glances/outputs/glances_curses.py` (`Tui`) → `glances/outputs/curses_renderer.py`\n   - Web server: `glances/webserver.py` → `glances/routes.py`\n   - WebUI: `glances/outputs/static/js/`\n   - Client/server: `glances/client.py` (`-c`, over the REST API) / `glances/webserver.py` (`-s`)\n   - Scheduler / alerts: `glances/scheduler.py` / `glances/alerts.py`\n   - Plugin issue: `glances/plugins/<name>/model.py` (TUI layout: `render_curses.py`)\n   - Export issue: `glances/exports/glances_<name>/export.py`\n   - Process list: `glances/processes.py`\n   - Configuration: `glances/config.py`\n\n3. **Run Glances in debug mode** to get detailed logs:\n   ```bash\n   .venv-uv/bin/uv run python -m glances -d 2>&1 | tail -100\n   ```\n\n4. **Reproduce with tests** when possible:\n   ```bash\n   .venv-uv/bin/uv run pytest tests/test_plugin_<name>.py -v -k \"test_name\" 2>&1\n   ```\n\n5. **Fix and verify**:\n   - Apply surgical, atomic edits\n   - Run the relevant test suite\n   - Run `make format && make lint`\n   - Verify no regressions with `make test`\n\n## Common pitfalls\n\n- **psutil version differences**: Some psutil APIs behave differently across OS — always check `glances/globals.py` for platform flags (`LINUX`, `MACOS`, `WINDOWS`, `BSD`)\n- **Plugin update cycle**: a plugin has no payload before its first update (`GET /api/5/<plugin>` → `200 null`), and `rate: True` fields are absent on the first cycle — handle gracefully\n- **Snap confinement**: `PermissionError` at `open()`, not at `read()` — wrap the right call\n- **Docker socket access**: Requires mounting `/var/run/docker.sock` and proper permissions\n",".claude/skills/docker.md":"# Skill: Build and Run Docker Images\n\nBuild, run, and troubleshoot Glances Docker images locally.\n\n## Instructions\n\n### Build images\n```bash\nmake docker                    # Build all images (Alpine + Ubuntu, full + minimal)\nmake docker-alpine-full        # Alpine full image\nmake docker-alpine-minimal     # Alpine minimal image\nmake docker-ubuntu-full        # Ubuntu full image\nmake docker-ubuntu-minimal     # Ubuntu minimal image\n```\n\nRequires Docker Buildx (`apt install docker-buildx` on Ubuntu).\n\n### Run containers\n```bash\nmake run-docker-alpine-full    # Run Alpine full in console mode\nmake run-docker-ubuntu-full    # Run Ubuntu full in console mode\n```\n\nDefault run options: `--rm --pid host --network host` with Docker/Podman socket mounts.\n\n### Security scan\n```bash\nmake trivy-docker              # Run Trivy on all local images\n```\n\n### Dockerfiles\nLocated in `docker-files/`:\n- `alpine.Dockerfile` — multi-stage: `minimal` and `full` targets\n- `ubuntu.Dockerfile` — multi-stage: `minimal` and `full` targets\n\n### Key considerations\n- Prefer `SYS_PTRACE` capability over `privileged: true`\n- Mount Docker socket read-only: `-v /var/run/docker.sock:/var/run/docker.sock:ro`\n- For Kubernetes: use DaemonSet (one pod per node) for system monitoring\n- The images run Glances v5 (`python -m glances ${GLANCES_OPT}`); in server mode (`-w`) bind to `0.0.0.0` (`bind_address` in `docker-compose/glances.conf`, or `-B 0.0.0.0`), v5 defaults to loopback\n- CI builds Docker images on `develop` (the `dev` image) and on tags only (see `.github/workflows/build_docker.yml`); a pre-release tag (`v5.0.0b1`, `rc`) never moves `latest`\n\n### Troubleshooting\n- **Socket permission errors**: Ensure the user is in the `docker` group or use Podman\n- **Multi-arch builds**: CI uses QEMU for cross-platform (ARM64) — local builds are native only by default\n- **Build timeouts**: Multi-arch builds can take 20-40 min; CI timeout is 60 min\n",".claude/skills/new-plugin.md":"# Skill: Create a New Plugin\n\nScaffold a new Glances monitoring plugin following project conventions.\n\n## Instructions\n\nAsk the user for:\n1. **Plugin name** (lowercase, e.g., `battery`)\n2. **What it monitors** (brief description)\n3. **Data source** (psutil, sysfs, external library, etc.)\n\nThen create the plugin following this structure:\n\n### File structure\n\n```\nglances/plugins/<name>/\n    __init__.py         # Package marker (docstring only)\n    model.py            # Plugin implementation (class PluginModel)\n    render_curses.py    # OPTIONAL — per-plugin TUI layout\n```\n\nNo registration needed — `discover_plugins()` (`glances/main.py`) imports\nevery `glances.plugins.<name>.model`. Its place in the TUI comes from the\nslot tuples in `glances/outputs/curses_renderer.py` (`LEFT_SLOT`,\n`RIGHT_SLOT`, …). The WebUI needs `glances/outputs/static/js/Plugin<Name>.vue`\nand one entry in `glances/outputs/static/js/plugins/index.js`.\n\n### Template\n\n`model.py` must follow this pattern (see `SKILL-plugin.md` for the full\ncontract and `glances/plugins/uptime/model.py` for a short example):\n\n```python\n#\n# Glances - An eye on your system\n#\n# SPDX-FileCopyrightText: 2026 Nicolas Hennion <nicolas@nicolargo.com>\n#\n# SPDX-License-Identifier: LGPL-3.0-only\n#\n\n\"\"\"Glances — <name> plugin (scalar).\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nfrom typing import Any, ClassVar\n\nimport psutil\n\nfrom glances.plugins.plugin.base import GlancesPluginBase\n\n\nclass PluginModel(GlancesPluginBase[dict]):\n    \"\"\"<Name> plugin (scalar).\"\"\"\n\n    plugin_name: ClassVar[str] = \"<name>\"\n    IS_COLLECTION: ClassVar[bool] = False\n\n    fields_description: ClassVar[dict[str, dict[str, Any]]] = {\n        \"field_name\": {\n            \"description\": \"Human-readable description.\",\n            \"unit\": \"percent\",  # percent, number, bytes, bytespers, seconds, string, ...\n            \"watched\": True,\n            \"default_thresholds\": {\"careful\": 50.0, \"warning\": 70.0, \"critical\": 90.0},\n        },\n    }\n\n    async def _grab_stats(self) -> dict:\n        \"\"\"Collect the raw stats. Blocking calls go through asyncio.to_thread().\"\"\"\n        value = await asyncio.to_thread(psutil.cpu_percent)\n        return {\"field_name\": value}\n```\n\n### Key conventions\n\n- Class name: `PluginModel` (exactly), in `glances/plugins/<name>/model.py`\n- Inherit from `GlancesPluginBase[dict]` (scalar) or `GlancesPluginBase[list]`\n  (collection, `IS_COLLECTION = True`, exactly one field with `primary_key: True`)\n- `_grab_stats()` is the only mandatory hook; never override `update()`\n- `fields_description` declares metadata for each field (unit, thresholds,\n  `rate`, `watched`, `exportable`); undeclared fields are stripped\n- Keep `model.py` free of curses / UI code — layout goes in `render_curses.py`\n- No SNMP: SNMP is not ported to v5\n\n### After creation\n\n1. Create a test file: `tests/test_plugin_<name>.py` (and\n   `tests/test_plugin_<name>_render_curses.py` with a `render_curses.py`)\n2. Run `make test-plugins` to verify\n3. Add configuration section in `conf/glances.conf` if needed\n",".claude/skills/lint.md":"# Skill: Lint and Format\n\nRun code formatting and linting on the Glances codebase.\n\n## Instructions\n\n1. Run formatting first, then linting:\n   ```bash\n   make format && make lint\n   ```\n2. If there are remaining issues that `--fix` could not resolve, list them and suggest manual fixes.\n3. Optionally, if the user asks for a full check, run all pre-commit hooks:\n   ```bash\n   make pre-commit\n   ```\n\n## Tools used\n\n- **Ruff** for both formatting and linting (configured in `pyproject.toml`)\n- **Pre-commit hooks** include: ruff, gitleaks (secret detection), shellcheck, and others\n\n## Notes\n\n- The virtualenv must already exist (`.venv-uv/`). If not, run `make venv-dev` first.\n- Never change ruff configuration without explicit approval — linting rules are shared across all contributors.\n",".claude/skills/new-export.md":"# Skill: Create a New Export Module\n\nScaffold a new Glances export module following project conventions.\n\n## Instructions\n\nAsk the user for:\n1. **Export name** (lowercase, e.g., `clickhouse`)\n2. **Target system** (database, message queue, file format, etc.)\n3. **Required Python library** (e.g., `clickhouse-driver`)\n\nThen create the export following this structure:\n\n### File structure\n\n```\nglances/exports/glances_<name>/\n    __init__.py    # Package marker (docstring only)\n    export.py      # Export implementation\n```\n\nNo registration needed — `discover_exporters()` (`glances/main.py`) imports\n`glances.exports.glances_<name>.export` when the user passes\n`--export <name>`.\n\n### Template\n\nThe `export.py` module must follow this pattern (see\n`glances/exports/glances_statsd/export.py` for a complete short example and\n`SKILL-exporter.md` for the contract):\n\n```python\n#\n# Glances - An eye on your system\n#\n# SPDX-FileCopyrightText: 2026 Nicolas Hennion <nicolas@nicolargo.com>\n#\n# SPDX-License-Identifier: LGPL-3.0-only\n#\n\n\"\"\"Glances — <Name> export module.\"\"\"\n\nfrom __future__ import annotations\n\nimport sys\nfrom typing import TYPE_CHECKING, Any\n\nfrom glances.exports.export_base import GlancesExportBase\nfrom glances.logger import logger\n\nif TYPE_CHECKING:\n    import argparse\n\n    from glances.config import GlancesConfig\n\n\nclass Export(GlancesExportBase):\n    \"\"\"Send Glances stats to <Name>.\"\"\"\n\n    export_name = \"<name>\"\n\n    def __init__(self, config: GlancesConfig, args: argparse.Namespace | None = None) -> None:\n        super().__init__(config, args)\n        # Load host, port (mandatory) and options from the [<name>] section\n        if not self.load_conf(\"<name>\", mandatories=(\"host\", \"port\"), options=()):\n            logger.critical(\"Missing <name> config\")\n            sys.exit(2)\n        self.client = self.init()\n\n    def init(self) -> Any:\n        \"\"\"Connect to the target system. Import the client library lazily.\"\"\"\n        try:\n            from <library> import Client\n        except ImportError as e:\n            logger.critical(\"Export <name> needs the <library> library (%s)\", e)\n            sys.exit(2)\n        logger.info(\"Stats will be exported to <Name> %s:%s\", self.host, self.port)\n        return Client(self.host, int(self.port))\n\n    def export(self, name: str, columns: list[str], points: list[Any]) -> None:\n        \"\"\"Export one plugin's stats (blocking I/O is fine: runs in a worker thread).\n\n        Args:\n            name: Plugin name (e.g., 'cpu', 'mem')\n            columns: List of field names\n            points: List of field values (same order as columns)\n        \"\"\"\n        try:\n            self.client.send(name, dict(zip(columns, points)))\n        except Exception as e:\n            logger.warning(\"Cannot export %s stats to <Name> (%s)\", name, e)\n\n    def exit(self) -> None:\n        super().exit()  # FIRST: waits for an in-flight update()\n        self.client.close()\n```\n\n### Key conventions\n\n- Directory: `glances/exports/glances_<name>/`, implementation in `export.py`\n- Class must be named `Export` (exactly) and inherit from `GlancesExportBase`\n- `export_name` matches the directory suffix and the `--export` token\n- Configuration is loaded from the `[<name>]` section in `glances.conf` with\n  `self.load_conf()`; a missing section or mandatory key is fatal\n- `update()` (base class) calls `plugin.get_export()` then\n  `export(name, columns, points)` for each plugin, once per `[export] refresh`\n- Never read the `StatsStore` directly; never interpolate monitored data\n  into a query (CVE-2026-32611 pattern)\n- Name sensitive config keys so they match `GlancesConfig.SECRET_KEYS` (`password`,\n  `token`, …) and `as_dict_secure()` redacts them\n\n### After creation\n\n1. Add the dependency to `pyproject.toml` under the appropriate optional group\n2. Add a `[<name>]` section in `conf/glances.conf` with all config keys (commented out)\n3. Add unit tests `tests/test_export_<name>.py` (fakes in `tests/export_fakes.py`)\n   and, optionally, an integration script `tests/test_export_<name>.sh`\n4. Run `make test` to verify no regressions\n",".claude/skills/test.md":"# Skill: Run Tests\n\nRun the appropriate Glances test suite based on the current changes or user request.\n\n## Instructions\n\n1. First, check which files have been modified using `git diff --name-only` (staged + unstaged).\n2. Determine which test suite(s) to run based on the changed files:\n   - Changes in `glances/plugins/` → `make test-plugins` and/or specific `pytest tests/test_plugin_<name>.py`\n   - Changes in `glances/webserver.py` or `glances/routes.py` → `make test-restful`\n   - Changes in `glances/outputs/glances_mcp.py` or `glances/outputs/mcp_adapter.py` → `pytest tests/test_mcp_adapter.py`\n   - Changes in `glances/outputs/static/` → `make test-js` and `make test-webui`\n   - Changes in `glances/exports/` → `pytest tests/test_export_*.py`; `make test-exports` for the integration scripts (requires Docker)\n   - Changes in `glances/client.py` → `pytest tests/test_client.py`\n   - Changes in core files (`glances/*.py`) → the matching `tests/test_<module>.py`, then `make test`\n   - If unsure or broad changes → `make test` (runs all tests)\n3. If the user specifies a test target (e.g., \"run core tests\"), use that directly.\n4. Run the tests using the Makefile targets. The virtualenv must already exist (`.venv-uv/`).\n5. If tests fail, analyze the output and suggest fixes.\n\n## Available test commands\n\n```bash\nmake test              # All tests\nmake test-core         # Core unit tests\nmake test-plugins      # Plugin tests\nmake test-api          # API unit tests\nmake test-restful      # REST API tests (routes + webserver)\nmake test-webui        # WebUI tests (browser tests need Playwright + Chromium)\nmake test-js           # WebUI JavaScript unit tests (Node.js)\nmake test-exports      # All export integration tests (needs Docker)\nmake memory-leak       # Memory leak profiling\n\n# Single test file or specific test:\n.venv-uv/bin/uv run pytest tests/test_core.py\n.venv-uv/bin/uv run pytest tests/test_core.py::TestGlances::test_016_subsample\n```\n",".claude/skills/webui.md":"# Skill: Build and Work with the WebUI\n\nBuild, update, or troubleshoot the Glances Vue.js WebUI.\n\n## Instructions\n\n### Build the WebUI\n```bash\nmake webui\n```\nThis runs `npm ci && npm run build` in `glances/outputs/static/`.\n\n### Development workflow\n1. Source files are in `glances/outputs/static/` (`js/*.vue`, `js/*.js`,\n   `css/glances.css`, `templates/index.html` and `browser.html`)\n2. Built output goes to `glances/outputs/static/public/` (`glances.js`,\n   `browser.js`; entries `js/app.js` and `js/browser_app.js`)\n3. Stack: Vue.js 3 + plain CSS (design tokens) — see `SKILL-webui.md`\n4. Test: `make test-js` (JavaScript unit tests) and `make test-webui`\n\n### Never commit the build artifacts\n\n`glances/outputs/static/public/glances.js` (and the other files under\n`glances/outputs/static/public/`) is generated by `make webui`. It is rebuilt\nand committed automatically at release time by the CI\n(see `.github/workflows/webui.yml`).\n\n**Never include `glances/outputs/static/public/glances.js` in a pull request.**\nCommit only the sources under `glances/outputs/static/`; a regenerated bundle\nin a PR creates huge unreviewable diffs and systematic merge conflicts.\nIf a local build dirtied it, restore it before committing:\n\n```bash\ngit checkout -- glances/outputs/static/public/\n```\n\n### Audit and update dependencies\n```bash\nmake webui-audit          # Run npm audit\nmake webui-audit-fix      # Fix audit issues and rebuild\nmake webui-update         # Update all JS dependencies and rebuild\n```\n\n### Run with WebUI\n```bash\nmake run-webserver        # Start Glances web server on port 61208\n```\nThen open http://localhost:61208 in a browser.\n\n### Design principles (from CLAUDE.md)\n- Strict typographic consistency across all plugins\n- Pixel-perfect sparkline alignment (CSS grid, fixed row heights)\n- Footer = vertical alert list (up to 10 entries)\n- No gauges — prefer sparklines with inline current value\n\n### Troubleshooting\n- If `npm ci` fails, check Node.js version (LTS required, currently 24.x in CI)\n- If build artifacts are stale, delete `glances/outputs/static/public/` and rebuild\n- The CI builds WebUI on `develop` branch only (see `.github/workflows/webui.yml`)\n"},"items":[{"name":"debug.md","path":".claude/skills/debug.md","rawUrl":"https://raw.githubusercontent.com/nicolargo/glances/HEAD/.claude/skills/debug.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# Skill: Debug a Glances Issue\n\nHelp diagnose and fix a bug in Glances.\n\n## Instructions\n\n1. **Understand the issue**: Ask for or identify:\n   - Error message or traceback\n   - Glances version and OS\n   - How to reproduce (standalone, web server, client/server, Docker?)\n   - Relevant config section from `glances.conf`\n\n2. **Locate the code path**: Based on the mode and plugin involved:\n   - Startup / CLI: `glances/main.py`\n   - Standalone TUI: `glances/outputs/glances_curses.py` (`Tui`) → `glances/outputs/curses_renderer.py`\n   - Web server: `glances/webserver.py` → `glances/routes.py`\n   - WebUI: `glances/outputs/static/js/`\n   - Client/server: `glances/client.py` (`-c`, over the REST API) / `glances/webserver.py` (`-s`)\n   - Scheduler / alerts: `glances/scheduler.py` / `glances/alerts.py`\n   - Plugin issue: `glances/plugins/<name>/model.py` (TUI layout: `render_curses.py`)\n   - Export issue: `glances/exports/glances_<name>/export.py`\n   - Process list: `glances/processes.py`\n   - Configuration: `glances/config.py`\n\n3. **Run Glances in debug mode** to get detailed logs:\n   ```bash\n   .venv-uv/bin/uv run python -m glances -d 2>&1 | tail -100\n   ```\n\n4. **Reproduce with tests** when possible:\n   ```bash\n   .venv-uv/bin/uv run pytest tests/test_plugin_<name>.py -v -k \"test_name\" 2>&1\n   ```\n\n5. **Fix and verify**:\n   - Apply surgical, atomic edits\n   - Run the relevant test suite\n   - Run `make format && make lint`\n   - Verify no regressions with `make test`\n\n## Common pitfalls\n\n- **psutil version differences**: Some psutil APIs behave differently across OS — always check `glances/globals.py` for platform flags (`LINUX`, `MACOS`, `WINDOWS`, `BSD`)\n- **Plugin update cycle**: a plugin has no payload before its first update (`GET /api/5/<plugin>` → `200 null`), and `rate: True` fields are absent on the first cycle — handle gracefully\n- **Snap confinement**: `PermissionError` at `open()`, not at `read()` — wrap the right call\n- **Docker socket access**: Requires mounting `/var/run/docker.sock` and proper permissions\n","isInternal":false,"tokens":571,"sizeBytes":2058},{"name":"docker.md","path":".claude/skills/docker.md","rawUrl":"https://raw.githubusercontent.com/nicolargo/glances/HEAD/.claude/skills/docker.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# Skill: Build and Run Docker Images\n\nBuild, run, and troubleshoot Glances Docker images locally.\n\n## Instructions\n\n### Build images\n```bash\nmake docker                    # Build all images (Alpine + Ubuntu, full + minimal)\nmake docker-alpine-full        # Alpine full image\nmake docker-alpine-minimal     # Alpine minimal image\nmake docker-ubuntu-full        # Ubuntu full image\nmake docker-ubuntu-minimal     # Ubuntu minimal image\n```\n\nRequires Docker Buildx (`apt install docker-buildx` on Ubuntu).\n\n### Run containers\n```bash\nmake run-docker-alpine-full    # Run Alpine full in console mode\nmake run-docker-ubuntu-full    # Run Ubuntu full in console mode\n```\n\nDefault run options: `--rm --pid host --network host` with Docker/Podman socket mounts.\n\n### Security scan\n```bash\nmake trivy-docker              # Run Trivy on all local images\n```\n\n### Dockerfiles\nLocated in `docker-files/`:\n- `alpine.Dockerfile` — multi-stage: `minimal` and `full` targets\n- `ubuntu.Dockerfile` — multi-stage: `minimal` and `full` targets\n\n### Key considerations\n- Prefer `SYS_PTRACE` capability over `privileged: true`\n- Mount Docker socket read-only: `-v /var/run/docker.sock:/var/run/docker.sock:ro`\n- For Kubernetes: use DaemonSet (one pod per node) for system monitoring\n- The images run Glances v5 (`python -m glances ${GLANCES_OPT}`); in server mode (`-w`) bind to `0.0.0.0` (`bind_address` in `docker-compose/glances.conf`, or `-B 0.0.0.0`), v5 defaults to loopback\n- CI builds Docker images on `develop` (the `dev` image) and on tags only (see `.github/workflows/build_docker.yml`); a pre-release tag (`v5.0.0b1`, `rc`) never moves `latest`\n\n### Troubleshooting\n- **Socket permission errors**: Ensure the user is in the `docker` group or use Podman\n- **Multi-arch builds**: CI uses QEMU for cross-platform (ARM64) — local builds are native only by default\n- **Build timeouts**: Multi-arch builds can take 20-40 min; CI timeout is 60 min\n","isInternal":false,"tokens":509,"sizeBytes":1939},{"name":"lint.md","path":".claude/skills/lint.md","rawUrl":"https://raw.githubusercontent.com/nicolargo/glances/HEAD/.claude/skills/lint.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# Skill: Lint and Format\n\nRun code formatting and linting on the Glances codebase.\n\n## Instructions\n\n1. Run formatting first, then linting:\n   ```bash\n   make format && make lint\n   ```\n2. If there are remaining issues that `--fix` could not resolve, list them and suggest manual fixes.\n3. Optionally, if the user asks for a full check, run all pre-commit hooks:\n   ```bash\n   make pre-commit\n   ```\n\n## Tools used\n\n- **Ruff** for both formatting and linting (configured in `pyproject.toml`)\n- **Pre-commit hooks** include: ruff, gitleaks (secret detection), shellcheck, and others\n\n## Notes\n\n- The virtualenv must already exist (`.venv-uv/`). If not, run `make venv-dev` first.\n- Never change ruff configuration without explicit approval — linting rules are shared across all contributors.\n","isInternal":false,"tokens":198,"sizeBytes":793},{"name":"new-export.md","path":".claude/skills/new-export.md","rawUrl":"https://raw.githubusercontent.com/nicolargo/glances/HEAD/.claude/skills/new-export.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# Skill: Create a New Export Module\n\nScaffold a new Glances export module following project conventions.\n\n## Instructions\n\nAsk the user for:\n1. **Export name** (lowercase, e.g., `clickhouse`)\n2. **Target system** (database, message queue, file format, etc.)\n3. **Required Python library** (e.g., `clickhouse-driver`)\n\nThen create the export following this structure:\n\n### File structure\n\n```\nglances/exports/glances_<name>/\n    __init__.py    # Package marker (docstring only)\n    export.py      # Export implementation\n```\n\nNo registration needed — `discover_exporters()` (`glances/main.py`) imports\n`glances.exports.glances_<name>.export` when the user passes\n`--export <name>`.\n\n### Template\n\nThe `export.py` module must follow this pattern (see\n`glances/exports/glances_statsd/export.py` for a complete short example and\n`SKILL-exporter.md` for the contract):\n\n```python\n#\n# Glances - An eye on your system\n#\n# SPDX-FileCopyrightText: 2026 Nicolas Hennion <nicolas@nicolargo.com>\n#\n# SPDX-License-Identifier: LGPL-3.0-only\n#\n\n\"\"\"Glances — <Name> export module.\"\"\"\n\nfrom __future__ import annotations\n\nimport sys\nfrom typing import TYPE_CHECKING, Any\n\nfrom glances.exports.export_base import GlancesExportBase\nfrom glances.logger import logger\n\nif TYPE_CHECKING:\n    import argparse\n\n    from glances.config import GlancesConfig\n\n\nclass Export(GlancesExportBase):\n    \"\"\"Send Glances stats to <Name>.\"\"\"\n\n    export_name = \"<name>\"\n\n    def __init__(self, config: GlancesConfig, args: argparse.Namespace | None = None) -> None:\n        super().__init__(config, args)\n        # Load host, port (mandatory) and options from the [<name>] section\n        if not self.load_conf(\"<name>\", mandatories=(\"host\", \"port\"), options=()):\n            logger.critical(\"Missing <name> config\")\n            sys.exit(2)\n        self.client = self.init()\n\n    def init(self) -> Any:\n        \"\"\"Connect to the target system. Import the client library lazily.\"\"\"\n        try:\n            from <library> import Client\n        except ImportError as e:\n            logger.critical(\"Export <name> needs the <library> library (%s)\", e)\n            sys.exit(2)\n        logger.info(\"Stats will be exported to <Name> %s:%s\", self.host, self.port)\n        return Client(self.host, int(self.port))\n\n    def export(self, name: str, columns: list[str], points: list[Any]) -> None:\n        \"\"\"Export one plugin's stats (blocking I/O is fine: runs in a worker thread).\n\n        Args:\n            name: Plugin name (e.g., 'cpu', 'mem')\n            columns: List of field names\n            points: List of field values (same order as columns)\n        \"\"\"\n        try:\n            self.client.send(name, dict(zip(columns, points)))\n        except Exception as e:\n            logger.warning(\"Cannot export %s stats to <Name> (%s)\", name, e)\n\n    def exit(self) -> None:\n        super().exit()  # FIRST: waits for an in-flight update()\n        self.client.close()\n```\n\n### Key conventions\n\n- Directory: `glances/exports/glances_<name>/`, implementation in `export.py`\n- Class must be named `Export` (exactly) and inherit from `GlancesExportBase`\n- `export_name` matches the directory suffix and the `--export` token\n- Configuration is loaded from the `[<name>]` section in `glances.conf` with\n  `self.load_conf()`; a missing section or mandatory key is fatal\n- `update()` (base class) calls `plugin.get_export()` then\n  `export(name, columns, points)` for each plugin, once per `[export] refresh`\n- Never read the `StatsStore` directly; never interpolate monitored data\n  into a query (CVE-2026-32611 pattern)\n- Name sensitive config keys so they match `GlancesConfig.SECRET_KEYS` (`password`,\n  `token`, …) and `as_dict_secure()` redacts them\n\n### After creation\n\n1. Add the dependency to `pyproject.toml` under the appropriate optional group\n2. Add a `[<name>]` section in `conf/glances.conf` with all config keys (commented out)\n3. Add unit tests `tests/test_export_<name>.py` (fakes in `tests/export_fakes.py`)\n   and, optionally, an integration script `tests/test_export_<name>.sh`\n4. Run `make test` to verify no regressions\n","isInternal":false,"tokens":1012,"sizeBytes":4101},{"name":"new-plugin.md","path":".claude/skills/new-plugin.md","rawUrl":"https://raw.githubusercontent.com/nicolargo/glances/HEAD/.claude/skills/new-plugin.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# Skill: Create a New Plugin\n\nScaffold a new Glances monitoring plugin following project conventions.\n\n## Instructions\n\nAsk the user for:\n1. **Plugin name** (lowercase, e.g., `battery`)\n2. **What it monitors** (brief description)\n3. **Data source** (psutil, sysfs, external library, etc.)\n\nThen create the plugin following this structure:\n\n### File structure\n\n```\nglances/plugins/<name>/\n    __init__.py         # Package marker (docstring only)\n    model.py            # Plugin implementation (class PluginModel)\n    render_curses.py    # OPTIONAL — per-plugin TUI layout\n```\n\nNo registration needed — `discover_plugins()` (`glances/main.py`) imports\nevery `glances.plugins.<name>.model`. Its place in the TUI comes from the\nslot tuples in `glances/outputs/curses_renderer.py` (`LEFT_SLOT`,\n`RIGHT_SLOT`, …). The WebUI needs `glances/outputs/static/js/Plugin<Name>.vue`\nand one entry in `glances/outputs/static/js/plugins/index.js`.\n\n### Template\n\n`model.py` must follow this pattern (see `SKILL-plugin.md` for the full\ncontract and `glances/plugins/uptime/model.py` for a short example):\n\n```python\n#\n# Glances - An eye on your system\n#\n# SPDX-FileCopyrightText: 2026 Nicolas Hennion <nicolas@nicolargo.com>\n#\n# SPDX-License-Identifier: LGPL-3.0-only\n#\n\n\"\"\"Glances — <name> plugin (scalar).\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nfrom typing import Any, ClassVar\n\nimport psutil\n\nfrom glances.plugins.plugin.base import GlancesPluginBase\n\n\nclass PluginModel(GlancesPluginBase[dict]):\n    \"\"\"<Name> plugin (scalar).\"\"\"\n\n    plugin_name: ClassVar[str] = \"<name>\"\n    IS_COLLECTION: ClassVar[bool] = False\n\n    fields_description: ClassVar[dict[str, dict[str, Any]]] = {\n        \"field_name\": {\n            \"description\": \"Human-readable description.\",\n            \"unit\": \"percent\",  # percent, number, bytes, bytespers, seconds, string, ...\n            \"watched\": True,\n            \"default_thresholds\": {\"careful\": 50.0, \"warning\": 70.0, \"critical\": 90.0},\n        },\n    }\n\n    async def _grab_stats(self) -> dict:\n        \"\"\"Collect the raw stats. Blocking calls go through asyncio.to_thread().\"\"\"\n        value = await asyncio.to_thread(psutil.cpu_percent)\n        return {\"field_name\": value}\n```\n\n### Key conventions\n\n- Class name: `PluginModel` (exactly), in `glances/plugins/<name>/model.py`\n- Inherit from `GlancesPluginBase[dict]` (scalar) or `GlancesPluginBase[list]`\n  (collection, `IS_COLLECTION = True`, exactly one field with `primary_key: True`)\n- `_grab_stats()` is the only mandatory hook; never override `update()`\n- `fields_description` declares metadata for each field (unit, thresholds,\n  `rate`, `watched`, `exportable`); undeclared fields are stripped\n- Keep `model.py` free of curses / UI code — layout goes in `render_curses.py`\n- No SNMP: SNMP is not ported to v5\n\n### After creation\n\n1. Create a test file: `tests/test_plugin_<name>.py` (and\n   `tests/test_plugin_<name>_render_curses.py` with a `render_curses.py`)\n2. Run `make test-plugins` to verify\n3. Add configuration section in `conf/glances.conf` if needed\n","isInternal":false,"tokens":786,"sizeBytes":3069},{"name":"review.md","path":".claude/skills/review.md","rawUrl":"https://raw.githubusercontent.com/nicolargo/glances/HEAD/.claude/skills/review.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# Skill: Review Code Changes\n\nReview the current changes (staged or unstaged) against Glances project guidelines.\n\n## Instructions\n\n1. Get the diff to review:\n   - If the user provides a PR number, fetch it with `gh pr diff <number>`\n   - Otherwise, use `git diff` (unstaged) and `git diff --cached` (staged)\n   - If a branch is specified, use `git diff <base>...<branch>`\n\n2. Check each item on this checklist:\n\n### Correctness\n- [ ] No dead code introduced (unused functions, imports, variables)\n- [ ] Exception handling wraps the right operation (e.g., `open()` not `read()` for Snap confinement)\n- [ ] No silent exception swallowing (`except: pass` or bare `except`)\n- [ ] No O(n²) patterns where O(n) is possible (linear search in a loop → use dict/set)\n- [ ] No `list.pop(0)` for queues → suggest `collections.deque(maxlen=N)`\n\n### Security\n- [ ] No credentials/secrets exposed in plain text\n- [ ] No new endpoints without considering auth implications\n- [ ] Sensitive config keys filtered from API responses\n- [ ] No command injection, XSS, or SQL injection vectors\n\n### Compatibility\n- [ ] Default behaviour unchanged (or breaking change explicitly documented)\n- [ ] Cross-platform considerations (Linux/macOS/Windows)\n- [ ] Configuration keys documented in `conf/glances.conf`\n- [ ] PR targets the right branch: `develop` for Glances v5, `support/glancesv4` for a v4 bugfix or security fix\n\n### Style\n- [ ] Code passes `make pre-commit` (supersedes `make format && make lint`)\n- [ ] No unnecessary comments, docstrings, or type annotations on unchanged code\n- [ ] Plugin/export conventions followed (class naming, auto-discovery, fields_description)\n\n3. Report findings grouped by severity:\n   - **Blocking** — must fix before merge\n   - **Suggestion** — improvement, not required\n   - **Nit** — style/cosmetic, optional\n\n4. Use the tone from CLAUDE.md: acknowledge quality, label problems as \"blocking concern\", invite collaboration.\n","isInternal":false,"tokens":474,"sizeBytes":1955},{"name":"test.md","path":".claude/skills/test.md","rawUrl":"https://raw.githubusercontent.com/nicolargo/glances/HEAD/.claude/skills/test.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# Skill: Run Tests\n\nRun the appropriate Glances test suite based on the current changes or user request.\n\n## Instructions\n\n1. First, check which files have been modified using `git diff --name-only` (staged + unstaged).\n2. Determine which test suite(s) to run based on the changed files:\n   - Changes in `glances/plugins/` → `make test-plugins` and/or specific `pytest tests/test_plugin_<name>.py`\n   - Changes in `glances/webserver.py` or `glances/routes.py` → `make test-restful`\n   - Changes in `glances/outputs/glances_mcp.py` or `glances/outputs/mcp_adapter.py` → `pytest tests/test_mcp_adapter.py`\n   - Changes in `glances/outputs/static/` → `make test-js` and `make test-webui`\n   - Changes in `glances/exports/` → `pytest tests/test_export_*.py`; `make test-exports` for the integration scripts (requires Docker)\n   - Changes in `glances/client.py` → `pytest tests/test_client.py`\n   - Changes in core files (`glances/*.py`) → the matching `tests/test_<module>.py`, then `make test`\n   - If unsure or broad changes → `make test` (runs all tests)\n3. If the user specifies a test target (e.g., \"run core tests\"), use that directly.\n4. Run the tests using the Makefile targets. The virtualenv must already exist (`.venv-uv/`).\n5. If tests fail, analyze the output and suggest fixes.\n\n## Available test commands\n\n```bash\nmake test              # All tests\nmake test-core         # Core unit tests\nmake test-plugins      # Plugin tests\nmake test-api          # API unit tests\nmake test-restful      # REST API tests (routes + webserver)\nmake test-webui        # WebUI tests (browser tests need Playwright + Chromium)\nmake test-js           # WebUI JavaScript unit tests (Node.js)\nmake test-exports      # All export integration tests (needs Docker)\nmake memory-leak       # Memory leak profiling\n\n# Single test file or specific test:\n.venv-uv/bin/uv run pytest tests/test_core.py\n.venv-uv/bin/uv run pytest tests/test_core.py::TestGlances::test_016_subsample\n```\n","isInternal":false,"tokens":502,"sizeBytes":1982},{"name":"webui.md","path":".claude/skills/webui.md","rawUrl":"https://raw.githubusercontent.com/nicolargo/glances/HEAD/.claude/skills/webui.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# Skill: Build and Work with the WebUI\n\nBuild, update, or troubleshoot the Glances Vue.js WebUI.\n\n## Instructions\n\n### Build the WebUI\n```bash\nmake webui\n```\nThis runs `npm ci && npm run build` in `glances/outputs/static/`.\n\n### Development workflow\n1. Source files are in `glances/outputs/static/` (`js/*.vue`, `js/*.js`,\n   `css/glances.css`, `templates/index.html` and `browser.html`)\n2. Built output goes to `glances/outputs/static/public/` (`glances.js`,\n   `browser.js`; entries `js/app.js` and `js/browser_app.js`)\n3. Stack: Vue.js 3 + plain CSS (design tokens) — see `SKILL-webui.md`\n4. Test: `make test-js` (JavaScript unit tests) and `make test-webui`\n\n### Never commit the build artifacts\n\n`glances/outputs/static/public/glances.js` (and the other files under\n`glances/outputs/static/public/`) is generated by `make webui`. It is rebuilt\nand committed automatically at release time by the CI\n(see `.github/workflows/webui.yml`).\n\n**Never include `glances/outputs/static/public/glances.js` in a pull request.**\nCommit only the sources under `glances/outputs/static/`; a regenerated bundle\nin a PR creates huge unreviewable diffs and systematic merge conflicts.\nIf a local build dirtied it, restore it before committing:\n\n```bash\ngit checkout -- glances/outputs/static/public/\n```\n\n### Audit and update dependencies\n```bash\nmake webui-audit          # Run npm audit\nmake webui-audit-fix      # Fix audit issues and rebuild\nmake webui-update         # Update all JS dependencies and rebuild\n```\n\n### Run with WebUI\n```bash\nmake run-webserver        # Start Glances web server on port 61208\n```\nThen open http://localhost:61208 in a browser.\n\n### Design principles (from CLAUDE.md)\n- Strict typographic consistency across all plugins\n- Pixel-perfect sparkline alignment (CSS grid, fixed row heights)\n- Footer = vertical alert list (up to 10 entries)\n- No gauges — prefer sparklines with inline current value\n\n### Troubleshooting\n- If `npm ci` fails, check Node.js version (LTS required, currently 24.x in CI)\n- If build artifacts are stale, delete `glances/outputs/static/public/` and rebuild\n- The CI builds WebUI on `develop` branch only (see `.github/workflows/webui.yml`)\n","isInternal":false,"tokens":557,"sizeBytes":2187},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/nicolargo/glances/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md — Glances Maintainer Context\n\nBehavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.\n\n## 1. Think Before Coding\n\n**Don't assume. Don't hide confusion. Surface tradeoffs.**\n\nBefore implementing:\n\n- State your assumptions explicitly. If uncertain, ask.\n- If multiple interpretations exist, present them - don't pick silently.\n- If a simpler approach exists, say so. Push back when warranted.\n- If something is unclear, stop. Name what's confusing. Ask.\n\n## 2. Simplicity First\n\n**Minimum code that solves the problem. Nothing speculative.**\n\n- No features beyond what was asked.\n- No abstractions for single-use code.\n- No \"flexibility\" or \"configurability\" that wasn't requested.\n- No error handling for impossible scenarios.\n- If you write 200 lines and it could be 50, rewrite it.\n\nAsk yourself: \"Would a senior engineer say this is overcomplicated?\" If yes, simplify.\n\n## 3. Surgical Changes\n\n**Touch only what you must. Clean up only your own mess.**\n\nWhen editing existing code:\n\n- Don't \"improve\" adjacent code, comments, or formatting.\n- Don't refactor things that aren't broken.\n- Match existing style, even if you'd do it differently.\n- If you notice unrelated dead code, mention it - don't delete it.\n\nWhen your changes create orphans:\n\n- Remove imports/variables/functions that YOUR changes made unused.\n- Don't remove pre-existing dead code unless asked.\n\nThe test: Every changed line should trace directly to the user's request.\n\n## 4. Goal-Driven Execution\n\n**Define success criteria. Loop until verified.**\n\nTransform tasks into verifiable goals:\n\n- \"Add validation\" → \"Write tests for invalid inputs, then make them pass\"\n- \"Fix the bug\" → \"Write a test that reproduces it, then make it pass\"\n- \"Refactor X\" → \"Ensure tests pass before and after\"\n\nFor multi-step tasks, state a brief plan:\n\n```md\n1. [Step] → verify: [check]\n2. [Step] → verify: [check]\n3. [Step] → verify: [check]\n```\n\nStrong success criteria let you loop independently. Weak criteria (\"make it work\") require constant clarification.\n\n## Additionnal context\n\n### Tech stack\n\n| Layer | Technology |\n| --- | --- |\n| Backend | Python, psutil, FastAPI (REST API), curses (TUI) |\n| Frontend | Vue.js 3, plain CSS (design tokens) |\n| Export plugins | InfluxDB, MongoDB, MQTT, DuckDB, and others |\n| Infrastructure | GitHub Actions, Helm/Kubernetes, Snap (snapcraft) |\n| LLM abstraction | LiteLLM (multi-provider) |\n| GPU/NPU monitoring | pynvml, sysfs/debugfs |\n\n### Code principles\n\n#### Exception handling for Snap confinement\n\nWrap the `open()` call inside `try/except`, not just the `read()`. Snap's strict\nconfinement blocks host file access at the open stage, not the read stage.\n\n### Security\n\nGlances runs unauthenticated by default — this is intentional and documented.\nMost users deploy on private networks for personal use. See global CLAUDE.md\nfor the general OSS security philosophy (4 rules).\n\n#### Sensitive endpoints\n\n- `/api/5/config` and `/api/5/args` — never expose credentials in plain text\n  (InfluxDB passwords, MongoDB tokens, MQTT passphrases, SSL key paths, etc.)\n  for unauthenticated access. `/api/5/config` serves `as_dict_secure()` when\n  `[outputs] password` is set, and only `as_dict_public()` otherwise;\n  `/api/5/args` is always redacted (`glances/routes.py`).\n- CORS: `allow_origins=[\"*\"]` + `allow_credentials=True` is invalid per the\n  CORS spec and reflected by Starlette. Default: `allow_credentials=False`.\n- DNS rebinding: `TrustedHostMiddleware` via `webui_allowed_hosts` (optional).\n  The MCP endpoint is already protected via `mcp_allowed_hosts` +\n  `TransportSecuritySettings` in `glances/outputs/glances_mcp.py`.\n\n### Contribution management\n\n#### Before merging a PR\n\n- [ ] The code is actively used (no dead code)\n- [ ] Existing tests pass\n- [ ] New behaviour is covered by tests\n- [ ] New configuration keys are documented\n- [ ] Breaking changes are identified and documented\n- [ ] The PR targets the correct branch\n- [ ] Pre-commit hooks pass (`make pre-commit` — supersedes `make lint && make format`)\n\n#### Glances-specific output formats\n\n| Deliverable | Format |\n| --- | --- |\n| Changelog entry | `.rst` file following the `NEWS.rst` format |\n| Helm Chart | `.tgz` archive or structured directory |\n\n### UI / TUI\n\n#### Web UI\n\nStack: Vue.js 3 + plain CSS (design tokens in\n`glances/outputs/static/css/glances.css`).\n\nDesign principles:\n\n- Strict typographic consistency across all plugins (size, weight, opacity,\n  letter-spacing).\n- Pixel-perfect sparkline alignment (CSS grid, fixed row heights).\n- Footer = vertical alert list (up to 10 entries), not a single horizontal row.\n- No gauges — prefer sparklines with an inline current value.\n\n#### TUI (curses)\n\n- 256-colour system with automatic fallback for limited terminals.\n- Lightweight hierarchical separators for information density.\n\n---\n\n**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.\n","isInternal":false,"tokens":1207,"sizeBytes":5110}],"systemPromptSnippet":"<yakaai_skills repo=\"nicolargo/glances\">\n<!-- File: .claude/skills/debug.md (Tokens: ~571 | Category: anthropic-skill) -->\n# Skill: Debug a Glances Issue\n\nHelp diagnose and fix a bug in Glances.\n\n## Instructions\n\n1. **Understand the issue**: Ask for or identify:\n   - Error message or traceback\n   - Glances version and OS\n   - How to reproduce (standalone, web server, client/server, Docker?)\n   - Relevant config section from `glances.conf`\n\n2. **Locate the code path**: Based on the mode and plugin involved:\n   - Startup / CLI: `glances/main.py`\n   - Standalone TUI: `glances/outputs/glances_curses.py` (`Tui`) → `glances/outputs/curses_renderer.py`\n   - Web server: `glances/webserver.py` → `glances/routes.py`\n   - WebUI: `glances/outputs/static/js/`\n   - Client/server: `glances/client.py` (`-c`, over the REST API) / `glances/webserver.py` (`-s`)\n   - Scheduler / alerts: `glances/scheduler.py` / `glances/alerts.py`\n   - Plugin issue: `glances/plugins/<name>/model.py` (TUI layout: `render_curses.py`)\n   - Export issue: `glances/exports/glances_<name>/export.py`\n   - Process list: `glances/processes.py`\n   - Configuration: `glances/config.py`\n\n3. **Run Glances in debug mode** to get detailed logs:\n   ```bash\n   .venv-uv/bin/uv run python -m glances -d 2>&1 | tail -100\n   ```\n\n4. **Reproduce with tests** when possible:\n   ```bash\n   .venv-uv/bin/uv run pytest tests/test_plugin_<name>.py -v -k \"test_name\" 2>&1\n   ```\n\n5. **Fix and verify**:\n   - Apply surgical, atomic edits\n   - Run the relevant test suite\n   - Run `make format && make lint`\n   - Verify no regressions with `make test`\n\n## Common pitfalls\n\n- **psutil version differences**: Some psutil APIs behave differently across OS — always check `glances/globals.py` for platform flags (`LINUX`, `MACOS`, `WINDOWS`, `BSD`)\n- **Plugin update cycle**: a plugin has no payload before its first update (`GET /api/5/<plugin>` → `200 null`), and `rate: True` fields are absent on the first cycle — handle gracefully\n- **Snap confinement**: `PermissionError` at `open()`, not at `read()` — wrap the right call\n- **Docker socket access**: Requires mounting `/var/run/docker.sock` and proper permissions\n\n<!-- File: .claude/skills/docker.md (Tokens: ~509 | Category: anthropic-skill) -->\n# Skill: Build and Run Docker Images\n\nBuild, run, and troubleshoot Glances Docker images locally.\n\n## Instructions\n\n### Build images\n```bash\nmake docker                    # Build all images (Alpine + Ubuntu, full + minimal)\nmake docker-alpine-full        # Alpine full image\nmake docker-alpine-minimal     # Alpine minimal image\nmake docker-ubuntu-full        # Ubuntu full image\nmake docker-ubuntu-minimal     # Ubuntu minimal image\n```\n\nRequires Docker Buildx (`apt install docker-buildx` on Ubuntu).\n\n### Run containers\n```bash\nmake run-docker-alpine-full    # Run Alpine full in console mode\nmake run-docker-ubuntu-full    # Run Ubuntu full in console mode\n```\n\nDefault run options: `--rm --pid host --network host` with Docker/Podman socket mounts.\n\n### Security scan\n```bash\nmake trivy-docker              # Run Trivy on all local images\n```\n\n### Dockerfiles\nLocated in `docker-files/`:\n- `alpine.Dockerfile` — multi-stage: `minimal` and `full` targets\n- `ubuntu.Dockerfile` — multi-stage: `minimal` and `full` targets\n\n### Key considerations\n- Prefer `SYS_PTRACE` capability over `privileged: true`\n- Mount Docker socket read-only: `-v /var/run/docker.sock:/var/run/docker.sock:ro`\n- For Kubernetes: use DaemonSet (one pod per node) for system monitoring\n- The images run Glances v5 (`python -m glances ${GLANCES_OPT}`); in server mode (`-w`) bind to `0.0.0.0` (`bind_address` in `docker-compose/glances.conf`, or `-B 0.0.0.0`), v5 defaults to loopback\n- CI builds Docker images on `develop` (the `dev` image) and on tags only (see `.github/workflows/build_docker.yml`); a pre-release tag (`v5.0.0b1`, `rc`) never moves `latest`\n\n### Troubleshooting\n- **Socket permission errors**: Ensure the user is in the `docker` group or use Podman\n- **Multi-arch builds**: CI uses QEMU for cross-platform (ARM64) — local builds are native only by default\n- **Build timeouts**: Multi-arch builds can take 20-40 min; CI timeout is 60 min\n\n<!-- File: .claude/skills/lint.md (Tokens: ~198 | Category: anthropic-skill) -->\n# Skill: Lint and Format\n\nRun code formatting and linting on the Glances codebase.\n\n## Instructions\n\n1. Run formatting first, then linting:\n   ```bash\n   make format && make lint\n   ```\n2. If there are remaining issues that `--fix` could not resolve, list them and suggest manual fixes.\n3. Optionally, if the user asks for a full check, run all pre-commit hooks:\n   ```bash\n   make pre-commit\n   ```\n\n## Tools used\n\n- **Ruff** for both formatting and linting (configured in `pyproject.toml`)\n- **Pre-commit hooks** include: ruff, gitleaks (secret detection), shellcheck, and others\n\n## Notes\n\n- The virtualenv must already exist (`.venv-uv/`). If not, run `make venv-dev` first.\n- Never change ruff configuration without explicit approval — linting rules are shared across all contributors.\n\n<!-- File: .claude/skills/new-export.md (Tokens: ~1012 | Category: anthropic-skill) -->\n# Skill: Create a New Export Module\n\nScaffold a new Glances export module following project conventions.\n\n## Instructions\n\nAsk the user for:\n1. **Export name** (lowercase, e.g., `clickhouse`)\n2. **Target system** (database, message queue, file format, etc.)\n3. **Required Python library** (e.g., `clickhouse-driver`)\n\nThen create the export following this structure:\n\n### File structure\n\n```\nglances/exports/glances_<name>/\n    __init__.py    # Package marker (docstring only)\n    export.py      # Export implementation\n```\n\nNo registration needed — `discover_exporters()` (`glances/main.py`) imports\n`glances.exports.glances_<name>.export` when the user passes\n`--export <name>`.\n\n### Template\n\nThe `export.py` module must follow this pattern (see\n`glances/exports/glances_statsd/export.py` for a complete short example and\n`SKILL-exporter.md` for the contract):\n\n```python\n#\n# Glances - An eye on your system\n#\n# SPDX-FileCopyrightText: 2026 Nicolas Hennion <nicolas@nicolargo.com>\n#\n# SPDX-License-Identifier: LGPL-3.0-only\n#\n\n\"\"\"Glances — <Name> export module.\"\"\"\n\nfrom __future__ import annotations\n\nimport sys\nfrom typing import TYPE_CHECKING, Any\n\nfrom glances.exports.export_base import GlancesExportBase\nfrom glances.logger import logger\n\nif TYPE_CHECKING:\n    import argparse\n\n    from glances.config import GlancesConfig\n\n\nclass Export(GlancesExportBase):\n    \"\"\"Send Glances stats to <Name>.\"\"\"\n\n    export_name = \"<name>\"\n\n    def __init__(self, config: GlancesConfig, args: argparse.Namespace | None = None) -> None:\n        super().__init__(config, args)\n        # Load host, port (mandatory) and options from the [<name>] section\n        if not self.load_conf(\"<name>\", mandatories=(\"host\", \"port\"), options=()):\n            logger.critical(\"Missing <name> config\")\n            sys.exit(2)\n        self.client = self.init()\n\n    def init(self) -> Any:\n        \"\"\"Connect to the target system. Import the client library lazily.\"\"\"\n        try:\n            from <library> import Client\n        except ImportError as e:\n            logger.critical(\"Export <name> needs the <library> library (%s)\", e)\n            sys.exit(2)\n        logger.info(\"Stats will be exported to <Name> %s:%s\", self.host, self.port)\n        return Client(self.host, int(self.port))\n\n    def export(self, name: str, columns: list[str], points: list[Any]) -> None:\n        \"\"\"Export one plugin's stats (blocking I/O is fine: runs in a worker thread).\n\n        Args:\n            name: Plugin name (e.g., 'cpu', 'mem')\n            columns: List of field names\n            points: List of field values (same order as columns)\n        \"\"\"\n        try:\n            self.client.send(name, dict(zip(columns, points)))\n        except Exception as e:\n            logger.warning(\"Cannot export %s stats to <Name> (%s)\", name, e)\n\n    def exit(self) -> None:\n        super().exit()  # FIRST: waits for an in-flight update()\n        self.client.close()\n```\n\n### Key conventions\n\n- Directory: `glances/exports/glances_<name>/`, implementation in `export.py`\n- Class must be named `Export` (exactly) and inherit from `GlancesExportBase`\n- `export_name` matches the directory suffix and the `--export` token\n- Configuration is loaded from the `[<name>]` section in `glances.conf` with\n  `self.load_conf()`; a missing section or mandatory key is fatal\n- `update()` (base class) calls `plugin.get_export()` then\n  `export(name, columns, points)` for each plugin, once per `[export] refresh`\n- Never read the `StatsStore` directly; never interpolate monitored data\n  into a query (CVE-2026-32611 pattern)\n- Name sensitive config keys so they match `GlancesConfig.SECRET_KEYS` (`password`,\n  `token`, …) and `as_dict_secure()` redacts them\n\n### After creation\n\n1. Add the dependency to `pyproject.toml` under the appropriate optional group\n2. Add a `[<name>]` section in `conf/glances.conf` with all config keys (commented out)\n3. Add unit tests `tests/test_export_<name>.py` (fakes in `tests/export_fakes.py`)\n   and, optionally, an integration script `tests/test_export_<name>.sh`\n4. Run `make test` to verify no regressions\n\n<!-- File: .claude/skills/new-plugin.md (Tokens: ~786 | Category: anthropic-skill) -->\n# Skill: Create a New Plugin\n\nScaffold a new Glances monitoring plugin following project conventions.\n\n## Instructions\n\nAsk the user for:\n1. **Plugin name** (lowercase, e.g., `battery`)\n2. **What it monitors** (brief description)\n3. **Data source** (psutil, sysfs, external library, etc.)\n\nThen create the plugin following this structure:\n\n### File structure\n\n```\nglances/plugins/<name>/\n    __init__.py         # Package marker (docstring only)\n    model.py            # Plugin implementation (class PluginModel)\n    render_curses.py    # OPTIONAL — per-plugin TUI layout\n```\n\nNo registration needed — `discover_plugins()` (`glances/main.py`) imports\nevery `glances.plugins.<name>.model`. Its place in the TUI comes from the\nslot tuples in `glances/outputs/curses_renderer.py` (`LEFT_SLOT`,\n`RIGHT_SLOT`, …). The WebUI needs `glances/outputs/static/js/Plugin<Name>.vue`\nand one entry in `glances/outputs/static/js/plugins/index.js`.\n\n### Template\n\n`model.py` must follow this pattern (see `SKILL-plugin.md` for the full\ncontract and `glances/plugins/uptime/model.py` for a short example):\n\n```python\n#\n# Glances - An eye on your system\n#\n# SPDX-FileCopyrightText: 2026 Nicolas Hennion <nicolas@nicolargo.com>\n#\n# SPDX-License-Identifier: LGPL-3.0-only\n#\n\n\"\"\"Glances — <name> plugin (scalar).\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nfrom typing import Any, ClassVar\n\nimport psutil\n\nfrom glances.plugins.plugin.base import GlancesPluginBase\n\n\nclass PluginModel(GlancesPluginBase[dict]):\n    \"\"\"<Name> plugin (scalar).\"\"\"\n\n    plugin_name: ClassVar[str] = \"<name>\"\n    IS_COLLECTION: ClassVar[bool] = False\n\n    fields_description: ClassVar[dict[str, dict[str, Any]]] = {\n        \"field_name\": {\n            \"description\": \"Human-readable description.\",\n            \"unit\": \"percent\",  # percent, number, bytes, bytespers, seconds, string, ...\n            \"watched\": True,\n            \"default_thresholds\": {\"careful\": 50.0, \"warning\": 70.0, \"critical\": 90.0},\n        },\n    }\n\n    async def _grab_stats(self) -> dict:\n        \"\"\"Collect the raw stats. Blocking calls go through asyncio.to_thread().\"\"\"\n        value = await asyncio.to_thread(psutil.cpu_percent)\n        return {\"field_name\": value}\n```\n\n### Key conventions\n\n- Class name: `PluginModel` (exactly), in `glances/plugins/<name>/model.py`\n- Inherit from `GlancesPluginBase[dict]` (scalar) or `GlancesPluginBase[list]`\n  (collection, `IS_COLLECTION = True`, exactly one field with `primary_key: True`)\n- `_grab_stats()` is the only mandatory hook; never override `update()`\n- `fields_description` declares metadata for each field (unit, thresholds,\n  `rate`, `watched`, `exportable`); undeclared fields are stripped\n- Keep `model.py` free of curses / UI code — layout goes in `render_curses.py`\n- No SNMP: SNMP is not ported to v5\n\n### After creation\n\n1. Create a test file: `tests/test_plugin_<name>.py` (and\n   `tests/test_plugin_<name>_render_curses.py` with a `render_curses.py`)\n2. Run `make test-plugins` to verify\n3. Add configuration section in `conf/glances.conf` if needed\n\n<!-- File: .claude/skills/review.md (Tokens: ~474 | Category: anthropic-skill) -->\n# Skill: Review Code Changes\n\nReview the current changes (staged or unstaged) against Glances project guidelines.\n\n## Instructions\n\n1. Get the diff to review:\n   - If the user provides a PR number, fetch it with `gh pr diff <number>`\n   - Otherwise, use `git diff` (unstaged) and `git diff --cached` (staged)\n   - If a branch is specified, use `git diff <base>...<branch>`\n\n2. Check each item on this checklist:\n\n### Correctness\n- [ ] No dead code introduced (unused functions, imports, variables)\n- [ ] Exception handling wraps the right operation (e.g., `open()` not `read()` for Snap confinement)\n- [ ] No silent exception swallowing (`except: pass` or bare `except`)\n- [ ] No O(n²) patterns where O(n) is possible (linear search in a loop → use dict/set)\n- [ ] No `list.pop(0)` for queues → suggest `collections.deque(maxlen=N)`\n\n### Security\n- [ ] No credentials/secrets exposed in plain text\n- [ ] No new endpoints without considering auth implications\n- [ ] Sensitive config keys filtered from API responses\n- [ ] No command injection, XSS, or SQL injection vectors\n\n### Compatibility\n- [ ] Default behaviour unchanged (or breaking change explicitly documented)\n- [ ] Cross-platform considerations (Linux/macOS/Windows)\n- [ ] Configuration keys documented in `conf/glances.conf`\n- [ ] PR targets the right branch: `develop` for Glances v5, `support/glancesv4` for a v4 bugfix or security fix\n\n### Style\n- [ ] Code passes `make pre-commit` (supersedes `make format && make lint`)\n- [ ] No unnecessary comments, docstrings, or type annotations on unchanged code\n- [ ] Plugin/export conventions followed (class naming, auto-discovery, fields_description)\n\n3. Report findings grouped by severity:\n   - **Blocking** — must fix before merge\n   - **Suggestion** — improvement, not required\n   - **Nit** — style/cosmetic, optional\n\n4. Use the tone from CLAUDE.md: acknowledge quality, label problems as \"blocking concern\", invite collaboration.\n\n<!-- File: .claude/skills/test.md (Tokens: ~502 | Category: anthropic-skill) -->\n# Skill: Run Tests\n\nRun the appropriate Glances test suite based on the current changes or user request.\n\n## Instructions\n\n1. First, check which files have been modified using `git diff --name-only` (staged + unstaged).\n2. Determine which test suite(s) to run based on the changed files:\n   - Changes in `glances/plugins/` → `make test-plugins` and/or specific `pytest tests/test_plugin_<name>.py`\n   - Changes in `glances/webserver.py` or `glances/routes.py` → `make test-restful`\n   - Changes in `glances/outputs/glances_mcp.py` or `glances/outputs/mcp_adapter.py` → `pytest tests/test_mcp_adapter.py`\n   - Changes in `glances/outputs/static/` → `make test-js` and `make test-webui`\n   - Changes in `glances/exports/` → `pytest tests/test_export_*.py`; `make test-exports` for the integration scripts (requires Docker)\n   - Changes in `glances/client.py` → `pytest tests/test_client.py`\n   - Changes in core files (`glances/*.py`) → the matching `tests/test_<module>.py`, then `make test`\n   - If unsure or broad changes → `make test` (runs all tests)\n3. If the user specifies a test target (e.g., \"run core tests\"), use that directly.\n4. Run the tests using the Makefile targets. The virtualenv must already exist (`.venv-uv/`).\n5. If tests fail, analyze the output and suggest fixes.\n\n## Available test commands\n\n```bash\nmake test              # All tests\nmake test-core         # Core unit tests\nmake test-plugins      # Plugin tests\nmake test-api          # API unit tests\nmake test-restful      # REST API tests (routes + webserver)\nmake test-webui        # WebUI tests (browser tests need Playwright + Chromium)\nmake test-js           # WebUI JavaScript unit tests (Node.js)\nmake test-exports      # All export integration tests (needs Docker)\nmake memory-leak       # Memory leak profiling\n\n# Single test file or specific test:\n.venv-uv/bin/uv run pytest tests/test_core.py\n.venv-uv/bin/uv run pytest tests/test_core.py::TestGlances::test_016_subsample\n```\n\n<!-- File: .claude/skills/webui.md (Tokens: ~557 | Category: anthropic-skill) -->\n# Skill: Build and Work with the WebUI\n\nBuild, update, or troubleshoot the Glances Vue.js WebUI.\n\n## Instructions\n\n### Build the WebUI\n```bash\nmake webui\n```\nThis runs `npm ci && npm run build` in `glances/outputs/static/`.\n\n### Development workflow\n1. Source files are in `glances/outputs/static/` (`js/*.vue`, `js/*.js`,\n   `css/glances.css`, `templates/index.html` and `browser.html`)\n2. Built output goes to `glances/outputs/static/public/` (`glances.js`,\n   `browser.js`; entries `js/app.js` and `js/browser_app.js`)\n3. Stack: Vue.js 3 + plain CSS (design tokens) — see `SKILL-webui.md`\n4. Test: `make test-js` (JavaScript unit tests) and `make test-webui`\n\n### Never commit the build artifacts\n\n`glances/outputs/static/public/glances.js` (and the other files under\n`glances/outputs/static/public/`) is generated by `make webui`. It is rebuilt\nand committed automatically at release time by the CI\n(see `.github/workflows/webui.yml`).\n\n**Never include `glances/outputs/static/public/glances.js` in a pull request.**\nCommit only the sources under `glances/outputs/static/`; a regenerated bundle\nin a PR creates huge unreviewable diffs and systematic merge conflicts.\nIf a local build dirtied it, restore it before committing:\n\n```bash\ngit checkout -- glances/outputs/static/public/\n```\n\n### Audit and update dependencies\n```bash\nmake webui-audit          # Run npm audit\nmake webui-audit-fix      # Fix audit issues and rebuild\nmake webui-update         # Update all JS dependencies and rebuild\n```\n\n### Run with WebUI\n```bash\nmake run-webserver        # Start Glances web server on port 61208\n```\nThen open http://localhost:61208 in a browser.\n\n### Design principles (from CLAUDE.md)\n- Strict typographic consistency across all plugins\n- Pixel-perfect sparkline alignment (CSS grid, fixed row heights)\n- Footer = vertical alert list (up to 10 entries)\n- No gauges — prefer sparklines with inline current value\n\n### Troubleshooting\n- If `npm ci` fails, check Node.js version (LTS required, currently 24.x in CI)\n- If build artifacts are stale, delete `glances/outputs/static/public/` and rebuild\n- The CI builds WebUI on `develop` branch only (see `.github/workflows/webui.yml`)\n\n<!-- File: CLAUDE.md (Tokens: ~1207 | Category: claude-rule) -->\n# CLAUDE.md — Glances Maintainer Context\n\nBehavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.\n\n## 1. Think Before Coding\n\n**Don't assume. Don't hide confusion. Surface tradeoffs.**\n\nBefore implementing:\n\n- State your assumptions explicitly. If uncertain, ask.\n- If multiple interpretations exist, present them - don't pick silently.\n- If a simpler approach exists, say so. Push back when warranted.\n- If something is unclear, stop. Name what's confusing. Ask.\n\n## 2. Simplicity First\n\n**Minimum code that solves the problem. Nothing speculative.**\n\n- No features beyond what was asked.\n- No abstractions for single-use code.\n- No \"flexibility\" or \"configurability\" that wasn't requested.\n- No error handling for impossible scenarios.\n- If you write 200 lines and it could be 50, rewrite it.\n\nAsk yourself: \"Would a senior engineer say this is overcomplicated?\" If yes, simplify.\n\n## 3. Surgical Changes\n\n**Touch only what you must. Clean up only your own mess.**\n\nWhen editing existing code:\n\n- Don't \"improve\" adjacent code, comments, or formatting.\n- Don't refactor things that aren't broken.\n- Match existing style, even if you'd do it differently.\n- If you notice unrelated dead code, mention it - don't delete it.\n\nWhen your changes create orphans:\n\n- Remove imports/variables/functions that YOUR changes made unused.\n- Don't remove pre-existing dead code unless asked.\n\nThe test: Every changed line should trace directly to the user's request.\n\n## 4. Goal-Driven Execution\n\n**Define success criteria. Loop until verified.**\n\nTransform tasks into verifiable goals:\n\n- \"Add validation\" → \"Write tests for invalid inputs, then make them pass\"\n- \"Fix the bug\" → \"Write a test that reproduces it, then make it pass\"\n- \"Refactor X\" → \"Ensure tests pass before and after\"\n\nFor multi-step tasks, state a brief plan:\n\n```md\n1. [Step] → verify: [check]\n2. [Step] → verify: [check]\n3. [Step] → verify: [check]\n```\n\nStrong success criteria let you loop independently. Weak criteria (\"make it work\") require constant clarification.\n\n## Additionnal context\n\n### Tech stack\n\n| Layer | Technology |\n| --- | --- |\n| Backend | Python, psutil, FastAPI (REST API), curses (TUI) |\n| Frontend | Vue.js 3, plain CSS (design tokens) |\n| Export plugins | InfluxDB, MongoDB, MQTT, DuckDB, and others |\n| Infrastructure | GitHub Actions, Helm/Kubernetes, Snap (snapcraft) |\n| LLM abstraction | LiteLLM (multi-provider) |\n| GPU/NPU monitoring | pynvml, sysfs/debugfs |\n\n### Code principles\n\n#### Exception handling for Snap confinement\n\nWrap the `open()` call inside `try/except`, not just the `read()`. Snap's strict\nconfinement blocks host file access at the open stage, not the read stage.\n\n### Security\n\nGlances runs unauthenticated by default — this is intentional and documented.\nMost users deploy on private networks for personal use. See global CLAUDE.md\nfor the general OSS security philosophy (4 rules).\n\n#### Sensitive endpoints\n\n- `/api/5/config` and `/api/5/args` — never expose credentials in plain text\n  (InfluxDB passwords, MongoDB tokens, MQTT passphrases, SSL key paths, etc.)\n  for unauthenticated access. `/api/5/config` serves `as_dict_secure()` when\n  `[outputs] password` is set, and only `as_dict_public()` otherwise;\n  `/api/5/args` is always redacted (`glances/routes.py`).\n- CORS: `allow_origins=[\"*\"]` + `allow_credentials=True` is invalid per the\n  CORS spec and reflected by Starlette. Default: `allow_credentials=False`.\n- DNS rebinding: `TrustedHostMiddleware` via `webui_allowed_hosts` (optional).\n  The MCP endpoint is already protected via `mcp_allowed_hosts` +\n  `TransportSecuritySettings` in `glances/outputs/glances_mcp.py`.\n\n### Contribution management\n\n#### Before merging a PR\n\n- [ ] The code is actively used (no dead code)\n- [ ] Existing tests pass\n- [ ] New behaviour is covered by tests\n- [ ] New configuration keys are documented\n- [ ] Breaking changes are identified and documented\n- [ ] The PR targets the correct branch\n- [ ] Pre-commit hooks pass (`make pre-commit` — supersedes `make lint && make format`)\n\n#### Glances-specific output formats\n\n| Deliverable | Format |\n| --- | --- |\n| Changelog entry | `.rst` file following the `NEWS.rst` format |\n| Helm Chart | `.tgz` archive or structured directory |\n\n### UI / TUI\n\n#### Web UI\n\nStack: Vue.js 3 + plain CSS (design tokens in\n`glances/outputs/static/css/glances.css`).\n\nDesign principles:\n\n- Strict typographic consistency across all plugins (size, weight, opacity,\n  letter-spacing).\n- Pixel-perfect sparkline alignment (CSS grid, fixed row heights).\n- Footer = vertical alert list (up to 10 entries), not a single horizontal row.\n- No gauges — prefer sparklines with an inline current value.\n\n#### TUI (curses)\n\n- 256-colour system with automatic fallback for limited terminals.\n- Lightweight hierarchical separators for information density.\n\n---\n\n**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.\n</yakaai_skills>"}