docs-kit 0.1.1__tar.gz → 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.
- {docs_kit-0.1.1 → docs_kit-0.1.2}/.gitignore +1 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/PKG-INFO +44 -13
- {docs_kit-0.1.1 → docs_kit-0.1.2}/README.md +42 -11
- docs_kit-0.1.2/docs_kit/_version.py +1 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/cli/commands.py +46 -10
- {docs_kit-0.1.1 → docs_kit-0.1.2}/pyproject.toml +1 -1
- {docs_kit-0.1.1 → docs_kit-0.1.2}/tests/test_cli.py +79 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/tests/test_install_cmd.py +90 -20
- docs_kit-0.1.1/docs_kit/_version.py +0 -1
- {docs_kit-0.1.1 → docs_kit-0.1.2}/.github/workflows/ci.yml +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/.github/workflows/publish.yml +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/AGENTS.md +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/CHANGELOG.md +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/CLAUDE.md +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/CONTRIBUTING.md +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/LICENSE +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/data/sample_docs/claude-code-changelog.md +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/data/sample_docs/the-adventure-of-the-speckled-band.md +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs-kit.yaml +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/__init__.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/__main__.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/agent.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/cli/__init__.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/cli/__main__.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/cli/help.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/__init__.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/embeddings/__init__.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/embeddings/base.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/embeddings/fastembed.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/fetchers/__init__.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/fetchers/base.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/fetchers/gitbook.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/fetchers/llms_txt.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/fetchers/mintlify.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/parsers/__init__.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/parsers/base.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/parsers/markdown.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/parsers/text.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/vector_stores/__init__.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/vector_stores/base.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/connectors/vector_stores/qdrant.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/core/__init__.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/core/chunking.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/core/config.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/core/html_utils.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/core/models.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/mcp/__init__.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/mcp/server.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/docs_kit/mcp/tools.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/npx-wrapper/bin/docs-kit.js +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/npx-wrapper/package.json +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/scripts/smoke_test.sh +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/tests/__init__.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/tests/test_agent.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/tests/test_chunking.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/tests/test_config.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/tests/test_embeddings_fastembed.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/tests/test_fetcher_gitbook.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/tests/test_fetcher_mintlify.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/tests/test_mcp_tools.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/tests/test_models.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/tests/test_parsers.py +0 -0
- {docs_kit-0.1.1 → docs_kit-0.1.2}/tests/test_vector_store_qdrant.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: docs-kit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Fetch docs, embed locally, expose via MCP for AI agents.
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -23,7 +23,7 @@ License: MIT License
|
|
|
23
23
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
24
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
25
|
SOFTWARE.
|
|
26
|
-
Requires-Python:
|
|
26
|
+
Requires-Python: <3.14,>=3.11
|
|
27
27
|
Requires-Dist: click>=8.0.0
|
|
28
28
|
Requires-Dist: fastembed>=0.6.0
|
|
29
29
|
Requires-Dist: httpx>=0.27.0
|
|
@@ -65,37 +65,54 @@ No API keys are required for the default local embedding path.
|
|
|
65
65
|
|
|
66
66
|
## Install
|
|
67
67
|
|
|
68
|
+
The recommended install method is **`pipx`** — it puts `docs-kit` on your global PATH without touching your system Python or any project venv:
|
|
69
|
+
|
|
68
70
|
```bash
|
|
69
|
-
|
|
71
|
+
pipx install docs-kit
|
|
70
72
|
```
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
Install `pipx` first if you don't have it:
|
|
73
75
|
|
|
74
76
|
```bash
|
|
75
|
-
|
|
77
|
+
brew install pipx
|
|
78
|
+
pipx ensurepath # adds ~/.local/bin to PATH
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This matters for MCP installs. When `docs-kit` is on PATH, `docs-kit install claude-code` writes the absolute binary path into the agent config (e.g. `/Users/you/.local/pipx/venvs/docs-kit/bin/docs-kit`) so the MCP server works from any directory, not just your project folder.
|
|
82
|
+
|
|
83
|
+
If you install globally with `pipx` and do not have a project yet, `docs-kit install <agent>` will bootstrap a user config at `~/.docs-kit/docs-kit.yaml` and point the MCP entry at it automatically.
|
|
84
|
+
|
|
85
|
+
Or with plain `pip` (works, but binary won't be on global PATH unless you're in the right venv):
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install docs-kit
|
|
76
89
|
```
|
|
77
90
|
|
|
78
91
|
## Quickstart
|
|
79
92
|
|
|
80
93
|
```bash
|
|
81
|
-
#
|
|
82
|
-
docs-kit
|
|
94
|
+
# 0. Install (once)
|
|
95
|
+
pipx install docs-kit
|
|
96
|
+
|
|
97
|
+
# 1. Install into your client
|
|
98
|
+
docs-kit install codex
|
|
99
|
+
docs-kit install claude-code
|
|
83
100
|
|
|
84
|
-
# 2. Ingest docs (
|
|
101
|
+
# 2. Ingest docs (uses ~/.docs-kit/docs-kit.yaml if no local config exists)
|
|
85
102
|
docs-kit ingest https://docs.elevenlabs.io
|
|
86
103
|
|
|
87
104
|
# 3. Check the collection
|
|
88
105
|
docs-kit inspect
|
|
89
106
|
|
|
90
|
-
# 4.
|
|
91
|
-
docs-kit
|
|
107
|
+
# 4. Optional: create a project-local config instead
|
|
108
|
+
docs-kit init
|
|
92
109
|
```
|
|
93
110
|
|
|
94
111
|
## Commands
|
|
95
112
|
|
|
96
113
|
### `docs-kit init`
|
|
97
114
|
|
|
98
|
-
Create `docs-kit.yaml`.
|
|
115
|
+
Create a project-local `docs-kit.yaml`.
|
|
99
116
|
|
|
100
117
|
```bash
|
|
101
118
|
docs-kit init
|
|
@@ -135,6 +152,10 @@ docs-kit serve --transport sse --port 3001
|
|
|
135
152
|
docs-kit serve --config ./docs-kit.yaml
|
|
136
153
|
```
|
|
137
154
|
|
|
155
|
+
Without `--config`, `docs-kit` uses this precedence:
|
|
156
|
+
1. `./docs-kit.yaml`
|
|
157
|
+
2. `~/.docs-kit/docs-kit.yaml` (auto-created on first global use)
|
|
158
|
+
|
|
138
159
|
### `docs-kit install <agent>`
|
|
139
160
|
|
|
140
161
|
Install docs-kit into a supported client config.
|
|
@@ -146,6 +167,8 @@ docs-kit install claude-code --project
|
|
|
146
167
|
docs-kit install cursor --config ./docs-kit.yaml
|
|
147
168
|
```
|
|
148
169
|
|
|
170
|
+
If you run `docs-kit install <agent>` outside a project and omit `--config`, docs-kit creates `~/.docs-kit/docs-kit.yaml` and installs the MCP server with an absolute `--config` pointing there.
|
|
171
|
+
|
|
149
172
|
### `docs-kit query <text>`
|
|
150
173
|
|
|
151
174
|
Run retrieval directly from the CLI.
|
|
@@ -227,7 +250,7 @@ ChatGPT aliases are accepted for guidance only:
|
|
|
227
250
|
|
|
228
251
|
## Configuration
|
|
229
252
|
|
|
230
|
-
`docs-kit.yaml` created by `docs-kit init`:
|
|
253
|
+
Project-local `docs-kit.yaml` created by `docs-kit init`:
|
|
231
254
|
|
|
232
255
|
```yaml
|
|
233
256
|
embedding:
|
|
@@ -250,6 +273,8 @@ mcp:
|
|
|
250
273
|
port: 3001
|
|
251
274
|
```
|
|
252
275
|
|
|
276
|
+
Global installs can also use a user config at `~/.docs-kit/docs-kit.yaml`, with data stored under `~/.docs-kit/qdrant`.
|
|
277
|
+
|
|
253
278
|
## Supported Sources
|
|
254
279
|
|
|
255
280
|
| Source | Strategy |
|
|
@@ -263,6 +288,12 @@ Both GitBook and Mintlify support the [`llms.txt` standard](https://llmstxt.org)
|
|
|
263
288
|
|
|
264
289
|
## Requirements
|
|
265
290
|
|
|
266
|
-
- Python 3.11+
|
|
291
|
+
- Python 3.11–3.13 (3.14+ not yet supported — `onnxruntime` wheels are unavailable for 3.14)
|
|
267
292
|
- Disk space for the local embedding model download
|
|
268
293
|
- Local Qdrant storage under `.docs-kit/` by default
|
|
294
|
+
|
|
295
|
+
If your system Python is 3.14, pass an explicit version to pipx:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
pipx install docs-kit --python python3.13
|
|
299
|
+
```
|
|
@@ -20,37 +20,54 @@ No API keys are required for the default local embedding path.
|
|
|
20
20
|
|
|
21
21
|
## Install
|
|
22
22
|
|
|
23
|
+
The recommended install method is **`pipx`** — it puts `docs-kit` on your global PATH without touching your system Python or any project venv:
|
|
24
|
+
|
|
23
25
|
```bash
|
|
24
|
-
|
|
26
|
+
pipx install docs-kit
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
Install `pipx` first if you don't have it:
|
|
28
30
|
|
|
29
31
|
```bash
|
|
30
|
-
|
|
32
|
+
brew install pipx
|
|
33
|
+
pipx ensurepath # adds ~/.local/bin to PATH
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This matters for MCP installs. When `docs-kit` is on PATH, `docs-kit install claude-code` writes the absolute binary path into the agent config (e.g. `/Users/you/.local/pipx/venvs/docs-kit/bin/docs-kit`) so the MCP server works from any directory, not just your project folder.
|
|
37
|
+
|
|
38
|
+
If you install globally with `pipx` and do not have a project yet, `docs-kit install <agent>` will bootstrap a user config at `~/.docs-kit/docs-kit.yaml` and point the MCP entry at it automatically.
|
|
39
|
+
|
|
40
|
+
Or with plain `pip` (works, but binary won't be on global PATH unless you're in the right venv):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install docs-kit
|
|
31
44
|
```
|
|
32
45
|
|
|
33
46
|
## Quickstart
|
|
34
47
|
|
|
35
48
|
```bash
|
|
36
|
-
#
|
|
37
|
-
docs-kit
|
|
49
|
+
# 0. Install (once)
|
|
50
|
+
pipx install docs-kit
|
|
51
|
+
|
|
52
|
+
# 1. Install into your client
|
|
53
|
+
docs-kit install codex
|
|
54
|
+
docs-kit install claude-code
|
|
38
55
|
|
|
39
|
-
# 2. Ingest docs (
|
|
56
|
+
# 2. Ingest docs (uses ~/.docs-kit/docs-kit.yaml if no local config exists)
|
|
40
57
|
docs-kit ingest https://docs.elevenlabs.io
|
|
41
58
|
|
|
42
59
|
# 3. Check the collection
|
|
43
60
|
docs-kit inspect
|
|
44
61
|
|
|
45
|
-
# 4.
|
|
46
|
-
docs-kit
|
|
62
|
+
# 4. Optional: create a project-local config instead
|
|
63
|
+
docs-kit init
|
|
47
64
|
```
|
|
48
65
|
|
|
49
66
|
## Commands
|
|
50
67
|
|
|
51
68
|
### `docs-kit init`
|
|
52
69
|
|
|
53
|
-
Create `docs-kit.yaml`.
|
|
70
|
+
Create a project-local `docs-kit.yaml`.
|
|
54
71
|
|
|
55
72
|
```bash
|
|
56
73
|
docs-kit init
|
|
@@ -90,6 +107,10 @@ docs-kit serve --transport sse --port 3001
|
|
|
90
107
|
docs-kit serve --config ./docs-kit.yaml
|
|
91
108
|
```
|
|
92
109
|
|
|
110
|
+
Without `--config`, `docs-kit` uses this precedence:
|
|
111
|
+
1. `./docs-kit.yaml`
|
|
112
|
+
2. `~/.docs-kit/docs-kit.yaml` (auto-created on first global use)
|
|
113
|
+
|
|
93
114
|
### `docs-kit install <agent>`
|
|
94
115
|
|
|
95
116
|
Install docs-kit into a supported client config.
|
|
@@ -101,6 +122,8 @@ docs-kit install claude-code --project
|
|
|
101
122
|
docs-kit install cursor --config ./docs-kit.yaml
|
|
102
123
|
```
|
|
103
124
|
|
|
125
|
+
If you run `docs-kit install <agent>` outside a project and omit `--config`, docs-kit creates `~/.docs-kit/docs-kit.yaml` and installs the MCP server with an absolute `--config` pointing there.
|
|
126
|
+
|
|
104
127
|
### `docs-kit query <text>`
|
|
105
128
|
|
|
106
129
|
Run retrieval directly from the CLI.
|
|
@@ -182,7 +205,7 @@ ChatGPT aliases are accepted for guidance only:
|
|
|
182
205
|
|
|
183
206
|
## Configuration
|
|
184
207
|
|
|
185
|
-
`docs-kit.yaml` created by `docs-kit init`:
|
|
208
|
+
Project-local `docs-kit.yaml` created by `docs-kit init`:
|
|
186
209
|
|
|
187
210
|
```yaml
|
|
188
211
|
embedding:
|
|
@@ -205,6 +228,8 @@ mcp:
|
|
|
205
228
|
port: 3001
|
|
206
229
|
```
|
|
207
230
|
|
|
231
|
+
Global installs can also use a user config at `~/.docs-kit/docs-kit.yaml`, with data stored under `~/.docs-kit/qdrant`.
|
|
232
|
+
|
|
208
233
|
## Supported Sources
|
|
209
234
|
|
|
210
235
|
| Source | Strategy |
|
|
@@ -218,6 +243,12 @@ Both GitBook and Mintlify support the [`llms.txt` standard](https://llmstxt.org)
|
|
|
218
243
|
|
|
219
244
|
## Requirements
|
|
220
245
|
|
|
221
|
-
- Python 3.11+
|
|
246
|
+
- Python 3.11–3.13 (3.14+ not yet supported — `onnxruntime` wheels are unavailable for 3.14)
|
|
222
247
|
- Disk space for the local embedding model download
|
|
223
248
|
- Local Qdrant storage under `.docs-kit/` by default
|
|
249
|
+
|
|
250
|
+
If your system Python is 3.14, pass an explicit version to pipx:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
pipx install docs-kit --python python3.13
|
|
254
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.2"
|
|
@@ -21,6 +21,8 @@ INSTALL_TARGETS = [
|
|
|
21
21
|
"chatgpt",
|
|
22
22
|
"chatgpt-desktop",
|
|
23
23
|
]
|
|
24
|
+
|
|
25
|
+
|
|
24
26
|
def _get_agent_class():
|
|
25
27
|
from docs_kit.agent import DocsKitAgent
|
|
26
28
|
|
|
@@ -51,6 +53,35 @@ def _get_qdrant_client_class():
|
|
|
51
53
|
return QdrantClient
|
|
52
54
|
|
|
53
55
|
|
|
56
|
+
def _get_user_config_dir() -> Path:
|
|
57
|
+
return Path.home() / ".docs-kit"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _get_user_config_path() -> Path:
|
|
61
|
+
return _get_user_config_dir() / "docs-kit.yaml"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _build_user_bootstrap_config():
|
|
65
|
+
DocsKitConfig = _get_config_class()
|
|
66
|
+
config = DocsKitConfig()
|
|
67
|
+
config.vector_store.local_path = str((_get_user_config_dir() / "qdrant").resolve())
|
|
68
|
+
return config
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _ensure_user_config() -> Path:
|
|
72
|
+
import yaml as _yaml
|
|
73
|
+
|
|
74
|
+
config_path = _get_user_config_path()
|
|
75
|
+
if config_path.exists():
|
|
76
|
+
return config_path
|
|
77
|
+
|
|
78
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
config = _build_user_bootstrap_config()
|
|
80
|
+
with open(config_path, "w") as f:
|
|
81
|
+
_yaml.dump(config.model_dump(), f, default_flow_style=False, sort_keys=False)
|
|
82
|
+
return config_path
|
|
83
|
+
|
|
84
|
+
|
|
54
85
|
def _load_config(config_path: str | None):
|
|
55
86
|
DocsKitConfig = _get_config_class()
|
|
56
87
|
if config_path:
|
|
@@ -58,7 +89,7 @@ def _load_config(config_path: str | None):
|
|
|
58
89
|
default_yaml = Path("docs-kit.yaml")
|
|
59
90
|
if default_yaml.exists():
|
|
60
91
|
return DocsKitConfig.from_yaml(default_yaml)
|
|
61
|
-
return DocsKitConfig()
|
|
92
|
+
return DocsKitConfig.from_yaml(_ensure_user_config())
|
|
62
93
|
|
|
63
94
|
|
|
64
95
|
def _describe_vector_store(config) -> str:
|
|
@@ -509,10 +540,11 @@ def install_cmd(agent: str, project: bool, config_path: str | None):
|
|
|
509
540
|
# Resolve the binary to an absolute path so the entry works from any CWD.
|
|
510
541
|
command, prefix_args = _resolve_command()
|
|
511
542
|
|
|
543
|
+
created_user_config = False
|
|
544
|
+
|
|
512
545
|
# Resolve --config to an absolute path. If not passed, auto-discover
|
|
513
|
-
# docs-kit.yaml in the current directory.
|
|
514
|
-
#
|
|
515
|
-
# may not find the user's ingested data.
|
|
546
|
+
# docs-kit.yaml in the current directory. For global installs with no
|
|
547
|
+
# project config, bootstrap a stable user-level config automatically.
|
|
516
548
|
if config_path:
|
|
517
549
|
config_path = str(Path(config_path).resolve())
|
|
518
550
|
else:
|
|
@@ -520,12 +552,10 @@ def install_cmd(agent: str, project: bool, config_path: str | None):
|
|
|
520
552
|
if default_yaml.exists():
|
|
521
553
|
config_path = str(default_yaml.resolve())
|
|
522
554
|
elif not project:
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
err=True,
|
|
528
|
-
)
|
|
555
|
+
user_config_already_exists = _get_user_config_path().exists()
|
|
556
|
+
user_config_path = _ensure_user_config()
|
|
557
|
+
config_path = str(user_config_path.resolve())
|
|
558
|
+
created_user_config = not user_config_already_exists
|
|
529
559
|
|
|
530
560
|
args = prefix_args + ["serve"]
|
|
531
561
|
if config_path:
|
|
@@ -535,8 +565,14 @@ def install_cmd(agent: str, project: bool, config_path: str | None):
|
|
|
535
565
|
_install_codex_mcp_server(settings_path, "docs-kit", command, args)
|
|
536
566
|
click.echo(f"✓ Installed docs-kit MCP server into {settings_path}")
|
|
537
567
|
click.echo(" Codex CLI and the Codex IDE extension share this config.")
|
|
568
|
+
if created_user_config:
|
|
569
|
+
click.echo(f" Created user config at {config_path}")
|
|
570
|
+
click.echo(f" Local data will be stored under {_get_user_config_dir() / 'qdrant'}")
|
|
538
571
|
return
|
|
539
572
|
|
|
540
573
|
_install_json_mcp_server(settings_path, "docs-kit", command, args)
|
|
541
574
|
click.echo(f"✓ Installed docs-kit MCP server into {settings_path}")
|
|
575
|
+
if created_user_config:
|
|
576
|
+
click.echo(f" Created user config at {config_path}")
|
|
577
|
+
click.echo(f" Local data will be stored under {_get_user_config_dir() / 'qdrant'}")
|
|
542
578
|
_print_restart_instructions(normalized_agent)
|
|
@@ -8,7 +8,7 @@ dynamic = ["version"]
|
|
|
8
8
|
description = "Fetch docs, embed locally, expose via MCP for AI agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
11
|
-
requires-python = ">=3.11"
|
|
11
|
+
requires-python = ">=3.11,<3.14"
|
|
12
12
|
dependencies = [
|
|
13
13
|
"pydantic>=2.0.0",
|
|
14
14
|
"pydantic-settings>=2.2.1",
|
|
@@ -1,9 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
1
5
|
from click.testing import CliRunner
|
|
2
6
|
from unittest.mock import MagicMock, patch
|
|
3
7
|
|
|
4
8
|
from docs_kit.cli.__main__ import cli
|
|
5
9
|
|
|
6
10
|
|
|
11
|
+
class FakeDocsKitConfig:
|
|
12
|
+
def __init__(self, data=None):
|
|
13
|
+
self._data = data or {
|
|
14
|
+
"embedding": {"provider": "fastembed", "model": "BAAI/bge-small-en-v1.5"},
|
|
15
|
+
"vector_store": {
|
|
16
|
+
"provider": "qdrant",
|
|
17
|
+
"url": "",
|
|
18
|
+
"collection_name": "knowledge_base",
|
|
19
|
+
"local_path": ".docs-kit/qdrant",
|
|
20
|
+
},
|
|
21
|
+
"ingestion": {"chunk_size": 800, "chunk_overlap": 120, "bm25_model": "Qdrant/bm25"},
|
|
22
|
+
"mcp": {"transport": "stdio", "host": "localhost", "port": 3001},
|
|
23
|
+
}
|
|
24
|
+
vector_store = type("VectorStore", (), {})()
|
|
25
|
+
vector_store.local_path = self._data["vector_store"]["local_path"]
|
|
26
|
+
self.vector_store = vector_store
|
|
27
|
+
|
|
28
|
+
def model_dump(self):
|
|
29
|
+
self._data["vector_store"]["local_path"] = self.vector_store.local_path
|
|
30
|
+
return self._data
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_yaml(cls, path):
|
|
34
|
+
with open(path) as f:
|
|
35
|
+
data = yaml.safe_load(f) or {}
|
|
36
|
+
local_path = data["vector_store"]["local_path"]
|
|
37
|
+
if not Path(local_path).is_absolute():
|
|
38
|
+
data["vector_store"]["local_path"] = str((Path(path).parent / local_path).resolve())
|
|
39
|
+
return cls(data)
|
|
40
|
+
|
|
41
|
+
|
|
7
42
|
PUBLIC_COMMAND_HELP_CASES = [
|
|
8
43
|
("init", ["docs-kit init --dir ./sandbox"]),
|
|
9
44
|
("ingest", ["docs-kit ingest https://docs.example.com"]),
|
|
@@ -68,6 +103,50 @@ def test_cli_init_creates_config(tmp_path):
|
|
|
68
103
|
assert "local_path" in config_file.read_text()
|
|
69
104
|
|
|
70
105
|
|
|
106
|
+
def test_load_config_bootstraps_user_config_when_no_local_config(tmp_path):
|
|
107
|
+
fake_home = tmp_path / "home"
|
|
108
|
+
fake_home.mkdir()
|
|
109
|
+
|
|
110
|
+
with patch("docs_kit.cli.commands.Path.home", return_value=fake_home), \
|
|
111
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
112
|
+
from docs_kit.cli.commands import _load_config
|
|
113
|
+
|
|
114
|
+
cwd = Path.cwd()
|
|
115
|
+
try:
|
|
116
|
+
os.chdir(tmp_path)
|
|
117
|
+
config = _load_config(None)
|
|
118
|
+
finally:
|
|
119
|
+
os.chdir(cwd)
|
|
120
|
+
|
|
121
|
+
user_config = fake_home / ".docs-kit" / "docs-kit.yaml"
|
|
122
|
+
assert user_config.exists()
|
|
123
|
+
assert config.vector_store.local_path == str((fake_home / ".docs-kit" / "qdrant").resolve())
|
|
124
|
+
|
|
125
|
+
data = yaml.safe_load(user_config.read_text())
|
|
126
|
+
assert data["vector_store"]["local_path"] == str((fake_home / ".docs-kit" / "qdrant").resolve())
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_load_config_prefers_local_docs_kit_yaml(tmp_path):
|
|
130
|
+
fake_home = tmp_path / "home"
|
|
131
|
+
fake_home.mkdir()
|
|
132
|
+
local_config = tmp_path / "docs-kit.yaml"
|
|
133
|
+
local_config.write_text("vector_store:\n local_path: .docs-kit/qdrant\n")
|
|
134
|
+
|
|
135
|
+
with patch("docs_kit.cli.commands.Path.home", return_value=fake_home), \
|
|
136
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
137
|
+
from docs_kit.cli.commands import _load_config
|
|
138
|
+
|
|
139
|
+
cwd = Path.cwd()
|
|
140
|
+
try:
|
|
141
|
+
os.chdir(tmp_path)
|
|
142
|
+
config = _load_config(None)
|
|
143
|
+
finally:
|
|
144
|
+
os.chdir(cwd)
|
|
145
|
+
|
|
146
|
+
assert config.vector_store.local_path == str((tmp_path / ".docs-kit" / "qdrant").resolve())
|
|
147
|
+
assert not (fake_home / ".docs-kit" / "docs-kit.yaml").exists()
|
|
148
|
+
|
|
149
|
+
|
|
71
150
|
def test_ingest_shows_total_and_calls_agent(tmp_path):
|
|
72
151
|
"""ingest_cmd should call agent.ingest() and print total chunk count."""
|
|
73
152
|
runner = CliRunner()
|
|
@@ -10,6 +10,36 @@ from docs_kit.cli.__main__ import cli
|
|
|
10
10
|
FAKE_DOCS_KIT_BIN = "/usr/local/bin/docs-kit"
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
class FakeDocsKitConfig:
|
|
14
|
+
def __init__(self, data=None):
|
|
15
|
+
self._data = data or {
|
|
16
|
+
"embedding": {"provider": "fastembed", "model": "BAAI/bge-small-en-v1.5"},
|
|
17
|
+
"vector_store": {
|
|
18
|
+
"provider": "qdrant",
|
|
19
|
+
"url": "",
|
|
20
|
+
"collection_name": "knowledge_base",
|
|
21
|
+
"local_path": ".docs-kit/qdrant",
|
|
22
|
+
},
|
|
23
|
+
"ingestion": {"chunk_size": 800, "chunk_overlap": 120, "bm25_model": "Qdrant/bm25"},
|
|
24
|
+
"mcp": {"transport": "stdio", "host": "localhost", "port": 3001},
|
|
25
|
+
}
|
|
26
|
+
vector_store = type("VectorStore", (), {})()
|
|
27
|
+
vector_store.local_path = self._data["vector_store"]["local_path"]
|
|
28
|
+
self.vector_store = vector_store
|
|
29
|
+
|
|
30
|
+
def model_dump(self):
|
|
31
|
+
self._data["vector_store"]["local_path"] = self.vector_store.local_path
|
|
32
|
+
return self._data
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_yaml(cls, path):
|
|
36
|
+
import yaml
|
|
37
|
+
|
|
38
|
+
with open(path) as f:
|
|
39
|
+
data = yaml.safe_load(f) or {}
|
|
40
|
+
return cls(data)
|
|
41
|
+
|
|
42
|
+
|
|
13
43
|
class TestInstallCmd(unittest.TestCase):
|
|
14
44
|
|
|
15
45
|
def test_install_claude_code_creates_settings(self):
|
|
@@ -18,7 +48,8 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
18
48
|
fake_home = Path(tmp_dir) / "home"
|
|
19
49
|
fake_home.mkdir()
|
|
20
50
|
with patch("docs_kit.cli.commands.Path.home", return_value=fake_home), \
|
|
21
|
-
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN)
|
|
51
|
+
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN), \
|
|
52
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
22
53
|
result = runner.invoke(cli, ["install", "claude-code"])
|
|
23
54
|
self.assertEqual(result.exit_code, 0, result.output)
|
|
24
55
|
self.assertIn("Installed docs-kit MCP server", result.output)
|
|
@@ -30,7 +61,8 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
30
61
|
fake_home = Path(tmp_dir) / "home"
|
|
31
62
|
fake_home.mkdir()
|
|
32
63
|
with patch("docs_kit.cli.commands.Path.home", return_value=fake_home), \
|
|
33
|
-
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN)
|
|
64
|
+
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN), \
|
|
65
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
34
66
|
result = runner.invoke(cli, ["install", "claude-code"])
|
|
35
67
|
self.assertEqual(result.exit_code, 0, result.output)
|
|
36
68
|
settings_file = fake_home / ".claude" / "settings.json"
|
|
@@ -41,7 +73,10 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
41
73
|
self.assertIn("docs-kit", data["mcpServers"])
|
|
42
74
|
entry = data["mcpServers"]["docs-kit"]
|
|
43
75
|
self.assertEqual(entry["command"], str(Path(FAKE_DOCS_KIT_BIN).resolve()))
|
|
44
|
-
self.assertEqual(
|
|
76
|
+
self.assertEqual(
|
|
77
|
+
entry["args"],
|
|
78
|
+
["serve", "--config", str((fake_home / ".docs-kit" / "docs-kit.yaml").resolve())],
|
|
79
|
+
)
|
|
45
80
|
|
|
46
81
|
def test_install_python_module_fallback_when_no_binary(self):
|
|
47
82
|
"""Falls back to sys.executable -m docs_kit when docs-kit is not on PATH."""
|
|
@@ -50,7 +85,8 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
50
85
|
fake_home = Path(tmp_dir) / "home"
|
|
51
86
|
fake_home.mkdir()
|
|
52
87
|
with patch("docs_kit.cli.commands.Path.home", return_value=fake_home), \
|
|
53
|
-
patch("docs_kit.cli.commands.shutil.which", return_value=None)
|
|
88
|
+
patch("docs_kit.cli.commands.shutil.which", return_value=None), \
|
|
89
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
54
90
|
result = runner.invoke(cli, ["install", "claude-code"])
|
|
55
91
|
self.assertEqual(result.exit_code, 0, result.output)
|
|
56
92
|
settings_file = fake_home / ".claude" / "settings.json"
|
|
@@ -58,7 +94,10 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
58
94
|
data = json.load(f)
|
|
59
95
|
entry = data["mcpServers"]["docs-kit"]
|
|
60
96
|
self.assertEqual(entry["command"], sys.executable)
|
|
61
|
-
self.assertEqual(
|
|
97
|
+
self.assertEqual(
|
|
98
|
+
entry["args"],
|
|
99
|
+
["-m", "docs_kit", "serve", "--config", str((fake_home / ".docs-kit" / "docs-kit.yaml").resolve())],
|
|
100
|
+
)
|
|
62
101
|
|
|
63
102
|
def test_install_with_config_writes_absolute_config_path(self):
|
|
64
103
|
"""--config is resolved to an absolute path in the written settings."""
|
|
@@ -70,7 +109,8 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
70
109
|
config_file = Path(tmp_dir) / "custom.yaml"
|
|
71
110
|
config_file.write_text("vector_store:\n local_path: .docs-kit/qdrant\n")
|
|
72
111
|
with patch("docs_kit.cli.commands.Path.home", return_value=fake_home), \
|
|
73
|
-
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN)
|
|
112
|
+
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN), \
|
|
113
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
74
114
|
result = runner.invoke(cli, ["install", "claude-code", "--config", str(config_file)])
|
|
75
115
|
self.assertEqual(result.exit_code, 0, result.output)
|
|
76
116
|
settings_file = fake_home / ".claude" / "settings.json"
|
|
@@ -93,7 +133,8 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
93
133
|
config_file = Path(tmp_dir) / "docs-kit.yaml"
|
|
94
134
|
config_file.write_text("vector_store:\n local_path: .docs-kit/qdrant\n")
|
|
95
135
|
with patch("docs_kit.cli.commands.Path.home", return_value=fake_home), \
|
|
96
|
-
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN)
|
|
136
|
+
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN), \
|
|
137
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
97
138
|
result = runner.invoke(cli, ["install", "claude-code"], catch_exceptions=False)
|
|
98
139
|
self.assertEqual(result.exit_code, 0, result.output)
|
|
99
140
|
settings_file = fake_home / ".claude" / "settings.json"
|
|
@@ -105,24 +146,33 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
105
146
|
self.assertTrue(Path(config_arg).is_absolute())
|
|
106
147
|
|
|
107
148
|
def test_install_warns_when_no_config_on_global_install(self):
|
|
108
|
-
"""
|
|
149
|
+
"""Global install bootstraps a user config when no config is present."""
|
|
109
150
|
runner = CliRunner()
|
|
110
151
|
with runner.isolated_filesystem() as tmp_dir:
|
|
111
152
|
fake_home = Path(tmp_dir) / "home"
|
|
112
153
|
fake_home.mkdir()
|
|
113
|
-
# No docs-kit.yaml in CWD
|
|
114
154
|
with patch("docs_kit.cli.commands.Path.home", return_value=fake_home), \
|
|
115
|
-
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN)
|
|
155
|
+
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN), \
|
|
156
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
116
157
|
result = runner.invoke(cli, ["install", "claude-code"])
|
|
117
158
|
self.assertEqual(result.exit_code, 0, result.output)
|
|
118
|
-
self.
|
|
119
|
-
self.assertIn("
|
|
159
|
+
self.assertNotIn("Warning", result.output)
|
|
160
|
+
self.assertIn("Created user config", result.output)
|
|
161
|
+
settings_file = fake_home / ".claude" / "settings.json"
|
|
162
|
+
user_config = fake_home / ".docs-kit" / "docs-kit.yaml"
|
|
163
|
+
self.assertTrue(user_config.exists())
|
|
164
|
+
with open(settings_file) as f:
|
|
165
|
+
data = json.load(f)
|
|
166
|
+
args = data["mcpServers"]["docs-kit"]["args"]
|
|
167
|
+
self.assertIn("--config", args)
|
|
168
|
+
self.assertEqual(args[args.index("--config") + 1], str(user_config.resolve()))
|
|
120
169
|
|
|
121
170
|
def test_install_no_warning_on_project_install_without_config(self):
|
|
122
171
|
"""--project install does not warn about missing config (project-local is expected)."""
|
|
123
172
|
runner = CliRunner()
|
|
124
173
|
with runner.isolated_filesystem() as tmp_dir:
|
|
125
|
-
with patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN)
|
|
174
|
+
with patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN), \
|
|
175
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
126
176
|
result = runner.invoke(cli, ["install", "claude-code", "--project"])
|
|
127
177
|
self.assertEqual(result.exit_code, 0, result.output)
|
|
128
178
|
self.assertNotIn("Warning", result.output)
|
|
@@ -140,7 +190,8 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
140
190
|
"mcpServers": {"other-tool": {"command": "other", "args": []}}
|
|
141
191
|
}))
|
|
142
192
|
with patch("docs_kit.cli.commands.Path.home", return_value=fake_home), \
|
|
143
|
-
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN)
|
|
193
|
+
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN), \
|
|
194
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
144
195
|
result = runner.invoke(cli, ["install", "claude-code"])
|
|
145
196
|
self.assertEqual(result.exit_code, 0, result.output)
|
|
146
197
|
with open(settings_file) as f:
|
|
@@ -152,7 +203,8 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
152
203
|
"""--project flag writes to .claude/settings.json in current dir."""
|
|
153
204
|
runner = CliRunner()
|
|
154
205
|
with runner.isolated_filesystem() as tmp_dir:
|
|
155
|
-
with patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN)
|
|
206
|
+
with patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN), \
|
|
207
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
156
208
|
result = runner.invoke(cli, ["install", "claude-code", "--project"])
|
|
157
209
|
self.assertEqual(result.exit_code, 0, result.output)
|
|
158
210
|
settings_file = Path(tmp_dir) / ".claude" / "settings.json"
|
|
@@ -164,13 +216,14 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
164
216
|
self.assertNotEqual(result.exit_code, 0)
|
|
165
217
|
|
|
166
218
|
def test_install_codex_writes_toml_config(self):
|
|
167
|
-
"""Codex install writes
|
|
219
|
+
"""Codex install writes bootstrap config path when no local config exists."""
|
|
168
220
|
runner = CliRunner()
|
|
169
221
|
with runner.isolated_filesystem() as tmp_dir:
|
|
170
222
|
fake_home = Path(tmp_dir) / "home"
|
|
171
223
|
fake_home.mkdir()
|
|
172
224
|
with patch("docs_kit.cli.commands.Path.home", return_value=fake_home), \
|
|
173
|
-
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN)
|
|
225
|
+
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN), \
|
|
226
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
174
227
|
result = runner.invoke(cli, ["install", "codex"])
|
|
175
228
|
self.assertEqual(result.exit_code, 0, result.output)
|
|
176
229
|
settings_file = fake_home / ".codex" / "config.toml"
|
|
@@ -181,7 +234,10 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
181
234
|
self.assertIn("docs-kit", data["mcp_servers"])
|
|
182
235
|
entry = data["mcp_servers"]["docs-kit"]
|
|
183
236
|
self.assertEqual(entry["command"], str(Path(FAKE_DOCS_KIT_BIN).resolve()))
|
|
184
|
-
self.assertEqual(
|
|
237
|
+
self.assertEqual(
|
|
238
|
+
entry["args"],
|
|
239
|
+
["serve", "--config", str((fake_home / ".docs-kit" / "docs-kit.yaml").resolve())],
|
|
240
|
+
)
|
|
185
241
|
|
|
186
242
|
def test_install_codex_with_config_uses_absolute_path(self):
|
|
187
243
|
"""Codex install resolves --config to an absolute path."""
|
|
@@ -192,7 +248,8 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
192
248
|
config_file = Path(tmp_dir) / "custom.yaml"
|
|
193
249
|
config_file.write_text("vector_store:\n local_path: .docs-kit/qdrant\n")
|
|
194
250
|
with patch("docs_kit.cli.commands.Path.home", return_value=fake_home), \
|
|
195
|
-
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN)
|
|
251
|
+
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN), \
|
|
252
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
196
253
|
result = runner.invoke(cli, ["install", "codex", "--config", str(config_file)])
|
|
197
254
|
self.assertEqual(result.exit_code, 0, result.output)
|
|
198
255
|
settings_file = fake_home / ".codex" / "config.toml"
|
|
@@ -209,11 +266,24 @@ class TestInstallCmd(unittest.TestCase):
|
|
|
209
266
|
fake_home = Path(tmp_dir) / "home"
|
|
210
267
|
fake_home.mkdir()
|
|
211
268
|
with patch("docs_kit.cli.commands.Path.home", return_value=fake_home), \
|
|
212
|
-
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN)
|
|
269
|
+
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN), \
|
|
270
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
213
271
|
result = runner.invoke(cli, ["install", "codex-desktop"])
|
|
214
272
|
self.assertEqual(result.exit_code, 0, result.output)
|
|
215
273
|
self.assertIn(".codex/config.toml", result.output)
|
|
216
274
|
|
|
275
|
+
def test_install_project_does_not_bootstrap_user_config(self):
|
|
276
|
+
runner = CliRunner()
|
|
277
|
+
with runner.isolated_filesystem() as tmp_dir:
|
|
278
|
+
fake_home = Path(tmp_dir) / "home"
|
|
279
|
+
fake_home.mkdir()
|
|
280
|
+
with patch("docs_kit.cli.commands.Path.home", return_value=fake_home), \
|
|
281
|
+
patch("docs_kit.cli.commands.shutil.which", return_value=FAKE_DOCS_KIT_BIN), \
|
|
282
|
+
patch("docs_kit.cli.commands._get_config_class", return_value=FakeDocsKitConfig):
|
|
283
|
+
result = runner.invoke(cli, ["install", "claude-code", "--project"])
|
|
284
|
+
self.assertEqual(result.exit_code, 0, result.output)
|
|
285
|
+
self.assertFalse((fake_home / ".docs-kit" / "docs-kit.yaml").exists())
|
|
286
|
+
|
|
217
287
|
def test_install_chatgpt_shows_remote_mcp_guidance(self):
|
|
218
288
|
runner = CliRunner()
|
|
219
289
|
result = runner.invoke(cli, ["install", "chatgpt"])
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|