@shnitzel/plugscout 0.3.24 → 0.3.25

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.
@@ -515,14 +515,12 @@ async function handleShow(args) {
515
515
  printHint(`Install with: plugscout install --id ${item.id} --yes`);
516
516
  printHint('Review provenance, risk, and capabilities first. Do not install blindly from a suggestion or score.');
517
517
  console.log(`Provenance: source=${item.source} catalogType=${getCatalogType(item)} confidence=${getSourceConfidence(item)}`);
518
- const metadata = getItemMetadata(item);
519
- const sourceRepo = typeof metadata.sourceRepo === 'string' ? metadata.sourceRepo : undefined;
520
- const sourcePage = typeof metadata.sourcePage === 'string' ? metadata.sourcePage : undefined;
521
- if (sourceRepo) {
522
- console.log(`Source repo: ${sourceRepo}`);
523
- }
524
- if (sourcePage) {
525
- console.log(`Source page: ${sourcePage}`);
518
+ const links = buildItemLinks(item);
519
+ if (links.length > 0) {
520
+ console.log('\nLinks:');
521
+ for (const link of links) {
522
+ console.log(` ${link.label}: ${link.url}`);
523
+ }
526
524
  }
527
525
  }
528
526
  async function handleSearch(args) {
@@ -816,6 +814,13 @@ async function handleAssess(args) {
816
814
  console.log(renderJson(assessment));
817
815
  await recordItemReview(found.id, 'assess');
818
816
  printHint(`Risk scale (lower is safer): ${formatRiskScale(policy)}`);
817
+ const links = buildItemLinks(found);
818
+ if (links.length > 0) {
819
+ console.log('\nLinks:');
820
+ for (const link of links) {
821
+ console.log(` ${link.label}: ${link.url}`);
822
+ }
823
+ }
819
824
  }
820
825
  async function handleInstall(args) {
821
826
  const id = readFlag(args, '--id');
@@ -1460,3 +1465,43 @@ function getSourceConfidence(item) {
1460
1465
  }
1461
1466
  return 'official';
1462
1467
  }
1468
+ function buildItemLinks(item) {
1469
+ const meta = getItemMetadata(item);
1470
+ const links = [];
1471
+ const seen = new Set();
1472
+ function add(label, url) {
1473
+ if (typeof url !== 'string' || !url.startsWith('http'))
1474
+ return;
1475
+ if (seen.has(url))
1476
+ return;
1477
+ seen.add(url);
1478
+ links.push({ label, url });
1479
+ }
1480
+ // Kind-specific derived URLs first (most useful)
1481
+ if (item.kind === 'cursor-extension') {
1482
+ const vsixId = meta.vsixId;
1483
+ if (typeof vsixId === 'string') {
1484
+ add('Marketplace', `https://marketplace.visualstudio.com/items?itemName=${vsixId}`);
1485
+ }
1486
+ }
1487
+ if (item.kind === 'gemini-extension') {
1488
+ const pkg = meta.npmPackage;
1489
+ if (typeof pkg === 'string') {
1490
+ add('npm', `https://www.npmjs.com/package/${encodeURIComponent(pkg)}`);
1491
+ }
1492
+ }
1493
+ // install.url is the most direct link for the user
1494
+ add('Install page', item.install.url);
1495
+ // Metadata links — priority order
1496
+ add('Repository', meta.repositoryUrl);
1497
+ add('Repository', meta.githubUrl);
1498
+ add('Repository', meta.sourceRepo?.toString().startsWith('http') ? meta.sourceRepo : undefined);
1499
+ add('Website', meta.websiteUrl);
1500
+ add('Source page', meta.sourcePage);
1501
+ // GitHub shorthand (e.g. "github/awesome-copilot") → full URL
1502
+ if (typeof meta.sourceRepo === 'string' && !meta.sourceRepo.startsWith('http')) {
1503
+ const ghUrl = `https://github.com/${meta.sourceRepo}`;
1504
+ add('Repository', ghUrl);
1505
+ }
1506
+ return links;
1507
+ }
@@ -454,10 +454,7 @@ function renderHtml(rows, allItems, stats, policy) {
454
454
  </html>`;
455
455
  }
456
456
  function renderDetailCard(entry) {
457
- const metadata = asMetadata(entry.item.metadata);
458
457
  const trustScore = computeTrustScore(entry.item);
459
- const sourceRepo = typeof metadata.sourceRepo === 'string' ? metadata.sourceRepo : '';
460
- const sourcePage = typeof metadata.sourcePage === 'string' ? metadata.sourcePage : '';
461
458
  const status = entry.blocked ? 'blocked' : entry.approved ? 'approved' : 'allowed';
462
459
  const statusClass = entry.blocked ? 'bad' : 'ok';
463
460
  const installHint = buildInstallHint(entry.item);
@@ -473,8 +470,6 @@ function renderDetailCard(entry) {
473
470
  const bodyId = `body-${safeId}`;
474
471
  const searchKey = escapeHtml(`${entry.item.id} ${entry.item.name} ${entry.item.capabilities.join(' ')}`.toLowerCase());
475
472
  const plugscoutCmd = `plugscout install --id ${entry.item.id} --yes`;
476
- const isManualUrl = entry.item.install.kind === 'manual' && typeof entry.item.install.url === 'string' && String(entry.item.install.url).startsWith('http');
477
- const manualUrl = isManualUrl ? String(entry.item.install.url) : '';
478
473
  const previewChips = entry.item.capabilities
479
474
  .slice(0, 3)
480
475
  .map((cap) => `<span class="chip">${escapeHtml(cap)}</span>`)
@@ -517,9 +512,7 @@ function renderDetailCard(entry) {
517
512
  </div>
518
513
  ${entry.assessment.reasons.length > 0 ? `<div class="line"><span class="label">Risk signals: </span>${escapeHtml(entry.assessment.reasons.join(' · '))}</div>` : ''}
519
514
  <div class="line"><span class="label">Provider:</span> ${escapeHtml(entry.item.provider)} &nbsp;·&nbsp; <span class="label">Source:</span> ${escapeHtml(entry.item.source)}</div>
520
- ${sourceRepo ? `<div class="line"><span class="label">Repo: </span><a class="link" href="${escapeHtml(sourceRepo)}" target="_blank" rel="noopener noreferrer">${escapeHtml(sourceRepo)}</a></div>` : ''}
521
- ${sourcePage ? `<div class="line"><span class="label">Page: </span><a class="link" href="${escapeHtml(sourcePage)}" target="_blank" rel="noopener noreferrer">${escapeHtml(sourcePage)}</a></div>` : ''}
522
- ${isManualUrl ? `<div class="line"><span class="label">Install page: </span><a class="link" href="${escapeHtml(manualUrl)}" target="_blank" rel="noopener noreferrer">${escapeHtml(manualUrl)}</a></div>` : ''}
515
+ ${buildItemLinks(entry.item).map(l => `<div class="line"><span class="label">${escapeHtml(l.label)}: </span><a class="link" href="${escapeHtml(l.url)}" target="_blank" rel="noopener noreferrer">${escapeHtml(l.url)}</a></div>`).join('')}
523
516
  <div class="install-block">
524
517
  <div class="install-header">
525
518
  <span class="install-label">Install command</span>
@@ -568,6 +561,43 @@ function asMetadata(value) {
568
561
  }
569
562
  return value;
570
563
  }
564
+ function buildItemLinks(item) {
565
+ const meta = asMetadata(item.metadata);
566
+ const links = [];
567
+ const seen = new Set();
568
+ function add(label, url) {
569
+ if (typeof url !== 'string' || !url.startsWith('http'))
570
+ return;
571
+ if (seen.has(url))
572
+ return;
573
+ seen.add(url);
574
+ links.push({ label, url });
575
+ }
576
+ if (item.kind === 'cursor-extension') {
577
+ const vsixId = meta.vsixId;
578
+ if (typeof vsixId === 'string') {
579
+ add('Marketplace', `https://marketplace.visualstudio.com/items?itemName=${vsixId}`);
580
+ }
581
+ }
582
+ if (item.kind === 'gemini-extension') {
583
+ const pkg = meta.npmPackage;
584
+ if (typeof pkg === 'string') {
585
+ add('npm', `https://www.npmjs.com/package/${encodeURIComponent(pkg)}`);
586
+ }
587
+ }
588
+ add('Install page', item.install.url);
589
+ add('Repository', meta.repositoryUrl);
590
+ add('Repository', meta.githubUrl);
591
+ if (typeof meta.sourceRepo === 'string' && meta.sourceRepo.startsWith('http')) {
592
+ add('Repository', meta.sourceRepo);
593
+ }
594
+ else if (typeof meta.sourceRepo === 'string' && meta.sourceRepo.includes('/')) {
595
+ add('Repository', `https://github.com/${meta.sourceRepo}`);
596
+ }
597
+ add('Website', meta.websiteUrl);
598
+ add('Source page', meta.sourcePage);
599
+ return links;
600
+ }
571
601
  function escapeHtml(value) {
572
602
  return value
573
603
  .replaceAll('&', '&amp;')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shnitzel/plugscout",
3
- "version": "0.3.24",
3
+ "version": "0.3.25",
4
4
  "description": "Claude plugins + Claude connectors + Copilot extensions + Skills + MCP security intelligence framework",
5
5
  "private": false,
6
6
  "type": "module",