ccdoctor 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.
- ccdoctor-0.1.0/PKG-INFO +5 -0
- ccdoctor-0.1.0/README.md +418 -0
- ccdoctor-0.1.0/ccdoctor/__init__.py +3 -0
- ccdoctor-0.1.0/ccdoctor/__main__.py +89 -0
- ccdoctor-0.1.0/ccdoctor/classifiers.py +69 -0
- ccdoctor-0.1.0/ccdoctor/collectors.py +570 -0
- ccdoctor-0.1.0/ccdoctor/diagnostics.py +27 -0
- ccdoctor-0.1.0/ccdoctor/models.py +45 -0
- ccdoctor-0.1.0/ccdoctor/renderers.py +346 -0
- ccdoctor-0.1.0/ccdoctor.egg-info/PKG-INFO +5 -0
- ccdoctor-0.1.0/ccdoctor.egg-info/SOURCES.txt +14 -0
- ccdoctor-0.1.0/ccdoctor.egg-info/dependency_links.txt +1 -0
- ccdoctor-0.1.0/ccdoctor.egg-info/entry_points.txt +3 -0
- ccdoctor-0.1.0/ccdoctor.egg-info/top_level.txt +1 -0
- ccdoctor-0.1.0/pyproject.toml +13 -0
- ccdoctor-0.1.0/setup.cfg +4 -0
ccdoctor-0.1.0/PKG-INFO
ADDED
ccdoctor-0.1.0/README.md
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://github.com/Cookie-HOO/ccdoctor">
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/logo-dark.svg">
|
|
5
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/assets/logo-light.svg">
|
|
6
|
+
<img src="docs/assets/logo-dark.svg" width="100px" alt="ccdoctor" />
|
|
7
|
+
</picture>
|
|
8
|
+
</a>
|
|
9
|
+
<h1 style="font-size: 28px; margin: 10px 0;">ccdoctor</h1>
|
|
10
|
+
<p>Inspect Claude Code visibility from your terminal, scripts, and agents.</p>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://github.com/Cookie-HOO/ccdoctor" target="_blank">
|
|
15
|
+
<img alt="GitHub Repository" src="https://img.shields.io/badge/GitHub-Cookie--HOO%2Fccdoctor-181717?logo=github" />
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://docs.astral.sh/uv/guides/tools/" target="_blank">
|
|
18
|
+
<img alt="uvx ready" src="https://img.shields.io/badge/uvx-ready-654ff0" />
|
|
19
|
+
</a>
|
|
20
|
+
<img alt="Python 3.12+" src="https://img.shields.io/badge/python-3.12%2B-3776ab?logo=python&logoColor=white" />
|
|
21
|
+
<img alt="Output modes" src="https://img.shields.io/badge/output-JSON%20%7C%20Markdown%20%7C%20TTY-0f766e" />
|
|
22
|
+
<img alt="Read-only" src="https://img.shields.io/badge/safety-read--only-16a34a" />
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
<p align="center">
|
|
26
|
+
<a href="README.zh-CN.md">简体中文</a>
|
|
27
|
+
·
|
|
28
|
+
<a href="#all-demos">View Demo</a>
|
|
29
|
+
·
|
|
30
|
+
<a href="https://github.com/Cookie-HOO/ccdoctor/issues/new?labels=bug">Report Bug</a>
|
|
31
|
+
·
|
|
32
|
+
<a href="https://github.com/Cookie-HOO/ccdoctor/issues/new?labels=enhancement">Request Feature</a>
|
|
33
|
+
·
|
|
34
|
+
<a href="#agent-and-llm-usage">Agent Usage</a>
|
|
35
|
+
·
|
|
36
|
+
<a href="#command-reference">Command Reference</a>
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
<br>
|
|
40
|
+
|
|
41
|
+
`ccdoctor` is a local diagnostics CLI for Claude Code projects. It reports what Claude Code can see from a project root: MCPs, skills, hooks, plugins, provider/model settings, agents, permissions, statusline configuration, diagnostics, and declared governance manifests.
|
|
42
|
+
|
|
43
|
+
It is intentionally a shell CLI instead of a global MCP. You can run it from any terminal, CI job, script, or agent without adding another always-visible MCP server to Claude Code.
|
|
44
|
+
|
|
45
|
+
> [!TIP]
|
|
46
|
+
> For agents and LLM pipelines, prefer narrow JSON queries: `NO_COLOR=1 ccd --json <category> [name] -p <project>`.
|
|
47
|
+
|
|
48
|
+
<details>
|
|
49
|
+
<summary>Table of contents (Click to show)</summary>
|
|
50
|
+
|
|
51
|
+
- [Why use ccdoctor?](#why-use-ccdoctor)
|
|
52
|
+
- [Quickstart](#quickstart)
|
|
53
|
+
- [All Demos](#all-demos)
|
|
54
|
+
- [Project overview](#project-overview)
|
|
55
|
+
- [Category view](#category-view)
|
|
56
|
+
- [Item detail view](#item-detail-view)
|
|
57
|
+
- [JSON automation](#json-automation)
|
|
58
|
+
- [Markdown output](#markdown-output)
|
|
59
|
+
- [Doctor mode](#doctor-mode)
|
|
60
|
+
- [Command reference](#command-reference)
|
|
61
|
+
- [Options](#options)
|
|
62
|
+
- [Categories](#categories)
|
|
63
|
+
- [Output field reference](#output-field-reference)
|
|
64
|
+
- [`scope` values](#scope-values)
|
|
65
|
+
- [`provider` values](#provider-values)
|
|
66
|
+
- [`effective` values](#effective-values)
|
|
67
|
+
- [Diagnostic severities](#diagnostic-severities)
|
|
68
|
+
- [Agent and LLM usage](#agent-and-llm-usage)
|
|
69
|
+
- [Safety](#safety)
|
|
70
|
+
- [Resources](#resources)
|
|
71
|
+
|
|
72
|
+
</details>
|
|
73
|
+
|
|
74
|
+
# Why use ccdoctor?
|
|
75
|
+
|
|
76
|
+
Claude Code configuration can come from several places at once: project files, user settings, plugins, symlinks, manifests, nested project roots, and runtime defaults. `ccdoctor` gives you one read-only report that separates what is configured, what is declared, and what is expected to be effective.
|
|
77
|
+
|
|
78
|
+
- **Visibility debugging** — See the MCPs, skills, hooks, plugins, agents, provider settings, and permissions Claude Code can discover for a project.
|
|
79
|
+
- **Agent-friendly inspection** — Use filtered JSON output so agents can reason over config without reading unrelated files.
|
|
80
|
+
- **Governance checks** — Compare project/runtime state against declared manifests and flag stale or nested config.
|
|
81
|
+
- **Safe diagnostics** — Runs read-only by default and redacts values whose keys look like tokens, keys, secrets, cookies, passwords, or auth fields.
|
|
82
|
+
- **Multiple output modes** — Human terminal output, JSON for automation, Markdown for PRs/issues, and `--doctor` exit codes for scripts.
|
|
83
|
+
|
|
84
|
+
# Quickstart
|
|
85
|
+
|
|
86
|
+
Run once without installing:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
uvx ccdoctor
|
|
90
|
+
uvx --from ccdoctor ccd mcp
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Install once, then run `ccd` anywhere:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
uv tool install ccdoctor
|
|
97
|
+
ccd
|
|
98
|
+
ccd mcp
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Install from GitHub instead of PyPI:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
uv tool install git+https://github.com/Cookie-HOO/ccdoctor
|
|
105
|
+
ccd
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Inspect another project:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
ccd -p ~/Projects/my_ai
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Inspect one category or one item:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
ccd mcp
|
|
118
|
+
ccd mcp playwright
|
|
119
|
+
ccd hook PreToolUse
|
|
120
|
+
ccd skill profile-project-bootstrap
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Get machine-readable output:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
NO_COLOR=1 ccd --json mcp playwright -p ~/Projects/my_ai
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
# All Demos
|
|
130
|
+
|
|
131
|
+
<p align="center">
|
|
132
|
+
<img alt="ccdoctor terminal demo" src="docs/assets/ccdoctor-demo.svg" width="800px" />
|
|
133
|
+
</p>
|
|
134
|
+
|
|
135
|
+
## Project overview
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
ccd -p ~/Projects/my_ai
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
✨ Claude Code Doctor
|
|
143
|
+
📁 Project: /Users/example/Projects/my_ai
|
|
144
|
+
|
|
145
|
+
🧠 Provider / Model
|
|
146
|
+
• 🧠 model: claude-opus-4-8
|
|
147
|
+
|
|
148
|
+
🔌 Plugins
|
|
149
|
+
✅ claude-mem@thedotmack [user, installed-plugin, effective=yes]
|
|
150
|
+
|
|
151
|
+
🧰 MCPs
|
|
152
|
+
✅ playwright [project, configured, effective=yes] type=stdio command=/bin/zsh
|
|
153
|
+
⚠️ fetch [declared, declared, effective=maybe, managed_by=my_ai manifest]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Category view
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
ccd mcp -p ~/Projects/my_ai
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
✨ Claude Code Doctor
|
|
164
|
+
📁 Project: /Users/example/Projects/my_ai
|
|
165
|
+
|
|
166
|
+
🧰 MCPs
|
|
167
|
+
✅ playwright [project, configured, effective=yes] type=stdio command=/bin/zsh source=/Users/example/Projects/my_ai/.mcp.json
|
|
168
|
+
✅ mcp-search [user, plugin-provided, effective=yes, managed_by=claude-mem@thedotmack] type=stdio command=node
|
|
169
|
+
⚠️ fetch [declared, declared, effective=maybe, managed_by=my_ai manifest]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Item detail view
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
ccd mcp playwright -p ~/Projects/my_ai
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
🧰 MCPs
|
|
180
|
+
✅ playwright [project, configured, effective=yes] type=stdio command=/bin/zsh source=/Users/example/Projects/my_ai/.mcp.json
|
|
181
|
+
kind: mcp
|
|
182
|
+
name: playwright
|
|
183
|
+
scope: project
|
|
184
|
+
provider: configured
|
|
185
|
+
effective: yes
|
|
186
|
+
source_file: /Users/example/Projects/my_ai/.mcp.json
|
|
187
|
+
metadata:
|
|
188
|
+
type: stdio
|
|
189
|
+
command: /bin/zsh
|
|
190
|
+
args:
|
|
191
|
+
[
|
|
192
|
+
"-lc",
|
|
193
|
+
"node \"$MY_AI_ROOT/mcps/playwright-mcp/node_modules/@playwright/mcp/cli.js\""
|
|
194
|
+
]
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Hook details work the same way:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
ccd hook PreToolUse -p ~/Projects/my_ai
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
🪝 Hooks
|
|
205
|
+
✅ PreToolUse [user, configured, effective=yes] summary=[{"hooks": [{"command": "...", "type": "command"}], "matcher": "*"}]
|
|
206
|
+
kind: hook
|
|
207
|
+
name: PreToolUse
|
|
208
|
+
scope: user
|
|
209
|
+
provider: configured
|
|
210
|
+
effective: yes
|
|
211
|
+
source_file: /Users/example/.claude/settings.json
|
|
212
|
+
metadata:
|
|
213
|
+
config:
|
|
214
|
+
[
|
|
215
|
+
{
|
|
216
|
+
"hooks": [{"command": "...", "type": "command"}],
|
|
217
|
+
"matcher": "*"
|
|
218
|
+
}
|
|
219
|
+
]
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## JSON automation
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
ccd --json mcp playwright -p ~/Projects/my_ai
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"mcps": [
|
|
231
|
+
{
|
|
232
|
+
"effective": "yes",
|
|
233
|
+
"kind": "mcp",
|
|
234
|
+
"metadata": {
|
|
235
|
+
"args": ["-lc", "node \"$MY_AI_ROOT/mcps/playwright-mcp/node_modules/@playwright/mcp/cli.js\""],
|
|
236
|
+
"command": "/bin/zsh",
|
|
237
|
+
"type": "stdio"
|
|
238
|
+
},
|
|
239
|
+
"name": "playwright",
|
|
240
|
+
"provider": "configured",
|
|
241
|
+
"scope": "project",
|
|
242
|
+
"source_file": "/Users/example/Projects/my_ai/.mcp.json"
|
|
243
|
+
}
|
|
244
|
+
],
|
|
245
|
+
"project_root": "/Users/example/Projects/my_ai"
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Markdown output
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
ccd --markdown hook PreToolUse -p ~/Projects/my_ai
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
This produces Markdown headings and JSON metadata blocks suitable for pasting into a GitHub issue or PR comment.
|
|
256
|
+
|
|
257
|
+
## Doctor mode
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
ccd --doctor -p ~/Projects/my_ai
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
| Code | Meaning |
|
|
264
|
+
|---:|---|
|
|
265
|
+
| `0` | No warnings or errors. |
|
|
266
|
+
| `1` | Warning diagnostics were found. |
|
|
267
|
+
| `2` | Error diagnostics were found. |
|
|
268
|
+
|
|
269
|
+
# Command reference
|
|
270
|
+
|
|
271
|
+
```text
|
|
272
|
+
ccd [options] [category] [name]
|
|
273
|
+
ccdoctor [options] [category] [name]
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Options
|
|
277
|
+
|
|
278
|
+
| Option | Meaning |
|
|
279
|
+
|---|---|
|
|
280
|
+
| `-p, --project PATH` | Inspect another project directory. Defaults to the current directory. |
|
|
281
|
+
| `--json` | Print stable redacted JSON for automation. |
|
|
282
|
+
| `--markdown` | Print Markdown tables/details for issues and PRs. |
|
|
283
|
+
| `--doctor` | Return non-zero when warnings/errors are found. |
|
|
284
|
+
| `--verbose, -v` | Include source paths, allowlists, and diagnostic hints. |
|
|
285
|
+
| `--include-runtime` | Run optional read-only runtime probes, currently localhost proxy `/health`. |
|
|
286
|
+
|
|
287
|
+
## Categories
|
|
288
|
+
|
|
289
|
+
| Category | Aliases | What it shows |
|
|
290
|
+
|---|---|---|
|
|
291
|
+
| Provider/model | `provider`, `model` | Model, statusline, Claude/Anthropic environment settings, optional runtime probe. |
|
|
292
|
+
| Plugins | `plugin`, `plugins` | Installed/enabled Claude Code plugins and plugin metadata. |
|
|
293
|
+
| MCPs | `mcp`, `mcps` | Project, nested-project, plugin-provided, and manifest-declared MCP servers. |
|
|
294
|
+
| Skills | `skill`, `skills` | Project skills, plugin-provided skills, and runtime/manifest-declared skills. |
|
|
295
|
+
| Agents | `agent`, `agents` | Project agents, profile-provided agents, and plugin-provided agents. |
|
|
296
|
+
| Hooks | `hook`, `hooks` | User/project hooks and plugin-provided hook events. |
|
|
297
|
+
| Permissions | `permission`, `permissions` | Claude Code allow/deny permission settings. |
|
|
298
|
+
| Diagnostics | `diagnostic`, `diagnostics`, `diag` | Warnings and errors discovered while collecting status. |
|
|
299
|
+
|
|
300
|
+
Passing a `name` after a category narrows output to matching entries and prints detail metadata:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
ccd mcp playwright
|
|
304
|
+
ccd hook PreToolUse
|
|
305
|
+
ccd skill profile-project-bootstrap
|
|
306
|
+
ccd plugin claude-mem
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
# Output field reference
|
|
310
|
+
|
|
311
|
+
Every collected record is a `StatusItem` with common fields:
|
|
312
|
+
|
|
313
|
+
| Field | Meaning |
|
|
314
|
+
|---|---|
|
|
315
|
+
| `kind` | Record type, such as `mcp`, `skill`, `hook`, `plugin`, `agent`, or `permission`. |
|
|
316
|
+
| `name` | Human-readable record name. |
|
|
317
|
+
| `scope` | Where the record comes from. |
|
|
318
|
+
| `provider` | How the record is provided or managed. |
|
|
319
|
+
| `effective` | Whether Claude Code should actually see or use the record. |
|
|
320
|
+
| `managed_by` | Optional owner/manager, such as a plugin name or `my_ai manifest`. |
|
|
321
|
+
| `source_file` | File or directory where the record was discovered. |
|
|
322
|
+
| `metadata` | Type-specific details, redacted where keys look secret-like. |
|
|
323
|
+
| `diagnostics` | Item-local diagnostic notes when present. |
|
|
324
|
+
|
|
325
|
+
## `scope` values
|
|
326
|
+
|
|
327
|
+
| Value | Meaning |
|
|
328
|
+
|---|---|
|
|
329
|
+
| `project` | Directly configured in the inspected project. Usually effective when Claude is launched there. |
|
|
330
|
+
| `user` | Comes from the current user's Claude Code configuration or plugin installation. |
|
|
331
|
+
| `nested-project` | Found under the project tree but not at the inspected root. Usually not effective for this root. |
|
|
332
|
+
| `runtime` | Declared as available from the Claude Code runtime or generated runtime manifest. |
|
|
333
|
+
| `declared` | Present in a governance manifest, but not necessarily configured for runtime use. |
|
|
334
|
+
| `custom` | A custom local source, currently used for some agent/profile records. |
|
|
335
|
+
|
|
336
|
+
## `provider` values
|
|
337
|
+
|
|
338
|
+
| Value | Meaning |
|
|
339
|
+
|---|---|
|
|
340
|
+
| `configured` | Found in a concrete Claude Code config file, such as `.mcp.json` or `.claude/settings.json`. |
|
|
341
|
+
| `custom` | Comes from this repository's custom skill/tool/profile area. |
|
|
342
|
+
| `installed` | Comes from this repository's installed managed assets. |
|
|
343
|
+
| `installed-plugin` | Comes from Claude Code's installed plugin registry. |
|
|
344
|
+
| `plugin-provided` | Provided by a Claude Code plugin. |
|
|
345
|
+
| `runtime-built-in` | Declared by the Claude Code runtime or runtime skill list. |
|
|
346
|
+
| `profile-provided` | Comes from a project profile definition. |
|
|
347
|
+
| `declared` | Listed in a manifest for tracking/governance. |
|
|
348
|
+
|
|
349
|
+
## `effective` values
|
|
350
|
+
|
|
351
|
+
| Value | Meaning |
|
|
352
|
+
|---|---|
|
|
353
|
+
| `yes` | Expected to be visible/effective for the inspected project. |
|
|
354
|
+
| `no` | Found but not expected to be effective for the inspected project. |
|
|
355
|
+
| `maybe` | Declared or inferred, but runtime effectiveness cannot be proven from static files alone. |
|
|
356
|
+
| `ok` | Runtime probe succeeded. |
|
|
357
|
+
| `failed` | Runtime probe failed. |
|
|
358
|
+
|
|
359
|
+
## Diagnostic severities
|
|
360
|
+
|
|
361
|
+
| Severity | Meaning |
|
|
362
|
+
|---|---|
|
|
363
|
+
| `info` | Informational note. |
|
|
364
|
+
| `warning` | Something may be stale, ineffective, missing, or surprising. `--doctor` exits `1`. |
|
|
365
|
+
| `error` | Something is malformed or broken enough to require action. `--doctor` exits `2`. |
|
|
366
|
+
|
|
367
|
+
# Agent and LLM usage
|
|
368
|
+
|
|
369
|
+
For agents and LLM pipelines, prefer narrow JSON queries. They are smaller, stable, and easier to parse than terminal output.
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
NO_COLOR=1 ccd --json <category> [name] -p <project>
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Examples:
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
NO_COLOR=1 ccd --json mcp playwright -p /repo
|
|
379
|
+
NO_COLOR=1 ccd --json hook PreToolUse -p /repo
|
|
380
|
+
NO_COLOR=1 ccd --json skill profile-project-bootstrap -p /repo
|
|
381
|
+
NO_COLOR=1 ccd --json diagnostics -p /repo
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Recommended agent flow:
|
|
385
|
+
|
|
386
|
+
1. Start with `ccd --json diagnostics -p <project>`.
|
|
387
|
+
2. If diagnostics mention MCPs, run `ccd --json mcp -p <project>`.
|
|
388
|
+
3. Query a specific item with `ccd --json mcp <name> -p <project>` before recommending config changes.
|
|
389
|
+
4. Quote `source_file`, `scope`, `provider`, and `effective` in your answer so the user can verify the finding.
|
|
390
|
+
|
|
391
|
+
Prompt snippet:
|
|
392
|
+
|
|
393
|
+
```text
|
|
394
|
+
Run `NO_COLOR=1 ccd --json diagnostics -p <project>`. If warnings or errors exist, inspect the relevant category with `ccd --json <category> [name] -p <project>`. Do not modify files. Summarize findings with source_file, scope, provider, effective, and a recommended next action.
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Why JSON instead of text for agents?
|
|
398
|
+
|
|
399
|
+
- `--json` has no ANSI color codes.
|
|
400
|
+
- It includes redacted metadata needed for reasoning.
|
|
401
|
+
- It can be filtered by category and name to reduce token use.
|
|
402
|
+
- It avoids over-reading unrelated project configuration.
|
|
403
|
+
|
|
404
|
+
# Safety
|
|
405
|
+
|
|
406
|
+
`ccdoctor` is read-only by default. It does not modify Claude Code settings, plugin config, MCP config, or project files. It does not read `.env` files. Values whose keys look like tokens, keys, secrets, cookies, passwords, or auth fields are redacted before output.
|
|
407
|
+
|
|
408
|
+
Optional runtime probing only runs when `--include-runtime` is passed. Runtime probing is restricted to localhost-style endpoints such as `127.0.0.1` or `localhost`.
|
|
409
|
+
|
|
410
|
+
Manifest-only entries are treated as declared governance state, not proof of runtime visibility.
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
# Resources
|
|
415
|
+
|
|
416
|
+
- [uv tools guide](https://docs.astral.sh/uv/guides/tools/) — run Python command-line tools with `uvx`.
|
|
417
|
+
- [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code) — Claude Code concepts and configuration.
|
|
418
|
+
- [GitHub repository](https://github.com/Cookie-HOO/ccdoctor) — source, issues, and releases.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .collectors import collect_status
|
|
8
|
+
from .diagnostics import summarize_exit_code
|
|
9
|
+
from .renderers import render_json, render_markdown, render_text
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
CATEGORY_ALIASES = {
|
|
13
|
+
"provider": "provider",
|
|
14
|
+
"model": "provider",
|
|
15
|
+
"plugin": "plugins",
|
|
16
|
+
"plugins": "plugins",
|
|
17
|
+
"mcp": "mcps",
|
|
18
|
+
"mcps": "mcps",
|
|
19
|
+
"skill": "skills",
|
|
20
|
+
"skills": "skills",
|
|
21
|
+
"agent": "agents",
|
|
22
|
+
"agents": "agents",
|
|
23
|
+
"hook": "hooks",
|
|
24
|
+
"hooks": "hooks",
|
|
25
|
+
"permission": "permissions",
|
|
26
|
+
"permissions": "permissions",
|
|
27
|
+
"diagnostic": "diagnostics",
|
|
28
|
+
"diagnostics": "diagnostics",
|
|
29
|
+
"diag": "diagnostics",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
34
|
+
parser = argparse.ArgumentParser(
|
|
35
|
+
description="Inspect Claude Code visible MCPs, skills, hooks, plugins, and provider settings for a project.",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument("--project", "-p", default=".", help="Project directory to inspect. Defaults to the current directory.")
|
|
38
|
+
parser.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
|
|
39
|
+
parser.add_argument("--markdown", action="store_true", help="Print Markdown tables.")
|
|
40
|
+
parser.add_argument("--doctor", action="store_true", help="Run diagnostics and return non-zero when warnings/errors are found.")
|
|
41
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="Include source paths, allowlists, and diagnostic hints.")
|
|
42
|
+
parser.add_argument("--include-runtime", action="store_true", help="Run optional read-only runtime probes, such as localhost proxy /health.")
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"category",
|
|
45
|
+
nargs="?",
|
|
46
|
+
choices=sorted(CATEGORY_ALIASES),
|
|
47
|
+
help="Show only one category: provider, plugin, mcp, skill, agent, hook, permission, or diagnostic.",
|
|
48
|
+
)
|
|
49
|
+
parser.add_argument("name", nargs="?", help="Show one item within the selected category, such as an MCP, hook, or skill name.")
|
|
50
|
+
return parser
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def main(argv: list[str] | None = None) -> int:
|
|
54
|
+
args = build_parser().parse_args(argv)
|
|
55
|
+
status = collect_status(Path(args.project), include_runtime=args.include_runtime, verbose=args.verbose)
|
|
56
|
+
category = CATEGORY_ALIASES.get(args.category) if args.category else None
|
|
57
|
+
|
|
58
|
+
if args.name and not category:
|
|
59
|
+
build_parser().error("name can only be used after a category, for example: ccd mcp playwright")
|
|
60
|
+
|
|
61
|
+
if args.json:
|
|
62
|
+
print(render_json(status, category=category, name=args.name))
|
|
63
|
+
elif args.markdown:
|
|
64
|
+
print(render_markdown(status, category=category, name=args.name))
|
|
65
|
+
else:
|
|
66
|
+
print(render_text(status, verbose=args.verbose or args.doctor or bool(category), category=category, name=args.name))
|
|
67
|
+
|
|
68
|
+
if args.doctor:
|
|
69
|
+
diagnostics = status.get("diagnostics", [])
|
|
70
|
+
return summarize_exit_code_from_dicts(diagnostics)
|
|
71
|
+
return 0
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def summarize_exit_code_from_dicts(diagnostics: list[dict[str, object]]) -> int:
|
|
75
|
+
from .models import Diagnostic
|
|
76
|
+
|
|
77
|
+
return summarize_exit_code([
|
|
78
|
+
Diagnostic(
|
|
79
|
+
severity=str(item.get("severity", "info")),
|
|
80
|
+
message=str(item.get("message", "")),
|
|
81
|
+
source_file=str(item["source_file"]) if item.get("source_file") else None,
|
|
82
|
+
hint=str(item["hint"]) if item.get("hint") else None,
|
|
83
|
+
)
|
|
84
|
+
for item in diagnostics
|
|
85
|
+
])
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
sys.exit(main())
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
SECRET_MARKERS = ("TOKEN", "KEY", "SECRET", "COOKIE", "PASSWORD", "AUTH")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def is_secret_key(key: str) -> bool:
|
|
9
|
+
upper = key.upper()
|
|
10
|
+
return any(marker in upper for marker in SECRET_MARKERS)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def redact_value(key: str, value: object) -> object:
|
|
14
|
+
if is_secret_key(key):
|
|
15
|
+
if value in (None, ""):
|
|
16
|
+
return value
|
|
17
|
+
return "<redacted>"
|
|
18
|
+
if isinstance(value, dict):
|
|
19
|
+
return redact_mapping(value)
|
|
20
|
+
if isinstance(value, list):
|
|
21
|
+
return [redact_sequence_value(item) for item in value]
|
|
22
|
+
return value
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def redact_sequence_value(value: object) -> object:
|
|
26
|
+
if isinstance(value, dict):
|
|
27
|
+
return redact_mapping(value)
|
|
28
|
+
if isinstance(value, list):
|
|
29
|
+
return [redact_sequence_value(item) for item in value]
|
|
30
|
+
return value
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def redact_any(value: object) -> object:
|
|
34
|
+
if isinstance(value, dict):
|
|
35
|
+
return redact_mapping(value)
|
|
36
|
+
if isinstance(value, list):
|
|
37
|
+
return [redact_sequence_value(item) for item in value]
|
|
38
|
+
return value
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def redact_mapping(mapping: dict[str, object]) -> dict[str, object]:
|
|
42
|
+
return {key: redact_value(key, value) for key, value in mapping.items()}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def classify_skill_target(target: Path | None, control_root: Path) -> tuple[str, str]:
|
|
46
|
+
if target is None:
|
|
47
|
+
return "configured", "unknown"
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
resolved = target.resolve(strict=False)
|
|
51
|
+
except OSError:
|
|
52
|
+
return "configured", "unknown"
|
|
53
|
+
|
|
54
|
+
custom_root = control_root / "skills" / "custom"
|
|
55
|
+
installed_root = control_root / "skills" / "installed"
|
|
56
|
+
|
|
57
|
+
if _is_relative_to(resolved, custom_root):
|
|
58
|
+
return "custom", "my_ai"
|
|
59
|
+
if _is_relative_to(resolved, installed_root):
|
|
60
|
+
return "installed", "my_ai"
|
|
61
|
+
return "configured", "project"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _is_relative_to(path: Path, parent: Path) -> bool:
|
|
65
|
+
try:
|
|
66
|
+
path.relative_to(parent.resolve(strict=False))
|
|
67
|
+
return True
|
|
68
|
+
except ValueError:
|
|
69
|
+
return False
|