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.
Files changed (67) hide show
  1. claude_code_capabilities-0.1.2/.claude-plugin/plugin.json +22 -0
  2. claude_code_capabilities-0.1.2/.github/workflows/ci.yml +19 -0
  3. claude_code_capabilities-0.1.2/.github/workflows/publish.yml +39 -0
  4. claude_code_capabilities-0.1.2/.gitignore +22 -0
  5. claude_code_capabilities-0.1.2/LICENSE +21 -0
  6. claude_code_capabilities-0.1.2/PKG-INFO +136 -0
  7. claude_code_capabilities-0.1.2/README.md +110 -0
  8. claude_code_capabilities-0.1.2/commands/capabilities-report.md +48 -0
  9. claude_code_capabilities-0.1.2/pyproject.toml +98 -0
  10. claude_code_capabilities-0.1.2/src/capdisc/__init__.py +69 -0
  11. claude_code_capabilities-0.1.2/src/capdisc/base.py +56 -0
  12. claude_code_capabilities-0.1.2/src/capdisc/catalog/__init__.py +71 -0
  13. claude_code_capabilities-0.1.2/src/capdisc/catalog/entries.py +104 -0
  14. claude_code_capabilities-0.1.2/src/capdisc/catalog/types.py +227 -0
  15. claude_code_capabilities-0.1.2/src/capdisc/discovery.py +278 -0
  16. claude_code_capabilities-0.1.2/src/capdisc/frontmatter.py +49 -0
  17. claude_code_capabilities-0.1.2/src/capdisc/hooks/__init__.py +61 -0
  18. claude_code_capabilities-0.1.2/src/capdisc/hooks/schema.py +216 -0
  19. claude_code_capabilities-0.1.2/src/capdisc/hooks/types.py +156 -0
  20. claude_code_capabilities-0.1.2/src/capdisc/html.py +75 -0
  21. claude_code_capabilities-0.1.2/src/capdisc/mcp_catalog.py +87 -0
  22. claude_code_capabilities-0.1.2/src/capdisc/mcp_harvest/__init__.py +17 -0
  23. claude_code_capabilities-0.1.2/src/capdisc/mcp_harvest/auth.py +120 -0
  24. claude_code_capabilities-0.1.2/src/capdisc/mcp_harvest/cache.py +144 -0
  25. claude_code_capabilities-0.1.2/src/capdisc/mcp_harvest/config.py +325 -0
  26. claude_code_capabilities-0.1.2/src/capdisc/mcp_harvest/connect.py +187 -0
  27. claude_code_capabilities-0.1.2/src/capdisc/mcp_harvest/types.py +29 -0
  28. claude_code_capabilities-0.1.2/src/capdisc/plugin_catalog.py +308 -0
  29. claude_code_capabilities-0.1.2/src/capdisc/py.typed +0 -0
  30. claude_code_capabilities-0.1.2/src/capdisc/report/__init__.py +36 -0
  31. claude_code_capabilities-0.1.2/src/capdisc/report/__main__.py +6 -0
  32. claude_code_capabilities-0.1.2/src/capdisc/report/assets.py +138 -0
  33. claude_code_capabilities-0.1.2/src/capdisc/report/cards.py +109 -0
  34. claude_code_capabilities-0.1.2/src/capdisc/report/cli.py +35 -0
  35. claude_code_capabilities-0.1.2/src/capdisc/report/components.py +144 -0
  36. claude_code_capabilities-0.1.2/src/capdisc/report/harvest.py +150 -0
  37. claude_code_capabilities-0.1.2/src/capdisc/report/html.py +79 -0
  38. claude_code_capabilities-0.1.2/src/capdisc/report/models.py +82 -0
  39. claude_code_capabilities-0.1.2/src/capdisc/report/page.py +129 -0
  40. claude_code_capabilities-0.1.2/src/capdisc/report/sections.py +154 -0
  41. claude_code_capabilities-0.1.2/src/capdisc/report/types.py +43 -0
  42. claude_code_capabilities-0.1.2/src/capdisc/scope/__init__.py +83 -0
  43. claude_code_capabilities-0.1.2/src/capdisc/scope/inventory/__init__.py +20 -0
  44. claude_code_capabilities-0.1.2/src/capdisc/scope/inventory/assets.py +54 -0
  45. claude_code_capabilities-0.1.2/src/capdisc/scope/inventory/capture.py +219 -0
  46. claude_code_capabilities-0.1.2/src/capdisc/scope/inventory/render.py +149 -0
  47. claude_code_capabilities-0.1.2/src/capdisc/scope/inventory/roots.py +175 -0
  48. claude_code_capabilities-0.1.2/src/capdisc/scope/locations.py +330 -0
  49. claude_code_capabilities-0.1.2/src/capdisc/scope/types.py +154 -0
  50. claude_code_capabilities-0.1.2/src/capdisc/settings.py +230 -0
  51. claude_code_capabilities-0.1.2/src/capdisc/tokens.py +48 -0
  52. claude_code_capabilities-0.1.2/tests/helpers/__init__.py +5 -0
  53. claude_code_capabilities-0.1.2/tests/helpers/scope_sandbox.py +129 -0
  54. claude_code_capabilities-0.1.2/tests/helpers/tree.py +21 -0
  55. claude_code_capabilities-0.1.2/tests/test_base.py +23 -0
  56. claude_code_capabilities-0.1.2/tests/test_frontmatter.py +29 -0
  57. claude_code_capabilities-0.1.2/tests/test_hooks.py +155 -0
  58. claude_code_capabilities-0.1.2/tests/test_html.py +42 -0
  59. claude_code_capabilities-0.1.2/tests/test_inventory.py +305 -0
  60. claude_code_capabilities-0.1.2/tests/test_mcp_catalog.py +36 -0
  61. claude_code_capabilities-0.1.2/tests/test_mcp_harvest.py +504 -0
  62. claude_code_capabilities-0.1.2/tests/test_plugin_catalog.py +154 -0
  63. claude_code_capabilities-0.1.2/tests/test_plugin_scan.py +93 -0
  64. claude_code_capabilities-0.1.2/tests/test_report.py +172 -0
  65. claude_code_capabilities-0.1.2/tests/test_scope.py +69 -0
  66. claude_code_capabilities-0.1.2/tests/test_settings.py +52 -0
  67. 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
+ [![PyPI](https://img.shields.io/pypi/v/claude-code-capabilities.svg)](https://pypi.org/project/claude-code-capabilities/)
30
+ [![Python versions](https://img.shields.io/pypi/pyversions/claude-code-capabilities.svg)](https://pypi.org/project/claude-code-capabilities/)
31
+ [![CI](https://github.com/Magic-Man-us/capability-discovery/actions/workflows/ci.yml/badge.svg)](https://github.com/Magic-Man-us/capability-discovery/actions/workflows/ci.yml)
32
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ [![PyPI](https://img.shields.io/pypi/v/claude-code-capabilities.svg)](https://pypi.org/project/claude-code-capabilities/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/claude-code-capabilities.svg)](https://pypi.org/project/claude-code-capabilities/)
5
+ [![CI](https://github.com/Magic-Man-us/capability-discovery/actions/workflows/ci.yml/badge.svg)](https://github.com/Magic-Man-us/capability-discovery/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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")