Files
claude-skills-reference/project-management/atlassian-templates/scripts/template_scaffolder.py
Alireza Rezvani a68ae3a05e Dev (#305)
* chore: update gitignore for audit reports and playwright cache

* fix: add YAML frontmatter (name + description) to all SKILL.md files

- Added frontmatter to 34 skills that were missing it entirely (0% Tessl score)
- Fixed name field format to kebab-case across all 169 skills
- Resolves #284

* chore: sync codex skills symlinks [automated]

* fix: optimize 14 low-scoring skills via Tessl review (#290)

Tessl optimization: 14 skills improved from ≤69% to 85%+. Closes #285, #286.

* chore: sync codex skills symlinks [automated]

* fix: optimize 18 skills via Tessl review + compliance fix (closes #287) (#291)

Phase 1: 18 skills optimized via Tessl (avg 77% → 95%). Closes #287.

* feat: add scripts and references to 4 prompt-only skills + Tessl optimization (#292)

Phase 2: 3 new scripts + 2 reference files for prompt-only skills. Tessl 45-55% → 94-100%.

* feat: add 6 agents + 5 slash commands for full coverage (v2.7.0) (#293)

Phase 3: 6 new agents (all 9 categories covered) + 5 slash commands.

* fix: Phase 5 verification fixes + docs update (#294)

Phase 5 verification fixes

* chore: sync codex skills symlinks [automated]

* fix: marketplace audit — all 11 plugins validated by Claude Code (#295)

Marketplace audit: all 11 plugins validated + installed + tested in Claude Code

* fix: restore 7 removed plugins + revert playwright-pro name to pw

Reverts two overly aggressive audit changes:
- Restored content-creator, demand-gen, fullstack-engineer, aws-architect,
  product-manager, scrum-master, skill-security-auditor to marketplace
- Reverted playwright-pro plugin.json name back to 'pw' (intentional short name)

* refactor: split 21 over-500-line skills into SKILL.md + references (#296)

* chore: sync codex skills symlinks [automated]

* docs: update all documentation with accurate counts and regenerated skill pages

- Update skill count to 170, Python tools to 213, references to 314 across all docs
- Regenerate all 170 skill doc pages from latest SKILL.md sources
- Update CLAUDE.md with v2.1.1 highlights, accurate architecture tree, and roadmap
- Update README.md badges and overview table
- Update marketplace.json metadata description and version
- Update mkdocs.yml, index.md, getting-started.md with correct numbers

* fix: add root-level SKILL.md and .codex/instructions.md to all domains (#301)

Root cause: CLI tools (ai-agent-skills, agent-skills-cli) look for SKILL.md
at the specified install path. 7 of 9 domain directories were missing this
file, causing "Skill not found" errors for bundle installs like:
  npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team

Fix:
- Add root-level SKILL.md with YAML frontmatter to 7 domains
- Add .codex/instructions.md to 8 domains (for Codex CLI discovery)
- Update INSTALLATION.md with accurate skill counts (53→170)
- Add troubleshooting entry for "Skill not found" error

All 9 domains now have: SKILL.md + .codex/instructions.md + plugin.json

Closes #301

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Gemini CLI + OpenClaw support, fix Codex missing 25 skills

Gemini CLI:
- Add GEMINI.md with activation instructions
- Add scripts/gemini-install.sh setup script
- Add scripts/sync-gemini-skills.py (194 skills indexed)
- Add .gemini/skills/ with symlinks for all skills, agents, commands
- Remove phantom medium-content-pro entries from sync script
- Add top-level folder filter to prevent gitignored dirs from leaking

Codex CLI:
- Fix sync-codex-skills.py missing "engineering" domain (25 POWERFUL skills)
- Regenerate .codex/skills-index.json: 124 → 149 skills
- Add 25 new symlinks in .codex/skills/

OpenClaw:
- Add OpenClaw installation section to INSTALLATION.md
- Add ClawHub install + manual install + YAML frontmatter docs

Documentation:
- Update INSTALLATION.md with all 4 platforms + accurate counts
- Update README.md: "three platforms" → "four platforms" + Gemini quick start
- Update CLAUDE.md with Gemini CLI support in v2.1.1 highlights
- Update SKILL-AUTHORING-STANDARD.md + SKILL_PIPELINE.md with Gemini steps
- Add OpenClaw + Gemini to installation locations reference table

Marketplace: all 18 plugins validated — sources exist, SKILL.md present

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(product,pm): world-class product & PM skills audit — 6 scripts, 5 agents, 7 commands, 23 references/assets

Phase 1 — Agent & Command Foundation:
- Rewrite cs-project-manager agent (55→515 lines, 4 workflows, 6 skill integrations)
- Expand cs-product-manager agent (408→684 lines, orchestrates all 8 product skills)
- Add 7 slash commands: /rice, /okr, /persona, /user-story, /sprint-health, /project-health, /retro

Phase 2 — Script Gap Closure (2,779 lines):
- jira-expert: jql_query_builder.py (22 patterns), workflow_validator.py
- confluence-expert: space_structure_generator.py, content_audit_analyzer.py
- atlassian-admin: permission_audit_tool.py
- atlassian-templates: template_scaffolder.py (Confluence XHTML generation)

Phase 3 — Reference & Asset Enrichment:
- 9 product references (competitive-teardown, landing-page-generator, saas-scaffolder)
- 6 PM references (confluence-expert, atlassian-admin, atlassian-templates)
- 7 product assets (templates for PRD, RICE, sprint, stories, OKR, research, design system)
- 1 PM asset (permission_scheme_template.json)

Phase 4 — New Agents:
- cs-agile-product-owner, cs-product-strategist, cs-ux-researcher

Phase 5 — Integration & Polish:
- Related Skills cross-references in 8 SKILL.md files
- Updated product-team/CLAUDE.md (5→8 skills, 6→9 tools, 4 agents, 5 commands)
- Updated project-management/CLAUDE.md (0→12 scripts, 3 commands)
- Regenerated docs site (177 pages), updated homepage and getting-started

Quality audit: 31 files reviewed, 29 PASS, 2 fixed (copy-frameworks.md, governance-framework.md)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: audit and repair all plugins, agents, and commands

- Fix 12 command files: correct CLI arg syntax, script paths, and usage docs
- Fix 3 agents with broken script/reference paths (cs-content-creator,
  cs-demand-gen-specialist, cs-financial-analyst)
- Add complete YAML frontmatter to 5 agents (cs-growth-strategist,
  cs-engineering-lead, cs-senior-engineer, cs-financial-analyst,
  cs-quality-regulatory)
- Fix cs-ceo-advisor related agent path
- Update marketplace.json metadata counts (224 tools, 341 refs, 14 agents,
  12 commands)

Verified: all 19 scripts pass --help, all 14 agent paths resolve, mkdocs
builds clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: repair 25 Python scripts failing --help across all domains

- Fix Python 3.10+ syntax (float | None → Optional[float]) in 2 scripts
- Add argparse CLI handling to 9 marketing scripts using raw sys.argv
- Fix 10 scripts crashing at module level (wrap in __main__, add argparse)
- Make yaml/prefect/mcp imports conditional with stdlib fallbacks (4 scripts)
- Fix f-string backslash syntax in project_bootstrapper.py
- Fix -h flag conflict in pr_analyzer.py
- Fix tech-debt.md description (score → prioritize)

All 237 scripts now pass python3 --help verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(product-team): close 3 verified gaps in product skills

- Fix competitive-teardown/SKILL.md: replace broken references
  DATA_COLLECTION.md → references/data-collection-guide.md and
  TEMPLATES.md → references/analysis-templates.md (workflow was broken
  at steps 2 and 4)

- Upgrade landing_page_scaffolder.py: add TSX + Tailwind output format
  (--format tsx) matching SKILL.md promise of Next.js/React components.
  4 design styles (dark-saas, clean-minimal, bold-startup, enterprise).
  TSX is now default; HTML preserved via --format html

- Rewrite README.md: fix stale counts (was 5 skills/15+ tools, now
  accurately shows 8 skills/9 tools), remove 7 ghost scripts that
  never existed (sprint_planner.py, velocity_tracker.py, etc.)

- Fix tech-debt.md description (score → prioritize)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* release: v2.1.2 — landing page TSX output, brand voice integration, docs update

- Landing page generator defaults to Next.js TSX + Tailwind CSS (4 design styles)
- Brand voice analyzer integrated into landing page generation workflow
- CHANGELOG, CLAUDE.md, README.md updated for v2.1.2
- All 13 plugin.json + marketplace.json bumped to 2.1.2
- Gemini/Codex skill indexes re-synced
- Backward compatible: --format html preserved, no breaking changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: alirezarezvani <5697919+alirezarezvani@users.noreply.github.com>
Co-authored-by: Leo <leo@openclaw.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:48:49 +01:00

419 lines
17 KiB
Python

#!/usr/bin/env python3
"""
Template Scaffolder
Generates Confluence page template markup in storage-format XHTML. Supports
built-in template types and custom section definitions with optional macros.
Usage:
python template_scaffolder.py meeting-notes
python template_scaffolder.py decision-log --format json
python template_scaffolder.py custom --sections "Overview,Goals,Action Items" --macros toc,status
python template_scaffolder.py --list
"""
import argparse
import json
import sys
from datetime import datetime
from typing import Any, Dict, List, Optional
# ---------------------------------------------------------------------------
# Macro Generators
# ---------------------------------------------------------------------------
def macro_toc() -> str:
"""Generate table of contents macro."""
return '<ac:structured-macro ac:name="toc"><ac:parameter ac:name="printable">true</ac:parameter><ac:parameter ac:name="style">disc</ac:parameter><ac:parameter ac:name="maxLevel">3</ac:parameter></ac:structured-macro>'
def macro_status(text: str = "IN PROGRESS", color: str = "Yellow") -> str:
"""Generate status macro."""
return f'<ac:structured-macro ac:name="status"><ac:parameter ac:name="colour">{color}</ac:parameter><ac:parameter ac:name="title">{text}</ac:parameter></ac:structured-macro>'
def macro_info_panel(content: str) -> str:
"""Generate info panel macro."""
return f'<ac:structured-macro ac:name="info"><ac:rich-text-body><p>{content}</p></ac:rich-text-body></ac:structured-macro>'
def macro_warning_panel(content: str) -> str:
"""Generate warning panel macro."""
return f'<ac:structured-macro ac:name="warning"><ac:rich-text-body><p>{content}</p></ac:rich-text-body></ac:structured-macro>'
def macro_note_panel(content: str) -> str:
"""Generate note panel macro."""
return f'<ac:structured-macro ac:name="note"><ac:rich-text-body><p>{content}</p></ac:rich-text-body></ac:structured-macro>'
def macro_expand(title: str, content: str) -> str:
"""Generate expand/collapse macro."""
return f'<ac:structured-macro ac:name="expand"><ac:parameter ac:name="title">{title}</ac:parameter><ac:rich-text-body>{content}</ac:rich-text-body></ac:structured-macro>'
def macro_jira_issues(jql: str) -> str:
"""Generate Jira issues macro."""
return f'<ac:structured-macro ac:name="jira"><ac:parameter ac:name="jqlQuery">{jql}</ac:parameter><ac:parameter ac:name="columns">key,summary,type,created,updated,due,assignee,reporter,priority,status,resolution</ac:parameter></ac:structured-macro>'
MACRO_MAP = {
"toc": macro_toc,
"status": macro_status,
"info": macro_info_panel,
"warning": macro_warning_panel,
"note": macro_note_panel,
"expand": macro_expand,
"jira-issues": macro_jira_issues,
}
# ---------------------------------------------------------------------------
# Built-in Templates
# ---------------------------------------------------------------------------
def _section(title: str, content: str) -> str:
"""Generate a section with heading and content."""
return f'<h2>{title}</h2>\n{content}\n'
def _table(headers: List[str], rows: List[List[str]]) -> str:
"""Generate an XHTML table."""
parts = ['<table><colgroup>']
for _ in headers:
parts.append('<col />')
parts.append('</colgroup><thead><tr>')
for h in headers:
parts.append(f'<th><p>{h}</p></th>')
parts.append('</tr></thead><tbody>')
for row in rows:
parts.append('<tr>')
for cell in row:
parts.append(f'<td><p>{cell}</p></td>')
parts.append('</tr>')
parts.append('</tbody></table>')
return ''.join(parts)
def template_meeting_notes() -> Dict[str, Any]:
"""Generate meeting notes template."""
today = datetime.now().strftime("%Y-%m-%d")
body = macro_toc() + '\n'
body += macro_info_panel("Replace placeholder text with your meeting details.") + '\n'
body += _section("Meeting Details", _table(
["Field", "Value"],
[["Date", today], ["Time", ""], ["Location", ""], ["Facilitator", ""], ["Note Taker", ""]],
))
body += _section("Attendees", '<ul><li><p>Name 1</p></li><li><p>Name 2</p></li></ul>')
body += _section("Agenda", '<ol><li><p>Item 1</p></li><li><p>Item 2</p></li><li><p>Item 3</p></li></ol>')
body += _section("Discussion Notes", '<p>Summary of discussion points...</p>')
body += _section("Decisions Made", _table(
["Decision", "Owner", "Date"],
[["", "", today]],
))
body += _section("Action Items", _table(
["Action", "Owner", "Due Date", "Status"],
[["", "", "", macro_status("TODO", "Grey")]],
))
body += _section("Next Meeting", '<p>Date: TBD</p><p>Agenda items for next time:</p><ul><li><p></p></li></ul>')
return {"name": "Meeting Notes", "body": body, "labels": ["meeting-notes", "template"]}
def template_decision_log() -> Dict[str, Any]:
"""Generate decision log template."""
today = datetime.now().strftime("%Y-%m-%d")
body = macro_toc() + '\n'
body += _section("Decision Log", macro_info_panel("Track key decisions, context, and outcomes."))
body += _table(
["ID", "Date", "Decision", "Context", "Alternatives Considered", "Outcome", "Owner", "Status"],
[
["D-001", today, "", "", "", "", "", macro_status("DECIDED", "Green")],
["D-002", "", "", "", "", "", "", macro_status("PENDING", "Yellow")],
],
)
body += '\n'
body += _section("Decision Template", macro_expand("Decision Details Template",
'<h3>Context</h3><p>What is the issue or situation requiring a decision?</p>'
'<h3>Options</h3><ol><li><p>Option A - pros/cons</p></li><li><p>Option B - pros/cons</p></li></ol>'
'<h3>Decision</h3><p>What was decided and why?</p>'
'<h3>Consequences</h3><p>What are the expected outcomes?</p>'
))
return {"name": "Decision Log", "body": body, "labels": ["decision-log", "template"]}
def template_runbook() -> Dict[str, Any]:
"""Generate runbook template."""
body = macro_toc() + '\n'
body += macro_warning_panel("This runbook should be tested and reviewed quarterly.") + '\n'
body += _section("Overview", '<p>Brief description of what this runbook covers.</p>'
+ _table(["Field", "Value"], [
["Service/System", ""], ["Owner", ""], ["Last Tested", ""],
["Severity", ""], ["Estimated Duration", ""],
]))
body += _section("Prerequisites", '<ul><li><p>Access to system X</p></li><li><p>VPN connected</p></li><li><p>Required tools installed</p></li></ul>')
body += _section("Steps", '<ol><li><p><strong>Step 1:</strong> Description</p><ac:structured-macro ac:name="code"><ac:parameter ac:name="language">bash</ac:parameter><ac:plain-text-body><![CDATA[# command here]]></ac:plain-text-body></ac:structured-macro></li>'
'<li><p><strong>Step 2:</strong> Description</p></li>'
'<li><p><strong>Step 3:</strong> Description</p></li></ol>')
body += _section("Verification", '<p>How to verify the issue is resolved:</p><ul><li><p>Check 1</p></li><li><p>Check 2</p></li></ul>')
body += _section("Rollback", macro_note_panel("If the above steps do not resolve the issue, follow these rollback steps.") +
'<ol><li><p>Rollback step 1</p></li><li><p>Rollback step 2</p></li></ol>')
body += _section("Escalation", _table(
["Level", "Contact", "When to Escalate"],
[["L1", "", ""], ["L2", "", ""], ["L3", "", ""]],
))
return {"name": "Runbook", "body": body, "labels": ["runbook", "operations", "template"]}
def template_project_kickoff() -> Dict[str, Any]:
"""Generate project kickoff template."""
today = datetime.now().strftime("%Y-%m-%d")
body = macro_toc() + '\n'
body += _section("Project Overview", _table(
["Field", "Value"],
[["Project Name", ""], ["Start Date", today], ["Target End Date", ""],
["Project Lead", ""], ["Sponsor", ""], ["Status", macro_status("KICKOFF", "Blue")]],
))
body += _section("Vision & Goals", '<h3>Vision</h3><p>What does success look like?</p>'
'<h3>Goals</h3><ol><li><p>Goal 1</p></li><li><p>Goal 2</p></li><li><p>Goal 3</p></li></ol>')
body += _section("Scope", '<h3>In Scope</h3><ul><li><p></p></li></ul><h3>Out of Scope</h3><ul><li><p></p></li></ul>')
body += _section("Stakeholders", _table(
["Name", "Role", "Responsibility", "Communication Preference"],
[["", "", "", ""]],
))
body += _section("Timeline & Milestones", _table(
["Milestone", "Target Date", "Status"],
[["Phase 1", "", macro_status("NOT STARTED", "Grey")],
["Phase 2", "", macro_status("NOT STARTED", "Grey")]],
))
body += _section("Risks", _table(
["Risk", "Likelihood", "Impact", "Mitigation"],
[["", "High/Medium/Low", "High/Medium/Low", ""]],
))
body += _section("Next Steps", '<ul><li><p></p></li></ul>')
return {"name": "Project Kickoff", "body": body, "labels": ["project-kickoff", "template"]}
def template_sprint_retro() -> Dict[str, Any]:
"""Generate sprint retrospective template."""
body = macro_toc() + '\n'
body += _section("Sprint Info", _table(
["Field", "Value"],
[["Sprint", ""], ["Date Range", ""], ["Facilitator", ""],
["Velocity", ""], ["Commitment", ""], ["Completion Rate", ""]],
))
body += _section("What Went Well", '<ul><li><p></p></li></ul>')
body += _section("What Could Be Improved", '<ul><li><p></p></li></ul>')
body += _section("Action Items from Last Retro", _table(
["Action", "Owner", "Status"],
[["", "", macro_status("DONE", "Green")], ["", "", macro_status("IN PROGRESS", "Yellow")]],
))
body += _section("New Action Items", _table(
["Action", "Owner", "Due Date", "Priority"],
[["", "", "", "High/Medium/Low"]],
))
body += _section("Team Health Check", macro_info_panel("Rate each area 1-5 (1=needs work, 5=great)") + _table(
["Area", "Rating", "Trend", "Notes"],
[["Teamwork", "", "", ""], ["Delivery", "", "", ""],
["Fun", "", "", ""], ["Learning", "", "", ""]],
))
return {"name": "Sprint Retrospective", "body": body, "labels": ["sprint-retro", "agile", "template"]}
def template_how_to_guide() -> Dict[str, Any]:
"""Generate how-to guide template."""
body = macro_toc() + '\n'
body += macro_info_panel("This guide explains how to accomplish a specific task.") + '\n'
body += _section("Overview", '<p>Brief description of what this guide covers and who it is for.</p>')
body += _section("Prerequisites", '<ul><li><p>Prerequisite 1</p></li><li><p>Prerequisite 2</p></li></ul>')
body += _section("Step-by-Step Instructions",
'<h3>Step 1: Title</h3><p>Description of what to do.</p>'
'<h3>Step 2: Title</h3><p>Description of what to do.</p>'
'<h3>Step 3: Title</h3><p>Description of what to do.</p>')
body += _section("Troubleshooting", macro_expand("Common Issues",
'<h3>Issue 1</h3><p>Solution...</p>'
'<h3>Issue 2</h3><p>Solution...</p>'))
body += _section("Related Resources", '<ul><li><p>Link 1</p></li><li><p>Link 2</p></li></ul>')
return {"name": "How-To Guide", "body": body, "labels": ["how-to", "guide", "template"]}
TEMPLATE_REGISTRY = {
"meeting-notes": template_meeting_notes,
"decision-log": template_decision_log,
"runbook": template_runbook,
"project-kickoff": template_project_kickoff,
"sprint-retro": template_sprint_retro,
"how-to-guide": template_how_to_guide,
}
# ---------------------------------------------------------------------------
# Custom Template Builder
# ---------------------------------------------------------------------------
def build_custom_template(
sections: List[str],
macros: List[str],
) -> Dict[str, Any]:
"""Build a custom template from sections and macros."""
body = ""
# Add requested macros at the top
if "toc" in macros:
body += macro_toc() + '\n'
if "status" in macros:
body += '<p>Status: ' + macro_status() + '</p>\n'
for section in sections:
section = section.strip()
if not section:
continue
body += _section(section, '<p></p>')
# Add panels if requested
if "info" in macros:
body = macro_info_panel("Add instructions or context here.") + '\n' + body
if "warning" in macros:
body += macro_warning_panel("Add warnings here.") + '\n'
if "note" in macros:
body += macro_note_panel("Add notes here.") + '\n'
return {"name": "Custom Template", "body": body, "labels": ["custom", "template"]}
# ---------------------------------------------------------------------------
# Output Formatting
# ---------------------------------------------------------------------------
def format_text_output(result: Dict[str, Any]) -> str:
"""Format results as readable text report."""
lines = []
lines.append("=" * 60)
lines.append(f"TEMPLATE: {result['name']}")
lines.append("=" * 60)
lines.append("")
lines.append(f"Labels: {', '.join(result.get('labels', []))}")
lines.append("")
lines.append("CONFLUENCE STORAGE FORMAT MARKUP")
lines.append("-" * 30)
lines.append(result["body"])
return "\n".join(lines)
def format_json_output(result: Dict[str, Any]) -> Dict[str, Any]:
"""Format results as JSON."""
return result
def format_list_output(output_format: str) -> str:
"""Format available templates list."""
if output_format == "json":
templates = {}
for name, func in TEMPLATE_REGISTRY.items():
result = func()
templates[name] = {
"name": result["name"],
"labels": result["labels"],
}
return json.dumps(templates, indent=2)
lines = []
lines.append("=" * 60)
lines.append("AVAILABLE TEMPLATES")
lines.append("=" * 60)
lines.append("")
for name, func in TEMPLATE_REGISTRY.items():
result = func()
lines.append(f" {name}")
lines.append(f" Name: {result['name']}")
lines.append(f" Labels: {', '.join(result['labels'])}")
lines.append("")
lines.append(f"Total templates: {len(TEMPLATE_REGISTRY)}")
lines.append("")
lines.append("Usage:")
lines.append(" python template_scaffolder.py <template-name>")
lines.append(' python template_scaffolder.py custom --sections "Section1,Section2" --macros toc,status')
return "\n".join(lines)
# ---------------------------------------------------------------------------
# CLI Interface
# ---------------------------------------------------------------------------
def main() -> int:
"""Main CLI entry point."""
parser = argparse.ArgumentParser(
description="Generate Confluence page template markup"
)
parser.add_argument(
"template",
nargs="?",
help="Template name or 'custom' for custom template",
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)",
)
parser.add_argument(
"--list",
action="store_true",
help="List all available template types",
)
parser.add_argument(
"--sections",
help='Comma-separated section names for custom template (e.g., "Overview,Goals,Action Items")',
)
parser.add_argument(
"--macros",
help='Comma-separated macro names to include (e.g., "toc,status,info")',
)
args = parser.parse_args()
try:
if args.list:
print(format_list_output(args.format))
return 0
if not args.template:
parser.error("template name is required unless --list is used")
template_name = args.template.lower()
if template_name == "custom":
if not args.sections:
parser.error("--sections is required for custom templates")
sections = [s.strip() for s in args.sections.split(",")]
macros = [m.strip() for m in args.macros.split(",")] if args.macros else []
result = build_custom_template(sections, macros)
elif template_name in TEMPLATE_REGISTRY:
result = TEMPLATE_REGISTRY[template_name]()
else:
available = ", ".join(sorted(TEMPLATE_REGISTRY.keys()))
print(f"Error: Unknown template '{template_name}'. Available: {available}", file=sys.stderr)
return 1
if args.format == "json":
print(json.dumps(format_json_output(result), indent=2))
else:
print(format_text_output(result))
return 0
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())