bashstats 0.2.0 → 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 -0
- package/README.md +159 -46
- package/dist/chunk-YAJO5WNW.js +2961 -0
- 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-7K77JJRD.js → chunk-CLSVLWCR.js} +263 -40
- 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 +194 -17
- package/dist/index.js +31 -5
- package/dist/static/index.html +836 -64
- package/package.json +1 -1
- package/dist/chunk-OYLQHCOY.js +0 -1489
- package/dist/chunk-OYLQHCOY.js.map +0 -1
- package/dist/hooks/chunk-7K77JJRD.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;
|
|
@@ -608,6 +752,17 @@
|
|
|
608
752
|
line-height: 1.4;
|
|
609
753
|
}
|
|
610
754
|
|
|
755
|
+
.badge-trigger {
|
|
756
|
+
font-size: 11px;
|
|
757
|
+
font-family: 'JetBrains Mono', monospace;
|
|
758
|
+
color: var(--accent);
|
|
759
|
+
line-height: 1.3;
|
|
760
|
+
padding: 3px 6px;
|
|
761
|
+
background: var(--bg-accent);
|
|
762
|
+
border-radius: 2px;
|
|
763
|
+
border-left: 2px solid var(--accent);
|
|
764
|
+
}
|
|
765
|
+
|
|
611
766
|
.badge-progress-row {
|
|
612
767
|
display: flex;
|
|
613
768
|
align-items: center;
|
|
@@ -982,19 +1137,19 @@
|
|
|
982
1137
|
}
|
|
983
1138
|
|
|
984
1139
|
/* ===== TIER COLORS ===== */
|
|
985
|
-
.tier-locked
|
|
986
|
-
.tier-bronze
|
|
987
|
-
.tier-silver
|
|
988
|
-
.tier-gold
|
|
989
|
-
.tier-diamond
|
|
990
|
-
.tier-
|
|
991
|
-
|
|
992
|
-
.tier-bg-locked
|
|
993
|
-
.tier-bg-bronze
|
|
994
|
-
.tier-bg-silver
|
|
995
|
-
.tier-bg-gold
|
|
996
|
-
.tier-bg-diamond
|
|
997
|
-
.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); }
|
|
998
1153
|
|
|
999
1154
|
/* ===== REFRESH INDICATOR ===== */
|
|
1000
1155
|
.refresh-indicator {
|
|
@@ -1010,6 +1165,304 @@
|
|
|
1010
1165
|
padding: 4px 8px;
|
|
1011
1166
|
box-shadow: 3px 3px 0 var(--shadow);
|
|
1012
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
|
+
}
|
|
1013
1466
|
</style>
|
|
1014
1467
|
</head>
|
|
1015
1468
|
<body>
|
|
@@ -1025,12 +1478,13 @@
|
|
|
1025
1478
|
</div>
|
|
1026
1479
|
</div>
|
|
1027
1480
|
<div class="header-right">
|
|
1028
|
-
<
|
|
1029
|
-
<
|
|
1030
|
-
<
|
|
1031
|
-
|
|
1032
|
-
</
|
|
1033
|
-
|
|
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>
|
|
1034
1488
|
<div class="streak-display" id="header-streak" style="display:none;">
|
|
1035
1489
|
<span id="streak-icon">🔥</span>
|
|
1036
1490
|
<span id="streak-days">0d</span>
|
|
@@ -1063,13 +1517,30 @@
|
|
|
1063
1517
|
<div class="card-title">Recent Badges</div>
|
|
1064
1518
|
<div id="overview-recent-badges" class="recent-badges-list"></div>
|
|
1065
1519
|
</div>
|
|
1066
|
-
<div class="card">
|
|
1520
|
+
<div class="card overview-rank-clickable" onclick="openRankModal()">
|
|
1067
1521
|
<div class="card-title">Current Rank</div>
|
|
1068
1522
|
<div id="overview-rank-card" class="rank-card-inner"></div>
|
|
1069
1523
|
</div>
|
|
1070
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>
|
|
1071
1540
|
<!-- Stat cards -->
|
|
1072
1541
|
<div class="stat-grid" id="overview-stat-cards"></div>
|
|
1542
|
+
<!-- Agent breakdown -->
|
|
1543
|
+
<div id="overview-agent-breakdown"></div>
|
|
1073
1544
|
<!-- Sparkline -->
|
|
1074
1545
|
<div class="sparkline-section card" id="overview-sparkline-card">
|
|
1075
1546
|
<div class="card-title">30-Day Activity</div>
|
|
@@ -1159,6 +1630,7 @@
|
|
|
1159
1630
|
<a class="settings-link" href="/api/achievements" target="_blank">/api/achievements</a>
|
|
1160
1631
|
<a class="settings-link" href="/api/activity" target="_blank">/api/activity</a>
|
|
1161
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>
|
|
1162
1634
|
</div>
|
|
1163
1635
|
</div>
|
|
1164
1636
|
</div>
|
|
@@ -1180,23 +1652,38 @@
|
|
|
1180
1652
|
<span id="refresh-text"></span>
|
|
1181
1653
|
</div>
|
|
1182
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
|
+
|
|
1183
1666
|
<script>
|
|
1184
1667
|
/* ===================================================================
|
|
1185
1668
|
bashstats Dashboard - Single Page Application
|
|
1186
1669
|
=================================================================== */
|
|
1187
1670
|
|
|
1188
1671
|
// ===== STATE =====
|
|
1672
|
+
let selectedAgent = ''
|
|
1673
|
+
let agentBreakdown = null
|
|
1674
|
+
|
|
1189
1675
|
const state = {
|
|
1190
1676
|
stats: null,
|
|
1191
1677
|
achievements: null,
|
|
1192
1678
|
activity: null,
|
|
1193
1679
|
sessions: null,
|
|
1680
|
+
weeklyGoals: null,
|
|
1194
1681
|
lastFetch: null,
|
|
1195
1682
|
};
|
|
1196
1683
|
|
|
1197
1684
|
// ===== TIER HELPERS =====
|
|
1198
|
-
const TIER_NAMES = ['Locked', 'Bronze', 'Silver', 'Gold', 'Diamond', '
|
|
1199
|
-
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'];
|
|
1200
1687
|
|
|
1201
1688
|
function tierClass(tier) {
|
|
1202
1689
|
return TIER_CLASSES[tier] || 'locked';
|
|
@@ -1209,20 +1696,21 @@
|
|
|
1209
1696
|
'var(--tier-silver)',
|
|
1210
1697
|
'var(--tier-gold)',
|
|
1211
1698
|
'var(--tier-diamond)',
|
|
1212
|
-
'var(--tier-
|
|
1699
|
+
'var(--tier-singularity)',
|
|
1213
1700
|
];
|
|
1214
1701
|
return colors[tier] || colors[0];
|
|
1215
1702
|
}
|
|
1216
1703
|
|
|
1217
|
-
function rankColor(
|
|
1704
|
+
function rankColor(rankTier) {
|
|
1218
1705
|
const map = {
|
|
1219
1706
|
'Bronze': 'var(--tier-bronze)',
|
|
1220
1707
|
'Silver': 'var(--tier-silver)',
|
|
1221
1708
|
'Gold': 'var(--tier-gold)',
|
|
1222
1709
|
'Diamond': 'var(--tier-diamond)',
|
|
1223
1710
|
'Obsidian': 'var(--tier-obsidian)',
|
|
1711
|
+
'System Anomaly': 'var(--tier-system-anomaly)',
|
|
1224
1712
|
};
|
|
1225
|
-
return map[
|
|
1713
|
+
return map[rankTier] || 'var(--tier-bronze)';
|
|
1226
1714
|
}
|
|
1227
1715
|
|
|
1228
1716
|
// ===== FORMAT HELPERS =====
|
|
@@ -1283,17 +1771,22 @@
|
|
|
1283
1771
|
}
|
|
1284
1772
|
|
|
1285
1773
|
async function loadAllData() {
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1288
|
-
fetchJSON('/api/
|
|
1289
|
-
fetchJSON('/api/
|
|
1290
|
-
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'),
|
|
1291
1782
|
]);
|
|
1292
1783
|
|
|
1293
|
-
state.stats = stats;
|
|
1294
|
-
state.achievements = achievements;
|
|
1295
|
-
state.activity = activity;
|
|
1296
|
-
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;
|
|
1297
1790
|
state.lastFetch = new Date();
|
|
1298
1791
|
|
|
1299
1792
|
renderAll();
|
|
@@ -1324,6 +1817,14 @@
|
|
|
1324
1817
|
});
|
|
1325
1818
|
}
|
|
1326
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
|
+
|
|
1327
1828
|
// ===== THEME SWITCHING =====
|
|
1328
1829
|
function setupTheme() {
|
|
1329
1830
|
const sel = document.getElementById('settings-theme');
|
|
@@ -1365,15 +1866,8 @@
|
|
|
1365
1866
|
const rankDot = document.getElementById('header-rank-dot');
|
|
1366
1867
|
const rankText = document.getElementById('header-rank-text');
|
|
1367
1868
|
rankBadge.style.display = 'inline-flex';
|
|
1368
|
-
rankDot.style.background = rankColor(xp.
|
|
1369
|
-
rankText.textContent = xp.
|
|
1370
|
-
|
|
1371
|
-
const xpContainer = document.getElementById('header-xp-container');
|
|
1372
|
-
const xpLabel = document.getElementById('header-xp-label');
|
|
1373
|
-
const xpFill = document.getElementById('header-xp-fill');
|
|
1374
|
-
xpContainer.style.display = 'flex';
|
|
1375
|
-
xpLabel.textContent = formatNumber(xp.totalXP) + ' XP';
|
|
1376
|
-
xpFill.style.width = Math.min(100, (xp.progress || 0) * 100) + '%';
|
|
1869
|
+
rankDot.style.background = rankColor(xp.rankTier);
|
|
1870
|
+
rankText.textContent = `Rank ${xp.rankNumber}`;
|
|
1377
1871
|
}
|
|
1378
1872
|
|
|
1379
1873
|
if (time) {
|
|
@@ -1410,13 +1904,14 @@
|
|
|
1410
1904
|
content.style.display = 'block';
|
|
1411
1905
|
|
|
1412
1906
|
const lt = state.stats.lifetime || {};
|
|
1907
|
+
const filesTouched = (lt.totalFilesRead || 0) + (lt.totalFilesEdited || 0) + (lt.totalFilesCreated || 0);
|
|
1413
1908
|
const cards = [
|
|
1414
1909
|
{ label: 'Sessions', value: formatNumber(lt.totalSessions || 0), sub: 'lifetime' },
|
|
1415
1910
|
{ label: 'Prompts', value: formatNumber(lt.totalPrompts || 0), sub: 'messages sent' },
|
|
1416
1911
|
{ label: 'Tool Calls', value: formatNumber(lt.totalToolCalls || 0), sub: 'total invocations' },
|
|
1417
1912
|
{ label: 'Hours', value: formatHours(lt.totalDurationSeconds || 0), sub: 'coding time' },
|
|
1418
|
-
{ label: 'Tokens Used', value: formatTokens(lt.totalTokens || 0), sub: '
|
|
1419
|
-
{ label: '
|
|
1913
|
+
{ label: 'Tokens Used', value: formatTokens(lt.totalTokens || 0), sub: 'all token types' },
|
|
1914
|
+
{ label: 'Files Touched', value: formatNumber(filesTouched), sub: 'read + edit + create' },
|
|
1420
1915
|
];
|
|
1421
1916
|
|
|
1422
1917
|
const cardsEl = document.getElementById('overview-stat-cards');
|
|
@@ -1428,6 +1923,10 @@
|
|
|
1428
1923
|
</div>
|
|
1429
1924
|
`).join('');
|
|
1430
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
|
+
|
|
1431
1930
|
// Sparkline
|
|
1432
1931
|
renderSparkline();
|
|
1433
1932
|
|
|
@@ -1440,6 +1939,11 @@
|
|
|
1440
1939
|
|
|
1441
1940
|
// Rank card
|
|
1442
1941
|
renderOverviewRank();
|
|
1942
|
+
|
|
1943
|
+
// Middle row cards
|
|
1944
|
+
renderWeeklyGoals();
|
|
1945
|
+
renderStreakToday();
|
|
1946
|
+
renderBadgeProgress();
|
|
1443
1947
|
}
|
|
1444
1948
|
|
|
1445
1949
|
function renderEmptyOverview() {
|
|
@@ -1449,7 +1953,7 @@
|
|
|
1449
1953
|
<div class="stat-card"><div class="stat-card-label">Tool Calls</div><div class="stat-card-number mono">0</div></div>
|
|
1450
1954
|
<div class="stat-card"><div class="stat-card-label">Hours</div><div class="stat-card-number mono">0</div></div>
|
|
1451
1955
|
<div class="stat-card"><div class="stat-card-label">Tokens Used</div><div class="stat-card-number mono">0</div></div>
|
|
1452
|
-
<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>
|
|
1453
1957
|
`;
|
|
1454
1958
|
}
|
|
1455
1959
|
|
|
@@ -1465,7 +1969,7 @@
|
|
|
1465
1969
|
for (let i = 29; i >= 0; i--) {
|
|
1466
1970
|
const d = new Date(today);
|
|
1467
1971
|
d.setDate(d.getDate() - i);
|
|
1468
|
-
const dateStr = d.
|
|
1972
|
+
const dateStr = d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0');
|
|
1469
1973
|
const found = activity.find(a => a.date === dateStr);
|
|
1470
1974
|
const val = found ? (found.sessions + found.prompts + found.tool_calls) : 0;
|
|
1471
1975
|
days.push({ date: dateStr, value: val });
|
|
@@ -1514,23 +2018,164 @@
|
|
|
1514
2018
|
}
|
|
1515
2019
|
|
|
1516
2020
|
const pct = Math.min(100, (xp.progress || 0) * 100);
|
|
1517
|
-
const rc = rankColor(xp.
|
|
1518
|
-
const
|
|
2021
|
+
const rc = rankColor(xp.rankTier);
|
|
2022
|
+
const rankNum = xp.rankNumber || 0;
|
|
1519
2023
|
el.innerHTML = `
|
|
1520
2024
|
<div class="rank-card-icon" style="border-color:${rc};background:${rc}22">
|
|
1521
|
-
<span class="rank-card-icon-text" style="color:${rc}">${
|
|
2025
|
+
<span class="rank-card-icon-text" style="color:${rc}">${rankNum}</span>
|
|
1522
2026
|
</div>
|
|
1523
2027
|
<div class="rank-card-info">
|
|
1524
|
-
<div class="rank-card-rank" style="color:${rc}"
|
|
2028
|
+
<div class="rank-card-rank" style="color:${rc}">Rank ${rankNum} · ${escapeHtml(xp.rankTier)}</div>
|
|
1525
2029
|
<div class="rank-card-xp">${formatNumber(xp.totalXP)} / ${formatNumber(xp.nextRankXP)} XP</div>
|
|
1526
2030
|
<div class="rank-progress-track">
|
|
1527
2031
|
<div class="rank-progress-fill" style="width:${pct}%;background:${rc}"></div>
|
|
1528
2032
|
</div>
|
|
1529
|
-
<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>
|
|
1530
2034
|
</div>
|
|
1531
2035
|
`;
|
|
1532
2036
|
}
|
|
1533
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
|
+
|
|
1534
2179
|
// ===== RENDER: STATS =====
|
|
1535
2180
|
function renderOverviewHeatmap() {
|
|
1536
2181
|
const wrapper = document.getElementById('overview-heatmap');
|
|
@@ -1621,11 +2266,12 @@
|
|
|
1621
2266
|
<div>Date</div><div>Project</div><div style="text-align:right">Duration</div><div style="text-align:right">Prompts</div><div style="text-align:right">Tools</div><div style="text-align:right">Tokens</div>
|
|
1622
2267
|
</div>
|
|
1623
2268
|
${recent.map(s => {
|
|
1624
|
-
const sessionTokens = (s.input_tokens || 0) + (s.output_tokens || 0);
|
|
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>';
|
|
1625
2271
|
return `
|
|
1626
2272
|
<div class="session-item">
|
|
1627
2273
|
<div class="session-date">${escapeHtml(formatDateTime(s.start_time || s.startTime || s.started_at || ''))}</div>
|
|
1628
|
-
<div class="session-project">${escapeHtml(s.project || s.projectName || 'Unknown')}</div>
|
|
2274
|
+
<div class="session-project">${escapeHtml(s.project || s.projectName || 'Unknown')}${agentBadge}</div>
|
|
1629
2275
|
<div class="session-stat">${formatDuration(s.duration_seconds || s.durationSeconds || 0)}</div>
|
|
1630
2276
|
<div class="session-stat">${formatNumber(s.prompt_count || s.prompts || s.promptCount || 0)}</div>
|
|
1631
2277
|
<div class="session-stat">${formatNumber(s.tool_count || s.tool_calls || s.toolCalls || 0)}</div>
|
|
@@ -1671,12 +2317,12 @@
|
|
|
1671
2317
|
|
|
1672
2318
|
// Token Usage
|
|
1673
2319
|
html += buildStatsSection('Token Usage', [
|
|
1674
|
-
['Total Tokens', formatTokens(
|
|
2320
|
+
['Total Tokens', formatTokens(lt.totalTokens || 0)],
|
|
1675
2321
|
['Input Tokens', formatTokens(lt.totalInputTokens)],
|
|
1676
2322
|
['Output Tokens', formatTokens(lt.totalOutputTokens)],
|
|
1677
2323
|
['Cache Read Tokens', formatTokens(lt.totalCacheReadTokens)],
|
|
1678
2324
|
['Cache Creation Tokens', formatTokens(lt.totalCacheCreationTokens)],
|
|
1679
|
-
['Avg Tokens / Session', formatTokens(lt.totalSessions ? Math.round((
|
|
2325
|
+
['Avg Tokens / Session', formatTokens(lt.totalSessions ? Math.round((lt.totalTokens || 0) / lt.totalSessions) : 0)],
|
|
1680
2326
|
]);
|
|
1681
2327
|
|
|
1682
2328
|
// Tool Breakdown
|
|
@@ -1757,16 +2403,24 @@
|
|
|
1757
2403
|
});
|
|
1758
2404
|
|
|
1759
2405
|
let html = '';
|
|
1760
|
-
const catOrder = ['volume', '
|
|
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'];
|
|
1761
2407
|
const catNames = {
|
|
1762
2408
|
volume: 'Volume',
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
time: 'Time',
|
|
1766
|
-
|
|
1767
|
-
|
|
2409
|
+
token_usage: 'Token Usage',
|
|
2410
|
+
tool_mastery: 'Tool Mastery',
|
|
2411
|
+
time: 'Time & Patterns',
|
|
2412
|
+
session_behavior: 'Session Behavior',
|
|
2413
|
+
behavioral: 'Behavioral',
|
|
2414
|
+
prompt_patterns: 'Prompt Patterns',
|
|
2415
|
+
resilience: 'Resilience',
|
|
2416
|
+
error_recovery: 'Error & Recovery',
|
|
2417
|
+
tool_combos: 'Tool Combos',
|
|
2418
|
+
shipping: 'Shipping & Projects',
|
|
2419
|
+
project_dedication: 'Project Dedication',
|
|
2420
|
+
multi_agent: 'Multi-Agent',
|
|
2421
|
+
wild_card: 'Wild Card',
|
|
2422
|
+
aspirational: 'Aspirational',
|
|
1768
2423
|
secret: 'Secret',
|
|
1769
|
-
general: 'General',
|
|
1770
2424
|
};
|
|
1771
2425
|
|
|
1772
2426
|
// Sort: known categories first, then others
|
|
@@ -1786,6 +2440,7 @@
|
|
|
1786
2440
|
const cardClass = isSecretLocked ? 'badge-card secret-locked' : (isLocked ? 'badge-card locked' : 'badge-card');
|
|
1787
2441
|
const displayName = isSecretLocked ? '???' : b.name;
|
|
1788
2442
|
const displayDesc = isSecretLocked ? 'Unlock this secret badge to reveal it.' : (b.description || '');
|
|
2443
|
+
const displayTrigger = isSecretLocked ? '' : (b.trigger || '');
|
|
1789
2444
|
const pct = b.maxed ? 100 : Math.min(100, (b.progress || 0) * 100);
|
|
1790
2445
|
const fillClass = b.maxed ? 'badge-progress-fill maxed' : 'badge-progress-fill';
|
|
1791
2446
|
|
|
@@ -1799,6 +2454,7 @@
|
|
|
1799
2454
|
<span class="badge-tier-label" style="color:${tierColor(b.tier)};border-color:${tierColor(b.tier)}">${escapeHtml(b.tierName || TIER_NAMES[b.tier] || 'Locked')}</span>
|
|
1800
2455
|
</div>
|
|
1801
2456
|
${displayDesc ? `<div class="badge-description">${escapeHtml(displayDesc)}</div>` : ''}
|
|
2457
|
+
${displayTrigger ? `<div class="badge-trigger">${escapeHtml(displayTrigger)}</div>` : ''}
|
|
1802
2458
|
<div class="badge-progress-row">
|
|
1803
2459
|
<div class="badge-progress-track">
|
|
1804
2460
|
<div class="${fillClass}" style="width:${pct}%"></div>
|
|
@@ -1886,6 +2542,106 @@
|
|
|
1886
2542
|
}
|
|
1887
2543
|
}
|
|
1888
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
|
+
|
|
1889
2645
|
// ===== AUTO-REFRESH =====
|
|
1890
2646
|
function startAutoRefresh() {
|
|
1891
2647
|
setInterval(() => {
|
|
@@ -1896,9 +2652,25 @@
|
|
|
1896
2652
|
// ===== INIT =====
|
|
1897
2653
|
function init() {
|
|
1898
2654
|
setupTabs();
|
|
2655
|
+
setupAgentFilter();
|
|
1899
2656
|
setupTheme();
|
|
1900
2657
|
loadAllData();
|
|
1901
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
|
+
});
|
|
1902
2674
|
}
|
|
1903
2675
|
|
|
1904
2676
|
document.addEventListener('DOMContentLoaded', init);
|