nogic 0.0.1__py3-none-any.whl
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/__init__.py +3 -0
- nogic/api/__init__.py +23 -0
- nogic/api/client.py +390 -0
- nogic/commands/__init__.py +1 -0
- nogic/commands/init.py +125 -0
- nogic/commands/login.py +75 -0
- nogic/commands/projects.py +138 -0
- nogic/commands/reindex.py +117 -0
- nogic/commands/status.py +165 -0
- nogic/commands/sync.py +72 -0
- nogic/commands/telemetry_cmd.py +65 -0
- nogic/commands/watch.py +167 -0
- nogic/config.py +157 -0
- nogic/ignore.py +109 -0
- nogic/main.py +58 -0
- nogic/parsing/__init__.py +22 -0
- nogic/parsing/js_extractor.py +674 -0
- nogic/parsing/parser.py +220 -0
- nogic/parsing/python_extractor.py +484 -0
- nogic/parsing/types.py +80 -0
- nogic/storage/__init__.py +14 -0
- nogic/storage/relationships.py +322 -0
- nogic/storage/schema.py +154 -0
- nogic/storage/symbols.py +203 -0
- nogic/telemetry.py +142 -0
- nogic/ui.py +60 -0
- nogic/watcher/__init__.py +7 -0
- nogic/watcher/monitor.py +80 -0
- nogic/watcher/storage.py +185 -0
- nogic/watcher/sync.py +879 -0
- nogic-0.0.1.dist-info/METADATA +201 -0
- nogic-0.0.1.dist-info/RECORD +35 -0
- nogic-0.0.1.dist-info/WHEEL +4 -0
- nogic-0.0.1.dist-info/entry_points.txt +2 -0
- nogic-0.0.1.dist-info/licenses/LICENSE +21 -0
nogic/commands/watch.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Watch command for file syncing."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Annotated, Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from nogic.config import Config, is_dev_mode, get_api_url
|
|
12
|
+
from nogic.ignore import build_ignore_matcher
|
|
13
|
+
from nogic.watcher import FileMonitor, SyncService
|
|
14
|
+
from nogic import ui
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _emit_json(event: str, **kwargs):
|
|
18
|
+
"""Emit a single NDJSON line to stdout."""
|
|
19
|
+
payload = {"event": event, "timestamp": int(time.time()), **kwargs}
|
|
20
|
+
sys.stdout.write(json.dumps(payload) + "\n")
|
|
21
|
+
sys.stdout.flush()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def watch(
|
|
25
|
+
directory: Annotated[Path, typer.Argument(help="Path to the directory to watch.")] = Path("."),
|
|
26
|
+
ignore: Annotated[Optional[list[str]], typer.Option("--ignore", help="Patterns to ignore.")] = None,
|
|
27
|
+
format: Annotated[Optional[str], typer.Option("--format", help="Output format: text or json.")] = None,
|
|
28
|
+
):
|
|
29
|
+
"""Watch a directory for file changes and sync to backend."""
|
|
30
|
+
directory = directory.resolve()
|
|
31
|
+
nogic_dir = directory / ".nogic"
|
|
32
|
+
ignore = ignore or []
|
|
33
|
+
json_mode = format == "json"
|
|
34
|
+
|
|
35
|
+
if not nogic_dir.exists():
|
|
36
|
+
if json_mode:
|
|
37
|
+
_emit_json("error", message="Not a Nogic project. Run `nogic init` to initialize.")
|
|
38
|
+
else:
|
|
39
|
+
ui.error("Not a Nogic project.")
|
|
40
|
+
ui.dim("Run `nogic init` to initialize your project.")
|
|
41
|
+
raise typer.Exit(1)
|
|
42
|
+
|
|
43
|
+
config = Config.load(directory)
|
|
44
|
+
|
|
45
|
+
if not config.api_key:
|
|
46
|
+
if json_mode:
|
|
47
|
+
_emit_json("error", message="Not logged in. Run `nogic login` to authenticate.")
|
|
48
|
+
else:
|
|
49
|
+
ui.error("Not logged in.")
|
|
50
|
+
ui.dim("Run `nogic login` to authenticate.")
|
|
51
|
+
raise typer.Exit(1)
|
|
52
|
+
|
|
53
|
+
if not config.project_id:
|
|
54
|
+
if json_mode:
|
|
55
|
+
_emit_json("error", message="No project configured. Run `nogic init` to initialize.")
|
|
56
|
+
else:
|
|
57
|
+
ui.error("No project configured.")
|
|
58
|
+
ui.dim("Run `nogic init` to initialize your project.")
|
|
59
|
+
raise typer.Exit(1)
|
|
60
|
+
|
|
61
|
+
if not json_mode:
|
|
62
|
+
if is_dev_mode():
|
|
63
|
+
ui.dev_banner(get_api_url())
|
|
64
|
+
ui.banner("nogic watch", str(directory))
|
|
65
|
+
ui.kv("Project", f"{config.project_id[:8]}...")
|
|
66
|
+
|
|
67
|
+
log_fn = (lambda msg: None) if json_mode else (lambda msg: ui.dim(f" {msg}"))
|
|
68
|
+
sync_service = SyncService(config, directory, log=log_fn, json_mode=json_mode)
|
|
69
|
+
|
|
70
|
+
should_ignore = build_ignore_matcher(directory, extra_patterns=ignore)
|
|
71
|
+
|
|
72
|
+
# Initial scan
|
|
73
|
+
try:
|
|
74
|
+
sync_service.initial_scan(directory, should_ignore)
|
|
75
|
+
if json_mode:
|
|
76
|
+
files_indexed = len(sync_service._file_cache)
|
|
77
|
+
_emit_json("initial_scan_complete", files_indexed=files_indexed)
|
|
78
|
+
except KeyboardInterrupt:
|
|
79
|
+
if not json_mode:
|
|
80
|
+
ui.console.print()
|
|
81
|
+
ui.dim("Interrupted during initial scan. Cleaning up...")
|
|
82
|
+
try:
|
|
83
|
+
sync_service.client.clear_staging(config.project_id)
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
sync_service.close()
|
|
87
|
+
raise typer.Exit(1)
|
|
88
|
+
|
|
89
|
+
def on_change(path: Path):
|
|
90
|
+
try:
|
|
91
|
+
rel = path.relative_to(directory)
|
|
92
|
+
except ValueError:
|
|
93
|
+
return
|
|
94
|
+
try:
|
|
95
|
+
if sync_service.sync_file_immediate(path):
|
|
96
|
+
if json_mode:
|
|
97
|
+
_emit_json("synced", path=str(rel))
|
|
98
|
+
else:
|
|
99
|
+
ui.console.print(f" [green]SYNCED[/] {rel}")
|
|
100
|
+
except Exception as e:
|
|
101
|
+
err_msg = str(e)
|
|
102
|
+
if "413" in err_msg:
|
|
103
|
+
if json_mode:
|
|
104
|
+
_emit_json("skip", path=str(rel), reason="file too large")
|
|
105
|
+
else:
|
|
106
|
+
ui.console.print(f" [yellow]SKIP[/] {rel} (file too large for API)")
|
|
107
|
+
elif "503" in err_msg or "502" in err_msg:
|
|
108
|
+
if json_mode:
|
|
109
|
+
_emit_json("error", path=str(rel), message="backend unavailable")
|
|
110
|
+
else:
|
|
111
|
+
ui.console.print(f" [red]ERROR[/] {rel} (backend unavailable, will sync on next change)")
|
|
112
|
+
else:
|
|
113
|
+
if json_mode:
|
|
114
|
+
_emit_json("error", path=str(rel), message=err_msg[:120])
|
|
115
|
+
else:
|
|
116
|
+
ui.console.print(f" [red]ERROR[/] {rel}: {err_msg[:120]}")
|
|
117
|
+
|
|
118
|
+
def on_delete(path: Path):
|
|
119
|
+
try:
|
|
120
|
+
rel = path.relative_to(directory)
|
|
121
|
+
except ValueError:
|
|
122
|
+
return
|
|
123
|
+
try:
|
|
124
|
+
if sync_service.delete_file_immediate(path):
|
|
125
|
+
if json_mode:
|
|
126
|
+
_emit_json("deleted", path=str(rel))
|
|
127
|
+
else:
|
|
128
|
+
ui.console.print(f" [red]DELETED[/] {rel}")
|
|
129
|
+
else:
|
|
130
|
+
if json_mode:
|
|
131
|
+
_emit_json("deleted", path=str(rel))
|
|
132
|
+
else:
|
|
133
|
+
ui.console.print(f" [dim]DELETED[/] {rel} (not indexed)")
|
|
134
|
+
except Exception as e:
|
|
135
|
+
if json_mode:
|
|
136
|
+
_emit_json("error", path=str(rel), message=str(e)[:80])
|
|
137
|
+
else:
|
|
138
|
+
ui.console.print(f" [red]DELETED[/] {rel} (error: {str(e)[:80]})")
|
|
139
|
+
|
|
140
|
+
monitor = FileMonitor(
|
|
141
|
+
root_path=directory,
|
|
142
|
+
on_change=on_change,
|
|
143
|
+
on_delete=on_delete,
|
|
144
|
+
should_ignore=should_ignore,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if json_mode:
|
|
148
|
+
_emit_json("ready", message="Watching for changes...")
|
|
149
|
+
else:
|
|
150
|
+
ui.console.print()
|
|
151
|
+
ui.info("Watching for changes... (Ctrl+C to stop)")
|
|
152
|
+
ui.console.print()
|
|
153
|
+
|
|
154
|
+
monitor.start()
|
|
155
|
+
try:
|
|
156
|
+
while monitor.is_alive():
|
|
157
|
+
time.sleep(1)
|
|
158
|
+
except KeyboardInterrupt:
|
|
159
|
+
if not json_mode:
|
|
160
|
+
ui.console.print()
|
|
161
|
+
ui.dim("Stopping...")
|
|
162
|
+
finally:
|
|
163
|
+
monitor.stop()
|
|
164
|
+
sync_service.close()
|
|
165
|
+
|
|
166
|
+
if not json_mode:
|
|
167
|
+
ui.success("Done.")
|
nogic/config.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Configuration management for Nogic CLI."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
|
|
11
|
+
load_dotenv()
|
|
12
|
+
|
|
13
|
+
CONFIG_DIR = ".nogic"
|
|
14
|
+
CONFIG_FILE = "config.json"
|
|
15
|
+
GLOBAL_CONFIG_DIR = Path.home() / ".nogic"
|
|
16
|
+
|
|
17
|
+
_PRODUCTION_URL = "https://api.nogic.dev"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_dev_mode() -> bool:
|
|
21
|
+
"""True when NOGIC_API_URL env var overrides the production URL."""
|
|
22
|
+
return bool(os.getenv("NOGIC_API_URL"))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_api_url() -> str:
|
|
26
|
+
"""Return NOGIC_API_URL if set, otherwise the production URL."""
|
|
27
|
+
return os.getenv("NOGIC_API_URL", _PRODUCTION_URL).strip().rstrip("/")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def dev_mode_banner() -> str | None:
|
|
31
|
+
"""Return a dev mode banner string, or None if in production mode."""
|
|
32
|
+
if is_dev_mode():
|
|
33
|
+
return f"[DEV] Using {get_api_url()}"
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class Config:
|
|
39
|
+
api_key: Optional[str]
|
|
40
|
+
project_id: Optional[str]
|
|
41
|
+
project_name: Optional[str] = None
|
|
42
|
+
directory_hash: Optional[str] = None
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def api_url(self) -> str:
|
|
46
|
+
return get_api_url()
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def load(cls, directory: Path = Path.cwd()) -> "Config":
|
|
50
|
+
"""
|
|
51
|
+
Load config from multiple sources (priority order):
|
|
52
|
+
1. Environment variables
|
|
53
|
+
2. Global config: ~/.nogic/config.json (api_key)
|
|
54
|
+
3. Local config: <directory>/.nogic/config.json (project_id, project_name, directory_hash)
|
|
55
|
+
"""
|
|
56
|
+
api_key = os.getenv("NOGIC_API_KEY")
|
|
57
|
+
project_id = None
|
|
58
|
+
project_name = None
|
|
59
|
+
directory_hash = None
|
|
60
|
+
|
|
61
|
+
global_config_path = GLOBAL_CONFIG_DIR / CONFIG_FILE
|
|
62
|
+
if global_config_path.exists():
|
|
63
|
+
try:
|
|
64
|
+
with open(global_config_path, encoding="utf-8") as f:
|
|
65
|
+
data = json.load(f)
|
|
66
|
+
if not api_key:
|
|
67
|
+
api_key = data.get("api_key")
|
|
68
|
+
except (json.JSONDecodeError, OSError):
|
|
69
|
+
pass # Corrupted config - continue with defaults
|
|
70
|
+
|
|
71
|
+
local_config_path = directory / CONFIG_DIR / CONFIG_FILE
|
|
72
|
+
if local_config_path.exists():
|
|
73
|
+
try:
|
|
74
|
+
with open(local_config_path, encoding="utf-8") as f:
|
|
75
|
+
data = json.load(f)
|
|
76
|
+
project_id = data.get("project_id")
|
|
77
|
+
project_name = data.get("project_name")
|
|
78
|
+
directory_hash = data.get("directory_hash")
|
|
79
|
+
except (json.JSONDecodeError, OSError):
|
|
80
|
+
pass # Corrupted config - continue with defaults
|
|
81
|
+
|
|
82
|
+
if api_key:
|
|
83
|
+
api_key = api_key.strip()
|
|
84
|
+
|
|
85
|
+
return cls(
|
|
86
|
+
api_key=api_key,
|
|
87
|
+
project_id=project_id,
|
|
88
|
+
project_name=project_name,
|
|
89
|
+
directory_hash=directory_hash,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def save_global(self):
|
|
93
|
+
"""Save API key to ~/.nogic/config.json (preserves other settings)."""
|
|
94
|
+
GLOBAL_CONFIG_DIR.mkdir(mode=0o700, exist_ok=True)
|
|
95
|
+
|
|
96
|
+
existing = {}
|
|
97
|
+
config_path = GLOBAL_CONFIG_DIR / CONFIG_FILE
|
|
98
|
+
if config_path.exists():
|
|
99
|
+
try:
|
|
100
|
+
with open(config_path, encoding="utf-8") as f:
|
|
101
|
+
existing = json.load(f)
|
|
102
|
+
except (json.JSONDecodeError, OSError):
|
|
103
|
+
pass # Corrupted config - will be overwritten
|
|
104
|
+
|
|
105
|
+
if self.api_key:
|
|
106
|
+
existing["api_key"] = self.api_key
|
|
107
|
+
|
|
108
|
+
# Never store api_url on disk
|
|
109
|
+
existing.pop("api_url", None)
|
|
110
|
+
|
|
111
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
112
|
+
json.dump(existing, f, indent=2)
|
|
113
|
+
|
|
114
|
+
os.chmod(config_path, 0o600)
|
|
115
|
+
|
|
116
|
+
def save_local(self, directory: Path = Path.cwd()):
|
|
117
|
+
"""Save project config to <directory>/.nogic/config.json."""
|
|
118
|
+
config_dir = directory / CONFIG_DIR
|
|
119
|
+
config_dir.mkdir(mode=0o700, exist_ok=True)
|
|
120
|
+
|
|
121
|
+
data = {}
|
|
122
|
+
if self.project_id:
|
|
123
|
+
data["project_id"] = self.project_id
|
|
124
|
+
if self.project_name:
|
|
125
|
+
data["project_name"] = self.project_name
|
|
126
|
+
if self.directory_hash:
|
|
127
|
+
data["directory_hash"] = self.directory_hash
|
|
128
|
+
|
|
129
|
+
config_path = config_dir / CONFIG_FILE
|
|
130
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
131
|
+
json.dump(data, f, indent=2)
|
|
132
|
+
|
|
133
|
+
os.chmod(config_path, 0o600)
|
|
134
|
+
|
|
135
|
+
def save(self, directory: Path = Path.cwd()):
|
|
136
|
+
"""Save both global and local config."""
|
|
137
|
+
self.save_global()
|
|
138
|
+
self.save_local(directory)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def get_cli_db_path(project_id: str) -> Path:
|
|
142
|
+
"""Get the CLI's sync database path: ~/.nogic/sync/<project_id>/data.db"""
|
|
143
|
+
sync_dir = GLOBAL_CONFIG_DIR / "sync" / project_id
|
|
144
|
+
sync_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
145
|
+
return sync_dir / "data.db"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def get_language(path: Path) -> Optional[str]:
|
|
149
|
+
"""Get language from file extension."""
|
|
150
|
+
ext_map = {
|
|
151
|
+
".py": "python",
|
|
152
|
+
".js": "javascript",
|
|
153
|
+
".jsx": "javascript",
|
|
154
|
+
".ts": "typescript",
|
|
155
|
+
".tsx": "typescript",
|
|
156
|
+
}
|
|
157
|
+
return ext_map.get(path.suffix.lower())
|
nogic/ignore.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Gitignore-aware file ignore logic.
|
|
2
|
+
|
|
3
|
+
Consolidates ignore pattern handling used across sync, watch, and reindex commands.
|
|
4
|
+
Uses pathspec for full .gitignore spec support (gitwildmatch).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Callable
|
|
9
|
+
|
|
10
|
+
import pathspec
|
|
11
|
+
|
|
12
|
+
DEFAULT_IGNORE_PATTERNS = [
|
|
13
|
+
".git/",
|
|
14
|
+
"__pycache__/",
|
|
15
|
+
"*.pyc",
|
|
16
|
+
".venv/",
|
|
17
|
+
"venv/",
|
|
18
|
+
"env/",
|
|
19
|
+
"node_modules/",
|
|
20
|
+
".nogic/",
|
|
21
|
+
".DS_Store",
|
|
22
|
+
"*.swp",
|
|
23
|
+
"*.swo",
|
|
24
|
+
"*.tmp.*",
|
|
25
|
+
"*~",
|
|
26
|
+
# Build output directories
|
|
27
|
+
"dist/",
|
|
28
|
+
"build/",
|
|
29
|
+
"out/",
|
|
30
|
+
".next/",
|
|
31
|
+
".nuxt/",
|
|
32
|
+
".output/",
|
|
33
|
+
# Coverage and test output
|
|
34
|
+
"coverage/",
|
|
35
|
+
".coverage",
|
|
36
|
+
"htmlcov/",
|
|
37
|
+
# IDE and editor files
|
|
38
|
+
".idea/",
|
|
39
|
+
".vscode/",
|
|
40
|
+
# Bundled/minified files
|
|
41
|
+
"*.min.js",
|
|
42
|
+
"*.min.css",
|
|
43
|
+
"*.bundle.js",
|
|
44
|
+
"*.chunk.js",
|
|
45
|
+
# Lock files and binary artifacts
|
|
46
|
+
"*.lock",
|
|
47
|
+
"package-lock.json",
|
|
48
|
+
"yarn.lock",
|
|
49
|
+
"pnpm-lock.yaml",
|
|
50
|
+
"*.map",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def load_gitignore_patterns(root: Path) -> list[str]:
|
|
55
|
+
"""Read .gitignore from root and return pattern lines.
|
|
56
|
+
|
|
57
|
+
Skips blank lines and comments. Returns empty list if no .gitignore exists.
|
|
58
|
+
"""
|
|
59
|
+
gitignore_path = root / ".gitignore"
|
|
60
|
+
if not gitignore_path.is_file():
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
text = gitignore_path.read_text(encoding="utf-8")
|
|
65
|
+
except (OSError, UnicodeDecodeError):
|
|
66
|
+
return []
|
|
67
|
+
|
|
68
|
+
patterns = []
|
|
69
|
+
for line in text.splitlines():
|
|
70
|
+
stripped = line.strip()
|
|
71
|
+
if stripped and not stripped.startswith("#"):
|
|
72
|
+
patterns.append(stripped)
|
|
73
|
+
return patterns
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def build_ignore_matcher(
|
|
77
|
+
root: Path,
|
|
78
|
+
extra_patterns: tuple[str, ...] | list[str] = (),
|
|
79
|
+
) -> Callable[[Path], bool]:
|
|
80
|
+
"""Build an ignore checker combining defaults + .gitignore + extra patterns.
|
|
81
|
+
|
|
82
|
+
Returns a callable that takes an absolute Path and returns True if it should
|
|
83
|
+
be ignored. Uses pathspec with gitwildmatch for full .gitignore compatibility.
|
|
84
|
+
"""
|
|
85
|
+
root = root.resolve()
|
|
86
|
+
|
|
87
|
+
all_patterns = (
|
|
88
|
+
list(DEFAULT_IGNORE_PATTERNS)
|
|
89
|
+
+ load_gitignore_patterns(root)
|
|
90
|
+
+ list(extra_patterns)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
spec = pathspec.PathSpec.from_lines("gitignore", all_patterns)
|
|
94
|
+
|
|
95
|
+
def should_ignore(path: Path) -> bool:
|
|
96
|
+
try:
|
|
97
|
+
rel_path = path.relative_to(root)
|
|
98
|
+
except ValueError:
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
rel_str = str(rel_path)
|
|
102
|
+
# pathspec needs forward slashes; on Windows Path uses backslashes
|
|
103
|
+
rel_str = rel_str.replace("\\", "/")
|
|
104
|
+
# Append trailing slash for directories so dir patterns match
|
|
105
|
+
if path.is_dir():
|
|
106
|
+
rel_str += "/"
|
|
107
|
+
return spec.match_file(rel_str)
|
|
108
|
+
|
|
109
|
+
return should_ignore
|
nogic/main.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Main CLI entry point."""
|
|
2
|
+
|
|
3
|
+
import atexit
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from nogic import __version__
|
|
8
|
+
from nogic import telemetry
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(
|
|
11
|
+
name="nogic",
|
|
12
|
+
help="Nogic CLI - Code Intelligence for AI Agents.",
|
|
13
|
+
no_args_is_help=True,
|
|
14
|
+
rich_markup_mode="rich",
|
|
15
|
+
pretty_exceptions_enable=False,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def version_callback(value: bool):
|
|
20
|
+
if value:
|
|
21
|
+
typer.echo(f"nogic {__version__}")
|
|
22
|
+
raise typer.Exit()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@app.callback()
|
|
26
|
+
def main(
|
|
27
|
+
version: bool = typer.Option(None, "--version", "-V", callback=version_callback, is_eager=True, help="Show version."),
|
|
28
|
+
):
|
|
29
|
+
"""Nogic CLI - Code Intelligence for AI Agents."""
|
|
30
|
+
atexit.register(telemetry.shutdown)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Import and register commands
|
|
34
|
+
from nogic.commands.login import login
|
|
35
|
+
from nogic.commands.init import init
|
|
36
|
+
from nogic.commands.watch import watch
|
|
37
|
+
from nogic.commands.sync import sync
|
|
38
|
+
from nogic.commands.reindex import reindex
|
|
39
|
+
from nogic.commands.status import status
|
|
40
|
+
from nogic.commands.projects import projects_app
|
|
41
|
+
from nogic.commands.telemetry_cmd import telemetry_app
|
|
42
|
+
|
|
43
|
+
app.command()(login)
|
|
44
|
+
app.command()(init)
|
|
45
|
+
app.command()(watch)
|
|
46
|
+
app.command()(sync)
|
|
47
|
+
app.command()(reindex)
|
|
48
|
+
app.command()(status)
|
|
49
|
+
app.add_typer(projects_app, name="projects", help="Manage Nogic projects.")
|
|
50
|
+
app.add_typer(telemetry_app, name="telemetry", help="Manage telemetry settings.")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def entry():
|
|
54
|
+
app()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
entry()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parsing module for extracting code intelligence from source files.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .types import (
|
|
6
|
+
ExtractedFunction,
|
|
7
|
+
ExtractedClass,
|
|
8
|
+
ExtractedCall,
|
|
9
|
+
ExtractedImport,
|
|
10
|
+
ParseResult,
|
|
11
|
+
)
|
|
12
|
+
from .parser import ParserService, parser_service
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"ExtractedFunction",
|
|
16
|
+
"ExtractedClass",
|
|
17
|
+
"ExtractedCall",
|
|
18
|
+
"ExtractedImport",
|
|
19
|
+
"ParseResult",
|
|
20
|
+
"ParserService",
|
|
21
|
+
"parser_service",
|
|
22
|
+
]
|