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.
Files changed (23) hide show
  1. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/PKG-INFO +2 -2
  2. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/pyproject.toml +1 -1
  3. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/_version.py +2 -2
  4. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/cli.py +166 -72
  5. cocoindex_code-0.2.6/src/cocoindex_code/client.py +500 -0
  6. cocoindex_code-0.2.6/src/cocoindex_code/daemon.py +591 -0
  7. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/indexer.py +7 -7
  8. cocoindex_code-0.2.6/src/cocoindex_code/project.py +285 -0
  9. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/protocol.py +30 -0
  10. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/server.py +20 -45
  11. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/settings.py +0 -4
  12. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/shared.py +3 -5
  13. cocoindex_code-0.2.4/src/cocoindex_code/client.py +0 -443
  14. cocoindex_code-0.2.4/src/cocoindex_code/daemon.py +0 -642
  15. cocoindex_code-0.2.4/src/cocoindex_code/project.py +0 -124
  16. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/.gitignore +0 -0
  17. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/LICENSE +0 -0
  18. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/README.md +0 -0
  19. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/__init__.py +0 -0
  20. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/__main__.py +0 -0
  21. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/config.py +0 -0
  22. {cocoindex_code-0.2.4 → cocoindex_code-0.2.6}/src/cocoindex_code/query.py +0 -0
  23. {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.4
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.0a35
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
@@ -23,7 +23,7 @@ classifiers = [
23
23
 
24
24
  dependencies = [
25
25
  "mcp>=1.0.0",
26
- "cocoindex[litellm]==1.0.0a35",
26
+ "cocoindex[litellm]==1.0.0a37",
27
27
  "sentence-transformers>=2.2.0",
28
28
  "sqlite-vec>=0.1.0",
29
29
  "pydantic>=2.0.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.4'
32
- __version_tuple__ = version_tuple = (0, 2, 4)
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
- if TYPE_CHECKING:
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. Run `ccc index` to build the index.")
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(client: DaemonClient, project_root: str) -> None:
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 = client.index(project_root, on_progress=_on_progress, on_waiting=_on_waiting)
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 = client.search(
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, project_root = require_daemon_for_project()
309
- print_project_header(project_root)
310
-
311
- _run_index_with_progress(client, project_root)
291
+ from . import client as _client
312
292
 
313
- status = client.project_status(project_root)
314
- print_index_stats(status)
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
- client, project_root = require_daemon_for_project()
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(client, project_root)
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, project_root = require_daemon_for_project()
338
+ from . import client as _client
339
+
340
+ project_root = str(require_project_root())
359
341
  print_project_header(project_root)
360
- resp = client.project_status(project_root)
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 .client import DaemonClient
384
+ from . import client as _client
404
385
 
405
- client = DaemonClient.connect()
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
- client, project_root = require_daemon_for_project()
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(client, project_root)
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 using a dedicated daemon connection.
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 .client import ensure_daemon
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, _run_index)
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 .client import ensure_daemon
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 = client.daemon_status()
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")