codegraphcontext-rust 0.5.5__cp310-cp310-win_amd64.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.
- codegraphcontext/__init__.py +2 -0
- codegraphcontext/__main__.py +5 -0
- codegraphcontext/_cgc_rust.cp310-win_amd64.pyd +0 -0
- codegraphcontext/cli/__init__.py +1 -0
- codegraphcontext/cli/cli_helpers.py +774 -0
- codegraphcontext/cli/config_manager.py +1051 -0
- codegraphcontext/cli/main.py +2386 -0
- codegraphcontext/cli/registry_commands.py +404 -0
- codegraphcontext/cli/setup_macos.py +93 -0
- codegraphcontext/cli/setup_wizard.py +991 -0
- codegraphcontext/cli/visualizer.py +50 -0
- codegraphcontext/core/__init__.py +165 -0
- codegraphcontext/core/bundle_registry.py +181 -0
- codegraphcontext/core/cgc_bundle.py +858 -0
- codegraphcontext/core/cgcignore.py +118 -0
- codegraphcontext/core/database.py +273 -0
- codegraphcontext/core/database_falkordb.py +480 -0
- codegraphcontext/core/database_falkordb_remote.py +199 -0
- codegraphcontext/core/database_kuzu.py +636 -0
- codegraphcontext/core/falkor_worker.py +132 -0
- codegraphcontext/core/jobs.py +132 -0
- codegraphcontext/core/watcher.py +260 -0
- codegraphcontext/prompts.py +125 -0
- codegraphcontext/server.py +471 -0
- codegraphcontext/tool_definitions.py +220 -0
- codegraphcontext/tools/__init__.py +0 -0
- codegraphcontext/tools/advanced_language_query_tool.py +103 -0
- codegraphcontext/tools/code_finder.py +1160 -0
- codegraphcontext/tools/graph_builder.py +337 -0
- codegraphcontext/tools/handlers/analysis_handlers.py +114 -0
- codegraphcontext/tools/handlers/indexing_handlers.py +116 -0
- codegraphcontext/tools/handlers/management_handlers.py +339 -0
- codegraphcontext/tools/handlers/query_handlers.py +83 -0
- codegraphcontext/tools/handlers/watcher_handlers.py +83 -0
- codegraphcontext/tools/indexing/__init__.py +1 -0
- codegraphcontext/tools/indexing/constants.py +25 -0
- codegraphcontext/tools/indexing/discovery.py +64 -0
- codegraphcontext/tools/indexing/engine.py +109 -0
- codegraphcontext/tools/indexing/persistence/__init__.py +3 -0
- codegraphcontext/tools/indexing/persistence/parquet_loader.py +643 -0
- codegraphcontext/tools/indexing/persistence/writer.py +1114 -0
- codegraphcontext/tools/indexing/pipeline.py +171 -0
- codegraphcontext/tools/indexing/pre_scan.py +105 -0
- codegraphcontext/tools/indexing/resolution/__init__.py +8 -0
- codegraphcontext/tools/indexing/resolution/calls.py +253 -0
- codegraphcontext/tools/indexing/resolution/inheritance.py +91 -0
- codegraphcontext/tools/indexing/sanitize.py +41 -0
- codegraphcontext/tools/indexing/schema.py +79 -0
- codegraphcontext/tools/indexing/schema_contract.py +44 -0
- codegraphcontext/tools/indexing/scip_pipeline.py +140 -0
- codegraphcontext/tools/languages/c.py +563 -0
- codegraphcontext/tools/languages/cpp.py +616 -0
- codegraphcontext/tools/languages/csharp.py +551 -0
- codegraphcontext/tools/languages/dart.py +378 -0
- codegraphcontext/tools/languages/elixir.py +460 -0
- codegraphcontext/tools/languages/go.py +499 -0
- codegraphcontext/tools/languages/haskell.py +426 -0
- codegraphcontext/tools/languages/java.py +500 -0
- codegraphcontext/tools/languages/javascript.py +589 -0
- codegraphcontext/tools/languages/kotlin.py +640 -0
- codegraphcontext/tools/languages/perl.py +260 -0
- codegraphcontext/tools/languages/php.py +519 -0
- codegraphcontext/tools/languages/python.py +576 -0
- codegraphcontext/tools/languages/ruby.py +537 -0
- codegraphcontext/tools/languages/rust.py +295 -0
- codegraphcontext/tools/languages/scala.py +519 -0
- codegraphcontext/tools/languages/swift.py +491 -0
- codegraphcontext/tools/languages/typescript.py +575 -0
- codegraphcontext/tools/languages/typescriptjsx.py +151 -0
- codegraphcontext/tools/package_resolver.py +472 -0
- codegraphcontext/tools/query_tool_languages/c_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +87 -0
- codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/dart_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/go_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/java_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/perl_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/python_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/rust_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/scala_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/swift_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +5 -0
- codegraphcontext/tools/scip_indexer.py +467 -0
- codegraphcontext/tools/scip_pb2.py +2455 -0
- codegraphcontext/tools/system.py +133 -0
- codegraphcontext/tools/tree_sitter_parser.py +103 -0
- codegraphcontext/utils/debug_log.py +90 -0
- codegraphcontext/utils/path_ignore.py +55 -0
- codegraphcontext/utils/repo_path.py +27 -0
- codegraphcontext/utils/tree_sitter_manager.py +285 -0
- codegraphcontext/utils/visualize_graph.py +0 -0
- codegraphcontext/viz/server.py +282 -0
- codegraphcontext_rust-0.5.5.dist-info/METADATA +537 -0
- codegraphcontext_rust-0.5.5.dist-info/RECORD +101 -0
- codegraphcontext_rust-0.5.5.dist-info/WHEEL +4 -0
- codegraphcontext_rust-0.5.5.dist-info/entry_points.txt +3 -0
- codegraphcontext_rust-0.5.5.dist-info/licenses/LICENSE +21 -0
- codegraphcontext_rust-0.5.5.dist-info/sboms/cgc-python.cyclonedx.json +2333 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
# src/codegraphcontext/cli/registry_commands.py
|
|
2
|
+
"""
|
|
3
|
+
CLI commands for interacting with the CodeGraphContext bundle registry.
|
|
4
|
+
Allows users to list, search, download, and request bundles from the command line.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional, List, Dict, Any
|
|
14
|
+
import time
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
GITHUB_ORG = "CodeGraphContext"
|
|
19
|
+
GITHUB_REPO = "CodeGraphContext"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def fetch_available_bundles() -> List[Dict[str, Any]]:
|
|
23
|
+
"""Fetch all available bundles from GitHub Releases (delegates to core BundleRegistry)."""
|
|
24
|
+
from ..core.bundle_registry import BundleRegistry
|
|
25
|
+
return BundleRegistry.fetch_available_bundles()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _get_base_package_name(bundle_name: str) -> str:
|
|
29
|
+
"""
|
|
30
|
+
Extract base package name from full bundle name.
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
'python-bitcoin-utils-main-61d1969' -> 'python-bitcoin-utils'
|
|
34
|
+
'flask-main-abc123' -> 'flask'
|
|
35
|
+
'requests' -> 'requests'
|
|
36
|
+
"""
|
|
37
|
+
# Remove .cgc extension if present
|
|
38
|
+
name = bundle_name.replace('.cgc', '')
|
|
39
|
+
|
|
40
|
+
# Split by hyphen and take the first part
|
|
41
|
+
# This assumes package names don't contain hyphens (may need refinement)
|
|
42
|
+
parts = name.split('-')
|
|
43
|
+
|
|
44
|
+
# For multi-word package names like 'python-bitcoin-utils',
|
|
45
|
+
# we need smarter logic. For now, take first part.
|
|
46
|
+
# TODO: Improve this with a known package list or better heuristics
|
|
47
|
+
return parts[0]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def list_bundles(verbose: bool = False, unique: bool = False):
|
|
51
|
+
"""
|
|
52
|
+
Display all available bundles in a table.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
verbose: Show additional details like download URLs
|
|
56
|
+
unique: Show only one version per package (most recent)
|
|
57
|
+
"""
|
|
58
|
+
console.print("[cyan]Fetching available bundles...[/cyan]")
|
|
59
|
+
|
|
60
|
+
bundles = fetch_available_bundles()
|
|
61
|
+
|
|
62
|
+
if not bundles:
|
|
63
|
+
console.print("[yellow]No bundles found in registry.[/yellow]")
|
|
64
|
+
console.print("[dim]The registry may be empty or unreachable.[/dim]")
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
# If unique flag is set, keep only the most recent version per package
|
|
68
|
+
if unique:
|
|
69
|
+
unique_bundles = {}
|
|
70
|
+
for bundle in bundles:
|
|
71
|
+
base_name = bundle.get('name', 'unknown')
|
|
72
|
+
# Keep the one with the most recent timestamp
|
|
73
|
+
if base_name not in unique_bundles:
|
|
74
|
+
unique_bundles[base_name] = bundle
|
|
75
|
+
else:
|
|
76
|
+
# Compare timestamps (generated_at field)
|
|
77
|
+
current_time = bundle.get('generated_at', '')
|
|
78
|
+
existing_time = unique_bundles[base_name].get('generated_at', '')
|
|
79
|
+
if current_time > existing_time:
|
|
80
|
+
unique_bundles[base_name] = bundle
|
|
81
|
+
bundles = list(unique_bundles.values())
|
|
82
|
+
|
|
83
|
+
# Create table
|
|
84
|
+
table = Table(show_header=True, header_style="bold magenta", title="Available Bundles")
|
|
85
|
+
table.add_column("Bundle Name", style="cyan", no_wrap=True)
|
|
86
|
+
table.add_column("Repository", style="dim")
|
|
87
|
+
table.add_column("Version", style="green")
|
|
88
|
+
table.add_column("Size", justify="right")
|
|
89
|
+
table.add_column("Source", style="yellow")
|
|
90
|
+
|
|
91
|
+
if verbose:
|
|
92
|
+
table.add_column("Download URL", style="blue", no_wrap=False)
|
|
93
|
+
|
|
94
|
+
# Sort by full_name to group versions together
|
|
95
|
+
bundles.sort(key=lambda b: (b.get('name', ''), b.get('full_name', '')))
|
|
96
|
+
|
|
97
|
+
for bundle in bundles:
|
|
98
|
+
# Use full_name for display (includes version info)
|
|
99
|
+
display_name = bundle.get('full_name', bundle.get('name', 'unknown'))
|
|
100
|
+
repo = bundle.get('repo', 'unknown')
|
|
101
|
+
version = bundle.get('version', bundle.get('tag', 'latest'))
|
|
102
|
+
size = bundle.get('size', 'unknown')
|
|
103
|
+
source = bundle.get('source', 'unknown')
|
|
104
|
+
|
|
105
|
+
if verbose:
|
|
106
|
+
download_url = bundle.get('download_url', 'N/A')
|
|
107
|
+
table.add_row(display_name, repo, version, size, source, download_url)
|
|
108
|
+
else:
|
|
109
|
+
table.add_row(display_name, repo, version, size, source)
|
|
110
|
+
|
|
111
|
+
console.print(table)
|
|
112
|
+
console.print(f"\n[dim]Total bundles: {len(bundles)}[/dim]")
|
|
113
|
+
if unique:
|
|
114
|
+
console.print("[dim]Showing only most recent version per package. Use without --unique to see all versions.[/dim]")
|
|
115
|
+
else:
|
|
116
|
+
console.print("[dim]Use --unique to show only one version per package[/dim]")
|
|
117
|
+
console.print("[dim]Use 'cgc registry download <name>' to download a bundle[/dim]")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def search_bundles(query: str):
|
|
121
|
+
"""Search for bundles matching the query."""
|
|
122
|
+
console.print(f"[cyan]Searching for '{query}'...[/cyan]")
|
|
123
|
+
|
|
124
|
+
bundles = fetch_available_bundles()
|
|
125
|
+
|
|
126
|
+
if not bundles:
|
|
127
|
+
console.print("[yellow]No bundles found in registry.[/yellow]")
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
# Filter bundles
|
|
131
|
+
query_lower = query.lower()
|
|
132
|
+
matching_bundles = [
|
|
133
|
+
b for b in bundles
|
|
134
|
+
if query_lower in b.get('name', '').lower() or
|
|
135
|
+
query_lower in b.get('full_name', '').lower() or
|
|
136
|
+
query_lower in b.get('repo', '').lower() or
|
|
137
|
+
query_lower in b.get('description', '').lower()
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
if not matching_bundles:
|
|
141
|
+
console.print(f"[yellow]No bundles found matching '{query}'[/yellow]")
|
|
142
|
+
console.print("[dim]Try a different search term or use 'cgc registry list' to see all bundles[/dim]")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
# Create table
|
|
146
|
+
table = Table(show_header=True, header_style="bold magenta", title=f"Search Results for '{query}'")
|
|
147
|
+
table.add_column("Name", style="cyan")
|
|
148
|
+
table.add_column("Repository", style="dim")
|
|
149
|
+
table.add_column("Version", style="green")
|
|
150
|
+
table.add_column("Size", justify="right")
|
|
151
|
+
|
|
152
|
+
for bundle in matching_bundles:
|
|
153
|
+
name = bundle.get('name', 'unknown')
|
|
154
|
+
repo = bundle.get('repo', 'unknown')
|
|
155
|
+
version = bundle.get('version', bundle.get('tag', 'latest'))
|
|
156
|
+
size = bundle.get('size', 'unknown')
|
|
157
|
+
table.add_row(name, repo, version, size)
|
|
158
|
+
|
|
159
|
+
console.print(table)
|
|
160
|
+
console.print(f"\n[dim]Found {len(matching_bundles)} matching bundle(s)[/dim]")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def download_bundle(name: str, output_dir: Optional[str] = None, auto_load: bool = False):
|
|
164
|
+
"""
|
|
165
|
+
Download a bundle from the registry.
|
|
166
|
+
|
|
167
|
+
Supports both full names (e.g., 'python-bitcoin-utils-main-61d1969')
|
|
168
|
+
and base names (e.g., 'python-bitcoin-utils' - picks most recent version).
|
|
169
|
+
"""
|
|
170
|
+
console.print(f"[cyan]Looking for bundle '{name}'...[/cyan]")
|
|
171
|
+
|
|
172
|
+
bundles = fetch_available_bundles()
|
|
173
|
+
|
|
174
|
+
if not bundles:
|
|
175
|
+
console.print("[bold red]Could not fetch bundle registry.[/bold red]")
|
|
176
|
+
raise typer.Exit(code=1)
|
|
177
|
+
|
|
178
|
+
# Strategy 1: Try exact match on full_name (with version)
|
|
179
|
+
bundle = None
|
|
180
|
+
for b in bundles:
|
|
181
|
+
if b.get('full_name', '').lower() == name.lower():
|
|
182
|
+
bundle = b
|
|
183
|
+
console.print(f"[dim]Found exact match: {b.get('full_name')}[/dim]")
|
|
184
|
+
break
|
|
185
|
+
|
|
186
|
+
# Strategy 2: If no exact match, try matching base package name
|
|
187
|
+
# and pick the most recent version
|
|
188
|
+
if not bundle:
|
|
189
|
+
matching_bundles = []
|
|
190
|
+
for b in bundles:
|
|
191
|
+
if b.get('name', '').lower() == name.lower():
|
|
192
|
+
matching_bundles.append(b)
|
|
193
|
+
|
|
194
|
+
if matching_bundles:
|
|
195
|
+
# Sort by timestamp and pick the most recent
|
|
196
|
+
matching_bundles.sort(key=lambda x: x.get('generated_at', ''), reverse=True)
|
|
197
|
+
bundle = matching_bundles[0]
|
|
198
|
+
|
|
199
|
+
console.print(f"[yellow]Multiple versions found for '{name}'. Using most recent:[/yellow]")
|
|
200
|
+
console.print(f"[cyan] → {bundle.get('full_name')}[/cyan]")
|
|
201
|
+
|
|
202
|
+
if len(matching_bundles) > 1:
|
|
203
|
+
console.print(f"\n[dim]Other available versions:[/dim]")
|
|
204
|
+
for b in matching_bundles[1:4]: # Show up to 3 alternatives
|
|
205
|
+
console.print(f"[dim] • {b.get('full_name')}[/dim]")
|
|
206
|
+
if len(matching_bundles) > 4:
|
|
207
|
+
console.print(f"[dim] ... and {len(matching_bundles) - 4} more[/dim]")
|
|
208
|
+
console.print()
|
|
209
|
+
|
|
210
|
+
# Strategy 3: No match found - show suggestions
|
|
211
|
+
if not bundle:
|
|
212
|
+
# Find bundles with similar base names
|
|
213
|
+
suggestions = []
|
|
214
|
+
name_lower = name.lower()
|
|
215
|
+
for b in bundles:
|
|
216
|
+
base_name = b.get('name', '').lower()
|
|
217
|
+
full_name = b.get('full_name', '').lower()
|
|
218
|
+
|
|
219
|
+
# Fuzzy matching: check if search term is in base or full name
|
|
220
|
+
if name_lower in base_name or name_lower in full_name:
|
|
221
|
+
suggestions.append(b.get('full_name', b.get('name', 'unknown')))
|
|
222
|
+
|
|
223
|
+
console.print(f"[bold red]Bundle '{name}' not found in registry.[/bold red]")
|
|
224
|
+
|
|
225
|
+
if suggestions:
|
|
226
|
+
console.print("\n[yellow]Did you mean one of these?[/yellow]")
|
|
227
|
+
for suggestion in suggestions[:5]: # Show top 5
|
|
228
|
+
console.print(f" • {suggestion}")
|
|
229
|
+
|
|
230
|
+
console.print("\n[dim]Use 'cgc registry list' to see all available bundles[/dim]")
|
|
231
|
+
raise typer.Exit(code=1)
|
|
232
|
+
|
|
233
|
+
# Get download URL
|
|
234
|
+
download_url = bundle.get('download_url')
|
|
235
|
+
if not download_url:
|
|
236
|
+
console.print(f"[bold red]No download URL found for bundle '{name}'[/bold red]")
|
|
237
|
+
raise typer.Exit(code=1)
|
|
238
|
+
|
|
239
|
+
# Determine output path
|
|
240
|
+
bundle_filename = bundle.get('bundle_name', f"{name}.cgc")
|
|
241
|
+
if output_dir:
|
|
242
|
+
output_path = Path(output_dir) / bundle_filename
|
|
243
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
244
|
+
else:
|
|
245
|
+
output_path = Path.cwd() / bundle_filename
|
|
246
|
+
|
|
247
|
+
# Check if already exists
|
|
248
|
+
if output_path.exists():
|
|
249
|
+
console.print(f"[yellow]Bundle already exists: {output_path}[/yellow]")
|
|
250
|
+
if not typer.confirm("Overwrite?", default=False):
|
|
251
|
+
console.print("[yellow]Download cancelled[/yellow]")
|
|
252
|
+
if auto_load:
|
|
253
|
+
console.print(f"[cyan]Using existing bundle for loading...[/cyan]")
|
|
254
|
+
return str(output_path)
|
|
255
|
+
return
|
|
256
|
+
output_path.unlink()
|
|
257
|
+
|
|
258
|
+
# Download with progress bar
|
|
259
|
+
try:
|
|
260
|
+
console.print(f"[cyan]Downloading {bundle_filename}...[/cyan]")
|
|
261
|
+
console.print(f"[dim]From: {download_url}[/dim]")
|
|
262
|
+
|
|
263
|
+
response = requests.get(download_url, stream=True, timeout=30)
|
|
264
|
+
response.raise_for_status()
|
|
265
|
+
|
|
266
|
+
total_size = int(response.headers.get('content-length', 0))
|
|
267
|
+
|
|
268
|
+
with Progress(
|
|
269
|
+
SpinnerColumn(),
|
|
270
|
+
TextColumn("[progress.description]{task.description}"),
|
|
271
|
+
console=console
|
|
272
|
+
) as progress:
|
|
273
|
+
task = progress.add_task(f"Downloading {bundle.get('size', 'unknown')}...", total=total_size)
|
|
274
|
+
|
|
275
|
+
with open(output_path, 'wb') as f:
|
|
276
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
277
|
+
if chunk:
|
|
278
|
+
f.write(chunk)
|
|
279
|
+
progress.update(task, advance=len(chunk))
|
|
280
|
+
|
|
281
|
+
console.print(f"[bold green]✓ Downloaded successfully: {output_path}[/bold green]")
|
|
282
|
+
|
|
283
|
+
if auto_load:
|
|
284
|
+
return str(output_path)
|
|
285
|
+
else:
|
|
286
|
+
console.print(f"[dim]Load with: cgc load {output_path}[/dim]")
|
|
287
|
+
|
|
288
|
+
except requests.exceptions.RequestException as e:
|
|
289
|
+
console.print(f"[bold red]Download failed: {e}[/bold red]")
|
|
290
|
+
if output_path.exists():
|
|
291
|
+
output_path.unlink()
|
|
292
|
+
raise typer.Exit(code=1)
|
|
293
|
+
except Exception as e:
|
|
294
|
+
console.print(f"[bold red]Error: {e}[/bold red]")
|
|
295
|
+
if output_path.exists():
|
|
296
|
+
output_path.unlink()
|
|
297
|
+
raise typer.Exit(code=1)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def request_bundle(repo_url: str, wait: bool = False):
|
|
301
|
+
"""Request on-demand generation of a bundle."""
|
|
302
|
+
console.print(f"[cyan]Requesting bundle generation for: {repo_url}[/cyan]")
|
|
303
|
+
|
|
304
|
+
# Validate GitHub URL
|
|
305
|
+
if not repo_url.startswith('https://github.com/'):
|
|
306
|
+
console.print("[bold red]Invalid GitHub URL. Must start with 'https://github.com/'[/bold red]")
|
|
307
|
+
raise typer.Exit(code=1)
|
|
308
|
+
|
|
309
|
+
# For now, provide instructions to use the website
|
|
310
|
+
# In the future, this could trigger the workflow via GitHub API
|
|
311
|
+
console.print("\n[yellow]Note: Bundle generation requires GitHub authentication.[/yellow]")
|
|
312
|
+
console.print("[cyan]Please use one of these methods:[/cyan]\n")
|
|
313
|
+
|
|
314
|
+
console.print("1. [bold]Via Website (Recommended):[/bold]")
|
|
315
|
+
console.print(f" Visit: https://codegraphcontext.vercel.app")
|
|
316
|
+
console.print(f" Enter: {repo_url}")
|
|
317
|
+
console.print(f" Click 'Generate Bundle'\n")
|
|
318
|
+
|
|
319
|
+
console.print("2. [bold]Via GitHub Actions (Manual):[/bold]")
|
|
320
|
+
console.print(f" Go to: https://github.com/{GITHUB_ORG}/{GITHUB_REPO}/actions")
|
|
321
|
+
console.print(f" Select: 'Generate Bundle On-Demand'")
|
|
322
|
+
console.print(f" Click: 'Run workflow'")
|
|
323
|
+
console.print(f" Enter: {repo_url}\n")
|
|
324
|
+
|
|
325
|
+
console.print("[dim]Bundle generation typically takes 5-10 minutes.[/dim]")
|
|
326
|
+
console.print("[dim]Use 'cgc registry list' to check when it's available.[/dim]")
|
|
327
|
+
|
|
328
|
+
if wait:
|
|
329
|
+
console.print("\n[yellow]Note: Automatic waiting not yet implemented.[/yellow]")
|
|
330
|
+
console.print("[dim]Please check back in 5-10 minutes and use 'cgc registry download <name>'[/dim]")
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def load_bundle_command(bundle_name: str, clear_existing: bool = False):
|
|
334
|
+
"""
|
|
335
|
+
Load a bundle (for MCP tool integration).
|
|
336
|
+
|
|
337
|
+
This is a wrapper around download_bundle that returns structured data
|
|
338
|
+
instead of using console output and typer.Exit.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
bundle_name: Name of the bundle to load
|
|
342
|
+
clear_existing: Whether to clear existing data before loading
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
Tuple of (success: bool, message: str, stats: dict)
|
|
346
|
+
"""
|
|
347
|
+
from pathlib import Path
|
|
348
|
+
from .cli_helpers import _initialize_services
|
|
349
|
+
from ..core.cgc_bundle import CGCBundle
|
|
350
|
+
|
|
351
|
+
try:
|
|
352
|
+
# Initialize services
|
|
353
|
+
services = _initialize_services()
|
|
354
|
+
if not all(services):
|
|
355
|
+
return (False, "Failed to initialize database services", {})
|
|
356
|
+
|
|
357
|
+
db_manager, _, _ = services
|
|
358
|
+
|
|
359
|
+
# Check if bundle exists locally
|
|
360
|
+
bundle_path = Path(bundle_name)
|
|
361
|
+
if not bundle_path.exists():
|
|
362
|
+
# Try to download from registry
|
|
363
|
+
try:
|
|
364
|
+
download_bundle(bundle_name, output_dir=None, auto_load=False)
|
|
365
|
+
# After download, the file should exist
|
|
366
|
+
if not bundle_path.exists():
|
|
367
|
+
# Try with .cgc extension
|
|
368
|
+
bundle_path = Path(f"{bundle_name}.cgc")
|
|
369
|
+
if not bundle_path.exists():
|
|
370
|
+
return (False, f"Bundle not found: {bundle_name}", {})
|
|
371
|
+
except Exception as e:
|
|
372
|
+
return (False, f"Failed to download bundle: {str(e)}", {})
|
|
373
|
+
|
|
374
|
+
# Load the bundle
|
|
375
|
+
bundle = CGCBundle(db_manager)
|
|
376
|
+
success, message = bundle.import_from_bundle(
|
|
377
|
+
bundle_path=bundle_path,
|
|
378
|
+
clear_existing=clear_existing
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if success:
|
|
382
|
+
# Extract stats from message if available
|
|
383
|
+
stats = {}
|
|
384
|
+
if "Nodes:" in message and "Edges:" in message:
|
|
385
|
+
try:
|
|
386
|
+
parts = message.split("|")
|
|
387
|
+
for part in parts:
|
|
388
|
+
if "Nodes:" in part:
|
|
389
|
+
stats["nodes"] = int(part.split(":")[1].strip().replace(",", ""))
|
|
390
|
+
elif "Edges:" in part:
|
|
391
|
+
stats["edges"] = int(part.split(":")[1].strip().replace(",", ""))
|
|
392
|
+
except:
|
|
393
|
+
pass
|
|
394
|
+
|
|
395
|
+
return (True, message, stats)
|
|
396
|
+
else:
|
|
397
|
+
return (False, message, {})
|
|
398
|
+
|
|
399
|
+
except Exception as e:
|
|
400
|
+
return (False, f"Error loading bundle: {str(e)}", {})
|
|
401
|
+
finally:
|
|
402
|
+
if 'db_manager' in locals():
|
|
403
|
+
db_manager.close_driver()
|
|
404
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# src/codegraphcontext/cli/setup_macos.py
|
|
2
|
+
import platform
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
def _has_brew(run_command, console) -> bool:
|
|
7
|
+
return run_command(["which", "brew"], console, check=False) is not None
|
|
8
|
+
|
|
9
|
+
def _brew_install_neo4j(run_command, console) -> str:
|
|
10
|
+
if run_command(["brew", "install", "neo4j@5"], console, check=False):
|
|
11
|
+
return "neo4j@5"
|
|
12
|
+
if run_command(["brew", "install", "neo4j"], console, check=False):
|
|
13
|
+
return "neo4j"
|
|
14
|
+
return ""
|
|
15
|
+
|
|
16
|
+
def _brew_start(service: str, run_command, console) -> bool:
|
|
17
|
+
return run_command(["brew", "services", "start", service], console, check=False) is not None
|
|
18
|
+
|
|
19
|
+
def _set_initial_password(new_pw: str, run_command, console) -> bool:
|
|
20
|
+
cmd = [
|
|
21
|
+
"cypher-shell",
|
|
22
|
+
"-u", "neo4j",
|
|
23
|
+
"-p", "neo4j",
|
|
24
|
+
f"ALTER CURRENT USER SET PASSWORD FROM 'neo4j' TO '{new_pw}'",
|
|
25
|
+
]
|
|
26
|
+
return run_command(cmd, console, check=False) is not None
|
|
27
|
+
|
|
28
|
+
def setup_macos_binary(console, prompt, run_command, _save_neo4j_credentials):
|
|
29
|
+
"""Automates Neo4j install & config on macOS via Homebrew."""
|
|
30
|
+
os_name = platform.system()
|
|
31
|
+
console.print(f"Detected Operating System: [bold yellow]{os_name}[/bold yellow]")
|
|
32
|
+
|
|
33
|
+
if os_name != "Darwin":
|
|
34
|
+
console.print("[yellow]This installer is for macOS only.[/yellow]")
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
console.print("[bold]Starting automated Neo4j installation for macOS.[/bold]")
|
|
38
|
+
|
|
39
|
+
if not prompt([{
|
|
40
|
+
"type": "confirm",
|
|
41
|
+
"message": "Proceed with Homebrew-based install of Neo4j?",
|
|
42
|
+
"name": "proceed",
|
|
43
|
+
"default": True
|
|
44
|
+
}]).get("proceed"):
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
console.print("\n[bold]Step: Checking for Homebrew...[/bold]")
|
|
48
|
+
if not _has_brew(run_command, console):
|
|
49
|
+
console.print(
|
|
50
|
+
"[bold red]Homebrew not found.[/bold red] "
|
|
51
|
+
"Install from [bold blue]https://brew.sh[/bold blue] and re-run this setup."
|
|
52
|
+
)
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
console.print("\n[bold]Step: Installing Neo4j via Homebrew...[/bold]")
|
|
56
|
+
service = _brew_install_neo4j(run_command, console)
|
|
57
|
+
if not service:
|
|
58
|
+
console.print("[bold red]Failed to install Neo4j via Homebrew.[/bold red]")
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
console.print(f"\n[bold]Step: Starting Neo4j service ({service})...[/bold]")
|
|
62
|
+
if not _brew_start(service, run_command, console):
|
|
63
|
+
console.print("[bold red]Failed to start Neo4j with brew services.[/bold red]")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
while True:
|
|
67
|
+
answers = prompt([
|
|
68
|
+
{"type": "password", "message": "Enter a new password for Neo4j:", "name": "pw"},
|
|
69
|
+
{"type": "password", "message": "Confirm the new password:", "name": "pw2"},
|
|
70
|
+
]) or {}
|
|
71
|
+
if not answers:
|
|
72
|
+
return
|
|
73
|
+
pw, pw2 = answers.get("pw"), answers.get("pw2")
|
|
74
|
+
if pw and pw == pw2:
|
|
75
|
+
new_password = pw
|
|
76
|
+
break
|
|
77
|
+
console.print("[red]Passwords do not match or are empty. Please try again.[/red]")
|
|
78
|
+
|
|
79
|
+
console.print("\n[yellow]Waiting 10 seconds for Neo4j to finish starting...[/yellow]")
|
|
80
|
+
time.sleep(10)
|
|
81
|
+
|
|
82
|
+
console.print("\n[bold]Step: Setting initial password with cypher-shell...[/bold]")
|
|
83
|
+
if not _set_initial_password(new_password, run_command, console):
|
|
84
|
+
console.print(
|
|
85
|
+
"[bold red]Failed to set the initial password.[/bold red]\n"
|
|
86
|
+
"Try manually:\n"
|
|
87
|
+
" cypher-shell -u neo4j -p neo4j \"ALTER CURRENT USER SET PASSWORD FROM 'neo4j' TO '<your_pw>'\""
|
|
88
|
+
)
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
creds = {"uri": "neo4j://localhost:7687", "username": "neo4j", "password": new_password}
|
|
92
|
+
_save_neo4j_credentials(creds)
|
|
93
|
+
|