verification-layer 0.20.0 โ†’ 0.22.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 (180) hide show
  1. package/README.md +251 -615
  2. package/dist/cli.js +542 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/marketplace/index.d.ts +8 -0
  5. package/dist/marketplace/index.d.ts.map +1 -0
  6. package/dist/marketplace/index.js +7 -0
  7. package/dist/marketplace/index.js.map +1 -0
  8. package/dist/marketplace/installer.d.ts +62 -0
  9. package/dist/marketplace/installer.d.ts.map +1 -0
  10. package/dist/marketplace/installer.js +254 -0
  11. package/dist/marketplace/installer.js.map +1 -0
  12. package/dist/marketplace/registry.d.ts +52 -0
  13. package/dist/marketplace/registry.d.ts.map +1 -0
  14. package/dist/marketplace/registry.js +759 -0
  15. package/dist/marketplace/registry.js.map +1 -0
  16. package/dist/marketplace/types.d.ts +123 -0
  17. package/dist/marketplace/types.d.ts.map +1 -0
  18. package/dist/marketplace/types.js +6 -0
  19. package/dist/marketplace/types.js.map +1 -0
  20. package/dist/reporters/audit-report.d.ts.map +1 -1
  21. package/dist/reporters/audit-report.js +180 -0
  22. package/dist/reporters/audit-report.js.map +1 -1
  23. package/dist/reporters/index.d.ts.map +1 -1
  24. package/dist/reporters/index.js +2612 -5
  25. package/dist/reporters/index.js.map +1 -1
  26. package/dist/scan.d.ts.map +1 -1
  27. package/dist/scan.js +15 -1
  28. package/dist/scan.js.map +1 -1
  29. package/dist/scanners/api-security/index.d.ts +7 -0
  30. package/dist/scanners/api-security/index.d.ts.map +1 -0
  31. package/dist/scanners/api-security/index.js +139 -0
  32. package/dist/scanners/api-security/index.js.map +1 -0
  33. package/dist/scanners/api-security/index.test.d.ts +5 -0
  34. package/dist/scanners/api-security/index.test.d.ts.map +1 -0
  35. package/dist/scanners/api-security/index.test.js +360 -0
  36. package/dist/scanners/api-security/index.test.js.map +1 -0
  37. package/dist/scanners/api-security/patterns.d.ts +32 -0
  38. package/dist/scanners/api-security/patterns.d.ts.map +1 -0
  39. package/dist/scanners/api-security/patterns.js +159 -0
  40. package/dist/scanners/api-security/patterns.js.map +1 -0
  41. package/dist/scanners/authentication/index.d.ts +7 -0
  42. package/dist/scanners/authentication/index.d.ts.map +1 -0
  43. package/dist/scanners/authentication/index.js +107 -0
  44. package/dist/scanners/authentication/index.js.map +1 -0
  45. package/dist/scanners/authentication/index.test.d.ts +5 -0
  46. package/dist/scanners/authentication/index.test.d.ts.map +1 -0
  47. package/dist/scanners/authentication/index.test.js +379 -0
  48. package/dist/scanners/authentication/index.test.js.map +1 -0
  49. package/dist/scanners/authentication/patterns.d.ts +32 -0
  50. package/dist/scanners/authentication/patterns.d.ts.map +1 -0
  51. package/dist/scanners/authentication/patterns.js +133 -0
  52. package/dist/scanners/authentication/patterns.js.map +1 -0
  53. package/dist/scanners/configuration/index.d.ts +8 -0
  54. package/dist/scanners/configuration/index.d.ts.map +1 -0
  55. package/dist/scanners/configuration/index.js +87 -0
  56. package/dist/scanners/configuration/index.js.map +1 -0
  57. package/dist/scanners/configuration/index.test.d.ts +5 -0
  58. package/dist/scanners/configuration/index.test.d.ts.map +1 -0
  59. package/dist/scanners/configuration/index.test.js +344 -0
  60. package/dist/scanners/configuration/index.test.js.map +1 -0
  61. package/dist/scanners/configuration/patterns.d.ts +32 -0
  62. package/dist/scanners/configuration/patterns.d.ts.map +1 -0
  63. package/dist/scanners/configuration/patterns.js +146 -0
  64. package/dist/scanners/configuration/patterns.js.map +1 -0
  65. package/dist/scanners/credentials/index.d.ts +7 -0
  66. package/dist/scanners/credentials/index.d.ts.map +1 -0
  67. package/dist/scanners/credentials/index.js +129 -0
  68. package/dist/scanners/credentials/index.js.map +1 -0
  69. package/dist/scanners/credentials/index.test.d.ts +5 -0
  70. package/dist/scanners/credentials/index.test.d.ts.map +1 -0
  71. package/dist/scanners/credentials/index.test.js +395 -0
  72. package/dist/scanners/credentials/index.test.js.map +1 -0
  73. package/dist/scanners/credentials/patterns.d.ts +32 -0
  74. package/dist/scanners/credentials/patterns.d.ts.map +1 -0
  75. package/dist/scanners/credentials/patterns.js +140 -0
  76. package/dist/scanners/credentials/patterns.js.map +1 -0
  77. package/dist/scanners/errors/index.d.ts +8 -0
  78. package/dist/scanners/errors/index.d.ts.map +1 -0
  79. package/dist/scanners/errors/index.js +78 -0
  80. package/dist/scanners/errors/index.js.map +1 -0
  81. package/dist/scanners/errors/index.test.d.ts +5 -0
  82. package/dist/scanners/errors/index.test.d.ts.map +1 -0
  83. package/dist/scanners/errors/index.test.js +330 -0
  84. package/dist/scanners/errors/index.test.js.map +1 -0
  85. package/dist/scanners/errors/patterns.d.ts +27 -0
  86. package/dist/scanners/errors/patterns.d.ts.map +1 -0
  87. package/dist/scanners/errors/patterns.js +97 -0
  88. package/dist/scanners/errors/patterns.js.map +1 -0
  89. package/dist/scanners/hipaa2026/index.d.ts +8 -0
  90. package/dist/scanners/hipaa2026/index.d.ts.map +1 -0
  91. package/dist/scanners/hipaa2026/index.js +345 -0
  92. package/dist/scanners/hipaa2026/index.js.map +1 -0
  93. package/dist/scanners/hipaa2026/index.test.d.ts +5 -0
  94. package/dist/scanners/hipaa2026/index.test.d.ts.map +1 -0
  95. package/dist/scanners/hipaa2026/index.test.js +332 -0
  96. package/dist/scanners/hipaa2026/index.test.js.map +1 -0
  97. package/dist/scanners/hipaa2026/patterns.d.ts +57 -0
  98. package/dist/scanners/hipaa2026/patterns.d.ts.map +1 -0
  99. package/dist/scanners/hipaa2026/patterns.js +268 -0
  100. package/dist/scanners/hipaa2026/patterns.js.map +1 -0
  101. package/dist/scanners/operational/index.d.ts +7 -0
  102. package/dist/scanners/operational/index.d.ts.map +1 -0
  103. package/dist/scanners/operational/index.js +171 -0
  104. package/dist/scanners/operational/index.js.map +1 -0
  105. package/dist/scanners/operational/index.test.d.ts +5 -0
  106. package/dist/scanners/operational/index.test.d.ts.map +1 -0
  107. package/dist/scanners/operational/index.test.js +406 -0
  108. package/dist/scanners/operational/index.test.js.map +1 -0
  109. package/dist/scanners/operational/patterns.d.ts +33 -0
  110. package/dist/scanners/operational/patterns.d.ts.map +1 -0
  111. package/dist/scanners/operational/patterns.js +151 -0
  112. package/dist/scanners/operational/patterns.js.map +1 -0
  113. package/dist/scanners/rbac/index.d.ts +7 -0
  114. package/dist/scanners/rbac/index.d.ts.map +1 -0
  115. package/dist/scanners/rbac/index.js +145 -0
  116. package/dist/scanners/rbac/index.js.map +1 -0
  117. package/dist/scanners/rbac/index.test.d.ts +5 -0
  118. package/dist/scanners/rbac/index.test.d.ts.map +1 -0
  119. package/dist/scanners/rbac/index.test.js +422 -0
  120. package/dist/scanners/rbac/index.test.js.map +1 -0
  121. package/dist/scanners/rbac/patterns.d.ts +32 -0
  122. package/dist/scanners/rbac/patterns.d.ts.map +1 -0
  123. package/dist/scanners/rbac/patterns.js +124 -0
  124. package/dist/scanners/rbac/patterns.js.map +1 -0
  125. package/dist/scanners/revocation/index.d.ts +8 -0
  126. package/dist/scanners/revocation/index.d.ts.map +1 -0
  127. package/dist/scanners/revocation/index.js +83 -0
  128. package/dist/scanners/revocation/index.js.map +1 -0
  129. package/dist/scanners/revocation/index.test.d.ts +5 -0
  130. package/dist/scanners/revocation/index.test.d.ts.map +1 -0
  131. package/dist/scanners/revocation/index.test.js +332 -0
  132. package/dist/scanners/revocation/index.test.js.map +1 -0
  133. package/dist/scanners/revocation/patterns.d.ts +27 -0
  134. package/dist/scanners/revocation/patterns.d.ts.map +1 -0
  135. package/dist/scanners/revocation/patterns.js +109 -0
  136. package/dist/scanners/revocation/patterns.js.map +1 -0
  137. package/dist/scanners/sanitization/index.d.ts +8 -0
  138. package/dist/scanners/sanitization/index.d.ts.map +1 -0
  139. package/dist/scanners/sanitization/index.js +98 -0
  140. package/dist/scanners/sanitization/index.js.map +1 -0
  141. package/dist/scanners/sanitization/index.test.d.ts +5 -0
  142. package/dist/scanners/sanitization/index.test.d.ts.map +1 -0
  143. package/dist/scanners/sanitization/index.test.js +370 -0
  144. package/dist/scanners/sanitization/index.test.js.map +1 -0
  145. package/dist/scanners/sanitization/patterns.d.ts +27 -0
  146. package/dist/scanners/sanitization/patterns.d.ts.map +1 -0
  147. package/dist/scanners/sanitization/patterns.js +117 -0
  148. package/dist/scanners/sanitization/patterns.js.map +1 -0
  149. package/dist/training/certificate.d.ts +26 -0
  150. package/dist/training/certificate.d.ts.map +1 -0
  151. package/dist/training/certificate.js +92 -0
  152. package/dist/training/certificate.js.map +1 -0
  153. package/dist/training/index.d.ts +3 -0
  154. package/dist/training/index.d.ts.map +1 -0
  155. package/dist/training/index.js +243 -0
  156. package/dist/training/index.js.map +1 -0
  157. package/dist/training/modules.d.ts +13 -0
  158. package/dist/training/modules.d.ts.map +1 -0
  159. package/dist/training/modules.js +608 -0
  160. package/dist/training/modules.js.map +1 -0
  161. package/dist/training/questions.d.ts +9 -0
  162. package/dist/training/questions.d.ts.map +1 -0
  163. package/dist/training/questions.js +505 -0
  164. package/dist/training/questions.js.map +1 -0
  165. package/dist/types.d.ts +45 -0
  166. package/dist/types.d.ts.map +1 -1
  167. package/dist/utils/npm-audit.d.ts +6 -0
  168. package/dist/utils/npm-audit.d.ts.map +1 -0
  169. package/dist/utils/npm-audit.js +95 -0
  170. package/dist/utils/npm-audit.js.map +1 -0
  171. package/dist/utils/scan-history.d.ts +59 -0
  172. package/dist/utils/scan-history.d.ts.map +1 -0
  173. package/dist/utils/scan-history.js +170 -0
  174. package/dist/utils/scan-history.js.map +1 -0
  175. package/package.json +4 -1
  176. package/templates/baa-verification-letter.md +105 -0
  177. package/templates/irp.md +545 -0
  178. package/templates/notice-of-privacy-practices.md +491 -0
  179. package/templates/physical-safeguards-checklist.md +247 -0
  180. package/templates/security-officer-designation.md +237 -0
package/dist/cli.js CHANGED
@@ -32,6 +32,7 @@ program
32
32
  .option('--min-confidence <level>', 'Minimum confidence level (high, medium, low)', 'low')
33
33
  .option('--fix', 'Automatically fix detected issues where possible')
34
34
  .option('--no-ai', 'Disable AI-powered triage and analysis')
35
+ .option('--audit', 'Run npm audit and include dependency vulnerabilities in report')
35
36
  .action(async (path, options) => {
36
37
  const spinner = ora('Scanning repository...').start();
37
38
  const absolutePath = resolve(path);
@@ -70,6 +71,21 @@ program
70
71
  minConfidence: options.minConfidence,
71
72
  });
72
73
  spinner.succeed(`Scan complete. Found ${result.findings.length} issues.`);
74
+ // Run npm audit if --audit flag is provided
75
+ let vulnerabilities;
76
+ if (options.audit) {
77
+ const auditSpinner = ora('Running npm audit...').start();
78
+ const { runNpmAudit } = await import('./utils/npm-audit.js');
79
+ const auditResult = await runNpmAudit(absolutePath);
80
+ if (auditResult.error) {
81
+ auditSpinner.warn(`npm audit: ${auditResult.error}`);
82
+ }
83
+ else {
84
+ const vulnCount = auditResult.vulnerabilities.length;
85
+ auditSpinner.succeed(`npm audit complete. Found ${vulnCount} vulnerabilities.`);
86
+ vulnerabilities = auditResult.vulnerabilities;
87
+ }
88
+ }
73
89
  // Apply fixes if --fix flag is provided
74
90
  if (options.fix) {
75
91
  const fixSpinner = ora('Applying automatic fixes...').start();
@@ -84,11 +100,23 @@ program
84
100
  console.log(chalk.yellow('\nRun `vlayer audit <path> --generate-report` to generate PDF audit report.'));
85
101
  }
86
102
  }
103
+ // Get previous scan history and create comparison
104
+ const { getMostRecentScan, compareScan, saveScanHistory } = await import('./utils/scan-history.js');
105
+ const previousScan = await getMostRecentScan(absolutePath);
106
+ const comparison = result.complianceScore
107
+ ? compareScan(result.complianceScore.score, result.findings, previousScan)
108
+ : null;
87
109
  const reportOptions = {
88
110
  format: options.format,
89
111
  outputPath: options.output,
112
+ vulnerabilities,
113
+ scanComparison: comparison,
90
114
  };
91
115
  await generateReport(result, path, reportOptions);
116
+ // Save current scan to history
117
+ if (result.complianceScore) {
118
+ await saveScanHistory(absolutePath, result.complianceScore.score, result.findings, result.scannedFiles);
119
+ }
92
120
  // Print summary
93
121
  const acknowledged = result.findings.filter(f => f.acknowledged && !f.acknowledgment?.expired).length;
94
122
  const suppressed = result.findings.filter(f => f.suppressed).length;
@@ -118,6 +146,29 @@ program
118
146
  if (high > 0) {
119
147
  console.log(chalk.yellow(` High (new): ${high}`));
120
148
  }
149
+ // Display vulnerability summary if audit was run
150
+ if (vulnerabilities && vulnerabilities.length > 0) {
151
+ const vulnCounts = {
152
+ critical: vulnerabilities.filter(v => v.severity === 'critical').length,
153
+ high: vulnerabilities.filter(v => v.severity === 'high').length,
154
+ moderate: vulnerabilities.filter(v => v.severity === 'moderate').length,
155
+ low: vulnerabilities.filter(v => v.severity === 'low').length,
156
+ };
157
+ console.log('\n' + chalk.bold('Dependency Vulnerabilities:'));
158
+ console.log(` Total: ${vulnerabilities.length}`);
159
+ if (vulnCounts.critical > 0) {
160
+ console.log(chalk.red(` Critical: ${vulnCounts.critical}`));
161
+ }
162
+ if (vulnCounts.high > 0) {
163
+ console.log(chalk.yellow(` High: ${vulnCounts.high}`));
164
+ }
165
+ if (vulnCounts.moderate > 0) {
166
+ console.log(chalk.hex('#ca8a04')(` Moderate: ${vulnCounts.moderate}`));
167
+ }
168
+ if (vulnCounts.low > 0) {
169
+ console.log(chalk.blue(` Low: ${vulnCounts.low}`));
170
+ }
171
+ }
121
172
  // Exit with error code if new critical issues found (only if not fixing)
122
173
  if (critical > 0 && !options.fix) {
123
174
  process.exit(1);
@@ -876,5 +927,496 @@ rulesCommand
876
927
  process.exit(1);
877
928
  }
878
929
  });
930
+ // Marketplace commands
931
+ const marketplaceCommand = program
932
+ .command('marketplace')
933
+ .alias('market')
934
+ .description('Community Rules Marketplace - Share and discover healthcare compliance rules');
935
+ marketplaceCommand
936
+ .command('search')
937
+ .description('Search for rules in the marketplace')
938
+ .argument('[query]', 'Search query (rule name, description, tags)', '')
939
+ .option('--framework <framework>', 'Filter by compliance framework (hipaa, state-law, payer-specific)')
940
+ .option('--jurisdiction <state>', 'Filter by state jurisdiction (california, new-york, texas, etc.)')
941
+ .option('--payer <payer>', 'Filter by payer (medicare, medicaid, bcbs, etc.)')
942
+ .option('--tech <stack>', 'Filter by tech stack (fhir, hl7, nextjs, etc.)')
943
+ .option('--verified', 'Show only verified rules')
944
+ .option('--min-rating <rating>', 'Minimum rating (1-5)', '0')
945
+ .option('--limit <number>', 'Maximum results to show', '20')
946
+ .action(async (query, options) => {
947
+ const spinner = ora('Searching marketplace...').start();
948
+ try {
949
+ const { MarketplaceRegistry } = await import('./marketplace/index.js');
950
+ const registry = new MarketplaceRegistry();
951
+ const filters = {};
952
+ if (options.framework)
953
+ filters.framework = options.framework;
954
+ if (options.jurisdiction)
955
+ filters.jurisdiction = options.jurisdiction;
956
+ if (options.payer)
957
+ filters.payer = options.payer;
958
+ if (options.tech)
959
+ filters.techStack = options.tech;
960
+ if (options.verified)
961
+ filters.verified = true;
962
+ if (options.minRating)
963
+ filters.minRating = parseFloat(options.minRating);
964
+ const result = await registry.search(query, filters, 1, parseInt(options.limit));
965
+ spinner.succeed(`Found ${result.total} rule(s)`);
966
+ if (result.rules.length === 0) {
967
+ console.log(chalk.yellow('\nNo rules found matching your criteria.'));
968
+ console.log(chalk.gray('Try broadening your search or removing filters.'));
969
+ return;
970
+ }
971
+ console.log(chalk.bold('\n๐Ÿ“ฆ Marketplace Rules:\n'));
972
+ for (const rule of result.rules) {
973
+ const verified = rule.verified ? chalk.green('โœ“ Verified') : chalk.gray('Unverified');
974
+ const rating = 'โญ'.repeat(Math.round(rule.rating));
975
+ console.log(chalk.cyan.bold(`${rule.id}`));
976
+ console.log(` ${rule.name} ${verified}`);
977
+ console.log(chalk.gray(` ${rule.description}`));
978
+ console.log(` ${rating} ${rule.rating.toFixed(1)} (${rule.reviews} reviews) | ${rule.downloads} downloads`);
979
+ console.log(chalk.gray(` Author: ${rule.author.name}${rule.author.organization ? ` (${rule.author.organization})` : ''}`));
980
+ console.log(chalk.gray(` Framework: ${rule.framework} | Severity: ${rule.severity} | Version: ${rule.version}`));
981
+ if (rule.jurisdiction) {
982
+ console.log(chalk.gray(` Jurisdiction: ${rule.jurisdiction}`));
983
+ }
984
+ if (rule.payer) {
985
+ console.log(chalk.gray(` Payer: ${rule.payer}`));
986
+ }
987
+ console.log(chalk.gray(` Tags: ${rule.tags.join(', ')}`));
988
+ console.log('');
989
+ }
990
+ console.log(chalk.gray(`\nShowing ${result.rules.length} of ${result.total} results`));
991
+ console.log(chalk.cyan('\nInstall a rule:') + chalk.white(' vlayer marketplace install <rule-id>'));
992
+ }
993
+ catch (error) {
994
+ spinner.fail('Search failed');
995
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
996
+ process.exit(1);
997
+ }
998
+ });
999
+ marketplaceCommand
1000
+ .command('install')
1001
+ .description('Install a rule from the marketplace')
1002
+ .argument('<rule-id>', 'Rule ID to install')
1003
+ .option('--version <version>', 'Specific version to install')
1004
+ .option('--package', 'Install as a package (collection of rules)')
1005
+ .action(async (ruleId, options) => {
1006
+ const spinner = ora(`Installing ${ruleId}...`).start();
1007
+ try {
1008
+ const { RulesInstaller } = await import('./marketplace/index.js');
1009
+ const installer = new RulesInstaller(process.cwd());
1010
+ if (options.package) {
1011
+ const installed = await installer.installPackage(ruleId);
1012
+ spinner.succeed(`Installed package with ${installed.length} rule(s)`);
1013
+ console.log(chalk.green('\nโœ“ Package installed successfully!'));
1014
+ console.log(chalk.gray(`\nInstalled rules:`));
1015
+ installed.forEach(r => console.log(chalk.gray(` โ€ข ${r.id} (v${r.version})`)));
1016
+ }
1017
+ else {
1018
+ const installed = await installer.install(ruleId, options.version);
1019
+ spinner.succeed('Rule installed');
1020
+ console.log(chalk.green('\nโœ“ Rule installed successfully!'));
1021
+ console.log(chalk.gray(`\nRule: ${installed.id}`));
1022
+ console.log(chalk.gray(`Version: ${installed.version}`));
1023
+ console.log(chalk.gray(`Location: .vlayer/marketplace/${installed.id}.yaml`));
1024
+ }
1025
+ console.log(chalk.cyan('\nRun a scan:') + chalk.white(' vlayer scan .'));
1026
+ }
1027
+ catch (error) {
1028
+ spinner.fail('Installation failed');
1029
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
1030
+ process.exit(1);
1031
+ }
1032
+ });
1033
+ marketplaceCommand
1034
+ .command('list')
1035
+ .description('List installed marketplace rules')
1036
+ .option('--enabled', 'Show only enabled rules')
1037
+ .option('--disabled', 'Show only disabled rules')
1038
+ .action(async (options) => {
1039
+ try {
1040
+ const { RulesInstaller } = await import('./marketplace/index.js');
1041
+ const installer = new RulesInstaller(process.cwd());
1042
+ let installed = await installer.listInstalled();
1043
+ if (options.enabled) {
1044
+ installed = installed.filter(r => r.enabled);
1045
+ }
1046
+ else if (options.disabled) {
1047
+ installed = installed.filter(r => !r.enabled);
1048
+ }
1049
+ if (installed.length === 0) {
1050
+ console.log(chalk.yellow('No marketplace rules installed.'));
1051
+ console.log(chalk.gray('\nSearch for rules:') + chalk.white(' vlayer marketplace search'));
1052
+ return;
1053
+ }
1054
+ console.log(chalk.bold(`\n๐Ÿ“ฆ Installed Rules (${installed.length}):\n`));
1055
+ for (const rule of installed) {
1056
+ const status = rule.enabled ? chalk.green('โœ“ Enabled') : chalk.gray('โ—‹ Disabled');
1057
+ const source = rule.source === 'marketplace' ? chalk.blue('[Marketplace]') : chalk.gray('[Local]');
1058
+ console.log(`${status} ${chalk.cyan(rule.id)} ${source}`);
1059
+ console.log(chalk.gray(` Version: ${rule.version} | Installed: ${new Date(rule.installedAt).toLocaleDateString()}`));
1060
+ console.log('');
1061
+ }
1062
+ console.log(chalk.gray('Update all:') + chalk.white(' vlayer marketplace update'));
1063
+ }
1064
+ catch (error) {
1065
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
1066
+ process.exit(1);
1067
+ }
1068
+ });
1069
+ marketplaceCommand
1070
+ .command('update')
1071
+ .description('Update installed rules to latest versions')
1072
+ .option('--dry-run', 'Show what would be updated without installing')
1073
+ .action(async (options) => {
1074
+ const spinner = ora('Checking for updates...').start();
1075
+ try {
1076
+ const { RulesInstaller } = await import('./marketplace/index.js');
1077
+ const installer = new RulesInstaller(process.cwd());
1078
+ const result = await installer.updateAll();
1079
+ spinner.succeed('Update check complete');
1080
+ if (result.updated.length === 0 && result.failed.length === 0) {
1081
+ console.log(chalk.green('\nโœ“ All rules are up to date!'));
1082
+ return;
1083
+ }
1084
+ if (result.updated.length > 0) {
1085
+ console.log(chalk.green(`\nโœ“ Updated ${result.updated.length} rule(s):`));
1086
+ result.updated.forEach(id => console.log(chalk.gray(` โ€ข ${id}`)));
1087
+ }
1088
+ if (result.failed.length > 0) {
1089
+ console.log(chalk.red(`\nโœ— Failed to update ${result.failed.length} rule(s):`));
1090
+ result.failed.forEach(id => console.log(chalk.gray(` โ€ข ${id}`)));
1091
+ }
1092
+ }
1093
+ catch (error) {
1094
+ spinner.fail('Update failed');
1095
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
1096
+ process.exit(1);
1097
+ }
1098
+ });
1099
+ marketplaceCommand
1100
+ .command('uninstall')
1101
+ .description('Uninstall a marketplace rule')
1102
+ .argument('<rule-id>', 'Rule ID to uninstall')
1103
+ .action(async (ruleId) => {
1104
+ const spinner = ora(`Uninstalling ${ruleId}...`).start();
1105
+ try {
1106
+ const { RulesInstaller } = await import('./marketplace/index.js');
1107
+ const installer = new RulesInstaller(process.cwd());
1108
+ await installer.uninstall(ruleId);
1109
+ spinner.succeed('Rule uninstalled');
1110
+ console.log(chalk.green(`\nโœ“ ${ruleId} has been removed.`));
1111
+ }
1112
+ catch (error) {
1113
+ spinner.fail('Uninstall failed');
1114
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
1115
+ process.exit(1);
1116
+ }
1117
+ });
1118
+ marketplaceCommand
1119
+ .command('featured')
1120
+ .description('Show featured/popular rules')
1121
+ .option('--limit <number>', 'Number of rules to show', '10')
1122
+ .action(async (options) => {
1123
+ const spinner = ora('Loading featured rules...').start();
1124
+ try {
1125
+ const { MarketplaceRegistry } = await import('./marketplace/index.js');
1126
+ const registry = new MarketplaceRegistry();
1127
+ const featured = await registry.getFeatured(parseInt(options.limit));
1128
+ spinner.succeed(`Found ${featured.length} featured rule(s)`);
1129
+ console.log(chalk.bold('\nโญ Featured Rules:\n'));
1130
+ for (const rule of featured) {
1131
+ const rating = 'โญ'.repeat(Math.round(rule.rating));
1132
+ console.log(chalk.cyan.bold(`${rule.id}`));
1133
+ console.log(` ${rule.name} ${chalk.green('โœ“')}`);
1134
+ console.log(chalk.gray(` ${rule.description}`));
1135
+ console.log(` ${rating} ${rule.rating.toFixed(1)} | ${rule.downloads} downloads`);
1136
+ console.log(chalk.gray(` ${rule.framework} โ€ข ${rule.category} โ€ข ${rule.severity}`));
1137
+ console.log('');
1138
+ }
1139
+ console.log(chalk.cyan('\nInstall:') + chalk.white(' vlayer marketplace install <rule-id>'));
1140
+ }
1141
+ catch (error) {
1142
+ spinner.fail('Failed to load featured rules');
1143
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
1144
+ process.exit(1);
1145
+ }
1146
+ });
1147
+ marketplaceCommand
1148
+ .command('stats')
1149
+ .description('Show marketplace statistics')
1150
+ .action(async () => {
1151
+ const spinner = ora('Loading marketplace stats...').start();
1152
+ try {
1153
+ const { MarketplaceRegistry } = await import('./marketplace/index.js');
1154
+ const registry = new MarketplaceRegistry();
1155
+ const metadata = await registry.getMetadata();
1156
+ spinner.succeed('Stats loaded');
1157
+ console.log(chalk.bold('\n๐Ÿ“Š Marketplace Statistics:\n'));
1158
+ console.log(`Total Rules: ${chalk.cyan(metadata.totalRules)}`);
1159
+ console.log(`Total Packages: ${chalk.cyan(metadata.totalPackages)}`);
1160
+ console.log('');
1161
+ console.log(chalk.bold('Rules by Framework:'));
1162
+ Object.entries(metadata.frameworks)
1163
+ .sort(([, a], [, b]) => b - a)
1164
+ .forEach(([framework, count]) => {
1165
+ console.log(` ${framework}: ${chalk.cyan(count)}`);
1166
+ });
1167
+ console.log('');
1168
+ console.log(chalk.bold('Rules by Category:'));
1169
+ Object.entries(metadata.categories)
1170
+ .sort(([, a], [, b]) => b - a)
1171
+ .forEach(([category, count]) => {
1172
+ console.log(` ${category}: ${chalk.cyan(count)}`);
1173
+ });
1174
+ console.log('');
1175
+ console.log(chalk.bold('Top Contributors:'));
1176
+ metadata.topContributors.slice(0, 5).forEach((contributor, i) => {
1177
+ console.log(` ${i + 1}. ${contributor.name}`);
1178
+ console.log(chalk.gray(` ${contributor.rulesPublished} rules | ${contributor.downloads} downloads`));
1179
+ });
1180
+ console.log('');
1181
+ console.log(chalk.gray('Search rules:') + chalk.white(' vlayer marketplace search'));
1182
+ }
1183
+ catch (error) {
1184
+ spinner.fail('Failed to load stats');
1185
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
1186
+ process.exit(1);
1187
+ }
1188
+ });
1189
+ // Templates commands
1190
+ const templatesCommand = program
1191
+ .command('templates')
1192
+ .description('Export compliance document templates');
1193
+ templatesCommand
1194
+ .command('export')
1195
+ .description('Export a compliance document template')
1196
+ .argument('<template>', 'Template name: baa, physical, irp, npp, security-officer')
1197
+ .option('-o, --output <path>', 'Output file path')
1198
+ .action(async (template, options) => {
1199
+ try {
1200
+ const validTemplates = ['baa', 'physical', 'irp', 'npp', 'security-officer'];
1201
+ if (!validTemplates.includes(template)) {
1202
+ console.error(chalk.red(`Unknown template: ${template}`));
1203
+ console.log(chalk.yellow('\nAvailable templates:'));
1204
+ console.log(chalk.gray(' โ€ข baa - Business Associate Agreement Verification Letter'));
1205
+ console.log(chalk.gray(' โ€ข physical - Physical Safeguards Checklist'));
1206
+ console.log(chalk.gray(' โ€ข irp - Incident Response Plan'));
1207
+ console.log(chalk.gray(' โ€ข npp - Notice of Privacy Practices'));
1208
+ console.log(chalk.gray(' โ€ข security-officer - Security Officer Designation'));
1209
+ process.exit(1);
1210
+ }
1211
+ // Read template file
1212
+ const { readFile: fsReadFile } = await import('fs/promises');
1213
+ const { fileURLToPath } = await import('url');
1214
+ const { dirname, join } = await import('path');
1215
+ // Get the directory of the current module
1216
+ const __filename = fileURLToPath(import.meta.url);
1217
+ const __dirname = dirname(__filename);
1218
+ // Map template names to file names
1219
+ const templateFiles = {
1220
+ 'baa': 'baa-verification-letter.md',
1221
+ 'physical': 'physical-safeguards-checklist.md',
1222
+ 'irp': 'irp.md',
1223
+ 'npp': 'notice-of-privacy-practices.md',
1224
+ 'security-officer': 'security-officer-designation.md',
1225
+ };
1226
+ const templateFileName = templateFiles[template];
1227
+ const templatePath = join(__dirname, '..', 'templates', templateFileName);
1228
+ const templateContent = await fsReadFile(templatePath, 'utf-8');
1229
+ // Determine output path
1230
+ const outputPath = options.output || `./${templateFileName}`;
1231
+ // Write template to output location
1232
+ await writeFile(outputPath, templateContent, 'utf-8');
1233
+ console.log(chalk.green(`\nโœ“ Template exported successfully!`));
1234
+ console.log(chalk.cyan(`\nFile: ${outputPath}`));
1235
+ // Provide template-specific instructions
1236
+ if (template === 'baa') {
1237
+ console.log(chalk.gray('\nNext steps:'));
1238
+ console.log(chalk.gray(' 1. Open the file and fill in the bracketed placeholders:'));
1239
+ console.log(chalk.gray(' [BA COMPANY NAME], [DATE], [SCORE], etc.'));
1240
+ console.log(chalk.gray(' 2. Check all applicable boxes (โ˜ โ†’ โ˜‘)'));
1241
+ console.log(chalk.gray(' 3. Attach your vlayer compliance report as Exhibit A'));
1242
+ console.log(chalk.gray(' 4. Have both parties sign and date'));
1243
+ console.log(chalk.gray(' 5. Retain for 6 years per HIPAA requirements\n'));
1244
+ }
1245
+ else if (template === 'physical') {
1246
+ console.log(chalk.gray('\nNext steps:'));
1247
+ console.log(chalk.gray(' 1. Review Section A (Remote/Cloud) - applies to ALL organizations'));
1248
+ console.log(chalk.gray(' 2. Review Section B (Physical Office) - only if you have a physical location'));
1249
+ console.log(chalk.gray(' 3. Check all applicable boxes (โ˜ โ†’ โ˜‘)'));
1250
+ console.log(chalk.gray(' 4. Fill in verification details and dates'));
1251
+ console.log(chalk.gray(' 5. Complete remediation plan for non-compliant items'));
1252
+ console.log(chalk.gray(' 6. Sign and date the attestation'));
1253
+ console.log(chalk.gray(' 7. Retain for 6 years per HIPAA requirements\n'));
1254
+ }
1255
+ else if (template === 'irp') {
1256
+ console.log(chalk.gray('\nNext steps:'));
1257
+ console.log(chalk.gray(' 1. Fill in organization and IRT contact information'));
1258
+ console.log(chalk.gray(' 2. Customize severity levels and response times for your organization'));
1259
+ console.log(chalk.gray(' 3. Update external contact information (HHS, FBI, insurance, etc.)'));
1260
+ console.log(chalk.gray(' 4. Review and customize incident response procedures'));
1261
+ console.log(chalk.gray(' 5. Obtain executive approval and signatures'));
1262
+ console.log(chalk.gray(' 6. Distribute to all IRT members'));
1263
+ console.log(chalk.gray(' 7. Schedule quarterly drills and annual updates\n'));
1264
+ }
1265
+ else if (template === 'npp') {
1266
+ console.log(chalk.gray('\nNext steps:'));
1267
+ console.log(chalk.gray(' 1. Replace all [PLACEHOLDERS] with your organization\'s information'));
1268
+ console.log(chalk.gray(' 2. Review 2024 updates: 15-day timeline, in-person inspection rights'));
1269
+ console.log(chalk.gray(' 3. Customize uses and disclosures to match your practices'));
1270
+ console.log(chalk.gray(' 4. Review with healthcare attorney for state-specific requirements'));
1271
+ console.log(chalk.gray(' 5. Have Privacy Officer approve the final version'));
1272
+ console.log(chalk.gray(' 6. Post prominently in your facility and on website'));
1273
+ console.log(chalk.gray(' 7. Provide to all patients and obtain signed acknowledgment\n'));
1274
+ }
1275
+ else if (template === 'security-officer') {
1276
+ console.log(chalk.gray('\nNext steps:'));
1277
+ console.log(chalk.gray(' 1. Fill in Security Officer name and contact information'));
1278
+ console.log(chalk.gray(' 2. Customize responsibilities and authorities for your organization'));
1279
+ console.log(chalk.gray(' 3. Define budget, resources, and staff allocation'));
1280
+ console.log(chalk.gray(' 4. Identify backup/interim Security Officers'));
1281
+ console.log(chalk.gray(' 5. Obtain executive signature (CEO/COO)'));
1282
+ console.log(chalk.gray(' 6. Obtain Security Officer signature accepting designation'));
1283
+ console.log(chalk.gray(' 7. Distribute to stakeholders and HR\n'));
1284
+ }
1285
+ }
1286
+ catch (error) {
1287
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
1288
+ process.exit(1);
1289
+ }
1290
+ });
1291
+ templatesCommand
1292
+ .command('list')
1293
+ .description('List available templates')
1294
+ .action(() => {
1295
+ console.log(chalk.bold('\n๐Ÿ“„ Available Compliance Templates:\n'));
1296
+ console.log(chalk.cyan('baa') + chalk.gray(' - Business Associate Agreement Verification Letter'));
1297
+ console.log(chalk.gray(' Annual security verification letter for HIPAA Business Associates'));
1298
+ console.log(chalk.gray(' Includes technical safeguards checklist and compliance report attachment'));
1299
+ console.log(chalk.gray(' Usage: vlayer templates export baa\n'));
1300
+ console.log(chalk.cyan('physical') + chalk.gray(' - Physical Safeguards Checklist'));
1301
+ console.log(chalk.gray(' HIPAA Physical Safeguards compliance checklist (ยง164.310)'));
1302
+ console.log(chalk.gray(' Section A: Remote/Cloud setup | Section B: Physical office controls'));
1303
+ console.log(chalk.gray(' Usage: vlayer templates export physical\n'));
1304
+ console.log(chalk.cyan('irp') + chalk.gray(' - Incident Response Plan'));
1305
+ console.log(chalk.gray(' Complete incident response procedures and breach notification guidelines'));
1306
+ console.log(chalk.gray(' Includes IRT roles, severity levels, 5-phase response, and HIPAA timeline'));
1307
+ console.log(chalk.gray(' Usage: vlayer templates export irp\n'));
1308
+ console.log(chalk.cyan('npp') + chalk.gray(' - Notice of Privacy Practices'));
1309
+ console.log(chalk.gray(' HIPAA ยง164.520 compliant patient notice (includes 2024 updates)'));
1310
+ console.log(chalk.gray(' 15-day access timeline, in-person inspection rights, electronic copy'));
1311
+ console.log(chalk.gray(' Usage: vlayer templates export npp\n'));
1312
+ console.log(chalk.cyan('security-officer') + chalk.gray(' - Security Officer Designation'));
1313
+ console.log(chalk.gray(' Official HIPAA ยง164.308(a)(2) required designation document'));
1314
+ console.log(chalk.gray(' Defines responsibilities, authorities, reporting structure, and signatures'));
1315
+ console.log(chalk.gray(' Usage: vlayer templates export security-officer\n'));
1316
+ });
1317
+ program
1318
+ .command('history')
1319
+ .description('Show scan history with compliance scores over time')
1320
+ .argument('[path]', 'Path to the project', '.')
1321
+ .option('-l, --limit <number>', 'Maximum number of scans to show', '10')
1322
+ .action(async (path, options) => {
1323
+ const absolutePath = resolve(path);
1324
+ try {
1325
+ const { getAllScans, formatHistoryDate } = await import('./utils/scan-history.js');
1326
+ const scans = await getAllScans(absolutePath);
1327
+ if (scans.length === 0) {
1328
+ console.log(chalk.yellow('\nNo scan history found.'));
1329
+ console.log(chalk.gray('Run `vlayer scan .` to create your first scan history entry.\n'));
1330
+ return;
1331
+ }
1332
+ const limit = parseInt(options.limit);
1333
+ const displayScans = scans.slice(0, limit);
1334
+ console.log(chalk.bold('\n๐Ÿ“Š Scan History\n'));
1335
+ console.log(chalk.gray(`Showing ${displayScans.length} of ${scans.length} scan(s)\n`));
1336
+ // Find max and min scores for context
1337
+ const scores = scans.map(s => s.complianceScore);
1338
+ const maxScore = Math.max(...scores);
1339
+ const minScore = Math.min(...scores);
1340
+ for (let i = 0; i < displayScans.length; i++) {
1341
+ const scan = displayScans[i];
1342
+ const prevScan = i < scans.length - 1 ? scans[i + 1] : null;
1343
+ const scoreChange = prevScan ? scan.complianceScore - prevScan.complianceScore : 0;
1344
+ const scoreColor = scan.complianceScore >= 90 ? chalk.green
1345
+ : scan.complianceScore >= 70 ? chalk.yellow
1346
+ : chalk.red;
1347
+ const changeArrow = scoreChange > 0 ? chalk.green('โ†‘')
1348
+ : scoreChange < 0 ? chalk.red('โ†“')
1349
+ : chalk.gray('โ†’');
1350
+ const changeStr = prevScan
1351
+ ? ` (${scoreChange >= 0 ? '+' : ''}${scoreChange}) ${changeArrow}`
1352
+ : '';
1353
+ const date = formatHistoryDate(scan.date);
1354
+ const isBest = scan.complianceScore === maxScore;
1355
+ const isWorst = scan.complianceScore === minScore;
1356
+ console.log(chalk.cyan(date) +
1357
+ ' ' +
1358
+ scoreColor.bold(`${scan.complianceScore}/100`) +
1359
+ changeStr +
1360
+ (isBest ? chalk.green.bold(' ๐Ÿ† Best') : '') +
1361
+ (isWorst ? chalk.red(' โš ๏ธ Lowest') : ''));
1362
+ // Show severity breakdown
1363
+ const severityStr = [
1364
+ scan.severity.critical > 0 ? chalk.red(`${scan.severity.critical}C`) : null,
1365
+ scan.severity.high > 0 ? chalk.yellow(`${scan.severity.high}H`) : null,
1366
+ scan.severity.medium > 0 ? chalk.hex('#ca8a04')(`${scan.severity.medium}M`) : null,
1367
+ scan.severity.low > 0 ? chalk.blue(`${scan.severity.low}L`) : null,
1368
+ ].filter(Boolean).join(' ');
1369
+ if (severityStr) {
1370
+ console.log(chalk.gray(` Issues: ${severityStr} โ€ข Files: ${scan.totalFilesScanned}`));
1371
+ }
1372
+ else {
1373
+ console.log(chalk.gray(` No active issues โ€ข Files: ${scan.totalFilesScanned}`));
1374
+ }
1375
+ console.log('');
1376
+ }
1377
+ if (scans.length > limit) {
1378
+ console.log(chalk.gray(`Use --limit ${scans.length} to see all scans\n`));
1379
+ }
1380
+ // Show statistics
1381
+ const avgScore = Math.round(scores.reduce((a, b) => a + b, 0) / scores.length);
1382
+ const trend = scans.length > 1
1383
+ ? scans[0].complianceScore - scans[scans.length - 1].complianceScore
1384
+ : 0;
1385
+ console.log(chalk.bold('Statistics:'));
1386
+ console.log(` Total scans: ${scans.length}`);
1387
+ console.log(` Average score: ${avgScore}/100`);
1388
+ console.log(` Best score: ${chalk.green(maxScore)}/100`);
1389
+ console.log(` Lowest score: ${chalk.red(minScore)}/100`);
1390
+ if (trend !== 0) {
1391
+ const trendColor = trend > 0 ? chalk.green : chalk.red;
1392
+ const trendArrow = trend > 0 ? 'โ†‘' : 'โ†“';
1393
+ console.log(` Overall trend: ${trendColor(`${trend >= 0 ? '+' : ''}${trend} ${trendArrow}`)}`);
1394
+ }
1395
+ console.log('');
1396
+ }
1397
+ catch (error) {
1398
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
1399
+ process.exit(1);
1400
+ }
1401
+ });
1402
+ program
1403
+ .command('training')
1404
+ .description('Interactive HIPAA security training for developers')
1405
+ .option('--status', 'Show training completion status')
1406
+ .action(async (options) => {
1407
+ try {
1408
+ const { runTraining, showTrainingStatus } = await import('./training/index.js');
1409
+ if (options.status) {
1410
+ await showTrainingStatus();
1411
+ }
1412
+ else {
1413
+ await runTraining();
1414
+ }
1415
+ }
1416
+ catch (error) {
1417
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
1418
+ process.exit(1);
1419
+ }
1420
+ });
879
1421
  program.parse();
880
1422
  //# sourceMappingURL=cli.js.map