aicodestat 0.0.1__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.
- aicodestat-0.0.1.dist-info/METADATA +110 -0
- aicodestat-0.0.1.dist-info/RECORD +34 -0
- aicodestat-0.0.1.dist-info/WHEEL +5 -0
- aicodestat-0.0.1.dist-info/entry_points.txt +5 -0
- aicodestat-0.0.1.dist-info/top_level.txt +10 -0
- cli/__init__.py +2 -0
- cli/exporter.py +111 -0
- cli/main.py +213 -0
- cli/menus.py +540 -0
- cli/views.py +277 -0
- compute/__init__.py +2 -0
- compute/cache.py +90 -0
- compute/diff_engine.py +69 -0
- compute/lcs_engine.py +73 -0
- compute/metrics_service.py +362 -0
- config.py +120 -0
- local_mcp_server.py +260 -0
- logging_config.py +68 -0
- main.py +164 -0
- mcp/__init__.py +2 -0
- mcp/agent_adapter.py +69 -0
- mcp/api_schemas.py +26 -0
- mcp/routes_after.py +121 -0
- mcp/routes_before.py +68 -0
- mcp/routes_tools.py +100 -0
- service_manager.py +221 -0
- storage/__init__.py +2 -0
- storage/backup.py +185 -0
- storage/db.py +156 -0
- storage/models.py +338 -0
- storage/scheduler.py +111 -0
- utils/__init__.py +2 -0
- utils/port_utils.py +59 -0
- utils/time_utils.py +37 -0
cli/menus.py
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"""Interactive main menu and sub-menus based on questionary"""
|
|
2
|
+
import logging
|
|
3
|
+
import questionary
|
|
4
|
+
from typing import Optional, List, Dict, Any
|
|
5
|
+
from compute.metrics_service import (
|
|
6
|
+
calculate_session_metrics,
|
|
7
|
+
calculate_file_metrics,
|
|
8
|
+
calculate_project_metrics,
|
|
9
|
+
calculate_global_metrics,
|
|
10
|
+
)
|
|
11
|
+
from storage.models import get_session_summaries
|
|
12
|
+
from cli.views import (
|
|
13
|
+
display_metrics_table,
|
|
14
|
+
display_session_info,
|
|
15
|
+
display_diff_lines_table,
|
|
16
|
+
display_agent_comparison,
|
|
17
|
+
display_global_dashboard,
|
|
18
|
+
)
|
|
19
|
+
from cli.exporter import export_metrics
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _arrow_menu(title: str, choices: List[Dict[str, Any]]) -> Optional[str]:
|
|
25
|
+
"""
|
|
26
|
+
Simple arrow-key menu implemented with prompt_toolkit.
|
|
27
|
+
Each choice is a dict: {"label": str, "value": str}.
|
|
28
|
+
Returns selected value or None if cancelled / unsupported.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
from prompt_toolkit import Application
|
|
32
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
33
|
+
from prompt_toolkit.layout import Layout
|
|
34
|
+
from prompt_toolkit.layout.containers import HSplit, Window
|
|
35
|
+
from prompt_toolkit.layout.controls import FormattedTextControl
|
|
36
|
+
from prompt_toolkit.styles import Style
|
|
37
|
+
except ImportError:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
current_index = {"value": 0}
|
|
41
|
+
|
|
42
|
+
def get_menu_text():
|
|
43
|
+
fragments = [("class:title", title + "\n\n")]
|
|
44
|
+
for idx, item in enumerate(choices):
|
|
45
|
+
is_current = idx == current_index["value"]
|
|
46
|
+
prefix = "➤ " if is_current else " "
|
|
47
|
+
style = "class:selected" if is_current else "class:item"
|
|
48
|
+
fragments.append((style, f"{prefix}{item['label']}\n"))
|
|
49
|
+
return fragments
|
|
50
|
+
|
|
51
|
+
text_control = FormattedTextControl(get_menu_text)
|
|
52
|
+
root_container = HSplit([Window(content=text_control, dont_extend_height=True)])
|
|
53
|
+
kb = KeyBindings()
|
|
54
|
+
|
|
55
|
+
@kb.add("up")
|
|
56
|
+
@kb.add("k")
|
|
57
|
+
def _up(event):
|
|
58
|
+
if current_index["value"] > 0:
|
|
59
|
+
current_index["value"] -= 1
|
|
60
|
+
|
|
61
|
+
@kb.add("down")
|
|
62
|
+
@kb.add("j")
|
|
63
|
+
def _down(event):
|
|
64
|
+
if current_index["value"] < len(choices) - 1:
|
|
65
|
+
current_index["value"] += 1
|
|
66
|
+
|
|
67
|
+
@kb.add("enter")
|
|
68
|
+
def _enter(event):
|
|
69
|
+
value = choices[current_index["value"]]["value"]
|
|
70
|
+
event.app.exit(result=value)
|
|
71
|
+
|
|
72
|
+
@kb.add("c-c")
|
|
73
|
+
@kb.add("q")
|
|
74
|
+
def _cancel(event):
|
|
75
|
+
event.app.exit(result=None)
|
|
76
|
+
|
|
77
|
+
style = Style.from_dict(
|
|
78
|
+
{
|
|
79
|
+
"title": "bold cyan",
|
|
80
|
+
"item": "",
|
|
81
|
+
"selected": "reverse bold",
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
app = Application(
|
|
86
|
+
layout=Layout(root_container),
|
|
87
|
+
key_bindings=kb,
|
|
88
|
+
style=style,
|
|
89
|
+
full_screen=False,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
return app.run()
|
|
94
|
+
except Exception:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def show_main_menu() -> str:
|
|
99
|
+
"""Show main menu; prefer custom arrow-key navigation, fallback to numeric input."""
|
|
100
|
+
from service_manager import get_service_manager
|
|
101
|
+
from cli.views import console
|
|
102
|
+
|
|
103
|
+
manager = get_service_manager()
|
|
104
|
+
from config import get_server_config
|
|
105
|
+
|
|
106
|
+
console.print()
|
|
107
|
+
# Title line
|
|
108
|
+
console.print("[bold cyan]CodeStat - AI Code Metrics[/bold cyan]")
|
|
109
|
+
|
|
110
|
+
# MCP server status (small text)
|
|
111
|
+
server_config = get_server_config()
|
|
112
|
+
if manager.is_running():
|
|
113
|
+
status = manager.get_status()
|
|
114
|
+
console.print(
|
|
115
|
+
f"[dim]MCP Server[/dim] [green]● ONLINE[/green] "
|
|
116
|
+
f"[dim]at[/dim] [cyan]http://{status['host']}:{status['port']}[/cyan]"
|
|
117
|
+
)
|
|
118
|
+
else:
|
|
119
|
+
console.print(
|
|
120
|
+
f"[dim]MCP Server[/dim] [red]● OFFLINE[/red] "
|
|
121
|
+
f"[dim]at[/dim] http://{server_config['host']}:{server_config['port']}"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Repo / author info (even smaller / dimmer)
|
|
125
|
+
console.print(
|
|
126
|
+
"[grey50]Repo: https://github.com/2hangchen/CodeStat Author: 2hangchen[/grey50]"
|
|
127
|
+
)
|
|
128
|
+
console.print("\n[dim]Use ↑/↓ to move, Enter to confirm:[/dim]\n")
|
|
129
|
+
|
|
130
|
+
# Preferred: custom arrow-key navigation via prompt_toolkit
|
|
131
|
+
menu_items: List[Dict[str, Any]] = [
|
|
132
|
+
{"label": "📈 Global Dashboard (All Data)", "value": "overview"},
|
|
133
|
+
{"label": "🔧 MCP Service Management", "value": "service"},
|
|
134
|
+
{"label": "📄 Query Metrics by File", "value": "file"},
|
|
135
|
+
{"label": "📋 Query Metrics by Session", "value": "session"},
|
|
136
|
+
{"label": "📊 Query Metrics by Project", "value": "project"},
|
|
137
|
+
{"label": "🆚 Compare Agents", "value": "compare"},
|
|
138
|
+
{"label": "📤 Export Data", "value": "export"},
|
|
139
|
+
{"label": "🧹 Data Management (Cleanup/Backup)", "value": "manage"},
|
|
140
|
+
{"label": "❌ Exit", "value": "exit"},
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
selected = _arrow_menu("Select operation:", menu_items)
|
|
144
|
+
if selected:
|
|
145
|
+
return selected
|
|
146
|
+
|
|
147
|
+
# Fallback: numeric input menu when arrow menu is not available
|
|
148
|
+
console.print("[yellow]⚠ Arrow-key menu not fully supported, fallback to numeric menu.[/yellow]")
|
|
149
|
+
console.print(" [bold magenta]1[/bold magenta] 📈 Global Dashboard (All Data)")
|
|
150
|
+
console.print(" [bold magenta]2[/bold magenta] 🔧 MCP Service Management")
|
|
151
|
+
console.print(" [bold magenta]3[/bold magenta] 📄 Query Metrics by File")
|
|
152
|
+
console.print(" [bold magenta]4[/bold magenta] 📋 Query Metrics by Session")
|
|
153
|
+
console.print(" [bold magenta]5[/bold magenta] 📊 Query Metrics by Project")
|
|
154
|
+
console.print(" [bold magenta]6[/bold magenta] 🆚 Compare Agents")
|
|
155
|
+
console.print(" [bold magenta]7[/bold magenta] 📤 Export Data")
|
|
156
|
+
console.print(" [bold magenta]8[/bold magenta] 🧹 Data Management (Cleanup/Backup)")
|
|
157
|
+
console.print(" [bold magenta]0[/bold magenta] ❌ Exit")
|
|
158
|
+
|
|
159
|
+
mapping = {
|
|
160
|
+
"1": "overview",
|
|
161
|
+
"2": "service",
|
|
162
|
+
"3": "file",
|
|
163
|
+
"4": "session",
|
|
164
|
+
"5": "project",
|
|
165
|
+
"6": "compare",
|
|
166
|
+
"7": "export",
|
|
167
|
+
"8": "manage",
|
|
168
|
+
"0": "exit",
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
raw = input("> ").strip()
|
|
173
|
+
except EOFError:
|
|
174
|
+
return "exit"
|
|
175
|
+
return mapping.get(raw, "exit")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def query_by_session():
|
|
179
|
+
"""Query metrics by session"""
|
|
180
|
+
session_id = questionary.text(
|
|
181
|
+
"Enter session ID (or 'all' to view all sessions):"
|
|
182
|
+
).ask()
|
|
183
|
+
|
|
184
|
+
if not session_id:
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
if session_id.lower() == "all":
|
|
188
|
+
# Show all session list
|
|
189
|
+
summaries = get_session_summaries()
|
|
190
|
+
if not summaries:
|
|
191
|
+
print("No session data available")
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
session_ids = list(set(s["session_id"] for s in summaries))
|
|
195
|
+
selected = questionary.select(
|
|
196
|
+
"Please select a session:",
|
|
197
|
+
choices=session_ids
|
|
198
|
+
).ask()
|
|
199
|
+
|
|
200
|
+
if selected:
|
|
201
|
+
session_id = selected
|
|
202
|
+
else:
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
print(f"\nQuerying session: {session_id}")
|
|
206
|
+
print("Calculating metrics (LCS comparison in progress...)")
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
metrics = calculate_session_metrics(session_id)
|
|
210
|
+
|
|
211
|
+
# Display session info
|
|
212
|
+
if metrics.get("summaries"):
|
|
213
|
+
display_session_info(metrics["summaries"])
|
|
214
|
+
|
|
215
|
+
# Display metrics table
|
|
216
|
+
display_metrics_table(metrics, "Session Metrics (Precise LCS Calculation)")
|
|
217
|
+
|
|
218
|
+
# Display diff lines details
|
|
219
|
+
diff_lines = metrics.get("diff_lines", [])
|
|
220
|
+
if diff_lines:
|
|
221
|
+
show_details = questionary.confirm("Show diff lines details?").ask()
|
|
222
|
+
if show_details:
|
|
223
|
+
display_diff_lines_table(diff_lines)
|
|
224
|
+
|
|
225
|
+
# Ask if export
|
|
226
|
+
export_choice = questionary.confirm("Export this session's metrics?").ask()
|
|
227
|
+
if export_choice:
|
|
228
|
+
format_choice = questionary.select(
|
|
229
|
+
"Select export format:",
|
|
230
|
+
choices=["JSON", "CSV"]
|
|
231
|
+
).ask()
|
|
232
|
+
|
|
233
|
+
if format_choice:
|
|
234
|
+
output_path = questionary.text(
|
|
235
|
+
f"Enter export file path (default: session_{session_id}.{format_choice.lower()}):"
|
|
236
|
+
).ask()
|
|
237
|
+
|
|
238
|
+
if not output_path:
|
|
239
|
+
output_path = f"session_{session_id}.{format_choice.lower()}"
|
|
240
|
+
|
|
241
|
+
export_metrics(metrics, output_path, format_choice.lower())
|
|
242
|
+
print(f"✅ Data exported to: {output_path}")
|
|
243
|
+
|
|
244
|
+
except Exception as e:
|
|
245
|
+
print(f"❌ Query failed: {e}")
|
|
246
|
+
logger.error(f"Query by session failed: {e}", exc_info=True)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def query_by_file():
|
|
250
|
+
"""Query metrics by file"""
|
|
251
|
+
file_path = questionary.text("Enter file path:").ask()
|
|
252
|
+
|
|
253
|
+
if not file_path:
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
print(f"\nQuerying file: {file_path}")
|
|
257
|
+
print("Calculating metrics (LCS comparison in progress...)")
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
metrics = calculate_file_metrics(file_path)
|
|
261
|
+
|
|
262
|
+
# Display metrics table
|
|
263
|
+
display_metrics_table(metrics, "File Metrics")
|
|
264
|
+
|
|
265
|
+
# Display diff lines details
|
|
266
|
+
diff_lines = metrics.get("diff_lines", [])
|
|
267
|
+
if diff_lines:
|
|
268
|
+
show_details = questionary.confirm("Show diff lines details?").ask()
|
|
269
|
+
if show_details:
|
|
270
|
+
display_diff_lines_table(diff_lines)
|
|
271
|
+
|
|
272
|
+
# Ask if export
|
|
273
|
+
export_choice = questionary.confirm("Export this file's metrics?").ask()
|
|
274
|
+
if export_choice:
|
|
275
|
+
format_choice = questionary.select(
|
|
276
|
+
"Select export format:",
|
|
277
|
+
choices=["JSON", "CSV"]
|
|
278
|
+
).ask()
|
|
279
|
+
|
|
280
|
+
if format_choice:
|
|
281
|
+
import os
|
|
282
|
+
safe_filename = os.path.basename(file_path).replace(".", "_")
|
|
283
|
+
output_path = questionary.text(
|
|
284
|
+
f"Enter export file path (default: file_{safe_filename}.{format_choice.lower()}):"
|
|
285
|
+
).ask()
|
|
286
|
+
|
|
287
|
+
if not output_path:
|
|
288
|
+
output_path = f"file_{safe_filename}.{format_choice.lower()}"
|
|
289
|
+
|
|
290
|
+
export_metrics(metrics, output_path, format_choice.lower())
|
|
291
|
+
print(f"✅ Data exported to: {output_path}")
|
|
292
|
+
|
|
293
|
+
except Exception as e:
|
|
294
|
+
print(f"❌ Query failed: {e}")
|
|
295
|
+
logger.error(f"Query by file failed: {e}", exc_info=True)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def query_by_project():
|
|
299
|
+
"""Query metrics by project"""
|
|
300
|
+
project_root = questionary.text("Enter project root directory path:").ask()
|
|
301
|
+
|
|
302
|
+
if not project_root:
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
print(f"\nQuerying project: {project_root}")
|
|
306
|
+
print("Calculating metrics (LCS comparison in progress...)")
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
metrics = calculate_project_metrics(project_root)
|
|
310
|
+
|
|
311
|
+
# Display metrics table
|
|
312
|
+
display_metrics_table(metrics, "Project Metrics")
|
|
313
|
+
|
|
314
|
+
# Ask if export
|
|
315
|
+
export_choice = questionary.confirm("Export this project's metrics?").ask()
|
|
316
|
+
if export_choice:
|
|
317
|
+
format_choice = questionary.select(
|
|
318
|
+
"Select export format:",
|
|
319
|
+
choices=["JSON", "CSV"]
|
|
320
|
+
).ask()
|
|
321
|
+
|
|
322
|
+
if format_choice:
|
|
323
|
+
import os
|
|
324
|
+
safe_dirname = os.path.basename(project_root).replace(".", "_")
|
|
325
|
+
output_path = questionary.text(
|
|
326
|
+
f"Enter export file path (default: project_{safe_dirname}.{format_choice.lower()}):"
|
|
327
|
+
).ask()
|
|
328
|
+
|
|
329
|
+
if not output_path:
|
|
330
|
+
output_path = f"project_{safe_dirname}.{format_choice.lower()}"
|
|
331
|
+
|
|
332
|
+
export_metrics(metrics, output_path, format_choice.lower())
|
|
333
|
+
print(f"✅ Data exported to: {output_path}")
|
|
334
|
+
|
|
335
|
+
except Exception as e:
|
|
336
|
+
print(f"❌ Query failed: {e}")
|
|
337
|
+
logger.error(f"Query by project failed: {e}", exc_info=True)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def compare_agents():
|
|
341
|
+
"""Compare metrics across agents"""
|
|
342
|
+
summaries = get_session_summaries()
|
|
343
|
+
if not summaries:
|
|
344
|
+
print("No session data available")
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
# Get all session IDs
|
|
348
|
+
session_ids = list(set(s["session_id"] for s in summaries))
|
|
349
|
+
|
|
350
|
+
if len(session_ids) < 2:
|
|
351
|
+
print("At least 2 sessions are required for comparison")
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
# Let user select sessions to compare
|
|
355
|
+
selected = questionary.checkbox(
|
|
356
|
+
"Select sessions to compare (at least 2):",
|
|
357
|
+
choices=session_ids
|
|
358
|
+
).ask()
|
|
359
|
+
|
|
360
|
+
if not selected or len(selected) < 2:
|
|
361
|
+
return
|
|
362
|
+
|
|
363
|
+
print("\nCalculating metrics (LCS comparison in progress...)")
|
|
364
|
+
|
|
365
|
+
try:
|
|
366
|
+
metrics_list = []
|
|
367
|
+
for session_id in selected:
|
|
368
|
+
metrics = calculate_session_metrics(session_id)
|
|
369
|
+
metrics_list.append({
|
|
370
|
+
"session_id": session_id,
|
|
371
|
+
"metrics": metrics
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
# Display comparison table
|
|
375
|
+
display_agent_comparison(metrics_list)
|
|
376
|
+
|
|
377
|
+
except Exception as e:
|
|
378
|
+
print(f"❌ Comparison failed: {e}")
|
|
379
|
+
logger.error(f"Compare agents failed: {e}", exc_info=True)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def show_global_dashboard():
|
|
383
|
+
"""Show global dashboard for all local data."""
|
|
384
|
+
try:
|
|
385
|
+
metrics = calculate_global_metrics()
|
|
386
|
+
display_global_dashboard(metrics)
|
|
387
|
+
except Exception as e:
|
|
388
|
+
print(f"❌ Failed to load global dashboard: {e}")
|
|
389
|
+
logger.error(f"Global dashboard failed: {e}", exc_info=True)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def manage_service():
|
|
393
|
+
"""MCP service management menu (uses custom arrow-key menu)."""
|
|
394
|
+
from service_manager import get_service_manager
|
|
395
|
+
from rich.console import Console
|
|
396
|
+
|
|
397
|
+
console = Console()
|
|
398
|
+
manager = get_service_manager()
|
|
399
|
+
|
|
400
|
+
menu_items: List[Dict[str, Any]] = [
|
|
401
|
+
{"label": "▶️ Start MCP Service", "value": "start"},
|
|
402
|
+
{"label": "⏹️ Stop MCP Service", "value": "stop"},
|
|
403
|
+
{"label": "🔄 Restart MCP Service", "value": "restart"},
|
|
404
|
+
{"label": "📊 View Service Status", "value": "status"},
|
|
405
|
+
{"label": "🔙 Back to Main Menu", "value": "back"},
|
|
406
|
+
]
|
|
407
|
+
|
|
408
|
+
choice = _arrow_menu("Please select service management operation:", menu_items)
|
|
409
|
+
if not choice:
|
|
410
|
+
# Fallback to simple text input if arrow menu not available
|
|
411
|
+
console.print("[yellow]⚠ Arrow-key menu not fully supported, fallback to numeric menu.[/yellow]")
|
|
412
|
+
console.print(" [bold magenta]1[/bold magenta] ▶️ Start MCP Service")
|
|
413
|
+
console.print(" [bold magenta]2[/bold magenta] ⏹️ Stop MCP Service")
|
|
414
|
+
console.print(" [bold magenta]3[/bold magenta] 🔄 Restart MCP Service")
|
|
415
|
+
console.print(" [bold magenta]4[/bold magenta] 📊 View Service Status")
|
|
416
|
+
console.print(" [bold magenta]0[/bold magenta] 🔙 Back to Main Menu")
|
|
417
|
+
|
|
418
|
+
mapping = {
|
|
419
|
+
"1": "start",
|
|
420
|
+
"2": "stop",
|
|
421
|
+
"3": "restart",
|
|
422
|
+
"4": "status",
|
|
423
|
+
"0": "back",
|
|
424
|
+
}
|
|
425
|
+
try:
|
|
426
|
+
raw = input("> ").strip()
|
|
427
|
+
except EOFError:
|
|
428
|
+
return
|
|
429
|
+
choice = mapping.get(raw, "back")
|
|
430
|
+
|
|
431
|
+
if choice == "start":
|
|
432
|
+
if manager.is_running():
|
|
433
|
+
console.print("[yellow]⚠️ Service is already running[/yellow]")
|
|
434
|
+
else:
|
|
435
|
+
console.print("[cyan]Starting MCP service...[/cyan]")
|
|
436
|
+
if manager.start(background=True):
|
|
437
|
+
console.print("[green]✅ MCP service started successfully[/green]")
|
|
438
|
+
else:
|
|
439
|
+
console.print("[red]❌ Failed to start MCP service[/red]")
|
|
440
|
+
|
|
441
|
+
elif choice == "stop":
|
|
442
|
+
if not manager.is_running():
|
|
443
|
+
console.print("[yellow]⚠️ Service is not running[/yellow]")
|
|
444
|
+
else:
|
|
445
|
+
console.print("[cyan]Stopping MCP service...[/cyan]")
|
|
446
|
+
if manager.stop():
|
|
447
|
+
console.print("[green]✅ MCP service stopped[/green]")
|
|
448
|
+
else:
|
|
449
|
+
console.print("[red]❌ Failed to stop MCP service[/red]")
|
|
450
|
+
|
|
451
|
+
elif choice == "restart":
|
|
452
|
+
console.print("[cyan]Restarting MCP service...[/cyan]")
|
|
453
|
+
if manager.restart():
|
|
454
|
+
console.print("[green]✅ MCP service restarted successfully[/green]")
|
|
455
|
+
else:
|
|
456
|
+
console.print("[red]❌ Failed to restart MCP service[/red]")
|
|
457
|
+
|
|
458
|
+
elif choice == "status":
|
|
459
|
+
status = manager.get_status()
|
|
460
|
+
from cli.views import display_service_status
|
|
461
|
+
display_service_status(status)
|
|
462
|
+
|
|
463
|
+
elif choice == "back":
|
|
464
|
+
return
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def manage_data():
|
|
468
|
+
"""Data management menu"""
|
|
469
|
+
from storage.backup import backup_database, get_backup_records, restore_database
|
|
470
|
+
from storage.models import delete_sessions
|
|
471
|
+
from utils.time_utils import get_current_time, get_time_range_days
|
|
472
|
+
from config import get_database_config
|
|
473
|
+
|
|
474
|
+
choice = questionary.select(
|
|
475
|
+
"Please select data management operation:",
|
|
476
|
+
choices=[
|
|
477
|
+
questionary.Choice("📦 Backup Data", "backup"),
|
|
478
|
+
questionary.Choice("📥 Restore Data", "restore"),
|
|
479
|
+
questionary.Choice("📋 View Backup Records", "list_backups"),
|
|
480
|
+
questionary.Choice("🧹 Clean Expired Data", "clean"),
|
|
481
|
+
questionary.Choice("🔙 Back to Main Menu", "back"),
|
|
482
|
+
]
|
|
483
|
+
).ask()
|
|
484
|
+
|
|
485
|
+
if choice == "backup":
|
|
486
|
+
output_path = questionary.text("Enter backup file path (leave empty for default path):").ask()
|
|
487
|
+
backup_path = backup_database(output_path if output_path else None)
|
|
488
|
+
if backup_path:
|
|
489
|
+
print(f"✅ Backup successful: {backup_path}")
|
|
490
|
+
else:
|
|
491
|
+
print("❌ Backup failed")
|
|
492
|
+
|
|
493
|
+
elif choice == "restore":
|
|
494
|
+
backup_path = questionary.text("Enter backup file path:").ask()
|
|
495
|
+
if backup_path:
|
|
496
|
+
confirm = questionary.confirm("Restore will overwrite existing data. Continue?").ask()
|
|
497
|
+
if confirm:
|
|
498
|
+
if restore_database(backup_path):
|
|
499
|
+
print("✅ Restore successful")
|
|
500
|
+
else:
|
|
501
|
+
print("❌ Restore failed")
|
|
502
|
+
|
|
503
|
+
elif choice == "list_backups":
|
|
504
|
+
limit = questionary.text("Number of records to display (default 10):").ask()
|
|
505
|
+
limit = int(limit) if limit and limit.isdigit() else 10
|
|
506
|
+
backups = get_backup_records(limit)
|
|
507
|
+
if backups:
|
|
508
|
+
from rich.table import Table
|
|
509
|
+
from rich.console import Console
|
|
510
|
+
console = Console()
|
|
511
|
+
table = Table(title="Backup Records")
|
|
512
|
+
table.add_column("ID", style="cyan")
|
|
513
|
+
table.add_column("Backup Path", style="green")
|
|
514
|
+
table.add_column("Backup Time", style="yellow")
|
|
515
|
+
table.add_column("Size (KB)", style="blue", justify="right")
|
|
516
|
+
for backup in backups:
|
|
517
|
+
table.add_row(
|
|
518
|
+
str(backup["id"]),
|
|
519
|
+
backup["backup_path"],
|
|
520
|
+
backup["backup_time"],
|
|
521
|
+
str(backup["backup_size"])
|
|
522
|
+
)
|
|
523
|
+
console.print(table)
|
|
524
|
+
else:
|
|
525
|
+
print("No backup records available")
|
|
526
|
+
|
|
527
|
+
elif choice == "clean":
|
|
528
|
+
config = get_database_config()
|
|
529
|
+
clean_cycle = config.get("clean_cycle", 30)
|
|
530
|
+
days = questionary.text(f"Clean data older than how many days (default {clean_cycle} days):").ask()
|
|
531
|
+
days = int(days) if days and days.isdigit() else clean_cycle
|
|
532
|
+
|
|
533
|
+
confirm = questionary.confirm(f"Confirm deletion of all data older than {days} days?").ask()
|
|
534
|
+
if confirm:
|
|
535
|
+
_, before_time = get_time_range_days(days)
|
|
536
|
+
deleted = delete_sessions(before_time=before_time)
|
|
537
|
+
print(f"✅ Deleted {deleted} session records")
|
|
538
|
+
|
|
539
|
+
elif choice == "back":
|
|
540
|
+
return
|