uv-suite 0.25.0 → 0.26.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uv-suite",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Portable framework for AI-assisted software development. 10 agents, 9 skills, 5 hooks, 4 personas. Works with Claude Code, Cursor, and Codex.",
5
5
  "author": "Utsav Anand",
6
6
  "license": "MIT",
@@ -5,95 +5,340 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>UV Suite Watchtower</title>
7
7
  <style>
8
+ :root {
9
+ color-scheme: dark;
10
+ --bg: #0b0b0d;
11
+ --surface: #16161a;
12
+ --surface-hover: #1d1d22;
13
+ --border: #26262c;
14
+ --border-subtle: #1a1a1e;
15
+ --text: #e9e9ec;
16
+ --text-muted: #9a9aa3;
17
+ --text-dim: #6a6a73;
18
+ --accent: #0a84ff;
19
+ --accent-contrast: #ffffff;
20
+ --success: #30d158;
21
+ --success-soft: rgba(48, 209, 88, 0.18);
22
+ --danger: #ff453a;
23
+ --danger-soft: #ff375f;
24
+ --warning: #ff9f0a;
25
+ --yellow: #ffd60a;
26
+ --info: #64d2ff;
27
+ --purple: #bf5af2;
28
+ --purple-soft: #ac8ee0;
29
+ --peach: #ff6961;
30
+
31
+ --event-latest-bg: #17171c;
32
+ --needs-human-bg: rgba(255, 55, 95, 0.14);
33
+ --failure-bg: rgba(255, 105, 97, 0.12);
34
+ --session-boundary-bg: rgba(48, 209, 88, 0.06);
35
+ --user-prompt-bg: rgba(255, 214, 10, 0.06);
36
+ --user-prompt-text: #ffd60a;
37
+
38
+ --font-sans: -apple-system, BlinkMacSystemFont, 'Inter', 'SF Pro Text', system-ui, sans-serif;
39
+ --font-mono: 'SF Mono', ui-monospace, Menlo, Consolas, monospace;
40
+ }
41
+
42
+ [data-theme="light"] {
43
+ color-scheme: light;
44
+ --bg: #fafafa;
45
+ --surface: #ffffff;
46
+ --surface-hover: #f4f4f5;
47
+ --border: #e4e4e7;
48
+ --border-subtle: #ededef;
49
+ --text: #18181b;
50
+ --text-muted: #52525b;
51
+ --text-dim: #8a8a93;
52
+ --accent: #0066cc;
53
+ --accent-contrast: #ffffff;
54
+ --success: #1a9e3e;
55
+ --success-soft: rgba(26, 158, 62, 0.15);
56
+ --danger: #d8302a;
57
+ --danger-soft: #dc184a;
58
+ --warning: #c07300;
59
+ --yellow: #a5720d;
60
+ --info: #0b8aa4;
61
+ --purple: #7c2fbc;
62
+ --purple-soft: #8b3fd4;
63
+ --peach: #c13a35;
64
+
65
+ --event-latest-bg: #f4f4f5;
66
+ --needs-human-bg: rgba(220, 24, 74, 0.08);
67
+ --failure-bg: rgba(216, 48, 42, 0.07);
68
+ --session-boundary-bg: rgba(26, 158, 62, 0.06);
69
+ --user-prompt-bg: rgba(165, 114, 13, 0.06);
70
+ --user-prompt-text: #8a5f0b;
71
+ }
72
+
8
73
  * { box-sizing: border-box; margin: 0; padding: 0; }
9
- body { font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif; background: #000; color: #f5f5f7; }
10
- .header { padding: 16px 24px; border-bottom: 1px solid #2d2d2f; display: flex; align-items: center; justify-content: space-between; }
11
- .header h1 { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
12
- .header .status { font-size: 11px; color: #86868b; }
13
- .header .status .dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; margin-right: 4px; }
14
- .header .status .dot.on { background: #30d158; }
15
- .header .status .dot.off { background: #ff453a; }
16
-
17
- .filters { padding: 8px 24px; border-bottom: 1px solid #1d1d1f; display: flex; gap: 8px; flex-wrap: wrap; }
18
- .filters select, .filters button { background: #1d1d1f; color: #a1a1a6; border: 1px solid #2d2d2f; border-radius: 6px; padding: 4px 10px; font-size: 11px; cursor: pointer; }
19
- .filters select:focus, .filters button:hover { border-color: #424245; color: #f5f5f7; }
20
- .filters button.active { background: #0071e3; color: #fff; border-color: #0071e3; }
21
-
22
- .stats { padding: 12px 24px; display: flex; gap: 24px; border-bottom: 1px solid #1d1d1f; }
23
- .stat { text-align: center; }
24
- .stat .n { font-size: 20px; font-weight: 600; }
25
- .stat .l { font-size: 10px; color: #86868b; text-transform: uppercase; letter-spacing: 0.5px; margin-top: 2px; }
26
- .stat .n.alert { color: #ff375f; }
27
-
28
- .sessions { padding: 12px 24px; border-bottom: 1px solid #1d1d1f; display: flex; gap: 8px; flex-wrap: wrap; }
29
- .session-tag { padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: 500; cursor: pointer; border: 1px solid transparent; }
30
- .session-tag:hover { border-color: #424245; }
31
- .session-tag.active { border-color: #fff; }
32
-
33
- .timeline { padding: 8px 0; overflow-y: auto; max-height: calc(100vh - 280px); }
34
-
35
- .event { padding: 10px 24px; display: grid; grid-template-columns: 70px 110px 140px 65px 1fr; gap: 10px; align-items: start; font-size: 13px; border-bottom: 1px solid #0d0d0d; transition: all 0.2s; opacity: 0.75; }
36
- .event:hover { background: #0d0d0d; opacity: 1; }
37
- .event .time { color: #6e6e73; font-variant-numeric: tabular-nums; font-family: 'SF Mono', monospace; font-size: 11px; padding-top: 2px; }
38
- .event .type { font-weight: 500; font-size: 12px; }
39
- .event .session { font-size: 11px; border-radius: 8px; padding: 2px 8px; display: inline-block; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
40
- .event .tool { color: #a1a1a6; font-family: 'SF Mono', monospace; font-size: 11px; padding-top: 2px; }
41
- .event .detail { color: #a1a1a6; font-size: 12px; line-height: 1.5; word-break: break-all; }
42
- .event .detail .filepath { color: #64d2ff; }
43
- .event .detail .cmd { color: #ffd60a; }
44
-
45
- /* Latest event — full opacity, larger, highlighted */
46
- .event.latest { opacity: 1; font-size: 14px; background: #0a0a0a; border-bottom: 2px solid #2d2d2f; }
47
- .event.latest .type { font-size: 13px; }
48
- .event.latest .detail { font-size: 13px; color: #d1d1d6; }
49
-
50
- /* Human intervention — strong highlight */
51
- .event.needs-human { background: #ff375f18; border-left: 4px solid #ff375f; opacity: 1; }
52
- .event.needs-human .type { color: #ff375f; }
53
- .event.needs-human .human-badge { display: inline-block; background: #ff375f; color: #fff; font-size: 9px; font-weight: 700; padding: 1px 6px; border-radius: 3px; letter-spacing: 0.5px; margin-left: 8px; vertical-align: middle; }
54
-
55
- /* Failure highlight */
56
- .event.failure { background: #ff696115; border-left: 4px solid #ff6961; opacity: 1; }
57
-
58
- /* Session start/end */
59
- .event.session-boundary { background: #30d15808; }
60
-
61
- /* User prompt — show what was asked */
62
- .event.user-prompt { background: #ffd60a08; }
63
- .event.user-prompt .detail { color: #ffd60a; font-style: italic; }
64
-
65
- /* Bottom loader area */
66
- .timeline-end { padding: 24px; text-align: center; }
67
- .loader { display: inline-block; width: 40px; height: 4px; position: relative; }
68
- .loader::before { content: ''; position: absolute; width: 8px; height: 4px; background: #424245; border-radius: 2px; animation: loader 1.2s ease-in-out infinite; }
74
+
75
+ html, body { height: 100%; }
76
+ body {
77
+ font-family: var(--font-sans);
78
+ background: var(--bg);
79
+ color: var(--text);
80
+ font-size: 14px;
81
+ line-height: 1.5;
82
+ -webkit-font-smoothing: antialiased;
83
+ -moz-osx-font-smoothing: grayscale;
84
+ display: flex;
85
+ flex-direction: column;
86
+ transition: background-color 0.2s ease, color 0.2s ease;
87
+ }
88
+
89
+ .header {
90
+ padding: 18px 28px;
91
+ border-bottom: 1px solid var(--border);
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: space-between;
95
+ gap: 16px;
96
+ }
97
+ .header h1 { font-size: 18px; font-weight: 600; letter-spacing: -0.02em; }
98
+ .header .meta { display: flex; align-items: center; gap: 14px; }
99
+ .header .status { font-size: 13px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
100
+ .header .status .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 7px; vertical-align: 1px; }
101
+ .header .status .dot.on { background: var(--success); box-shadow: 0 0 0 3px var(--success-soft); }
102
+ .header .status .dot.off { background: var(--danger); }
103
+
104
+ .theme-toggle {
105
+ background: transparent;
106
+ color: var(--text-muted);
107
+ border: 1px solid var(--border);
108
+ border-radius: 7px;
109
+ width: 34px;
110
+ height: 30px;
111
+ display: inline-flex;
112
+ align-items: center;
113
+ justify-content: center;
114
+ cursor: pointer;
115
+ padding: 0;
116
+ transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
117
+ }
118
+ .theme-toggle:hover { color: var(--text); border-color: var(--text-dim); background: var(--surface); }
119
+ .theme-toggle svg { width: 15px; height: 15px; }
120
+ .theme-toggle .icon-sun { display: block; }
121
+ .theme-toggle .icon-moon { display: none; }
122
+ [data-theme="light"] .theme-toggle .icon-sun { display: none; }
123
+ [data-theme="light"] .theme-toggle .icon-moon { display: block; }
124
+
125
+ .stats {
126
+ padding: 16px 28px;
127
+ display: flex;
128
+ gap: 36px;
129
+ border-bottom: 1px solid var(--border-subtle);
130
+ }
131
+ .stat { display: flex; flex-direction: column; }
132
+ .stat .n {
133
+ font-size: 26px;
134
+ font-weight: 600;
135
+ letter-spacing: -0.02em;
136
+ font-variant-numeric: tabular-nums;
137
+ line-height: 1.1;
138
+ }
139
+ .stat .l {
140
+ font-size: 11px;
141
+ color: var(--text-muted);
142
+ text-transform: uppercase;
143
+ letter-spacing: 0.08em;
144
+ font-weight: 500;
145
+ margin-top: 4px;
146
+ }
147
+ .stat .n.alert { color: var(--danger-soft); }
148
+
149
+ .sessions {
150
+ padding: 12px 28px;
151
+ border-bottom: 1px solid var(--border-subtle);
152
+ display: flex;
153
+ gap: 8px;
154
+ flex-wrap: wrap;
155
+ }
156
+ .session-tag {
157
+ padding: 4px 12px;
158
+ border-radius: 14px;
159
+ font-size: 13px;
160
+ font-weight: 500;
161
+ cursor: pointer;
162
+ border: 1px solid transparent;
163
+ transition: border-color 0.15s ease;
164
+ }
165
+ .session-tag:hover { border-color: var(--text-dim); }
166
+ .session-tag.active { border-color: var(--text); }
167
+
168
+ .filters {
169
+ padding: 10px 28px;
170
+ border-bottom: 1px solid var(--border-subtle);
171
+ display: flex;
172
+ gap: 10px;
173
+ flex-wrap: wrap;
174
+ align-items: center;
175
+ }
176
+ .filters select, .filters button {
177
+ background: var(--surface);
178
+ color: var(--text-muted);
179
+ border: 1px solid var(--border);
180
+ border-radius: 6px;
181
+ padding: 0 12px;
182
+ font-size: 13px;
183
+ font-family: inherit;
184
+ cursor: pointer;
185
+ height: 32px;
186
+ transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
187
+ }
188
+ .filters select:hover, .filters button:hover { border-color: var(--text-dim); color: var(--text); }
189
+ .filters select:focus { outline: none; border-color: var(--accent); }
190
+ .filters button.active { background: var(--accent); color: var(--accent-contrast); border-color: var(--accent); }
191
+
192
+ .timeline { flex: 1; min-height: 0; overflow-y: auto; padding: 4px 0; }
193
+
194
+ .event {
195
+ padding: 14px 28px;
196
+ display: grid;
197
+ grid-template-columns: 82px 150px 170px 90px 1fr;
198
+ gap: 14px;
199
+ align-items: start;
200
+ font-size: 14px;
201
+ border-bottom: 1px solid var(--border-subtle);
202
+ transition: background-color 0.15s ease, opacity 0.15s ease;
203
+ opacity: 0.82;
204
+ }
205
+ .event:hover { background: var(--surface-hover); opacity: 1; }
206
+ .event .time {
207
+ color: var(--text-dim);
208
+ font-variant-numeric: tabular-nums;
209
+ font-family: var(--font-mono);
210
+ font-size: 12.5px;
211
+ padding-top: 2px;
212
+ }
213
+ .event .type {
214
+ font-weight: 600;
215
+ font-size: 14px;
216
+ letter-spacing: -0.01em;
217
+ }
218
+ .event .session {
219
+ font-size: 12.5px;
220
+ font-weight: 500;
221
+ border-radius: 10px;
222
+ padding: 3px 10px;
223
+ display: inline-block;
224
+ max-width: 170px;
225
+ overflow: hidden;
226
+ text-overflow: ellipsis;
227
+ white-space: nowrap;
228
+ }
229
+ .event .tool {
230
+ color: var(--text-muted);
231
+ font-family: var(--font-mono);
232
+ font-size: 12.5px;
233
+ padding-top: 2px;
234
+ }
235
+ .event .detail {
236
+ color: var(--text-muted);
237
+ font-size: 14px;
238
+ line-height: 1.55;
239
+ word-break: break-word;
240
+ }
241
+ .event .detail .filepath { color: var(--info); }
242
+ .event .detail .cmd { color: var(--yellow); font-family: var(--font-mono); }
243
+
244
+ .event.latest {
245
+ opacity: 1;
246
+ background: var(--event-latest-bg);
247
+ border-bottom: 2px solid var(--border);
248
+ }
249
+ .event.latest .type { font-size: 15px; }
250
+ .event.latest .detail { font-size: 15px; color: var(--text); }
251
+
252
+ .event.needs-human {
253
+ background: var(--needs-human-bg);
254
+ border-left: 4px solid var(--danger-soft);
255
+ padding-left: 24px;
256
+ opacity: 1;
257
+ }
258
+ .event.needs-human .type { color: var(--danger-soft); }
259
+ .event.needs-human .human-badge {
260
+ display: inline-block;
261
+ background: var(--danger-soft);
262
+ color: #fff;
263
+ font-size: 10px;
264
+ font-weight: 700;
265
+ padding: 2px 7px;
266
+ border-radius: 4px;
267
+ letter-spacing: 0.06em;
268
+ margin-left: 10px;
269
+ vertical-align: middle;
270
+ }
271
+
272
+ .event.failure {
273
+ background: var(--failure-bg);
274
+ border-left: 4px solid var(--peach);
275
+ padding-left: 24px;
276
+ opacity: 1;
277
+ }
278
+ .event.session-boundary { background: var(--session-boundary-bg); }
279
+ .event.user-prompt { background: var(--user-prompt-bg); }
280
+ .event.user-prompt .detail { color: var(--user-prompt-text); font-style: italic; }
281
+
282
+ .timeline-end { padding: 20px 24px; text-align: center; border-bottom: 1px solid var(--border-subtle); }
283
+ .loader { display: inline-block; width: 48px; height: 4px; position: relative; }
284
+ .loader::before {
285
+ content: '';
286
+ position: absolute;
287
+ width: 10px;
288
+ height: 4px;
289
+ background: var(--text-dim);
290
+ border-radius: 2px;
291
+ animation: loader 1.4s ease-in-out infinite;
292
+ }
69
293
  @keyframes loader {
70
294
  0%, 100% { left: 0; }
71
- 50% { left: 32px; }
72
- }
73
- .timeline-end .waiting { font-size: 11px; color: #424245; margin-top: 8px; }
74
-
75
- .empty { padding: 60px 24px; text-align: center; color: #6e6e73; }
76
- .empty p { margin-top: 8px; font-size: 13px; }
77
-
78
- /* Event type colors */
79
- .type-SessionStart { color: #30d158; }
80
- .type-SessionEnd, .type-Stop { color: #ff453a; }
81
- .type-PreToolUse { color: #0a84ff; }
82
- .type-PostToolUse { color: #64d2ff; }
83
- .type-PostToolUseFailure { color: #ff6961; }
84
- .type-UserPromptSubmit { color: #ffd60a; }
85
- .type-SubagentStart { color: #bf5af2; }
86
- .type-SubagentStop { color: #ac8ee0; }
87
- .type-Notification { color: #ff9f0a; }
88
- .type-PermissionRequest { color: #ff375f; }
89
- .type-PreCompact { color: #6e6e73; }
295
+ 50% { left: 38px; }
296
+ }
297
+ .timeline-end .waiting {
298
+ font-size: 13px;
299
+ color: var(--text-dim);
300
+ margin-top: 10px;
301
+ font-variant-numeric: tabular-nums;
302
+ }
303
+
304
+ .empty { padding: 72px 28px; text-align: center; color: var(--text-dim); }
305
+ .empty p { margin-top: 10px; font-size: 14px; }
306
+ .empty p strong { color: var(--text-muted); font-weight: 600; font-size: 15px; }
307
+
308
+ .timeline::-webkit-scrollbar { width: 10px; }
309
+ .timeline::-webkit-scrollbar-track { background: transparent; }
310
+ .timeline::-webkit-scrollbar-thumb { background: var(--border); border-radius: 5px; }
311
+ .timeline::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
312
+
313
+ .type-SessionStart { color: var(--success); }
314
+ .type-SessionEnd, .type-Stop { color: var(--danger); }
315
+ .type-PreToolUse { color: var(--accent); }
316
+ .type-PostToolUse { color: var(--info); }
317
+ .type-PostToolUseFailure { color: var(--peach); }
318
+ .type-UserPromptSubmit { color: var(--yellow); }
319
+ .type-SubagentStart { color: var(--purple); }
320
+ .type-SubagentStop { color: var(--purple-soft); }
321
+ .type-Notification { color: var(--warning); }
322
+ .type-PermissionRequest { color: var(--danger-soft); }
323
+ .type-PreCompact { color: var(--text-muted); }
90
324
  </style>
91
325
  </head>
92
326
  <body>
93
327
 
94
328
  <div class="header">
95
329
  <h1>UV Suite Watchtower</h1>
96
- <div class="status"><span class="dot" id="statusDot"></span><span id="statusText">Connecting...</span></div>
330
+ <div class="meta">
331
+ <div class="status"><span class="dot" id="statusDot"></span><span id="statusText">Connecting...</span></div>
332
+ <button class="theme-toggle" id="themeToggle" type="button" aria-label="Toggle theme" title="Toggle theme">
333
+ <svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
334
+ <circle cx="12" cy="12" r="4"/>
335
+ <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>
336
+ </svg>
337
+ <svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
338
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
339
+ </svg>
340
+ </button>
341
+ </div>
97
342
  </div>
98
343
 
99
344
  <div class="stats">
@@ -456,6 +701,27 @@ function connect() {
456
701
  };
457
702
  }
458
703
 
704
+ // Theme: persist choice in localStorage, fall back to system preference
705
+ const THEME_KEY = 'uv-watchtower-theme';
706
+ const themeToggle = document.getElementById('themeToggle');
707
+ const mql = window.matchMedia('(prefers-color-scheme: light)');
708
+
709
+ function applyTheme(t) { document.documentElement.setAttribute('data-theme', t); }
710
+ function resolvedTheme() {
711
+ return localStorage.getItem(THEME_KEY) || (mql.matches ? 'light' : 'dark');
712
+ }
713
+ applyTheme(resolvedTheme());
714
+
715
+ themeToggle.onclick = () => {
716
+ const next = resolvedTheme() === 'light' ? 'dark' : 'light';
717
+ localStorage.setItem(THEME_KEY, next);
718
+ applyTheme(next);
719
+ };
720
+
721
+ mql.addEventListener('change', () => {
722
+ if (!localStorage.getItem(THEME_KEY)) applyTheme(resolvedTheme());
723
+ });
724
+
459
725
  connect();
460
726
  </script>
461
727
  </body>