shipwright-cli 1.7.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/.claude/agents/code-reviewer.md +90 -0
  2. package/.claude/agents/devops-engineer.md +142 -0
  3. package/.claude/agents/pipeline-agent.md +80 -0
  4. package/.claude/agents/shell-script-specialist.md +150 -0
  5. package/.claude/agents/test-specialist.md +196 -0
  6. package/.claude/hooks/post-tool-use.sh +38 -0
  7. package/.claude/hooks/pre-tool-use.sh +25 -0
  8. package/.claude/hooks/session-started.sh +37 -0
  9. package/README.md +212 -814
  10. package/claude-code/CLAUDE.md.shipwright +54 -0
  11. package/claude-code/hooks/notify-idle.sh +2 -2
  12. package/claude-code/hooks/session-start.sh +24 -0
  13. package/claude-code/hooks/task-completed.sh +6 -2
  14. package/claude-code/settings.json.template +12 -0
  15. package/dashboard/public/app.js +4422 -0
  16. package/dashboard/public/index.html +816 -0
  17. package/dashboard/public/styles.css +4755 -0
  18. package/dashboard/server.ts +4315 -0
  19. package/docs/KNOWN-ISSUES.md +18 -10
  20. package/docs/TIPS.md +38 -26
  21. package/docs/patterns/README.md +33 -23
  22. package/package.json +9 -5
  23. package/scripts/adapters/iterm2-adapter.sh +1 -1
  24. package/scripts/adapters/tmux-adapter.sh +52 -23
  25. package/scripts/adapters/wezterm-adapter.sh +26 -14
  26. package/scripts/lib/compat.sh +200 -0
  27. package/scripts/lib/helpers.sh +72 -0
  28. package/scripts/postinstall.mjs +72 -13
  29. package/scripts/{cct → sw} +109 -21
  30. package/scripts/sw-adversarial.sh +274 -0
  31. package/scripts/sw-architecture-enforcer.sh +330 -0
  32. package/scripts/sw-checkpoint.sh +390 -0
  33. package/scripts/{cct-cleanup.sh → sw-cleanup.sh} +3 -1
  34. package/scripts/sw-connect.sh +619 -0
  35. package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
  36. package/scripts/{cct-daemon.sh → sw-daemon.sh} +2217 -204
  37. package/scripts/sw-dashboard.sh +477 -0
  38. package/scripts/sw-developer-simulation.sh +252 -0
  39. package/scripts/sw-docs.sh +635 -0
  40. package/scripts/sw-doctor.sh +907 -0
  41. package/scripts/{cct-fix.sh → sw-fix.sh} +10 -6
  42. package/scripts/{cct-fleet.sh → sw-fleet.sh} +498 -22
  43. package/scripts/sw-github-checks.sh +521 -0
  44. package/scripts/sw-github-deploy.sh +533 -0
  45. package/scripts/sw-github-graphql.sh +972 -0
  46. package/scripts/sw-heartbeat.sh +293 -0
  47. package/scripts/{cct-init.sh → sw-init.sh} +144 -11
  48. package/scripts/sw-intelligence.sh +1196 -0
  49. package/scripts/sw-jira.sh +643 -0
  50. package/scripts/sw-launchd.sh +364 -0
  51. package/scripts/sw-linear.sh +648 -0
  52. package/scripts/{cct-logs.sh → sw-logs.sh} +72 -2
  53. package/scripts/{cct-loop.sh → sw-loop.sh} +534 -44
  54. package/scripts/{cct-memory.sh → sw-memory.sh} +321 -38
  55. package/scripts/sw-patrol-meta.sh +417 -0
  56. package/scripts/sw-pipeline-composer.sh +455 -0
  57. package/scripts/{cct-pipeline.sh → sw-pipeline.sh} +2319 -178
  58. package/scripts/sw-predictive.sh +820 -0
  59. package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
  60. package/scripts/{cct-ps.sh → sw-ps.sh} +6 -4
  61. package/scripts/{cct-reaper.sh → sw-reaper.sh} +6 -4
  62. package/scripts/sw-remote.sh +687 -0
  63. package/scripts/sw-self-optimize.sh +947 -0
  64. package/scripts/sw-session.sh +519 -0
  65. package/scripts/sw-setup.sh +234 -0
  66. package/scripts/sw-status.sh +605 -0
  67. package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
  68. package/scripts/sw-tmux.sh +591 -0
  69. package/scripts/sw-tracker-jira.sh +277 -0
  70. package/scripts/sw-tracker-linear.sh +292 -0
  71. package/scripts/sw-tracker.sh +409 -0
  72. package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
  73. package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
  74. package/templates/pipelines/autonomous.json +27 -5
  75. package/templates/pipelines/full.json +12 -0
  76. package/templates/pipelines/standard.json +12 -0
  77. package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
  78. package/tmux/templates/accessibility.json +34 -0
  79. package/tmux/templates/api-design.json +35 -0
  80. package/tmux/templates/architecture.json +1 -0
  81. package/tmux/templates/bug-fix.json +9 -0
  82. package/tmux/templates/code-review.json +1 -0
  83. package/tmux/templates/compliance.json +36 -0
  84. package/tmux/templates/data-pipeline.json +36 -0
  85. package/tmux/templates/debt-paydown.json +34 -0
  86. package/tmux/templates/devops.json +1 -0
  87. package/tmux/templates/documentation.json +1 -0
  88. package/tmux/templates/exploration.json +1 -0
  89. package/tmux/templates/feature-dev.json +1 -0
  90. package/tmux/templates/full-stack.json +8 -0
  91. package/tmux/templates/i18n.json +34 -0
  92. package/tmux/templates/incident-response.json +36 -0
  93. package/tmux/templates/migration.json +1 -0
  94. package/tmux/templates/observability.json +35 -0
  95. package/tmux/templates/onboarding.json +33 -0
  96. package/tmux/templates/performance.json +35 -0
  97. package/tmux/templates/refactor.json +1 -0
  98. package/tmux/templates/release.json +35 -0
  99. package/tmux/templates/security-audit.json +8 -0
  100. package/tmux/templates/spike.json +34 -0
  101. package/tmux/templates/testing.json +1 -0
  102. package/tmux/tmux.conf +98 -9
  103. package/scripts/cct-doctor.sh +0 -414
  104. package/scripts/cct-session.sh +0 -284
  105. package/scripts/cct-status.sh +0 -169
@@ -0,0 +1,816 @@
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>Fleet Command — Shipwright</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500;700&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap"
11
+ rel="stylesheet"
12
+ />
13
+ <link rel="stylesheet" href="styles.css" />
14
+ <link
15
+ rel="icon"
16
+ href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text y='28' font-size='28'>⚓</text></svg>"
17
+ />
18
+ </head>
19
+ <body>
20
+ <!-- Header -->
21
+ <header class="header" id="header">
22
+ <div class="header-left">
23
+ <svg
24
+ class="header-icon"
25
+ viewBox="0 0 28 28"
26
+ width="28"
27
+ height="28"
28
+ fill="none"
29
+ xmlns="http://www.w3.org/2000/svg"
30
+ >
31
+ <defs>
32
+ <linearGradient
33
+ id="ship-grad"
34
+ x1="0"
35
+ y1="0"
36
+ x2="28"
37
+ y2="28"
38
+ gradientUnits="userSpaceOnUse"
39
+ >
40
+ <stop offset="0%" stop-color="#00d4ff" />
41
+ <stop offset="50%" stop-color="#0066ff" />
42
+ <stop offset="100%" stop-color="#7c3aed" />
43
+ </linearGradient>
44
+ </defs>
45
+ <path
46
+ d="M14 2L4 22h4l2-4h8l2 4h4L14 2zm0 6l3.5 8h-7L14 8z"
47
+ fill="url(#ship-grad)"
48
+ />
49
+ <path
50
+ d="M2 24c2-2 5-2 7 0s5 2 7 0 5-2 7 0"
51
+ stroke="url(#ship-grad)"
52
+ stroke-width="1.5"
53
+ fill="none"
54
+ stroke-linecap="round"
55
+ />
56
+ </svg>
57
+ <span class="header-title">Fleet Command</span>
58
+ </div>
59
+ <div class="header-center" id="connection">
60
+ <span class="connection-dot offline" id="connection-dot"></span>
61
+ <span class="connection-text" id="connection-text">OFFLINE</span>
62
+ <span class="cost-ticker" id="cost-ticker"></span>
63
+ </div>
64
+ <div class="header-right">
65
+ <button
66
+ class="emergency-brake-btn"
67
+ id="emergency-brake"
68
+ style="display: none"
69
+ title="Emergency Brake — Pause All"
70
+ >
71
+ <svg
72
+ viewBox="0 0 24 24"
73
+ width="18"
74
+ height="18"
75
+ fill="none"
76
+ stroke="currentColor"
77
+ stroke-width="2"
78
+ >
79
+ <circle cx="12" cy="12" r="10" />
80
+ <rect x="8" y="8" width="8" height="8" rx="1" fill="currentColor" />
81
+ </svg>
82
+ </button>
83
+ <div class="user-menu" id="user-menu">
84
+ <button class="user-avatar" id="user-avatar" aria-label="User menu">
85
+ <span class="avatar-initials" id="avatar-initials">?</span>
86
+ </button>
87
+ <div class="user-dropdown" id="user-dropdown">
88
+ <div class="dropdown-username" id="dropdown-username">—</div>
89
+ <a class="dropdown-link" href="/auth/logout">Sign out</a>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </header>
94
+
95
+ <!-- Tab Navigation -->
96
+ <nav class="tab-bar" id="tab-bar">
97
+ <div class="tab-bar-inner">
98
+ <button class="tab-btn active" data-tab="overview">Overview</button>
99
+ <button class="tab-btn" data-tab="agents">Agents</button>
100
+ <button class="tab-btn" data-tab="pipelines">Pipelines</button>
101
+ <button class="tab-btn" data-tab="timeline">Timeline</button>
102
+ <button class="tab-btn" data-tab="activity">Activity</button>
103
+ <button class="tab-btn" data-tab="metrics">Metrics</button>
104
+ <button class="tab-btn" data-tab="machines">Machines</button>
105
+ <button class="tab-btn" data-tab="insights">Insights</button>
106
+ <button class="tab-btn" data-tab="team">Team</button>
107
+ <div class="tab-indicator" id="tab-indicator"></div>
108
+ </div>
109
+ </nav>
110
+
111
+ <!-- Alert Banner -->
112
+ <div class="alert-banner" id="alert-banner" style="display: none">
113
+ <div class="alert-banner-inner">
114
+ <span class="alert-icon" id="alert-icon"></span>
115
+ <span class="alert-message" id="alert-message"></span>
116
+ <div class="alert-actions" id="alert-actions"></div>
117
+ <button class="alert-dismiss" id="alert-dismiss" aria-label="Dismiss">
118
+ &times;
119
+ </button>
120
+ </div>
121
+ </div>
122
+
123
+ <!-- Main Content -->
124
+ <main class="main" id="main-content">
125
+ <!-- ═══ OVERVIEW TAB ═══ -->
126
+ <div class="tab-panel active" id="panel-overview">
127
+ <!-- Stats Row -->
128
+ <section class="stats-row" id="stats-row">
129
+ <div class="stat-card">
130
+ <span class="stat-label">STATUS</span>
131
+ <div class="stat-value-row">
132
+ <span class="pulse-dot offline" id="status-dot"></span>
133
+ <span class="stat-value" id="stat-status">OFFLINE</span>
134
+ </div>
135
+ </div>
136
+ <div class="stat-card">
137
+ <span class="stat-label">ACTIVE</span>
138
+ <div class="stat-value gradient-text" id="stat-active">0 / 0</div>
139
+ <div class="stat-bar-track">
140
+ <div class="stat-bar-fill" id="stat-active-bar"></div>
141
+ </div>
142
+ </div>
143
+ <div class="stat-card">
144
+ <span class="stat-label">QUEUE</span>
145
+ <div class="stat-value" id="stat-queue">0</div>
146
+ <div class="stat-subtitle" id="stat-queue-sub">issues waiting</div>
147
+ </div>
148
+ <div class="stat-card">
149
+ <span class="stat-label">COMPLETED</span>
150
+ <div class="stat-value gradient-text" id="stat-completed">0</div>
151
+ <div class="stat-subtitle" id="stat-failed-sub">0 failed</div>
152
+ </div>
153
+ </section>
154
+
155
+ <!-- Daemon Control Bar -->
156
+ <div class="daemon-control-bar" id="daemon-control-bar">
157
+ <div class="daemon-control-status">
158
+ <span class="daemon-status-badge" id="daemon-status-badge"
159
+ >Unknown</span
160
+ >
161
+ <span class="daemon-status-label">Daemon</span>
162
+ </div>
163
+ <div class="daemon-control-actions">
164
+ <button
165
+ class="btn btn-sm btn-start"
166
+ id="daemon-btn-start"
167
+ onclick="daemonControl('start')"
168
+ title="Start daemon"
169
+ >
170
+ Start
171
+ </button>
172
+ <button
173
+ class="btn btn-sm btn-stop"
174
+ id="daemon-btn-stop"
175
+ onclick="daemonControl('stop')"
176
+ title="Stop daemon"
177
+ >
178
+ Stop
179
+ </button>
180
+ <button
181
+ class="btn btn-sm btn-pause"
182
+ id="daemon-btn-pause"
183
+ onclick="daemonControl('pause')"
184
+ title="Pause/Resume daemon"
185
+ >
186
+ Pause
187
+ </button>
188
+ <button
189
+ class="btn btn-sm btn-patrol"
190
+ id="daemon-btn-patrol"
191
+ onclick="daemonControl('patrol')"
192
+ title="Run patrol now"
193
+ >
194
+ Run Patrol
195
+ </button>
196
+ </div>
197
+ <div class="daemon-control-info" id="daemon-control-info">
198
+ <span class="daemon-info-item"
199
+ ><strong>Workers:</strong>
200
+ <span id="daemon-info-workers">-</span></span
201
+ >
202
+ <span class="daemon-info-item"
203
+ ><strong>Poll:</strong>
204
+ <span id="daemon-info-poll">-</span>s</span
205
+ >
206
+ <span class="daemon-info-item"
207
+ ><strong>Patrol:</strong>
208
+ <span id="daemon-info-patrol">-</span>s</span
209
+ >
210
+ <span class="daemon-info-item"
211
+ ><strong>Budget:</strong> $<span id="daemon-info-budget"
212
+ >-</span
213
+ ></span
214
+ >
215
+ </div>
216
+ </div>
217
+
218
+ <!-- Active Pipelines -->
219
+ <section class="section">
220
+ <h2 class="section-label">ACTIVE PIPELINES</h2>
221
+ <div id="active-pipelines">
222
+ <div class="empty-state">
223
+ <svg
224
+ class="empty-icon"
225
+ viewBox="0 0 24 24"
226
+ width="32"
227
+ height="32"
228
+ fill="none"
229
+ stroke="currentColor"
230
+ stroke-width="1.5"
231
+ >
232
+ <path d="M12 6v6l4 2M12 2a10 10 0 100 20 10 10 0 000-20z" />
233
+ </svg>
234
+ <p>No active pipelines</p>
235
+ </div>
236
+ </div>
237
+ </section>
238
+
239
+ <!-- Queue Section -->
240
+ <section class="section">
241
+ <h2 class="section-label">AWAITING DEPLOYMENT</h2>
242
+ <div id="queue-list">
243
+ <div class="empty-state"><p>Queue clear</p></div>
244
+ </div>
245
+ </section>
246
+
247
+ <!-- Two-column layout: Activity + Resources -->
248
+ <div class="bottom-grid">
249
+ <!-- Activity Feed (compact, recent 10) -->
250
+ <section class="section">
251
+ <h2 class="section-label">ACTIVITY</h2>
252
+ <div class="activity-feed" id="activity-feed">
253
+ <div class="empty-state"><p>Awaiting events...</p></div>
254
+ </div>
255
+ </section>
256
+
257
+ <!-- Resource Scaling -->
258
+ <section class="section">
259
+ <h2 class="section-label">RESOURCE SCALING</h2>
260
+ <div class="resources-panel" id="resources-panel">
261
+ <div class="resource-row">
262
+ <span class="resource-label">CPU</span>
263
+ <div class="resource-bar-track">
264
+ <div class="resource-bar-fill" id="res-cpu-bar"></div>
265
+ </div>
266
+ <span class="resource-info" id="res-cpu-info">—</span>
267
+ </div>
268
+ <div class="resource-row">
269
+ <span class="resource-label">MEM</span>
270
+ <div class="resource-bar-track">
271
+ <div class="resource-bar-fill" id="res-mem-bar"></div>
272
+ </div>
273
+ <span class="resource-info" id="res-mem-info">—</span>
274
+ </div>
275
+ <div class="resource-row">
276
+ <span class="resource-label">BUDGET</span>
277
+ <div class="resource-bar-track">
278
+ <div class="resource-bar-fill" id="res-budget-bar"></div>
279
+ </div>
280
+ <span class="resource-info" id="res-budget-info">—</span>
281
+ </div>
282
+ <div class="resource-constraint" id="resource-constraint">
283
+ <span class="constraint-badge nominal">NOMINAL</span>
284
+ </div>
285
+ </div>
286
+ </section>
287
+ </div>
288
+
289
+ <!-- Machines (only shown if registered) -->
290
+ <section class="section" id="machines-section" style="display: none">
291
+ <h2 class="section-label">MACHINES</h2>
292
+ <div class="machines-grid" id="machines-grid"></div>
293
+ </section>
294
+ </div>
295
+
296
+ <!-- ═══ AGENTS TAB ═══ -->
297
+ <div class="tab-panel" id="panel-agents">
298
+ <div class="agents-grid" id="agents-grid">
299
+ <div class="empty-state"><p>No active agents</p></div>
300
+ </div>
301
+ </div>
302
+
303
+ <!-- ═══ PIPELINES TAB ═══ -->
304
+ <div class="tab-panel" id="panel-pipelines">
305
+ <!-- Filter bar -->
306
+ <div class="filter-bar" id="pipeline-filters">
307
+ <button class="filter-chip active" data-filter="all">All</button>
308
+ <button class="filter-chip" data-filter="active">Active</button>
309
+ <button class="filter-chip" data-filter="completed">Completed</button>
310
+ <button class="filter-chip" data-filter="failed">Failed</button>
311
+ </div>
312
+
313
+ <div class="bulk-actions" id="bulk-actions" style="display: none">
314
+ <span class="bulk-count" id="bulk-count">0 selected</span>
315
+ <button class="bulk-btn" id="bulk-pause">Pause Selected</button>
316
+ <button class="bulk-btn" id="bulk-resume">Resume Selected</button>
317
+ <button class="bulk-btn bulk-btn-danger" id="bulk-abort">
318
+ Abort Selected
319
+ </button>
320
+ </div>
321
+
322
+ <!-- Pipeline table -->
323
+ <div class="pipeline-table-wrap">
324
+ <table class="pipeline-table" id="pipeline-table">
325
+ <thead>
326
+ <tr>
327
+ <th>
328
+ <input
329
+ type="checkbox"
330
+ id="pipeline-select-all"
331
+ class="pipeline-checkbox"
332
+ />
333
+ </th>
334
+ <th>Issue</th>
335
+ <th>Title</th>
336
+ <th>Status</th>
337
+ <th>Stage</th>
338
+ <th>Duration</th>
339
+ <th>Branch</th>
340
+ </tr>
341
+ </thead>
342
+ <tbody id="pipeline-table-body">
343
+ <tr>
344
+ <td colspan="7" class="empty-state"><p>No pipeline data</p></td>
345
+ </tr>
346
+ </tbody>
347
+ </table>
348
+ </div>
349
+
350
+ <!-- Pipeline detail panel (hidden until row click) -->
351
+ <div class="pipeline-detail-panel" id="pipeline-detail-panel">
352
+ <div class="detail-panel-header">
353
+ <h3 class="detail-panel-title" id="detail-panel-title">
354
+ Pipeline Detail
355
+ </h3>
356
+ <button
357
+ class="detail-panel-close"
358
+ id="detail-panel-close"
359
+ aria-label="Close detail panel"
360
+ >
361
+ &times;
362
+ </button>
363
+ </div>
364
+ <div class="detail-panel-body" id="detail-panel-body">
365
+ <!-- Populated dynamically -->
366
+ </div>
367
+ </div>
368
+ </div>
369
+
370
+ <!-- ═══ TIMELINE TAB ═══ -->
371
+ <div class="tab-panel" id="panel-timeline">
372
+ <div class="timeline-controls">
373
+ <select class="timeline-range-select" id="timeline-range">
374
+ <option value="6">Last 6 hours</option>
375
+ <option value="24" selected>Last 24 hours</option>
376
+ <option value="72">Last 3 days</option>
377
+ <option value="168">Last 7 days</option>
378
+ </select>
379
+ </div>
380
+ <div class="gantt-chart" id="gantt-chart">
381
+ <div class="empty-state"><p>No timeline data</p></div>
382
+ </div>
383
+ </div>
384
+
385
+ <!-- ═══ ACTIVITY TAB ═══ -->
386
+ <div class="tab-panel" id="panel-activity">
387
+ <!-- Filter chips -->
388
+ <div class="filter-bar" id="activity-filters">
389
+ <button class="filter-chip active" data-filter="all">All</button>
390
+ <button class="filter-chip" data-filter="spawn">Spawn</button>
391
+ <button class="filter-chip" data-filter="stage">Stage</button>
392
+ <button class="filter-chip" data-filter="completed">Complete</button>
393
+ <button class="filter-chip" data-filter="failed">Failed</button>
394
+ <button class="filter-chip" data-filter="scale">Scale</button>
395
+ <button class="filter-chip" data-filter="poll">Poll</button>
396
+ <div class="filter-issue-input">
397
+ <input
398
+ type="text"
399
+ id="activity-issue-filter"
400
+ placeholder="Filter by issue #"
401
+ class="issue-filter-input"
402
+ />
403
+ </div>
404
+ </div>
405
+
406
+ <!-- Activity timeline -->
407
+ <div class="activity-timeline" id="activity-timeline">
408
+ <div class="empty-state"><p>Loading activity...</p></div>
409
+ </div>
410
+
411
+ <!-- Load more button -->
412
+ <div
413
+ class="load-more-wrap"
414
+ id="activity-load-more"
415
+ style="display: none"
416
+ >
417
+ <button class="load-more-btn" id="load-more-btn">Load more</button>
418
+ </div>
419
+ </div>
420
+
421
+ <!-- ═══ METRICS TAB ═══ -->
422
+ <div class="tab-panel" id="panel-metrics">
423
+ <div class="metrics-grid" id="metrics-grid">
424
+ <!-- Top row: big numbers -->
425
+ <div class="metrics-top-row">
426
+ <div class="metric-card metric-card-lg">
427
+ <span class="metric-label">SUCCESS RATE</span>
428
+ <div id="metric-donut-wrap" class="donut-wrap">
429
+ <div class="donut" id="metric-donut" style="--pct: 0%">
430
+ <span class="donut-value" id="metric-success-rate">—</span>
431
+ </div>
432
+ </div>
433
+ </div>
434
+ <div class="metric-card metric-card-lg">
435
+ <span class="metric-label">AVG DURATION</span>
436
+ <div class="metric-big-value" id="metric-avg-duration">—</div>
437
+ <span class="metric-sub">per pipeline</span>
438
+ </div>
439
+ <div class="metric-card metric-card-lg">
440
+ <span class="metric-label">THROUGHPUT</span>
441
+ <div class="metric-big-value" id="metric-throughput">—</div>
442
+ <span class="metric-sub">issues / hour</span>
443
+ </div>
444
+ <div class="metric-card metric-card-lg">
445
+ <span class="metric-label">TOTAL</span>
446
+ <div
447
+ class="metric-big-value gradient-text"
448
+ id="metric-total-completed"
449
+ >
450
+
451
+ </div>
452
+ <span class="metric-sub" id="metric-total-failed">0 failed</span>
453
+ </div>
454
+ </div>
455
+
456
+ <!-- Stage duration breakdown -->
457
+ <div class="metric-card metric-card-wide">
458
+ <span class="metric-label">STAGE DURATION BREAKDOWN</span>
459
+ <div class="stage-breakdown" id="stage-breakdown">
460
+ <div class="empty-state"><p>No data</p></div>
461
+ </div>
462
+ </div>
463
+
464
+ <!-- Daily activity chart -->
465
+ <div class="metric-card metric-card-wide">
466
+ <span class="metric-label">DAILY ACTIVITY</span>
467
+ <div class="daily-chart-wrap" id="daily-chart">
468
+ <div class="empty-state"><p>No data</p></div>
469
+ </div>
470
+ </div>
471
+
472
+ <!-- DORA Grades -->
473
+ <div
474
+ class="metric-card metric-card-wide"
475
+ id="dora-grades-container"
476
+ style="display: none"
477
+ >
478
+ <span class="metric-label">DORA GRADES</span>
479
+ </div>
480
+ </div>
481
+ </div>
482
+
483
+ <!-- ═══ MACHINES TAB ═══ -->
484
+ <div class="tab-panel" id="panel-machines">
485
+ <!-- Summary row -->
486
+ <div class="machines-summary" id="machines-summary"></div>
487
+
488
+ <!-- Action bar -->
489
+ <div class="machines-actions">
490
+ <button class="btn-primary" id="btn-add-machine">
491
+ + Add Machine
492
+ </button>
493
+ <button class="btn-secondary" id="btn-join-link">
494
+ Generate Join Link
495
+ </button>
496
+ </div>
497
+
498
+ <!-- Machine cards grid -->
499
+ <div class="machines-grid" id="machines-tab-grid">
500
+ <div class="empty-state"><p>No machines registered</p></div>
501
+ </div>
502
+
503
+ <!-- Active join tokens -->
504
+ <section class="section" id="join-tokens-section" style="display: none">
505
+ <h2 class="section-label">ACTIVE JOIN LINKS</h2>
506
+ <div id="join-tokens-list"></div>
507
+ </section>
508
+ </div>
509
+
510
+ <!-- ═══ INSIGHTS TAB ═══ -->
511
+ <div class="tab-panel" id="panel-insights">
512
+ <div class="insights-grid">
513
+ <!-- Failure Patterns -->
514
+ <section class="section">
515
+ <h2 class="section-label">FAILURE PATTERNS</h2>
516
+ <div id="failure-patterns">
517
+ <div class="empty-state"><p>Loading patterns...</p></div>
518
+ </div>
519
+ </section>
520
+
521
+ <!-- Patrol Findings -->
522
+ <section class="section">
523
+ <h2 class="section-label">PATROL FINDINGS</h2>
524
+ <div id="patrol-findings">
525
+ <div class="empty-state"><p>No patrol data</p></div>
526
+ </div>
527
+ </section>
528
+
529
+ <!-- Decision Log -->
530
+ <section class="section">
531
+ <h2 class="section-label">DECISION LOG</h2>
532
+ <div id="decision-log">
533
+ <div class="empty-state"><p>No decisions recorded</p></div>
534
+ </div>
535
+ </section>
536
+
537
+ <!-- Failure Heatmap -->
538
+ <section class="section">
539
+ <h2 class="section-label">FAILURE HEATMAP</h2>
540
+ <div id="failure-heatmap">
541
+ <div class="empty-state"><p>No data</p></div>
542
+ </div>
543
+ </section>
544
+ </div>
545
+ </div>
546
+
547
+ <!-- ═══ TEAM TAB ═══ -->
548
+ <div class="tab-panel" id="panel-team">
549
+ <!-- Team Stats Bar -->
550
+ <section class="team-stats-bar" id="team-stats-bar">
551
+ <div class="team-stat">
552
+ <span class="team-stat-label">DEVELOPERS ONLINE</span>
553
+ <div class="team-stat-value gradient-text" id="team-stat-online">
554
+ 0
555
+ </div>
556
+ </div>
557
+ <div class="team-stat">
558
+ <span class="team-stat-label">ACTIVE PIPELINES</span>
559
+ <div class="team-stat-value" id="team-stat-pipelines">0</div>
560
+ </div>
561
+ <div class="team-stat">
562
+ <span class="team-stat-label">TOTAL QUEUED</span>
563
+ <div class="team-stat-value" id="team-stat-queued">0</div>
564
+ </div>
565
+ </section>
566
+
567
+ <!-- Developer Cards -->
568
+ <section class="section-header">
569
+ <h2 class="section-label">CONNECTED DEVELOPERS</h2>
570
+ </section>
571
+ <div class="team-grid" id="team-grid">
572
+ <div class="empty-state">
573
+ No developers connected. Run
574
+ <code>shipwright connect start</code> to join.
575
+ </div>
576
+ </div>
577
+
578
+ <!-- Team Activity Feed -->
579
+ <section class="section-header" style="margin-top: 2rem">
580
+ <h2 class="section-label">TEAM ACTIVITY</h2>
581
+ </section>
582
+ <div class="team-activity" id="team-activity">
583
+ <div class="empty-state">No team activity yet.</div>
584
+ </div>
585
+ </div>
586
+ </main>
587
+
588
+ <!-- Footer -->
589
+ <footer class="footer">
590
+ <span>Shipwright Fleet Command v1.7.1</span>
591
+ <span>Dashboard refreshes via WebSocket</span>
592
+ </footer>
593
+
594
+ <!-- Intervention Modal -->
595
+ <div class="modal-overlay" id="intervention-modal" style="display: none">
596
+ <div class="modal-card">
597
+ <div class="modal-header">
598
+ <h3 class="modal-title" id="modal-title">Send Message</h3>
599
+ <button class="modal-close" id="modal-close">&times;</button>
600
+ </div>
601
+ <div class="modal-body">
602
+ <textarea
603
+ class="modal-textarea"
604
+ id="modal-message"
605
+ placeholder="Type a message for the agent..."
606
+ rows="4"
607
+ ></textarea>
608
+ </div>
609
+ <div class="modal-footer">
610
+ <button class="modal-btn modal-btn-cancel" id="modal-cancel">
611
+ Cancel
612
+ </button>
613
+ <button class="modal-btn modal-btn-send" id="modal-send">Send</button>
614
+ </div>
615
+ </div>
616
+ </div>
617
+
618
+ <!-- Emergency Brake Modal -->
619
+ <div class="modal-overlay" id="emergency-modal" style="display: none">
620
+ <div class="modal-card">
621
+ <div class="modal-header">
622
+ <h3 class="modal-title">Emergency Brake</h3>
623
+ <button class="modal-close" id="emergency-modal-close">
624
+ &times;
625
+ </button>
626
+ </div>
627
+ <div class="modal-body">
628
+ <p class="emergency-warning">
629
+ This will pause
630
+ <strong id="emergency-active-count">0</strong> active pipelines and
631
+ <strong id="emergency-queue-count">0</strong> queued items.
632
+ </p>
633
+ </div>
634
+ <div class="modal-footer">
635
+ <button class="modal-btn modal-btn-cancel" id="emergency-cancel">
636
+ Cancel
637
+ </button>
638
+ <button class="modal-btn modal-btn-danger" id="emergency-confirm">
639
+ Pause Everything
640
+ </button>
641
+ </div>
642
+ </div>
643
+ </div>
644
+
645
+ <!-- Add Machine Modal -->
646
+ <div class="modal-overlay" id="add-machine-modal" style="display: none">
647
+ <div class="modal-card modal-wide">
648
+ <div class="modal-header">
649
+ <h3 class="modal-title">Register Machine</h3>
650
+ <button class="modal-close" id="machine-modal-close">&times;</button>
651
+ </div>
652
+ <div class="modal-body">
653
+ <div class="form-row">
654
+ <div class="form-group">
655
+ <label class="form-label" for="machine-name">Name</label>
656
+ <input
657
+ type="text"
658
+ id="machine-name"
659
+ class="form-input"
660
+ placeholder="e.g. build-server-1"
661
+ />
662
+ </div>
663
+ <div class="form-group">
664
+ <label class="form-label" for="machine-host">Host</label>
665
+ <input
666
+ type="text"
667
+ id="machine-host"
668
+ class="form-input"
669
+ placeholder="e.g. 10.0.1.5 or localhost"
670
+ />
671
+ </div>
672
+ </div>
673
+ <div class="form-row">
674
+ <div class="form-group">
675
+ <label class="form-label" for="machine-ssh-user">SSH User</label>
676
+ <input
677
+ type="text"
678
+ id="machine-ssh-user"
679
+ class="form-input"
680
+ placeholder="optional"
681
+ />
682
+ </div>
683
+ <div class="form-group">
684
+ <label class="form-label" for="machine-path"
685
+ >Shipwright Path</label
686
+ >
687
+ <input
688
+ type="text"
689
+ id="machine-path"
690
+ class="form-input"
691
+ placeholder="/opt/shipwright"
692
+ />
693
+ </div>
694
+ </div>
695
+ <div class="form-row">
696
+ <div class="form-group">
697
+ <label class="form-label" for="machine-workers"
698
+ >Max Workers</label
699
+ >
700
+ <input
701
+ type="number"
702
+ id="machine-workers"
703
+ class="form-input"
704
+ value="4"
705
+ min="1"
706
+ max="64"
707
+ />
708
+ </div>
709
+ <div class="form-group">
710
+ <label class="form-label" for="machine-role">Role</label>
711
+ <select id="machine-role" class="form-input">
712
+ <option value="worker">Worker</option>
713
+ <option value="primary">Primary</option>
714
+ </select>
715
+ </div>
716
+ </div>
717
+ <div
718
+ id="machine-modal-error"
719
+ class="form-error"
720
+ style="display: none"
721
+ ></div>
722
+ </div>
723
+ <div class="modal-footer">
724
+ <button class="modal-btn modal-btn-cancel" id="machine-modal-cancel">
725
+ Cancel
726
+ </button>
727
+ <button class="modal-btn modal-btn-send" id="machine-modal-submit">
728
+ Register
729
+ </button>
730
+ </div>
731
+ </div>
732
+ </div>
733
+
734
+ <!-- Join Link Modal -->
735
+ <div class="modal-overlay" id="join-link-modal" style="display: none">
736
+ <div class="modal-card">
737
+ <div class="modal-header">
738
+ <h3 class="modal-title">Generate Join Link</h3>
739
+ <button class="modal-close" id="join-modal-close">&times;</button>
740
+ </div>
741
+ <div class="modal-body">
742
+ <div class="form-group">
743
+ <label class="form-label" for="join-label">Label (optional)</label>
744
+ <input
745
+ type="text"
746
+ id="join-label"
747
+ class="form-input"
748
+ placeholder="e.g. Seth's laptop"
749
+ />
750
+ </div>
751
+ <div class="form-group">
752
+ <label class="form-label" for="join-workers">Max Workers</label>
753
+ <input
754
+ type="number"
755
+ id="join-workers"
756
+ class="form-input"
757
+ value="4"
758
+ min="1"
759
+ max="64"
760
+ />
761
+ </div>
762
+ <div id="join-command-display" style="display: none">
763
+ <label class="form-label">Run this command on the machine:</label>
764
+ <div class="join-command">
765
+ <code id="join-command-text"></code>
766
+ <button
767
+ class="join-copy-btn"
768
+ id="join-copy-btn"
769
+ title="Copy to clipboard"
770
+ >
771
+ Copy
772
+ </button>
773
+ </div>
774
+ </div>
775
+ </div>
776
+ <div class="modal-footer">
777
+ <button class="modal-btn modal-btn-cancel" id="join-modal-cancel">
778
+ Cancel
779
+ </button>
780
+ <button class="modal-btn modal-btn-send" id="join-modal-generate">
781
+ Generate
782
+ </button>
783
+ </div>
784
+ </div>
785
+ </div>
786
+
787
+ <!-- Remove Machine Modal -->
788
+ <div class="modal-overlay" id="remove-machine-modal" style="display: none">
789
+ <div class="modal-card">
790
+ <div class="modal-header">
791
+ <h3 class="modal-title">Remove Machine</h3>
792
+ <button class="modal-close" id="remove-modal-close">&times;</button>
793
+ </div>
794
+ <div class="modal-body">
795
+ <p class="emergency-warning">
796
+ Remove <strong id="remove-machine-name"></strong> from the fleet?
797
+ </p>
798
+ <label class="form-checkbox-label">
799
+ <input type="checkbox" id="remove-stop-daemon" />
800
+ Also stop daemon on this machine
801
+ </label>
802
+ </div>
803
+ <div class="modal-footer">
804
+ <button class="modal-btn modal-btn-cancel" id="remove-modal-cancel">
805
+ Cancel
806
+ </button>
807
+ <button class="modal-btn modal-btn-danger" id="remove-modal-confirm">
808
+ Remove
809
+ </button>
810
+ </div>
811
+ </div>
812
+ </div>
813
+
814
+ <script src="app.js"></script>
815
+ </body>
816
+ </html>