codegraphcontext-rust 0.5.5__cp311-cp311-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.
Files changed (101) hide show
  1. codegraphcontext/__init__.py +2 -0
  2. codegraphcontext/__main__.py +5 -0
  3. codegraphcontext/_cgc_rust.cp311-win_amd64.pyd +0 -0
  4. codegraphcontext/cli/__init__.py +1 -0
  5. codegraphcontext/cli/cli_helpers.py +774 -0
  6. codegraphcontext/cli/config_manager.py +1051 -0
  7. codegraphcontext/cli/main.py +2386 -0
  8. codegraphcontext/cli/registry_commands.py +404 -0
  9. codegraphcontext/cli/setup_macos.py +93 -0
  10. codegraphcontext/cli/setup_wizard.py +991 -0
  11. codegraphcontext/cli/visualizer.py +50 -0
  12. codegraphcontext/core/__init__.py +165 -0
  13. codegraphcontext/core/bundle_registry.py +181 -0
  14. codegraphcontext/core/cgc_bundle.py +858 -0
  15. codegraphcontext/core/cgcignore.py +118 -0
  16. codegraphcontext/core/database.py +273 -0
  17. codegraphcontext/core/database_falkordb.py +480 -0
  18. codegraphcontext/core/database_falkordb_remote.py +199 -0
  19. codegraphcontext/core/database_kuzu.py +636 -0
  20. codegraphcontext/core/falkor_worker.py +132 -0
  21. codegraphcontext/core/jobs.py +132 -0
  22. codegraphcontext/core/watcher.py +260 -0
  23. codegraphcontext/prompts.py +125 -0
  24. codegraphcontext/server.py +471 -0
  25. codegraphcontext/tool_definitions.py +220 -0
  26. codegraphcontext/tools/__init__.py +0 -0
  27. codegraphcontext/tools/advanced_language_query_tool.py +103 -0
  28. codegraphcontext/tools/code_finder.py +1160 -0
  29. codegraphcontext/tools/graph_builder.py +337 -0
  30. codegraphcontext/tools/handlers/analysis_handlers.py +114 -0
  31. codegraphcontext/tools/handlers/indexing_handlers.py +116 -0
  32. codegraphcontext/tools/handlers/management_handlers.py +339 -0
  33. codegraphcontext/tools/handlers/query_handlers.py +83 -0
  34. codegraphcontext/tools/handlers/watcher_handlers.py +83 -0
  35. codegraphcontext/tools/indexing/__init__.py +1 -0
  36. codegraphcontext/tools/indexing/constants.py +25 -0
  37. codegraphcontext/tools/indexing/discovery.py +64 -0
  38. codegraphcontext/tools/indexing/engine.py +109 -0
  39. codegraphcontext/tools/indexing/persistence/__init__.py +3 -0
  40. codegraphcontext/tools/indexing/persistence/parquet_loader.py +643 -0
  41. codegraphcontext/tools/indexing/persistence/writer.py +1114 -0
  42. codegraphcontext/tools/indexing/pipeline.py +171 -0
  43. codegraphcontext/tools/indexing/pre_scan.py +105 -0
  44. codegraphcontext/tools/indexing/resolution/__init__.py +8 -0
  45. codegraphcontext/tools/indexing/resolution/calls.py +253 -0
  46. codegraphcontext/tools/indexing/resolution/inheritance.py +91 -0
  47. codegraphcontext/tools/indexing/sanitize.py +41 -0
  48. codegraphcontext/tools/indexing/schema.py +79 -0
  49. codegraphcontext/tools/indexing/schema_contract.py +44 -0
  50. codegraphcontext/tools/indexing/scip_pipeline.py +140 -0
  51. codegraphcontext/tools/languages/c.py +563 -0
  52. codegraphcontext/tools/languages/cpp.py +616 -0
  53. codegraphcontext/tools/languages/csharp.py +551 -0
  54. codegraphcontext/tools/languages/dart.py +378 -0
  55. codegraphcontext/tools/languages/elixir.py +460 -0
  56. codegraphcontext/tools/languages/go.py +499 -0
  57. codegraphcontext/tools/languages/haskell.py +426 -0
  58. codegraphcontext/tools/languages/java.py +500 -0
  59. codegraphcontext/tools/languages/javascript.py +589 -0
  60. codegraphcontext/tools/languages/kotlin.py +640 -0
  61. codegraphcontext/tools/languages/perl.py +260 -0
  62. codegraphcontext/tools/languages/php.py +519 -0
  63. codegraphcontext/tools/languages/python.py +576 -0
  64. codegraphcontext/tools/languages/ruby.py +537 -0
  65. codegraphcontext/tools/languages/rust.py +295 -0
  66. codegraphcontext/tools/languages/scala.py +519 -0
  67. codegraphcontext/tools/languages/swift.py +491 -0
  68. codegraphcontext/tools/languages/typescript.py +575 -0
  69. codegraphcontext/tools/languages/typescriptjsx.py +151 -0
  70. codegraphcontext/tools/package_resolver.py +472 -0
  71. codegraphcontext/tools/query_tool_languages/c_toolkit.py +5 -0
  72. codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +87 -0
  73. codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +5 -0
  74. codegraphcontext/tools/query_tool_languages/dart_toolkit.py +5 -0
  75. codegraphcontext/tools/query_tool_languages/go_toolkit.py +5 -0
  76. codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +5 -0
  77. codegraphcontext/tools/query_tool_languages/java_toolkit.py +5 -0
  78. codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +5 -0
  79. codegraphcontext/tools/query_tool_languages/perl_toolkit.py +5 -0
  80. codegraphcontext/tools/query_tool_languages/python_toolkit.py +5 -0
  81. codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +5 -0
  82. codegraphcontext/tools/query_tool_languages/rust_toolkit.py +5 -0
  83. codegraphcontext/tools/query_tool_languages/scala_toolkit.py +5 -0
  84. codegraphcontext/tools/query_tool_languages/swift_toolkit.py +5 -0
  85. codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +5 -0
  86. codegraphcontext/tools/scip_indexer.py +467 -0
  87. codegraphcontext/tools/scip_pb2.py +2455 -0
  88. codegraphcontext/tools/system.py +133 -0
  89. codegraphcontext/tools/tree_sitter_parser.py +103 -0
  90. codegraphcontext/utils/debug_log.py +90 -0
  91. codegraphcontext/utils/path_ignore.py +55 -0
  92. codegraphcontext/utils/repo_path.py +27 -0
  93. codegraphcontext/utils/tree_sitter_manager.py +285 -0
  94. codegraphcontext/utils/visualize_graph.py +0 -0
  95. codegraphcontext/viz/server.py +282 -0
  96. codegraphcontext_rust-0.5.5.dist-info/METADATA +537 -0
  97. codegraphcontext_rust-0.5.5.dist-info/RECORD +101 -0
  98. codegraphcontext_rust-0.5.5.dist-info/WHEEL +4 -0
  99. codegraphcontext_rust-0.5.5.dist-info/entry_points.txt +3 -0
  100. codegraphcontext_rust-0.5.5.dist-info/licenses/LICENSE +21 -0
  101. 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
+