cocoindex-code 0.2.34__tar.gz → 0.2.36__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.
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/PKG-INFO +66 -5
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/README.md +65 -4
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/__init__.py +0 -3
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/_version.py +2 -2
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/cli.py +140 -47
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/client.py +28 -14
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/.gitignore +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/LICENSE +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/pyproject.toml +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/__main__.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/_daemon_paths.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/chunking.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/daemon.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/embedder_defaults.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/embedder_params.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/indexer.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/litellm_embedder.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/project.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/protocol.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/query.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/schema.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/server.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/settings.py +0 -0
- {cocoindex_code-0.2.34 → cocoindex_code-0.2.36}/src/cocoindex_code/shared.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cocoindex-code
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.36
|
|
4
4
|
Summary: MCP server for indexing and querying codebases using CocoIndex
|
|
5
5
|
Project-URL: Homepage, https://github.com/cocoindex-io/cocoindex-code
|
|
6
6
|
Project-URL: Repository, https://github.com/cocoindex-io/cocoindex-code
|
|
@@ -121,6 +121,17 @@ The agent uses semantic search automatically when it would be helpful. You can a
|
|
|
121
121
|
|
|
122
122
|
Works with [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and other skill-compatible agents.
|
|
123
123
|
|
|
124
|
+
#### Claude Code plugin marketplace
|
|
125
|
+
|
|
126
|
+
For Claude Code users, this repository is also a [plugin marketplace](https://code.claude.com/docs/en/plugin-marketplaces). Install the skill from inside Claude Code with:
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
/plugin marketplace add Roxabi/cocoindex-code
|
|
130
|
+
/plugin install cocoindex-code@cocoindex-code
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
This bundles the same `ccc` skill, with version pinning and `/plugin marketplace update` for updates.
|
|
134
|
+
|
|
124
135
|
### MCP Server
|
|
125
136
|
|
|
126
137
|
Alternatively, use `ccc mcp` to run as an MCP server:
|
|
@@ -424,6 +435,8 @@ docker build -t cocoindex-code:local -f docker/Dockerfile .
|
|
|
424
435
|
|
|
425
436
|
## Configuration
|
|
426
437
|
|
|
438
|
+
For a detailed guide on choosing and configuring embedding models, see [EMBEDDINGS.md](EMBEDDINGS.md).
|
|
439
|
+
|
|
427
440
|
Configuration lives in two YAML files, both created automatically by `ccc init`.
|
|
428
441
|
|
|
429
442
|
### User Settings (`~/.cocoindex_code/global_settings.yml`)
|
|
@@ -781,6 +794,33 @@ Using uv (install or upgrade):
|
|
|
781
794
|
uv tool install --upgrade cocoindex-code
|
|
782
795
|
```
|
|
783
796
|
|
|
797
|
+
### `MDB_MAP_FULL: Environment mapsize limit reached`
|
|
798
|
+
|
|
799
|
+
The index is stored in an LMDB database whose maximum size is fixed when the daemon starts. The default ceiling is **4 GiB**, which is plenty for most projects but can be exhausted by very large codebases (tens of thousands of files), especially with high-dimensional embedding models like `nomic-ai/CodeRankEmbed`.
|
|
800
|
+
|
|
801
|
+
Raise the ceiling with the `COCOINDEX_LMDB_MAP_SIZE` environment variable (value in **bytes**). LMDB only grows the file as data is written, so a high limit doesn't pre-allocate disk — it's safe to set it generously:
|
|
802
|
+
|
|
803
|
+
```yaml
|
|
804
|
+
# ~/.cocoindex_code/global_settings.yml
|
|
805
|
+
envs:
|
|
806
|
+
COCOINDEX_LMDB_MAP_SIZE: "34359738368" # 32 GiB (= 32 * 1024^3)
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
Or, if you prefer to set it in your shell environment (the daemon inherits it):
|
|
810
|
+
|
|
811
|
+
```bash
|
|
812
|
+
export COCOINDEX_LMDB_MAP_SIZE=$((32 * 1024 * 1024 * 1024)) # 32 GiB
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
The map size is read when the daemon starts, so restart it to pick up the change, then re-index:
|
|
816
|
+
|
|
817
|
+
```bash
|
|
818
|
+
ccc daemon restart
|
|
819
|
+
ccc index
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
> This manual step is temporary. Once [cocoindex#2108](https://github.com/cocoindex-io/cocoindex/issues/2108) lands, the map size grows automatically when needed and `COCOINDEX_LMDB_MAP_SIZE` won't be necessary.
|
|
823
|
+
|
|
784
824
|
## Legacy: Environment Variables
|
|
785
825
|
|
|
786
826
|
If you previously configured `cocoindex-code` via environment variables, the `cocoindex-code` MCP command still reads them and auto-migrates to YAML settings on first run. We recommend switching to the YAML settings for new setups.
|
|
@@ -808,18 +848,39 @@ export COCOINDEX_DISABLE_USAGE_TRACKING=1
|
|
|
808
848
|
## Large codebase / Enterprise
|
|
809
849
|
[CocoIndex](https://github.com/cocoindex-io/cocoindex) is an ultra efficient indexing engine that also works on large codebases at scale for enterprises. In enterprise scenarios it is a lot more efficient to share indexes with teammates when there are large or many repos. We also have advanced features like branch dedupe etc designed for enterprise users.
|
|
810
850
|
|
|
851
|
+
> Indexing a very large codebase and hitting `MDB_MAP_FULL`? Raise the LMDB map size — see [`MDB_MAP_FULL: Environment mapsize limit reached`](#mdb_map_full-environment-mapsize-limit-reached) under Troubleshooting.
|
|
852
|
+
|
|
811
853
|
If you need help with remote setup, please email our maintainer linghua@cocoindex.io, happy to help!
|
|
812
854
|
|
|
813
855
|
## Contributing
|
|
814
856
|
|
|
815
|
-
We welcome contributions!
|
|
857
|
+
We welcome contributions! This project uses [uv](https://docs.astral.sh/uv/getting-started/installation/) for development, and every PR is gated on the same lint, format, type-check, and test suite in CI. **Please run these checks locally before opening a PR** — failing pre-commit checks are the most common cause of red CI on incoming PRs.
|
|
858
|
+
|
|
859
|
+
### 1. Install the dev dependencies
|
|
860
|
+
|
|
861
|
+
After installing [uv](https://docs.astral.sh/uv/getting-started/installation/), sync the project. This installs everything the checks need — including [prek](https://github.com/j178/prek), a fast pre-commit runner, plus Ruff, mypy, and pytest:
|
|
816
862
|
|
|
817
863
|
```bash
|
|
818
|
-
|
|
819
|
-
pre-commit install
|
|
864
|
+
uv sync
|
|
820
865
|
```
|
|
821
866
|
|
|
822
|
-
|
|
867
|
+
### 2. Run all checks before every PR
|
|
868
|
+
|
|
869
|
+
Run the full hook suite across all files — this is exactly what CI runs:
|
|
870
|
+
|
|
871
|
+
```bash
|
|
872
|
+
uv run prek run --all-files
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
It runs trailing-whitespace/end-of-file fixes, Ruff lint (`--fix`) and format, `uv.lock` validation, mypy type checking, and the pytest suite. Fix anything it reports (Ruff auto-fixes most lint/format issues for you), re-run until it passes, then push.
|
|
876
|
+
|
|
877
|
+
### 3. (Optional) Run automatically on each commit
|
|
878
|
+
|
|
879
|
+
To have the same checks run on every `git commit`, install the git hook once:
|
|
880
|
+
|
|
881
|
+
```bash
|
|
882
|
+
uv run prek install
|
|
883
|
+
```
|
|
823
884
|
|
|
824
885
|
For more details, see our [contributing guide](https://cocoindex.io/docs/contributing/guide).
|
|
825
886
|
|
|
@@ -77,6 +77,17 @@ The agent uses semantic search automatically when it would be helpful. You can a
|
|
|
77
77
|
|
|
78
78
|
Works with [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and other skill-compatible agents.
|
|
79
79
|
|
|
80
|
+
#### Claude Code plugin marketplace
|
|
81
|
+
|
|
82
|
+
For Claude Code users, this repository is also a [plugin marketplace](https://code.claude.com/docs/en/plugin-marketplaces). Install the skill from inside Claude Code with:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
/plugin marketplace add Roxabi/cocoindex-code
|
|
86
|
+
/plugin install cocoindex-code@cocoindex-code
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This bundles the same `ccc` skill, with version pinning and `/plugin marketplace update` for updates.
|
|
90
|
+
|
|
80
91
|
### MCP Server
|
|
81
92
|
|
|
82
93
|
Alternatively, use `ccc mcp` to run as an MCP server:
|
|
@@ -380,6 +391,8 @@ docker build -t cocoindex-code:local -f docker/Dockerfile .
|
|
|
380
391
|
|
|
381
392
|
## Configuration
|
|
382
393
|
|
|
394
|
+
For a detailed guide on choosing and configuring embedding models, see [EMBEDDINGS.md](EMBEDDINGS.md).
|
|
395
|
+
|
|
383
396
|
Configuration lives in two YAML files, both created automatically by `ccc init`.
|
|
384
397
|
|
|
385
398
|
### User Settings (`~/.cocoindex_code/global_settings.yml`)
|
|
@@ -737,6 +750,33 @@ Using uv (install or upgrade):
|
|
|
737
750
|
uv tool install --upgrade cocoindex-code
|
|
738
751
|
```
|
|
739
752
|
|
|
753
|
+
### `MDB_MAP_FULL: Environment mapsize limit reached`
|
|
754
|
+
|
|
755
|
+
The index is stored in an LMDB database whose maximum size is fixed when the daemon starts. The default ceiling is **4 GiB**, which is plenty for most projects but can be exhausted by very large codebases (tens of thousands of files), especially with high-dimensional embedding models like `nomic-ai/CodeRankEmbed`.
|
|
756
|
+
|
|
757
|
+
Raise the ceiling with the `COCOINDEX_LMDB_MAP_SIZE` environment variable (value in **bytes**). LMDB only grows the file as data is written, so a high limit doesn't pre-allocate disk — it's safe to set it generously:
|
|
758
|
+
|
|
759
|
+
```yaml
|
|
760
|
+
# ~/.cocoindex_code/global_settings.yml
|
|
761
|
+
envs:
|
|
762
|
+
COCOINDEX_LMDB_MAP_SIZE: "34359738368" # 32 GiB (= 32 * 1024^3)
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
Or, if you prefer to set it in your shell environment (the daemon inherits it):
|
|
766
|
+
|
|
767
|
+
```bash
|
|
768
|
+
export COCOINDEX_LMDB_MAP_SIZE=$((32 * 1024 * 1024 * 1024)) # 32 GiB
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
The map size is read when the daemon starts, so restart it to pick up the change, then re-index:
|
|
772
|
+
|
|
773
|
+
```bash
|
|
774
|
+
ccc daemon restart
|
|
775
|
+
ccc index
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
> This manual step is temporary. Once [cocoindex#2108](https://github.com/cocoindex-io/cocoindex/issues/2108) lands, the map size grows automatically when needed and `COCOINDEX_LMDB_MAP_SIZE` won't be necessary.
|
|
779
|
+
|
|
740
780
|
## Legacy: Environment Variables
|
|
741
781
|
|
|
742
782
|
If you previously configured `cocoindex-code` via environment variables, the `cocoindex-code` MCP command still reads them and auto-migrates to YAML settings on first run. We recommend switching to the YAML settings for new setups.
|
|
@@ -764,18 +804,39 @@ export COCOINDEX_DISABLE_USAGE_TRACKING=1
|
|
|
764
804
|
## Large codebase / Enterprise
|
|
765
805
|
[CocoIndex](https://github.com/cocoindex-io/cocoindex) is an ultra efficient indexing engine that also works on large codebases at scale for enterprises. In enterprise scenarios it is a lot more efficient to share indexes with teammates when there are large or many repos. We also have advanced features like branch dedupe etc designed for enterprise users.
|
|
766
806
|
|
|
807
|
+
> Indexing a very large codebase and hitting `MDB_MAP_FULL`? Raise the LMDB map size — see [`MDB_MAP_FULL: Environment mapsize limit reached`](#mdb_map_full-environment-mapsize-limit-reached) under Troubleshooting.
|
|
808
|
+
|
|
767
809
|
If you need help with remote setup, please email our maintainer linghua@cocoindex.io, happy to help!
|
|
768
810
|
|
|
769
811
|
## Contributing
|
|
770
812
|
|
|
771
|
-
We welcome contributions!
|
|
813
|
+
We welcome contributions! This project uses [uv](https://docs.astral.sh/uv/getting-started/installation/) for development, and every PR is gated on the same lint, format, type-check, and test suite in CI. **Please run these checks locally before opening a PR** — failing pre-commit checks are the most common cause of red CI on incoming PRs.
|
|
814
|
+
|
|
815
|
+
### 1. Install the dev dependencies
|
|
816
|
+
|
|
817
|
+
After installing [uv](https://docs.astral.sh/uv/getting-started/installation/), sync the project. This installs everything the checks need — including [prek](https://github.com/j178/prek), a fast pre-commit runner, plus Ruff, mypy, and pytest:
|
|
772
818
|
|
|
773
819
|
```bash
|
|
774
|
-
|
|
775
|
-
pre-commit install
|
|
820
|
+
uv sync
|
|
776
821
|
```
|
|
777
822
|
|
|
778
|
-
|
|
823
|
+
### 2. Run all checks before every PR
|
|
824
|
+
|
|
825
|
+
Run the full hook suite across all files — this is exactly what CI runs:
|
|
826
|
+
|
|
827
|
+
```bash
|
|
828
|
+
uv run prek run --all-files
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
It runs trailing-whitespace/end-of-file fixes, Ruff lint (`--fix`) and format, `uv.lock` validation, mypy type checking, and the pytest suite. Fix anything it reports (Ruff auto-fixes most lint/format issues for you), re-run until it passes, then push.
|
|
832
|
+
|
|
833
|
+
### 3. (Optional) Run automatically on each commit
|
|
834
|
+
|
|
835
|
+
To have the same checks run on every `git commit`, install the git hook once:
|
|
836
|
+
|
|
837
|
+
```bash
|
|
838
|
+
uv run prek install
|
|
839
|
+
```
|
|
779
840
|
|
|
780
841
|
For more details, see our [contributing guide](https://cocoindex.io/docs/contributing/guide).
|
|
781
842
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import logging
|
|
6
5
|
import os
|
|
7
6
|
from typing import TYPE_CHECKING, Any
|
|
8
7
|
|
|
@@ -11,8 +10,6 @@ from typing import TYPE_CHECKING, Any
|
|
|
11
10
|
# init time). See cocoindex-io/cocoindex#1992.
|
|
12
11
|
os.environ.setdefault("COCOINDEX_APPLICATION_FOR_TRACKING", "cocoindex-code")
|
|
13
12
|
|
|
14
|
-
logging.basicConfig(level=logging.WARNING)
|
|
15
|
-
|
|
16
13
|
from ._version import __version__ # noqa: E402
|
|
17
14
|
|
|
18
15
|
if TYPE_CHECKING:
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.36'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 36)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -321,12 +321,35 @@ def remove_from_gitignore(project_root: Path) -> None:
|
|
|
321
321
|
_LITELLM_MODELS_URL = "https://docs.litellm.ai/docs/embedding/supported_embedding"
|
|
322
322
|
|
|
323
323
|
|
|
324
|
+
def _st_model_rejection_reason(model: str) -> str | None:
|
|
325
|
+
"""Why ``model`` can't be a sentence-transformers model, or None if it's fine.
|
|
326
|
+
|
|
327
|
+
sentence-transformers loads HuggingFace model ids. An ``ollama/`` prefix is a
|
|
328
|
+
LiteLLM/Ollama route that ST tries (and fails) to resolve as a HuggingFace
|
|
329
|
+
repo — the user wants the litellm provider instead (issue #181). Real
|
|
330
|
+
HuggingFace ids that contain an ``org/`` slash (``Snowflake/...``,
|
|
331
|
+
``openai/...``) are left alone.
|
|
332
|
+
"""
|
|
333
|
+
if model.strip().lower().startswith("ollama/"):
|
|
334
|
+
return (
|
|
335
|
+
"ollama/… models run via litellm, not sentence-transformers — "
|
|
336
|
+
"go back and pick the litellm provider instead."
|
|
337
|
+
)
|
|
338
|
+
return None
|
|
339
|
+
|
|
340
|
+
|
|
324
341
|
def _resolve_embedding_choice(
|
|
325
342
|
litellm_model_flag: str | None,
|
|
326
343
|
st_installed: bool,
|
|
327
344
|
tty: bool,
|
|
345
|
+
previous: EmbeddingSettings | None = None,
|
|
328
346
|
) -> EmbeddingSettings:
|
|
329
|
-
"""Resolve the embedding settings per the init control-flow diagram.
|
|
347
|
+
"""Resolve the embedding settings per the init control-flow diagram.
|
|
348
|
+
|
|
349
|
+
On a retry, ``previous`` holds the choice from the last attempt; its
|
|
350
|
+
provider and model become the prompt defaults so the user only edits
|
|
351
|
+
what was wrong instead of retyping everything.
|
|
352
|
+
"""
|
|
330
353
|
if litellm_model_flag is not None:
|
|
331
354
|
return EmbeddingSettings(provider="litellm", model=litellm_model_flag)
|
|
332
355
|
|
|
@@ -349,14 +372,15 @@ def _resolve_embedding_choice(
|
|
|
349
372
|
"Embedding provider",
|
|
350
373
|
choices=[
|
|
351
374
|
questionary.Choice(
|
|
352
|
-
title="sentence-transformers (local, free)",
|
|
375
|
+
title="sentence-transformers (local, free — built-in HuggingFace models)",
|
|
353
376
|
value="sentence-transformers",
|
|
354
377
|
),
|
|
355
378
|
questionary.Choice(
|
|
356
|
-
title="litellm (
|
|
379
|
+
title="litellm (100+ providers — cloud APIs & local Ollama)",
|
|
357
380
|
value="litellm",
|
|
358
381
|
),
|
|
359
382
|
],
|
|
383
|
+
default=previous.provider if previous is not None else None,
|
|
360
384
|
).ask()
|
|
361
385
|
else:
|
|
362
386
|
_typer.echo(
|
|
@@ -369,10 +393,16 @@ def _resolve_embedding_choice(
|
|
|
369
393
|
raise _typer.Exit(code=1)
|
|
370
394
|
|
|
371
395
|
if provider == "sentence-transformers":
|
|
372
|
-
|
|
396
|
+
default_model = previous.model if previous is not None else DEFAULT_ST_MODEL
|
|
397
|
+
model = questionary.text(
|
|
398
|
+
"Model name",
|
|
399
|
+
default=default_model,
|
|
400
|
+
validate=lambda m: _st_model_rejection_reason(m) or True,
|
|
401
|
+
).ask()
|
|
373
402
|
elif provider == "litellm":
|
|
374
403
|
_typer.echo(f"See supported LiteLLM embedding models: {_LITELLM_MODELS_URL}")
|
|
375
|
-
|
|
404
|
+
default_model = previous.model if previous is not None else ""
|
|
405
|
+
model = questionary.text("Model name", default=default_model).ask()
|
|
376
406
|
else:
|
|
377
407
|
_typer.echo(f"Error: unknown provider {provider!r}", err=True)
|
|
378
408
|
raise _typer.Exit(code=1)
|
|
@@ -392,13 +422,14 @@ def _ok_fail_tag(ok: bool) -> str:
|
|
|
392
422
|
return _click.style("[FAIL]", fg="red", bold=True)
|
|
393
423
|
|
|
394
424
|
|
|
395
|
-
def _run_init_model_check(
|
|
396
|
-
"""Ask the daemon to test the embedding model; print results
|
|
425
|
+
def _run_init_model_check() -> bool:
|
|
426
|
+
"""Ask the daemon to test the embedding model; print results. Return True if all pass.
|
|
397
427
|
|
|
398
428
|
Drives the check via `DoctorRequest(project_root=None)`. The daemon loads
|
|
399
429
|
the model once and stays running, so the user's next `ccc index` starts
|
|
400
430
|
warm. Both DaemonStartError and generic exceptions are rendered as a
|
|
401
|
-
synthetic failed DoctorCheckResult — uniform failure-output shape.
|
|
431
|
+
synthetic failed DoctorCheckResult — uniform failure-output shape. The
|
|
432
|
+
caller decides what to show on failure (retry prompt / next-steps block).
|
|
402
433
|
"""
|
|
403
434
|
from rich.console import Console as _Console
|
|
404
435
|
from rich.live import Live as _Live
|
|
@@ -426,55 +457,101 @@ def _run_init_model_check(settings_path: Path) -> None:
|
|
|
426
457
|
)
|
|
427
458
|
]
|
|
428
459
|
|
|
429
|
-
|
|
460
|
+
ok = True
|
|
430
461
|
for r in results:
|
|
431
462
|
if r.name == "done":
|
|
432
463
|
continue
|
|
433
|
-
_print_doctor_result(r)
|
|
464
|
+
_print_doctor_result(r, verbose=False)
|
|
434
465
|
if not r.ok:
|
|
435
|
-
|
|
466
|
+
ok = False
|
|
467
|
+
return ok
|
|
436
468
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
469
|
+
|
|
470
|
+
def _print_init_next_steps(settings_path: Path) -> None:
|
|
471
|
+
"""Prominent recovery block shown after a failed init model check."""
|
|
472
|
+
import click as _click
|
|
473
|
+
|
|
474
|
+
display_path = format_path_for_display(settings_path)
|
|
475
|
+
_typer.echo(err=True)
|
|
476
|
+
_typer.echo(_click.style(" Next steps", bold=True), err=True)
|
|
477
|
+
_typer.echo(_click.style(f" {'─' * 38}", fg="bright_black"), err=True)
|
|
478
|
+
_typer.echo(
|
|
479
|
+
f" 1. Edit {_click.style(display_path, fg='cyan', bold=True)}\n"
|
|
480
|
+
" to change the model or add API keys under `envs:`.",
|
|
481
|
+
err=True,
|
|
482
|
+
)
|
|
483
|
+
_typer.echo(" 2. Run `ccc doctor` to verify.", err=True)
|
|
484
|
+
_typer.echo() # trailing blank before whatever init prints next
|
|
444
485
|
|
|
445
486
|
|
|
446
487
|
def _setup_user_settings_interactive(litellm_model_flag: str | None) -> None:
|
|
447
|
-
"""Interactive global-settings setup — only runs when settings are missing.
|
|
488
|
+
"""Interactive global-settings setup — only runs when settings are missing.
|
|
489
|
+
|
|
490
|
+
Loops until the configured model passes its check or the user chooses to
|
|
491
|
+
keep the current settings. On failure we offer a retry, but only when we
|
|
492
|
+
can actually re-prompt for a different model — i.e. interactive and not
|
|
493
|
+
pinned by ``--litellm-model``; otherwise we just print the next steps.
|
|
494
|
+
"""
|
|
448
495
|
from .embedder_defaults import lookup_defaults
|
|
449
496
|
from .shared import is_sentence_transformers_installed
|
|
450
497
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
tty=sys.stdin.isatty(),
|
|
455
|
-
)
|
|
498
|
+
st_installed = is_sentence_transformers_installed()
|
|
499
|
+
interactive = sys.stdin.isatty()
|
|
500
|
+
previous: EmbeddingSettings | None = None
|
|
456
501
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
502
|
+
while True:
|
|
503
|
+
embedding = _resolve_embedding_choice(
|
|
504
|
+
litellm_model_flag=litellm_model_flag,
|
|
505
|
+
st_installed=st_installed,
|
|
506
|
+
tty=interactive,
|
|
507
|
+
previous=previous,
|
|
508
|
+
)
|
|
509
|
+
previous = embedding # remembered as the defaults for a potential retry
|
|
463
510
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
511
|
+
# Apply curated defaults if the model is in our table.
|
|
512
|
+
indexing_defaults, query_defaults = lookup_defaults(embedding.provider, embedding.model)
|
|
513
|
+
defaults_applied = indexing_defaults is not None or query_defaults is not None
|
|
514
|
+
if defaults_applied:
|
|
515
|
+
embedding.indexing_params = indexing_defaults or {}
|
|
516
|
+
embedding.query_params = query_defaults or {}
|
|
467
517
|
|
|
468
|
-
|
|
518
|
+
path = save_initial_user_settings(embedding, defaults_applied=defaults_applied)
|
|
469
519
|
_typer.echo()
|
|
470
|
-
_typer.echo(f"
|
|
471
|
-
_typer.echo(f" indexing_params: {embedding.indexing_params}")
|
|
472
|
-
_typer.echo(f" query_params: {embedding.query_params}")
|
|
520
|
+
_typer.echo(f"Created user settings: {format_path_for_display(path)}")
|
|
473
521
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
522
|
+
if defaults_applied:
|
|
523
|
+
_typer.echo()
|
|
524
|
+
_typer.echo(f"Applied recommended defaults for {embedding.model}:")
|
|
525
|
+
_typer.echo(f" indexing_params: {embedding.indexing_params}")
|
|
526
|
+
_typer.echo(f" query_params: {embedding.query_params}")
|
|
527
|
+
|
|
528
|
+
_typer.echo()
|
|
529
|
+
_typer.echo(f"Testing embedding model: {embedding.provider} / {embedding.model}")
|
|
530
|
+
if _run_init_model_check():
|
|
531
|
+
_typer.echo()
|
|
532
|
+
return
|
|
533
|
+
|
|
534
|
+
# Model check failed. Retry only makes sense if we can re-prompt.
|
|
535
|
+
if interactive and litellm_model_flag is None:
|
|
536
|
+
import questionary
|
|
537
|
+
|
|
538
|
+
_typer.echo() # separate the failure output from the prompt below
|
|
539
|
+
choice = questionary.select(
|
|
540
|
+
"The embedding model couldn't be loaded. What would you like to do?",
|
|
541
|
+
choices=[
|
|
542
|
+
questionary.Choice(title="Try a different provider/model", value="retry"),
|
|
543
|
+
questionary.Choice(
|
|
544
|
+
title="Keep these settings and finish — I'll edit the file myself",
|
|
545
|
+
value="keep",
|
|
546
|
+
),
|
|
547
|
+
],
|
|
548
|
+
).ask()
|
|
549
|
+
if choice == "retry":
|
|
550
|
+
continue
|
|
551
|
+
# "keep" or None (cancelled) falls through to the next-steps block.
|
|
552
|
+
|
|
553
|
+
_print_init_next_steps(path)
|
|
554
|
+
return
|
|
478
555
|
|
|
479
556
|
|
|
480
557
|
@app.command()
|
|
@@ -692,7 +769,7 @@ def _print_error(msg: str) -> None:
|
|
|
692
769
|
_typer.echo(_click.style(f" ERROR: {msg}", fg="red"), err=True)
|
|
693
770
|
|
|
694
771
|
|
|
695
|
-
def _print_doctor_result(result: DoctorCheckResult) -> None:
|
|
772
|
+
def _print_doctor_result(result: DoctorCheckResult, *, verbose: bool = False) -> None:
|
|
696
773
|
import click as _click
|
|
697
774
|
|
|
698
775
|
if result.name == "done":
|
|
@@ -704,13 +781,26 @@ def _print_doctor_result(result: DoctorCheckResult) -> None:
|
|
|
704
781
|
for err in result.errors:
|
|
705
782
|
_typer.echo(_click.style(f" ERROR: {err}", fg="red"), err=True)
|
|
706
783
|
if result.traceback:
|
|
707
|
-
|
|
708
|
-
|
|
784
|
+
if verbose:
|
|
785
|
+
for line in result.traceback.splitlines():
|
|
786
|
+
_typer.echo(_click.style(f" {line}", fg="bright_black"), err=True)
|
|
787
|
+
else:
|
|
788
|
+
_typer.echo(
|
|
789
|
+
_click.style(" Run `ccc doctor -v` for the full traceback.", fg="bright_black"),
|
|
790
|
+
err=True,
|
|
791
|
+
)
|
|
709
792
|
|
|
710
793
|
|
|
711
794
|
@app.command()
|
|
712
795
|
@_catch_daemon_start_error
|
|
713
|
-
def doctor(
|
|
796
|
+
def doctor(
|
|
797
|
+
verbose: bool = _typer.Option(
|
|
798
|
+
False,
|
|
799
|
+
"-v",
|
|
800
|
+
"--verbose",
|
|
801
|
+
help="Show full exception tracebacks for failed checks.",
|
|
802
|
+
),
|
|
803
|
+
) -> None:
|
|
714
804
|
"""Check system health and report issues."""
|
|
715
805
|
from . import client as _client
|
|
716
806
|
from .settings import (
|
|
@@ -720,6 +810,9 @@ def doctor() -> None:
|
|
|
720
810
|
load_user_settings as _load_user_settings,
|
|
721
811
|
)
|
|
722
812
|
|
|
813
|
+
def _on_result(result: DoctorCheckResult) -> None:
|
|
814
|
+
_print_doctor_result(result, verbose=verbose)
|
|
815
|
+
|
|
723
816
|
# --- 1. Global settings (local, no daemon needed) ---
|
|
724
817
|
_print_section("Global Settings")
|
|
725
818
|
settings_path = user_settings_path()
|
|
@@ -773,7 +866,7 @@ def doctor() -> None:
|
|
|
773
866
|
try:
|
|
774
867
|
_client.doctor(
|
|
775
868
|
project_root=None,
|
|
776
|
-
on_result=
|
|
869
|
+
on_result=_on_result,
|
|
777
870
|
)
|
|
778
871
|
except Exception as e:
|
|
779
872
|
_print_error(f"Model check failed: {e}")
|
|
@@ -804,7 +897,7 @@ def doctor() -> None:
|
|
|
804
897
|
try:
|
|
805
898
|
_client.doctor(
|
|
806
899
|
project_root=str(project_root),
|
|
807
|
-
on_result=
|
|
900
|
+
on_result=_on_result,
|
|
808
901
|
)
|
|
809
902
|
except Exception as e:
|
|
810
903
|
_print_error(f"Project checks failed: {e}")
|
|
@@ -111,25 +111,33 @@ def _connect_and_handshake() -> Connection:
|
|
|
111
111
|
|
|
112
112
|
Returns the open connection for the caller to send exactly one request.
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
``
|
|
117
|
-
|
|
114
|
+
Automatically starts or restarts the daemon when it is absent or running
|
|
115
|
+
with stale global settings (e.g. a ``ccc init`` retry rewrote
|
|
116
|
+
``global_settings.yml`` after the daemon loaded it). A genuine *version*
|
|
117
|
+
mismatch after we have already reached a matching daemon means the binary
|
|
118
|
+
was replaced under us mid-session — that fails fast instead of looping on
|
|
119
|
+
restarts.
|
|
118
120
|
"""
|
|
119
121
|
global _daemon_ensured # noqa: PLW0603
|
|
120
122
|
|
|
121
|
-
if _daemon_ensured:
|
|
122
|
-
return _raw_connect_and_handshake()
|
|
123
|
-
|
|
124
|
-
# First connection — auto-start/restart as needed.
|
|
125
123
|
try:
|
|
126
124
|
conn = _raw_connect_and_handshake()
|
|
127
125
|
_daemon_ensured = True
|
|
128
126
|
return conn
|
|
129
|
-
except DaemonVersionError:
|
|
127
|
+
except DaemonVersionError as e:
|
|
128
|
+
# `resp.ok` is False only for a real version mismatch. Once we have
|
|
129
|
+
# ensured a matching daemon, a fresh version mismatch means the binary
|
|
130
|
+
# was swapped under us — fail fast. A settings-only restart request
|
|
131
|
+
# (resp.ok True, but the loaded settings mtime moved) is expected;
|
|
132
|
+
# restart the daemon below so it reloads them.
|
|
133
|
+
if _daemon_ensured and not e.resp.ok:
|
|
134
|
+
raise
|
|
130
135
|
stop_daemon()
|
|
131
136
|
except (ConnectionRefusedError, OSError):
|
|
132
|
-
|
|
137
|
+
# No daemon answered. Normal on the first call (start one below); if we
|
|
138
|
+
# had already ensured one it vanished mid-session — surface that.
|
|
139
|
+
if _daemon_ensured:
|
|
140
|
+
raise
|
|
133
141
|
|
|
134
142
|
if _is_daemon_supervised():
|
|
135
143
|
# Supervisor is responsible for (re)starting the daemon — just wait
|
|
@@ -192,10 +200,16 @@ class DaemonVersionError(RuntimeError):
|
|
|
192
200
|
|
|
193
201
|
def __init__(self, resp: HandshakeResponse) -> None:
|
|
194
202
|
self.resp = resp
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
203
|
+
if not resp.ok:
|
|
204
|
+
message = (
|
|
205
|
+
f"Daemon version mismatch (daemon={resp.daemon_version}, "
|
|
206
|
+
f"client={__version__}). Please retry — the daemon may need a restart."
|
|
207
|
+
)
|
|
208
|
+
else:
|
|
209
|
+
message = (
|
|
210
|
+
"Daemon is running with stale global settings and needs a restart. Please retry."
|
|
211
|
+
)
|
|
212
|
+
super().__init__(message)
|
|
199
213
|
|
|
200
214
|
|
|
201
215
|
class DaemonStartError(RuntimeError):
|
|
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
|