ma-agents 3.13.2 → 3.14.0
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.
- package/lib/agents.js +7 -0
- package/lib/bmad-cache/bmb/.claude-plugin/marketplace.json +3 -3
- package/lib/bmad-cache/bmb/_git_preserved/index +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-b388526edf307cf56f8e65673466cff074105abf.idx +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/{pack-8f8b045fef5af6911495cf3b2a89f1ed75e120f7.pack → pack-b388526edf307cf56f8e65673466cff074105abf.pack} +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-b388526edf307cf56f8e65673466cff074105abf.rev +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/packed-refs +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/bmb/package-lock.json +2 -2
- package/lib/bmad-cache/bmb/package.json +1 -1
- package/lib/bmad-cache/bmb/samples/bmad-agent-code-coach/scripts/wake.py +78 -0
- package/lib/bmad-cache/bmb/samples/bmad-agent-creative-muse/scripts/wake.py +78 -0
- package/lib/bmad-cache/bmb/samples/bmad-agent-sentinel/scripts/wake.py +78 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/customize-template.toml +3 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/init-sanctum-template.py +11 -5
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/report-shell.html +1073 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/wake-template.py +78 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/customize.toml +48 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/count_tokens.py +78 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass.py +258 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/process-template.py +20 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/render_report.py +387 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/assets/adapter-claude-code.json +9 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/aggregate_benchmark.py +236 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/memlog.py +197 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/run_evals.py +513 -390
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/run_triggers.py +347 -251
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/tests/test_env_isolation.py +79 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/tests/test_trigger_detection.py +123 -0
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/tests/test-validate-module.py +82 -4
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/validate-module.py +55 -11
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/assets/customize-template.toml +3 -5
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/assets/report-shell.html +860 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/customize.toml +59 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/count_tokens.py +77 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/init_skill.py +132 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/prepass-prompt-metrics.py +186 -221
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/quick_validate.py +117 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/render_report.py +387 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/tests/test_canon_sync.py +46 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/tests/test_count_tokens.py +180 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/tests/test_render_report.py +171 -0
- package/lib/bmad-cache/cache-manifest.json +4 -4
- package/lib/bmad-extension/.claude-plugin/marketplace.json.template +1 -1
- package/lib/bmad-extension-plugin/.claude-plugin/marketplace.json +2 -2
- package/lib/bmad.js +6 -1
- package/lib/templates/instruction-block-git.template.md +25 -25
- package/lib/templates/instruction-block-onprem.template.md +86 -86
- package/lib/templates/instruction-block-universal.template.md +29 -29
- package/package.json +3 -3
- package/lib/.bmad-extension-plugin.build-22612-1782454014750/.claude-plugin/marketplace.json +0 -109
- package/lib/.bmad-extension-plugin.build-22612-1782454014750/skills/module-help.csv +0 -62
- package/lib/.bmad-extension-plugin.build-22612-1782454014750/skills/module.yaml +0 -20
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-8f8b045fef5af6911495cf3b2a89f1ed75e120f7.idx +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-8f8b045fef5af6911495cf3b2a89f1ed75e120f7.rev +0 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/references/sample-init-sanctum.py +0 -274
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/generate-html-report.py +0 -534
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-execution-deps.py +0 -337
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-prompt-metrics.py +0 -425
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-sanctum-architecture.py +0 -385
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-structure-capabilities.py +0 -482
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/assets/Dockerfile +0 -29
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/docker_setup.py +0 -115
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/generate_report.py +0 -184
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/pty_runner.py +0 -171
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/utils.py +0 -260
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/extract-report-json.py +0 -287
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/generate-html-report.py +0 -588
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/prepass-execution-deps.py +0 -288
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Deterministic extraction of report-data.json from analysis outputs.
|
|
3
|
-
|
|
4
|
-
Reads scanner outputs (markdown + JSON) and extracts structured data without
|
|
5
|
-
LLM synthesis. Ensures no data loss and completes in <10 seconds.
|
|
6
|
-
|
|
7
|
-
Usage:
|
|
8
|
-
python3 extract-report-json.py {skill-path} {quality-report-dir} -o {output-file}
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
import argparse
|
|
14
|
-
import json
|
|
15
|
-
import re
|
|
16
|
-
import sys
|
|
17
|
-
from datetime import datetime, timezone
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def extract_section(content: str, section_name: str, level: int = 2) -> str | None:
|
|
22
|
-
"""Extract a section from markdown by heading name."""
|
|
23
|
-
pattern = r'^#{' + str(level) + r'}\s+' + re.escape(section_name) + r'\s*\n(.*?)(?=^#{1,' + str(level) + r'}\s|\Z)'
|
|
24
|
-
match = re.search(pattern, content, re.MULTILINE | re.DOTALL)
|
|
25
|
-
return match.group(1).strip() if match else None
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def extract_journeys(content: str) -> list[dict]:
|
|
29
|
-
"""Extract user journey archetypes from enhancement-analysis.md."""
|
|
30
|
-
journeys = []
|
|
31
|
-
# Match ### N. {Name}: {Description}
|
|
32
|
-
pattern = r'^###\s+\d+\.\s+([^:]+):\s+(.+?)(?=^###|\Z)'
|
|
33
|
-
for match in re.finditer(pattern, content, re.MULTILINE | re.DOTALL):
|
|
34
|
-
name = match.group(1).strip()
|
|
35
|
-
section = match.group(2)
|
|
36
|
-
|
|
37
|
-
# Extract narrative (after "Narrative." or "Narrative\n")
|
|
38
|
-
narrative_match = re.search(r'(?:Narrative[:.]\s*)?([^\n]+(?:\n[^*\n][^\n]*)*?)(?=\n\*\*|\n[A-Z])', section)
|
|
39
|
-
summary = narrative_match.group(1).strip() if narrative_match else ""
|
|
40
|
-
|
|
41
|
-
# Extract friction points
|
|
42
|
-
friction_points = []
|
|
43
|
-
friction_section = re.search(r'\*\*Friction points?[:\*]*\*\*\s*\n(.*?)(?=\n\*\*|\n[A-Z]|$)', section, re.DOTALL)
|
|
44
|
-
if friction_section:
|
|
45
|
-
for line in friction_section.group(1).split('\n'):
|
|
46
|
-
line = line.strip()
|
|
47
|
-
if line.startswith('- '):
|
|
48
|
-
friction_points.append(line[2:].strip())
|
|
49
|
-
|
|
50
|
-
# Extract bright spots
|
|
51
|
-
bright_spots = []
|
|
52
|
-
bright_section = re.search(r'\*\*Bright spots?[:\*]*\*\*\s*\n(.*?)(?=\n\*\*|\n[A-Z]|$)', section, re.DOTALL)
|
|
53
|
-
if bright_section:
|
|
54
|
-
for line in bright_section.group(1).split('\n'):
|
|
55
|
-
line = line.strip()
|
|
56
|
-
if line.startswith('- '):
|
|
57
|
-
bright_spots.append(line[2:].strip())
|
|
58
|
-
|
|
59
|
-
journeys.append({
|
|
60
|
-
'archetype': name,
|
|
61
|
-
'summary': summary,
|
|
62
|
-
'friction_points': friction_points,
|
|
63
|
-
'bright_spots': bright_spots
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
return journeys
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def extract_autonomous(content: str) -> dict:
|
|
70
|
-
"""Extract headless/automation assessment from enhancement-analysis.md."""
|
|
71
|
-
assessment_section = extract_section(content, 'Headless Assessment', level=2)
|
|
72
|
-
if not assessment_section:
|
|
73
|
-
return {}
|
|
74
|
-
|
|
75
|
-
# Look for "Current Level:" or "Potential:" pattern
|
|
76
|
-
potential_match = re.search(r'(?:Current Level|Potential)[:\*]*\s*([^\n.]+)', assessment_section)
|
|
77
|
-
potential = potential_match.group(1).strip() if potential_match else "unknown"
|
|
78
|
-
|
|
79
|
-
# Get the rest as notes
|
|
80
|
-
notes = assessment_section
|
|
81
|
-
if potential_match:
|
|
82
|
-
notes = assessment_section[potential_match.end():].strip()
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
'potential': potential,
|
|
86
|
-
'notes': notes[:200] if notes else "" # Truncate to 200 chars
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def extract_findings_from_md(content: str, source_scanner: str) -> list[dict]:
|
|
91
|
-
"""Extract individual findings from analysis markdown.
|
|
92
|
-
|
|
93
|
-
Handles multiple formats:
|
|
94
|
-
- Architecture: level 4 headings under severity sections (### HIGH, etc)
|
|
95
|
-
- Determinism: bold headings with severity markers [HIGH], [LOW]
|
|
96
|
-
- Customization: bold headings with opportunity markers (HIGH-OPPORTUNITY, etc)
|
|
97
|
-
- Enhancement: numbered findings with severity/opportunity markers
|
|
98
|
-
"""
|
|
99
|
-
findings = []
|
|
100
|
-
|
|
101
|
-
if source_scanner == 'architecture':
|
|
102
|
-
# Architecture format: ### SEVERITY followed by #### N. Title
|
|
103
|
-
severity_pattern = r'^###\s+(CRITICAL|HIGH|MEDIUM|LOW)\s*$'
|
|
104
|
-
severity_sections = re.split(severity_pattern, content, flags=re.MULTILINE)
|
|
105
|
-
|
|
106
|
-
for i in range(1, len(severity_sections), 2):
|
|
107
|
-
severity = severity_sections[i].lower() if i < len(severity_sections) else "medium"
|
|
108
|
-
section_content = severity_sections[i + 1] if i + 1 < len(severity_sections) else ""
|
|
109
|
-
|
|
110
|
-
if not section_content.strip() or section_content.strip() == "None":
|
|
111
|
-
continue
|
|
112
|
-
|
|
113
|
-
# Extract level 4 findings (#### N. Title)
|
|
114
|
-
finding_pattern = r'^####\s+(\d+\.\s+)?(.+?)$'
|
|
115
|
-
for match in re.finditer(finding_pattern, section_content, re.MULTILINE):
|
|
116
|
-
finding_title = match.group(2).strip()
|
|
117
|
-
if finding_title:
|
|
118
|
-
findings.append({
|
|
119
|
-
'title': finding_title,
|
|
120
|
-
'severity': severity,
|
|
121
|
-
'source': source_scanner
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
elif source_scanner == 'determinism':
|
|
125
|
-
# Determinism format: ### **[SEVERITY] Title**
|
|
126
|
-
pattern = r'###\s+\*\*\[([A-Z]+)\]\s+([^*]+)\*\*'
|
|
127
|
-
for match in re.finditer(pattern, content, re.MULTILINE):
|
|
128
|
-
severity = match.group(1).lower()
|
|
129
|
-
title = match.group(2).strip()
|
|
130
|
-
if title:
|
|
131
|
-
findings.append({
|
|
132
|
-
'title': title,
|
|
133
|
-
'severity': severity,
|
|
134
|
-
'source': source_scanner
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
elif source_scanner == 'customization':
|
|
138
|
-
# Customization format: ### N. **Title** (OPPORTUNITY-TYPE)
|
|
139
|
-
pattern = r'###\s+\d+\.\s+\*\*([^*]+)\*\*\s+\(([A-Z-]+)\)'
|
|
140
|
-
for match in re.finditer(pattern, content, re.MULTILINE):
|
|
141
|
-
title = match.group(1).strip()
|
|
142
|
-
opportunity = match.group(2).lower()
|
|
143
|
-
# Map opportunity to severity
|
|
144
|
-
severity = 'high' if 'high' in opportunity else 'medium' if 'medium' in opportunity else 'low'
|
|
145
|
-
if title:
|
|
146
|
-
findings.append({
|
|
147
|
-
'title': title,
|
|
148
|
-
'severity': severity,
|
|
149
|
-
'source': source_scanner
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
elif source_scanner == 'enhancement':
|
|
153
|
-
# Enhancement format: ### LEVEL Findings section followed by #### N. Title
|
|
154
|
-
# Extract opportunity sections (HIGH-OPPORTUNITY, SECONDARY-OPPORTUNITY, etc)
|
|
155
|
-
opportunity_pattern = r'^###\s+([A-Z-]+)\s+(?:Findings|Opportunities?)'
|
|
156
|
-
opportunity_sections = re.split(opportunity_pattern, content, flags=re.MULTILINE)
|
|
157
|
-
|
|
158
|
-
for i in range(1, len(opportunity_sections), 2):
|
|
159
|
-
opportunity = opportunity_sections[i].lower() if i < len(opportunity_sections) else "medium"
|
|
160
|
-
section_content = opportunity_sections[i + 1] if i + 1 < len(opportunity_sections) else ""
|
|
161
|
-
|
|
162
|
-
if not section_content.strip():
|
|
163
|
-
continue
|
|
164
|
-
|
|
165
|
-
# Map opportunity to severity
|
|
166
|
-
severity = 'high' if 'high' in opportunity else 'medium' if 'secondary' in opportunity else 'low'
|
|
167
|
-
|
|
168
|
-
# Extract level 4 findings (#### N. Title)
|
|
169
|
-
finding_pattern = r'^####\s+(\d+\.\s+)?(.+?)$'
|
|
170
|
-
for match in re.finditer(finding_pattern, section_content, re.MULTILINE):
|
|
171
|
-
finding_title = match.group(2).strip()
|
|
172
|
-
if finding_title:
|
|
173
|
-
findings.append({
|
|
174
|
-
'title': finding_title,
|
|
175
|
-
'severity': severity,
|
|
176
|
-
'source': source_scanner
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
return findings
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def merge_prepass_data(report_dir: Path) -> dict:
|
|
183
|
-
"""Load and merge all prepass JSON data."""
|
|
184
|
-
merged = {}
|
|
185
|
-
|
|
186
|
-
for json_file in report_dir.glob('*-prepass.json'):
|
|
187
|
-
try:
|
|
188
|
-
data = json.loads(json_file.read_text(encoding='utf-8'))
|
|
189
|
-
merged.update(data)
|
|
190
|
-
except Exception:
|
|
191
|
-
pass # Skip if not valid JSON
|
|
192
|
-
|
|
193
|
-
return merged
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def build_report_json(skill_path: str, quality_report_dir: str) -> dict:
|
|
197
|
-
"""Extract and build complete report-data.json."""
|
|
198
|
-
report_dir = Path(quality_report_dir)
|
|
199
|
-
skill_name = Path(skill_path).name
|
|
200
|
-
timestamp = datetime.now(timezone.utc).isoformat()
|
|
201
|
-
|
|
202
|
-
# Read all analysis files
|
|
203
|
-
architecture_content = (report_dir / 'architecture-analysis.md').read_text(encoding='utf-8') if (report_dir / 'architecture-analysis.md').exists() else ""
|
|
204
|
-
determinism_content = (report_dir / 'determinism-analysis.md').read_text(encoding='utf-8') if (report_dir / 'determinism-analysis.md').exists() else ""
|
|
205
|
-
customization_content = (report_dir / 'customization-analysis.md').read_text(encoding='utf-8') if (report_dir / 'customization-analysis.md').exists() else ""
|
|
206
|
-
enhancement_content = (report_dir / 'enhancement-analysis.md').read_text(encoding='utf-8') if (report_dir / 'enhancement-analysis.md').exists() else ""
|
|
207
|
-
|
|
208
|
-
# Extract assessments
|
|
209
|
-
arch_assessment = extract_section(architecture_content, 'Assessment', level=2) or ""
|
|
210
|
-
det_assessment = extract_section(determinism_content, 'Assessment', level=2) or ""
|
|
211
|
-
cust_assessment = extract_section(customization_content, 'Overall Assessment', level=2) or ""
|
|
212
|
-
enh_assessment = extract_section(enhancement_content, 'Summary', level=2) or ""
|
|
213
|
-
|
|
214
|
-
# Extract journeys and autonomous from enhancement
|
|
215
|
-
journeys = extract_journeys(enhancement_content)
|
|
216
|
-
autonomous = extract_autonomous(enhancement_content)
|
|
217
|
-
|
|
218
|
-
# Build detailed_analysis
|
|
219
|
-
detailed_analysis = {
|
|
220
|
-
'architecture': {
|
|
221
|
-
'assessment': arch_assessment[:500], # First 500 chars
|
|
222
|
-
'findings': extract_findings_from_md(architecture_content, 'architecture')
|
|
223
|
-
},
|
|
224
|
-
'determinism': {
|
|
225
|
-
'assessment': det_assessment[:500],
|
|
226
|
-
'findings': extract_findings_from_md(determinism_content, 'determinism')
|
|
227
|
-
},
|
|
228
|
-
'customization': {
|
|
229
|
-
'assessment': cust_assessment[:500],
|
|
230
|
-
'posture': 'not-opted-in', # From content
|
|
231
|
-
'findings': extract_findings_from_md(customization_content, 'customization')
|
|
232
|
-
},
|
|
233
|
-
'enhancement': {
|
|
234
|
-
'assessment': enh_assessment[:500],
|
|
235
|
-
'journeys': journeys,
|
|
236
|
-
'autonomous': autonomous,
|
|
237
|
-
'findings': extract_findings_from_md(enhancement_content, 'enhancement')
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
# Build basic structure - minimal for now, will be expanded by report creator if needed
|
|
242
|
-
report_data = {
|
|
243
|
-
'meta': {
|
|
244
|
-
'skill_name': skill_name,
|
|
245
|
-
'skill_path': skill_path,
|
|
246
|
-
'timestamp': timestamp,
|
|
247
|
-
'scanner_count': 4
|
|
248
|
-
},
|
|
249
|
-
'narrative': enh_assessment[:150] if enh_assessment else "", # Placeholder
|
|
250
|
-
'grade': 'Good', # Placeholder - report creator sets this
|
|
251
|
-
'broken': [],
|
|
252
|
-
'opportunities': [],
|
|
253
|
-
'strengths': [],
|
|
254
|
-
'recommendations': [],
|
|
255
|
-
'detailed_analysis': detailed_analysis
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return report_data
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def main():
|
|
262
|
-
parser = argparse.ArgumentParser(description='Extract report-data.json from analysis outputs')
|
|
263
|
-
parser.add_argument('skill_path', help='Path to the skill being analyzed')
|
|
264
|
-
parser.add_argument('quality_report_dir', help='Directory with analysis outputs and where to write report')
|
|
265
|
-
parser.add_argument('-o', '--output', help='Output file path (default: {quality_report_dir}/report-data.json)')
|
|
266
|
-
|
|
267
|
-
args = parser.parse_args()
|
|
268
|
-
|
|
269
|
-
output_path = args.output or str(Path(args.quality_report_dir) / 'report-data.json')
|
|
270
|
-
|
|
271
|
-
try:
|
|
272
|
-
report_json = build_report_json(args.skill_path, args.quality_report_dir)
|
|
273
|
-
|
|
274
|
-
# Write output
|
|
275
|
-
output_file = Path(output_path)
|
|
276
|
-
output_file.write_text(json.dumps(report_json, indent=2, ensure_ascii=False), encoding='utf-8')
|
|
277
|
-
|
|
278
|
-
print(f'Report JSON written to {output_path}', file=sys.stderr)
|
|
279
|
-
print(json.dumps({'status': 'success', 'output': output_path}, indent=2))
|
|
280
|
-
|
|
281
|
-
except Exception as e:
|
|
282
|
-
print(f'Error: {e}', file=sys.stderr)
|
|
283
|
-
sys.exit(1)
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if __name__ == '__main__':
|
|
287
|
-
main()
|