nogic 0.0.1__tar.gz → 0.1.0__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.
- {nogic-0.0.1 → nogic-0.1.0}/PKG-INFO +2 -2
- {nogic-0.0.1 → nogic-0.1.0}/pyproject.toml +2 -2
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/__init__.py +1 -1
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/commands/init.py +4 -5
- nogic-0.1.0/src/nogic/commands/reindex.py +89 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/commands/status.py +3 -65
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/commands/sync.py +13 -16
- nogic-0.1.0/src/nogic/commands/watch.py +116 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/watcher/sync.py +101 -287
- nogic-0.0.1/src/nogic/commands/reindex.py +0 -117
- nogic-0.0.1/src/nogic/commands/watch.py +0 -167
- {nogic-0.0.1 → nogic-0.1.0}/.claude/settings.local.json +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/.gitignore +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/.python-version +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/DEVELOPMENT.md +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/LICENSE +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/README.md +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/api/__init__.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/api/client.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/commands/__init__.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/commands/login.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/commands/projects.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/commands/telemetry_cmd.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/config.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/ignore.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/main.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/parsing/__init__.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/parsing/js_extractor.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/parsing/parser.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/parsing/python_extractor.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/parsing/types.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/storage/__init__.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/storage/relationships.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/storage/schema.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/storage/symbols.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/telemetry.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/ui.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/watcher/__init__.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/watcher/monitor.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/src/nogic/watcher/storage.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/tests/test_e2e.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/tests/test_ignore.py +0 -0
- {nogic-0.0.1 → nogic-0.1.0}/tests/test_sync_batching.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nogic
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: Code intelligence CLI for AI agents — index, search, and understand codebases via graph + vector embeddings.
|
|
5
5
|
Project-URL: Homepage, https://nogic.dev
|
|
6
6
|
Project-URL: Repository, https://github.com/nogic-dev/cli
|
|
7
7
|
Project-URL: Documentation, https://docs.nogic.dev
|
|
8
|
-
Author-email: Nogic <
|
|
8
|
+
Author-email: Nogic <hello@nogic.dev>
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Keywords: ai-agents,code-graph,code-intelligence,embeddings,mcp,tree-sitter
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nogic"
|
|
3
|
-
version = "0.0
|
|
3
|
+
version = "0.1.0"
|
|
4
4
|
description = "Code intelligence CLI for AI agents — index, search, and understand codebases via graph + vector embeddings."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
7
7
|
requires-python = ">=3.11"
|
|
8
8
|
authors = [
|
|
9
|
-
{ name = "Nogic", email = "
|
|
9
|
+
{ name = "Nogic", email = "hello@nogic.dev" },
|
|
10
10
|
]
|
|
11
11
|
classifiers = [
|
|
12
12
|
"Development Status :: 4 - Beta",
|
|
@@ -17,7 +17,6 @@ def init(
|
|
|
17
17
|
project_id: Annotated[Optional[str], typer.Option("--project-id", "-p", help="Use existing project ID.")] = None,
|
|
18
18
|
name: Annotated[Optional[str], typer.Option("--name", "-n", help="Project name.")] = None,
|
|
19
19
|
link: Annotated[bool, typer.Option("--link", help="Re-link an existing project.")] = False,
|
|
20
|
-
yes: Annotated[bool, typer.Option("--yes", "-y", help="Accept defaults, skip prompts.")] = False,
|
|
21
20
|
):
|
|
22
21
|
"""Initialize a Nogic project in a directory."""
|
|
23
22
|
directory = directory.resolve()
|
|
@@ -58,7 +57,7 @@ def init(
|
|
|
58
57
|
ui.info(f"Found existing project '{existing_project.name}'")
|
|
59
58
|
ui.kv("Project ID", existing_project.id)
|
|
60
59
|
|
|
61
|
-
if
|
|
60
|
+
if typer.confirm("Use this project?", default=True):
|
|
62
61
|
config.project_id = existing_project.id
|
|
63
62
|
config.project_name = existing_project.name
|
|
64
63
|
config.directory_hash = dir_hash
|
|
@@ -68,7 +67,7 @@ def init(
|
|
|
68
67
|
ui.console.print(" 1. Wipe graph data and reuse this project")
|
|
69
68
|
ui.console.print(" 2. Abort")
|
|
70
69
|
|
|
71
|
-
choice =
|
|
70
|
+
choice = typer.prompt("Choose option", default="1")
|
|
72
71
|
|
|
73
72
|
if choice == "1":
|
|
74
73
|
with ui.status_spinner("Wiping graph data..."):
|
|
@@ -82,7 +81,7 @@ def init(
|
|
|
82
81
|
ui.dim("Aborted.")
|
|
83
82
|
raise typer.Exit(0)
|
|
84
83
|
else:
|
|
85
|
-
project_name = name or
|
|
84
|
+
project_name = name or typer.prompt("Project name", default=directory.name)
|
|
86
85
|
|
|
87
86
|
try:
|
|
88
87
|
with ui.status_spinner("Creating project..."):
|
|
@@ -96,7 +95,7 @@ def init(
|
|
|
96
95
|
if e.response.status_code == 409:
|
|
97
96
|
data = e.response.json()
|
|
98
97
|
ui.warn(f"Project already exists: {data.get('project_name')}")
|
|
99
|
-
if
|
|
98
|
+
if typer.confirm("Use this project?", default=True):
|
|
100
99
|
config.project_id = data.get("project_id")
|
|
101
100
|
config.project_name = data.get("project_name")
|
|
102
101
|
config.directory_hash = dir_hash
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Reindex command - wipe graph data and re-index from scratch."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from nogic.config import Config, CONFIG_DIR, is_dev_mode, get_api_url
|
|
10
|
+
from nogic.ignore import build_ignore_matcher
|
|
11
|
+
from nogic.watcher import SyncService
|
|
12
|
+
from nogic.api import NogicClient
|
|
13
|
+
from nogic import ui
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def reindex(
|
|
17
|
+
directory: Annotated[Path, typer.Argument(help="Path to the project directory.")] = Path("."),
|
|
18
|
+
ignore: Annotated[Optional[list[str]], typer.Option("--ignore", help="Patterns to ignore.")] = None,
|
|
19
|
+
yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation.")] = False,
|
|
20
|
+
):
|
|
21
|
+
"""Wipe graph data and re-index the entire project."""
|
|
22
|
+
directory = directory.resolve()
|
|
23
|
+
nogic_dir = directory / CONFIG_DIR
|
|
24
|
+
ignore = ignore or []
|
|
25
|
+
|
|
26
|
+
if not nogic_dir.exists():
|
|
27
|
+
ui.error("Not a Nogic project.")
|
|
28
|
+
ui.dim("Run `nogic init` to initialize your project.")
|
|
29
|
+
raise typer.Exit(1)
|
|
30
|
+
|
|
31
|
+
config = Config.load(directory)
|
|
32
|
+
|
|
33
|
+
if not config.api_key:
|
|
34
|
+
ui.error("Not logged in.")
|
|
35
|
+
ui.dim("Run `nogic login` to authenticate.")
|
|
36
|
+
raise typer.Exit(1)
|
|
37
|
+
|
|
38
|
+
if not config.project_id:
|
|
39
|
+
ui.error("No project configured.")
|
|
40
|
+
ui.dim("Run `nogic init` to initialize your project.")
|
|
41
|
+
raise typer.Exit(1)
|
|
42
|
+
|
|
43
|
+
if is_dev_mode():
|
|
44
|
+
ui.dev_banner(get_api_url())
|
|
45
|
+
|
|
46
|
+
if not yes:
|
|
47
|
+
ui.banner("nogic reindex")
|
|
48
|
+
ui.kv("Project", config.project_name or f"{config.project_id[:8]}...")
|
|
49
|
+
ui.kv("Directory", str(directory))
|
|
50
|
+
ui.console.print()
|
|
51
|
+
ui.warn("This will delete all graph data and re-index from scratch.")
|
|
52
|
+
ui.console.print()
|
|
53
|
+
if not typer.confirm("Continue?", default=False):
|
|
54
|
+
ui.dim("Aborted.")
|
|
55
|
+
raise typer.Exit(0)
|
|
56
|
+
|
|
57
|
+
client = NogicClient(config)
|
|
58
|
+
nodes_deleted = 0
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
with ui.status_spinner("Wiping graph data..."):
|
|
62
|
+
try:
|
|
63
|
+
result = client.wipe_project_graph(config.project_id)
|
|
64
|
+
nodes_deleted = result.nodes_deleted
|
|
65
|
+
except httpx.HTTPStatusError as e:
|
|
66
|
+
if e.response.status_code == 404:
|
|
67
|
+
pass
|
|
68
|
+
else:
|
|
69
|
+
ui.error(f"Error wiping graph ({e.response.status_code})")
|
|
70
|
+
raise typer.Exit(1)
|
|
71
|
+
|
|
72
|
+
if nodes_deleted:
|
|
73
|
+
ui.info(f"Deleted {nodes_deleted} nodes")
|
|
74
|
+
|
|
75
|
+
should_ignore = build_ignore_matcher(directory, extra_patterns=ignore)
|
|
76
|
+
sync_service = SyncService(config, directory, log=lambda msg: ui.dim(f" {msg}"))
|
|
77
|
+
|
|
78
|
+
sync_service.initial_scan(directory, should_ignore)
|
|
79
|
+
sync_service.close()
|
|
80
|
+
|
|
81
|
+
ui.console.print()
|
|
82
|
+
ui.success("Reindex complete!")
|
|
83
|
+
if nodes_deleted > 0:
|
|
84
|
+
ui.dim(f" Old nodes deleted: {nodes_deleted}")
|
|
85
|
+
ui.console.print()
|
|
86
|
+
ui.dim("Run 'nogic watch' to continue monitoring for changes.")
|
|
87
|
+
|
|
88
|
+
finally:
|
|
89
|
+
client.close()
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
"""Status command - show project status and verify configuration."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
|
-
import sys
|
|
5
3
|
from pathlib import Path
|
|
6
4
|
from datetime import datetime
|
|
7
|
-
from typing import Annotated
|
|
5
|
+
from typing import Annotated
|
|
8
6
|
|
|
9
7
|
import httpx
|
|
10
8
|
import typer
|
|
@@ -17,28 +15,19 @@ from nogic import ui
|
|
|
17
15
|
|
|
18
16
|
def status(
|
|
19
17
|
directory: Annotated[Path, typer.Argument(help="Path to the project directory.")] = Path("."),
|
|
20
|
-
format: Annotated[Optional[str], typer.Option("--format", help="Output format: text or json.")] = None,
|
|
21
18
|
):
|
|
22
19
|
"""Show project status and verify configuration."""
|
|
23
20
|
directory = directory.resolve()
|
|
24
21
|
nogic_dir = directory / CONFIG_DIR
|
|
25
22
|
current_dir_hash = get_directory_hash(str(directory))
|
|
26
|
-
json_mode = format == "json"
|
|
27
23
|
|
|
28
24
|
if not nogic_dir.exists():
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
else:
|
|
32
|
-
ui.error("Not a Nogic project.")
|
|
33
|
-
ui.dim("Run `nogic init` to initialize your project.")
|
|
25
|
+
ui.error("Not a Nogic project.")
|
|
26
|
+
ui.dim("Run `nogic init` to initialize your project.")
|
|
34
27
|
raise typer.Exit(1)
|
|
35
28
|
|
|
36
29
|
config = Config.load(directory)
|
|
37
30
|
|
|
38
|
-
if json_mode:
|
|
39
|
-
_status_json(directory, config, current_dir_hash)
|
|
40
|
-
return
|
|
41
|
-
|
|
42
31
|
if is_dev_mode():
|
|
43
32
|
ui.dev_banner(get_api_url())
|
|
44
33
|
|
|
@@ -106,57 +95,6 @@ def status(
|
|
|
106
95
|
ui.console.print()
|
|
107
96
|
|
|
108
97
|
|
|
109
|
-
def _status_json(directory: Path, config: Config, dir_hash: str):
|
|
110
|
-
"""Output status as a single JSON object to stdout."""
|
|
111
|
-
result: dict = {
|
|
112
|
-
"project_name": config.project_name or None,
|
|
113
|
-
"project_id": config.project_id or None,
|
|
114
|
-
"directory": str(directory),
|
|
115
|
-
"logged_in": bool(config.api_key),
|
|
116
|
-
"backend": None,
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if config.api_key and config.project_id:
|
|
120
|
-
client = NogicClient(config)
|
|
121
|
-
try:
|
|
122
|
-
backend_project = client.get_project_by_directory(dir_hash)
|
|
123
|
-
if backend_project:
|
|
124
|
-
result["backend"] = {
|
|
125
|
-
"status": "connected",
|
|
126
|
-
"project_name": backend_project.name,
|
|
127
|
-
"project_id": backend_project.id,
|
|
128
|
-
"created_at": backend_project.created_at,
|
|
129
|
-
"updated_at": backend_project.updated_at,
|
|
130
|
-
}
|
|
131
|
-
else:
|
|
132
|
-
result["backend"] = {"status": "not_found"}
|
|
133
|
-
except httpx.HTTPStatusError as e:
|
|
134
|
-
result["backend"] = {
|
|
135
|
-
"status": "error",
|
|
136
|
-
"error": f"HTTP {e.response.status_code}",
|
|
137
|
-
}
|
|
138
|
-
except httpx.RequestError as e:
|
|
139
|
-
result["backend"] = {
|
|
140
|
-
"status": "error",
|
|
141
|
-
"error": str(e),
|
|
142
|
-
}
|
|
143
|
-
finally:
|
|
144
|
-
client.close()
|
|
145
|
-
elif not config.api_key:
|
|
146
|
-
result["backend"] = {"status": "not_logged_in"}
|
|
147
|
-
else:
|
|
148
|
-
result["backend"] = {"status": "not_configured"}
|
|
149
|
-
|
|
150
|
-
sys.stdout.write(json.dumps(result) + "\n")
|
|
151
|
-
sys.stdout.flush()
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def _emit_json_status(**kwargs):
|
|
155
|
-
"""Emit a JSON status object with error info."""
|
|
156
|
-
sys.stdout.write(json.dumps(kwargs) + "\n")
|
|
157
|
-
sys.stdout.flush()
|
|
158
|
-
|
|
159
|
-
|
|
160
98
|
def _format_datetime(dt_string: str) -> str:
|
|
161
99
|
try:
|
|
162
100
|
dt = datetime.fromisoformat(dt_string.replace("Z", "+00:00"))
|
|
@@ -14,13 +14,11 @@ from nogic import telemetry, ui
|
|
|
14
14
|
def sync(
|
|
15
15
|
directory: Annotated[Path, typer.Argument(help="Path to the directory to sync.")] = Path("."),
|
|
16
16
|
ignore: Annotated[Optional[list[str]], typer.Option("--ignore", help="Patterns to ignore.")] = None,
|
|
17
|
-
format: Annotated[Optional[str], typer.Option("--format", help="Output format: text or json.")] = None,
|
|
18
17
|
):
|
|
19
18
|
"""One-time sync of a directory to backend."""
|
|
20
19
|
directory = directory.resolve()
|
|
21
20
|
nogic_dir = directory / ".nogic"
|
|
22
21
|
ignore = ignore or []
|
|
23
|
-
json_mode = format == "json"
|
|
24
22
|
|
|
25
23
|
if not nogic_dir.exists():
|
|
26
24
|
ui.error("Not a Nogic project.")
|
|
@@ -39,22 +37,22 @@ def sync(
|
|
|
39
37
|
ui.dim("Run `nogic init` to initialize your project.")
|
|
40
38
|
raise typer.Exit(1)
|
|
41
39
|
|
|
42
|
-
if
|
|
43
|
-
|
|
44
|
-
ui.dev_banner(get_api_url())
|
|
45
|
-
ui.banner("nogic sync", str(directory))
|
|
46
|
-
ui.kv("Project", f"{config.project_id[:8]}...")
|
|
40
|
+
if is_dev_mode():
|
|
41
|
+
ui.dev_banner(get_api_url())
|
|
47
42
|
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
ui.banner("nogic sync", str(directory))
|
|
44
|
+
ui.kv("Project", f"{config.project_id[:8]}...")
|
|
45
|
+
|
|
46
|
+
should_ignore = build_ignore_matcher(directory, extra_patterns=ignore)
|
|
47
|
+
|
|
48
|
+
sync_service = SyncService(config, directory, log=lambda msg: ui.dim(f" {msg}"))
|
|
50
49
|
|
|
51
50
|
try:
|
|
52
|
-
sync_service.initial_scan(directory,
|
|
51
|
+
sync_service.initial_scan(directory, should_ignore)
|
|
53
52
|
telemetry.capture("cli_sync", {"status": "success"})
|
|
54
53
|
except KeyboardInterrupt:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
ui.dim("Interrupted. Cleaning up...")
|
|
54
|
+
ui.console.print()
|
|
55
|
+
ui.dim("Interrupted. Cleaning up...")
|
|
58
56
|
try:
|
|
59
57
|
sync_service.client.clear_staging(config.project_id)
|
|
60
58
|
except Exception:
|
|
@@ -67,6 +65,5 @@ def sync(
|
|
|
67
65
|
finally:
|
|
68
66
|
sync_service.close()
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
ui.success("Done.")
|
|
68
|
+
ui.console.print()
|
|
69
|
+
ui.success("Done.")
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Watch command for file syncing."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated, Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from nogic.config import Config, is_dev_mode, get_api_url
|
|
10
|
+
from nogic.ignore import build_ignore_matcher
|
|
11
|
+
from nogic.watcher import FileMonitor, SyncService
|
|
12
|
+
from nogic import ui
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def watch(
|
|
16
|
+
directory: Annotated[Path, typer.Argument(help="Path to the directory to watch.")] = Path("."),
|
|
17
|
+
ignore: Annotated[Optional[list[str]], typer.Option("--ignore", help="Patterns to ignore.")] = None,
|
|
18
|
+
):
|
|
19
|
+
"""Watch a directory for file changes and sync to backend."""
|
|
20
|
+
directory = directory.resolve()
|
|
21
|
+
nogic_dir = directory / ".nogic"
|
|
22
|
+
ignore = ignore or []
|
|
23
|
+
|
|
24
|
+
if not nogic_dir.exists():
|
|
25
|
+
ui.error("Not a Nogic project.")
|
|
26
|
+
ui.dim("Run `nogic init` to initialize your project.")
|
|
27
|
+
raise typer.Exit(1)
|
|
28
|
+
|
|
29
|
+
config = Config.load(directory)
|
|
30
|
+
|
|
31
|
+
if not config.api_key:
|
|
32
|
+
ui.error("Not logged in.")
|
|
33
|
+
ui.dim("Run `nogic login` to authenticate.")
|
|
34
|
+
raise typer.Exit(1)
|
|
35
|
+
|
|
36
|
+
if not config.project_id:
|
|
37
|
+
ui.error("No project configured.")
|
|
38
|
+
ui.dim("Run `nogic init` to initialize your project.")
|
|
39
|
+
raise typer.Exit(1)
|
|
40
|
+
|
|
41
|
+
if is_dev_mode():
|
|
42
|
+
ui.dev_banner(get_api_url())
|
|
43
|
+
|
|
44
|
+
ui.banner("nogic watch", str(directory))
|
|
45
|
+
ui.kv("Project", f"{config.project_id[:8]}...")
|
|
46
|
+
|
|
47
|
+
sync_service = SyncService(config, directory, log=lambda msg: ui.dim(f" {msg}"))
|
|
48
|
+
|
|
49
|
+
should_ignore = build_ignore_matcher(directory, extra_patterns=ignore)
|
|
50
|
+
|
|
51
|
+
# Initial scan
|
|
52
|
+
try:
|
|
53
|
+
sync_service.initial_scan(directory, should_ignore)
|
|
54
|
+
except KeyboardInterrupt:
|
|
55
|
+
ui.console.print()
|
|
56
|
+
ui.dim("Interrupted during initial scan. Cleaning up...")
|
|
57
|
+
try:
|
|
58
|
+
sync_service.client.clear_staging(config.project_id)
|
|
59
|
+
except Exception:
|
|
60
|
+
pass
|
|
61
|
+
sync_service.close()
|
|
62
|
+
raise typer.Exit(1)
|
|
63
|
+
|
|
64
|
+
def on_change(path: Path):
|
|
65
|
+
try:
|
|
66
|
+
rel = path.relative_to(directory)
|
|
67
|
+
except ValueError:
|
|
68
|
+
return
|
|
69
|
+
try:
|
|
70
|
+
if sync_service.sync_file_immediate(path):
|
|
71
|
+
ui.console.print(f" [green]SYNCED[/] {rel}")
|
|
72
|
+
except Exception as e:
|
|
73
|
+
err_msg = str(e)
|
|
74
|
+
if "413" in err_msg:
|
|
75
|
+
ui.console.print(f" [yellow]SKIP[/] {rel} (file too large for API)")
|
|
76
|
+
elif "503" in err_msg or "502" in err_msg:
|
|
77
|
+
ui.console.print(f" [red]ERROR[/] {rel} (backend unavailable, will sync on next change)")
|
|
78
|
+
else:
|
|
79
|
+
ui.console.print(f" [red]ERROR[/] {rel}: {err_msg[:120]}")
|
|
80
|
+
|
|
81
|
+
def on_delete(path: Path):
|
|
82
|
+
try:
|
|
83
|
+
rel = path.relative_to(directory)
|
|
84
|
+
except ValueError:
|
|
85
|
+
return
|
|
86
|
+
try:
|
|
87
|
+
if sync_service.delete_file_immediate(path):
|
|
88
|
+
ui.console.print(f" [red]DELETED[/] {rel}")
|
|
89
|
+
else:
|
|
90
|
+
ui.console.print(f" [dim]DELETED[/] {rel} (not indexed)")
|
|
91
|
+
except Exception as e:
|
|
92
|
+
ui.console.print(f" [red]DELETED[/] {rel} (error: {str(e)[:80]})")
|
|
93
|
+
|
|
94
|
+
monitor = FileMonitor(
|
|
95
|
+
root_path=directory,
|
|
96
|
+
on_change=on_change,
|
|
97
|
+
on_delete=on_delete,
|
|
98
|
+
should_ignore=should_ignore,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
ui.console.print()
|
|
102
|
+
ui.info("Watching for changes... (Ctrl+C to stop)")
|
|
103
|
+
ui.console.print()
|
|
104
|
+
|
|
105
|
+
monitor.start()
|
|
106
|
+
try:
|
|
107
|
+
while monitor.is_alive():
|
|
108
|
+
time.sleep(1)
|
|
109
|
+
except KeyboardInterrupt:
|
|
110
|
+
ui.console.print()
|
|
111
|
+
ui.dim("Stopping...")
|
|
112
|
+
finally:
|
|
113
|
+
monitor.stop()
|
|
114
|
+
sync_service.close()
|
|
115
|
+
|
|
116
|
+
ui.success("Done.")
|