codeboarding 0.12.0__tar.gz → 0.12.2__tar.gz

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 (192) hide show
  1. {codeboarding-0.12.0/codeboarding.egg-info → codeboarding-0.12.2}/PKG-INFO +7 -3
  2. {codeboarding-0.12.0 → codeboarding-0.12.2}/PYPI.md +4 -2
  3. {codeboarding-0.12.0 → codeboarding-0.12.2}/README.md +24 -7
  4. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/cluster_methods_mixin.py +27 -5
  5. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/llm_config.py +135 -39
  6. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/model_capabilities.py +6 -2
  7. {codeboarding-0.12.0 → codeboarding-0.12.2/codeboarding.egg-info}/PKG-INFO +7 -3
  8. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding.egg-info/SOURCES.txt +8 -0
  9. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding.egg-info/requires.txt +2 -0
  10. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding.egg-info/top_level.txt +1 -0
  11. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding_workflows/analysis.py +4 -3
  12. {codeboarding-0.12.0 → codeboarding-0.12.2}/diagram_analysis/diagram_generator.py +11 -2
  13. {codeboarding-0.12.0 → codeboarding-0.12.2}/github_action.py +1 -0
  14. codeboarding-0.12.2/health_main.py +152 -0
  15. {codeboarding-0.12.0 → codeboarding-0.12.2}/main.py +2 -0
  16. {codeboarding-0.12.0 → codeboarding-0.12.2}/pyproject.toml +4 -1
  17. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/__init__.py +88 -12
  18. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/cfg_skip_planner.py +7 -0
  19. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/csharp_config_scanner.py +14 -7
  20. codeboarding-0.12.2/static_analyzer/dotnet_sdk.py +327 -0
  21. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/adapters/csharp_adapter.py +75 -29
  22. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/adapters/go_adapter.py +1 -1
  23. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/language_adapter.py +6 -1
  24. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/lsp_client.py +15 -8
  25. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/scanner.py +2 -0
  26. codeboarding-0.12.2/telemetry/__init__.py +10 -0
  27. codeboarding-0.12.2/telemetry/device_id.py +91 -0
  28. codeboarding-0.12.2/telemetry/events.py +246 -0
  29. codeboarding-0.12.2/telemetry/schemas.py +67 -0
  30. codeboarding-0.12.2/telemetry/service.py +97 -0
  31. codeboarding-0.12.2/tests/test_telemetry_events.py +196 -0
  32. {codeboarding-0.12.0 → codeboarding-0.12.2}/tests/test_tool_registry.py +93 -8
  33. codeboarding-0.12.2/tests/test_user_config.py +220 -0
  34. {codeboarding-0.12.0 → codeboarding-0.12.2}/tool_registry/__init__.py +1 -0
  35. {codeboarding-0.12.0 → codeboarding-0.12.2}/tool_registry/installers.py +79 -4
  36. {codeboarding-0.12.0 → codeboarding-0.12.2}/tool_registry/manifest.py +8 -8
  37. {codeboarding-0.12.0 → codeboarding-0.12.2}/tool_registry/paths.py +13 -0
  38. {codeboarding-0.12.0 → codeboarding-0.12.2}/tool_registry/registry.py +5 -7
  39. {codeboarding-0.12.0 → codeboarding-0.12.2}/user_config.py +31 -5
  40. codeboarding-0.12.0/tests/test_user_config.py +0 -98
  41. {codeboarding-0.12.0 → codeboarding-0.12.2}/LICENSE +0 -0
  42. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/__init__.py +0 -0
  43. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/abstraction_agent.py +0 -0
  44. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/agent.py +0 -0
  45. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/agent_responses.py +0 -0
  46. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/change_status.py +0 -0
  47. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/cluster_budget.py +0 -0
  48. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/constants.py +0 -0
  49. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/dependency_discovery.py +0 -0
  50. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/details_agent.py +0 -0
  51. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/incremental_agent.py +0 -0
  52. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/meta_agent.py +0 -0
  53. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/planner_agent.py +0 -0
  54. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/prompts/__init__.py +0 -0
  55. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/prompts/abstract_prompt_factory.py +0 -0
  56. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/prompts/claude_prompts.py +0 -0
  57. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/prompts/deepseek_prompts.py +0 -0
  58. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/prompts/gemini_flash_prompts.py +0 -0
  59. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/prompts/glm_prompts.py +0 -0
  60. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/prompts/gpt_prompts.py +0 -0
  61. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/prompts/kimi_prompts.py +0 -0
  62. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/prompts/prompt_factory.py +0 -0
  63. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/retry.py +0 -0
  64. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/tools/__init__.py +0 -0
  65. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/tools/base.py +0 -0
  66. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/tools/get_external_deps.py +0 -0
  67. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/tools/get_method_invocations.py +0 -0
  68. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/tools/read_cfg.py +0 -0
  69. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/tools/read_docs.py +0 -0
  70. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/tools/read_file.py +0 -0
  71. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/tools/read_file_structure.py +0 -0
  72. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/tools/read_packages.py +0 -0
  73. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/tools/read_source.py +0 -0
  74. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/tools/read_structure.py +0 -0
  75. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/tools/toolkit.py +0 -0
  76. {codeboarding-0.12.0 → codeboarding-0.12.2}/agents/validation.py +0 -0
  77. {codeboarding-0.12.0 → codeboarding-0.12.2}/caching/__init__.py +0 -0
  78. {codeboarding-0.12.0 → codeboarding-0.12.2}/caching/cache.py +0 -0
  79. {codeboarding-0.12.0 → codeboarding-0.12.2}/caching/details_cache.py +0 -0
  80. {codeboarding-0.12.0 → codeboarding-0.12.2}/caching/meta_cache.py +0 -0
  81. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding.egg-info/dependency_links.txt +0 -0
  82. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding.egg-info/entry_points.txt +0 -0
  83. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding_cli/__init__.py +0 -0
  84. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding_cli/bootstrap.py +0 -0
  85. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding_cli/commands/__init__.py +0 -0
  86. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding_cli/commands/full_analysis.py +0 -0
  87. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding_cli/commands/incremental_analysis.py +0 -0
  88. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding_cli/commands/partial_analysis.py +0 -0
  89. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding_workflows/__init__.py +0 -0
  90. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding_workflows/orchestration.py +0 -0
  91. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding_workflows/rendering.py +0 -0
  92. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding_workflows/sources/__init__.py +0 -0
  93. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding_workflows/sources/local.py +0 -0
  94. {codeboarding-0.12.0 → codeboarding-0.12.2}/codeboarding_workflows/sources/remote.py +0 -0
  95. {codeboarding-0.12.0 → codeboarding-0.12.2}/constants.py +0 -0
  96. {codeboarding-0.12.0 → codeboarding-0.12.2}/core/__init__.py +0 -0
  97. {codeboarding-0.12.0 → codeboarding-0.12.2}/core/plugin_loader.py +0 -0
  98. {codeboarding-0.12.0 → codeboarding-0.12.2}/core/protocols.py +0 -0
  99. {codeboarding-0.12.0 → codeboarding-0.12.2}/core/registry.py +0 -0
  100. {codeboarding-0.12.0 → codeboarding-0.12.2}/diagram_analysis/__init__.py +0 -0
  101. {codeboarding-0.12.0 → codeboarding-0.12.2}/diagram_analysis/analysis_json.py +0 -0
  102. {codeboarding-0.12.0 → codeboarding-0.12.2}/diagram_analysis/cluster_delta.py +0 -0
  103. {codeboarding-0.12.0 → codeboarding-0.12.2}/diagram_analysis/cluster_snapshot.py +0 -0
  104. {codeboarding-0.12.0 → codeboarding-0.12.2}/diagram_analysis/exceptions.py +0 -0
  105. {codeboarding-0.12.0 → codeboarding-0.12.2}/diagram_analysis/file_coverage.py +0 -0
  106. {codeboarding-0.12.0 → codeboarding-0.12.2}/diagram_analysis/io_utils.py +0 -0
  107. {codeboarding-0.12.0 → codeboarding-0.12.2}/diagram_analysis/run_context.py +0 -0
  108. {codeboarding-0.12.0 → codeboarding-0.12.2}/diagram_analysis/run_mode.py +0 -0
  109. {codeboarding-0.12.0 → codeboarding-0.12.2}/diagram_analysis/version.py +0 -0
  110. {codeboarding-0.12.0 → codeboarding-0.12.2}/health/__init__.py +0 -0
  111. {codeboarding-0.12.0 → codeboarding-0.12.2}/health/checks/__init__.py +0 -0
  112. {codeboarding-0.12.0 → codeboarding-0.12.2}/health/checks/circular_deps.py +0 -0
  113. {codeboarding-0.12.0 → codeboarding-0.12.2}/health/checks/cohesion.py +0 -0
  114. {codeboarding-0.12.0 → codeboarding-0.12.2}/health/checks/coupling.py +0 -0
  115. {codeboarding-0.12.0 → codeboarding-0.12.2}/health/checks/function_size.py +0 -0
  116. {codeboarding-0.12.0 → codeboarding-0.12.2}/health/checks/god_class.py +0 -0
  117. {codeboarding-0.12.0 → codeboarding-0.12.2}/health/checks/inheritance.py +0 -0
  118. {codeboarding-0.12.0 → codeboarding-0.12.2}/health/checks/instability.py +0 -0
  119. {codeboarding-0.12.0 → codeboarding-0.12.2}/health/checks/unused_code_diagnostics.py +0 -0
  120. {codeboarding-0.12.0 → codeboarding-0.12.2}/health/config.py +0 -0
  121. {codeboarding-0.12.0 → codeboarding-0.12.2}/health/models.py +0 -0
  122. {codeboarding-0.12.0 → codeboarding-0.12.2}/health/runner.py +0 -0
  123. {codeboarding-0.12.0 → codeboarding-0.12.2}/install.py +0 -0
  124. {codeboarding-0.12.0 → codeboarding-0.12.2}/logging_config.py +0 -0
  125. {codeboarding-0.12.0 → codeboarding-0.12.2}/monitoring/__init__.py +0 -0
  126. {codeboarding-0.12.0 → codeboarding-0.12.2}/monitoring/callbacks.py +0 -0
  127. {codeboarding-0.12.0 → codeboarding-0.12.2}/monitoring/context.py +0 -0
  128. {codeboarding-0.12.0 → codeboarding-0.12.2}/monitoring/mixin.py +0 -0
  129. {codeboarding-0.12.0 → codeboarding-0.12.2}/monitoring/paths.py +0 -0
  130. {codeboarding-0.12.0 → codeboarding-0.12.2}/monitoring/stats.py +0 -0
  131. {codeboarding-0.12.0 → codeboarding-0.12.2}/monitoring/writers.py +0 -0
  132. {codeboarding-0.12.0 → codeboarding-0.12.2}/output_generators/__init__.py +0 -0
  133. {codeboarding-0.12.0 → codeboarding-0.12.2}/output_generators/html.py +0 -0
  134. {codeboarding-0.12.0 → codeboarding-0.12.2}/output_generators/html_template.py +0 -0
  135. {codeboarding-0.12.0 → codeboarding-0.12.2}/output_generators/markdown.py +0 -0
  136. {codeboarding-0.12.0 → codeboarding-0.12.2}/output_generators/mdx.py +0 -0
  137. {codeboarding-0.12.0 → codeboarding-0.12.2}/output_generators/sphinx.py +0 -0
  138. {codeboarding-0.12.0 → codeboarding-0.12.2}/repo_utils/__init__.py +0 -0
  139. {codeboarding-0.12.0 → codeboarding-0.12.2}/repo_utils/change_detector.py +0 -0
  140. {codeboarding-0.12.0 → codeboarding-0.12.2}/repo_utils/diff_parser.py +0 -0
  141. {codeboarding-0.12.0 → codeboarding-0.12.2}/repo_utils/errors.py +0 -0
  142. {codeboarding-0.12.0 → codeboarding-0.12.2}/repo_utils/git_ops.py +0 -0
  143. {codeboarding-0.12.0 → codeboarding-0.12.2}/repo_utils/ignore.py +0 -0
  144. {codeboarding-0.12.0 → codeboarding-0.12.2}/setup.cfg +0 -0
  145. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/analysis_cache.py +0 -0
  146. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/analysis_result.py +0 -0
  147. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/cluster_helpers.py +0 -0
  148. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/cluster_relations.py +0 -0
  149. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/constants.py +0 -0
  150. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/__init__.py +0 -0
  151. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/adapters/__init__.py +0 -0
  152. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/adapters/java_adapter.py +0 -0
  153. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/adapters/php_adapter.py +0 -0
  154. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/adapters/python_adapter.py +0 -0
  155. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/adapters/rust_adapter.py +0 -0
  156. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/adapters/typescript_adapter.py +0 -0
  157. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/call_graph_builder.py +0 -0
  158. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/edge_build_context.py +0 -0
  159. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/edge_builder.py +0 -0
  160. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/hierarchy_builder.py +0 -0
  161. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/lsp_constants.py +0 -0
  162. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/models.py +0 -0
  163. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/progress.py +0 -0
  164. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/protocols.py +0 -0
  165. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/result_converter.py +0 -0
  166. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/source_inspector.py +0 -0
  167. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/symbol_table.py +0 -0
  168. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/engine/utils.py +0 -0
  169. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/graph.py +0 -0
  170. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/incremental_orchestrator.py +0 -0
  171. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/java_config_scanner.py +0 -0
  172. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/java_utils.py +0 -0
  173. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/language_results.py +0 -0
  174. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/leiden_utils.py +0 -0
  175. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/lsp_client/__init__.py +0 -0
  176. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/lsp_client/diagnostics.py +0 -0
  177. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/node.py +0 -0
  178. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/programming_language.py +0 -0
  179. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/reference_resolve_mixin.py +0 -0
  180. {codeboarding-0.12.0 → codeboarding-0.12.2}/static_analyzer/typescript_config_scanner.py +0 -0
  181. {codeboarding-0.12.0 → codeboarding-0.12.2}/tests/test_cli_parser.py +0 -0
  182. {codeboarding-0.12.0 → codeboarding-0.12.2}/tests/test_github_action.py +0 -0
  183. {codeboarding-0.12.0 → codeboarding-0.12.2}/tests/test_install.py +0 -0
  184. {codeboarding-0.12.0 → codeboarding-0.12.2}/tests/test_logging_config.py +0 -0
  185. {codeboarding-0.12.0 → codeboarding-0.12.2}/tests/test_main.py +0 -0
  186. {codeboarding-0.12.0 → codeboarding-0.12.2}/tests/test_pyproject_packages.py +0 -0
  187. {codeboarding-0.12.0 → codeboarding-0.12.2}/tests/test_registry_coverage.py +0 -0
  188. {codeboarding-0.12.0 → codeboarding-0.12.2}/tests/test_vscode_constants.py +0 -0
  189. {codeboarding-0.12.0 → codeboarding-0.12.2}/tests/test_windows_compatibility.py +0 -0
  190. {codeboarding-0.12.0 → codeboarding-0.12.2}/tests/test_windows_encoding.py +0 -0
  191. {codeboarding-0.12.0 → codeboarding-0.12.2}/utils.py +0 -0
  192. {codeboarding-0.12.0 → codeboarding-0.12.2}/vscode_constants.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.12.0
3
+ Version: 0.12.2
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -40,6 +40,8 @@ Requires-Dist: markitdown>=0.1
40
40
  Requires-Dist: networkx>=3.4
41
41
  Requires-Dist: nodeenv>=1.10.0
42
42
  Requires-Dist: pathspec>=0.12
43
+ Requires-Dist: posthog>=3.7
44
+ Requires-Dist: pydantic>=2.0
43
45
  Requires-Dist: pyyaml>=6.0
44
46
  Requires-Dist: regex>=2024.11
45
47
  Requires-Dist: rich>=12.6
@@ -93,15 +95,17 @@ Dynamic: license-file
93
95
  The recommended way to install the CLI is with [pipx](https://pipx.pypa.io), which automatically creates an isolated environment:
94
96
 
95
97
  ```bash
96
- pipx install codeboarding --python python3.12
98
+ pipx install codeboarding --python python3.12 --pip-args="--extra-index-url https://pip.codeboarding.org/simple/"
97
99
  ```
98
100
 
99
101
  Alternatively, install into an existing virtual environment with pip:
100
102
 
101
103
  ```bash
102
- pip install codeboarding
104
+ pip install codeboarding --extra-index-url https://pip.codeboarding.org/simple/
103
105
  ```
104
106
 
107
+
108
+
105
109
  > Installing into the global Python environment with `pip` is not recommended — it can cause dependency conflicts and will fail if the system Python is not 3.12 or 3.13.
106
110
 
107
111
  Language server binaries are downloaded automatically on first use. To pre-install them explicitly (useful in CI or restricted environments):
@@ -23,15 +23,17 @@
23
23
  The recommended way to install the CLI is with [pipx](https://pipx.pypa.io), which automatically creates an isolated environment:
24
24
 
25
25
  ```bash
26
- pipx install codeboarding --python python3.12
26
+ pipx install codeboarding --python python3.12 --pip-args="--extra-index-url https://pip.codeboarding.org/simple/"
27
27
  ```
28
28
 
29
29
  Alternatively, install into an existing virtual environment with pip:
30
30
 
31
31
  ```bash
32
- pip install codeboarding
32
+ pip install codeboarding --extra-index-url https://pip.codeboarding.org/simple/
33
33
  ```
34
34
 
35
+
36
+
35
37
  > Installing into the global Python environment with `pip` is not recommended — it can cause dependency conflicts and will fail if the system Python is not 3.12 or 3.13.
36
38
 
37
39
  Language server binaries are downloaded automatically on first use. To pre-install them explicitly (useful in CI or restricted environments):
@@ -4,9 +4,9 @@ See what your AI is building before it breaks.
4
4
 
5
5
  CodeBoarding gives developers and coding agents a visual map of a codebase. It combines static analysis with LLM reasoning to generate architecture diagrams, component-level documentation, and navigable outputs you can use in your IDE, CI, and docs.
6
6
 
7
- [Website](https://codeboarding.org) · [Open VSX extension](https://open-vsx.org/extension/CodeBoarding/codeboarding) · [Explore examples](https://codeboarding.org/diagrams) · [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding) · [GitHub Action](https://github.com/marketplace/actions/codeboarding-diagram-first-documentation) ·[Discord](https://discord.gg/T5zHTJYFuy)
7
+ [Website](https://codeboarding.org) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=0855d476-b2d0-44cc-b93d-69b47504719c" width="0" height="0" /> · [Open VSX extension](https://open-vsx.org/extension/CodeBoarding/codeboarding) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=ce87464c-2792-46b0-9ea2-87eefe853d7e" width="0" height="0" /> · [Explore examples](https://codeboarding.org/diagrams) · [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=8a3d26e0-6f6b-49c0-8482-114445de56a5" width="0" height="0" /> · [GitHub Action](https://github.com/marketplace/actions/codeboarding-action) ·[Discord](https://discord.gg/T5zHTJYFuy)
8
8
 
9
- [![CodeBoarding demo](https://gist.githubusercontent.com/ivanmilevtues/1c4f921066613516cfd7b938014a6877/raw/611aec7711556807860ff2e1679a5dc4c0c23fed/CodeBoarding_extension_demo.gif)](https://open-vsx.org/extension/CodeBoarding/codeboarding)
9
+ [![CodeBoarding demo](https://gist.githubusercontent.com/ivanmilevtues/1c4f921066613516cfd7b938014a6877/raw/611aec7711556807860ff2e1679a5dc4c0c23fed/CodeBoarding_extension_demo.gif)](https://open-vsx.org/extension/CodeBoarding/codeboarding) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=ce87464c-2792-46b0-9ea2-87eefe853d7e" width="0" height="0" />
10
10
 
11
11
  Install the extension from Open VSX.
12
12
 
@@ -86,7 +86,7 @@ codeboarding full --local /path/to/repo
86
86
  Or, if you prefer pip, install into a virtual environment (not the global Python):
87
87
 
88
88
  ```bash
89
- pip install codeboarding
89
+ pip install codeboarding --extra-index-url https://pip.codeboarding.org/simple/
90
90
  codeboarding-setup
91
91
  codeboarding full --local /path/to/repo
92
92
  ```
@@ -108,6 +108,8 @@ On first run, CodeBoarding creates `~/.codeboarding/config.toml`. Set one provid
108
108
  # aws_bearer_token_bedrock = "..."
109
109
  # ollama_base_url = "http://localhost:11434"
110
110
  # openrouter_api_key = "sk-..."
111
+ # litellm_base_url = "http://localhost:4000" # LiteLLM proxy server URL (required)
112
+ # litellm_api_key = "sk-..." # LiteLLM proxy server key (optional)
111
113
 
112
114
  [llm]
113
115
  # agent_model = "gemini-3-flash"
@@ -138,19 +140,32 @@ python main.py full https://github.com/pytorch/pytorch
138
140
  ## Where to use it
139
141
 
140
142
  - [CLI](https://github.com/CodeBoarding/CodeBoarding) for local analysis, automation, and CI workflows.
141
- - [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding) for in-editor visual architecture.
142
- - [GitHub Action](https://github.com/marketplace/actions/codeboarding-diagram-first-documentation) to keep diagrams updated in CI.
143
+ - [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=8a3d26e0-6f6b-49c0-8482-114445de56a5" width="0" height="0" /> for in-editor visual architecture.
144
+ - [GitHub Action](https://github.com/marketplace/actions/codeboarding-action) to keep diagrams updated in CI.
143
145
 
144
146
  ## Supported stack
145
147
 
146
148
  - Languages: Python, TypeScript, JavaScript, Java, Go, PHP, Rust, C#.
147
- - LLM providers: OpenAI, Anthropic, Google, Vercel AI Gateway, AWS Bedrock, Ollama, OpenRouter, and more.
149
+ - LLM providers: OpenAI, Anthropic, Google, Vercel AI Gateway, AWS Bedrock, Ollama, OpenRouter, LiteLLM proxy, and more.
148
150
 
149
151
  ## Examples
150
152
 
151
153
  - Visualized 800+ open-source repositories.
152
154
  - Browse generated examples in [GeneratedOnBoardings](https://github.com/CodeBoarding/GeneratedOnBoardings).
153
- - Try the hosted explorer at [codeboarding.org/diagrams](https://codeboarding.org/diagrams).
155
+ - Try the hosted explorer at [codeboarding.org/diagrams](https://codeboarding.org/diagrams) <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=0855d476-b2d0-44cc-b93d-69b47504719c" width="0" height="0" />.
156
+
157
+ ## Telemetry
158
+
159
+ CodeBoarding collects anonymous, aggregate usage telemetry (which command ran,
160
+ success/failure, duration, and token cost) to help us improve the tool. It is on
161
+ by default and never collects source code, file names, repository names, paths,
162
+ prompts, model outputs, API keys, or any personal information. Opt out anytime:
163
+
164
+ ```bash
165
+ export CODEBOARDING_TELEMETRY=false # or: export DO_NOT_TRACK=1
166
+ ```
167
+
168
+ See [TELEMETRY.md](TELEMETRY.md) for the full list of events and properties.
154
169
 
155
170
  ## Contributing
156
171
 
@@ -159,3 +174,5 @@ If you want to improve CodeBoarding, open an [issue](https://github.com/CodeBoar
159
174
  ## Vision
160
175
 
161
176
  CodeBoarding is building an open standard for code understanding: a visual, accurate, high-level representation of a codebase that both humans and agents can use.
177
+
178
+ <img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=1942e7e7-0762-4cdd-9f08-024acc098071" />
@@ -16,7 +16,8 @@ from agents.agent_responses import (
16
16
  MethodEntry,
17
17
  )
18
18
  from agents.cluster_budget import ClusterPromptBudget
19
- from agents.llm_config import get_current_agent_context_window
19
+ from agents.llm_config import get_current_agent_context_window, get_current_agent_model_ref
20
+ from agents.model_capabilities import ContextWindow
20
21
  from constants import MIN_CLUSTERS_THRESHOLD
21
22
  from static_analyzer.analysis_result import StaticAnalysisResults
22
23
  from static_analyzer.cfg_skip_planner import ContextBudgetExceededError, plan_skip_set
@@ -46,6 +47,20 @@ class _RenderedClusterString:
46
47
  cluster_ids: set[int]
47
48
 
48
49
 
50
+ def _describe_window(ctx: ContextWindow) -> str:
51
+ suffix = "; fallback default, model window unresolved" if ctx.is_fallback else ""
52
+ return f"{ctx.input_tokens} input tokens for {get_current_agent_model_ref()}{suffix}"
53
+
54
+
55
+ def _window_telemetry(ctx: ContextWindow, char_budget: int) -> dict:
56
+ return {
57
+ "char_budget": char_budget,
58
+ "window_input_tokens": ctx.input_tokens,
59
+ "window_is_fallback": ctx.is_fallback,
60
+ "agent_model": get_current_agent_model_ref(),
61
+ }
62
+
63
+
49
64
  class ClusterMethodsMixin:
50
65
  """
51
66
  Mixin providing shared cluster-related functionality for agents.
@@ -156,10 +171,10 @@ class ClusterMethodsMixin:
156
171
  ctx = get_current_agent_context_window()
157
172
  msg = (
158
173
  f"Prompt overhead ({prompt_overhead_chars} chars) consumes the entire agent input "
159
- f"window ({ctx.input_tokens} tokens); no room for cluster renderings."
174
+ f"window ({_describe_window(ctx)}); no room for cluster renderings."
160
175
  )
161
176
  logger.error("[CFG skip planner] %s", msg)
162
- raise ContextBudgetExceededError(msg)
177
+ raise ContextBudgetExceededError(msg, telemetry_properties=_window_telemetry(ctx, char_budget))
163
178
 
164
179
  langs_with_clusters = [l for l in programming_langs if cluster_results.get(l)]
165
180
  if not langs_with_clusters:
@@ -240,14 +255,21 @@ class ClusterMethodsMixin:
240
255
  rendered: _RenderedClusterString,
241
256
  skip_sets: dict[str, set[str]],
242
257
  ) -> NoReturn:
258
+ ctx = get_current_agent_context_window()
243
259
  per_lang_sizes = {lang: len(text) for lang, text in rendered.by_language.items()}
244
260
  skipped_counts = {lang: len(skip) for lang, skip in skip_sets.items() if skip}
245
261
  msg = (
246
- f"Cluster render {len(rendered.text)} chars exceeds budget {char_budget}. "
262
+ f"Cluster render {len(rendered.text)} chars exceeds budget {char_budget} "
263
+ f"(agent window: {_describe_window(ctx)}). "
247
264
  f"Per-language sizes: {per_lang_sizes}; skipped nodes: {skipped_counts}."
248
265
  )
249
266
  logger.error("[CFG skip planner] %s", msg)
250
- raise ContextBudgetExceededError(msg)
267
+ telemetry = _window_telemetry(ctx, char_budget) | {
268
+ "render_chars": len(rendered.text),
269
+ "per_language_chars": per_lang_sizes,
270
+ "skipped_node_counts": skipped_counts,
271
+ }
272
+ raise ContextBudgetExceededError(msg, telemetry_properties=telemetry)
251
273
 
252
274
  @staticmethod
253
275
  def _cluster_prompt_budget(prompt_overhead_chars: int) -> int:
@@ -23,6 +23,8 @@ MONITORING_CALLBACK = MonitoringCallback(stats_container=RunStats())
23
23
 
24
24
  logger = logging.getLogger(__name__)
25
25
 
26
+ _OPENROUTER_FALLBACK_CONTEXT_WINDOW = ContextWindow(1_048_576, 65_536, is_fallback=True)
27
+
26
28
  # ---------------------------------------------------------------------------
27
29
  # Module-level model overrides – set once by the orchestrator (main.py) and
28
30
  # consumed by initialize_llms() without needing to thread the values through
@@ -67,6 +69,9 @@ class LLMConfig:
67
69
  Configuration for LLM providers.
68
70
 
69
71
  Attributes:
72
+ selection_envs: Env vars that select this provider — any one being set selects it.
73
+ api_key_env: Env var holding the provider's secret, or None when the
74
+ underlying SDK reads its credentials from the environment itself.
70
75
  agent_model: The "agent" model used for complex reasoning and agentic tasks.
71
76
  parsing_model: The "parsing" model used for fast, cost-effective extraction and parsing tasks.
72
77
  agent_temperature: Temperature for the agent model. Defaults to 0 for deterministic behavior
@@ -77,23 +82,37 @@ class LLMConfig:
77
82
  """
78
83
 
79
84
  chat_class: Type[BaseChatModel]
80
- api_key_env: str
85
+ selection_envs: list[str]
81
86
  agent_model: str
82
87
  parsing_model: str
83
88
  llm_type: LLMType
84
89
  agent_temperature: float = LLMDefaults.DEFAULT_AGENT_TEMPERATURE
85
90
  parsing_temperature: float = LLMDefaults.DEFAULT_PARSING_TEMPERATURE
86
91
  extra_args: dict[str, Any] = field(default_factory=dict)
87
- alt_env_vars: list[str] = field(default_factory=list)
92
+ api_key_env: str | None = None
93
+ keyless_capable: bool = False
94
+ """Whether this provider can run without a real API key.
95
+
96
+ True for self-hosted / OpenAI-compatible endpoints that accept
97
+ unauthenticated requests. When such a provider is the sole selected one
98
+ and no real key is set, key validation warns instead of failing, and the
99
+ client uses a placeholder.
100
+ """
88
101
 
89
102
  def get_api_key(self) -> str | None:
90
- return os.getenv(self.api_key_env)
103
+ return os.getenv(self.api_key_env) if self.api_key_env else None
104
+
105
+ def has_real_api_key(self) -> bool:
106
+ """True if the provider's API-key env var holds a value.
107
+
108
+ Distinct from ``is_selected_by_env()``: a keyless-capable provider can
109
+ be selected via a base-URL var while having no real key here.
110
+ """
111
+ return bool(self.get_api_key())
91
112
 
92
- def is_active(self) -> bool:
93
- """Check if any of the environment variables (primary or alternate) are set."""
94
- if os.getenv(self.api_key_env):
95
- return True
96
- return any(os.getenv(var) for var in self.alt_env_vars)
113
+ def is_selected_by_env(self) -> bool:
114
+ """True when any of this provider's selection env vars is set."""
115
+ return any(os.getenv(var) for var in self.selection_envs)
97
116
 
98
117
  def get_resolved_extra_args(self) -> dict[str, Any]:
99
118
  resolved = {}
@@ -108,11 +127,12 @@ class LLMConfig:
108
127
  LLM_PROVIDERS = {
109
128
  "openai": LLMConfig(
110
129
  chat_class=ChatOpenAI,
130
+ selection_envs=["OPENAI_API_KEY", "OPENAI_BASE_URL"],
111
131
  api_key_env="OPENAI_API_KEY",
112
132
  agent_model="gpt-4o",
113
133
  parsing_model="gpt-4o-mini",
114
134
  llm_type=LLMType.GPT4,
115
- alt_env_vars=["OPENAI_BASE_URL"],
135
+ keyless_capable=True,
116
136
  extra_args={
117
137
  "base_url": lambda: os.getenv("OPENAI_BASE_URL"),
118
138
  "max_tokens": None,
@@ -122,11 +142,11 @@ LLM_PROVIDERS = {
122
142
  ),
123
143
  "vercel": LLMConfig(
124
144
  chat_class=ChatOpenAI,
145
+ selection_envs=["VERCEL_API_KEY", "VERCEL_BASE_URL"],
125
146
  api_key_env="VERCEL_API_KEY",
126
147
  agent_model="google/gemini-3-flash",
127
148
  parsing_model="openai/gpt-5-mini",
128
149
  llm_type=LLMType.GEMINI_FLASH,
129
- alt_env_vars=["VERCEL_BASE_URL"],
130
150
  extra_args={
131
151
  "base_url": lambda: os.getenv("VERCEL_BASE_URL", f"https://ai-gateway.vercel.sh/v1"),
132
152
  "max_tokens": None,
@@ -136,6 +156,7 @@ LLM_PROVIDERS = {
136
156
  ),
137
157
  "anthropic": LLMConfig(
138
158
  chat_class=ChatAnthropic,
159
+ selection_envs=["ANTHROPIC_API_KEY"],
139
160
  api_key_env="ANTHROPIC_API_KEY",
140
161
  agent_model="claude-sonnet-4-6",
141
162
  parsing_model="claude-haiku-4-5",
@@ -148,6 +169,7 @@ LLM_PROVIDERS = {
148
169
  ),
149
170
  "google": LLMConfig(
150
171
  chat_class=ChatGoogleGenerativeAI,
172
+ selection_envs=["GOOGLE_API_KEY"],
151
173
  api_key_env="GOOGLE_API_KEY",
152
174
  agent_model="gemini-3-flash-preview",
153
175
  parsing_model="gemini-3.1-flash-lite",
@@ -160,7 +182,8 @@ LLM_PROVIDERS = {
160
182
  ),
161
183
  "aws": LLMConfig(
162
184
  chat_class=ChatBedrockConverse,
163
- api_key_env="AWS_BEARER_TOKEN_BEDROCK", # Used for existence check
185
+ # No api_key_env: botocore reads AWS_BEARER_TOKEN_BEDROCK from the environment itself.
186
+ selection_envs=["AWS_BEARER_TOKEN_BEDROCK"],
164
187
  agent_model="anthropic.claude-sonnet-4-6",
165
188
  parsing_model="claude-haiku-4-5",
166
189
  llm_type=LLMType.CLAUDE_SONNET,
@@ -172,6 +195,7 @@ LLM_PROVIDERS = {
172
195
  ),
173
196
  "cerebras": LLMConfig(
174
197
  chat_class=ChatCerebras,
198
+ selection_envs=["CEREBRAS_API_KEY"],
175
199
  api_key_env="CEREBRAS_API_KEY",
176
200
  agent_model="zai-glm-4.7",
177
201
  parsing_model="gpt-oss-120b",
@@ -184,7 +208,11 @@ LLM_PROVIDERS = {
184
208
  ),
185
209
  "ollama": LLMConfig(
186
210
  chat_class=ChatOllama,
187
- api_key_env="OLLAMA_BASE_URL", # Used for existence check
211
+ # OLLAMA_HOST is Ollama's canonical host var; the client falls back to it
212
+ # when no base_url is passed, and sends OLLAMA_API_KEY (Ollama cloud) itself.
213
+ selection_envs=["OLLAMA_BASE_URL", "OLLAMA_HOST"],
214
+ api_key_env="OLLAMA_API_KEY",
215
+ keyless_capable=True,
188
216
  agent_model="qwen3:30b",
189
217
  parsing_model="qwen2.5:7b",
190
218
  llm_type=LLMType.GEMINI_FLASH,
@@ -196,11 +224,11 @@ LLM_PROVIDERS = {
196
224
  ),
197
225
  "deepseek": LLMConfig(
198
226
  chat_class=ChatOpenAI,
227
+ selection_envs=["DEEPSEEK_API_KEY", "DEEPSEEK_BASE_URL"],
199
228
  api_key_env="DEEPSEEK_API_KEY",
200
229
  agent_model="deepseek-chat",
201
230
  parsing_model="deepseek-chat",
202
231
  llm_type=LLMType.DEEPSEEK,
203
- alt_env_vars=["DEEPSEEK_BASE_URL"],
204
232
  extra_args={
205
233
  "base_url": lambda: os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com/v1"),
206
234
  "max_tokens": None,
@@ -210,11 +238,11 @@ LLM_PROVIDERS = {
210
238
  ),
211
239
  "glm": LLMConfig(
212
240
  chat_class=ChatOpenAI,
241
+ selection_envs=["GLM_API_KEY", "GLM_BASE_URL"],
213
242
  api_key_env="GLM_API_KEY",
214
243
  agent_model="glm-4.7-flash",
215
244
  parsing_model="glm-4.7-flash",
216
245
  llm_type=LLMType.GLM,
217
- alt_env_vars=["GLM_BASE_URL"],
218
246
  extra_args={
219
247
  "base_url": lambda: os.getenv("GLM_BASE_URL", "https://open.bigmodel.cn/api/paas/v4"),
220
248
  "max_tokens": None,
@@ -224,11 +252,11 @@ LLM_PROVIDERS = {
224
252
  ),
225
253
  "kimi": LLMConfig(
226
254
  chat_class=ChatOpenAI,
255
+ selection_envs=["KIMI_API_KEY", "KIMI_BASE_URL"],
227
256
  api_key_env="KIMI_API_KEY",
228
257
  agent_model="kimi-k2.5",
229
258
  parsing_model="kimi-k2.5",
230
259
  llm_type=LLMType.KIMI,
231
- alt_env_vars=["KIMI_BASE_URL"],
232
260
  extra_args={
233
261
  "base_url": lambda: os.getenv("KIMI_BASE_URL", "https://api.moonshot.cn/v1"),
234
262
  "max_tokens": None,
@@ -238,6 +266,7 @@ LLM_PROVIDERS = {
238
266
  ),
239
267
  "openrouter": LLMConfig(
240
268
  chat_class=ChatOpenAI,
269
+ selection_envs=["OPENROUTER_API_KEY"],
241
270
  api_key_env="OPENROUTER_API_KEY",
242
271
  agent_model="google/gemini-3-flash-preview",
243
272
  parsing_model="google/gemini-3-flash-preview",
@@ -249,9 +278,45 @@ LLM_PROVIDERS = {
249
278
  "max_retries": 0,
250
279
  },
251
280
  ),
281
+ "litellm": LLMConfig(
282
+ chat_class=ChatOpenAI,
283
+ # Base URL only: a key alone must not select litellm, since there is no
284
+ # universal proxy endpoint to default to.
285
+ selection_envs=["LITELLM_BASE_URL"],
286
+ api_key_env="LITELLM_API_KEY",
287
+ agent_model="gpt-4o",
288
+ parsing_model="gpt-4o-mini",
289
+ llm_type=LLMType.GPT4,
290
+ keyless_capable=True,
291
+ extra_args={
292
+ "base_url": lambda: os.getenv("LITELLM_BASE_URL"),
293
+ "max_tokens": None,
294
+ "timeout": None,
295
+ "max_retries": 0,
296
+ },
297
+ ),
252
298
  }
253
299
 
254
300
 
301
+ def _all_selection_envs() -> list[str]:
302
+ return sorted({var for config in LLM_PROVIDERS.values() for var in config.selection_envs})
303
+
304
+
305
+ def _unselected_key_hints() -> list[str]:
306
+ """Messages for providers whose API key is set but which nothing selects."""
307
+ return [
308
+ f"{config.api_key_env} is set, but the '{name}' provider is selected by "
309
+ f"{' or '.join(config.selection_envs)}."
310
+ for name, config in LLM_PROVIDERS.items()
311
+ if config.has_real_api_key() and not config.is_selected_by_env()
312
+ ]
313
+
314
+
315
+ def selected_providers() -> list[str]:
316
+ """Names of providers the environment currently selects."""
317
+ return [name for name, config in LLM_PROVIDERS.items() if config.is_selected_by_env()]
318
+
319
+
255
320
  def _initialize_llm(
256
321
  model_override: str | None,
257
322
  model_attr: str,
@@ -259,16 +324,10 @@ def _initialize_llm(
259
324
  log_prefix: str,
260
325
  init_factory: bool = False,
261
326
  ) -> tuple[BaseChatModel, str]:
262
- resolved = _resolve_active_provider(model_override, model_attr)
327
+ resolved = _resolve_selected_provider(model_override, model_attr)
263
328
  if resolved is None:
264
- required_vars = []
265
- for config in LLM_PROVIDERS.values():
266
- required_vars.append(config.api_key_env)
267
- required_vars.extend(config.alt_env_vars)
268
-
269
- raise ValueError(
270
- f"No valid LLM configuration found. Please set one of: {', '.join(sorted(set(required_vars)))}"
271
- )
329
+ message = f"No valid LLM configuration found. Please set one of: {', '.join(_all_selection_envs())}."
330
+ raise ValueError(" ".join([message, *_unselected_key_hints()]))
272
331
 
273
332
  name, config, model_name = resolved
274
333
 
@@ -288,6 +347,8 @@ def _initialize_llm(
288
347
  }
289
348
  kwargs.update(config.get_resolved_extra_args())
290
349
 
350
+ # ChatBedrockConverse and ChatOllama take no api_key kwarg; their SDKs read
351
+ # AWS_BEARER_TOKEN_BEDROCK / OLLAMA_API_KEY from the environment directly.
291
352
  if name not in ["aws", "ollama"]:
292
353
  api_key = config.get_api_key()
293
354
  kwargs["api_key"] = api_key or "no-key-required"
@@ -296,13 +357,13 @@ def _initialize_llm(
296
357
  return model, model_name
297
358
 
298
359
 
299
- def _resolve_active_provider(
360
+ def _resolve_selected_provider(
300
361
  model_override: str | None,
301
362
  model_attr: str,
302
363
  ) -> tuple[str, LLMConfig, str] | None:
303
- """Return the active provider, config, and resolved model name."""
364
+ """Return the selected provider, config, and resolved model name."""
304
365
  for name, config in LLM_PROVIDERS.items():
305
- if not config.is_active():
366
+ if not config.is_selected_by_env():
306
367
  continue
307
368
  return name, config, model_override or getattr(config, model_attr)
308
369
  return None
@@ -313,13 +374,36 @@ class LLMConfigError(ValueError):
313
374
 
314
375
 
315
376
  def validate_api_key_provided() -> None:
316
- """Raise LLMConfigError if zero or more than one LLM provider key is configured."""
317
- active = [name for name, config in LLM_PROVIDERS.items() if config.is_active()]
318
- if not active:
319
- required = sorted({config.api_key_env for config in LLM_PROVIDERS.values()})
320
- raise LLMConfigError(f"No LLM provider API key found. Set one of: {', '.join(required)}")
321
- if len(active) > 1:
322
- raise LLMConfigError(f"Multiple LLM provider keys detected ({', '.join(active)}); please set only one.")
377
+ """Raise LLMConfigError unless exactly one LLM provider is selected.
378
+
379
+ A provider is selected when any of its ``selection_envs`` is set. Keyless-
380
+ capable providers (self-hosted / OpenAI-compatible endpoints, e.g. an
381
+ ``OPENAI_BASE_URL`` with no key) are therefore valid: they are selected by
382
+ their base URL, and the client falls back to a placeholder key downstream.
383
+ In that case we log a warning rather than fail. Ambiguity detection (more
384
+ than one selected provider) is preserved so a stray second key is still
385
+ surfaced, and a key set for an unselected provider (e.g. LITELLM_API_KEY
386
+ without LITELLM_BASE_URL) is reported rather than silently ignored.
387
+ """
388
+ hints = _unselected_key_hints()
389
+ selected = selected_providers()
390
+ if not selected:
391
+ message = f"No LLM provider selected. Set one of: {', '.join(_all_selection_envs())}."
392
+ raise LLMConfigError(" ".join([message, *hints]))
393
+ if len(selected) > 1:
394
+ raise LLMConfigError(f"Multiple LLM providers selected ({', '.join(selected)}); please set only one.")
395
+ for hint in hints:
396
+ logger.warning(hint)
397
+
398
+ (name,) = selected
399
+ config = LLM_PROVIDERS[name]
400
+ if config.keyless_capable and not config.has_real_api_key():
401
+ logger.warning(
402
+ "Provider '%s' is selected via a base URL with no %s set; "
403
+ "treating as a keyless local endpoint (a placeholder key is used).",
404
+ name,
405
+ config.api_key_env,
406
+ )
323
407
 
324
408
 
325
409
  def initialize_agent_llm(model_override: str | None = None) -> BaseChatModel:
@@ -329,17 +413,29 @@ def initialize_agent_llm(model_override: str | None = None) -> BaseChatModel:
329
413
 
330
414
 
331
415
  def get_current_agent_context_window() -> ContextWindow:
332
- """Context window for the currently active agent provider/model.
416
+ """Context window for the currently selected agent provider/model.
333
417
 
334
- Resolves the first active provider (same rule as ``_initialize_llm``) on
418
+ Resolves the first selected provider (same rule as ``_initialize_llm``) on
335
419
  every call. ``get_context_window`` handles its own caching, so this is
336
420
  cheap enough to call without a module-level cache.
337
421
  """
338
- resolved = _resolve_active_provider(_agent_model_override or os.getenv("AGENT_MODEL"), "agent_model")
422
+ resolved = _resolve_selected_provider(_agent_model_override or os.getenv("AGENT_MODEL"), "agent_model")
339
423
  if resolved is not None:
340
424
  name, _config, model_name = resolved
341
- return get_context_window(name, model_name)
342
- return ContextWindow(ModelCapabilities.FALLBACK_INPUT, ModelCapabilities.FALLBACK_OUTPUT)
425
+ ctx = get_context_window(name, model_name)
426
+ if name == "openrouter" and ctx.is_fallback:
427
+ return _OPENROUTER_FALLBACK_CONTEXT_WINDOW
428
+ return ctx
429
+ return ContextWindow(ModelCapabilities.FALLBACK_INPUT, ModelCapabilities.FALLBACK_OUTPUT, is_fallback=True)
430
+
431
+
432
+ def get_current_agent_model_ref() -> str:
433
+ """``provider/model`` for the currently active agent LLM, or ``"unknown"``."""
434
+ resolved = _resolve_selected_provider(_agent_model_override or os.getenv("AGENT_MODEL"), "agent_model")
435
+ if resolved is None:
436
+ return "unknown"
437
+ name, _config, model_name = resolved
438
+ return f"{name}/{model_name}"
343
439
 
344
440
 
345
441
  def initialize_parsing_llm(model_override: str | None = None) -> BaseChatModel:
@@ -24,6 +24,7 @@ _OLLAMA_CACHE: dict[tuple[str, str], tuple[int, int]] = {}
24
24
  class ContextWindow:
25
25
  input_tokens: int
26
26
  output_tokens: int
27
+ is_fallback: bool = False
27
28
 
28
29
 
29
30
  def get_context_window(provider: str, model_name: str) -> ContextWindow:
@@ -40,7 +41,7 @@ def get_context_window(provider: str, model_name: str) -> ContextWindow:
40
41
  if hit is not None:
41
42
  return ContextWindow(*hit)
42
43
  logger.warning(f"No context window for {provider}/{model_name}; using fallback {ModelCapabilities.FALLBACK_INPUT}")
43
- return ContextWindow(ModelCapabilities.FALLBACK_INPUT, ModelCapabilities.FALLBACK_OUTPUT)
44
+ return ContextWindow(ModelCapabilities.FALLBACK_INPUT, ModelCapabilities.FALLBACK_OUTPUT, is_fallback=True)
44
45
 
45
46
 
46
47
  def _resolve_env(provider: str, model_name: str) -> tuple[int, int] | None:
@@ -81,9 +82,12 @@ def _user_context_window_override() -> int | None:
81
82
  def _resolve_ollama(provider: str, model_name: str) -> tuple[int, int] | None:
82
83
  if provider != "ollama":
83
84
  return None
84
- base = os.getenv("OLLAMA_BASE_URL")
85
+ base = os.getenv("OLLAMA_BASE_URL") or os.getenv("OLLAMA_HOST")
85
86
  if not base:
86
87
  return None
88
+ if "://" not in base:
89
+ # OLLAMA_HOST conventionally allows bare host:port.
90
+ base = f"http://{base}"
87
91
  return _ollama_show(model_name, base.rstrip("/"))
88
92
 
89
93
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.12.0
3
+ Version: 0.12.2
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -40,6 +40,8 @@ Requires-Dist: markitdown>=0.1
40
40
  Requires-Dist: networkx>=3.4
41
41
  Requires-Dist: nodeenv>=1.10.0
42
42
  Requires-Dist: pathspec>=0.12
43
+ Requires-Dist: posthog>=3.7
44
+ Requires-Dist: pydantic>=2.0
43
45
  Requires-Dist: pyyaml>=6.0
44
46
  Requires-Dist: regex>=2024.11
45
47
  Requires-Dist: rich>=12.6
@@ -93,15 +95,17 @@ Dynamic: license-file
93
95
  The recommended way to install the CLI is with [pipx](https://pipx.pypa.io), which automatically creates an isolated environment:
94
96
 
95
97
  ```bash
96
- pipx install codeboarding --python python3.12
98
+ pipx install codeboarding --python python3.12 --pip-args="--extra-index-url https://pip.codeboarding.org/simple/"
97
99
  ```
98
100
 
99
101
  Alternatively, install into an existing virtual environment with pip:
100
102
 
101
103
  ```bash
102
- pip install codeboarding
104
+ pip install codeboarding --extra-index-url https://pip.codeboarding.org/simple/
103
105
  ```
104
106
 
107
+
108
+
105
109
  > Installing into the global Python environment with `pip` is not recommended — it can cause dependency conflicts and will fail if the system Python is not 3.12 or 3.13.
106
110
 
107
111
  Language server binaries are downloaded automatically on first use. To pre-install them explicitly (useful in CI or restricted environments):
@@ -3,6 +3,7 @@ PYPI.md
3
3
  README.md
4
4
  constants.py
5
5
  github_action.py
6
+ health_main.py
6
7
  install.py
7
8
  logging_config.py
8
9
  main.py
@@ -126,6 +127,7 @@ static_analyzer/cluster_helpers.py
126
127
  static_analyzer/cluster_relations.py
127
128
  static_analyzer/constants.py
128
129
  static_analyzer/csharp_config_scanner.py
130
+ static_analyzer/dotnet_sdk.py
129
131
  static_analyzer/graph.py
130
132
  static_analyzer/incremental_orchestrator.py
131
133
  static_analyzer/java_config_scanner.py
@@ -162,6 +164,11 @@ static_analyzer/engine/adapters/rust_adapter.py
162
164
  static_analyzer/engine/adapters/typescript_adapter.py
163
165
  static_analyzer/lsp_client/__init__.py
164
166
  static_analyzer/lsp_client/diagnostics.py
167
+ telemetry/__init__.py
168
+ telemetry/device_id.py
169
+ telemetry/events.py
170
+ telemetry/schemas.py
171
+ telemetry/service.py
165
172
  tests/test_cli_parser.py
166
173
  tests/test_github_action.py
167
174
  tests/test_install.py
@@ -169,6 +176,7 @@ tests/test_logging_config.py
169
176
  tests/test_main.py
170
177
  tests/test_pyproject_packages.py
171
178
  tests/test_registry_coverage.py
179
+ tests/test_telemetry_events.py
172
180
  tests/test_tool_registry.py
173
181
  tests/test_user_config.py
174
182
  tests/test_vscode_constants.py
@@ -22,6 +22,8 @@ markitdown>=0.1
22
22
  networkx>=3.4
23
23
  nodeenv>=1.10.0
24
24
  pathspec>=0.12
25
+ posthog>=3.7
26
+ pydantic>=2.0
25
27
  pyyaml>=6.0
26
28
  regex>=2024.11
27
29
  rich>=12.6