hackmyagent 0.10.0 → 0.11.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.
Files changed (132) hide show
  1. package/README.md +111 -257
  2. package/dist/arp/index.d.ts +5 -1
  3. package/dist/arp/index.d.ts.map +1 -1
  4. package/dist/arp/index.js +38 -1
  5. package/dist/arp/index.js.map +1 -1
  6. package/dist/arp/monitors/skill-capability-monitor.d.ts +119 -0
  7. package/dist/arp/monitors/skill-capability-monitor.d.ts.map +1 -0
  8. package/dist/arp/monitors/skill-capability-monitor.js +258 -0
  9. package/dist/arp/monitors/skill-capability-monitor.js.map +1 -0
  10. package/dist/arp/telemetry/forwarder.d.ts +62 -0
  11. package/dist/arp/telemetry/forwarder.d.ts.map +1 -0
  12. package/dist/arp/telemetry/forwarder.js +106 -0
  13. package/dist/arp/telemetry/forwarder.js.map +1 -0
  14. package/dist/arp/telemetry/gtin.d.ts +87 -0
  15. package/dist/arp/telemetry/gtin.d.ts.map +1 -0
  16. package/dist/arp/telemetry/gtin.js +239 -0
  17. package/dist/arp/telemetry/gtin.js.map +1 -0
  18. package/dist/arp/telemetry/index.d.ts +6 -0
  19. package/dist/arp/telemetry/index.d.ts.map +1 -0
  20. package/dist/arp/telemetry/index.js +17 -0
  21. package/dist/arp/telemetry/index.js.map +1 -0
  22. package/dist/arp/types.d.ts +10 -0
  23. package/dist/arp/types.d.ts.map +1 -1
  24. package/dist/attack/index.d.ts +1 -1
  25. package/dist/attack/index.d.ts.map +1 -1
  26. package/dist/attack/index.js +5 -1
  27. package/dist/attack/index.js.map +1 -1
  28. package/dist/attack/payloads/context-window.d.ts +7 -0
  29. package/dist/attack/payloads/context-window.d.ts.map +1 -0
  30. package/dist/attack/payloads/context-window.js +110 -0
  31. package/dist/attack/payloads/context-window.js.map +1 -0
  32. package/dist/attack/payloads/index.d.ts +5 -1
  33. package/dist/attack/payloads/index.d.ts.map +1 -1
  34. package/dist/attack/payloads/index.js +17 -1
  35. package/dist/attack/payloads/index.js.map +1 -1
  36. package/dist/attack/payloads/memory-weaponization.d.ts +7 -0
  37. package/dist/attack/payloads/memory-weaponization.d.ts.map +1 -0
  38. package/dist/attack/payloads/memory-weaponization.js +110 -0
  39. package/dist/attack/payloads/memory-weaponization.js.map +1 -0
  40. package/dist/attack/payloads/supply-chain.d.ts +7 -0
  41. package/dist/attack/payloads/supply-chain.d.ts.map +1 -0
  42. package/dist/attack/payloads/supply-chain.js +110 -0
  43. package/dist/attack/payloads/supply-chain.js.map +1 -0
  44. package/dist/attack/payloads/tool-shadow.d.ts +8 -0
  45. package/dist/attack/payloads/tool-shadow.d.ts.map +1 -0
  46. package/dist/attack/payloads/tool-shadow.js +209 -0
  47. package/dist/attack/payloads/tool-shadow.js.map +1 -0
  48. package/dist/attack/scanner.d.ts.map +1 -1
  49. package/dist/attack/scanner.js +4 -0
  50. package/dist/attack/scanner.js.map +1 -1
  51. package/dist/attack/types.d.ts +1 -1
  52. package/dist/attack/types.d.ts.map +1 -1
  53. package/dist/attack/types.js +20 -0
  54. package/dist/attack/types.js.map +1 -1
  55. package/dist/checker/index.d.ts +2 -0
  56. package/dist/checker/index.d.ts.map +1 -1
  57. package/dist/checker/index.js +8 -1
  58. package/dist/checker/index.js.map +1 -1
  59. package/dist/checker/skill-dependency-graph.d.ts +55 -0
  60. package/dist/checker/skill-dependency-graph.d.ts.map +1 -0
  61. package/dist/checker/skill-dependency-graph.js +288 -0
  62. package/dist/checker/skill-dependency-graph.js.map +1 -0
  63. package/dist/cli.js +481 -66
  64. package/dist/cli.js.map +1 -1
  65. package/dist/hardening/index.d.ts +5 -0
  66. package/dist/hardening/index.d.ts.map +1 -1
  67. package/dist/hardening/index.js +11 -1
  68. package/dist/hardening/index.js.map +1 -1
  69. package/dist/hardening/scanner.d.ts +40 -0
  70. package/dist/hardening/scanner.d.ts.map +1 -1
  71. package/dist/hardening/scanner.js +988 -11
  72. package/dist/hardening/scanner.js.map +1 -1
  73. package/dist/hardening/security-check.d.ts +2 -0
  74. package/dist/hardening/security-check.d.ts.map +1 -1
  75. package/dist/hardening/skill-capability-validator.d.ts +31 -0
  76. package/dist/hardening/skill-capability-validator.d.ts.map +1 -0
  77. package/dist/hardening/skill-capability-validator.js +237 -0
  78. package/dist/hardening/skill-capability-validator.js.map +1 -0
  79. package/dist/hardening/skill-context.d.ts +22 -0
  80. package/dist/hardening/skill-context.d.ts.map +1 -0
  81. package/dist/hardening/skill-context.js +127 -0
  82. package/dist/hardening/skill-context.js.map +1 -0
  83. package/dist/hardening/taxonomy.d.ts +17 -0
  84. package/dist/hardening/taxonomy.d.ts.map +1 -0
  85. package/dist/hardening/taxonomy.js +152 -0
  86. package/dist/hardening/taxonomy.js.map +1 -0
  87. package/dist/index.d.ts +12 -4
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +36 -3
  90. package/dist/index.js.map +1 -1
  91. package/dist/plugins/credvault.js +2 -2
  92. package/dist/plugins/credvault.js.map +1 -1
  93. package/dist/plugins/secretless.d.ts +15 -0
  94. package/dist/plugins/secretless.d.ts.map +1 -0
  95. package/dist/plugins/secretless.js +199 -0
  96. package/dist/plugins/secretless.js.map +1 -0
  97. package/dist/plugins/signcrypt.js +2 -2
  98. package/dist/plugins/signcrypt.js.map +1 -1
  99. package/dist/plugins/skillguard.js +2 -2
  100. package/dist/plugins/skillguard.js.map +1 -1
  101. package/dist/registry/client.d.ts +1 -1
  102. package/dist/registry/client.d.ts.map +1 -1
  103. package/dist/registry/client.js +4 -1
  104. package/dist/registry/client.js.map +1 -1
  105. package/dist/registry/publish.d.ts.map +1 -1
  106. package/dist/registry/publish.js +7 -1
  107. package/dist/registry/publish.js.map +1 -1
  108. package/dist/resolve-mcp.d.ts +21 -0
  109. package/dist/resolve-mcp.d.ts.map +1 -0
  110. package/dist/resolve-mcp.js +42 -0
  111. package/dist/resolve-mcp.js.map +1 -0
  112. package/dist/scanner/external-scanner.d.ts.map +1 -1
  113. package/dist/scanner/external-scanner.js +48 -14
  114. package/dist/scanner/external-scanner.js.map +1 -1
  115. package/dist/scanner/types.d.ts +1 -0
  116. package/dist/scanner/types.d.ts.map +1 -1
  117. package/dist/soul/scanner.d.ts.map +1 -1
  118. package/dist/soul/scanner.js +2 -1
  119. package/dist/soul/scanner.js.map +1 -1
  120. package/dist/telemetry/contribute.d.ts +60 -0
  121. package/dist/telemetry/contribute.d.ts.map +1 -0
  122. package/dist/telemetry/contribute.js +169 -0
  123. package/dist/telemetry/contribute.js.map +1 -0
  124. package/dist/telemetry/index.d.ts +6 -0
  125. package/dist/telemetry/index.d.ts.map +1 -0
  126. package/dist/telemetry/index.js +18 -0
  127. package/dist/telemetry/index.js.map +1 -0
  128. package/dist/telemetry/opt-in.d.ts +46 -0
  129. package/dist/telemetry/opt-in.d.ts.map +1 -0
  130. package/dist/telemetry/opt-in.js +220 -0
  131. package/dist/telemetry/opt-in.js.map +1 -0
  132. package/package.json +9 -3
package/dist/cli.js CHANGED
@@ -40,6 +40,7 @@ var __importStar = (this && this.__importStar) || (function () {
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
41
  const commander_1 = require("commander");
42
42
  const index_1 = require("./index");
43
+ const resolve_mcp_1 = require("./resolve-mcp");
43
44
  const program = new commander_1.Command();
44
45
  // Write JSON to stdout synchronously with retry for pipe backpressure.
45
46
  // process.stdout.write() is async and gets truncated when process.exit()
@@ -80,7 +81,7 @@ function resolveCliPrefix() {
80
81
  }
81
82
  const CLI_PREFIX = resolveCliPrefix();
82
83
  // Check for NO_COLOR env or non-TTY to disable colors by default
83
- const noColorEnv = process.env.NO_COLOR !== undefined || process.stdout.isTTY === false;
84
+ const noColorEnv = process.env.NO_COLOR !== undefined || !process.stdout.isTTY;
84
85
  // Color codes - will be cleared if --no-color is passed
85
86
  let colors = {
86
87
  green: '\x1b[32m',
@@ -102,7 +103,7 @@ program
102
103
  .name('hackmyagent')
103
104
  .description(`Find it. Break it. Fix it.
104
105
 
105
- The hacker's toolkit for AI agents. 147+ security checks, 75 attack
106
+ The hacker's toolkit for AI agents. 147+ security checks, 115 attack
106
107
  payloads, auto-fix with rollback, and OASB benchmark compliance.
107
108
 
108
109
  Documentation: https://hackmyagent.com/docs
@@ -115,11 +116,11 @@ Updates (v${index_1.VERSION}):
115
116
 
116
117
  Examples:
117
118
  $ hackmyagent secure Find vulnerabilities (147+ checks)
118
- $ hackmyagent attack --local Break it with 75 attack payloads
119
+ $ hackmyagent attack --local Break it with 115 attack payloads
119
120
  $ hackmyagent secure --fix Fix issues automatically
120
121
  $ hackmyagent fix-all Run all security plugins
121
122
  $ hackmyagent scan example.com Scan external infrastructure`)
122
- .version(index_1.VERSION, '-V, --version', 'Output the version number')
123
+ .version(index_1.VERSION, '-v, --version', 'Output the version number')
123
124
  .option('--no-color', 'Disable colored output (also respects NO_COLOR env)')
124
125
  .hook('preAction', (thisCommand) => {
125
126
  const opts = thisCommand.opts();
@@ -572,21 +573,15 @@ function generateHtmlReport(result) {
572
573
  .sort((a, b) => a.compliance - b.compliance)[0];
573
574
  // Security grade based on compliance
574
575
  const getGrade = (pct) => {
575
- if (pct >= 95)
576
- return { letter: 'A+', color: '#22c55e' };
577
576
  if (pct >= 90)
578
- return { letter: 'A', color: '#22c55e' };
579
- if (pct >= 85)
580
- return { letter: 'B+', color: '#84cc16' };
577
+ return { letter: 'strong', color: '#22c55e' };
581
578
  if (pct >= 80)
582
- return { letter: 'B', color: '#84cc16' };
583
- if (pct >= 75)
584
- return { letter: 'C+', color: '#eab308' };
579
+ return { letter: 'good', color: '#84cc16' };
585
580
  if (pct >= 70)
586
- return { letter: 'C', color: '#eab308' };
581
+ return { letter: 'moderate', color: '#eab308' };
587
582
  if (pct >= 60)
588
- return { letter: 'D', color: '#f97316' };
589
- return { letter: 'F', color: '#ef4444' };
583
+ return { letter: 'improving', color: '#f97316' };
584
+ return { letter: 'needs-attention', color: '#ef4444' };
590
585
  };
591
586
  const grade = getGrade(result.compliance);
592
587
  // Generate executive summary items
@@ -766,15 +761,15 @@ function generateHtmlReport(result) {
766
761
  border-bottom: 1px solid var(--border);
767
762
  }
768
763
  .score-grade {
769
- width: 56px;
770
- height: 56px;
764
+ width: 72px;
765
+ height: 72px;
771
766
  border-radius: 12px;
772
767
  border: 2px solid;
773
768
  display: flex;
774
769
  align-items: center;
775
770
  justify-content: center;
776
771
  }
777
- .grade-letter { font-size: 1.75rem; font-weight: 800; }
772
+ .grade-letter { font-size: 0.65rem; font-weight: 800; text-transform: uppercase; text-align: center; line-height: 1.2; }
778
773
  .score-main { flex: 1; }
779
774
  .score-pct { font-size: 2rem; font-weight: 700; color: var(--text-primary); line-height: 1; }
780
775
  .score-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; }
@@ -1233,7 +1228,7 @@ function generateScanHtmlReport(scanResult, targetDir) {
1233
1228
  const fixedFindings = scanResult.findings.filter(f => f.fixed);
1234
1229
  const score = scanResult.score;
1235
1230
  const scoreColor = score >= 90 ? '#22c55e' : score >= 70 ? '#eab308' : score >= 50 ? '#f97316' : '#ef4444';
1236
- const gradeLetters = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'F';
1231
+ const gradeLetters = score >= 90 ? 'strong' : score >= 80 ? 'good' : score >= 70 ? 'moderate' : score >= 60 ? 'improving' : 'needs-attention';
1237
1232
  const severityOrder = ['critical', 'high', 'medium', 'low'];
1238
1233
  const severityColors = {
1239
1234
  critical: '#ef4444', high: '#f97316', medium: '#eab308', low: '#22c55e',
@@ -1274,7 +1269,7 @@ function generateScanHtmlReport(scanResult, targetDir) {
1274
1269
  h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
1275
1270
  .meta { color: var(--text-secondary); margin-bottom: 2rem; }
1276
1271
  .score-card { display: flex; align-items: center; gap: 2rem; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 12px; padding: 1.5rem; margin-bottom: 2rem; }
1277
- .grade { font-size: 3rem; font-weight: 700; width: 80px; height: 80px; display: flex; align-items: center; justify-content: center; border-radius: 50%; border: 3px solid ${scoreColor}; }
1272
+ .grade { font-size: 0.75rem; font-weight: 700; width: 100px; height: 100px; display: flex; align-items: center; justify-content: center; border-radius: 50%; border: 3px solid ${scoreColor}; text-transform: uppercase; text-align: center; line-height: 1.2; padding: 0.5rem; }
1278
1273
  .score-details { flex: 1; }
1279
1274
  .score-num { font-size: 2rem; font-weight: 700; }
1280
1275
  .stats { display: flex; gap: 2rem; margin-top: 0.5rem; }
@@ -1537,6 +1532,95 @@ function resolvePackageVersion(targetDir) {
1537
1532
  catch { /* ignore */ }
1538
1533
  return null;
1539
1534
  }
1535
+ /**
1536
+ * Handle community contribution after a scan completes.
1537
+ *
1538
+ * Determines whether to contribute based on:
1539
+ * 1. --contribute / --no-contribute CLI flags (highest priority)
1540
+ * 2. ~/.opena2a/config.json contribute.enabled setting
1541
+ * 3. Interactive opt-in prompt (first scan or scan #10)
1542
+ *
1543
+ * If contributing, builds an anonymized payload and submits it
1544
+ * asynchronously (non-blocking). Failures are logged as warnings.
1545
+ */
1546
+ async function handleContribution(contributeFlag, targetDir, findings, registryUrl, format) {
1547
+ try {
1548
+ const { isContributeEnabled, shouldPromptContribute, showContributePrompt, incrementScanCount, buildContributionPayloadFromDir, submitContribution, } = await Promise.resolve().then(() => __importStar(require('./telemetry')));
1549
+ // Always increment scan count
1550
+ incrementScanCount();
1551
+ // Determine whether to contribute
1552
+ let shouldContribute;
1553
+ if (contributeFlag === true) {
1554
+ // --contribute flag: always contribute this scan
1555
+ shouldContribute = true;
1556
+ }
1557
+ else if (contributeFlag === false) {
1558
+ // --no-contribute flag: skip this scan
1559
+ shouldContribute = false;
1560
+ }
1561
+ else {
1562
+ // Check config
1563
+ const configSetting = isContributeEnabled();
1564
+ if (configSetting === true) {
1565
+ shouldContribute = true;
1566
+ }
1567
+ else if (configSetting === false) {
1568
+ shouldContribute = false;
1569
+ }
1570
+ else {
1571
+ // Not configured -- prompt after 3 scans (interactive TTY only)
1572
+ if (format === 'text' && process.stdout.isTTY && shouldPromptContribute()) {
1573
+ shouldContribute = await showContributePrompt();
1574
+ }
1575
+ else {
1576
+ shouldContribute = false;
1577
+ }
1578
+ }
1579
+ }
1580
+ if (!shouldContribute)
1581
+ return;
1582
+ // Build and submit contribution (non-blocking)
1583
+ const packageName = resolvePackageName(targetDir);
1584
+ if (!packageName)
1585
+ return;
1586
+ const payload = buildContributionPayloadFromDir(packageName, targetDir, findings);
1587
+ const result = await submitContribution(payload, registryUrl);
1588
+ if (result.success && format === 'text') {
1589
+ console.log('Contributed anonymized scan summary to OpenA2A Registry (--no-contribute to opt out)');
1590
+ }
1591
+ // Failures are silently ignored -- contribution is best-effort
1592
+ }
1593
+ catch {
1594
+ // Non-fatal: contribution failure must never crash the scan
1595
+ }
1596
+ }
1597
+ /**
1598
+ * Handle community contribution for scan-soul results.
1599
+ *
1600
+ * Converts SoulScanResult controls into SecurityFinding-like objects
1601
+ * for the contribution module, then delegates to handleContribution.
1602
+ */
1603
+ async function handleSoulContribution(contributeFlag, targetDir, result, registryUrl, format) {
1604
+ // Convert soul controls into SecurityFinding-shaped objects
1605
+ const findings = [];
1606
+ for (const domain of result.domains) {
1607
+ if (domain.skippedByProfile || domain.skippedByTier)
1608
+ continue;
1609
+ for (const ctrl of domain.controls) {
1610
+ findings.push({
1611
+ checkId: ctrl.id,
1612
+ name: ctrl.name,
1613
+ description: '',
1614
+ category: domain.domain,
1615
+ severity: 'medium',
1616
+ passed: ctrl.passed,
1617
+ message: '',
1618
+ fixable: false,
1619
+ });
1620
+ }
1621
+ }
1622
+ await handleContribution(contributeFlag, targetDir, findings, registryUrl, format);
1623
+ }
1540
1624
  program
1541
1625
  .command('secure')
1542
1626
  .description(`Scan and harden your agent setup
@@ -1592,8 +1676,10 @@ Examples:
1592
1676
  .option('--registry-report', 'Post results to OpenA2A Registry')
1593
1677
  .option('--no-registry', 'Skip auto-publishing results to OpenA2A Registry')
1594
1678
  .option('--version-id <id>', 'Registry version ID to report against')
1595
- .option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://registry.opena2a.org')
1679
+ .option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://api.oa2a.org')
1596
1680
  .option('--registry-key <key>', 'Registry API key (default: REGISTRY_API_KEY env)')
1681
+ .option('--contribute', 'Share anonymized scan findings with OpenA2A Registry (overrides config)')
1682
+ .option('--no-contribute', 'Do not share findings for this scan (overrides config)')
1597
1683
  .action(async (directory, options) => {
1598
1684
  try {
1599
1685
  const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
@@ -1768,7 +1854,7 @@ Examples:
1768
1854
  if (options.publish && options.registry !== false) {
1769
1855
  try {
1770
1856
  const { publishScanResults } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
1771
- const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
1857
+ const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
1772
1858
  const packageName = resolvePackageName(targetDir);
1773
1859
  if (packageName) {
1774
1860
  const publishData = {
@@ -1797,6 +1883,8 @@ Examples:
1797
1883
  else {
1798
1884
  writeJsonStdout(jsonOutput);
1799
1885
  }
1886
+ // Community contribution (non-blocking, runs in JSON mode too)
1887
+ await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format);
1800
1888
  const critHigh = result.findings.filter((f) => !f.passed && !f.fixed && (f.severity === 'critical' || f.severity === 'high'));
1801
1889
  if (critHigh.length > 0)
1802
1890
  process.exitCode = 1;
@@ -1936,7 +2024,7 @@ Examples:
1936
2024
  if (options.versionId || options.registryReport) {
1937
2025
  try {
1938
2026
  const core = await Promise.resolve().then(() => __importStar(require('./index')));
1939
- const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
2027
+ const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
1940
2028
  if (options.versionId) {
1941
2029
  // Authenticated path: existing behavior (version-id + API key)
1942
2030
  const registryKey = options.registryKey || process.env.REGISTRY_API_KEY;
@@ -1983,7 +2071,7 @@ Examples:
1983
2071
  else if (options.publish) {
1984
2072
  try {
1985
2073
  const { publishScanResults, formatPublishOutput } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
1986
- const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
2074
+ const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
1987
2075
  const packageName = resolvePackageName(targetDir);
1988
2076
  if (!packageName) {
1989
2077
  console.error('\nCould not determine package name. Publish requires a package.json with a name field.');
@@ -2015,6 +2103,8 @@ Examples:
2015
2103
  console.error('Scan results are still available locally.');
2016
2104
  }
2017
2105
  }
2106
+ // Community contribution: share anonymized findings with OpenA2A Registry
2107
+ await handleContribution(options.contribute, targetDir, result.findings, options.registryUrl, format);
2018
2108
  // Star prompt (interactive TTY only, text format only)
2019
2109
  if (process.stdout.isTTY) {
2020
2110
  console.log(`${colors.cyan}Helpful?${RESET()} Star the project: https://github.com/opena2a-org/opena2a\n`);
@@ -2270,7 +2360,7 @@ Detects externally exposed:
2270
2360
  • API keys in responses
2271
2361
  • Debug/admin interfaces
2272
2362
 
2273
- Scoring: A (90-100), B (80-89), C (70-79), D (60-69), Needs Improvement (<60)
2363
+ Scoring: strong (90-100), good (80-89), moderate (70-79), improving (60-69), needs-attention (<60)
2274
2364
  Exit code 1 if critical/high issues found.
2275
2365
 
2276
2366
  Examples:
@@ -2299,13 +2389,11 @@ Examples:
2299
2389
  return;
2300
2390
  }
2301
2391
  // Print header
2302
- const gradeColor = result.grade === 'A'
2392
+ const gradeColor = result.grade === 'strong' || result.grade === 'good'
2303
2393
  ? colors.green
2304
- : result.grade === 'B'
2305
- ? colors.green
2306
- : result.grade === 'C'
2307
- ? colors.yellow
2308
- : colors.red;
2394
+ : result.grade === 'moderate'
2395
+ ? colors.yellow
2396
+ : colors.red;
2309
2397
  console.log(`Target: ${result.target}`);
2310
2398
  console.log(`Score: ${gradeColor}${result.score}/100 (${result.grade})${RESET()}`);
2311
2399
  console.log(`Open Ports: ${result.openPorts.length > 0 ? result.openPorts.join(', ') : 'None detected'}`);
@@ -2435,7 +2523,7 @@ Examples:
2435
2523
  .option('--registry-report', 'Post results to OpenA2A Registry')
2436
2524
  .option('--no-registry', 'Skip auto-publishing results to OpenA2A Registry')
2437
2525
  .option('--version-id <id>', 'Registry version ID to report against')
2438
- .option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://registry.opena2a.org')
2526
+ .option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://api.oa2a.org')
2439
2527
  .option('--registry-key <key>', 'Registry API key (default: REGISTRY_API_KEY env)')
2440
2528
  .action(async (targetUrl, options) => {
2441
2529
  try {
@@ -2591,7 +2679,7 @@ Examples:
2591
2679
  if (shouldReport) {
2592
2680
  try {
2593
2681
  const core = await Promise.resolve().then(() => __importStar(require('./index')));
2594
- const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
2682
+ const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
2595
2683
  if (options.versionId) {
2596
2684
  // Authenticated path: existing behavior (version-id + API key)
2597
2685
  const registryKey = options.registryKey || process.env.REGISTRY_API_KEY;
@@ -2638,7 +2726,7 @@ Examples:
2638
2726
  else if (options.publish && targetType !== 'local') {
2639
2727
  try {
2640
2728
  const { publishScanResults, formatPublishOutput } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
2641
- const regUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
2729
+ const regUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
2642
2730
  const packageName = target.url || targetUrl || 'unknown';
2643
2731
  if (format === 'text') {
2644
2732
  console.log('\nPublishing results to registry...\n');
@@ -2792,14 +2880,14 @@ function generateAttackHtmlReport(report) {
2792
2880
  // Risk grade based on score
2793
2881
  const getGrade = (score) => {
2794
2882
  if (score <= 10)
2795
- return { letter: 'A', color: '#22c55e' };
2883
+ return { letter: 'strong', color: '#22c55e' };
2796
2884
  if (score <= 25)
2797
- return { letter: 'B', color: '#84cc16' };
2885
+ return { letter: 'good', color: '#84cc16' };
2798
2886
  if (score <= 50)
2799
- return { letter: 'C', color: '#eab308' };
2887
+ return { letter: 'moderate', color: '#eab308' };
2800
2888
  if (score <= 70)
2801
- return { letter: 'D', color: '#f97316' };
2802
- return { letter: 'F', color: '#ef4444' };
2889
+ return { letter: 'improving', color: '#f97316' };
2890
+ return { letter: 'needs-attention', color: '#ef4444' };
2803
2891
  };
2804
2892
  const grade = getGrade(report.riskScore);
2805
2893
  const ratingColor = {
@@ -2834,6 +2922,10 @@ function generateAttackHtmlReport(report) {
2834
2922
  'context-manipulation': 'CM',
2835
2923
  'mcp-exploitation': 'MCP',
2836
2924
  'a2a-attack': 'A2A',
2925
+ 'memory-weaponization': 'MEM',
2926
+ 'context-window': 'CTX',
2927
+ 'supply-chain': 'SUP',
2928
+ 'tool-shadow': 'SHADOW',
2837
2929
  };
2838
2930
  // Donut chart for attack results
2839
2931
  const donutRadius = 60;
@@ -3069,15 +3161,15 @@ function generateAttackHtmlReport(report) {
3069
3161
  border-bottom: 1px solid var(--border);
3070
3162
  }
3071
3163
  .score-grade {
3072
- width: 56px;
3073
- height: 56px;
3164
+ width: 72px;
3165
+ height: 72px;
3074
3166
  border-radius: 12px;
3075
3167
  border: 2px solid;
3076
3168
  display: flex;
3077
3169
  align-items: center;
3078
3170
  justify-content: center;
3079
3171
  }
3080
- .grade-letter { font-size: 1.75rem; font-weight: 800; }
3172
+ .grade-letter { font-size: 0.65rem; font-weight: 800; text-transform: uppercase; text-align: center; line-height: 1.2; }
3081
3173
  .score-main { flex: 1; }
3082
3174
  .score-pct { font-size: 2rem; font-weight: 700; color: var(--text-primary); line-height: 1; }
3083
3175
  .score-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; }
@@ -3524,6 +3616,7 @@ function generateAttackHtmlReport(report) {
3524
3616
  }
3525
3617
  // --- fix-all: Run all OpenClaw plugins to scan and remediate ---
3526
3618
  const credvault_1 = require("./plugins/credvault");
3619
+ const secretless_1 = require("./plugins/secretless");
3527
3620
  const signcrypt_1 = require("./plugins/signcrypt");
3528
3621
  const skillguard_1 = require("./plugins/skillguard");
3529
3622
  const aim_core_1 = require("@opena2a/aim-core");
@@ -3539,27 +3632,35 @@ program
3539
3632
  .description(`Run all OpenA2A security plugins to scan and auto-fix agent issues
3540
3633
 
3541
3634
  Runs the full plugin suite in order:
3542
- 1. SkillGuard hash pinning, tamper detection, dangerous patterns
3543
- 2. SignCrypt Ed25519 signing, heartbeat hash pins
3544
- 3. CredVault credential detection, env var replacement
3635
+ 1. Credential Protection find hardcoded secrets, replace with env vars
3636
+ 2. AI Visibility Protection block .env from AI tools, encrypt MCP keys
3637
+ 3. File Signing sign skills and heartbeats with Ed25519
3638
+ 4. Skill Safety Scanner — detect dangerous patterns, pin hashes
3545
3639
 
3546
3640
  Each plugin scans for findings, then auto-fixes what it can.
3547
3641
  Dangerous patterns (reverse shells, exfil, etc.) require manual review.
3548
3642
 
3643
+ Step 2 requires secretless-ai (npm install -g secretless-ai). If not
3644
+ installed, the plugin reports this and continues with the remaining steps.
3645
+
3646
+ Use --with-aim to create a cryptographic identity for your agent.
3647
+ This enables automatic file signing, audit logging, and trust scoring
3648
+ so you don't need to manage keys or track files manually.
3649
+
3549
3650
  Exit code 1 if critical/high issues remain after fixing.
3550
3651
 
3551
3652
  Examples:
3552
3653
  $ hackmyagent fix-all Scan and fix current directory
3553
3654
  $ hackmyagent fix-all ./my-agent Scan specific directory
3655
+ $ hackmyagent fix-all --with-aim Create identity + sign + audit (recommended)
3554
3656
  $ hackmyagent fix-all --dry-run Preview fixes without applying
3555
3657
  $ hackmyagent fix-all --scan-only Scan without fixing
3556
- $ hackmyagent fix-all --json JSON output for CI
3557
- $ hackmyagent fix-all --with-aim Enable AIM identity and audit`)
3658
+ $ hackmyagent fix-all --json JSON output for CI`)
3558
3659
  .argument('[directory]', 'Agent directory to scan (default: current directory)', '')
3559
3660
  .option('--dry-run', 'Preview fixes without applying them')
3560
3661
  .option('--scan-only', 'Only scan, do not fix')
3561
3662
  .option('--json', 'Output as JSON (for scripting/CI)')
3562
- .option('--with-aim', 'Initialize AIM Core for identity-aware audit logging')
3663
+ .option('--with-aim', 'Create agent identity for automatic signing, audit logging, and trust scoring')
3563
3664
  .option('-v, --verbose', 'Show all findings including passed plugins')
3564
3665
  .action(async (directory, options) => {
3565
3666
  try {
@@ -3607,12 +3708,15 @@ Examples:
3607
3708
  });
3608
3709
  }
3609
3710
  // Create and initialize plugins in execution order
3610
- // Order matters: CredVault replaces creds, SignCrypt signs skills,
3611
- // SkillGuard pins last so hashes reflect the final file state.
3711
+ // 1. CredVault finds hardcoded secrets, replaces with ${VAR}
3712
+ // 2. Secretless blocks .env from AI visibility (completes the credential lifecycle)
3713
+ // 3. SignCrypt signs skill and heartbeat files
3714
+ // 4. SkillGuard pins hashes last so they reflect the final file state
3612
3715
  const pluginFactories = [
3613
- { name: 'CredVault', create: credvault_1.createPlugin },
3614
- { name: 'SignCrypt', create: signcrypt_1.createPlugin },
3615
- { name: 'SkillGuard', create: skillguard_1.createPlugin },
3716
+ { name: 'Credential Protection', create: credvault_1.createPlugin },
3717
+ { name: 'AI Visibility Protection', create: secretless_1.createPlugin },
3718
+ { name: 'File Signing', create: signcrypt_1.createPlugin },
3719
+ { name: 'Skill Safety Scanner', create: skillguard_1.createPlugin },
3616
3720
  ];
3617
3721
  const plugins = [];
3618
3722
  for (const factory of pluginFactories) {
@@ -3818,16 +3922,6 @@ Examples:
3818
3922
  process.exit(1);
3819
3923
  }
3820
3924
  });
3821
- // Grade display colors
3822
- function gradeColor(grade) {
3823
- switch (grade) {
3824
- case 'A': return colors.green;
3825
- case 'B': return colors.green;
3826
- case 'C': return colors.yellow;
3827
- case 'D': return colors.red;
3828
- case 'F': return colors.brightRed;
3829
- }
3830
- }
3831
3925
  function levelColor(level) {
3832
3926
  switch (level) {
3833
3927
  case 'hardened': return colors.green;
@@ -3906,7 +4000,9 @@ Examples:
3906
4000
  .option('--fail-below <score>', 'Exit 1 if score below threshold (0-100)')
3907
4001
  .option('--deep', 'Enable LLM semantic analysis for ambiguous controls (requires claude CLI or ANTHROPIC_API_KEY)')
3908
4002
  .option('--publish', 'Push scan results to the OpenA2A Registry')
3909
- .option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://registry.opena2a.org')
4003
+ .option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', process.env.REGISTRY_URL || 'https://api.oa2a.org')
4004
+ .option('--contribute', 'Share anonymized scan findings with OpenA2A Registry (overrides config)')
4005
+ .option('--no-contribute', 'Do not share findings for this scan (overrides config)')
3910
4006
  .action(async (directory, options) => {
3911
4007
  try {
3912
4008
  const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
@@ -3929,7 +4025,7 @@ Examples:
3929
4025
  if (options.publish) {
3930
4026
  try {
3931
4027
  const { publishScanResults } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
3932
- const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
4028
+ const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
3933
4029
  const packageName = resolvePackageName(targetDir);
3934
4030
  if (packageName) {
3935
4031
  const publishData = {
@@ -4043,7 +4139,7 @@ Examples:
4043
4139
  if (options.publish) {
4044
4140
  try {
4045
4141
  const { publishScanResults, formatPublishOutput } = await Promise.resolve().then(() => __importStar(require('./registry/publish')));
4046
- const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
4142
+ const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://api.oa2a.org';
4047
4143
  const packageName = resolvePackageName(targetDir);
4048
4144
  if (!packageName) {
4049
4145
  process.stderr.write('Could not determine package name. Publish requires a package.json with a name field.\n');
@@ -4070,6 +4166,9 @@ Examples:
4070
4166
  process.stderr.write('Scan results are still available locally.\n');
4071
4167
  }
4072
4168
  }
4169
+ // Community contribution: share anonymized findings with OpenA2A Registry
4170
+ const soulFormat = options.json ? 'json' : 'text';
4171
+ await handleSoulContribution(options.contribute, targetDir, result, options.registryUrl, soulFormat);
4073
4172
  // Check fail threshold
4074
4173
  if (options.failBelow) {
4075
4174
  const threshold = parseInt(options.failBelow, 10);
@@ -4175,5 +4274,321 @@ Examples:
4175
4274
  process.exit(1);
4176
4275
  }
4177
4276
  });
4277
+ // ---------------------------------------------------------------------------
4278
+ // trust — Trust verification via OpenA2A Registry (powered by ai-trust)
4279
+ // ---------------------------------------------------------------------------
4280
+ const REGISTRY_DEFAULT_URL = 'https://api.oa2a.org';
4281
+ async function trustCheck(name, registryUrl, type) {
4282
+ const params = new URLSearchParams({ name, includeProfile: 'true', includeDeps: 'true' });
4283
+ if (type)
4284
+ params.set('type', type);
4285
+ const url = `${registryUrl}/api/v1/trust/query?${params.toString()}`;
4286
+ const res = await fetch(url, {
4287
+ method: 'GET',
4288
+ headers: { 'Accept': 'application/json', 'User-Agent': `hackmyagent/${index_1.VERSION}` },
4289
+ });
4290
+ if (!res.ok) {
4291
+ if (res.status === 404) {
4292
+ throw new Error(`Package "${name}" not found in the OpenA2A Registry.`);
4293
+ }
4294
+ const body = await res.text();
4295
+ throw new Error(`Registry API returned ${res.status}: ${body}`);
4296
+ }
4297
+ const data = (await res.json());
4298
+ data.found = !!data.packageId;
4299
+ return data;
4300
+ }
4301
+ async function trustBatch(packages, registryUrl) {
4302
+ const url = `${registryUrl}/api/v1/trust/batch`;
4303
+ const res = await fetch(url, {
4304
+ method: 'POST',
4305
+ headers: {
4306
+ 'Content-Type': 'application/json',
4307
+ 'Accept': 'application/json',
4308
+ 'User-Agent': `hackmyagent/${index_1.VERSION}`,
4309
+ },
4310
+ body: JSON.stringify({ packages }),
4311
+ });
4312
+ if (!res.ok) {
4313
+ if (res.status === 404) {
4314
+ throw new Error('Registry batch endpoint not found. The registry may be unavailable.');
4315
+ }
4316
+ const body = await res.text();
4317
+ throw new Error(`Registry API returned ${res.status}: ${body}`);
4318
+ }
4319
+ const raw = (await res.json());
4320
+ const NULL_UUID = '00000000-0000-0000-0000-000000000000';
4321
+ for (const r of raw.results) {
4322
+ r.found = !!r.packageId && r.packageId !== NULL_UUID;
4323
+ }
4324
+ const found = raw.results.filter((r) => r.found).length;
4325
+ return {
4326
+ results: raw.results,
4327
+ meta: { total: raw.total, found, notFound: raw.total - found },
4328
+ };
4329
+ }
4330
+ function trustLevelLabel(level) {
4331
+ switch (level) {
4332
+ case 0: return 'Blocked';
4333
+ case 1: return 'Warning';
4334
+ case 2: return 'Listed';
4335
+ case 3: return 'Scanned';
4336
+ case 4: return 'Verified';
4337
+ default: return `Unknown (${level})`;
4338
+ }
4339
+ }
4340
+ function trustLevelColor(level) {
4341
+ if (level >= 3)
4342
+ return colors.green;
4343
+ if (level >= 1)
4344
+ return colors.yellow;
4345
+ return colors.red;
4346
+ }
4347
+ function trustVerdictColor(verdict) {
4348
+ switch (verdict) {
4349
+ case 'safe': return colors.green;
4350
+ case 'warning': return colors.yellow;
4351
+ case 'blocked': return colors.red;
4352
+ default: return colors.dim;
4353
+ }
4354
+ }
4355
+ function formatTrustCheck(answer) {
4356
+ if (!answer.found) {
4357
+ return [
4358
+ '',
4359
+ ` ${answer.name}`,
4360
+ ` ${colors.dim}Type: ${answer.packageType || 'unknown'}${colors.reset}`,
4361
+ ` ${colors.dim}Status: Not found in registry${colors.reset}`,
4362
+ '',
4363
+ ].join('\n');
4364
+ }
4365
+ const vc = trustVerdictColor(answer.verdict);
4366
+ const tc = trustLevelColor(answer.trustLevel);
4367
+ const lines = [
4368
+ '',
4369
+ ` ${answer.name}`,
4370
+ ` Type: ${answer.packageType || 'unknown'}`,
4371
+ ` Verdict: ${vc}${answer.verdict.toUpperCase()}${colors.reset}`,
4372
+ ` Trust Level: ${tc}${trustLevelLabel(answer.trustLevel)}${colors.reset} (${answer.trustLevel}/4)`,
4373
+ ` Trust Score: ${Math.round(answer.trustScore * 100)}/100`,
4374
+ ` Scan Status: ${answer.scanStatus || 'unknown'}`,
4375
+ ];
4376
+ if (answer.dependencies && answer.dependencies.totalDeps > 0) {
4377
+ const deps = answer.dependencies;
4378
+ lines.push('');
4379
+ lines.push(' Dependencies');
4380
+ lines.push(` Total: ${deps.totalDeps}`);
4381
+ lines.push(` Vulnerable: ${deps.vulnerableDeps > 0 ? colors.red + deps.vulnerableDeps + colors.reset : colors.green + '0' + colors.reset}`);
4382
+ lines.push(` Min Trust: ${deps.minTrustLevel}/4`);
4383
+ }
4384
+ lines.push('');
4385
+ return lines.join('\n');
4386
+ }
4387
+ function formatTrustBatch(response, minTrust) {
4388
+ const lines = [];
4389
+ lines.push('');
4390
+ lines.push(` Trust Audit: ${response.meta.total} packages queried, ${response.meta.found} found, ${response.meta.notFound} not found`);
4391
+ lines.push('');
4392
+ const nameW = 40, typeW = 14, verdictW = 10, levelW = 12, scoreW = 8, scanW = 10;
4393
+ lines.push(' ' +
4394
+ 'PACKAGE'.padEnd(nameW) +
4395
+ 'TYPE'.padEnd(typeW) +
4396
+ 'VERDICT'.padEnd(verdictW) +
4397
+ 'TRUST'.padEnd(levelW) +
4398
+ 'SCORE'.padEnd(scoreW) +
4399
+ 'SCAN'.padEnd(scanW));
4400
+ lines.push(' ' + '-'.repeat(nameW + typeW + verdictW + levelW + scoreW + scanW));
4401
+ for (const result of response.results) {
4402
+ const vc = trustVerdictColor(result.verdict);
4403
+ const tc = trustLevelColor(result.trustLevel);
4404
+ const name = result.name.length > nameW - 2
4405
+ ? result.name.substring(0, nameW - 5) + '...'
4406
+ : result.name;
4407
+ lines.push(' ' +
4408
+ name.padEnd(nameW) +
4409
+ (result.packageType || '-').padEnd(typeW) +
4410
+ vc + result.verdict.toUpperCase().padEnd(verdictW) + colors.reset +
4411
+ tc + trustLevelLabel(result.trustLevel).padEnd(levelW) + colors.reset +
4412
+ (result.found ? `${Math.round(result.trustScore * 100)}/100` : '-').padEnd(scoreW) +
4413
+ (result.scanStatus || '-').padEnd(scanW));
4414
+ }
4415
+ const belowThreshold = response.results.filter((r) => r.found && r.trustLevel < minTrust);
4416
+ const notFound = response.results.filter((r) => !r.found);
4417
+ lines.push('');
4418
+ if (belowThreshold.length > 0) {
4419
+ lines.push(` ${colors.yellow}[!] ${belowThreshold.length} package(s) below minimum trust level ${minTrust}:${colors.reset}`);
4420
+ for (const pkg of belowThreshold) {
4421
+ lines.push(` ${colors.yellow} - ${pkg.name} (trust level ${pkg.trustLevel}, verdict: ${pkg.verdict})${colors.reset}`);
4422
+ }
4423
+ }
4424
+ if (notFound.length > 0) {
4425
+ lines.push(` ${colors.dim}[?] ${notFound.length} package(s) not found in registry:${colors.reset}`);
4426
+ for (const pkg of notFound) {
4427
+ lines.push(` ${colors.dim} - ${pkg.name}${colors.reset}`);
4428
+ }
4429
+ }
4430
+ if (belowThreshold.length === 0 && notFound.length === 0) {
4431
+ lines.push(` ${colors.green}All ${response.meta.found} packages meet minimum trust level ${minTrust}.${colors.reset}`);
4432
+ }
4433
+ lines.push('');
4434
+ return lines.join('\n');
4435
+ }
4436
+ async function parseDepsFile(filePath) {
4437
+ const fs = require('fs');
4438
+ const path = require('path');
4439
+ let content;
4440
+ try {
4441
+ content = fs.readFileSync(filePath, 'utf-8');
4442
+ }
4443
+ catch (err) {
4444
+ if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {
4445
+ throw new Error(`File not found: ${filePath}`);
4446
+ }
4447
+ throw err;
4448
+ }
4449
+ const fileName = path.basename(filePath);
4450
+ if (fileName === 'package.json') {
4451
+ const pkg = JSON.parse(content);
4452
+ const packages = [];
4453
+ const seen = new Set();
4454
+ for (const deps of [pkg.dependencies, pkg.devDependencies]) {
4455
+ if (!deps)
4456
+ continue;
4457
+ for (const name of Object.keys(deps)) {
4458
+ if (!seen.has(name)) {
4459
+ seen.add(name);
4460
+ packages.push({ name });
4461
+ }
4462
+ }
4463
+ }
4464
+ return packages;
4465
+ }
4466
+ if (fileName === 'requirements.txt') {
4467
+ const packages = [];
4468
+ const seen = new Set();
4469
+ for (const rawLine of content.split('\n')) {
4470
+ const line = rawLine.trim();
4471
+ if (!line || line.startsWith('#') || line.startsWith('-'))
4472
+ continue;
4473
+ const match = line.match(/^([a-zA-Z0-9_-]+(?:\[[a-zA-Z0-9_,-]+\])?)/);
4474
+ if (match) {
4475
+ const name = match[1].replace(/\[.*\]/, '');
4476
+ if (!seen.has(name)) {
4477
+ seen.add(name);
4478
+ packages.push({ name });
4479
+ }
4480
+ }
4481
+ }
4482
+ return packages;
4483
+ }
4484
+ throw new Error(`Unsupported dependency file: ${fileName}. Supported: package.json, requirements.txt`);
4485
+ }
4486
+ program
4487
+ .command('trust')
4488
+ .description(`Check trust level for AI packages before installing
4489
+
4490
+ Query the OpenA2A Registry to verify trust scores, vulnerability status,
4491
+ and dependency risk for MCP servers, A2A agents, and AI tools.
4492
+
4493
+ Modes:
4494
+ trust <package> Single package lookup
4495
+ trust --audit <file> Audit a dependency file (package.json, requirements.txt)
4496
+ trust --batch pkg1 pkg2 Batch lookup for multiple packages
4497
+
4498
+ Examples:
4499
+ $ ${CLI_PREFIX} trust @anthropic/claude-mcp
4500
+ $ ${CLI_PREFIX} trust server-filesystem (resolves to @modelcontextprotocol/server-filesystem)
4501
+ $ ${CLI_PREFIX} trust mcp-server-fetch (resolves to @modelcontextprotocol/server-fetch)
4502
+ $ ${CLI_PREFIX} trust my-mcp-server --type mcp_server
4503
+ $ ${CLI_PREFIX} trust --audit package.json
4504
+ $ ${CLI_PREFIX} trust --audit requirements.txt --min-trust 3
4505
+ $ ${CLI_PREFIX} trust --batch langchain openai anthropic`)
4506
+ .argument('[package]', 'Package name to look up')
4507
+ .option('-t, --type <type>', 'Package type (mcp_server, a2a_agent, ai_tool, etc.)')
4508
+ .option('--audit <file>', 'Audit a dependency file (package.json or requirements.txt)')
4509
+ .option('--batch <names...>', 'Batch trust lookup for multiple packages')
4510
+ .option('--min-trust <level>', 'Minimum trust level threshold (0-4)', '3')
4511
+ .option('--registry-url <url>', 'Registry base URL', REGISTRY_DEFAULT_URL)
4512
+ .option('--json', 'Output as JSON')
4513
+ .action(async (packageName, opts) => {
4514
+ const registryUrl = opts.registryUrl.replace(/\/+$/, '');
4515
+ const minTrust = parseInt(opts.minTrust, 10);
4516
+ if (isNaN(minTrust) || minTrust < 0 || minTrust > 4) {
4517
+ process.stderr.write('Error: --min-trust must be a number between 0 and 4\n');
4518
+ process.exit(1);
4519
+ }
4520
+ try {
4521
+ // Mode: audit a dependency file
4522
+ if (opts.audit) {
4523
+ const rawPackages = await parseDepsFile(opts.audit);
4524
+ const packages = rawPackages.map((pkg) => ({
4525
+ ...pkg,
4526
+ name: (0, resolve_mcp_1.resolveAndLogMcpShorthand)(pkg.name),
4527
+ }));
4528
+ if (packages.length === 0) {
4529
+ process.stdout.write('No dependencies found in the specified file.\n');
4530
+ return;
4531
+ }
4532
+ if (packages.length > 100) {
4533
+ process.stderr.write(`Error: Too many dependencies (${packages.length}). Maximum 100 per request.\n`);
4534
+ process.exit(1);
4535
+ }
4536
+ const response = await trustBatch(packages, registryUrl);
4537
+ if (opts.json) {
4538
+ writeJsonStdout(response);
4539
+ }
4540
+ else {
4541
+ process.stdout.write(formatTrustBatch(response, minTrust));
4542
+ }
4543
+ const belowThreshold = response.results.some((r) => r.found && r.trustLevel < minTrust);
4544
+ if (belowThreshold)
4545
+ process.exitCode = 1;
4546
+ return;
4547
+ }
4548
+ // Mode: batch lookup
4549
+ if (opts.batch && opts.batch.length > 0) {
4550
+ if (opts.batch.length > 100) {
4551
+ process.stderr.write(`Error: Too many packages (${opts.batch.length}). Maximum 100 per request.\n`);
4552
+ process.exit(1);
4553
+ }
4554
+ const packages = opts.batch.map((name) => ({
4555
+ name: (0, resolve_mcp_1.resolveAndLogMcpShorthand)(name),
4556
+ ...(opts.type ? { type: opts.type } : {}),
4557
+ }));
4558
+ const response = await trustBatch(packages, registryUrl);
4559
+ if (opts.json) {
4560
+ writeJsonStdout(response);
4561
+ }
4562
+ else {
4563
+ process.stdout.write(formatTrustBatch(response, minTrust));
4564
+ }
4565
+ const belowThreshold = response.results.some((r) => r.found && r.trustLevel < minTrust);
4566
+ if (belowThreshold)
4567
+ process.exitCode = 1;
4568
+ return;
4569
+ }
4570
+ // Mode: single package lookup
4571
+ if (!packageName) {
4572
+ process.stderr.write(`Error: Provide a package name or use --audit/--batch.\n`);
4573
+ process.stderr.write(`Usage: ${CLI_PREFIX} trust <package>\n`);
4574
+ process.exit(1);
4575
+ }
4576
+ packageName = (0, resolve_mcp_1.resolveAndLogMcpShorthand)(packageName);
4577
+ const result = await trustCheck(packageName, registryUrl, opts.type);
4578
+ if (opts.json) {
4579
+ writeJsonStdout(result);
4580
+ }
4581
+ else {
4582
+ process.stdout.write(formatTrustCheck(result));
4583
+ }
4584
+ if (result.found && (result.verdict === 'blocked' || result.verdict === 'warning')) {
4585
+ process.exitCode = 1;
4586
+ }
4587
+ }
4588
+ catch (error) {
4589
+ process.stderr.write(`Error: ${error instanceof Error ? error.message : 'Unknown error'}\n`);
4590
+ process.exit(1);
4591
+ }
4592
+ });
4178
4593
  program.parse();
4179
4594
  //# sourceMappingURL=cli.js.map