claude-rpc 0.7.2 → 0.7.4
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/README.md +4 -0
- package/SECURITY.md +169 -0
- package/package.json +3 -2
- package/src/doctor.js +21 -10
- package/src/privacy.js +62 -2
- package/src/server/api.js +35 -0
- package/src/server/assets/dashboard.client.js +647 -0
- package/src/server/assets/dashboard.css +326 -0
- package/src/server/assets/dashboard.html +276 -0
- package/src/server/assets.js +36 -0
- package/src/server/page.js +24 -1248
- package/src/server/routes.js +24 -1
- package/src/version.js +1 -1
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--paper: #f4ede0;
|
|
3
|
+
--paper-dark: #ebe2d2;
|
|
4
|
+
--ink: #1a1611;
|
|
5
|
+
--ink-soft: #2d2520;
|
|
6
|
+
--ink-mute: #5c5147;
|
|
7
|
+
--ink-faint: #8a7c6d;
|
|
8
|
+
|
|
9
|
+
--rust: #c2491e;
|
|
10
|
+
--tape: #f2d76e;
|
|
11
|
+
--grass: #4a9462;
|
|
12
|
+
|
|
13
|
+
--font-sans: 'Inter', system-ui, sans-serif;
|
|
14
|
+
--font-mono: 'JetBrains Mono', monospace;
|
|
15
|
+
--font-disp: 'Space Grotesk', sans-serif;
|
|
16
|
+
|
|
17
|
+
--shadow: 3px 3px 0 var(--ink);
|
|
18
|
+
--shadow-hover: 5px 5px 0 var(--ink);
|
|
19
|
+
--radius: 0;
|
|
20
|
+
|
|
21
|
+
/* Fallbacks for existing variables used by page.js */
|
|
22
|
+
--bg: var(--paper);
|
|
23
|
+
--text: var(--ink);
|
|
24
|
+
--text-2: var(--ink-soft);
|
|
25
|
+
--text-3: var(--ink-mute);
|
|
26
|
+
--text-4: var(--ink-faint);
|
|
27
|
+
--border: var(--ink);
|
|
28
|
+
--surface: #fff;
|
|
29
|
+
--surface-hover: var(--paper);
|
|
30
|
+
--green: var(--grass);
|
|
31
|
+
--red: #c54a3a;
|
|
32
|
+
--purple: #5865f2; /* using blurple */
|
|
33
|
+
--amber: var(--tape);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
37
|
+
::selection { background: rgba(26,22,17,0.16); }
|
|
38
|
+
html, body { background: var(--paper); color: var(--ink); }
|
|
39
|
+
body {
|
|
40
|
+
font-family: var(--font-sans);
|
|
41
|
+
font-size: 14px; line-height: 1.5;
|
|
42
|
+
min-height: 100vh;
|
|
43
|
+
background-image: radial-gradient(circle at 1px 1px, rgba(26,22,17,0.07) 1px, transparent 0);
|
|
44
|
+
background-size: 20px 20px;
|
|
45
|
+
}
|
|
46
|
+
.num { font-variant-numeric: tabular-nums; }
|
|
47
|
+
a { color: inherit; text-decoration: none; }
|
|
48
|
+
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
|
|
49
|
+
h1, h2, h3 { font-family: var(--font-disp); }
|
|
50
|
+
|
|
51
|
+
.page { max-width: 1000px; margin: 0 auto; padding: 40px 40px 100px; }
|
|
52
|
+
|
|
53
|
+
/* ── Top bar ─────────────────────────────────────────── */
|
|
54
|
+
.topbar {
|
|
55
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
56
|
+
padding-bottom: 24px;
|
|
57
|
+
border-bottom: 2px dashed var(--ink-mute);
|
|
58
|
+
margin-bottom: 40px;
|
|
59
|
+
}
|
|
60
|
+
.brand {
|
|
61
|
+
display: flex; align-items: center; gap: 12px;
|
|
62
|
+
font-family: var(--font-disp);
|
|
63
|
+
font-weight: 700; font-size: 24px;
|
|
64
|
+
}
|
|
65
|
+
.brand .mark {
|
|
66
|
+
width: 32px; height: 32px;
|
|
67
|
+
background: var(--ink); color: var(--paper);
|
|
68
|
+
font-family: var(--font-mono); font-size: 14px;
|
|
69
|
+
display: grid; place-items: center;
|
|
70
|
+
border: 2px solid var(--ink);
|
|
71
|
+
box-shadow: 2px 2px 0 var(--rust);
|
|
72
|
+
transform: rotate(-3deg);
|
|
73
|
+
}
|
|
74
|
+
.brand .sep { display: none; }
|
|
75
|
+
.brand .meta { display: none; }
|
|
76
|
+
.top-right { margin-left: auto; display: flex; align-items: center; gap: 16px; }
|
|
77
|
+
|
|
78
|
+
.range-pills {
|
|
79
|
+
display: inline-flex; gap: 4px;
|
|
80
|
+
}
|
|
81
|
+
.range-pills button {
|
|
82
|
+
font-family: var(--font-mono);
|
|
83
|
+
font-size: 12px; padding: 6px 12px; font-weight: 700;
|
|
84
|
+
border: 2px solid transparent; text-transform: uppercase;
|
|
85
|
+
transition: transform 0.12s;
|
|
86
|
+
}
|
|
87
|
+
.range-pills button:hover { transform: translateY(-2px); }
|
|
88
|
+
.range-pills button.active {
|
|
89
|
+
background: var(--tape);
|
|
90
|
+
border-color: var(--ink);
|
|
91
|
+
box-shadow: 2px 2px 0 var(--ink);
|
|
92
|
+
transform: rotate(2deg);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.status {
|
|
96
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
97
|
+
font-family: var(--font-mono);
|
|
98
|
+
font-size: 12px; font-weight: 700; text-transform: uppercase;
|
|
99
|
+
background: var(--grass); color: var(--paper);
|
|
100
|
+
padding: 6px 12px; border: 2px solid var(--ink); box-shadow: 2px 2px 0 var(--ink);
|
|
101
|
+
transform: rotate(-2deg);
|
|
102
|
+
}
|
|
103
|
+
.status .dot { display: none; }
|
|
104
|
+
.theme-btn { display: none; } /* Hide theme toggle, we only use paper now */
|
|
105
|
+
.model { font-family: var(--font-mono); font-size: 12px; font-weight: 700; background: #fff; padding: 4px 8px; border: 2px solid var(--ink); box-shadow: 2px 2px 0 var(--ink); }
|
|
106
|
+
|
|
107
|
+
/* ── Live rail ───────────────────────────────────────── */
|
|
108
|
+
.live-rail {
|
|
109
|
+
display: grid; grid-template-columns: 80px 1fr auto; gap: 24px; align-items: center;
|
|
110
|
+
background: #fff; padding: 24px;
|
|
111
|
+
border: 2px solid var(--ink); box-shadow: var(--shadow);
|
|
112
|
+
margin-bottom: 48px; position: relative;
|
|
113
|
+
transform: rotate(-0.5deg); transition: transform 0.2s, box-shadow 0.2s;
|
|
114
|
+
}
|
|
115
|
+
.live-rail:hover { transform: rotate(0deg) translate(-2px, -2px); box-shadow: var(--shadow-hover); }
|
|
116
|
+
.live-rail::before {
|
|
117
|
+
content: "LIVE PREVIEW"; position: absolute; top: -12px; right: 24px;
|
|
118
|
+
background: var(--tape); font-family: var(--font-mono); font-weight: 700; font-size: 11px;
|
|
119
|
+
padding: 4px 10px; border: 2px solid var(--ink); box-shadow: 2px 2px 0 var(--ink); transform: rotate(3deg);
|
|
120
|
+
}
|
|
121
|
+
.live-rail .avatar {
|
|
122
|
+
width: 80px; height: 80px; background: var(--paper-dark);
|
|
123
|
+
border: 2px solid var(--ink); box-shadow: 2px 2px 0 var(--ink) inset; overflow: hidden;
|
|
124
|
+
}
|
|
125
|
+
.live-rail .avatar img { width: 100%; height: 100%; object-fit: cover; }
|
|
126
|
+
.live-rail .frame-app { display: none; }
|
|
127
|
+
.live-rail .frame-details { font-family: var(--font-disp); font-size: 22px; font-weight: 700; margin-bottom: 4px; }
|
|
128
|
+
.live-rail .frame-state { font-family: var(--font-mono); font-size: 13px; color: var(--ink-mute); }
|
|
129
|
+
.live-rail .right { text-align: right; border-left: 2px dashed var(--ink); padding-left: 24px; }
|
|
130
|
+
.live-rail .right .frame-num { font-family: var(--font-mono); font-size: 11px; font-weight: 700; color: var(--ink-mute); margin-bottom: 8px; text-transform: uppercase; }
|
|
131
|
+
.live-rail .right .elapsed { font-family: var(--font-mono); font-size: 24px; font-weight: 700; }
|
|
132
|
+
|
|
133
|
+
/* ── Hero ────────────────────────────────────────────── */
|
|
134
|
+
.hero {
|
|
135
|
+
display: grid; grid-template-columns: 1fr 1.4fr; gap: 56px;
|
|
136
|
+
align-items: end; margin-bottom: 48px;
|
|
137
|
+
}
|
|
138
|
+
.hero .eyebrow {
|
|
139
|
+
font-family: var(--font-mono); font-size: 12px; font-weight: 700; color: var(--ink-mute);
|
|
140
|
+
text-transform: uppercase; margin-bottom: 12px;
|
|
141
|
+
border-bottom: 2px solid var(--rust); display: inline-block; padding-bottom: 4px;
|
|
142
|
+
}
|
|
143
|
+
.hero .figure { font-family: var(--font-disp); font-size: 86px; font-weight: 700; line-height: 0.92; letter-spacing: -0.05em; }
|
|
144
|
+
.hero .unit { font-family: var(--font-mono); font-size: 24px; color: var(--ink-mute); margin-left: 10px; font-weight: 700; }
|
|
145
|
+
.hero .caption { margin-top: 20px; font-family: var(--font-mono); font-size: 13px; color: var(--ink-soft); max-width: 380px; }
|
|
146
|
+
|
|
147
|
+
.chart-block .chart-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 12px; font-family: var(--font-mono); font-weight: 700; }
|
|
148
|
+
.chart-block .chart-title { font-size: 12px; color: var(--ink-mute); text-transform: uppercase; }
|
|
149
|
+
.chart-block .chart-side { font-size: 12px; color: var(--ink-mute); }
|
|
150
|
+
.chart-block .chart-side strong { color: var(--ink); }
|
|
151
|
+
.chart-wrap { position: relative; height: 130px; background: #fff; border: 2px solid var(--ink); box-shadow: var(--shadow); padding: 12px; }
|
|
152
|
+
svg.chart { width: 100%; height: 100%; overflow: visible; }
|
|
153
|
+
svg.chart .grid { stroke: var(--ink-faint); stroke-width: 1; stroke-dasharray: 2 2; }
|
|
154
|
+
svg.chart .area { fill: url(#whiteGrad); }
|
|
155
|
+
svg.chart .line { fill: none; stroke: var(--rust); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
|
|
156
|
+
svg.chart .dot { fill: var(--rust); stroke: #fff; stroke-width: 2px; }
|
|
157
|
+
svg.chart .ax { fill: var(--ink-mute); font-size: 10px; font-family: var(--font-mono); font-weight: 700; }
|
|
158
|
+
.chart-wrap, .churn-spark { cursor: crosshair; }
|
|
159
|
+
|
|
160
|
+
/* Cursor-following hover tooltip shared by the activity chart + churn spark. */
|
|
161
|
+
.chart-tip {
|
|
162
|
+
position: fixed; z-index: 80; pointer-events: none;
|
|
163
|
+
background: var(--ink); color: var(--paper);
|
|
164
|
+
font-family: var(--font-mono); font-size: 11px; font-weight: 700;
|
|
165
|
+
padding: 4px 8px; border: 2px solid var(--ink); box-shadow: 2px 2px 0 var(--rust);
|
|
166
|
+
white-space: nowrap; opacity: 0; transition: opacity 0.1s;
|
|
167
|
+
}
|
|
168
|
+
.chart-tip.show { opacity: 1; }
|
|
169
|
+
|
|
170
|
+
/* ── Stat cards ──────────────────────────────────────── */
|
|
171
|
+
.stat-row {
|
|
172
|
+
display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px;
|
|
173
|
+
margin-bottom: 48px;
|
|
174
|
+
}
|
|
175
|
+
.stat-card {
|
|
176
|
+
background: #fff; border: 2px solid var(--ink); padding: 24px;
|
|
177
|
+
box-shadow: var(--shadow); position: relative;
|
|
178
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
179
|
+
}
|
|
180
|
+
.stat-card:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-hover); }
|
|
181
|
+
.stat-card::before {
|
|
182
|
+
content: ''; position: absolute; top: -8px; left: 50%; transform: translateX(-50%);
|
|
183
|
+
width: 40px; height: 16px; background: rgba(0,0,0,0.05); /* tape piece effect */
|
|
184
|
+
}
|
|
185
|
+
.stat-card .label { font-family: var(--font-mono); font-size: 11px; font-weight: 700; color: var(--ink-mute); text-transform: uppercase; border-bottom: 2px solid var(--rust); display: inline-block; padding-bottom: 4px; }
|
|
186
|
+
.stat-card .value { margin-top: 16px; display: flex; align-items: baseline; gap: 6px; font-family: var(--font-disp); font-size: 36px; font-weight: 700; line-height: 1; }
|
|
187
|
+
.stat-card .value .unit { font-family: var(--font-mono); font-size: 16px; color: var(--ink-mute); font-weight: 700; }
|
|
188
|
+
.stat-card .meta { margin-top: 12px; font-family: var(--font-mono); font-size: 12px; color: var(--ink-mute); font-weight: 500; }
|
|
189
|
+
.delta { font-weight: 700; }
|
|
190
|
+
.delta.up { color: var(--grass); }
|
|
191
|
+
.delta.down { color: var(--rust); }
|
|
192
|
+
.delta.flat { color: var(--ink-mute); }
|
|
193
|
+
|
|
194
|
+
/* ── Split: cost + languages ─────────────────────── */
|
|
195
|
+
.split-row { display: grid; grid-template-columns: 1.2fr 1fr; gap: 24px; margin-bottom: 48px; }
|
|
196
|
+
.card { background: #fff; border: 2px solid var(--ink); padding: 24px; box-shadow: var(--shadow); position: relative; }
|
|
197
|
+
.card-h { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 20px; font-family: var(--font-mono); font-weight: 700; }
|
|
198
|
+
.card-h h3 { font-size: 14px; text-transform: uppercase; border-bottom: 2px solid var(--rust); display: inline-block; padding-bottom: 4px; }
|
|
199
|
+
.card-h .meta { font-size: 11px; color: var(--ink-mute); }
|
|
200
|
+
|
|
201
|
+
.cost-grid { display: grid; grid-template-columns: 1.2fr 1fr; gap: 24px; align-items: center; }
|
|
202
|
+
.cost-figure { font-family: var(--font-disp); font-size: 48px; font-weight: 700; line-height: 1; }
|
|
203
|
+
.cost-sub { font-family: var(--font-mono); color: var(--ink-mute); font-size: 12px; margin-top: 8px; font-weight: 700; }
|
|
204
|
+
.cost-bars { display: grid; gap: 12px; font-family: var(--font-mono); font-size: 12px; font-weight: 700; }
|
|
205
|
+
.cost-bar { display: grid; grid-template-columns: 60px 1fr auto; gap: 8px; align-items: center; }
|
|
206
|
+
.cost-bar .name { color: var(--ink-soft); }
|
|
207
|
+
.cost-bar .track { height: 8px; background: var(--paper-dark); border: 1.5px solid var(--ink); overflow: hidden; }
|
|
208
|
+
.cost-bar .fill { height: 100%; background: var(--purple); border-right: 1.5px solid var(--ink); }
|
|
209
|
+
.cost-bar .val { color: var(--ink); }
|
|
210
|
+
|
|
211
|
+
.lang-stack { display: flex; height: 16px; background: var(--paper-dark); border: 2px solid var(--ink); margin-bottom: 20px; box-shadow: 2px 2px 0 var(--ink); }
|
|
212
|
+
.lang-stack > span { display: block; border-right: 1.5px solid var(--ink); }
|
|
213
|
+
.lang-stack > span:last-child { border-right: none; }
|
|
214
|
+
.lang-list { display: grid; gap: 8px; font-family: var(--font-mono); font-size: 12px; font-weight: 700; }
|
|
215
|
+
.lang-list .row { display: grid; grid-template-columns: 14px 1fr auto; gap: 10px; align-items: center; }
|
|
216
|
+
.lang-list .swatch { width: 14px; height: 14px; border: 2px solid var(--ink); }
|
|
217
|
+
.lang-list .val { color: var(--ink-mute); }
|
|
218
|
+
|
|
219
|
+
/* ── Code churn ──────────────────────────────────── */
|
|
220
|
+
.churn-row { display: grid; grid-template-columns: 1fr 200px; gap: 32px; align-items: center; }
|
|
221
|
+
.churn-spark svg { width: 100%; height: 60px; display: block; overflow: visible; }
|
|
222
|
+
.churn-spark .add { fill: var(--grass); stroke: var(--ink); stroke-width: 1.5; }
|
|
223
|
+
.churn-spark .rem { fill: var(--rust); stroke: var(--ink); stroke-width: 1.5; }
|
|
224
|
+
.churn-numbers { display: grid; gap: 8px; font-family: var(--font-mono); font-size: 13px; font-weight: 700; }
|
|
225
|
+
.churn-numbers .row { display: flex; justify-content: space-between; padding-bottom: 4px; border-bottom: 1.5px dashed var(--ink-faint); }
|
|
226
|
+
.churn-numbers .row:last-child { border-bottom: none; }
|
|
227
|
+
.churn-numbers .label { color: var(--ink-mute); }
|
|
228
|
+
.churn-numbers .added { color: var(--grass); }
|
|
229
|
+
.churn-numbers .removed { color: var(--rust); }
|
|
230
|
+
.churn-numbers .net { color: var(--ink); }
|
|
231
|
+
|
|
232
|
+
/* ── Section heading ─────────────────────────────────── */
|
|
233
|
+
.section-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 20px; font-family: var(--font-mono); font-weight: 700; }
|
|
234
|
+
.section-head h2 { font-size: 16px; text-transform: uppercase; background: var(--tape); display: inline-block; padding: 4px 12px; border: 2px solid var(--ink); box-shadow: 2px 2px 0 var(--ink); transform: rotate(-1deg); }
|
|
235
|
+
.section-head .right { font-size: 12px; color: var(--ink-mute); }
|
|
236
|
+
section { margin-bottom: 48px; }
|
|
237
|
+
|
|
238
|
+
/* ── Leaderboards ────────────────────────────────────── */
|
|
239
|
+
.lb-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
|
|
240
|
+
.lb { background: #fff; border: 2px solid var(--ink); box-shadow: var(--shadow); position: relative; }
|
|
241
|
+
.lb-h { display: flex; justify-content: space-between; align-items: baseline; padding: 16px 20px 12px; font-family: var(--font-mono); font-weight: 700; border-bottom: 2px solid var(--ink); background: var(--paper-dark); }
|
|
242
|
+
.lb-h .t { font-size: 13px; text-transform: uppercase; }
|
|
243
|
+
.lb-h .s { font-size: 11px; color: var(--ink-mute); }
|
|
244
|
+
.lb table { width: 100%; border-collapse: collapse; font-family: var(--font-mono); font-weight: 600; font-size: 12px; }
|
|
245
|
+
.lb td { padding: 10px 20px; border-bottom: 1.5px dashed var(--ink-faint); }
|
|
246
|
+
.lb tr:last-child td { border-bottom: none; }
|
|
247
|
+
.lb tr:hover td { background: var(--paper); }
|
|
248
|
+
.lb td.name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 1px; color: var(--ink); }
|
|
249
|
+
.lb td.val { color: var(--ink-soft); text-align: right; white-space: nowrap; }
|
|
250
|
+
.lb td.name .ico { width: 12px; height: 12px; border: 1.5px solid var(--ink); margin-right: 8px; vertical-align: -2px; }
|
|
251
|
+
|
|
252
|
+
/* ── Discord card ────────────────────────────────────── */
|
|
253
|
+
.discord { background: #fff; border: 2px solid var(--ink); padding: 32px; box-shadow: var(--shadow); position: relative; }
|
|
254
|
+
.discord-h { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 24px; font-family: var(--font-mono); font-weight: 700; }
|
|
255
|
+
.discord-h .t { font-size: 14px; text-transform: uppercase; border-bottom: 2px solid var(--rust); padding-bottom: 4px; }
|
|
256
|
+
.live-frame { padding: 24px 0; border-top: 2px solid var(--ink); border-bottom: 2px dashed var(--ink); }
|
|
257
|
+
.live-frame .label-tag { font-family: var(--font-mono); font-size: 11px; font-weight: 700; background: var(--grass); color: #fff; padding: 4px 8px; border: 1.5px solid var(--ink); box-shadow: 2px 2px 0 var(--ink); display: inline-block; margin-bottom: 12px; transform: rotate(-2deg); }
|
|
258
|
+
.live-frame .label-tag::before { display: none; }
|
|
259
|
+
.live-frame .details { font-family: var(--font-disp); font-size: 24px; font-weight: 700; margin-bottom: 4px; }
|
|
260
|
+
.live-frame .state { font-family: var(--font-mono); font-size: 13px; color: var(--ink-soft); font-weight: 600; }
|
|
261
|
+
.rotation-list { list-style: none; margin-top: 24px; display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px 24px; font-family: var(--font-mono); font-size: 12px; font-weight: 700; }
|
|
262
|
+
.rotation-list li { display: flex; align-items: center; gap: 10px; color: var(--ink-soft); }
|
|
263
|
+
.rotation-list li .pip { width: 8px; height: 8px; background: var(--paper-dark); border: 1.5px solid var(--ink); flex-shrink: 0; }
|
|
264
|
+
.rotation-list li.live .pip { background: var(--grass); }
|
|
265
|
+
.rotation-list li.current { color: var(--ink); }
|
|
266
|
+
.rotation-list li.current .pip { background: var(--ink); box-shadow: 2px 2px 0 var(--ink); }
|
|
267
|
+
|
|
268
|
+
/* ── Achievements, Insights, Modals ─────────────────── */
|
|
269
|
+
.achievements { display: grid; grid-template-columns: repeat(6, 1fr); gap: 16px; margin-bottom: 48px; }
|
|
270
|
+
.achievement { background: #fff; border: 2px dashed var(--ink-faint); padding: 16px; text-align: center; opacity: 0.5; transition: all 0.2s; }
|
|
271
|
+
.achievement.unlocked { opacity: 1; border: 2px solid var(--ink); box-shadow: var(--shadow); transform: rotate(1deg); }
|
|
272
|
+
.achievement .ico { font-size: 24px; margin-bottom: 8px; display: block; }
|
|
273
|
+
.achievement .t { font-family: var(--font-mono); font-size: 12px; font-weight: 700; text-transform: uppercase; margin-bottom: 4px; }
|
|
274
|
+
.achievement .s { font-size: 11px; color: var(--ink-mute); }
|
|
275
|
+
|
|
276
|
+
.insights { background: #fff; border: 2px solid var(--ink); padding: 24px; margin-bottom: 48px; box-shadow: var(--shadow); display: grid; gap: 12px; }
|
|
277
|
+
.insights .insight { display: flex; align-items: baseline; gap: 12px; font-family: var(--font-mono); font-size: 13px; font-weight: 600; }
|
|
278
|
+
.insights .insight::before { content: '→'; color: var(--rust); font-weight: 700; }
|
|
279
|
+
|
|
280
|
+
.scrim { position: fixed; inset: 0; background: rgba(26,22,17,0.4); backdrop-filter: blur(2px); display: none; z-index: 50; }
|
|
281
|
+
.scrim.open { display: block; }
|
|
282
|
+
.drawer { position: fixed; top: 0; right: 0; bottom: 0; width: 480px; max-width: 100%; background: var(--paper); border-left: 2px solid var(--ink); transform: translateX(100%); transition: transform 0.2s ease; z-index: 60; padding: 40px; overflow-y: auto; box-shadow: -10px 0 30px rgba(0,0,0,0.1); }
|
|
283
|
+
.drawer.open { transform: translateX(0); }
|
|
284
|
+
.drawer h3 { font-family: var(--font-disp); font-size: 28px; margin-bottom: 8px; }
|
|
285
|
+
.drawer .sub { font-family: var(--font-mono); color: var(--ink-mute); font-weight: 700; margin-bottom: 32px; }
|
|
286
|
+
|
|
287
|
+
.modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.96); background: var(--paper); border: 2px solid var(--ink); padding: 40px; box-shadow: 10px 10px 0 var(--ink); z-index: 60; opacity: 0; pointer-events: none; transition: all 0.2s; }
|
|
288
|
+
.modal.open { opacity: 1; transform: translate(-50%, -50%) scale(1); pointer-events: auto; }
|
|
289
|
+
.modal h4 { font-family: var(--font-disp); font-size: 24px; margin-bottom: 8px; }
|
|
290
|
+
|
|
291
|
+
footer { margin-top: 60px; padding-top: 32px; border-top: 2px dashed var(--ink); display: flex; justify-content: space-between; align-items: center; font-family: var(--font-mono); font-weight: 700; font-size: 12px; color: var(--ink-mute); }
|
|
292
|
+
footer .pulse-dot { background: var(--grass); border: 1.5px solid var(--ink); }
|
|
293
|
+
|
|
294
|
+
/* ── Heatmap ─────────────────────────────────────────── */
|
|
295
|
+
.heatmap-card { padding: 20px 22px; }
|
|
296
|
+
.heatmap { display: grid; grid-template-columns: 20px 1fr; gap: 6px; }
|
|
297
|
+
.heatmap .day-labels { display: grid; grid-template-rows: repeat(7, 12px); gap: 3px; font-size: 9px; color: var(--text-3); padding-top: 14px; }
|
|
298
|
+
.heatmap .grid {
|
|
299
|
+
display: grid; grid-auto-flow: column; grid-template-rows: repeat(7, 12px); gap: 3px;
|
|
300
|
+
font-size: 0;
|
|
301
|
+
}
|
|
302
|
+
.heatmap .cell { width: 12px; height: 12px; border-radius: 2px; background: rgba(0,0,0,0.06); cursor: pointer; transition: transform 0.1s; }
|
|
303
|
+
.heatmap .cell:hover { transform: scale(1.4); outline: 1px solid var(--text); }
|
|
304
|
+
|
|
305
|
+
/* ── Help overlay ────────────────────────────────────── */
|
|
306
|
+
.help { position: fixed; inset: 0; background: rgba(26,22,17,0.7); display: none; z-index: 70; align-items: center; justify-content: center; }
|
|
307
|
+
.help.open { display: flex; }
|
|
308
|
+
.help-card { background: var(--paper); border: 2px solid var(--ink); box-shadow: var(--shadow); padding: 28px 32px; max-width: 420px; width: 90%; }
|
|
309
|
+
.help-card h4 { font-family: var(--font-disp); font-size: 20px; margin-bottom: 16px; }
|
|
310
|
+
.help-card .kbd { display: inline-block; padding: 2px 6px; border: 1.5px solid var(--ink); background: #fff; font-size: 11px; font-family: var(--font-mono); margin-right: 8px; font-weight: 700; box-shadow: 1.5px 1.5px 0 var(--ink); }
|
|
311
|
+
.help-card .row { display: flex; padding: 8px 0; border-top: 1.5px dashed var(--ink-faint); font-family: var(--font-mono); font-size: 12px; font-weight: 700; color: var(--ink-soft); }
|
|
312
|
+
.help-card .row:first-of-type { border-top: 0; }
|
|
313
|
+
.help-card .keys { width: 110px; }
|
|
314
|
+
|
|
315
|
+
/* ── Responsive ──────────────────────────────────────── */
|
|
316
|
+
@media (max-width: 1100px) {
|
|
317
|
+
.stat-row { grid-template-columns: repeat(2, 1fr); }
|
|
318
|
+
.split-row { grid-template-columns: 1fr; }
|
|
319
|
+
.achievements { grid-template-columns: repeat(3, 1fr); }
|
|
320
|
+
}
|
|
321
|
+
@media (max-width: 760px) {
|
|
322
|
+
.hero { grid-template-columns: 1fr; gap: 28px; }
|
|
323
|
+
.lb-grid { grid-template-columns: 1fr; }
|
|
324
|
+
.rotation-list { grid-template-columns: 1fr; }
|
|
325
|
+
.drawer { width: 100%; }
|
|
326
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
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>Claude</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
{{STYLES}}</style>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<main class="page">
|
|
15
|
+
|
|
16
|
+
<!-- ── Top bar ─────────────────────────────────────── -->
|
|
17
|
+
<header class="topbar">
|
|
18
|
+
<div class="brand">
|
|
19
|
+
<span class="mark">◆</span>
|
|
20
|
+
<span>Claude</span>
|
|
21
|
+
<span class="sep">·</span>
|
|
22
|
+
<span class="meta" id="meta">—</span>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="top-right">
|
|
25
|
+
<div class="range-pills" id="range-pills">
|
|
26
|
+
<button data-range="7d">7d</button>
|
|
27
|
+
<button data-range="30d">30d</button>
|
|
28
|
+
<button data-range="90d" class="active">90d</button>
|
|
29
|
+
<button data-range="1y">1y</button>
|
|
30
|
+
<button data-range="all">All</button>
|
|
31
|
+
</div>
|
|
32
|
+
<button class="theme-btn" id="theme-btn" title="Toggle theme">◐</button>
|
|
33
|
+
<span class="model" id="model">—</span>
|
|
34
|
+
<span class="status"><span class="dot" id="dot"></span><span id="statustext">—</span></span>
|
|
35
|
+
</div>
|
|
36
|
+
</header>
|
|
37
|
+
|
|
38
|
+
<!-- ── Live rail ───────────────────────────────────── -->
|
|
39
|
+
<section class="live-rail" id="live-rail">
|
|
40
|
+
<div class="avatar" id="live-avatar"></div>
|
|
41
|
+
<div>
|
|
42
|
+
<div class="frame-app">Claude Code</div>
|
|
43
|
+
<div class="frame-details" id="frame-details">—</div>
|
|
44
|
+
<div class="frame-state" id="frame-state">—</div>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="right">
|
|
47
|
+
<div class="frame-num" id="frame-num">—</div>
|
|
48
|
+
<div class="elapsed" id="elapsed">—</div>
|
|
49
|
+
</div>
|
|
50
|
+
</section>
|
|
51
|
+
|
|
52
|
+
<!-- ── Insights ────────────────────────────────────── -->
|
|
53
|
+
<section class="insights" id="insights"><div class="insight">Loading…</div></section>
|
|
54
|
+
|
|
55
|
+
<!-- ── Hero ────────────────────────────────────────── -->
|
|
56
|
+
<section class="hero">
|
|
57
|
+
<div>
|
|
58
|
+
<div class="eyebrow">Active time</div>
|
|
59
|
+
<div><span class="figure" id="hero-num">—</span><span class="unit" id="hero-unit">hours</span></div>
|
|
60
|
+
<div class="caption" id="hero-caption">—</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="chart-block">
|
|
63
|
+
<div class="chart-head">
|
|
64
|
+
<span class="chart-title" id="chart-title">Last 90 days</span>
|
|
65
|
+
<span class="chart-side"><strong id="chart-total">—</strong> <span style="color: var(--text-4); margin: 0 6px;">·</span> peak <strong id="chart-peak">—</strong></span>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="chart-wrap">
|
|
68
|
+
<svg id="chart" class="chart" viewBox="0 0 800 130" preserveAspectRatio="none">
|
|
69
|
+
<defs>
|
|
70
|
+
<linearGradient id="whiteGrad" x1="0" y1="0" x2="0" y2="1">
|
|
71
|
+
<stop offset="0%" stop-color="var(--text)" stop-opacity="0.14"/>
|
|
72
|
+
<stop offset="100%" stop-color="var(--text)" stop-opacity="0"/>
|
|
73
|
+
</linearGradient>
|
|
74
|
+
</defs>
|
|
75
|
+
</svg>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</section>
|
|
79
|
+
|
|
80
|
+
<!-- ── Stat row ────────────────────────────────────── -->
|
|
81
|
+
<section class="stat-row">
|
|
82
|
+
<div class="stat-card">
|
|
83
|
+
<div class="label">Today</div>
|
|
84
|
+
<div class="value"><span id="today-num">—</span><span class="unit" id="today-unit">hrs</span></div>
|
|
85
|
+
<div class="meta"><span class="delta" id="today-delta">—</span> <span id="today-sub" class="num">—</span></div>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="stat-card">
|
|
88
|
+
<div class="label">This range</div>
|
|
89
|
+
<div class="value"><span id="range-num">—</span><span class="unit" id="range-unit">hrs</span></div>
|
|
90
|
+
<div class="meta"><span class="delta" id="range-delta">—</span> <span id="range-sub" class="num">—</span></div>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="stat-card">
|
|
93
|
+
<div class="label">Streak</div>
|
|
94
|
+
<div class="value"><span id="streak-num">—</span><span class="unit">days</span></div>
|
|
95
|
+
<div class="meta"><span id="streak-sub">—</span></div>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="stat-card">
|
|
98
|
+
<div class="label">Cost · range</div>
|
|
99
|
+
<div class="value"><span id="cost-num">—</span></div>
|
|
100
|
+
<div class="meta"><span id="cost-sub">—</span></div>
|
|
101
|
+
</div>
|
|
102
|
+
</section>
|
|
103
|
+
|
|
104
|
+
<!-- ── Achievements ────────────────────────────────── -->
|
|
105
|
+
<section class="achievements" id="achievements"></section>
|
|
106
|
+
|
|
107
|
+
<!-- ── Heatmap ─────────────────────────────────────── -->
|
|
108
|
+
<section>
|
|
109
|
+
<div class="section-head">
|
|
110
|
+
<h2>Activity</h2>
|
|
111
|
+
<div class="right" id="heatmap-meta">click a day for details</div>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="card heatmap-card">
|
|
114
|
+
<div class="heatmap">
|
|
115
|
+
<div class="day-labels"><span></span><span>M</span><span></span><span>W</span><span></span><span>F</span><span></span></div>
|
|
116
|
+
<div class="grid" id="heatmap-grid"></div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</section>
|
|
120
|
+
|
|
121
|
+
<!-- ── Split: cost + languages ─────────────────────── -->
|
|
122
|
+
<section class="split-row">
|
|
123
|
+
<div class="card">
|
|
124
|
+
<div class="card-h"><h3>Cost</h3><div class="meta" id="cost-card-meta">approximate · range</div></div>
|
|
125
|
+
<div class="cost-grid">
|
|
126
|
+
<div>
|
|
127
|
+
<div class="cost-figure" id="cost-figure">—</div>
|
|
128
|
+
<div class="cost-sub" id="cost-figure-sub">—</div>
|
|
129
|
+
</div>
|
|
130
|
+
<div class="cost-bars" id="cost-bars"></div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="card">
|
|
134
|
+
<div class="card-h"><h3>Languages</h3><div class="meta" id="lang-meta">by edits</div></div>
|
|
135
|
+
<div class="lang-stack" id="lang-stack"></div>
|
|
136
|
+
<div class="lang-list" id="lang-list"></div>
|
|
137
|
+
</div>
|
|
138
|
+
</section>
|
|
139
|
+
|
|
140
|
+
<!-- ── Code churn ──────────────────────────────────── -->
|
|
141
|
+
<section class="card" style="margin-bottom: 28px;">
|
|
142
|
+
<div class="card-h"><h3>Code churn</h3><div class="meta" id="churn-meta">lines added / removed · range</div></div>
|
|
143
|
+
<div class="churn-row">
|
|
144
|
+
<div class="churn-spark">
|
|
145
|
+
<svg id="churn-svg" viewBox="0 0 800 60" preserveAspectRatio="none"></svg>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="churn-numbers">
|
|
148
|
+
<div class="row"><span class="label">Added</span><span class="added" id="churn-added">—</span></div>
|
|
149
|
+
<div class="row"><span class="label">Removed</span><span class="removed" id="churn-removed">—</span></div>
|
|
150
|
+
<div class="row"><span class="label">Net</span><span class="net" id="churn-net">—</span></div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</section>
|
|
154
|
+
|
|
155
|
+
<!-- ── Tokens ──────────────────────────────────────── -->
|
|
156
|
+
<section>
|
|
157
|
+
<div class="section-head">
|
|
158
|
+
<h2>Tokens</h2>
|
|
159
|
+
<div class="right"><span id="tok-cache-pct">—</span> from cache</div>
|
|
160
|
+
</div>
|
|
161
|
+
<div class="stat-row" style="margin-bottom: 0; grid-template-columns: repeat(3, 1fr);">
|
|
162
|
+
<div class="stat-card"><div class="label">Grand total</div><div class="value"><span id="tok-grand">—</span></div><div class="meta"><span class="num" id="tok-grand-sub">in + out + cache</span></div></div>
|
|
163
|
+
<div class="stat-card"><div class="label">Output</div><div class="value"><span id="tok-out">—</span></div><div class="meta"><span class="num" id="tok-in-sub">input —</span></div></div>
|
|
164
|
+
<div class="stat-card"><div class="label">Cache</div><div class="value"><span id="tok-cache">—</span></div><div class="meta"><span class="num" id="tok-cache-sub">read — · write —</span></div></div>
|
|
165
|
+
</div>
|
|
166
|
+
</section>
|
|
167
|
+
|
|
168
|
+
<!-- ── Leaderboards ────────────────────────────────── -->
|
|
169
|
+
<section>
|
|
170
|
+
<div class="section-head">
|
|
171
|
+
<h2>Projects · tools · files</h2>
|
|
172
|
+
<div class="right" id="lb-meta">across <span id="lb-sessions">—</span> sessions</div>
|
|
173
|
+
</div>
|
|
174
|
+
<div class="lb-grid">
|
|
175
|
+
<div class="lb">
|
|
176
|
+
<div class="lb-h"><span class="t">Projects</span><span class="s">by hours</span></div>
|
|
177
|
+
<table id="projects-tbl"></table>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="lb">
|
|
180
|
+
<div class="lb-h"><span class="t">Tools</span><span class="s">by calls</span></div>
|
|
181
|
+
<table id="tools-tbl"></table>
|
|
182
|
+
</div>
|
|
183
|
+
<div class="lb">
|
|
184
|
+
<div class="lb-h"><span class="t">Files</span><span class="s">by edits</span></div>
|
|
185
|
+
<table id="files-tbl"></table>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</section>
|
|
189
|
+
|
|
190
|
+
<!-- ── More leaderboards: bash + domains + subagents ─ -->
|
|
191
|
+
<section>
|
|
192
|
+
<div class="section-head">
|
|
193
|
+
<h2>Shell · web · subagents</h2>
|
|
194
|
+
<div class="right"><span id="mcp-label">—</span></div>
|
|
195
|
+
</div>
|
|
196
|
+
<div class="lb-grid">
|
|
197
|
+
<div class="lb">
|
|
198
|
+
<div class="lb-h"><span class="t">Bash commands</span><span class="s">by invocations</span></div>
|
|
199
|
+
<table id="bash-tbl"></table>
|
|
200
|
+
</div>
|
|
201
|
+
<div class="lb">
|
|
202
|
+
<div class="lb-h"><span class="t">WebFetch domains</span><span class="s">by hits</span></div>
|
|
203
|
+
<table id="domains-tbl"></table>
|
|
204
|
+
</div>
|
|
205
|
+
<div class="lb">
|
|
206
|
+
<div class="lb-h"><span class="t">Subagents</span><span class="s">by invocations</span></div>
|
|
207
|
+
<table id="subagents-tbl"></table>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</section>
|
|
211
|
+
|
|
212
|
+
<!-- ── Discord card ────────────────────────────────── -->
|
|
213
|
+
<section>
|
|
214
|
+
<div class="section-head">
|
|
215
|
+
<h2>Discord presence</h2>
|
|
216
|
+
<div class="right"><span id="frames-live">—</span> live · <span id="frames-total">—</span> total</div>
|
|
217
|
+
</div>
|
|
218
|
+
<div class="discord">
|
|
219
|
+
<div class="discord-h"><span class="t">Now showing</span><span class="s" id="frame-no">—</span></div>
|
|
220
|
+
<div class="live-frame">
|
|
221
|
+
<div class="label-tag">On air</div>
|
|
222
|
+
<div class="details" id="frame-details-2">—</div>
|
|
223
|
+
<div class="state" id="frame-state-2">—</div>
|
|
224
|
+
</div>
|
|
225
|
+
<ul class="rotation-list" id="rotation-list"></ul>
|
|
226
|
+
</div>
|
|
227
|
+
</section>
|
|
228
|
+
|
|
229
|
+
<footer>
|
|
230
|
+
<span class="pulse"><span class="pulse-dot"></span><span id="conn-state">live</span></span>
|
|
231
|
+
<span>
|
|
232
|
+
<a href="/api/badge.svg?metric=hours&range=7d" target="_blank">badges</a>
|
|
233
|
+
·
|
|
234
|
+
<a href="/api/export.json" download>export json</a>
|
|
235
|
+
/
|
|
236
|
+
<a href="/api/export.csv" download>csv</a>
|
|
237
|
+
·
|
|
238
|
+
<span>127.0.0.1:{{PORT}}</span>
|
|
239
|
+
·
|
|
240
|
+
<span style="color: var(--text-4);">?</span> for help
|
|
241
|
+
</span>
|
|
242
|
+
</footer>
|
|
243
|
+
</main>
|
|
244
|
+
|
|
245
|
+
<!-- Drawer (project drilldown) -->
|
|
246
|
+
<div class="scrim" id="scrim"></div>
|
|
247
|
+
<div class="drawer" id="drawer">
|
|
248
|
+
<button class="close" id="drawer-close">×</button>
|
|
249
|
+
<h3 id="drawer-title">—</h3>
|
|
250
|
+
<div class="sub" id="drawer-sub">—</div>
|
|
251
|
+
<div class="grid" id="drawer-body"></div>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<!-- Modal (day detail) -->
|
|
255
|
+
<div class="modal" id="modal">
|
|
256
|
+
<button class="close" id="modal-close">×</button>
|
|
257
|
+
<h4 id="modal-title">—</h4>
|
|
258
|
+
<div class="sub" id="modal-sub">—</div>
|
|
259
|
+
<div id="modal-body"></div>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<!-- Keyboard help -->
|
|
263
|
+
<div class="help" id="help">
|
|
264
|
+
<div class="help-card">
|
|
265
|
+
<h4>Keyboard shortcuts</h4>
|
|
266
|
+
<div class="row"><span class="keys"><span class="kbd">1</span><span class="kbd">5</span></span><span>switch range</span></div>
|
|
267
|
+
<div class="row"><span class="keys"><span class="kbd">t</span></span><span>toggle theme</span></div>
|
|
268
|
+
<div class="row"><span class="keys"><span class="kbd">esc</span></span><span>close drawer / modal</span></div>
|
|
269
|
+
<div class="row"><span class="keys"><span class="kbd">?</span></span><span>this help</span></div>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<script>
|
|
274
|
+
{{SCRIPT}}</script>
|
|
275
|
+
</body>
|
|
276
|
+
</html>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Loads the dashboard's browser assets (CSS / HTML / client JS).
|
|
2
|
+
//
|
|
3
|
+
// Three runtime modes, two storage strategies:
|
|
4
|
+
// dev + npm-install → the files sit on disk under ./assets/; read them.
|
|
5
|
+
// packaged SEA exe → there is no filesystem beside the binary, so the
|
|
6
|
+
// files are baked into the SEA blob (see the `assets`
|
|
7
|
+
// map in sea-config.json) and pulled out at runtime
|
|
8
|
+
// via node:sea getAsset().
|
|
9
|
+
//
|
|
10
|
+
// node:sea exists only on Node 20.12+, and isSea() is false unless we're
|
|
11
|
+
// actually inside a packaged binary — so a guarded require keeps the
|
|
12
|
+
// Node 18 dev/npm path working and falls through to disk reads everywhere
|
|
13
|
+
// that isn't a real SEA.
|
|
14
|
+
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
|
|
20
|
+
const ASSET_DIR = join(dirname(fileURLToPath(import.meta.url)), 'assets');
|
|
21
|
+
|
|
22
|
+
let seaApi;
|
|
23
|
+
function sea() {
|
|
24
|
+
if (seaApi !== undefined) return seaApi;
|
|
25
|
+
try { seaApi = createRequire(import.meta.url)('node:sea'); }
|
|
26
|
+
catch { seaApi = null; } // node:sea absent (Node < 20.12) — disk path only
|
|
27
|
+
return seaApi;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function loadAsset(name) {
|
|
31
|
+
const s = sea();
|
|
32
|
+
if (s && typeof s.isSea === 'function' && s.isSea()) {
|
|
33
|
+
return s.getAsset(name, 'utf8');
|
|
34
|
+
}
|
|
35
|
+
return readFileSync(join(ASSET_DIR, name), 'utf8');
|
|
36
|
+
}
|