codevira 1.6.0__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.
- codevira-1.6.0.dist-info/LICENSE +21 -0
- codevira-1.6.0.dist-info/METADATA +477 -0
- codevira-1.6.0.dist-info/RECORD +58 -0
- codevira-1.6.0.dist-info/WHEEL +5 -0
- codevira-1.6.0.dist-info/entry_points.txt +2 -0
- codevira-1.6.0.dist-info/top_level.txt +2 -0
- indexer/__init__.py +1 -0
- indexer/chunker.py +428 -0
- indexer/global_db.py +197 -0
- indexer/graph_generator.py +380 -0
- indexer/index_codebase.py +588 -0
- indexer/outcome_tracker.py +172 -0
- indexer/rule_learner.py +186 -0
- indexer/sqlite_graph.py +640 -0
- indexer/treesitter_parser.py +423 -0
- mcp_server/__init__.py +1 -0
- mcp_server/__main__.py +20 -0
- mcp_server/auto_init.py +257 -0
- mcp_server/cli.py +622 -0
- mcp_server/crash_logger.py +236 -0
- mcp_server/data/__init__.py +1 -0
- mcp_server/data/agents/builder.md +84 -0
- mcp_server/data/agents/developer.md +111 -0
- mcp_server/data/agents/documenter.md +138 -0
- mcp_server/data/agents/orchestrator.md +96 -0
- mcp_server/data/agents/planner.md +106 -0
- mcp_server/data/agents/reviewer.md +82 -0
- mcp_server/data/agents/tester.md +83 -0
- mcp_server/data/config.example.yaml +33 -0
- mcp_server/data/rules/coding-standards.md +48 -0
- mcp_server/data/rules/engineering-excellence.md +28 -0
- mcp_server/data/rules/git-cicd-governance.md +32 -0
- mcp_server/data/rules/git_commits.md +130 -0
- mcp_server/data/rules/incremental-updates.md +5 -0
- mcp_server/data/rules/master_rule.md +187 -0
- mcp_server/data/rules/multi-language.md +19 -0
- mcp_server/data/rules/persistence.md +21 -0
- mcp_server/data/rules/resilience-observability.md +17 -0
- mcp_server/data/rules/smoke-testing.md +48 -0
- mcp_server/data/rules/testing-standards.md +23 -0
- mcp_server/detect.py +284 -0
- mcp_server/gitignore.py +284 -0
- mcp_server/global_sync.py +187 -0
- mcp_server/http_server.py +341 -0
- mcp_server/ide_inject.py +444 -0
- mcp_server/launchd.py +156 -0
- mcp_server/migrate.py +215 -0
- mcp_server/paths.py +256 -0
- mcp_server/prompts.py +136 -0
- mcp_server/server.py +1049 -0
- mcp_server/tools/__init__.py +0 -0
- mcp_server/tools/changesets.py +223 -0
- mcp_server/tools/code_reader.py +335 -0
- mcp_server/tools/graph.py +637 -0
- mcp_server/tools/learning.py +238 -0
- mcp_server/tools/playbook.py +89 -0
- mcp_server/tools/roadmap.py +599 -0
- mcp_server/tools/search.py +145 -0
mcp_server/cli.py
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cli.py — Entry point for the `codevira` command.
|
|
3
|
+
|
|
4
|
+
Dispatches subcommands:
|
|
5
|
+
codevira → start MCP server (default)
|
|
6
|
+
codevira init → initialize project in centralized storage
|
|
7
|
+
codevira register → one-time global IDE registration (v1.6)
|
|
8
|
+
codevira index → run incremental index update
|
|
9
|
+
codevira index --full → full index rebuild
|
|
10
|
+
codevira status → show index health and stats
|
|
11
|
+
codevira report → show recent crash logs
|
|
12
|
+
codevira report --clear → clear the crash log
|
|
13
|
+
codevira serve → start MCP HTTP server
|
|
14
|
+
codevira serve --install-service → install macOS launchd auto-start
|
|
15
|
+
codevira serve --uninstall-service → remove macOS launchd service
|
|
16
|
+
|
|
17
|
+
Global flags:
|
|
18
|
+
--project-dir <path> → override project directory (for Google Antigravity,
|
|
19
|
+
which doesn't support `cwd` in its MCP config)
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _set_project_dir_early(args: list[str]) -> Path | None:
|
|
29
|
+
"""
|
|
30
|
+
Parse --project-dir before any subcommand handling so that
|
|
31
|
+
paths.set_project_dir() is called before any module-level path resolution.
|
|
32
|
+
"""
|
|
33
|
+
for i, arg in enumerate(args):
|
|
34
|
+
if arg == "--project-dir" and i + 1 < len(args):
|
|
35
|
+
return Path(args[i + 1]).resolve()
|
|
36
|
+
if arg.startswith("--project-dir="):
|
|
37
|
+
return Path(arg.split("=", 1)[1]).resolve()
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _detect_project_root_markers(path: Path) -> bool:
|
|
42
|
+
"""Return True if the given path looks like a project root."""
|
|
43
|
+
markers = [
|
|
44
|
+
".git", "pyproject.toml", "setup.py", "setup.cfg",
|
|
45
|
+
"package.json", "Cargo.toml", "go.mod", "Makefile",
|
|
46
|
+
"pom.xml", "build.gradle",
|
|
47
|
+
]
|
|
48
|
+
return any((path / m).exists() for m in markers)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def cmd_init() -> None:
|
|
52
|
+
"""Initialize Codevira in the current project."""
|
|
53
|
+
from mcp_server.paths import get_project_root, get_data_dir, get_package_data_dir
|
|
54
|
+
import shutil
|
|
55
|
+
import yaml
|
|
56
|
+
|
|
57
|
+
cwd = get_project_root()
|
|
58
|
+
data_dir = get_data_dir()
|
|
59
|
+
|
|
60
|
+
print()
|
|
61
|
+
print(" Codevira — Project Initialization")
|
|
62
|
+
print(" " + "─" * 40)
|
|
63
|
+
print()
|
|
64
|
+
|
|
65
|
+
# Step 1: Validate project root
|
|
66
|
+
if not _detect_project_root_markers(cwd):
|
|
67
|
+
parent = cwd.parent
|
|
68
|
+
if _detect_project_root_markers(parent):
|
|
69
|
+
print(f" Warning: It looks like you may be in a subdirectory.")
|
|
70
|
+
print(f" Project markers found in: {parent}")
|
|
71
|
+
print(f" Current directory: {cwd}")
|
|
72
|
+
print()
|
|
73
|
+
answer = input(" Continue initializing here anyway? [y/N] ").strip().lower()
|
|
74
|
+
if answer != "y":
|
|
75
|
+
print(" Aborted. Run `codevira init` from your project root.")
|
|
76
|
+
sys.exit(0)
|
|
77
|
+
print()
|
|
78
|
+
|
|
79
|
+
# Step 2a: Auto-migrate legacy .codevira/ if present
|
|
80
|
+
git_dir = cwd / ".git"
|
|
81
|
+
from mcp_server.migrate import detect_migration_needed, migrate_to_centralized
|
|
82
|
+
if detect_migration_needed(cwd):
|
|
83
|
+
print(f" Migrating legacy .codevira/ to centralized storage ...", end="", flush=True)
|
|
84
|
+
try:
|
|
85
|
+
result = migrate_to_centralized(cwd)
|
|
86
|
+
if result.get("migrated"):
|
|
87
|
+
print(f" done ({result.get('files_copied', 0)} files → {result.get('new_path', '')})")
|
|
88
|
+
# Re-evaluate data_dir after migration — now points to centralized path
|
|
89
|
+
data_dir = get_data_dir()
|
|
90
|
+
else:
|
|
91
|
+
print(f" skipped ({result.get('reason', '')})")
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print(f" failed ({e})")
|
|
94
|
+
|
|
95
|
+
# Step 2b: Create centralized directory structure
|
|
96
|
+
is_centralized = str(data_dir).startswith(str(Path.home() / ".codevira" / "projects"))
|
|
97
|
+
if is_centralized:
|
|
98
|
+
print(f" Creating centralized data dir ...")
|
|
99
|
+
print(f" {data_dir}")
|
|
100
|
+
else:
|
|
101
|
+
print(f" Creating .codevira/ in {cwd} ...")
|
|
102
|
+
for subdir in ["graph/changesets", "codeindex", "logs"]:
|
|
103
|
+
(data_dir / subdir).mkdir(parents=True, exist_ok=True)
|
|
104
|
+
print(f" Data directory ready ... done")
|
|
105
|
+
|
|
106
|
+
# Step 3: For new centralized projects, no .gitignore entry needed.
|
|
107
|
+
# For legacy mode (in-project), add .codevira/ to .gitignore.
|
|
108
|
+
if not is_centralized and git_dir.exists():
|
|
109
|
+
gitignore = cwd / ".gitignore"
|
|
110
|
+
entry = ".codevira/"
|
|
111
|
+
needs_add = True
|
|
112
|
+
if gitignore.exists():
|
|
113
|
+
content = gitignore.read_text()
|
|
114
|
+
if ".codevira" in content:
|
|
115
|
+
needs_add = False
|
|
116
|
+
if needs_add:
|
|
117
|
+
print(f" Adding .codevira/ to .gitignore ... ", end="", flush=True)
|
|
118
|
+
with open(gitignore, "a") as f:
|
|
119
|
+
if gitignore.exists() and gitignore.stat().st_size > 0:
|
|
120
|
+
existing = gitignore.read_text()
|
|
121
|
+
if not existing.endswith("\n"):
|
|
122
|
+
f.write("\n")
|
|
123
|
+
f.write(f"\n# Codevira — auto-generated, do not commit\n{entry}\n")
|
|
124
|
+
print("done")
|
|
125
|
+
|
|
126
|
+
# Step 4: Zero-config auto-detection (no interactive prompts)
|
|
127
|
+
print()
|
|
128
|
+
from mcp_server.detect import auto_detect_project
|
|
129
|
+
detected = auto_detect_project(cwd)
|
|
130
|
+
|
|
131
|
+
# Apply CLI overrides if provided (parsed from args later)
|
|
132
|
+
if hasattr(cmd_init, '_overrides'):
|
|
133
|
+
overrides = cmd_init._overrides
|
|
134
|
+
if overrides.get("name"): detected["name"] = overrides["name"]
|
|
135
|
+
if overrides.get("language"): detected["language"] = overrides["language"]
|
|
136
|
+
if overrides.get("dirs"): detected["watched_dirs"] = [d.strip() for d in overrides["dirs"].split(",")]
|
|
137
|
+
if overrides.get("ext"): detected["file_extensions"] = [e.strip() for e in overrides["ext"].split(",")]
|
|
138
|
+
|
|
139
|
+
print(f" Auto-detected:")
|
|
140
|
+
print(f" Project: {detected['name']}")
|
|
141
|
+
print(f" Language: {detected['language']}")
|
|
142
|
+
print(f" Source dirs: {', '.join(detected['watched_dirs'])}")
|
|
143
|
+
print(f" Extensions: {', '.join(detected['file_extensions'])}")
|
|
144
|
+
|
|
145
|
+
# Write config.yaml
|
|
146
|
+
config = {
|
|
147
|
+
"project": {
|
|
148
|
+
"name": detected["name"],
|
|
149
|
+
"language": detected["language"],
|
|
150
|
+
"collection_name": detected["collection_name"],
|
|
151
|
+
"watched_dirs": detected["watched_dirs"],
|
|
152
|
+
"file_extensions": detected["file_extensions"],
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# Try to copy example config as base, then merge project settings
|
|
157
|
+
pkg_config_example = get_package_data_dir() / "config.example.yaml"
|
|
158
|
+
config_path = data_dir / "config.yaml"
|
|
159
|
+
if pkg_config_example.exists():
|
|
160
|
+
shutil.copy(pkg_config_example, config_path)
|
|
161
|
+
# Merge project section on top
|
|
162
|
+
with open(config_path) as f:
|
|
163
|
+
base = yaml.safe_load(f) or {}
|
|
164
|
+
base.update(config)
|
|
165
|
+
with open(config_path, "w") as f:
|
|
166
|
+
yaml.dump(base, f, default_flow_style=False, sort_keys=False)
|
|
167
|
+
else:
|
|
168
|
+
with open(config_path, "w") as f:
|
|
169
|
+
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|
|
170
|
+
|
|
171
|
+
print()
|
|
172
|
+
|
|
173
|
+
# Step 5: Run full index build — let rich progress bars render directly.
|
|
174
|
+
# Suppress noisy HuggingFace/transformers output via env vars.
|
|
175
|
+
import os as _os
|
|
176
|
+
import contextlib, io
|
|
177
|
+
print(" Building code index ...")
|
|
178
|
+
try:
|
|
179
|
+
from indexer.index_codebase import cmd_full_rebuild
|
|
180
|
+
_os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error")
|
|
181
|
+
_os.environ.setdefault("HF_HUB_VERBOSITY", "error")
|
|
182
|
+
# Suppress stderr noise (BertModel LOAD REPORT, HF_TOKEN warnings)
|
|
183
|
+
with contextlib.redirect_stderr(io.StringIO()):
|
|
184
|
+
cmd_full_rebuild()
|
|
185
|
+
except Exception as e:
|
|
186
|
+
print(f" skipped ({e})")
|
|
187
|
+
try:
|
|
188
|
+
from mcp_server.crash_logger import log_crash
|
|
189
|
+
log_crash(e, context="codevira init: index build", project_path=str(cwd))
|
|
190
|
+
except Exception: pass
|
|
191
|
+
|
|
192
|
+
# Step 6: Generate graph stubs
|
|
193
|
+
print(" Generating graph stubs ... ", end="", flush=True)
|
|
194
|
+
try:
|
|
195
|
+
from indexer.index_codebase import cmd_generate_graph
|
|
196
|
+
buf = io.StringIO()
|
|
197
|
+
with contextlib.redirect_stdout(buf):
|
|
198
|
+
cmd_generate_graph()
|
|
199
|
+
output = buf.getvalue()
|
|
200
|
+
nodes = "0"
|
|
201
|
+
for line in output.splitlines():
|
|
202
|
+
if "Nodes added:" in line:
|
|
203
|
+
nodes = line.split(":")[-1].strip()
|
|
204
|
+
break
|
|
205
|
+
print(f"done ({nodes} nodes)")
|
|
206
|
+
except Exception as e:
|
|
207
|
+
print(f"skipped ({e})")
|
|
208
|
+
try:
|
|
209
|
+
from mcp_server.crash_logger import log_crash
|
|
210
|
+
log_crash(e, context="codevira init: graph stubs", project_path=str(cwd))
|
|
211
|
+
except Exception: pass
|
|
212
|
+
|
|
213
|
+
# Step 7: Bootstrap roadmap
|
|
214
|
+
print(" Bootstrapping roadmap ... ", end="", flush=True)
|
|
215
|
+
try:
|
|
216
|
+
from indexer.index_codebase import cmd_bootstrap_roadmap
|
|
217
|
+
import io
|
|
218
|
+
import contextlib
|
|
219
|
+
|
|
220
|
+
buf = io.StringIO()
|
|
221
|
+
with contextlib.redirect_stdout(buf):
|
|
222
|
+
cmd_bootstrap_roadmap()
|
|
223
|
+
print("done")
|
|
224
|
+
except Exception as e:
|
|
225
|
+
print(f"skipped ({e})")
|
|
226
|
+
try:
|
|
227
|
+
from mcp_server.crash_logger import log_crash
|
|
228
|
+
log_crash(e, context="codevira init: roadmap bootstrap", project_path=str(cwd))
|
|
229
|
+
except Exception: pass
|
|
230
|
+
|
|
231
|
+
# Step 8: Install git hook
|
|
232
|
+
if git_dir.exists():
|
|
233
|
+
print(" Installing git hook ... ", end="", flush=True)
|
|
234
|
+
try:
|
|
235
|
+
hooks_dir = git_dir / "hooks"
|
|
236
|
+
hooks_dir.mkdir(exist_ok=True)
|
|
237
|
+
hook_path = hooks_dir / "post-commit"
|
|
238
|
+
|
|
239
|
+
# Find codevira executable path
|
|
240
|
+
import shutil as _shutil
|
|
241
|
+
cmd_path = _shutil.which("codevira") or "codevira"
|
|
242
|
+
|
|
243
|
+
hook_content = (
|
|
244
|
+
"#!/bin/sh\n"
|
|
245
|
+
"# Codevira post-commit hook — auto-reindex changed files\n"
|
|
246
|
+
f'"{cmd_path}" index --quiet 2>/dev/null || true\n'
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Backup existing hook if it exists and is not ours
|
|
250
|
+
if hook_path.exists():
|
|
251
|
+
existing = hook_path.read_text()
|
|
252
|
+
if "codevira" not in existing.lower():
|
|
253
|
+
hook_path.rename(hook_path.with_suffix(".bak"))
|
|
254
|
+
|
|
255
|
+
hook_path.write_text(hook_content)
|
|
256
|
+
hook_path.chmod(0o755)
|
|
257
|
+
print("done")
|
|
258
|
+
except Exception as e:
|
|
259
|
+
print(f"skipped ({e})")
|
|
260
|
+
try:
|
|
261
|
+
from mcp_server.crash_logger import log_crash
|
|
262
|
+
log_crash(e, context="codevira init: git hook", project_path=str(cwd))
|
|
263
|
+
except Exception: pass
|
|
264
|
+
|
|
265
|
+
# Step 9: Auto-inject IDE configurations
|
|
266
|
+
print()
|
|
267
|
+
print(" " + "─" * 60)
|
|
268
|
+
print(f" ✓ Codevira initialized in {data_dir}")
|
|
269
|
+
print()
|
|
270
|
+
|
|
271
|
+
no_inject = getattr(cmd_init, '_no_inject', False)
|
|
272
|
+
if not no_inject:
|
|
273
|
+
print(" Configuring AI tools ... ", end="", flush=True)
|
|
274
|
+
try:
|
|
275
|
+
from mcp_server.ide_inject import inject_ide_config
|
|
276
|
+
results = inject_ide_config(cwd, project_name=detected["name"])
|
|
277
|
+
if results:
|
|
278
|
+
print("done")
|
|
279
|
+
for ide_name, config_path in results.items():
|
|
280
|
+
print(f" ✓ {ide_name}: {config_path}")
|
|
281
|
+
else:
|
|
282
|
+
print("no AI tools detected")
|
|
283
|
+
except Exception as e:
|
|
284
|
+
print(f"skipped ({e})")
|
|
285
|
+
try:
|
|
286
|
+
from mcp_server.crash_logger import log_crash
|
|
287
|
+
log_crash(e, context="codevira init: IDE inject", project_path=str(cwd))
|
|
288
|
+
except Exception: pass
|
|
289
|
+
|
|
290
|
+
# Step 10: Register in global memory (with git_remote for rename-resilient lookup)
|
|
291
|
+
try:
|
|
292
|
+
from mcp_server.global_sync import import_global_to_project
|
|
293
|
+
from mcp_server.paths import get_global_db_path, _get_git_remote_url
|
|
294
|
+
from indexer.global_db import GlobalDB
|
|
295
|
+
from mcp_server.auto_init import _write_metadata
|
|
296
|
+
|
|
297
|
+
git_remote = _get_git_remote_url(cwd)
|
|
298
|
+
gdb = GlobalDB(get_global_db_path())
|
|
299
|
+
gdb.register_project(str(data_dir), detected["name"], detected["language"], git_remote=git_remote)
|
|
300
|
+
proj_count = gdb.get_project_count()
|
|
301
|
+
gdb.close()
|
|
302
|
+
if proj_count > 1:
|
|
303
|
+
print(f" Registered in global memory ({proj_count} projects)")
|
|
304
|
+
|
|
305
|
+
# Write metadata.json for centralized storage marker
|
|
306
|
+
_write_metadata(data_dir, cwd)
|
|
307
|
+
except Exception as e:
|
|
308
|
+
print(f" Global memory registration skipped ({e})")
|
|
309
|
+
try:
|
|
310
|
+
from mcp_server.crash_logger import log_crash
|
|
311
|
+
log_crash(e, context="codevira init: global memory register", project_path=str(cwd))
|
|
312
|
+
except Exception: pass
|
|
313
|
+
|
|
314
|
+
# Print fallback for undetected tools
|
|
315
|
+
import shutil as _shutil
|
|
316
|
+
import sys as _sys
|
|
317
|
+
python_exe = _sys.executable
|
|
318
|
+
project_path = str(cwd)
|
|
319
|
+
|
|
320
|
+
print()
|
|
321
|
+
print(" For other AI tools, add this to their MCP config:")
|
|
322
|
+
print()
|
|
323
|
+
print(' {')
|
|
324
|
+
print(' "mcpServers": {')
|
|
325
|
+
print(' "codevira": {')
|
|
326
|
+
print(f' "command": "{python_exe}",')
|
|
327
|
+
print(f' "args": ["-m", "mcp_server", "--project-dir", "{project_path}"]')
|
|
328
|
+
print(' }')
|
|
329
|
+
print(' }')
|
|
330
|
+
print(' }')
|
|
331
|
+
print()
|
|
332
|
+
print(" Verify: ask your agent to call get_roadmap()")
|
|
333
|
+
print()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def cmd_index(full: bool = False, quiet: bool = False) -> None:
|
|
337
|
+
"""Run the indexer (incremental by default, or --full for complete rebuild)."""
|
|
338
|
+
from indexer.index_codebase import cmd_full_rebuild, cmd_incremental
|
|
339
|
+
|
|
340
|
+
if full:
|
|
341
|
+
cmd_full_rebuild()
|
|
342
|
+
else:
|
|
343
|
+
cmd_incremental(quiet=quiet)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def cmd_status() -> None:
|
|
347
|
+
"""Show index health and statistics."""
|
|
348
|
+
from indexer.index_codebase import cmd_status as _cmd_status
|
|
349
|
+
_cmd_status()
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def cmd_report(limit: int = 20, clear: bool = False) -> None:
|
|
353
|
+
"""Show recent crash logs."""
|
|
354
|
+
from mcp_server.crash_logger import read_recent_crashes, get_crash_log_path
|
|
355
|
+
|
|
356
|
+
if clear:
|
|
357
|
+
log_path = get_crash_log_path()
|
|
358
|
+
if log_path.exists():
|
|
359
|
+
log_path.unlink()
|
|
360
|
+
print(" Crash log cleared.")
|
|
361
|
+
else:
|
|
362
|
+
print(" No crash log to clear.")
|
|
363
|
+
return
|
|
364
|
+
|
|
365
|
+
print()
|
|
366
|
+
print(" Codevira — Crash Report")
|
|
367
|
+
print(" " + "-" * 40)
|
|
368
|
+
print()
|
|
369
|
+
print(read_recent_crashes(limit=limit))
|
|
370
|
+
print()
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def cmd_server(project_dir: Path | None = None) -> None:
|
|
374
|
+
"""Start the MCP server (stdio transport)."""
|
|
375
|
+
from mcp_server.server import main as server_main
|
|
376
|
+
server_main()
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def cmd_serve(
|
|
380
|
+
host: str = "127.0.0.1",
|
|
381
|
+
port: int = 7007,
|
|
382
|
+
use_https: bool = False,
|
|
383
|
+
project_dir: Path | None = None,
|
|
384
|
+
install_service: bool = False,
|
|
385
|
+
uninstall_service: bool = False,
|
|
386
|
+
) -> None:
|
|
387
|
+
"""Start the MCP HTTP server (Streamable HTTP transport)."""
|
|
388
|
+
if install_service:
|
|
389
|
+
from mcp_server.launchd import install_launchd
|
|
390
|
+
try:
|
|
391
|
+
plist = install_launchd(port=port, use_https=use_https, host=host, project_dir=project_dir)
|
|
392
|
+
print(f" Launchd service installed: {plist}")
|
|
393
|
+
print(" Codevira MCP server will start automatically on login.")
|
|
394
|
+
except RuntimeError as e:
|
|
395
|
+
print(f" Error: {e}")
|
|
396
|
+
sys.exit(1)
|
|
397
|
+
return
|
|
398
|
+
|
|
399
|
+
if uninstall_service:
|
|
400
|
+
from mcp_server.launchd import uninstall_launchd
|
|
401
|
+
try:
|
|
402
|
+
removed = uninstall_launchd()
|
|
403
|
+
if removed:
|
|
404
|
+
print(" Launchd service removed.")
|
|
405
|
+
else:
|
|
406
|
+
print(" No launchd service was installed.")
|
|
407
|
+
except RuntimeError as e:
|
|
408
|
+
print(f" Error: {e}")
|
|
409
|
+
sys.exit(1)
|
|
410
|
+
return
|
|
411
|
+
|
|
412
|
+
from mcp_server.http_server import run_http_server
|
|
413
|
+
run_http_server(host=host, port=port, use_https=use_https, project_dir=project_dir)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def cmd_register(
|
|
417
|
+
global_mode: bool = True,
|
|
418
|
+
claude_desktop: bool = False,
|
|
419
|
+
http_url: str | None = None,
|
|
420
|
+
) -> None:
|
|
421
|
+
"""One-time global IDE registration (v1.6).
|
|
422
|
+
|
|
423
|
+
Injects codevira into all detected AI tools' global configs so that
|
|
424
|
+
every project the developer opens automatically has Codevira memory.
|
|
425
|
+
"""
|
|
426
|
+
from mcp_server.paths import get_project_root
|
|
427
|
+
from mcp_server.ide_inject import (
|
|
428
|
+
_resolve_command, detect_installed_ides,
|
|
429
|
+
inject_global_claude_code, inject_global_cursor, inject_global_windsurf,
|
|
430
|
+
_inject_claude_desktop, inject_claude_http_url,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
project_root = get_project_root()
|
|
434
|
+
cmd_path, python_exe = _resolve_command()
|
|
435
|
+
|
|
436
|
+
print()
|
|
437
|
+
print(" Codevira — Global IDE Registration (v1.6)")
|
|
438
|
+
print(" " + "─" * 44)
|
|
439
|
+
print()
|
|
440
|
+
|
|
441
|
+
if http_url:
|
|
442
|
+
path = inject_claude_http_url(http_url)
|
|
443
|
+
print(f" ✓ Claude Code (HTTP URL): {path}")
|
|
444
|
+
print()
|
|
445
|
+
return
|
|
446
|
+
|
|
447
|
+
if claude_desktop:
|
|
448
|
+
path = _inject_claude_desktop(project_root, cmd_path, python_exe)
|
|
449
|
+
print(f" ✓ Claude Desktop: {path}")
|
|
450
|
+
print(" Note: Claude Desktop uses stdio — restart it to pick up changes.")
|
|
451
|
+
print()
|
|
452
|
+
return
|
|
453
|
+
|
|
454
|
+
# Global mode: inject into all detected IDEs
|
|
455
|
+
ides = detect_installed_ides(project_root)
|
|
456
|
+
results: dict[str, str] = {}
|
|
457
|
+
|
|
458
|
+
for ide in ides:
|
|
459
|
+
try:
|
|
460
|
+
if ide == "claude":
|
|
461
|
+
path = inject_global_claude_code(cmd_path, python_exe)
|
|
462
|
+
if path:
|
|
463
|
+
results["Claude Code (global)"] = path
|
|
464
|
+
elif ide == "cursor":
|
|
465
|
+
path = inject_global_cursor(cmd_path, python_exe)
|
|
466
|
+
if path:
|
|
467
|
+
results["Cursor (global)"] = path
|
|
468
|
+
elif ide == "windsurf":
|
|
469
|
+
path = inject_global_windsurf(cmd_path, python_exe)
|
|
470
|
+
if path:
|
|
471
|
+
results["Windsurf (global)"] = path
|
|
472
|
+
elif ide == "claude_desktop":
|
|
473
|
+
path = _inject_claude_desktop(project_root, cmd_path, python_exe)
|
|
474
|
+
if path:
|
|
475
|
+
results["Claude Desktop"] = path
|
|
476
|
+
except Exception as e:
|
|
477
|
+
print(f" Warning: could not configure {ide}: {e}")
|
|
478
|
+
|
|
479
|
+
if results:
|
|
480
|
+
for ide_name, config_path in results.items():
|
|
481
|
+
print(f" ✓ {ide_name}: {config_path}")
|
|
482
|
+
print()
|
|
483
|
+
print(" Restart your AI tools to pick up the new configuration.")
|
|
484
|
+
print(" Every project you open will now have Codevira memory automatically.")
|
|
485
|
+
else:
|
|
486
|
+
print(" No AI tools detected. Install Claude Code, Cursor, or Windsurf first.")
|
|
487
|
+
print()
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def main() -> None:
|
|
491
|
+
# Pre-parse --project-dir before argparse so we can initialize paths early.
|
|
492
|
+
raw_args = sys.argv[1:]
|
|
493
|
+
project_dir = _set_project_dir_early(raw_args)
|
|
494
|
+
|
|
495
|
+
if project_dir is not None:
|
|
496
|
+
from mcp_server.paths import set_project_dir
|
|
497
|
+
set_project_dir(project_dir)
|
|
498
|
+
|
|
499
|
+
parser = argparse.ArgumentParser(
|
|
500
|
+
prog="codevira",
|
|
501
|
+
description="Codevira — AI context layer for your codebase",
|
|
502
|
+
)
|
|
503
|
+
parser.add_argument(
|
|
504
|
+
"--project-dir",
|
|
505
|
+
metavar="PATH",
|
|
506
|
+
help="Project directory (alternative to cwd; useful for Google Antigravity)",
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
510
|
+
|
|
511
|
+
# init
|
|
512
|
+
init_parser = subparsers.add_parser("init", help="Initialize Codevira in the current project")
|
|
513
|
+
init_parser.add_argument("--name", help="Override project name")
|
|
514
|
+
init_parser.add_argument("--language", help="Override detected language")
|
|
515
|
+
init_parser.add_argument("--dirs", help="Override source directories (comma-separated)")
|
|
516
|
+
init_parser.add_argument("--ext", help="Override file extensions (comma-separated)")
|
|
517
|
+
init_parser.add_argument("--no-inject", action="store_true", help="Skip auto-injecting IDE configs")
|
|
518
|
+
|
|
519
|
+
# index
|
|
520
|
+
index_parser = subparsers.add_parser("index", help="Run the code indexer")
|
|
521
|
+
index_parser.add_argument("--full", action="store_true", help="Full rebuild from scratch")
|
|
522
|
+
index_parser.add_argument("--quiet", action="store_true", help="Suppress output (used by git hook)")
|
|
523
|
+
|
|
524
|
+
# status
|
|
525
|
+
subparsers.add_parser("status", help="Show index health and statistics")
|
|
526
|
+
|
|
527
|
+
# report
|
|
528
|
+
report_parser = subparsers.add_parser("report", help="Show recent crash logs")
|
|
529
|
+
report_parser.add_argument("--limit", type=int, default=20, help="Number of recent crashes to show (default: 20)")
|
|
530
|
+
report_parser.add_argument("--clear", action="store_true", help="Clear the crash log")
|
|
531
|
+
|
|
532
|
+
# serve
|
|
533
|
+
serve_parser = subparsers.add_parser(
|
|
534
|
+
"serve",
|
|
535
|
+
help="Start MCP HTTP server (Streamable HTTP transport — register via URL)",
|
|
536
|
+
)
|
|
537
|
+
serve_parser.add_argument(
|
|
538
|
+
"--port", type=int, default=7007,
|
|
539
|
+
help="TCP port to listen on (default: 7007)",
|
|
540
|
+
)
|
|
541
|
+
serve_parser.add_argument(
|
|
542
|
+
"--host", default="127.0.0.1",
|
|
543
|
+
help="Bind address (default: 127.0.0.1; use 0.0.0.0 for LAN access)",
|
|
544
|
+
)
|
|
545
|
+
serve_parser.add_argument(
|
|
546
|
+
"--https", action="store_true",
|
|
547
|
+
help="Enable HTTPS using mkcert certs from ~/.codevira/certs/",
|
|
548
|
+
)
|
|
549
|
+
serve_parser.add_argument(
|
|
550
|
+
"--install-service", action="store_true",
|
|
551
|
+
help="Install macOS launchd service so the server starts automatically on login",
|
|
552
|
+
)
|
|
553
|
+
serve_parser.add_argument(
|
|
554
|
+
"--uninstall-service", action="store_true",
|
|
555
|
+
help="Remove the macOS launchd service",
|
|
556
|
+
)
|
|
557
|
+
serve_parser.add_argument(
|
|
558
|
+
"--project-dir",
|
|
559
|
+
metavar="PATH",
|
|
560
|
+
help="Project directory override (same as the global --project-dir flag)",
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
# register (v1.6: one-time global IDE registration)
|
|
564
|
+
register_parser = subparsers.add_parser(
|
|
565
|
+
"register",
|
|
566
|
+
help="One-time global IDE registration — inject Codevira into all detected AI tools",
|
|
567
|
+
)
|
|
568
|
+
register_parser.add_argument(
|
|
569
|
+
"--claude-desktop", action="store_true",
|
|
570
|
+
help="Only configure Claude Desktop (stdio mode)",
|
|
571
|
+
)
|
|
572
|
+
register_parser.add_argument(
|
|
573
|
+
"--http-url",
|
|
574
|
+
metavar="URL",
|
|
575
|
+
help="Inject an HTTP URL config into Claude Code (e.g. https://localhost:7443/mcp)",
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
args = parser.parse_args(raw_args)
|
|
579
|
+
|
|
580
|
+
if args.command == "init":
|
|
581
|
+
# Pass overrides via function attribute (avoids changing signature)
|
|
582
|
+
cmd_init._overrides = {
|
|
583
|
+
"name": getattr(args, "name", None),
|
|
584
|
+
"language": getattr(args, "language", None),
|
|
585
|
+
"dirs": getattr(args, "dirs", None),
|
|
586
|
+
"ext": getattr(args, "ext", None),
|
|
587
|
+
}
|
|
588
|
+
cmd_init._no_inject = getattr(args, "no_inject", False)
|
|
589
|
+
cmd_init()
|
|
590
|
+
elif args.command == "index":
|
|
591
|
+
cmd_index(full=args.full, quiet=args.quiet)
|
|
592
|
+
elif args.command == "status":
|
|
593
|
+
cmd_status()
|
|
594
|
+
elif args.command == "report":
|
|
595
|
+
cmd_report(limit=args.limit, clear=args.clear)
|
|
596
|
+
elif args.command == "serve":
|
|
597
|
+
# --project-dir may appear after "serve" — merge with pre-parsed value
|
|
598
|
+
sub_project_dir = getattr(args, "project_dir", None)
|
|
599
|
+
if sub_project_dir and project_dir is None:
|
|
600
|
+
from mcp_server.paths import set_project_dir
|
|
601
|
+
project_dir = Path(sub_project_dir).resolve()
|
|
602
|
+
set_project_dir(project_dir)
|
|
603
|
+
cmd_serve(
|
|
604
|
+
host=args.host,
|
|
605
|
+
port=args.port,
|
|
606
|
+
use_https=args.https,
|
|
607
|
+
project_dir=project_dir,
|
|
608
|
+
install_service=getattr(args, "install_service", False),
|
|
609
|
+
uninstall_service=getattr(args, "uninstall_service", False),
|
|
610
|
+
)
|
|
611
|
+
elif args.command == "register":
|
|
612
|
+
cmd_register(
|
|
613
|
+
claude_desktop=getattr(args, "claude_desktop", False),
|
|
614
|
+
http_url=getattr(args, "http_url", None),
|
|
615
|
+
)
|
|
616
|
+
else:
|
|
617
|
+
# No subcommand → start MCP server (stdio transport)
|
|
618
|
+
cmd_server()
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
if __name__ == "__main__":
|
|
622
|
+
main()
|