This is page 19 of 22. Use http://codebase.md/shashankss1205/codegraphcontext?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .cgcignore
├── .github
│ ├── FUNDING.yml
│ └── workflows
│ ├── e2e-tests.yml
│ ├── post_discord_invite.yml
│ ├── test.yml
│ └── update-contributors.yml
├── .gitignore
├── CLI_Commands.md
├── CONTRIBUTING.md
├── contributors.md
├── docs
│ ├── docs
│ │ ├── architecture.md
│ │ ├── cli.md
│ │ ├── contributing_languages.md
│ │ ├── contributing.md
│ │ ├── cookbook.md
│ │ ├── core.md
│ │ ├── future_work.md
│ │ ├── images
│ │ │ ├── 1.png
│ │ │ ├── 11.png
│ │ │ ├── 12.png
│ │ │ ├── 13.png
│ │ │ ├── 14.png
│ │ │ ├── 16.png
│ │ │ ├── 19.png
│ │ │ ├── 2.png
│ │ │ ├── 20.png
│ │ │ ├── 21.png
│ │ │ ├── 22.png
│ │ │ ├── 23.png
│ │ │ ├── 24.png
│ │ │ ├── 26.png
│ │ │ ├── 28.png
│ │ │ ├── 29.png
│ │ │ ├── 3.png
│ │ │ ├── 30.png
│ │ │ ├── 31.png
│ │ │ ├── 32.png
│ │ │ ├── 33.png
│ │ │ ├── 34.png
│ │ │ ├── 35.png
│ │ │ ├── 36.png
│ │ │ ├── 38.png
│ │ │ ├── 39.png
│ │ │ ├── 4.png
│ │ │ ├── 40.png
│ │ │ ├── 41.png
│ │ │ ├── 42.png
│ │ │ ├── 43.png
│ │ │ ├── 44.png
│ │ │ ├── 5.png
│ │ │ ├── 6.png
│ │ │ ├── 7.png
│ │ │ ├── 8.png
│ │ │ ├── 9.png
│ │ │ ├── Indexing.gif
│ │ │ ├── tool_images
│ │ │ │ ├── 1.png
│ │ │ │ ├── 2.png
│ │ │ │ └── 3.png
│ │ │ └── Usecase.gif
│ │ ├── index.md
│ │ ├── installation.md
│ │ ├── license.md
│ │ ├── server.md
│ │ ├── tools.md
│ │ ├── troubleshooting.md
│ │ ├── use_cases.md
│ │ └── watching.md
│ ├── mkdocs.yml
│ └── site
│ ├── 404.html
│ ├── architecture
│ │ └── index.html
│ ├── assets
│ │ ├── images
│ │ │ └── favicon.png
│ │ ├── javascripts
│ │ │ ├── bundle.79ae519e.min.js
│ │ │ ├── bundle.79ae519e.min.js.map
│ │ │ ├── lunr
│ │ │ │ ├── min
│ │ │ │ │ ├── lunr.ar.min.js
│ │ │ │ │ ├── lunr.da.min.js
│ │ │ │ │ ├── lunr.de.min.js
│ │ │ │ │ ├── lunr.du.min.js
│ │ │ │ │ ├── lunr.el.min.js
│ │ │ │ │ ├── lunr.es.min.js
│ │ │ │ │ ├── lunr.fi.min.js
│ │ │ │ │ ├── lunr.fr.min.js
│ │ │ │ │ ├── lunr.he.min.js
│ │ │ │ │ ├── lunr.hi.min.js
│ │ │ │ │ ├── lunr.hu.min.js
│ │ │ │ │ ├── lunr.hy.min.js
│ │ │ │ │ ├── lunr.it.min.js
│ │ │ │ │ ├── lunr.ja.min.js
│ │ │ │ │ ├── lunr.jp.min.js
│ │ │ │ │ ├── lunr.kn.min.js
│ │ │ │ │ ├── lunr.ko.min.js
│ │ │ │ │ ├── lunr.multi.min.js
│ │ │ │ │ ├── lunr.nl.min.js
│ │ │ │ │ ├── lunr.no.min.js
│ │ │ │ │ ├── lunr.pt.min.js
│ │ │ │ │ ├── lunr.ro.min.js
│ │ │ │ │ ├── lunr.ru.min.js
│ │ │ │ │ ├── lunr.sa.min.js
│ │ │ │ │ ├── lunr.stemmer.support.min.js
│ │ │ │ │ ├── lunr.sv.min.js
│ │ │ │ │ ├── lunr.ta.min.js
│ │ │ │ │ ├── lunr.te.min.js
│ │ │ │ │ ├── lunr.th.min.js
│ │ │ │ │ ├── lunr.tr.min.js
│ │ │ │ │ ├── lunr.vi.min.js
│ │ │ │ │ └── lunr.zh.min.js
│ │ │ │ ├── tinyseg.js
│ │ │ │ └── wordcut.js
│ │ │ └── workers
│ │ │ ├── search.2c215733.min.js
│ │ │ └── search.2c215733.min.js.map
│ │ └── stylesheets
│ │ ├── main.484c7ddc.min.css
│ │ ├── main.484c7ddc.min.css.map
│ │ ├── palette.ab4e12ef.min.css
│ │ └── palette.ab4e12ef.min.css.map
│ ├── cli
│ │ └── index.html
│ ├── contributing
│ │ └── index.html
│ ├── contributing_languages
│ │ └── index.html
│ ├── cookbook
│ │ └── index.html
│ ├── core
│ │ └── index.html
│ ├── future_work
│ │ └── index.html
│ ├── images
│ │ ├── 1.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 13.png
│ │ ├── 14.png
│ │ ├── 16.png
│ │ ├── 19.png
│ │ ├── 2.png
│ │ ├── 20.png
│ │ ├── 21.png
│ │ ├── 22.png
│ │ ├── 23.png
│ │ ├── 24.png
│ │ ├── 26.png
│ │ ├── 28.png
│ │ ├── 29.png
│ │ ├── 3.png
│ │ ├── 30.png
│ │ ├── 31.png
│ │ ├── 32.png
│ │ ├── 33.png
│ │ ├── 34.png
│ │ ├── 35.png
│ │ ├── 36.png
│ │ ├── 38.png
│ │ ├── 39.png
│ │ ├── 4.png
│ │ ├── 40.png
│ │ ├── 41.png
│ │ ├── 42.png
│ │ ├── 43.png
│ │ ├── 44.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ ├── 9.png
│ │ ├── Indexing.gif
│ │ ├── tool_images
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ └── 3.png
│ │ └── Usecase.gif
│ ├── index.html
│ ├── installation
│ │ └── index.html
│ ├── license
│ │ └── index.html
│ ├── search
│ │ └── search_index.json
│ ├── server
│ │ └── index.html
│ ├── sitemap.xml
│ ├── sitemap.xml.gz
│ ├── tools
│ │ └── index.html
│ ├── troubleshooting
│ │ └── index.html
│ ├── use_cases
│ │ └── index.html
│ └── watching
│ └── index.html
├── funding.json
├── images
│ ├── 1.png
│ ├── 11.png
│ ├── 12.png
│ ├── 13.png
│ ├── 14.png
│ ├── 16.png
│ ├── 19.png
│ ├── 2.png
│ ├── 20.png
│ ├── 21.png
│ ├── 22.png
│ ├── 23.png
│ ├── 24.png
│ ├── 26.png
│ ├── 28.png
│ ├── 29.png
│ ├── 3.png
│ ├── 30.png
│ ├── 31.png
│ ├── 32.png
│ ├── 33.png
│ ├── 34.png
│ ├── 35.png
│ ├── 36.png
│ ├── 38.png
│ ├── 39.png
│ ├── 4.png
│ ├── 40.png
│ ├── 41.png
│ ├── 42.png
│ ├── 43.png
│ ├── 44.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ ├── 8.png
│ ├── 9.png
│ ├── Indexing.gif
│ ├── tool_images
│ │ ├── 1.png
│ │ ├── 2.png
│ │ └── 3.png
│ └── Usecase.gif
├── LICENSE
├── MANIFEST.in
├── organizer
│ ├── CONTRIBUTING_LANGUAGES.md
│ ├── cookbook.md
│ ├── docs.md
│ ├── language_specific_nodes.md
│ ├── Tools_Exploration.md
│ └── troubleshoot.md
├── pyproject.toml
├── README.md
├── scripts
│ ├── generate_lang_contributors.py
│ ├── post_install_fix.sh
│ ├── test_all_parsers.py
│ └── update_language_parsers.py
├── SECURITY.md
├── src
│ └── codegraphcontext
│ ├── __init__.py
│ ├── __main__.py
│ ├── cli
│ │ ├── __init__.py
│ │ ├── cli_helpers.py
│ │ ├── config_manager.py
│ │ ├── main.py
│ │ ├── setup_macos.py
│ │ └── setup_wizard.py
│ ├── core
│ │ ├── __init__.py
│ │ ├── database_falkordb.py
│ │ ├── database.py
│ │ ├── falkor_worker.py
│ │ ├── jobs.py
│ │ └── watcher.py
│ ├── prompts.py
│ ├── server.py
│ ├── tools
│ │ ├── __init__.py
│ │ ├── advanced_language_query_tool.py
│ │ ├── code_finder.py
│ │ ├── graph_builder.py
│ │ ├── languages
│ │ │ ├── c.py
│ │ │ ├── cpp.py
│ │ │ ├── csharp.py
│ │ │ ├── go.py
│ │ │ ├── java.py
│ │ │ ├── javascript.py
│ │ │ ├── kotlin.py
│ │ │ ├── php.py
│ │ │ ├── python.py
│ │ │ ├── ruby.py
│ │ │ ├── rust.py
│ │ │ ├── scala.py
│ │ │ ├── swift.py
│ │ │ ├── typescript.py
│ │ │ └── typescriptjsx.py
│ │ ├── package_resolver.py
│ │ ├── query_tool_languages
│ │ │ ├── c_toolkit.py
│ │ │ ├── cpp_toolkit.py
│ │ │ ├── csharp_toolkit.py
│ │ │ ├── go_toolkit.py
│ │ │ ├── java_toolkit.py
│ │ │ ├── javascript_toolkit.py
│ │ │ ├── python_toolkit.py
│ │ │ ├── ruby_toolkit.py
│ │ │ ├── rust_toolkit.py
│ │ │ ├── scala_toolkit.py
│ │ │ ├── swift_toolkit.py
│ │ │ └── typescript_toolkit.py
│ │ └── system.py
│ └── utils
│ ├── debug_log.py
│ └── tree_sitter_manager.py
├── tests
│ ├── __init__.py
│ ├── conftest.py
│ ├── sample_project
│ │ ├── advanced_calls.py
│ │ ├── advanced_classes.py
│ │ ├── advanced_classes2.py
│ │ ├── advanced_functions.py
│ │ ├── advanced_imports.py
│ │ ├── async_features.py
│ │ ├── callbacks_decorators.py
│ │ ├── circular1.py
│ │ ├── circular2.py
│ │ ├── class_instantiation.py
│ │ ├── cli_and_dunder.py
│ │ ├── complex_classes.py
│ │ ├── comprehensions_generators.py
│ │ ├── context_managers.py
│ │ ├── control_flow.py
│ │ ├── datatypes.py
│ │ ├── dynamic_dispatch.py
│ │ ├── dynamic_imports.py
│ │ ├── edge_cases
│ │ │ ├── comments_only.py
│ │ │ ├── docstring_only.py
│ │ │ ├── empty.py
│ │ │ ├── hardcoded_secrets.py
│ │ │ ├── long_functions.py
│ │ │ └── syntax_error.py
│ │ ├── function_chains.py
│ │ ├── generators.py
│ │ ├── import_reexports.py
│ │ ├── mapping_calls.py
│ │ ├── module_a.py
│ │ ├── module_b.py
│ │ ├── module_c
│ │ │ ├── __init__.py
│ │ │ ├── submodule1.py
│ │ │ └── submodule2.py
│ │ ├── namespace_pkg
│ │ │ └── ns_module.py
│ │ ├── pattern_matching.py
│ │ └── typing_examples.py
│ ├── sample_project_c
│ │ ├── cgc_sample
│ │ ├── include
│ │ │ ├── config.h
│ │ │ ├── math
│ │ │ │ └── vec.h
│ │ │ ├── module.h
│ │ │ ├── platform.h
│ │ │ └── util.h
│ │ ├── Makefile
│ │ ├── README.md
│ │ └── src
│ │ ├── main.c
│ │ ├── math
│ │ │ └── vec.c
│ │ ├── module.c
│ │ └── util.c
│ ├── sample_project_cpp
│ │ ├── class_features.cpp
│ │ ├── classes.cpp
│ │ ├── control_flow.cpp
│ │ ├── edge_cases.cpp
│ │ ├── enum_struct_union.cpp
│ │ ├── exceptions.cpp
│ │ ├── file_io.cpp
│ │ ├── function_chain.cpp
│ │ ├── function_chain.h
│ │ ├── function_types.cpp
│ │ ├── main.cpp
│ │ ├── main.exe
│ │ ├── namespaces.cpp
│ │ ├── raii_example.cpp
│ │ ├── README.md
│ │ ├── sample_project.exe
│ │ ├── stl_usage.cpp
│ │ ├── templates.cpp
│ │ └── types_variable_assignments.cpp
│ ├── sample_project_csharp
│ │ ├── README.md
│ │ └── src
│ │ └── Example.App
│ │ ├── Attributes
│ │ │ └── CustomAttributes.cs
│ │ ├── Example.App.csproj
│ │ ├── Models
│ │ │ ├── Person.cs
│ │ │ ├── Point.cs
│ │ │ ├── Role.cs
│ │ │ └── User.cs
│ │ ├── OuterClass.cs
│ │ ├── Program.cs
│ │ ├── Services
│ │ │ ├── GreetingService.cs
│ │ │ ├── IGreetingService.cs
│ │ │ └── LegacyService.cs
│ │ └── Utils
│ │ ├── CollectionHelper.cs
│ │ └── FileHelper.cs
│ ├── sample_project_go
│ │ ├── advanced_types.go
│ │ ├── basic_functions.go
│ │ ├── embedded_composition.go
│ │ ├── error_handling.go
│ │ ├── generics.go
│ │ ├── go.mod
│ │ ├── goroutines_channels.go
│ │ ├── interfaces.go
│ │ ├── packages_imports.go
│ │ ├── README.md
│ │ ├── structs_methods.go
│ │ └── util
│ │ └── helpers.go
│ ├── sample_project_java
│ │ ├── out
│ │ │ └── com
│ │ │ └── example
│ │ │ └── app
│ │ │ ├── annotations
│ │ │ │ └── Logged.class
│ │ │ ├── Main.class
│ │ │ ├── misc
│ │ │ │ ├── Outer.class
│ │ │ │ └── Outer$Inner.class
│ │ │ ├── model
│ │ │ │ ├── Role.class
│ │ │ │ └── User.class
│ │ │ ├── service
│ │ │ │ ├── AbstractGreeter.class
│ │ │ │ ├── GreetingService.class
│ │ │ │ └── impl
│ │ │ │ └── GreetingServiceImpl.class
│ │ │ └── util
│ │ │ ├── CollectionUtils.class
│ │ │ └── IOHelper.class
│ │ ├── README.md
│ │ ├── sources.txt
│ │ └── src
│ │ └── com
│ │ └── example
│ │ └── app
│ │ ├── annotations
│ │ │ └── Logged.java
│ │ ├── Main.java
│ │ ├── misc
│ │ │ └── Outer.java
│ │ ├── model
│ │ │ ├── Role.java
│ │ │ └── User.java
│ │ ├── service
│ │ │ ├── AbstractGreeter.java
│ │ │ ├── GreetingService.java
│ │ │ └── impl
│ │ │ └── GreetingServiceImpl.java
│ │ └── util
│ │ ├── CollectionUtils.java
│ │ └── IOHelper.java
│ ├── sample_project_javascript
│ │ ├── arrays.js
│ │ ├── asyncAwait.js
│ │ ├── classes.js
│ │ ├── dom.js
│ │ ├── errorHandling.js
│ │ ├── events.js
│ │ ├── exporter.js
│ │ ├── fetchAPI.js
│ │ ├── fixtures
│ │ │ └── js
│ │ │ └── accessors.js
│ │ ├── functions.js
│ │ ├── importer.js
│ │ ├── objects.js
│ │ ├── promises.js
│ │ ├── README.md
│ │ └── variables.js
│ ├── sample_project_kotlin
│ │ ├── AdvancedClasses.kt
│ │ ├── Annotations.kt
│ │ ├── Coroutines.kt
│ │ ├── EdgeCases.kt
│ │ ├── Functions.kt
│ │ ├── Main.kt
│ │ ├── Properties.kt
│ │ └── User.kt
│ ├── sample_project_misc
│ │ ├── index.html
│ │ ├── README.md
│ │ ├── styles.css
│ │ ├── tables.css
│ │ └── tables.html
│ ├── sample_project_php
│ │ ├── classes_objects.php
│ │ ├── database.php
│ │ ├── edgecases.php
│ │ ├── error_handling.php
│ │ ├── file_handling.php
│ │ ├── functions.php
│ │ ├── generators_iterators.php
│ │ ├── globals_superglobals.php
│ │ ├── Inheritance.php
│ │ ├── interface_traits.php
│ │ └── README.md
│ ├── sample_project_ruby
│ │ ├── class_example.rb
│ │ ├── enumerables.rb
│ │ ├── error_handling.rb
│ │ ├── file_io.rb
│ │ ├── inheritance_example.rb
│ │ ├── main.rb
│ │ ├── metaprogramming.rb
│ │ ├── mixins_example.rb
│ │ ├── module_example.rb
│ │ └── tests
│ │ ├── test_mixins.py
│ │ └── test_sample.rb
│ ├── sample_project_rust
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ └── src
│ │ ├── basic_functions.rs
│ │ ├── concurrency.rs
│ │ ├── error_handling.rs
│ │ ├── generics.rs
│ │ ├── iterators_closures.rs
│ │ ├── lib.rs
│ │ ├── lifetimes_references.rs
│ │ ├── modules.rs
│ │ ├── smart_pointers.rs
│ │ ├── structs_enums.rs
│ │ └── traits.rs
│ ├── sample_project_scala
│ │ ├── Animals.scala
│ │ ├── Complex.scala
│ │ ├── Functional.scala
│ │ ├── Geometry.scala
│ │ ├── Main.scala
│ │ ├── PackageObject.scala
│ │ ├── Script.sc
│ │ ├── Services.scala
│ │ ├── Shapes.scala
│ │ ├── Utils.scala
│ │ └── Variables.scala
│ ├── sample_project_swift
│ │ ├── Generics.swift
│ │ ├── Main.swift
│ │ ├── README.md
│ │ ├── Shapes.swift
│ │ ├── User.swift
│ │ └── Vehicles.swift
│ ├── sample_project_typescript
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── sample_tsx.tsx
│ │ ├── src
│ │ │ ├── advanced-types.ts
│ │ │ ├── async-promises.ts
│ │ │ ├── classes-inheritance.ts
│ │ │ ├── decorators-metadata.ts
│ │ │ ├── error-validation.ts
│ │ │ ├── functions-generics.ts
│ │ │ ├── index.ts
│ │ │ ├── modules-namespaces.ts
│ │ │ ├── types-interfaces.ts
│ │ │ └── utilities-helpers.ts
│ │ └── tsconfig.json
│ ├── test_cpp_parser.py
│ ├── test_database_validation.py
│ ├── test_end_to_end.py
│ ├── test_graph_indexing_js.py
│ ├── test_graph_indexing.py
│ ├── test_kotlin_parser.py
│ ├── test_swift_parser.py
│ ├── test_tree_sitter
│ │ ├── __init__.py
│ │ ├── class_instantiation.py
│ │ ├── complex_classes.py
│ │ └── test_file.py
│ ├── test_tree_sitter_manager.py
│ └── test_typescript_parser.py
├── visualize_graph.py
├── website
│ ├── .example.env
│ ├── .gitignore
│ ├── api
│ │ └── pypi.ts
│ ├── bun.lockb
│ ├── components.json
│ ├── eslint.config.js
│ ├── index.html
│ ├── package-lock.json
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ │ ├── favicon.ico
│ │ ├── placeholder.svg
│ │ └── robots.txt
│ ├── README.md
│ ├── src
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── assets
│ │ │ ├── function-calls.png
│ │ │ ├── graph-total.png
│ │ │ ├── hero-graph.jpg
│ │ │ └── hierarchy.png
│ │ ├── components
│ │ │ ├── ComparisonTable.tsx
│ │ │ ├── CookbookSection.tsx
│ │ │ ├── DemoSection.tsx
│ │ │ ├── ExamplesSection.tsx
│ │ │ ├── FeaturesSection.tsx
│ │ │ ├── Footer.tsx
│ │ │ ├── HeroSection.tsx
│ │ │ ├── InstallationSection.tsx
│ │ │ ├── MoveToTop.tsx
│ │ │ ├── ShowDownloads.tsx
│ │ │ ├── ShowStarGraph.tsx
│ │ │ ├── SocialMentionsTimeline.tsx
│ │ │ ├── TestimonialSection.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── ThemeToggle.tsx
│ │ │ └── ui
│ │ │ ├── accordion.tsx
│ │ │ ├── alert-dialog.tsx
│ │ │ ├── alert.tsx
│ │ │ ├── aspect-ratio.tsx
│ │ │ ├── avatar.tsx
│ │ │ ├── badge.tsx
│ │ │ ├── breadcrumb.tsx
│ │ │ ├── button.tsx
│ │ │ ├── calendar.tsx
│ │ │ ├── card.tsx
│ │ │ ├── carousel.tsx
│ │ │ ├── chart.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── collapsible.tsx
│ │ │ ├── command.tsx
│ │ │ ├── context-menu.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── drawer.tsx
│ │ │ ├── dropdown-menu.tsx
│ │ │ ├── form.tsx
│ │ │ ├── hover-card.tsx
│ │ │ ├── input-otp.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── menubar.tsx
│ │ │ ├── navigation-menu.tsx
│ │ │ ├── orbiting-circles.tsx
│ │ │ ├── pagination.tsx
│ │ │ ├── popover.tsx
│ │ │ ├── progress.tsx
│ │ │ ├── radio-group.tsx
│ │ │ ├── resizable.tsx
│ │ │ ├── scroll-area.tsx
│ │ │ ├── select.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── sheet.tsx
│ │ │ ├── sidebar.tsx
│ │ │ ├── skeleton.tsx
│ │ │ ├── slider.tsx
│ │ │ ├── sonner.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── table.tsx
│ │ │ ├── tabs.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── toast.tsx
│ │ │ ├── toaster.tsx
│ │ │ ├── toggle-group.tsx
│ │ │ ├── toggle.tsx
│ │ │ ├── tooltip.tsx
│ │ │ └── use-toast.ts
│ │ ├── hooks
│ │ │ ├── use-mobile.tsx
│ │ │ └── use-toast.ts
│ │ ├── index.css
│ │ ├── lib
│ │ │ └── utils.ts
│ │ ├── main.tsx
│ │ ├── pages
│ │ │ ├── Index.tsx
│ │ │ └── NotFound.tsx
│ │ └── vite-env.d.ts
│ ├── tailwind.config.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ ├── vercel.json
│ └── vite.config.ts
└── windows_setup_guide.md
```
# Files
--------------------------------------------------------------------------------
/src/codegraphcontext/cli/main.py:
--------------------------------------------------------------------------------
```python
1 | # src/codegraphcontext/cli/main.py
2 | """
3 | This module defines the command-line interface (CLI) for the CodeGraphContext application.
4 | It uses the Typer library to create a user-friendly and well-documented CLI.
5 |
6 | Commands:
7 | - mcp setup: Runs an interactive wizard to configure the MCP client.
8 | - mcp start: Launches the main MCP server.
9 | - help: Displays help information.
10 | - version: Show the installed version.
11 | """
12 | import typer
13 | from rich.console import Console
14 | from rich.table import Table
15 | from rich import box
16 | from typing import Optional
17 | import asyncio
18 | import logging
19 | import json
20 | import os
21 | from pathlib import Path
22 | from dotenv import load_dotenv, find_dotenv, set_key
23 | from importlib.metadata import version as pkg_version, PackageNotFoundError
24 |
25 | from codegraphcontext.server import MCPServer
26 | from codegraphcontext.core.database import DatabaseManager
27 | from .setup_wizard import run_neo4j_setup_wizard, configure_mcp_client
28 | from . import config_manager
29 | # Import the new helper functions
30 | from .cli_helpers import (
31 | index_helper,
32 | add_package_helper,
33 | list_repos_helper,
34 | delete_helper,
35 | cypher_helper,
36 | visualize_helper,
37 | reindex_helper,
38 | clean_helper,
39 | stats_helper,
40 | _initialize_services,
41 | watch_helper,
42 | unwatch_helper,
43 | list_watching_helper,
44 | )
45 |
46 | # Set the log level for the noisy neo4j and asyncio logger to WARNING to keep the output clean.
47 | logging.getLogger("neo4j").setLevel(logging.WARNING)
48 | logging.getLogger("asyncio").setLevel(logging.WARNING)
49 |
50 | # Initialize the Typer app and Rich console for formatted output.
51 | app = typer.Typer(
52 | name="cgc",
53 | help="CodeGraphContext: An MCP server for AI-powered code analysis.",
54 | add_completion=True,
55 | )
56 | console = Console(stderr=True)
57 |
58 | # Configure basic logging for the application.
59 | logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
60 |
61 |
62 | def get_version() -> str:
63 | """
64 | Try to read version from the installed package metadata.
65 | Fallback to a dev version if not installed.
66 | """
67 | try:
68 | return pkg_version("codegraphcontext") # must match [project].name in pyproject.toml
69 | except PackageNotFoundError:
70 | return "0.0.0 (dev)"
71 |
72 |
73 | # Create MCP command group
74 | mcp_app = typer.Typer(help="MCP client configuration commands")
75 | app.add_typer(mcp_app, name="mcp")
76 |
77 | @mcp_app.command("setup")
78 | def mcp_setup():
79 | """
80 | Configure MCP Client (IDE/CLI Integration).
81 |
82 | Sets up CodeGraphContext integration with your IDE or CLI tool:
83 | - VS Code, Cursor, Windsurf
84 | - Claude Desktop, Gemini CLI
85 | - Cline, RooCode, Amazon Q Developer
86 |
87 | Works with FalkorDB by default (no database setup needed).
88 | """
89 | console.print("\n[bold cyan]MCP Client Setup[/bold cyan]")
90 | console.print("Configure your IDE or CLI tool to use CodeGraphContext.\n")
91 | configure_mcp_client()
92 |
93 | @mcp_app.command("start")
94 | def mcp_start():
95 | """
96 | Start the CodeGraphContext MCP server.
97 |
98 | Starts the server which listens for JSON-RPC requests from stdin.
99 | This is used by IDE integrations (VS Code, Cursor, etc.).
100 | """
101 | console.print("[bold green]Starting CodeGraphContext Server...[/bold green]")
102 | _load_credentials()
103 |
104 | server = None
105 | loop = asyncio.new_event_loop()
106 | asyncio.set_event_loop(loop)
107 | try:
108 | # Initialize and run the main server.
109 | server = MCPServer(loop=loop)
110 | loop.run_until_complete(server.run())
111 | except ValueError as e:
112 | # This typically happens if credentials are still not found after all checks.
113 | console.print(f"[bold red]Configuration Error:[/bold red] {e}")
114 | console.print("Please run `cgc neo4j setup` or use FalkorDB (default).")
115 | except KeyboardInterrupt:
116 | # Handle graceful shutdown on Ctrl+C.
117 | console.print("\n[bold yellow]Server stopped by user.[/bold yellow]")
118 | finally:
119 | # Ensure server and event loop are properly closed.
120 | if server:
121 | server.shutdown()
122 | loop.close()
123 |
124 | @mcp_app.command("tools")
125 | def mcp_tools():
126 | """
127 | List all available MCP tools.
128 |
129 | Shows all tools that can be called by AI assistants through the MCP interface.
130 | """
131 | _load_credentials()
132 | console.print("[bold green]Available MCP Tools:[/bold green]")
133 | try:
134 | # Instantiate the server to access the tool definitions.
135 | server = MCPServer()
136 | tools = server.tools.values()
137 |
138 | table = Table(show_header=True, header_style="bold magenta")
139 | table.add_column("Tool Name", style="dim", width=30)
140 | table.add_column("Description")
141 |
142 | for tool in sorted(tools, key=lambda t: t['name']):
143 | table.add_row(tool['name'], tool['description'])
144 |
145 | console.print(table)
146 |
147 | except ValueError as e:
148 | console.print(f"[bold red]Error loading tools:[/bold red] {e}")
149 | console.print("Please ensure your database is configured correctly.")
150 | except Exception as e:
151 | console.print(f"[bold red]An unexpected error occurred:[/bold red] {e}")
152 |
153 | # Abbreviation for mcp setup
154 | @app.command("m", rich_help_panel="Shortcuts")
155 | def mcp_setup_alias():
156 | """Shortcut for 'cgc mcp setup'"""
157 | mcp_setup()
158 |
159 |
160 | # Create Neo4j command group
161 | neo4j_app = typer.Typer(help="Neo4j database configuration commands")
162 | app.add_typer(neo4j_app, name="neo4j")
163 |
164 | @neo4j_app.command("setup")
165 | def neo4j_setup():
166 | """
167 | Configure Neo4j Database Connection.
168 |
169 | Choose from multiple setup options:
170 | - Local (Docker-based, recommended)
171 | - Local (Binary installation on Linux)
172 | - Hosted (Neo4j AuraDB or remote instance)
173 | - Connect to existing Neo4j instance
174 |
175 | Note: This is optional. CodeGraphContext works with FalkorDB by default.
176 | """
177 | console.print("\n[bold cyan]Neo4j Database Setup[/bold cyan]")
178 | console.print("Configure Neo4j database connection for CodeGraphContext.\n")
179 | run_neo4j_setup_wizard()
180 |
181 | # Abbreviation for neo4j setup
182 | @app.command("n", rich_help_panel="Shortcuts")
183 | def neo4j_setup_alias():
184 | """Shortcut for 'cgc neo4j setup'"""
185 | neo4j_setup()
186 |
187 |
188 |
189 | def _load_credentials():
190 | """
191 | Loads configuration and credentials from various sources into environment variables.
192 | Uses per-variable precedence - each variable is loaded from the highest priority source.
193 | Priority order (highest to lowest):
194 | 1. Local `mcp.json` env vars (highest - explicit MCP server config)
195 | 2. Local `.env` in project directory (high - project-specific overrides)
196 | 3. Global `~/.codegraphcontext/.env` (lowest - user defaults)
197 | """
198 | from dotenv import dotenv_values
199 |
200 | # Collect all config sources in reverse priority order (lowest to highest)
201 | config_sources = []
202 | config_source_names = []
203 |
204 | # 3. Global .env file (lowest priority - user defaults)
205 | global_env_path = Path.home() / ".codegraphcontext" / ".env"
206 | if global_env_path.exists():
207 | try:
208 | config_sources.append(dotenv_values(str(global_env_path)))
209 | config_source_names.append(str(global_env_path))
210 | except Exception as e:
211 | console.print(f"[yellow]Warning: Could not load global .env: {e}[/yellow]")
212 |
213 | # 2. Local project .env (higher priority - project-specific overrides)
214 | try:
215 | dotenv_path = find_dotenv(usecwd=True, raise_error_if_not_found=False)
216 | if dotenv_path:
217 | config_sources.append(dotenv_values(dotenv_path))
218 | config_source_names.append(str(dotenv_path))
219 | except Exception as e:
220 | console.print(f"[yellow]Warning: Could not load .env from current directory: {e}[/yellow]")
221 |
222 | # 1. Local mcp.json (highest priority - explicit MCP server config)
223 | mcp_file_path = Path.cwd() / "mcp.json"
224 | if mcp_file_path.exists():
225 | try:
226 | with open(mcp_file_path, "r") as f:
227 | mcp_config = json.load(f)
228 | server_env = mcp_config.get("mcpServers", {}).get("CodeGraphContext", {}).get("env", {})
229 | if server_env:
230 | config_sources.append(server_env)
231 | config_source_names.append("mcp.json")
232 | except Exception as e:
233 | console.print(f"[yellow]Warning: Could not load mcp.json: {e}[/yellow]")
234 |
235 | # Merge all configs with proper precedence (later sources override earlier ones)
236 | merged_config = {}
237 | for config in config_sources:
238 | merged_config.update(config)
239 |
240 | # Apply merged config to environment
241 | for key, value in merged_config.items():
242 | if value is not None: # Only set non-None values
243 | os.environ[key] = str(value)
244 |
245 | # Report what was loaded
246 | if config_source_names:
247 | if len(config_source_names) == 1:
248 | console.print(f"[dim]Loaded configuration from: {config_source_names[-1]}[/dim]")
249 | else:
250 | console.print(f"[dim]Loaded configuration from: {', '.join(config_source_names)} (highest priority: {config_source_names[-1]})[/dim]")
251 | else:
252 | console.print("[yellow]No configuration file found. Using defaults.[/yellow]")
253 |
254 |
255 | # Show which database is actually being used
256 | # Check for runtime override first (from -db/--database flag)
257 | runtime_db = os.environ.get("CGC_RUNTIME_DB_TYPE")
258 | if runtime_db:
259 | default_db = runtime_db.lower()
260 | else:
261 | default_db = os.environ.get("DEFAULT_DATABASE", "falkordb").lower()
262 |
263 | if default_db == "neo4j":
264 | has_neo4j_creds = all([
265 | os.environ.get("NEO4J_URI"),
266 | os.environ.get("NEO4J_USERNAME"),
267 | os.environ.get("NEO4J_PASSWORD")
268 | ])
269 | if has_neo4j_creds:
270 | console.print("[cyan]Using database: Neo4j[/cyan]")
271 | else:
272 | console.print("[yellow]⚠ DEFAULT_DATABASE=neo4j but credentials not found. Falling back to FalkorDB.[/yellow]")
273 | else:
274 | console.print("[cyan]Using database: FalkorDB[/cyan]")
275 |
276 |
277 |
278 | # ============================================================================
279 | # CONFIG COMMAND GROUP
280 | # ============================================================================
281 |
282 | config_app = typer.Typer(help="Manage configuration settings")
283 | app.add_typer(config_app, name="config")
284 |
285 | @config_app.command("show")
286 | def config_show():
287 | """
288 | Display current configuration settings.
289 |
290 | Shows all configuration values including database, indexing options,
291 | logging settings, and performance tuning parameters.
292 | """
293 | config_manager.show_config()
294 |
295 | @config_app.command("set")
296 | def config_set(
297 | key: str = typer.Argument(..., help="Configuration key to set"),
298 | value: str = typer.Argument(..., help="Value to set")
299 | ):
300 | """
301 | Set a configuration value.
302 |
303 | Examples:
304 | cgc config set DEFAULT_DATABASE neo4j
305 | cgc config set INDEX_VARIABLES false
306 | cgc config set MAX_FILE_SIZE_MB 20
307 | cgc config set DEBUG_LOGS true
308 | """
309 | config_manager.set_config_value(key, value)
310 |
311 | @config_app.command("reset")
312 | def config_reset():
313 | """
314 | Reset all configuration to default values.
315 |
316 | This will restore all settings to their defaults.
317 | Your current configuration will be backed up.
318 | """
319 | if typer.confirm("Are you sure you want to reset all configuration to defaults?", default=False):
320 | config_manager.reset_config()
321 | else:
322 | console.print("[yellow]Reset cancelled[/yellow]")
323 |
324 | @config_app.command("db")
325 | def config_db(backend: str = typer.Argument(..., help="Database backend: 'neo4j' or 'falkordb'")):
326 | """
327 | Quickly switch the default database backend.
328 |
329 | Shortcut for 'cgc config set DEFAULT_DATABASE <backend>'.
330 |
331 | Examples:
332 | cgc config db neo4j
333 | cgc config db falkordb
334 | """
335 | backend = backend.lower()
336 | if backend not in ['falkordb', 'neo4j']:
337 | console.print(f"[bold red]Invalid backend: {backend}[/bold red]")
338 | console.print("Must be 'falkordb' or 'neo4j'")
339 | raise typer.Exit(code=1)
340 |
341 | config_manager.set_config_value("DEFAULT_DATABASE", backend)
342 | console.print(f"[green]✔ Default database switched to {backend}[/green]")
343 |
344 | # ============================================================================
345 | # DOCTOR DIAGNOSTIC COMMAND
346 | # ============================================================================
347 |
348 | @app.command()
349 | def doctor():
350 | """
351 | Run diagnostics to check system health and configuration.
352 |
353 | Checks:
354 | - Configuration validity
355 | - Database connectivity
356 | - Tree-sitter installation
357 | - Required dependencies
358 | - File permissions
359 | """
360 | console.print("[bold cyan]🏥 Running CodeGraphContext Diagnostics...[/bold cyan]\n")
361 |
362 | all_checks_passed = True
363 |
364 | # 1. Check configuration
365 | console.print("[bold]1. Checking Configuration...[/bold]")
366 | try:
367 | config = config_manager.load_config()
368 | console.print(f" [green]✓[/green] Configuration loaded from {config_manager.CONFIG_FILE}")
369 |
370 | # Validate each config value
371 | invalid_configs = []
372 | for key, value in config.items():
373 | is_valid, error_msg = config_manager.validate_config_value(key, value)
374 | if not is_valid:
375 | invalid_configs.append(f"{key}: {error_msg}")
376 |
377 | if invalid_configs:
378 | console.print(f" [red]✗[/red] Invalid configuration values found:")
379 | for err in invalid_configs:
380 | console.print(f" - {err}")
381 | all_checks_passed = False
382 | else:
383 | console.print(f" [green]✓[/green] All configuration values are valid")
384 | except Exception as e:
385 | console.print(f" [red]✗[/red] Configuration error: {e}")
386 | all_checks_passed = False
387 |
388 | # 2. Check database connectivity
389 | console.print("\n[bold]2. Checking Database Connection...[/bold]")
390 | try:
391 | _load_credentials()
392 | default_db = config.get("DEFAULT_DATABASE", "falkordb")
393 | console.print(f" Default database: {default_db}")
394 |
395 | if default_db == "neo4j":
396 | uri = os.environ.get("NEO4J_URI")
397 | username = os.environ.get("NEO4J_USERNAME")
398 | password = os.environ.get("NEO4J_PASSWORD")
399 |
400 | if uri and username and password:
401 | console.print(f" [cyan]Testing Neo4j connection to {uri}...[/cyan]")
402 | is_connected, error_msg = DatabaseManager.test_connection(uri, username, password)
403 | if is_connected:
404 | console.print(f" [green]✓[/green] Neo4j connection successful")
405 | else:
406 | console.print(f" [red]✗[/red] Neo4j connection failed: {error_msg}")
407 | all_checks_passed = False
408 | else:
409 | console.print(f" [yellow]⚠[/yellow] Neo4j credentials not set. Run 'cgc neo4j setup'")
410 | else:
411 | # FalkorDB
412 | try:
413 | import falkordb
414 | console.print(f" [green]✓[/green] FalkorDB Lite is installed")
415 | except ImportError:
416 | console.print(f" [yellow]⚠[/yellow] FalkorDB Lite not installed (Python 3.12+ only)")
417 | console.print(f" Run: pip install falkordblite")
418 | except Exception as e:
419 | console.print(f" [red]✗[/red] Database check error: {e}")
420 | all_checks_passed = False
421 |
422 | # 3. Check tree-sitter installation
423 | console.print("\n[bold]3. Checking Tree-Sitter Installation...[/bold]")
424 | try:
425 | from tree_sitter import Language, Parser
426 | console.print(f" [green]✓[/green] tree-sitter is installed")
427 |
428 | try:
429 | from tree_sitter_language_pack import get_language
430 | console.print(f" [green]✓[/green] tree-sitter-language-pack is installed")
431 |
432 | # Test a few languages
433 | test_langs = ["python", "javascript", "typescript"]
434 | for lang in test_langs:
435 | try:
436 | get_language(lang)
437 | console.print(f" [green]✓[/green] {lang} parser available")
438 | except Exception:
439 | console.print(f" [yellow]⚠[/yellow] {lang} parser not available")
440 | except ImportError:
441 | console.print(f" [red]✗[/red] tree-sitter-language-pack not installed")
442 | all_checks_passed = False
443 | except ImportError as e:
444 | console.print(f" [red]✗[/red] tree-sitter not installed: {e}")
445 | all_checks_passed = False
446 |
447 | # 4. Check file permissions
448 | console.print("\n[bold]4. Checking File Permissions...[/bold]")
449 | try:
450 | config_dir = config_manager.CONFIG_DIR
451 | if config_dir.exists():
452 | console.print(f" [green]✓[/green] Config directory exists: {config_dir}")
453 |
454 | # Check if writable
455 | test_file = config_dir / ".test_write"
456 | try:
457 | test_file.touch()
458 | test_file.unlink()
459 | console.print(f" [green]✓[/green] Config directory is writable")
460 | except Exception as e:
461 | console.print(f" [red]✗[/red] Config directory not writable: {e}")
462 | all_checks_passed = False
463 | else:
464 | console.print(f" [yellow]⚠[/yellow] Config directory doesn't exist, will be created on first use")
465 | except Exception as e:
466 | console.print(f" [red]✗[/red] Permission check error: {e}")
467 | all_checks_passed = False
468 |
469 | # 5. Check cgc command availability
470 | console.print("\n[bold]5. Checking CGC Command...[/bold]")
471 | import shutil
472 | cgc_path = shutil.which("cgc")
473 | if cgc_path:
474 | console.print(f" [green]✓[/green] cgc command found at: {cgc_path}")
475 | else:
476 | console.print(f" [yellow]⚠[/yellow] cgc command not in PATH (using python -m cgc)")
477 |
478 | # Final summary
479 | console.print("\n" + "=" * 60)
480 | if all_checks_passed:
481 | console.print("[bold green]✅ All diagnostics passed! System is healthy.[/bold green]")
482 | else:
483 | console.print("[bold yellow]⚠️ Some issues detected. Please review the output above.[/bold yellow]")
484 | console.print("\n[cyan]Common fixes:[/cyan]")
485 | console.print(" • For Neo4j issues: Run 'cgc neo4j setup'")
486 | console.print(" • For missing packages: pip install codegraphcontext")
487 | console.print(" • For config issues: Run 'cgc config reset'")
488 | console.print("=" * 60 + "\n")
489 |
490 |
491 |
492 |
493 | @app.command()
494 | def start():
495 | """
496 | Start the MCP server.
497 |
498 | [yellow]⚠️ Deprecated: Use 'cgc mcp start' instead.[/yellow]
499 | This command will be removed in a future version.
500 | """
501 | console.print("[yellow]⚠️ 'cgc start' is deprecated. Use 'cgc mcp start' instead.[/yellow]")
502 | mcp_start()
503 |
504 |
505 | @app.command()
506 | def index(
507 | path: Optional[str] = typer.Argument(None, help="Path to the directory or file to index. Defaults to the current directory."),
508 | force: bool = typer.Option(False, "--force", "-f", help="Force re-index (delete existing and rebuild)")
509 | ):
510 | """
511 | Indexes a directory or file by adding it to the code graph.
512 | If no path is provided, it indexes the current directory.
513 |
514 | Use --force to delete the existing index and rebuild from scratch.
515 | """
516 | _load_credentials()
517 | if path is None:
518 | path = str(Path.cwd())
519 |
520 | if force:
521 | console.print("[yellow]Force re-indexing (--force flag detected)[/yellow]")
522 | reindex_helper(path)
523 | else:
524 | index_helper(path)
525 |
526 | @app.command()
527 | def clean():
528 | """
529 | Remove orphaned nodes and relationships from the database.
530 |
531 | This will clean up nodes that are not connected to any repository,
532 | helping to keep your database tidy and performant.
533 | """
534 | _load_credentials()
535 | clean_helper()
536 |
537 | @app.command()
538 | def stats(path: Optional[str] = typer.Argument(None, help="Path to show stats for. Omit for overall stats.")):
539 | """
540 | Show indexing statistics.
541 |
542 | If a path is provided, shows stats for that specific repository.
543 | Otherwise, shows overall database statistics.
544 | """
545 | _load_credentials()
546 | if path:
547 | path = str(Path(path).resolve())
548 | stats_helper(path)
549 |
550 | @app.command()
551 | def delete(
552 | path: Optional[str] = typer.Argument(None, help="Path of the repository to delete from the code graph."),
553 | all_repos: bool = typer.Option(False, "--all", help="Delete all indexed repositories")
554 | ):
555 | """
556 | Deletes a repository from the code graph.
557 |
558 | Use --all to delete all repositories at once (requires confirmation).
559 |
560 | Examples:
561 | cgc delete ./my-project # Delete specific repository
562 | cgc delete --all # Delete all repositories
563 | """
564 | _load_credentials()
565 |
566 | if all_repos:
567 | # Delete all repositories
568 | services = _initialize_services()
569 | if not all(services):
570 | return
571 | db_manager, graph_builder, code_finder = services
572 |
573 | try:
574 | # Get list of repositories
575 | repos = code_finder.list_indexed_repositories()
576 |
577 | if not repos:
578 | console.print("[yellow]No repositories to delete.[/yellow]")
579 | return
580 |
581 | # Show what will be deleted
582 | console.print(f"\n[bold red]⚠️ WARNING: You are about to delete ALL {len(repos)} repositories![/bold red]\n")
583 |
584 | table = Table(show_header=True, header_style="bold magenta")
585 | table.add_column("Name", style="cyan")
586 | table.add_column("Path", style="dim")
587 |
588 | for repo in repos:
589 | table.add_row(repo.get("name", ""), repo.get("path", ""))
590 |
591 | console.print(table)
592 | console.print()
593 |
594 | # Double confirmation
595 | if not typer.confirm("Are you sure you want to delete ALL repositories?", default=False):
596 | console.print("[yellow]Deletion cancelled.[/yellow]")
597 | return
598 |
599 | console.print("[yellow]Please type 'delete all' to confirm:[/yellow] ", end="")
600 | confirmation = input()
601 |
602 | if confirmation.strip().lower() != "delete all":
603 | console.print("[yellow]Deletion cancelled. Confirmation text did not match.[/yellow]")
604 | return
605 |
606 | # Delete all repositories
607 | console.print("\n[cyan]Deleting all repositories...[/cyan]")
608 | deleted_count = 0
609 |
610 | for repo in repos:
611 | repo_path = repo.get("path", "")
612 | try:
613 | graph_builder.delete_repository_from_graph(repo_path)
614 | console.print(f"[green]✓[/green] Deleted: {repo.get('name', '')}")
615 | deleted_count += 1
616 | except Exception as e:
617 | console.print(f"[red]✗[/red] Failed to delete {repo.get('name', '')}: {e}")
618 |
619 | console.print(f"\n[bold green]Successfully deleted {deleted_count}/{len(repos)} repositories![/bold green]")
620 |
621 | finally:
622 | db_manager.close_driver()
623 | else:
624 | # Delete specific repository
625 | if not path:
626 | console.print("[red]Error: Please provide a path or use --all to delete all repositories[/red]")
627 | console.print("Usage: cgc delete <path> or cgc delete --all")
628 | raise typer.Exit(code=1)
629 |
630 | delete_helper(path)
631 |
632 | @app.command()
633 | def visualize(query: Optional[str] = typer.Argument(None, help="The Cypher query to visualize.")):
634 | """
635 | Generates a URL to visualize a Cypher query in the Neo4j Browser.
636 | If no query is provided, a default query will be used.
637 | """
638 | if query is None:
639 | query = "MATCH p=()-->() RETURN p"
640 | _load_credentials()
641 | visualize_helper(query)
642 |
643 | @app.command("list")
644 | def list_repositories():
645 | """
646 | List all indexed repositories.
647 |
648 | Shows all projects and packages that have been indexed in the code graph.
649 | """
650 | _load_credentials()
651 | list_repos_helper()
652 |
653 | @app.command(name="add-package")
654 | def add_package(package_name: str = typer.Argument(..., help="Name of the package to add."), language: str = typer.Argument(..., help="Language of the package." )):
655 | """
656 | Adds a package to the code graph.
657 | """
658 | _load_credentials()
659 | add_package_helper(package_name, language)
660 |
661 | # ============================================================================
662 | # WATCH COMMAND GROUP - Live File Monitoring
663 | # ============================================================================
664 |
665 | @app.command()
666 | def watch(
667 | path: str = typer.Argument(".", help="Path to the directory to watch. Defaults to current directory.")
668 | ):
669 | """
670 | Watch a directory for file changes and automatically update the code graph.
671 |
672 | This command runs in the foreground and monitors the specified directory
673 | for any file changes. When changes are detected, the code graph is
674 | automatically updated.
675 |
676 | The watcher will:
677 | - Perform an initial scan if the directory is not yet indexed
678 | - Monitor for file creation, modification, deletion, and moves
679 | - Automatically re-index affected files and update relationships
680 |
681 | Press Ctrl+C to stop watching.
682 |
683 | Examples:
684 | cgc watch . # Watch current directory
685 | cgc watch /path/to/project # Watch specific directory
686 | cgc w . # Using shortcut alias
687 | """
688 | _load_credentials()
689 | watch_helper(path)
690 |
691 | @app.command()
692 | def unwatch(
693 | path: str = typer.Argument(..., help="Path to stop watching")
694 | ):
695 | """
696 | Stop watching a directory for changes.
697 |
698 | Note: This command is primarily for MCP server mode.
699 | For CLI watch mode, simply press Ctrl+C in the watch terminal.
700 |
701 | Examples:
702 | cgc unwatch /path/to/project
703 | """
704 | _load_credentials()
705 | unwatch_helper(path)
706 |
707 | @app.command()
708 | def watching():
709 | """
710 | List all directories currently being watched for changes.
711 |
712 | Note: This command is primarily for MCP server mode.
713 | For CLI watch mode, check the terminal where you ran 'cgc watch'.
714 |
715 | Examples:
716 | cgc watching
717 | """
718 | _load_credentials()
719 | list_watching_helper()
720 |
721 |
722 |
723 | # ============================================================================
724 | # FIND COMMAND GROUP - Code Search & Discovery
725 | # ============================================================================
726 |
727 | find_app = typer.Typer(help="Find and search code elements")
728 | app.add_typer(find_app, name="find")
729 |
730 | @find_app.command("name")
731 | def find_by_name(
732 | name: str = typer.Argument(..., help="Exact name to search for"),
733 | type: Optional[str] = typer.Option(None, "--type", "-t", help="Filter by type (function, class, file, module)")
734 | ):
735 | """
736 | Find code elements by exact name.
737 |
738 | Examples:
739 | cgc find name MyClass
740 | cgc find name calculate --type function
741 | """
742 | _load_credentials()
743 | services = _initialize_services()
744 | if not all(services):
745 | return
746 | db_manager, graph_builder, code_finder = services
747 |
748 | try:
749 | results = []
750 |
751 | # Search based on type filter
752 | if type is None or type.lower() == 'all':
753 | funcs = code_finder.find_by_function_name(name, fuzzy_search=False)
754 | classes = code_finder.find_by_class_name(name, fuzzy_search=False)
755 | variables = code_finder.find_by_variable_name(name)
756 | modules = code_finder.find_by_module_name(name)
757 | imports = code_finder.find_imports(name)
758 |
759 | for f in funcs: f['type'] = 'Function'
760 | for c in classes: c['type'] = 'Class'
761 | for v in variables: v['type'] = 'Variable'
762 | for m in modules: m['type'] = 'Module'; m['file_path'] = m.get('name', 'External') # Modules might differ
763 | for i in imports:
764 | i['type'] = 'Import'
765 | i['name'] = i.get('alias') or i.get('imported_name')
766 |
767 | results.extend(funcs)
768 | results.extend(classes)
769 | results.extend(variables)
770 | results.extend(modules)
771 | results.extend(imports)
772 |
773 | elif type.lower() == 'function':
774 | results = code_finder.find_by_function_name(name, fuzzy_search=False)
775 | for r in results: r['type'] = 'Function'
776 |
777 | elif type.lower() == 'class':
778 | results = code_finder.find_by_class_name(name, fuzzy_search=False)
779 | for r in results: r['type'] = 'Class'
780 |
781 | elif type.lower() == 'variable':
782 | results = code_finder.find_by_variable_name(name)
783 | for r in results: r['type'] = 'Variable'
784 |
785 | elif type.lower() == 'module':
786 | results = code_finder.find_by_module_name(name)
787 | for r in results:
788 | r['type'] = 'Module'
789 | r['file_path'] = r.get('name')
790 |
791 | elif type.lower() == 'file':
792 | # Quick query for file
793 | with db_manager.get_driver().session() as session:
794 | res = session.run("MATCH (n:File) WHERE n.name = $name RETURN n.name as name, n.path as file_path, n.is_dependency as is_dependency", name=name)
795 | results = [dict(record) for record in res]
796 | for r in results: r['type'] = 'File'
797 |
798 | if not results:
799 | console.print(f"[yellow]No code elements found with name '{name}'[/yellow]")
800 | return
801 |
802 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
803 | table.add_column("Name", style="cyan")
804 | table.add_column("Type", style="bold blue")
805 | table.add_column("Location", style="dim", overflow="fold")
806 |
807 | for res in results:
808 | file_path = res.get('file_path', '') or ''
809 | line_str = str(res.get('line_number', ''))
810 | location_str = f"{file_path}:{line_str}" if line_str else file_path
811 |
812 | table.add_row(
813 | res.get('name', ''),
814 | res.get('type', 'Unknown'),
815 | location_str
816 | )
817 |
818 | console.print(f"[cyan]Found {len(results)} matches for '{name}':[/cyan]")
819 | console.print(table)
820 | finally:
821 | db_manager.close_driver()
822 |
823 | @find_app.command("pattern")
824 | def find_by_pattern(
825 | pattern: str = typer.Argument(..., help="Substring pattern to search (fuzzy search fallback)"),
826 | case_sensitive: bool = typer.Option(False, "--case-sensitive", "-c", help="Case-sensitive search")
827 | ):
828 | """
829 | Find code elements using substring matching.
830 |
831 | Examples:
832 | cgc find pattern "Auth" # Finds Auth, Authentication, Authorize...
833 | cgc find pattern "process_" # Finds process_data, process_request...
834 | """
835 | _load_credentials()
836 | services = _initialize_services()
837 | if not all(services):
838 | return
839 | db_manager, graph_builder, code_finder = services
840 |
841 | try:
842 | with db_manager.get_driver().session() as session:
843 | # Search Functions, Classes, and Modules
844 | # Note: FalkorDB Lite might not support regex, using CONTAINS
845 |
846 | if not case_sensitive:
847 | query = """
848 | MATCH (n)
849 | WHERE (n:Function OR n:Class OR n:Module OR n:Variable) AND toLower(n.name) CONTAINS toLower($pattern)
850 | RETURN
851 | labels(n)[0] as type,
852 | n.name as name,
853 | n.file_path as file_path,
854 | n.line_number as line_number,
855 | n.is_dependency as is_dependency
856 | ORDER BY n.is_dependency ASC, n.name
857 | LIMIT 50
858 | """
859 | else:
860 | query = """
861 | MATCH (n)
862 | WHERE (n:Function OR n:Class OR n:Module OR n:Variable) AND n.name CONTAINS $pattern
863 | RETURN
864 | labels(n)[0] as type,
865 | n.name as name,
866 | n.file_path as file_path,
867 | n.line_number as line_number,
868 | n.is_dependency as is_dependency
869 | ORDER BY n.is_dependency ASC, n.name
870 | LIMIT 50
871 | """
872 |
873 | result = session.run(query, pattern=pattern)
874 |
875 | results = [dict(record) for record in result]
876 |
877 | if not results:
878 | console.print(f"[yellow]No matches found for pattern '{pattern}'[/yellow]")
879 | return
880 |
881 | if not case_sensitive and any(c in pattern for c in "*?["):
882 | console.print("[yellow]Note: Wildcards/Regex are not fully supported in this mode. Performing substring search.[/yellow]")
883 |
884 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
885 | table.add_column("Name", style="cyan")
886 | table.add_column("Type", style="blue")
887 | table.add_column("Location", style="dim", overflow="fold")
888 | table.add_column("Source", style="yellow")
889 |
890 | for res in results:
891 | file_path = res.get('file_path', '') or ''
892 | line_str = str(res.get('line_number', '') if res.get('line_number') is not None else '')
893 | location_str = f"{file_path}:{line_str}" if line_str else file_path
894 |
895 | table.add_row(
896 | res.get('name', ''),
897 | res.get('type', 'Unknown'),
898 | location_str,
899 | "📦 Dependency" if res.get('is_dependency') else "📝 Project"
900 | )
901 |
902 | console.print(f"[cyan]Found {len(results)} matches for pattern '{pattern}':[/cyan]")
903 | console.print(table)
904 | finally:
905 | db_manager.close_driver()
906 |
907 | @find_app.command("type")
908 | def find_by_type(
909 | element_type: str = typer.Argument(..., help="Type to search for (function, class, file, module)"),
910 | limit: int = typer.Option(50, "--limit", "-l", help="Maximum results to return")
911 | ):
912 | """
913 | Find all elements of a specific type.
914 |
915 | Examples:
916 | cgc find type class
917 | cgc find type function --limit 100
918 | """
919 | _load_credentials()
920 | services = _initialize_services()
921 | if not all(services):
922 | return
923 | db_manager, graph_builder, code_finder = services
924 |
925 | try:
926 | results = code_finder.find_by_type(element_type, limit)
927 |
928 | if not results:
929 | console.print(f"[yellow]No elements found of type '{element_type}'[/yellow]")
930 | return
931 |
932 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
933 | table.add_column("Name", style="cyan")
934 | table.add_column("Location", style="dim", overflow="fold")
935 | table.add_column("Source", style="yellow")
936 |
937 | for res in results:
938 | file_path = res.get('file_path', '') or ''
939 | line_str = str(res.get('line_number', ''))
940 | location_str = f"{file_path}:{line_str}" if line_str else file_path
941 |
942 | table.add_row(
943 | res.get('name', ''),
944 | location_str,
945 | "📦 Dependency" if res.get('is_dependency') else "📝 Project"
946 | )
947 |
948 | console.print(f"[cyan]Found {len(results)} {element_type}s:[/cyan]")
949 | console.print(table)
950 | finally:
951 | db_manager.close_driver()
952 |
953 | @find_app.command("variable")
954 | def find_by_variable(
955 | name: str = typer.Argument(..., help="Variable name to search for")
956 | ):
957 | """
958 | Find variables by name.
959 |
960 | Examples:
961 | cgc find variable MAX_RETRIES
962 | cgc find variable config
963 | """
964 | _load_credentials()
965 | services = _initialize_services()
966 | if not all(services):
967 | return
968 | db_manager, graph_builder, code_finder = services
969 |
970 | try:
971 | results = code_finder.find_by_variable_name(name)
972 |
973 | if not results:
974 | console.print(f"[yellow]No variables found with name '{name}'[/yellow]")
975 | return
976 |
977 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
978 | table.add_column("Name", style="cyan")
979 | table.add_column("Location", style="dim", overflow="fold")
980 | table.add_column("Context", style="yellow")
981 |
982 | for res in results:
983 | file_path = res.get('file_path', '') or ''
984 | line_str = str(res.get('line_number', ''))
985 | location_str = f"{file_path}:{line_str}" if line_str else file_path
986 |
987 | table.add_row(
988 | res.get('name', ''),
989 | location_str,
990 | res.get('context', '') or 'module'
991 | )
992 |
993 | console.print(f"[cyan]Found {len(results)} variable(s) named '{name}':[/cyan]")
994 | console.print(table)
995 | finally:
996 | db_manager.close_driver()
997 |
998 | @find_app.command("content")
999 | def find_by_content_search(
1000 | query: str = typer.Argument(..., help="Text to search for in source code and docstrings")
1001 | ):
1002 | """
1003 | Search code content (source and docstrings) using full-text index.
1004 |
1005 | Examples:
1006 | cgc find content "error 503"
1007 | cgc find content "TODO: refactor"
1008 | """
1009 | _load_credentials()
1010 | services = _initialize_services()
1011 | if not all(services):
1012 | return
1013 | db_manager, graph_builder, code_finder = services
1014 |
1015 | try:
1016 | try:
1017 | results = code_finder.find_by_content(query)
1018 | except Exception as e:
1019 | error_msg = str(e).lower()
1020 | if 'fulltext' in error_msg or 'db.index.fulltext' in error_msg:
1021 | console.print("\n[bold red]❌ Full-text search is not supported on FalkorDB[/bold red]\n")
1022 | console.print("[yellow]💡 You have two options:[/yellow]\n")
1023 | console.print(" 1. [cyan]Switch to Neo4j:[/cyan]")
1024 | console.print(f" [dim]cgc --database neo4j find content \"{query}\"[/dim]\n")
1025 | console.print(" 2. [cyan]Use pattern search instead:[/cyan]")
1026 | console.print(f" [dim]cgc find pattern \"{query}\"[/dim]")
1027 | console.print(" [dim](searches in names only, not source code)[/dim]\n")
1028 | return
1029 | else:
1030 | # Re-raise if it's a different error
1031 | raise
1032 |
1033 | if not results:
1034 | console.print(f"[yellow]No content matches found for '{query}'[/yellow]")
1035 | return
1036 |
1037 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1038 | table.add_column("Name", style="cyan")
1039 | table.add_column("Type", style="blue")
1040 | table.add_column("Location", style="dim", overflow="fold")
1041 |
1042 | for res in results:
1043 | file_path = res.get('file_path', '') or ''
1044 | line_str = str(res.get('line_number', ''))
1045 | location_str = f"{file_path}:{line_str}" if line_str else file_path
1046 |
1047 | table.add_row(
1048 | res.get('name', ''),
1049 | res.get('type', 'Unknown'),
1050 | location_str
1051 | )
1052 |
1053 | console.print(f"[cyan]Found {len(results)} content match(es) for '{query}':[/cyan]")
1054 | console.print(table)
1055 | finally:
1056 | db_manager.close_driver()
1057 |
1058 | @find_app.command("decorator")
1059 | def find_by_decorator_search(
1060 | decorator: str = typer.Argument(..., help="Decorator name to search for"),
1061 | file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path")
1062 | ):
1063 | """
1064 | Find functions with a specific decorator.
1065 |
1066 | Examples:
1067 | cgc find decorator app.route
1068 | cgc find decorator test --file tests/test_main.py
1069 | """
1070 | _load_credentials()
1071 | services = _initialize_services()
1072 | if not all(services):
1073 | return
1074 | db_manager, graph_builder, code_finder = services
1075 |
1076 | try:
1077 | results = code_finder.find_functions_by_decorator(decorator, file)
1078 |
1079 | if not results:
1080 | console.print(f"[yellow]No functions found with decorator '@{decorator}'[/yellow]")
1081 | return
1082 |
1083 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1084 | table.add_column("Function", style="cyan")
1085 | table.add_column("Location", style="dim", overflow="fold")
1086 | table.add_column("Decorators", style="yellow")
1087 |
1088 | for res in results:
1089 | decorators_str = ", ".join(res.get('decorators', []))
1090 | file_path = res.get('file_path', '') or ''
1091 | line_str = str(res.get('line_number', ''))
1092 | location_str = f"{file_path}:{line_str}" if line_str else file_path
1093 |
1094 | table.add_row(
1095 | res.get('function_name', ''),
1096 | location_str,
1097 | decorators_str
1098 | )
1099 |
1100 | console.print(f"[cyan]Found {len(results)} function(s) with decorator '@{decorator}':[/cyan]")
1101 | console.print(table)
1102 | finally:
1103 | db_manager.close_driver()
1104 |
1105 | @find_app.command("argument")
1106 | def find_by_argument_search(
1107 | argument: str = typer.Argument(..., help="Argument/parameter name to search for"),
1108 | file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path")
1109 | ):
1110 | """
1111 | Find functions that take a specific argument/parameter.
1112 |
1113 | Examples:
1114 | cgc find argument password
1115 | cgc find argument user_id --file src/auth.py
1116 | """
1117 | _load_credentials()
1118 | services = _initialize_services()
1119 | if not all(services):
1120 | return
1121 | db_manager, graph_builder, code_finder = services
1122 |
1123 | try:
1124 | results = code_finder.find_functions_by_argument(argument, file)
1125 |
1126 | if not results:
1127 | console.print(f"[yellow]No functions found with argument '{argument}'[/yellow]")
1128 | return
1129 |
1130 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1131 | table.add_column("Function", style="cyan")
1132 | table.add_column("Location", style="dim", overflow="fold")
1133 |
1134 | for res in results:
1135 | file_path = res.get('file_path', '') or ''
1136 | line_str = str(res.get('line_number', ''))
1137 | location_str = f"{file_path}:{line_str}" if line_str else file_path
1138 |
1139 | table.add_row(
1140 | res.get('function_name', ''),
1141 | location_str
1142 | )
1143 |
1144 | console.print(f"[cyan]Found {len(results)} function(s) with argument '{argument}':[/cyan]")
1145 | console.print(table)
1146 | finally:
1147 | db_manager.close_driver()
1148 |
1149 |
1150 | # ============================================================================
1151 | # ANALYZE COMMAND GROUP - Code Analysis & Relationships
1152 | # ============================================================================
1153 |
1154 | analyze_app = typer.Typer(help="Analyze code relationships, dependencies, and quality")
1155 | app.add_typer(analyze_app, name="analyze")
1156 |
1157 | @analyze_app.command("calls")
1158 | def analyze_calls(
1159 | function: str = typer.Argument(..., help="Function name to analyze"),
1160 | file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path")
1161 | ):
1162 | """
1163 | Show what functions this function calls (callees).
1164 |
1165 | Example:
1166 | cgc analyze calls process_data
1167 | cgc analyze calls process_data --file src/main.py
1168 | """
1169 | _load_credentials()
1170 | services = _initialize_services()
1171 | if not all(services):
1172 | return
1173 | db_manager, graph_builder, code_finder = services
1174 |
1175 | try:
1176 | results = code_finder.what_does_function_call(function, file)
1177 |
1178 | if not results:
1179 | console.print(f"[yellow]No function calls found for '{function}'[/yellow]")
1180 | return
1181 |
1182 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1183 | table.add_column("Called Function", style="cyan")
1184 | table.add_column("Location", style="dim", overflow="fold")
1185 | table.add_column("Type", style="yellow")
1186 |
1187 | for result in results:
1188 | file_path = result.get("called_file_path", "")
1189 | line_str = str(result.get("called_line_number", ""))
1190 | location_str = f"{file_path}:{line_str}" if line_str else file_path
1191 |
1192 | table.add_row(
1193 | result.get("called_function", ""),
1194 | location_str,
1195 | "📦 Dependency" if result.get("called_is_dependency") else "📝 Project"
1196 | )
1197 |
1198 | console.print(f"\n[bold cyan]Function '{function}' calls:[/bold cyan]")
1199 | console.print(table)
1200 | console.print(f"\n[dim]Total: {len(results)} function(s)[/dim]")
1201 | finally:
1202 | db_manager.close_driver()
1203 |
1204 | @analyze_app.command("callers")
1205 | def analyze_callers(
1206 | function: str = typer.Argument(..., help="Function name to analyze"),
1207 | file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path")
1208 | ):
1209 | """
1210 | Show what functions call this function.
1211 |
1212 | Example:
1213 | cgc analyze callers process_data
1214 | cgc analyze callers process_data --file src/main.py
1215 | """
1216 | _load_credentials()
1217 | services = _initialize_services()
1218 | if not all(services):
1219 | return
1220 | db_manager, graph_builder, code_finder = services
1221 |
1222 | try:
1223 | results = code_finder.who_calls_function(function, file)
1224 |
1225 | if not results:
1226 | console.print(f"[yellow]No callers found for '{function}'[/yellow]")
1227 | return
1228 |
1229 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1230 | table.add_column("Caller Function", style="cyan")
1231 | table.add_column("Location", style="green")
1232 | table.add_column("Call Type", style="yellow")
1233 |
1234 |
1235 | for result in results:
1236 | file_path = result.get("caller_file_path", "")
1237 | line_number = result.get("caller_line_number")
1238 |
1239 | location = f"{file_path}:{line_number}" if line_number else file_path
1240 |
1241 | table.add_row(
1242 | result.get("caller_function", ""),
1243 | location,
1244 | "📦 Dependency" if result.get("caller_is_dependency") else "📝 Project"
1245 | )
1246 |
1247 | console.print(f"\n[bold cyan]Functions that call '{function}':[/bold cyan]")
1248 | console.print(table)
1249 | console.print(f"\n[dim]Total: {len(results)} caller(s)[/dim]")
1250 | finally:
1251 | db_manager.close_driver()
1252 |
1253 | @analyze_app.command("chain")
1254 | def analyze_chain(
1255 | from_func: str = typer.Argument(..., help="Starting function"),
1256 | to_func: str = typer.Argument(..., help="Target function"),
1257 | max_depth: int = typer.Option(5, "--depth", "-d", help="Maximum call chain depth"),
1258 | from_file: Optional[str] = typer.Option(None, "--from-file", help="File for starting function"),
1259 | to_file: Optional[str] = typer.Option(None, "--to-file", help="File for target function")
1260 | ):
1261 | """
1262 | Show call chain between two functions.
1263 |
1264 | Example:
1265 | cgc analyze chain main process_data --depth 10
1266 | cgc analyze chain main process --from-file main.py --to-file utils.py
1267 | """
1268 | _load_credentials()
1269 | services = _initialize_services()
1270 | if not all(services):
1271 | return
1272 | db_manager, graph_builder, code_finder = services
1273 |
1274 | try:
1275 | results = code_finder.find_function_call_chain(from_func, to_func, max_depth, from_file, to_file)
1276 |
1277 | if not results:
1278 | console.print(f"[yellow]No call chain found between '{from_func}' and '{to_func}' within depth {max_depth}[/yellow]")
1279 | return
1280 |
1281 | for idx, chain in enumerate(results, 1):
1282 | console.print(f"\n[bold cyan]Call Chain #{idx} (length: {chain.get('chain_length', 0)}):[/bold cyan]")
1283 |
1284 | functions = chain.get('function_chain', [])
1285 | call_details = chain.get('call_details', [])
1286 |
1287 | for i, func in enumerate(functions):
1288 | indent = " " * i
1289 |
1290 | # Print function
1291 | console.print(f"{indent}[cyan]{func.get('name', 'Unknown')}[/cyan] [dim]({func.get('file_path', '')}:{func.get('line_number', '')})[/dim]")
1292 |
1293 | # If there is a next step, print the connecting call detail
1294 | if i < len(functions) - 1 and i < len(call_details):
1295 | detail = call_details[i]
1296 | line = detail.get('call_line', '?')
1297 |
1298 | # Format args for display
1299 | args_info = ""
1300 | args_val = detail.get('args', [])
1301 | if args_val:
1302 | if isinstance(args_val, list):
1303 | # Filter legacy punctuation just in case
1304 | clean_args = [str(a) for a in args_val if str(a) not in ('(', ')', ',')]
1305 | args_str = ", ".join(clean_args)
1306 | else:
1307 | args_str = str(args_val)
1308 |
1309 | # Truncate if too long
1310 | if len(args_str) > 50:
1311 | args_str = args_str[:47] + "..."
1312 | args_info = f" [dim]({args_str})[/dim]"
1313 |
1314 | console.print(f"{indent} ⬇ [dim]calls at line {line}[/dim]{args_info}")
1315 | finally:
1316 | db_manager.close_driver()
1317 |
1318 | @analyze_app.command("deps")
1319 | def analyze_dependencies(
1320 | target: str = typer.Argument(..., help="Module name"),
1321 | show_external: bool = typer.Option(True, "--external/--no-external", help="Show external dependencies")
1322 | ):
1323 | """
1324 | Show dependencies and imports for a module.
1325 |
1326 | Example:
1327 | cgc analyze deps numpy
1328 | cgc analyze deps mymodule --no-external
1329 | """
1330 | _load_credentials()
1331 | services = _initialize_services()
1332 | if not all(services):
1333 | return
1334 | db_manager, graph_builder, code_finder = services
1335 |
1336 | try:
1337 | results = code_finder.find_module_dependencies(target)
1338 |
1339 | if not results.get('importers') and not results.get('imports'):
1340 | console.print(f"[yellow]No dependency information found for '{target}'[/yellow]")
1341 | return
1342 |
1343 | # Show who imports this module
1344 | if results.get('importers'):
1345 | console.print(f"\n[bold cyan]Files that import '{target}':[/bold cyan]")
1346 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1347 | table.add_column("Location", style="cyan", overflow="fold")
1348 |
1349 | for imp in results['importers']:
1350 | file_path = imp.get('importer_file_path', '')
1351 | line_str = str(imp.get('import_line_number', ''))
1352 | location_str = f"{file_path}:{line_str}" if line_str else file_path
1353 |
1354 | table.add_row(
1355 | location_str
1356 | )
1357 | console.print(table)
1358 | finally:
1359 | db_manager.close_driver()
1360 |
1361 | @analyze_app.command("tree")
1362 | def analyze_inheritance_tree(
1363 | class_name: str = typer.Argument(..., help="Class name"),
1364 | file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path")
1365 | ):
1366 | """
1367 | Show inheritance hierarchy for a class.
1368 |
1369 | Example:
1370 | cgc analyze tree MyClass
1371 | cgc analyze tree MyClass --file src/models.py
1372 | """
1373 | _load_credentials()
1374 | services = _initialize_services()
1375 | if not all(services):
1376 | return
1377 | db_manager, graph_builder, code_finder = services
1378 |
1379 | try:
1380 | results = code_finder.find_class_hierarchy(class_name, file)
1381 |
1382 | console.print(f"\n[bold cyan]Class Hierarchy for '{class_name}':[/bold cyan]\n")
1383 |
1384 | # Show parent classes
1385 | if results.get('parent_classes'):
1386 | console.print("[bold yellow]Parents (inherits from):[/bold yellow]")
1387 | for parent in results['parent_classes']:
1388 | console.print(f" ⬆ [cyan]{parent.get('parent_class', '')}[/cyan] [dim]({parent.get('parent_file_path', '')}:{parent.get('parent_line_number', '')})[/dim]")
1389 | else:
1390 | console.print("[dim]No parent classes found[/dim]")
1391 |
1392 | console.print()
1393 |
1394 | # Show child classes
1395 | if results.get('child_classes'):
1396 | console.print("[bold yellow]Children (classes that inherit from this):[/bold yellow]")
1397 | for child in results['child_classes']:
1398 | console.print(f" ⬇ [cyan]{child.get('child_class', '')}[/cyan] [dim]({child.get('child_file_path', '')}:{child.get('child_line_number', '')})[/dim]")
1399 | else:
1400 | console.print("[dim]No child classes found[/dim]")
1401 |
1402 | console.print()
1403 |
1404 | # Show methods
1405 | if results.get('methods'):
1406 | console.print(f"[bold yellow]Methods ({len(results['methods'])}):[/bold yellow]")
1407 | for method in results['methods'][:10]: # Limit to 10
1408 | console.print(f" • [green]{method.get('method_name', '')}[/green]({method.get('method_args', '')})")
1409 | if len(results['methods']) > 10:
1410 | console.print(f" [dim]... and {len(results['methods']) - 10} more[/dim]")
1411 | finally:
1412 | db_manager.close_driver()
1413 |
1414 | @analyze_app.command("complexity")
1415 | def analyze_complexity(
1416 | path: Optional[str] = typer.Argument(None, help="Specific function name to analyze"),
1417 | threshold: int = typer.Option(10, "--threshold", "-t", help="Complexity threshold for warnings"),
1418 | limit: int = typer.Option(20, "--limit", "-l", help="Maximum results to show"),
1419 | file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path (only used when function name is provided)")
1420 | ):
1421 | """
1422 | Show cyclomatic complexity for functions.
1423 |
1424 | Example:
1425 | cgc analyze complexity # Most complex functions
1426 | cgc analyze complexity --threshold 15 # Functions over threshold
1427 | cgc analyze complexity my_function # Specific function
1428 | cgc analyze complexity my_function -f file.py # Specific function in file
1429 | """
1430 | _load_credentials()
1431 | services = _initialize_services()
1432 | if not all(services):
1433 | return
1434 | db_manager, graph_builder, code_finder = services
1435 |
1436 | try:
1437 | if path:
1438 | # Specific function
1439 | result = code_finder.get_cyclomatic_complexity(path, file)
1440 | if result:
1441 | console.print(f"\n[bold cyan]Complexity for '{path}':[/bold cyan]")
1442 | console.print(f" Cyclomatic Complexity: [yellow]{result.get('complexity', 'N/A')}[/yellow]")
1443 | console.print(f" File: [dim]{result.get('file_path', '')}[/dim]")
1444 | console.print(f" Line: [dim]{result.get('line_number', '')}[/dim]")
1445 | else:
1446 | console.print(f"[yellow]Function '{path}' not found or has no complexity data[/yellow]")
1447 | else:
1448 | # Most complex functions
1449 | results = code_finder.find_most_complex_functions(limit)
1450 |
1451 | if not results:
1452 | console.print("[yellow]No complexity data available[/yellow]")
1453 | return
1454 |
1455 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1456 | table.add_column("Function", style="cyan")
1457 | table.add_column("Complexity", style="yellow", justify="right")
1458 | table.add_column("Location", style="dim", overflow="fold")
1459 |
1460 | for func in results:
1461 | complexity = func.get('complexity', 0)
1462 | color = "red" if complexity > threshold else "yellow" if complexity > threshold/2 else "green"
1463 | file_path = func.get('file_path', '')
1464 | line_str = str(func.get('line_number', ''))
1465 | location_str = f"{file_path}:{line_str}" if line_str else file_path
1466 |
1467 | table.add_row(
1468 | func.get('function_name', ''),
1469 | f"[{color}]{complexity}[/{color}]",
1470 | location_str
1471 | )
1472 |
1473 | console.print(f"\n[bold cyan]Most Complex Functions (threshold: {threshold}):[/bold cyan]")
1474 | console.print(table)
1475 | console.print(f"\n[dim]{len([f for f in results if f.get('complexity', 0) > threshold])} function(s) exceed threshold[/dim]")
1476 | finally:
1477 | db_manager.close_driver()
1478 |
1479 | @analyze_app.command("dead-code")
1480 | def analyze_dead_code(
1481 | path: Optional[str] = typer.Argument(None, help="Path to analyze (not yet implemented)"),
1482 | exclude_decorators: Optional[str] = typer.Option(None, "--exclude", "-e", help="Comma-separated decorators to exclude")
1483 | ):
1484 | """
1485 | Find potentially unused functions and classes.
1486 |
1487 | Example:
1488 | cgc analyze dead-code
1489 | cgc analyze dead-code --exclude route,task,api
1490 | """
1491 | _load_credentials()
1492 | services = _initialize_services()
1493 | if not all(services):
1494 | return
1495 | db_manager, graph_builder, code_finder = services
1496 |
1497 | try:
1498 | exclude_list = exclude_decorators.split(',') if exclude_decorators else []
1499 | results = code_finder.find_dead_code(exclude_list)
1500 |
1501 | unused_funcs = results.get('potentially_unused_functions', [])
1502 |
1503 | if not unused_funcs:
1504 | console.print("[green]✓ No dead code found![/green]")
1505 | return
1506 |
1507 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1508 | table.add_column("Function", style="cyan")
1509 | table.add_column("Location", style="dim", overflow="fold")
1510 |
1511 | for func in unused_funcs:
1512 | file_path = func.get('file_path', '')
1513 | line_str = str(func.get('line_number', ''))
1514 | location_str = f"{file_path}:{line_str}" if line_str else file_path
1515 |
1516 | table.add_row(
1517 | func.get('function_name', ''),
1518 | location_str
1519 | )
1520 |
1521 | console.print(f"\n[bold yellow]⚠️ Potentially Unused Functions:[/bold yellow]")
1522 | console.print(table)
1523 | console.print(f"\n[dim]Total: {len(unused_funcs)} function(s)[/dim]")
1524 | console.print(f"[dim]Note: {results.get('note', '')}[/dim]")
1525 | finally:
1526 | db_manager.close_driver()
1527 |
1528 | @analyze_app.command("overrides")
1529 | def analyze_overrides(
1530 | function_name: str = typer.Argument(..., help="Function/method name to find implementations of")
1531 | ):
1532 | """
1533 | Find all implementations of a function across different classes.
1534 |
1535 | Useful for finding polymorphic implementations and method overrides.
1536 |
1537 | Example:
1538 | cgc analyze overrides area
1539 | cgc analyze overrides process
1540 | """
1541 | _load_credentials()
1542 | services = _initialize_services()
1543 | if not all(services):
1544 | return
1545 | db_manager, graph_builder, code_finder = services
1546 |
1547 | try:
1548 | results = code_finder.find_function_overrides(function_name)
1549 |
1550 | if not results:
1551 | console.print(f"[yellow]No implementations found for function '{function_name}'[/yellow]")
1552 | return
1553 |
1554 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1555 | table.add_column("Class", style="cyan")
1556 | table.add_column("Function", style="green")
1557 | table.add_column("Location", style="dim", overflow="fold")
1558 |
1559 | for res in results:
1560 | file_path = res.get('class_file_path', '')
1561 | line_str = str(res.get('function_line_number', ''))
1562 | location_str = f"{file_path}:{line_str}" if line_str else file_path
1563 |
1564 | table.add_row(
1565 | res.get('class_name', ''),
1566 | res.get('function_name', ''),
1567 | location_str
1568 | )
1569 |
1570 | console.print(f"\n[bold cyan]Found {len(results)} implementation(s) of '{function_name}':[/bold cyan]")
1571 | console.print(table)
1572 | finally:
1573 | db_manager.close_driver()
1574 |
1575 | @analyze_app.command("variable")
1576 | def analyze_variable_usage(
1577 | variable_name: str = typer.Argument(..., help="Variable name to analyze"),
1578 | file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path")
1579 | ):
1580 | """
1581 | Analyze where a variable is defined and used across the codebase.
1582 |
1583 | Shows all instances of the variable and their scope (function, class, module).
1584 |
1585 | Example:
1586 | cgc analyze variable MAX_RETRIES
1587 | cgc analyze variable config
1588 | cgc analyze variable x --file math_utils.py
1589 | """
1590 | _load_credentials()
1591 | services = _initialize_services()
1592 | if not all(services):
1593 | return
1594 | db_manager, graph_builder, code_finder = services
1595 |
1596 | try:
1597 | # Get variable usage scope
1598 | scope_results = code_finder.find_variable_usage_scope(variable_name, file)
1599 | instances = scope_results.get('instances', [])
1600 |
1601 | if not instances:
1602 | console.print(f"[yellow]No instances found for variable '{variable_name}'[/yellow]")
1603 | return
1604 |
1605 | console.print(f"\n[bold cyan]Variable '{variable_name}' Usage Analysis:[/bold cyan]\n")
1606 |
1607 | # Group by scope type
1608 | by_scope = {}
1609 | for inst in instances:
1610 | scope_type = inst.get('scope_type', 'unknown')
1611 | if scope_type not in by_scope:
1612 | by_scope[scope_type] = []
1613 | by_scope[scope_type].append(inst)
1614 |
1615 | # Display by scope
1616 | for scope_type, items in by_scope.items():
1617 | console.print(f"[bold yellow]{scope_type.upper()} Scope ({len(items)} instance(s)):[/bold yellow]")
1618 |
1619 | table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1620 | table.add_column("Scope Name", style="cyan")
1621 | table.add_column("Location", style="dim", overflow="fold")
1622 | table.add_column("Value", style="yellow")
1623 |
1624 | for item in items:
1625 | file_path = item.get('file_path', '')
1626 | line_str = str(item.get('line_number', ''))
1627 | location_str = f"{file_path}:{line_str}" if line_str else file_path
1628 |
1629 | table.add_row(
1630 | item.get('scope_name', ''),
1631 | location_str,
1632 | str(item.get('variable_value', ''))[:50] if item.get('variable_value') else '-'
1633 | )
1634 |
1635 | console.print(table)
1636 | console.print()
1637 |
1638 | console.print(f"[dim]Total: {len(instances)} instance(s) across {len(by_scope)} scope type(s)[/dim]")
1639 | finally:
1640 | db_manager.close_driver()
1641 |
1642 |
1643 | # ============================================================================
1644 | # QUERY COMMAND - Raw Cypher Queries
1645 | # ============================================================================
1646 |
1647 | @app.command("query")
1648 | def query_graph(query: str = typer.Argument(..., help="Cypher query to execute (read-only)")):
1649 | """
1650 | Execute a custom Cypher query on the code graph.
1651 |
1652 | Examples:
1653 | cgc query "MATCH (f:Function) RETURN f.name LIMIT 10"
1654 | cgc query "MATCH (c:Class)-[:CONTAINS]->(m) RETURN c.name, count(m)"
1655 | """
1656 | _load_credentials()
1657 | cypher_helper(query)
1658 |
1659 | # Keep old 'cypher' as alias for backward compatibility
1660 | @app.command("cypher", hidden=True)
1661 | def cypher_legacy(query: str = typer.Argument(..., help="The read-only Cypher query to execute.")):
1662 | """[Deprecated] Use 'cgc query' instead."""
1663 | console.print("[yellow]⚠️ 'cgc cypher' is deprecated. Use 'cgc query' instead.[/yellow]")
1664 | cypher_helper(query)
1665 |
1666 |
1667 |
1668 | # ============================================================================
1669 | # ABBREVIATIONS / SHORTCUTS for common commands
1670 | # ============================================================================
1671 |
1672 | @app.command("i", rich_help_panel="Shortcuts")
1673 | def index_abbrev(path: Optional[str] = typer.Argument(None, help="Path to index")):
1674 | """Shortcut for 'cgc index'"""
1675 | index(path)
1676 |
1677 | @app.command("ls", rich_help_panel="Shortcuts")
1678 | def list_abbrev():
1679 | """Shortcut for 'cgc list'"""
1680 | list_repositories()
1681 |
1682 | @app.command("rm", rich_help_panel="Shortcuts")
1683 | def delete_abbrev(
1684 | path: Optional[str] = typer.Argument(None, help="Path to delete"),
1685 | all_repos: bool = typer.Option(False, "--all", help="Delete all indexed repositories")
1686 | ):
1687 | """Shortcut for 'cgc delete'"""
1688 | delete(path, all_repos)
1689 |
1690 | @app.command("v", rich_help_panel="Shortcuts")
1691 | def visualize_abbrev(query: Optional[str] = typer.Argument(None, help="Cypher query")):
1692 | """Shortcut for 'cgc visualize'"""
1693 | visualize(query)
1694 |
1695 | @app.command("w", rich_help_panel="Shortcuts")
1696 | def watch_abbrev(path: str = typer.Argument(".", help="Path to watch")):
1697 | """Shortcut for 'cgc watch'"""
1698 | watch(path)
1699 |
1700 |
1701 | # ============================================================================
1702 |
1703 |
1704 |
1705 | @app.command()
1706 | def help(ctx: typer.Context):
1707 | """Show the main help message and exit."""
1708 | root_ctx = ctx.parent or ctx
1709 | typer.echo(root_ctx.get_help())
1710 |
1711 |
1712 | @app.command("version")
1713 | def version_cmd():
1714 | """Show the application version."""
1715 | console.print(f"CodeGraphContext [bold cyan]{get_version()}[/bold cyan]")
1716 |
1717 |
1718 | @app.callback(invoke_without_command=True)
1719 | def main(
1720 | ctx: typer.Context,
1721 | database: Optional[str] = typer.Option(
1722 | None,
1723 | "--database",
1724 | "-db",
1725 | help="[Global] Temporarily override database backend (falkordb or neo4j) for any command"
1726 | ),
1727 | version_: bool = typer.Option(
1728 | None,
1729 | "--version",
1730 | "-v",
1731 | help="[Root-level only] Show version and exit",
1732 | is_eager=True,
1733 | ),
1734 | help_: bool = typer.Option(
1735 | None,
1736 | "--help",
1737 | "-h",
1738 | help="[Root-level only] Show help and exit",
1739 | is_eager=True,
1740 | ),
1741 | ):
1742 | """
1743 | Main entry point for the cgc CLI application.
1744 | If no subcommand is provided, it displays a welcome message with instructions.
1745 | """
1746 | if database:
1747 | os.environ["CGC_RUNTIME_DB_TYPE"] = database
1748 |
1749 | if version_:
1750 | console.print(f"CodeGraphContext [bold cyan]{get_version()}[/bold cyan]")
1751 | raise typer.Exit()
1752 |
1753 | if ctx.invoked_subcommand is None:
1754 | console.print("[bold green]👋 Welcome to CodeGraphContext (cgc)![/bold green]\n")
1755 | console.print("CodeGraphContext is both an [bold cyan]MCP server[/bold cyan] and a [bold cyan]CLI toolkit[/bold cyan] for code analysis.\n")
1756 | console.print("🤖 [bold]For MCP Server Mode (AI assistants):[/bold]")
1757 | console.print(" 1. Run [cyan]cgc mcp setup[/cyan] (or [cyan]cgc m[/cyan]) to configure your IDE")
1758 | console.print(" 2. Run [cyan]cgc mcp start[/cyan] to launch the server\n")
1759 | console.print("🛠️ [bold]For CLI Toolkit Mode (direct usage):[/bold]")
1760 | console.print(" • [cyan]cgc index .[/cyan] - Index your current directory")
1761 | console.print(" • [cyan]cgc list[/cyan] - List indexed repositories\n")
1762 | console.print("📊 [bold]Using Neo4j instead of FalkorDB?[/bold]")
1763 | console.print(" Run [cyan]cgc neo4j setup[/cyan] (or [cyan]cgc n[/cyan]) to configure Neo4j\n")
1764 | console.print("👉 Run [cyan]cgc help[/cyan] to see all available commands")
1765 | console.print("👉 Run [cyan]cgc --version[/cyan] to check the version\n")
1766 | console.print("👉 Running [green]codegraphcontext[/green] works the same as using [green]cgc[/green]")
1767 |
1768 |
1769 | if __name__ == "__main__":
1770 | app()
```