@cocaxcode/ai-context-inspector 0.2.0 → 0.3.1
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-UKKIDJCN.js} +1182 -93
- package/dist/index.js +2 -2
- package/dist/{server-AOHGLJDI.js → server-AC6HNUZR.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,176 @@ 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 Scroll helper with offset for sticky nav + search \u2500\u2500
|
|
2103
|
+
const SCROLL_OFFSET = 110
|
|
2104
|
+
|
|
2105
|
+
function scrollToSection(targetId) {
|
|
2106
|
+
const target = document.getElementById(targetId)
|
|
2107
|
+
if (!target) return
|
|
2108
|
+
if (target.classList.contains('collapsed')) {
|
|
2109
|
+
target.classList.remove('collapsed')
|
|
2110
|
+
const content = target.querySelector('.section-content')
|
|
2111
|
+
if (content) {
|
|
2112
|
+
content.style.maxHeight = content.scrollHeight + 'px'
|
|
2113
|
+
setTimeout(() => { content.style.maxHeight = '' }, 400)
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
const y = target.getBoundingClientRect().top + window.scrollY - SCROLL_OFFSET
|
|
2117
|
+
window.scrollTo({ top: y, behavior: 'smooth' })
|
|
2118
|
+
target.style.boxShadow = '0 0 0 2px var(--accent)'
|
|
2119
|
+
setTimeout(() => { target.style.boxShadow = '' }, 1500)
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
// \u2500\u2500 Animated Counters \u2500\u2500
|
|
2123
|
+
document.querySelectorAll('.stat-number').forEach(el => {
|
|
2124
|
+
const target = parseInt(el.getAttribute('data-target') || '0')
|
|
2125
|
+
if (target === 0) { el.textContent = '0'; return }
|
|
2126
|
+
let current = 0
|
|
2127
|
+
const step = Math.max(1, Math.ceil(target / 25))
|
|
2128
|
+
const interval = setInterval(() => {
|
|
2129
|
+
current = Math.min(current + step, target)
|
|
2130
|
+
el.textContent = current
|
|
2131
|
+
if (current >= target) clearInterval(interval)
|
|
2132
|
+
}, 35)
|
|
2133
|
+
})
|
|
2134
|
+
|
|
2135
|
+
// \u2500\u2500 Section Collapse/Expand \u2500\u2500
|
|
1632
2136
|
document.querySelectorAll('.section-header').forEach(header => {
|
|
1633
2137
|
header.addEventListener('click', () => {
|
|
1634
|
-
header.closest('.section')
|
|
2138
|
+
const section = header.closest('.section')
|
|
2139
|
+
if (!section) return
|
|
2140
|
+
const content = section.querySelector('.section-content')
|
|
2141
|
+
if (!content) return
|
|
2142
|
+
|
|
2143
|
+
if (section.classList.contains('collapsed')) {
|
|
2144
|
+
// Expand
|
|
2145
|
+
section.classList.remove('collapsed')
|
|
2146
|
+
content.style.maxHeight = content.scrollHeight + 'px'
|
|
2147
|
+
setTimeout(() => { content.style.maxHeight = '' }, 400)
|
|
2148
|
+
} else {
|
|
2149
|
+
// Collapse
|
|
2150
|
+
content.style.maxHeight = content.scrollHeight + 'px'
|
|
2151
|
+
requestAnimationFrame(() => {
|
|
2152
|
+
content.style.maxHeight = '0px'
|
|
2153
|
+
section.classList.add('collapsed')
|
|
2154
|
+
})
|
|
2155
|
+
}
|
|
1635
2156
|
})
|
|
1636
2157
|
})
|
|
1637
2158
|
|
|
1638
|
-
// Preview
|
|
2159
|
+
// \u2500\u2500 Preview Toggle \u2500\u2500
|
|
1639
2160
|
document.querySelectorAll('.preview-toggle').forEach(btn => {
|
|
1640
2161
|
btn.addEventListener('click', (e) => {
|
|
1641
2162
|
e.stopPropagation()
|
|
@@ -1647,55 +2168,522 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1647
2168
|
})
|
|
1648
2169
|
})
|
|
1649
2170
|
|
|
1650
|
-
// Global
|
|
2171
|
+
// \u2500\u2500 Global Search \u2500\u2500
|
|
1651
2172
|
const searchInput = document.getElementById('search-input')
|
|
2173
|
+
const resultsCount = document.querySelector('.search-results-count')
|
|
2174
|
+
const searchKbd = document.querySelector('.search-kbd')
|
|
2175
|
+
|
|
1652
2176
|
if (searchInput) {
|
|
1653
2177
|
searchInput.addEventListener('input', (e) => {
|
|
1654
2178
|
const query = e.target.value.toLowerCase().trim()
|
|
2179
|
+
let visible = 0
|
|
2180
|
+
let total = 0
|
|
2181
|
+
|
|
1655
2182
|
document.querySelectorAll('[data-searchable]').forEach(el => {
|
|
1656
2183
|
const text = el.getAttribute('data-searchable').toLowerCase()
|
|
1657
|
-
|
|
2184
|
+
const match = !query || text.includes(query)
|
|
2185
|
+
el.style.display = match ? '' : 'none'
|
|
2186
|
+
total++
|
|
2187
|
+
if (match) visible++
|
|
1658
2188
|
})
|
|
2189
|
+
|
|
2190
|
+
if (resultsCount) {
|
|
2191
|
+
resultsCount.textContent = query ? visible + ' / ' + total : ''
|
|
2192
|
+
}
|
|
2193
|
+
if (searchKbd) {
|
|
2194
|
+
searchKbd.style.display = query ? 'none' : ''
|
|
2195
|
+
}
|
|
2196
|
+
})
|
|
2197
|
+
|
|
2198
|
+
searchInput.addEventListener('focus', () => {
|
|
2199
|
+
if (searchKbd) searchKbd.style.display = 'none'
|
|
2200
|
+
})
|
|
2201
|
+
|
|
2202
|
+
searchInput.addEventListener('blur', () => {
|
|
2203
|
+
if (searchKbd && !searchInput.value) searchKbd.style.display = ''
|
|
1659
2204
|
})
|
|
1660
2205
|
}
|
|
2206
|
+
|
|
2207
|
+
// \u2500\u2500 Keyboard Shortcuts \u2500\u2500
|
|
2208
|
+
document.addEventListener('keydown', (e) => {
|
|
2209
|
+
// Ignore when typing in input
|
|
2210
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
|
|
2211
|
+
if (e.key === 'Escape') {
|
|
2212
|
+
e.target.value = ''
|
|
2213
|
+
e.target.dispatchEvent(new Event('input'))
|
|
2214
|
+
e.target.blur()
|
|
2215
|
+
}
|
|
2216
|
+
return
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
if (e.key === '/') {
|
|
2220
|
+
e.preventDefault()
|
|
2221
|
+
if (searchInput) searchInput.focus()
|
|
2222
|
+
}
|
|
2223
|
+
})
|
|
2224
|
+
|
|
2225
|
+
// \u2500\u2500 Nav Links (scroll to section) \u2500\u2500
|
|
2226
|
+
document.querySelectorAll('.nav-link[data-target]').forEach(link => {
|
|
2227
|
+
link.addEventListener('click', (e) => {
|
|
2228
|
+
e.preventDefault()
|
|
2229
|
+
scrollToSection(link.getAttribute('data-target'))
|
|
2230
|
+
})
|
|
2231
|
+
})
|
|
2232
|
+
|
|
2233
|
+
// \u2500\u2500 Ecosystem Map (click to scroll) \u2500\u2500
|
|
2234
|
+
document.querySelectorAll('.eco-cat-node[data-section]').forEach(node => {
|
|
2235
|
+
node.addEventListener('click', () => {
|
|
2236
|
+
scrollToSection(node.getAttribute('data-section'))
|
|
2237
|
+
})
|
|
2238
|
+
node.addEventListener('keydown', (e) => {
|
|
2239
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
2240
|
+
e.preventDefault()
|
|
2241
|
+
scrollToSection(node.getAttribute('data-section'))
|
|
2242
|
+
}
|
|
2243
|
+
})
|
|
2244
|
+
})
|
|
2245
|
+
|
|
2246
|
+
// \u2500\u2500 Ecosystem Map Tooltips \u2500\u2500
|
|
2247
|
+
const ecoTooltip = document.getElementById('eco-tooltip')
|
|
2248
|
+
const ecoTooltipText = document.getElementById('eco-tooltip-text')
|
|
2249
|
+
|
|
2250
|
+
document.querySelectorAll('.eco-item[data-tooltip]').forEach(item => {
|
|
2251
|
+
item.addEventListener('mouseenter', () => {
|
|
2252
|
+
if (!ecoTooltip || !ecoTooltipText) return
|
|
2253
|
+
const text = item.getAttribute('data-tooltip')
|
|
2254
|
+
ecoTooltipText.textContent = text
|
|
2255
|
+
const textLen = Math.min(text.length * 6.5 + 20, 220)
|
|
2256
|
+
const rect = ecoTooltip.querySelector('rect')
|
|
2257
|
+
if (rect) {
|
|
2258
|
+
rect.setAttribute('width', textLen)
|
|
2259
|
+
ecoTooltipText.setAttribute('x', textLen / 2)
|
|
2260
|
+
}
|
|
2261
|
+
const circle = item.querySelector('circle')
|
|
2262
|
+
if (circle) {
|
|
2263
|
+
const cx = parseFloat(circle.getAttribute('cx'))
|
|
2264
|
+
const cy = parseFloat(circle.getAttribute('cy'))
|
|
2265
|
+
ecoTooltip.setAttribute('transform', 'translate(' + (cx - textLen / 2) + ',' + (cy - 38) + ')')
|
|
2266
|
+
}
|
|
2267
|
+
ecoTooltip.style.display = ''
|
|
2268
|
+
})
|
|
2269
|
+
item.addEventListener('mouseleave', () => {
|
|
2270
|
+
if (ecoTooltip) ecoTooltip.style.display = 'none'
|
|
2271
|
+
})
|
|
2272
|
+
})
|
|
2273
|
+
|
|
2274
|
+
// \u2500\u2500 Theme Toggle \u2500\u2500
|
|
2275
|
+
const themeToggle = document.getElementById('theme-toggle')
|
|
2276
|
+
if (themeToggle) {
|
|
2277
|
+
themeToggle.addEventListener('click', () => {
|
|
2278
|
+
const html = document.documentElement
|
|
2279
|
+
const current = html.getAttribute('data-theme')
|
|
2280
|
+
const next = current === 'light' ? 'dark' : 'light'
|
|
2281
|
+
html.setAttribute('data-theme', next)
|
|
2282
|
+
themeToggle.innerHTML = next === 'light' ? '☀' : '☾'
|
|
2283
|
+
})
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
// \u2500\u2500 Export JSON \u2500\u2500
|
|
2287
|
+
const exportBtn = document.getElementById('export-btn')
|
|
2288
|
+
if (exportBtn) {
|
|
2289
|
+
exportBtn.addEventListener('click', () => {
|
|
2290
|
+
const dataEl = document.getElementById('scan-data')
|
|
2291
|
+
if (!dataEl) return
|
|
2292
|
+
const text = dataEl.textContent || ''
|
|
2293
|
+
const fallback = () => {
|
|
2294
|
+
const ta = document.createElement('textarea')
|
|
2295
|
+
ta.value = text
|
|
2296
|
+
ta.style.position = 'fixed'
|
|
2297
|
+
ta.style.opacity = '0'
|
|
2298
|
+
document.body.appendChild(ta)
|
|
2299
|
+
ta.select()
|
|
2300
|
+
document.execCommand('copy')
|
|
2301
|
+
document.body.removeChild(ta)
|
|
2302
|
+
}
|
|
2303
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
2304
|
+
navigator.clipboard.writeText(text).catch(fallback)
|
|
2305
|
+
} else {
|
|
2306
|
+
fallback()
|
|
2307
|
+
}
|
|
2308
|
+
const orig = exportBtn.innerHTML
|
|
2309
|
+
exportBtn.textContent = '\\u2713'
|
|
2310
|
+
setTimeout(() => { exportBtn.innerHTML = orig }, 1500)
|
|
2311
|
+
})
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
// \u2500\u2500 Copy Buttons \u2500\u2500
|
|
2315
|
+
document.querySelectorAll('.copy-btn').forEach(btn => {
|
|
2316
|
+
btn.addEventListener('click', (e) => {
|
|
2317
|
+
e.stopPropagation()
|
|
2318
|
+
const text = btn.getAttribute('data-copy') || ''
|
|
2319
|
+
const fallback = () => {
|
|
2320
|
+
const ta = document.createElement('textarea')
|
|
2321
|
+
ta.value = text
|
|
2322
|
+
ta.style.position = 'fixed'
|
|
2323
|
+
ta.style.opacity = '0'
|
|
2324
|
+
document.body.appendChild(ta)
|
|
2325
|
+
ta.select()
|
|
2326
|
+
document.execCommand('copy')
|
|
2327
|
+
document.body.removeChild(ta)
|
|
2328
|
+
}
|
|
2329
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
2330
|
+
navigator.clipboard.writeText(text).catch(fallback)
|
|
2331
|
+
} else {
|
|
2332
|
+
fallback()
|
|
2333
|
+
}
|
|
2334
|
+
btn.classList.add('copy-btn--copied')
|
|
2335
|
+
btn.textContent = '\\u2713'
|
|
2336
|
+
setTimeout(() => {
|
|
2337
|
+
btn.classList.remove('copy-btn--copied')
|
|
2338
|
+
btn.textContent = 'copiar'
|
|
2339
|
+
}, 1500)
|
|
2340
|
+
})
|
|
2341
|
+
})
|
|
2342
|
+
|
|
1661
2343
|
})
|
|
1662
2344
|
`;
|
|
1663
2345
|
|
|
1664
|
-
// src/report/
|
|
2346
|
+
// src/report/ecosystem-map.ts
|
|
1665
2347
|
function esc(str) {
|
|
1666
2348
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1667
2349
|
}
|
|
2350
|
+
function buildCategories(result, summary) {
|
|
2351
|
+
return [
|
|
2352
|
+
{
|
|
2353
|
+
id: "section-mcp",
|
|
2354
|
+
label: "MCP",
|
|
2355
|
+
icon: "\u2699",
|
|
2356
|
+
count: summary.totalMcpServers,
|
|
2357
|
+
color: "#00d4ff",
|
|
2358
|
+
items: result.mcpServers.slice(0, 6).map((s) => ({
|
|
2359
|
+
name: s.name,
|
|
2360
|
+
detail: `${s.introspection?.tools.length ?? 0} tools`
|
|
2361
|
+
}))
|
|
2362
|
+
},
|
|
2363
|
+
{
|
|
2364
|
+
id: "section-context",
|
|
2365
|
+
label: "Contexto",
|
|
2366
|
+
icon: "\u{1F4C4}",
|
|
2367
|
+
count: summary.totalFiles,
|
|
2368
|
+
color: "#b388ff",
|
|
2369
|
+
items: result.contextFiles.slice(0, 6).map((f) => ({
|
|
2370
|
+
name: f.path,
|
|
2371
|
+
detail: f.tool
|
|
2372
|
+
}))
|
|
2373
|
+
},
|
|
2374
|
+
{
|
|
2375
|
+
id: "section-skills",
|
|
2376
|
+
label: "Skills",
|
|
2377
|
+
icon: "\u26A1",
|
|
2378
|
+
count: summary.totalSkills,
|
|
2379
|
+
color: "#ffab40",
|
|
2380
|
+
items: result.skills.slice(0, 6).map((s) => ({
|
|
2381
|
+
name: s.name
|
|
2382
|
+
}))
|
|
2383
|
+
},
|
|
2384
|
+
{
|
|
2385
|
+
id: "section-agents",
|
|
2386
|
+
label: "Agents",
|
|
2387
|
+
icon: "\u{1F916}",
|
|
2388
|
+
count: summary.totalAgents,
|
|
2389
|
+
color: "#00e676",
|
|
2390
|
+
items: result.agents.slice(0, 6).map((a) => ({
|
|
2391
|
+
name: a.name,
|
|
2392
|
+
detail: a.model
|
|
2393
|
+
}))
|
|
2394
|
+
},
|
|
2395
|
+
{
|
|
2396
|
+
id: "section-memories",
|
|
2397
|
+
label: "Memorias",
|
|
2398
|
+
icon: "\u{1F9E0}",
|
|
2399
|
+
count: summary.totalMemories,
|
|
2400
|
+
color: "#ff80ab",
|
|
2401
|
+
items: result.memories.slice(0, 6).map((m) => ({
|
|
2402
|
+
name: m.type,
|
|
2403
|
+
detail: m.status
|
|
2404
|
+
}))
|
|
2405
|
+
}
|
|
2406
|
+
];
|
|
2407
|
+
}
|
|
2408
|
+
var COCAXCODE_ICON = `<g transform="translate(-16,-16)">
|
|
2409
|
+
<defs>
|
|
2410
|
+
<linearGradient id="cxc-g1" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
2411
|
+
<stop offset="0%" stop-color="#22d3ee"/>
|
|
2412
|
+
<stop offset="100%" stop-color="#06b6d4"/>
|
|
2413
|
+
</linearGradient>
|
|
2414
|
+
<linearGradient id="cxc-g2" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
2415
|
+
<stop offset="0%" stop-color="#0891b2"/>
|
|
2416
|
+
<stop offset="100%" stop-color="#0e7490"/>
|
|
2417
|
+
</linearGradient>
|
|
2418
|
+
</defs>
|
|
2419
|
+
<rect width="32" height="32" fill="#0a0a0c" rx="4"/>
|
|
2420
|
+
<rect x="9" y="7.5" width="16" height="18.5" rx="4" fill="url(#cxc-g2)" opacity="0.5"/>
|
|
2421
|
+
<rect x="8" y="6.5" width="16" height="18.5" rx="4" fill="url(#cxc-g1)"/>
|
|
2422
|
+
<path d="M11.5,11 L18,16 L11.5,21" fill="none" stroke="#0a0a0c" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
|
2423
|
+
<line x1="19" y1="20" x2="21.5" y2="20" stroke="#0a0a0c" stroke-width="1.8" stroke-linecap="round">
|
|
2424
|
+
<animate attributeName="x1" values="19;21.5;19" dur="1.4s" repeatCount="indefinite"/>
|
|
2425
|
+
<animate attributeName="x2" values="21.5;24;21.5" dur="1.4s" repeatCount="indefinite"/>
|
|
2426
|
+
</line>
|
|
2427
|
+
</g>`;
|
|
2428
|
+
function renderEcosystemMap(result, summary) {
|
|
2429
|
+
const categories = buildCategories(result, summary);
|
|
2430
|
+
const totalItems = summary.totalMcpServers + summary.totalFiles + summary.totalSkills + summary.totalAgents + summary.totalMemories;
|
|
2431
|
+
if (totalItems === 0) return "";
|
|
2432
|
+
const W = 900;
|
|
2433
|
+
const H = 480;
|
|
2434
|
+
const cx = W / 2;
|
|
2435
|
+
const cy = H / 2;
|
|
2436
|
+
const catRadius = 165;
|
|
2437
|
+
const itemRadius = 55;
|
|
2438
|
+
const startAngle = -Math.PI / 2;
|
|
2439
|
+
let defs = "";
|
|
2440
|
+
let connections = "";
|
|
2441
|
+
let itemNodes = "";
|
|
2442
|
+
let catNodes = "";
|
|
2443
|
+
defs += `<defs>
|
|
2444
|
+
<filter id="eco-glow" x="-40%" y="-40%" width="180%" height="180%">
|
|
2445
|
+
<feGaussianBlur stdDeviation="6" result="blur"/>
|
|
2446
|
+
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
|
|
2447
|
+
</filter>
|
|
2448
|
+
<filter id="eco-glow-center" x="-50%" y="-50%" width="200%" height="200%">
|
|
2449
|
+
<feGaussianBlur stdDeviation="10" result="blur"/>
|
|
2450
|
+
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
2451
|
+
</filter>
|
|
2452
|
+
<radialGradient id="eco-bg-grad" cx="50%" cy="50%" r="55%">
|
|
2453
|
+
<stop offset="0%" stop-color="var(--accent)" stop-opacity="0.03"/>
|
|
2454
|
+
<stop offset="100%" stop-color="var(--accent)" stop-opacity="0"/>
|
|
2455
|
+
</radialGradient>
|
|
2456
|
+
</defs>`;
|
|
2457
|
+
let bg = `<rect width="${W}" height="${H}" fill="none"/>
|
|
2458
|
+
<circle cx="${cx}" cy="${cy}" r="280" fill="url(#eco-bg-grad)"/>`;
|
|
2459
|
+
bg += `<circle cx="${cx}" cy="${cy}" r="${catRadius}" fill="none"
|
|
2460
|
+
stroke="var(--border)" stroke-width="0.5" stroke-dasharray="4 8" opacity="0.4"/>`;
|
|
2461
|
+
categories.forEach((cat, i) => {
|
|
2462
|
+
const angle = startAngle + i / categories.length * Math.PI * 2;
|
|
2463
|
+
const x = cx + Math.cos(angle) * catRadius;
|
|
2464
|
+
const y = cy + Math.sin(angle) * catRadius;
|
|
2465
|
+
const dimmed = cat.count === 0;
|
|
2466
|
+
const opacity = dimmed ? 0.2 : 1;
|
|
2467
|
+
const midX = (cx + x) / 2;
|
|
2468
|
+
const midY = (cy + y) / 2;
|
|
2469
|
+
const perpX = -(y - cy) * 0.08;
|
|
2470
|
+
const perpY = (x - cx) * 0.08;
|
|
2471
|
+
connections += `<path d="M${cx},${cy} Q${midX + perpX},${midY + perpY} ${x},${y}"
|
|
2472
|
+
fill="none" stroke="${cat.color}" stroke-width="${dimmed ? 0.8 : 1.5}"
|
|
2473
|
+
opacity="${dimmed ? 0.15 : 0.35}"
|
|
2474
|
+
stroke-dasharray="${dimmed ? "3 6" : "6 4"}"
|
|
2475
|
+
${!dimmed ? `style="animation: eco-dash 15s linear infinite; animation-delay: ${i * 0.5}s"` : ""}/>`;
|
|
2476
|
+
if (!dimmed && cat.items.length > 0) {
|
|
2477
|
+
const maxItems = Math.min(cat.items.length, 6);
|
|
2478
|
+
const arcSpread = Math.min(Math.PI * 0.6, maxItems * 0.25);
|
|
2479
|
+
cat.items.slice(0, maxItems).forEach((item, j) => {
|
|
2480
|
+
const itemAngle = angle - arcSpread / 2 + (maxItems > 1 ? j / (maxItems - 1) * arcSpread : 0);
|
|
2481
|
+
const ix = x + Math.cos(itemAngle) * itemRadius;
|
|
2482
|
+
const iy = y + Math.sin(itemAngle) * itemRadius;
|
|
2483
|
+
connections += `<line x1="${x}" y1="${y}" x2="${ix}" y2="${iy}"
|
|
2484
|
+
stroke="${cat.color}" stroke-width="0.7" opacity="0.2"/>`;
|
|
2485
|
+
const shortName = item.name.length > 15 ? item.name.slice(0, 13) + ".." : item.name;
|
|
2486
|
+
itemNodes += `<g class="eco-item" data-tooltip="${esc(item.name)}${item.detail ? " (" + esc(item.detail) + ")" : ""}">
|
|
2487
|
+
<circle cx="${ix}" cy="${iy}" r="4.5" fill="${cat.color}" opacity="0.5"
|
|
2488
|
+
class="eco-item-dot"/>
|
|
2489
|
+
<circle cx="${ix}" cy="${iy}" r="4.5" fill="transparent" stroke="${cat.color}"
|
|
2490
|
+
stroke-width="1" opacity="0.3"/>
|
|
2491
|
+
<title>${esc(item.name)}${item.detail ? " \u2014 " + esc(item.detail) : ""}</title>
|
|
2492
|
+
</g>`;
|
|
2493
|
+
});
|
|
2494
|
+
if (cat.count > maxItems) {
|
|
2495
|
+
const extra = cat.count - maxItems;
|
|
2496
|
+
const moreAngle = angle + arcSpread / 2 + 0.35;
|
|
2497
|
+
const mx = x + Math.cos(moreAngle) * (itemRadius * 0.85);
|
|
2498
|
+
const my = y + Math.sin(moreAngle) * (itemRadius * 0.85);
|
|
2499
|
+
itemNodes += `<text x="${mx}" y="${my}"
|
|
2500
|
+
font-family="var(--font-mono)" font-size="9" fill="${cat.color}"
|
|
2501
|
+
opacity="0.5" text-anchor="middle" dominant-baseline="middle">+${extra}</text>`;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
const nodeR = dimmed ? 24 : 32;
|
|
2505
|
+
const labelY = y - nodeR - 10;
|
|
2506
|
+
catNodes += `<g class="eco-cat-node" data-section="${cat.id}"
|
|
2507
|
+
style="cursor:pointer;opacity:${opacity}" role="button" tabindex="0">
|
|
2508
|
+
<!-- Hit area -->
|
|
2509
|
+
<circle cx="${x}" cy="${y}" r="${nodeR + 8}" fill="transparent"/>
|
|
2510
|
+
<!-- Outer glow ring -->
|
|
2511
|
+
${!dimmed ? `<circle cx="${x}" cy="${y}" r="${nodeR + 3}" fill="none"
|
|
2512
|
+
stroke="${cat.color}" stroke-width="1" opacity="0.15"
|
|
2513
|
+
style="animation: eco-pulse 3s ease-in-out infinite; animation-delay: ${i * 0.4}s"/>` : ""}
|
|
2514
|
+
<!-- Main circle -->
|
|
2515
|
+
<circle cx="${x}" cy="${y}" r="${nodeR}" fill="${cat.color}"
|
|
2516
|
+
opacity="${dimmed ? 0.08 : 0.12}" stroke="${cat.color}"
|
|
2517
|
+
stroke-width="${dimmed ? 1 : 2}" class="eco-cat-circle"
|
|
2518
|
+
${!dimmed ? 'filter="url(#eco-glow)"' : ""}/>
|
|
2519
|
+
<!-- Count inside -->
|
|
2520
|
+
<text x="${x}" y="${y + 1}" text-anchor="middle" dominant-baseline="middle"
|
|
2521
|
+
font-family="var(--font-mono)" font-size="${dimmed ? 14 : 18}"
|
|
2522
|
+
font-weight="700" fill="${cat.color}" opacity="${dimmed ? 0.3 : 0.9}">${cat.count}</text>
|
|
2523
|
+
<!-- Label + icon ABOVE circle -->
|
|
2524
|
+
<text x="${x}" y="${labelY}" text-anchor="middle" dominant-baseline="auto"
|
|
2525
|
+
font-family="var(--font-mono)" font-size="11" font-weight="600"
|
|
2526
|
+
fill="${cat.color}" opacity="${dimmed ? 0.3 : 0.85}">${cat.icon} ${cat.label}</text>
|
|
2527
|
+
</g>`;
|
|
2528
|
+
});
|
|
2529
|
+
const projectName = result.project.name.length > 20 ? result.project.name.slice(0, 18) + ".." : result.project.name;
|
|
2530
|
+
const centerNode = `<g class="eco-center">
|
|
2531
|
+
<!-- Outer pulse ring -->
|
|
2532
|
+
<circle cx="${cx}" cy="${cy}" r="48" fill="none" stroke="var(--accent)"
|
|
2533
|
+
stroke-width="1" opacity="0.2"
|
|
2534
|
+
style="animation: eco-pulse 4s ease-in-out infinite"/>
|
|
2535
|
+
<!-- Main circle -->
|
|
2536
|
+
<circle cx="${cx}" cy="${cy}" r="42" fill="var(--bg-alt)" stroke="var(--accent)"
|
|
2537
|
+
stroke-width="2.5" filter="url(#eco-glow-center)"/>
|
|
2538
|
+
<!-- cocaxcode icon in center -->
|
|
2539
|
+
<g transform="translate(${cx},${cy - 8}) scale(0.85)">
|
|
2540
|
+
${COCAXCODE_ICON}
|
|
2541
|
+
</g>
|
|
2542
|
+
<!-- Project name below icon -->
|
|
2543
|
+
<text x="${cx}" y="${cy + 20}" text-anchor="middle" dominant-baseline="auto"
|
|
2544
|
+
font-family="var(--font-mono)" font-size="10" font-weight="600"
|
|
2545
|
+
fill="var(--text-bright)" opacity="0.9">${esc(projectName)}</text>
|
|
2546
|
+
<!-- Total count -->
|
|
2547
|
+
<text x="${cx}" y="${cy + 32}" text-anchor="middle" dominant-baseline="auto"
|
|
2548
|
+
font-family="var(--font-mono)" font-size="8.5"
|
|
2549
|
+
fill="var(--text-dim)">${totalItems} elementos</text>
|
|
2550
|
+
</g>`;
|
|
2551
|
+
const tooltip = `<g id="eco-tooltip" style="display:none;pointer-events:none">
|
|
2552
|
+
<rect x="0" y="0" width="160" height="28" rx="4"
|
|
2553
|
+
fill="var(--bg-card)" stroke="var(--border)" stroke-width="1" opacity="0.95"/>
|
|
2554
|
+
<text x="80" y="18" text-anchor="middle" font-family="var(--font-mono)"
|
|
2555
|
+
font-size="10" fill="var(--text)" id="eco-tooltip-text"></text>
|
|
2556
|
+
</g>`;
|
|
2557
|
+
const watermark = `<text x="${W - 12}" y="${H - 10}" text-anchor="end"
|
|
2558
|
+
font-family="var(--font-mono)" font-size="9" fill="var(--text-dim)" opacity="0.3"
|
|
2559
|
+
letter-spacing="0.05em">cocaxcode</text>`;
|
|
2560
|
+
const svgContent = `${defs}${bg}${connections}${itemNodes}${catNodes}${centerNode}${tooltip}${watermark}`;
|
|
2561
|
+
return `
|
|
2562
|
+
<div class="ecosystem-map">
|
|
2563
|
+
<svg class="ecosystem-svg" viewBox="0 0 ${W} ${H}" xmlns="http://www.w3.org/2000/svg"
|
|
2564
|
+
role="img" aria-label="Mapa del ecosistema AI del proyecto">
|
|
2565
|
+
<style>
|
|
2566
|
+
@keyframes eco-dash { to { stroke-dashoffset: -80; } }
|
|
2567
|
+
@keyframes eco-pulse { 0%,100% { opacity: 0.15; transform-origin: center; } 50% { opacity: 0.4; } }
|
|
2568
|
+
.eco-cat-node:hover .eco-cat-circle { stroke-width: 3; opacity: 0.25; }
|
|
2569
|
+
.eco-cat-node:hover { filter: brightness(1.2); }
|
|
2570
|
+
.eco-cat-node:focus { outline: none; }
|
|
2571
|
+
.eco-cat-node:focus .eco-cat-circle { stroke-width: 3; stroke-dasharray: 4 2; }
|
|
2572
|
+
.eco-item-dot { transition: r 0.2s, opacity 0.2s; }
|
|
2573
|
+
.eco-item:hover .eco-item-dot { r: 7; opacity: 0.8; }
|
|
2574
|
+
</style>
|
|
2575
|
+
${svgContent}
|
|
2576
|
+
</svg>
|
|
2577
|
+
</div>`;
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// src/report/sections.ts
|
|
2581
|
+
function esc2(str) {
|
|
2582
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2583
|
+
}
|
|
1668
2584
|
function formatBytes(bytes) {
|
|
1669
2585
|
if (bytes < 1024) return `${bytes} B`;
|
|
1670
2586
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1671
2587
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1672
2588
|
}
|
|
2589
|
+
function renderNavBar(summary) {
|
|
2590
|
+
const links = [
|
|
2591
|
+
{ id: "section-mcp", label: "MCP", count: summary.totalMcpServers },
|
|
2592
|
+
{ id: "section-context", label: "Contexto", count: summary.totalFiles },
|
|
2593
|
+
{ id: "section-skills", label: "Skills", count: summary.totalSkills },
|
|
2594
|
+
{ id: "section-agents", label: "Agents", count: summary.totalAgents },
|
|
2595
|
+
{ id: "section-memories", label: "Memorias", count: summary.totalMemories }
|
|
2596
|
+
];
|
|
2597
|
+
const navLinks = links.filter((l) => l.count > 0).map(
|
|
2598
|
+
(l) => `<a class="nav-link" data-target="${l.id}">${l.label} (${l.count})</a>`
|
|
2599
|
+
).join("");
|
|
2600
|
+
return `
|
|
2601
|
+
<nav class="nav-bar">
|
|
2602
|
+
<div class="nav-links">
|
|
2603
|
+
<span style="font-family:var(--font-mono);font-size:0.8rem;color:var(--accent);margin-right:0.5rem">>_</span>
|
|
2604
|
+
${navLinks}
|
|
2605
|
+
</div>
|
|
2606
|
+
<div class="nav-actions">
|
|
2607
|
+
<button class="nav-btn" id="theme-toggle" title="Cambiar tema">☾</button>
|
|
2608
|
+
<button class="nav-btn" id="export-btn" title="Exportar JSON">📋</button>
|
|
2609
|
+
</div>
|
|
2610
|
+
</nav>`;
|
|
2611
|
+
}
|
|
1673
2612
|
function renderHeader(project, summary, scanDuration) {
|
|
1674
2613
|
const date = new Date(project.scannedAt).toLocaleString();
|
|
1675
2614
|
return `
|
|
1676
2615
|
<header class="header">
|
|
1677
2616
|
<h1>> ai-context-inspector</h1>
|
|
1678
|
-
<div class="subtitle">${
|
|
2617
|
+
<div class="subtitle">${esc2(project.name)} — ${date} — ${scanDuration}ms</div>
|
|
1679
2618
|
<div class="badges">
|
|
1680
2619
|
<span class="badge badge--accent">${summary.totalMcpServers} MCPs</span>
|
|
1681
2620
|
<span class="badge badge--green">${summary.totalTools} tools</span>
|
|
1682
2621
|
<span class="badge badge--purple">${summary.totalFiles} archivos</span>
|
|
1683
2622
|
<span class="badge badge--orange">${summary.totalSkills} skills</span>
|
|
1684
2623
|
<span class="badge badge--blue">${summary.totalAgents} agents</span>
|
|
1685
|
-
<span class="badge badge--
|
|
2624
|
+
<span class="badge badge--pink">${summary.totalMemories} memorias</span>
|
|
1686
2625
|
</div>
|
|
1687
2626
|
</header>`;
|
|
1688
2627
|
}
|
|
2628
|
+
function renderStatsGrid(summary) {
|
|
2629
|
+
const stats = [
|
|
2630
|
+
{
|
|
2631
|
+
icon: "\u2699\uFE0F",
|
|
2632
|
+
value: summary.totalMcpServers,
|
|
2633
|
+
label: "MCP Servers",
|
|
2634
|
+
color: "#00d4ff"
|
|
2635
|
+
},
|
|
2636
|
+
{
|
|
2637
|
+
icon: "\u{1F6E0}\uFE0F",
|
|
2638
|
+
value: summary.totalTools,
|
|
2639
|
+
label: "MCP Tools",
|
|
2640
|
+
color: "#00e676"
|
|
2641
|
+
},
|
|
2642
|
+
{
|
|
2643
|
+
icon: "\u{1F4C4}",
|
|
2644
|
+
value: summary.totalFiles,
|
|
2645
|
+
label: "Archivos AI",
|
|
2646
|
+
color: "#b388ff"
|
|
2647
|
+
},
|
|
2648
|
+
{
|
|
2649
|
+
icon: "\u26A1",
|
|
2650
|
+
value: summary.totalSkills,
|
|
2651
|
+
label: "Skills",
|
|
2652
|
+
color: "#ffab40"
|
|
2653
|
+
},
|
|
2654
|
+
{
|
|
2655
|
+
icon: "\u{1F916}",
|
|
2656
|
+
value: summary.totalAgents,
|
|
2657
|
+
label: "Agents",
|
|
2658
|
+
color: "#4285f4"
|
|
2659
|
+
},
|
|
2660
|
+
{
|
|
2661
|
+
icon: "\u{1F9E0}",
|
|
2662
|
+
value: summary.totalMemories,
|
|
2663
|
+
label: "Memorias",
|
|
2664
|
+
color: "#ff80ab"
|
|
2665
|
+
}
|
|
2666
|
+
];
|
|
2667
|
+
const cards = stats.map(
|
|
2668
|
+
(s) => `
|
|
2669
|
+
<div class="stat-card" style="--stat-color: ${s.color}">
|
|
2670
|
+
<span class="stat-icon">${s.icon}</span>
|
|
2671
|
+
<span class="stat-number" data-target="${s.value}">0</span>
|
|
2672
|
+
<span class="stat-label">${s.label}</span>
|
|
2673
|
+
</div>`
|
|
2674
|
+
).join("");
|
|
2675
|
+
return `<div class="stats-grid">${cards}</div>`;
|
|
2676
|
+
}
|
|
1689
2677
|
function renderMcpServers(servers) {
|
|
1690
2678
|
if (servers.length === 0) return "";
|
|
1691
|
-
const cards = servers.map((s) => {
|
|
2679
|
+
const cards = servers.map((s, i) => {
|
|
1692
2680
|
const intro = s.introspection;
|
|
1693
2681
|
const statusClass = intro ? `status--${intro.status}` : "status--configured";
|
|
1694
2682
|
const statusText = intro ? intro.status === "ok" ? "OK" : intro.status === "timeout" ? "Timeout" : "Error" : "No introspectado";
|
|
1695
2683
|
let toolsHtml = "";
|
|
1696
2684
|
if (intro && intro.tools.length > 0) {
|
|
1697
2685
|
const items = intro.tools.map(
|
|
1698
|
-
(t) => `<li data-searchable="${
|
|
2686
|
+
(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
2687
|
).join("");
|
|
1700
2688
|
toolsHtml = `<div class="card-meta">${intro.tools.length} tools</div><ul class="tool-list">${items}</ul>`;
|
|
1701
2689
|
}
|
|
@@ -1707,13 +2695,17 @@ function renderMcpServers(servers) {
|
|
|
1707
2695
|
if (intro && intro.prompts.length > 0) {
|
|
1708
2696
|
promptsHtml = `<div class="card-meta" style="margin-top:0.5rem">${intro.prompts.length} prompts</div>`;
|
|
1709
2697
|
}
|
|
1710
|
-
const serverVersion = intro?.serverInfo ? ` v${
|
|
1711
|
-
const errorHtml = intro?.error ? `<div class="card-meta" style="color:var(--red)">${
|
|
1712
|
-
const
|
|
2698
|
+
const serverVersion = intro?.serverInfo ? ` v${esc2(intro.serverInfo.version)}` : "";
|
|
2699
|
+
const errorHtml = intro?.error ? `<div class="card-meta" style="color:var(--red)">${esc2(intro.error)}</div>` : "";
|
|
2700
|
+
const cmdStr = s.config.command ? `${s.config.command} ${(s.config.args ?? []).join(" ")}` : s.config.url ?? "";
|
|
2701
|
+
const configHtml = cmdStr ? `<div class="card-meta">${esc2(cmdStr)}</div>` : "";
|
|
2702
|
+
const copyData = cmdStr || s.name;
|
|
1713
2703
|
return `
|
|
1714
|
-
<div class="card" data-searchable="${
|
|
2704
|
+
<div class="card" data-searchable="${esc2(s.name + " " + (intro?.serverInfo?.name ?? ""))}"
|
|
2705
|
+
style="animation-delay: ${i * 0.05}s">
|
|
2706
|
+
<button class="copy-btn" data-copy="${esc2(copyData)}">copiar</button>
|
|
1715
2707
|
<div class="card-title">
|
|
1716
|
-
<span>${
|
|
2708
|
+
<span>${esc2(s.name)}${serverVersion}</span>
|
|
1717
2709
|
<span class="status ${statusClass}">${statusText}</span>
|
|
1718
2710
|
<span class="scope-badge scope-badge--${s.source}">${s.source}</span>
|
|
1719
2711
|
</div>
|
|
@@ -1729,10 +2721,13 @@ function renderMcpServers(servers) {
|
|
|
1729
2721
|
0
|
|
1730
2722
|
);
|
|
1731
2723
|
return `
|
|
1732
|
-
<div class="section">
|
|
2724
|
+
<div class="section" id="section-mcp">
|
|
1733
2725
|
<div class="section-header">
|
|
1734
|
-
<
|
|
1735
|
-
|
|
2726
|
+
<div class="section-header-left">
|
|
2727
|
+
<span class="section-icon">\u2699\uFE0F</span>
|
|
2728
|
+
<h2>MCP Servers</h2>
|
|
2729
|
+
</div>
|
|
2730
|
+
<div class="section-header-right">
|
|
1736
2731
|
<span class="count">${servers.length} servers · ${totalTools} tools</span>
|
|
1737
2732
|
<span class="arrow">▼</span>
|
|
1738
2733
|
</div>
|
|
@@ -1742,6 +2737,7 @@ function renderMcpServers(servers) {
|
|
|
1742
2737
|
}
|
|
1743
2738
|
function renderContextFiles(files) {
|
|
1744
2739
|
if (files.length === 0) return "";
|
|
2740
|
+
const maxSize = Math.max(...files.map((f) => f.size), 1);
|
|
1745
2741
|
const groups = /* @__PURE__ */ new Map();
|
|
1746
2742
|
for (const f of files) {
|
|
1747
2743
|
const existing = groups.get(f.tool) ?? [];
|
|
@@ -1750,16 +2746,21 @@ function renderContextFiles(files) {
|
|
|
1750
2746
|
}
|
|
1751
2747
|
let groupsHtml = "";
|
|
1752
2748
|
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>` : "";
|
|
2749
|
+
const items = toolFiles.map((f, i) => {
|
|
2750
|
+
const previewHtml = f.preview ? `<button class="preview-toggle">Ver contenido</button><div class="preview">${esc2(f.preview)}</div>` : "";
|
|
2751
|
+
const errorHtml = f.error ? `<span class="status status--error">${esc2(f.error)}</span>` : "";
|
|
1756
2752
|
const sizeStr = f.size > 0 ? formatBytes(f.size) : "";
|
|
1757
|
-
const typeIcon = f.type === "directory" ? "
|
|
2753
|
+
const typeIcon = f.type === "directory" ? "\u{1F4C1}" : "\u{1F4C4}";
|
|
2754
|
+
const sizePercent = f.size > 0 ? Math.max(3, f.size / maxSize * 100) : 0;
|
|
2755
|
+
const sizeBar = f.size > 0 ? `<div class="size-bar"><div class="size-bar-fill" style="width:${sizePercent}%"></div></div>` : "";
|
|
1758
2756
|
const childrenHtml = f.children && f.children.length > 0 ? `<div class="card-meta" style="margin-top:0.3rem">${f.children.length} archivos dentro</div>` : "";
|
|
1759
2757
|
return `
|
|
1760
|
-
<div class="card" data-searchable="${
|
|
1761
|
-
|
|
2758
|
+
<div class="card" data-searchable="${esc2(f.path + " " + tool)}"
|
|
2759
|
+
style="animation-delay: ${i * 0.04}s">
|
|
2760
|
+
<button class="copy-btn" data-copy="${esc2(f.path)}">copiar</button>
|
|
2761
|
+
<div class="card-title">${typeIcon} ${esc2(f.path)} ${errorHtml}</div>
|
|
1762
2762
|
<div class="card-meta">${sizeStr} · <span class="scope-badge scope-badge--${f.scope}">${f.scope}</span></div>
|
|
2763
|
+
${sizeBar}
|
|
1763
2764
|
${childrenHtml}
|
|
1764
2765
|
${previewHtml}
|
|
1765
2766
|
</div>`;
|
|
@@ -1774,10 +2775,13 @@ function renderContextFiles(files) {
|
|
|
1774
2775
|
</div>`;
|
|
1775
2776
|
}
|
|
1776
2777
|
return `
|
|
1777
|
-
<div class="section">
|
|
2778
|
+
<div class="section" id="section-context">
|
|
1778
2779
|
<div class="section-header">
|
|
1779
|
-
<
|
|
1780
|
-
|
|
2780
|
+
<div class="section-header-left">
|
|
2781
|
+
<span class="section-icon">\u{1F4C4}</span>
|
|
2782
|
+
<h2>Archivos de Contexto</h2>
|
|
2783
|
+
</div>
|
|
2784
|
+
<div class="section-header-right">
|
|
1781
2785
|
<span class="count">${files.length} archivos · ${groups.size} herramientas</span>
|
|
1782
2786
|
<span class="arrow">▼</span>
|
|
1783
2787
|
</div>
|
|
@@ -1787,23 +2791,27 @@ function renderContextFiles(files) {
|
|
|
1787
2791
|
}
|
|
1788
2792
|
function renderSkills(skills) {
|
|
1789
2793
|
if (skills.length === 0) return "";
|
|
1790
|
-
const items = skills.map((s) => {
|
|
1791
|
-
const triggersHtml = s.triggers ? `<div class="card-meta">${s.triggers.map((t) =>
|
|
2794
|
+
const items = skills.map((s, i) => {
|
|
2795
|
+
const triggersHtml = s.triggers ? `<div class="card-meta">${s.triggers.map((t) => esc2(t)).join(", ")}</div>` : "";
|
|
1792
2796
|
return `
|
|
1793
|
-
<div class="card" data-searchable="${
|
|
2797
|
+
<div class="card" data-searchable="${esc2(s.name + " " + (s.description ?? ""))}"
|
|
2798
|
+
style="animation-delay: ${i * 0.04}s">
|
|
1794
2799
|
<div class="card-title">
|
|
1795
|
-
${
|
|
2800
|
+
\u26A1 ${esc2(s.name)}
|
|
1796
2801
|
<span class="scope-badge scope-badge--${s.scope}">${s.scope}</span>
|
|
1797
2802
|
</div>
|
|
1798
|
-
${s.description ? `<div class="card-meta">${
|
|
2803
|
+
${s.description ? `<div class="card-meta">${esc2(s.description)}</div>` : ""}
|
|
1799
2804
|
${triggersHtml}
|
|
1800
2805
|
</div>`;
|
|
1801
2806
|
}).join("");
|
|
1802
2807
|
return `
|
|
1803
|
-
<div class="section">
|
|
2808
|
+
<div class="section" id="section-skills">
|
|
1804
2809
|
<div class="section-header">
|
|
1805
|
-
<
|
|
1806
|
-
|
|
2810
|
+
<div class="section-header-left">
|
|
2811
|
+
<span class="section-icon">\u26A1</span>
|
|
2812
|
+
<h2>Skills</h2>
|
|
2813
|
+
</div>
|
|
2814
|
+
<div class="section-header-right">
|
|
1807
2815
|
<span class="count">${skills.length}</span>
|
|
1808
2816
|
<span class="arrow">▼</span>
|
|
1809
2817
|
</div>
|
|
@@ -1813,25 +2821,29 @@ function renderSkills(skills) {
|
|
|
1813
2821
|
}
|
|
1814
2822
|
function renderAgents(agents) {
|
|
1815
2823
|
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--
|
|
2824
|
+
const items = agents.map((a, i) => {
|
|
2825
|
+
const modelHtml = a.model ? `<span class="badge badge--green" style="font-size:0.65rem;padding:0.1rem 0.5rem">${esc2(a.model)}</span>` : "";
|
|
2826
|
+
const memoryHtml = a.hasMemory ? '<span class="badge badge--pink" style="font-size:0.65rem;padding:0.1rem 0.5rem">\u{1F9E0} memoria</span>' : "";
|
|
1819
2827
|
return `
|
|
1820
|
-
<div class="card" data-searchable="${
|
|
2828
|
+
<div class="card" data-searchable="${esc2(a.name + " " + (a.description ?? ""))}"
|
|
2829
|
+
style="animation-delay: ${i * 0.04}s">
|
|
1821
2830
|
<div class="card-title">
|
|
1822
|
-
${
|
|
2831
|
+
\u{1F916} ${esc2(a.name)}
|
|
1823
2832
|
${modelHtml}
|
|
1824
2833
|
${memoryHtml}
|
|
1825
2834
|
<span class="scope-badge scope-badge--${a.scope}">${a.scope}</span>
|
|
1826
2835
|
</div>
|
|
1827
|
-
${a.description ? `<div class="card-meta">${
|
|
2836
|
+
${a.description ? `<div class="card-meta">${esc2(a.description)}</div>` : ""}
|
|
1828
2837
|
</div>`;
|
|
1829
2838
|
}).join("");
|
|
1830
2839
|
return `
|
|
1831
|
-
<div class="section">
|
|
2840
|
+
<div class="section" id="section-agents">
|
|
1832
2841
|
<div class="section-header">
|
|
1833
|
-
<
|
|
1834
|
-
|
|
2842
|
+
<div class="section-header-left">
|
|
2843
|
+
<span class="section-icon">\u{1F916}</span>
|
|
2844
|
+
<h2>Agents</h2>
|
|
2845
|
+
</div>
|
|
2846
|
+
<div class="section-header-right">
|
|
1835
2847
|
<span class="count">${agents.length}</span>
|
|
1836
2848
|
<span class="arrow">▼</span>
|
|
1837
2849
|
</div>
|
|
@@ -1841,23 +2853,33 @@ function renderAgents(agents) {
|
|
|
1841
2853
|
}
|
|
1842
2854
|
function renderMemories(memories) {
|
|
1843
2855
|
if (memories.length === 0) return "";
|
|
1844
|
-
const items = memories.map((m) => {
|
|
1845
|
-
const
|
|
2856
|
+
const items = memories.map((m, i) => {
|
|
2857
|
+
const detailEntries = m.details ? Object.entries(m.details).filter(
|
|
2858
|
+
([k]) => k !== "preview"
|
|
2859
|
+
) : [];
|
|
2860
|
+
const detailsHtml = detailEntries.length > 0 ? `<div class="card-meta">${detailEntries.map(([k, v]) => `${esc2(k)}: ${esc2(String(v))}`).join(" · ")}</div>` : "";
|
|
2861
|
+
const previewVal = m.details && typeof m.details.preview === "string" ? m.details.preview : null;
|
|
2862
|
+
const previewHtml = previewVal ? `<button class="preview-toggle">Ver contenido</button><div class="preview">${esc2(previewVal)}</div>` : "";
|
|
1846
2863
|
return `
|
|
1847
|
-
<div class="card" data-searchable="${
|
|
2864
|
+
<div class="card" data-searchable="${esc2(m.type + " " + (m.path ?? ""))}"
|
|
2865
|
+
style="animation-delay: ${i * 0.04}s">
|
|
1848
2866
|
<div class="card-title">
|
|
1849
|
-
${
|
|
2867
|
+
\u{1F9E0} ${esc2(m.type)}
|
|
1850
2868
|
<span class="status status--${m.status}">${m.status}</span>
|
|
1851
2869
|
</div>
|
|
1852
|
-
<div class="card-meta">${m.source}${m.path ? ` · ${
|
|
2870
|
+
<div class="card-meta">${m.source}${m.path ? ` · ${esc2(m.path)}` : ""}</div>
|
|
1853
2871
|
${detailsHtml}
|
|
2872
|
+
${previewHtml}
|
|
1854
2873
|
</div>`;
|
|
1855
2874
|
}).join("");
|
|
1856
2875
|
return `
|
|
1857
|
-
<div class="section">
|
|
2876
|
+
<div class="section" id="section-memories">
|
|
1858
2877
|
<div class="section-header">
|
|
1859
|
-
<
|
|
1860
|
-
|
|
2878
|
+
<div class="section-header-left">
|
|
2879
|
+
<span class="section-icon">\u{1F9E0}</span>
|
|
2880
|
+
<h2>Memorias</h2>
|
|
2881
|
+
</div>
|
|
2882
|
+
<div class="section-header-right">
|
|
1861
2883
|
<span class="count">${memories.length}</span>
|
|
1862
2884
|
<span class="arrow">▼</span>
|
|
1863
2885
|
</div>
|
|
@@ -1865,14 +2887,40 @@ function renderMemories(memories) {
|
|
|
1865
2887
|
<div class="section-content">${items}</div>
|
|
1866
2888
|
</div>`;
|
|
1867
2889
|
}
|
|
2890
|
+
function renderWarnings(warnings) {
|
|
2891
|
+
if (warnings.length === 0) return "";
|
|
2892
|
+
const items = warnings.map(
|
|
2893
|
+
(w) => `
|
|
2894
|
+
<div class="warning-card">
|
|
2895
|
+
<div class="warning-scanner">${esc2(w.scanner)}${w.path ? ` — ${esc2(w.path)}` : ""}</div>
|
|
2896
|
+
<div>${esc2(w.message)}</div>
|
|
2897
|
+
</div>`
|
|
2898
|
+
).join("");
|
|
2899
|
+
return `
|
|
2900
|
+
<div class="section" id="section-warnings">
|
|
2901
|
+
<div class="section-header">
|
|
2902
|
+
<div class="section-header-left">
|
|
2903
|
+
<span class="section-icon">\u26A0\uFE0F</span>
|
|
2904
|
+
<h2>Advertencias</h2>
|
|
2905
|
+
</div>
|
|
2906
|
+
<div class="section-header-right">
|
|
2907
|
+
<span class="count">${warnings.length}</span>
|
|
2908
|
+
<span class="arrow">▼</span>
|
|
2909
|
+
</div>
|
|
2910
|
+
</div>
|
|
2911
|
+
<div class="section-content">${items}</div>
|
|
2912
|
+
</div>`;
|
|
2913
|
+
}
|
|
1868
2914
|
function renderEmptyState() {
|
|
1869
2915
|
return `
|
|
1870
2916
|
<div class="empty-state">
|
|
2917
|
+
<span class="empty-state-icon">\u{1F50D}</span>
|
|
1871
2918
|
<h3>No se encontr\xF3 configuraci\xF3n AI en este proyecto</h3>
|
|
1872
2919
|
<p>Este proyecto no tiene archivos de configuraci\xF3n de herramientas AI.</p>
|
|
1873
|
-
<p style="margin-top:1rem;font-size:0.85rem">
|
|
2920
|
+
<p style="margin-top:1rem;font-size:0.85rem;color:var(--text-dim)">
|
|
1874
2921
|
Herramientas soportadas: Claude, Cursor, Windsurf, Copilot, Gemini,
|
|
1875
|
-
Codex, Aider, Cline, Continue, Amazon Q, Augment,
|
|
2922
|
+
Codex, OpenCode, Aider, Cline, Roo, Continue, Amazon Q, Augment,
|
|
2923
|
+
Replit, Firebase Studio, Tabnine, Sourcegraph
|
|
1876
2924
|
</p>
|
|
1877
2925
|
</div>`;
|
|
1878
2926
|
}
|
|
@@ -1894,14 +2942,46 @@ function computeSummary(result) {
|
|
|
1894
2942
|
function generateHtml(result) {
|
|
1895
2943
|
const summary = computeSummary(result);
|
|
1896
2944
|
const isEmpty = summary.totalMcpServers === 0 && summary.totalFiles === 0 && summary.totalSkills === 0 && summary.totalAgents === 0 && summary.totalMemories === 0;
|
|
2945
|
+
const navBar = renderNavBar(summary);
|
|
1897
2946
|
const header = renderHeader(result.project, summary, result.scanDuration);
|
|
2947
|
+
const statsGrid = renderStatsGrid(summary);
|
|
2948
|
+
const ecosystemMap = renderEcosystemMap(result, summary);
|
|
1898
2949
|
const content = isEmpty ? renderEmptyState() : [
|
|
1899
2950
|
renderMcpServers(result.mcpServers),
|
|
1900
2951
|
renderContextFiles(result.contextFiles),
|
|
1901
2952
|
renderSkills(result.skills),
|
|
1902
2953
|
renderAgents(result.agents),
|
|
1903
|
-
renderMemories(result.memories)
|
|
2954
|
+
renderMemories(result.memories),
|
|
2955
|
+
renderWarnings(result.warnings)
|
|
1904
2956
|
].join("");
|
|
2957
|
+
const exportData = {
|
|
2958
|
+
project: result.project.name,
|
|
2959
|
+
scannedAt: result.project.scannedAt,
|
|
2960
|
+
scanDuration: result.scanDuration,
|
|
2961
|
+
summary,
|
|
2962
|
+
mcpServers: result.mcpServers.map((s) => ({
|
|
2963
|
+
name: s.name,
|
|
2964
|
+
source: s.source,
|
|
2965
|
+
transport: s.config.transport,
|
|
2966
|
+
tools: s.introspection?.tools.length ?? 0
|
|
2967
|
+
})),
|
|
2968
|
+
contextFiles: result.contextFiles.map((f) => ({
|
|
2969
|
+
path: f.path,
|
|
2970
|
+
tool: f.tool,
|
|
2971
|
+
scope: f.scope
|
|
2972
|
+
})),
|
|
2973
|
+
skills: result.skills.map((s) => ({ name: s.name, scope: s.scope })),
|
|
2974
|
+
agents: result.agents.map((a) => ({
|
|
2975
|
+
name: a.name,
|
|
2976
|
+
scope: a.scope,
|
|
2977
|
+
model: a.model
|
|
2978
|
+
})),
|
|
2979
|
+
memories: result.memories.map((m) => ({
|
|
2980
|
+
type: m.type,
|
|
2981
|
+
source: m.source,
|
|
2982
|
+
status: m.status
|
|
2983
|
+
}))
|
|
2984
|
+
};
|
|
1905
2985
|
return `<!DOCTYPE html>
|
|
1906
2986
|
<html lang="es">
|
|
1907
2987
|
<head>
|
|
@@ -1911,16 +2991,25 @@ function generateHtml(result) {
|
|
|
1911
2991
|
<style>${CSS_STYLES}</style>
|
|
1912
2992
|
</head>
|
|
1913
2993
|
<body>
|
|
2994
|
+
${navBar}
|
|
1914
2995
|
<div class="container">
|
|
1915
2996
|
${header}
|
|
2997
|
+
${statsGrid}
|
|
2998
|
+
${ecosystemMap}
|
|
1916
2999
|
<div class="search-bar">
|
|
1917
|
-
<
|
|
3000
|
+
<div class="search-bar-inner">
|
|
3001
|
+
<span class="search-icon">\u{1F50D}</span>
|
|
3002
|
+
<input type="text" id="search-input" placeholder="Buscar tools, archivos, skills, agents..." />
|
|
3003
|
+
<span class="search-kbd">/</span>
|
|
3004
|
+
</div>
|
|
3005
|
+
<span class="search-results-count"></span>
|
|
1918
3006
|
</div>
|
|
1919
3007
|
${content}
|
|
1920
3008
|
<footer class="footer">
|
|
1921
3009
|
Generado por ai-context-inspector — ${new Date(result.project.scannedAt).toLocaleString()}
|
|
1922
3010
|
</footer>
|
|
1923
3011
|
</div>
|
|
3012
|
+
<script type="application/json" id="scan-data">${JSON.stringify(exportData)}</script>
|
|
1924
3013
|
<script>${JS_SCRIPTS}</script>
|
|
1925
3014
|
</body>
|
|
1926
3015
|
</html>`;
|