velune-cli 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. velune/__init__.py +5 -0
  2. velune/__main__.py +6 -0
  3. velune/cli/__init__.py +5 -0
  4. velune/cli/app.py +212 -0
  5. velune/cli/autocomplete.py +76 -0
  6. velune/cli/banner.py +98 -0
  7. velune/cli/commands/__init__.py +32 -0
  8. velune/cli/commands/ask.py +149 -0
  9. velune/cli/commands/base.py +16 -0
  10. velune/cli/commands/chat.py +188 -0
  11. velune/cli/commands/config.py +182 -0
  12. velune/cli/commands/daemon.py +85 -0
  13. velune/cli/commands/doctor.py +373 -0
  14. velune/cli/commands/init.py +160 -0
  15. velune/cli/commands/mcp.py +80 -0
  16. velune/cli/commands/memory.py +269 -0
  17. velune/cli/commands/models.py +462 -0
  18. velune/cli/commands/preflight.py +95 -0
  19. velune/cli/commands/run.py +171 -0
  20. velune/cli/commands/setup.py +182 -0
  21. velune/cli/commands/workspace.py +217 -0
  22. velune/cli/context.py +37 -0
  23. velune/cli/councilmodel_ui.py +171 -0
  24. velune/cli/display/council_view.py +240 -0
  25. velune/cli/display/memory_view.py +93 -0
  26. velune/cli/display/panels.py +35 -0
  27. velune/cli/display/progress.py +25 -0
  28. velune/cli/display/themes.py +21 -0
  29. velune/cli/main.py +15 -0
  30. velune/cli/model_selector.py +44 -0
  31. velune/cli/modes.py +86 -0
  32. velune/cli/pull_ui.py +118 -0
  33. velune/cli/registry.py +81 -0
  34. velune/cli/repl.py +1178 -0
  35. velune/cli/session_manager.py +69 -0
  36. velune/cli/slash_commands.py +37 -0
  37. velune/cognition/__init__.py +19 -0
  38. velune/cognition/arbitrator.py +216 -0
  39. velune/cognition/architecture.py +398 -0
  40. velune/cognition/council/__init__.py +47 -0
  41. velune/cognition/council/base.py +216 -0
  42. velune/cognition/council/challenger.py +70 -0
  43. velune/cognition/council/coder.py +79 -0
  44. velune/cognition/council/critic_agent.py +39 -0
  45. velune/cognition/council/critic_configs.py +111 -0
  46. velune/cognition/council/critics.py +41 -0
  47. velune/cognition/council/debate.py +44 -0
  48. velune/cognition/council/factory.py +140 -0
  49. velune/cognition/council/messages.py +53 -0
  50. velune/cognition/council/planner.py +119 -0
  51. velune/cognition/council/reviewer.py +72 -0
  52. velune/cognition/council/synthesizer.py +67 -0
  53. velune/cognition/council/tiers.py +181 -0
  54. velune/cognition/firewall.py +256 -0
  55. velune/cognition/module.py +38 -0
  56. velune/cognition/orchestrator.py +886 -0
  57. velune/cognition/personality.py +236 -0
  58. velune/cognition/style_resolver.py +62 -0
  59. velune/cognition/verification.py +201 -0
  60. velune/context/__init__.py +11 -0
  61. velune/context/extractive.py +94 -0
  62. velune/context/window.py +62 -0
  63. velune/core/__init__.py +89 -0
  64. velune/core/background.py +5 -0
  65. velune/core/config/__init__.py +37 -0
  66. velune/core/errors/__init__.py +53 -0
  67. velune/core/errors/execution.py +26 -0
  68. velune/core/errors/memory.py +21 -0
  69. velune/core/errors/orchestration.py +26 -0
  70. velune/core/errors/provider.py +31 -0
  71. velune/core/event_loop.py +30 -0
  72. velune/core/logging.py +83 -0
  73. velune/core/runtime.py +106 -0
  74. velune/core/task_registry.py +120 -0
  75. velune/core/trace.py +80 -0
  76. velune/core/types/__init__.py +48 -0
  77. velune/core/types/agent.py +49 -0
  78. velune/core/types/context.py +39 -0
  79. velune/core/types/inference.py +35 -0
  80. velune/core/types/memory.py +39 -0
  81. velune/core/types/model.py +64 -0
  82. velune/core/types/provider.py +35 -0
  83. velune/core/types/repository.py +35 -0
  84. velune/core/types/task.py +56 -0
  85. velune/core/types/workspace.py +28 -0
  86. velune/daemon/client.py +13 -0
  87. velune/daemon/server.py +115 -0
  88. velune/daemon/transport.py +169 -0
  89. velune/events.py +194 -0
  90. velune/execution/__init__.py +22 -0
  91. velune/execution/benchmarker.py +311 -0
  92. velune/execution/cancellation.py +53 -0
  93. velune/execution/checkpointer.py +128 -0
  94. velune/execution/command_spec.py +140 -0
  95. velune/execution/diff_preview.py +172 -0
  96. velune/execution/executor.py +173 -0
  97. velune/execution/module.py +16 -0
  98. velune/execution/multi_diff.py +70 -0
  99. velune/execution/path_guard.py +19 -0
  100. velune/execution/planner.py +91 -0
  101. velune/execution/rollback.py +80 -0
  102. velune/execution/sandbox.py +257 -0
  103. velune/execution/validator.py +113 -0
  104. velune/hardware/__init__.py +1 -0
  105. velune/hardware/detector.py +162 -0
  106. velune/kernel/__init__.py +58 -0
  107. velune/kernel/bootstrap.py +107 -0
  108. velune/kernel/config.py +252 -0
  109. velune/kernel/health.py +54 -0
  110. velune/kernel/lifecycle.py +102 -0
  111. velune/kernel/module.py +15 -0
  112. velune/kernel/modules.py +23 -0
  113. velune/kernel/registry.py +93 -0
  114. velune/kernel/schemas.py +28 -0
  115. velune/main.py +9 -0
  116. velune/mcp/__init__.py +9 -0
  117. velune/mcp/client.py +113 -0
  118. velune/mcp/config.py +19 -0
  119. velune/mcp/server.py +90 -0
  120. velune/memory/__init__.py +28 -0
  121. velune/memory/lifecycle.py +154 -0
  122. velune/memory/module.py +94 -0
  123. velune/memory/prioritizer.py +65 -0
  124. velune/memory/storage/sqlite_manager.py +368 -0
  125. velune/memory/tiers/episodic.py +156 -0
  126. velune/memory/tiers/graph.py +282 -0
  127. velune/memory/tiers/lineage.py +367 -0
  128. velune/memory/tiers/semantic.py +198 -0
  129. velune/memory/tiers/working.py +165 -0
  130. velune/models/__init__.py +16 -0
  131. velune/models/module.py +18 -0
  132. velune/models/probes.py +182 -0
  133. velune/models/profile_cache.py +82 -0
  134. velune/models/profiler.py +105 -0
  135. velune/models/registry.py +201 -0
  136. velune/models/scorer.py +225 -0
  137. velune/models/specializations.py +196 -0
  138. velune/orchestration/__init__.py +15 -0
  139. velune/orchestration/module.py +14 -0
  140. velune/orchestration/role_assignments.py +78 -0
  141. velune/orchestration/schemas.py +99 -0
  142. velune/plugins/__init__.py +13 -0
  143. velune/plugins/hooks.py +49 -0
  144. velune/plugins/loader.py +95 -0
  145. velune/plugins/registry.py +54 -0
  146. velune/plugins/schemas.py +19 -0
  147. velune/providers/__init__.py +18 -0
  148. velune/providers/adapters/anthropic.py +231 -0
  149. velune/providers/adapters/fireworks.py +115 -0
  150. velune/providers/adapters/google.py +234 -0
  151. velune/providers/adapters/groq.py +151 -0
  152. velune/providers/adapters/huggingface.py +203 -0
  153. velune/providers/adapters/llamacpp.py +202 -0
  154. velune/providers/adapters/lmstudio.py +173 -0
  155. velune/providers/adapters/ollama.py +186 -0
  156. velune/providers/adapters/openai.py +207 -0
  157. velune/providers/adapters/openrouter.py +81 -0
  158. velune/providers/adapters/together.py +134 -0
  159. velune/providers/adapters/xai.py +60 -0
  160. velune/providers/base.py +86 -0
  161. velune/providers/benchmarker.py +135 -0
  162. velune/providers/discovery/__init__.py +33 -0
  163. velune/providers/discovery/anthropic.py +77 -0
  164. velune/providers/discovery/benchmarks.py +44 -0
  165. velune/providers/discovery/classifier.py +69 -0
  166. velune/providers/discovery/fireworks.py +95 -0
  167. velune/providers/discovery/gguf.py +87 -0
  168. velune/providers/discovery/google.py +95 -0
  169. velune/providers/discovery/gpu.py +109 -0
  170. velune/providers/discovery/groq.py +20 -0
  171. velune/providers/discovery/huggingface.py +67 -0
  172. velune/providers/discovery/lmstudio.py +80 -0
  173. velune/providers/discovery/ollama.py +165 -0
  174. velune/providers/discovery/openai.py +96 -0
  175. velune/providers/discovery/openrouter.py +113 -0
  176. velune/providers/discovery/scanner.py +114 -0
  177. velune/providers/discovery/together.py +114 -0
  178. velune/providers/discovery/xai.py +57 -0
  179. velune/providers/keystore.py +83 -0
  180. velune/providers/local_paths.py +49 -0
  181. velune/providers/local_resolver.py +208 -0
  182. velune/providers/module.py +15 -0
  183. velune/providers/ollama_manager.py +193 -0
  184. velune/providers/registry.py +173 -0
  185. velune/providers/router.py +82 -0
  186. velune/py.typed +0 -0
  187. velune/repository/__init__.py +21 -0
  188. velune/repository/analyzer.py +123 -0
  189. velune/repository/cognition.py +172 -0
  190. velune/repository/grapher.py +182 -0
  191. velune/repository/indexer.py +229 -0
  192. velune/repository/module.py +15 -0
  193. velune/repository/parser.py +378 -0
  194. velune/repository/project_type.py +293 -0
  195. velune/repository/scanner.py +177 -0
  196. velune/repository/schemas.py +102 -0
  197. velune/repository/tracker.py +233 -0
  198. velune/retrieval/__init__.py +27 -0
  199. velune/retrieval/graph.py +117 -0
  200. velune/retrieval/hybrid.py +250 -0
  201. velune/retrieval/keyword.py +113 -0
  202. velune/retrieval/module.py +19 -0
  203. velune/retrieval/reranker.py +68 -0
  204. velune/retrieval/schemas.py +59 -0
  205. velune/retrieval/vector.py +163 -0
  206. velune/telemetry/__init__.py +7 -0
  207. velune/telemetry/cognition.py +260 -0
  208. velune/telemetry/token_tracker.py +133 -0
  209. velune/tools/__init__.py +41 -0
  210. velune/tools/base/registry.py +84 -0
  211. velune/tools/base/tool.py +63 -0
  212. velune/tools/code/navigate.py +107 -0
  213. velune/tools/code/search.py +112 -0
  214. velune/tools/filesystem/read.py +75 -0
  215. velune/tools/filesystem/search.py +123 -0
  216. velune/tools/filesystem/write.py +160 -0
  217. velune/tools/git/history.py +185 -0
  218. velune/tools/git/operations.py +132 -0
  219. velune/tools/git/state.py +134 -0
  220. velune/tools/module.py +65 -0
  221. velune/tools/terminal/execute.py +72 -0
  222. velune/tools/terminal/history.py +47 -0
  223. velune/tools/web/fetch.py +55 -0
  224. velune/tools/web/validator.py +96 -0
  225. velune_cli-1.0.0.dist-info/METADATA +497 -0
  226. velune_cli-1.0.0.dist-info/RECORD +229 -0
  227. velune_cli-1.0.0.dist-info/WHEEL +4 -0
  228. velune_cli-1.0.0.dist-info/entry_points.txt +2 -0
  229. velune_cli-1.0.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,373 @@
1
+ """Environment health diagnostics for Velune."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ import sys
7
+ import tempfile
8
+ from pathlib import Path
9
+
10
+ import typer
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+
14
+ from velune.providers.keystore import get_key
15
+
16
+ console = Console()
17
+ doctor_cmd = typer.Typer(help="Environment health diagnostics")
18
+
19
+ @doctor_cmd.command(name="check")
20
+ def check(
21
+ fix: bool = typer.Option(False, "--fix", help="Attempt to fix issues automatically"),
22
+ json_output: bool = typer.Option(False, "--json", help="Output results as JSON"),
23
+ ) -> None:
24
+ """Run Velune environment health checks."""
25
+ if fix:
26
+ console.print("[yellow]Attempting automatic fixes...[/yellow]")
27
+
28
+ # Fix 1: Create .velune/ directory
29
+ velune_dir = Path.cwd() / ".velune"
30
+ if not velune_dir.exists():
31
+ try:
32
+ velune_dir.mkdir(parents=True, exist_ok=True)
33
+ console.print("[green]✓ Created .velune/ directory.[/green]")
34
+ except Exception as e:
35
+ console.print(f"[red]✗ Failed to create .velune/: {e}[/red]")
36
+
37
+ # Fix 2: Create default velune.toml if missing
38
+ config_file = Path.cwd() / "velune.toml"
39
+ if not config_file.exists():
40
+ try:
41
+ import toml # type: ignore[import-untyped]
42
+
43
+ from velune.kernel.config import get_default_config
44
+ default_config = get_default_config()
45
+ with open(config_file, "w") as f:
46
+ toml.dump(default_config.model_dump(), f)
47
+ console.print("[green]✓ Created default velune.toml config file.[/green]")
48
+ except Exception as e:
49
+ console.print(f"[red]✗ Failed to create default velune.toml: {e}[/red]")
50
+
51
+ # Fix 3: Initialize databases
52
+ db_file = velune_dir / "velune_cognitive_core.db"
53
+ try:
54
+ from velune.telemetry.cognition import CognitivePerformanceAnalytics
55
+ CognitivePerformanceAnalytics(db_path=db_file)
56
+ console.print("[green]✓ SQLite database successfully initialized.[/green]")
57
+ except Exception as e:
58
+ console.print(f"[red]✗ Failed to initialize SQLite database: {e}[/red]")
59
+
60
+ console.print("[yellow]Re-running checks after fixes...[/yellow]\n")
61
+
62
+ checks = [
63
+ _check_python_version,
64
+ _check_core_dependencies,
65
+ _check_ollama_connectivity,
66
+ _check_ollama_models,
67
+ _check_lm_studio,
68
+ _check_openai_api_key,
69
+ _check_anthropic_api_key,
70
+ _check_groq,
71
+ _check_google,
72
+ _check_velune_dir,
73
+ _check_sqlite,
74
+ _check_qdrant,
75
+ _check_config,
76
+ _check_treesitter,
77
+ _check_git,
78
+ _check_gpu,
79
+ _check_vram,
80
+ _check_model_benchmarks,
81
+ ]
82
+
83
+ results = []
84
+ for check_fn in checks:
85
+ try:
86
+ result = check_fn()
87
+ results.append(result)
88
+ except Exception as e:
89
+ results.append({"name": check_fn.__name__.replace("_check_", "").replace("_", " ").title(), "status": "error", "message": str(e)})
90
+
91
+ if json_output:
92
+ import json
93
+ print(json.dumps(results, indent=2))
94
+ return
95
+
96
+ _render_results(results)
97
+
98
+ failures = [r for r in results if r["status"] == "fail"]
99
+ if failures:
100
+ console.print(f"\n[red]✗ {len(failures)} check(s) failed.[/red]")
101
+ console.print("[dim]Run 'velune doctor --fix' to attempt automatic fixes.[/dim]")
102
+ raise typer.Exit(1)
103
+ else:
104
+ console.print("\n[green]✓ All checks passed. Velune is ready.[/green]")
105
+
106
+ def _check_python_version() -> dict:
107
+ version = sys.version_info
108
+ clean_version = sys.version.replace('\n', ' ')
109
+ if version >= (3, 11):
110
+ return {"name": "Python Version", "status": "ok", "message": f"{clean_version}"}
111
+ return {"name": "Python Version", "status": "fail",
112
+ "message": f"Python {version.major}.{version.minor} < 3.11. Install Python 3.11+. Details: {clean_version}"}
113
+
114
+ def _check_core_dependencies() -> dict:
115
+ deps = ["pydantic", "typer", "rich", "httpx", "qdrant_client", "toml"]
116
+ missing = []
117
+ for dep in deps:
118
+ try:
119
+ __import__(dep)
120
+ except ImportError:
121
+ missing.append(dep)
122
+
123
+ if not missing:
124
+ return {"name": "Core Dependencies", "status": "ok", "message": "All core dependencies installed."}
125
+ return {"name": "Core Dependencies", "status": "fail", "message": f"Missing core dependencies: {', '.join(missing)}"}
126
+
127
+ def _check_ollama_connectivity() -> dict:
128
+ import httpx
129
+ try:
130
+ httpx.get("http://localhost:11434/api/tags", timeout=3.0)
131
+ return {"name": "Ollama Connectivity", "status": "ok", "message": "Connected successfully to http://localhost:11434."}
132
+ except Exception:
133
+ return {"name": "Ollama Connectivity", "status": "warn", "message": "Could not connect to Ollama at http://localhost:11434."}
134
+
135
+ def _check_ollama_models() -> dict:
136
+ import httpx
137
+ try:
138
+ r = httpx.get("http://localhost:11434/api/tags", timeout=3.0)
139
+ models = r.json().get("models", [])
140
+ if models:
141
+ model_names = [m.get("name") for m in models]
142
+ return {"name": "Ollama Model Availability", "status": "ok", "message": f"{len(models)} model(s) found: {', '.join(model_names[:3])}{'...' if len(model_names) > 3 else ''}"}
143
+ return {"name": "Ollama Model Availability", "status": "warn", "message": "No local Ollama models installed. Run 'ollama pull llama3.2'."}
144
+ except Exception:
145
+ return {"name": "Ollama Model Availability", "status": "warn", "message": "Unable to check model list (Ollama not connected)."}
146
+
147
+ def _check_lm_studio() -> dict:
148
+ import httpx
149
+ try:
150
+ r = httpx.get("http://localhost:1234/v1/models", timeout=3.0)
151
+ if r.status_code == 200:
152
+ return {"name": "LM Studio Connectivity", "status": "ok", "message": "Connected successfully to http://localhost:1234."}
153
+ return {"name": "LM Studio Connectivity", "status": "warn", "message": f"Connected to http://localhost:1234 but received status {r.status_code}."}
154
+ except Exception:
155
+ return {"name": "LM Studio Connectivity", "status": "warn", "message": "Not running or not accessible at http://localhost:1234."}
156
+
157
+ def _check_openai_api_key() -> dict:
158
+ key = get_key("openai")
159
+ if key:
160
+ return {"name": "OpenAI API Key", "status": "ok", "message": f"Configured ({key[:4]}...{key[-4:] if len(key) > 8 else ''})"}
161
+ return {"name": "OpenAI API Key", "status": "warn", "message": "Not configured. Run 'velune setup' to add your key."}
162
+
163
+ def _check_anthropic_api_key() -> dict:
164
+ key = get_key("anthropic")
165
+ if key:
166
+ return {"name": "Anthropic API Key", "status": "ok", "message": f"Configured ({key[:4]}...{key[-4:] if len(key) > 8 else ''})"}
167
+ return {"name": "Anthropic API Key", "status": "warn", "message": "Not configured. Run 'velune setup' to add your key."}
168
+
169
+ def _check_velune_dir() -> dict:
170
+ velune_dir = Path.cwd() / ".velune"
171
+ try:
172
+ velune_dir.mkdir(exist_ok=True)
173
+ test_file = velune_dir / ".write_test"
174
+ test_file.write_text("test")
175
+ test_file.unlink()
176
+ return {"name": ".velune Directory Writable", "status": "ok", "message": f"Writable directory at {velune_dir}"}
177
+ except Exception as e:
178
+ return {"name": ".velune Directory Writable", "status": "fail", "message": f"Cannot write to {velune_dir}: {e}"}
179
+
180
+ def _check_sqlite() -> dict:
181
+ velune_dir = Path.cwd() / ".velune"
182
+ db_file = velune_dir / "velune_cognitive_core.db"
183
+ try:
184
+ velune_dir.mkdir(exist_ok=True)
185
+ import sqlite3
186
+ conn = sqlite3.connect(str(db_file), timeout=3.0)
187
+ cursor = conn.cursor()
188
+ cursor.execute("SELECT 1")
189
+ conn.close()
190
+ return {"name": "SQLite DB Initializable", "status": "ok", "message": f"Successfully initialized/opened sqlite database at {db_file}"}
191
+ except Exception as e:
192
+ return {"name": "SQLite DB Initializable", "status": "fail", "message": f"Failed to initialize SQLite database: {e}"}
193
+
194
+ def _check_qdrant() -> dict:
195
+ try:
196
+ from qdrant_client import QdrantClient
197
+ with tempfile.TemporaryDirectory(prefix="velune-qdrant-") as temp_dir:
198
+ qdrant_path = Path(temp_dir)
199
+ client = QdrantClient(path=str(qdrant_path))
200
+ client.get_collections()
201
+ client.close()
202
+ return {"name": "Qdrant In-Process Initializable", "status": "ok", "message": f"Qdrant local storage successfully initialized at {qdrant_path}"}
203
+ except Exception as e:
204
+ return {"name": "Qdrant In-Process Initializable", "status": "fail", "message": f"Failed to initialize local Qdrant client: {e}"}
205
+
206
+ def _check_config() -> dict:
207
+ config_file = Path.cwd() / "velune.toml"
208
+ if not config_file.exists():
209
+ return {"name": "velune.toml Config File", "status": "warn", "message": "No velune.toml found in current workspace. Using defaults."}
210
+
211
+ try:
212
+ import toml # type: ignore[import-untyped]
213
+
214
+ from velune.kernel.config import VeluneConfig
215
+ data = toml.load(config_file)
216
+ VeluneConfig(**data)
217
+ return {"name": "velune.toml Config File", "status": "ok", "message": f"Found and validated successfully at {config_file}"}
218
+ except Exception as e:
219
+ return {"name": "velune.toml Config File", "status": "fail", "message": f"Invalid velune.toml format or schema validation error: {e}"}
220
+
221
+ def _check_treesitter() -> dict:
222
+ try:
223
+ import tree_sitter_go
224
+ import tree_sitter_python
225
+ import tree_sitter_rust
226
+ import tree_sitter_typescript
227
+ from tree_sitter import Language
228
+
229
+ langs = []
230
+ for name, mod in [("python", tree_sitter_python), ("typescript", tree_sitter_typescript), ("go", tree_sitter_go), ("rust", tree_sitter_rust)]:
231
+ try:
232
+ if name == "typescript":
233
+ Language(mod.language_typescript())
234
+ else:
235
+ Language(mod.language())
236
+ langs.append(name)
237
+ except Exception:
238
+ pass
239
+ if langs:
240
+ return {"name": "Tree-sitter Grammars", "status": "ok", "message": f"Tree-sitter grammars loaded: {', '.join(langs)}."}
241
+ return {"name": "Tree-sitter Grammars", "status": "warn", "message": "tree-sitter installed but no grammars loaded correctly."}
242
+ except ImportError as e:
243
+ return {"name": "Tree-sitter Grammars", "status": "warn", "message": f"Tree-sitter package or parser modules missing: {e}."}
244
+
245
+ def _check_git() -> dict:
246
+ git_path = shutil.which("git")
247
+ if git_path:
248
+ return {"name": "Git in PATH", "status": "ok", "message": f"Found Git at {git_path}"}
249
+ return {"name": "Git in PATH", "status": "fail", "message": "Git is not installed or not in system PATH."}
250
+
251
+ def _check_gpu() -> dict:
252
+ from velune.providers.discovery.gpu import GPUDetector
253
+ try:
254
+ gpu_info = GPUDetector().detect()
255
+ if gpu_info.get("has_gpu"):
256
+ gpu_name = gpu_info.get("gpu_name", "Unknown Name")
257
+ gpu_type = gpu_info.get("gpu_type", "Unknown")
258
+ return {"name": "GPU Detection", "status": "ok", "message": f"Detected GPU: {gpu_name} ({gpu_type.upper()})"}
259
+ return {"name": "GPU Detection", "status": "warn", "message": "No dedicated GPU detected. Models will run on CPU."}
260
+ except Exception as e:
261
+ return {"name": "GPU Detection", "status": "warn", "message": f"Failed to run GPU detection: {e}"}
262
+
263
+ def _check_vram() -> dict:
264
+ from velune.providers.discovery.gpu import GPUDetector
265
+ try:
266
+ gpu_info = GPUDetector().detect()
267
+ if gpu_info.get("has_gpu") and gpu_info.get("vram_total_gb") is not None:
268
+ total = gpu_info.get("vram_total_gb", 0)
269
+ free = gpu_info.get("vram_free_gb", 0)
270
+ return {"name": "Available VRAM", "status": "ok", "message": f"VRAM Total: {total:.2f} GB, VRAM Free: {free:.2f} GB"}
271
+ return {"name": "Available VRAM", "status": "warn", "message": "Unified or CPU-only memory in use."}
272
+ except Exception as e:
273
+ return {"name": "Available VRAM", "status": "warn", "message": f"Failed to query VRAM: {e}"}
274
+
275
+ def _check_groq() -> dict:
276
+ from velune.providers.keystore import get_key, has_key
277
+ if not has_key("groq"):
278
+ return {
279
+ "name": "Groq",
280
+ "status": "warn",
281
+ "message": "Not configured — free tier available at console.groq.com/keys",
282
+ }
283
+ try:
284
+ import httpx
285
+ key = get_key("groq")
286
+ r = httpx.get(
287
+ "https://api.groq.com/openai/v1/models",
288
+ headers={"Authorization": f"Bearer {key}"},
289
+ timeout=5,
290
+ )
291
+ if r.status_code == 200:
292
+ models = r.json().get("data", [])
293
+ return {
294
+ "name": "Groq",
295
+ "status": "ok",
296
+ "message": f"Connected — {len(models)} models available",
297
+ }
298
+ return {
299
+ "name": "Groq",
300
+ "status": "fail",
301
+ "message": f"Auth failed (HTTP {r.status_code}) — check your key",
302
+ }
303
+ except Exception as e:
304
+ return {
305
+ "name": "Groq",
306
+ "status": "fail",
307
+ "message": f"Cannot reach api.groq.com — {e}",
308
+ }
309
+
310
+
311
+ def _check_google() -> dict:
312
+ from velune.providers.keystore import get_key, has_key
313
+ if not has_key("google"):
314
+ return {
315
+ "name": "Google Gemini",
316
+ "status": "warn",
317
+ "message": "Not configured — free quota at aistudio.google.com",
318
+ }
319
+ try:
320
+ import httpx
321
+ key = get_key("google")
322
+ r = httpx.get(
323
+ f"https://generativelanguage.googleapis.com/v1beta/models?key={key}",
324
+ timeout=5,
325
+ )
326
+ if r.status_code == 200:
327
+ models = r.json().get("models", [])
328
+ return {
329
+ "name": "Google Gemini",
330
+ "status": "ok",
331
+ "message": f"Connected — {len(models)} models available",
332
+ }
333
+ return {
334
+ "name": "Google Gemini",
335
+ "status": "fail",
336
+ "message": f"Auth failed (HTTP {r.status_code})",
337
+ }
338
+ except Exception as e:
339
+ return {
340
+ "name": "Google Gemini",
341
+ "status": "fail",
342
+ "message": f"Cannot reach googleapis.com — {e}",
343
+ }
344
+
345
+
346
+ def _check_model_benchmarks() -> dict:
347
+ profile_path = Path.cwd() / ".velune" / "model_profiles.json"
348
+ if profile_path.exists():
349
+ try:
350
+ import json
351
+ data = json.loads(profile_path.read_text())
352
+ if data:
353
+ return {"name": "Empirical Model Benchmarks", "status": "ok", "message": f"Cached capability profiles found for {len(data)} model(s)."}
354
+ except Exception:
355
+ pass
356
+ return {"name": "Empirical Model Benchmarks", "status": "warn", "message": "No empirical model capability benchmarks cached. Run: velune models scan --probe"}
357
+
358
+ def _render_results(results: list) -> None:
359
+ table = Table(title="Velune Environment Check", show_header=True)
360
+ table.add_column("Check", style="bold")
361
+ table.add_column("Status")
362
+ table.add_column("Details")
363
+
364
+ status_styles = {"ok": "[green]✓ OK[/green]", "warn": "[yellow]⚠ WARN[/yellow]",
365
+ "fail": "[red]✗ FAIL[/red]", "error": "[red]✗ ERROR[/red]"}
366
+
367
+ for result in results:
368
+ table.add_row(
369
+ result["name"],
370
+ status_styles.get(result["status"], result["status"]),
371
+ result.get("message", "")
372
+ )
373
+ console.print(table)
@@ -0,0 +1,160 @@
1
+ """velune init — workspace initialisation command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import typer
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+
12
+ app_init = typer.Typer()
13
+ console = Console()
14
+
15
+
16
+ @app_init.command("init")
17
+ def init_command(
18
+ path: Path = typer.Argument(
19
+ default=None,
20
+ help="Path to project root (defaults to current directory)",
21
+ ),
22
+ provider: str = typer.Option(
23
+ None, "--provider", "-p",
24
+ help="Default provider: ollama | groq | openai | anthropic",
25
+ ),
26
+ skip_hardware: bool = typer.Option(
27
+ False, "--skip-hardware-check",
28
+ help="Skip hardware detection",
29
+ ),
30
+ ) -> None:
31
+ """Initialize Velune in a project.
32
+
33
+ Creates .velune/ config, detects hardware, checks providers,
34
+ and prepares the workspace for first use.
35
+ """
36
+ workspace = (path or Path.cwd()).resolve()
37
+
38
+ console.print(Panel(
39
+ "[bold cyan]Velune Init[/bold cyan]\n"
40
+ f"[dim]Setting up workspace: {workspace}[/dim]",
41
+ border_style="cyan", padding=(0, 1),
42
+ ))
43
+
44
+ if not skip_hardware:
45
+ _run_hardware_check()
46
+
47
+ # .velune/ directory tree
48
+ velune_dir = workspace / ".velune"
49
+ velune_dir.mkdir(exist_ok=True)
50
+ (velune_dir / "sessions").mkdir(exist_ok=True)
51
+ (velune_dir / "index").mkdir(exist_ok=True)
52
+ console.print("[green]✓[/green] Created .velune/ directory")
53
+
54
+ # Project type detection
55
+ try:
56
+ import json as _json
57
+
58
+ from velune.repository.project_type import ProjectTypeDetector
59
+ _profile = ProjectTypeDetector().detect(workspace)
60
+ (velune_dir / "project_profile.json").write_text(_json.dumps({
61
+ "project_type": _profile.project_type.value,
62
+ "display_name": _profile.display_name,
63
+ "primary_language": _profile.primary_language,
64
+ "detected_frameworks": _profile.detected_frameworks,
65
+ "entry_points": _profile.entry_points,
66
+ "test_directories": _profile.test_directories,
67
+ "config_files": _profile.config_files,
68
+ }, indent=2))
69
+ console.print(
70
+ f"[green]✓[/green] Detected project type: "
71
+ f"[cyan]{_profile.display_name}[/cyan]"
72
+ )
73
+ if _profile.detected_frameworks:
74
+ console.print(
75
+ f" [dim]Frameworks: {', '.join(_profile.detected_frameworks)}[/dim]"
76
+ )
77
+ except Exception:
78
+ pass
79
+
80
+ # .veluneignore
81
+ ignore_file = workspace / ".veluneignore"
82
+ if not ignore_file.exists():
83
+ from velune.repository.scanner import DEFAULT_VELUNEIGNORE
84
+ ignore_file.write_text(DEFAULT_VELUNEIGNORE)
85
+ console.print("[green]✓[/green] Created .veluneignore")
86
+
87
+ # config.toml
88
+ config_path = velune_dir / "config.toml"
89
+ if not config_path.exists():
90
+ default_provider = provider or _suggest_provider()
91
+ config_content = f"""\
92
+ [project]
93
+ name = "{workspace.name}"
94
+ workspace = "."
95
+
96
+ [providers]
97
+ default_provider = "{default_provider}"
98
+
99
+ [council]
100
+ default_tier = "auto"
101
+
102
+ [memory]
103
+ enabled = true
104
+ """
105
+ config_path.write_text(config_content)
106
+ console.print(
107
+ f"[green]✓[/green] Created .velune/config.toml "
108
+ f"(provider: {default_provider})"
109
+ )
110
+
111
+ # .gitignore
112
+ gitignore = workspace / ".gitignore"
113
+ if gitignore.exists():
114
+ content = gitignore.read_text()
115
+ if ".velune/" not in content:
116
+ with open(gitignore, "a") as f:
117
+ f.write("\n# Velune\n.velune/\n")
118
+ console.print("[green]✓[/green] Added .velune/ to .gitignore")
119
+
120
+ console.print()
121
+ console.print("[bold green]✓ Velune initialized.[/bold green]")
122
+ console.print("[dim]Next steps:[/dim]")
123
+ console.print(" [cyan]velune[/cyan] — start the REPL")
124
+ console.print(" [cyan]velune doctor[/cyan] — verify configuration")
125
+
126
+
127
+ def _run_hardware_check() -> None:
128
+ from velune.hardware.detector import HardwareDetector
129
+ profile = HardwareDetector().detect()
130
+
131
+ table = Table(border_style="dim", padding=(0, 1), show_header=False)
132
+ table.add_column("Key", style="dim", width=20)
133
+ table.add_column("Value", style="white")
134
+
135
+ table.add_row("RAM", f"{profile.total_ram_gb:.0f} GB")
136
+ table.add_row("GPU", profile.gpu_name or "None (CPU only)")
137
+ table.add_row(
138
+ "VRAM",
139
+ f"{profile.vram_total_gb:.0f} GB" if profile.vram_total_gb else "—",
140
+ )
141
+ table.add_row("Tier", f"[cyan]{profile.tier.value}[/cyan]")
142
+ table.add_row("Best local model", profile.recommended_model_size)
143
+
144
+ console.print(table)
145
+
146
+ for w in profile.warnings:
147
+ console.print(f" [yellow]⚠[/yellow] {w}")
148
+ for s in profile.suggestions:
149
+ console.print(f" [dim]→ {s}[/dim]")
150
+
151
+
152
+ def _suggest_provider() -> str:
153
+ try:
154
+ import httpx
155
+ r = httpx.get("http://localhost:11434/api/tags", timeout=2)
156
+ if r.status_code == 200:
157
+ return "ollama"
158
+ except Exception:
159
+ pass
160
+ return "groq"
@@ -0,0 +1,80 @@
1
+ """MCP command - velune mcp-serve and velune mcp connect."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import typer
8
+ from rich.console import Console
9
+
10
+ from velune.cli.context import CLIContext
11
+ from velune.core.event_loop import submit
12
+ from velune.core.runtime import build_runtime
13
+ from velune.mcp.client import VeluneMCPClient
14
+ from velune.mcp.server import VeluneMCPServer
15
+
16
+ console = Console()
17
+ mcp_cmd = typer.Typer(help="Model Context Protocol (MCP) commands")
18
+
19
+
20
+ @mcp_cmd.command("connect")
21
+ def mcp_connect(
22
+ ctx: typer.Context,
23
+ server_url: str = typer.Argument(..., help="SSE URL of the external MCP server"),
24
+ name: str = typer.Argument(..., help="Name of the MCP server"),
25
+ ) -> None:
26
+ """Connect to an external MCP server and list its tools."""
27
+ console.print(f"[cyan]Connecting to external MCP server '{name}' at {server_url}...[/cyan]")
28
+
29
+ client = VeluneMCPClient(server_url, name)
30
+
31
+ async def _connect_and_list():
32
+ try:
33
+ tools = await client.connect()
34
+ console.print(f"[green]✓ Connected to {name} successfully![/green]")
35
+ console.print(f"[bold]Exposed Tools ({len(tools)}):[/bold]")
36
+ for tool in tools:
37
+ desc = tool.get("description", "No description")
38
+ if len(desc) > 80:
39
+ desc = desc[:77] + "..."
40
+ console.print(f" - [cyan]{name}_{tool['name']}[/cyan]: {desc}")
41
+ except Exception as e:
42
+ console.print(f"[red]Error connecting to MCP server: {e}[/red]")
43
+ raise typer.Exit(1)
44
+ finally:
45
+ await client.disconnect()
46
+
47
+ submit(_connect_and_list())
48
+
49
+
50
+ @mcp_cmd.command("serve")
51
+ def mcp_serve_subcmd(ctx: typer.Context) -> None:
52
+ """Start Velune as an MCP server for Claude Desktop / VS Code."""
53
+ cli_context = ctx.obj if isinstance(ctx.obj, CLIContext) else None
54
+ workspace = cli_context.workspace if cli_context else Path.cwd()
55
+ config_path = cli_context.config_path if cli_context else None
56
+
57
+ container = build_runtime(workspace, config_path=config_path).container
58
+ tool_registry = container.get("runtime.tool_registry")
59
+ server = VeluneMCPServer(tool_registry)
60
+
61
+ import logging
62
+ logging.getLogger("velune").setLevel(logging.WARNING)
63
+
64
+ submit(server.run_stdio())
65
+
66
+
67
+ def mcp_serve(ctx: typer.Context) -> None:
68
+ """Start Velune as an MCP server for Claude Desktop / VS Code."""
69
+ cli_context = ctx.obj if isinstance(ctx.obj, CLIContext) else None
70
+ workspace = cli_context.workspace if cli_context else Path.cwd()
71
+ config_path = cli_context.config_path if cli_context else None
72
+
73
+ container = build_runtime(workspace, config_path=config_path).container
74
+ tool_registry = container.get("runtime.tool_registry")
75
+ server = VeluneMCPServer(tool_registry)
76
+
77
+ import logging
78
+ logging.getLogger("velune").setLevel(logging.WARNING)
79
+
80
+ submit(server.run_stdio())