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.
- gator_command/__init__.py +2 -0
- gator_command/cli.py +137 -0
- gator_command/scripts/crawler.py +633 -0
- gator_command/scripts/dashboard/dashboard.css +982 -0
- gator_command/scripts/dashboard/dashboard.html +84 -0
- gator_command/scripts/dashboard/dashboard.js +419 -0
- gator_command/scripts/dashboard/views/audit.js +270 -0
- gator_command/scripts/dashboard/views/fleet.js +307 -0
- gator_command/scripts/dashboard/views/repo.js +599 -0
- gator_command/scripts/dashboard/views/settings.js +173 -0
- gator_command/scripts/dashboard/views/updates.js +308 -0
- gator_command/scripts/enforcer-prompt.md +22 -0
- gator_command/scripts/extract-claude-sessions.py +489 -0
- gator_command/scripts/extract-codex-sessions.py +477 -0
- gator_command/scripts/extract-gemini-sessions.py +410 -0
- gator_command/scripts/gator-audit.py +956 -0
- gator_command/scripts/gator-charter-draft.py +919 -0
- gator_command/scripts/gator-charter-lint.py +427 -0
- gator_command/scripts/gator-charter-verify.py +606 -0
- gator_command/scripts/gator-dashboard.py +1271 -0
- gator_command/scripts/gator-deploy.py +916 -0
- gator_command/scripts/gator-drift.py +569 -0
- gator_command/scripts/gator-enforce.py +82 -0
- gator_command/scripts/gator-fleet-intel.py +460 -0
- gator_command/scripts/gator-fleet-report.py +615 -0
- gator_command/scripts/gator-init-command-post.py +315 -0
- gator_command/scripts/gator-init.py +434 -0
- gator_command/scripts/gator-machine-id.py +153 -0
- gator_command/scripts/gator-policy-status.py +631 -0
- gator_command/scripts/gator-pulse.py +459 -0
- gator_command/scripts/gator-repo-status.py +649 -0
- gator_command/scripts/gator-session-common.py +372 -0
- gator_command/scripts/gator-session-sink.py +831 -0
- gator_command/scripts/gator-sessions.py +1244 -0
- gator_command/scripts/gator-update.py +615 -0
- gator_command/scripts/gator-version.py +38 -0
- gator_command/scripts/gator_core.py +489 -0
- gator_command/scripts/gator_remote.py +381 -0
- gator_command/scripts/gator_runtime.py +142 -0
- gator_command/scripts/gatorize-actions.sh +989 -0
- gator_command/scripts/gatorize-lib.sh +166 -0
- gator_command/scripts/gatorize-post.sh +394 -0
- gator_command/scripts/gatorize.py +1163 -0
- gator_command/scripts/gatorize.sh +185 -0
- gator_command/scripts/generate_markdown.py +212 -0
- gator_command/scripts/generate_wiki.py +424 -0
- gator_command/scripts/graph_health.py +780 -0
- gator_command/scripts/memex-lint.py +286 -0
- gator_command/scripts/memex-lint.sh +205 -0
- gator_command/scripts/memex.py +1472 -0
- gator_command/scripts/memex_formatters.py +191 -0
- gator_command/scripts/memex_state.py +236 -0
- gator_command/scripts/spawn.py +650 -0
- gator_command/templates/gator-starter/blueprints/README.md +32 -0
- gator_command/templates/gator-starter/charterignore +53 -0
- gator_command/templates/gator-starter/charters/README.md +178 -0
- gator_command/templates/gator-starter/charters/_template.md +31 -0
- gator_command/templates/gator-starter/commands/commit.md +33 -0
- gator_command/templates/gator-starter/commands/init.md +11 -0
- gator_command/templates/gator-starter/commands/update.md +5 -0
- gator_command/templates/gator-starter/constitution.md +165 -0
- gator_command/templates/gator-starter/field-guides/README.md +25 -0
- gator_command/templates/gator-starter/gator-start-up.md +119 -0
- gator_command/templates/gator-starter/procedures/charter-alignment.md +83 -0
- gator_command/templates/gator-starter/procedures/enforcer-review.md +317 -0
- gator_command/templates/gator-starter/procedures/field-guide-generation.md +176 -0
- gator_command/templates/gator-starter/procedures/knowledge-capture.md +57 -0
- gator_command/templates/gator-starter/procedures/significance-check.md +69 -0
- gator_command/templates/gator-starter/reference-notes/concierge-responses.md +535 -0
- gator_command/templates/gator-starter/reference-notes/dangerous-patterns.md +91 -0
- gator_command/templates/gator-starter/reference-notes/dashboard-operations.md +22 -0
- gator_command/templates/gator-starter/reference-notes/enforcer-configuration.md +232 -0
- gator_command/templates/gator-starter/reference-notes/example-project.md +289 -0
- gator_command/templates/gator-starter/reference-notes/failure-modes-and-self-correction.md +72 -0
- gator_command/templates/gator-starter/reference-notes/git-workflow.md +60 -0
- gator_command/templates/gator-starter/reference-notes/identity-and-ownership.md +37 -0
- gator_command/templates/gator-starter/reference-notes/refactor-approach.md +155 -0
- gator_command/templates/gator-starter/reference-notes/what-gator-requires-from-a-model.md +108 -0
- gator_command/templates/gator-starter/reference-notes/why-navigation-coding-feels-different.md +99 -0
- gator_command/templates/gator-starter/reference-notes/workflow-profiles.md +155 -0
- gator_command/templates/gator-starter/scripts/__pycache__/enforcer-review.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-approve.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-init.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-pre-commit.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-update.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/enforcer-prompt.md +55 -0
- gator_command/templates/gator-starter/scripts/enforcer-review.py +1551 -0
- gator_command/templates/gator-starter/scripts/gator-approve.py +139 -0
- gator_command/templates/gator-starter/scripts/gator-enforce.py +82 -0
- gator_command/templates/gator-starter/scripts/gator-init.py +434 -0
- gator_command/templates/gator-starter/scripts/gator-pre-commit.py +2670 -0
- gator_command/templates/gator-starter/scripts/gator-pulse.py +459 -0
- gator_command/templates/gator-starter/scripts/gator-update.py +615 -0
- gator_command/templates/gator-starter/scripts/gator-version.py +38 -0
- gator_command/templates/gator-starter/scripts/gator_core.py +487 -0
- gator_command/templates/gator-starter/scripts/hooks/__pycache__/commit-msgcpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/hooks/__pycache__/post-commitcpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/hooks/__pycache__/pre-commitcpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/hooks/commit-msg +5 -0
- gator_command/templates/gator-starter/scripts/hooks/post-commit +7 -0
- gator_command/templates/gator-starter/scripts/hooks/pre-commit +5 -0
- gator_command/templates/gator-starter/sessions/.gitignore +7 -0
- gator_command/templates/gator-starter/vault/.gitkeep +0 -0
- gator_command/templates/gator-starter/whiteboard.md +5 -0
- gator_command-1.0.0.dist-info/METADATA +122 -0
- gator_command-1.0.0.dist-info/RECORD +110 -0
- gator_command-1.0.0.dist-info/WHEEL +5 -0
- gator_command-1.0.0.dist-info/entry_points.txt +2 -0
- gator_command-1.0.0.dist-info/licenses/LICENSE +21 -0
- gator_command-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repo.js — Repo view for Gator Dashboard.
|
|
3
|
+
*
|
|
4
|
+
* Markdown file browser for a repo's .gator/ knowledge layer.
|
|
5
|
+
* Secondary sidebar lists files, main content renders markdown.
|
|
6
|
+
* Default document: pulse.md (if it exists), otherwise mission.md.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
(function () {
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
window.GatorViews = window.GatorViews || {};
|
|
13
|
+
|
|
14
|
+
function escHtml(str) {
|
|
15
|
+
return String(str)
|
|
16
|
+
.replace(/&/g, "&")
|
|
17
|
+
.replace(/</g, "<")
|
|
18
|
+
.replace(/>/g, ">")
|
|
19
|
+
.replace(/"/g, """);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ── simple markdown renderer ──────────────────────────────────────────────
|
|
23
|
+
// Handles: headers, bold, italic, links, code blocks, inline code,
|
|
24
|
+
// lists, blockquotes, horizontal rules, tables. No external deps.
|
|
25
|
+
|
|
26
|
+
function renderMarkdown(md) {
|
|
27
|
+
let html = "";
|
|
28
|
+
const lines = md.split("\n");
|
|
29
|
+
let i = 0;
|
|
30
|
+
let inCodeBlock = false;
|
|
31
|
+
let codeLines = [];
|
|
32
|
+
let inList = false;
|
|
33
|
+
|
|
34
|
+
while (i < lines.length) {
|
|
35
|
+
const line = lines[i];
|
|
36
|
+
|
|
37
|
+
// Fenced code blocks
|
|
38
|
+
if (line.trimStart().startsWith("```")) {
|
|
39
|
+
if (inCodeBlock) {
|
|
40
|
+
html += '<pre class="md-code-block">' + escHtml(codeLines.join("\n")) + "</pre>\n";
|
|
41
|
+
codeLines = [];
|
|
42
|
+
inCodeBlock = false;
|
|
43
|
+
} else {
|
|
44
|
+
if (inList) { html += "</ul>\n"; inList = false; }
|
|
45
|
+
inCodeBlock = true;
|
|
46
|
+
}
|
|
47
|
+
i++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (inCodeBlock) {
|
|
51
|
+
codeLines.push(line);
|
|
52
|
+
i++;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Blank line
|
|
57
|
+
if (line.trim() === "") {
|
|
58
|
+
if (inList) { html += "</ul>\n"; inList = false; }
|
|
59
|
+
i++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Horizontal rule
|
|
64
|
+
if (/^---+$/.test(line.trim()) || /^\*\*\*+$/.test(line.trim())) {
|
|
65
|
+
if (inList) { html += "</ul>\n"; inList = false; }
|
|
66
|
+
html += "<hr>\n";
|
|
67
|
+
i++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Headers
|
|
72
|
+
const hMatch = line.match(/^(#{1,6})\s+(.*)/);
|
|
73
|
+
if (hMatch) {
|
|
74
|
+
if (inList) { html += "</ul>\n"; inList = false; }
|
|
75
|
+
const level = hMatch[1].length;
|
|
76
|
+
html += `<h${level}>${inlineFormat(hMatch[2])}</h${level}>\n`;
|
|
77
|
+
i++;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Blockquote
|
|
82
|
+
if (line.startsWith(">")) {
|
|
83
|
+
if (inList) { html += "</ul>\n"; inList = false; }
|
|
84
|
+
const text = line.replace(/^>\s?/, "");
|
|
85
|
+
html += `<blockquote class="md-blockquote">${inlineFormat(text)}</blockquote>\n`;
|
|
86
|
+
i++;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Unordered list
|
|
91
|
+
if (/^\s*[-*+]\s+/.test(line)) {
|
|
92
|
+
if (!inList) { html += "<ul>\n"; inList = true; }
|
|
93
|
+
const text = line.replace(/^\s*[-*+]\s+/, "");
|
|
94
|
+
html += `<li>${inlineFormat(text)}</li>\n`;
|
|
95
|
+
i++;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Ordered list
|
|
100
|
+
if (/^\s*\d+\.\s+/.test(line)) {
|
|
101
|
+
if (!inList) { html += "<ul>\n"; inList = true; }
|
|
102
|
+
const text = line.replace(/^\s*\d+\.\s+/, "");
|
|
103
|
+
html += `<li>${inlineFormat(text)}</li>\n`;
|
|
104
|
+
i++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Table detection
|
|
109
|
+
if (line.includes("|") && i + 1 < lines.length && /^\|?\s*[-:]+/.test(lines[i + 1])) {
|
|
110
|
+
if (inList) { html += "</ul>\n"; inList = false; }
|
|
111
|
+
html += renderTable(lines, i);
|
|
112
|
+
// Skip past table
|
|
113
|
+
while (i < lines.length && lines[i].includes("|")) i++;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Paragraph
|
|
118
|
+
if (inList) { html += "</ul>\n"; inList = false; }
|
|
119
|
+
html += `<p>${inlineFormat(line)}</p>\n`;
|
|
120
|
+
i++;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (inList) html += "</ul>\n";
|
|
124
|
+
if (inCodeBlock) {
|
|
125
|
+
html += '<pre class="md-code-block">' + escHtml(codeLines.join("\n")) + "</pre>\n";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return html;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function inlineFormat(text) {
|
|
132
|
+
// Order matters: bold before italic, links before other patterns
|
|
133
|
+
let s = escHtml(text);
|
|
134
|
+
// Images:  — must come before links
|
|
135
|
+
s = s.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, function (_, alt, src) {
|
|
136
|
+
// Resolve relative image paths through the file endpoint
|
|
137
|
+
if (!src.startsWith("http") && window._gatorRepoContext) {
|
|
138
|
+
const ctx = window._gatorRepoContext;
|
|
139
|
+
let imgPath = src;
|
|
140
|
+
if (ctx.currentDir && !src.startsWith("/")) {
|
|
141
|
+
imgPath = ctx.currentDir + "/" + src;
|
|
142
|
+
}
|
|
143
|
+
imgPath = imgPath.replace(/^\.\//, "");
|
|
144
|
+
// Route through binary file endpoint
|
|
145
|
+
src = "/api/repo/" + encodeURIComponent(ctx.repoName) + "/raw/" + imgPath.split("/").map(encodeURIComponent).join("/");
|
|
146
|
+
}
|
|
147
|
+
return '<img src="' + src + '" alt="' + alt + '" style="max-width:100%;margin:8px 0">';
|
|
148
|
+
});
|
|
149
|
+
// Links: [text](url)
|
|
150
|
+
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
|
|
151
|
+
// Bold: **text** or __text__
|
|
152
|
+
s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
153
|
+
s = s.replace(/__([^_]+)__/g, "<strong>$1</strong>");
|
|
154
|
+
// Italic: *text* or _text_
|
|
155
|
+
s = s.replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
156
|
+
// Inline code: `text`
|
|
157
|
+
s = s.replace(/`([^`]+)`/g, '<code class="md-inline-code">$1</code>');
|
|
158
|
+
return s;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function renderTable(lines, startIdx) {
|
|
162
|
+
let html = '<table class="md-table"><thead><tr>';
|
|
163
|
+
const headerCells = lines[startIdx].split("|").map(c => c.trim()).filter(Boolean);
|
|
164
|
+
for (const cell of headerCells) {
|
|
165
|
+
html += `<th>${inlineFormat(cell)}</th>`;
|
|
166
|
+
}
|
|
167
|
+
html += "</tr></thead><tbody>";
|
|
168
|
+
|
|
169
|
+
for (let j = startIdx + 2; j < lines.length; j++) {
|
|
170
|
+
if (!lines[j].includes("|")) break;
|
|
171
|
+
const cells = lines[j].split("|").map(c => c.trim()).filter(Boolean);
|
|
172
|
+
html += "<tr>";
|
|
173
|
+
for (const cell of cells) {
|
|
174
|
+
html += `<td>${inlineFormat(cell)}</td>`;
|
|
175
|
+
}
|
|
176
|
+
html += "</tr>";
|
|
177
|
+
}
|
|
178
|
+
html += "</tbody></table>\n";
|
|
179
|
+
return html;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── file grouping ─────────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
// Priority order for file sidebar
|
|
185
|
+
const FILE_ORDER = [
|
|
186
|
+
"pulse.md", "mission.md", "roadmap.md", "inbox.md", "issues.md",
|
|
187
|
+
"whiteboard.md", "identity.md", "patterns.md",
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
// Files to hide from the browser (infrastructure, not user content)
|
|
191
|
+
const HIDDEN_FILES = new Set([
|
|
192
|
+
"commit_draft.md", "constitution.md", "gator-start-up.md",
|
|
193
|
+
"commit_issues.md",
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
// Directories to hide entirely
|
|
197
|
+
const HIDDEN_DIRS = new Set(["scripts", "hooks"]);
|
|
198
|
+
|
|
199
|
+
// Default template files — only show user-created content in these dirs
|
|
200
|
+
const DEFAULT_PROCEDURES = new Set([
|
|
201
|
+
"charter-alignment.md", "enforcer-review.md", "field-guide-generation.md",
|
|
202
|
+
"knowledge-capture.md", "significance-check.md",
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
const DEFAULT_REFERENCE_NOTES = new Set([
|
|
206
|
+
"concierge-responses.md", "dangerous-patterns.md", "enforcer-configuration.md",
|
|
207
|
+
"example-project.md", "failure-modes-and-self-correction.md", "git-workflow.md",
|
|
208
|
+
"identity-and-ownership.md", "refactor-approach.md",
|
|
209
|
+
"what-gator-requires-from-a-model.md", "why-navigation-coding-feels-different.md",
|
|
210
|
+
"workflow-profiles.md",
|
|
211
|
+
]);
|
|
212
|
+
|
|
213
|
+
function filterFiles(files) {
|
|
214
|
+
return files.filter(f => {
|
|
215
|
+
const dir = f.dir || "";
|
|
216
|
+
// Normalize: strip gator-command/ prefix for filter matching
|
|
217
|
+
const normDir = dir.replace(/^gator-command\//, "");
|
|
218
|
+
|
|
219
|
+
// Hide specific top-level files
|
|
220
|
+
if (!dir && HIDDEN_FILES.has(f.name)) return false;
|
|
221
|
+
if (dir === "gator-command/" && HIDDEN_FILES.has(f.name)) return false;
|
|
222
|
+
// Hide entire directories
|
|
223
|
+
const topDir = normDir.split("/")[0];
|
|
224
|
+
if (topDir && HIDDEN_DIRS.has(topDir)) return false;
|
|
225
|
+
// Hide default template files in procedures/ and reference-notes/
|
|
226
|
+
if (normDir === "procedures" && DEFAULT_PROCEDURES.has(f.name)) return false;
|
|
227
|
+
if (normDir === "reference-notes" && DEFAULT_REFERENCE_NOTES.has(f.name)) return false;
|
|
228
|
+
// In .gator/ source: hide templates/ and deploy/ (fleet repo scaffolding)
|
|
229
|
+
// In gator-command/ source: keep them (product source code)
|
|
230
|
+
if (f.source === ".gator" && normDir.startsWith("templates")) return false;
|
|
231
|
+
if (f.source === ".gator" && normDir.startsWith("deploy")) return false;
|
|
232
|
+
return true;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function sortFiles(files) {
|
|
237
|
+
const filtered = filterFiles(files);
|
|
238
|
+
const top = [];
|
|
239
|
+
const dirs = {};
|
|
240
|
+
const rest = [];
|
|
241
|
+
|
|
242
|
+
for (const f of filtered) {
|
|
243
|
+
const orderIdx = FILE_ORDER.indexOf(f.name);
|
|
244
|
+
if (!f.dir && orderIdx >= 0) {
|
|
245
|
+
top.push({ ...f, _order: orderIdx });
|
|
246
|
+
} else if (f.dir) {
|
|
247
|
+
if (!dirs[f.dir]) dirs[f.dir] = [];
|
|
248
|
+
dirs[f.dir].push(f);
|
|
249
|
+
} else {
|
|
250
|
+
rest.push(f);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
top.sort((a, b) => a._order - b._order);
|
|
255
|
+
return { top, dirs, rest };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── main render ───────────────────────────────────────────────────────────
|
|
259
|
+
|
|
260
|
+
window.GatorViews.repo = function (data, container, repoName, version) {
|
|
261
|
+
const verLabel = version ? ` @ ${version.substring(0, 8)}` : "";
|
|
262
|
+
container.innerHTML = `
|
|
263
|
+
<div class="repo-browser">
|
|
264
|
+
<div class="repo-sidebar" id="repo-file-list">
|
|
265
|
+
<div class="repo-sidebar-header">${escHtml(repoName)}${version ? '<span class="muted" style="font-size:10px;margin-left:6px;">' + escHtml(verLabel) + '</span>' : ''}</div>
|
|
266
|
+
<div class="muted" style="padding:8px 12px;font-size:12px">Loading files...</div>
|
|
267
|
+
</div>
|
|
268
|
+
<div class="repo-sidebar-resize" id="repo-resize-handle"></div>
|
|
269
|
+
<div class="repo-content" id="repo-file-content">
|
|
270
|
+
<div class="muted" style="padding:40px;text-align:center">Select a file to view</div>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
`;
|
|
274
|
+
|
|
275
|
+
loadFileList(repoName, container, version);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
async function loadFileList(repoName, container, version) {
|
|
279
|
+
const sidebar = container.querySelector("#repo-file-list");
|
|
280
|
+
const content = container.querySelector("#repo-file-content");
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
let filesUrl = `/api/repo/${encodeURIComponent(repoName)}/files`;
|
|
284
|
+
if (version) filesUrl += `?version=${encodeURIComponent(version)}`;
|
|
285
|
+
const resp = await fetch(filesUrl);
|
|
286
|
+
const data = await resp.json();
|
|
287
|
+
|
|
288
|
+
if (data.error) {
|
|
289
|
+
sidebar.innerHTML = `<div class="repo-sidebar-header">${escHtml(repoName)}</div><div class="muted" style="padding:8px 12px">${escHtml(data.error)}</div>`;
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const files = data.files || [];
|
|
294
|
+
const { top, dirs, rest } = sortFiles(files);
|
|
295
|
+
|
|
296
|
+
// Separate files by source: .gator, gator-command, source (repo)
|
|
297
|
+
const gatorTop = top.filter(f => f.source !== "repo" && !f.dir?.startsWith("gator-command"));
|
|
298
|
+
const gatorRest = rest.filter(f => f.source !== "repo" && !f.dir?.startsWith("gator-command"));
|
|
299
|
+
const gatorDirs = {};
|
|
300
|
+
const gcDirs = {};
|
|
301
|
+
const sourceDirs = {};
|
|
302
|
+
for (const [dir, items] of Object.entries(dirs)) {
|
|
303
|
+
if (dir.startsWith("source")) {
|
|
304
|
+
sourceDirs[dir] = items;
|
|
305
|
+
} else if (dir.startsWith("gator-command")) {
|
|
306
|
+
gcDirs[dir] = items;
|
|
307
|
+
} else {
|
|
308
|
+
gatorDirs[dir] = items;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let html = `<div class="repo-sidebar-header">${escHtml(repoName)}</div>`;
|
|
313
|
+
|
|
314
|
+
// ── .gator/ section (collapsible, open by default)
|
|
315
|
+
const hasGator = gatorTop.length > 0 || gatorRest.length > 0 || Object.keys(gatorDirs).length > 0;
|
|
316
|
+
if (hasGator) {
|
|
317
|
+
html += `<button class="repo-sidebar-section" data-group="section-gator"><span class="group-arrow">▾</span> .gator/</button>`;
|
|
318
|
+
html += `<div class="repo-dir-contents" id="section-gator">`;
|
|
319
|
+
for (const f of gatorTop) html += fileItem(repoName, f);
|
|
320
|
+
for (const f of gatorRest) html += fileItem(repoName, f);
|
|
321
|
+
for (const dir of Object.keys(gatorDirs).sort()) {
|
|
322
|
+
const groupId = "dir-" + dir.replace(/[^a-zA-Z0-9]/g, "-");
|
|
323
|
+
html += `<button class="repo-sidebar-group" data-group="${groupId}"><span class="group-arrow">▸</span> ${escHtml(dir)}/</button>`;
|
|
324
|
+
html += `<div class="repo-dir-contents" id="${groupId}" style="display:none">`;
|
|
325
|
+
for (const f of gatorDirs[dir]) html += fileItem(repoName, f, true);
|
|
326
|
+
html += `</div>`;
|
|
327
|
+
}
|
|
328
|
+
html += `</div>`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ── gator-command/ section (command-post repos only, collapsible)
|
|
332
|
+
const gcTopFiles = top.filter(f => f.dir?.startsWith("gator-command") && !f.dir?.includes("/"));
|
|
333
|
+
const hasGc = gcTopFiles.length > 0 || Object.keys(gcDirs).length > 0;
|
|
334
|
+
if (hasGc) {
|
|
335
|
+
html += `<button class="repo-sidebar-section" data-group="section-gc"><span class="group-arrow">▸</span> gator-command/</button>`;
|
|
336
|
+
html += `<div class="repo-dir-contents" id="section-gc" style="display:none">`;
|
|
337
|
+
// Top-level gator-command files
|
|
338
|
+
const gcRoot = gcDirs["gator-command"] || [];
|
|
339
|
+
for (const f of gcRoot) html += fileItem(repoName, f);
|
|
340
|
+
// Subdirectories
|
|
341
|
+
for (const dir of Object.keys(gcDirs).sort()) {
|
|
342
|
+
if (dir === "gator-command") continue;
|
|
343
|
+
const groupId = "dir-" + dir.replace(/[^a-zA-Z0-9]/g, "-");
|
|
344
|
+
html += `<button class="repo-sidebar-group" data-group="${groupId}"><span class="group-arrow">▸</span> ${escHtml(dir.replace("gator-command/", ""))}/</button>`;
|
|
345
|
+
html += `<div class="repo-dir-contents" id="${groupId}" style="display:none">`;
|
|
346
|
+
for (const f of gcDirs[dir]) html += fileItem(repoName, f, true);
|
|
347
|
+
html += `</div>`;
|
|
348
|
+
}
|
|
349
|
+
html += `</div>`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ── source/ section (repo code, collapsible, collapsed by default)
|
|
353
|
+
const hasSource = Object.keys(sourceDirs).length > 0;
|
|
354
|
+
if (hasSource) {
|
|
355
|
+
html += `<button class="repo-sidebar-section" data-group="section-source"><span class="group-arrow">▸</span> source/</button>`;
|
|
356
|
+
html += `<div class="repo-dir-contents" id="section-source" style="display:none">`;
|
|
357
|
+
// Root source files
|
|
358
|
+
const srcRoot = sourceDirs["source"] || [];
|
|
359
|
+
for (const f of srcRoot) html += fileItem(repoName, f);
|
|
360
|
+
// Subdirectories
|
|
361
|
+
for (const dir of Object.keys(sourceDirs).sort()) {
|
|
362
|
+
if (dir === "source") continue;
|
|
363
|
+
const groupId = "dir-" + dir.replace(/[^a-zA-Z0-9]/g, "-");
|
|
364
|
+
const shortDir = dir.replace("source/", "");
|
|
365
|
+
html += `<button class="repo-sidebar-group" data-group="${groupId}"><span class="group-arrow">▸</span> ${escHtml(shortDir)}/</button>`;
|
|
366
|
+
html += `<div class="repo-dir-contents" id="${groupId}" style="display:none">`;
|
|
367
|
+
for (const f of sourceDirs[dir]) html += fileItem(repoName, f, true);
|
|
368
|
+
html += `</div>`;
|
|
369
|
+
}
|
|
370
|
+
html += `</div>`;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
sidebar.innerHTML = html;
|
|
374
|
+
|
|
375
|
+
// Section and directory collapse/expand handlers
|
|
376
|
+
sidebar.querySelectorAll(".repo-sidebar-section, .repo-sidebar-group").forEach(group => {
|
|
377
|
+
group.addEventListener("click", function () {
|
|
378
|
+
const contents = document.getElementById(this.dataset.group);
|
|
379
|
+
const arrow = this.querySelector(".group-arrow");
|
|
380
|
+
if (contents.style.display === "none") {
|
|
381
|
+
contents.style.display = "block";
|
|
382
|
+
arrow.innerHTML = "▾"; // down arrow
|
|
383
|
+
} else {
|
|
384
|
+
contents.style.display = "none";
|
|
385
|
+
arrow.innerHTML = "▸"; // right arrow
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// File click handlers
|
|
391
|
+
sidebar.querySelectorAll(".repo-file-item").forEach(item => {
|
|
392
|
+
item.addEventListener("click", function () {
|
|
393
|
+
sidebar.querySelectorAll(".repo-file-item").forEach(i => i.classList.remove("active"));
|
|
394
|
+
this.classList.add("active");
|
|
395
|
+
loadFile(repoName, this.dataset.path, content, window._gatorRepoVersion);
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Auto-load default document
|
|
400
|
+
const defaultFile = top.find(f => f.name === "pulse.md")
|
|
401
|
+
|| top.find(f => f.name === "mission.md")
|
|
402
|
+
|| top[0];
|
|
403
|
+
if (defaultFile) {
|
|
404
|
+
const defaultItem = sidebar.querySelector(`[data-path="${defaultFile.path}"]`);
|
|
405
|
+
if (defaultItem) {
|
|
406
|
+
defaultItem.classList.add("active");
|
|
407
|
+
loadFile(repoName, defaultFile.path, content, window._gatorRepoVersion);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Resize handle
|
|
412
|
+
initResizeHandle(sidebar);
|
|
413
|
+
|
|
414
|
+
} catch (err) {
|
|
415
|
+
sidebar.innerHTML = `<div class="repo-sidebar-header">${escHtml(repoName)}</div><div class="muted" style="padding:8px 12px">Failed to load files</div>`;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function initResizeHandle(sidebar) {
|
|
420
|
+
const browser = sidebar.parentElement;
|
|
421
|
+
if (!browser) return;
|
|
422
|
+
const handle = browser.querySelector("#repo-resize-handle");
|
|
423
|
+
if (!handle) return;
|
|
424
|
+
let startX, startWidth;
|
|
425
|
+
handle.addEventListener("mousedown", function (e) {
|
|
426
|
+
startX = e.clientX;
|
|
427
|
+
startWidth = sidebar.offsetWidth;
|
|
428
|
+
e.preventDefault();
|
|
429
|
+
function onMove(e) {
|
|
430
|
+
const newWidth = startWidth + (e.clientX - startX);
|
|
431
|
+
if (newWidth >= 120 && newWidth <= 500) {
|
|
432
|
+
sidebar.style.width = newWidth + "px";
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function onUp() {
|
|
436
|
+
document.removeEventListener("mousemove", onMove);
|
|
437
|
+
document.removeEventListener("mouseup", onUp);
|
|
438
|
+
}
|
|
439
|
+
document.addEventListener("mousemove", onMove);
|
|
440
|
+
document.addEventListener("mouseup", onUp);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function fileItem(repoName, f, indented) {
|
|
445
|
+
const label = f.name.replace(/\.md$/, "");
|
|
446
|
+
const indent = indented ? ' style="padding-left:24px"' : "";
|
|
447
|
+
return `<button class="repo-file-item" data-path="${escHtml(f.path)}"${indent}>${escHtml(label)}</button>`;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function loadFile(repoName, filePath, contentEl, version) {
|
|
451
|
+
contentEl.innerHTML = '<div class="muted" style="padding:40px;text-align:center">Loading...</div>';
|
|
452
|
+
// Set context for image path resolution
|
|
453
|
+
const currentDir = filePath.includes("/") ? filePath.substring(0, filePath.lastIndexOf("/")) : "";
|
|
454
|
+
window._gatorRepoContext = { repoName: repoName, currentDir: currentDir };
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
// Encode path segments individually — don't encode the slashes
|
|
458
|
+
const encodedPath = filePath.split("/").map(encodeURIComponent).join("/");
|
|
459
|
+
let fileUrl = `/api/repo/${encodeURIComponent(repoName)}/file/${encodedPath}`;
|
|
460
|
+
if (version) fileUrl += `?version=${encodeURIComponent(version)}`;
|
|
461
|
+
const resp = await fetch(fileUrl);
|
|
462
|
+
const data = await resp.json();
|
|
463
|
+
|
|
464
|
+
if (data.error) {
|
|
465
|
+
contentEl.innerHTML = `<div class="muted" style="padding:40px">${escHtml(data.error)}</div>`;
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const dateLabel = data.version
|
|
470
|
+
? `Viewing: ${escHtml(data.version.substring(0, 8))}${data.last_modified ? ' (' + escHtml(data.last_modified.substring(0, 19)) + ')' : ''}`
|
|
471
|
+
: `Last updated: ${escHtml((data.last_modified || "").substring(0, 19))}`;
|
|
472
|
+
const lastMod = data.last_modified || data.version
|
|
473
|
+
? `<span class="repo-file-date" style="position:relative;">${dateLabel}<button class="history-toggle-btn" title="Show file history">▾</button><div class="history-dropdown" id="history-dropdown" style="display:none;"></div></span>`
|
|
474
|
+
: "";
|
|
475
|
+
// Build the repo-relative path for copying
|
|
476
|
+
let copyPath;
|
|
477
|
+
if (filePath.startsWith("source/")) {
|
|
478
|
+
copyPath = filePath.substring("source/".length); // strip source/ prefix — it's repo root
|
|
479
|
+
} else if (filePath.startsWith("gator-command/")) {
|
|
480
|
+
copyPath = filePath; // already repo-relative
|
|
481
|
+
} else {
|
|
482
|
+
copyPath = ".gator/" + filePath; // .gator/ files
|
|
483
|
+
}
|
|
484
|
+
contentEl.innerHTML = `
|
|
485
|
+
<div class="repo-file-header">
|
|
486
|
+
<span>${escHtml(filePath)} <button class="copy-path-btn" title="Copy path" data-path="${escHtml(copyPath)}">⎘</button></span>
|
|
487
|
+
${lastMod}
|
|
488
|
+
</div>
|
|
489
|
+
<div class="repo-markdown">${filePath.endsWith(".md") ? renderMarkdown(data.content || "") : '<pre class="md-code-block">' + escHtml(data.content || "") + '</pre>'}</div>
|
|
490
|
+
`;
|
|
491
|
+
|
|
492
|
+
// Copy path button handler
|
|
493
|
+
contentEl.querySelector(".copy-path-btn").addEventListener("click", function (e) {
|
|
494
|
+
e.stopPropagation();
|
|
495
|
+
const path = this.dataset.path;
|
|
496
|
+
navigator.clipboard.writeText(path).then(() => {
|
|
497
|
+
this.textContent = "\u2713";
|
|
498
|
+
setTimeout(() => { this.innerHTML = "⎘"; }, 1500);
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// History dropdown handler
|
|
503
|
+
const histBtn = contentEl.querySelector(".history-toggle-btn");
|
|
504
|
+
const histDropdown = contentEl.querySelector("#history-dropdown");
|
|
505
|
+
if (histBtn && histDropdown) {
|
|
506
|
+
let historyLoaded = false;
|
|
507
|
+
|
|
508
|
+
histBtn.addEventListener("click", async function (e) {
|
|
509
|
+
e.stopPropagation();
|
|
510
|
+
|
|
511
|
+
// Toggle visibility
|
|
512
|
+
if (histDropdown.style.display !== "none") {
|
|
513
|
+
histDropdown.style.display = "none";
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
histDropdown.style.display = "block";
|
|
518
|
+
|
|
519
|
+
if (historyLoaded) return;
|
|
520
|
+
|
|
521
|
+
histDropdown.innerHTML = '<div style="padding:8px; color:#aaa;">Loading…</div>';
|
|
522
|
+
try {
|
|
523
|
+
const encodedHist = filePath.split("/").map(encodeURIComponent).join("/");
|
|
524
|
+
const hResp = await fetch(`/api/repo/${encodeURIComponent(repoName)}/history/${encodedHist}`);
|
|
525
|
+
const hData = await hResp.json();
|
|
526
|
+
|
|
527
|
+
if (!hData.commits || !hData.commits.length) {
|
|
528
|
+
histDropdown.innerHTML = '<div style="padding:8px; color:#aaa;">No history available</div>';
|
|
529
|
+
historyLoaded = true;
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
let html = "";
|
|
534
|
+
for (const c of hData.commits) {
|
|
535
|
+
const dateStr = (c.date || "").substring(0, 19);
|
|
536
|
+
const isActive = data.version === c.hash;
|
|
537
|
+
html += `<div class="history-item${isActive ? " history-active" : ""}" data-hash="${escHtml(c.hash)}" title="${escHtml(c.message)}">
|
|
538
|
+
<span class="history-hash">${escHtml(c.short_hash)}</span>
|
|
539
|
+
<span class="history-date">${escHtml(dateStr)}</span>
|
|
540
|
+
<span class="history-msg">${escHtml((c.message || "").substring(0, 50))}</span>
|
|
541
|
+
</div>`;
|
|
542
|
+
}
|
|
543
|
+
histDropdown.innerHTML = html;
|
|
544
|
+
historyLoaded = true;
|
|
545
|
+
|
|
546
|
+
// Click handler on each history item
|
|
547
|
+
histDropdown.querySelectorAll(".history-item").forEach(item => {
|
|
548
|
+
item.addEventListener("click", function (e) {
|
|
549
|
+
e.stopPropagation();
|
|
550
|
+
histDropdown.style.display = "none";
|
|
551
|
+
const hash = this.dataset.hash;
|
|
552
|
+
loadFile(repoName, filePath, contentEl, hash);
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
} catch (err) {
|
|
556
|
+
histDropdown.innerHTML = `<div style="padding:8px; color:var(--color-critical);">Error: ${escHtml(err.message)}</div>`;
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// Close dropdown on outside click
|
|
561
|
+
document.addEventListener("click", function closeHist() {
|
|
562
|
+
histDropdown.style.display = "none";
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Intercept .md links — load them in the file browser instead of navigating
|
|
567
|
+
contentEl.querySelectorAll(".repo-markdown a").forEach(link => {
|
|
568
|
+
const href = link.getAttribute("href") || "";
|
|
569
|
+
if (href.endsWith(".md") && !href.startsWith("http")) {
|
|
570
|
+
link.addEventListener("click", function (e) {
|
|
571
|
+
e.preventDefault();
|
|
572
|
+
// Resolve relative to current file's directory
|
|
573
|
+
const currentDir = filePath.includes("/") ? filePath.substring(0, filePath.lastIndexOf("/")) : "";
|
|
574
|
+
let target = href;
|
|
575
|
+
if (currentDir && !href.startsWith("/")) {
|
|
576
|
+
target = currentDir + "/" + href;
|
|
577
|
+
}
|
|
578
|
+
// Normalize: remove ./ and resolve ../
|
|
579
|
+
target = target.replace(/^\.\//, "");
|
|
580
|
+
while (target.includes("/../")) {
|
|
581
|
+
target = target.replace(/[^/]+\/\.\.\//, "");
|
|
582
|
+
}
|
|
583
|
+
target = target.replace(/^\.\.\//, "");
|
|
584
|
+
loadFile(repoName, target, contentEl, window._gatorRepoVersion);
|
|
585
|
+
// Update sidebar active state
|
|
586
|
+
const sidebar = contentEl.parentElement.querySelector("#repo-file-list");
|
|
587
|
+
if (sidebar) {
|
|
588
|
+
sidebar.querySelectorAll(".repo-file-item").forEach(i => i.classList.remove("active"));
|
|
589
|
+
const match = sidebar.querySelector(`[data-path="${target}"]`);
|
|
590
|
+
if (match) match.classList.add("active");
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
} catch (err) {
|
|
596
|
+
contentEl.innerHTML = `<div class="muted" style="padding:40px">Failed to load file</div>`;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
})();
|