@cocaxcode/ai-context-inspector 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +330 -76
- package/dist/{chunk-UFTX3Z5M.js → chunk-T5N3KKUJ.js} +1068 -93
- package/dist/index.js +2 -2
- package/dist/{server-AOHGLJDI.js → server-EMQUALI5.js} +1 -1
- package/package.json +3 -2
|
@@ -1242,39 +1242,74 @@ async function runAllScanners(config) {
|
|
|
1242
1242
|
|
|
1243
1243
|
// src/report/styles.ts
|
|
1244
1244
|
var CSS_STYLES = `
|
|
1245
|
+
/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
1246
|
+
AI Context Inspector \u2014 Dashboard Styles
|
|
1247
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
|
|
1248
|
+
|
|
1245
1249
|
:root {
|
|
1246
1250
|
--bg: #0a0a0f;
|
|
1251
|
+
--bg-alt: #0e0e16;
|
|
1247
1252
|
--bg-card: #12121a;
|
|
1248
1253
|
--bg-card-hover: #1a1a25;
|
|
1249
1254
|
--border: #2a2a3a;
|
|
1255
|
+
--border-hover: #3a3a4a;
|
|
1250
1256
|
--text: #e0e0e8;
|
|
1251
1257
|
--text-dim: #8888a0;
|
|
1252
1258
|
--text-bright: #ffffff;
|
|
1253
1259
|
--accent: #00d4ff;
|
|
1254
1260
|
--accent-dim: #0099bb;
|
|
1261
|
+
--accent-glow: #00d4ff30;
|
|
1255
1262
|
--green: #00e676;
|
|
1256
1263
|
--red: #ff5252;
|
|
1257
1264
|
--orange: #ffab40;
|
|
1258
1265
|
--purple: #b388ff;
|
|
1266
|
+
--pink: #ff80ab;
|
|
1267
|
+
--blue: #4285f4;
|
|
1259
1268
|
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
|
|
1260
1269
|
--font-sans: system-ui, -apple-system, sans-serif;
|
|
1261
|
-
--radius:
|
|
1270
|
+
--radius: 10px;
|
|
1271
|
+
--radius-sm: 6px;
|
|
1272
|
+
--shadow: 0 2px 12px rgba(0,0,0,0.3);
|
|
1273
|
+
--shadow-hover: 0 4px 20px rgba(0,212,255,0.1);
|
|
1274
|
+
--transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
1262
1275
|
}
|
|
1263
1276
|
|
|
1264
1277
|
@media (prefers-color-scheme: light) {
|
|
1265
|
-
:root {
|
|
1278
|
+
:root:not([data-theme="dark"]) {
|
|
1266
1279
|
--bg: #f5f5f8;
|
|
1280
|
+
--bg-alt: #eeeef3;
|
|
1267
1281
|
--bg-card: #ffffff;
|
|
1268
1282
|
--bg-card-hover: #f0f0f5;
|
|
1269
1283
|
--border: #d0d0dd;
|
|
1284
|
+
--border-hover: #b0b0c0;
|
|
1270
1285
|
--text: #2a2a3a;
|
|
1271
1286
|
--text-dim: #666680;
|
|
1272
1287
|
--text-bright: #000000;
|
|
1273
1288
|
--accent: #0088cc;
|
|
1274
1289
|
--accent-dim: #006699;
|
|
1290
|
+
--accent-glow: #0088cc20;
|
|
1291
|
+
--shadow: 0 2px 12px rgba(0,0,0,0.08);
|
|
1292
|
+
--shadow-hover: 0 4px 20px rgba(0,136,204,0.1);
|
|
1275
1293
|
}
|
|
1276
1294
|
}
|
|
1277
1295
|
|
|
1296
|
+
[data-theme="light"] {
|
|
1297
|
+
--bg: #f5f5f8;
|
|
1298
|
+
--bg-alt: #eeeef3;
|
|
1299
|
+
--bg-card: #ffffff;
|
|
1300
|
+
--bg-card-hover: #f0f0f5;
|
|
1301
|
+
--border: #d0d0dd;
|
|
1302
|
+
--border-hover: #b0b0c0;
|
|
1303
|
+
--text: #2a2a3a;
|
|
1304
|
+
--text-dim: #666680;
|
|
1305
|
+
--text-bright: #000000;
|
|
1306
|
+
--accent: #0088cc;
|
|
1307
|
+
--accent-dim: #006699;
|
|
1308
|
+
--accent-glow: #0088cc20;
|
|
1309
|
+
--shadow: 0 2px 12px rgba(0,0,0,0.08);
|
|
1310
|
+
--shadow-hover: 0 4px 20px rgba(0,136,204,0.1);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1278
1313
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1279
1314
|
|
|
1280
1315
|
body {
|
|
@@ -1285,6 +1320,72 @@ body {
|
|
|
1285
1320
|
min-height: 100vh;
|
|
1286
1321
|
}
|
|
1287
1322
|
|
|
1323
|
+
/* \u2500\u2500 Nav Bar \u2500\u2500 */
|
|
1324
|
+
.nav-bar {
|
|
1325
|
+
position: sticky;
|
|
1326
|
+
top: 0;
|
|
1327
|
+
z-index: 100;
|
|
1328
|
+
background: color-mix(in srgb, var(--bg) 85%, transparent);
|
|
1329
|
+
backdrop-filter: blur(12px);
|
|
1330
|
+
-webkit-backdrop-filter: blur(12px);
|
|
1331
|
+
border-bottom: 1px solid var(--border);
|
|
1332
|
+
padding: 0.5rem 2rem;
|
|
1333
|
+
display: flex;
|
|
1334
|
+
align-items: center;
|
|
1335
|
+
justify-content: space-between;
|
|
1336
|
+
gap: 1rem;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
.nav-links {
|
|
1340
|
+
display: flex;
|
|
1341
|
+
gap: 0.25rem;
|
|
1342
|
+
flex-wrap: wrap;
|
|
1343
|
+
align-items: center;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
.nav-link {
|
|
1347
|
+
padding: 0.3rem 0.7rem;
|
|
1348
|
+
border-radius: var(--radius-sm);
|
|
1349
|
+
font-size: 0.75rem;
|
|
1350
|
+
font-family: var(--font-mono);
|
|
1351
|
+
color: var(--text-dim);
|
|
1352
|
+
text-decoration: none;
|
|
1353
|
+
cursor: pointer;
|
|
1354
|
+
transition: all var(--transition);
|
|
1355
|
+
border: 1px solid transparent;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
.nav-link:hover {
|
|
1359
|
+
color: var(--accent);
|
|
1360
|
+
background: var(--accent-glow);
|
|
1361
|
+
border-color: var(--accent);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
.nav-actions {
|
|
1365
|
+
display: flex;
|
|
1366
|
+
gap: 0.5rem;
|
|
1367
|
+
align-items: center;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
.nav-btn {
|
|
1371
|
+
background: none;
|
|
1372
|
+
border: 1px solid var(--border);
|
|
1373
|
+
color: var(--text-dim);
|
|
1374
|
+
padding: 0.3rem 0.6rem;
|
|
1375
|
+
border-radius: var(--radius-sm);
|
|
1376
|
+
font-size: 0.8rem;
|
|
1377
|
+
cursor: pointer;
|
|
1378
|
+
transition: all var(--transition);
|
|
1379
|
+
font-family: var(--font-mono);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
.nav-btn:hover {
|
|
1383
|
+
color: var(--accent);
|
|
1384
|
+
border-color: var(--accent);
|
|
1385
|
+
background: var(--accent-glow);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/* \u2500\u2500 Container \u2500\u2500 */
|
|
1288
1389
|
.container {
|
|
1289
1390
|
max-width: 1200px;
|
|
1290
1391
|
margin: 0 auto;
|
|
@@ -1294,21 +1395,22 @@ body {
|
|
|
1294
1395
|
/* \u2500\u2500 Header \u2500\u2500 */
|
|
1295
1396
|
.header {
|
|
1296
1397
|
text-align: center;
|
|
1297
|
-
padding:
|
|
1298
|
-
|
|
1299
|
-
margin-bottom: 1.5rem;
|
|
1398
|
+
padding: 2.5rem 0 2rem;
|
|
1399
|
+
margin-bottom: 2rem;
|
|
1300
1400
|
}
|
|
1301
1401
|
|
|
1302
1402
|
.header h1 {
|
|
1303
1403
|
font-family: var(--font-mono);
|
|
1304
|
-
font-size: 1.
|
|
1404
|
+
font-size: 1.8rem;
|
|
1305
1405
|
color: var(--accent);
|
|
1306
1406
|
margin-bottom: 0.5rem;
|
|
1407
|
+
letter-spacing: -0.02em;
|
|
1307
1408
|
}
|
|
1308
1409
|
|
|
1309
1410
|
.header .subtitle {
|
|
1310
1411
|
color: var(--text-dim);
|
|
1311
1412
|
font-size: 0.85rem;
|
|
1413
|
+
font-family: var(--font-mono);
|
|
1312
1414
|
}
|
|
1313
1415
|
|
|
1314
1416
|
.badges {
|
|
@@ -1336,21 +1438,174 @@ body {
|
|
|
1336
1438
|
.badge--green { border-color: var(--green); color: var(--green); }
|
|
1337
1439
|
.badge--purple { border-color: var(--purple); color: var(--purple); }
|
|
1338
1440
|
.badge--orange { border-color: var(--orange); color: var(--orange); }
|
|
1339
|
-
.badge--blue { border-color:
|
|
1441
|
+
.badge--blue { border-color: var(--blue); color: var(--blue); }
|
|
1442
|
+
.badge--pink { border-color: var(--pink); color: var(--pink); }
|
|
1443
|
+
|
|
1444
|
+
/* \u2500\u2500 Stats Grid \u2500\u2500 */
|
|
1445
|
+
.stats-grid {
|
|
1446
|
+
display: grid;
|
|
1447
|
+
grid-template-columns: repeat(6, 1fr);
|
|
1448
|
+
gap: 1rem;
|
|
1449
|
+
margin: 2rem 0;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
.stat-card {
|
|
1453
|
+
background: var(--bg-card);
|
|
1454
|
+
border: 1px solid var(--border);
|
|
1455
|
+
border-radius: var(--radius);
|
|
1456
|
+
padding: 1.2rem 1rem;
|
|
1457
|
+
text-align: center;
|
|
1458
|
+
transition: all var(--transition);
|
|
1459
|
+
cursor: default;
|
|
1460
|
+
position: relative;
|
|
1461
|
+
overflow: hidden;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
.stat-card::before {
|
|
1465
|
+
content: '';
|
|
1466
|
+
position: absolute;
|
|
1467
|
+
top: 0;
|
|
1468
|
+
left: 0;
|
|
1469
|
+
right: 0;
|
|
1470
|
+
height: 3px;
|
|
1471
|
+
background: var(--stat-color, var(--accent));
|
|
1472
|
+
opacity: 0.8;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
.stat-card:hover {
|
|
1476
|
+
border-color: var(--stat-color, var(--accent));
|
|
1477
|
+
box-shadow: var(--shadow-hover);
|
|
1478
|
+
transform: translateY(-2px);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
.stat-icon {
|
|
1482
|
+
font-size: 1.5rem;
|
|
1483
|
+
margin-bottom: 0.3rem;
|
|
1484
|
+
display: block;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
.stat-number {
|
|
1488
|
+
font-size: 2rem;
|
|
1489
|
+
font-weight: 700;
|
|
1490
|
+
font-family: var(--font-mono);
|
|
1491
|
+
color: var(--stat-color, var(--accent));
|
|
1492
|
+
line-height: 1.1;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
.stat-label {
|
|
1496
|
+
font-size: 0.7rem;
|
|
1497
|
+
color: var(--text-dim);
|
|
1498
|
+
text-transform: uppercase;
|
|
1499
|
+
letter-spacing: 0.08em;
|
|
1500
|
+
font-family: var(--font-mono);
|
|
1501
|
+
margin-top: 0.3rem;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
/* \u2500\u2500 Ecosystem Map \u2500\u2500 */
|
|
1505
|
+
.ecosystem-map {
|
|
1506
|
+
margin: 2rem 0;
|
|
1507
|
+
border: 1px solid var(--border);
|
|
1508
|
+
border-radius: var(--radius);
|
|
1509
|
+
background: var(--bg-alt);
|
|
1510
|
+
overflow: hidden;
|
|
1511
|
+
position: relative;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
.ecosystem-svg {
|
|
1515
|
+
width: 100%;
|
|
1516
|
+
height: auto;
|
|
1517
|
+
display: block;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
.eco-connection {
|
|
1521
|
+
stroke: var(--border);
|
|
1522
|
+
stroke-width: 1.5;
|
|
1523
|
+
fill: none;
|
|
1524
|
+
stroke-dasharray: 6 4;
|
|
1525
|
+
animation: dashFlow 20s linear infinite;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
.eco-connection--active {
|
|
1529
|
+
stroke: var(--accent);
|
|
1530
|
+
stroke-width: 2;
|
|
1531
|
+
opacity: 0.6;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
@keyframes dashFlow {
|
|
1535
|
+
to { stroke-dashoffset: -100; }
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
.eco-node {
|
|
1539
|
+
cursor: pointer;
|
|
1540
|
+
transition: transform 0.2s ease;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
.eco-node:hover {
|
|
1544
|
+
transform: scale(1.1);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
.eco-node-circle {
|
|
1548
|
+
transition: all 0.2s ease;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
.eco-node:hover .eco-node-circle {
|
|
1552
|
+
filter: brightness(1.3);
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
.eco-center-circle {
|
|
1556
|
+
filter: drop-shadow(0 0 12px var(--accent-glow));
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
.eco-label {
|
|
1560
|
+
font-family: var(--font-mono);
|
|
1561
|
+
font-size: 11px;
|
|
1562
|
+
fill: var(--text);
|
|
1563
|
+
text-anchor: middle;
|
|
1564
|
+
pointer-events: none;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
.eco-label--center {
|
|
1568
|
+
font-size: 14px;
|
|
1569
|
+
font-weight: 700;
|
|
1570
|
+
fill: var(--text-bright);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
.eco-label--count {
|
|
1574
|
+
font-size: 10px;
|
|
1575
|
+
fill: var(--text-dim);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
.eco-item-circle {
|
|
1579
|
+
opacity: 0.7;
|
|
1580
|
+
transition: opacity 0.2s;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
.eco-item-circle:hover {
|
|
1584
|
+
opacity: 1;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
.eco-dimmed {
|
|
1588
|
+
opacity: 0.2;
|
|
1589
|
+
}
|
|
1340
1590
|
|
|
1341
1591
|
/* \u2500\u2500 Search \u2500\u2500 */
|
|
1342
1592
|
.search-bar {
|
|
1343
1593
|
position: sticky;
|
|
1344
|
-
top:
|
|
1594
|
+
top: 45px;
|
|
1345
1595
|
z-index: 10;
|
|
1346
|
-
background: var(--bg);
|
|
1596
|
+
background: color-mix(in srgb, var(--bg) 90%, transparent);
|
|
1597
|
+
backdrop-filter: blur(8px);
|
|
1598
|
+
-webkit-backdrop-filter: blur(8px);
|
|
1347
1599
|
padding: 0.75rem 0;
|
|
1348
|
-
margin-bottom:
|
|
1600
|
+
margin-bottom: 1.5rem;
|
|
1601
|
+
display: flex;
|
|
1602
|
+
align-items: center;
|
|
1603
|
+
gap: 0.75rem;
|
|
1349
1604
|
}
|
|
1350
1605
|
|
|
1351
1606
|
.search-bar input {
|
|
1352
|
-
|
|
1353
|
-
padding: 0.
|
|
1607
|
+
flex: 1;
|
|
1608
|
+
padding: 0.65rem 1rem 0.65rem 2.5rem;
|
|
1354
1609
|
border: 1px solid var(--border);
|
|
1355
1610
|
border-radius: var(--radius);
|
|
1356
1611
|
background: var(--bg-card);
|
|
@@ -1358,11 +1613,47 @@ body {
|
|
|
1358
1613
|
font-family: var(--font-mono);
|
|
1359
1614
|
font-size: 0.85rem;
|
|
1360
1615
|
outline: none;
|
|
1361
|
-
transition:
|
|
1616
|
+
transition: all var(--transition);
|
|
1362
1617
|
}
|
|
1363
1618
|
|
|
1364
1619
|
.search-bar input:focus {
|
|
1365
1620
|
border-color: var(--accent);
|
|
1621
|
+
box-shadow: 0 0 0 3px var(--accent-glow);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
.search-icon {
|
|
1625
|
+
position: absolute;
|
|
1626
|
+
left: 0.85rem;
|
|
1627
|
+
color: var(--text-dim);
|
|
1628
|
+
font-size: 0.9rem;
|
|
1629
|
+
pointer-events: none;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
.search-bar-inner {
|
|
1633
|
+
position: relative;
|
|
1634
|
+
flex: 1;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
.search-results-count {
|
|
1638
|
+
font-size: 0.75rem;
|
|
1639
|
+
color: var(--text-dim);
|
|
1640
|
+
font-family: var(--font-mono);
|
|
1641
|
+
white-space: nowrap;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
.search-kbd {
|
|
1645
|
+
font-size: 0.65rem;
|
|
1646
|
+
color: var(--text-dim);
|
|
1647
|
+
font-family: var(--font-mono);
|
|
1648
|
+
padding: 0.1rem 0.4rem;
|
|
1649
|
+
border: 1px solid var(--border);
|
|
1650
|
+
border-radius: 3px;
|
|
1651
|
+
position: absolute;
|
|
1652
|
+
right: 0.7rem;
|
|
1653
|
+
top: 50%;
|
|
1654
|
+
transform: translateY(-50%);
|
|
1655
|
+
pointer-events: none;
|
|
1656
|
+
opacity: 0.6;
|
|
1366
1657
|
}
|
|
1367
1658
|
|
|
1368
1659
|
/* \u2500\u2500 Sections \u2500\u2500 */
|
|
@@ -1371,29 +1662,48 @@ body {
|
|
|
1371
1662
|
border: 1px solid var(--border);
|
|
1372
1663
|
border-radius: var(--radius);
|
|
1373
1664
|
overflow: hidden;
|
|
1665
|
+
box-shadow: var(--shadow);
|
|
1666
|
+
animation: fadeInUp 0.4s ease both;
|
|
1374
1667
|
}
|
|
1375
1668
|
|
|
1376
1669
|
.section-header {
|
|
1377
1670
|
display: flex;
|
|
1378
1671
|
align-items: center;
|
|
1379
1672
|
justify-content: space-between;
|
|
1380
|
-
padding: 0.
|
|
1673
|
+
padding: 0.85rem 1.2rem;
|
|
1381
1674
|
background: var(--bg-card);
|
|
1382
1675
|
cursor: pointer;
|
|
1383
1676
|
user-select: none;
|
|
1384
|
-
transition: background
|
|
1677
|
+
transition: background var(--transition);
|
|
1678
|
+
gap: 1rem;
|
|
1385
1679
|
}
|
|
1386
1680
|
|
|
1387
1681
|
.section-header:hover {
|
|
1388
1682
|
background: var(--bg-card-hover);
|
|
1389
1683
|
}
|
|
1390
1684
|
|
|
1685
|
+
.section-header-left {
|
|
1686
|
+
display: flex;
|
|
1687
|
+
align-items: center;
|
|
1688
|
+
gap: 0.6rem;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
.section-icon {
|
|
1692
|
+
font-size: 1.1rem;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1391
1695
|
.section-header h2 {
|
|
1392
1696
|
font-size: 1rem;
|
|
1393
1697
|
font-family: var(--font-mono);
|
|
1394
1698
|
color: var(--text-bright);
|
|
1395
1699
|
}
|
|
1396
1700
|
|
|
1701
|
+
.section-header-right {
|
|
1702
|
+
display: flex;
|
|
1703
|
+
align-items: center;
|
|
1704
|
+
gap: 0.75rem;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1397
1707
|
.section-header .count {
|
|
1398
1708
|
font-size: 0.8rem;
|
|
1399
1709
|
color: var(--text-dim);
|
|
@@ -1401,35 +1711,47 @@ body {
|
|
|
1401
1711
|
}
|
|
1402
1712
|
|
|
1403
1713
|
.section-header .arrow {
|
|
1404
|
-
transition: transform
|
|
1714
|
+
transition: transform var(--transition);
|
|
1405
1715
|
color: var(--text-dim);
|
|
1716
|
+
font-size: 0.7rem;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
.section-content {
|
|
1720
|
+
max-height: 8000px;
|
|
1721
|
+
overflow: hidden;
|
|
1722
|
+
transition: max-height 0.4s ease, opacity 0.3s ease, padding 0.3s ease;
|
|
1723
|
+
opacity: 1;
|
|
1724
|
+
padding: 1rem 1.2rem;
|
|
1725
|
+
border-top: 1px solid var(--border);
|
|
1406
1726
|
}
|
|
1407
1727
|
|
|
1408
1728
|
.section.collapsed .section-content {
|
|
1409
|
-
|
|
1729
|
+
max-height: 0;
|
|
1730
|
+
opacity: 0;
|
|
1731
|
+
padding-top: 0;
|
|
1732
|
+
padding-bottom: 0;
|
|
1733
|
+
border-top-color: transparent;
|
|
1410
1734
|
}
|
|
1411
1735
|
|
|
1412
1736
|
.section.collapsed .arrow {
|
|
1413
1737
|
transform: rotate(-90deg);
|
|
1414
1738
|
}
|
|
1415
1739
|
|
|
1416
|
-
.section-content {
|
|
1417
|
-
padding: 1rem;
|
|
1418
|
-
border-top: 1px solid var(--border);
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
1740
|
/* \u2500\u2500 Cards \u2500\u2500 */
|
|
1422
1741
|
.card {
|
|
1423
1742
|
background: var(--bg-card);
|
|
1424
1743
|
border: 1px solid var(--border);
|
|
1425
1744
|
border-radius: var(--radius);
|
|
1426
|
-
padding: 1rem;
|
|
1745
|
+
padding: 1rem 1.2rem;
|
|
1427
1746
|
margin-bottom: 0.75rem;
|
|
1428
|
-
transition:
|
|
1747
|
+
transition: all var(--transition);
|
|
1748
|
+
animation: fadeInUp 0.3s ease both;
|
|
1749
|
+
position: relative;
|
|
1429
1750
|
}
|
|
1430
1751
|
|
|
1431
1752
|
.card:hover {
|
|
1432
|
-
border-color: var(--
|
|
1753
|
+
border-color: var(--border-hover);
|
|
1754
|
+
box-shadow: var(--shadow-hover);
|
|
1433
1755
|
}
|
|
1434
1756
|
|
|
1435
1757
|
.card:last-child {
|
|
@@ -1444,6 +1766,7 @@ body {
|
|
|
1444
1766
|
display: flex;
|
|
1445
1767
|
align-items: center;
|
|
1446
1768
|
gap: 0.5rem;
|
|
1769
|
+
flex-wrap: wrap;
|
|
1447
1770
|
}
|
|
1448
1771
|
|
|
1449
1772
|
.card-meta {
|
|
@@ -1452,6 +1775,53 @@ body {
|
|
|
1452
1775
|
margin-bottom: 0.5rem;
|
|
1453
1776
|
}
|
|
1454
1777
|
|
|
1778
|
+
/* \u2500\u2500 Copy Button \u2500\u2500 */
|
|
1779
|
+
.copy-btn {
|
|
1780
|
+
background: none;
|
|
1781
|
+
border: 1px solid var(--border);
|
|
1782
|
+
color: var(--text-dim);
|
|
1783
|
+
font-size: 0.7rem;
|
|
1784
|
+
padding: 0.15rem 0.4rem;
|
|
1785
|
+
border-radius: 3px;
|
|
1786
|
+
cursor: pointer;
|
|
1787
|
+
transition: all var(--transition);
|
|
1788
|
+
font-family: var(--font-mono);
|
|
1789
|
+
opacity: 0;
|
|
1790
|
+
position: absolute;
|
|
1791
|
+
top: 0.75rem;
|
|
1792
|
+
right: 0.75rem;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
.card:hover .copy-btn {
|
|
1796
|
+
opacity: 1;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
.copy-btn:hover {
|
|
1800
|
+
color: var(--accent);
|
|
1801
|
+
border-color: var(--accent);
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
.copy-btn--copied {
|
|
1805
|
+
color: var(--green) !important;
|
|
1806
|
+
border-color: var(--green) !important;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
/* \u2500\u2500 Size Bar \u2500\u2500 */
|
|
1810
|
+
.size-bar {
|
|
1811
|
+
height: 3px;
|
|
1812
|
+
background: var(--border);
|
|
1813
|
+
border-radius: 2px;
|
|
1814
|
+
margin-top: 0.3rem;
|
|
1815
|
+
overflow: hidden;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
.size-bar-fill {
|
|
1819
|
+
height: 100%;
|
|
1820
|
+
border-radius: 2px;
|
|
1821
|
+
background: linear-gradient(90deg, var(--accent-dim), var(--accent));
|
|
1822
|
+
transition: width 0.6s ease;
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1455
1825
|
/* \u2500\u2500 Tool badges \u2500\u2500 */
|
|
1456
1826
|
.tool-badge {
|
|
1457
1827
|
display: inline-block;
|
|
@@ -1497,6 +1867,7 @@ body {
|
|
|
1497
1867
|
.status--error { background: #ff525220; color: var(--red); }
|
|
1498
1868
|
.status--active { background: #00e67620; color: var(--green); }
|
|
1499
1869
|
.status--configured { background: #00d4ff20; color: var(--accent); }
|
|
1870
|
+
.status--detected { background: #b388ff20; color: var(--purple); }
|
|
1500
1871
|
|
|
1501
1872
|
/* \u2500\u2500 Tool list \u2500\u2500 */
|
|
1502
1873
|
.tool-list {
|
|
@@ -1505,11 +1876,12 @@ body {
|
|
|
1505
1876
|
}
|
|
1506
1877
|
|
|
1507
1878
|
.tool-list li {
|
|
1508
|
-
padding: 0.
|
|
1879
|
+
padding: 0.35rem 0;
|
|
1509
1880
|
font-size: 0.8rem;
|
|
1510
1881
|
border-bottom: 1px solid var(--border);
|
|
1511
1882
|
display: flex;
|
|
1512
1883
|
gap: 0.5rem;
|
|
1884
|
+
align-items: baseline;
|
|
1513
1885
|
}
|
|
1514
1886
|
|
|
1515
1887
|
.tool-list li:last-child {
|
|
@@ -1520,33 +1892,37 @@ body {
|
|
|
1520
1892
|
font-family: var(--font-mono);
|
|
1521
1893
|
color: var(--accent);
|
|
1522
1894
|
white-space: nowrap;
|
|
1895
|
+
font-size: 0.8rem;
|
|
1523
1896
|
}
|
|
1524
1897
|
|
|
1525
1898
|
.tool-desc {
|
|
1526
1899
|
color: var(--text-dim);
|
|
1527
1900
|
overflow: hidden;
|
|
1528
1901
|
text-overflow: ellipsis;
|
|
1902
|
+
font-size: 0.78rem;
|
|
1529
1903
|
}
|
|
1530
1904
|
|
|
1531
1905
|
/* \u2500\u2500 Preview \u2500\u2500 */
|
|
1532
1906
|
.preview {
|
|
1533
1907
|
background: var(--bg);
|
|
1534
1908
|
border: 1px solid var(--border);
|
|
1535
|
-
border-radius:
|
|
1536
|
-
padding: 0.
|
|
1909
|
+
border-radius: var(--radius-sm);
|
|
1910
|
+
padding: 0.75rem;
|
|
1537
1911
|
margin-top: 0.5rem;
|
|
1538
1912
|
font-family: var(--font-mono);
|
|
1539
|
-
font-size: 0.
|
|
1913
|
+
font-size: 0.73rem;
|
|
1540
1914
|
color: var(--text-dim);
|
|
1541
1915
|
white-space: pre-wrap;
|
|
1542
1916
|
word-break: break-all;
|
|
1543
|
-
max-height:
|
|
1917
|
+
max-height: 200px;
|
|
1544
1918
|
overflow-y: auto;
|
|
1545
1919
|
display: none;
|
|
1920
|
+
line-height: 1.5;
|
|
1546
1921
|
}
|
|
1547
1922
|
|
|
1548
1923
|
.preview.open {
|
|
1549
1924
|
display: block;
|
|
1925
|
+
animation: fadeIn 0.2s ease;
|
|
1550
1926
|
}
|
|
1551
1927
|
|
|
1552
1928
|
.preview-toggle {
|
|
@@ -1557,15 +1933,17 @@ body {
|
|
|
1557
1933
|
cursor: pointer;
|
|
1558
1934
|
font-family: var(--font-mono);
|
|
1559
1935
|
padding: 0;
|
|
1936
|
+
transition: color var(--transition);
|
|
1560
1937
|
}
|
|
1561
1938
|
|
|
1562
1939
|
.preview-toggle:hover {
|
|
1940
|
+
color: var(--accent-dim);
|
|
1563
1941
|
text-decoration: underline;
|
|
1564
1942
|
}
|
|
1565
1943
|
|
|
1566
1944
|
/* \u2500\u2500 Groups \u2500\u2500 */
|
|
1567
1945
|
.tool-group {
|
|
1568
|
-
margin-bottom:
|
|
1946
|
+
margin-bottom: 1.2rem;
|
|
1569
1947
|
}
|
|
1570
1948
|
|
|
1571
1949
|
.tool-group:last-child {
|
|
@@ -1576,22 +1954,30 @@ body {
|
|
|
1576
1954
|
display: flex;
|
|
1577
1955
|
align-items: center;
|
|
1578
1956
|
gap: 0.5rem;
|
|
1579
|
-
margin-bottom: 0.
|
|
1580
|
-
padding-bottom: 0.
|
|
1957
|
+
margin-bottom: 0.6rem;
|
|
1958
|
+
padding-bottom: 0.4rem;
|
|
1581
1959
|
border-bottom: 1px solid var(--border);
|
|
1582
1960
|
}
|
|
1583
1961
|
|
|
1584
1962
|
/* \u2500\u2500 Empty state \u2500\u2500 */
|
|
1585
1963
|
.empty-state {
|
|
1586
1964
|
text-align: center;
|
|
1587
|
-
padding:
|
|
1965
|
+
padding: 4rem 2rem;
|
|
1588
1966
|
color: var(--text-dim);
|
|
1589
1967
|
}
|
|
1590
1968
|
|
|
1969
|
+
.empty-state-icon {
|
|
1970
|
+
font-size: 3rem;
|
|
1971
|
+
margin-bottom: 1rem;
|
|
1972
|
+
display: block;
|
|
1973
|
+
opacity: 0.5;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1591
1976
|
.empty-state h3 {
|
|
1592
|
-
font-size: 1.
|
|
1977
|
+
font-size: 1.2rem;
|
|
1593
1978
|
margin-bottom: 0.5rem;
|
|
1594
1979
|
color: var(--text);
|
|
1980
|
+
font-family: var(--font-mono);
|
|
1595
1981
|
}
|
|
1596
1982
|
|
|
1597
1983
|
/* \u2500\u2500 Scope badge \u2500\u2500 */
|
|
@@ -1601,41 +1987,156 @@ body {
|
|
|
1601
1987
|
border-radius: 3px;
|
|
1602
1988
|
font-weight: 600;
|
|
1603
1989
|
text-transform: uppercase;
|
|
1990
|
+
letter-spacing: 0.03em;
|
|
1604
1991
|
}
|
|
1605
1992
|
|
|
1606
1993
|
.scope-badge--project { background: var(--accent); color: var(--bg); }
|
|
1607
1994
|
.scope-badge--user { background: var(--purple); color: var(--bg); }
|
|
1995
|
+
.scope-badge--vscode { background: #007acc; color: #fff; }
|
|
1996
|
+
.scope-badge--desktop { background: var(--orange); color: var(--bg); }
|
|
1997
|
+
|
|
1998
|
+
/* \u2500\u2500 Warnings \u2500\u2500 */
|
|
1999
|
+
.warning-card {
|
|
2000
|
+
background: var(--bg-card);
|
|
2001
|
+
border: 1px solid var(--orange);
|
|
2002
|
+
border-left: 4px solid var(--orange);
|
|
2003
|
+
border-radius: var(--radius);
|
|
2004
|
+
padding: 0.8rem 1rem;
|
|
2005
|
+
margin-bottom: 0.5rem;
|
|
2006
|
+
font-size: 0.8rem;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
.warning-card:last-child { margin-bottom: 0; }
|
|
2010
|
+
|
|
2011
|
+
.warning-scanner {
|
|
2012
|
+
font-family: var(--font-mono);
|
|
2013
|
+
font-size: 0.7rem;
|
|
2014
|
+
color: var(--orange);
|
|
2015
|
+
margin-bottom: 0.2rem;
|
|
2016
|
+
}
|
|
1608
2017
|
|
|
1609
2018
|
/* \u2500\u2500 Footer \u2500\u2500 */
|
|
1610
2019
|
.footer {
|
|
1611
2020
|
text-align: center;
|
|
1612
|
-
padding:
|
|
1613
|
-
margin-top:
|
|
2021
|
+
padding: 2rem 0;
|
|
2022
|
+
margin-top: 2rem;
|
|
1614
2023
|
border-top: 1px solid var(--border);
|
|
1615
2024
|
font-size: 0.75rem;
|
|
1616
2025
|
color: var(--text-dim);
|
|
1617
2026
|
font-family: var(--font-mono);
|
|
1618
2027
|
}
|
|
1619
2028
|
|
|
2029
|
+
.footer a {
|
|
2030
|
+
color: var(--accent);
|
|
2031
|
+
text-decoration: none;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
.footer a:hover {
|
|
2035
|
+
text-decoration: underline;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
/* \u2500\u2500 Animations \u2500\u2500 */
|
|
2039
|
+
@keyframes fadeInUp {
|
|
2040
|
+
from {
|
|
2041
|
+
opacity: 0;
|
|
2042
|
+
transform: translateY(12px);
|
|
2043
|
+
}
|
|
2044
|
+
to {
|
|
2045
|
+
opacity: 1;
|
|
2046
|
+
transform: translateY(0);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
@keyframes fadeIn {
|
|
2051
|
+
from { opacity: 0; }
|
|
2052
|
+
to { opacity: 1; }
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
@keyframes pulse {
|
|
2056
|
+
0%, 100% { opacity: 0.6; }
|
|
2057
|
+
50% { opacity: 1; }
|
|
2058
|
+
}
|
|
2059
|
+
|
|
1620
2060
|
/* \u2500\u2500 Responsive \u2500\u2500 */
|
|
2061
|
+
@media (max-width: 1024px) {
|
|
2062
|
+
.stats-grid {
|
|
2063
|
+
grid-template-columns: repeat(3, 1fr);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
|
|
1621
2067
|
@media (max-width: 768px) {
|
|
1622
2068
|
.container { padding: 1rem; }
|
|
2069
|
+
.nav-bar { padding: 0.5rem 1rem; flex-wrap: wrap; }
|
|
2070
|
+
.nav-links { display: none; }
|
|
1623
2071
|
.badges { gap: 0.5rem; }
|
|
1624
2072
|
.badge { font-size: 0.7rem; padding: 0.2rem 0.6rem; }
|
|
2073
|
+
.stats-grid { grid-template-columns: repeat(2, 1fr); gap: 0.6rem; }
|
|
2074
|
+
.stat-number { font-size: 1.5rem; }
|
|
2075
|
+
.header h1 { font-size: 1.3rem; }
|
|
2076
|
+
.ecosystem-map { display: none; }
|
|
2077
|
+
.section-content { padding: 0.75rem; }
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
@media (max-width: 480px) {
|
|
2081
|
+
.stats-grid { grid-template-columns: repeat(2, 1fr); }
|
|
2082
|
+
.nav-actions { gap: 0.3rem; }
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
/* \u2500\u2500 Print \u2500\u2500 */
|
|
2086
|
+
@media print {
|
|
2087
|
+
.nav-bar, .search-bar, .ecosystem-map, .nav-btn, .copy-btn, .preview-toggle { display: none !important; }
|
|
2088
|
+
body { background: #fff; color: #000; }
|
|
2089
|
+
.container { max-width: 100%; padding: 1rem; }
|
|
2090
|
+
.card, .section { break-inside: avoid; box-shadow: none; border-color: #ccc; }
|
|
2091
|
+
.section-content { max-height: none !important; opacity: 1 !important; }
|
|
2092
|
+
.header h1 { color: #000; }
|
|
2093
|
+
.stat-card { border-color: #ccc; }
|
|
2094
|
+
.stat-number { color: #333; }
|
|
1625
2095
|
}
|
|
1626
2096
|
`;
|
|
1627
2097
|
|
|
1628
2098
|
// src/report/scripts.ts
|
|
1629
2099
|
var JS_SCRIPTS = `
|
|
1630
2100
|
document.addEventListener('DOMContentLoaded', () => {
|
|
1631
|
-
|
|
2101
|
+
|
|
2102
|
+
// \u2500\u2500 Animated Counters \u2500\u2500
|
|
2103
|
+
document.querySelectorAll('.stat-number').forEach(el => {
|
|
2104
|
+
const target = parseInt(el.getAttribute('data-target') || '0')
|
|
2105
|
+
if (target === 0) { el.textContent = '0'; return }
|
|
2106
|
+
let current = 0
|
|
2107
|
+
const step = Math.max(1, Math.ceil(target / 25))
|
|
2108
|
+
const interval = setInterval(() => {
|
|
2109
|
+
current = Math.min(current + step, target)
|
|
2110
|
+
el.textContent = current
|
|
2111
|
+
if (current >= target) clearInterval(interval)
|
|
2112
|
+
}, 35)
|
|
2113
|
+
})
|
|
2114
|
+
|
|
2115
|
+
// \u2500\u2500 Section Collapse/Expand \u2500\u2500
|
|
1632
2116
|
document.querySelectorAll('.section-header').forEach(header => {
|
|
1633
2117
|
header.addEventListener('click', () => {
|
|
1634
|
-
header.closest('.section')
|
|
2118
|
+
const section = header.closest('.section')
|
|
2119
|
+
if (!section) return
|
|
2120
|
+
const content = section.querySelector('.section-content')
|
|
2121
|
+
if (!content) return
|
|
2122
|
+
|
|
2123
|
+
if (section.classList.contains('collapsed')) {
|
|
2124
|
+
// Expand
|
|
2125
|
+
section.classList.remove('collapsed')
|
|
2126
|
+
content.style.maxHeight = content.scrollHeight + 'px'
|
|
2127
|
+
setTimeout(() => { content.style.maxHeight = '' }, 400)
|
|
2128
|
+
} else {
|
|
2129
|
+
// Collapse
|
|
2130
|
+
content.style.maxHeight = content.scrollHeight + 'px'
|
|
2131
|
+
requestAnimationFrame(() => {
|
|
2132
|
+
content.style.maxHeight = '0px'
|
|
2133
|
+
section.classList.add('collapsed')
|
|
2134
|
+
})
|
|
2135
|
+
}
|
|
1635
2136
|
})
|
|
1636
2137
|
})
|
|
1637
2138
|
|
|
1638
|
-
// Preview
|
|
2139
|
+
// \u2500\u2500 Preview Toggle \u2500\u2500
|
|
1639
2140
|
document.querySelectorAll('.preview-toggle').forEach(btn => {
|
|
1640
2141
|
btn.addEventListener('click', (e) => {
|
|
1641
2142
|
e.stopPropagation()
|
|
@@ -1647,55 +2148,428 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1647
2148
|
})
|
|
1648
2149
|
})
|
|
1649
2150
|
|
|
1650
|
-
// Global
|
|
2151
|
+
// \u2500\u2500 Global Search \u2500\u2500
|
|
1651
2152
|
const searchInput = document.getElementById('search-input')
|
|
2153
|
+
const resultsCount = document.querySelector('.search-results-count')
|
|
2154
|
+
const searchKbd = document.querySelector('.search-kbd')
|
|
2155
|
+
|
|
1652
2156
|
if (searchInput) {
|
|
1653
2157
|
searchInput.addEventListener('input', (e) => {
|
|
1654
2158
|
const query = e.target.value.toLowerCase().trim()
|
|
2159
|
+
let visible = 0
|
|
2160
|
+
let total = 0
|
|
2161
|
+
|
|
1655
2162
|
document.querySelectorAll('[data-searchable]').forEach(el => {
|
|
1656
2163
|
const text = el.getAttribute('data-searchable').toLowerCase()
|
|
1657
|
-
|
|
2164
|
+
const match = !query || text.includes(query)
|
|
2165
|
+
el.style.display = match ? '' : 'none'
|
|
2166
|
+
total++
|
|
2167
|
+
if (match) visible++
|
|
1658
2168
|
})
|
|
2169
|
+
|
|
2170
|
+
if (resultsCount) {
|
|
2171
|
+
resultsCount.textContent = query ? visible + ' / ' + total : ''
|
|
2172
|
+
}
|
|
2173
|
+
if (searchKbd) {
|
|
2174
|
+
searchKbd.style.display = query ? 'none' : ''
|
|
2175
|
+
}
|
|
2176
|
+
})
|
|
2177
|
+
|
|
2178
|
+
searchInput.addEventListener('focus', () => {
|
|
2179
|
+
if (searchKbd) searchKbd.style.display = 'none'
|
|
2180
|
+
})
|
|
2181
|
+
|
|
2182
|
+
searchInput.addEventListener('blur', () => {
|
|
2183
|
+
if (searchKbd && !searchInput.value) searchKbd.style.display = ''
|
|
1659
2184
|
})
|
|
1660
2185
|
}
|
|
2186
|
+
|
|
2187
|
+
// \u2500\u2500 Keyboard Shortcuts \u2500\u2500
|
|
2188
|
+
document.addEventListener('keydown', (e) => {
|
|
2189
|
+
// Ignore when typing in input
|
|
2190
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
|
|
2191
|
+
if (e.key === 'Escape') {
|
|
2192
|
+
e.target.value = ''
|
|
2193
|
+
e.target.dispatchEvent(new Event('input'))
|
|
2194
|
+
e.target.blur()
|
|
2195
|
+
}
|
|
2196
|
+
return
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
if (e.key === '/') {
|
|
2200
|
+
e.preventDefault()
|
|
2201
|
+
if (searchInput) searchInput.focus()
|
|
2202
|
+
}
|
|
2203
|
+
})
|
|
2204
|
+
|
|
2205
|
+
// \u2500\u2500 Nav Links (scroll to section) \u2500\u2500
|
|
2206
|
+
document.querySelectorAll('.nav-link[data-target]').forEach(link => {
|
|
2207
|
+
link.addEventListener('click', (e) => {
|
|
2208
|
+
e.preventDefault()
|
|
2209
|
+
const targetId = link.getAttribute('data-target')
|
|
2210
|
+
const target = document.getElementById(targetId)
|
|
2211
|
+
if (target) {
|
|
2212
|
+
// Expand if collapsed
|
|
2213
|
+
if (target.classList.contains('collapsed')) {
|
|
2214
|
+
target.classList.remove('collapsed')
|
|
2215
|
+
const content = target.querySelector('.section-content')
|
|
2216
|
+
if (content) {
|
|
2217
|
+
content.style.maxHeight = content.scrollHeight + 'px'
|
|
2218
|
+
setTimeout(() => { content.style.maxHeight = '' }, 400)
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
2222
|
+
}
|
|
2223
|
+
})
|
|
2224
|
+
})
|
|
2225
|
+
|
|
2226
|
+
// \u2500\u2500 Ecosystem Map (click to scroll) \u2500\u2500
|
|
2227
|
+
document.querySelectorAll('.eco-node[data-section]').forEach(node => {
|
|
2228
|
+
node.addEventListener('click', () => {
|
|
2229
|
+
const targetId = node.getAttribute('data-section')
|
|
2230
|
+
const target = document.getElementById(targetId)
|
|
2231
|
+
if (target) {
|
|
2232
|
+
if (target.classList.contains('collapsed')) {
|
|
2233
|
+
target.classList.remove('collapsed')
|
|
2234
|
+
const content = target.querySelector('.section-content')
|
|
2235
|
+
if (content) {
|
|
2236
|
+
content.style.maxHeight = content.scrollHeight + 'px'
|
|
2237
|
+
setTimeout(() => { content.style.maxHeight = '' }, 400)
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
2241
|
+
}
|
|
2242
|
+
})
|
|
2243
|
+
})
|
|
2244
|
+
|
|
2245
|
+
// \u2500\u2500 Theme Toggle \u2500\u2500
|
|
2246
|
+
const themeToggle = document.getElementById('theme-toggle')
|
|
2247
|
+
if (themeToggle) {
|
|
2248
|
+
themeToggle.addEventListener('click', () => {
|
|
2249
|
+
const html = document.documentElement
|
|
2250
|
+
const current = html.getAttribute('data-theme')
|
|
2251
|
+
const next = current === 'light' ? 'dark' : 'light'
|
|
2252
|
+
html.setAttribute('data-theme', next)
|
|
2253
|
+
themeToggle.innerHTML = next === 'light' ? '☀' : '☾'
|
|
2254
|
+
})
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
// \u2500\u2500 Export JSON \u2500\u2500
|
|
2258
|
+
const exportBtn = document.getElementById('export-btn')
|
|
2259
|
+
if (exportBtn) {
|
|
2260
|
+
exportBtn.addEventListener('click', () => {
|
|
2261
|
+
const dataEl = document.getElementById('scan-data')
|
|
2262
|
+
if (!dataEl) return
|
|
2263
|
+
const text = dataEl.textContent || ''
|
|
2264
|
+
const fallback = () => {
|
|
2265
|
+
const ta = document.createElement('textarea')
|
|
2266
|
+
ta.value = text
|
|
2267
|
+
ta.style.position = 'fixed'
|
|
2268
|
+
ta.style.opacity = '0'
|
|
2269
|
+
document.body.appendChild(ta)
|
|
2270
|
+
ta.select()
|
|
2271
|
+
document.execCommand('copy')
|
|
2272
|
+
document.body.removeChild(ta)
|
|
2273
|
+
}
|
|
2274
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
2275
|
+
navigator.clipboard.writeText(text).catch(fallback)
|
|
2276
|
+
} else {
|
|
2277
|
+
fallback()
|
|
2278
|
+
}
|
|
2279
|
+
const orig = exportBtn.innerHTML
|
|
2280
|
+
exportBtn.textContent = '\\u2713'
|
|
2281
|
+
setTimeout(() => { exportBtn.innerHTML = orig }, 1500)
|
|
2282
|
+
})
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
// \u2500\u2500 Copy Buttons \u2500\u2500
|
|
2286
|
+
document.querySelectorAll('.copy-btn').forEach(btn => {
|
|
2287
|
+
btn.addEventListener('click', (e) => {
|
|
2288
|
+
e.stopPropagation()
|
|
2289
|
+
const text = btn.getAttribute('data-copy') || ''
|
|
2290
|
+
const fallback = () => {
|
|
2291
|
+
const ta = document.createElement('textarea')
|
|
2292
|
+
ta.value = text
|
|
2293
|
+
ta.style.position = 'fixed'
|
|
2294
|
+
ta.style.opacity = '0'
|
|
2295
|
+
document.body.appendChild(ta)
|
|
2296
|
+
ta.select()
|
|
2297
|
+
document.execCommand('copy')
|
|
2298
|
+
document.body.removeChild(ta)
|
|
2299
|
+
}
|
|
2300
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
2301
|
+
navigator.clipboard.writeText(text).catch(fallback)
|
|
2302
|
+
} else {
|
|
2303
|
+
fallback()
|
|
2304
|
+
}
|
|
2305
|
+
btn.classList.add('copy-btn--copied')
|
|
2306
|
+
btn.textContent = '\\u2713'
|
|
2307
|
+
setTimeout(() => {
|
|
2308
|
+
btn.classList.remove('copy-btn--copied')
|
|
2309
|
+
btn.textContent = 'copiar'
|
|
2310
|
+
}, 1500)
|
|
2311
|
+
})
|
|
2312
|
+
})
|
|
2313
|
+
|
|
1661
2314
|
})
|
|
1662
2315
|
`;
|
|
1663
2316
|
|
|
1664
|
-
// src/report/
|
|
2317
|
+
// src/report/ecosystem-map.ts
|
|
1665
2318
|
function esc(str) {
|
|
1666
2319
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1667
2320
|
}
|
|
2321
|
+
function buildCategories(result, summary) {
|
|
2322
|
+
return [
|
|
2323
|
+
{
|
|
2324
|
+
id: "section-mcp",
|
|
2325
|
+
label: "MCP",
|
|
2326
|
+
icon: "\u2699",
|
|
2327
|
+
count: summary.totalMcpServers,
|
|
2328
|
+
color: "#00d4ff",
|
|
2329
|
+
items: result.mcpServers.slice(0, 8).map((s) => ({
|
|
2330
|
+
name: s.name,
|
|
2331
|
+
detail: `${s.introspection?.tools.length ?? 0} tools`
|
|
2332
|
+
}))
|
|
2333
|
+
},
|
|
2334
|
+
{
|
|
2335
|
+
id: "section-context",
|
|
2336
|
+
label: "Contexto",
|
|
2337
|
+
icon: "\u{1F4C4}",
|
|
2338
|
+
count: summary.totalFiles,
|
|
2339
|
+
color: "#b388ff",
|
|
2340
|
+
items: result.contextFiles.slice(0, 8).map((f) => ({
|
|
2341
|
+
name: f.path,
|
|
2342
|
+
detail: f.tool
|
|
2343
|
+
}))
|
|
2344
|
+
},
|
|
2345
|
+
{
|
|
2346
|
+
id: "section-skills",
|
|
2347
|
+
label: "Skills",
|
|
2348
|
+
icon: "\u26A1",
|
|
2349
|
+
count: summary.totalSkills,
|
|
2350
|
+
color: "#ffab40",
|
|
2351
|
+
items: result.skills.slice(0, 8).map((s) => ({
|
|
2352
|
+
name: s.name
|
|
2353
|
+
}))
|
|
2354
|
+
},
|
|
2355
|
+
{
|
|
2356
|
+
id: "section-agents",
|
|
2357
|
+
label: "Agents",
|
|
2358
|
+
icon: "\u{1F916}",
|
|
2359
|
+
count: summary.totalAgents,
|
|
2360
|
+
color: "#00e676",
|
|
2361
|
+
items: result.agents.slice(0, 8).map((a) => ({
|
|
2362
|
+
name: a.name,
|
|
2363
|
+
detail: a.model
|
|
2364
|
+
}))
|
|
2365
|
+
},
|
|
2366
|
+
{
|
|
2367
|
+
id: "section-memories",
|
|
2368
|
+
label: "Memorias",
|
|
2369
|
+
icon: "\u{1F9E0}",
|
|
2370
|
+
count: summary.totalMemories,
|
|
2371
|
+
color: "#ff80ab",
|
|
2372
|
+
items: result.memories.slice(0, 8).map((m) => ({
|
|
2373
|
+
name: m.type,
|
|
2374
|
+
detail: m.status
|
|
2375
|
+
}))
|
|
2376
|
+
}
|
|
2377
|
+
];
|
|
2378
|
+
}
|
|
2379
|
+
function renderEcosystemMap(result, summary) {
|
|
2380
|
+
const categories = buildCategories(result, summary);
|
|
2381
|
+
const totalItems = summary.totalMcpServers + summary.totalFiles + summary.totalSkills + summary.totalAgents + summary.totalMemories;
|
|
2382
|
+
if (totalItems === 0) return "";
|
|
2383
|
+
const W = 800;
|
|
2384
|
+
const H = 420;
|
|
2385
|
+
const cx = W / 2;
|
|
2386
|
+
const cy = H / 2;
|
|
2387
|
+
const catRadius = 155;
|
|
2388
|
+
const itemRadius = 70;
|
|
2389
|
+
const startAngle = -Math.PI / 2;
|
|
2390
|
+
let svg = "";
|
|
2391
|
+
svg += `<defs>
|
|
2392
|
+
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
|
2393
|
+
<feGaussianBlur stdDeviation="4" result="blur"/>
|
|
2394
|
+
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
|
|
2395
|
+
</filter>
|
|
2396
|
+
<filter id="glow-strong" x="-50%" y="-50%" width="200%" height="200%">
|
|
2397
|
+
<feGaussianBlur stdDeviation="8" result="blur"/>
|
|
2398
|
+
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
2399
|
+
</filter>
|
|
2400
|
+
</defs>`;
|
|
2401
|
+
svg += `<pattern id="grid" width="30" height="30" patternUnits="userSpaceOnUse">
|
|
2402
|
+
<circle cx="15" cy="15" r="0.5" fill="var(--text-dim)" opacity="0.15"/>
|
|
2403
|
+
</pattern>
|
|
2404
|
+
<rect width="${W}" height="${H}" fill="url(#grid)"/>`;
|
|
2405
|
+
const catPositions = [];
|
|
2406
|
+
categories.forEach((cat, i) => {
|
|
2407
|
+
const angle = startAngle + i / categories.length * Math.PI * 2;
|
|
2408
|
+
const x = cx + Math.cos(angle) * catRadius;
|
|
2409
|
+
const y = cy + Math.sin(angle) * catRadius;
|
|
2410
|
+
catPositions.push({ x, y, cat });
|
|
2411
|
+
const dimmed = cat.count === 0;
|
|
2412
|
+
svg += `<line x1="${cx}" y1="${cy}" x2="${x}" y2="${y}"
|
|
2413
|
+
class="eco-connection ${dimmed ? "eco-dimmed" : "eco-connection--active"}"
|
|
2414
|
+
style="animation-delay: ${i * 0.3}s"/>`;
|
|
2415
|
+
if (!dimmed && cat.items.length > 0) {
|
|
2416
|
+
const maxItems = Math.min(cat.items.length, 8);
|
|
2417
|
+
const arcSpread = Math.min(Math.PI * 0.5, maxItems * 0.2);
|
|
2418
|
+
cat.items.slice(0, maxItems).forEach((item, j) => {
|
|
2419
|
+
const itemAngle = angle - arcSpread / 2 + (maxItems > 1 ? j / (maxItems - 1) * arcSpread : 0);
|
|
2420
|
+
const ix = x + Math.cos(itemAngle) * itemRadius;
|
|
2421
|
+
const iy = y + Math.sin(itemAngle) * itemRadius;
|
|
2422
|
+
svg += `<line x1="${x}" y1="${y}" x2="${ix}" y2="${iy}"
|
|
2423
|
+
stroke="${cat.color}" stroke-width="0.8" opacity="0.25"/>`;
|
|
2424
|
+
svg += `<circle cx="${ix}" cy="${iy}" r="4" fill="${cat.color}"
|
|
2425
|
+
class="eco-item-circle" opacity="0.5">
|
|
2426
|
+
<title>${esc(item.name)}${item.detail ? " (" + esc(item.detail) + ")" : ""}</title>
|
|
2427
|
+
</circle>`;
|
|
2428
|
+
});
|
|
2429
|
+
if (cat.items.length < cat.count) {
|
|
2430
|
+
const extra = cat.count - cat.items.length;
|
|
2431
|
+
const moreAngle = angle + arcSpread / 2 + 0.3;
|
|
2432
|
+
const mx = x + Math.cos(moreAngle) * (itemRadius - 10);
|
|
2433
|
+
const my = y + Math.sin(moreAngle) * (itemRadius - 10);
|
|
2434
|
+
svg += `<text x="${mx}" y="${my}" font-family="var(--font-mono)"
|
|
2435
|
+
font-size="9" fill="${cat.color}" opacity="0.6" text-anchor="middle"
|
|
2436
|
+
dominant-baseline="middle">+${extra}</text>`;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
const nodeR = dimmed ? 22 : 28;
|
|
2440
|
+
svg += `<g class="eco-node ${dimmed ? "eco-dimmed" : ""}" data-section="${cat.id}">
|
|
2441
|
+
<circle cx="${x}" cy="${y}" r="${nodeR}"
|
|
2442
|
+
fill="${cat.color}15" stroke="${cat.color}" stroke-width="2"
|
|
2443
|
+
class="eco-node-circle" ${!dimmed ? 'filter="url(#glow)"' : ""}/>
|
|
2444
|
+
<text x="${x}" y="${y - 3}" class="eco-label"
|
|
2445
|
+
fill="${cat.color}" font-weight="600">${cat.icon} ${cat.label}</text>
|
|
2446
|
+
<text x="${x}" y="${y + 12}" class="eco-label eco-label--count">${cat.count}</text>
|
|
2447
|
+
</g>`;
|
|
2448
|
+
});
|
|
2449
|
+
const projectName = result.project.name.length > 18 ? result.project.name.slice(0, 16) + ".." : result.project.name;
|
|
2450
|
+
svg += `<g class="eco-node">
|
|
2451
|
+
<circle cx="${cx}" cy="${cy}" r="38" fill="var(--accent)" opacity="0.1"
|
|
2452
|
+
stroke="var(--accent)" stroke-width="2" class="eco-center-circle"
|
|
2453
|
+
filter="url(#glow-strong)"/>
|
|
2454
|
+
<circle cx="${cx}" cy="${cy}" r="36" fill="var(--bg-alt)" opacity="0.9"/>
|
|
2455
|
+
<text x="${cx}" y="${cy - 5}" class="eco-label eco-label--center">${esc(projectName)}</text>
|
|
2456
|
+
<text x="${cx}" y="${cy + 12}" class="eco-label eco-label--count">${totalItems} elementos</text>
|
|
2457
|
+
</g>`;
|
|
2458
|
+
return `
|
|
2459
|
+
<div class="ecosystem-map">
|
|
2460
|
+
<svg class="ecosystem-svg" viewBox="0 0 ${W} ${H}" xmlns="http://www.w3.org/2000/svg">
|
|
2461
|
+
${svg}
|
|
2462
|
+
</svg>
|
|
2463
|
+
</div>`;
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
// src/report/sections.ts
|
|
2467
|
+
function esc2(str) {
|
|
2468
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2469
|
+
}
|
|
1668
2470
|
function formatBytes(bytes) {
|
|
1669
2471
|
if (bytes < 1024) return `${bytes} B`;
|
|
1670
2472
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1671
2473
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1672
2474
|
}
|
|
2475
|
+
function renderNavBar(summary) {
|
|
2476
|
+
const links = [
|
|
2477
|
+
{ id: "section-mcp", label: "MCP", count: summary.totalMcpServers },
|
|
2478
|
+
{ id: "section-context", label: "Contexto", count: summary.totalFiles },
|
|
2479
|
+
{ id: "section-skills", label: "Skills", count: summary.totalSkills },
|
|
2480
|
+
{ id: "section-agents", label: "Agents", count: summary.totalAgents },
|
|
2481
|
+
{ id: "section-memories", label: "Memorias", count: summary.totalMemories }
|
|
2482
|
+
];
|
|
2483
|
+
const navLinks = links.filter((l) => l.count > 0).map(
|
|
2484
|
+
(l) => `<a class="nav-link" data-target="${l.id}">${l.label} (${l.count})</a>`
|
|
2485
|
+
).join("");
|
|
2486
|
+
return `
|
|
2487
|
+
<nav class="nav-bar">
|
|
2488
|
+
<div class="nav-links">
|
|
2489
|
+
<span style="font-family:var(--font-mono);font-size:0.8rem;color:var(--accent);margin-right:0.5rem">>_</span>
|
|
2490
|
+
${navLinks}
|
|
2491
|
+
</div>
|
|
2492
|
+
<div class="nav-actions">
|
|
2493
|
+
<button class="nav-btn" id="theme-toggle" title="Cambiar tema">☾</button>
|
|
2494
|
+
<button class="nav-btn" id="export-btn" title="Exportar JSON">📋</button>
|
|
2495
|
+
</div>
|
|
2496
|
+
</nav>`;
|
|
2497
|
+
}
|
|
1673
2498
|
function renderHeader(project, summary, scanDuration) {
|
|
1674
2499
|
const date = new Date(project.scannedAt).toLocaleString();
|
|
1675
2500
|
return `
|
|
1676
2501
|
<header class="header">
|
|
1677
2502
|
<h1>> ai-context-inspector</h1>
|
|
1678
|
-
<div class="subtitle">${
|
|
2503
|
+
<div class="subtitle">${esc2(project.name)} — ${date} — ${scanDuration}ms</div>
|
|
1679
2504
|
<div class="badges">
|
|
1680
2505
|
<span class="badge badge--accent">${summary.totalMcpServers} MCPs</span>
|
|
1681
2506
|
<span class="badge badge--green">${summary.totalTools} tools</span>
|
|
1682
2507
|
<span class="badge badge--purple">${summary.totalFiles} archivos</span>
|
|
1683
2508
|
<span class="badge badge--orange">${summary.totalSkills} skills</span>
|
|
1684
2509
|
<span class="badge badge--blue">${summary.totalAgents} agents</span>
|
|
1685
|
-
<span class="badge badge--
|
|
2510
|
+
<span class="badge badge--pink">${summary.totalMemories} memorias</span>
|
|
1686
2511
|
</div>
|
|
1687
2512
|
</header>`;
|
|
1688
2513
|
}
|
|
2514
|
+
function renderStatsGrid(summary) {
|
|
2515
|
+
const stats = [
|
|
2516
|
+
{
|
|
2517
|
+
icon: "\u2699\uFE0F",
|
|
2518
|
+
value: summary.totalMcpServers,
|
|
2519
|
+
label: "MCP Servers",
|
|
2520
|
+
color: "#00d4ff"
|
|
2521
|
+
},
|
|
2522
|
+
{
|
|
2523
|
+
icon: "\u{1F6E0}\uFE0F",
|
|
2524
|
+
value: summary.totalTools,
|
|
2525
|
+
label: "MCP Tools",
|
|
2526
|
+
color: "#00e676"
|
|
2527
|
+
},
|
|
2528
|
+
{
|
|
2529
|
+
icon: "\u{1F4C4}",
|
|
2530
|
+
value: summary.totalFiles,
|
|
2531
|
+
label: "Archivos AI",
|
|
2532
|
+
color: "#b388ff"
|
|
2533
|
+
},
|
|
2534
|
+
{
|
|
2535
|
+
icon: "\u26A1",
|
|
2536
|
+
value: summary.totalSkills,
|
|
2537
|
+
label: "Skills",
|
|
2538
|
+
color: "#ffab40"
|
|
2539
|
+
},
|
|
2540
|
+
{
|
|
2541
|
+
icon: "\u{1F916}",
|
|
2542
|
+
value: summary.totalAgents,
|
|
2543
|
+
label: "Agents",
|
|
2544
|
+
color: "#4285f4"
|
|
2545
|
+
},
|
|
2546
|
+
{
|
|
2547
|
+
icon: "\u{1F9E0}",
|
|
2548
|
+
value: summary.totalMemories,
|
|
2549
|
+
label: "Memorias",
|
|
2550
|
+
color: "#ff80ab"
|
|
2551
|
+
}
|
|
2552
|
+
];
|
|
2553
|
+
const cards = stats.map(
|
|
2554
|
+
(s) => `
|
|
2555
|
+
<div class="stat-card" style="--stat-color: ${s.color}">
|
|
2556
|
+
<span class="stat-icon">${s.icon}</span>
|
|
2557
|
+
<span class="stat-number" data-target="${s.value}">0</span>
|
|
2558
|
+
<span class="stat-label">${s.label}</span>
|
|
2559
|
+
</div>`
|
|
2560
|
+
).join("");
|
|
2561
|
+
return `<div class="stats-grid">${cards}</div>`;
|
|
2562
|
+
}
|
|
1689
2563
|
function renderMcpServers(servers) {
|
|
1690
2564
|
if (servers.length === 0) return "";
|
|
1691
|
-
const cards = servers.map((s) => {
|
|
2565
|
+
const cards = servers.map((s, i) => {
|
|
1692
2566
|
const intro = s.introspection;
|
|
1693
2567
|
const statusClass = intro ? `status--${intro.status}` : "status--configured";
|
|
1694
2568
|
const statusText = intro ? intro.status === "ok" ? "OK" : intro.status === "timeout" ? "Timeout" : "Error" : "No introspectado";
|
|
1695
2569
|
let toolsHtml = "";
|
|
1696
2570
|
if (intro && intro.tools.length > 0) {
|
|
1697
2571
|
const items = intro.tools.map(
|
|
1698
|
-
(t) => `<li data-searchable="${
|
|
2572
|
+
(t) => `<li data-searchable="${esc2(t.name + " " + (t.description ?? ""))}"><span class="tool-name">${esc2(t.name)}</span><span class="tool-desc">${esc2(t.description ?? "")}</span></li>`
|
|
1699
2573
|
).join("");
|
|
1700
2574
|
toolsHtml = `<div class="card-meta">${intro.tools.length} tools</div><ul class="tool-list">${items}</ul>`;
|
|
1701
2575
|
}
|
|
@@ -1707,13 +2581,17 @@ function renderMcpServers(servers) {
|
|
|
1707
2581
|
if (intro && intro.prompts.length > 0) {
|
|
1708
2582
|
promptsHtml = `<div class="card-meta" style="margin-top:0.5rem">${intro.prompts.length} prompts</div>`;
|
|
1709
2583
|
}
|
|
1710
|
-
const serverVersion = intro?.serverInfo ? ` v${
|
|
1711
|
-
const errorHtml = intro?.error ? `<div class="card-meta" style="color:var(--red)">${
|
|
1712
|
-
const
|
|
2584
|
+
const serverVersion = intro?.serverInfo ? ` v${esc2(intro.serverInfo.version)}` : "";
|
|
2585
|
+
const errorHtml = intro?.error ? `<div class="card-meta" style="color:var(--red)">${esc2(intro.error)}</div>` : "";
|
|
2586
|
+
const cmdStr = s.config.command ? `${s.config.command} ${(s.config.args ?? []).join(" ")}` : s.config.url ?? "";
|
|
2587
|
+
const configHtml = cmdStr ? `<div class="card-meta">${esc2(cmdStr)}</div>` : "";
|
|
2588
|
+
const copyData = cmdStr || s.name;
|
|
1713
2589
|
return `
|
|
1714
|
-
<div class="card" data-searchable="${
|
|
2590
|
+
<div class="card" data-searchable="${esc2(s.name + " " + (intro?.serverInfo?.name ?? ""))}"
|
|
2591
|
+
style="animation-delay: ${i * 0.05}s">
|
|
2592
|
+
<button class="copy-btn" data-copy="${esc2(copyData)}">copiar</button>
|
|
1715
2593
|
<div class="card-title">
|
|
1716
|
-
<span>${
|
|
2594
|
+
<span>${esc2(s.name)}${serverVersion}</span>
|
|
1717
2595
|
<span class="status ${statusClass}">${statusText}</span>
|
|
1718
2596
|
<span class="scope-badge scope-badge--${s.source}">${s.source}</span>
|
|
1719
2597
|
</div>
|
|
@@ -1729,10 +2607,13 @@ function renderMcpServers(servers) {
|
|
|
1729
2607
|
0
|
|
1730
2608
|
);
|
|
1731
2609
|
return `
|
|
1732
|
-
<div class="section">
|
|
2610
|
+
<div class="section" id="section-mcp">
|
|
1733
2611
|
<div class="section-header">
|
|
1734
|
-
<
|
|
1735
|
-
|
|
2612
|
+
<div class="section-header-left">
|
|
2613
|
+
<span class="section-icon">\u2699\uFE0F</span>
|
|
2614
|
+
<h2>MCP Servers</h2>
|
|
2615
|
+
</div>
|
|
2616
|
+
<div class="section-header-right">
|
|
1736
2617
|
<span class="count">${servers.length} servers · ${totalTools} tools</span>
|
|
1737
2618
|
<span class="arrow">▼</span>
|
|
1738
2619
|
</div>
|
|
@@ -1742,6 +2623,7 @@ function renderMcpServers(servers) {
|
|
|
1742
2623
|
}
|
|
1743
2624
|
function renderContextFiles(files) {
|
|
1744
2625
|
if (files.length === 0) return "";
|
|
2626
|
+
const maxSize = Math.max(...files.map((f) => f.size), 1);
|
|
1745
2627
|
const groups = /* @__PURE__ */ new Map();
|
|
1746
2628
|
for (const f of files) {
|
|
1747
2629
|
const existing = groups.get(f.tool) ?? [];
|
|
@@ -1750,16 +2632,21 @@ function renderContextFiles(files) {
|
|
|
1750
2632
|
}
|
|
1751
2633
|
let groupsHtml = "";
|
|
1752
2634
|
for (const [tool, toolFiles] of groups) {
|
|
1753
|
-
const items = toolFiles.map((f) => {
|
|
1754
|
-
const previewHtml = f.preview ? `<button class="preview-toggle">Ver contenido</button><div class="preview">${
|
|
1755
|
-
const errorHtml = f.error ? `<span class="status status--error">${f.error}</span>` : "";
|
|
2635
|
+
const items = toolFiles.map((f, i) => {
|
|
2636
|
+
const previewHtml = f.preview ? `<button class="preview-toggle">Ver contenido</button><div class="preview">${esc2(f.preview)}</div>` : "";
|
|
2637
|
+
const errorHtml = f.error ? `<span class="status status--error">${esc2(f.error)}</span>` : "";
|
|
1756
2638
|
const sizeStr = f.size > 0 ? formatBytes(f.size) : "";
|
|
1757
|
-
const typeIcon = f.type === "directory" ? "
|
|
2639
|
+
const typeIcon = f.type === "directory" ? "\u{1F4C1}" : "\u{1F4C4}";
|
|
2640
|
+
const sizePercent = f.size > 0 ? Math.max(3, f.size / maxSize * 100) : 0;
|
|
2641
|
+
const sizeBar = f.size > 0 ? `<div class="size-bar"><div class="size-bar-fill" style="width:${sizePercent}%"></div></div>` : "";
|
|
1758
2642
|
const childrenHtml = f.children && f.children.length > 0 ? `<div class="card-meta" style="margin-top:0.3rem">${f.children.length} archivos dentro</div>` : "";
|
|
1759
2643
|
return `
|
|
1760
|
-
<div class="card" data-searchable="${
|
|
1761
|
-
|
|
2644
|
+
<div class="card" data-searchable="${esc2(f.path + " " + tool)}"
|
|
2645
|
+
style="animation-delay: ${i * 0.04}s">
|
|
2646
|
+
<button class="copy-btn" data-copy="${esc2(f.path)}">copiar</button>
|
|
2647
|
+
<div class="card-title">${typeIcon} ${esc2(f.path)} ${errorHtml}</div>
|
|
1762
2648
|
<div class="card-meta">${sizeStr} · <span class="scope-badge scope-badge--${f.scope}">${f.scope}</span></div>
|
|
2649
|
+
${sizeBar}
|
|
1763
2650
|
${childrenHtml}
|
|
1764
2651
|
${previewHtml}
|
|
1765
2652
|
</div>`;
|
|
@@ -1774,10 +2661,13 @@ function renderContextFiles(files) {
|
|
|
1774
2661
|
</div>`;
|
|
1775
2662
|
}
|
|
1776
2663
|
return `
|
|
1777
|
-
<div class="section">
|
|
2664
|
+
<div class="section" id="section-context">
|
|
1778
2665
|
<div class="section-header">
|
|
1779
|
-
<
|
|
1780
|
-
|
|
2666
|
+
<div class="section-header-left">
|
|
2667
|
+
<span class="section-icon">\u{1F4C4}</span>
|
|
2668
|
+
<h2>Archivos de Contexto</h2>
|
|
2669
|
+
</div>
|
|
2670
|
+
<div class="section-header-right">
|
|
1781
2671
|
<span class="count">${files.length} archivos · ${groups.size} herramientas</span>
|
|
1782
2672
|
<span class="arrow">▼</span>
|
|
1783
2673
|
</div>
|
|
@@ -1787,23 +2677,27 @@ function renderContextFiles(files) {
|
|
|
1787
2677
|
}
|
|
1788
2678
|
function renderSkills(skills) {
|
|
1789
2679
|
if (skills.length === 0) return "";
|
|
1790
|
-
const items = skills.map((s) => {
|
|
1791
|
-
const triggersHtml = s.triggers ? `<div class="card-meta">${s.triggers.map((t) =>
|
|
2680
|
+
const items = skills.map((s, i) => {
|
|
2681
|
+
const triggersHtml = s.triggers ? `<div class="card-meta">${s.triggers.map((t) => esc2(t)).join(", ")}</div>` : "";
|
|
1792
2682
|
return `
|
|
1793
|
-
<div class="card" data-searchable="${
|
|
2683
|
+
<div class="card" data-searchable="${esc2(s.name + " " + (s.description ?? ""))}"
|
|
2684
|
+
style="animation-delay: ${i * 0.04}s">
|
|
1794
2685
|
<div class="card-title">
|
|
1795
|
-
${
|
|
2686
|
+
\u26A1 ${esc2(s.name)}
|
|
1796
2687
|
<span class="scope-badge scope-badge--${s.scope}">${s.scope}</span>
|
|
1797
2688
|
</div>
|
|
1798
|
-
${s.description ? `<div class="card-meta">${
|
|
2689
|
+
${s.description ? `<div class="card-meta">${esc2(s.description)}</div>` : ""}
|
|
1799
2690
|
${triggersHtml}
|
|
1800
2691
|
</div>`;
|
|
1801
2692
|
}).join("");
|
|
1802
2693
|
return `
|
|
1803
|
-
<div class="section">
|
|
2694
|
+
<div class="section" id="section-skills">
|
|
1804
2695
|
<div class="section-header">
|
|
1805
|
-
<
|
|
1806
|
-
|
|
2696
|
+
<div class="section-header-left">
|
|
2697
|
+
<span class="section-icon">\u26A1</span>
|
|
2698
|
+
<h2>Skills</h2>
|
|
2699
|
+
</div>
|
|
2700
|
+
<div class="section-header-right">
|
|
1807
2701
|
<span class="count">${skills.length}</span>
|
|
1808
2702
|
<span class="arrow">▼</span>
|
|
1809
2703
|
</div>
|
|
@@ -1813,25 +2707,29 @@ function renderSkills(skills) {
|
|
|
1813
2707
|
}
|
|
1814
2708
|
function renderAgents(agents) {
|
|
1815
2709
|
if (agents.length === 0) return "";
|
|
1816
|
-
const items = agents.map((a) => {
|
|
1817
|
-
const modelHtml = a.model ? `<span class="badge badge--green">${
|
|
1818
|
-
const memoryHtml = a.hasMemory ? '<span class="badge badge--
|
|
2710
|
+
const items = agents.map((a, i) => {
|
|
2711
|
+
const modelHtml = a.model ? `<span class="badge badge--green" style="font-size:0.65rem;padding:0.1rem 0.5rem">${esc2(a.model)}</span>` : "";
|
|
2712
|
+
const memoryHtml = a.hasMemory ? '<span class="badge badge--pink" style="font-size:0.65rem;padding:0.1rem 0.5rem">\u{1F9E0} memoria</span>' : "";
|
|
1819
2713
|
return `
|
|
1820
|
-
<div class="card" data-searchable="${
|
|
2714
|
+
<div class="card" data-searchable="${esc2(a.name + " " + (a.description ?? ""))}"
|
|
2715
|
+
style="animation-delay: ${i * 0.04}s">
|
|
1821
2716
|
<div class="card-title">
|
|
1822
|
-
${
|
|
2717
|
+
\u{1F916} ${esc2(a.name)}
|
|
1823
2718
|
${modelHtml}
|
|
1824
2719
|
${memoryHtml}
|
|
1825
2720
|
<span class="scope-badge scope-badge--${a.scope}">${a.scope}</span>
|
|
1826
2721
|
</div>
|
|
1827
|
-
${a.description ? `<div class="card-meta">${
|
|
2722
|
+
${a.description ? `<div class="card-meta">${esc2(a.description)}</div>` : ""}
|
|
1828
2723
|
</div>`;
|
|
1829
2724
|
}).join("");
|
|
1830
2725
|
return `
|
|
1831
|
-
<div class="section">
|
|
2726
|
+
<div class="section" id="section-agents">
|
|
1832
2727
|
<div class="section-header">
|
|
1833
|
-
<
|
|
1834
|
-
|
|
2728
|
+
<div class="section-header-left">
|
|
2729
|
+
<span class="section-icon">\u{1F916}</span>
|
|
2730
|
+
<h2>Agents</h2>
|
|
2731
|
+
</div>
|
|
2732
|
+
<div class="section-header-right">
|
|
1835
2733
|
<span class="count">${agents.length}</span>
|
|
1836
2734
|
<span class="arrow">▼</span>
|
|
1837
2735
|
</div>
|
|
@@ -1841,23 +2739,33 @@ function renderAgents(agents) {
|
|
|
1841
2739
|
}
|
|
1842
2740
|
function renderMemories(memories) {
|
|
1843
2741
|
if (memories.length === 0) return "";
|
|
1844
|
-
const items = memories.map((m) => {
|
|
1845
|
-
const
|
|
2742
|
+
const items = memories.map((m, i) => {
|
|
2743
|
+
const detailEntries = m.details ? Object.entries(m.details).filter(
|
|
2744
|
+
([k]) => k !== "preview"
|
|
2745
|
+
) : [];
|
|
2746
|
+
const detailsHtml = detailEntries.length > 0 ? `<div class="card-meta">${detailEntries.map(([k, v]) => `${esc2(k)}: ${esc2(String(v))}`).join(" · ")}</div>` : "";
|
|
2747
|
+
const previewVal = m.details && typeof m.details.preview === "string" ? m.details.preview : null;
|
|
2748
|
+
const previewHtml = previewVal ? `<button class="preview-toggle">Ver contenido</button><div class="preview">${esc2(previewVal)}</div>` : "";
|
|
1846
2749
|
return `
|
|
1847
|
-
<div class="card" data-searchable="${
|
|
2750
|
+
<div class="card" data-searchable="${esc2(m.type + " " + (m.path ?? ""))}"
|
|
2751
|
+
style="animation-delay: ${i * 0.04}s">
|
|
1848
2752
|
<div class="card-title">
|
|
1849
|
-
${
|
|
2753
|
+
\u{1F9E0} ${esc2(m.type)}
|
|
1850
2754
|
<span class="status status--${m.status}">${m.status}</span>
|
|
1851
2755
|
</div>
|
|
1852
|
-
<div class="card-meta">${m.source}${m.path ? ` · ${
|
|
2756
|
+
<div class="card-meta">${m.source}${m.path ? ` · ${esc2(m.path)}` : ""}</div>
|
|
1853
2757
|
${detailsHtml}
|
|
2758
|
+
${previewHtml}
|
|
1854
2759
|
</div>`;
|
|
1855
2760
|
}).join("");
|
|
1856
2761
|
return `
|
|
1857
|
-
<div class="section">
|
|
2762
|
+
<div class="section" id="section-memories">
|
|
1858
2763
|
<div class="section-header">
|
|
1859
|
-
<
|
|
1860
|
-
|
|
2764
|
+
<div class="section-header-left">
|
|
2765
|
+
<span class="section-icon">\u{1F9E0}</span>
|
|
2766
|
+
<h2>Memorias</h2>
|
|
2767
|
+
</div>
|
|
2768
|
+
<div class="section-header-right">
|
|
1861
2769
|
<span class="count">${memories.length}</span>
|
|
1862
2770
|
<span class="arrow">▼</span>
|
|
1863
2771
|
</div>
|
|
@@ -1865,14 +2773,40 @@ function renderMemories(memories) {
|
|
|
1865
2773
|
<div class="section-content">${items}</div>
|
|
1866
2774
|
</div>`;
|
|
1867
2775
|
}
|
|
2776
|
+
function renderWarnings(warnings) {
|
|
2777
|
+
if (warnings.length === 0) return "";
|
|
2778
|
+
const items = warnings.map(
|
|
2779
|
+
(w) => `
|
|
2780
|
+
<div class="warning-card">
|
|
2781
|
+
<div class="warning-scanner">${esc2(w.scanner)}${w.path ? ` — ${esc2(w.path)}` : ""}</div>
|
|
2782
|
+
<div>${esc2(w.message)}</div>
|
|
2783
|
+
</div>`
|
|
2784
|
+
).join("");
|
|
2785
|
+
return `
|
|
2786
|
+
<div class="section" id="section-warnings">
|
|
2787
|
+
<div class="section-header">
|
|
2788
|
+
<div class="section-header-left">
|
|
2789
|
+
<span class="section-icon">\u26A0\uFE0F</span>
|
|
2790
|
+
<h2>Advertencias</h2>
|
|
2791
|
+
</div>
|
|
2792
|
+
<div class="section-header-right">
|
|
2793
|
+
<span class="count">${warnings.length}</span>
|
|
2794
|
+
<span class="arrow">▼</span>
|
|
2795
|
+
</div>
|
|
2796
|
+
</div>
|
|
2797
|
+
<div class="section-content">${items}</div>
|
|
2798
|
+
</div>`;
|
|
2799
|
+
}
|
|
1868
2800
|
function renderEmptyState() {
|
|
1869
2801
|
return `
|
|
1870
2802
|
<div class="empty-state">
|
|
2803
|
+
<span class="empty-state-icon">\u{1F50D}</span>
|
|
1871
2804
|
<h3>No se encontr\xF3 configuraci\xF3n AI en este proyecto</h3>
|
|
1872
2805
|
<p>Este proyecto no tiene archivos de configuraci\xF3n de herramientas AI.</p>
|
|
1873
|
-
<p style="margin-top:1rem;font-size:0.85rem">
|
|
2806
|
+
<p style="margin-top:1rem;font-size:0.85rem;color:var(--text-dim)">
|
|
1874
2807
|
Herramientas soportadas: Claude, Cursor, Windsurf, Copilot, Gemini,
|
|
1875
|
-
Codex, Aider, Cline, Continue, Amazon Q, Augment,
|
|
2808
|
+
Codex, OpenCode, Aider, Cline, Roo, Continue, Amazon Q, Augment,
|
|
2809
|
+
Replit, Firebase Studio, Tabnine, Sourcegraph
|
|
1876
2810
|
</p>
|
|
1877
2811
|
</div>`;
|
|
1878
2812
|
}
|
|
@@ -1894,14 +2828,46 @@ function computeSummary(result) {
|
|
|
1894
2828
|
function generateHtml(result) {
|
|
1895
2829
|
const summary = computeSummary(result);
|
|
1896
2830
|
const isEmpty = summary.totalMcpServers === 0 && summary.totalFiles === 0 && summary.totalSkills === 0 && summary.totalAgents === 0 && summary.totalMemories === 0;
|
|
2831
|
+
const navBar = renderNavBar(summary);
|
|
1897
2832
|
const header = renderHeader(result.project, summary, result.scanDuration);
|
|
2833
|
+
const statsGrid = renderStatsGrid(summary);
|
|
2834
|
+
const ecosystemMap = renderEcosystemMap(result, summary);
|
|
1898
2835
|
const content = isEmpty ? renderEmptyState() : [
|
|
1899
2836
|
renderMcpServers(result.mcpServers),
|
|
1900
2837
|
renderContextFiles(result.contextFiles),
|
|
1901
2838
|
renderSkills(result.skills),
|
|
1902
2839
|
renderAgents(result.agents),
|
|
1903
|
-
renderMemories(result.memories)
|
|
2840
|
+
renderMemories(result.memories),
|
|
2841
|
+
renderWarnings(result.warnings)
|
|
1904
2842
|
].join("");
|
|
2843
|
+
const exportData = {
|
|
2844
|
+
project: result.project.name,
|
|
2845
|
+
scannedAt: result.project.scannedAt,
|
|
2846
|
+
scanDuration: result.scanDuration,
|
|
2847
|
+
summary,
|
|
2848
|
+
mcpServers: result.mcpServers.map((s) => ({
|
|
2849
|
+
name: s.name,
|
|
2850
|
+
source: s.source,
|
|
2851
|
+
transport: s.config.transport,
|
|
2852
|
+
tools: s.introspection?.tools.length ?? 0
|
|
2853
|
+
})),
|
|
2854
|
+
contextFiles: result.contextFiles.map((f) => ({
|
|
2855
|
+
path: f.path,
|
|
2856
|
+
tool: f.tool,
|
|
2857
|
+
scope: f.scope
|
|
2858
|
+
})),
|
|
2859
|
+
skills: result.skills.map((s) => ({ name: s.name, scope: s.scope })),
|
|
2860
|
+
agents: result.agents.map((a) => ({
|
|
2861
|
+
name: a.name,
|
|
2862
|
+
scope: a.scope,
|
|
2863
|
+
model: a.model
|
|
2864
|
+
})),
|
|
2865
|
+
memories: result.memories.map((m) => ({
|
|
2866
|
+
type: m.type,
|
|
2867
|
+
source: m.source,
|
|
2868
|
+
status: m.status
|
|
2869
|
+
}))
|
|
2870
|
+
};
|
|
1905
2871
|
return `<!DOCTYPE html>
|
|
1906
2872
|
<html lang="es">
|
|
1907
2873
|
<head>
|
|
@@ -1911,16 +2877,25 @@ function generateHtml(result) {
|
|
|
1911
2877
|
<style>${CSS_STYLES}</style>
|
|
1912
2878
|
</head>
|
|
1913
2879
|
<body>
|
|
2880
|
+
${navBar}
|
|
1914
2881
|
<div class="container">
|
|
1915
2882
|
${header}
|
|
2883
|
+
${statsGrid}
|
|
2884
|
+
${ecosystemMap}
|
|
1916
2885
|
<div class="search-bar">
|
|
1917
|
-
<
|
|
2886
|
+
<div class="search-bar-inner">
|
|
2887
|
+
<span class="search-icon">\u{1F50D}</span>
|
|
2888
|
+
<input type="text" id="search-input" placeholder="Buscar tools, archivos, skills, agents..." />
|
|
2889
|
+
<span class="search-kbd">/</span>
|
|
2890
|
+
</div>
|
|
2891
|
+
<span class="search-results-count"></span>
|
|
1918
2892
|
</div>
|
|
1919
2893
|
${content}
|
|
1920
2894
|
<footer class="footer">
|
|
1921
2895
|
Generado por ai-context-inspector — ${new Date(result.project.scannedAt).toLocaleString()}
|
|
1922
2896
|
</footer>
|
|
1923
2897
|
</div>
|
|
2898
|
+
<script type="application/json" id="scan-data">${JSON.stringify(exportData)}</script>
|
|
1924
2899
|
<script>${JS_SCRIPTS}</script>
|
|
1925
2900
|
</body>
|
|
1926
2901
|
</html>`;
|