bashstats 0.2.1 → 0.2.2
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/NUL +0 -10
- package/README.md +159 -46
- package/dist/{chunk-4HVDBCTU.js → chunk-YAJO5WNW.js} +1021 -179
- package/dist/chunk-YAJO5WNW.js.map +1 -0
- package/dist/cli.js +103 -40
- package/dist/cli.js.map +1 -1
- package/dist/hooks/{chunk-37VUNTM4.js → chunk-CLSVLWCR.js} +243 -28
- package/dist/hooks/chunk-CLSVLWCR.js.map +1 -0
- package/dist/hooks/notification.js +1 -1
- package/dist/hooks/permission-request.js +1 -1
- package/dist/hooks/post-tool-failure.js +1 -1
- package/dist/hooks/post-tool-use.js +1 -1
- package/dist/hooks/pre-compact.js +1 -1
- package/dist/hooks/pre-tool-use.js +1 -1
- package/dist/hooks/session-start.js +1 -1
- package/dist/hooks/setup.js +1 -1
- package/dist/hooks/stop.js +1 -1
- package/dist/hooks/subagent-start.js +1 -1
- package/dist/hooks/subagent-stop.js +1 -1
- package/dist/hooks/user-prompt-submit.js +1 -1
- package/dist/index.d.ts +145 -17
- package/dist/index.js +31 -5
- package/dist/static/index.html +806 -55
- package/package.json +1 -1
- package/dist/chunk-4HVDBCTU.js.map +0 -1
- package/dist/hooks/chunk-37VUNTM4.js.map +0 -1
package/dist/static/index.html
CHANGED
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
--tier-silver: #C0C0C0;
|
|
27
27
|
--tier-gold: #FFD700;
|
|
28
28
|
--tier-diamond: #B9F2FF;
|
|
29
|
-
--tier-
|
|
29
|
+
--tier-singularity: #2D1B69;
|
|
30
|
+
--tier-obsidian: #1a0a3e;
|
|
31
|
+
--tier-system-anomaly: #00ff41;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
:root[data-theme="dark"] {
|
|
@@ -371,6 +373,148 @@
|
|
|
371
373
|
}
|
|
372
374
|
}
|
|
373
375
|
|
|
376
|
+
/* ===== OVERVIEW MIDDLE ROW ===== */
|
|
377
|
+
.overview-mid {
|
|
378
|
+
display: grid;
|
|
379
|
+
grid-template-columns: 1fr 1fr 1fr;
|
|
380
|
+
gap: 16px;
|
|
381
|
+
margin-bottom: 16px;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.overview-mid > .card {
|
|
385
|
+
display: flex;
|
|
386
|
+
flex-direction: column;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
@media (max-width: 768px) {
|
|
390
|
+
.overview-mid {
|
|
391
|
+
grid-template-columns: 1fr;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.weekly-challenge-item {
|
|
396
|
+
display: flex;
|
|
397
|
+
flex-direction: column;
|
|
398
|
+
gap: 4px;
|
|
399
|
+
padding: 8px 0;
|
|
400
|
+
border-bottom: 1px solid var(--bg-accent);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.weekly-challenge-item:last-child {
|
|
404
|
+
border-bottom: none;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.weekly-challenge-desc {
|
|
408
|
+
font-size: 12px;
|
|
409
|
+
color: var(--text-primary);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.weekly-challenge-meta {
|
|
413
|
+
display: flex;
|
|
414
|
+
justify-content: space-between;
|
|
415
|
+
align-items: center;
|
|
416
|
+
font-size: 11px;
|
|
417
|
+
font-family: 'JetBrains Mono', monospace;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.weekly-challenge-xp {
|
|
421
|
+
color: var(--accent);
|
|
422
|
+
font-weight: 600;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.weekly-multiplier {
|
|
426
|
+
font-family: 'JetBrains Mono', monospace;
|
|
427
|
+
font-size: 11px;
|
|
428
|
+
color: var(--accent);
|
|
429
|
+
font-weight: 700;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.today-streak {
|
|
433
|
+
display: flex;
|
|
434
|
+
align-items: center;
|
|
435
|
+
justify-content: center;
|
|
436
|
+
gap: 8px;
|
|
437
|
+
margin-bottom: 12px;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.today-streak-number {
|
|
441
|
+
font-family: 'JetBrains Mono', monospace;
|
|
442
|
+
font-size: 36px;
|
|
443
|
+
font-weight: 700;
|
|
444
|
+
color: var(--accent);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.today-streak-label {
|
|
448
|
+
font-size: 12px;
|
|
449
|
+
color: var(--text-secondary);
|
|
450
|
+
line-height: 1.3;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.today-stat-grid {
|
|
454
|
+
display: grid;
|
|
455
|
+
grid-template-columns: 1fr 1fr;
|
|
456
|
+
gap: 8px;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.today-stat-item {
|
|
460
|
+
text-align: center;
|
|
461
|
+
padding: 8px;
|
|
462
|
+
background: var(--bg-accent);
|
|
463
|
+
border-radius: 2px;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.today-stat-value {
|
|
467
|
+
font-family: 'JetBrains Mono', monospace;
|
|
468
|
+
font-size: 20px;
|
|
469
|
+
font-weight: 700;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.today-stat-label {
|
|
473
|
+
font-size: 10px;
|
|
474
|
+
color: var(--text-secondary);
|
|
475
|
+
text-transform: uppercase;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.badge-progress-summary {
|
|
479
|
+
display: flex;
|
|
480
|
+
align-items: center;
|
|
481
|
+
gap: 12px;
|
|
482
|
+
margin-bottom: 12px;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.badge-progress-count {
|
|
486
|
+
font-family: 'JetBrains Mono', monospace;
|
|
487
|
+
font-size: 28px;
|
|
488
|
+
font-weight: 700;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.badge-progress-total {
|
|
492
|
+
font-size: 12px;
|
|
493
|
+
color: var(--text-secondary);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.badge-next-item {
|
|
497
|
+
padding: 8px;
|
|
498
|
+
background: var(--bg-accent);
|
|
499
|
+
border-radius: 2px;
|
|
500
|
+
border-left: 3px solid var(--accent);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.badge-next-name {
|
|
504
|
+
font-size: 13px;
|
|
505
|
+
font-weight: 600;
|
|
506
|
+
margin-bottom: 4px;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.badge-next-meta {
|
|
510
|
+
display: flex;
|
|
511
|
+
justify-content: space-between;
|
|
512
|
+
font-size: 11px;
|
|
513
|
+
font-family: 'JetBrains Mono', monospace;
|
|
514
|
+
color: var(--text-secondary);
|
|
515
|
+
margin-top: 4px;
|
|
516
|
+
}
|
|
517
|
+
|
|
374
518
|
/* ===== RECENT BADGES ===== */
|
|
375
519
|
.recent-badges-list {
|
|
376
520
|
display: flex;
|
|
@@ -993,19 +1137,19 @@
|
|
|
993
1137
|
}
|
|
994
1138
|
|
|
995
1139
|
/* ===== TIER COLORS ===== */
|
|
996
|
-
.tier-locked
|
|
997
|
-
.tier-bronze
|
|
998
|
-
.tier-silver
|
|
999
|
-
.tier-gold
|
|
1000
|
-
.tier-diamond
|
|
1001
|
-
.tier-
|
|
1002
|
-
|
|
1003
|
-
.tier-bg-locked
|
|
1004
|
-
.tier-bg-bronze
|
|
1005
|
-
.tier-bg-silver
|
|
1006
|
-
.tier-bg-gold
|
|
1007
|
-
.tier-bg-diamond
|
|
1008
|
-
.tier-bg-
|
|
1140
|
+
.tier-locked { color: var(--text-secondary); }
|
|
1141
|
+
.tier-bronze { color: var(--tier-bronze); }
|
|
1142
|
+
.tier-silver { color: var(--tier-silver); }
|
|
1143
|
+
.tier-gold { color: var(--tier-gold); }
|
|
1144
|
+
.tier-diamond { color: var(--tier-diamond); }
|
|
1145
|
+
.tier-singularity { color: var(--tier-singularity); }
|
|
1146
|
+
|
|
1147
|
+
.tier-bg-locked { background: var(--text-secondary); }
|
|
1148
|
+
.tier-bg-bronze { background: var(--tier-bronze); }
|
|
1149
|
+
.tier-bg-silver { background: var(--tier-silver); }
|
|
1150
|
+
.tier-bg-gold { background: var(--tier-gold); }
|
|
1151
|
+
.tier-bg-diamond { background: var(--tier-diamond); }
|
|
1152
|
+
.tier-bg-singularity { background: var(--tier-singularity); }
|
|
1009
1153
|
|
|
1010
1154
|
/* ===== REFRESH INDICATOR ===== */
|
|
1011
1155
|
.refresh-indicator {
|
|
@@ -1021,6 +1165,304 @@
|
|
|
1021
1165
|
padding: 4px 8px;
|
|
1022
1166
|
box-shadow: 3px 3px 0 var(--shadow);
|
|
1023
1167
|
}
|
|
1168
|
+
|
|
1169
|
+
.agent-filter {
|
|
1170
|
+
background: var(--bg-card);
|
|
1171
|
+
color: var(--text-primary);
|
|
1172
|
+
border: 3px solid var(--border);
|
|
1173
|
+
border-radius: 2px;
|
|
1174
|
+
padding: 4px 12px;
|
|
1175
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1176
|
+
font-size: 13px;
|
|
1177
|
+
font-weight: 600;
|
|
1178
|
+
cursor: pointer;
|
|
1179
|
+
outline: none;
|
|
1180
|
+
box-shadow: 3px 3px 0 var(--shadow);
|
|
1181
|
+
}
|
|
1182
|
+
.agent-filter:focus {
|
|
1183
|
+
border-color: var(--accent);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
.agent-badge {
|
|
1187
|
+
display: inline-block;
|
|
1188
|
+
font-size: 0.65rem;
|
|
1189
|
+
padding: 1px 5px;
|
|
1190
|
+
border-radius: 2px;
|
|
1191
|
+
background: var(--bg-card);
|
|
1192
|
+
border: 2px solid var(--border);
|
|
1193
|
+
margin-left: 6px;
|
|
1194
|
+
vertical-align: middle;
|
|
1195
|
+
}
|
|
1196
|
+
.agent-badge.claude-code { border-color: #d97706; color: #d97706; }
|
|
1197
|
+
.agent-badge.gemini-cli { border-color: #4285f4; color: #4285f4; }
|
|
1198
|
+
.agent-badge.copilot-cli { border-color: #6e40c9; color: #6e40c9; }
|
|
1199
|
+
.agent-badge.opencode { border-color: #10b981; color: #10b981; }
|
|
1200
|
+
|
|
1201
|
+
.stat-na {
|
|
1202
|
+
opacity: 0.4;
|
|
1203
|
+
font-style: italic;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
.agent-breakdown-card {
|
|
1207
|
+
margin-top: 1rem;
|
|
1208
|
+
}
|
|
1209
|
+
.agent-bar {
|
|
1210
|
+
display: flex;
|
|
1211
|
+
align-items: center;
|
|
1212
|
+
gap: 8px;
|
|
1213
|
+
margin: 6px 0;
|
|
1214
|
+
}
|
|
1215
|
+
.agent-bar-label {
|
|
1216
|
+
width: 100px;
|
|
1217
|
+
font-size: 0.8rem;
|
|
1218
|
+
text-align: right;
|
|
1219
|
+
white-space: nowrap;
|
|
1220
|
+
}
|
|
1221
|
+
.agent-bar-track {
|
|
1222
|
+
flex: 1;
|
|
1223
|
+
background: var(--border);
|
|
1224
|
+
border-radius: 2px;
|
|
1225
|
+
height: 20px;
|
|
1226
|
+
overflow: hidden;
|
|
1227
|
+
}
|
|
1228
|
+
.agent-bar-fill {
|
|
1229
|
+
height: 100%;
|
|
1230
|
+
border-radius: 2px;
|
|
1231
|
+
transition: width 0.3s ease;
|
|
1232
|
+
min-width: 3px;
|
|
1233
|
+
}
|
|
1234
|
+
.agent-bar-value {
|
|
1235
|
+
font-size: 0.8rem;
|
|
1236
|
+
width: 100px;
|
|
1237
|
+
text-align: right;
|
|
1238
|
+
white-space: nowrap;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/* ===== RANK PROGRESSION MODAL ===== */
|
|
1242
|
+
.rank-modal-overlay {
|
|
1243
|
+
position: fixed;
|
|
1244
|
+
inset: 0;
|
|
1245
|
+
background: rgba(0,0,0,0.5);
|
|
1246
|
+
backdrop-filter: blur(4px);
|
|
1247
|
+
z-index: 200;
|
|
1248
|
+
display: flex;
|
|
1249
|
+
align-items: center;
|
|
1250
|
+
justify-content: center;
|
|
1251
|
+
padding: 20px;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
.rank-modal {
|
|
1255
|
+
background: var(--bg-card);
|
|
1256
|
+
border: 3px solid var(--border);
|
|
1257
|
+
border-radius: 2px;
|
|
1258
|
+
box-shadow: 8px 8px 0 var(--shadow);
|
|
1259
|
+
width: 100%;
|
|
1260
|
+
max-width: 520px;
|
|
1261
|
+
max-height: 85vh;
|
|
1262
|
+
display: flex;
|
|
1263
|
+
flex-direction: column;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
.rank-modal-header {
|
|
1267
|
+
display: flex;
|
|
1268
|
+
align-items: center;
|
|
1269
|
+
justify-content: space-between;
|
|
1270
|
+
padding: 16px 20px;
|
|
1271
|
+
border-bottom: 3px solid var(--border);
|
|
1272
|
+
flex-shrink: 0;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
.rank-modal-title {
|
|
1276
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1277
|
+
font-size: 16px;
|
|
1278
|
+
font-weight: 700;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
.rank-modal-close {
|
|
1282
|
+
background: none;
|
|
1283
|
+
border: 2px solid var(--border);
|
|
1284
|
+
border-radius: 2px;
|
|
1285
|
+
width: 32px;
|
|
1286
|
+
height: 32px;
|
|
1287
|
+
font-size: 20px;
|
|
1288
|
+
line-height: 1;
|
|
1289
|
+
cursor: pointer;
|
|
1290
|
+
color: var(--text-primary);
|
|
1291
|
+
display: flex;
|
|
1292
|
+
align-items: center;
|
|
1293
|
+
justify-content: center;
|
|
1294
|
+
box-shadow: 2px 2px 0 var(--shadow);
|
|
1295
|
+
transition: transform 0.1s, box-shadow 0.1s;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
.rank-modal-close:hover {
|
|
1299
|
+
transform: translate(-1px, -1px);
|
|
1300
|
+
box-shadow: 3px 3px 0 var(--shadow);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
.rank-modal-body {
|
|
1304
|
+
overflow-y: auto;
|
|
1305
|
+
padding: 20px;
|
|
1306
|
+
flex: 1;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
/* Tier sections */
|
|
1310
|
+
.rank-tier-section {
|
|
1311
|
+
margin-bottom: 24px;
|
|
1312
|
+
padding-left: 16px;
|
|
1313
|
+
border-left: 3px solid var(--text-secondary);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
.rank-tier-section:last-child {
|
|
1317
|
+
margin-bottom: 0;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
.rank-tier-header {
|
|
1321
|
+
display: flex;
|
|
1322
|
+
align-items: center;
|
|
1323
|
+
justify-content: space-between;
|
|
1324
|
+
margin-bottom: 12px;
|
|
1325
|
+
padding-bottom: 8px;
|
|
1326
|
+
border-bottom: 1px solid var(--bg-accent);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
.rank-tier-name {
|
|
1330
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1331
|
+
font-size: 14px;
|
|
1332
|
+
font-weight: 700;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
.rank-tier-range {
|
|
1336
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1337
|
+
font-size: 11px;
|
|
1338
|
+
color: var(--text-secondary);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
/* Track nodes */
|
|
1342
|
+
.rank-track {
|
|
1343
|
+
position: relative;
|
|
1344
|
+
padding-left: 20px;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
.rank-track::before {
|
|
1348
|
+
content: '';
|
|
1349
|
+
position: absolute;
|
|
1350
|
+
left: 7px;
|
|
1351
|
+
top: 0;
|
|
1352
|
+
bottom: 0;
|
|
1353
|
+
width: 2px;
|
|
1354
|
+
background: var(--bg-accent);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
.rank-node {
|
|
1358
|
+
position: relative;
|
|
1359
|
+
display: flex;
|
|
1360
|
+
align-items: center;
|
|
1361
|
+
gap: 12px;
|
|
1362
|
+
padding: 6px 0;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
.rank-node-dot {
|
|
1366
|
+
width: 16px;
|
|
1367
|
+
height: 16px;
|
|
1368
|
+
border-radius: 50%;
|
|
1369
|
+
border: 2px solid var(--text-secondary);
|
|
1370
|
+
background: var(--bg-card);
|
|
1371
|
+
flex-shrink: 0;
|
|
1372
|
+
position: relative;
|
|
1373
|
+
z-index: 1;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
.rank-node-dot.unlocked {
|
|
1377
|
+
border-color: currentColor;
|
|
1378
|
+
background: currentColor;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
.rank-node-dot.milestone {
|
|
1382
|
+
width: 20px;
|
|
1383
|
+
height: 20px;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
.rank-node-info {
|
|
1387
|
+
display: flex;
|
|
1388
|
+
align-items: baseline;
|
|
1389
|
+
gap: 8px;
|
|
1390
|
+
flex: 1;
|
|
1391
|
+
min-width: 0;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
.rank-node-number {
|
|
1395
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1396
|
+
font-size: 13px;
|
|
1397
|
+
font-weight: 700;
|
|
1398
|
+
white-space: nowrap;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
.rank-node-xp {
|
|
1402
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1403
|
+
font-size: 11px;
|
|
1404
|
+
color: var(--text-secondary);
|
|
1405
|
+
white-space: nowrap;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
.rank-node-label {
|
|
1409
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1410
|
+
font-size: 10px;
|
|
1411
|
+
font-weight: 700;
|
|
1412
|
+
padding: 1px 6px;
|
|
1413
|
+
border: 2px solid;
|
|
1414
|
+
border-radius: 2px;
|
|
1415
|
+
white-space: nowrap;
|
|
1416
|
+
flex-shrink: 0;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/* Current rank node */
|
|
1420
|
+
.rank-node.current .rank-node-dot {
|
|
1421
|
+
width: 22px;
|
|
1422
|
+
height: 22px;
|
|
1423
|
+
box-shadow: 0 0 0 3px var(--bg-card), 0 0 0 5px currentColor;
|
|
1424
|
+
animation: rankPulse 2s ease-in-out infinite;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
.rank-node.current {
|
|
1428
|
+
padding: 10px 0;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/* Locked/future nodes */
|
|
1432
|
+
.rank-node.locked .rank-node-number {
|
|
1433
|
+
color: var(--text-secondary);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
.rank-node.locked .rank-node-xp {
|
|
1437
|
+
color: var(--text-secondary);
|
|
1438
|
+
opacity: 0.6;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
@keyframes rankPulse {
|
|
1442
|
+
0%, 100% { box-shadow: 0 0 0 3px var(--bg-card), 0 0 0 5px currentColor; }
|
|
1443
|
+
50% { box-shadow: 0 0 0 3px var(--bg-card), 0 0 8px 5px currentColor; }
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
/* Clickable rank badge and rank card */
|
|
1447
|
+
#header-rank-badge {
|
|
1448
|
+
cursor: pointer;
|
|
1449
|
+
transition: transform 0.1s, box-shadow 0.1s;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
#header-rank-badge:hover {
|
|
1453
|
+
transform: translate(-2px, -2px);
|
|
1454
|
+
box-shadow: 5px 5px 0 var(--shadow);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
.overview-rank-clickable {
|
|
1458
|
+
cursor: pointer;
|
|
1459
|
+
transition: transform 0.1s, box-shadow 0.1s;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
.overview-rank-clickable:hover {
|
|
1463
|
+
transform: translate(-2px, -2px);
|
|
1464
|
+
box-shadow: 8px 8px 0 var(--shadow);
|
|
1465
|
+
}
|
|
1024
1466
|
</style>
|
|
1025
1467
|
</head>
|
|
1026
1468
|
<body>
|
|
@@ -1036,12 +1478,13 @@
|
|
|
1036
1478
|
</div>
|
|
1037
1479
|
</div>
|
|
1038
1480
|
<div class="header-right">
|
|
1039
|
-
<
|
|
1040
|
-
<
|
|
1041
|
-
<
|
|
1042
|
-
|
|
1043
|
-
</
|
|
1044
|
-
|
|
1481
|
+
<select id="agent-filter" class="agent-filter" title="Filter by agent">
|
|
1482
|
+
<option value="">All Agents</option>
|
|
1483
|
+
<option value="claude-code">Claude Code</option>
|
|
1484
|
+
<option value="gemini-cli">Gemini CLI</option>
|
|
1485
|
+
<option value="copilot-cli">Copilot CLI</option>
|
|
1486
|
+
<option value="opencode">OpenCode</option>
|
|
1487
|
+
</select>
|
|
1045
1488
|
<div class="streak-display" id="header-streak" style="display:none;">
|
|
1046
1489
|
<span id="streak-icon">🔥</span>
|
|
1047
1490
|
<span id="streak-days">0d</span>
|
|
@@ -1074,13 +1517,30 @@
|
|
|
1074
1517
|
<div class="card-title">Recent Badges</div>
|
|
1075
1518
|
<div id="overview-recent-badges" class="recent-badges-list"></div>
|
|
1076
1519
|
</div>
|
|
1077
|
-
<div class="card">
|
|
1520
|
+
<div class="card overview-rank-clickable" onclick="openRankModal()">
|
|
1078
1521
|
<div class="card-title">Current Rank</div>
|
|
1079
1522
|
<div id="overview-rank-card" class="rank-card-inner"></div>
|
|
1080
1523
|
</div>
|
|
1081
1524
|
</div>
|
|
1525
|
+
<!-- Middle row: Weekly Goals + Streak & Today + Badge Progress -->
|
|
1526
|
+
<div class="overview-mid">
|
|
1527
|
+
<div class="card">
|
|
1528
|
+
<div class="card-title">Weekly Goals <span class="weekly-multiplier" id="weekly-multiplier-label"></span></div>
|
|
1529
|
+
<div id="overview-weekly-goals"></div>
|
|
1530
|
+
</div>
|
|
1531
|
+
<div class="card">
|
|
1532
|
+
<div class="card-title">Streak & Today</div>
|
|
1533
|
+
<div id="overview-streak-today"></div>
|
|
1534
|
+
</div>
|
|
1535
|
+
<div class="card">
|
|
1536
|
+
<div class="card-title">Badge Progress</div>
|
|
1537
|
+
<div id="overview-badge-progress"></div>
|
|
1538
|
+
</div>
|
|
1539
|
+
</div>
|
|
1082
1540
|
<!-- Stat cards -->
|
|
1083
1541
|
<div class="stat-grid" id="overview-stat-cards"></div>
|
|
1542
|
+
<!-- Agent breakdown -->
|
|
1543
|
+
<div id="overview-agent-breakdown"></div>
|
|
1084
1544
|
<!-- Sparkline -->
|
|
1085
1545
|
<div class="sparkline-section card" id="overview-sparkline-card">
|
|
1086
1546
|
<div class="card-title">30-Day Activity</div>
|
|
@@ -1170,6 +1630,7 @@
|
|
|
1170
1630
|
<a class="settings-link" href="/api/achievements" target="_blank">/api/achievements</a>
|
|
1171
1631
|
<a class="settings-link" href="/api/activity" target="_blank">/api/activity</a>
|
|
1172
1632
|
<a class="settings-link" href="/api/sessions" target="_blank">/api/sessions</a>
|
|
1633
|
+
<a class="settings-link" href="/api/weekly-goals" target="_blank">/api/weekly-goals</a>
|
|
1173
1634
|
</div>
|
|
1174
1635
|
</div>
|
|
1175
1636
|
</div>
|
|
@@ -1191,23 +1652,38 @@
|
|
|
1191
1652
|
<span id="refresh-text"></span>
|
|
1192
1653
|
</div>
|
|
1193
1654
|
|
|
1655
|
+
<!-- Rank Progression Modal -->
|
|
1656
|
+
<div id="rank-modal-overlay" class="rank-modal-overlay" style="display:none">
|
|
1657
|
+
<div class="rank-modal">
|
|
1658
|
+
<div class="rank-modal-header">
|
|
1659
|
+
<div class="rank-modal-title">Rank Progression</div>
|
|
1660
|
+
<button class="rank-modal-close" onclick="closeRankModal()">×</button>
|
|
1661
|
+
</div>
|
|
1662
|
+
<div class="rank-modal-body" id="rank-modal-body"></div>
|
|
1663
|
+
</div>
|
|
1664
|
+
</div>
|
|
1665
|
+
|
|
1194
1666
|
<script>
|
|
1195
1667
|
/* ===================================================================
|
|
1196
1668
|
bashstats Dashboard - Single Page Application
|
|
1197
1669
|
=================================================================== */
|
|
1198
1670
|
|
|
1199
1671
|
// ===== STATE =====
|
|
1672
|
+
let selectedAgent = ''
|
|
1673
|
+
let agentBreakdown = null
|
|
1674
|
+
|
|
1200
1675
|
const state = {
|
|
1201
1676
|
stats: null,
|
|
1202
1677
|
achievements: null,
|
|
1203
1678
|
activity: null,
|
|
1204
1679
|
sessions: null,
|
|
1680
|
+
weeklyGoals: null,
|
|
1205
1681
|
lastFetch: null,
|
|
1206
1682
|
};
|
|
1207
1683
|
|
|
1208
1684
|
// ===== TIER HELPERS =====
|
|
1209
|
-
const TIER_NAMES = ['Locked', 'Bronze', 'Silver', 'Gold', 'Diamond', '
|
|
1210
|
-
const TIER_CLASSES = ['locked', 'bronze', 'silver', 'gold', 'diamond', '
|
|
1685
|
+
const TIER_NAMES = ['Locked', 'Bronze', 'Silver', 'Gold', 'Diamond', 'Singularity'];
|
|
1686
|
+
const TIER_CLASSES = ['locked', 'bronze', 'silver', 'gold', 'diamond', 'singularity'];
|
|
1211
1687
|
|
|
1212
1688
|
function tierClass(tier) {
|
|
1213
1689
|
return TIER_CLASSES[tier] || 'locked';
|
|
@@ -1220,20 +1696,21 @@
|
|
|
1220
1696
|
'var(--tier-silver)',
|
|
1221
1697
|
'var(--tier-gold)',
|
|
1222
1698
|
'var(--tier-diamond)',
|
|
1223
|
-
'var(--tier-
|
|
1699
|
+
'var(--tier-singularity)',
|
|
1224
1700
|
];
|
|
1225
1701
|
return colors[tier] || colors[0];
|
|
1226
1702
|
}
|
|
1227
1703
|
|
|
1228
|
-
function rankColor(
|
|
1704
|
+
function rankColor(rankTier) {
|
|
1229
1705
|
const map = {
|
|
1230
1706
|
'Bronze': 'var(--tier-bronze)',
|
|
1231
1707
|
'Silver': 'var(--tier-silver)',
|
|
1232
1708
|
'Gold': 'var(--tier-gold)',
|
|
1233
1709
|
'Diamond': 'var(--tier-diamond)',
|
|
1234
1710
|
'Obsidian': 'var(--tier-obsidian)',
|
|
1711
|
+
'System Anomaly': 'var(--tier-system-anomaly)',
|
|
1235
1712
|
};
|
|
1236
|
-
return map[
|
|
1713
|
+
return map[rankTier] || 'var(--tier-bronze)';
|
|
1237
1714
|
}
|
|
1238
1715
|
|
|
1239
1716
|
// ===== FORMAT HELPERS =====
|
|
@@ -1294,17 +1771,22 @@
|
|
|
1294
1771
|
}
|
|
1295
1772
|
|
|
1296
1773
|
async function loadAllData() {
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
fetchJSON('/api/
|
|
1300
|
-
fetchJSON('/api/
|
|
1301
|
-
fetchJSON('/api/
|
|
1774
|
+
const agentParam = selectedAgent ? '?agent=' + selectedAgent : ''
|
|
1775
|
+
const [stats, achievements, activity, sessions, agents, weeklyGoals] = await Promise.all([
|
|
1776
|
+
fetchJSON('/api/stats' + agentParam),
|
|
1777
|
+
fetchJSON('/api/achievements' + agentParam),
|
|
1778
|
+
fetchJSON('/api/activity?days=365'),
|
|
1779
|
+
fetchJSON('/api/sessions' + agentParam),
|
|
1780
|
+
fetchJSON('/api/agents'),
|
|
1781
|
+
fetchJSON('/api/weekly-goals'),
|
|
1302
1782
|
]);
|
|
1303
1783
|
|
|
1304
|
-
state.stats = stats;
|
|
1305
|
-
state.achievements = achievements;
|
|
1306
|
-
state.activity = activity;
|
|
1307
|
-
state.sessions = sessions;
|
|
1784
|
+
if (stats) state.stats = stats;
|
|
1785
|
+
if (achievements) state.achievements = achievements;
|
|
1786
|
+
if (activity) state.activity = activity;
|
|
1787
|
+
if (sessions) state.sessions = sessions;
|
|
1788
|
+
if (agents) agentBreakdown = agents;
|
|
1789
|
+
if (weeklyGoals) state.weeklyGoals = weeklyGoals;
|
|
1308
1790
|
state.lastFetch = new Date();
|
|
1309
1791
|
|
|
1310
1792
|
renderAll();
|
|
@@ -1335,6 +1817,14 @@
|
|
|
1335
1817
|
});
|
|
1336
1818
|
}
|
|
1337
1819
|
|
|
1820
|
+
// ===== AGENT FILTER =====
|
|
1821
|
+
function setupAgentFilter() {
|
|
1822
|
+
document.getElementById('agent-filter').addEventListener('change', (e) => {
|
|
1823
|
+
selectedAgent = e.target.value
|
|
1824
|
+
loadAllData()
|
|
1825
|
+
})
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1338
1828
|
// ===== THEME SWITCHING =====
|
|
1339
1829
|
function setupTheme() {
|
|
1340
1830
|
const sel = document.getElementById('settings-theme');
|
|
@@ -1376,15 +1866,8 @@
|
|
|
1376
1866
|
const rankDot = document.getElementById('header-rank-dot');
|
|
1377
1867
|
const rankText = document.getElementById('header-rank-text');
|
|
1378
1868
|
rankBadge.style.display = 'inline-flex';
|
|
1379
|
-
rankDot.style.background = rankColor(xp.
|
|
1380
|
-
rankText.textContent = xp.
|
|
1381
|
-
|
|
1382
|
-
const xpContainer = document.getElementById('header-xp-container');
|
|
1383
|
-
const xpLabel = document.getElementById('header-xp-label');
|
|
1384
|
-
const xpFill = document.getElementById('header-xp-fill');
|
|
1385
|
-
xpContainer.style.display = 'flex';
|
|
1386
|
-
xpLabel.textContent = formatNumber(xp.totalXP) + ' XP';
|
|
1387
|
-
xpFill.style.width = Math.min(100, (xp.progress || 0) * 100) + '%';
|
|
1869
|
+
rankDot.style.background = rankColor(xp.rankTier);
|
|
1870
|
+
rankText.textContent = `Rank ${xp.rankNumber}`;
|
|
1388
1871
|
}
|
|
1389
1872
|
|
|
1390
1873
|
if (time) {
|
|
@@ -1421,13 +1904,14 @@
|
|
|
1421
1904
|
content.style.display = 'block';
|
|
1422
1905
|
|
|
1423
1906
|
const lt = state.stats.lifetime || {};
|
|
1907
|
+
const filesTouched = (lt.totalFilesRead || 0) + (lt.totalFilesEdited || 0) + (lt.totalFilesCreated || 0);
|
|
1424
1908
|
const cards = [
|
|
1425
1909
|
{ label: 'Sessions', value: formatNumber(lt.totalSessions || 0), sub: 'lifetime' },
|
|
1426
1910
|
{ label: 'Prompts', value: formatNumber(lt.totalPrompts || 0), sub: 'messages sent' },
|
|
1427
1911
|
{ label: 'Tool Calls', value: formatNumber(lt.totalToolCalls || 0), sub: 'total invocations' },
|
|
1428
1912
|
{ label: 'Hours', value: formatHours(lt.totalDurationSeconds || 0), sub: 'coding time' },
|
|
1429
1913
|
{ label: 'Tokens Used', value: formatTokens(lt.totalTokens || 0), sub: 'all token types' },
|
|
1430
|
-
{ label: '
|
|
1914
|
+
{ label: 'Files Touched', value: formatNumber(filesTouched), sub: 'read + edit + create' },
|
|
1431
1915
|
];
|
|
1432
1916
|
|
|
1433
1917
|
const cardsEl = document.getElementById('overview-stat-cards');
|
|
@@ -1439,6 +1923,10 @@
|
|
|
1439
1923
|
</div>
|
|
1440
1924
|
`).join('');
|
|
1441
1925
|
|
|
1926
|
+
// Agent breakdown panel (only shown for "All Agents")
|
|
1927
|
+
const breakdownEl = document.getElementById('overview-agent-breakdown');
|
|
1928
|
+
if (breakdownEl) breakdownEl.innerHTML = renderAgentBreakdown();
|
|
1929
|
+
|
|
1442
1930
|
// Sparkline
|
|
1443
1931
|
renderSparkline();
|
|
1444
1932
|
|
|
@@ -1451,6 +1939,11 @@
|
|
|
1451
1939
|
|
|
1452
1940
|
// Rank card
|
|
1453
1941
|
renderOverviewRank();
|
|
1942
|
+
|
|
1943
|
+
// Middle row cards
|
|
1944
|
+
renderWeeklyGoals();
|
|
1945
|
+
renderStreakToday();
|
|
1946
|
+
renderBadgeProgress();
|
|
1454
1947
|
}
|
|
1455
1948
|
|
|
1456
1949
|
function renderEmptyOverview() {
|
|
@@ -1460,7 +1953,7 @@
|
|
|
1460
1953
|
<div class="stat-card"><div class="stat-card-label">Tool Calls</div><div class="stat-card-number mono">0</div></div>
|
|
1461
1954
|
<div class="stat-card"><div class="stat-card-label">Hours</div><div class="stat-card-number mono">0</div></div>
|
|
1462
1955
|
<div class="stat-card"><div class="stat-card-label">Tokens Used</div><div class="stat-card-number mono">0</div></div>
|
|
1463
|
-
<div class="stat-card"><div class="stat-card-label">
|
|
1956
|
+
<div class="stat-card"><div class="stat-card-label">Files Touched</div><div class="stat-card-number mono">0</div></div>
|
|
1464
1957
|
`;
|
|
1465
1958
|
}
|
|
1466
1959
|
|
|
@@ -1476,7 +1969,7 @@
|
|
|
1476
1969
|
for (let i = 29; i >= 0; i--) {
|
|
1477
1970
|
const d = new Date(today);
|
|
1478
1971
|
d.setDate(d.getDate() - i);
|
|
1479
|
-
const dateStr = d.
|
|
1972
|
+
const dateStr = d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0');
|
|
1480
1973
|
const found = activity.find(a => a.date === dateStr);
|
|
1481
1974
|
const val = found ? (found.sessions + found.prompts + found.tool_calls) : 0;
|
|
1482
1975
|
days.push({ date: dateStr, value: val });
|
|
@@ -1525,23 +2018,164 @@
|
|
|
1525
2018
|
}
|
|
1526
2019
|
|
|
1527
2020
|
const pct = Math.min(100, (xp.progress || 0) * 100);
|
|
1528
|
-
const rc = rankColor(xp.
|
|
1529
|
-
const
|
|
2021
|
+
const rc = rankColor(xp.rankTier);
|
|
2022
|
+
const rankNum = xp.rankNumber || 0;
|
|
1530
2023
|
el.innerHTML = `
|
|
1531
2024
|
<div class="rank-card-icon" style="border-color:${rc};background:${rc}22">
|
|
1532
|
-
<span class="rank-card-icon-text" style="color:${rc}">${
|
|
2025
|
+
<span class="rank-card-icon-text" style="color:${rc}">${rankNum}</span>
|
|
1533
2026
|
</div>
|
|
1534
2027
|
<div class="rank-card-info">
|
|
1535
|
-
<div class="rank-card-rank" style="color:${rc}"
|
|
2028
|
+
<div class="rank-card-rank" style="color:${rc}">Rank ${rankNum} · ${escapeHtml(xp.rankTier)}</div>
|
|
1536
2029
|
<div class="rank-card-xp">${formatNumber(xp.totalXP)} / ${formatNumber(xp.nextRankXP)} XP</div>
|
|
1537
2030
|
<div class="rank-progress-track">
|
|
1538
2031
|
<div class="rank-progress-fill" style="width:${pct}%;background:${rc}"></div>
|
|
1539
2032
|
</div>
|
|
1540
|
-
<div class="rank-progress-label">${pct.toFixed(1)}% to ${escapeHtml(
|
|
2033
|
+
<div class="rank-progress-label">${pct.toFixed(1)}% to ${escapeHtml(rankNum >= 500 ? 'System Anomaly' : 'Rank ' + (rankNum + 1))}</div>
|
|
1541
2034
|
</div>
|
|
1542
2035
|
`;
|
|
1543
2036
|
}
|
|
1544
2037
|
|
|
2038
|
+
// ===== RENDER: WEEKLY GOALS =====
|
|
2039
|
+
function renderWeeklyGoals() {
|
|
2040
|
+
const el = document.getElementById('overview-weekly-goals');
|
|
2041
|
+
const label = document.getElementById('weekly-multiplier-label');
|
|
2042
|
+
const goals = state.weeklyGoals;
|
|
2043
|
+
|
|
2044
|
+
if (!goals || !goals.challenges) {
|
|
2045
|
+
el.innerHTML = '<div style="font-size:12px;color:var(--text-secondary);padding:8px 0;">No weekly goals data</div>';
|
|
2046
|
+
if (label) label.textContent = '';
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
if (label) label.textContent = goals.multiplier > 1 ? '\u00B7 ' + goals.multiplier.toFixed(1) + 'x' : '';
|
|
2051
|
+
|
|
2052
|
+
let html = goals.challenges.map(c => {
|
|
2053
|
+
const pct = Math.min(100, c.progress * 100);
|
|
2054
|
+
return '<div class="weekly-challenge-item">' +
|
|
2055
|
+
'<div class="weekly-challenge-desc">' + (c.completed ? '<span style="color:var(--success);font-weight:700">\u2713</span> ' : '') + escapeHtml(c.description) + '</div>' +
|
|
2056
|
+
'<div class="badge-progress-row">' +
|
|
2057
|
+
'<div class="badge-progress-track">' +
|
|
2058
|
+
'<div class="badge-progress-fill' + (c.completed ? ' maxed' : '') + '" style="width:' + pct + '%"></div>' +
|
|
2059
|
+
'</div>' +
|
|
2060
|
+
'<span class="badge-progress-text">' + c.current + '/' + c.threshold + '</span>' +
|
|
2061
|
+
'</div>' +
|
|
2062
|
+
'<div class="weekly-challenge-meta">' +
|
|
2063
|
+
'<span></span>' +
|
|
2064
|
+
'<span class="weekly-challenge-xp">+' + c.xpReward + ' XP</span>' +
|
|
2065
|
+
'</div>' +
|
|
2066
|
+
'</div>';
|
|
2067
|
+
}).join('');
|
|
2068
|
+
|
|
2069
|
+
html += '<div style="margin-top:8px;font-size:11px;color:var(--text-secondary);font-family:\'JetBrains Mono\',monospace;">' +
|
|
2070
|
+
goals.daysActive + '/7 days active \u00B7 ' + goals.multiplier.toFixed(1) + 'x multiplier</div>';
|
|
2071
|
+
|
|
2072
|
+
el.innerHTML = html;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// ===== RENDER: STREAK & TODAY =====
|
|
2076
|
+
function renderStreakToday() {
|
|
2077
|
+
const el = document.getElementById('overview-streak-today');
|
|
2078
|
+
const time = state.stats?.time;
|
|
2079
|
+
const activity = state.activity || [];
|
|
2080
|
+
|
|
2081
|
+
const today = new Date();
|
|
2082
|
+
const todayStr = today.getFullYear() + '-' + String(today.getMonth()+1).padStart(2,'0') + '-' + String(today.getDate()).padStart(2,'0');
|
|
2083
|
+
const todayActivity = activity.find(a => a.date === todayStr);
|
|
2084
|
+
|
|
2085
|
+
const streak = time?.currentStreak || 0;
|
|
2086
|
+
const todaySessions = todayActivity?.sessions || 0;
|
|
2087
|
+
const todayPrompts = todayActivity?.prompts || 0;
|
|
2088
|
+
const todayTools = todayActivity?.tool_calls || 0;
|
|
2089
|
+
const todayHours = todayActivity ? (todayActivity.duration_seconds / 3600).toFixed(1) : '0';
|
|
2090
|
+
|
|
2091
|
+
el.innerHTML =
|
|
2092
|
+
'<div class="today-streak">' +
|
|
2093
|
+
'<span class="today-streak-number">' + streak + '</span>' +
|
|
2094
|
+
'<span class="today-streak-label">day streak' + (streak > 0 ? '<br>\uD83D\uDD25' : '') + '</span>' +
|
|
2095
|
+
'</div>' +
|
|
2096
|
+
'<div class="today-stat-grid">' +
|
|
2097
|
+
'<div class="today-stat-item"><div class="today-stat-value">' + todaySessions + '</div><div class="today-stat-label">Sessions</div></div>' +
|
|
2098
|
+
'<div class="today-stat-item"><div class="today-stat-value">' + todayPrompts + '</div><div class="today-stat-label">Prompts</div></div>' +
|
|
2099
|
+
'<div class="today-stat-item"><div class="today-stat-value">' + todayTools + '</div><div class="today-stat-label">Tools</div></div>' +
|
|
2100
|
+
'<div class="today-stat-item"><div class="today-stat-value">' + todayHours + '</div><div class="today-stat-label">Hours</div></div>' +
|
|
2101
|
+
'</div>';
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
// ===== RENDER: BADGE PROGRESS =====
|
|
2105
|
+
function renderBadgeProgress() {
|
|
2106
|
+
const el = document.getElementById('overview-badge-progress');
|
|
2107
|
+
const badges = state.achievements?.badges || [];
|
|
2108
|
+
|
|
2109
|
+
const totalBadges = badges.length;
|
|
2110
|
+
const unlockedBadges = badges.filter(b => b.unlocked).length;
|
|
2111
|
+
|
|
2112
|
+
const locked = badges.filter(b => !b.unlocked && !b.secret);
|
|
2113
|
+
locked.sort((a, b) => (b.progress || 0) - (a.progress || 0));
|
|
2114
|
+
const nextBadge = locked[0];
|
|
2115
|
+
|
|
2116
|
+
const pct = totalBadges > 0 ? Math.round((unlockedBadges / totalBadges) * 100) : 0;
|
|
2117
|
+
|
|
2118
|
+
let html =
|
|
2119
|
+
'<div class="badge-progress-summary">' +
|
|
2120
|
+
'<span class="badge-progress-count">' + unlockedBadges + '</span>' +
|
|
2121
|
+
'<span class="badge-progress-total">/ ' + totalBadges + ' badges unlocked (' + pct + '%)</span>' +
|
|
2122
|
+
'</div>' +
|
|
2123
|
+
'<div class="badge-progress-row" style="margin-bottom:12px;">' +
|
|
2124
|
+
'<div class="badge-progress-track">' +
|
|
2125
|
+
'<div class="badge-progress-fill" style="width:' + pct + '%"></div>' +
|
|
2126
|
+
'</div>' +
|
|
2127
|
+
'</div>';
|
|
2128
|
+
|
|
2129
|
+
if (nextBadge) {
|
|
2130
|
+
const nextPct = Math.min(100, (nextBadge.progress || 0) * 100);
|
|
2131
|
+
html +=
|
|
2132
|
+
'<div class="badge-next-item">' +
|
|
2133
|
+
'<div class="badge-next-name">' + (nextBadge.icon || '\uD83C\uDFC5') + ' ' + escapeHtml(nextBadge.name) + '</div>' +
|
|
2134
|
+
'<div class="badge-progress-row">' +
|
|
2135
|
+
'<div class="badge-progress-track">' +
|
|
2136
|
+
'<div class="badge-progress-fill" style="width:' + nextPct + '%"></div>' +
|
|
2137
|
+
'</div>' +
|
|
2138
|
+
'<span class="badge-progress-text">' + formatNumber(nextBadge.value || 0) + '/' + formatNumber(nextBadge.nextThreshold || 0) + '</span>' +
|
|
2139
|
+
'</div>' +
|
|
2140
|
+
'<div class="badge-next-meta">' +
|
|
2141
|
+
'<span>' + escapeHtml(nextBadge.description || '') + '</span>' +
|
|
2142
|
+
'<span>' + nextPct.toFixed(0) + '%</span>' +
|
|
2143
|
+
'</div>' +
|
|
2144
|
+
'</div>';
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
el.innerHTML = html;
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
// ===== RENDER: AGENT BREAKDOWN =====
|
|
2151
|
+
function renderAgentBreakdown() {
|
|
2152
|
+
if (selectedAgent || !agentBreakdown || !agentBreakdown.sessionsPerAgent) return ''
|
|
2153
|
+
const entries = Object.entries(agentBreakdown.sessionsPerAgent).sort(([,a], [,b]) => b - a)
|
|
2154
|
+
if (entries.length <= 1) return ''
|
|
2155
|
+
const maxSessions = Math.max(...entries.map(([,c]) => c), 1)
|
|
2156
|
+
const agentColors = { 'claude-code': '#d97706', 'gemini-cli': '#4285f4', 'copilot-cli': '#6e40c9', 'opencode': '#10b981' }
|
|
2157
|
+
const agentNames = { 'claude-code': 'Claude Code', 'gemini-cli': 'Gemini CLI', 'copilot-cli': 'Copilot CLI', 'opencode': 'OpenCode' }
|
|
2158
|
+
|
|
2159
|
+
const bars = entries.map(([agent, count]) => {
|
|
2160
|
+
const pct = Math.round(count / maxSessions * 100)
|
|
2161
|
+
const hours = agentBreakdown.hoursPerAgent[agent] ?? 0
|
|
2162
|
+
const color = agentColors[agent] ?? 'var(--accent)'
|
|
2163
|
+
const name = agentNames[agent] ?? agent
|
|
2164
|
+
return '<div class="agent-bar">' +
|
|
2165
|
+
'<span class="agent-bar-label">' + escapeHtml(name) + '</span>' +
|
|
2166
|
+
'<div class="agent-bar-track"><div class="agent-bar-fill" style="width:' + pct + '%;background:' + color + '"></div></div>' +
|
|
2167
|
+
'<span class="agent-bar-value">' + count + ' / ' + hours + 'h</span>' +
|
|
2168
|
+
'</div>'
|
|
2169
|
+
}).join('')
|
|
2170
|
+
|
|
2171
|
+
const favName = agentNames[agentBreakdown.favoriteAgent] ?? agentBreakdown.favoriteAgent
|
|
2172
|
+
return '<div class="card agent-breakdown-card">' +
|
|
2173
|
+
'<h3>Agent Breakdown</h3>' +
|
|
2174
|
+
'<p style="margin:0 0 8px;font-size:0.85rem">Favorite: <strong>' + escapeHtml(favName) + '</strong> · ' + agentBreakdown.distinctAgents + ' agent' + (agentBreakdown.distinctAgents !== 1 ? 's' : '') + ' used</p>' +
|
|
2175
|
+
bars +
|
|
2176
|
+
'</div>'
|
|
2177
|
+
}
|
|
2178
|
+
|
|
1545
2179
|
// ===== RENDER: STATS =====
|
|
1546
2180
|
function renderOverviewHeatmap() {
|
|
1547
2181
|
const wrapper = document.getElementById('overview-heatmap');
|
|
@@ -1633,10 +2267,11 @@
|
|
|
1633
2267
|
</div>
|
|
1634
2268
|
${recent.map(s => {
|
|
1635
2269
|
const sessionTokens = (s.input_tokens || 0) + (s.output_tokens || 0) + (s.cache_creation_input_tokens || 0) + (s.cache_read_input_tokens || 0);
|
|
2270
|
+
const agentBadge = '<span class="agent-badge ' + (s.agent || 'claude-code') + '">' + escapeHtml(s.agent || 'claude-code') + '</span>';
|
|
1636
2271
|
return `
|
|
1637
2272
|
<div class="session-item">
|
|
1638
2273
|
<div class="session-date">${escapeHtml(formatDateTime(s.start_time || s.startTime || s.started_at || ''))}</div>
|
|
1639
|
-
<div class="session-project">${escapeHtml(s.project || s.projectName || 'Unknown')}</div>
|
|
2274
|
+
<div class="session-project">${escapeHtml(s.project || s.projectName || 'Unknown')}${agentBadge}</div>
|
|
1640
2275
|
<div class="session-stat">${formatDuration(s.duration_seconds || s.durationSeconds || 0)}</div>
|
|
1641
2276
|
<div class="session-stat">${formatNumber(s.prompt_count || s.prompts || s.promptCount || 0)}</div>
|
|
1642
2277
|
<div class="session-stat">${formatNumber(s.tool_count || s.tool_calls || s.toolCalls || 0)}</div>
|
|
@@ -1768,7 +2403,7 @@
|
|
|
1768
2403
|
});
|
|
1769
2404
|
|
|
1770
2405
|
let html = '';
|
|
1771
|
-
const catOrder = ['volume', 'token_usage', 'tool_mastery', 'time', 'session_behavior', 'behavioral', 'prompt_patterns', 'resilience', 'error_recovery', 'tool_combos', 'shipping', 'project_dedication', 'multi_agent', '
|
|
2406
|
+
const catOrder = ['volume', 'token_usage', 'tool_mastery', 'time', 'session_behavior', 'behavioral', 'prompt_patterns', 'resilience', 'error_recovery', 'tool_combos', 'shipping', 'project_dedication', 'multi_agent', 'wild_card', 'aspirational', 'secret'];
|
|
1772
2407
|
const catNames = {
|
|
1773
2408
|
volume: 'Volume',
|
|
1774
2409
|
token_usage: 'Token Usage',
|
|
@@ -1783,7 +2418,7 @@
|
|
|
1783
2418
|
shipping: 'Shipping & Projects',
|
|
1784
2419
|
project_dedication: 'Project Dedication',
|
|
1785
2420
|
multi_agent: 'Multi-Agent',
|
|
1786
|
-
|
|
2421
|
+
wild_card: 'Wild Card',
|
|
1787
2422
|
aspirational: 'Aspirational',
|
|
1788
2423
|
secret: 'Secret',
|
|
1789
2424
|
};
|
|
@@ -1907,6 +2542,106 @@
|
|
|
1907
2542
|
}
|
|
1908
2543
|
}
|
|
1909
2544
|
|
|
2545
|
+
// ===== RANK PROGRESSION MODAL =====
|
|
2546
|
+
const RANK_TIER_BRACKETS = [
|
|
2547
|
+
{ tier: 'Bronze', minRank: 1, maxRank: 100, color: 'var(--tier-bronze)' },
|
|
2548
|
+
{ tier: 'Silver', minRank: 101, maxRank: 200, color: 'var(--tier-silver)' },
|
|
2549
|
+
{ tier: 'Gold', minRank: 201, maxRank: 300, color: 'var(--tier-gold)' },
|
|
2550
|
+
{ tier: 'Diamond', minRank: 301, maxRank: 400, color: 'var(--tier-diamond)' },
|
|
2551
|
+
{ tier: 'Obsidian', minRank: 401, maxRank: 499, color: 'var(--tier-obsidian)' },
|
|
2552
|
+
{ tier: 'System Anomaly', minRank: 500, maxRank: 500, color: 'var(--tier-system-anomaly)' },
|
|
2553
|
+
];
|
|
2554
|
+
|
|
2555
|
+
function xpForRankClient(rank) {
|
|
2556
|
+
if (rank <= 0) return 0;
|
|
2557
|
+
return Math.floor(10 * Math.pow(rank, 2.2));
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
function openRankModal() {
|
|
2561
|
+
const xp = state.achievements?.xp;
|
|
2562
|
+
if (!xp) return;
|
|
2563
|
+
|
|
2564
|
+
const currentRank = xp.rankNumber || 1;
|
|
2565
|
+
const totalXP = xp.totalXP || 0;
|
|
2566
|
+
const body = document.getElementById('rank-modal-body');
|
|
2567
|
+
let html = '';
|
|
2568
|
+
|
|
2569
|
+
RANK_TIER_BRACKETS.forEach(bracket => {
|
|
2570
|
+
const isCurrent = currentRank >= bracket.minRank && currentRank <= bracket.maxRank;
|
|
2571
|
+
const isPast = currentRank > bracket.maxRank;
|
|
2572
|
+
const tierOpacity = !isPast && !isCurrent ? ' opacity:0.5;' : '';
|
|
2573
|
+
const xpStart = formatNumber(xpForRankClient(bracket.minRank));
|
|
2574
|
+
const xpEnd = formatNumber(xpForRankClient(bracket.maxRank));
|
|
2575
|
+
|
|
2576
|
+
html += `<div class="rank-tier-section" style="border-left-color:${bracket.color};${tierOpacity}">`;
|
|
2577
|
+
html += `<div class="rank-tier-header">`;
|
|
2578
|
+
html += `<span class="rank-tier-name" style="color:${bracket.color}">${escapeHtml(bracket.tier)}</span>`;
|
|
2579
|
+
html += `<span class="rank-tier-range">Rank ${bracket.minRank}-${bracket.maxRank} · ${xpStart}-${xpEnd} XP</span>`;
|
|
2580
|
+
html += `</div>`;
|
|
2581
|
+
html += `<div class="rank-track" style="color:${bracket.color}">`;
|
|
2582
|
+
|
|
2583
|
+
// Determine milestone ranks: tier start, every 25th within tier, tier end, plus current rank
|
|
2584
|
+
const milestones = new Set();
|
|
2585
|
+
milestones.add(bracket.minRank);
|
|
2586
|
+
for (let r = bracket.minRank; r <= bracket.maxRank; r += 25) {
|
|
2587
|
+
milestones.add(r);
|
|
2588
|
+
}
|
|
2589
|
+
milestones.add(bracket.maxRank);
|
|
2590
|
+
|
|
2591
|
+
// Always include the current rank if it's in this tier
|
|
2592
|
+
if (isCurrent) {
|
|
2593
|
+
milestones.add(currentRank);
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
const sortedMilestones = [...milestones].sort((a, b) => a - b);
|
|
2597
|
+
|
|
2598
|
+
sortedMilestones.forEach(rank => {
|
|
2599
|
+
const xpRequired = xpForRankClient(rank);
|
|
2600
|
+
const isUnlocked = rank <= currentRank;
|
|
2601
|
+
const isCurrentRank = rank === currentRank;
|
|
2602
|
+
const isMilestoneRank = rank % 25 === 0 || rank === bracket.minRank || rank === bracket.maxRank;
|
|
2603
|
+
|
|
2604
|
+
const nodeClasses = ['rank-node'];
|
|
2605
|
+
if (isCurrentRank) nodeClasses.push('current');
|
|
2606
|
+
if (!isUnlocked) nodeClasses.push('locked');
|
|
2607
|
+
|
|
2608
|
+
const dotClasses = ['rank-node-dot'];
|
|
2609
|
+
if (isUnlocked) dotClasses.push('unlocked');
|
|
2610
|
+
if (isMilestoneRank || isCurrentRank) dotClasses.push('milestone');
|
|
2611
|
+
|
|
2612
|
+
html += `<div class="${nodeClasses.join(' ')}" ${isCurrentRank ? 'id="rank-current-node"' : ''}>`;
|
|
2613
|
+
html += `<div class="${dotClasses.join(' ')}"></div>`;
|
|
2614
|
+
html += `<div class="rank-node-info">`;
|
|
2615
|
+
html += `<span class="rank-node-number" ${isUnlocked ? `style="color:${bracket.color}"` : ''}>Rank ${rank}</span>`;
|
|
2616
|
+
html += `<span class="rank-node-xp">${formatNumber(xpRequired)} XP</span>`;
|
|
2617
|
+
html += `</div>`;
|
|
2618
|
+
if (isCurrentRank) {
|
|
2619
|
+
html += `<span class="rank-node-label" style="color:${bracket.color};border-color:${bracket.color}">CURRENT</span>`;
|
|
2620
|
+
}
|
|
2621
|
+
html += `</div>`;
|
|
2622
|
+
});
|
|
2623
|
+
|
|
2624
|
+
html += `</div></div>`;
|
|
2625
|
+
});
|
|
2626
|
+
|
|
2627
|
+
body.innerHTML = html;
|
|
2628
|
+
|
|
2629
|
+
const overlay = document.getElementById('rank-modal-overlay');
|
|
2630
|
+
overlay.style.display = 'flex';
|
|
2631
|
+
|
|
2632
|
+
// Scroll to current rank after a tick to allow rendering
|
|
2633
|
+
requestAnimationFrame(() => {
|
|
2634
|
+
const currentNode = document.getElementById('rank-current-node');
|
|
2635
|
+
if (currentNode) {
|
|
2636
|
+
currentNode.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
2637
|
+
}
|
|
2638
|
+
});
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
function closeRankModal() {
|
|
2642
|
+
document.getElementById('rank-modal-overlay').style.display = 'none';
|
|
2643
|
+
}
|
|
2644
|
+
|
|
1910
2645
|
// ===== AUTO-REFRESH =====
|
|
1911
2646
|
function startAutoRefresh() {
|
|
1912
2647
|
setInterval(() => {
|
|
@@ -1917,9 +2652,25 @@
|
|
|
1917
2652
|
// ===== INIT =====
|
|
1918
2653
|
function init() {
|
|
1919
2654
|
setupTabs();
|
|
2655
|
+
setupAgentFilter();
|
|
1920
2656
|
setupTheme();
|
|
1921
2657
|
loadAllData();
|
|
1922
2658
|
startAutoRefresh();
|
|
2659
|
+
|
|
2660
|
+
// Rank modal: header badge click
|
|
2661
|
+
document.getElementById('header-rank-badge').addEventListener('click', openRankModal);
|
|
2662
|
+
|
|
2663
|
+
// Rank modal: overlay click to close
|
|
2664
|
+
document.getElementById('rank-modal-overlay').addEventListener('click', (e) => {
|
|
2665
|
+
if (e.target === e.currentTarget) closeRankModal();
|
|
2666
|
+
});
|
|
2667
|
+
|
|
2668
|
+
// Rank modal: escape key
|
|
2669
|
+
document.addEventListener('keydown', (e) => {
|
|
2670
|
+
if (e.key === 'Escape' && document.getElementById('rank-modal-overlay').style.display !== 'none') {
|
|
2671
|
+
closeRankModal();
|
|
2672
|
+
}
|
|
2673
|
+
});
|
|
1923
2674
|
}
|
|
1924
2675
|
|
|
1925
2676
|
document.addEventListener('DOMContentLoaded', init);
|