@cotal-ai/web 0.0.0 → 0.11.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/LICENSE +202 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/web/app.js +823 -0
- package/dist/web/graph.html +207 -0
- package/dist/web/graph.js +528 -0
- package/dist/web/index.html +350 -0
- package/dist/web.d.ts +12 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +258 -0
- package/dist/web.js.map +1 -0
- package/package.json +32 -5
- package/README.md +0 -4
|
@@ -0,0 +1,350 @@
|
|
|
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" />
|
|
6
|
+
<title>Cotal · observability</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=Work+Sans:wght@400;500;600;700&display=swap"
|
|
11
|
+
rel="stylesheet"
|
|
12
|
+
/>
|
|
13
|
+
<style>
|
|
14
|
+
:root {
|
|
15
|
+
--bg: #0d1117; --panel: #161b22; --tile: #1c2230; --line: #21262d;
|
|
16
|
+
--fg: #e6edf3; --dim: #8b949e; --faint: #6e7681;
|
|
17
|
+
--blue: #58a6ff; --green: #3fb950; --amber: #d29922; --orange: #db6d28; --red: #f85149;
|
|
18
|
+
--tint-green: #1f2d24; --tint-amber: #2a2417; --peer-amber: #221e12; --tag-amber: #3a2e12;
|
|
19
|
+
--sel: #15233a; --intent: #10203a; --intent-badge: #16345c; --rollup: #12161d;
|
|
20
|
+
--font: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
21
|
+
}
|
|
22
|
+
* { box-sizing: border-box; }
|
|
23
|
+
html, body { height: 100%; }
|
|
24
|
+
body {
|
|
25
|
+
margin: 0; display: grid; grid-template-rows: 76px 1fr;
|
|
26
|
+
font-family: var(--font); font-size: 13px; line-height: 1.45;
|
|
27
|
+
background: var(--bg); color: var(--fg);
|
|
28
|
+
-webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ── Header ── */
|
|
32
|
+
header {
|
|
33
|
+
display: flex; align-items: center; gap: 20px; padding: 0 24px;
|
|
34
|
+
background: var(--panel); border-bottom: 1px solid var(--line);
|
|
35
|
+
}
|
|
36
|
+
.brand { display: flex; align-items: center; gap: 10px; }
|
|
37
|
+
.brand .mark { width: 10px; height: 10px; border-radius: 50%; background: var(--blue); }
|
|
38
|
+
.brand .title { font-size: 18px; font-weight: 700; color: var(--fg); }
|
|
39
|
+
.brand .space { font-size: 14px; color: var(--dim); }
|
|
40
|
+
.brand-graph {
|
|
41
|
+
font-size: 12px; font-weight: 600; color: var(--dim); text-decoration: none;
|
|
42
|
+
padding: 4px 10px; border: 1px solid var(--line); border-radius: 8px;
|
|
43
|
+
}
|
|
44
|
+
.brand-graph:hover { color: var(--fg); border-color: var(--blue); }
|
|
45
|
+
.pill {
|
|
46
|
+
display: inline-flex; align-items: center; gap: 6px; padding: 4px 9px;
|
|
47
|
+
border-radius: 20px; background: var(--tint-green);
|
|
48
|
+
}
|
|
49
|
+
.pill .d { width: 7px; height: 7px; border-radius: 50%; background: var(--green); }
|
|
50
|
+
.pill .t { font-size: 11px; font-weight: 600; color: var(--green); }
|
|
51
|
+
.pill.down { background: #2a1717; }
|
|
52
|
+
.pill.down .d { background: var(--red); } .pill.down .t { color: var(--red); }
|
|
53
|
+
|
|
54
|
+
/* HealthBand tiles */
|
|
55
|
+
.tiles { margin-left: auto; display: flex; gap: 10px; }
|
|
56
|
+
.tile {
|
|
57
|
+
display: flex; align-items: center; gap: 9px; padding: 8px 12px;
|
|
58
|
+
border-radius: 8px; background: var(--tile); border: 1px solid var(--line);
|
|
59
|
+
}
|
|
60
|
+
.tile .bar { width: 3px; height: 24px; border-radius: 2px; background: var(--faint); }
|
|
61
|
+
.tile .c { display: flex; flex-direction: column; gap: 1px; }
|
|
62
|
+
.tile .n { font-size: 17px; font-weight: 700; color: var(--fg); line-height: 1.1; }
|
|
63
|
+
.tile .lbl { font-size: 10.5px; color: var(--dim); letter-spacing: .3px; line-height: 1.1; }
|
|
64
|
+
.tile.working .bar { background: var(--green); }
|
|
65
|
+
.tile.waiting { background: var(--tint-amber); border-color: var(--amber); }
|
|
66
|
+
.tile.waiting .bar { background: var(--amber); } .tile.waiting .n { color: var(--amber); }
|
|
67
|
+
.tile.oldest .bar { background: var(--orange); }
|
|
68
|
+
|
|
69
|
+
/* ── Body grid ── */
|
|
70
|
+
main { display: grid; grid-template-columns: 300px 1fr 340px; min-height: 0; }
|
|
71
|
+
aside {
|
|
72
|
+
background: var(--panel); border-right: 1px solid var(--line);
|
|
73
|
+
overflow-y: auto; min-height: 0;
|
|
74
|
+
}
|
|
75
|
+
aside.rail { border-right: 0; border-left: 1px solid var(--line); }
|
|
76
|
+
|
|
77
|
+
.ghead {
|
|
78
|
+
display: flex; align-items: center; gap: 6px; padding: 12px 16px;
|
|
79
|
+
font-size: 11px; font-weight: 600; letter-spacing: .8px; color: var(--dim);
|
|
80
|
+
}
|
|
81
|
+
.ghead .c { margin-left: auto; font-size: 11px; font-weight: 400; color: var(--faint); }
|
|
82
|
+
.divider { height: 1px; background: var(--line); }
|
|
83
|
+
|
|
84
|
+
/* ── Roster ── */
|
|
85
|
+
.peer {
|
|
86
|
+
display: flex; gap: 9px; padding: 8px 16px; align-items: flex-start;
|
|
87
|
+
}
|
|
88
|
+
.peer.waiting { background: var(--peer-amber); border-left: 2px solid var(--amber); padding-left: 14px; }
|
|
89
|
+
.peer .dot { width: 7px; font-size: 11px; line-height: 19px; flex: none; text-align: center; }
|
|
90
|
+
.dot.working { color: var(--green); } .dot.waiting { color: var(--amber); }
|
|
91
|
+
.dot.idle, .dot.offline { color: var(--faint); }
|
|
92
|
+
.peer .c { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
|
|
93
|
+
.peer .l1 { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
94
|
+
.peer .name { font-size: 13.5px; font-weight: 600; color: var(--fg); white-space: nowrap; }
|
|
95
|
+
.peer .role { font-size: 11px; color: var(--faint); white-space: nowrap;
|
|
96
|
+
overflow: hidden; text-overflow: ellipsis; }
|
|
97
|
+
.peer .act { font-size: 11.5px; color: var(--dim); white-space: nowrap;
|
|
98
|
+
overflow: hidden; text-overflow: ellipsis; }
|
|
99
|
+
.peer.waiting .act { color: var(--amber); }
|
|
100
|
+
.tag {
|
|
101
|
+
font-size: 9.5px; font-weight: 600; padding: 1px 6px; border-radius: 4px;
|
|
102
|
+
white-space: nowrap; background: var(--tag-amber); color: var(--amber);
|
|
103
|
+
}
|
|
104
|
+
/* A peer row is a click-through into its Agent Detail card. */
|
|
105
|
+
.peer.nav { cursor: pointer; }
|
|
106
|
+
.peer.nav:hover { background: #ffffff06; }
|
|
107
|
+
.peer.nav.sel { background: var(--sel); }
|
|
108
|
+
.peer.nav:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }
|
|
109
|
+
|
|
110
|
+
/* ── Channels ── */
|
|
111
|
+
.chan {
|
|
112
|
+
display: flex; align-items: center; gap: 8px; padding: 6px 16px;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
}
|
|
115
|
+
.chan:hover { background: #ffffff06; }
|
|
116
|
+
.chan.sel { background: var(--sel); border-left: 2px solid var(--blue); padding-left: 14px; }
|
|
117
|
+
.chan .l { display: flex; align-items: center; gap: 7px; min-width: 0; flex: 1; }
|
|
118
|
+
.chan .hash { font-size: 12px; color: var(--dim); flex: none; }
|
|
119
|
+
.chan .glyph { font-size: 12px; color: var(--blue); flex: none; }
|
|
120
|
+
.chan .name { font-size: 13px; color: var(--blue); overflow: hidden;
|
|
121
|
+
text-overflow: ellipsis; white-space: nowrap; }
|
|
122
|
+
.chan.sel .name { font-weight: 600; color: var(--fg); }
|
|
123
|
+
.chan.muted .hash, .chan.muted .name { color: var(--faint); }
|
|
124
|
+
.chan .count { font-size: 11px; color: var(--faint); flex: none; }
|
|
125
|
+
.chan.sel .count { color: var(--dim); }
|
|
126
|
+
.mention {
|
|
127
|
+
font-size: 10px; font-weight: 700; color: var(--bg); background: var(--blue);
|
|
128
|
+
padding: 1px 6px; border-radius: 9px; flex: none;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* ── Feed ── */
|
|
132
|
+
section { display: flex; flex-direction: column; min-height: 0; background: var(--bg); }
|
|
133
|
+
.feed-head {
|
|
134
|
+
display: flex; align-items: center; gap: 10px; padding: 14px 20px;
|
|
135
|
+
border-bottom: 1px solid var(--line); flex: none;
|
|
136
|
+
}
|
|
137
|
+
.feed-head .h { font-size: 15px; font-weight: 700; color: var(--fg); }
|
|
138
|
+
.feed-head .sub { font-size: 12px; color: var(--dim); }
|
|
139
|
+
.feed-head .ctrls { margin-left: auto; display: flex; gap: 8px; align-items: center; }
|
|
140
|
+
.chip {
|
|
141
|
+
font-size: 11px; padding: 4px 10px; border-radius: 14px; cursor: pointer;
|
|
142
|
+
color: var(--faint); user-select: none; border: 1px solid transparent; white-space: nowrap;
|
|
143
|
+
}
|
|
144
|
+
.chip.mode.on { background: var(--tile); border-color: var(--line); color: var(--fg); }
|
|
145
|
+
.chip.pause.on { color: var(--amber); }
|
|
146
|
+
.chip.static { cursor: default; }
|
|
147
|
+
.chip.danger { color: var(--red); }
|
|
148
|
+
.chip.danger:hover { background: #2a1717; border-color: var(--red); }
|
|
149
|
+
|
|
150
|
+
.feed { overflow-y: auto; min-height: 0; flex: 1; display: flex; flex-direction: column;
|
|
151
|
+
gap: 2px; padding: 8px; }
|
|
152
|
+
.sys { text-align: center; padding: 5px 12px; font-size: 11px; color: var(--faint); }
|
|
153
|
+
.msg { display: flex; gap: 10px; padding: 7px 12px; align-items: flex-start; }
|
|
154
|
+
.msg .ts { font-size: 11px; color: var(--faint); flex: none; width: 40px; line-height: 19px; }
|
|
155
|
+
.msg .badge {
|
|
156
|
+
font-size: 9.5px; font-weight: 600; padding: 2px 7px; border-radius: 4px;
|
|
157
|
+
background: var(--line); flex: none; line-height: 1.4;
|
|
158
|
+
}
|
|
159
|
+
.badge.chat { color: var(--blue); } .badge.unicast { color: var(--amber); }
|
|
160
|
+
.badge.anycast { color: var(--green); } .badge.intent { color: var(--blue); background: var(--intent-badge); }
|
|
161
|
+
.msg .c { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
|
|
162
|
+
.msg .l1 { display: flex; align-items: center; gap: 6px; min-width: 0; flex-wrap: wrap; }
|
|
163
|
+
.msg .who { font-size: 13px; font-weight: 600; color: var(--fg); }
|
|
164
|
+
.msg .role { font-size: 10.5px; color: var(--faint); }
|
|
165
|
+
.msg .tgt { font-size: 11px; color: var(--dim); }
|
|
166
|
+
.msg .body { font-size: 12.5px; color: var(--fg); word-break: break-word; }
|
|
167
|
+
.msg.intent { background: var(--intent); border-left: 2px solid var(--blue); padding-left: 10px; }
|
|
168
|
+
.msg.intent .tgt { color: var(--blue); }
|
|
169
|
+
.subpill {
|
|
170
|
+
font-size: 9.5px; font-weight: 600; padding: 1px 6px; border-radius: 4px;
|
|
171
|
+
background: var(--tint-amber); color: var(--amber); flex: none;
|
|
172
|
+
}
|
|
173
|
+
.rollup {
|
|
174
|
+
display: flex; align-items: center; gap: 8px; margin: 0 4px; padding: 6px 12px;
|
|
175
|
+
border-radius: 6px; background: var(--rollup);
|
|
176
|
+
}
|
|
177
|
+
.rollup .ar { font-size: 13px; color: var(--faint); }
|
|
178
|
+
.rollup .t { font-size: 11.5px; color: var(--dim); }
|
|
179
|
+
|
|
180
|
+
/* ── NEEDS YOU rail ── */
|
|
181
|
+
aside.rail { padding: 16px; display: flex; flex-direction: column; gap: 12px; }
|
|
182
|
+
.rail-head { display: flex; align-items: center; justify-content: space-between; }
|
|
183
|
+
.rail-head .t { font-size: 12px; font-weight: 700; color: var(--fg); letter-spacing: .8px; }
|
|
184
|
+
.rail-head .n {
|
|
185
|
+
font-size: 11px; font-weight: 700; color: var(--bg); background: var(--red);
|
|
186
|
+
padding: 2px 8px; border-radius: 12px;
|
|
187
|
+
}
|
|
188
|
+
.card {
|
|
189
|
+
border-radius: 10px; background: var(--tile); border: 1px solid var(--ac, var(--line));
|
|
190
|
+
padding: 14px; display: flex; flex-direction: column; gap: 9px;
|
|
191
|
+
}
|
|
192
|
+
.card.nav { cursor: pointer; transition: border-color .12s, background .12s; }
|
|
193
|
+
.card.nav:hover { border-color: var(--ac); }
|
|
194
|
+
.card.nav.sel { border-color: var(--ac); background: var(--tint-amber, var(--tile)); }
|
|
195
|
+
.card .top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
196
|
+
.card .cat-l { display: flex; align-items: center; gap: 7px; }
|
|
197
|
+
.card .cdot { width: 8px; height: 8px; border-radius: 50%; background: var(--ac); }
|
|
198
|
+
.card .cat { font-size: 10.5px; font-weight: 700; color: var(--ac); letter-spacing: .4px; }
|
|
199
|
+
.card .age { font-size: 10px; color: var(--dim); background: var(--line);
|
|
200
|
+
padding: 1px 6px; border-radius: 4px; }
|
|
201
|
+
.card .title { font-size: 13.5px; font-weight: 600; color: var(--fg); }
|
|
202
|
+
.card .crole { font-size: 11px; font-weight: 500; color: var(--faint); margin-left: 7px; }
|
|
203
|
+
.card .desc { font-size: 12px; color: var(--dim); }
|
|
204
|
+
/* Live waiting cards: one-line preview (gist at a glance); full text lives in the click-through detail. */
|
|
205
|
+
.card.nav .desc { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
206
|
+
.card .btns { display: flex; gap: 8px; }
|
|
207
|
+
.btn {
|
|
208
|
+
font-size: 11.5px; padding: 6px 12px; border-radius: 6px; cursor: pointer;
|
|
209
|
+
border: 1px solid transparent; white-space: nowrap;
|
|
210
|
+
}
|
|
211
|
+
.btn.primary { background: var(--ac); color: var(--bg); font-weight: 700; }
|
|
212
|
+
.btn.secondary { border-color: var(--line); color: var(--fg); font-weight: 500; }
|
|
213
|
+
.tone-amber { --ac: var(--amber); } .tone-red { --ac: var(--red); }
|
|
214
|
+
.tone-orange { --ac: var(--orange); } .tone-blue { --ac: var(--blue); }
|
|
215
|
+
.rail-foot { font-size: 10.5px; color: var(--faint); }
|
|
216
|
+
|
|
217
|
+
/* ── Channel view (center) ── */
|
|
218
|
+
.ch-head {
|
|
219
|
+
display: flex; flex-direction: column; gap: 5px; padding: 14px 20px;
|
|
220
|
+
background: var(--panel); border-bottom: 1px solid var(--line); flex: none;
|
|
221
|
+
}
|
|
222
|
+
.ch-head .row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
223
|
+
.ch-head .title { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
224
|
+
.ch-head .h { font-size: 17px; font-weight: 700; color: var(--fg); }
|
|
225
|
+
.ch-head .sub { font-size: 11px; color: var(--faint); white-space: nowrap; }
|
|
226
|
+
.ch-head .purpose { font-size: 12px; color: var(--dim); }
|
|
227
|
+
.clist { overflow-y: auto; min-height: 0; flex: 1; display: flex; flex-direction: column;
|
|
228
|
+
gap: 2px; padding: 10px; }
|
|
229
|
+
.cmsg { display: flex; gap: 10px; padding: 7px 12px; align-items: flex-start; }
|
|
230
|
+
.cmsg .ts { font-size: 11px; color: var(--faint); flex: none; width: 40px; line-height: 19px; }
|
|
231
|
+
.cmsg .dot { font-size: 11px; flex: none; width: 14px; text-align: center; line-height: 19px; }
|
|
232
|
+
.cmsg .c { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
|
|
233
|
+
.cmsg .l1 { display: flex; align-items: center; gap: 6px; }
|
|
234
|
+
.cmsg .who { font-size: 13px; font-weight: 600; color: var(--fg); }
|
|
235
|
+
.cmsg .role { font-size: 10.5px; color: var(--faint); }
|
|
236
|
+
.cmsg .body { font-size: 12.5px; color: var(--fg); word-break: break-word; }
|
|
237
|
+
.thread {
|
|
238
|
+
align-self: flex-start; margin-top: 2px; font-size: 10.5px; color: var(--blue);
|
|
239
|
+
background: var(--line); padding: 3px 8px; border-radius: 6px;
|
|
240
|
+
}
|
|
241
|
+
.unread-mark { display: flex; align-items: center; gap: 10px; padding: 6px 12px; }
|
|
242
|
+
.unread-mark .line { flex: 1; height: 1px; background: var(--red); }
|
|
243
|
+
.unread-mark .t { font-size: 10.5px; font-weight: 600; color: var(--red); white-space: nowrap; }
|
|
244
|
+
|
|
245
|
+
.ch-head .ctrls { display: flex; align-items: center; gap: 8px; }
|
|
246
|
+
.cmsg .dir { font-size: 11px; color: var(--dim); }
|
|
247
|
+
.dtag { font-size: 9.5px; font-weight: 600; padding: 2px 7px; border-radius: 4px;
|
|
248
|
+
background: var(--line); color: var(--amber); }
|
|
249
|
+
|
|
250
|
+
/* ── Direct messages (sidebar lens) ── */
|
|
251
|
+
.dm { display: flex; align-items: center; gap: 6px; padding: 7px 16px; cursor: pointer; }
|
|
252
|
+
.dm:hover { background: #ffffff06; }
|
|
253
|
+
.dm.sel { background: var(--sel); }
|
|
254
|
+
.dm .caret, .dm .ln { width: 10px; flex: none; text-align: center; font-size: 9px; color: var(--faint); }
|
|
255
|
+
.dm .l { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; }
|
|
256
|
+
.dm .dot { width: 12px; flex: none; text-align: center; font-size: 11px; }
|
|
257
|
+
.dm .nm { font-size: 13.5px; font-weight: 600; color: var(--fg); white-space: nowrap; }
|
|
258
|
+
.dm .role { font-size: 11px; color: var(--faint); white-space: nowrap;
|
|
259
|
+
overflow: hidden; text-overflow: ellipsis; }
|
|
260
|
+
.dm .threads { font-size: 11px; color: var(--faint); flex: none; }
|
|
261
|
+
.dm.sub { padding-left: 34px; }
|
|
262
|
+
.dm.sub .nm { font-size: 12.5px; font-weight: 400; }
|
|
263
|
+
.dm.sub .dot { width: 10px; font-size: 10px; }
|
|
264
|
+
|
|
265
|
+
.empty { color: var(--faint); padding: 22px 16px; font-size: 12px; }
|
|
266
|
+
|
|
267
|
+
/* ── Agent Detail (centre drill-down) ── */
|
|
268
|
+
.detail { padding: 22px 24px; display: flex; flex-direction: column; gap: 13px; overflow-y: auto; }
|
|
269
|
+
.detail .d-head { display: flex; align-items: center; gap: 9px; }
|
|
270
|
+
.detail .d-head .dot { font-size: 13px; }
|
|
271
|
+
.detail .d-status { font-size: 11px; font-weight: 700; letter-spacing: .5px; color: var(--dim); }
|
|
272
|
+
.detail.amber .d-status { color: var(--amber); }
|
|
273
|
+
.detail .d-age { margin-left: auto; font-size: 11px; color: var(--faint); }
|
|
274
|
+
.detail .d-who { font-size: 20px; font-weight: 700; color: var(--fg); display: flex; align-items: baseline; }
|
|
275
|
+
.detail .d-who .crole { font-size: 12px; font-weight: 500; color: var(--faint); margin-left: 8px; }
|
|
276
|
+
.detail .d-label { font-size: 10.5px; font-weight: 600; letter-spacing: .4px; color: var(--faint); text-transform: uppercase; }
|
|
277
|
+
.detail .d-block { font-size: 13px; color: var(--fg); line-height: 1.5; padding: 12px 14px; border-radius: 8px;
|
|
278
|
+
background: var(--tile); border: 1px solid var(--line); white-space: pre-wrap; word-break: break-word; }
|
|
279
|
+
.detail.amber .d-block { border-color: var(--amber); background: var(--tint-amber); }
|
|
280
|
+
.detail .d-block.muted { color: var(--dim); }
|
|
281
|
+
.detail .d-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: -6px; }
|
|
282
|
+
.detail .d-badge {
|
|
283
|
+
font-size: 10.5px; font-weight: 600; padding: 2px 8px; border-radius: 5px;
|
|
284
|
+
background: var(--tile); border: 1px solid var(--line); color: var(--dim);
|
|
285
|
+
}
|
|
286
|
+
.detail .d-badge.model { font-family: ui-monospace, monospace; color: var(--fg); letter-spacing: -.2px; }
|
|
287
|
+
/* Harness (host connector) badge — brand-coloured via --hc; used in the detail view (pill
|
|
288
|
+
with label) and the roster row (bare icon). */
|
|
289
|
+
.harness-badge {
|
|
290
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
291
|
+
font-size: 11px; font-weight: 600; padding: 3px 10px 3px 8px; border-radius: 999px;
|
|
292
|
+
color: var(--hc); border: 1px solid color-mix(in srgb, var(--hc) 45%, transparent);
|
|
293
|
+
background: color-mix(in srgb, var(--hc) 15%, transparent);
|
|
294
|
+
}
|
|
295
|
+
.harness-ico { display: inline-flex; align-items: center; flex: none; }
|
|
296
|
+
.harness-badge svg, .harness-ico svg { width: 13px; height: 13px; display: block; }
|
|
297
|
+
.detail .d-desc { font-size: 13px; color: var(--fg); line-height: 1.5; }
|
|
298
|
+
.detail .d-sec { display: flex; flex-direction: column; gap: 7px; }
|
|
299
|
+
.detail .d-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
300
|
+
.detail .d-chip {
|
|
301
|
+
font-size: 11px; padding: 2px 9px; border-radius: 12px;
|
|
302
|
+
background: var(--tile); border: 1px solid var(--line); color: var(--dim);
|
|
303
|
+
}
|
|
304
|
+
.detail .d-skills { display: flex; flex-direction: column; gap: 8px; }
|
|
305
|
+
.detail .d-skill {
|
|
306
|
+
display: flex; flex-direction: column; gap: 1px; padding: 8px 12px;
|
|
307
|
+
border-radius: 8px; background: var(--tile); border: 1px solid var(--line);
|
|
308
|
+
}
|
|
309
|
+
.detail .d-skill .nm { font-size: 12.5px; font-weight: 600; color: var(--fg); }
|
|
310
|
+
.detail .d-skill .dsc { font-size: 11.5px; color: var(--dim); }
|
|
311
|
+
.detail .d-kv { display: flex; flex-direction: column; gap: 4px; }
|
|
312
|
+
.detail .d-kv .row { display: flex; gap: 10px; font-size: 12px; }
|
|
313
|
+
.detail .d-kv .k { color: var(--faint); min-width: 90px; flex: none; }
|
|
314
|
+
.detail .d-kv .v { color: var(--fg); word-break: break-word; }
|
|
315
|
+
.detail .d-foot {
|
|
316
|
+
display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 4px;
|
|
317
|
+
padding-top: 11px; border-top: 1px solid var(--line);
|
|
318
|
+
}
|
|
319
|
+
.detail .d-foot-item { font-size: 10.5px; color: var(--faint); }
|
|
320
|
+
.detail .d-foot-item.id { font-family: ui-monospace, monospace; word-break: break-all; }
|
|
321
|
+
</style>
|
|
322
|
+
</head>
|
|
323
|
+
<body>
|
|
324
|
+
<header>
|
|
325
|
+
<span class="brand">
|
|
326
|
+
<span class="mark"></span>
|
|
327
|
+
<span class="title">Cotal</span>
|
|
328
|
+
<span class="space" id="space"></span>
|
|
329
|
+
<span class="pill down" id="conn"><span class="d"></span><span class="t">connecting</span></span>
|
|
330
|
+
<a class="brand-graph" href="/graph">Graph view →</a>
|
|
331
|
+
</span>
|
|
332
|
+
<div class="tiles" id="tiles"></div>
|
|
333
|
+
</header>
|
|
334
|
+
<main id="main">
|
|
335
|
+
<aside>
|
|
336
|
+
<div class="ghead">ONLINE <span class="c" id="online-c"></span></div>
|
|
337
|
+
<div id="roster"></div>
|
|
338
|
+
<div class="divider"></div>
|
|
339
|
+
<div class="ghead">CHANNELS</div>
|
|
340
|
+
<div id="channels"></div>
|
|
341
|
+
<div class="divider"></div>
|
|
342
|
+
<div class="ghead">DIRECT MESSAGES</div>
|
|
343
|
+
<div id="dms"></div>
|
|
344
|
+
</aside>
|
|
345
|
+
<section id="center"></section>
|
|
346
|
+
<aside class="rail" id="rail"></aside>
|
|
347
|
+
</main>
|
|
348
|
+
<script src="/app.js"></script>
|
|
349
|
+
</body>
|
|
350
|
+
</html>
|
package/dist/web.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ParsedArgs } from "@cotal-ai/core";
|
|
2
|
+
/** The dashboard's default port and its branded address. The server binds loopback
|
|
3
|
+
* (127.0.0.1) but serves any Host, so `cotal.localhost` — which Chrome/Firefox/Edge
|
|
4
|
+
* resolve to loopback with no DNS setup — just works. (Safari may not resolve
|
|
5
|
+
* `*.localhost`; plain http://127.0.0.1:7799 always does.) */
|
|
6
|
+
export declare const WEB_PORT = 7799;
|
|
7
|
+
export declare const WEB_URL = "http://cotal.localhost:7799/";
|
|
8
|
+
/** A live observability dashboard for a space, served over HTTP + SSE. A read-only
|
|
9
|
+
* observer endpoint (invisible to peers) feeds the page presence, channel history,
|
|
10
|
+
* and a live message stream — no manager required. Bound to loopback. */
|
|
11
|
+
export declare function web(args: ParsedArgs): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAKA,OAAO,EAQL,KAAK,UAAU,EAChB,MAAM,gBAAgB,CAAC;AAKxB;;;+DAG+D;AAC/D,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,OAAO,iCAAwC,CAAC;AAS7D;;0EAE0E;AAC1E,wBAAsB,GAAG,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAmMzD"}
|
package/dist/web.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { CotalEndpoint, deliveryOf, parseSubject, spaceWildcard, mintCreds, newIdentity, clearChannel, } from "@cotal-ai/core";
|
|
7
|
+
import { c, connectOrExit, userViewAuth, userViewAuthOrExit } from "@cotal-ai/workspace";
|
|
8
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
/** The dashboard's default port and its branded address. The server binds loopback
|
|
10
|
+
* (127.0.0.1) but serves any Host, so `cotal.localhost` — which Chrome/Firefox/Edge
|
|
11
|
+
* resolve to loopback with no DNS setup — just works. (Safari may not resolve
|
|
12
|
+
* `*.localhost`; plain http://127.0.0.1:7799 always does.) */
|
|
13
|
+
export const WEB_PORT = 7799;
|
|
14
|
+
export const WEB_URL = `http://cotal.localhost:${WEB_PORT}/`;
|
|
15
|
+
const PAGE = {
|
|
16
|
+
"/": { path: join(here, "web/index.html"), type: "text/html; charset=utf-8" },
|
|
17
|
+
"/app.js": { path: join(here, "web/app.js"), type: "text/javascript; charset=utf-8" },
|
|
18
|
+
"/graph": { path: join(here, "web/graph.html"), type: "text/html; charset=utf-8" },
|
|
19
|
+
"/graph.js": { path: join(here, "web/graph.js"), type: "text/javascript; charset=utf-8" },
|
|
20
|
+
};
|
|
21
|
+
/** A live observability dashboard for a space, served over HTTP + SSE. A read-only
|
|
22
|
+
* observer endpoint (invisible to peers) feeds the page presence, channel history,
|
|
23
|
+
* and a live message stream — no manager required. Bound to loopback. */
|
|
24
|
+
export async function web(args) {
|
|
25
|
+
const values = args.values;
|
|
26
|
+
// Resolve WHICH running mesh + creds (admin god-view: shows DMs + anycast), then DROP the account
|
|
27
|
+
// seed. The dashboard is a loopback HTTP process; holding the space signing seed (`auth` — it can
|
|
28
|
+
// mint ANY identity/role) for the whole session would make a dashboard compromise = full account
|
|
29
|
+
// control. Instead pre-mint ONE scoped `channel-purger` cred for the only write path (channel delete
|
|
30
|
+
// = filtered CHAT purge + a channel-registry key delete) and let the seed fall out of scope here, so
|
|
31
|
+
// it isn't reachable from the request handlers. `--creds` / open mode have no seed → the connection
|
|
32
|
+
// creds carry the purge rights.
|
|
33
|
+
//
|
|
34
|
+
// USER MODE: the god view rides an exchange-gated "admin" VIEW bearer (ledger scope "admin",
|
|
35
|
+
// fresh-checked at every mint and every connect) — standing via a bearer SOURCE so the tap
|
|
36
|
+
// survives the ≤5-minute token life. No pre-minted purge cred: channel delete mints a one-shot
|
|
37
|
+
// "channel-purger" view per action, so each destructive click is a fresh ledger check, and
|
|
38
|
+
// `cotal actor revoke` kills the dashboard live (eviction) while a scope edit bites at the next
|
|
39
|
+
// refresh.
|
|
40
|
+
const { conn, user } = await (async () => {
|
|
41
|
+
const conn = await connectOrExit(values, "admin");
|
|
42
|
+
return { conn, user: conn.bearer ? await userViewAuthOrExit(conn, "admin") : undefined };
|
|
43
|
+
})();
|
|
44
|
+
const { server, space } = conn;
|
|
45
|
+
const purgeCreds = !user && conn.auth ? await mintCreds(conn.auth, newIdentity(), "channel-purger") : conn.creds;
|
|
46
|
+
const port = values.port ? Number(values.port) : WEB_PORT;
|
|
47
|
+
// Observer: never registers presence, never consumes an inbox — invisible to peers.
|
|
48
|
+
const ep = new CotalEndpoint({
|
|
49
|
+
space,
|
|
50
|
+
servers: server,
|
|
51
|
+
...(user
|
|
52
|
+
? { bearer: user.source, sentinelCreds: user.sentinelCreds, card: { owner: user.owner, actor: user.actor, name: "web", kind: "endpoint" } }
|
|
53
|
+
: { creds: conn.creds, card: { name: "web", kind: "endpoint" } }),
|
|
54
|
+
channels: [],
|
|
55
|
+
consume: false, // observer: reads via tap + history + presence-watch, binds no durables
|
|
56
|
+
registerPresence: false,
|
|
57
|
+
watchPresence: true,
|
|
58
|
+
});
|
|
59
|
+
ep.on("error", (e) => console.error(c.red("! " + e.message)));
|
|
60
|
+
await ep.start();
|
|
61
|
+
const clients = new Set();
|
|
62
|
+
const send = (res, event, data) => res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
63
|
+
const broadcast = (event, data) => {
|
|
64
|
+
for (const res of clients)
|
|
65
|
+
if (!res.writableEnded)
|
|
66
|
+
send(res, event, data);
|
|
67
|
+
};
|
|
68
|
+
// Presence changes → push the whole roster; the client just re-renders it.
|
|
69
|
+
ep.on("presence", () => broadcast("roster", ep.getRoster()));
|
|
70
|
+
// Broker-sourced channel membership (the authoritative graph spokes): push a `membership` SSE event
|
|
71
|
+
// on every feed change (debounced; the client re-reads the snapshot). Best-effort — a space without the
|
|
72
|
+
// feed (no delivery daemon, or provisioned before this feature) simply never emits, and the graph
|
|
73
|
+
// degrades to traffic-only. The admin cred carries the read grant; agents never do.
|
|
74
|
+
let membershipWatch;
|
|
75
|
+
const pushMembership = debounce(() => {
|
|
76
|
+
void ep.readMembership().then((m) => broadcast("membership", m)).catch(() => { });
|
|
77
|
+
}, 150);
|
|
78
|
+
try {
|
|
79
|
+
membershipWatch = await ep.watchMembership(pushMembership);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
console.error(c.dim(`• membership feed unavailable - graph shows traffic only (${e.message})`));
|
|
83
|
+
}
|
|
84
|
+
// Every comm on the mesh (chat / unicast / anycast) → push to the live feed. The admin cred
|
|
85
|
+
// allows the whole space, so the observer taps everything — DMs + anycast included.
|
|
86
|
+
const tapSubject = spaceWildcard(space);
|
|
87
|
+
ep.tap((subject, msg) => {
|
|
88
|
+
const mode = deliveryOf(subject);
|
|
89
|
+
if (!mode || !msg)
|
|
90
|
+
return;
|
|
91
|
+
// senderId is the subject's sender token — the *verified* publisher (the server
|
|
92
|
+
// policed who could publish it), vs the advisory `from` in the payload.
|
|
93
|
+
const senderId = parseSubject(subject)?.sender;
|
|
94
|
+
broadcast("message", { mode, senderId, msg });
|
|
95
|
+
}, { subject: tapSubject });
|
|
96
|
+
const httpServer = createServer(async (req, res) => {
|
|
97
|
+
const path = (req.url ?? "/").split("?")[0];
|
|
98
|
+
const query = new URLSearchParams((req.url ?? "").split("?")[1] ?? "");
|
|
99
|
+
if (path === "/feed") {
|
|
100
|
+
res.writeHead(200, {
|
|
101
|
+
"content-type": "text/event-stream",
|
|
102
|
+
"cache-control": "no-cache",
|
|
103
|
+
connection: "keep-alive",
|
|
104
|
+
});
|
|
105
|
+
clients.add(res);
|
|
106
|
+
send(res, "roster", ep.getRoster());
|
|
107
|
+
// Seed this client's graph with the current membership snapshot (the live tap only carries
|
|
108
|
+
// post-connect traffic; membership is state, so a fresh client needs it explicitly).
|
|
109
|
+
void ep.readMembership().then((m) => { if (!res.writableEnded)
|
|
110
|
+
send(res, "membership", m); }).catch(() => { });
|
|
111
|
+
req.on("close", () => clients.delete(res));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (path === "/api/meta")
|
|
115
|
+
return json(res, { space });
|
|
116
|
+
if (path === "/api/roster")
|
|
117
|
+
return json(res, ep.getRoster());
|
|
118
|
+
if (path === "/api/membership") {
|
|
119
|
+
// Authoritative who-is-subscribed (broker-sourced); {asOf, members:[{id,live,durable,observedAt}]}.
|
|
120
|
+
// An unavailable feed returns an empty snapshot so the graph cleanly degrades to traffic-only.
|
|
121
|
+
try {
|
|
122
|
+
return json(res, await ep.readMembership());
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return json(res, { asOf: undefined, members: [] });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (path === "/api/channels")
|
|
129
|
+
return json(res, await ep.listChannels());
|
|
130
|
+
if (path === "/api/activity") {
|
|
131
|
+
// Backfill the all-activity feed: merge recent channel history with DM history (the live
|
|
132
|
+
// SSE tap only carries messages from after a client connects). Entries are mode-tagged
|
|
133
|
+
// ({mode, msg}) to match the live feed so DMs render as DMs.
|
|
134
|
+
const limit = query.get("limit") ? Number(query.get("limit")) : 200;
|
|
135
|
+
const chans = await ep.listChannels();
|
|
136
|
+
const chat = (await Promise.all(chans.map((ch) => ep.channelHistory(ch.channel, { limit }))))
|
|
137
|
+
.flat()
|
|
138
|
+
.map((msg) => ({ mode: "chat", msg }));
|
|
139
|
+
const dms = (await ep.dmHistory({ limit })).map((msg) => ({ mode: "unicast", msg }));
|
|
140
|
+
const all = [...chat, ...dms].sort((a, b) => a.msg.ts - b.msg.ts);
|
|
141
|
+
return json(res, all.slice(-limit));
|
|
142
|
+
}
|
|
143
|
+
if (path === "/api/dms") {
|
|
144
|
+
// DM history for the Direct-messages lens (god-view); the client groups it by peer/pair.
|
|
145
|
+
const limit = query.get("limit") ? Number(query.get("limit")) : 500;
|
|
146
|
+
return json(res, await ep.dmHistory({ limit }));
|
|
147
|
+
}
|
|
148
|
+
if (path.startsWith("/api/channels/") && path.endsWith("/history")) {
|
|
149
|
+
const name = decodeURIComponent(path.slice("/api/channels/".length, -"/history".length));
|
|
150
|
+
const limit = query.get("limit") ? Number(query.get("limit")) : 200;
|
|
151
|
+
return json(res, await ep.channelHistory(name, { limit }));
|
|
152
|
+
}
|
|
153
|
+
// Delete a channel and its content. The only write path on this otherwise read-only
|
|
154
|
+
// dashboard, so it's POST-gated and guarded by a confirm in the UI. Uses the manager cred
|
|
155
|
+
// pre-minted at startup (auth mode) or the connection creds (open / --creds), NOT the account
|
|
156
|
+
// seed (which we dropped). A wildcard / missing channel is a 400.
|
|
157
|
+
if (path === "/api/channel/delete" && req.method === "POST") {
|
|
158
|
+
const body = await readBody(req).catch(() => ({}));
|
|
159
|
+
const channel = typeof body.channel === "string" ? body.channel : "";
|
|
160
|
+
if (!channel) {
|
|
161
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
162
|
+
res.end(JSON.stringify({ error: "channel required" }));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
// User mode mints a one-shot channel-purger VIEW per delete — the ledger is re-checked at
|
|
167
|
+
// this click, and a mid-session revoke becomes this handler's 400, never a dead dashboard.
|
|
168
|
+
const result = user
|
|
169
|
+
? await userViewAuth(conn, "channel-purger").then((p) => clearChannel({ servers: server, space, channel, bearer: p.bearer, sentinelCreds: p.sentinelCreds }))
|
|
170
|
+
: await clearChannel({ servers: server, space, channel, creds: purgeCreds });
|
|
171
|
+
return json(res, { ok: true, ...result });
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
175
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const file = PAGE[path];
|
|
180
|
+
if (file) {
|
|
181
|
+
// no-cache: always revalidate so a `cotal` upgrade's new dashboard code is picked up on
|
|
182
|
+
// reload — a stale cached graph.js silently runs old behavior (e.g. pre-fix filters).
|
|
183
|
+
res.writeHead(200, { "content-type": file.type, "cache-control": "no-cache" });
|
|
184
|
+
res.end(readFileSync(file.path));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
res.writeHead(404).end("not found");
|
|
188
|
+
});
|
|
189
|
+
// Comment ping keeps idle SSE connections alive through proxies.
|
|
190
|
+
const ping = setInterval(() => {
|
|
191
|
+
for (const res of clients)
|
|
192
|
+
if (!res.writableEnded)
|
|
193
|
+
res.write(": ping\n\n");
|
|
194
|
+
}, 20_000);
|
|
195
|
+
httpServer.on("error", (e) => {
|
|
196
|
+
if (e.code === "EADDRINUSE")
|
|
197
|
+
console.error(c.red(`Port ${port} is in use. Pass --port <n>.`));
|
|
198
|
+
else
|
|
199
|
+
console.error(c.red("! " + e.message));
|
|
200
|
+
process.exit(1);
|
|
201
|
+
});
|
|
202
|
+
await new Promise((ready) => httpServer.listen(port, "127.0.0.1", ready));
|
|
203
|
+
// Branded URL only when on the default port; a custom --port keeps the plain loopback address.
|
|
204
|
+
const url = port === WEB_PORT ? WEB_URL : `http://127.0.0.1:${port}/`;
|
|
205
|
+
console.log(`${c.bold("Cotal web")} - observing space ${c.bold(space)}`);
|
|
206
|
+
console.log(c.dim(" god-view - DMs + anycast visible"));
|
|
207
|
+
console.log(` ${c.cyan(url)} ${c.dim("(Ctrl-C to stop)")}`);
|
|
208
|
+
if (!values["no-open"])
|
|
209
|
+
openBrowser(url);
|
|
210
|
+
const shutdown = async () => {
|
|
211
|
+
clearInterval(ping);
|
|
212
|
+
membershipWatch?.stop();
|
|
213
|
+
for (const res of clients)
|
|
214
|
+
res.end();
|
|
215
|
+
httpServer.close();
|
|
216
|
+
await ep.stop();
|
|
217
|
+
process.exit(0);
|
|
218
|
+
};
|
|
219
|
+
process.on("SIGINT", () => void shutdown());
|
|
220
|
+
process.on("SIGTERM", () => void shutdown());
|
|
221
|
+
await new Promise(() => { });
|
|
222
|
+
}
|
|
223
|
+
function json(res, data) {
|
|
224
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
225
|
+
res.end(JSON.stringify(data));
|
|
226
|
+
}
|
|
227
|
+
/** Trailing-edge debounce — coalesces a burst of membership-feed deltas into one push. */
|
|
228
|
+
function debounce(fn, ms) {
|
|
229
|
+
let t;
|
|
230
|
+
return () => {
|
|
231
|
+
if (t)
|
|
232
|
+
clearTimeout(t);
|
|
233
|
+
t = setTimeout(fn, ms);
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
async function readBody(req) {
|
|
237
|
+
const chunks = [];
|
|
238
|
+
for await (const chunk of req)
|
|
239
|
+
chunks.push(chunk);
|
|
240
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
241
|
+
return raw ? JSON.parse(raw) : {};
|
|
242
|
+
}
|
|
243
|
+
/** Best-effort open of the dashboard in the default browser. The URL is already
|
|
244
|
+
* printed, so a failure here is harmless — never block startup on it. */
|
|
245
|
+
function openBrowser(url) {
|
|
246
|
+
const [cmd, args] = process.platform === "darwin"
|
|
247
|
+
? ["open", [url]]
|
|
248
|
+
: process.platform === "win32"
|
|
249
|
+
? ["cmd", ["/c", "start", "", url]]
|
|
250
|
+
: ["xdg-open", [url]];
|
|
251
|
+
try {
|
|
252
|
+
spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
/* no opener on this platform — the printed URL is the fallback */
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=web.js.map
|
package/dist/web.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"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"}
|