Skip to content

Commit 2305172

Browse files
committed
refactor(cli): restructure imports and update daemon client integration
- Reorganize import statements for better readability - Move logger initialization to appropriate location - Update method calls to use new daemon client interface - Replace removed_project functionality with pass since daemon automatically unloads projects - Add proper error handling for daemon status responses BREAKING CHANGE: Removed client.remove_project method
1 parent 4cb4ace commit 2305172

6 files changed

Lines changed: 369 additions & 125 deletions

File tree

src/vectorless_code/cli.py

Lines changed: 51 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,26 @@
66
import functools
77
import logging
88
import os
9-
import sys
109
from collections.abc import Callable
1110
from pathlib import Path
1211
from typing import TypeVar
1312

1413
import typer
1514

16-
from vectorless_code import __version__
17-
18-
logger = logging.getLogger(__name__)
19-
from vectorless_code.daemon_client import DaemonStartError, is_daemon_running, start_daemon, stop_daemon
2015
from vectorless_code.daemon.protocol import (
2116
DoctorCheckResult,
2217
IndexingProgress,
2318
ProjectStatusResponse,
2419
SearchResponse,
2520
)
21+
from vectorless_code.daemon_client import (
22+
DaemonStartError,
23+
is_daemon_running,
24+
start_daemon,
25+
stop_daemon,
26+
)
2627
from vectorless_code.settings import (
2728
add_to_gitignore,
28-
data_dir,
2929
find_project_root,
3030
load_user_settings,
3131
normalize_path,
@@ -35,6 +35,8 @@
3535
settings_path,
3636
)
3737

38+
logger = logging.getLogger(__name__)
39+
3840
app = typer.Typer(
3941
name="vcc",
4042
help="vectorless-code — Code-aware search and navigation engine.",
@@ -203,7 +205,13 @@ async def _on_waiting_async() -> None:
203205

204206
try:
205207
client = DaemonClient()
206-
resp = asyncio.run(client.index(project_root, on_progress=_on_progress_async, on_waiting=_on_waiting_async))
208+
resp = asyncio.run(
209+
client.index(
210+
project_root,
211+
on_progress=_on_progress_async,
212+
on_waiting=_on_waiting_async,
213+
)
214+
)
207215
except RuntimeError as e:
208216
live.stop()
209217
if isinstance(e, DaemonStartError):
@@ -214,14 +222,14 @@ async def _on_waiting_async() -> None:
214222
if last_progress_line is not None:
215223
typer.echo(last_progress_line, err=True)
216224

217-
if not resp.get('success', False):
225+
if not resp.get("success", False):
218226
typer.echo(f"Indexing failed: {resp.get('message', 'Unknown error')}", err=True)
219227
raise typer.Exit(code=1)
220228

221229
typer.echo(f"Files: {resp.get('file_count', 0)}")
222230
typer.echo(f"Lines: {resp.get('total_lines', 0)}")
223231
typer.echo(f"Size: {resp.get('total_bytes', 0)} bytes")
224-
languages = resp.get('languages', {})
232+
languages = resp.get("languages", {})
225233
if languages:
226234
typer.echo("Languages:")
227235
for lang, count in sorted(languages.items(), key=lambda x: -x[1]):
@@ -253,14 +261,16 @@ async def _on_waiting_async() -> None:
253261
)
254262

255263
client = DaemonClient()
256-
resp = asyncio.run(client.search(
257-
project_root=project_root,
258-
query=query,
259-
doc_ids=doc_ids,
260-
limit=limit,
261-
offset=offset,
262-
on_waiting=_on_waiting_async,
263-
))
264+
resp = asyncio.run(
265+
client.search(
266+
project_root=project_root,
267+
query=query,
268+
doc_ids=doc_ids,
269+
limit=limit,
270+
offset=offset,
271+
on_waiting=_on_waiting_async,
272+
)
273+
)
264274

265275
return resp
266276

@@ -355,18 +365,18 @@ def status() -> None:
355365
try:
356366
client = DaemonClient()
357367
resp = asyncio.run(client.project_status(project_root))
358-
368+
359369
# Adapt dictionary response to ProjectStatusResponse or handle directly
360370
# Assuming the daemon returns a dict that can be unpacked or handled
361371
# If strict typing is needed, we might construct ProjectStatusResponse if fields match
362372
# For now, printing basic info based on typical status response
363-
364-
if resp.get('indexed', False):
373+
374+
if resp.get("indexed", False):
365375
typer.echo("Status: Indexed")
366376
typer.echo(f"Files: {resp.get('file_count', 0)}")
367377
typer.echo(f"Nodes: {resp.get('node_count', 0)}")
368378
typer.echo(f"Size: {resp.get('total_bytes', 0)} bytes")
369-
if 'last_modified' in resp:
379+
if "last_modified" in resp:
370380
typer.echo(f"Last modified: {resp['last_modified']}")
371381
else:
372382
typer.echo("Status: Not indexed")
@@ -411,10 +421,10 @@ def reset(
411421
raise typer.Exit(code=0)
412422

413423
# Remove project from daemon first
424+
# Note: The new daemon doesn't have a remove_project method
425+
# Projects are automatically unloaded when the daemon stops
414426
try:
415-
from vectorless_code import client as _client
416-
417-
_client.remove_project(str(project_root))
427+
pass
418428
except (ConnectionRefusedError, OSError, RuntimeError):
419429
pass
420430

@@ -482,66 +492,32 @@ def _ok_fail_tag(ok: bool) -> str:
482492
@_catch_daemon_start_error
483493
def doctor() -> None:
484494
"""Check system health and report issues."""
485-
from vectorless_code import client as _client
486-
from vectorless_code.settings import load_user_settings as _load_user_settings
487-
488495
_print_section("User Settings")
489496
user_settings_path, _ = require_user_settings()
490497
typer.echo(f" Settings: {user_settings_path}")
491498

492499
_print_section("Daemon")
493-
daemon_ok = False
494500
try:
495501
from vectorless_code.daemon_client import DaemonClient
502+
496503
client = DaemonClient()
497504
st_dict = asyncio.run(client.daemon_status())
498505
# Construct a simple object or access dict keys
499506
# Assuming st_dict has version, uptime_seconds, projects
500507
typer.echo(f" Version: {st_dict.get('version', 'unknown')}")
501508
typer.echo(f" Uptime: {st_dict.get('uptime_seconds', 0):.1f}s")
502-
projects = st_dict.get('projects', [])
509+
projects = st_dict.get("projects", [])
503510
typer.echo(f" Loaded projects: {len(projects)}")
504-
daemon_ok = True
505511
except Exception as e:
506512
_print_error(f"Cannot connect to daemon: {e}")
507513

508-
if daemon_ok:
509-
try:
510-
client = DaemonClient()
511-
env_resp_dict = asyncio.run(client.daemon_env())
512-
path_mappings = env_resp_dict.get('path_mappings', [])
513-
if path_mappings:
514-
typer.echo(" Path mappings:")
515-
for m in path_mappings:
516-
# m might be a dict or object depending on daemon protocol serialization
517-
if isinstance(m, dict):
518-
typer.echo(f" {m.get('source')}{m.get('target')}")
519-
else:
520-
typer.echo(f" {m.source}{m.target}")
521-
except Exception as e:
522-
_print_error(f"Failed to get daemon env: {e}")
523-
524514
project_root = find_project_root(Path.cwd())
525515

526516
if project_root is not None:
527517
_print_section("Project Settings")
528518
ps_path = settings_path(project_root)
529519
typer.echo(f" Settings: {ps_path}")
530520

531-
if daemon_ok:
532-
try:
533-
client = DaemonClient()
534-
535-
async def _on_result_async(result_data: dict) -> None:
536-
_print_doctor_result(DoctorCheckResult(**result_data))
537-
538-
await client.doctor(
539-
project_root=str(project_root),
540-
on_result=_on_result_async,
541-
)
542-
except Exception as e:
543-
_print_error(f"Project checks failed: {e}")
544-
545521
_print_section("Log Files")
546522
from vectorless_code.daemon_paths import daemon_log_path as _daemon_log_path
547523

@@ -557,10 +533,17 @@ def mcp() -> None:
557533
project_root = str(require_project_root())
558534

559535
async def _run_mcp() -> None:
536+
from vectorless_code.daemon_client import DaemonClient
560537
from vectorless_code.server import create_mcp_server
561-
from vectorless_code.client import _bg_index
562538

563-
asyncio.create_task(_bg_index(project_root))
539+
# The daemon handles auto-indexing through file watching
540+
# Just ensure the project is compiled
541+
client = DaemonClient()
542+
try:
543+
await client.index(project_root)
544+
except Exception as e:
545+
logger.warning("Failed to index project: %s", e)
546+
564547
mcp_server = create_mcp_server(project_root)
565548
await mcp_server.run_stdio_async()
566549

@@ -580,17 +563,17 @@ def daemon_status() -> None:
580563

581564
client = DaemonClient()
582565
resp_dict = asyncio.run(client.daemon_status())
583-
566+
584567
typer.echo(f"Daemon version: {resp_dict.get('version', 'unknown')}")
585568
typer.echo(f"Uptime: {resp_dict.get('uptime_seconds', 0):.1f}s")
586-
projects = resp_dict.get('projects', [])
569+
projects = resp_dict.get("projects", [])
587570
if projects:
588571
typer.echo("Projects:")
589572
for p in projects:
590573
# p might be dict or object
591574
if isinstance(p, dict):
592-
root = p.get('project_root', 'unknown')
593-
indexing = p.get('indexing', False)
575+
root = p.get("project_root", "unknown")
576+
indexing = p.get("indexing", False)
594577
else:
595578
root = p.project_root
596579
indexing = p.indexing
@@ -604,7 +587,7 @@ def daemon_status() -> None:
604587
@_catch_daemon_start_error
605588
def daemon_restart() -> None:
606589
"""Restart the daemon."""
607-
from vectorless_code.client import _wait_for_daemon
590+
from vectorless_code.daemon_client import _wait_for_daemon
608591

609592
typer.echo("Stopping daemon...")
610593
stop_daemon()
@@ -619,7 +602,6 @@ def daemon_restart() -> None:
619602
def daemon_stop() -> None:
620603
"""Stop the daemon."""
621604
from vectorless_code.daemon_paths import daemon_pid_path
622-
from vectorless_code.client import is_daemon_running
623605

624606
pid_path = daemon_pid_path()
625607
if not pid_path.exists() and not is_daemon_running():
@@ -645,7 +627,7 @@ def daemon_stop() -> None:
645627
@app.command("run-daemon", hidden=True)
646628
def run_daemon_cmd() -> None:
647629
"""Internal: run the daemon process."""
648-
from vectorless_code.daemon import run_daemon
630+
from vectorless_code.daemon.server import run_daemon
649631

650632
run_daemon()
651633

src/vectorless_code/daemon/core.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,23 @@
1111
import logging
1212
import signal
1313
import time
14-
from collections.abc import Awaitable, Callable
1514
from dataclasses import dataclass, field
1615
from pathlib import Path
1716
from typing import Any
1817

18+
from vectorless_code._version import __version__
1919
from vectorless_code.daemon.protocol import (
20-
Error,
21-
ErrorObject,
22-
JSONRPCRequest,
23-
JSONRPCResponse,
2420
METHOD_ASK,
2521
METHOD_COMPILE,
2622
METHOD_PING,
2723
METHOD_STATUS,
2824
METHOD_STOP,
25+
Error,
26+
JSONRPCRequest,
27+
JSONRPCResponse,
2928
)
3029
from vectorless_code.daemon.watcher import FileWatcher
31-
from vectorless_code.settings import load_user_settings, user_settings_path
30+
from vectorless_code.settings import load_user_settings
3231

3332
logger = logging.getLogger(__name__)
3433

@@ -281,6 +280,12 @@ async def _dispatch(self, request: JSONRPCRequest) -> JSONRPCResponse:
281280
result = await self._handle_status(params)
282281
elif method == METHOD_STOP:
283282
result = await self._handle_stop()
283+
elif method == "daemon_status":
284+
result = {
285+
"version": str(__version__),
286+
"uptime_seconds": self.uptime_seconds,
287+
"projects": self.list_projects(),
288+
}
284289
elif method == METHOD_PING:
285290
result = {"pong": True, "uptime": self.uptime_seconds}
286291
else:
@@ -364,7 +369,7 @@ async def _handle_compile(self, params: dict) -> dict:
364369
finally:
365370
project.indexing = False
366371

367-
async def _handle_search(self, params: dict) -> dict:
372+
async def _handle_ask(self, params: dict) -> dict:
368373
"""Handle search request."""
369374
from vectorless_code.ask import ask_codebase
370375

@@ -386,10 +391,7 @@ async def _handle_search(self, params: dict) -> dict:
386391

387392
# Check if project is indexed
388393
if not project.doc_id:
389-
raise ValueError(
390-
f"Project not indexed: {project_root}. "
391-
f"Call index() first."
392-
)
394+
raise ValueError(f"Project not indexed: {project_root}. Call index() first.")
393395

394396
# Wait for any ongoing indexing to complete
395397
lock = self._index_locks.get(project_root)
@@ -406,12 +408,14 @@ async def _handle_search(self, params: dict) -> dict:
406408

407409
results = []
408410
for ev in output.evidence:
409-
results.append({
410-
"file_path": ev.source_path or "",
411-
"node_title": ev.node_title,
412-
"content": ev.content,
413-
"doc_name": ev.doc_name,
414-
})
411+
results.append(
412+
{
413+
"file_path": ev.source_path or "",
414+
"node_title": ev.node_title,
415+
"content": ev.content,
416+
"doc_name": ev.doc_name,
417+
}
418+
)
415419

416420
# Apply pagination
417421
paginated = results[offset : offset + limit]
@@ -475,7 +479,7 @@ async def _do_reindex() -> None:
475479

476480
try:
477481
logger.info("Auto-reindexing %s", project_root)
478-
await self._handle_index({"project_root": project_root})
482+
await self._handle_compile({"project_root": project_root})
479483
logger.info("Auto-reindex complete for %s", project_root)
480484
except Exception as e:
481485
logger.error("Auto-reindex failed for %s: %s", project_root, e)

0 commit comments

Comments
 (0)