skillvitals 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. skillvitals-0.1.0/.gitignore +17 -0
  2. skillvitals-0.1.0/.python-version +1 -0
  3. skillvitals-0.1.0/LICENSE +21 -0
  4. skillvitals-0.1.0/PKG-INFO +178 -0
  5. skillvitals-0.1.0/PUBLISHING.md +71 -0
  6. skillvitals-0.1.0/README.md +149 -0
  7. skillvitals-0.1.0/docs/sample-dashboard.html +205 -0
  8. skillvitals-0.1.0/docs/superpowers/plans/2026-05-25-skillvitals.md +151 -0
  9. skillvitals-0.1.0/pyproject.toml +65 -0
  10. skillvitals-0.1.0/scripts/gen_demo.py +70 -0
  11. skillvitals-0.1.0/src/skillvitals/__init__.py +3 -0
  12. skillvitals-0.1.0/src/skillvitals/__main__.py +4 -0
  13. skillvitals-0.1.0/src/skillvitals/analysis.py +139 -0
  14. skillvitals-0.1.0/src/skillvitals/cli.py +283 -0
  15. skillvitals-0.1.0/src/skillvitals/config.py +73 -0
  16. skillvitals-0.1.0/src/skillvitals/dashboard.py +84 -0
  17. skillvitals-0.1.0/src/skillvitals/hooks.py +49 -0
  18. skillvitals-0.1.0/src/skillvitals/logparser.py +196 -0
  19. skillvitals-0.1.0/src/skillvitals/models.py +158 -0
  20. skillvitals-0.1.0/src/skillvitals/pipeline.py +58 -0
  21. skillvitals-0.1.0/src/skillvitals/prescribe.py +148 -0
  22. skillvitals-0.1.0/src/skillvitals/registry.py +210 -0
  23. skillvitals-0.1.0/src/skillvitals/report.py +107 -0
  24. skillvitals-0.1.0/src/skillvitals/server.py +125 -0
  25. skillvitals-0.1.0/src/skillvitals/storage.py +158 -0
  26. skillvitals-0.1.0/src/skillvitals/templates/dashboard.html.j2 +113 -0
  27. skillvitals-0.1.0/src/skillvitals/testharness.py +145 -0
  28. skillvitals-0.1.0/src/skillvitals/tokens.py +26 -0
  29. skillvitals-0.1.0/tests/__init__.py +0 -0
  30. skillvitals-0.1.0/tests/conftest.py +35 -0
  31. skillvitals-0.1.0/tests/test_analysis.py +72 -0
  32. skillvitals-0.1.0/tests/test_cli.py +59 -0
  33. skillvitals-0.1.0/tests/test_dashboard.py +42 -0
  34. skillvitals-0.1.0/tests/test_logparser.py +95 -0
  35. skillvitals-0.1.0/tests/test_prescribe.py +87 -0
  36. skillvitals-0.1.0/tests/test_registry.py +93 -0
  37. skillvitals-0.1.0/tests/test_report.py +41 -0
  38. skillvitals-0.1.0/tests/test_server.py +36 -0
  39. skillvitals-0.1.0/tests/test_storage.py +65 -0
  40. skillvitals-0.1.0/tests/test_testharness.py +72 -0
  41. skillvitals-0.1.0/tests/test_tokens.py +16 -0
  42. skillvitals-0.1.0/uv.lock +1887 -0
@@ -0,0 +1,17 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # skillvitals
13
+ .skillvitals/
14
+ *.sqlite
15
+ dashboard.html
16
+ .coverage
17
+ htmlcov/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: skillvitals
3
+ Version: 0.1.0
4
+ Summary: Skill observability for Claude Code — see which skills fire, which are dormant, what they cost in context, and what's broken.
5
+ Project-URL: Homepage, https://github.com/PraveenKumarSridhar/skillvitals
6
+ Project-URL: Repository, https://github.com/PraveenKumarSridhar/skillvitals
7
+ Project-URL: Issues, https://github.com/PraveenKumarSridhar/skillvitals/issues
8
+ Author: Pk
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: anthropic,claude,claude-code,mcp,observability,skills
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: click>=8.1
22
+ Requires-Dist: fastmcp>=2.0
23
+ Requires-Dist: jinja2>=3.1
24
+ Requires-Dist: pyyaml>=6.0
25
+ Requires-Dist: rich>=13.0
26
+ Provides-Extra: llm
27
+ Requires-Dist: anthropic>=0.40; extra == 'llm'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # skillvitals
31
+
32
+ **Skill observability for Claude Code.** See which of your skills fire, which are
33
+ dormant, what they cost in context, and what's broken.
34
+
35
+ Claude Code skills are supposed to auto-activate. In practice many never fire —
36
+ they just sit in every session burning context tokens. The ecosystem has plenty
37
+ of tools to *generate* skill-activation hooks. `skillvitals` is the missing
38
+ diagnostic layer: it treats every installed skill as a monitored service and
39
+ tells you *did it fire? when? at what context cost? is it dead weight?*
40
+
41
+ ```text
42
+ $ skillvitals scan
43
+
44
+ skillvitals
45
+ ┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
46
+ ┃ skill ┃ fires ┃ engaged ┃ ctx ┃ last seen ┃ status ┃
47
+ ┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
48
+ │ frontend-design │ 31 │ 140 │ 6.4k │ today │ ✅ healthy │
49
+ │ docx │ 47 │ 120 │ 2.1k │ today │ ✅ healthy │
50
+ │ pdf │ 23 │ 60 │ 1.8k │ 1d ago │ ✅ healthy │
51
+ │ sql-tuner │ 4 │ 9 │ 2.6k │ 9d ago │ ✅ healthy │
52
+ │ ab-test-coach │ 2 │ 2 │ 5.7k │ 3d ago │ ⚠️ misfiring │
53
+ │ leakcheck │ 1 │ 3 │ 3.1k │ 40d ago │ ⚠️ dormant │
54
+ │ data-analysis │ 0 │ 0 │ 4.2k │ never │ 💤 never-fired │
55
+ │ changelog-writer │ 0 │ 0 │ 1.4k │ never │ 💤 never-fired │
56
+ └────────────────────┴───────┴─────────┴──────┴───────────┴────────────────┘
57
+
58
+ 3 dormant/never-fired skills are costing you 8.7k tokens per session.
59
+ Run `skillvitals prescribe` for fixes.
60
+ ```
61
+
62
+ That last line is the point: **most people have thousands of tokens of dead
63
+ weight in every single session and no way to see it.**
64
+
65
+ ## Install
66
+
67
+ ```bash
68
+ uvx skillvitals scan # run without installing
69
+ # or
70
+ pip install skillvitals
71
+ ```
72
+
73
+ Requires Python 3.11+. Reads your existing Claude Code data under `~/.claude` —
74
+ nothing is sent anywhere (see [Privacy](#privacy)).
75
+
76
+ ## Commands
77
+
78
+ | Command | What it does |
79
+ |---------|--------------|
80
+ | `skillvitals scan` | Headline table: fires, engagement, context cost, health, per skill. |
81
+ | `skillvitals report` | Markdown report (`-o report.md` to save / share). |
82
+ | `skillvitals history` | Per-skill activation history across sessions. |
83
+ | `skillvitals dormancy` | Skills inactive for N days and the tokens they cost (`--days 14`). |
84
+ | `skillvitals prescribe` | Concrete fixes for weak/dormant/redundant skills (`--rewrite` for LLM rewrites). |
85
+ | `skillvitals test --skill X` | Synthetic activation-test prompts (`--live` runs them through headless Claude Code). |
86
+ | `skillvitals dashboard --open` | Self-contained HTML dashboard at `~/.skillvitals/dashboard.html`. |
87
+ | `skillvitals serve` | Run as an MCP server. |
88
+
89
+ ## Use it as an MCP server
90
+
91
+ `skillvitals` is also an MCP server, so you can ask Claude Code about your skills
92
+ in plain language ("which of my skills are dormant?").
93
+
94
+ ```bash
95
+ claude mcp add skillvitals -- uvx skillvitals serve
96
+ ```
97
+
98
+ Or add it to your MCP config manually:
99
+
100
+ ```json
101
+ {
102
+ "mcpServers": {
103
+ "skillvitals": { "command": "uvx", "args": ["skillvitals", "serve"] }
104
+ }
105
+ }
106
+ ```
107
+
108
+ Exposed tools: `vitals_scan`, `vitals_history`, `vitals_dormancy`,
109
+ `vitals_report`, `vitals_prescribe`, `vitals_test`, `vitals_dashboard`.
110
+
111
+ ## How it works
112
+
113
+ skillvitals reads two things, entirely locally:
114
+
115
+ 1. **Your installed skills** — every `SKILL.md` under `~/.claude/skills`, the
116
+ plugin cache, and the current project's `.claude/skills`. It parses the
117
+ frontmatter, estimates the context cost (tokens of the loaded `SKILL.md`),
118
+ and scores description quality.
119
+
120
+ 2. **Your session logs** — the JSONL transcripts under `~/.claude/projects`. It
121
+ extracts two activation signals per skill:
122
+ - **fires** — explicit `Skill()` invocations (the skill was activated).
123
+ - **engaged** — assistant messages tagged with that skill's `attributionSkill`
124
+ (how much the skill was actually leaned on afterward).
125
+
126
+ Joining the two gives each skill a health status:
127
+
128
+ | status | meaning |
129
+ |--------|---------|
130
+ | ✅ **healthy** | activated recently with real follow-through |
131
+ | ⚠️ **misfiring** | invoked but barely used afterward — may be matching the wrong prompts |
132
+ | ⚠️ **dormant** | activated before, but not within the window |
133
+ | 💤 **never-fired** | installed, costs tokens, has never activated |
134
+ | ❓ **orphan** | appears in logs but is no longer installed |
135
+
136
+ These are honest heuristics, not ground truth — the thresholds are documented in
137
+ `analysis.py` and `prescribe.py`.
138
+
139
+ ## Privacy
140
+
141
+ 100% local. skillvitals only reads files already on your machine under
142
+ `~/.claude`, writes a local SQLite cache to `~/.skillvitals/db.sqlite`, and a
143
+ local HTML file. **No network calls, no telemetry, nothing leaves your machine** —
144
+ with two explicit, opt-in exceptions:
145
+
146
+ - `skillvitals test --live` spawns headless Claude Code to measure real activation.
147
+ - `skillvitals prescribe --rewrite` calls the Anthropic API to rewrite weak
148
+ descriptions. Requires `pip install 'skillvitals[llm]'` and `ANTHROPIC_API_KEY`.
149
+
150
+ Both are off by default.
151
+
152
+ ## What it deliberately doesn't do
153
+
154
+ - **Generate activation hooks.** That space is well covered (`skills-hook`,
155
+ `claude-skills-supercharged`, …). Pair skillvitals with one of those — it
156
+ *reports* whether a `UserPromptSubmit` hook exists, it doesn't write one.
157
+ - **Auto-apply fixes.** v1 shows prescriptions; it doesn't edit your skills.
158
+ - **Phone home.** No cloud, no accounts, no team aggregation.
159
+
160
+ ## Configuration
161
+
162
+ Environment variables (all optional):
163
+
164
+ - `SKILLVITALS_CLAUDE_HOME` — the `.claude` dir (default `~/.claude`).
165
+ - `SKILLVITALS_HOME` — where the db + dashboard live (default `~/.skillvitals`).
166
+ - `ANTHROPIC_API_KEY` — only needed for the opt-in LLM features.
167
+
168
+ ## Development
169
+
170
+ ```bash
171
+ uv sync --extra llm
172
+ uv run pytest # 47 tests
173
+ uv run ruff check src tests
174
+ ```
175
+
176
+ ## License
177
+
178
+ MIT © 2026 Pk
@@ -0,0 +1,71 @@
1
+ # Publishing skillvitals
2
+
3
+ Everything here is **left for you to run** — I built and validated the package
4
+ locally but deliberately did not publish to PyPI or push to a public GitHub
5
+ repo, because those are irreversible, outward-facing, and tied to your identity
6
+ and credentials. Do these when you're awake and ready to launch.
7
+
8
+ ## 0. Pre-flight (already done / verify)
9
+
10
+ ```bash
11
+ uv run pytest # 47 passing
12
+ uv run ruff check src tests # clean
13
+ uv build # builds dist/*.whl and dist/*.tar.gz
14
+ ```
15
+
16
+ A clean-room install of the built wheel has been verified to expose the
17
+ `skillvitals` console script and run `skillvitals scan` (see
18
+ `docs/superpowers/plans/` notes).
19
+
20
+ ## 1. Set the real homepage URLs
21
+
22
+ `pyproject.toml` currently points `Homepage`/`Repository`/`Issues` at
23
+ `github.com/pk/skillvitals`. Update those to your actual GitHub username/org
24
+ before publishing.
25
+
26
+ ## 2. Create the GitHub repo
27
+
28
+ ```bash
29
+ gh repo create skillvitals --public --source=. --remote=origin \
30
+ --description "Skill observability for Claude Code"
31
+ git push -u origin main
32
+ ```
33
+
34
+ (Record a demo GIF of `skillvitals scan` and drop it in the README — the
35
+ dormant-token line is the viral asset.)
36
+
37
+ ## 3. Publish to PyPI
38
+
39
+ Test on TestPyPI first:
40
+
41
+ ```bash
42
+ uv publish --publish-url https://test.pypi.org/legacy/ --token <test-token>
43
+ uvx --index-url https://test.pypi.org/simple/ skillvitals scan # smoke test
44
+ ```
45
+
46
+ Then the real thing:
47
+
48
+ ```bash
49
+ uv publish --token <pypi-token> # uses dist/ from `uv build`
50
+ ```
51
+
52
+ Verify:
53
+
54
+ ```bash
55
+ uvx skillvitals scan
56
+ ```
57
+
58
+ ## 4. Register with MCP directories
59
+
60
+ - Glama, mcpmarket.com (per the PRD launch plan).
61
+
62
+ ## 5. Launch posts
63
+
64
+ Drafts/checklist live in the PRD (Show HN, r/ClaudeAI, LinkedIn, X thread, the
65
+ DM tour). The headline is always the dead-token number from your own machine.
66
+
67
+ ## Name availability (from the PRD, last checked 2026-05-25)
68
+
69
+ `skillvitals` was reported available on PyPI / npm / GitHub and `skillvitals.dev`
70
+ was free. **Re-check immediately before publishing** — these can change.
71
+ Fallbacks: `skillscope`, `skillmon`, `skill-doctor`.
@@ -0,0 +1,149 @@
1
+ # skillvitals
2
+
3
+ **Skill observability for Claude Code.** See which of your skills fire, which are
4
+ dormant, what they cost in context, and what's broken.
5
+
6
+ Claude Code skills are supposed to auto-activate. In practice many never fire —
7
+ they just sit in every session burning context tokens. The ecosystem has plenty
8
+ of tools to *generate* skill-activation hooks. `skillvitals` is the missing
9
+ diagnostic layer: it treats every installed skill as a monitored service and
10
+ tells you *did it fire? when? at what context cost? is it dead weight?*
11
+
12
+ ```text
13
+ $ skillvitals scan
14
+
15
+ skillvitals
16
+ ┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
17
+ ┃ skill ┃ fires ┃ engaged ┃ ctx ┃ last seen ┃ status ┃
18
+ ┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
19
+ │ frontend-design │ 31 │ 140 │ 6.4k │ today │ ✅ healthy │
20
+ │ docx │ 47 │ 120 │ 2.1k │ today │ ✅ healthy │
21
+ │ pdf │ 23 │ 60 │ 1.8k │ 1d ago │ ✅ healthy │
22
+ │ sql-tuner │ 4 │ 9 │ 2.6k │ 9d ago │ ✅ healthy │
23
+ │ ab-test-coach │ 2 │ 2 │ 5.7k │ 3d ago │ ⚠️ misfiring │
24
+ │ leakcheck │ 1 │ 3 │ 3.1k │ 40d ago │ ⚠️ dormant │
25
+ │ data-analysis │ 0 │ 0 │ 4.2k │ never │ 💤 never-fired │
26
+ │ changelog-writer │ 0 │ 0 │ 1.4k │ never │ 💤 never-fired │
27
+ └────────────────────┴───────┴─────────┴──────┴───────────┴────────────────┘
28
+
29
+ 3 dormant/never-fired skills are costing you 8.7k tokens per session.
30
+ Run `skillvitals prescribe` for fixes.
31
+ ```
32
+
33
+ That last line is the point: **most people have thousands of tokens of dead
34
+ weight in every single session and no way to see it.**
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ uvx skillvitals scan # run without installing
40
+ # or
41
+ pip install skillvitals
42
+ ```
43
+
44
+ Requires Python 3.11+. Reads your existing Claude Code data under `~/.claude` —
45
+ nothing is sent anywhere (see [Privacy](#privacy)).
46
+
47
+ ## Commands
48
+
49
+ | Command | What it does |
50
+ |---------|--------------|
51
+ | `skillvitals scan` | Headline table: fires, engagement, context cost, health, per skill. |
52
+ | `skillvitals report` | Markdown report (`-o report.md` to save / share). |
53
+ | `skillvitals history` | Per-skill activation history across sessions. |
54
+ | `skillvitals dormancy` | Skills inactive for N days and the tokens they cost (`--days 14`). |
55
+ | `skillvitals prescribe` | Concrete fixes for weak/dormant/redundant skills (`--rewrite` for LLM rewrites). |
56
+ | `skillvitals test --skill X` | Synthetic activation-test prompts (`--live` runs them through headless Claude Code). |
57
+ | `skillvitals dashboard --open` | Self-contained HTML dashboard at `~/.skillvitals/dashboard.html`. |
58
+ | `skillvitals serve` | Run as an MCP server. |
59
+
60
+ ## Use it as an MCP server
61
+
62
+ `skillvitals` is also an MCP server, so you can ask Claude Code about your skills
63
+ in plain language ("which of my skills are dormant?").
64
+
65
+ ```bash
66
+ claude mcp add skillvitals -- uvx skillvitals serve
67
+ ```
68
+
69
+ Or add it to your MCP config manually:
70
+
71
+ ```json
72
+ {
73
+ "mcpServers": {
74
+ "skillvitals": { "command": "uvx", "args": ["skillvitals", "serve"] }
75
+ }
76
+ }
77
+ ```
78
+
79
+ Exposed tools: `vitals_scan`, `vitals_history`, `vitals_dormancy`,
80
+ `vitals_report`, `vitals_prescribe`, `vitals_test`, `vitals_dashboard`.
81
+
82
+ ## How it works
83
+
84
+ skillvitals reads two things, entirely locally:
85
+
86
+ 1. **Your installed skills** — every `SKILL.md` under `~/.claude/skills`, the
87
+ plugin cache, and the current project's `.claude/skills`. It parses the
88
+ frontmatter, estimates the context cost (tokens of the loaded `SKILL.md`),
89
+ and scores description quality.
90
+
91
+ 2. **Your session logs** — the JSONL transcripts under `~/.claude/projects`. It
92
+ extracts two activation signals per skill:
93
+ - **fires** — explicit `Skill()` invocations (the skill was activated).
94
+ - **engaged** — assistant messages tagged with that skill's `attributionSkill`
95
+ (how much the skill was actually leaned on afterward).
96
+
97
+ Joining the two gives each skill a health status:
98
+
99
+ | status | meaning |
100
+ |--------|---------|
101
+ | ✅ **healthy** | activated recently with real follow-through |
102
+ | ⚠️ **misfiring** | invoked but barely used afterward — may be matching the wrong prompts |
103
+ | ⚠️ **dormant** | activated before, but not within the window |
104
+ | 💤 **never-fired** | installed, costs tokens, has never activated |
105
+ | ❓ **orphan** | appears in logs but is no longer installed |
106
+
107
+ These are honest heuristics, not ground truth — the thresholds are documented in
108
+ `analysis.py` and `prescribe.py`.
109
+
110
+ ## Privacy
111
+
112
+ 100% local. skillvitals only reads files already on your machine under
113
+ `~/.claude`, writes a local SQLite cache to `~/.skillvitals/db.sqlite`, and a
114
+ local HTML file. **No network calls, no telemetry, nothing leaves your machine** —
115
+ with two explicit, opt-in exceptions:
116
+
117
+ - `skillvitals test --live` spawns headless Claude Code to measure real activation.
118
+ - `skillvitals prescribe --rewrite` calls the Anthropic API to rewrite weak
119
+ descriptions. Requires `pip install 'skillvitals[llm]'` and `ANTHROPIC_API_KEY`.
120
+
121
+ Both are off by default.
122
+
123
+ ## What it deliberately doesn't do
124
+
125
+ - **Generate activation hooks.** That space is well covered (`skills-hook`,
126
+ `claude-skills-supercharged`, …). Pair skillvitals with one of those — it
127
+ *reports* whether a `UserPromptSubmit` hook exists, it doesn't write one.
128
+ - **Auto-apply fixes.** v1 shows prescriptions; it doesn't edit your skills.
129
+ - **Phone home.** No cloud, no accounts, no team aggregation.
130
+
131
+ ## Configuration
132
+
133
+ Environment variables (all optional):
134
+
135
+ - `SKILLVITALS_CLAUDE_HOME` — the `.claude` dir (default `~/.claude`).
136
+ - `SKILLVITALS_HOME` — where the db + dashboard live (default `~/.skillvitals`).
137
+ - `ANTHROPIC_API_KEY` — only needed for the opt-in LLM features.
138
+
139
+ ## Development
140
+
141
+ ```bash
142
+ uv sync --extra llm
143
+ uv run pytest # 47 tests
144
+ uv run ruff check src tests
145
+ ```
146
+
147
+ ## License
148
+
149
+ MIT © 2026 Pk
@@ -0,0 +1,205 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>skillvitals dashboard</title>
7
+ <style>
8
+ :root { --bg:#0d1117; --card:#161b22; --line:#30363d; --fg:#e6edf3; --dim:#8b949e;
9
+ --green:#3fb950; --yellow:#d29922; --red:#f85149; --accent:#58a6ff; }
10
+ * { box-sizing: border-box; }
11
+ body { margin:0; background:var(--bg); color:var(--fg);
12
+ font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; }
13
+ .wrap { max-width: 1100px; margin: 0 auto; padding: 32px 20px 64px; }
14
+ h1 { font-size: 22px; margin: 0 0 4px; }
15
+ .sub { color: var(--dim); margin: 0 0 24px; }
16
+ .cards { display:flex; gap:14px; flex-wrap:wrap; margin-bottom:28px; }
17
+ .card { background:var(--card); border:1px solid var(--line); border-radius:10px;
18
+ padding:16px 18px; min-width:150px; flex:1; }
19
+ .card .n { font-size:26px; font-weight:700; }
20
+ .card .l { color:var(--dim); font-size:12px; text-transform:uppercase; letter-spacing:.04em; }
21
+ .card.alert .n { color: var(--yellow); }
22
+ table { width:100%; border-collapse:collapse; background:var(--card);
23
+ border:1px solid var(--line); border-radius:10px; overflow:hidden; }
24
+ th,td { text-align:left; padding:10px 14px; border-bottom:1px solid var(--line); }
25
+ th { cursor:pointer; user-select:none; color:var(--dim); font-weight:600;
26
+ font-size:12px; text-transform:uppercase; letter-spacing:.03em; }
27
+ th:hover { color: var(--fg); }
28
+ tbody tr:last-child td { border-bottom:none; }
29
+ tbody tr:hover { background: #1c2330; }
30
+ td.num { text-align:right; font-variant-numeric: tabular-nums; }
31
+ .pill { display:inline-block; padding:2px 8px; border-radius:999px; font-size:12px; font-weight:600; }
32
+ .healthy { color:var(--green); } .dormant,.misfiring { color:var(--yellow); }
33
+ .never-fired { color:var(--dim); } .orphan { color:var(--red); }
34
+ .bar { height:6px; background:#21262d; border-radius:4px; overflow:hidden; min-width:60px; }
35
+ .bar > i { display:block; height:100%; background:var(--accent); }
36
+ .rx { margin-top:28px; }
37
+ .rx h2 { font-size:16px; }
38
+ .rx li { margin:6px 0; color:var(--dim); }
39
+ .rx b { color:var(--fg); }
40
+ footer { margin-top:30px; color:var(--dim); font-size:12px; }
41
+ code { background:#21262d; padding:1px 6px; border-radius:5px; }
42
+ </style>
43
+ </head>
44
+ <body>
45
+ <div class="wrap">
46
+ <h1>skillvitals</h1>
47
+ <p class="sub">8 skills scanned · generated 2026-05-25 (demo data)</p>
48
+
49
+ <div class="cards">
50
+ <div class="card"><div class="n">8</div><div class="l">skills</div></div>
51
+ <div class="card"><div class="n">4</div><div class="l">healthy</div></div>
52
+ <div class="card alert"><div class="n">3</div><div class="l">dormant / never-fired</div></div>
53
+ <div class="card alert"><div class="n">8.7k</div><div class="l">dead tokens / session</div></div>
54
+ </div>
55
+
56
+ <table id="vitals">
57
+ <thead><tr>
58
+ <th onclick="sortTable(0,'s')">skill</th>
59
+ <th onclick="sortTable(1,'n')">fires</th>
60
+ <th onclick="sortTable(2,'n')">engaged</th>
61
+ <th onclick="sortTable(3,'n')">ctx tokens</th>
62
+ <th onclick="sortTable(4,'n')">quality</th>
63
+ <th onclick="sortTable(5,'n')">last seen</th>
64
+ <th onclick="sortTable(6,'s')">status</th>
65
+ </tr></thead>
66
+ <tbody>
67
+
68
+ <tr class="skill-row">
69
+ <td>frontend-design</td>
70
+ <td class="num" data-v="31">31</td>
71
+ <td class="num" data-v="140">140</td>
72
+ <td class="num" data-v="6400">6.4k</td>
73
+ <td class="num" data-v="88">
74
+ <div class="bar" title="88/100"><i style="width:88%"></i></div>
75
+ </td>
76
+ <td class="num" data-v="0">today</td>
77
+ <td><span class="pill healthy">healthy</span></td>
78
+ </tr>
79
+
80
+ <tr class="skill-row">
81
+ <td>docx</td>
82
+ <td class="num" data-v="47">47</td>
83
+ <td class="num" data-v="120">120</td>
84
+ <td class="num" data-v="2100">2.1k</td>
85
+ <td class="num" data-v="82">
86
+ <div class="bar" title="82/100"><i style="width:82%"></i></div>
87
+ </td>
88
+ <td class="num" data-v="0">today</td>
89
+ <td><span class="pill healthy">healthy</span></td>
90
+ </tr>
91
+
92
+ <tr class="skill-row">
93
+ <td>pdf</td>
94
+ <td class="num" data-v="23">23</td>
95
+ <td class="num" data-v="60">60</td>
96
+ <td class="num" data-v="1800">1.8k</td>
97
+ <td class="num" data-v="80">
98
+ <div class="bar" title="80/100"><i style="width:80%"></i></div>
99
+ </td>
100
+ <td class="num" data-v="1">1d ago</td>
101
+ <td><span class="pill healthy">healthy</span></td>
102
+ </tr>
103
+
104
+ <tr class="skill-row">
105
+ <td>sql-tuner</td>
106
+ <td class="num" data-v="4">4</td>
107
+ <td class="num" data-v="9">9</td>
108
+ <td class="num" data-v="2600">2.6k</td>
109
+ <td class="num" data-v="72">
110
+ <div class="bar" title="72/100"><i style="width:72%"></i></div>
111
+ </td>
112
+ <td class="num" data-v="9">9d ago</td>
113
+ <td><span class="pill healthy">healthy</span></td>
114
+ </tr>
115
+
116
+ <tr class="skill-row">
117
+ <td>ab-test-coach</td>
118
+ <td class="num" data-v="2">2</td>
119
+ <td class="num" data-v="2">2</td>
120
+ <td class="num" data-v="5700">5.7k</td>
121
+ <td class="num" data-v="70">
122
+ <div class="bar" title="70/100"><i style="width:70%"></i></div>
123
+ </td>
124
+ <td class="num" data-v="3">3d ago</td>
125
+ <td><span class="pill misfiring">misfiring</span></td>
126
+ </tr>
127
+
128
+ <tr class="skill-row">
129
+ <td>leakcheck</td>
130
+ <td class="num" data-v="1">1</td>
131
+ <td class="num" data-v="3">3</td>
132
+ <td class="num" data-v="3100">3.1k</td>
133
+ <td class="num" data-v="76">
134
+ <div class="bar" title="76/100"><i style="width:76%"></i></div>
135
+ </td>
136
+ <td class="num" data-v="40">40d ago</td>
137
+ <td><span class="pill dormant">dormant</span></td>
138
+ </tr>
139
+
140
+ <tr class="skill-row">
141
+ <td>data-analysis</td>
142
+ <td class="num" data-v="0">0</td>
143
+ <td class="num" data-v="0">0</td>
144
+ <td class="num" data-v="4200">4.2k</td>
145
+ <td class="num" data-v="34">
146
+ <div class="bar" title="34/100"><i style="width:34%"></i></div>
147
+ </td>
148
+ <td class="num" data-v="-1">never</td>
149
+ <td><span class="pill never-fired">never-fired</span></td>
150
+ </tr>
151
+
152
+ <tr class="skill-row">
153
+ <td>changelog-writer</td>
154
+ <td class="num" data-v="0">0</td>
155
+ <td class="num" data-v="0">0</td>
156
+ <td class="num" data-v="1400">1.4k</td>
157
+ <td class="num" data-v="64">
158
+ <div class="bar" title="64/100"><i style="width:64%"></i></div>
159
+ </td>
160
+ <td class="num" data-v="-1">never</td>
161
+ <td><span class="pill never-fired">never-fired</span></td>
162
+ </tr>
163
+
164
+ </tbody>
165
+ </table>
166
+
167
+
168
+ <div class="rx">
169
+ <h2>Prescriptions (5)</h2>
170
+ <ul>
171
+
172
+ <li>[warn] <b>ab-test-coach</b> — Invoked 2× but barely used afterward (engagement 1.0). The description may match prompts it shouldn&#39;t — tighten it to the cases it actually handles.</li>
173
+
174
+ <li>[warn] <b>data-analysis</b> — Description has no trigger phrasing (&#39;Use when…&#39;, &#39;when the user…&#39;). Activation relies on the model matching intent — make it explicit.</li>
175
+
176
+ <li>[info] <b>data-analysis</b> — Description quality score is 34/100. Add specifics: concrete nouns, examples, the triggering situation.</li>
177
+
178
+ <li>[warn] <b>data-analysis</b> — Never fired but costs ~4200 ctx tokens every session. Improve its description so it activates, or remove it to reclaim the budget.</li>
179
+
180
+ <li>[warn] <b>leakcheck</b> — Dormant 40d but costs ~3100 ctx tokens every session. Improve its description so it activates, or remove it to reclaim the budget.</li>
181
+
182
+ </ul>
183
+ </div>
184
+
185
+
186
+ <footer>
187
+ skillvitals · local-only · reads <code>~/.claude</code> · no data leaves your machine
188
+ </footer>
189
+ </div>
190
+ <script>
191
+ function sortTable(col, type){
192
+ var tb=document.querySelector('#vitals tbody');
193
+ var rows=Array.prototype.slice.call(tb.querySelectorAll('tr'));
194
+ var asc = tb.getAttribute('data-col')==col && tb.getAttribute('data-dir')!='asc';
195
+ rows.sort(function(a,b){
196
+ var x=a.children[col], y=b.children[col];
197
+ if(type=='n'){ x=parseFloat(x.getAttribute('data-v')||0); y=parseFloat(y.getAttribute('data-v')||0); return asc?x-y:y-x; }
198
+ x=x.textContent.trim(); y=y.textContent.trim(); return asc?x.localeCompare(y):y.localeCompare(x);
199
+ });
200
+ rows.forEach(function(r){ tb.appendChild(r); });
201
+ tb.setAttribute('data-col',col); tb.setAttribute('data-dir',asc?'asc':'desc');
202
+ }
203
+ </script>
204
+ </body>
205
+ </html>