cocoindex-code 0.2.4__tar.gz → 0.2.6__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.4 → cocoindex_code-0.2.6}/PKG-INFO +2 -2
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/pyproject.toml +1 -1
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/_version.py +2 -2
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/cli.py +166 -72
- cocoindex_code-0.2.6/src/cocoindex_code/client.py +500 -0
- cocoindex_code-0.2.6/src/cocoindex_code/daemon.py +591 -0
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/indexer.py +7 -7
- cocoindex_code-0.2.6/src/cocoindex_code/project.py +285 -0
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/protocol.py +30 -0
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/server.py +20 -45
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/settings.py +0 -4
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/shared.py +3 -5
- cocoindex_code-0.2.4/src/cocoindex_code/client.py +0 -443
- cocoindex_code-0.2.4/src/cocoindex_code/daemon.py +0 -642
- cocoindex_code-0.2.4/src/cocoindex_code/project.py +0 -124
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/.gitignore +0 -0
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/LICENSE +0 -0
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/README.md +0 -0
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/__init__.py +0 -0
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/__main__.py +0 -0
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/config.py +0 -0
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/query.py +0 -0
- {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/schema.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.6
|
|
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
|
|
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
19
|
Requires-Python: >=3.11
|
|
20
|
-
Requires-Dist: cocoindex[litellm]==1.0.
|
|
20
|
+
Requires-Dist: cocoindex[litellm]==1.0.0a37
|
|
21
21
|
Requires-Dist: einops>=0.8.2
|
|
22
22
|
Requires-Dist: mcp>=1.0.0
|
|
23
23
|
Requires-Dist: msgspec>=0.19.0
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.2.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
31
|
+
__version__ = version = '0.2.6'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 2, 6)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -3,14 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
7
6
|
|
|
8
7
|
import typer as _typer
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
from .client import DaemonClient
|
|
12
|
-
|
|
13
|
-
from .protocol import IndexingProgress, ProjectStatusResponse, SearchResponse
|
|
9
|
+
from .protocol import DoctorCheckResult, IndexingProgress, ProjectStatusResponse, SearchResponse
|
|
14
10
|
from .settings import (
|
|
15
11
|
default_project_settings,
|
|
16
12
|
default_user_settings,
|
|
@@ -52,22 +48,6 @@ def require_project_root() -> Path:
|
|
|
52
48
|
return root
|
|
53
49
|
|
|
54
50
|
|
|
55
|
-
def require_daemon_for_project() -> tuple[DaemonClient, str]:
|
|
56
|
-
"""Resolve project root, then connect to daemon (auto-starting if needed).
|
|
57
|
-
|
|
58
|
-
Returns ``(client, project_root_str)``. Exits on failure.
|
|
59
|
-
"""
|
|
60
|
-
from .client import ensure_daemon
|
|
61
|
-
|
|
62
|
-
project_root = require_project_root()
|
|
63
|
-
try:
|
|
64
|
-
client = ensure_daemon()
|
|
65
|
-
except Exception as e:
|
|
66
|
-
_typer.echo(f"Error: Failed to connect to daemon: {e}", err=True)
|
|
67
|
-
raise _typer.Exit(code=1)
|
|
68
|
-
return client, str(project_root)
|
|
69
|
-
|
|
70
|
-
|
|
71
51
|
def resolve_default_path(project_root: Path) -> str | None:
|
|
72
52
|
"""Compute default ``--path`` filter from CWD relative to project root."""
|
|
73
53
|
cwd = Path.cwd().resolve()
|
|
@@ -101,7 +81,7 @@ def print_index_stats(status: ProjectStatusResponse) -> None:
|
|
|
101
81
|
if status.progress is not None:
|
|
102
82
|
_typer.echo(f"Indexing in progress: {_format_progress(status.progress)}")
|
|
103
83
|
if not status.index_exists:
|
|
104
|
-
_typer.echo("\nIndex not created yet.
|
|
84
|
+
_typer.echo("\nIndex not created yet.")
|
|
105
85
|
return
|
|
106
86
|
_typer.echo("\nIndex stats:")
|
|
107
87
|
_typer.echo(f" Chunks: {status.total_chunks}")
|
|
@@ -128,12 +108,14 @@ def print_search_results(response: SearchResponse) -> None:
|
|
|
128
108
|
_typer.echo(r.content)
|
|
129
109
|
|
|
130
110
|
|
|
131
|
-
def _run_index_with_progress(
|
|
111
|
+
def _run_index_with_progress(project_root: str) -> None:
|
|
132
112
|
"""Run indexing with streaming progress display. Exits on failure."""
|
|
133
113
|
from rich.console import Console as _Console
|
|
134
114
|
from rich.live import Live as _Live
|
|
135
115
|
from rich.spinner import Spinner as _Spinner
|
|
136
116
|
|
|
117
|
+
from . import client as _client
|
|
118
|
+
|
|
137
119
|
err_console = _Console(stderr=True)
|
|
138
120
|
last_progress_line: str | None = None
|
|
139
121
|
|
|
@@ -153,7 +135,7 @@ def _run_index_with_progress(client: DaemonClient, project_root: str) -> None:
|
|
|
153
135
|
live.update(_Spinner("dots", last_progress_line))
|
|
154
136
|
|
|
155
137
|
try:
|
|
156
|
-
resp =
|
|
138
|
+
resp = _client.index(project_root, on_progress=_on_progress, on_waiting=_on_waiting)
|
|
157
139
|
except RuntimeError as e:
|
|
158
140
|
live.stop()
|
|
159
141
|
_typer.echo(f"Indexing failed: {e}", err=True)
|
|
@@ -169,7 +151,6 @@ def _run_index_with_progress(client: DaemonClient, project_root: str) -> None:
|
|
|
169
151
|
|
|
170
152
|
|
|
171
153
|
def _search_with_wait_spinner(
|
|
172
|
-
client: DaemonClient,
|
|
173
154
|
project_root: str,
|
|
174
155
|
query: str,
|
|
175
156
|
languages: list[str] | None = None,
|
|
@@ -182,6 +163,8 @@ def _search_with_wait_spinner(
|
|
|
182
163
|
from rich.live import Live as _Live
|
|
183
164
|
from rich.spinner import Spinner as _Spinner
|
|
184
165
|
|
|
166
|
+
from . import client as _client
|
|
167
|
+
|
|
185
168
|
err_console = _Console(stderr=True)
|
|
186
169
|
|
|
187
170
|
with _Live(_Spinner("dots", "Searching..."), console=err_console, transient=True) as live:
|
|
@@ -192,7 +175,7 @@ def _search_with_wait_spinner(
|
|
|
192
175
|
refresh=True,
|
|
193
176
|
)
|
|
194
177
|
|
|
195
|
-
resp =
|
|
178
|
+
resp = _client.search(
|
|
196
179
|
project_root=project_root,
|
|
197
180
|
query=query,
|
|
198
181
|
languages=languages,
|
|
@@ -305,13 +288,12 @@ def init(
|
|
|
305
288
|
@app.command()
|
|
306
289
|
def index() -> None:
|
|
307
290
|
"""Create/update index for the codebase."""
|
|
308
|
-
client
|
|
309
|
-
print_project_header(project_root)
|
|
310
|
-
|
|
311
|
-
_run_index_with_progress(client, project_root)
|
|
291
|
+
from . import client as _client
|
|
312
292
|
|
|
313
|
-
|
|
314
|
-
|
|
293
|
+
project_root = str(require_project_root())
|
|
294
|
+
print_project_header(project_root)
|
|
295
|
+
_run_index_with_progress(project_root)
|
|
296
|
+
print_index_stats(_client.project_status(project_root))
|
|
315
297
|
|
|
316
298
|
|
|
317
299
|
@app.command()
|
|
@@ -324,12 +306,11 @@ def search(
|
|
|
324
306
|
refresh: bool = _typer.Option(False, "--refresh", help="Refresh index before searching"),
|
|
325
307
|
) -> None:
|
|
326
308
|
"""Semantic search across the codebase."""
|
|
327
|
-
|
|
309
|
+
project_root = str(require_project_root())
|
|
328
310
|
query_str = " ".join(query)
|
|
329
311
|
|
|
330
|
-
# Refresh index with progress display before searching
|
|
331
312
|
if refresh:
|
|
332
|
-
_run_index_with_progress(
|
|
313
|
+
_run_index_with_progress(project_root)
|
|
333
314
|
|
|
334
315
|
# Default path filter from CWD
|
|
335
316
|
paths: list[str] | None = None
|
|
@@ -341,7 +322,6 @@ def search(
|
|
|
341
322
|
paths = [default]
|
|
342
323
|
|
|
343
324
|
resp = _search_with_wait_spinner(
|
|
344
|
-
client,
|
|
345
325
|
project_root=project_root,
|
|
346
326
|
query=query_str,
|
|
347
327
|
languages=lang or None,
|
|
@@ -355,10 +335,11 @@ def search(
|
|
|
355
335
|
@app.command()
|
|
356
336
|
def status() -> None:
|
|
357
337
|
"""Show project status."""
|
|
358
|
-
client
|
|
338
|
+
from . import client as _client
|
|
339
|
+
|
|
340
|
+
project_root = str(require_project_root())
|
|
359
341
|
print_project_header(project_root)
|
|
360
|
-
|
|
361
|
-
print_index_stats(resp)
|
|
342
|
+
print_index_stats(_client.project_status(project_root))
|
|
362
343
|
|
|
363
344
|
|
|
364
345
|
@app.command()
|
|
@@ -400,12 +381,9 @@ def reset(
|
|
|
400
381
|
|
|
401
382
|
# Remove project from daemon first so it releases file handles
|
|
402
383
|
try:
|
|
403
|
-
from .
|
|
384
|
+
from . import client as _client
|
|
404
385
|
|
|
405
|
-
|
|
406
|
-
client.handshake()
|
|
407
|
-
client.remove_project(str(project_root))
|
|
408
|
-
client.close()
|
|
386
|
+
_client.remove_project(str(project_root))
|
|
409
387
|
except (ConnectionRefusedError, OSError, RuntimeError):
|
|
410
388
|
pass # Daemon not running — that's fine
|
|
411
389
|
|
|
@@ -437,18 +415,154 @@ def reset(
|
|
|
437
415
|
)
|
|
438
416
|
|
|
439
417
|
|
|
418
|
+
def _print_section(name: str) -> None:
|
|
419
|
+
import click as _click
|
|
420
|
+
|
|
421
|
+
_typer.echo()
|
|
422
|
+
_typer.echo(_click.style(f" {name}", bold=True))
|
|
423
|
+
_typer.echo(_click.style(f" {'─' * 38}", fg="bright_black"))
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _print_error(msg: str) -> None:
|
|
427
|
+
import click as _click
|
|
428
|
+
|
|
429
|
+
_typer.echo(_click.style(f" ERROR: {msg}", fg="red"), err=True)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _print_doctor_result(result: DoctorCheckResult) -> None:
|
|
433
|
+
import click as _click
|
|
434
|
+
|
|
435
|
+
if result.name == "done":
|
|
436
|
+
return
|
|
437
|
+
if result.ok:
|
|
438
|
+
tag = _click.style("[OK]", fg="green", bold=True)
|
|
439
|
+
else:
|
|
440
|
+
tag = _click.style("[FAIL]", fg="red", bold=True)
|
|
441
|
+
_typer.echo(f"\n {tag} {result.name}")
|
|
442
|
+
for line in result.details:
|
|
443
|
+
_typer.echo(f" {line}")
|
|
444
|
+
for err in result.errors:
|
|
445
|
+
_typer.echo(_click.style(f" ERROR: {err}", fg="red"), err=True)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
@app.command()
|
|
449
|
+
def doctor() -> None:
|
|
450
|
+
"""Check system health and report issues."""
|
|
451
|
+
from . import client as _client
|
|
452
|
+
from .settings import (
|
|
453
|
+
load_project_settings as _load_project_settings,
|
|
454
|
+
)
|
|
455
|
+
from .settings import (
|
|
456
|
+
load_user_settings as _load_user_settings,
|
|
457
|
+
)
|
|
458
|
+
from .settings import (
|
|
459
|
+
project_settings_path as _project_settings_path,
|
|
460
|
+
)
|
|
461
|
+
from .settings import (
|
|
462
|
+
user_settings_path as _user_settings_path,
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
# --- 1. Global settings (local, no daemon needed) ---
|
|
466
|
+
_print_section("Global Settings")
|
|
467
|
+
settings_path = _user_settings_path()
|
|
468
|
+
_typer.echo(f" Settings: {settings_path}")
|
|
469
|
+
try:
|
|
470
|
+
user_settings = _load_user_settings()
|
|
471
|
+
emb = user_settings.embedding
|
|
472
|
+
device_str = f", device={emb.device}" if emb.device else ""
|
|
473
|
+
_typer.echo(f" Embedding: provider={emb.provider}, model={emb.model}{device_str}")
|
|
474
|
+
if user_settings.envs:
|
|
475
|
+
_typer.echo(
|
|
476
|
+
f" Env vars (from settings): {', '.join(sorted(user_settings.envs.keys()))}"
|
|
477
|
+
)
|
|
478
|
+
except (FileNotFoundError, ValueError) as e:
|
|
479
|
+
_print_error(str(e))
|
|
480
|
+
|
|
481
|
+
# --- 2. Connect to daemon (handshake with auto-start/restart) ---
|
|
482
|
+
_print_section("Daemon")
|
|
483
|
+
daemon_ok = False
|
|
484
|
+
try:
|
|
485
|
+
status = _client.daemon_status()
|
|
486
|
+
_typer.echo(f" Version: {status.version}")
|
|
487
|
+
_typer.echo(f" Uptime: {status.uptime_seconds:.1f}s")
|
|
488
|
+
_typer.echo(f" Loaded projects: {len(status.projects)}")
|
|
489
|
+
daemon_ok = True
|
|
490
|
+
except Exception as e:
|
|
491
|
+
_print_error(f"Cannot connect to daemon: {e}")
|
|
492
|
+
_typer.echo(" Remaining daemon-side checks will be skipped.")
|
|
493
|
+
|
|
494
|
+
# --- 3. Daemon environment (requires daemon) ---
|
|
495
|
+
if daemon_ok:
|
|
496
|
+
try:
|
|
497
|
+
env_resp = _client.daemon_env()
|
|
498
|
+
settings_keys = set(env_resp.settings_env_names)
|
|
499
|
+
other_keys = [k for k in env_resp.env_names if k not in settings_keys]
|
|
500
|
+
if other_keys:
|
|
501
|
+
_typer.echo(f" Other env vars in daemon: {', '.join(sorted(other_keys))}")
|
|
502
|
+
except Exception as e:
|
|
503
|
+
_print_error(f"Failed to get daemon env: {e}")
|
|
504
|
+
|
|
505
|
+
# --- 4. Model check (daemon-side, global — before project checks) ---
|
|
506
|
+
if daemon_ok:
|
|
507
|
+
try:
|
|
508
|
+
_client.doctor(
|
|
509
|
+
project_root=None,
|
|
510
|
+
on_result=_print_doctor_result,
|
|
511
|
+
)
|
|
512
|
+
except Exception as e:
|
|
513
|
+
_print_error(f"Model check failed: {e}")
|
|
514
|
+
|
|
515
|
+
# --- 5. Detect project ---
|
|
516
|
+
project_root = find_project_root(Path.cwd())
|
|
517
|
+
|
|
518
|
+
# --- 6. Project settings (local, no daemon needed) ---
|
|
519
|
+
if project_root is not None:
|
|
520
|
+
_print_section("Project Settings")
|
|
521
|
+
ps_path = _project_settings_path(project_root)
|
|
522
|
+
_typer.echo(f" Settings: {ps_path}")
|
|
523
|
+
try:
|
|
524
|
+
ps = _load_project_settings(project_root)
|
|
525
|
+
_typer.echo(f" Include patterns ({len(ps.include_patterns)}):")
|
|
526
|
+
_typer.echo(f" {', '.join(ps.include_patterns)}")
|
|
527
|
+
_typer.echo(f" Exclude patterns ({len(ps.exclude_patterns)}):")
|
|
528
|
+
_typer.echo(f" {', '.join(ps.exclude_patterns)}")
|
|
529
|
+
if ps.language_overrides:
|
|
530
|
+
_typer.echo(" Language overrides:")
|
|
531
|
+
for lo in ps.language_overrides:
|
|
532
|
+
_typer.echo(f" .{lo.ext} -> {lo.lang}")
|
|
533
|
+
except (FileNotFoundError, ValueError) as e:
|
|
534
|
+
_print_error(str(e))
|
|
535
|
+
|
|
536
|
+
# --- 7. Project daemon-side checks (file walk + index status) ---
|
|
537
|
+
if daemon_ok and project_root is not None:
|
|
538
|
+
try:
|
|
539
|
+
_client.doctor(
|
|
540
|
+
project_root=str(project_root),
|
|
541
|
+
on_result=_print_doctor_result,
|
|
542
|
+
)
|
|
543
|
+
except Exception as e:
|
|
544
|
+
_print_error(f"Project checks failed: {e}")
|
|
545
|
+
|
|
546
|
+
# --- 8. Log files ---
|
|
547
|
+
_print_section("Log Files")
|
|
548
|
+
from .daemon import daemon_dir as _daemon_dir
|
|
549
|
+
|
|
550
|
+
log_dir = _daemon_dir()
|
|
551
|
+
_typer.echo(f" Daemon logs: {log_dir / 'daemon.log'}")
|
|
552
|
+
_typer.echo(" Check logs above for further troubleshooting.")
|
|
553
|
+
|
|
554
|
+
|
|
440
555
|
@app.command()
|
|
441
556
|
def mcp() -> None:
|
|
442
557
|
"""Run as MCP server (stdio mode)."""
|
|
443
558
|
import asyncio
|
|
444
559
|
|
|
445
|
-
|
|
560
|
+
project_root = str(require_project_root())
|
|
446
561
|
|
|
447
562
|
async def _run_mcp() -> None:
|
|
448
563
|
from .server import create_mcp_server
|
|
449
564
|
|
|
450
|
-
mcp_server = create_mcp_server(
|
|
451
|
-
# Trigger initial indexing in background
|
|
565
|
+
mcp_server = create_mcp_server(project_root)
|
|
452
566
|
asyncio.create_task(_bg_index(project_root))
|
|
453
567
|
await mcp_server.run_stdio_async()
|
|
454
568
|
|
|
@@ -456,27 +570,14 @@ def mcp() -> None:
|
|
|
456
570
|
|
|
457
571
|
|
|
458
572
|
async def _bg_index(project_root: str) -> None:
|
|
459
|
-
"""Index in background
|
|
460
|
-
|
|
461
|
-
A fresh DaemonClient is used so that background indexing does not share
|
|
462
|
-
the multiprocessing connection used by foreground MCP requests, which
|
|
463
|
-
would corrupt data ("Input data was truncated").
|
|
464
|
-
"""
|
|
573
|
+
"""Index in background. Each call opens its own daemon connection."""
|
|
465
574
|
import asyncio
|
|
466
575
|
|
|
467
|
-
from .
|
|
576
|
+
from . import client as _client
|
|
468
577
|
|
|
469
578
|
loop = asyncio.get_event_loop()
|
|
470
|
-
|
|
471
|
-
def _run_index() -> None:
|
|
472
|
-
bg_client = ensure_daemon()
|
|
473
|
-
try:
|
|
474
|
-
bg_client.index(project_root)
|
|
475
|
-
finally:
|
|
476
|
-
bg_client.close()
|
|
477
|
-
|
|
478
579
|
try:
|
|
479
|
-
await loop.run_in_executor(None,
|
|
580
|
+
await loop.run_in_executor(None, lambda: _client.index(project_root))
|
|
480
581
|
except Exception:
|
|
481
582
|
pass
|
|
482
583
|
|
|
@@ -487,15 +588,9 @@ async def _bg_index(project_root: str) -> None:
|
|
|
487
588
|
@daemon_app.command("status")
|
|
488
589
|
def daemon_status() -> None:
|
|
489
590
|
"""Show daemon status."""
|
|
490
|
-
from .
|
|
491
|
-
|
|
492
|
-
try:
|
|
493
|
-
client = ensure_daemon()
|
|
494
|
-
except Exception as e:
|
|
495
|
-
_typer.echo(f"Error: {e}", err=True)
|
|
496
|
-
raise _typer.Exit(code=1)
|
|
591
|
+
from . import client as _client
|
|
497
592
|
|
|
498
|
-
resp =
|
|
593
|
+
resp = _client.daemon_status()
|
|
499
594
|
_typer.echo(f"Daemon version: {resp.version}")
|
|
500
595
|
_typer.echo(f"Uptime: {resp.uptime_seconds:.1f}s")
|
|
501
596
|
if resp.projects:
|
|
@@ -505,7 +600,6 @@ def daemon_status() -> None:
|
|
|
505
600
|
_typer.echo(f" {p.project_root} [{state}]")
|
|
506
601
|
else:
|
|
507
602
|
_typer.echo("No projects loaded.")
|
|
508
|
-
client.close()
|
|
509
603
|
|
|
510
604
|
|
|
511
605
|
@daemon_app.command("restart")
|