gator-command 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.
Files changed (110) hide show
  1. gator_command/__init__.py +2 -0
  2. gator_command/cli.py +137 -0
  3. gator_command/scripts/crawler.py +633 -0
  4. gator_command/scripts/dashboard/dashboard.css +982 -0
  5. gator_command/scripts/dashboard/dashboard.html +84 -0
  6. gator_command/scripts/dashboard/dashboard.js +419 -0
  7. gator_command/scripts/dashboard/views/audit.js +270 -0
  8. gator_command/scripts/dashboard/views/fleet.js +307 -0
  9. gator_command/scripts/dashboard/views/repo.js +599 -0
  10. gator_command/scripts/dashboard/views/settings.js +173 -0
  11. gator_command/scripts/dashboard/views/updates.js +308 -0
  12. gator_command/scripts/enforcer-prompt.md +22 -0
  13. gator_command/scripts/extract-claude-sessions.py +489 -0
  14. gator_command/scripts/extract-codex-sessions.py +477 -0
  15. gator_command/scripts/extract-gemini-sessions.py +410 -0
  16. gator_command/scripts/gator-audit.py +956 -0
  17. gator_command/scripts/gator-charter-draft.py +919 -0
  18. gator_command/scripts/gator-charter-lint.py +427 -0
  19. gator_command/scripts/gator-charter-verify.py +606 -0
  20. gator_command/scripts/gator-dashboard.py +1271 -0
  21. gator_command/scripts/gator-deploy.py +916 -0
  22. gator_command/scripts/gator-drift.py +569 -0
  23. gator_command/scripts/gator-enforce.py +82 -0
  24. gator_command/scripts/gator-fleet-intel.py +460 -0
  25. gator_command/scripts/gator-fleet-report.py +615 -0
  26. gator_command/scripts/gator-init-command-post.py +315 -0
  27. gator_command/scripts/gator-init.py +434 -0
  28. gator_command/scripts/gator-machine-id.py +153 -0
  29. gator_command/scripts/gator-policy-status.py +631 -0
  30. gator_command/scripts/gator-pulse.py +459 -0
  31. gator_command/scripts/gator-repo-status.py +649 -0
  32. gator_command/scripts/gator-session-common.py +372 -0
  33. gator_command/scripts/gator-session-sink.py +831 -0
  34. gator_command/scripts/gator-sessions.py +1244 -0
  35. gator_command/scripts/gator-update.py +615 -0
  36. gator_command/scripts/gator-version.py +38 -0
  37. gator_command/scripts/gator_core.py +489 -0
  38. gator_command/scripts/gator_remote.py +381 -0
  39. gator_command/scripts/gator_runtime.py +142 -0
  40. gator_command/scripts/gatorize-actions.sh +989 -0
  41. gator_command/scripts/gatorize-lib.sh +166 -0
  42. gator_command/scripts/gatorize-post.sh +394 -0
  43. gator_command/scripts/gatorize.py +1163 -0
  44. gator_command/scripts/gatorize.sh +185 -0
  45. gator_command/scripts/generate_markdown.py +212 -0
  46. gator_command/scripts/generate_wiki.py +424 -0
  47. gator_command/scripts/graph_health.py +780 -0
  48. gator_command/scripts/memex-lint.py +286 -0
  49. gator_command/scripts/memex-lint.sh +205 -0
  50. gator_command/scripts/memex.py +1472 -0
  51. gator_command/scripts/memex_formatters.py +191 -0
  52. gator_command/scripts/memex_state.py +236 -0
  53. gator_command/scripts/spawn.py +650 -0
  54. gator_command/templates/gator-starter/blueprints/README.md +32 -0
  55. gator_command/templates/gator-starter/charterignore +53 -0
  56. gator_command/templates/gator-starter/charters/README.md +178 -0
  57. gator_command/templates/gator-starter/charters/_template.md +31 -0
  58. gator_command/templates/gator-starter/commands/commit.md +33 -0
  59. gator_command/templates/gator-starter/commands/init.md +11 -0
  60. gator_command/templates/gator-starter/commands/update.md +5 -0
  61. gator_command/templates/gator-starter/constitution.md +165 -0
  62. gator_command/templates/gator-starter/field-guides/README.md +25 -0
  63. gator_command/templates/gator-starter/gator-start-up.md +119 -0
  64. gator_command/templates/gator-starter/procedures/charter-alignment.md +83 -0
  65. gator_command/templates/gator-starter/procedures/enforcer-review.md +317 -0
  66. gator_command/templates/gator-starter/procedures/field-guide-generation.md +176 -0
  67. gator_command/templates/gator-starter/procedures/knowledge-capture.md +57 -0
  68. gator_command/templates/gator-starter/procedures/significance-check.md +69 -0
  69. gator_command/templates/gator-starter/reference-notes/concierge-responses.md +535 -0
  70. gator_command/templates/gator-starter/reference-notes/dangerous-patterns.md +91 -0
  71. gator_command/templates/gator-starter/reference-notes/dashboard-operations.md +22 -0
  72. gator_command/templates/gator-starter/reference-notes/enforcer-configuration.md +232 -0
  73. gator_command/templates/gator-starter/reference-notes/example-project.md +289 -0
  74. gator_command/templates/gator-starter/reference-notes/failure-modes-and-self-correction.md +72 -0
  75. gator_command/templates/gator-starter/reference-notes/git-workflow.md +60 -0
  76. gator_command/templates/gator-starter/reference-notes/identity-and-ownership.md +37 -0
  77. gator_command/templates/gator-starter/reference-notes/refactor-approach.md +155 -0
  78. gator_command/templates/gator-starter/reference-notes/what-gator-requires-from-a-model.md +108 -0
  79. gator_command/templates/gator-starter/reference-notes/why-navigation-coding-feels-different.md +99 -0
  80. gator_command/templates/gator-starter/reference-notes/workflow-profiles.md +155 -0
  81. gator_command/templates/gator-starter/scripts/__pycache__/enforcer-review.cpython-313.pyc +0 -0
  82. gator_command/templates/gator-starter/scripts/__pycache__/gator-approve.cpython-313.pyc +0 -0
  83. gator_command/templates/gator-starter/scripts/__pycache__/gator-init.cpython-313.pyc +0 -0
  84. gator_command/templates/gator-starter/scripts/__pycache__/gator-pre-commit.cpython-313.pyc +0 -0
  85. gator_command/templates/gator-starter/scripts/__pycache__/gator-update.cpython-313.pyc +0 -0
  86. gator_command/templates/gator-starter/scripts/enforcer-prompt.md +55 -0
  87. gator_command/templates/gator-starter/scripts/enforcer-review.py +1551 -0
  88. gator_command/templates/gator-starter/scripts/gator-approve.py +139 -0
  89. gator_command/templates/gator-starter/scripts/gator-enforce.py +82 -0
  90. gator_command/templates/gator-starter/scripts/gator-init.py +434 -0
  91. gator_command/templates/gator-starter/scripts/gator-pre-commit.py +2670 -0
  92. gator_command/templates/gator-starter/scripts/gator-pulse.py +459 -0
  93. gator_command/templates/gator-starter/scripts/gator-update.py +615 -0
  94. gator_command/templates/gator-starter/scripts/gator-version.py +38 -0
  95. gator_command/templates/gator-starter/scripts/gator_core.py +487 -0
  96. gator_command/templates/gator-starter/scripts/hooks/__pycache__/commit-msgcpython-313.pyc +0 -0
  97. gator_command/templates/gator-starter/scripts/hooks/__pycache__/post-commitcpython-313.pyc +0 -0
  98. gator_command/templates/gator-starter/scripts/hooks/__pycache__/pre-commitcpython-313.pyc +0 -0
  99. gator_command/templates/gator-starter/scripts/hooks/commit-msg +5 -0
  100. gator_command/templates/gator-starter/scripts/hooks/post-commit +7 -0
  101. gator_command/templates/gator-starter/scripts/hooks/pre-commit +5 -0
  102. gator_command/templates/gator-starter/sessions/.gitignore +7 -0
  103. gator_command/templates/gator-starter/vault/.gitkeep +0 -0
  104. gator_command/templates/gator-starter/whiteboard.md +5 -0
  105. gator_command-1.0.0.dist-info/METADATA +122 -0
  106. gator_command-1.0.0.dist-info/RECORD +110 -0
  107. gator_command-1.0.0.dist-info/WHEEL +5 -0
  108. gator_command-1.0.0.dist-info/entry_points.txt +2 -0
  109. gator_command-1.0.0.dist-info/licenses/LICENSE +21 -0
  110. gator_command-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # gatorize.sh — Install, upgrade, or morph the Gator knowledge layer into a
5
+ # project repo and connect it to this Gator Command post.
6
+ #
7
+ # Handles five scenarios:
8
+ # 1. Fresh directory (no git) → git init + install
9
+ # 2. Git repo, clean → install
10
+ # 3. Git repo, has .gator/ → upgrade (preserve knowledge)
11
+ # 4. Git repo, has memex structure → morph memex → gator
12
+ # 5. Git repo, has both → warn, let PI choose
13
+ #
14
+ # Every scenario creates a 'gator-install' feature branch. No exceptions.
15
+ #
16
+ # Structure:
17
+ # gatorize.sh — this file (orchestrator: constants, args, dispatch)
18
+ # gatorize-lib.sh — utilities, detection, shared helpers
19
+ # gatorize-actions.sh — scenario actions: install, upgrade, morph, dual
20
+ # gatorize-post.sh — thin link, outbox, entry points, register, summary
21
+ #
22
+ # Procedure: gator-command/procedures/gatorize-permutations.md
23
+ #
24
+ # Usage:
25
+ # bash gator-command/scripts/gatorize.sh /path/to/your/project
26
+ # bash gator-command/scripts/gatorize.sh .
27
+
28
+ # =============================================================================
29
+ # Constants
30
+ # =============================================================================
31
+
32
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
33
+ COMMAND_POST="$(cd "$SCRIPT_DIR/../.." && pwd)"
34
+ # Templates are siblings of scripts/ — works whether parent is
35
+ # gator-command/ (dev) or gator-engine/ (public distribution)
36
+ TEMPLATES="$(cd "$SCRIPT_DIR/../templates/gator-starter" 2>/dev/null && pwd)"
37
+ if [ -z "$TEMPLATES" ] || [ ! -d "$TEMPLATES" ]; then
38
+ # Fallback: try gator-command/ path (legacy)
39
+ TEMPLATES="$COMMAND_POST/gator-command/templates/gator-starter"
40
+ fi
41
+ GATOR_BRANCH="gator-install"
42
+ TODAY="$(date +%Y-%m-%d)"
43
+
44
+ # Detect whether we are running from a live command post.
45
+ # Uses find_command_post() from gator_core.py as the single canonical predicate.
46
+ HAS_COMMAND_POST="false"
47
+ DETECTED_CP=$(python3 -c "import sys; sys.path.insert(0,'$SCRIPT_DIR'); from gator_core import find_command_post; cp=find_command_post('$SCRIPT_DIR'); print(cp or '')" 2>/dev/null || echo "")
48
+ if [ -n "$DETECTED_CP" ]; then
49
+ HAS_COMMAND_POST="true"
50
+ fi
51
+
52
+ # Template generation — read from gator_core.py (single source of truth).
53
+ GATOR_GEN=$(sed -n 's/^CURRENT_GENERATION\s*=\s*\([0-9]*\)/\1/p' "$SCRIPT_DIR/gator_core.py" 2>/dev/null)
54
+ if [ -z "$GATOR_GEN" ]; then
55
+ GATOR_GEN=2 # fallback if gator_core.py not found
56
+ fi
57
+
58
+ # Entry point markers (used for idempotent detection)
59
+ GATOR_MARKER="# --- Gator Navigation Coding ---"
60
+ COMMAND_POST_MARKER="# --- Gator Command Post ---"
61
+
62
+ # =============================================================================
63
+ # Source libraries
64
+ # =============================================================================
65
+
66
+ source "$SCRIPT_DIR/gatorize-lib.sh"
67
+ source "$SCRIPT_DIR/gatorize-actions.sh"
68
+ source "$SCRIPT_DIR/gatorize-post.sh"
69
+
70
+ # =============================================================================
71
+ # Main
72
+ # =============================================================================
73
+
74
+ # --- Validate args ---
75
+
76
+ if [ $# -eq 0 ]; then
77
+ echo "Usage: bash gator-command/scripts/gatorize.sh <target-directory>"
78
+ echo ""
79
+ echo "Installs, upgrades, or morphs .gator/ into the target project and"
80
+ echo "connects it to this Gator Command post."
81
+ echo ""
82
+ echo "Handles: fresh dirs, existing repos, prior gator installs, and"
83
+ echo "legacy memex structures (.memex/, memex/)."
84
+ echo ""
85
+ echo "Always creates a 'gator-install' feature branch for safety."
86
+ echo ""
87
+ echo "Procedure: gator-command/procedures/gatorize-permutations.md"
88
+ exit 1
89
+ fi
90
+
91
+ if [ ! -d "$1" ]; then
92
+ echo ""
93
+ echo " Directory does not exist: $1"
94
+ echo " Gatorize can create it from scratch — git init, .gator/, the works."
95
+ echo ""
96
+ if confirm "Create $1 and gatorize it?" "Y"; then
97
+ mkdir -p "$1"
98
+ echo " Created $1"
99
+ else
100
+ echo " Aborted."
101
+ exit 1
102
+ fi
103
+ fi
104
+
105
+ TARGET="$(cd "$1" && pwd)"
106
+
107
+ if [ "$TARGET" = "$COMMAND_POST" ]; then
108
+ echo "Error: Target is the command post itself. Gatorize a project repo, not gator-command."
109
+ exit 1
110
+ fi
111
+
112
+ if [ ! -d "$TEMPLATES" ]; then
113
+ echo "Error: Templates not found at $TEMPLATES"
114
+ echo "Are you running this from the gator-command repo root?"
115
+ exit 1
116
+ fi
117
+
118
+ # --- Detect scenario ---
119
+
120
+ SCENARIO=$(detect_scenario "$TARGET")
121
+
122
+ echo ""
123
+ echo " ================================================================"
124
+ echo " gatorize.sh — $(basename "$TARGET")"
125
+ echo " ================================================================"
126
+
127
+ case "$SCENARIO" in
128
+ 1) echo " Scenario 1: Fresh directory (no git, no memex, no gator)" ;;
129
+ 2) echo " Scenario 2: Git repo, clean (no prior knowledge layer)" ;;
130
+ 3)
131
+ DETECTED_GEN=$(detect_gator_generation "$TARGET")
132
+ echo " Gator is already installed (generation $DETECTED_GEN). Running update..."
133
+ echo ""
134
+ # Use the SOURCE repo's gator-update with --source pointing to this gator clone
135
+ python "$SCRIPT_DIR/gator-update.py" --path "$TARGET" --source "$COMMAND_POST" --no-policy
136
+ echo ""
137
+ echo " ================================================================"
138
+ echo " SUCCESS — updated"
139
+ echo " ================================================================"
140
+ echo ""
141
+ echo " Templates and scripts refreshed. Your content is preserved."
142
+ echo ""
143
+ exit 0
144
+ ;;
145
+ 4) echo " Scenario 4: Git repo, legacy memex structure (morph)" ;;
146
+ 5) echo " Scenario 5: Git repo, both memex and gator (dual)" ;;
147
+ esac
148
+
149
+ echo ""
150
+
151
+ # --- Dispatch ---
152
+
153
+ case "$SCENARIO" in
154
+ 1)
155
+ action_git_init "$TARGET"
156
+ action_install_gator "$TARGET"
157
+ ;;
158
+ 2)
159
+ action_feature_branch "$TARGET"
160
+ action_install_gator "$TARGET"
161
+ ;;
162
+ 3)
163
+ action_feature_branch "$TARGET"
164
+ action_upgrade_gator "$TARGET"
165
+ ;;
166
+ 4)
167
+ action_feature_branch "$TARGET"
168
+ action_morph_memex "$TARGET"
169
+ ;;
170
+ 5)
171
+ action_feature_branch "$TARGET"
172
+ action_warn_dual "$TARGET"
173
+ ;;
174
+ esac
175
+
176
+ # --- Common tail (all scenarios) ---
177
+
178
+ if [ "$HAS_COMMAND_POST" = "true" ]; then
179
+ action_install_thin_link "$TARGET"
180
+ fi
181
+ action_install_entry_points "$TARGET" "$HAS_COMMAND_POST"
182
+ action_install_outbox "$TARGET"
183
+ action_install_product_source "$TARGET"
184
+ action_register "$TARGET"
185
+ print_summary "$TARGET" "$SCENARIO" "$HAS_COMMAND_POST"
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env python3
2
+ """Render the Memex thread graph into Markdown."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import datetime as dt
8
+ import re
9
+ from pathlib import Path
10
+
11
+ from generate_wiki import (
12
+ CATEGORY_ORDER,
13
+ IDENTITY_SECTIONS,
14
+ category_threads,
15
+ extract_links,
16
+ load_threads,
17
+ parse_identity,
18
+ section_blocks,
19
+ )
20
+
21
+
22
+ LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
23
+
24
+
25
+ def slugify_heading(text: str) -> str:
26
+ slug = text.strip().lower()
27
+ slug = re.sub(r"[^\w\s-]", "", slug)
28
+ slug = re.sub(r"\s", "-", slug)
29
+ return slug
30
+
31
+
32
+ def convert_inline(text: str, title_lookup: dict[Path, str], source_dir: Path) -> str:
33
+ def replace_link(match: re.Match[str]) -> str:
34
+ label = match.group(1)
35
+ raw_target = match.group(2)
36
+ if raw_target.endswith(".md"):
37
+ target_path = (source_dir / raw_target).resolve()
38
+ target_title = title_lookup.get(target_path)
39
+ if target_title:
40
+ return f"[{label}](#{slugify_heading(target_title)})"
41
+ return label
42
+
43
+ return LINK_RE.sub(replace_link, text)
44
+
45
+
46
+ def render_markdown_lines(
47
+ lines: list[str], title_lookup: dict[Path, str], source_dir: Path
48
+ ) -> list[str]:
49
+ rendered: list[str] = []
50
+ for kind, block in section_blocks(lines):
51
+ if kind == "bullet":
52
+ for line in block:
53
+ rendered.append("- " + convert_inline(line.lstrip()[2:], title_lookup, source_dir))
54
+ else:
55
+ paragraph = " ".join(line.strip() for line in block)
56
+ rendered.append(convert_inline(paragraph, title_lookup, source_dir))
57
+ rendered.append("")
58
+
59
+ if rendered and rendered[-1] == "":
60
+ rendered.pop()
61
+ return rendered
62
+
63
+
64
+ def render_identity(identity_path: Path, title_lookup: dict[Path, str]) -> list[str]:
65
+ sections = parse_identity(identity_path)
66
+ rendered = ["## About the Operator", ""]
67
+ for section_name in IDENTITY_SECTIONS:
68
+ lines = sections.get(section_name, [])
69
+ if not lines:
70
+ continue
71
+ rendered.append(f"### {section_name}")
72
+ rendered.extend(
73
+ render_markdown_lines(lines, title_lookup=title_lookup, source_dir=identity_path.parent)
74
+ )
75
+ rendered.append("")
76
+ if rendered[-1] == "":
77
+ rendered.pop()
78
+ return rendered
79
+
80
+
81
+ def render_see_also(thread, title_lookup: dict[Path, str]) -> list[str]:
82
+ items: list[str] = []
83
+ seen: set[Path] = set()
84
+ for link in extract_links(thread.connection_lines, thread.source_path):
85
+ if link.target_path is None or link.target_path in seen:
86
+ continue
87
+ target_title = title_lookup.get(link.target_path)
88
+ if not target_title:
89
+ continue
90
+ seen.add(link.target_path)
91
+ item = f"- [{link.label}](#{slugify_heading(target_title)})"
92
+ if link.annotation:
93
+ item += f" - {link.annotation}"
94
+ items.append(item)
95
+ return items
96
+
97
+
98
+ def render_thread(thread, title_lookup: dict[Path, str]) -> list[str]:
99
+ lines = [
100
+ f"### {thread.title}",
101
+ "",
102
+ f"*{thread.tier_label} | hits: {thread.hits} | last touched: {thread.last_touched} | tags: {', '.join(thread.tags)}*",
103
+ "",
104
+ ]
105
+
106
+ if thread.summary_lines:
107
+ lines.extend(
108
+ render_markdown_lines(
109
+ thread.summary_lines,
110
+ title_lookup=title_lookup,
111
+ source_dir=thread.source_path.parent,
112
+ )
113
+ )
114
+ else:
115
+ lines.append("*Missing Summary section in source thread.*")
116
+
117
+ see_also = render_see_also(thread, title_lookup)
118
+ if see_also:
119
+ lines.extend(["", "**See also**", ""])
120
+ lines.extend(see_also)
121
+ return lines
122
+
123
+
124
+ def render_main_page(repo_root: Path, output_path: Path) -> str:
125
+ memex_dir = repo_root / "gator-command"
126
+ identity_path = memex_dir / "identity.md"
127
+ threads = load_threads(memex_dir)
128
+ title_lookup = {thread.source_path.resolve(): thread.title for thread in threads}
129
+ grouped = category_threads(threads)
130
+
131
+ today = dt.date.today().isoformat()
132
+ lines = [
133
+ "# Memex - Personal Knowledge System",
134
+ "",
135
+ f"**Last rendered:** {today} ",
136
+ f"**Threads:** {len(threads)} ",
137
+ f"**Categories:** {len(CATEGORY_ORDER)}",
138
+ "",
139
+ "This page is auto-generated from the Memex thread graph. The Memex is the source of truth; this file is a rendered summary for human navigation.",
140
+ "",
141
+ "**To regenerate:** `python gator-command/scripts/generate_wiki.py && python gator-command/scripts/generate_markdown.py`",
142
+ "",
143
+ "**Memex CLI** (`python gator-command/scripts/memex.py`):",
144
+ "- `memex status` — graph health, inbox count, patterns due, active threads (`--full` for complete state dump)",
145
+ "- `memex search <query>` — full-text search across threads, artifacts, designs, reports",
146
+ "- `memex read <type> <name>` — render thread/artifact/design/report (`--section` for specific sections)",
147
+ "- Add `--format json` for structured agent output, `--role <name>` for role-aware views",
148
+ "",
149
+ ]
150
+
151
+ lines.extend(render_identity(identity_path, title_lookup))
152
+ lines.extend(["", "", "## Categories", ""])
153
+
154
+ for key, label in CATEGORY_ORDER:
155
+ titles = [thread.title for thread in grouped.get(key, [])]
156
+ if not titles:
157
+ continue
158
+ lines.append(f"- [{label}](#{slugify_heading(label)}) - {', '.join(titles)}")
159
+
160
+ for key, label in CATEGORY_ORDER:
161
+ category_items = grouped.get(key, [])
162
+ if not category_items:
163
+ continue
164
+ lines.extend(["", "", f"## {label}", ""])
165
+ for thread in category_items:
166
+ lines.extend(render_thread(thread, title_lookup))
167
+ lines.extend(["", ""])
168
+
169
+ while lines and lines[-1] == "":
170
+ lines.pop()
171
+
172
+ lines.extend(
173
+ [
174
+ "",
175
+ "",
176
+ "_This Markdown render was generated mechanically from the Memex thread graph. Summaries are extracted from source threads; no thread prose is synthesized during rendering._",
177
+ "",
178
+ ]
179
+ )
180
+
181
+ text = "\n".join(lines)
182
+ output_path.write_text(text, encoding="utf-8")
183
+ return text
184
+
185
+
186
+ def main() -> int:
187
+ parser = argparse.ArgumentParser(description=__doc__)
188
+ parser.add_argument(
189
+ "--repo-root",
190
+ default=Path(__file__).resolve().parents[2],
191
+ type=Path,
192
+ help="Path to the repository root.",
193
+ )
194
+ parser.add_argument(
195
+ "--output",
196
+ default=Path("docs/wiki/Main_Page.md"),
197
+ type=Path,
198
+ help="Output path, relative to repo root unless absolute.",
199
+ )
200
+ args = parser.parse_args()
201
+
202
+ repo_root = args.repo_root.resolve()
203
+ output_path = args.output
204
+ if not output_path.is_absolute():
205
+ output_path = repo_root / output_path
206
+
207
+ render_main_page(repo_root=repo_root, output_path=output_path)
208
+ return 0
209
+
210
+
211
+ if __name__ == "__main__":
212
+ raise SystemExit(main())