visor-mcp 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.
- visor/__init__.py +8 -0
- visor/__pycache__/__init__.cpython-310.pyc +0 -0
- visor/__pycache__/__init__.cpython-312.pyc +0 -0
- visor/__pycache__/cli.cpython-310.pyc +0 -0
- visor/__pycache__/server.cpython-310.pyc +0 -0
- visor/cli.py +287 -0
- visor/db/__init__.py +0 -0
- visor/db/__pycache__/__init__.cpython-310.pyc +0 -0
- visor/db/__pycache__/__init__.cpython-312.pyc +0 -0
- visor/db/__pycache__/client.cpython-310.pyc +0 -0
- visor/db/__pycache__/client.cpython-312.pyc +0 -0
- visor/db/__pycache__/embeddings.cpython-310.pyc +0 -0
- visor/db/__pycache__/migration.cpython-310.pyc +0 -0
- visor/db/client.py +526 -0
- visor/db/embeddings.py +27 -0
- visor/db/migration.py +291 -0
- visor/extension/.gitignore +4 -0
- visor/extension/.vscodeignore +10 -0
- visor/extension/LICENSE +21 -0
- visor/extension/assets/hero.png +0 -0
- visor/extension/assets/visor-icon.svg +4 -0
- visor/extension/build.log +35 -0
- visor/extension/esbuild.js +18 -0
- visor/extension/package-lock.json +1670 -0
- visor/extension/package.json +87 -0
- visor/extension/src/extension.ts +343 -0
- visor/extension/test_connect.mjs +97 -0
- visor/extension/test_mcp.mjs +32 -0
- visor/extension/tsconfig.json +14 -0
- visor/extension/visor-hud-0.10.0.vsix +0 -0
- visor/hud/.gitignore +24 -0
- visor/hud/README.md +73 -0
- visor/hud/dump_dom.mjs +16 -0
- visor/hud/e2e/drift.spec.ts +16 -0
- visor/hud/eslint.config.js +23 -0
- visor/hud/index.html +13 -0
- visor/hud/package-lock.json +4186 -0
- visor/hud/package.json +37 -0
- visor/hud/playwright-report/index.html +90 -0
- visor/hud/playwright.config.ts +30 -0
- visor/hud/public/favicon.svg +1 -0
- visor/hud/public/icons.svg +24 -0
- visor/hud/run_playwright.mjs +27 -0
- visor/hud/src/App.css +184 -0
- visor/hud/src/App.tsx +41 -0
- visor/hud/src/assets/hero.png +0 -0
- visor/hud/src/assets/react.svg +1 -0
- visor/hud/src/assets/vite.svg +1 -0
- visor/hud/src/components/ErrorBoundary.tsx +38 -0
- visor/hud/src/components/GraphCanvas.tsx +832 -0
- visor/hud/src/components/TelemetryHUD.tsx +359 -0
- visor/hud/src/index.css +160 -0
- visor/hud/src/main.tsx +10 -0
- visor/hud/test-results/.last-run.json +4 -0
- visor/hud/test_three.js +7 -0
- visor/hud/tsconfig.app.json +25 -0
- visor/hud/tsconfig.json +7 -0
- visor/hud/tsconfig.node.json +24 -0
- visor/hud/vite.config.ts +8 -0
- visor/parser/__init__.py +0 -0
- visor/parser/__pycache__/__init__.cpython-310.pyc +0 -0
- visor/parser/__pycache__/__init__.cpython-312.pyc +0 -0
- visor/parser/__pycache__/treesitter.cpython-310.pyc +0 -0
- visor/parser/__pycache__/watcher.cpython-310.pyc +0 -0
- visor/parser/__pycache__/watcher.cpython-312.pyc +0 -0
- visor/parser/treesitter.py +263 -0
- visor/parser/watcher.py +261 -0
- visor/server.py +151 -0
- visor/tools/__init__.py +0 -0
- visor/tools/__pycache__/__init__.cpython-310.pyc +0 -0
- visor/tools/__pycache__/context_engine.cpython-310.pyc +0 -0
- visor/tools/__pycache__/core.cpython-310.pyc +0 -0
- visor/tools/context_engine.py +342 -0
- visor/tools/core.py +504 -0
- visor_mcp-1.0.0.dist-info/METADATA +256 -0
- visor_mcp-1.0.0.dist-info/RECORD +78 -0
- visor_mcp-1.0.0.dist-info/WHEEL +4 -0
- visor_mcp-1.0.0.dist-info/entry_points.txt +4 -0
visor/__init__.py
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
visor/cli.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
V.I.S.O.R. CLI — Terminal interface for the Context Intelligence Engine
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Usage:
|
|
5
|
+
visor context "how is authentication handled"
|
|
6
|
+
visor fix "login crash on null user"
|
|
7
|
+
visor explain "database client module"
|
|
8
|
+
visor trace src/auth.py src/db/client.py
|
|
9
|
+
visor drift
|
|
10
|
+
"""
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import sys
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _init_workspace():
|
|
18
|
+
"""Ensure WORKSPACE_ROOT is set for the engine."""
|
|
19
|
+
if not os.environ.get("WORKSPACE_ROOT"):
|
|
20
|
+
os.environ["WORKSPACE_ROOT"] = os.getcwd()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _format_output(data: dict, human: bool = True) -> str:
|
|
24
|
+
"""Format output as human-readable table or raw JSON."""
|
|
25
|
+
if not human:
|
|
26
|
+
return json.dumps(data, indent=2)
|
|
27
|
+
|
|
28
|
+
lines = []
|
|
29
|
+
|
|
30
|
+
# Header: query + intent + skill
|
|
31
|
+
debug = data.get("debug", {})
|
|
32
|
+
lines.append(f"\n{'='*60}")
|
|
33
|
+
lines.append(" V.I.S.O.R. Context Intelligence Engine")
|
|
34
|
+
lines.append(f"{'='*60}")
|
|
35
|
+
lines.append(f" Query: {data.get('query', '?')}")
|
|
36
|
+
lines.append(f" Intent: {debug.get('intent', '?')}")
|
|
37
|
+
if debug.get("skill"):
|
|
38
|
+
lines.append(f" Skill: {debug['skill']}")
|
|
39
|
+
lines.append(f"{'─'*60}")
|
|
40
|
+
|
|
41
|
+
# Metrics
|
|
42
|
+
metrics = data.get("metrics", {})
|
|
43
|
+
if metrics:
|
|
44
|
+
without = metrics.get("estimated_tokens_without", 0)
|
|
45
|
+
with_t = metrics.get("estimated_tokens_with", 0)
|
|
46
|
+
reduction = metrics.get("reduction_percent", 0)
|
|
47
|
+
lines.append(f" Tokens without V.I.S.O.R.: {without:,}")
|
|
48
|
+
lines.append(f" Tokens with V.I.S.O.R.: {with_t:,}")
|
|
49
|
+
lines.append(f" Reduction: {reduction}%")
|
|
50
|
+
lines.append(f"{'─'*60}")
|
|
51
|
+
|
|
52
|
+
# Context nodes
|
|
53
|
+
context = data.get("context", [])
|
|
54
|
+
if context:
|
|
55
|
+
lines.append(f" Selected {len(context)} nodes (truncated={data.get('truncated', False)}):")
|
|
56
|
+
lines.append("")
|
|
57
|
+
for node in context:
|
|
58
|
+
nid = str(node.get("id", "?"))
|
|
59
|
+
score = node.get("relevance_score", 0)
|
|
60
|
+
lines.append(f" [{score:.4f}] {node.get('file_path', '?')}:{node.get('name', '?')}")
|
|
61
|
+
|
|
62
|
+
# Reasoning
|
|
63
|
+
reasoning = debug.get("reasoning", {}).get(nid, [])
|
|
64
|
+
for r in reasoning:
|
|
65
|
+
lines.append(f" → {r}")
|
|
66
|
+
lines.append("")
|
|
67
|
+
else:
|
|
68
|
+
lines.append(" No relevant context found.")
|
|
69
|
+
lines.append(" → V.I.S.O.R. needs to index your workspace first.")
|
|
70
|
+
lines.append(" → Run the MCP server or use the IDE extension to trigger indexing.")
|
|
71
|
+
|
|
72
|
+
# Recommended next tools
|
|
73
|
+
rec = data.get("recommended_next", [])
|
|
74
|
+
if rec:
|
|
75
|
+
lines.append(f" Recommended next: {', '.join(rec)}")
|
|
76
|
+
|
|
77
|
+
lines.append(f"{'='*60}\n")
|
|
78
|
+
return "\n".join(lines)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def cmd_context(args):
|
|
82
|
+
"""Run build_context with optional skill."""
|
|
83
|
+
_init_workspace()
|
|
84
|
+
from visor.tools.context_engine import build_context
|
|
85
|
+
result = build_context(args.query, skill_name=args.skill)
|
|
86
|
+
print(_format_output(result, human=not args.json))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def cmd_fix(args):
|
|
90
|
+
"""Shortcut: build_context with bug-fixer skill."""
|
|
91
|
+
_init_workspace()
|
|
92
|
+
from visor.tools.context_engine import build_context
|
|
93
|
+
result = build_context(args.query, skill_name="bug-fixer")
|
|
94
|
+
print(_format_output(result, human=not args.json))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def cmd_explain(args):
|
|
98
|
+
"""Shortcut: build_context with architecture-explainer skill."""
|
|
99
|
+
_init_workspace()
|
|
100
|
+
from visor.tools.context_engine import build_context
|
|
101
|
+
result = build_context(args.query, skill_name="architecture-explainer")
|
|
102
|
+
print(_format_output(result, human=not args.json))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def cmd_trace(args):
|
|
106
|
+
"""Trace architectural path between two files."""
|
|
107
|
+
_init_workspace()
|
|
108
|
+
import networkx as nx
|
|
109
|
+
from visor.db.client import db_client
|
|
110
|
+
|
|
111
|
+
db_client.conn.commit()
|
|
112
|
+
cursor = db_client.conn.cursor()
|
|
113
|
+
cursor.execute("SELECT from_node, to_node FROM edges")
|
|
114
|
+
|
|
115
|
+
G = nx.DiGraph()
|
|
116
|
+
for from_n, to_n in cursor.fetchall():
|
|
117
|
+
G.add_edge(from_n, to_n)
|
|
118
|
+
|
|
119
|
+
if not G.has_node(args.source) or not G.has_node(args.target):
|
|
120
|
+
print(json.dumps({"error": "Source or target node not found in graph."}))
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
path = nx.shortest_path(G, source=args.source, target=args.target)
|
|
125
|
+
result = {"source": args.source, "target": args.target, "path": path, "hops": len(path) - 1}
|
|
126
|
+
except nx.NetworkXNoPath:
|
|
127
|
+
result = {"error": "No path found between these nodes."}
|
|
128
|
+
|
|
129
|
+
print(json.dumps(result, indent=2))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def cmd_drift(args):
|
|
133
|
+
"""Check drift status for all tracked files."""
|
|
134
|
+
_init_workspace()
|
|
135
|
+
from visor.db.client import db_client
|
|
136
|
+
|
|
137
|
+
db_client.conn.commit()
|
|
138
|
+
cursor = db_client.conn.cursor()
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
cursor.execute("SELECT file_path, changed_at FROM file_changelog ORDER BY changed_at DESC LIMIT 20")
|
|
142
|
+
rows = cursor.fetchall()
|
|
143
|
+
except Exception:
|
|
144
|
+
rows = []
|
|
145
|
+
|
|
146
|
+
if not rows:
|
|
147
|
+
print("No file changes tracked yet. The file watcher may not have started.")
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
print(f"\n{'='*60}")
|
|
151
|
+
print(" V.I.S.O.R. Drift Monitor — Recent Changes")
|
|
152
|
+
print(f"{'='*60}")
|
|
153
|
+
for path, changed_at in rows:
|
|
154
|
+
print(f" {changed_at} {path}")
|
|
155
|
+
print(f"{'='*60}\n")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def cmd_init(args):
|
|
159
|
+
"""Auto-detect IDE and generate MCP config for V.I.S.O.R."""
|
|
160
|
+
import shutil
|
|
161
|
+
|
|
162
|
+
visor_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
163
|
+
|
|
164
|
+
# Try to find the visor executable/package path
|
|
165
|
+
visor_bin = shutil.which("visor-mcp")
|
|
166
|
+
|
|
167
|
+
mcp_entry = {
|
|
168
|
+
"command": "uv",
|
|
169
|
+
"args": [
|
|
170
|
+
"--directory", visor_path,
|
|
171
|
+
"run", "-q", os.path.join(visor_path, "src", "visor", "server.py"),
|
|
172
|
+
],
|
|
173
|
+
"env": {}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# If installed via pip, use the entry point directly
|
|
177
|
+
if visor_bin:
|
|
178
|
+
mcp_entry = {
|
|
179
|
+
"command": "visor-mcp",
|
|
180
|
+
"args": [],
|
|
181
|
+
"env": {}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# Detect IDE configs
|
|
185
|
+
ide_configs = [
|
|
186
|
+
("Antigravity", os.path.expanduser("~/.gemini/antigravity/mcp_config.json")),
|
|
187
|
+
("Cursor", os.path.expanduser("~/.cursor/mcp.json")),
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
config_written = False
|
|
191
|
+
for ide_name, config_path in ide_configs:
|
|
192
|
+
if os.path.exists(os.path.dirname(config_path)):
|
|
193
|
+
# Read existing config or create new
|
|
194
|
+
if os.path.exists(config_path):
|
|
195
|
+
with open(config_path, "r") as f:
|
|
196
|
+
try:
|
|
197
|
+
config = json.load(f)
|
|
198
|
+
except json.JSONDecodeError:
|
|
199
|
+
config = {}
|
|
200
|
+
else:
|
|
201
|
+
config = {}
|
|
202
|
+
|
|
203
|
+
if "mcpServers" not in config:
|
|
204
|
+
config["mcpServers"] = {}
|
|
205
|
+
|
|
206
|
+
if "visor" in config["mcpServers"]:
|
|
207
|
+
print(f" ✓ {ide_name}: V.I.S.O.R. already configured at {config_path}")
|
|
208
|
+
config_written = True
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
config["mcpServers"]["visor"] = mcp_entry
|
|
212
|
+
|
|
213
|
+
with open(config_path, "w") as f:
|
|
214
|
+
json.dump(config, f, indent=2)
|
|
215
|
+
|
|
216
|
+
print(f" ✓ {ide_name}: Config written to {config_path}")
|
|
217
|
+
config_written = True
|
|
218
|
+
|
|
219
|
+
if not config_written:
|
|
220
|
+
# Fallback: print the config for manual setup
|
|
221
|
+
print(" No supported IDE detected. Add this to your MCP config:\n")
|
|
222
|
+
print(json.dumps({"visor": mcp_entry}, indent=2))
|
|
223
|
+
print()
|
|
224
|
+
|
|
225
|
+
print(f"\n{'='*60}")
|
|
226
|
+
print(" V.I.S.O.R. is ready!")
|
|
227
|
+
print(f"{'='*60}")
|
|
228
|
+
print(" Next steps:")
|
|
229
|
+
print(" 1. Restart your IDE / AI session")
|
|
230
|
+
print(" 2. Your AI agent now has access to 16 V.I.S.O.R. tools")
|
|
231
|
+
print(" 3. Try: ask your agent to 'use build_context to find auth code'")
|
|
232
|
+
print(f"{'='*60}\n")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def main():
|
|
236
|
+
parser = argparse.ArgumentParser(
|
|
237
|
+
prog="visor",
|
|
238
|
+
description="V.I.S.O.R. — Context Intelligence Engine CLI",
|
|
239
|
+
)
|
|
240
|
+
from visor import __version__
|
|
241
|
+
parser.add_argument("--version", "-v", action="version", version=f"V.I.S.O.R. {__version__}")
|
|
242
|
+
sub = parser.add_subparsers(dest="command", help="Available commands")
|
|
243
|
+
|
|
244
|
+
# visor init
|
|
245
|
+
p_init = sub.add_parser("init", help="Auto-configure V.I.S.O.R. for your IDE")
|
|
246
|
+
p_init.set_defaults(func=cmd_init)
|
|
247
|
+
|
|
248
|
+
# visor context
|
|
249
|
+
p_ctx = sub.add_parser("context", help="Build ranked context for a query")
|
|
250
|
+
p_ctx.add_argument("query", help="Natural language query")
|
|
251
|
+
p_ctx.add_argument("--skill", "-s", default=None, help="Apply a skill strategy")
|
|
252
|
+
p_ctx.add_argument("--json", "-j", action="store_true", help="Output raw JSON")
|
|
253
|
+
p_ctx.set_defaults(func=cmd_context)
|
|
254
|
+
|
|
255
|
+
# visor fix
|
|
256
|
+
p_fix = sub.add_parser("fix", help="Find context for a bug (uses bug-fixer skill)")
|
|
257
|
+
p_fix.add_argument("query", help="Bug description")
|
|
258
|
+
p_fix.add_argument("--json", "-j", action="store_true", help="Output raw JSON")
|
|
259
|
+
p_fix.set_defaults(func=cmd_fix)
|
|
260
|
+
|
|
261
|
+
# visor explain
|
|
262
|
+
p_exp = sub.add_parser("explain", help="Explain a module (uses architecture-explainer skill)")
|
|
263
|
+
p_exp.add_argument("query", help="What to explain")
|
|
264
|
+
p_exp.add_argument("--json", "-j", action="store_true", help="Output raw JSON")
|
|
265
|
+
p_exp.set_defaults(func=cmd_explain)
|
|
266
|
+
|
|
267
|
+
# visor trace
|
|
268
|
+
p_trace = sub.add_parser("trace", help="Trace path between two files")
|
|
269
|
+
p_trace.add_argument("source", help="Source file path")
|
|
270
|
+
p_trace.add_argument("target", help="Target file path")
|
|
271
|
+
p_trace.set_defaults(func=cmd_trace)
|
|
272
|
+
|
|
273
|
+
# visor drift
|
|
274
|
+
p_drift = sub.add_parser("drift", help="Show recent file changes (drift detection)")
|
|
275
|
+
p_drift.set_defaults(func=cmd_drift)
|
|
276
|
+
|
|
277
|
+
args = parser.parse_args()
|
|
278
|
+
if not args.command:
|
|
279
|
+
parser.print_help()
|
|
280
|
+
sys.exit(1)
|
|
281
|
+
|
|
282
|
+
args.func(args)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
if __name__ == "__main__":
|
|
286
|
+
main()
|
|
287
|
+
|
visor/db/__init__.py
ADDED
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|