traccia 0.1.2__py3-none-any.whl → 0.1.6__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.
- traccia/__init__.py +73 -0
- traccia/auto.py +748 -0
- traccia/auto_instrumentation.py +74 -0
- traccia/cli.py +349 -0
- traccia/config.py +699 -0
- traccia/context/__init__.py +33 -0
- traccia/context/context.py +67 -0
- traccia/context/propagators.py +283 -0
- traccia/errors.py +48 -0
- traccia/exporter/__init__.py +8 -0
- traccia/exporter/console_exporter.py +31 -0
- traccia/exporter/file_exporter.py +178 -0
- traccia/exporter/http_exporter.py +214 -0
- traccia/exporter/otlp_exporter.py +190 -0
- traccia/instrumentation/__init__.py +26 -0
- traccia/instrumentation/anthropic.py +92 -0
- traccia/instrumentation/decorator.py +263 -0
- traccia/instrumentation/fastapi.py +38 -0
- traccia/instrumentation/http_client.py +21 -0
- traccia/instrumentation/http_server.py +25 -0
- traccia/instrumentation/openai.py +358 -0
- traccia/instrumentation/requests.py +68 -0
- traccia/integrations/__init__.py +39 -0
- traccia/integrations/langchain/__init__.py +14 -0
- traccia/integrations/langchain/callback.py +418 -0
- traccia/integrations/langchain/utils.py +129 -0
- traccia/integrations/openai_agents/__init__.py +73 -0
- traccia/integrations/openai_agents/processor.py +262 -0
- traccia/pricing_config.py +58 -0
- traccia/processors/__init__.py +35 -0
- traccia/processors/agent_enricher.py +159 -0
- traccia/processors/batch_processor.py +140 -0
- traccia/processors/cost_engine.py +71 -0
- traccia/processors/cost_processor.py +70 -0
- traccia/processors/drop_policy.py +44 -0
- traccia/processors/logging_processor.py +31 -0
- traccia/processors/rate_limiter.py +223 -0
- traccia/processors/sampler.py +22 -0
- traccia/processors/token_counter.py +216 -0
- traccia/runtime_config.py +127 -0
- traccia/tracer/__init__.py +15 -0
- traccia/tracer/otel_adapter.py +577 -0
- traccia/tracer/otel_utils.py +24 -0
- traccia/tracer/provider.py +155 -0
- traccia/tracer/span.py +286 -0
- traccia/tracer/span_context.py +16 -0
- traccia/tracer/tracer.py +243 -0
- traccia/utils/__init__.py +19 -0
- traccia/utils/helpers.py +95 -0
- {traccia-0.1.2.dist-info → traccia-0.1.6.dist-info}/METADATA +72 -15
- traccia-0.1.6.dist-info/RECORD +55 -0
- traccia-0.1.6.dist-info/top_level.txt +1 -0
- traccia-0.1.2.dist-info/RECORD +0 -6
- traccia-0.1.2.dist-info/top_level.txt +0 -1
- {traccia-0.1.2.dist-info → traccia-0.1.6.dist-info}/WHEEL +0 -0
- {traccia-0.1.2.dist-info → traccia-0.1.6.dist-info}/entry_points.txt +0 -0
- {traccia-0.1.2.dist-info → traccia-0.1.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Optional auto-instrumentation for user-provided tool functions.
|
|
2
|
+
|
|
3
|
+
The SDK supports explicit instrumentation via the `@observe` decorator. This
|
|
4
|
+
module provides a best-effort utility for wrapping existing callables by dotted
|
|
5
|
+
path strings.
|
|
6
|
+
|
|
7
|
+
Accepted include formats:
|
|
8
|
+
- "package.module:function"
|
|
9
|
+
- "package.module.function"
|
|
10
|
+
|
|
11
|
+
This is intentionally conservative: it mainly targets module-level functions.
|
|
12
|
+
If a target can't be resolved or isn't callable, it's skipped.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import importlib
|
|
18
|
+
from typing import Iterable, Optional, Tuple
|
|
19
|
+
|
|
20
|
+
from traccia.instrumentation.decorator import observe
|
|
21
|
+
from traccia import runtime_config
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _split_target(spec: str) -> Optional[Tuple[str, str]]:
|
|
25
|
+
if not spec:
|
|
26
|
+
return None
|
|
27
|
+
if ":" in spec:
|
|
28
|
+
mod, attr = spec.split(":", 1)
|
|
29
|
+
return mod.strip(), attr.strip()
|
|
30
|
+
# Heuristic: last segment is the attribute name; rest is module
|
|
31
|
+
if "." not in spec:
|
|
32
|
+
return None
|
|
33
|
+
mod, attr = spec.rsplit(".", 1)
|
|
34
|
+
return mod.strip(), attr.strip()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def instrument_functions(include: Iterable[str]) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Wrap and replace functions referenced by include specs.
|
|
40
|
+
|
|
41
|
+
This is best-effort and will silently skip invalid entries.
|
|
42
|
+
"""
|
|
43
|
+
if not runtime_config.get_auto_instrument_tools():
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
for spec in include or []:
|
|
47
|
+
parsed = _split_target(str(spec))
|
|
48
|
+
if not parsed:
|
|
49
|
+
continue
|
|
50
|
+
module_name, attr_name = parsed
|
|
51
|
+
try:
|
|
52
|
+
mod = importlib.import_module(module_name)
|
|
53
|
+
except Exception:
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
target = getattr(mod, attr_name)
|
|
58
|
+
except Exception:
|
|
59
|
+
continue
|
|
60
|
+
if not callable(target):
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
# Avoid double wrapping
|
|
64
|
+
if getattr(target, "_agent_trace_observed", False):
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
wrapped = observe(name=f"{module_name}.{attr_name}", as_type="tool")(target)
|
|
68
|
+
setattr(wrapped, "_agent_trace_observed", True)
|
|
69
|
+
try:
|
|
70
|
+
setattr(mod, attr_name, wrapped)
|
|
71
|
+
except Exception:
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
|
traccia/cli.py
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""CLI for traccia utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import urllib.request
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from traccia.config import validate_config, load_config, find_config_file, ENV_VAR_MAPPING
|
|
12
|
+
from traccia.errors import ConfigError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _check(args) -> int:
|
|
16
|
+
"""Check connectivity to the configured exporter endpoint."""
|
|
17
|
+
# Load config to get endpoint
|
|
18
|
+
try:
|
|
19
|
+
config = load_config(config_file=args.config if hasattr(args, 'config') else None)
|
|
20
|
+
endpoint = args.endpoint or config.tracing.endpoint
|
|
21
|
+
|
|
22
|
+
if not endpoint:
|
|
23
|
+
print("❌ No endpoint configured.", file=sys.stderr)
|
|
24
|
+
print(" Set endpoint in traccia.toml or use --endpoint flag", file=sys.stderr)
|
|
25
|
+
return 1
|
|
26
|
+
|
|
27
|
+
print(f"🔍 Checking connectivity to {endpoint}...")
|
|
28
|
+
sys.stdout.flush() # Ensure output appears before any errors
|
|
29
|
+
|
|
30
|
+
# Try HEAD request first
|
|
31
|
+
req = urllib.request.Request(endpoint, method="HEAD")
|
|
32
|
+
if args.api_key or config.tracing.api_key:
|
|
33
|
+
api_key = args.api_key or config.tracing.api_key
|
|
34
|
+
req.add_header("Authorization", f"Bearer {api_key}")
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
with urllib.request.urlopen(req, timeout=5) as resp:
|
|
38
|
+
code = resp.getcode()
|
|
39
|
+
print(f"✅ Endpoint is reachable (HTTP {code})")
|
|
40
|
+
print("💡 Connectivity test successful!")
|
|
41
|
+
return 0
|
|
42
|
+
except urllib.error.HTTPError as e:
|
|
43
|
+
# HTTP 405 (Method Not Allowed), 400 (Bad Request), or 401 (Unauthorized)
|
|
44
|
+
# means the endpoint is reachable and responding - just doesn't like our test request
|
|
45
|
+
if e.code in [400, 401, 405]:
|
|
46
|
+
print(f"✅ Endpoint is reachable (HTTP {e.code})")
|
|
47
|
+
if e.code == 405:
|
|
48
|
+
print("💡 Endpoint only accepts specific methods (expected for OTLP endpoints)")
|
|
49
|
+
elif e.code == 401:
|
|
50
|
+
print("⚠️ Authentication required - check your API key")
|
|
51
|
+
elif e.code == 400:
|
|
52
|
+
print("💡 Endpoint rejected test payload (expected for OTLP endpoints)")
|
|
53
|
+
print("✅ Connectivity test successful!")
|
|
54
|
+
return 0
|
|
55
|
+
else:
|
|
56
|
+
# Other HTTP errors (404, 500, etc.) are actual failures
|
|
57
|
+
print(f"❌ HTTP Error {e.code}: {e.reason}", file=sys.stderr)
|
|
58
|
+
return 1
|
|
59
|
+
|
|
60
|
+
except ConfigError as exc:
|
|
61
|
+
print(f"❌ Configuration error: {exc}", file=sys.stderr)
|
|
62
|
+
return 1
|
|
63
|
+
except urllib.error.URLError as exc:
|
|
64
|
+
print(f"❌ Connection failed: {exc.reason}", file=sys.stderr)
|
|
65
|
+
print(" Make sure the endpoint is running and accessible", file=sys.stderr)
|
|
66
|
+
return 1
|
|
67
|
+
except Exception as exc:
|
|
68
|
+
print(f"❌ Unexpected error: {exc}", file=sys.stderr)
|
|
69
|
+
return 1
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _config_init(args) -> int:
|
|
73
|
+
"""Initialize traccia.toml config file in current directory."""
|
|
74
|
+
config_path = os.path.join(os.getcwd(), "traccia.toml")
|
|
75
|
+
|
|
76
|
+
# Check if file already exists
|
|
77
|
+
if os.path.exists(config_path) and not args.force:
|
|
78
|
+
print(f"❌ Config file already exists at {config_path}", file=sys.stderr)
|
|
79
|
+
print(" Use --force to overwrite", file=sys.stderr)
|
|
80
|
+
return 1
|
|
81
|
+
|
|
82
|
+
# Create config template with important parameters
|
|
83
|
+
config_template = """# Traccia SDK Configuration File
|
|
84
|
+
# Documentation: https://github.com/traccia-ai/traccia
|
|
85
|
+
|
|
86
|
+
[tracing]
|
|
87
|
+
# API key for authentication (required for SaaS, optional for open-source)
|
|
88
|
+
api_key = ""
|
|
89
|
+
|
|
90
|
+
# Endpoint URL for trace ingestion
|
|
91
|
+
# For local Tempo: endpoint = "http://localhost:4318/v1/traces"
|
|
92
|
+
# For local Jaeger: endpoint = "http://localhost:4318/v1/traces"
|
|
93
|
+
# endpoint = "http://localhost:4318/v1/traces"
|
|
94
|
+
|
|
95
|
+
# Sampling rate (0.0 to 1.0) - controls what percentage of traces are sent
|
|
96
|
+
sample_rate = 1.0
|
|
97
|
+
|
|
98
|
+
# Auto-start a root trace on init (default: true)
|
|
99
|
+
auto_start_trace = true
|
|
100
|
+
|
|
101
|
+
# Name for the auto-started root trace
|
|
102
|
+
auto_trace_name = "root"
|
|
103
|
+
|
|
104
|
+
# Use OTLP exporter (default: true)
|
|
105
|
+
# Set to false if using console or file exporter
|
|
106
|
+
use_otlp = true
|
|
107
|
+
|
|
108
|
+
# Service name (optional)
|
|
109
|
+
# service_name = "my-app"
|
|
110
|
+
|
|
111
|
+
[exporters]
|
|
112
|
+
# IMPORTANT: Only enable ONE exporter at a time (console, file, or OTLP via use_otlp)
|
|
113
|
+
|
|
114
|
+
# Enable console exporter for local debugging
|
|
115
|
+
enable_console = false
|
|
116
|
+
|
|
117
|
+
# Enable file exporter to write traces to local file
|
|
118
|
+
enable_file = false
|
|
119
|
+
|
|
120
|
+
# File path for file exporter (only used if enable_file = true)
|
|
121
|
+
file_exporter_path = "traces.jsonl"
|
|
122
|
+
|
|
123
|
+
# Reset/clear trace file on initialization
|
|
124
|
+
reset_trace_file = false
|
|
125
|
+
|
|
126
|
+
[instrumentation]
|
|
127
|
+
# Auto-patch popular libraries (OpenAI, Anthropic, requests)
|
|
128
|
+
enable_patching = true
|
|
129
|
+
|
|
130
|
+
# Count tokens for LLM calls
|
|
131
|
+
enable_token_counting = true
|
|
132
|
+
|
|
133
|
+
# Calculate costs for LLM calls
|
|
134
|
+
enable_costs = true
|
|
135
|
+
|
|
136
|
+
# Auto-instrument tool calls (experimental)
|
|
137
|
+
auto_instrument_tools = false
|
|
138
|
+
|
|
139
|
+
# Maximum number of tool spans to create
|
|
140
|
+
max_tool_spans = 100
|
|
141
|
+
|
|
142
|
+
# Maximum depth of nested spans
|
|
143
|
+
max_span_depth = 10
|
|
144
|
+
|
|
145
|
+
[rate_limiting]
|
|
146
|
+
# Maximum spans per second (uncomment to enable rate limiting)
|
|
147
|
+
# max_spans_per_second = 100.0
|
|
148
|
+
|
|
149
|
+
# Maximum queue size for buffered spans
|
|
150
|
+
max_queue_size = 5000
|
|
151
|
+
|
|
152
|
+
# Maximum milliseconds to block before dropping spans
|
|
153
|
+
max_block_ms = 100
|
|
154
|
+
|
|
155
|
+
# Maximum number of spans in a single export batch
|
|
156
|
+
max_export_batch_size = 512
|
|
157
|
+
|
|
158
|
+
# Delay in milliseconds between export batches
|
|
159
|
+
schedule_delay_millis = 5000
|
|
160
|
+
|
|
161
|
+
[runtime]
|
|
162
|
+
# Runtime metadata (optional - can be set per-session)
|
|
163
|
+
# session_id = ""
|
|
164
|
+
# user_id = ""
|
|
165
|
+
# tenant_id = ""
|
|
166
|
+
# project_id = ""
|
|
167
|
+
|
|
168
|
+
[logging]
|
|
169
|
+
# Enable debug logging
|
|
170
|
+
debug = false
|
|
171
|
+
|
|
172
|
+
# Enable span-level logging
|
|
173
|
+
enable_span_logging = false
|
|
174
|
+
|
|
175
|
+
[advanced]
|
|
176
|
+
# Maximum length for attribute values (uncomment to set limit)
|
|
177
|
+
# attr_truncation_limit = 1000
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
with open(config_path, 'w', encoding='utf-8') as f:
|
|
182
|
+
f.write(config_template)
|
|
183
|
+
print(f"✅ Created config file at {config_path}")
|
|
184
|
+
print("\n📝 Next steps:")
|
|
185
|
+
print(" 1. Edit the config file to add your API key and endpoint")
|
|
186
|
+
print(" 2. Run `traccia doctor` to validate your configuration")
|
|
187
|
+
print(" 3. Run `traccia check` to test connectivity")
|
|
188
|
+
return 0
|
|
189
|
+
except Exception as exc:
|
|
190
|
+
print(f"❌ Failed to create config file: {exc}", file=sys.stderr)
|
|
191
|
+
return 1
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _doctor(args) -> int:
|
|
195
|
+
"""Validate configuration and diagnose common issues."""
|
|
196
|
+
print("🩺 Running Traccia configuration diagnostics...\n")
|
|
197
|
+
|
|
198
|
+
issues_found = 0
|
|
199
|
+
|
|
200
|
+
# 1. Check for config file
|
|
201
|
+
config_file = None
|
|
202
|
+
if hasattr(args, 'config') and args.config:
|
|
203
|
+
config_file = args.config
|
|
204
|
+
if not os.path.exists(config_file):
|
|
205
|
+
print(f"❌ Specified config file not found: {config_file}")
|
|
206
|
+
issues_found += 1
|
|
207
|
+
return 1
|
|
208
|
+
else:
|
|
209
|
+
config_file = find_config_file()
|
|
210
|
+
if config_file:
|
|
211
|
+
print(f"✅ Found config file: {config_file}")
|
|
212
|
+
else:
|
|
213
|
+
print("⚠️ No config file found (checked ./traccia.toml and ~/.traccia/config.toml)")
|
|
214
|
+
print(" Run `traccia config init` to create one")
|
|
215
|
+
issues_found += 1
|
|
216
|
+
|
|
217
|
+
# 2. Check environment variables
|
|
218
|
+
print("\n📋 Environment variables:")
|
|
219
|
+
found_env_vars = []
|
|
220
|
+
for config_key, env_vars in ENV_VAR_MAPPING.items():
|
|
221
|
+
for env_var in env_vars:
|
|
222
|
+
if os.getenv(env_var):
|
|
223
|
+
found_env_vars.append(env_var)
|
|
224
|
+
print(f" ✅ {env_var} is set")
|
|
225
|
+
|
|
226
|
+
if not found_env_vars:
|
|
227
|
+
print(" ℹ️ No Traccia environment variables set")
|
|
228
|
+
|
|
229
|
+
# 3. Validate configuration
|
|
230
|
+
print("\n🔍 Validating configuration...")
|
|
231
|
+
is_valid, message, config = validate_config(config_file=config_file)
|
|
232
|
+
|
|
233
|
+
if is_valid:
|
|
234
|
+
print(f"✅ {message}")
|
|
235
|
+
|
|
236
|
+
# Print configuration summary
|
|
237
|
+
print("\n📊 Configuration summary:")
|
|
238
|
+
print(f" • API Key: {'✅ Set' if config.tracing.api_key else '❌ Not set'}")
|
|
239
|
+
print(f" • Endpoint: {config.tracing.endpoint or '❌ Not set'}")
|
|
240
|
+
print(f" • Sample Rate: {config.tracing.sample_rate}")
|
|
241
|
+
print(f" • OTLP Exporter: {'✅ Enabled' if config.tracing.use_otlp else '❌ Disabled'}")
|
|
242
|
+
print(f" • Console Exporter: {'✅ Enabled' if config.exporters.enable_console else '❌ Disabled'}")
|
|
243
|
+
print(f" • File Exporter: {'✅ Enabled' if config.exporters.enable_file else '❌ Disabled'}")
|
|
244
|
+
print(f" • Auto-patching: {'✅ Enabled' if config.instrumentation.enable_patching else '❌ Disabled'}")
|
|
245
|
+
|
|
246
|
+
# Check for potential issues
|
|
247
|
+
if config.tracing.use_otlp and not config.tracing.endpoint:
|
|
248
|
+
print("\n⚠️ Warning: OTLP exporter is enabled but no endpoint is configured")
|
|
249
|
+
issues_found += 1
|
|
250
|
+
|
|
251
|
+
if not config.tracing.use_otlp and not config.exporters.enable_console and not config.exporters.enable_file:
|
|
252
|
+
print("\n❌ Error: No exporter is enabled! Traces won't be exported anywhere.")
|
|
253
|
+
issues_found += 1
|
|
254
|
+
|
|
255
|
+
if config.rate_limiting.max_spans_per_second:
|
|
256
|
+
print(f"\n ℹ️ Rate limiting enabled: {config.rate_limiting.max_spans_per_second} spans/sec")
|
|
257
|
+
else:
|
|
258
|
+
print(f"❌ {message}")
|
|
259
|
+
issues_found += 1
|
|
260
|
+
|
|
261
|
+
# 4. Environment variable mapping reference
|
|
262
|
+
print("\n📖 Environment Variable Reference:")
|
|
263
|
+
print(" Common variables:")
|
|
264
|
+
print(" • TRACCIA_API_KEY or AGENT_DASHBOARD_API_KEY")
|
|
265
|
+
print(" • TRACCIA_ENDPOINT or AGENT_DASHBOARD_ENDPOINT")
|
|
266
|
+
print(" • TRACCIA_SAMPLE_RATE")
|
|
267
|
+
print(" • TRACCIA_DEBUG")
|
|
268
|
+
print("\n For a complete list, see: ENV_VAR_MAPPING in traccia/config.py")
|
|
269
|
+
|
|
270
|
+
# Summary
|
|
271
|
+
print("\n" + "="*60)
|
|
272
|
+
if issues_found == 0:
|
|
273
|
+
print("✅ No issues found! Your configuration looks good.")
|
|
274
|
+
print("\n💡 Tip: Run `traccia check` to test connectivity to your endpoint")
|
|
275
|
+
return 0
|
|
276
|
+
else:
|
|
277
|
+
print(f"⚠️ Found {issues_found} issue(s). Please review the messages above.")
|
|
278
|
+
return 1
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def main(argv=None) -> int:
|
|
282
|
+
"""Main CLI entry point."""
|
|
283
|
+
parser = argparse.ArgumentParser(
|
|
284
|
+
prog="traccia",
|
|
285
|
+
description="Traccia SDK - Production-ready tracing for AI agents",
|
|
286
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
287
|
+
epilog="""
|
|
288
|
+
Examples:
|
|
289
|
+
traccia config init Create a new config file
|
|
290
|
+
traccia doctor Validate configuration
|
|
291
|
+
traccia check Test connectivity to exporter
|
|
292
|
+
traccia check --endpoint URL Test specific endpoint
|
|
293
|
+
|
|
294
|
+
For more information, visit: https://github.com/traccia-ai/traccia
|
|
295
|
+
"""
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Global options
|
|
299
|
+
parser.add_argument(
|
|
300
|
+
"--config",
|
|
301
|
+
help="Path to config file (default: ./traccia.toml or ~/.traccia/config.toml)"
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
305
|
+
|
|
306
|
+
# Check command
|
|
307
|
+
check = sub.add_parser(
|
|
308
|
+
"check",
|
|
309
|
+
help="Verify connectivity to ingest endpoint",
|
|
310
|
+
description="Test connectivity to the configured exporter endpoint"
|
|
311
|
+
)
|
|
312
|
+
check.add_argument("--endpoint", help="Override endpoint URL")
|
|
313
|
+
check.add_argument("--api-key", help="API key for authentication")
|
|
314
|
+
check.set_defaults(func=_check)
|
|
315
|
+
|
|
316
|
+
# Config command
|
|
317
|
+
config = sub.add_parser(
|
|
318
|
+
"config",
|
|
319
|
+
help="Configuration management",
|
|
320
|
+
description="Manage Traccia configuration files"
|
|
321
|
+
)
|
|
322
|
+
config_sub = config.add_subparsers(dest="config_command", required=True)
|
|
323
|
+
|
|
324
|
+
config_init = config_sub.add_parser(
|
|
325
|
+
"init",
|
|
326
|
+
help="Create traccia.toml config file",
|
|
327
|
+
description="Initialize a new traccia.toml configuration file in the current directory"
|
|
328
|
+
)
|
|
329
|
+
config_init.add_argument(
|
|
330
|
+
"--force",
|
|
331
|
+
action="store_true",
|
|
332
|
+
help="Overwrite existing config file"
|
|
333
|
+
)
|
|
334
|
+
config_init.set_defaults(func=_config_init)
|
|
335
|
+
|
|
336
|
+
# Doctor command
|
|
337
|
+
doctor = sub.add_parser(
|
|
338
|
+
"doctor",
|
|
339
|
+
help="Validate configuration and diagnose issues",
|
|
340
|
+
description="Run diagnostics on your Traccia configuration"
|
|
341
|
+
)
|
|
342
|
+
doctor.set_defaults(func=_doctor)
|
|
343
|
+
|
|
344
|
+
args = parser.parse_args(argv)
|
|
345
|
+
return args.func(args)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
if __name__ == "__main__":
|
|
349
|
+
raise SystemExit(main())
|