mcli-framework 7.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.
Potentially problematic release.
This version of mcli-framework might be problematic. Click here for more details.
- mcli/app/chat_cmd.py +42 -0
- mcli/app/commands_cmd.py +226 -0
- mcli/app/completion_cmd.py +216 -0
- mcli/app/completion_helpers.py +288 -0
- mcli/app/cron_test_cmd.py +697 -0
- mcli/app/logs_cmd.py +419 -0
- mcli/app/main.py +492 -0
- mcli/app/model/model.py +1060 -0
- mcli/app/model_cmd.py +227 -0
- mcli/app/redis_cmd.py +269 -0
- mcli/app/video/video.py +1114 -0
- mcli/app/visual_cmd.py +303 -0
- mcli/chat/chat.py +2409 -0
- mcli/chat/command_rag.py +514 -0
- mcli/chat/enhanced_chat.py +652 -0
- mcli/chat/system_controller.py +1010 -0
- mcli/chat/system_integration.py +1016 -0
- mcli/cli.py +25 -0
- mcli/config.toml +20 -0
- mcli/lib/api/api.py +586 -0
- mcli/lib/api/daemon_client.py +203 -0
- mcli/lib/api/daemon_client_local.py +44 -0
- mcli/lib/api/daemon_decorator.py +217 -0
- mcli/lib/api/mcli_decorators.py +1032 -0
- mcli/lib/auth/auth.py +85 -0
- mcli/lib/auth/aws_manager.py +85 -0
- mcli/lib/auth/azure_manager.py +91 -0
- mcli/lib/auth/credential_manager.py +192 -0
- mcli/lib/auth/gcp_manager.py +93 -0
- mcli/lib/auth/key_manager.py +117 -0
- mcli/lib/auth/mcli_manager.py +93 -0
- mcli/lib/auth/token_manager.py +75 -0
- mcli/lib/auth/token_util.py +1011 -0
- mcli/lib/config/config.py +47 -0
- mcli/lib/discovery/__init__.py +1 -0
- mcli/lib/discovery/command_discovery.py +274 -0
- mcli/lib/erd/erd.py +1345 -0
- mcli/lib/erd/generate_graph.py +453 -0
- mcli/lib/files/files.py +76 -0
- mcli/lib/fs/fs.py +109 -0
- mcli/lib/lib.py +29 -0
- mcli/lib/logger/logger.py +611 -0
- mcli/lib/performance/optimizer.py +409 -0
- mcli/lib/performance/rust_bridge.py +502 -0
- mcli/lib/performance/uvloop_config.py +154 -0
- mcli/lib/pickles/pickles.py +50 -0
- mcli/lib/search/cached_vectorizer.py +479 -0
- mcli/lib/services/data_pipeline.py +460 -0
- mcli/lib/services/lsh_client.py +441 -0
- mcli/lib/services/redis_service.py +387 -0
- mcli/lib/shell/shell.py +137 -0
- mcli/lib/toml/toml.py +33 -0
- mcli/lib/ui/styling.py +47 -0
- mcli/lib/ui/visual_effects.py +634 -0
- mcli/lib/watcher/watcher.py +185 -0
- mcli/ml/api/app.py +215 -0
- mcli/ml/api/middleware.py +224 -0
- mcli/ml/api/routers/admin_router.py +12 -0
- mcli/ml/api/routers/auth_router.py +244 -0
- mcli/ml/api/routers/backtest_router.py +12 -0
- mcli/ml/api/routers/data_router.py +12 -0
- mcli/ml/api/routers/model_router.py +302 -0
- mcli/ml/api/routers/monitoring_router.py +12 -0
- mcli/ml/api/routers/portfolio_router.py +12 -0
- mcli/ml/api/routers/prediction_router.py +267 -0
- mcli/ml/api/routers/trade_router.py +12 -0
- mcli/ml/api/routers/websocket_router.py +76 -0
- mcli/ml/api/schemas.py +64 -0
- mcli/ml/auth/auth_manager.py +425 -0
- mcli/ml/auth/models.py +154 -0
- mcli/ml/auth/permissions.py +302 -0
- mcli/ml/backtesting/backtest_engine.py +502 -0
- mcli/ml/backtesting/performance_metrics.py +393 -0
- mcli/ml/cache.py +400 -0
- mcli/ml/cli/main.py +398 -0
- mcli/ml/config/settings.py +394 -0
- mcli/ml/configs/dvc_config.py +230 -0
- mcli/ml/configs/mlflow_config.py +131 -0
- mcli/ml/configs/mlops_manager.py +293 -0
- mcli/ml/dashboard/app.py +532 -0
- mcli/ml/dashboard/app_integrated.py +738 -0
- mcli/ml/dashboard/app_supabase.py +560 -0
- mcli/ml/dashboard/app_training.py +615 -0
- mcli/ml/dashboard/cli.py +51 -0
- mcli/ml/data_ingestion/api_connectors.py +501 -0
- mcli/ml/data_ingestion/data_pipeline.py +567 -0
- mcli/ml/data_ingestion/stream_processor.py +512 -0
- mcli/ml/database/migrations/env.py +94 -0
- mcli/ml/database/models.py +667 -0
- mcli/ml/database/session.py +200 -0
- mcli/ml/experimentation/ab_testing.py +845 -0
- mcli/ml/features/ensemble_features.py +607 -0
- mcli/ml/features/political_features.py +676 -0
- mcli/ml/features/recommendation_engine.py +809 -0
- mcli/ml/features/stock_features.py +573 -0
- mcli/ml/features/test_feature_engineering.py +346 -0
- mcli/ml/logging.py +85 -0
- mcli/ml/mlops/data_versioning.py +518 -0
- mcli/ml/mlops/experiment_tracker.py +377 -0
- mcli/ml/mlops/model_serving.py +481 -0
- mcli/ml/mlops/pipeline_orchestrator.py +614 -0
- mcli/ml/models/base_models.py +324 -0
- mcli/ml/models/ensemble_models.py +675 -0
- mcli/ml/models/recommendation_models.py +474 -0
- mcli/ml/models/test_models.py +487 -0
- mcli/ml/monitoring/drift_detection.py +676 -0
- mcli/ml/monitoring/metrics.py +45 -0
- mcli/ml/optimization/portfolio_optimizer.py +834 -0
- mcli/ml/preprocessing/data_cleaners.py +451 -0
- mcli/ml/preprocessing/feature_extractors.py +491 -0
- mcli/ml/preprocessing/ml_pipeline.py +382 -0
- mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
- mcli/ml/preprocessing/test_preprocessing.py +294 -0
- mcli/ml/scripts/populate_sample_data.py +200 -0
- mcli/ml/tasks.py +400 -0
- mcli/ml/tests/test_integration.py +429 -0
- mcli/ml/tests/test_training_dashboard.py +387 -0
- mcli/public/oi/oi.py +15 -0
- mcli/public/public.py +4 -0
- mcli/self/self_cmd.py +1246 -0
- mcli/workflow/daemon/api_daemon.py +800 -0
- mcli/workflow/daemon/async_command_database.py +681 -0
- mcli/workflow/daemon/async_process_manager.py +591 -0
- mcli/workflow/daemon/client.py +530 -0
- mcli/workflow/daemon/commands.py +1196 -0
- mcli/workflow/daemon/daemon.py +905 -0
- mcli/workflow/daemon/daemon_api.py +59 -0
- mcli/workflow/daemon/enhanced_daemon.py +571 -0
- mcli/workflow/daemon/process_cli.py +244 -0
- mcli/workflow/daemon/process_manager.py +439 -0
- mcli/workflow/daemon/test_daemon.py +275 -0
- mcli/workflow/dashboard/dashboard_cmd.py +113 -0
- mcli/workflow/docker/docker.py +0 -0
- mcli/workflow/file/file.py +100 -0
- mcli/workflow/gcloud/config.toml +21 -0
- mcli/workflow/gcloud/gcloud.py +58 -0
- mcli/workflow/git_commit/ai_service.py +328 -0
- mcli/workflow/git_commit/commands.py +430 -0
- mcli/workflow/lsh_integration.py +355 -0
- mcli/workflow/model_service/client.py +594 -0
- mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
- mcli/workflow/model_service/lightweight_embedder.py +397 -0
- mcli/workflow/model_service/lightweight_model_server.py +714 -0
- mcli/workflow/model_service/lightweight_test.py +241 -0
- mcli/workflow/model_service/model_service.py +1955 -0
- mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
- mcli/workflow/model_service/pdf_processor.py +386 -0
- mcli/workflow/model_service/test_efficient_runner.py +234 -0
- mcli/workflow/model_service/test_example.py +315 -0
- mcli/workflow/model_service/test_integration.py +131 -0
- mcli/workflow/model_service/test_new_features.py +149 -0
- mcli/workflow/openai/openai.py +99 -0
- mcli/workflow/politician_trading/commands.py +1790 -0
- mcli/workflow/politician_trading/config.py +134 -0
- mcli/workflow/politician_trading/connectivity.py +490 -0
- mcli/workflow/politician_trading/data_sources.py +395 -0
- mcli/workflow/politician_trading/database.py +410 -0
- mcli/workflow/politician_trading/demo.py +248 -0
- mcli/workflow/politician_trading/models.py +165 -0
- mcli/workflow/politician_trading/monitoring.py +413 -0
- mcli/workflow/politician_trading/scrapers.py +966 -0
- mcli/workflow/politician_trading/scrapers_california.py +412 -0
- mcli/workflow/politician_trading/scrapers_eu.py +377 -0
- mcli/workflow/politician_trading/scrapers_uk.py +350 -0
- mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
- mcli/workflow/politician_trading/supabase_functions.py +354 -0
- mcli/workflow/politician_trading/workflow.py +852 -0
- mcli/workflow/registry/registry.py +180 -0
- mcli/workflow/repo/repo.py +223 -0
- mcli/workflow/scheduler/commands.py +493 -0
- mcli/workflow/scheduler/cron_parser.py +238 -0
- mcli/workflow/scheduler/job.py +182 -0
- mcli/workflow/scheduler/monitor.py +139 -0
- mcli/workflow/scheduler/persistence.py +324 -0
- mcli/workflow/scheduler/scheduler.py +679 -0
- mcli/workflow/sync/sync_cmd.py +437 -0
- mcli/workflow/sync/test_cmd.py +314 -0
- mcli/workflow/videos/videos.py +242 -0
- mcli/workflow/wakatime/wakatime.py +11 -0
- mcli/workflow/workflow.py +37 -0
- mcli_framework-7.0.0.dist-info/METADATA +479 -0
- mcli_framework-7.0.0.dist-info/RECORD +186 -0
- mcli_framework-7.0.0.dist-info/WHEEL +5 -0
- mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
- mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
- mcli_framework-7.0.0.dist-info/top_level.txt +1 -0
mcli/app/logs_cmd.py
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Log streaming and management commands
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.live import Live
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
from rich.text import Text
|
|
18
|
+
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.group(name="logs")
|
|
23
|
+
def logs_group():
|
|
24
|
+
"""Stream and manage MCLI log files"""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@logs_group.command(name="stream")
|
|
29
|
+
@click.option(
|
|
30
|
+
"--type",
|
|
31
|
+
"-t",
|
|
32
|
+
type=click.Choice(["main", "trace", "system", "all"]),
|
|
33
|
+
default="main",
|
|
34
|
+
help="Type of logs to stream (default: main)",
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
"--lines", "-n", type=int, default=50, help="Number of initial lines to show (default: 50)"
|
|
38
|
+
)
|
|
39
|
+
@click.option(
|
|
40
|
+
"--follow",
|
|
41
|
+
"-f",
|
|
42
|
+
is_flag=True,
|
|
43
|
+
default=True,
|
|
44
|
+
help="Follow log output in real-time (default: enabled)",
|
|
45
|
+
)
|
|
46
|
+
def stream_logs(type: str, lines: int, follow: bool):
|
|
47
|
+
"""Stream MCLI log files in real-time
|
|
48
|
+
|
|
49
|
+
Shows log output with syntax highlighting and real-time updates.
|
|
50
|
+
Similar to 'tail -f' but with enhanced formatting for MCLI logs.
|
|
51
|
+
"""
|
|
52
|
+
logs_dir = Path("logs")
|
|
53
|
+
|
|
54
|
+
if not logs_dir.exists():
|
|
55
|
+
console.print(
|
|
56
|
+
"❌ No logs directory found. Run some MCLI commands to generate logs.", style="red"
|
|
57
|
+
)
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
# Get today's log files
|
|
61
|
+
today = datetime.now().strftime("%Y%m%d")
|
|
62
|
+
|
|
63
|
+
log_files = {
|
|
64
|
+
"main": logs_dir / f"mcli_{today}.log",
|
|
65
|
+
"trace": logs_dir / f"mcli_trace_{today}.log",
|
|
66
|
+
"system": logs_dir / f"mcli_system_{today}.log",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Filter to existing files
|
|
70
|
+
existing_files = {k: v for k, v in log_files.items() if v.exists()}
|
|
71
|
+
|
|
72
|
+
if not existing_files:
|
|
73
|
+
console.print(f"❌ No log files found for today ({today})", style="red")
|
|
74
|
+
_list_available_logs(logs_dir)
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
# Determine which files to stream
|
|
78
|
+
if type == "all":
|
|
79
|
+
files_to_stream = list(existing_files.values())
|
|
80
|
+
else:
|
|
81
|
+
if type not in existing_files:
|
|
82
|
+
console.print(f"❌ {type} log file not found for today", style="red")
|
|
83
|
+
console.print(f"Available logs: {', '.join(existing_files.keys())}", style="yellow")
|
|
84
|
+
return
|
|
85
|
+
files_to_stream = [existing_files[type]]
|
|
86
|
+
|
|
87
|
+
# Start streaming
|
|
88
|
+
try:
|
|
89
|
+
if len(files_to_stream) == 1:
|
|
90
|
+
_stream_single_file(files_to_stream[0], lines, follow)
|
|
91
|
+
else:
|
|
92
|
+
_stream_multiple_files(files_to_stream, lines, follow)
|
|
93
|
+
except KeyboardInterrupt:
|
|
94
|
+
console.print("\n👋 Log streaming stopped", style="cyan")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@logs_group.command(name="list")
|
|
98
|
+
@click.option("--date", "-d", help="Show logs for specific date (YYYYMMDD format)")
|
|
99
|
+
def list_logs(date: Optional[str]):
|
|
100
|
+
"""List available log files"""
|
|
101
|
+
logs_dir = Path("logs")
|
|
102
|
+
|
|
103
|
+
if not logs_dir.exists():
|
|
104
|
+
console.print("❌ No logs directory found", style="red")
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
_list_available_logs(logs_dir, date)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@logs_group.command(name="tail")
|
|
111
|
+
@click.argument("log_type", type=click.Choice(["main", "trace", "system"]))
|
|
112
|
+
@click.option("--lines", "-n", type=int, default=20, help="Number of lines to show (default: 20)")
|
|
113
|
+
@click.option("--date", "-d", help="Date for log file (YYYYMMDD format, default: today)")
|
|
114
|
+
def tail_logs(log_type: str, lines: int, date: Optional[str]):
|
|
115
|
+
"""Show the last N lines of a specific log file"""
|
|
116
|
+
logs_dir = Path("logs")
|
|
117
|
+
|
|
118
|
+
if not logs_dir.exists():
|
|
119
|
+
console.print("❌ No logs directory found", style="red")
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
# Use provided date or default to today
|
|
123
|
+
log_date = date or datetime.now().strftime("%Y%m%d")
|
|
124
|
+
|
|
125
|
+
# Map log types to file patterns
|
|
126
|
+
log_files = {
|
|
127
|
+
"main": f"mcli_{log_date}.log",
|
|
128
|
+
"trace": f"mcli_trace_{log_date}.log",
|
|
129
|
+
"system": f"mcli_system_{log_date}.log",
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
log_file = logs_dir / log_files[log_type]
|
|
133
|
+
|
|
134
|
+
if not log_file.exists():
|
|
135
|
+
console.print(f"❌ Log file not found: {log_file}", style="red")
|
|
136
|
+
_list_available_logs(logs_dir, log_date)
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
# Read last N lines
|
|
141
|
+
with open(log_file, "r") as f:
|
|
142
|
+
all_lines = f.readlines()
|
|
143
|
+
tail_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
|
|
144
|
+
|
|
145
|
+
# Display with formatting
|
|
146
|
+
console.print(f"\n📋 **Last {len(tail_lines)} lines from {log_file.name}**\n", style="cyan")
|
|
147
|
+
|
|
148
|
+
for line in tail_lines:
|
|
149
|
+
formatted_line = _format_log_line(line.rstrip())
|
|
150
|
+
console.print(formatted_line)
|
|
151
|
+
|
|
152
|
+
except Exception as e:
|
|
153
|
+
console.print(f"❌ Error reading log file: {e}", style="red")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@logs_group.command(name="grep")
|
|
157
|
+
@click.argument("pattern")
|
|
158
|
+
@click.option(
|
|
159
|
+
"--type",
|
|
160
|
+
"-t",
|
|
161
|
+
type=click.Choice(["main", "trace", "system", "all"]),
|
|
162
|
+
default="all",
|
|
163
|
+
help="Type of logs to search (default: all)",
|
|
164
|
+
)
|
|
165
|
+
@click.option("--date", "-d", help="Date for log files (YYYYMMDD format, default: today)")
|
|
166
|
+
@click.option(
|
|
167
|
+
"--context", "-C", type=int, default=3, help="Lines of context around matches (default: 3)"
|
|
168
|
+
)
|
|
169
|
+
def grep_logs(pattern: str, type: str, date: Optional[str], context: int):
|
|
170
|
+
"""Search for patterns in log files"""
|
|
171
|
+
logs_dir = Path("logs")
|
|
172
|
+
|
|
173
|
+
if not logs_dir.exists():
|
|
174
|
+
console.print("❌ No logs directory found", style="red")
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
# Use provided date or default to today
|
|
178
|
+
log_date = date or datetime.now().strftime("%Y%m%d")
|
|
179
|
+
|
|
180
|
+
# Get log files to search
|
|
181
|
+
log_files = {
|
|
182
|
+
"main": logs_dir / f"mcli_{log_date}.log",
|
|
183
|
+
"trace": logs_dir / f"mcli_trace_{log_date}.log",
|
|
184
|
+
"system": logs_dir / f"mcli_system_{log_date}.log",
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
files_to_search = []
|
|
188
|
+
if type == "all":
|
|
189
|
+
files_to_search = [f for f in log_files.values() if f.exists()]
|
|
190
|
+
else:
|
|
191
|
+
if log_files[type].exists():
|
|
192
|
+
files_to_search = [log_files[type]]
|
|
193
|
+
|
|
194
|
+
if not files_to_search:
|
|
195
|
+
console.print(f"❌ No log files found for {log_date}", style="red")
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
# Search each file
|
|
199
|
+
total_matches = 0
|
|
200
|
+
for log_file in files_to_search:
|
|
201
|
+
matches = _search_log_file(log_file, pattern, context)
|
|
202
|
+
if matches:
|
|
203
|
+
console.print(f"\n📁 **{log_file.name}** ({len(matches)} matches)", style="cyan")
|
|
204
|
+
for match in matches:
|
|
205
|
+
console.print(match)
|
|
206
|
+
total_matches += len(matches)
|
|
207
|
+
|
|
208
|
+
if total_matches == 0:
|
|
209
|
+
console.print(f"❌ No matches found for pattern: {pattern}", style="yellow")
|
|
210
|
+
else:
|
|
211
|
+
console.print(f"\n✅ Found {total_matches} total matches", style="green")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@logs_group.command(name="clear")
|
|
215
|
+
@click.option("--older-than", type=int, help="Clear logs older than N days")
|
|
216
|
+
@click.option("--confirm", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
217
|
+
def clear_logs(older_than: Optional[int], confirm: bool):
|
|
218
|
+
"""Clear old log files"""
|
|
219
|
+
logs_dir = Path("logs")
|
|
220
|
+
|
|
221
|
+
if not logs_dir.exists():
|
|
222
|
+
console.print("❌ No logs directory found", style="red")
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
# Find log files
|
|
226
|
+
log_files = list(logs_dir.glob("mcli*.log"))
|
|
227
|
+
|
|
228
|
+
if not log_files:
|
|
229
|
+
console.print("ℹ️ No log files to clear", style="blue")
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
# Filter by age if specified
|
|
233
|
+
if older_than:
|
|
234
|
+
import time
|
|
235
|
+
|
|
236
|
+
cutoff_time = time.time() - (older_than * 24 * 60 * 60) # Convert days to seconds
|
|
237
|
+
log_files = [f for f in log_files if f.stat().st_mtime < cutoff_time]
|
|
238
|
+
|
|
239
|
+
if not log_files:
|
|
240
|
+
console.print(f"ℹ️ No log files older than {older_than} days", style="blue")
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
# Show what will be deleted
|
|
244
|
+
console.print(f"📋 **Log files to clear:**", style="yellow")
|
|
245
|
+
total_size = 0
|
|
246
|
+
for log_file in log_files:
|
|
247
|
+
size = log_file.stat().st_size
|
|
248
|
+
total_size += size
|
|
249
|
+
console.print(f" • {log_file.name} ({_format_file_size(size)})")
|
|
250
|
+
|
|
251
|
+
console.print(f"\n📊 **Total size:** {_format_file_size(total_size)}", style="cyan")
|
|
252
|
+
|
|
253
|
+
# Confirm deletion
|
|
254
|
+
if not confirm:
|
|
255
|
+
if not click.confirm(f"\nDelete {len(log_files)} log files?"):
|
|
256
|
+
console.print("❌ Operation cancelled", style="yellow")
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
# Delete files
|
|
260
|
+
deleted_count = 0
|
|
261
|
+
for log_file in log_files:
|
|
262
|
+
try:
|
|
263
|
+
log_file.unlink()
|
|
264
|
+
deleted_count += 1
|
|
265
|
+
except Exception as e:
|
|
266
|
+
console.print(f"❌ Failed to delete {log_file.name}: {e}", style="red")
|
|
267
|
+
|
|
268
|
+
console.print(
|
|
269
|
+
f"✅ Deleted {deleted_count} log files ({_format_file_size(total_size)} freed)",
|
|
270
|
+
style="green",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _stream_single_file(log_file: Path, lines: int, follow: bool):
|
|
275
|
+
"""Stream a single log file"""
|
|
276
|
+
console.print(f"📡 **Streaming {log_file.name}**", style="cyan")
|
|
277
|
+
console.print("Press Ctrl+C to stop\n")
|
|
278
|
+
|
|
279
|
+
if follow:
|
|
280
|
+
# Use tail -f for real-time following
|
|
281
|
+
cmd = ["tail", f"-{lines}", "-f", str(log_file)]
|
|
282
|
+
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
for line in iter(process.stdout.readline, ""):
|
|
286
|
+
if line:
|
|
287
|
+
formatted_line = _format_log_line(line.rstrip())
|
|
288
|
+
console.print(formatted_line)
|
|
289
|
+
except KeyboardInterrupt:
|
|
290
|
+
process.terminate()
|
|
291
|
+
else:
|
|
292
|
+
# Just show last N lines
|
|
293
|
+
with open(log_file, "r") as f:
|
|
294
|
+
all_lines = f.readlines()
|
|
295
|
+
tail_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
|
|
296
|
+
|
|
297
|
+
for line in tail_lines:
|
|
298
|
+
formatted_line = _format_log_line(line.rstrip())
|
|
299
|
+
console.print(formatted_line)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _stream_multiple_files(log_files: list, lines: int, follow: bool):
|
|
303
|
+
"""Stream multiple log files simultaneously"""
|
|
304
|
+
console.print(f"📡 **Streaming {len(log_files)} log files**", style="cyan")
|
|
305
|
+
for log_file in log_files:
|
|
306
|
+
console.print(f" • {log_file.name}")
|
|
307
|
+
console.print("Press Ctrl+C to stop\n")
|
|
308
|
+
|
|
309
|
+
# Use multitail or custom implementation
|
|
310
|
+
# For simplicity, we'll cycle through files
|
|
311
|
+
try:
|
|
312
|
+
while True:
|
|
313
|
+
for log_file in log_files:
|
|
314
|
+
if log_file.exists():
|
|
315
|
+
# Show recent lines from each file
|
|
316
|
+
with open(log_file, "r") as f:
|
|
317
|
+
all_lines = f.readlines()
|
|
318
|
+
recent_lines = all_lines[-5:] if len(all_lines) > 5 else all_lines
|
|
319
|
+
|
|
320
|
+
if recent_lines:
|
|
321
|
+
console.print(f"\n--- {log_file.name} ---", style="blue")
|
|
322
|
+
for line in recent_lines:
|
|
323
|
+
formatted_line = _format_log_line(line.rstrip())
|
|
324
|
+
console.print(formatted_line)
|
|
325
|
+
|
|
326
|
+
if follow:
|
|
327
|
+
time.sleep(2) # Update every 2 seconds
|
|
328
|
+
else:
|
|
329
|
+
break
|
|
330
|
+
|
|
331
|
+
except KeyboardInterrupt:
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _format_log_line(line: str) -> Text:
|
|
336
|
+
"""Format a log line with syntax highlighting"""
|
|
337
|
+
text = Text()
|
|
338
|
+
|
|
339
|
+
# Color-code by log level
|
|
340
|
+
if "ERROR" in line:
|
|
341
|
+
text.append(line, style="red")
|
|
342
|
+
elif "WARNING" in line or "WARN" in line:
|
|
343
|
+
text.append(line, style="yellow")
|
|
344
|
+
elif "INFO" in line:
|
|
345
|
+
text.append(line, style="green")
|
|
346
|
+
elif "DEBUG" in line:
|
|
347
|
+
text.append(line, style="dim blue")
|
|
348
|
+
else:
|
|
349
|
+
text.append(line, style="white")
|
|
350
|
+
|
|
351
|
+
return text
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _list_available_logs(logs_dir: Path, date_filter: Optional[str] = None):
|
|
355
|
+
"""List available log files"""
|
|
356
|
+
log_files = sorted(logs_dir.glob("mcli*.log"))
|
|
357
|
+
|
|
358
|
+
if not log_files:
|
|
359
|
+
console.print("ℹ️ No log files found", style="blue")
|
|
360
|
+
return
|
|
361
|
+
|
|
362
|
+
# Filter by date if specified
|
|
363
|
+
if date_filter:
|
|
364
|
+
log_files = [f for f in log_files if date_filter in f.name]
|
|
365
|
+
if not log_files:
|
|
366
|
+
console.print(f"ℹ️ No log files found for date: {date_filter}", style="blue")
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
console.print("📋 **Available log files:**", style="cyan")
|
|
370
|
+
|
|
371
|
+
for log_file in log_files:
|
|
372
|
+
size = log_file.stat().st_size
|
|
373
|
+
mtime = datetime.fromtimestamp(log_file.stat().st_mtime)
|
|
374
|
+
console.print(
|
|
375
|
+
f" • {log_file.name} ({_format_file_size(size)}, {mtime.strftime('%Y-%m-%d %H:%M')})"
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _search_log_file(log_file: Path, pattern: str, context: int) -> list:
|
|
380
|
+
"""Search for pattern in log file with context"""
|
|
381
|
+
matches = []
|
|
382
|
+
|
|
383
|
+
try:
|
|
384
|
+
with open(log_file, "r") as f:
|
|
385
|
+
lines = f.readlines()
|
|
386
|
+
|
|
387
|
+
for i, line in enumerate(lines):
|
|
388
|
+
if pattern.lower() in line.lower():
|
|
389
|
+
# Get context lines
|
|
390
|
+
start = max(0, i - context)
|
|
391
|
+
end = min(len(lines), i + context + 1)
|
|
392
|
+
|
|
393
|
+
context_lines = []
|
|
394
|
+
for j in range(start, end):
|
|
395
|
+
prefix = ">>> " if j == i else " "
|
|
396
|
+
style = "bright_yellow" if j == i else "dim"
|
|
397
|
+
context_lines.append(f"{prefix}{lines[j].rstrip()}")
|
|
398
|
+
|
|
399
|
+
matches.append("\n".join(context_lines) + "\n")
|
|
400
|
+
|
|
401
|
+
except Exception as e:
|
|
402
|
+
console.print(f"❌ Error searching {log_file.name}: {e}", style="red")
|
|
403
|
+
|
|
404
|
+
return matches
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _format_file_size(size_bytes: int) -> str:
|
|
408
|
+
"""Format file size in human readable format"""
|
|
409
|
+
for unit in ["B", "KB", "MB", "GB"]:
|
|
410
|
+
if size_bytes < 1024.0:
|
|
411
|
+
return f"{size_bytes:.1f} {unit}"
|
|
412
|
+
size_bytes /= 1024.0
|
|
413
|
+
return f"{size_bytes:.1f} TB"
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
# Register with main CLI
|
|
417
|
+
def register_logs_commands(cli):
|
|
418
|
+
"""Register logs commands with the main CLI"""
|
|
419
|
+
cli.add_command(logs_group)
|