cocoindex-code 0.2.6__tar.gz → 0.2.7__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.6 → cocoindex_code-0.2.7}/PKG-INFO +4 -1
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/README.md +3 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/_version.py +2 -2
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/cli.py +53 -14
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/client.py +58 -10
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/settings.py +17 -11
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/.gitignore +0 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/LICENSE +0 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/pyproject.toml +0 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/__init__.py +0 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/__main__.py +0 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/config.py +0 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/daemon.py +0 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/indexer.py +0 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/project.py +0 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/protocol.py +0 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/query.py +0 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/schema.py +0 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/src/cocoindex_code/server.py +0 -0
- {cocoindex_code-0.2.6 → cocoindex_code-0.2.7}/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.7
|
|
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
|
|
@@ -208,6 +208,7 @@ The background daemon starts automatically on first use.
|
|
|
208
208
|
| `ccc search <query>` | Semantic search across the codebase |
|
|
209
209
|
| `ccc status` | Show index stats (chunk count, file count, language breakdown) |
|
|
210
210
|
| `ccc mcp` | Run as MCP server in stdio mode |
|
|
211
|
+
| `ccc doctor` | Run diagnostics — checks settings, daemon, model, file matching, and index health |
|
|
211
212
|
| `ccc reset` | Delete index databases. `--all` also removes settings. `-f` skips confirmation. |
|
|
212
213
|
| `ccc daemon status` | Show daemon version, uptime, and loaded projects |
|
|
213
214
|
| `ccc daemon restart` | Restart the background daemon |
|
|
@@ -458,6 +459,8 @@ embedding:
|
|
|
458
459
|
|
|
459
460
|
## Troubleshooting
|
|
460
461
|
|
|
462
|
+
Run `ccc doctor` to diagnose common issues. It checks your settings, daemon health, embedding model, file matching, and index status — all in one command.
|
|
463
|
+
|
|
461
464
|
### `sqlite3.Connection object has no attribute enable_load_extension`
|
|
462
465
|
|
|
463
466
|
Some Python installations (e.g. the one pre-installed on macOS) ship with a SQLite library that doesn't enable extensions.
|
|
@@ -169,6 +169,7 @@ The background daemon starts automatically on first use.
|
|
|
169
169
|
| `ccc search <query>` | Semantic search across the codebase |
|
|
170
170
|
| `ccc status` | Show index stats (chunk count, file count, language breakdown) |
|
|
171
171
|
| `ccc mcp` | Run as MCP server in stdio mode |
|
|
172
|
+
| `ccc doctor` | Run diagnostics — checks settings, daemon, model, file matching, and index health |
|
|
172
173
|
| `ccc reset` | Delete index databases. `--all` also removes settings. `-f` skips confirmation. |
|
|
173
174
|
| `ccc daemon status` | Show daemon version, uptime, and loaded projects |
|
|
174
175
|
| `ccc daemon restart` | Restart the background daemon |
|
|
@@ -419,6 +420,8 @@ embedding:
|
|
|
419
420
|
|
|
420
421
|
## Troubleshooting
|
|
421
422
|
|
|
423
|
+
Run `ccc doctor` to diagnose common issues. It checks your settings, daemon health, embedding model, file matching, and index status — all in one command.
|
|
424
|
+
|
|
422
425
|
### `sqlite3.Connection object has no attribute enable_load_extension`
|
|
423
426
|
|
|
424
427
|
Some Python installations (e.g. the one pre-installed on macOS) ship with a SQLite library that doesn't enable extensions.
|
|
@@ -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.7'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 2, 7)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import functools
|
|
6
|
+
from collections.abc import Callable
|
|
5
7
|
from pathlib import Path
|
|
8
|
+
from typing import TypeVar
|
|
6
9
|
|
|
7
10
|
import typer as _typer
|
|
8
11
|
|
|
12
|
+
from .client import DaemonStartError
|
|
9
13
|
from .protocol import DoctorCheckResult, IndexingProgress, ProjectStatusResponse, SearchResponse
|
|
10
14
|
from .settings import (
|
|
11
15
|
default_project_settings,
|
|
@@ -35,8 +39,17 @@ app.add_typer(daemon_app, name="daemon")
|
|
|
35
39
|
def require_project_root() -> Path:
|
|
36
40
|
"""Find the project root by walking up from CWD.
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
Checks global settings first (more fundamental), then project settings.
|
|
43
|
+
Exits with code 1 if either check fails.
|
|
39
44
|
"""
|
|
45
|
+
gs_path = user_settings_path()
|
|
46
|
+
if not gs_path.is_file():
|
|
47
|
+
_typer.echo(
|
|
48
|
+
f"Error: Global settings not found: {gs_path}\n"
|
|
49
|
+
"Run `ccc init` to create it with default settings.",
|
|
50
|
+
err=True,
|
|
51
|
+
)
|
|
52
|
+
raise _typer.Exit(code=1)
|
|
40
53
|
root = find_project_root(Path.cwd())
|
|
41
54
|
if root is None:
|
|
42
55
|
_typer.echo(
|
|
@@ -48,6 +61,26 @@ def require_project_root() -> Path:
|
|
|
48
61
|
return root
|
|
49
62
|
|
|
50
63
|
|
|
64
|
+
_F = TypeVar("_F", bound=Callable[..., object])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _catch_daemon_start_error(func: _F) -> _F:
|
|
68
|
+
"""Decorator that catches ``DaemonStartError`` and exits with a clean message.
|
|
69
|
+
|
|
70
|
+
Apply to any CLI command that may trigger daemon auto-start.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
@functools.wraps(func)
|
|
74
|
+
def wrapper(*args: object, **kwargs: object) -> object:
|
|
75
|
+
try:
|
|
76
|
+
return func(*args, **kwargs)
|
|
77
|
+
except DaemonStartError as e:
|
|
78
|
+
_typer.echo(f"Error: {e}", err=True)
|
|
79
|
+
raise _typer.Exit(code=1)
|
|
80
|
+
|
|
81
|
+
return wrapper # type: ignore[return-value]
|
|
82
|
+
|
|
83
|
+
|
|
51
84
|
def resolve_default_path(project_root: Path) -> str | None:
|
|
52
85
|
"""Compute default ``--path`` filter from CWD relative to project root."""
|
|
53
86
|
cwd = Path.cwd().resolve()
|
|
@@ -138,6 +171,9 @@ def _run_index_with_progress(project_root: str) -> None:
|
|
|
138
171
|
resp = _client.index(project_root, on_progress=_on_progress, on_waiting=_on_waiting)
|
|
139
172
|
except RuntimeError as e:
|
|
140
173
|
live.stop()
|
|
174
|
+
# Let DaemonStartError propagate to the decorator for consistent handling.
|
|
175
|
+
if isinstance(e, DaemonStartError):
|
|
176
|
+
raise
|
|
141
177
|
_typer.echo(f"Indexing failed: {e}", err=True)
|
|
142
178
|
raise _typer.Exit(code=1)
|
|
143
179
|
|
|
@@ -252,6 +288,12 @@ def init(
|
|
|
252
288
|
cwd = Path.cwd().resolve()
|
|
253
289
|
settings_file = _project_settings_path(cwd)
|
|
254
290
|
|
|
291
|
+
# Always ensure user settings exist
|
|
292
|
+
user_path = user_settings_path()
|
|
293
|
+
if not user_path.is_file():
|
|
294
|
+
save_user_settings(default_user_settings())
|
|
295
|
+
_typer.echo(f"Created user settings: {user_path}")
|
|
296
|
+
|
|
255
297
|
# Check if already initialized
|
|
256
298
|
if settings_file.is_file():
|
|
257
299
|
_typer.echo("Project already initialized.")
|
|
@@ -268,12 +310,6 @@ def init(
|
|
|
268
310
|
)
|
|
269
311
|
raise _typer.Exit(code=1)
|
|
270
312
|
|
|
271
|
-
# Create user settings if missing
|
|
272
|
-
user_path = user_settings_path()
|
|
273
|
-
if not user_path.is_file():
|
|
274
|
-
save_user_settings(default_user_settings())
|
|
275
|
-
_typer.echo(f"Created user settings: {user_path}")
|
|
276
|
-
|
|
277
313
|
# Create project settings
|
|
278
314
|
save_project_settings(cwd, default_project_settings())
|
|
279
315
|
_typer.echo(f"Created project settings: {settings_file}")
|
|
@@ -286,6 +322,7 @@ def init(
|
|
|
286
322
|
|
|
287
323
|
|
|
288
324
|
@app.command()
|
|
325
|
+
@_catch_daemon_start_error
|
|
289
326
|
def index() -> None:
|
|
290
327
|
"""Create/update index for the codebase."""
|
|
291
328
|
from . import client as _client
|
|
@@ -297,6 +334,7 @@ def index() -> None:
|
|
|
297
334
|
|
|
298
335
|
|
|
299
336
|
@app.command()
|
|
337
|
+
@_catch_daemon_start_error
|
|
300
338
|
def search(
|
|
301
339
|
query: list[str] = _typer.Argument(..., help="Search query"),
|
|
302
340
|
lang: list[str] = _typer.Option([], "--lang", help="Filter by language"),
|
|
@@ -333,6 +371,7 @@ def search(
|
|
|
333
371
|
|
|
334
372
|
|
|
335
373
|
@app.command()
|
|
374
|
+
@_catch_daemon_start_error
|
|
336
375
|
def status() -> None:
|
|
337
376
|
"""Show project status."""
|
|
338
377
|
from . import client as _client
|
|
@@ -446,6 +485,7 @@ def _print_doctor_result(result: DoctorCheckResult) -> None:
|
|
|
446
485
|
|
|
447
486
|
|
|
448
487
|
@app.command()
|
|
488
|
+
@_catch_daemon_start_error
|
|
449
489
|
def doctor() -> None:
|
|
450
490
|
"""Check system health and report issues."""
|
|
451
491
|
from . import client as _client
|
|
@@ -553,6 +593,7 @@ def doctor() -> None:
|
|
|
553
593
|
|
|
554
594
|
|
|
555
595
|
@app.command()
|
|
596
|
+
@_catch_daemon_start_error
|
|
556
597
|
def mcp() -> None:
|
|
557
598
|
"""Run as MCP server (stdio mode)."""
|
|
558
599
|
import asyncio
|
|
@@ -586,6 +627,7 @@ async def _bg_index(project_root: str) -> None:
|
|
|
586
627
|
|
|
587
628
|
|
|
588
629
|
@daemon_app.command("status")
|
|
630
|
+
@_catch_daemon_start_error
|
|
589
631
|
def daemon_status() -> None:
|
|
590
632
|
"""Show daemon status."""
|
|
591
633
|
from . import client as _client
|
|
@@ -603,6 +645,7 @@ def daemon_status() -> None:
|
|
|
603
645
|
|
|
604
646
|
|
|
605
647
|
@daemon_app.command("restart")
|
|
648
|
+
@_catch_daemon_start_error
|
|
606
649
|
def daemon_restart() -> None:
|
|
607
650
|
"""Restart the daemon."""
|
|
608
651
|
from .client import _wait_for_daemon, start_daemon, stop_daemon
|
|
@@ -611,13 +654,9 @@ def daemon_restart() -> None:
|
|
|
611
654
|
stop_daemon()
|
|
612
655
|
|
|
613
656
|
_typer.echo("Starting daemon...")
|
|
614
|
-
start_daemon()
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
_typer.echo("Daemon restarted.")
|
|
618
|
-
except TimeoutError:
|
|
619
|
-
_typer.echo("Error: Daemon did not start in time.", err=True)
|
|
620
|
-
raise _typer.Exit(code=1)
|
|
657
|
+
proc = start_daemon()
|
|
658
|
+
_wait_for_daemon(proc=proc)
|
|
659
|
+
_typer.echo("Daemon restarted.")
|
|
621
660
|
|
|
622
661
|
|
|
623
662
|
@daemon_app.command("stop")
|
|
@@ -18,7 +18,7 @@ from multiprocessing.connection import Client, Connection
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
|
|
20
20
|
from ._version import __version__
|
|
21
|
-
from .daemon import _connection_family, daemon_pid_path, daemon_socket_path
|
|
21
|
+
from .daemon import _connection_family, daemon_log_path, daemon_pid_path, daemon_socket_path
|
|
22
22
|
from .protocol import (
|
|
23
23
|
DaemonEnvRequest,
|
|
24
24
|
DaemonEnvResponse,
|
|
@@ -84,8 +84,8 @@ def _connect_and_handshake() -> Connection:
|
|
|
84
84
|
except (ConnectionRefusedError, OSError):
|
|
85
85
|
pass
|
|
86
86
|
|
|
87
|
-
start_daemon()
|
|
88
|
-
_wait_for_daemon()
|
|
87
|
+
proc = start_daemon()
|
|
88
|
+
_wait_for_daemon(proc=proc)
|
|
89
89
|
|
|
90
90
|
# Verify the fresh daemon is reachable
|
|
91
91
|
for _attempt in range(10):
|
|
@@ -145,6 +145,27 @@ class DaemonVersionError(RuntimeError):
|
|
|
145
145
|
)
|
|
146
146
|
|
|
147
147
|
|
|
148
|
+
class DaemonStartError(RuntimeError):
|
|
149
|
+
"""Raised when the daemon process fails to start.
|
|
150
|
+
|
|
151
|
+
Carries the daemon log content so callers can display it to the user.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
def __init__(self, message: str, log: str | None = None) -> None:
|
|
155
|
+
self.log = log
|
|
156
|
+
super().__init__(message)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _read_daemon_log() -> str | None:
|
|
160
|
+
"""Read the daemon log file, returning its content or None."""
|
|
161
|
+
log_path = daemon_log_path()
|
|
162
|
+
try:
|
|
163
|
+
content = log_path.read_text().strip()
|
|
164
|
+
return content if content else None
|
|
165
|
+
except (FileNotFoundError, OSError):
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
|
|
148
169
|
def _send(req: Request) -> Response:
|
|
149
170
|
"""Open connection, handshake, send one request, read one response, close."""
|
|
150
171
|
conn = _connect_and_handshake()
|
|
@@ -316,8 +337,12 @@ def is_daemon_running() -> bool:
|
|
|
316
337
|
return os.path.exists(daemon_socket_path())
|
|
317
338
|
|
|
318
339
|
|
|
319
|
-
def start_daemon() ->
|
|
320
|
-
"""Start the daemon as a background process.
|
|
340
|
+
def start_daemon() -> subprocess.Popen[bytes]:
|
|
341
|
+
"""Start the daemon as a background process.
|
|
342
|
+
|
|
343
|
+
Returns the ``Popen`` object so callers can detect early process death
|
|
344
|
+
(via ``proc.poll()``) instead of waiting for a full timeout.
|
|
345
|
+
"""
|
|
321
346
|
from .daemon import daemon_dir
|
|
322
347
|
|
|
323
348
|
daemon_dir().mkdir(parents=True, exist_ok=True)
|
|
@@ -332,7 +357,7 @@ def start_daemon() -> None:
|
|
|
332
357
|
log_fd = open(log_path, "w")
|
|
333
358
|
if sys.platform == "win32":
|
|
334
359
|
_create_no_window = 0x08000000
|
|
335
|
-
subprocess.Popen(
|
|
360
|
+
proc = subprocess.Popen(
|
|
336
361
|
cmd,
|
|
337
362
|
stdout=log_fd,
|
|
338
363
|
stderr=log_fd,
|
|
@@ -340,7 +365,7 @@ def start_daemon() -> None:
|
|
|
340
365
|
creationflags=_create_no_window,
|
|
341
366
|
)
|
|
342
367
|
else:
|
|
343
|
-
subprocess.Popen(
|
|
368
|
+
proc = subprocess.Popen(
|
|
344
369
|
cmd,
|
|
345
370
|
start_new_session=True,
|
|
346
371
|
stdout=log_fd,
|
|
@@ -348,6 +373,7 @@ def start_daemon() -> None:
|
|
|
348
373
|
stdin=subprocess.DEVNULL,
|
|
349
374
|
)
|
|
350
375
|
log_fd.close()
|
|
376
|
+
return proc
|
|
351
377
|
|
|
352
378
|
|
|
353
379
|
def _find_ccc_executable() -> str | None:
|
|
@@ -469,11 +495,27 @@ def _cleanup_stale_files(pid_path: Path, pid: int | None) -> None:
|
|
|
469
495
|
pass
|
|
470
496
|
|
|
471
497
|
|
|
472
|
-
def _wait_for_daemon(
|
|
473
|
-
|
|
498
|
+
def _wait_for_daemon(
|
|
499
|
+
timeout: float = 30.0,
|
|
500
|
+
proc: subprocess.Popen[bytes] | None = None,
|
|
501
|
+
) -> None:
|
|
502
|
+
"""Wait for the daemon socket/pipe to become available.
|
|
503
|
+
|
|
504
|
+
If *proc* is given, polls the process each iteration. When the process
|
|
505
|
+
exits before the socket appears, raises ``DaemonStartError`` immediately
|
|
506
|
+
with the daemon log content — no need to wait for the full timeout.
|
|
507
|
+
"""
|
|
474
508
|
deadline = time.monotonic() + timeout
|
|
475
509
|
sock_path = daemon_socket_path()
|
|
476
510
|
while time.monotonic() < deadline:
|
|
511
|
+
# Check if the daemon process died before the socket appeared.
|
|
512
|
+
if proc is not None and proc.poll() is not None:
|
|
513
|
+
log = _read_daemon_log()
|
|
514
|
+
msg = "Daemon process exited before it became ready."
|
|
515
|
+
if log:
|
|
516
|
+
msg += f"\n\nDaemon log:\n{log}"
|
|
517
|
+
raise DaemonStartError(msg, log=log)
|
|
518
|
+
|
|
477
519
|
if sys.platform == "win32":
|
|
478
520
|
try:
|
|
479
521
|
conn = Client(sock_path, family=_connection_family())
|
|
@@ -485,7 +527,13 @@ def _wait_for_daemon(timeout: float = 30.0) -> None:
|
|
|
485
527
|
if os.path.exists(sock_path):
|
|
486
528
|
return
|
|
487
529
|
time.sleep(0.2)
|
|
488
|
-
|
|
530
|
+
|
|
531
|
+
# Timeout — also include log for diagnostics.
|
|
532
|
+
log = _read_daemon_log()
|
|
533
|
+
msg = "Daemon did not start in time."
|
|
534
|
+
if log:
|
|
535
|
+
msg += f"\n\nDaemon log:\n{log}"
|
|
536
|
+
raise DaemonStartError(msg, log=log)
|
|
489
537
|
|
|
490
538
|
|
|
491
539
|
def _needs_restart(resp: HandshakeResponse) -> bool:
|
|
@@ -240,7 +240,7 @@ def _user_settings_to_dict(settings: UserSettings) -> dict[str, Any]:
|
|
|
240
240
|
def _user_settings_from_dict(d: dict[str, Any]) -> UserSettings:
|
|
241
241
|
emb_dict = d.get("embedding")
|
|
242
242
|
if not emb_dict or "model" not in emb_dict:
|
|
243
|
-
raise ValueError("
|
|
243
|
+
raise ValueError("Must contain 'embedding' with at least 'model' field")
|
|
244
244
|
# Only pass keys that are present; provider uses dataclass default ("litellm") if omitted
|
|
245
245
|
emb_kwargs: dict[str, Any] = {"model": emb_dict["model"]}
|
|
246
246
|
if "provider" in emb_dict:
|
|
@@ -288,11 +288,14 @@ def load_user_settings() -> UserSettings:
|
|
|
288
288
|
path = user_settings_path()
|
|
289
289
|
if not path.is_file():
|
|
290
290
|
raise FileNotFoundError(f"User settings not found: {path}")
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
291
|
+
try:
|
|
292
|
+
with open(path) as f:
|
|
293
|
+
data = _yaml.safe_load(f)
|
|
294
|
+
if not data:
|
|
295
|
+
raise ValueError("File is empty")
|
|
296
|
+
return _user_settings_from_dict(data)
|
|
297
|
+
except Exception as e:
|
|
298
|
+
raise type(e)(f"Error loading {path}: {e}") from e
|
|
296
299
|
|
|
297
300
|
|
|
298
301
|
def save_user_settings(settings: UserSettings) -> Path:
|
|
@@ -312,11 +315,14 @@ def load_project_settings(project_root: Path) -> ProjectSettings:
|
|
|
312
315
|
path = project_settings_path(project_root)
|
|
313
316
|
if not path.is_file():
|
|
314
317
|
raise FileNotFoundError(f"Project settings not found: {path}")
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
318
|
+
try:
|
|
319
|
+
with open(path) as f:
|
|
320
|
+
data = _yaml.safe_load(f)
|
|
321
|
+
if not data:
|
|
322
|
+
return default_project_settings()
|
|
323
|
+
return _project_settings_from_dict(data)
|
|
324
|
+
except Exception as e:
|
|
325
|
+
raise type(e)(f"Error loading {path}: {e}") from e
|
|
320
326
|
|
|
321
327
|
|
|
322
328
|
def save_project_settings(project_root: Path, settings: ProjectSettings) -> Path:
|
|
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
|