cortexcode 0.6.0__py3-none-any.whl → 0.7.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.
- cortexcode/cli/cli_bundle.py +143 -0
- cortexcode/cli/cli_githook.py +106 -0
- cortexcode/cli/cli_jobs.py +145 -0
- cortexcode/cli/cli_package.py +201 -0
- cortexcode/cli/cli_servers.py +149 -0
- cortexcode/cli/cli_shell.py +144 -0
- cortexcode/cli/cli_trace.py +232 -0
- cortexcode/docs/javascript_sections.py +5 -5
- cortexcode/docs/templates.py +109 -84
- cortexcode/main.py +752 -44
- cortexcode/reports/html/dashboard.py +4 -1
- cortexcode/reports/site/viz.py +853 -0
- cortexcode/vuln_scan.py +75 -0
- {cortexcode-0.6.0.dist-info → cortexcode-0.7.0.dist-info}/METADATA +632 -555
- {cortexcode-0.6.0.dist-info → cortexcode-0.7.0.dist-info}/RECORD +19 -12
- {cortexcode-0.6.0.dist-info → cortexcode-0.7.0.dist-info}/WHEEL +1 -1
- {cortexcode-0.6.0.dist-info → cortexcode-0.7.0.dist-info}/entry_points.txt +1 -0
- {cortexcode-0.6.0.dist-info → cortexcode-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {cortexcode-0.6.0.dist-info → cortexcode-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Bundle management - export/import pre-indexed code graphs."""
|
|
2
|
+
|
|
3
|
+
import gzip
|
|
4
|
+
import json
|
|
5
|
+
import shutil
|
|
6
|
+
import zipfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
BUNDLE_VERSION = "1.0"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def export_bundle(index_path: Path, output_path: Path, name: str = None) -> Path:
|
|
17
|
+
"""Export index as a shareable bundle."""
|
|
18
|
+
if not index_path.exists():
|
|
19
|
+
raise FileNotFoundError(f"Index not found at {index_path}")
|
|
20
|
+
|
|
21
|
+
with open(index_path) as f:
|
|
22
|
+
index_data = json.load(f)
|
|
23
|
+
|
|
24
|
+
bundle_name = name or index_data.get("project_root", "project")
|
|
25
|
+
bundle_name = Path(bundle_name).name
|
|
26
|
+
|
|
27
|
+
bundle_data = {
|
|
28
|
+
"version": BUNDLE_VERSION,
|
|
29
|
+
"name": bundle_name,
|
|
30
|
+
"exported": str(Path.cwd()),
|
|
31
|
+
"index": index_data,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
output_path = Path(output_path)
|
|
35
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
36
|
+
|
|
37
|
+
if not output_path.suffix:
|
|
38
|
+
output_path = output_path / f"{bundle_name}.ccb"
|
|
39
|
+
|
|
40
|
+
with gzip.open(output_path, "wt", encoding="utf-8") as f:
|
|
41
|
+
json.dump(bundle_data, f)
|
|
42
|
+
|
|
43
|
+
return output_path
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def import_bundle(bundle_path: Path, output_dir: Path) -> Path:
|
|
47
|
+
"""Import a bundle and return the index path."""
|
|
48
|
+
with gzip.open(bundle_path, "rt", encoding="utf-8") as f:
|
|
49
|
+
bundle_data = json.load(f)
|
|
50
|
+
|
|
51
|
+
if bundle_data.get("version") != BUNDLE_VERSION:
|
|
52
|
+
raise ValueError(f"Unsupported bundle version: {bundle_data.get('version')}")
|
|
53
|
+
|
|
54
|
+
index_data = bundle_data["index"]
|
|
55
|
+
|
|
56
|
+
output_dir = Path(output_dir)
|
|
57
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
|
|
59
|
+
index_path = output_dir / "index.json"
|
|
60
|
+
with open(index_path, "w") as f:
|
|
61
|
+
json.dump(index_data, f, indent=2)
|
|
62
|
+
|
|
63
|
+
return index_path
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def list_bundle_info(bundle_path: Path) -> dict:
|
|
67
|
+
"""Get info about a bundle without importing it."""
|
|
68
|
+
with gzip.open(bundle_path, "rt", encoding="utf-8") as f:
|
|
69
|
+
bundle_data = json.load(f)
|
|
70
|
+
|
|
71
|
+
index = bundle_data.get("index", {})
|
|
72
|
+
files = index.get("files", {})
|
|
73
|
+
symbols = sum(len(f.get("symbols", [])) for f in files.values())
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
"name": bundle_data.get("name"),
|
|
77
|
+
"version": bundle_data.get("version"),
|
|
78
|
+
"exported_from": bundle_data.get("exported"),
|
|
79
|
+
"files": len(files),
|
|
80
|
+
"symbols": symbols,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def handle_bundle_export(console: Console, path: str, output: str, name: str = None) -> None:
|
|
85
|
+
"""CLI handler for bundle export."""
|
|
86
|
+
path = Path(path)
|
|
87
|
+
index_path = path / ".cortexcode" / "index.json"
|
|
88
|
+
|
|
89
|
+
if not index_path.exists():
|
|
90
|
+
console.print(f"[red]No index found at {index_path}[/red]")
|
|
91
|
+
console.print("Run [cyan]cortexcode index[/cyan] first")
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
output_path = Path(output) if output else Path.cwd()
|
|
95
|
+
|
|
96
|
+
with Progress(
|
|
97
|
+
SpinnerColumn(),
|
|
98
|
+
TextColumn("[progress.description]{task.description}"),
|
|
99
|
+
console=console,
|
|
100
|
+
) as progress:
|
|
101
|
+
progress.add_task("Exporting bundle...", total=None)
|
|
102
|
+
result = export_bundle(index_path, output_path, name)
|
|
103
|
+
|
|
104
|
+
size_kb = result.stat().st_size / 1024
|
|
105
|
+
console.print(f"[green]✓[/green] Bundle exported: {result} ({size_kb:.1f} KB)")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def handle_bundle_import(console: Console, bundle_path: str, output: str = None) -> None:
|
|
109
|
+
"""CLI handler for bundle import."""
|
|
110
|
+
bundle_path = Path(bundle_path)
|
|
111
|
+
|
|
112
|
+
if not bundle_path.exists():
|
|
113
|
+
console.print(f"[red]Bundle not found: {bundle_path}[/red]")
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
output_dir = Path(output) if output else Path.cwd() / ".cortexcode"
|
|
117
|
+
|
|
118
|
+
with Progress(
|
|
119
|
+
SpinnerColumn(),
|
|
120
|
+
TextColumn("[progress.description]{task.description}"),
|
|
121
|
+
console=console,
|
|
122
|
+
) as progress:
|
|
123
|
+
progress.add_task("Importing bundle...", total=None)
|
|
124
|
+
result = import_bundle(bundle_path, output_dir)
|
|
125
|
+
|
|
126
|
+
console.print(f"[green]✓[/green] Bundle imported: {result}")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def handle_bundle_info(console: Console, bundle_path: str) -> None:
|
|
130
|
+
"""CLI handler for bundle info."""
|
|
131
|
+
bundle_path = Path(bundle_path)
|
|
132
|
+
|
|
133
|
+
if not bundle_path.exists():
|
|
134
|
+
console.print(f"[red]Bundle not found: {bundle_path}[/red]")
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
info = list_bundle_info(bundle_path)
|
|
138
|
+
|
|
139
|
+
console.print(f"\n[bold cyan]Bundle: {info['name']}[/bold cyan]")
|
|
140
|
+
console.print(f" Version: {info['version']}")
|
|
141
|
+
console.print(f" Exported: {info['exported_from']}")
|
|
142
|
+
console.print(f" Files: {info['files']}")
|
|
143
|
+
console.print(f" Symbols: {info['symbols']}")
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Git hook integration for auto-indexing."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import stat
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def install_githook(console: Console, hook_type: str = "post-commit") -> None:
|
|
12
|
+
"""Install git hook for auto-indexing."""
|
|
13
|
+
git_dir = Path.cwd() / ".git"
|
|
14
|
+
hooks_dir = git_dir / "hooks"
|
|
15
|
+
|
|
16
|
+
if not git_dir.exists():
|
|
17
|
+
console.print("[red]Not a git repository[/red]")
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
hooks_dir.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
|
|
22
|
+
hook_content = f"""#!/bin/sh
|
|
23
|
+
# CortexCode auto-index hook
|
|
24
|
+
# Generated by cortexcode githook install
|
|
25
|
+
|
|
26
|
+
# Only run on post-commit
|
|
27
|
+
if [ "$CORTEXCODE_HOOK" != "post-commit" ]; then
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Check if index exists
|
|
32
|
+
if [ -f ".cortexcode/index.json" ]; then
|
|
33
|
+
# Only re-index if there were changes to code files
|
|
34
|
+
CHANGED_FILES=$(git diff-tree -r --name-only --no-commit-id HEAD~1 HEAD -- "*.py" "*.js" "*.ts" "*.jsx" "*.tsx" "*.go" "*.rs" "*.java" "*.cs" 2>/dev/null)
|
|
35
|
+
if [ -n "$CHANGED_FILES" ]; then
|
|
36
|
+
cortexcode index -i 2>/dev/null || true
|
|
37
|
+
fi
|
|
38
|
+
fi
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
hook_path = hooks_dir / hook_type
|
|
42
|
+
hook_path.write_text(hook_content)
|
|
43
|
+
|
|
44
|
+
hook_path.chmod(hook_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
45
|
+
|
|
46
|
+
console.print(f"[green]✓[/green] Installed {hook_type} hook at {hook_path}")
|
|
47
|
+
console.print(f"[dim]Run 'CORTEXCODE_HOOK=post-commit git commit ...' to trigger auto-index[/dim]")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def uninstall_githook(console: Console, hook_type: str = "post-commit") -> None:
|
|
51
|
+
"""Remove git hook."""
|
|
52
|
+
git_dir = Path.cwd() / ".git"
|
|
53
|
+
hook_path = git_dir / "hooks" / hook_type
|
|
54
|
+
|
|
55
|
+
if hook_path.exists():
|
|
56
|
+
hook_path.unlink()
|
|
57
|
+
console.print(f"[green]✓[/green] Removed {hook_type} hook")
|
|
58
|
+
else:
|
|
59
|
+
console.print(f"[yellow]No {hook_type} hook found[/yellow]")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def install_pre_commit(console: Console) -> None:
|
|
63
|
+
"""Install pre-commit hook for code quality checks."""
|
|
64
|
+
git_dir = Path.cwd() / ".git"
|
|
65
|
+
hooks_dir = git_dir / "hooks"
|
|
66
|
+
|
|
67
|
+
if not git_dir.exists():
|
|
68
|
+
console.print("[red]Not a git repository[/red]")
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
hooks_dir.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
|
|
73
|
+
hook_content = """#!/bin/sh
|
|
74
|
+
# CortexCode pre-commit hook
|
|
75
|
+
# Runs security scan before commit
|
|
76
|
+
|
|
77
|
+
# Run security scan on staged files
|
|
78
|
+
STAGED_PY=$(git diff --cached --name-only --diff-filter=ACM | grep '\\.py$' | head -10)
|
|
79
|
+
if [ -n "$STAGED_PY" ]; then
|
|
80
|
+
echo "Running CortexCode security scan..."
|
|
81
|
+
cortexcode scan 2>/dev/null | head -20
|
|
82
|
+
fi
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
hook_path = hooks_dir / "pre-commit"
|
|
86
|
+
hook_path.write_text(hook_content)
|
|
87
|
+
|
|
88
|
+
hook_path.chmod(hook_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
89
|
+
|
|
90
|
+
console.print(f"[green]✓[/green] Installed pre-commit hook")
|
|
91
|
+
console.print(f"[dim]Security scan will run before each commit[/dim]")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def handle_githook_install(console: Console, hook_type: str) -> None:
|
|
95
|
+
"""CLI handler for githook install."""
|
|
96
|
+
install_githook(console, hook_type)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def handle_githook_uninstall(console: Console, hook_type: str) -> None:
|
|
100
|
+
"""CLI handler for githook uninstall."""
|
|
101
|
+
uninstall_githook(console, hook_type)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def handle_githook_precommit(console: Console) -> None:
|
|
105
|
+
"""CLI handler for pre-commit install."""
|
|
106
|
+
install_pre_commit(console)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Background job tracking for indexing operations."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
JOBS_FILE = Path.home() / ".cortexcode" / "jobs.json"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JobTracker:
|
|
15
|
+
"""Track background indexing jobs."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.jobs: dict[str, dict] = {}
|
|
19
|
+
self.load()
|
|
20
|
+
|
|
21
|
+
def load(self):
|
|
22
|
+
"""Load jobs from disk."""
|
|
23
|
+
if JOBS_FILE.exists():
|
|
24
|
+
try:
|
|
25
|
+
self.jobs = json.loads(JOBS_FILE.read_text())
|
|
26
|
+
except:
|
|
27
|
+
self.jobs = {}
|
|
28
|
+
|
|
29
|
+
def save(self):
|
|
30
|
+
"""Save jobs to disk."""
|
|
31
|
+
JOBS_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
32
|
+
JOBS_FILE.write_text(json.dumps(self.jobs, indent=2))
|
|
33
|
+
|
|
34
|
+
def create(self, job_type: str, path: str, description: str = "") -> str:
|
|
35
|
+
"""Create a new job."""
|
|
36
|
+
job_id = f"{job_type}_{int(time.time())}"
|
|
37
|
+
self.jobs[job_id] = {
|
|
38
|
+
"id": job_id,
|
|
39
|
+
"type": job_type,
|
|
40
|
+
"path": path,
|
|
41
|
+
"description": description,
|
|
42
|
+
"status": "running",
|
|
43
|
+
"created": time.time(),
|
|
44
|
+
"updated": time.time(),
|
|
45
|
+
"progress": 0,
|
|
46
|
+
"total": 100,
|
|
47
|
+
"message": "Starting...",
|
|
48
|
+
}
|
|
49
|
+
self.save()
|
|
50
|
+
return job_id
|
|
51
|
+
|
|
52
|
+
def update(self, job_id: str, progress: int = None, total: int = None, message: str = None, status: str = None):
|
|
53
|
+
"""Update a job."""
|
|
54
|
+
if job_id in self.jobs:
|
|
55
|
+
if progress is not None:
|
|
56
|
+
self.jobs[job_id]["progress"] = progress
|
|
57
|
+
if total is not None:
|
|
58
|
+
self.jobs[job_id]["total"] = total
|
|
59
|
+
if message is not None:
|
|
60
|
+
self.jobs[job_id]["message"] = message
|
|
61
|
+
if status is not None:
|
|
62
|
+
self.jobs[job_id]["status"] = status
|
|
63
|
+
self.jobs[job_id]["updated"] = time.time()
|
|
64
|
+
self.save()
|
|
65
|
+
|
|
66
|
+
def complete(self, job_id: str, message: str = "Done"):
|
|
67
|
+
"""Mark a job as complete."""
|
|
68
|
+
self.update(job_id, status="completed", message=message, progress=100)
|
|
69
|
+
|
|
70
|
+
def fail(self, job_id: str, message: str = "Failed"):
|
|
71
|
+
"""Mark a job as failed."""
|
|
72
|
+
self.update(job_id, status="failed", message=message)
|
|
73
|
+
|
|
74
|
+
def get(self, job_id: str) -> dict | None:
|
|
75
|
+
"""Get a job by ID."""
|
|
76
|
+
return self.jobs.get(job_id)
|
|
77
|
+
|
|
78
|
+
def list(self, status: str = None) -> list[dict]:
|
|
79
|
+
"""List all jobs, optionally filtered by status."""
|
|
80
|
+
jobs = list(self.jobs.values())
|
|
81
|
+
if status:
|
|
82
|
+
jobs = [j for j in jobs if j["status"] == status]
|
|
83
|
+
return sorted(jobs, key=lambda x: x["updated"], reverse=True)
|
|
84
|
+
|
|
85
|
+
def clear(self, completed: bool = True):
|
|
86
|
+
"""Clear completed or all jobs."""
|
|
87
|
+
if completed:
|
|
88
|
+
self.jobs = {k: v for k, v in self.jobs.items() if v["status"] == "running"}
|
|
89
|
+
else:
|
|
90
|
+
self.jobs = {}
|
|
91
|
+
self.save()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def handle_jobs_list(console: Console, status: str = None) -> None:
|
|
95
|
+
"""List all background jobs."""
|
|
96
|
+
tracker = JobTracker()
|
|
97
|
+
jobs = tracker.list(status)
|
|
98
|
+
|
|
99
|
+
if not jobs:
|
|
100
|
+
console.print("[dim]No jobs found[/dim]")
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
console.print(f"\n[bold]Background Jobs:[/bold]\n")
|
|
104
|
+
for job in jobs:
|
|
105
|
+
status_icon = {"running": "🔄", "completed": "✓", "failed": "✗"}[job["status"]]
|
|
106
|
+
status_color = {"running": "cyan", "completed": "green", "failed": "red"}[job["status"]]
|
|
107
|
+
|
|
108
|
+
progress = job.get("progress", 0)
|
|
109
|
+
total = job.get("total", 100)
|
|
110
|
+
pct = int(progress / total * 100) if total > 0 else 0
|
|
111
|
+
|
|
112
|
+
console.print(f" {status_icon} [{status_color}]{job['id']}[/{status_color}]")
|
|
113
|
+
console.print(f" {job['type']}: {job.get('path', 'N/A')}")
|
|
114
|
+
console.print(f" Status: {job['status']} ({pct}%) - {job.get('message', '')}")
|
|
115
|
+
console.print()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def handle_jobs_clear(console: Console, all: bool = False) -> None:
|
|
119
|
+
"""Clear completed jobs."""
|
|
120
|
+
tracker = JobTracker()
|
|
121
|
+
tracker.clear(completed=not all)
|
|
122
|
+
console.print("[green]✓[/green] Jobs cleared")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def handle_jobs_watch(console: Console, job_id: str) -> None:
|
|
126
|
+
"""Watch a job's progress."""
|
|
127
|
+
tracker = JobTracker()
|
|
128
|
+
|
|
129
|
+
while True:
|
|
130
|
+
job = tracker.get(job_id)
|
|
131
|
+
if not job:
|
|
132
|
+
console.print(f"[red]Job not found: {job_id}[/red]")
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
progress = job.get("progress", 0)
|
|
136
|
+
total = job.get("total", 100)
|
|
137
|
+
pct = int(progress / total * 100) if total > 0 else 0
|
|
138
|
+
|
|
139
|
+
console.print(f"\r[{job['status']}] {job['message']} ({pct}%)", end="")
|
|
140
|
+
|
|
141
|
+
if job["status"] != "running":
|
|
142
|
+
console.print()
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
time.sleep(1)
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Package indexing - index external packages (pip, npm, etc.)."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import json
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
LANGUAGE_LOCATIONS = {
|
|
15
|
+
"python": {
|
|
16
|
+
"site_packages": lambda: Path(sys.prefix) / "Lib" / "site-packages",
|
|
17
|
+
"src_patterns": ["**/*.py"],
|
|
18
|
+
},
|
|
19
|
+
"javascript": {
|
|
20
|
+
"site_packages": lambda: Path.cwd() / "node_modules",
|
|
21
|
+
"src_patterns": ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx"],
|
|
22
|
+
},
|
|
23
|
+
"typescript": {
|
|
24
|
+
"site_packages": lambda: Path.cwd() / "node_modules",
|
|
25
|
+
"src_patterns": ["**/*.ts", "**/*.tsx"],
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def find_package_location(package_name: str, language: str) -> Path | None:
|
|
31
|
+
"""Find where a package is installed."""
|
|
32
|
+
if language == "python":
|
|
33
|
+
try:
|
|
34
|
+
spec = importlib.util.find_spec(package_name)
|
|
35
|
+
if spec and spec.submodule_search_locations:
|
|
36
|
+
return Path(spec.submodule_search_locations[0]).parent
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
site_packages = Path(sys.prefix) / "Lib" / "site-packages"
|
|
41
|
+
if (site_packages / package_name).exists():
|
|
42
|
+
return site_packages / package_name
|
|
43
|
+
if (site_packages / f"{package_name}-*").exists():
|
|
44
|
+
for p in site_packages.glob(f"{package_name}-*"):
|
|
45
|
+
return p
|
|
46
|
+
|
|
47
|
+
elif language in ("javascript", "typescript"):
|
|
48
|
+
node_modules = Path.cwd() / "node_modules" / package_name
|
|
49
|
+
if node_modules.exists():
|
|
50
|
+
return node_modules
|
|
51
|
+
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def index_package(package_name: str, language: str, output_dir: Path) -> dict:
|
|
56
|
+
"""Index a package and return the index data."""
|
|
57
|
+
package_path = find_package_location(package_name, language)
|
|
58
|
+
|
|
59
|
+
if not package_path:
|
|
60
|
+
raise FileNotFoundError(f"Package '{package_name}' not found. Install it first: pip install {package_name}")
|
|
61
|
+
|
|
62
|
+
index_data = {
|
|
63
|
+
"project_root": str(package_path),
|
|
64
|
+
"last_indexed": "2024-01-01T00:00:00",
|
|
65
|
+
"languages": [language],
|
|
66
|
+
"files": {},
|
|
67
|
+
"call_graph": {},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if language == "python":
|
|
71
|
+
for py_file in package_path.rglob("*.py"):
|
|
72
|
+
if "__pycache__" in str(py_file) or ".pyc" in str(py_file):
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
content = py_file.read_text(encoding="utf-8", errors="ignore")
|
|
77
|
+
except:
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
symbols = []
|
|
81
|
+
for line_num, line in enumerate(content.split("\n"), 1):
|
|
82
|
+
line = line.strip()
|
|
83
|
+
|
|
84
|
+
if line.startswith("def "):
|
|
85
|
+
func_name = line[4:].split("(")[0].strip()
|
|
86
|
+
if func_name and not func_name.startswith("_"):
|
|
87
|
+
symbols.append({
|
|
88
|
+
"name": func_name,
|
|
89
|
+
"type": "function",
|
|
90
|
+
"line": line_num,
|
|
91
|
+
"params": [],
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
elif line.startswith("class "):
|
|
95
|
+
class_name = line[6:].split("(")[0].strip()
|
|
96
|
+
if class_name and not class_name.startswith("_"):
|
|
97
|
+
symbols.append({
|
|
98
|
+
"name": class_name,
|
|
99
|
+
"type": "class",
|
|
100
|
+
"line": line_num,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
if symbols:
|
|
104
|
+
rel_path = py_file.relative_to(package_path)
|
|
105
|
+
index_data["files"][str(rel_path)] = {"symbols": symbols}
|
|
106
|
+
|
|
107
|
+
elif language in ("javascript", "typescript"):
|
|
108
|
+
for js_file in package_path.rglob("*.js"):
|
|
109
|
+
try:
|
|
110
|
+
content = js_file.read_text(encoding="utf-8", errors="ignore")
|
|
111
|
+
except:
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
symbols = []
|
|
115
|
+
for line_num, line in enumerate(content.split("\n"), 1):
|
|
116
|
+
line = line.strip()
|
|
117
|
+
|
|
118
|
+
if line.startswith("function ") or line.startswith("const ") and " = " in line:
|
|
119
|
+
if "function " in line:
|
|
120
|
+
func_name = line.split("function ")[1].split("(")[0].strip()
|
|
121
|
+
sym_type = "function"
|
|
122
|
+
else:
|
|
123
|
+
func_name = line.split("const ")[1].split(" = ")[0].strip()
|
|
124
|
+
sym_type = "function"
|
|
125
|
+
|
|
126
|
+
if func_name and not func_name.startswith("_"):
|
|
127
|
+
symbols.append({
|
|
128
|
+
"name": func_name,
|
|
129
|
+
"type": sym_type,
|
|
130
|
+
"line": line_num,
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
elif line.startswith("class "):
|
|
134
|
+
class_name = line.split("class ")[1].split(" ")[0].strip()
|
|
135
|
+
if class_name and not class_name.startswith("_"):
|
|
136
|
+
symbols.append({
|
|
137
|
+
"name": class_name,
|
|
138
|
+
"type": "class",
|
|
139
|
+
"line": line_num,
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
if symbols:
|
|
143
|
+
rel_path = js_file.relative_to(package_path)
|
|
144
|
+
index_data["files"][str(rel_path)] = {"symbols": symbols}
|
|
145
|
+
|
|
146
|
+
return index_data
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def handle_package_index(console: Console, package_name: str, language: str, output: str = None) -> None:
|
|
150
|
+
"""CLI handler for package indexing."""
|
|
151
|
+
output_dir = Path(output) if output else Path.cwd() / ".cortexcode"
|
|
152
|
+
|
|
153
|
+
with Progress(
|
|
154
|
+
SpinnerColumn(),
|
|
155
|
+
TextColumn("[progress.description]{task.description}"),
|
|
156
|
+
console=console,
|
|
157
|
+
) as progress:
|
|
158
|
+
progress.add_task(f"Indexing package {package_name}...", total=None)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
index_data = index_package(package_name, language, output_dir)
|
|
162
|
+
|
|
163
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
164
|
+
index_path = output_dir / "index.json"
|
|
165
|
+
|
|
166
|
+
existing = {}
|
|
167
|
+
if index_path.exists():
|
|
168
|
+
try:
|
|
169
|
+
existing = json.loads(index_path.read_text())
|
|
170
|
+
except:
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
for k, v in index_data.get("files", {}).items():
|
|
174
|
+
if k not in existing.get("files", {}):
|
|
175
|
+
existing.setdefault("files", {})[k] = v
|
|
176
|
+
|
|
177
|
+
index_path.write_text(json.dumps(existing, indent=2))
|
|
178
|
+
|
|
179
|
+
console.print(f"[green]✓[/green] Indexed package: {package_name}")
|
|
180
|
+
console.print(f" Files: {len(index_data.get('files', {}))}")
|
|
181
|
+
|
|
182
|
+
except FileNotFoundError as e:
|
|
183
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
184
|
+
console.print(f"[dim]Install with: pip install {package_name}[/dim]")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def handle_package_list(console: Console) -> None:
|
|
188
|
+
"""List available packages in site-packages/node_modules."""
|
|
189
|
+
console.print("\n[bold]Python packages:[/bold]")
|
|
190
|
+
site_packages = Path(sys.prefix) / "Lib" / "site-packages"
|
|
191
|
+
if site_packages.exists():
|
|
192
|
+
for p in sorted(site_packages.iterdir())[:20]:
|
|
193
|
+
if p.is_dir() and not p.name.startswith("_"):
|
|
194
|
+
console.print(f" • {p.name}")
|
|
195
|
+
|
|
196
|
+
console.print("\n[bold]JavaScript packages:[/bold]")
|
|
197
|
+
node_modules = Path.cwd() / "node_modules"
|
|
198
|
+
if node_modules.exists():
|
|
199
|
+
for p in sorted(node_modules.iterdir())[:20]:
|
|
200
|
+
if p.is_dir() and not p.name.startswith("_"):
|
|
201
|
+
console.print(f" • {p.name}")
|