ai-agent-inspector 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.
- agent_inspector/__init__.py +148 -0
- agent_inspector/cli.py +532 -0
- agent_inspector/ui/static/app.css +630 -0
- agent_inspector/ui/static/app.js +379 -0
- agent_inspector/ui/templates/index.html +441 -0
- ai_agent_inspector-1.0.0.dist-info/METADATA +1094 -0
- ai_agent_inspector-1.0.0.dist-info/RECORD +11 -0
- ai_agent_inspector-1.0.0.dist-info/WHEEL +5 -0
- ai_agent_inspector-1.0.0.dist-info/entry_points.txt +2 -0
- ai_agent_inspector-1.0.0.dist-info/licenses/LICENSE +21 -0
- ai_agent_inspector-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Inspector - Framework-agnostic observability for AI agents.
|
|
3
|
+
|
|
4
|
+
A lightweight, non-blocking tracing system for monitoring and debugging
|
|
5
|
+
AI agent reasoning, tool usage, and execution flow.
|
|
6
|
+
|
|
7
|
+
Example Usage:
|
|
8
|
+
>>> from agent_inspector import trace
|
|
9
|
+
>>>
|
|
10
|
+
>>> with trace.run("my_agent"):
|
|
11
|
+
... trace.llm(model="gpt-4", prompt="Hello", response="Hi there!")
|
|
12
|
+
... trace.tool(name="search", args={"q": "flights"}, result="5 flights found")
|
|
13
|
+
... trace.final(answer="I found 5 flights for you")
|
|
14
|
+
|
|
15
|
+
Example with LangChain:
|
|
16
|
+
>>> from agent_inspector.adapters.langchain_adapter import enable
|
|
17
|
+
>>>
|
|
18
|
+
>>> with enable() as callbacks:
|
|
19
|
+
... result = agent.run("Search for flights to New York")
|
|
20
|
+
>>> print(result)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
__version__ = version("ai-agent-inspector")
|
|
27
|
+
except PackageNotFoundError:
|
|
28
|
+
__version__ = "0.0.0.dev"
|
|
29
|
+
|
|
30
|
+
__author__ = "Agent Inspector Team"
|
|
31
|
+
__license__ = "MIT"
|
|
32
|
+
|
|
33
|
+
# Core tracing API
|
|
34
|
+
# Configuration
|
|
35
|
+
from .core.config import (
|
|
36
|
+
Profile,
|
|
37
|
+
TraceConfig,
|
|
38
|
+
get_config,
|
|
39
|
+
set_config,
|
|
40
|
+
)
|
|
41
|
+
from .core.exporters import CompositeExporter
|
|
42
|
+
from .core.interfaces import Exporter, Sampler
|
|
43
|
+
from .core.trace import (
|
|
44
|
+
Trace,
|
|
45
|
+
TraceContext,
|
|
46
|
+
error,
|
|
47
|
+
final,
|
|
48
|
+
get_trace,
|
|
49
|
+
llm,
|
|
50
|
+
memory_read,
|
|
51
|
+
memory_write,
|
|
52
|
+
run,
|
|
53
|
+
set_trace,
|
|
54
|
+
tool,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# LangChain adapter
|
|
58
|
+
try:
|
|
59
|
+
from .adapters.langchain_adapter import enable as enable_langchain
|
|
60
|
+
except ImportError:
|
|
61
|
+
# LangChain is optional
|
|
62
|
+
enable_langchain = None
|
|
63
|
+
|
|
64
|
+
# API server
|
|
65
|
+
try:
|
|
66
|
+
from .api.main import get_api_server, run_server
|
|
67
|
+
except ImportError:
|
|
68
|
+
# FastAPI is optional
|
|
69
|
+
run_server = None
|
|
70
|
+
get_api_server = None
|
|
71
|
+
|
|
72
|
+
# Event types
|
|
73
|
+
from .core.events import (
|
|
74
|
+
BaseEvent,
|
|
75
|
+
ErrorEvent,
|
|
76
|
+
EventStatus,
|
|
77
|
+
EventType,
|
|
78
|
+
FinalAnswerEvent,
|
|
79
|
+
LLMCallEvent,
|
|
80
|
+
MemoryReadEvent,
|
|
81
|
+
MemoryWriteEvent,
|
|
82
|
+
RunEndEvent,
|
|
83
|
+
RunStartEvent,
|
|
84
|
+
ToolCallEvent,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
__all__ = [
|
|
88
|
+
# Version
|
|
89
|
+
"__version__",
|
|
90
|
+
# Core tracing
|
|
91
|
+
"Trace",
|
|
92
|
+
"TraceContext",
|
|
93
|
+
"trace", # Convenience alias for get_trace()
|
|
94
|
+
"run",
|
|
95
|
+
"llm",
|
|
96
|
+
"tool",
|
|
97
|
+
"memory_read",
|
|
98
|
+
"memory_write",
|
|
99
|
+
"error",
|
|
100
|
+
"final",
|
|
101
|
+
"get_trace",
|
|
102
|
+
"set_trace",
|
|
103
|
+
# Configuration
|
|
104
|
+
"TraceConfig",
|
|
105
|
+
"Profile",
|
|
106
|
+
"get_config",
|
|
107
|
+
"set_config",
|
|
108
|
+
# Extensibility
|
|
109
|
+
"Exporter",
|
|
110
|
+
"Sampler",
|
|
111
|
+
"CompositeExporter",
|
|
112
|
+
# Event types
|
|
113
|
+
"EventType",
|
|
114
|
+
"EventStatus",
|
|
115
|
+
"BaseEvent",
|
|
116
|
+
"RunStartEvent",
|
|
117
|
+
"RunEndEvent",
|
|
118
|
+
"LLMCallEvent",
|
|
119
|
+
"ToolCallEvent",
|
|
120
|
+
"MemoryReadEvent",
|
|
121
|
+
"MemoryWriteEvent",
|
|
122
|
+
"ErrorEvent",
|
|
123
|
+
"FinalAnswerEvent",
|
|
124
|
+
# Adapters (optional)
|
|
125
|
+
"enable_langchain",
|
|
126
|
+
# API server (optional)
|
|
127
|
+
"run_server",
|
|
128
|
+
"get_api_server",
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# Convenience property for global trace instance
|
|
133
|
+
class _GlobalTrace:
|
|
134
|
+
"""Convenience wrapper for global trace instance."""
|
|
135
|
+
|
|
136
|
+
def __getattr__(self, name):
|
|
137
|
+
"""Proxy all attribute access to global trace instance."""
|
|
138
|
+
global_trace = get_trace()
|
|
139
|
+
return getattr(global_trace, name)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
trace = _GlobalTrace()
|
|
143
|
+
"""Global trace instance for convenience usage.
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
>>> trace.run("my_agent")
|
|
147
|
+
>>> trace.llm(model="gpt-4", prompt="...", response="...")
|
|
148
|
+
"""
|
agent_inspector/cli.py
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for Agent Inspector.
|
|
3
|
+
|
|
4
|
+
Provides commands for:
|
|
5
|
+
- Starting the API server
|
|
6
|
+
- Viewing database statistics
|
|
7
|
+
- Pruning old trace data
|
|
8
|
+
- Exporting runs to JSON
|
|
9
|
+
- Managing configuration
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
import sys
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
from . import __version__
|
|
19
|
+
from .api.main import run_server
|
|
20
|
+
from .core.config import Profile, TraceConfig, get_config, set_config
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def setup_logging(log_level: str = "INFO", log_file: Optional[str] = None):
|
|
24
|
+
"""
|
|
25
|
+
Setup logging configuration.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
log_level: Logging level (DEBUG, INFO, WARNING, ERROR).
|
|
29
|
+
log_file: Optional path to log file. If None, logs to stdout.
|
|
30
|
+
"""
|
|
31
|
+
logging.basicConfig(
|
|
32
|
+
level=getattr(logging, log_level.upper()),
|
|
33
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
34
|
+
filename=log_file,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def cmd_server(args):
|
|
39
|
+
"""Start the API server."""
|
|
40
|
+
print("🚀 Starting Agent Inspector API server...")
|
|
41
|
+
|
|
42
|
+
# Setup logging
|
|
43
|
+
setup_logging(log_level=args.log_level, log_file=args.log_file)
|
|
44
|
+
|
|
45
|
+
# Get or create configuration
|
|
46
|
+
config = get_config()
|
|
47
|
+
|
|
48
|
+
# Override with CLI arguments
|
|
49
|
+
if args.host:
|
|
50
|
+
config.api_host = args.host
|
|
51
|
+
if args.port:
|
|
52
|
+
config.api_port = args.port
|
|
53
|
+
|
|
54
|
+
# Set the config
|
|
55
|
+
set_config(config)
|
|
56
|
+
|
|
57
|
+
# Start server
|
|
58
|
+
print(f"📍 Server running on http://{config.api_host}:{config.api_port}")
|
|
59
|
+
print(f"🌐 UI available at http://{config.api_host}:{config.api_port}/ui")
|
|
60
|
+
print(f"📚 API docs at http://{config.api_host}:{config.api_port}/docs")
|
|
61
|
+
print("\nPress Ctrl+C to stop the server\n")
|
|
62
|
+
|
|
63
|
+
run_server(host=config.api_host, port=config.api_port)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def cmd_stats(args):
|
|
67
|
+
"""View database statistics."""
|
|
68
|
+
from .storage.database import Database
|
|
69
|
+
|
|
70
|
+
print("📊 Agent Inspector Statistics\n")
|
|
71
|
+
|
|
72
|
+
# Setup logging
|
|
73
|
+
setup_logging(log_level="WARNING")
|
|
74
|
+
|
|
75
|
+
# Get configuration
|
|
76
|
+
config = get_config()
|
|
77
|
+
|
|
78
|
+
# Initialize database
|
|
79
|
+
db = Database(config)
|
|
80
|
+
db.initialize()
|
|
81
|
+
|
|
82
|
+
# Get stats
|
|
83
|
+
stats = db.get_stats()
|
|
84
|
+
|
|
85
|
+
if not stats:
|
|
86
|
+
print("❌ No statistics available")
|
|
87
|
+
return 1
|
|
88
|
+
|
|
89
|
+
# Display stats
|
|
90
|
+
print(f"Total Runs: {stats.get('total_runs', 0)}")
|
|
91
|
+
print(f" - Running: {stats.get('running_runs', 0)}")
|
|
92
|
+
print(f" - Completed: {stats.get('completed_runs', 0)}")
|
|
93
|
+
print(f" - Failed: {stats.get('failed_runs', 0)}")
|
|
94
|
+
print(f"\nTotal Steps (Events): {stats.get('total_steps', 0)}")
|
|
95
|
+
print(f"Database Size: {stats.get('db_size_bytes', 0):,} bytes")
|
|
96
|
+
print(f"\nRecent Activity (24h): {stats.get('recent_runs_24h', 0)} runs")
|
|
97
|
+
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def cmd_prune(args):
|
|
102
|
+
"""Prune old trace data."""
|
|
103
|
+
from .storage.database import Database
|
|
104
|
+
|
|
105
|
+
print("🧹 Pruning old trace data...")
|
|
106
|
+
|
|
107
|
+
# Setup logging
|
|
108
|
+
setup_logging(log_level=args.log_level or "INFO")
|
|
109
|
+
|
|
110
|
+
# Get configuration
|
|
111
|
+
config = get_config()
|
|
112
|
+
|
|
113
|
+
# Override with CLI arguments
|
|
114
|
+
if args.retention_days is not None:
|
|
115
|
+
config.retention_days = args.retention_days
|
|
116
|
+
|
|
117
|
+
# Initialize database
|
|
118
|
+
db = Database(config)
|
|
119
|
+
db.initialize()
|
|
120
|
+
|
|
121
|
+
# Prune old runs
|
|
122
|
+
deleted_count = db.prune_old_runs(retention_days=config.retention_days)
|
|
123
|
+
|
|
124
|
+
if deleted_count > 0:
|
|
125
|
+
print(f"✅ Pruned {deleted_count} old runs")
|
|
126
|
+
|
|
127
|
+
# Optionally vacuum to reclaim space
|
|
128
|
+
if args.vacuum:
|
|
129
|
+
print("💾 Running VACUUM to reclaim disk space...")
|
|
130
|
+
if db.vacuum():
|
|
131
|
+
print("✅ VACUUM completed")
|
|
132
|
+
else:
|
|
133
|
+
print("⚠️ VACUUM failed")
|
|
134
|
+
else:
|
|
135
|
+
print("ℹ️ No runs to prune")
|
|
136
|
+
|
|
137
|
+
return 0
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def cmd_vacuum(args):
|
|
141
|
+
"""Run VACUUM to reclaim disk space."""
|
|
142
|
+
from .storage.database import Database
|
|
143
|
+
|
|
144
|
+
print("💾 Running VACUUM to reclaim disk space...")
|
|
145
|
+
|
|
146
|
+
# Setup logging
|
|
147
|
+
setup_logging(log_level="WARNING")
|
|
148
|
+
|
|
149
|
+
# Get configuration
|
|
150
|
+
config = get_config()
|
|
151
|
+
|
|
152
|
+
# Initialize database
|
|
153
|
+
db = Database(config)
|
|
154
|
+
db.initialize()
|
|
155
|
+
|
|
156
|
+
# Run vacuum
|
|
157
|
+
if db.vacuum():
|
|
158
|
+
print("✅ VACUUM completed successfully")
|
|
159
|
+
return 0
|
|
160
|
+
else:
|
|
161
|
+
print("❌ VACUUM failed")
|
|
162
|
+
return 1
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def cmd_backup(args):
|
|
166
|
+
"""Create a database backup."""
|
|
167
|
+
from .storage.database import Database
|
|
168
|
+
|
|
169
|
+
print(f"💾 Creating backup to {args.backup_path}...")
|
|
170
|
+
|
|
171
|
+
# Setup logging
|
|
172
|
+
setup_logging(log_level="INFO")
|
|
173
|
+
|
|
174
|
+
# Get configuration
|
|
175
|
+
config = get_config()
|
|
176
|
+
|
|
177
|
+
# Initialize database
|
|
178
|
+
db = Database(config)
|
|
179
|
+
db.initialize()
|
|
180
|
+
|
|
181
|
+
# Create backup
|
|
182
|
+
if db.backup(args.backup_path):
|
|
183
|
+
print(f"✅ Backup created at {args.backup_path}")
|
|
184
|
+
return 0
|
|
185
|
+
else:
|
|
186
|
+
print("❌ Backup failed")
|
|
187
|
+
return 1
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def cmd_export(args):
|
|
191
|
+
"""Export run(s) to JSON file or stdout."""
|
|
192
|
+
from .processing.pipeline import ProcessingPipeline
|
|
193
|
+
from .storage.database import Database
|
|
194
|
+
|
|
195
|
+
if not getattr(args, "all_runs", False) and not getattr(args, "run_id", None):
|
|
196
|
+
print("Error: provide run_id or use --all", file=sys.stderr)
|
|
197
|
+
return 1
|
|
198
|
+
|
|
199
|
+
setup_logging(log_level="INFO")
|
|
200
|
+
config = get_config()
|
|
201
|
+
db = Database(config)
|
|
202
|
+
db.initialize()
|
|
203
|
+
pipeline = ProcessingPipeline(config)
|
|
204
|
+
|
|
205
|
+
def export_one(run_id: str) -> Optional[dict]:
|
|
206
|
+
run = db.get_run(run_id)
|
|
207
|
+
if not run:
|
|
208
|
+
print(f"Run {run_id} not found", file=sys.stderr)
|
|
209
|
+
return None
|
|
210
|
+
timeline = db.get_run_timeline(run_id=run_id, include_data=True)
|
|
211
|
+
for event in timeline:
|
|
212
|
+
if event.get("data"):
|
|
213
|
+
try:
|
|
214
|
+
event["data"] = pipeline.reverse(event["data"])
|
|
215
|
+
except Exception:
|
|
216
|
+
event["data"] = None
|
|
217
|
+
return {"run": dict(run), "timeline": timeline}
|
|
218
|
+
|
|
219
|
+
if getattr(args, "all_runs", False):
|
|
220
|
+
runs = db.list_runs(limit=args.limit or 1000, offset=0)
|
|
221
|
+
payload = []
|
|
222
|
+
for r in runs:
|
|
223
|
+
rid = r.get("id")
|
|
224
|
+
if rid:
|
|
225
|
+
one = export_one(rid)
|
|
226
|
+
if one:
|
|
227
|
+
payload.append(one)
|
|
228
|
+
data = {"runs": payload, "total": len(payload)}
|
|
229
|
+
else:
|
|
230
|
+
one = export_one(args.run_id)
|
|
231
|
+
if not one:
|
|
232
|
+
return 1
|
|
233
|
+
data = one
|
|
234
|
+
|
|
235
|
+
out = args.output
|
|
236
|
+
json_str = json.dumps(data, indent=2, default=str)
|
|
237
|
+
if out:
|
|
238
|
+
with open(out, "w") as f:
|
|
239
|
+
f.write(json_str)
|
|
240
|
+
print(f"✅ Exported to {out}")
|
|
241
|
+
else:
|
|
242
|
+
print(json_str)
|
|
243
|
+
return 0
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def cmd_config(args):
|
|
247
|
+
"""View or set configuration."""
|
|
248
|
+
config = get_config()
|
|
249
|
+
|
|
250
|
+
if args.show:
|
|
251
|
+
# Show current configuration
|
|
252
|
+
print("⚙️ Current Configuration\n")
|
|
253
|
+
print(config.to_json())
|
|
254
|
+
elif args.profile:
|
|
255
|
+
# Set profile
|
|
256
|
+
try:
|
|
257
|
+
profile = Profile(args.profile.lower())
|
|
258
|
+
|
|
259
|
+
if profile == Profile.PRODUCTION:
|
|
260
|
+
new_config = TraceConfig.production()
|
|
261
|
+
elif profile == Profile.DEVELOPMENT:
|
|
262
|
+
new_config = TraceConfig.development()
|
|
263
|
+
elif profile == Profile.DEBUG:
|
|
264
|
+
new_config = TraceConfig.debug()
|
|
265
|
+
else:
|
|
266
|
+
print(f"❌ Unknown profile: {args.profile}")
|
|
267
|
+
return 1
|
|
268
|
+
|
|
269
|
+
set_config(new_config)
|
|
270
|
+
print(f"✅ Configuration set to {profile.value} profile")
|
|
271
|
+
return 0
|
|
272
|
+
except ValueError as e:
|
|
273
|
+
print(f"❌ Invalid profile: {e}")
|
|
274
|
+
return 1
|
|
275
|
+
else:
|
|
276
|
+
# Show brief configuration
|
|
277
|
+
print("⚙️ Agent Inspector Configuration\n")
|
|
278
|
+
print(f"Sample Rate: {config.sample_rate * 100:.1f}%")
|
|
279
|
+
print(f"Only on Error: {config.only_on_error}")
|
|
280
|
+
print(f"Encryption: {'Enabled' if config.encryption_enabled else 'Disabled'}")
|
|
281
|
+
print(f"Compression: {'Enabled' if config.compression_enabled else 'Disabled'}")
|
|
282
|
+
print(f"API Host: {config.api_host}:{config.api_port}")
|
|
283
|
+
print(f"Database: {config.db_path}")
|
|
284
|
+
|
|
285
|
+
return 0
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def cmd_init(args):
|
|
289
|
+
"""Initialize Agent Inspector."""
|
|
290
|
+
print("🔧 Initializing Agent Inspector...")
|
|
291
|
+
|
|
292
|
+
# Create default configuration
|
|
293
|
+
config = TraceConfig()
|
|
294
|
+
|
|
295
|
+
# Override with arguments
|
|
296
|
+
if args.profile:
|
|
297
|
+
try:
|
|
298
|
+
profile = Profile(args.profile.lower())
|
|
299
|
+
if profile == Profile.PRODUCTION:
|
|
300
|
+
config = TraceConfig.production()
|
|
301
|
+
elif profile == Profile.DEVELOPMENT:
|
|
302
|
+
config = TraceConfig.development()
|
|
303
|
+
elif profile == Profile.DEBUG:
|
|
304
|
+
config = TraceConfig.debug()
|
|
305
|
+
except ValueError as e:
|
|
306
|
+
print(f"❌ Invalid profile: {e}")
|
|
307
|
+
return 1
|
|
308
|
+
|
|
309
|
+
# Initialize database
|
|
310
|
+
from .storage.database import Database
|
|
311
|
+
|
|
312
|
+
db = Database(config)
|
|
313
|
+
db.initialize()
|
|
314
|
+
|
|
315
|
+
print("✅ Agent Inspector initialized!")
|
|
316
|
+
print(f"\n📍 Database: {config.db_path}")
|
|
317
|
+
print(f"📊 Sample Rate: {config.sample_rate * 100:.1f}%")
|
|
318
|
+
print(f"🔒 Encryption: {'Enabled' if config.encryption_enabled else 'Disabled'}")
|
|
319
|
+
|
|
320
|
+
print("\n💡 Next steps:")
|
|
321
|
+
print(" - Start API server: agent-inspector server")
|
|
322
|
+
print(" - Run examples: python examples/basic_tracing.py")
|
|
323
|
+
print(" - View UI: http://localhost:8000/ui")
|
|
324
|
+
|
|
325
|
+
return 0
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def main():
|
|
329
|
+
"""Main entry point for CLI."""
|
|
330
|
+
parser = argparse.ArgumentParser(
|
|
331
|
+
prog="agent-inspector",
|
|
332
|
+
description="Framework-agnostic observability for AI agents",
|
|
333
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
334
|
+
epilog="""
|
|
335
|
+
Examples:
|
|
336
|
+
# Start API server
|
|
337
|
+
agent-inspector server
|
|
338
|
+
|
|
339
|
+
# Start server on custom port
|
|
340
|
+
agent-inspector server --port 8080
|
|
341
|
+
|
|
342
|
+
# View statistics
|
|
343
|
+
agent-inspector stats
|
|
344
|
+
|
|
345
|
+
# Prune data older than 30 days
|
|
346
|
+
agent-inspector prune --retention-days 30
|
|
347
|
+
|
|
348
|
+
# Set development profile
|
|
349
|
+
agent-inspector config --profile development
|
|
350
|
+
|
|
351
|
+
# Initialize with debug profile
|
|
352
|
+
agent-inspector init --profile debug
|
|
353
|
+
""",
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
parser.add_argument(
|
|
357
|
+
"--version",
|
|
358
|
+
action="version",
|
|
359
|
+
version=f"%(prog)s {__version__}",
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
363
|
+
|
|
364
|
+
# Server command
|
|
365
|
+
server_parser = subparsers.add_parser(
|
|
366
|
+
"server",
|
|
367
|
+
help="Start the API server",
|
|
368
|
+
description="Start the FastAPI server for serving trace data and UI",
|
|
369
|
+
)
|
|
370
|
+
server_parser.add_argument(
|
|
371
|
+
"--host",
|
|
372
|
+
type=str,
|
|
373
|
+
help="Host to bind to (default: 127.0.0.1)",
|
|
374
|
+
)
|
|
375
|
+
server_parser.add_argument(
|
|
376
|
+
"--port",
|
|
377
|
+
type=int,
|
|
378
|
+
help="Port to bind to (default: 8000)",
|
|
379
|
+
)
|
|
380
|
+
server_parser.add_argument(
|
|
381
|
+
"--log-level",
|
|
382
|
+
type=str,
|
|
383
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
|
|
384
|
+
default="INFO",
|
|
385
|
+
help="Log level (default: INFO)",
|
|
386
|
+
)
|
|
387
|
+
server_parser.add_argument(
|
|
388
|
+
"--log-file",
|
|
389
|
+
type=str,
|
|
390
|
+
help="Path to log file (default: stdout)",
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# Stats command
|
|
394
|
+
_stats_parser = subparsers.add_parser(
|
|
395
|
+
"stats",
|
|
396
|
+
help="View database statistics",
|
|
397
|
+
description="Display statistics about stored trace data",
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# Prune command
|
|
401
|
+
prune_parser = subparsers.add_parser(
|
|
402
|
+
"prune",
|
|
403
|
+
help="Prune old trace data",
|
|
404
|
+
description="Delete trace data older than the retention period",
|
|
405
|
+
)
|
|
406
|
+
prune_parser.add_argument(
|
|
407
|
+
"--retention-days",
|
|
408
|
+
type=int,
|
|
409
|
+
help="Retention period in days (default: from config)",
|
|
410
|
+
)
|
|
411
|
+
prune_parser.add_argument(
|
|
412
|
+
"--vacuum",
|
|
413
|
+
action="store_true",
|
|
414
|
+
help="Run VACUUM after pruning to reclaim disk space",
|
|
415
|
+
)
|
|
416
|
+
prune_parser.add_argument(
|
|
417
|
+
"--log-level",
|
|
418
|
+
type=str,
|
|
419
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
|
|
420
|
+
help="Log level",
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
# Vacuum command
|
|
424
|
+
_vacuum_parser = subparsers.add_parser(
|
|
425
|
+
"vacuum",
|
|
426
|
+
help="Run VACUUM to reclaim disk space",
|
|
427
|
+
description="Run SQLite VACUUM to reclaim disk space",
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
# Backup command
|
|
431
|
+
backup_parser = subparsers.add_parser(
|
|
432
|
+
"backup",
|
|
433
|
+
help="Create database backup",
|
|
434
|
+
description="Create a backup of the SQLite database",
|
|
435
|
+
)
|
|
436
|
+
backup_parser.add_argument(
|
|
437
|
+
"backup_path",
|
|
438
|
+
type=str,
|
|
439
|
+
help="Path where backup should be saved",
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# Export command
|
|
443
|
+
export_parser = subparsers.add_parser(
|
|
444
|
+
"export",
|
|
445
|
+
help="Export run(s) to JSON",
|
|
446
|
+
description="Export a run or all runs to JSON (run metadata + timeline with decoded event data)",
|
|
447
|
+
)
|
|
448
|
+
export_parser.add_argument(
|
|
449
|
+
"run_id",
|
|
450
|
+
nargs="?",
|
|
451
|
+
type=str,
|
|
452
|
+
help="Run ID to export (omit if using --all)",
|
|
453
|
+
)
|
|
454
|
+
export_parser.add_argument(
|
|
455
|
+
"--all",
|
|
456
|
+
dest="all_runs",
|
|
457
|
+
action="store_true",
|
|
458
|
+
help="Export all runs",
|
|
459
|
+
)
|
|
460
|
+
export_parser.add_argument(
|
|
461
|
+
"--limit",
|
|
462
|
+
type=int,
|
|
463
|
+
default=1000,
|
|
464
|
+
help="Max runs to export when using --all (default: 1000)",
|
|
465
|
+
)
|
|
466
|
+
export_parser.add_argument(
|
|
467
|
+
"--output",
|
|
468
|
+
"-o",
|
|
469
|
+
type=str,
|
|
470
|
+
default=None,
|
|
471
|
+
help="Output file path (default: stdout)",
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
# Config command
|
|
475
|
+
config_parser = subparsers.add_parser(
|
|
476
|
+
"config",
|
|
477
|
+
help="View or set configuration",
|
|
478
|
+
description="View current configuration or set a profile preset",
|
|
479
|
+
)
|
|
480
|
+
config_parser.add_argument(
|
|
481
|
+
"--show",
|
|
482
|
+
action="store_true",
|
|
483
|
+
help="Show full configuration",
|
|
484
|
+
)
|
|
485
|
+
config_parser.add_argument(
|
|
486
|
+
"--profile",
|
|
487
|
+
type=str,
|
|
488
|
+
choices=["production", "development", "debug"],
|
|
489
|
+
help="Set configuration profile (production, development, debug)",
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
# Init command
|
|
493
|
+
init_parser = subparsers.add_parser(
|
|
494
|
+
"init",
|
|
495
|
+
help="Initialize Agent Inspector",
|
|
496
|
+
description="Initialize Agent Inspector with default configuration",
|
|
497
|
+
)
|
|
498
|
+
init_parser.add_argument(
|
|
499
|
+
"--profile",
|
|
500
|
+
type=str,
|
|
501
|
+
choices=["production", "development", "debug"],
|
|
502
|
+
help="Configuration profile to use",
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
# Parse arguments
|
|
506
|
+
args = parser.parse_args()
|
|
507
|
+
|
|
508
|
+
# Execute command
|
|
509
|
+
if args.command == "server":
|
|
510
|
+
return cmd_server(args)
|
|
511
|
+
elif args.command == "stats":
|
|
512
|
+
return cmd_stats(args)
|
|
513
|
+
elif args.command == "prune":
|
|
514
|
+
return cmd_prune(args)
|
|
515
|
+
elif args.command == "vacuum":
|
|
516
|
+
return cmd_vacuum(args)
|
|
517
|
+
elif args.command == "backup":
|
|
518
|
+
return cmd_backup(args)
|
|
519
|
+
elif args.command == "export":
|
|
520
|
+
return cmd_export(args)
|
|
521
|
+
elif args.command == "config":
|
|
522
|
+
return cmd_config(args)
|
|
523
|
+
elif args.command == "init":
|
|
524
|
+
return cmd_init(args)
|
|
525
|
+
else:
|
|
526
|
+
# No command specified, show help
|
|
527
|
+
parser.print_help()
|
|
528
|
+
return 0
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
if __name__ == "__main__":
|
|
532
|
+
sys.exit(main() or 0)
|