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,84 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Gator Dashboard</title>
7
+ <link rel="stylesheet" href="dashboard.css">
8
+ </head>
9
+ <body>
10
+ <div id="app-shell">
11
+
12
+ <aside id="sidebar">
13
+ <div id="sidebar-brand">
14
+ <span id="brand-icon">⚙</span>
15
+ <span id="brand-name">Gator</span>
16
+ </div>
17
+
18
+ <nav id="sidebar-nav">
19
+ <div class="nav-group-label">Overview</div>
20
+ <button class="sidebar-item active" data-view="fleet">
21
+ <span class="sidebar-icon">◈</span> Fleet
22
+ </button>
23
+ <button class="sidebar-item dimmed" data-view="repo" id="repo-tab" style="padding-left:28px">
24
+ <span class="sidebar-icon">▸</span> Repo
25
+ </button>
26
+ <button class="sidebar-item" data-view="audit">
27
+ <span class="sidebar-icon">◉</span> Audit
28
+ </button>
29
+
30
+ <div class="nav-group-label">Knowledge</div>
31
+ <button class="sidebar-item placeholder" data-view="docs" disabled>
32
+ <span class="sidebar-icon">▤</span> Docs
33
+ <span class="coming-soon">soon</span>
34
+ </button>
35
+
36
+ <div class="nav-group-label">System</div>
37
+ <button class="sidebar-item" data-view="settings">
38
+ <span class="sidebar-icon">⚙</span> Settings
39
+ </button>
40
+ <button class="sidebar-item" data-view="updates">
41
+ <span class="sidebar-icon">↻</span> Updates
42
+ </button>
43
+ </nav>
44
+
45
+ <div id="sidebar-footer">
46
+ <div id="sidebar-footer-text">Local-first · Git-native<br>governance for AI coding</div>
47
+ </div>
48
+ </aside>
49
+
50
+ <div id="main-shell">
51
+ <button id="sidebar-toggle" title="Toggle sidebar">&#9707;</button>
52
+ <header id="topbar">
53
+ <div id="topbar-left">
54
+ <h1 id="topbar-title">Fleet</h1>
55
+ <span id="topbar-subtitle"></span>
56
+ </div>
57
+ <div id="topbar-right">
58
+ <span id="generated-at"></span>
59
+ <button id="refresh-btn" title="Re-run fleet scan">↺ Refresh</button>
60
+ </div>
61
+ </header>
62
+
63
+ <div id="snapshot-banner" style="display:none" class="snapshot-note">
64
+ Snapshot — data captured at generation time.
65
+ Run <code>gator dashboard</code> for live view with Repo drill-down.
66
+ </div>
67
+
68
+ <main id="view-slot">
69
+ <div id="loading">
70
+ <p>Loading fleet data...</p>
71
+ </div>
72
+ </main>
73
+ </div>
74
+
75
+ </div>
76
+
77
+ <script src="views/fleet.js"></script>
78
+ <script src="views/audit.js"></script>
79
+ <script src="views/repo.js"></script>
80
+ <script src="views/updates.js"></script>
81
+ <script src="views/settings.js"></script>
82
+ <script src="dashboard.js"></script>
83
+ </body>
84
+ </html>
@@ -0,0 +1,419 @@
1
+ /**
2
+ * dashboard.js — Gator Dashboard shell controller.
3
+ *
4
+ * Responsibilities:
5
+ * - Load Tier 1 data from /api/data (or window.DASHBOARD_DATA in snapshot mode)
6
+ * - Route sidebar clicks to view modules
7
+ * - Handle ?repo=<name> query param to pre-load Repo view
8
+ * - Manage Refresh button (re-fetches /api/refresh then /api/data)
9
+ * - Update topbar title/subtitle from active view
10
+ *
11
+ * View modules register via window.GatorViews.<name> = function(data, container, ...extras)
12
+ */
13
+
14
+ (function () {
15
+ "use strict";
16
+
17
+ // ── state ──────────────────────────────────────────────────────────────────
18
+
19
+ const state = {
20
+ data: null, // fast_data from server
21
+ activeView: "fleet",
22
+ activeRepo: null, // name of currently loaded repo (Repo view)
23
+ settings: null, // dashboard settings (mode, etc.)
24
+ standalone: false, // true when no command post detected
25
+ };
26
+
27
+ // ── view metadata ─────────────────────────────────────────────────────────
28
+
29
+ const VIEW_META = {
30
+ fleet: { title: "Fleet", subtitle: "" },
31
+ audit: { title: "Audit", subtitle: "" },
32
+ repo: { title: "Repo", subtitle: "" },
33
+ updates: { title: "Updates", subtitle: "" },
34
+ settings: { title: "Settings", subtitle: "" },
35
+ };
36
+
37
+ // ── DOM refs ───────────────────────────────────────────────────────────────
38
+
39
+ const viewSlot = document.getElementById("view-slot");
40
+ const generatedAt = document.getElementById("generated-at");
41
+ const refreshBtn = document.getElementById("refresh-btn");
42
+ const repoTab = document.getElementById("repo-tab");
43
+ const topbarTitle = document.getElementById("topbar-title");
44
+
45
+ // ── sidebar toggle ────────────────────────────────────────────────────────
46
+
47
+ const sidebarToggle = document.getElementById("sidebar-toggle");
48
+ const sidebar = document.getElementById("sidebar");
49
+ if (sidebarToggle && sidebar) {
50
+ sidebarToggle.addEventListener("click", function () {
51
+ sidebar.classList.toggle("collapsed");
52
+ });
53
+ }
54
+ const topbarSubtitle = document.getElementById("topbar-subtitle");
55
+
56
+ // ── data loading ───────────────────────────────────────────────────────────
57
+
58
+ async function loadData() {
59
+ if (window.DASHBOARD_DATA) {
60
+ // Snapshot mode — data is already inlined
61
+ return window.DASHBOARD_DATA;
62
+ }
63
+ const resp = await fetch("/api/data");
64
+ if (!resp.ok) throw new Error(`/api/data returned ${resp.status}`);
65
+ return resp.json();
66
+ }
67
+
68
+ function updateTimestamp(ts) {
69
+ if (!ts) return;
70
+ try {
71
+ const d = new Date(ts);
72
+ generatedAt.textContent = "Updated " + d.toLocaleTimeString();
73
+ } catch (_) {
74
+ generatedAt.textContent = ts;
75
+ }
76
+ }
77
+
78
+ // ── topbar ─────────────────────────────────────────────────────────────────
79
+
80
+ function updateTopbar(name, subtitle) {
81
+ const meta = VIEW_META[name] || { title: name, subtitle: "" };
82
+ topbarTitle.textContent = meta.title;
83
+ topbarSubtitle.innerHTML = subtitle || meta.subtitle;
84
+ }
85
+
86
+ // ── view routing ───────────────────────────────────────────────────────────
87
+
88
+ function showView(name, extra) {
89
+ state.activeView = name;
90
+
91
+ // Update sidebar active state
92
+ document.querySelectorAll(".sidebar-item").forEach(btn => {
93
+ btn.classList.toggle("active", btn.dataset.view === name);
94
+ });
95
+
96
+ // Clear slot
97
+ viewSlot.innerHTML = "";
98
+
99
+ const views = window.GatorViews || {};
100
+
101
+ if (name === "fleet") {
102
+ const fleet = state.data && state.data.fleet || {};
103
+ const summary = fleet.summary || {};
104
+ const total = summary.total || (fleet.repos || []).length;
105
+ const accessible = summary.accessible || 0;
106
+ updateTopbar("fleet", `${total} repos · ${accessible} accessible`);
107
+ if (views.fleet) {
108
+ views.fleet(state.data, viewSlot);
109
+ } else {
110
+ viewSlot.innerHTML = "<p class='muted'>Fleet view not available.</p>";
111
+ }
112
+ return;
113
+ }
114
+
115
+ if (name === "audit") {
116
+ updateTopbar("audit", "Governance posture");
117
+ if (views.audit) {
118
+ views.audit(state.data, viewSlot);
119
+ } else {
120
+ viewSlot.innerHTML = "<p class='muted'>Audit view not available.</p>";
121
+ }
122
+ return;
123
+ }
124
+
125
+ if (name === "repo") {
126
+ const repoName = extra || state.activeRepo;
127
+ if (!repoName) {
128
+ updateTopbar("repo", "Select a repo from Fleet");
129
+ viewSlot.innerHTML = "<p class='muted' style='padding:40px;text-align:center'>Select a repo from the Fleet view.</p>";
130
+ return;
131
+ }
132
+ state.activeRepo = repoName;
133
+ // Update repo sidebar label
134
+ repoTab.textContent = "";
135
+ const icon = document.createElement("span");
136
+ icon.className = "sidebar-icon";
137
+ icon.textContent = "▸";
138
+ repoTab.appendChild(icon);
139
+ repoTab.appendChild(document.createTextNode(" " + repoName));
140
+ repoTab.classList.remove("dimmed");
141
+ // Find branch from fleet data
142
+ const fleetRepos = (state.data && state.data.fleet && state.data.fleet.repos) || (state.data && state.data.repos) || [];
143
+ const repoInfo = fleetRepos.find(r => r.name === repoName);
144
+ const branch = repoInfo && repoInfo.branch ? repoInfo.branch : "";
145
+ const subtitle = branch
146
+ ? repoName + ' <span class="muted" style="font-weight:normal">' + branch + '</span>'
147
+ + ' <button class="history-toggle-btn" id="branch-history-btn" title="Browse commit history" style="font-size:13px;">&#9662;</button>'
148
+ + '<span id="branch-version-label"></span>'
149
+ + '<div class="history-dropdown" id="branch-history-dropdown" style="display:none; left:0; right:auto;"></div>'
150
+ : repoName;
151
+ updateTopbar("repo", subtitle);
152
+ // Make subtitle container position:relative for dropdown
153
+ topbarSubtitle.style.position = "relative";
154
+ if (views.repo) {
155
+ views.repo(state.data, viewSlot, repoName);
156
+ } else {
157
+ viewSlot.innerHTML = "<p class='muted'>Repo view not available.</p>";
158
+ }
159
+ // Wire up branch history dropdown
160
+ const branchBtn = document.getElementById("branch-history-btn");
161
+ const branchDropdown = document.getElementById("branch-history-dropdown");
162
+ if (branchBtn && branchDropdown) {
163
+ let branchHistLoaded = false;
164
+ branchBtn.addEventListener("click", async function (e) {
165
+ e.stopPropagation();
166
+ if (branchDropdown.style.display !== "none") {
167
+ branchDropdown.style.display = "none";
168
+ return;
169
+ }
170
+ branchDropdown.style.display = "block";
171
+ if (branchHistLoaded) return;
172
+ branchDropdown.innerHTML = '<div style="padding:8px; color:#aaa;">Loading…</div>';
173
+ try {
174
+ const resp = await fetch(`/api/repo/${encodeURIComponent(repoName)}/commits`);
175
+ const data = await resp.json();
176
+ if (!data.commits || !data.commits.length) {
177
+ branchDropdown.innerHTML = '<div style="padding:8px; color:#aaa;">No history</div>';
178
+ branchHistLoaded = true;
179
+ return;
180
+ }
181
+ // Add "Current (HEAD)" as first option
182
+ let html = '<div class="history-item history-active" data-hash="" title="Current working state">'
183
+ + '<span class="history-hash">HEAD</span>'
184
+ + '<span class="history-date">current</span>'
185
+ + '<span class="history-msg">Working state</span></div>';
186
+ for (const c of data.commits) {
187
+ const dateStr = (c.date || "").substring(0, 19);
188
+ html += `<div class="history-item" data-hash="${c.hash}" title="${c.message}">
189
+ <span class="history-hash">${c.short_hash}</span>
190
+ <span class="history-date">${dateStr}</span>
191
+ <span class="history-msg">${(c.message || "").substring(0, 50)}</span>
192
+ </div>`;
193
+ }
194
+ branchDropdown.innerHTML = html;
195
+ branchHistLoaded = true;
196
+ branchDropdown.querySelectorAll(".history-item").forEach(item => {
197
+ item.addEventListener("click", function (e) {
198
+ e.stopPropagation();
199
+ branchDropdown.style.display = "none";
200
+ const hash = this.dataset.hash;
201
+ // Update active highlight
202
+ branchDropdown.querySelectorAll(".history-item").forEach(i => i.classList.remove("history-active"));
203
+ this.classList.add("history-active");
204
+ // Update version label in header
205
+ const vLabel = document.getElementById("branch-version-label");
206
+ if (vLabel) {
207
+ const commitDate = this.querySelector(".history-date");
208
+ const dateStr = commitDate ? commitDate.textContent.trim() : "";
209
+ if (hash) {
210
+ vLabel.innerHTML = ' <span class="muted" style="font-weight:normal; font-size:12px;">Viewing: ' + hash.substring(0, 8) + (dateStr && dateStr !== "current" ? ' (' + dateStr.substring(0, 19) + ')' : '') + '</span>';
211
+ } else {
212
+ vLabel.innerHTML = '';
213
+ }
214
+ }
215
+ // Set version context and reload file list
216
+ window._gatorRepoVersion = hash || null;
217
+ if (views.repo) {
218
+ views.repo(state.data, viewSlot, repoName, hash || null);
219
+ }
220
+ });
221
+ });
222
+ } catch (err) {
223
+ branchDropdown.innerHTML = '<div style="padding:8px; color:var(--color-critical);">Error</div>';
224
+ }
225
+ });
226
+ document.addEventListener("click", function () { branchDropdown.style.display = "none"; });
227
+ }
228
+ return;
229
+ }
230
+
231
+ if (name === "updates") {
232
+ updateTopbar("updates", "Gator updates");
233
+ if (views.updates) {
234
+ views.updates(state.data, viewSlot);
235
+ } else {
236
+ viewSlot.innerHTML = "<p class='muted'>Updates view not available.</p>";
237
+ }
238
+ return;
239
+ }
240
+
241
+ if (name === "settings") {
242
+ updateTopbar("settings", "Fleet settings");
243
+ if (views.settings) {
244
+ views.settings(state.data, viewSlot, {
245
+ standalone: state.standalone,
246
+ });
247
+ } else {
248
+ viewSlot.innerHTML = "<p class='muted'>Settings view not available.</p>";
249
+ }
250
+ return;
251
+ }
252
+ }
253
+
254
+ // ── sidebar click handler ─────────────────────────────────────────────────
255
+
256
+ document.getElementById("sidebar-nav").addEventListener("click", function (e) {
257
+ const item = e.target.closest(".sidebar-item");
258
+ if (!item || item.classList.contains("dimmed") || item.classList.contains("placeholder")) return;
259
+ const view = item.dataset.view;
260
+ if (view === "repo" && !state.activeRepo) return;
261
+ showView(view);
262
+ });
263
+
264
+ // ── session evidence modal ─────────────────────────────────────────────
265
+
266
+ let modalOverlay = null;
267
+
268
+ function ensureModal() {
269
+ if (modalOverlay) return;
270
+ modalOverlay = document.createElement("div");
271
+ modalOverlay.className = "session-overlay";
272
+ modalOverlay.style.display = "none";
273
+ modalOverlay.innerHTML = `
274
+ <div class="session-modal">
275
+ <div class="session-modal-header">
276
+ <span class="session-modal-title"></span>
277
+ <button class="session-modal-close" title="Close">&times;</button>
278
+ </div>
279
+ <div class="session-modal-body"></div>
280
+ </div>
281
+ `;
282
+ document.body.appendChild(modalOverlay);
283
+
284
+ // Close handlers
285
+ modalOverlay.querySelector(".session-modal-close").addEventListener("click", closeModal);
286
+ modalOverlay.addEventListener("click", function (e) {
287
+ if (e.target === modalOverlay) closeModal();
288
+ });
289
+ document.addEventListener("keydown", function (e) {
290
+ if (e.key === "Escape" && modalOverlay.style.display !== "none") closeModal();
291
+ });
292
+ }
293
+
294
+ function closeModal() {
295
+ if (modalOverlay) modalOverlay.style.display = "none";
296
+ }
297
+
298
+ window.showSessionModal = async function (repo, sourceKind, filename) {
299
+ ensureModal();
300
+ const titleEl = modalOverlay.querySelector(".session-modal-title");
301
+ const bodyEl = modalOverlay.querySelector(".session-modal-body");
302
+
303
+ titleEl.textContent = filename;
304
+ bodyEl.innerHTML = '<span class="muted">Loading…</span>';
305
+ modalOverlay.style.display = "flex";
306
+
307
+ try {
308
+ const resp = await fetch("/api/session", {
309
+ method: "POST",
310
+ headers: {
311
+ "Content-Type": "application/json",
312
+ "X-Gator-Dashboard": "1",
313
+ },
314
+ body: JSON.stringify({
315
+ repo: repo,
316
+ source_kind: sourceKind,
317
+ filename: filename,
318
+ }),
319
+ });
320
+ const data = await resp.json();
321
+ if (data.error) {
322
+ bodyEl.innerHTML = '<div class="session-modal-error">' + escHtml(data.error) + '</div>';
323
+ } else {
324
+ bodyEl.innerHTML = '<pre class="session-markdown">' + escHtml(data.content || "") + '</pre>';
325
+ }
326
+ } catch (err) {
327
+ bodyEl.innerHTML = '<div class="session-modal-error">Failed to load: ' + escHtml(err.message) + '</div>';
328
+ }
329
+ };
330
+
331
+ function escHtml(str) {
332
+ return String(str)
333
+ .replace(/&/g, "&amp;")
334
+ .replace(/</g, "&lt;")
335
+ .replace(/>/g, "&gt;")
336
+ .replace(/"/g, "&quot;");
337
+ }
338
+
339
+ // ── repo navigation (called by fleet view) ─────────────────────────────────
340
+
341
+ window.gatorNavToRepo = function (repoName) {
342
+ showView("repo", repoName);
343
+ };
344
+
345
+ // ── refresh ────────────────────────────────────────────────────────────────
346
+
347
+ let refreshPending = false;
348
+
349
+ async function doRefresh() {
350
+ if (refreshPending || window.GATOR_SNAPSHOT) return;
351
+ refreshPending = true;
352
+ refreshBtn.classList.add("spinning");
353
+ refreshBtn.textContent = "↺ Refreshing…";
354
+
355
+ try {
356
+ await fetch("/api/refresh");
357
+ const oldTs = state.data && state.data.generated_at;
358
+ let attempts = 0;
359
+ while (attempts < 30) {
360
+ await sleep(1000);
361
+ const fresh = await loadData();
362
+ if (fresh.generated_at !== oldTs) {
363
+ state.data = fresh;
364
+ updateTimestamp(fresh.generated_at);
365
+ showView(state.activeView, state.activeRepo);
366
+ break;
367
+ }
368
+ attempts++;
369
+ }
370
+ } catch (err) {
371
+ console.error("Refresh failed:", err);
372
+ } finally {
373
+ refreshBtn.classList.remove("spinning");
374
+ refreshBtn.textContent = "↺ Refresh";
375
+ refreshPending = false;
376
+ }
377
+ }
378
+
379
+ refreshBtn.addEventListener("click", doRefresh);
380
+
381
+ // Exposed so view modules (e.g. fleet.js) can trigger a fleet refresh
382
+ window.gatorRefreshFleet = doRefresh;
383
+
384
+ function sleep(ms) {
385
+ return new Promise(resolve => setTimeout(resolve, ms));
386
+ }
387
+
388
+ // ── initial load ───────────────────────────────────────────────────────────
389
+
390
+ async function init() {
391
+ // Snapshot mode: show persistent banner and hide Refresh button.
392
+ if (window.GATOR_SNAPSHOT) {
393
+ const banner = document.getElementById("snapshot-banner");
394
+ if (banner) banner.style.display = "block";
395
+ refreshBtn.style.display = "none";
396
+ }
397
+
398
+ try {
399
+ state.data = await loadData();
400
+ updateTimestamp(state.data.generated_at);
401
+ } catch (err) {
402
+ viewSlot.innerHTML = `<div class="error-block">Failed to load dashboard data: ${err.message}</div>`;
403
+ return;
404
+ }
405
+
406
+ state.standalone = !!(state.data && state.data.standalone);
407
+
408
+ // Check for ?repo= query param
409
+ const params = new URLSearchParams(window.location.search);
410
+ const preloadRepo = params.get("repo");
411
+ if (preloadRepo) {
412
+ showView("repo", preloadRepo);
413
+ } else {
414
+ showView("fleet");
415
+ }
416
+ }
417
+
418
+ init();
419
+ })();