velune-cli 0.9.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.
- velune/__init__.py +5 -0
- velune/__main__.py +6 -0
- velune/cli/__init__.py +5 -0
- velune/cli/app.py +208 -0
- velune/cli/autocomplete.py +80 -0
- velune/cli/banner.py +60 -0
- velune/cli/commands/__init__.py +32 -0
- velune/cli/commands/ask.py +175 -0
- velune/cli/commands/base.py +16 -0
- velune/cli/commands/chat.py +228 -0
- velune/cli/commands/config.py +224 -0
- velune/cli/commands/daemon.py +88 -0
- velune/cli/commands/doctor.py +721 -0
- velune/cli/commands/init.py +170 -0
- velune/cli/commands/mcp.py +82 -0
- velune/cli/commands/memory.py +293 -0
- velune/cli/commands/models.py +683 -0
- velune/cli/commands/preflight.py +95 -0
- velune/cli/commands/run.py +270 -0
- velune/cli/commands/setup.py +184 -0
- velune/cli/commands/workspace.py +249 -0
- velune/cli/context.py +36 -0
- velune/cli/councilmodel_ui.py +199 -0
- velune/cli/display/council_view.py +254 -0
- velune/cli/display/memory_view.py +126 -0
- velune/cli/display/panels.py +35 -0
- velune/cli/display/progress.py +25 -0
- velune/cli/display/themes.py +25 -0
- velune/cli/main.py +15 -0
- velune/cli/model_selector.py +51 -0
- velune/cli/modes.py +86 -0
- velune/cli/pull_ui.py +123 -0
- velune/cli/registry.py +80 -0
- velune/cli/rendering/__init__.py +5 -0
- velune/cli/rendering/error_panel.py +79 -0
- velune/cli/rendering/markdown.py +63 -0
- velune/cli/repl.py +1855 -0
- velune/cli/session_manager.py +71 -0
- velune/cli/slash_commands.py +37 -0
- velune/cli/theme.py +8 -0
- velune/cognition/__init__.py +23 -0
- velune/cognition/agents/__init__.py +7 -0
- velune/cognition/agents/coder.py +209 -0
- velune/cognition/agents/planner.py +156 -0
- velune/cognition/agents/reviewer.py +195 -0
- velune/cognition/arbitrator.py +220 -0
- velune/cognition/architecture.py +415 -0
- velune/cognition/budget.py +65 -0
- velune/cognition/council/__init__.py +47 -0
- velune/cognition/council/base.py +217 -0
- velune/cognition/council/challenger.py +74 -0
- velune/cognition/council/coder.py +79 -0
- velune/cognition/council/critic_agent.py +43 -0
- velune/cognition/council/critic_configs.py +111 -0
- velune/cognition/council/critics.py +41 -0
- velune/cognition/council/debate.py +46 -0
- velune/cognition/council/factory.py +140 -0
- velune/cognition/council/messages.py +56 -0
- velune/cognition/council/planner.py +124 -0
- velune/cognition/council/reviewer.py +74 -0
- velune/cognition/council/synthesizer.py +67 -0
- velune/cognition/council/tiers.py +188 -0
- velune/cognition/council_orchestrator.py +282 -0
- velune/cognition/firewall.py +354 -0
- velune/cognition/module.py +46 -0
- velune/cognition/orchestrator.py +1205 -0
- velune/cognition/personality.py +238 -0
- velune/cognition/state.py +104 -0
- velune/cognition/style_resolver.py +64 -0
- velune/cognition/verification.py +205 -0
- velune/context/__init__.py +28 -0
- velune/context/assembler.py +240 -0
- velune/context/budget.py +97 -0
- velune/context/extractive.py +95 -0
- velune/context/prompt_adaptation.py +480 -0
- velune/context/sections.py +99 -0
- velune/context/token_counter.py +134 -0
- velune/context/utilization.py +33 -0
- velune/context/window.py +63 -0
- velune/core/__init__.py +89 -0
- velune/core/background.py +5 -0
- velune/core/config/__init__.py +37 -0
- velune/core/errors/__init__.py +90 -0
- velune/core/errors/catalog.py +188 -0
- velune/core/errors/execution.py +31 -0
- velune/core/errors/memory.py +25 -0
- velune/core/errors/orchestration.py +31 -0
- velune/core/errors/provider.py +37 -0
- velune/core/event_loop.py +35 -0
- velune/core/logging.py +83 -0
- velune/core/paths.py +165 -0
- velune/core/runtime.py +113 -0
- velune/core/startup_profiler.py +56 -0
- velune/core/task_registry.py +117 -0
- velune/core/trace.py +83 -0
- velune/core/types/__init__.py +48 -0
- velune/core/types/agent.py +53 -0
- velune/core/types/context.py +42 -0
- velune/core/types/inference.py +38 -0
- velune/core/types/memory.py +42 -0
- velune/core/types/model.py +70 -0
- velune/core/types/provider.py +62 -0
- velune/core/types/repository.py +38 -0
- velune/core/types/task.py +61 -0
- velune/core/types/workspace.py +28 -0
- velune/daemon/client.py +13 -0
- velune/daemon/server.py +127 -0
- velune/daemon/transport.py +179 -0
- velune/events.py +204 -0
- velune/execution/__init__.py +22 -0
- velune/execution/benchmarker.py +315 -0
- velune/execution/cancellation.py +53 -0
- velune/execution/checkpointer.py +130 -0
- velune/execution/command_spec.py +165 -0
- velune/execution/diff_preview.py +197 -0
- velune/execution/executor.py +181 -0
- velune/execution/module.py +18 -0
- velune/execution/multi_diff.py +67 -0
- velune/execution/path_guard.py +74 -0
- velune/execution/planner.py +91 -0
- velune/execution/rollback.py +89 -0
- velune/execution/sandbox.py +268 -0
- velune/execution/validator.py +115 -0
- velune/hardware/__init__.py +1 -0
- velune/hardware/detector.py +192 -0
- velune/kernel/__init__.py +55 -0
- velune/kernel/bootstrap.py +125 -0
- velune/kernel/config.py +426 -0
- velune/kernel/entrypoint.py +78 -0
- velune/kernel/health.py +54 -0
- velune/kernel/lifecycle.py +143 -0
- velune/kernel/module.py +17 -0
- velune/kernel/modules.py +23 -0
- velune/kernel/registry.py +96 -0
- velune/kernel/schemas.py +28 -0
- velune/main.py +9 -0
- velune/mcp/__init__.py +9 -0
- velune/mcp/client.py +115 -0
- velune/mcp/config.py +19 -0
- velune/mcp/server.py +624 -0
- velune/memory/__init__.py +32 -0
- velune/memory/compaction.py +506 -0
- velune/memory/embedding_pipeline.py +241 -0
- velune/memory/lifecycle.py +680 -0
- velune/memory/module.py +218 -0
- velune/memory/prioritizer.py +67 -0
- velune/memory/storage/episodic_schema.sql +53 -0
- velune/memory/storage/lancedb_store.py +282 -0
- velune/memory/storage/sqlite_manager.py +369 -0
- velune/memory/storage/sqlite_pool.py +149 -0
- velune/memory/tiers/episodic.py +588 -0
- velune/memory/tiers/graph.py +378 -0
- velune/memory/tiers/lineage.py +416 -0
- velune/memory/tiers/semantic.py +475 -0
- velune/memory/tiers/working.py +168 -0
- velune/memory/vitality.py +132 -0
- velune/models/__init__.py +15 -0
- velune/models/family.py +76 -0
- velune/models/module.py +20 -0
- velune/models/probes.py +192 -0
- velune/models/profile_cache.py +84 -0
- velune/models/profiler.py +108 -0
- velune/models/registry.py +251 -0
- velune/models/scorer.py +233 -0
- velune/models/specializations.py +205 -0
- velune/orchestration/__init__.py +19 -0
- velune/orchestration/engine.py +239 -0
- velune/orchestration/module.py +15 -0
- velune/orchestration/role_assignments.py +82 -0
- velune/orchestration/schemas.py +98 -0
- velune/plugins/__init__.py +20 -0
- velune/plugins/hooks.py +50 -0
- velune/plugins/loader.py +161 -0
- velune/plugins/registry.py +56 -0
- velune/plugins/schemas.py +21 -0
- velune/providers/__init__.py +23 -0
- velune/providers/adapters/anthropic.py +257 -0
- velune/providers/adapters/fireworks.py +115 -0
- velune/providers/adapters/google.py +234 -0
- velune/providers/adapters/groq.py +151 -0
- velune/providers/adapters/huggingface.py +210 -0
- velune/providers/adapters/llamacpp.py +208 -0
- velune/providers/adapters/lmstudio.py +175 -0
- velune/providers/adapters/ollama.py +233 -0
- velune/providers/adapters/openai.py +213 -0
- velune/providers/adapters/openrouter.py +81 -0
- velune/providers/adapters/together.py +134 -0
- velune/providers/adapters/xai.py +60 -0
- velune/providers/base.py +86 -0
- velune/providers/benchmarker.py +138 -0
- velune/providers/discovery/__init__.py +33 -0
- velune/providers/discovery/anthropic.py +79 -0
- velune/providers/discovery/benchmarks.py +44 -0
- velune/providers/discovery/classifier.py +69 -0
- velune/providers/discovery/fireworks.py +95 -0
- velune/providers/discovery/gguf.py +88 -0
- velune/providers/discovery/google.py +95 -0
- velune/providers/discovery/gpu.py +117 -0
- velune/providers/discovery/groq.py +21 -0
- velune/providers/discovery/huggingface.py +67 -0
- velune/providers/discovery/lmstudio.py +80 -0
- velune/providers/discovery/ollama.py +162 -0
- velune/providers/discovery/openai.py +96 -0
- velune/providers/discovery/openrouter.py +113 -0
- velune/providers/discovery/scanner.py +115 -0
- velune/providers/discovery/together.py +114 -0
- velune/providers/discovery/xai.py +57 -0
- velune/providers/health.py +67 -0
- velune/providers/health_monitor.py +169 -0
- velune/providers/keystore.py +142 -0
- velune/providers/local_paths.py +49 -0
- velune/providers/local_resolver.py +229 -0
- velune/providers/module.py +51 -0
- velune/providers/ollama_manager.py +193 -0
- velune/providers/registry.py +220 -0
- velune/providers/router.py +255 -0
- velune/providers/task_classifier.py +288 -0
- velune/py.typed +0 -0
- velune/repository/__init__.py +33 -0
- velune/repository/analyzer.py +127 -0
- velune/repository/ast_parser.py +822 -0
- velune/repository/blast_radius.py +298 -0
- velune/repository/boundary_classifier.py +295 -0
- velune/repository/cognition.py +316 -0
- velune/repository/grapher.py +179 -0
- velune/repository/import_graph.py +263 -0
- velune/repository/incremental_indexer.py +275 -0
- velune/repository/index_state.py +96 -0
- velune/repository/indexer.py +243 -0
- velune/repository/module.py +17 -0
- velune/repository/parser.py +474 -0
- velune/repository/project_type.py +300 -0
- velune/repository/rename_journal.py +287 -0
- velune/repository/scanner.py +193 -0
- velune/repository/schemas.py +102 -0
- velune/repository/symbol_registry.py +365 -0
- velune/repository/tracker.py +252 -0
- velune/retrieval/__init__.py +27 -0
- velune/retrieval/cache.py +110 -0
- velune/retrieval/fast_path.py +391 -0
- velune/retrieval/graph.py +124 -0
- velune/retrieval/hybrid.py +271 -0
- velune/retrieval/keyword.py +131 -0
- velune/retrieval/module.py +26 -0
- velune/retrieval/pipeline.py +303 -0
- velune/retrieval/reranker.py +102 -0
- velune/retrieval/schemas.py +59 -0
- velune/retrieval/slow_path.py +364 -0
- velune/retrieval/vector.py +203 -0
- velune/telemetry/__init__.py +59 -0
- velune/telemetry/cognition.py +267 -0
- velune/telemetry/cost_estimator.py +92 -0
- velune/telemetry/debug.py +304 -0
- velune/telemetry/doctor.py +244 -0
- velune/telemetry/logging.py +286 -0
- velune/telemetry/spans.py +277 -0
- velune/telemetry/token_tracker.py +140 -0
- velune/telemetry/usage_tracker.py +340 -0
- velune/tools/__init__.py +41 -0
- velune/tools/base/registry.py +87 -0
- velune/tools/base/tool.py +63 -0
- velune/tools/code/navigate.py +116 -0
- velune/tools/code/search.py +123 -0
- velune/tools/filesystem/read.py +75 -0
- velune/tools/filesystem/search.py +136 -0
- velune/tools/filesystem/write.py +163 -0
- velune/tools/git/history.py +177 -0
- velune/tools/git/operations.py +122 -0
- velune/tools/git/state.py +121 -0
- velune/tools/module.py +81 -0
- velune/tools/terminal/execute.py +72 -0
- velune/tools/terminal/history.py +47 -0
- velune/tools/web/fetch.py +55 -0
- velune/tools/web/validator.py +122 -0
- velune_cli-0.9.0.dist-info/METADATA +518 -0
- velune_cli-0.9.0.dist-info/RECORD +279 -0
- velune_cli-0.9.0.dist-info/WHEEL +4 -0
- velune_cli-0.9.0.dist-info/entry_points.txt +2 -0
- velune_cli-0.9.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,721 @@
|
|
|
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
|
+
from velune.telemetry import print_provider_health_report
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
doctor_cmd = typer.Typer(help="Environment health diagnostics")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@doctor_cmd.callback(invoke_without_command=True)
|
|
22
|
+
def doctor_main(
|
|
23
|
+
ctx: typer.Context,
|
|
24
|
+
perf: bool = typer.Option(False, "--perf", help="Check startup performance"),
|
|
25
|
+
json_output: bool = typer.Option(False, "--json", help="Output results as JSON"),
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Callback for doctor command group to enable startup performance diagnostics."""
|
|
28
|
+
if ctx.invoked_subcommand is None:
|
|
29
|
+
if perf:
|
|
30
|
+
import time
|
|
31
|
+
|
|
32
|
+
from velune.core.startup_profiler import _PROCESS_START
|
|
33
|
+
|
|
34
|
+
startup_time_ms = (time.perf_counter() - _PROCESS_START) * 1000.0
|
|
35
|
+
|
|
36
|
+
if json_output:
|
|
37
|
+
import json
|
|
38
|
+
|
|
39
|
+
print(
|
|
40
|
+
json.dumps(
|
|
41
|
+
{
|
|
42
|
+
"startup_time_ms": round(startup_time_ms, 2),
|
|
43
|
+
"status": "ok" if startup_time_ms < 3000 else "fail",
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
else:
|
|
48
|
+
status = "OK" if startup_time_ms < 3000 else "FAIL"
|
|
49
|
+
console.print(f"Startup performance: {startup_time_ms:.2f}ms [{status}]")
|
|
50
|
+
raise typer.Exit()
|
|
51
|
+
else:
|
|
52
|
+
console.print(ctx.get_help())
|
|
53
|
+
raise typer.Exit()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@doctor_cmd.command(name="providers")
|
|
57
|
+
def show_providers() -> None:
|
|
58
|
+
"""Show provider health and capability status."""
|
|
59
|
+
print_provider_health_report(console)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@doctor_cmd.command(name="check")
|
|
63
|
+
def check(
|
|
64
|
+
fix: bool = typer.Option(False, "--fix", help="Attempt to fix issues automatically"),
|
|
65
|
+
json_output: bool = typer.Option(False, "--json", help="Output results as JSON"),
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Run Velune environment health checks."""
|
|
68
|
+
if fix:
|
|
69
|
+
console.print("[yellow]Attempting automatic fixes...[/yellow]")
|
|
70
|
+
|
|
71
|
+
# Fix 1: Create .velune/ directory
|
|
72
|
+
velune_dir = Path.cwd() / ".velune"
|
|
73
|
+
if not velune_dir.exists():
|
|
74
|
+
try:
|
|
75
|
+
velune_dir.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
console.print("[green]✓ Created .velune/ directory.[/green]")
|
|
77
|
+
except Exception as e:
|
|
78
|
+
console.print(f"[red]✗ Failed to create .velune/: {e}[/red]")
|
|
79
|
+
|
|
80
|
+
# Fix 2: Create default velune.toml if missing
|
|
81
|
+
config_file = Path.cwd() / "velune.toml"
|
|
82
|
+
if not config_file.exists():
|
|
83
|
+
try:
|
|
84
|
+
import toml # type: ignore[import-untyped]
|
|
85
|
+
|
|
86
|
+
from velune.kernel.config import get_default_config
|
|
87
|
+
|
|
88
|
+
default_config = get_default_config()
|
|
89
|
+
with open(config_file, "w") as f:
|
|
90
|
+
toml.dump(default_config.model_dump(), f)
|
|
91
|
+
console.print("[green]✓ Created default velune.toml config file.[/green]")
|
|
92
|
+
except Exception as e:
|
|
93
|
+
console.print(f"[red]✗ Failed to create default velune.toml: {e}[/red]")
|
|
94
|
+
|
|
95
|
+
# Fix 3: Initialize databases
|
|
96
|
+
db_file = velune_dir / "velune_cognitive_core.db"
|
|
97
|
+
try:
|
|
98
|
+
from velune.telemetry.cognition import CognitivePerformanceAnalytics
|
|
99
|
+
|
|
100
|
+
CognitivePerformanceAnalytics(db_path=db_file)
|
|
101
|
+
console.print("[green]✓ SQLite database successfully initialized.[/green]")
|
|
102
|
+
except Exception as e:
|
|
103
|
+
console.print(f"[red]✗ Failed to initialize SQLite database: {e}[/red]")
|
|
104
|
+
|
|
105
|
+
console.print("[yellow]Re-running checks after fixes...[/yellow]\n")
|
|
106
|
+
|
|
107
|
+
checks = [
|
|
108
|
+
_check_python_version,
|
|
109
|
+
_check_core_dependencies,
|
|
110
|
+
_check_internet_connectivity,
|
|
111
|
+
_check_ollama_connectivity,
|
|
112
|
+
_check_ollama_models,
|
|
113
|
+
_check_lm_studio,
|
|
114
|
+
_check_openai_api_key,
|
|
115
|
+
_check_anthropic_api_key,
|
|
116
|
+
_check_groq,
|
|
117
|
+
_check_google,
|
|
118
|
+
_check_velune_dir,
|
|
119
|
+
_check_sqlite,
|
|
120
|
+
_check_qdrant,
|
|
121
|
+
_check_config,
|
|
122
|
+
_check_treesitter,
|
|
123
|
+
_check_git,
|
|
124
|
+
_check_gpu,
|
|
125
|
+
_check_vram,
|
|
126
|
+
_check_model_benchmarks,
|
|
127
|
+
_check_session_cost,
|
|
128
|
+
_check_memory_health,
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
results = []
|
|
132
|
+
for check_fn in checks:
|
|
133
|
+
try:
|
|
134
|
+
result = check_fn()
|
|
135
|
+
results.append(result)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
results.append(
|
|
138
|
+
{
|
|
139
|
+
"name": check_fn.__name__.replace("_check_", "").replace("_", " ").title(),
|
|
140
|
+
"status": "error",
|
|
141
|
+
"message": str(e),
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if json_output:
|
|
146
|
+
import json
|
|
147
|
+
|
|
148
|
+
print(json.dumps(results, indent=2))
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
_render_results(results)
|
|
152
|
+
|
|
153
|
+
failures = [r for r in results if r["status"] == "fail"]
|
|
154
|
+
if failures:
|
|
155
|
+
console.print(f"\n[red]✗ {len(failures)} check(s) failed.[/red]")
|
|
156
|
+
console.print("[dim]Run 'velune doctor --fix' to attempt automatic fixes.[/dim]")
|
|
157
|
+
raise typer.Exit(1)
|
|
158
|
+
else:
|
|
159
|
+
console.print("\n[green]✓ All checks passed. Velune is ready.[/green]")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _check_python_version() -> dict:
|
|
163
|
+
version = sys.version_info
|
|
164
|
+
clean_version = sys.version.replace("\n", " ")
|
|
165
|
+
if version >= (3, 11):
|
|
166
|
+
return {"name": "Python Version", "status": "ok", "message": f"{clean_version}"}
|
|
167
|
+
return {
|
|
168
|
+
"name": "Python Version",
|
|
169
|
+
"status": "fail",
|
|
170
|
+
"message": f"Python {version.major}.{version.minor} < 3.11. Install Python 3.11+. Details: {clean_version}",
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _check_core_dependencies() -> dict:
|
|
175
|
+
deps = ["pydantic", "typer", "rich", "httpx", "qdrant_client", "toml"]
|
|
176
|
+
missing = []
|
|
177
|
+
for dep in deps:
|
|
178
|
+
try:
|
|
179
|
+
__import__(dep)
|
|
180
|
+
except ImportError:
|
|
181
|
+
missing.append(dep)
|
|
182
|
+
|
|
183
|
+
if not missing:
|
|
184
|
+
return {
|
|
185
|
+
"name": "Core Dependencies",
|
|
186
|
+
"status": "ok",
|
|
187
|
+
"message": "All core dependencies installed.",
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
"name": "Core Dependencies",
|
|
191
|
+
"status": "fail",
|
|
192
|
+
"message": f"Missing core dependencies: {', '.join(missing)}",
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _check_ollama_connectivity() -> dict:
|
|
197
|
+
import httpx
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
httpx.get("http://localhost:11434/api/tags", timeout=3.0)
|
|
201
|
+
return {
|
|
202
|
+
"name": "Ollama Connectivity",
|
|
203
|
+
"status": "ok",
|
|
204
|
+
"message": "Connected successfully to http://localhost:11434.",
|
|
205
|
+
}
|
|
206
|
+
except Exception:
|
|
207
|
+
return {
|
|
208
|
+
"name": "Ollama Connectivity",
|
|
209
|
+
"status": "warn",
|
|
210
|
+
"message": "Could not connect to Ollama at http://localhost:11434.",
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _check_ollama_models() -> dict:
|
|
215
|
+
import httpx
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
r = httpx.get("http://localhost:11434/api/tags", timeout=3.0)
|
|
219
|
+
models = r.json().get("models", [])
|
|
220
|
+
if models:
|
|
221
|
+
model_names = [m.get("name") for m in models]
|
|
222
|
+
return {
|
|
223
|
+
"name": "Ollama Model Availability",
|
|
224
|
+
"status": "ok",
|
|
225
|
+
"message": f"{len(models)} model(s) found: {', '.join(model_names[:3])}{'...' if len(model_names) > 3 else ''}",
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
"name": "Ollama Model Availability",
|
|
229
|
+
"status": "warn",
|
|
230
|
+
"message": "No local Ollama models installed. Run 'ollama pull llama3.2'.",
|
|
231
|
+
}
|
|
232
|
+
except Exception:
|
|
233
|
+
return {
|
|
234
|
+
"name": "Ollama Model Availability",
|
|
235
|
+
"status": "warn",
|
|
236
|
+
"message": "Unable to check model list (Ollama not connected).",
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _check_lm_studio() -> dict:
|
|
241
|
+
import httpx
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
r = httpx.get("http://localhost:1234/v1/models", timeout=3.0)
|
|
245
|
+
if r.status_code == 200:
|
|
246
|
+
return {
|
|
247
|
+
"name": "LM Studio Connectivity",
|
|
248
|
+
"status": "ok",
|
|
249
|
+
"message": "Connected successfully to http://localhost:1234.",
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
"name": "LM Studio Connectivity",
|
|
253
|
+
"status": "warn",
|
|
254
|
+
"message": f"Connected to http://localhost:1234 but received status {r.status_code}.",
|
|
255
|
+
}
|
|
256
|
+
except Exception:
|
|
257
|
+
return {
|
|
258
|
+
"name": "LM Studio Connectivity",
|
|
259
|
+
"status": "warn",
|
|
260
|
+
"message": "Not running or not accessible at http://localhost:1234.",
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _check_openai_api_key() -> dict:
|
|
265
|
+
key = get_key("openai")
|
|
266
|
+
if key:
|
|
267
|
+
return {
|
|
268
|
+
"name": "OpenAI API Key",
|
|
269
|
+
"status": "ok",
|
|
270
|
+
"message": f"Configured ({key[:4]}...{key[-4:] if len(key) > 8 else ''})",
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
"name": "OpenAI API Key",
|
|
274
|
+
"status": "warn",
|
|
275
|
+
"message": "Not configured. Run 'velune setup' to add your key.",
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _check_anthropic_api_key() -> dict:
|
|
280
|
+
key = get_key("anthropic")
|
|
281
|
+
if key:
|
|
282
|
+
return {
|
|
283
|
+
"name": "Anthropic API Key",
|
|
284
|
+
"status": "ok",
|
|
285
|
+
"message": f"Configured ({key[:4]}...{key[-4:] if len(key) > 8 else ''})",
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
"name": "Anthropic API Key",
|
|
289
|
+
"status": "warn",
|
|
290
|
+
"message": "Not configured. Run 'velune setup' to add your key.",
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _check_velune_dir() -> dict:
|
|
295
|
+
velune_dir = Path.cwd() / ".velune"
|
|
296
|
+
try:
|
|
297
|
+
velune_dir.mkdir(exist_ok=True)
|
|
298
|
+
test_file = velune_dir / ".write_test"
|
|
299
|
+
test_file.write_text("test")
|
|
300
|
+
test_file.unlink()
|
|
301
|
+
return {
|
|
302
|
+
"name": ".velune Directory Writable",
|
|
303
|
+
"status": "ok",
|
|
304
|
+
"message": f"Writable directory at {velune_dir}",
|
|
305
|
+
}
|
|
306
|
+
except Exception as e:
|
|
307
|
+
return {
|
|
308
|
+
"name": ".velune Directory Writable",
|
|
309
|
+
"status": "fail",
|
|
310
|
+
"message": f"Cannot write to {velune_dir}: {e}",
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _check_sqlite() -> dict:
|
|
315
|
+
velune_dir = Path.cwd() / ".velune"
|
|
316
|
+
db_file = velune_dir / "velune_cognitive_core.db"
|
|
317
|
+
try:
|
|
318
|
+
velune_dir.mkdir(exist_ok=True)
|
|
319
|
+
import sqlite3
|
|
320
|
+
|
|
321
|
+
conn = sqlite3.connect(str(db_file), timeout=3.0)
|
|
322
|
+
cursor = conn.cursor()
|
|
323
|
+
cursor.execute("SELECT 1")
|
|
324
|
+
conn.close()
|
|
325
|
+
return {
|
|
326
|
+
"name": "SQLite DB Initializable",
|
|
327
|
+
"status": "ok",
|
|
328
|
+
"message": f"Successfully initialized/opened sqlite database at {db_file}",
|
|
329
|
+
}
|
|
330
|
+
except Exception as e:
|
|
331
|
+
return {
|
|
332
|
+
"name": "SQLite DB Initializable",
|
|
333
|
+
"status": "fail",
|
|
334
|
+
"message": f"Failed to initialize SQLite database: {e}",
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _check_qdrant() -> dict:
|
|
339
|
+
try:
|
|
340
|
+
from qdrant_client import QdrantClient
|
|
341
|
+
|
|
342
|
+
with tempfile.TemporaryDirectory(prefix="velune-qdrant-") as temp_dir:
|
|
343
|
+
qdrant_path = Path(temp_dir)
|
|
344
|
+
client = QdrantClient(path=str(qdrant_path))
|
|
345
|
+
client.get_collections()
|
|
346
|
+
client.close()
|
|
347
|
+
return {
|
|
348
|
+
"name": "Qdrant In-Process Initializable",
|
|
349
|
+
"status": "ok",
|
|
350
|
+
"message": f"Qdrant local storage successfully initialized at {qdrant_path}",
|
|
351
|
+
}
|
|
352
|
+
except Exception as e:
|
|
353
|
+
return {
|
|
354
|
+
"name": "Qdrant In-Process Initializable",
|
|
355
|
+
"status": "fail",
|
|
356
|
+
"message": f"Failed to initialize local Qdrant client: {e}",
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _check_config() -> dict:
|
|
361
|
+
config_file = Path.cwd() / "velune.toml"
|
|
362
|
+
if not config_file.exists():
|
|
363
|
+
return {
|
|
364
|
+
"name": "velune.toml Config File",
|
|
365
|
+
"status": "warn",
|
|
366
|
+
"message": "No velune.toml found in current workspace. Using defaults.",
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
try:
|
|
370
|
+
import toml # type: ignore[import-untyped]
|
|
371
|
+
|
|
372
|
+
from velune.kernel.config import VeluneConfig
|
|
373
|
+
|
|
374
|
+
data = toml.load(config_file)
|
|
375
|
+
VeluneConfig(**data)
|
|
376
|
+
return {
|
|
377
|
+
"name": "velune.toml Config File",
|
|
378
|
+
"status": "ok",
|
|
379
|
+
"message": f"Found and validated successfully at {config_file}",
|
|
380
|
+
}
|
|
381
|
+
except Exception as e:
|
|
382
|
+
return {
|
|
383
|
+
"name": "velune.toml Config File",
|
|
384
|
+
"status": "fail",
|
|
385
|
+
"message": f"Invalid velune.toml format or schema validation error: {e}",
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _check_treesitter() -> dict:
|
|
390
|
+
try:
|
|
391
|
+
import tree_sitter_go
|
|
392
|
+
import tree_sitter_python
|
|
393
|
+
import tree_sitter_rust
|
|
394
|
+
import tree_sitter_typescript
|
|
395
|
+
from tree_sitter import Language
|
|
396
|
+
|
|
397
|
+
langs = []
|
|
398
|
+
for name, mod in [
|
|
399
|
+
("python", tree_sitter_python),
|
|
400
|
+
("typescript", tree_sitter_typescript),
|
|
401
|
+
("go", tree_sitter_go),
|
|
402
|
+
("rust", tree_sitter_rust),
|
|
403
|
+
]:
|
|
404
|
+
try:
|
|
405
|
+
if name == "typescript":
|
|
406
|
+
Language(mod.language_typescript())
|
|
407
|
+
else:
|
|
408
|
+
Language(mod.language())
|
|
409
|
+
langs.append(name)
|
|
410
|
+
except Exception:
|
|
411
|
+
pass
|
|
412
|
+
if langs:
|
|
413
|
+
return {
|
|
414
|
+
"name": "Tree-sitter Grammars",
|
|
415
|
+
"status": "ok",
|
|
416
|
+
"message": f"Tree-sitter grammars loaded: {', '.join(langs)}.",
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
"name": "Tree-sitter Grammars",
|
|
420
|
+
"status": "warn",
|
|
421
|
+
"message": "tree-sitter installed but no grammars loaded correctly.",
|
|
422
|
+
}
|
|
423
|
+
except ImportError as e:
|
|
424
|
+
return {
|
|
425
|
+
"name": "Tree-sitter Grammars",
|
|
426
|
+
"status": "warn",
|
|
427
|
+
"message": f"Tree-sitter package or parser modules missing: {e}.",
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _check_git() -> dict:
|
|
432
|
+
git_path = shutil.which("git")
|
|
433
|
+
if git_path:
|
|
434
|
+
return {"name": "Git in PATH", "status": "ok", "message": f"Found Git at {git_path}"}
|
|
435
|
+
return {
|
|
436
|
+
"name": "Git in PATH",
|
|
437
|
+
"status": "fail",
|
|
438
|
+
"message": "Git is not installed or not in system PATH.",
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _check_gpu() -> dict:
|
|
443
|
+
from velune.providers.discovery.gpu import GPUDetector
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
gpu_info = GPUDetector().detect()
|
|
447
|
+
if gpu_info.get("has_gpu"):
|
|
448
|
+
gpu_name = gpu_info.get("gpu_name", "Unknown Name")
|
|
449
|
+
gpu_type = gpu_info.get("gpu_type", "Unknown")
|
|
450
|
+
return {
|
|
451
|
+
"name": "GPU Detection",
|
|
452
|
+
"status": "ok",
|
|
453
|
+
"message": f"Detected GPU: {gpu_name} ({gpu_type.upper()})",
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
"name": "GPU Detection",
|
|
457
|
+
"status": "warn",
|
|
458
|
+
"message": "No dedicated GPU detected. Models will run on CPU.",
|
|
459
|
+
}
|
|
460
|
+
except Exception as e:
|
|
461
|
+
return {
|
|
462
|
+
"name": "GPU Detection",
|
|
463
|
+
"status": "warn",
|
|
464
|
+
"message": f"Failed to run GPU detection: {e}",
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _check_vram() -> dict:
|
|
469
|
+
from velune.providers.discovery.gpu import GPUDetector
|
|
470
|
+
|
|
471
|
+
try:
|
|
472
|
+
gpu_info = GPUDetector().detect()
|
|
473
|
+
if gpu_info.get("has_gpu") and gpu_info.get("vram_total_gb") is not None:
|
|
474
|
+
total = gpu_info.get("vram_total_gb", 0)
|
|
475
|
+
free = gpu_info.get("vram_free_gb", 0)
|
|
476
|
+
return {
|
|
477
|
+
"name": "Available VRAM",
|
|
478
|
+
"status": "ok",
|
|
479
|
+
"message": f"VRAM Total: {total:.2f} GB, VRAM Free: {free:.2f} GB",
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
"name": "Available VRAM",
|
|
483
|
+
"status": "warn",
|
|
484
|
+
"message": "Unified or CPU-only memory in use.",
|
|
485
|
+
}
|
|
486
|
+
except Exception as e:
|
|
487
|
+
return {"name": "Available VRAM", "status": "warn", "message": f"Failed to query VRAM: {e}"}
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _check_groq() -> dict:
|
|
491
|
+
from velune.providers.keystore import get_key, has_key
|
|
492
|
+
|
|
493
|
+
if not has_key("groq"):
|
|
494
|
+
return {
|
|
495
|
+
"name": "Groq",
|
|
496
|
+
"status": "warn",
|
|
497
|
+
"message": "Not configured — free tier available at console.groq.com/keys",
|
|
498
|
+
}
|
|
499
|
+
try:
|
|
500
|
+
import httpx
|
|
501
|
+
|
|
502
|
+
key = get_key("groq")
|
|
503
|
+
r = httpx.get(
|
|
504
|
+
"https://api.groq.com/openai/v1/models",
|
|
505
|
+
headers={"Authorization": f"Bearer {key}"},
|
|
506
|
+
timeout=5,
|
|
507
|
+
)
|
|
508
|
+
if r.status_code == 200:
|
|
509
|
+
models = r.json().get("data", [])
|
|
510
|
+
return {
|
|
511
|
+
"name": "Groq",
|
|
512
|
+
"status": "ok",
|
|
513
|
+
"message": f"Connected — {len(models)} models available",
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
"name": "Groq",
|
|
517
|
+
"status": "fail",
|
|
518
|
+
"message": f"Auth failed (HTTP {r.status_code}) — check your key",
|
|
519
|
+
}
|
|
520
|
+
except Exception as e:
|
|
521
|
+
return {
|
|
522
|
+
"name": "Groq",
|
|
523
|
+
"status": "fail",
|
|
524
|
+
"message": f"Cannot reach api.groq.com — {e}",
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _check_google() -> dict:
|
|
529
|
+
from velune.providers.keystore import get_key, has_key
|
|
530
|
+
|
|
531
|
+
if not has_key("google"):
|
|
532
|
+
return {
|
|
533
|
+
"name": "Google Gemini",
|
|
534
|
+
"status": "warn",
|
|
535
|
+
"message": "Not configured — free quota at aistudio.google.com",
|
|
536
|
+
}
|
|
537
|
+
try:
|
|
538
|
+
import httpx
|
|
539
|
+
|
|
540
|
+
key = get_key("google")
|
|
541
|
+
r = httpx.get(
|
|
542
|
+
f"https://generativelanguage.googleapis.com/v1beta/models?key={key}",
|
|
543
|
+
timeout=5,
|
|
544
|
+
)
|
|
545
|
+
if r.status_code == 200:
|
|
546
|
+
models = r.json().get("models", [])
|
|
547
|
+
return {
|
|
548
|
+
"name": "Google Gemini",
|
|
549
|
+
"status": "ok",
|
|
550
|
+
"message": f"Connected — {len(models)} models available",
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
553
|
+
"name": "Google Gemini",
|
|
554
|
+
"status": "fail",
|
|
555
|
+
"message": f"Auth failed (HTTP {r.status_code})",
|
|
556
|
+
}
|
|
557
|
+
except Exception as e:
|
|
558
|
+
return {
|
|
559
|
+
"name": "Google Gemini",
|
|
560
|
+
"status": "fail",
|
|
561
|
+
"message": f"Cannot reach googleapis.com — {e}",
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def _check_model_benchmarks() -> dict:
|
|
566
|
+
profile_path = Path.cwd() / ".velune" / "model_profiles.json"
|
|
567
|
+
if profile_path.exists():
|
|
568
|
+
try:
|
|
569
|
+
import json
|
|
570
|
+
|
|
571
|
+
data = json.loads(profile_path.read_text())
|
|
572
|
+
if data:
|
|
573
|
+
return {
|
|
574
|
+
"name": "Empirical Model Benchmarks",
|
|
575
|
+
"status": "ok",
|
|
576
|
+
"message": f"Cached capability profiles found for {len(data)} model(s).",
|
|
577
|
+
}
|
|
578
|
+
except Exception:
|
|
579
|
+
pass
|
|
580
|
+
return {
|
|
581
|
+
"name": "Empirical Model Benchmarks",
|
|
582
|
+
"status": "warn",
|
|
583
|
+
"message": "No empirical model capability benchmarks cached. Run: velune models scan --probe",
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _check_internet_connectivity() -> dict:
|
|
588
|
+
from velune.providers.health import get_checker
|
|
589
|
+
|
|
590
|
+
checker = get_checker()
|
|
591
|
+
if checker.is_online:
|
|
592
|
+
return {
|
|
593
|
+
"name": "Internet Connectivity",
|
|
594
|
+
"status": "ok",
|
|
595
|
+
"message": "Online — cloud providers reachable",
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
"name": "Internet Connectivity",
|
|
599
|
+
"status": "warn",
|
|
600
|
+
"message": "Offline — router will fall back to local models only",
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def _check_session_cost() -> dict:
|
|
605
|
+
from velune.telemetry.token_tracker import current_session
|
|
606
|
+
|
|
607
|
+
total_tokens = current_session.total_tokens
|
|
608
|
+
total_cost = current_session.total_cost
|
|
609
|
+
if total_tokens == 0:
|
|
610
|
+
return {
|
|
611
|
+
"name": "Session Cost Tracking",
|
|
612
|
+
"status": "ok",
|
|
613
|
+
"message": "No inference calls recorded in this session",
|
|
614
|
+
}
|
|
615
|
+
cost_str = f"~${total_cost:.4f}" if total_cost > 0 else "free (local models only)"
|
|
616
|
+
return {
|
|
617
|
+
"name": "Session Cost Tracking",
|
|
618
|
+
"status": "ok",
|
|
619
|
+
"message": f"{total_tokens:,} tokens used · {cost_str}",
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def _check_memory_health() -> dict:
|
|
624
|
+
import os
|
|
625
|
+
|
|
626
|
+
from velune.core.paths import cognitive_db_path, lancedb_store_path
|
|
627
|
+
|
|
628
|
+
workspace = Path.cwd()
|
|
629
|
+
|
|
630
|
+
# Check cognitive DB
|
|
631
|
+
db_path = cognitive_db_path(workspace)
|
|
632
|
+
db_status = "missing"
|
|
633
|
+
if db_path.exists():
|
|
634
|
+
db_size = db_path.stat().st_size / (1024 * 1024)
|
|
635
|
+
db_status = f"{db_size:.1f} MB"
|
|
636
|
+
|
|
637
|
+
# Check LanceDB store
|
|
638
|
+
lancedb_path = lancedb_store_path(workspace)
|
|
639
|
+
lancedb_status = "missing"
|
|
640
|
+
if lancedb_path.exists() and lancedb_path.is_dir():
|
|
641
|
+
total_size = sum(
|
|
642
|
+
os.path.getsize(os.path.join(root, f))
|
|
643
|
+
for root, _, files in os.walk(lancedb_path)
|
|
644
|
+
for f in files
|
|
645
|
+
)
|
|
646
|
+
lancedb_status = f"{total_size / (1024 * 1024):.1f} MB"
|
|
647
|
+
|
|
648
|
+
message = f"Cognitive DB: {db_status} · LanceDB: {lancedb_status}"
|
|
649
|
+
|
|
650
|
+
return {
|
|
651
|
+
"name": "Memory Subsystem",
|
|
652
|
+
"status": "ok",
|
|
653
|
+
"message": message,
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def _render_results(results: list) -> None:
|
|
658
|
+
from velune.cli import theme
|
|
659
|
+
|
|
660
|
+
table = Table(title="Velune Environment Diagnostics", show_header=True, expand=True)
|
|
661
|
+
table.add_column("Category", style=f"bold {theme.ACCENT}", width=15)
|
|
662
|
+
table.add_column("Check", style="bold white", width=30)
|
|
663
|
+
table.add_column("Status", width=12)
|
|
664
|
+
table.add_column("Details", style=theme.DIM)
|
|
665
|
+
|
|
666
|
+
status_styles = {
|
|
667
|
+
"ok": f"[{theme.SUCCESS}]✓ OK[/{theme.SUCCESS}]",
|
|
668
|
+
"warn": f"[{theme.WARNING}]⚠ WARN[/{theme.WARNING}]",
|
|
669
|
+
"fail": f"[{theme.ERROR}]✗ FAIL[/{theme.ERROR}]",
|
|
670
|
+
"error": f"[{theme.ERROR}]✗ ERROR[/{theme.ERROR}]",
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
categories_map = {
|
|
674
|
+
"Internet Connectivity": "Providers",
|
|
675
|
+
"Ollama Connectivity": "Providers",
|
|
676
|
+
"Ollama Model Availability": "Providers",
|
|
677
|
+
"LM Studio Connectivity": "Providers",
|
|
678
|
+
".velune Directory Writable": "Storage",
|
|
679
|
+
"SQLite DB Initializable": "Storage",
|
|
680
|
+
"Qdrant In-Process Initializable": "Storage",
|
|
681
|
+
"velune.toml Config File": "Storage",
|
|
682
|
+
"Memory Subsystem": "Storage",
|
|
683
|
+
"OpenAI API Key": "Security",
|
|
684
|
+
"Anthropic API Key": "Security",
|
|
685
|
+
"Groq": "Security",
|
|
686
|
+
"Google Gemini": "Security",
|
|
687
|
+
"Python Version": "Performance",
|
|
688
|
+
"Core Dependencies": "Performance",
|
|
689
|
+
"Git in PATH": "Performance",
|
|
690
|
+
"Tree-sitter Grammars": "Performance",
|
|
691
|
+
"GPU Detection": "Performance",
|
|
692
|
+
"Available VRAM": "Performance",
|
|
693
|
+
"Empirical Model Benchmarks": "Performance",
|
|
694
|
+
"Session Cost Tracking": "Performance",
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
categories = ["Providers", "Storage", "Security", "Performance"]
|
|
698
|
+
grouped = {cat: [] for cat in categories}
|
|
699
|
+
|
|
700
|
+
for r in results:
|
|
701
|
+
cat = categories_map.get(r["name"], "Performance")
|
|
702
|
+
grouped[cat].append(r)
|
|
703
|
+
|
|
704
|
+
for cat in categories:
|
|
705
|
+
cat_results = grouped[cat]
|
|
706
|
+
if not cat_results:
|
|
707
|
+
continue
|
|
708
|
+
|
|
709
|
+
first = True
|
|
710
|
+
for result in cat_results:
|
|
711
|
+
cat_cell = cat if first else ""
|
|
712
|
+
first = False
|
|
713
|
+
|
|
714
|
+
table.add_row(
|
|
715
|
+
cat_cell,
|
|
716
|
+
result["name"],
|
|
717
|
+
status_styles.get(result["status"], result["status"]),
|
|
718
|
+
result.get("message", ""),
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
console.print(table)
|