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
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aicodestat
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A local-first metrics tool that analyzes how you use AI coding assistants.
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: fastapi>=0.104.0
|
|
8
|
+
Requires-Dist: uvicorn[standard]>=0.24.0
|
|
9
|
+
Requires-Dist: pydantic>=2.0.0
|
|
10
|
+
Requires-Dist: rich>=13.0.0
|
|
11
|
+
Requires-Dist: questionary>=2.0.0
|
|
12
|
+
Requires-Dist: httpx>=0.25.0
|
|
13
|
+
Requires-Dist: mcp>=1.0.0
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
16
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
17
|
+
|
|
18
|
+
## CodeStat · AI Code Metrics
|
|
19
|
+
|
|
20
|
+
> Quantify how much AI actually contributes to your codebase.
|
|
21
|
+
|
|
22
|
+
[](https://github.com/2hangchen/CodeStat/actions)
|
|
23
|
+
[](LICENSE)
|
|
24
|
+
|
|
25
|
+
`CodeStat` is a local metrics tool that analyzes how you use AI coding assistants:
|
|
26
|
+
how many lines are generated by AI, how many are kept, and how this evolves over time.
|
|
27
|
+
|
|
28
|
+
> 中文文档见:[`README.zh-CN.md`](./README.zh-CN.md)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **Global dashboard for all data**
|
|
35
|
+
- AI generated lines, adopted lines, adoption & generation rates
|
|
36
|
+
- File count, session count, quick bar chart overview
|
|
37
|
+
|
|
38
|
+
- **Multi‑dimension queries**
|
|
39
|
+
- **By file**: see how much of a file comes from AI and how much you kept
|
|
40
|
+
- **By session**: analyze one coding session with detailed diff lines
|
|
41
|
+
- **By project**: aggregate metrics for an entire repository
|
|
42
|
+
|
|
43
|
+
- **Agent / model comparison**
|
|
44
|
+
- Compare multiple sessions (agents / models / settings) side‑by‑side
|
|
45
|
+
- See which one actually produces more adopted code instead of just more tokens
|
|
46
|
+
|
|
47
|
+
- **Local‑first & privacy‑friendly**
|
|
48
|
+
- All metrics are computed locally from your own diffs
|
|
49
|
+
- No source code or prompts are sent to any remote service
|
|
50
|
+
|
|
51
|
+
- **Nice CLI UX**
|
|
52
|
+
- Rich‑based tables & colors, arrow‑key navigation
|
|
53
|
+
- Minimal but informative header (MCP status + repo info)
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Demo
|
|
58
|
+
|
|
59
|
+
> TODO: add real screenshots / GIFs from your terminal
|
|
60
|
+
|
|
61
|
+
- **Global dashboard**
|
|
62
|
+
|
|
63
|
+
*(insert GIF or screenshot here)*
|
|
64
|
+
|
|
65
|
+
- **Session metrics with diff lines**
|
|
66
|
+
|
|
67
|
+
*(insert GIF or screenshot here)*
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Quickstart
|
|
72
|
+
|
|
73
|
+
### Install
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
git clone https://github.com/2hangchen/CodeStat.git
|
|
77
|
+
cd CodeStat
|
|
78
|
+
pip install -r requirements.txt
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> Once published to PyPI you can alternatively run:
|
|
82
|
+
> `pip install codestat-ai`
|
|
83
|
+
|
|
84
|
+
### Start the CLI
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python .\cli\main.py
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Use `↑/↓` to move, `Enter` to confirm.
|
|
91
|
+
Choose **“📈 Global Dashboard (All Data)”** to see an overview of your local metrics.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Typical Workflows
|
|
96
|
+
|
|
97
|
+
- **Measure your own AI usage**
|
|
98
|
+
- Record one or more coding sessions with your IDE + MCP server
|
|
99
|
+
- Run `CodeStat` and inspect:
|
|
100
|
+
- AI generated vs adopted lines
|
|
101
|
+
- Which files receive the most AI help
|
|
102
|
+
|
|
103
|
+
- **Compare agents / models / prompts**
|
|
104
|
+
- Map different sessions to different agents / models
|
|
105
|
+
- Use **Compare Agents** to get a per‑session comparison table
|
|
106
|
+
|
|
107
|
+
- **Project‑level health check**
|
|
108
|
+
- For a given repo, run project metrics to see:
|
|
109
|
+
- Where AI contributes the most
|
|
110
|
+
- Whether AI‑generated code is actually being kept
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
config.py,sha256=EUE6AGyiO2-gaP2USqpO8MVMwDrrAwdqtaBTRz_HNZg,3829
|
|
2
|
+
local_mcp_server.py,sha256=YTH4D_BH7BK7XWzhFrQgyxdPjlabNtQ0TPz9AcmcLIE,10901
|
|
3
|
+
logging_config.py,sha256=_BQ8zNBM9jNDIASQZeX3pvTmitQbzXqgIvPIPiUy7II,1997
|
|
4
|
+
main.py,sha256=LtsgvkCaSynwK4sGaDvCSd0ae2B0hBfQ3RJM9IT9ey0,5843
|
|
5
|
+
service_manager.py,sha256=k1eEEioAlD44Z1zWJ5JHNv6sVkOdr92xsOgvkX0lYNg,6909
|
|
6
|
+
cli/__init__.py,sha256=AqvbeRtWawDr_fC3owziHPI3LTZPEOMprtqhPUv6HzM,34
|
|
7
|
+
cli/exporter.py,sha256=9eh3TKBY2VT1AScU2RTFEElYtEOtz_M7fmuR3LHr7Dc,3567
|
|
8
|
+
cli/main.py,sha256=kpZxNMlb4fC1naY5PHj7zK3HfVFSrraRee3_zwItZLI,7072
|
|
9
|
+
cli/menus.py,sha256=6JMBD0HT5DauB9eDsR5Hy6XOoo3nDAnibR-5Ki37lLs,19379
|
|
10
|
+
cli/views.py,sha256=495tKFwW2PmKjDDCRweONKX_f3E3k6y-9WVBlJ1dXwg,9858
|
|
11
|
+
compute/__init__.py,sha256=UZBI-hgsvGzb3BFH70Ce-QvWgxs5OvM4oQ1NzbDxGsY,25
|
|
12
|
+
compute/cache.py,sha256=4U8bsFy0yBdq1bhsmjTnNTWFuuHK6h3_hrkGPmxxVC8,2417
|
|
13
|
+
compute/diff_engine.py,sha256=ySYqGZfDDYfkTaKhA4BdYad4ACaeurWgWy3jwMbDE1I,2685
|
|
14
|
+
compute/lcs_engine.py,sha256=zh6TJWZCzP8s-Tls2VHkVUfW2DJ63tZgSvatxtLCT78,2217
|
|
15
|
+
compute/metrics_service.py,sha256=jHyQV5UfK6RpzCuAmq_C6UTknsynpKEc-aKFGQ-sLHQ,11128
|
|
16
|
+
mcp/__init__.py,sha256=xoKlMDnbq-owd19TqBCnbAPudUyPHr3S2nGGrb-TghI,30
|
|
17
|
+
mcp/agent_adapter.py,sha256=gBp4eaWZJ_z1XTynOsINrGLK6rR_AYC5IOWFYke6BKs,1777
|
|
18
|
+
mcp/api_schemas.py,sha256=_ZkX5Q_yM-m447f2VcPqjsSAgR2cP4bn-m23oDyv1Ro,1304
|
|
19
|
+
mcp/routes_after.py,sha256=ZuTWxgl7bmYxRqxX8VLvwiDRY_H7AtHh3R-6rhYxXNI,4374
|
|
20
|
+
mcp/routes_before.py,sha256=NbNVVqZ5OnzsSY9I0gAYzDd-YFx9r01oAWrJVjtvEEo,2234
|
|
21
|
+
mcp/routes_tools.py,sha256=1sgoaiVEw3LM6C_FE0WyzxLmssMXyH1k3NF_PPHcdSQ,4360
|
|
22
|
+
storage/__init__.py,sha256=t8_StbWwdo4NOqdm0mDgisj3a1YNRET_qktZoKO_oDY,25
|
|
23
|
+
storage/backup.py,sha256=UUV5-bazOV6Rs4-Vg39C4-dTdw4RQSqSvLYmVr3JxA0,5715
|
|
24
|
+
storage/db.py,sha256=PF_ZkQcruYlMegdkCINOwjScS5FB_qVYKd_iilnOrqY,5522
|
|
25
|
+
storage/models.py,sha256=-2x2loKCTVX4i56-9MWsU4NPDEUMx7llvmzseRarR-4,9842
|
|
26
|
+
storage/scheduler.py,sha256=J9I2AHymduyaSG33bpZzxc4JfoIjRyYMNCkU2Sr0Wzc,3960
|
|
27
|
+
utils/__init__.py,sha256=42ItHJEKhCz4PIrPFZnXNQV-hR0B5gJ9bb9dfW_uL-U,22
|
|
28
|
+
utils/port_utils.py,sha256=2df9-gtSKT4yaQZapTZxkczvDDMBKL7gY5RPOTzQOxo,1907
|
|
29
|
+
utils/time_utils.py,sha256=ruVPmc9pTIl_-HzG2uMaluoSJo5qRxExYRcJI84i02s,1126
|
|
30
|
+
aicodestat-0.0.1.dist-info/METADATA,sha256=WJVZMlSmM2VohaqB3IyUXoxplqwvZ4DH6ai1GdxHzDQ,3383
|
|
31
|
+
aicodestat-0.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
32
|
+
aicodestat-0.0.1.dist-info/entry_points.txt,sha256=TDZ9VLa7H2-5HXrc1HTEthOGdZL4B2HBt1cHfqzaJI4,149
|
|
33
|
+
aicodestat-0.0.1.dist-info/top_level.txt,sha256=N8Qc1-jbxHDZ9jirkiVXwUSWxjMNySZxMaqd5_QeCx0,90
|
|
34
|
+
aicodestat-0.0.1.dist-info/RECORD,,
|
cli/__init__.py
ADDED
cli/exporter.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Export query results to CSV/JSON"""
|
|
2
|
+
import json
|
|
3
|
+
import csv
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, Any, List
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from utils.time_utils import format_datetime
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def export_to_json(data: Dict[str, Any], output_path: str) -> bool:
|
|
14
|
+
"""
|
|
15
|
+
Export data as JSON format
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
data: Data to export
|
|
19
|
+
output_path: Output file path
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Whether successful
|
|
23
|
+
"""
|
|
24
|
+
try:
|
|
25
|
+
output_file = Path(output_path)
|
|
26
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
|
|
28
|
+
export_data = {
|
|
29
|
+
"export_time": format_datetime(datetime.now()),
|
|
30
|
+
"data": data
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
34
|
+
json.dump(export_data, f, ensure_ascii=False, indent=2)
|
|
35
|
+
|
|
36
|
+
logger.info(f"Data exported to JSON: {output_path}")
|
|
37
|
+
return True
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.error(f"Failed to export to JSON: {e}")
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def export_to_csv(metrics: Dict[str, Any], output_path: str) -> bool:
|
|
44
|
+
"""
|
|
45
|
+
Export metrics data as CSV format
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
metrics: Metrics data
|
|
49
|
+
output_path: Output file path
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Whether successful
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
output_file = Path(output_path)
|
|
56
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
|
|
58
|
+
with open(output_file, 'w', newline='', encoding='utf-8') as f:
|
|
59
|
+
writer = csv.writer(f)
|
|
60
|
+
|
|
61
|
+
# Write header row
|
|
62
|
+
writer.writerow(["Metric Name", "Value"])
|
|
63
|
+
|
|
64
|
+
# Write metrics data
|
|
65
|
+
writer.writerow(["AI Generated Lines", metrics.get("ai_total_lines", 0)])
|
|
66
|
+
writer.writerow(["Adopted Lines", metrics.get("adopted_lines", 0)])
|
|
67
|
+
writer.writerow(["Code Adoption Rate (%)", metrics.get("adoption_rate", 0.0)])
|
|
68
|
+
writer.writerow(["Code Generation Rate (%)", metrics.get("generation_rate", 0.0)])
|
|
69
|
+
|
|
70
|
+
if "file_count" in metrics:
|
|
71
|
+
writer.writerow(["Files Involved", metrics.get("file_count", 0)])
|
|
72
|
+
|
|
73
|
+
if "session_count" in metrics:
|
|
74
|
+
writer.writerow(["Sessions", metrics.get("session_count", 0)])
|
|
75
|
+
|
|
76
|
+
# If there are diff lines details, write them
|
|
77
|
+
diff_lines = metrics.get("diff_lines", [])
|
|
78
|
+
if diff_lines:
|
|
79
|
+
writer.writerow([]) # Empty row
|
|
80
|
+
writer.writerow(["Diff Lines Details"])
|
|
81
|
+
writer.writerow(["Diff Type", "Line Number", "Code Content"])
|
|
82
|
+
for diff_line in diff_lines:
|
|
83
|
+
writer.writerow([
|
|
84
|
+
diff_line.get("diff_type", ""),
|
|
85
|
+
diff_line.get("line_number", ""),
|
|
86
|
+
diff_line.get("line_content", "")
|
|
87
|
+
])
|
|
88
|
+
|
|
89
|
+
logger.info(f"Data exported to CSV: {output_path}")
|
|
90
|
+
return True
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logger.error(f"Failed to export to CSV: {e}")
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def export_metrics(metrics: Dict[str, Any], output_path: str, format: str = "json") -> bool:
|
|
97
|
+
"""
|
|
98
|
+
Export metrics data
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
metrics: Metrics data
|
|
102
|
+
output_path: Output file path
|
|
103
|
+
format: Export format (json or csv)
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Whether successful
|
|
107
|
+
"""
|
|
108
|
+
if format.lower() == "csv":
|
|
109
|
+
return export_to_csv(metrics, output_path)
|
|
110
|
+
else:
|
|
111
|
+
return export_to_json(metrics, output_path)
|
cli/main.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""CLI entry point, parses command line arguments"""
|
|
2
|
+
import argparse
|
|
3
|
+
import sys
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# Add project root to Python path to ensure modules can be imported
|
|
8
|
+
_project_root = Path(__file__).parent.parent
|
|
9
|
+
if str(_project_root) not in sys.path:
|
|
10
|
+
sys.path.insert(0, str(_project_root))
|
|
11
|
+
|
|
12
|
+
from config import get_cli_config
|
|
13
|
+
from logging_config import setup_logging
|
|
14
|
+
from storage.db import get_db
|
|
15
|
+
from cli.menus import (
|
|
16
|
+
show_main_menu,
|
|
17
|
+
query_by_session,
|
|
18
|
+
query_by_file,
|
|
19
|
+
query_by_project,
|
|
20
|
+
compare_agents,
|
|
21
|
+
manage_data,
|
|
22
|
+
manage_service,
|
|
23
|
+
show_global_dashboard,
|
|
24
|
+
)
|
|
25
|
+
from cli.views import console
|
|
26
|
+
from cli.exporter import export_metrics
|
|
27
|
+
from compute.metrics_service import calculate_session_metrics, calculate_file_metrics, calculate_project_metrics
|
|
28
|
+
import questionary
|
|
29
|
+
|
|
30
|
+
logger = setup_logging(log_level="INFO", module_name="cli")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def print_banner():
|
|
34
|
+
"""Minimal banner (kept for future extension, currently no-op)."""
|
|
35
|
+
# Intentionally kept very light; main layout is handled in the menu.
|
|
36
|
+
console.print()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main():
|
|
40
|
+
"""Main function"""
|
|
41
|
+
parser = argparse.ArgumentParser(description="Local MCP AI Code Statistics Tool CLI")
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--session",
|
|
44
|
+
type=str,
|
|
45
|
+
help="Query by session ID (quick command)"
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--file",
|
|
49
|
+
type=str,
|
|
50
|
+
help="Query by file path (quick command)"
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--project",
|
|
54
|
+
type=str,
|
|
55
|
+
help="Query by project root directory (quick command)"
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--export",
|
|
59
|
+
type=str,
|
|
60
|
+
help="Export format (json/csv), must be used with --session/--file/--project"
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"--output",
|
|
64
|
+
type=str,
|
|
65
|
+
help="Export file path, must be used with --export"
|
|
66
|
+
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"--start-service",
|
|
69
|
+
action="store_true",
|
|
70
|
+
help="Start MCP service (background)"
|
|
71
|
+
)
|
|
72
|
+
parser.add_argument(
|
|
73
|
+
"--stop-service",
|
|
74
|
+
action="store_true",
|
|
75
|
+
help="Stop MCP service"
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument(
|
|
78
|
+
"--service-status",
|
|
79
|
+
action="store_true",
|
|
80
|
+
help="Check MCP service status"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
args = parser.parse_args()
|
|
84
|
+
|
|
85
|
+
# Initialize database
|
|
86
|
+
try:
|
|
87
|
+
db = get_db()
|
|
88
|
+
db.initialize()
|
|
89
|
+
except Exception as e:
|
|
90
|
+
console.print(f"[red]❌ Database initialization failed: {e}[/red]")
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
# Handle service management commands
|
|
94
|
+
if args.start_service:
|
|
95
|
+
from service_manager import get_service_manager
|
|
96
|
+
manager = get_service_manager()
|
|
97
|
+
if manager.start(background=True):
|
|
98
|
+
console.print("[green]✅ MCP service started successfully[/green]")
|
|
99
|
+
else:
|
|
100
|
+
console.print("[red]❌ Failed to start MCP service[/red]")
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
if args.stop_service:
|
|
104
|
+
from service_manager import get_service_manager
|
|
105
|
+
manager = get_service_manager()
|
|
106
|
+
if manager.stop():
|
|
107
|
+
console.print("[green]✅ MCP service stopped[/green]")
|
|
108
|
+
else:
|
|
109
|
+
console.print("[yellow]⚠️ MCP service is not running or failed to stop[/yellow]")
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
if args.service_status:
|
|
113
|
+
from service_manager import get_service_manager
|
|
114
|
+
from cli.views import display_service_status
|
|
115
|
+
manager = get_service_manager()
|
|
116
|
+
status = manager.get_status()
|
|
117
|
+
display_service_status(status)
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
# Quick command mode
|
|
121
|
+
if args.session:
|
|
122
|
+
try:
|
|
123
|
+
metrics = calculate_session_metrics(args.session)
|
|
124
|
+
from cli.views import display_metrics_table, display_session_info
|
|
125
|
+
if metrics.get("summaries"):
|
|
126
|
+
display_session_info(metrics["summaries"])
|
|
127
|
+
display_metrics_table(metrics)
|
|
128
|
+
|
|
129
|
+
if args.export and args.output:
|
|
130
|
+
export_metrics(metrics, args.output, args.export)
|
|
131
|
+
console.print(f"[green]✅ Data exported to: {args.output}[/green]")
|
|
132
|
+
except Exception as e:
|
|
133
|
+
console.print(f"[red]❌ Query failed: {e}[/red]")
|
|
134
|
+
sys.exit(1)
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
if args.file:
|
|
138
|
+
try:
|
|
139
|
+
metrics = calculate_file_metrics(args.file)
|
|
140
|
+
from cli.views import display_metrics_table
|
|
141
|
+
display_metrics_table(metrics)
|
|
142
|
+
|
|
143
|
+
if args.export and args.output:
|
|
144
|
+
export_metrics(metrics, args.output, args.export)
|
|
145
|
+
console.print(f"[green]✅ Data exported to: {args.output}[/green]")
|
|
146
|
+
except Exception as e:
|
|
147
|
+
console.print(f"[red]❌ Query failed: {e}[/red]")
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
if args.project:
|
|
152
|
+
try:
|
|
153
|
+
metrics = calculate_project_metrics(args.project)
|
|
154
|
+
from cli.views import display_metrics_table
|
|
155
|
+
display_metrics_table(metrics)
|
|
156
|
+
|
|
157
|
+
if args.export and args.output:
|
|
158
|
+
export_metrics(metrics, args.output, args.export)
|
|
159
|
+
console.print(f"[green]✅ Data exported to: {args.output}[/green]")
|
|
160
|
+
except Exception as e:
|
|
161
|
+
console.print(f"[red]❌ Query failed: {e}[/red]")
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
# Interactive menu mode
|
|
166
|
+
print_banner()
|
|
167
|
+
|
|
168
|
+
while True:
|
|
169
|
+
try:
|
|
170
|
+
choice = show_main_menu()
|
|
171
|
+
|
|
172
|
+
if choice == "exit":
|
|
173
|
+
console.print("\n[green]👋 Exiting tool, data is safely stored locally~[/green]")
|
|
174
|
+
break
|
|
175
|
+
elif choice == "overview":
|
|
176
|
+
show_global_dashboard()
|
|
177
|
+
elif choice == "file":
|
|
178
|
+
query_by_file()
|
|
179
|
+
elif choice == "session":
|
|
180
|
+
query_by_session()
|
|
181
|
+
elif choice == "project":
|
|
182
|
+
query_by_project()
|
|
183
|
+
elif choice == "compare":
|
|
184
|
+
compare_agents()
|
|
185
|
+
elif choice == "export":
|
|
186
|
+
# Export functionality is integrated in query functions
|
|
187
|
+
console.print("[yellow]Please select export option when querying[/yellow]")
|
|
188
|
+
elif choice == "service":
|
|
189
|
+
manage_service()
|
|
190
|
+
elif choice == "manage":
|
|
191
|
+
manage_data()
|
|
192
|
+
|
|
193
|
+
# Ask if continue
|
|
194
|
+
if choice != "exit":
|
|
195
|
+
continue_choice = questionary.confirm("Continue querying?").ask()
|
|
196
|
+
if not continue_choice:
|
|
197
|
+
console.print("\n[green]👋 Exiting tool, data is safely stored locally~[/green]")
|
|
198
|
+
break
|
|
199
|
+
print() # Empty line separator
|
|
200
|
+
|
|
201
|
+
except KeyboardInterrupt:
|
|
202
|
+
console.print("\n[yellow]Operation cancelled[/yellow]")
|
|
203
|
+
break
|
|
204
|
+
except Exception as e:
|
|
205
|
+
console.print(f"[red]❌ Error occurred: {e}[/red]")
|
|
206
|
+
logger.error(f"CLI error: {e}", exc_info=True)
|
|
207
|
+
continue_choice = questionary.confirm("Continue?").ask()
|
|
208
|
+
if not continue_choice:
|
|
209
|
+
break
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
main()
|