claude-code-capabilities 0.1.2__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.
- claude_code_capabilities-0.1.2/.claude-plugin/plugin.json +22 -0
- claude_code_capabilities-0.1.2/.github/workflows/ci.yml +19 -0
- claude_code_capabilities-0.1.2/.github/workflows/publish.yml +39 -0
- claude_code_capabilities-0.1.2/.gitignore +22 -0
- claude_code_capabilities-0.1.2/LICENSE +21 -0
- claude_code_capabilities-0.1.2/PKG-INFO +136 -0
- claude_code_capabilities-0.1.2/README.md +110 -0
- claude_code_capabilities-0.1.2/commands/capabilities-report.md +48 -0
- claude_code_capabilities-0.1.2/pyproject.toml +98 -0
- claude_code_capabilities-0.1.2/src/capdisc/__init__.py +69 -0
- claude_code_capabilities-0.1.2/src/capdisc/base.py +56 -0
- claude_code_capabilities-0.1.2/src/capdisc/catalog/__init__.py +71 -0
- claude_code_capabilities-0.1.2/src/capdisc/catalog/entries.py +104 -0
- claude_code_capabilities-0.1.2/src/capdisc/catalog/types.py +227 -0
- claude_code_capabilities-0.1.2/src/capdisc/discovery.py +278 -0
- claude_code_capabilities-0.1.2/src/capdisc/frontmatter.py +49 -0
- claude_code_capabilities-0.1.2/src/capdisc/hooks/__init__.py +61 -0
- claude_code_capabilities-0.1.2/src/capdisc/hooks/schema.py +216 -0
- claude_code_capabilities-0.1.2/src/capdisc/hooks/types.py +156 -0
- claude_code_capabilities-0.1.2/src/capdisc/html.py +75 -0
- claude_code_capabilities-0.1.2/src/capdisc/mcp_catalog.py +87 -0
- claude_code_capabilities-0.1.2/src/capdisc/mcp_harvest/__init__.py +17 -0
- claude_code_capabilities-0.1.2/src/capdisc/mcp_harvest/auth.py +120 -0
- claude_code_capabilities-0.1.2/src/capdisc/mcp_harvest/cache.py +144 -0
- claude_code_capabilities-0.1.2/src/capdisc/mcp_harvest/config.py +325 -0
- claude_code_capabilities-0.1.2/src/capdisc/mcp_harvest/connect.py +187 -0
- claude_code_capabilities-0.1.2/src/capdisc/mcp_harvest/types.py +29 -0
- claude_code_capabilities-0.1.2/src/capdisc/plugin_catalog.py +308 -0
- claude_code_capabilities-0.1.2/src/capdisc/py.typed +0 -0
- claude_code_capabilities-0.1.2/src/capdisc/report/__init__.py +36 -0
- claude_code_capabilities-0.1.2/src/capdisc/report/__main__.py +6 -0
- claude_code_capabilities-0.1.2/src/capdisc/report/assets.py +138 -0
- claude_code_capabilities-0.1.2/src/capdisc/report/cards.py +109 -0
- claude_code_capabilities-0.1.2/src/capdisc/report/cli.py +35 -0
- claude_code_capabilities-0.1.2/src/capdisc/report/components.py +144 -0
- claude_code_capabilities-0.1.2/src/capdisc/report/harvest.py +150 -0
- claude_code_capabilities-0.1.2/src/capdisc/report/html.py +79 -0
- claude_code_capabilities-0.1.2/src/capdisc/report/models.py +82 -0
- claude_code_capabilities-0.1.2/src/capdisc/report/page.py +129 -0
- claude_code_capabilities-0.1.2/src/capdisc/report/sections.py +154 -0
- claude_code_capabilities-0.1.2/src/capdisc/report/types.py +43 -0
- claude_code_capabilities-0.1.2/src/capdisc/scope/__init__.py +83 -0
- claude_code_capabilities-0.1.2/src/capdisc/scope/inventory/__init__.py +20 -0
- claude_code_capabilities-0.1.2/src/capdisc/scope/inventory/assets.py +54 -0
- claude_code_capabilities-0.1.2/src/capdisc/scope/inventory/capture.py +219 -0
- claude_code_capabilities-0.1.2/src/capdisc/scope/inventory/render.py +149 -0
- claude_code_capabilities-0.1.2/src/capdisc/scope/inventory/roots.py +175 -0
- claude_code_capabilities-0.1.2/src/capdisc/scope/locations.py +330 -0
- claude_code_capabilities-0.1.2/src/capdisc/scope/types.py +154 -0
- claude_code_capabilities-0.1.2/src/capdisc/settings.py +230 -0
- claude_code_capabilities-0.1.2/src/capdisc/tokens.py +48 -0
- claude_code_capabilities-0.1.2/tests/helpers/__init__.py +5 -0
- claude_code_capabilities-0.1.2/tests/helpers/scope_sandbox.py +129 -0
- claude_code_capabilities-0.1.2/tests/helpers/tree.py +21 -0
- claude_code_capabilities-0.1.2/tests/test_base.py +23 -0
- claude_code_capabilities-0.1.2/tests/test_frontmatter.py +29 -0
- claude_code_capabilities-0.1.2/tests/test_hooks.py +155 -0
- claude_code_capabilities-0.1.2/tests/test_html.py +42 -0
- claude_code_capabilities-0.1.2/tests/test_inventory.py +305 -0
- claude_code_capabilities-0.1.2/tests/test_mcp_catalog.py +36 -0
- claude_code_capabilities-0.1.2/tests/test_mcp_harvest.py +504 -0
- claude_code_capabilities-0.1.2/tests/test_plugin_catalog.py +154 -0
- claude_code_capabilities-0.1.2/tests/test_plugin_scan.py +93 -0
- claude_code_capabilities-0.1.2/tests/test_report.py +172 -0
- claude_code_capabilities-0.1.2/tests/test_scope.py +69 -0
- claude_code_capabilities-0.1.2/tests/test_settings.py +52 -0
- claude_code_capabilities-0.1.2/uv.lock +1687 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "capdisc",
|
|
3
|
+
"displayName": "Capabilities Discovery",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Inventory the machine's Claude Code capabilities — skills, agents, plugins, MCP servers, hooks, and built-in tools across user/project/plugin scopes — into a typed JSON catalog and an HTML environment report.",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Magic-Man-us"
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://github.com/Magic-Man-us/capability-discovery",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"capabilities",
|
|
13
|
+
"catalog",
|
|
14
|
+
"skills",
|
|
15
|
+
"agents",
|
|
16
|
+
"plugins",
|
|
17
|
+
"mcp",
|
|
18
|
+
"hooks",
|
|
19
|
+
"environment-report"
|
|
20
|
+
],
|
|
21
|
+
"defaultEnabled": true
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v7
|
|
13
|
+
- uses: astral-sh/setup-uv@v8.3.2
|
|
14
|
+
with:
|
|
15
|
+
enable-cache: true
|
|
16
|
+
- run: uv sync --locked
|
|
17
|
+
- run: uv run ruff check .
|
|
18
|
+
- run: uv run mypy src
|
|
19
|
+
- run: uv run pytest -q
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v7
|
|
12
|
+
- uses: astral-sh/setup-uv@v8.3.2
|
|
13
|
+
with:
|
|
14
|
+
enable-cache: true
|
|
15
|
+
- run: uv sync --locked
|
|
16
|
+
- run: uv run ruff check .
|
|
17
|
+
- run: uv run mypy src
|
|
18
|
+
- run: uv run pytest -q
|
|
19
|
+
- run: uv build
|
|
20
|
+
- uses: actions/upload-artifact@v7
|
|
21
|
+
with:
|
|
22
|
+
name: dist
|
|
23
|
+
path: dist/
|
|
24
|
+
|
|
25
|
+
publish:
|
|
26
|
+
needs: build
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
environment:
|
|
29
|
+
name: pypi
|
|
30
|
+
permissions:
|
|
31
|
+
id-token: write
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/download-artifact@v8
|
|
34
|
+
with:
|
|
35
|
+
name: dist
|
|
36
|
+
path: dist/
|
|
37
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
38
|
+
with:
|
|
39
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.pyc
|
|
3
|
+
.pytest_cache/
|
|
4
|
+
.ruff_cache/
|
|
5
|
+
.mypy_cache/
|
|
6
|
+
*.egg-info/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
.venv/
|
|
10
|
+
venv/
|
|
11
|
+
.coverage
|
|
12
|
+
|
|
13
|
+
# >>> AgentForge managed gitignore (do not edit between markers) >>>
|
|
14
|
+
**/.agentforge/audit.jsonl
|
|
15
|
+
**/.agentforge/backups/
|
|
16
|
+
**/.agentforge/tombstones/
|
|
17
|
+
**/.agentforge/logs/
|
|
18
|
+
**/.agentforge/sessions/
|
|
19
|
+
**/.agentforge/diagnostics/
|
|
20
|
+
**/.agentforge/cache/
|
|
21
|
+
# <<< AgentForge managed gitignore <<<
|
|
22
|
+
discovery-report.html
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Magic-Man-us
|
|
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,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: claude-code-capabilities
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Discovers Claude Code capabilities — skills, agents, plugins, MCP servers, hooks — into typed catalogs and an environment report.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Magic-Man-us/capability-discovery
|
|
6
|
+
Project-URL: Repository, https://github.com/Magic-Man-us/capability-discovery
|
|
7
|
+
Author: Magic-Man-us
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: fastmcp>=3.4.4
|
|
21
|
+
Requires-Dist: py-key-value-aio[filetree]>=0.4.5
|
|
22
|
+
Requires-Dist: pydantic-settings>=2.14.2
|
|
23
|
+
Requires-Dist: pydantic>=2.13.4
|
|
24
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# capdisc
|
|
28
|
+
|
|
29
|
+
[](https://pypi.org/project/claude-code-capabilities/)
|
|
30
|
+
[](https://pypi.org/project/claude-code-capabilities/)
|
|
31
|
+
[](https://github.com/Magic-Man-us/capability-discovery/actions/workflows/ci.yml)
|
|
32
|
+
[](https://github.com/Magic-Man-us/capability-discovery/blob/main/LICENSE)
|
|
33
|
+
|
|
34
|
+
Discovers Claude Code capabilities — skills, agents, plugins, MCP servers, hooks — into typed
|
|
35
|
+
Pydantic catalogs and an environment report.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv add claude-code-capabilities
|
|
41
|
+
# or
|
|
42
|
+
pip install claude-code-capabilities
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## CLI
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
capdisc # scan this machine, write discovery-report.json + .html
|
|
49
|
+
capdisc --oauth # also allow the interactive OAuth flow for HTTP MCP servers
|
|
50
|
+
# with a pre-registered client (forces a fresh MCP harvest)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Both files are written to `~/.claude/capdisc/` by default. Configure paths and MCP auth via env
|
|
54
|
+
vars (`CAPDISC_` prefix), a `.env` file, or `~/.claude/capdisc/config.json`/`config.yaml` — see
|
|
55
|
+
`DiscoverySettings` in `settings.py` for every field.
|
|
56
|
+
|
|
57
|
+
## Library
|
|
58
|
+
|
|
59
|
+
Scan the machine into a typed catalog:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from pathlib import Path
|
|
63
|
+
from capdisc.discovery import scan_environment
|
|
64
|
+
from capdisc.scope import ScopeRoots
|
|
65
|
+
|
|
66
|
+
roots = ScopeRoots.discover(start=Path.cwd(), home_dir=Path.home())
|
|
67
|
+
catalog = scan_environment(roots)
|
|
68
|
+
for entry in catalog.entries:
|
|
69
|
+
... # CatalogSkill | CatalogTool | CatalogMcpServer | CatalogPlugin
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Build and persist the full environment report (what the CLI does):
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from capdisc.report import build_report, write_report
|
|
76
|
+
|
|
77
|
+
report = build_report(oauth=False)
|
|
78
|
+
write_report(report) # -> ~/.claude/capdisc/discovery-report.{json,html}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Inspect the on-disk scope inventory directly — every skill/agent/command/hook found, its
|
|
82
|
+
precedence, and which ones actually win a collision:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from capdisc.scope import ScopeInventory, ScopeRoots
|
|
86
|
+
|
|
87
|
+
inventory = ScopeInventory.scan(ScopeRoots.discover(start=Path.cwd(), home_dir=Path.home()))
|
|
88
|
+
print(len(inventory.artifacts), "captured,", len(inventory.effective), "in effect")
|
|
89
|
+
for hooks in inventory.hook_configs:
|
|
90
|
+
... # HookConfig, unified from settings.json and component frontmatter
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### MCP servers
|
|
94
|
+
|
|
95
|
+
Read the last harvested tool inventory (fast, no network — served from a 12h cache):
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from capdisc.mcp_harvest import read_mcp_cache, cache_is_stale
|
|
99
|
+
|
|
100
|
+
servers = read_mcp_cache() # [] if there's no cache yet
|
|
101
|
+
if cache_is_stale():
|
|
102
|
+
... # trigger a refresh (below) before trusting this
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Force a fresh harvest — connects to every configured server concurrently (bounded) and lists
|
|
106
|
+
their real tool schemas:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from capdisc.mcp_harvest import refresh_mcp_cache
|
|
110
|
+
|
|
111
|
+
servers = refresh_mcp_cache(oauth=False) # also (re)writes the cache
|
|
112
|
+
for server in servers:
|
|
113
|
+
print(server.ref, [t.name for t in server.tools])
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Bearer/OAuth auth for HTTP servers is opt-in via settings, bound to an exact hostname so a
|
|
117
|
+
same-named server elsewhere can never receive a credential meant for another:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
export CAPDISC_MCP_BEARER_ENV='{"github": {"env": "GH_TOKEN", "host": "api.githubcopilot.com"}}'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`report.EnvironmentReport` captures the full discovery harvest (scan roots, on-disk inventory, skills,
|
|
124
|
+
builtin tools, plugins with per-component token cost, MCP servers). `mcp_harvest` and `mcp_catalog`
|
|
125
|
+
enumerate connected MCP servers; `plugin_catalog` reads installed plugins; `scope` resolves which
|
|
126
|
+
roots to scan.
|
|
127
|
+
|
|
128
|
+
The package ships `py.typed`.
|
|
129
|
+
|
|
130
|
+
## Tests
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
uv run pytest
|
|
134
|
+
uv run mypy src
|
|
135
|
+
uv run ruff check .
|
|
136
|
+
```
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# capdisc
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/claude-code-capabilities/)
|
|
4
|
+
[](https://pypi.org/project/claude-code-capabilities/)
|
|
5
|
+
[](https://github.com/Magic-Man-us/capability-discovery/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/Magic-Man-us/capability-discovery/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
Discovers Claude Code capabilities — skills, agents, plugins, MCP servers, hooks — into typed
|
|
9
|
+
Pydantic catalogs and an environment report.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv add claude-code-capabilities
|
|
15
|
+
# or
|
|
16
|
+
pip install claude-code-capabilities
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## CLI
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
capdisc # scan this machine, write discovery-report.json + .html
|
|
23
|
+
capdisc --oauth # also allow the interactive OAuth flow for HTTP MCP servers
|
|
24
|
+
# with a pre-registered client (forces a fresh MCP harvest)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Both files are written to `~/.claude/capdisc/` by default. Configure paths and MCP auth via env
|
|
28
|
+
vars (`CAPDISC_` prefix), a `.env` file, or `~/.claude/capdisc/config.json`/`config.yaml` — see
|
|
29
|
+
`DiscoverySettings` in `settings.py` for every field.
|
|
30
|
+
|
|
31
|
+
## Library
|
|
32
|
+
|
|
33
|
+
Scan the machine into a typed catalog:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from capdisc.discovery import scan_environment
|
|
38
|
+
from capdisc.scope import ScopeRoots
|
|
39
|
+
|
|
40
|
+
roots = ScopeRoots.discover(start=Path.cwd(), home_dir=Path.home())
|
|
41
|
+
catalog = scan_environment(roots)
|
|
42
|
+
for entry in catalog.entries:
|
|
43
|
+
... # CatalogSkill | CatalogTool | CatalogMcpServer | CatalogPlugin
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Build and persist the full environment report (what the CLI does):
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from capdisc.report import build_report, write_report
|
|
50
|
+
|
|
51
|
+
report = build_report(oauth=False)
|
|
52
|
+
write_report(report) # -> ~/.claude/capdisc/discovery-report.{json,html}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Inspect the on-disk scope inventory directly — every skill/agent/command/hook found, its
|
|
56
|
+
precedence, and which ones actually win a collision:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from capdisc.scope import ScopeInventory, ScopeRoots
|
|
60
|
+
|
|
61
|
+
inventory = ScopeInventory.scan(ScopeRoots.discover(start=Path.cwd(), home_dir=Path.home()))
|
|
62
|
+
print(len(inventory.artifacts), "captured,", len(inventory.effective), "in effect")
|
|
63
|
+
for hooks in inventory.hook_configs:
|
|
64
|
+
... # HookConfig, unified from settings.json and component frontmatter
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### MCP servers
|
|
68
|
+
|
|
69
|
+
Read the last harvested tool inventory (fast, no network — served from a 12h cache):
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from capdisc.mcp_harvest import read_mcp_cache, cache_is_stale
|
|
73
|
+
|
|
74
|
+
servers = read_mcp_cache() # [] if there's no cache yet
|
|
75
|
+
if cache_is_stale():
|
|
76
|
+
... # trigger a refresh (below) before trusting this
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Force a fresh harvest — connects to every configured server concurrently (bounded) and lists
|
|
80
|
+
their real tool schemas:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from capdisc.mcp_harvest import refresh_mcp_cache
|
|
84
|
+
|
|
85
|
+
servers = refresh_mcp_cache(oauth=False) # also (re)writes the cache
|
|
86
|
+
for server in servers:
|
|
87
|
+
print(server.ref, [t.name for t in server.tools])
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Bearer/OAuth auth for HTTP servers is opt-in via settings, bound to an exact hostname so a
|
|
91
|
+
same-named server elsewhere can never receive a credential meant for another:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
export CAPDISC_MCP_BEARER_ENV='{"github": {"env": "GH_TOKEN", "host": "api.githubcopilot.com"}}'
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`report.EnvironmentReport` captures the full discovery harvest (scan roots, on-disk inventory, skills,
|
|
98
|
+
builtin tools, plugins with per-component token cost, MCP servers). `mcp_harvest` and `mcp_catalog`
|
|
99
|
+
enumerate connected MCP servers; `plugin_catalog` reads installed plugins; `scope` resolves which
|
|
100
|
+
roots to scan.
|
|
101
|
+
|
|
102
|
+
The package ships `py.typed`.
|
|
103
|
+
|
|
104
|
+
## Tests
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv run pytest
|
|
108
|
+
uv run mypy src
|
|
109
|
+
uv run ruff check .
|
|
110
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Inventory this machine's Claude Code capabilities — skills, agents, plugins, MCP servers, hooks, and built-in tools across user/project/plugin scopes — as a typed JSON catalog and an HTML environment report.
|
|
3
|
+
allowed-tools: Bash, Read
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Build the environment report and summarize it. The scan covers every scope root
|
|
7
|
+
(user, project, plugin), installed plugins with per-component token cost,
|
|
8
|
+
connected MCP servers and their tool schemas, indexed skills, and built-in
|
|
9
|
+
tools. Distinct from `/context-cartographer:map-context`, which budgets one
|
|
10
|
+
repo's context surface — this maps what the whole machine/session has.
|
|
11
|
+
|
|
12
|
+
Run exactly this:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
if [ -z "$CLAUDE_PLUGIN_ROOT" ]; then
|
|
16
|
+
echo "CLAUDE_PLUGIN_ROOT is unset — this command must run from an installed plugin." >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
PLUGIN="$CLAUDE_PLUGIN_ROOT"
|
|
20
|
+
# Bearer auth for the GitHub MCP server, when gh is logged in — the host binding below is
|
|
21
|
+
# enforced: the token is attached only if the server's own url resolves to that exact host,
|
|
22
|
+
# so a same-named server elsewhere can never receive it. Neither printed nor stored.
|
|
23
|
+
if TOKEN=$(gh auth token 2>/dev/null) && [ -n "$TOKEN" ]; then
|
|
24
|
+
export GH_TOKEN="$TOKEN"
|
|
25
|
+
export CAPDISC_MCP_BEARER_ENV='{"github": {"env": "GH_TOKEN", "host": "api.githubcopilot.com"}}'
|
|
26
|
+
fi
|
|
27
|
+
uv run --project "$PLUGIN" capdisc
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
It prints the two output paths (JSON snapshot + HTML report, under
|
|
31
|
+
`~/.claude/capdisc/` by default). Then:
|
|
32
|
+
|
|
33
|
+
- Read the JSON snapshot and report the counts per catalog kind (skills, tools,
|
|
34
|
+
MCP servers, plugins), the scan roots used, and anything anomalous (servers
|
|
35
|
+
that failed to connect, empty scopes).
|
|
36
|
+
- Point the user at the HTML report path for the full browsable view.
|
|
37
|
+
|
|
38
|
+
Notes:
|
|
39
|
+
- MCP tool inventories are served from a 12-hour cache; a fresh probe happens in
|
|
40
|
+
the background when the cache is stale.
|
|
41
|
+
- HTTP MCP servers needing auth: `mcp_bearer_env` in settings maps a server name
|
|
42
|
+
to `{env, host}` — an env var holding a bearer token, and the exact host the
|
|
43
|
+
server's url must resolve to before the token is attached (non-interactive).
|
|
44
|
+
`--oauth` additionally runs the browser OAuth flow for servers with a
|
|
45
|
+
pre-registered, host-bound client in `mcp_oauth_clients`; never pass it here
|
|
46
|
+
— this command must stay non-interactive.
|
|
47
|
+
- The JSON round-trips through `EnvironmentReport.model_validate_json` for any
|
|
48
|
+
downstream typed consumer.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "claude-code-capabilities"
|
|
3
|
+
version = "0.1.2"
|
|
4
|
+
description = "Discovers Claude Code capabilities — skills, agents, plugins, MCP servers, hooks — into typed catalogs and an environment report."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
authors = [{ name = "Magic-Man-us" }]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 3 - Alpha",
|
|
11
|
+
"Intended Audience :: Developers",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Operating System :: MacOS",
|
|
14
|
+
"Operating System :: POSIX :: Linux",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"fastmcp>=3.4.4",
|
|
22
|
+
"py-key-value-aio[filetree]>=0.4.5",
|
|
23
|
+
"pydantic>=2.13.4",
|
|
24
|
+
"pydantic-settings>=2.14.2",
|
|
25
|
+
"pyyaml>=6.0.3",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/Magic-Man-us/capability-discovery"
|
|
30
|
+
Repository = "https://github.com/Magic-Man-us/capability-discovery"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
capdisc = "capdisc.report:main"
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
"mypy>=2.3.0",
|
|
38
|
+
"pytest>=9.1.1",
|
|
39
|
+
"pytest-cov>=7.1.0",
|
|
40
|
+
"ruff>=0.15.22",
|
|
41
|
+
"types-pyyaml>=6.0.12.20260518",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[build-system]
|
|
45
|
+
requires = ["hatchling>=1.31.0"]
|
|
46
|
+
build-backend = "hatchling.build"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.wheel]
|
|
49
|
+
packages = ["src/capdisc"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
target-version = "py312"
|
|
53
|
+
line-length = 100
|
|
54
|
+
src = ["src", "tests"]
|
|
55
|
+
|
|
56
|
+
[tool.ruff.lint]
|
|
57
|
+
select = [
|
|
58
|
+
"E", "W", "F", "I", "N", "UP", "B", "A", "C4", "DTZ", "SIM", "PTH",
|
|
59
|
+
"RET", "TRY", "S", "ARG", "PL", "RUF",
|
|
60
|
+
]
|
|
61
|
+
ignore = ["TRY003", "PLR0913", "RUF012"]
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.per-file-ignores]
|
|
64
|
+
"tests/**" = ["S101", "S102", "PLR2004", "ARG", "PLC0415"]
|
|
65
|
+
"__init__.py" = ["F401"]
|
|
66
|
+
# These models bind external camelCase JSON config keys (mcpServers, pluginConfigs) by field name.
|
|
67
|
+
"src/capdisc/mcp_harvest/config.py" = ["N815"]
|
|
68
|
+
"src/capdisc/plugin_catalog.py" = ["N815"]
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint.isort]
|
|
71
|
+
known-first-party = ["capdisc"]
|
|
72
|
+
|
|
73
|
+
[tool.ruff.format]
|
|
74
|
+
docstring-code-format = true
|
|
75
|
+
|
|
76
|
+
[tool.mypy]
|
|
77
|
+
python_version = "3.12"
|
|
78
|
+
mypy_path = "src"
|
|
79
|
+
plugins = ["pydantic.mypy"]
|
|
80
|
+
strict = true
|
|
81
|
+
warn_unreachable = true
|
|
82
|
+
warn_redundant_casts = true
|
|
83
|
+
warn_unused_ignores = true
|
|
84
|
+
|
|
85
|
+
[tool.pydantic-mypy]
|
|
86
|
+
init_forbid_extra = true
|
|
87
|
+
init_typed = true
|
|
88
|
+
warn_required_dynamic_aliases = true
|
|
89
|
+
warn_untyped_fields = true
|
|
90
|
+
|
|
91
|
+
[tool.pytest.ini_options]
|
|
92
|
+
pythonpath = ["src", "tests"]
|
|
93
|
+
testpaths = ["tests"]
|
|
94
|
+
addopts = "--strict-markers --strict-config -ra"
|
|
95
|
+
|
|
96
|
+
[tool.coverage.run]
|
|
97
|
+
source = ["src"]
|
|
98
|
+
branch = true
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Discovery of Claude Code capabilities: skills, agents, plugins, MCP servers, hooks.
|
|
2
|
+
|
|
3
|
+
Scans the environment's scope roots into a typed inventory, harvests plugin and MCP
|
|
4
|
+
metadata, and assembles capability catalogs and the environment report.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from .catalog import (
|
|
10
|
+
Catalog,
|
|
11
|
+
CatalogEntry,
|
|
12
|
+
CatalogMcpServer,
|
|
13
|
+
CatalogPlugin,
|
|
14
|
+
CatalogSkill,
|
|
15
|
+
CatalogTool,
|
|
16
|
+
McpTool,
|
|
17
|
+
)
|
|
18
|
+
from .discovery import BUILTIN_TOOLS, scan_environment, scan_indexed_skills, scan_skills
|
|
19
|
+
from .mcp_catalog import enumerate_mcp_servers
|
|
20
|
+
from .mcp_harvest import (
|
|
21
|
+
cache_is_stale,
|
|
22
|
+
read_mcp_cache,
|
|
23
|
+
refresh_in_background,
|
|
24
|
+
refresh_mcp_cache,
|
|
25
|
+
write_mcp_cache,
|
|
26
|
+
)
|
|
27
|
+
from .plugin_catalog import (
|
|
28
|
+
enumerate_plugins,
|
|
29
|
+
enumerate_plugins_with_paths,
|
|
30
|
+
installed_plugin_dirs,
|
|
31
|
+
installed_plugins,
|
|
32
|
+
)
|
|
33
|
+
from .report import EnvironmentReport, build_report, write_report, write_report_on_start
|
|
34
|
+
from .scope import ScopeInventory, ScopeRoots, default_managed_dir
|
|
35
|
+
from .settings import DiscoverySettings, ExtraSourceDir, get_settings
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"BUILTIN_TOOLS",
|
|
39
|
+
"Catalog",
|
|
40
|
+
"CatalogEntry",
|
|
41
|
+
"CatalogMcpServer",
|
|
42
|
+
"CatalogPlugin",
|
|
43
|
+
"CatalogSkill",
|
|
44
|
+
"CatalogTool",
|
|
45
|
+
"DiscoverySettings",
|
|
46
|
+
"EnvironmentReport",
|
|
47
|
+
"ExtraSourceDir",
|
|
48
|
+
"McpTool",
|
|
49
|
+
"ScopeInventory",
|
|
50
|
+
"ScopeRoots",
|
|
51
|
+
"build_report",
|
|
52
|
+
"cache_is_stale",
|
|
53
|
+
"default_managed_dir",
|
|
54
|
+
"enumerate_mcp_servers",
|
|
55
|
+
"enumerate_plugins",
|
|
56
|
+
"enumerate_plugins_with_paths",
|
|
57
|
+
"get_settings",
|
|
58
|
+
"installed_plugin_dirs",
|
|
59
|
+
"installed_plugins",
|
|
60
|
+
"read_mcp_cache",
|
|
61
|
+
"refresh_in_background",
|
|
62
|
+
"refresh_mcp_cache",
|
|
63
|
+
"scan_environment",
|
|
64
|
+
"scan_indexed_skills",
|
|
65
|
+
"scan_skills",
|
|
66
|
+
"write_mcp_cache",
|
|
67
|
+
"write_report",
|
|
68
|
+
"write_report_on_start",
|
|
69
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
from pydantic.alias_generators import to_camel
|
|
7
|
+
from pydantic.fields import ComputedFieldInfo, FieldInfo
|
|
8
|
+
|
|
9
|
+
_CAMEL_BOUNDARY = re.compile(r"(?<=[a-z0-9])(?=[A-Z])")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def humanize_field_title(field_name: str, _info: FieldInfo | ComputedFieldInfo) -> str:
|
|
13
|
+
"""A clean, alias-independent JSON-Schema title from a field's Python name.
|
|
14
|
+
|
|
15
|
+
Splits snake_case, kebab-case, and camelCase into words and renders them sentence-cased
|
|
16
|
+
(`status_message` -> `Status message`). Avoids the mangled titles Pydantic derives from a
|
|
17
|
+
camelCase/hyphenated alias when a schema is exported `by_alias` (`statusMessage` -> the broken
|
|
18
|
+
`Statusmessage`). Set as `field_title_generator`, so it drives the field-level `title` only;
|
|
19
|
+
a domain-primitive alias's own `title` on the inner type is untouched.
|
|
20
|
+
"""
|
|
21
|
+
spaced = _CAMEL_BOUNDARY.sub(" ", field_name.replace("_", " ").replace("-", " "))
|
|
22
|
+
return " ".join(spaced.split()).capitalize()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FrozenModel(BaseModel):
|
|
26
|
+
"""Immutable, strict domain model — the single config preset for this package."""
|
|
27
|
+
|
|
28
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class FrozenWireModel(BaseModel):
|
|
32
|
+
"""Immutable model whose snake_case fields bind to the camelCase JSON keys of a Claude Code
|
|
33
|
+
config file — accepts either spelling on load, emits camelCase on dump. Tolerant of unknown
|
|
34
|
+
keys (`extra="ignore"`): this is the ingest boundary for config written by a Claude Code that
|
|
35
|
+
may be newer than us, so a field we don't model yet is dropped, never a parse failure."""
|
|
36
|
+
|
|
37
|
+
model_config = ConfigDict(
|
|
38
|
+
extra="ignore",
|
|
39
|
+
frozen=True,
|
|
40
|
+
alias_generator=to_camel,
|
|
41
|
+
populate_by_name=True,
|
|
42
|
+
serialize_by_alias=True,
|
|
43
|
+
field_title_generator=humanize_field_title,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MutableModel(BaseModel):
|
|
48
|
+
"""Strict but mutable domain model — for in-place accumulators that fill across a loop."""
|
|
49
|
+
|
|
50
|
+
model_config = ConfigDict(extra="forbid")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class InputModel(BaseModel):
|
|
54
|
+
"""Lenient boundary model — ignores unknown keys from external sources."""
|
|
55
|
+
|
|
56
|
+
model_config = ConfigDict(extra="ignore")
|