hackmyagent 0.16.4 → 0.16.7

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 (69) hide show
  1. package/dist/.integrity-manifest.json +1 -1
  2. package/dist/arp/crypto/hybrid-signing.d.ts +107 -0
  3. package/dist/arp/crypto/hybrid-signing.d.ts.map +1 -0
  4. package/dist/arp/crypto/hybrid-signing.js +321 -0
  5. package/dist/arp/crypto/hybrid-signing.js.map +1 -0
  6. package/dist/arp/crypto/index.d.ts +13 -0
  7. package/dist/arp/crypto/index.d.ts.map +1 -0
  8. package/dist/arp/crypto/index.js +33 -0
  9. package/dist/arp/crypto/index.js.map +1 -0
  10. package/dist/arp/crypto/manifest-loader.d.ts +117 -0
  11. package/dist/arp/crypto/manifest-loader.d.ts.map +1 -0
  12. package/dist/arp/crypto/manifest-loader.js +361 -0
  13. package/dist/arp/crypto/manifest-loader.js.map +1 -0
  14. package/dist/arp/crypto/types.d.ts +69 -0
  15. package/dist/arp/crypto/types.d.ts.map +1 -0
  16. package/dist/arp/crypto/types.js +11 -0
  17. package/dist/arp/crypto/types.js.map +1 -0
  18. package/dist/arp/index.d.ts +27 -0
  19. package/dist/arp/index.d.ts.map +1 -1
  20. package/dist/arp/index.js +94 -1
  21. package/dist/arp/index.js.map +1 -1
  22. package/dist/arp/intelligence/behavioral-risk-server.d.ts +82 -0
  23. package/dist/arp/intelligence/behavioral-risk-server.d.ts.map +1 -0
  24. package/dist/arp/intelligence/behavioral-risk-server.js +258 -0
  25. package/dist/arp/intelligence/behavioral-risk-server.js.map +1 -0
  26. package/dist/arp/intelligence/behavioral-risk.d.ts +217 -0
  27. package/dist/arp/intelligence/behavioral-risk.d.ts.map +1 -0
  28. package/dist/arp/intelligence/behavioral-risk.js +429 -0
  29. package/dist/arp/intelligence/behavioral-risk.js.map +1 -0
  30. package/dist/arp/intelligence/coordinator.d.ts +93 -2
  31. package/dist/arp/intelligence/coordinator.d.ts.map +1 -1
  32. package/dist/arp/intelligence/coordinator.js +281 -1
  33. package/dist/arp/intelligence/coordinator.js.map +1 -1
  34. package/dist/arp/intelligence/guard-anomaly.d.ts +349 -0
  35. package/dist/arp/intelligence/guard-anomaly.d.ts.map +1 -0
  36. package/dist/arp/intelligence/guard-anomaly.js +399 -0
  37. package/dist/arp/intelligence/guard-anomaly.js.map +1 -0
  38. package/dist/arp/intelligence/nanomind-l1.d.ts +37 -0
  39. package/dist/arp/intelligence/nanomind-l1.d.ts.map +1 -1
  40. package/dist/arp/intelligence/nanomind-l1.js +78 -0
  41. package/dist/arp/intelligence/nanomind-l1.js.map +1 -1
  42. package/dist/arp/intelligence/verify-classification.d.ts +124 -0
  43. package/dist/arp/intelligence/verify-classification.d.ts.map +1 -0
  44. package/dist/arp/intelligence/verify-classification.js +329 -0
  45. package/dist/arp/intelligence/verify-classification.js.map +1 -0
  46. package/dist/arp/proxy/server.d.ts +38 -8
  47. package/dist/arp/proxy/server.d.ts.map +1 -1
  48. package/dist/arp/proxy/server.js +89 -0
  49. package/dist/arp/proxy/server.js.map +1 -1
  50. package/dist/arp/types.d.ts +228 -1
  51. package/dist/arp/types.d.ts.map +1 -1
  52. package/dist/cli.js +152 -31
  53. package/dist/cli.js.map +1 -1
  54. package/dist/lifecycle/assembly-scanner.js +3 -3
  55. package/dist/lifecycle/assembly-scanner.js.map +1 -1
  56. package/dist/nanomind-core/compiler/semantic-compiler.d.ts.map +1 -1
  57. package/dist/nanomind-core/compiler/semantic-compiler.js +170 -10
  58. package/dist/nanomind-core/compiler/semantic-compiler.js.map +1 -1
  59. package/dist/nanomind-core/compiler/source-code-preprocessor.d.ts +64 -0
  60. package/dist/nanomind-core/compiler/source-code-preprocessor.d.ts.map +1 -0
  61. package/dist/nanomind-core/compiler/source-code-preprocessor.js +656 -0
  62. package/dist/nanomind-core/compiler/source-code-preprocessor.js.map +1 -0
  63. package/dist/nanomind-core/ingestion/artifact-parser.d.ts.map +1 -1
  64. package/dist/nanomind-core/ingestion/artifact-parser.js +15 -6
  65. package/dist/nanomind-core/ingestion/artifact-parser.js.map +1 -1
  66. package/dist/scanner/external-scanner.d.ts.map +1 -1
  67. package/dist/scanner/external-scanner.js +25 -1
  68. package/dist/scanner/external-scanner.js.map +1 -1
  69. package/package.json +3 -1
package/dist/cli.js CHANGED
@@ -46,7 +46,9 @@ const program = new commander_1.Command();
46
46
  program.showHelpAfterError('(run with --help for usage)');
47
47
  // Total security check count across all scanner modules.
48
48
  // Update when adding new checks (verify with: grep -r "checkId:" src/hardening/ | grep -o "checkId: '[^']*'" | sort -u | wc -l)
49
- const CHECK_COUNT = 208;
49
+ const CHECK_COUNT = 209;
50
+ // How long registry-cached scan data is considered fresh before `check` re-scans.
51
+ const STALE_SCAN_DAYS = 3;
50
52
  // Write a string to stdout synchronously with retry for pipe backpressure.
51
53
  // process.stdout.write() is async and gets truncated when process.exit()
52
54
  // runs before the stream flushes. fs.writeFileSync(1, ...) can fail with
@@ -171,17 +173,21 @@ program
171
173
  .description(`Check if a package, repo, or skill is safe
172
174
 
173
175
  Accepts npm packages, GitHub repos, local paths, or skill identifiers:
174
- • npm package: downloads and runs full security analysis (${CHECK_COUNT} checks + NanoMind)
175
- GitHub repo: shallow clones and runs full security analysis
176
+ • npm package: queries the registry; downloads + scans (${CHECK_COUNT} checks + NanoMind) if data is missing or stale (>${STALE_SCAN_DAYS}d)
177
+ PyPI package: downloads + scans (${CHECK_COUNT} checks + NanoMind)
178
+ • GitHub repo: queries the registry; shallow clones + scans if data is missing or stale (>${STALE_SCAN_DAYS}d)
176
179
  • Local path: runs NanoMind semantic analysis
177
180
  • Skill identifier: verifies publisher, permissions, revocation
178
181
 
182
+ Use --rescan to skip the registry cache and force a fresh local scan.
183
+
179
184
  Risk levels: low, medium, high, critical
180
185
  Exit code 1 if high/critical risk detected.
181
186
 
182
187
  Examples:
183
188
  $ hackmyagent check express
184
189
  $ hackmyagent check @modelcontextprotocol/server-filesystem
190
+ $ hackmyagent check @sentry/mcp-server --rescan
185
191
  $ hackmyagent check modelcontextprotocol/servers
186
192
  $ hackmyagent check https://github.com/punkpeye/awesome-mcp-servers
187
193
  $ hackmyagent check ./my-agent/
@@ -195,6 +201,7 @@ Examples:
195
201
  .option('-v, --verbose', 'Show detailed verification info')
196
202
  .option('--json', 'Output as JSON (for scripting/CI)')
197
203
  .option('--offline', 'Skip DNS verification (offline mode)')
204
+ .option('--rescan', 'Force a fresh local scan, bypassing cached registry data')
198
205
  .action(async (skill, options) => {
199
206
  try {
200
207
  // Detect local file/directory paths - run NanoMind scan instead of registry lookup
@@ -245,6 +252,7 @@ Examples:
245
252
  console.log(`\n ... and ${issues.length - 10} more`);
246
253
  console.log();
247
254
  }
255
+ printCheckNextSteps(resolved);
248
256
  if (risk === 'critical' || risk === 'high')
249
257
  process.exit(1);
250
258
  return;
@@ -269,12 +277,17 @@ Examples:
269
277
  await checkNpmPackage(skill, options);
270
278
  return;
271
279
  }
280
+ // --rescan only applies to targets that otherwise hit the registry cache.
281
+ // For skill identifiers we fall through to the registry lookup below.
282
+ if (options.rescan && !options.json) {
283
+ console.error(`Note: --rescan has no effect on skill identifiers; it applies to npm/PyPI/GitHub targets.`);
284
+ }
272
285
  // Registry lookup path (non-local identifier) with 10s timeout
273
286
  const checkPromise = (0, index_1.checkSkill)(skill, {
274
287
  skipDnsVerification: options.offline,
275
288
  });
276
289
  const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Timed out verifying "${skill}" (10s). The publisher may not exist or DNS is unreachable.\n` +
277
- `Try: ${CLI_PREFIX.replace(' scan', '')} check ${skill} --offline`)), 10000));
290
+ `Try: ${getCheckCommand()} ${skill} --offline`)), 10000));
278
291
  const result = await Promise.race([checkPromise, timeoutPromise]);
279
292
  if (options.json) {
280
293
  writeJsonStdout(result);
@@ -2869,7 +2882,7 @@ Examples:
2869
2882
  const customPorts = options.ports
2870
2883
  ? options.ports.split(',').map((p) => parseInt(p.trim(), 10))
2871
2884
  : undefined;
2872
- const portCount = customPorts?.length ?? 11;
2885
+ const portCount = customPorts?.length ?? 5;
2873
2886
  if (!options.json) {
2874
2887
  console.log(`\nScanning ${target} (${portCount} ports, ${timeoutMs}ms timeout)...\n`);
2875
2888
  }
@@ -5300,9 +5313,46 @@ program
5300
5313
  console.log(`${checkId}: ${explanation}`);
5301
5314
  }
5302
5315
  else {
5303
- console.log(`No explanation available for ${findingId}.`);
5304
- if (!available) {
5305
- console.log(`\nFor dynamic explanations, install NanoMind: npm install -g @nanomind/cli && nanomind-daemon start`);
5316
+ // Fallback: generate explanation from taxonomy metadata
5317
+ const { getAttackClass } = require('./hardening/taxonomy');
5318
+ const attackClass = getAttackClass(checkId);
5319
+ // Map check ID prefixes to human-readable category descriptions
5320
+ const prefixDescriptions = {
5321
+ 'CRED': 'Credential exposure',
5322
+ 'MCP': 'MCP server configuration',
5323
+ 'SKILL': 'Skill package security',
5324
+ 'GOV': 'Governance policy',
5325
+ 'PERM': 'Permission scope',
5326
+ 'SOUL': 'Behavioral governance (SOUL.md)',
5327
+ 'PRIV': 'Privacy and data handling',
5328
+ 'DATA': 'Data protection',
5329
+ 'INJECT': 'Prompt injection defense',
5330
+ 'ATTEST': 'Agent attestation',
5331
+ 'SUPPLY': 'Supply chain security',
5332
+ 'NET': 'Network security',
5333
+ 'GIT': 'Git repository hygiene',
5334
+ 'PROMPT': 'Prompt security',
5335
+ 'NEMO': 'Static analysis pattern',
5336
+ 'LIFECYCLE': 'Prompt assembly lifecycle',
5337
+ 'AST': 'Deep code analysis',
5338
+ 'ENCRYPT': 'Encryption and hashing',
5339
+ 'LOG': 'Logging and audit',
5340
+ 'AUTH': 'Authentication',
5341
+ 'TOOL': 'Tool permission and safety',
5342
+ };
5343
+ const prefix = checkId.split('-')[0];
5344
+ const categoryDesc = prefixDescriptions[prefix];
5345
+ if (attackClass || categoryDesc) {
5346
+ console.log(`${checkId}: ${categoryDesc || 'Security check'}.`);
5347
+ if (attackClass) {
5348
+ console.log(` Attack class: ${attackClass}`);
5349
+ }
5350
+ console.log(`\n Run 'hackmyagent secure --verbose' to see this check in context with fix guidance.`);
5351
+ console.log(` Run 'hackmyagent check-metadata --json' for full check details.`);
5352
+ }
5353
+ else {
5354
+ console.log(`No explanation available for ${findingId}. This may not be a valid check ID.`);
5355
+ console.log(`\nRun 'hackmyagent check-metadata --json' to see all ${CHECK_COUNT} valid check IDs.`);
5306
5356
  }
5307
5357
  }
5308
5358
  });
@@ -5765,7 +5815,6 @@ function parseGitHubTarget(target) {
5765
5815
  };
5766
5816
  }
5767
5817
  const REGISTRY_URL = 'https://api.oa2a.org';
5768
- const STALE_SCAN_DAYS = 3;
5769
5818
  // ============================================================================
5770
5819
  // Scan counter + contribute preference (~/.hackmyagent/config.json)
5771
5820
  // ============================================================================
@@ -5938,6 +5987,57 @@ function displayRegistryResult(data) {
5938
5987
  const days = Math.floor((Date.now() - new Date(data.lastScannedAt).getTime()) / (1000 * 60 * 60 * 24));
5939
5988
  console.log(` Scanned: ${days === 0 ? 'today' : days + ' day(s) ago'}`);
5940
5989
  }
5990
+ printCheckNextSteps(data.name);
5991
+ }
5992
+ /**
5993
+ * Resolve the "run a check" command string for use in user-facing hints.
5994
+ *
5995
+ * Precedence:
5996
+ * 1. HMA_CHECK_COMMAND env var (full command string, e.g. "opena2a check")
5997
+ * 2. `${CLI_PREFIX} check` — sensible default derived from how HMA was
5998
+ * invoked.
5999
+ *
6000
+ * Parent CLIs should set HMA_CHECK_COMMAND when their verb layout differs
6001
+ * from hackmyagent's, rather than trying to encode the full verb into
6002
+ * HMA_CLI_PREFIX (which is treated as a binary-level prefix everywhere else).
6003
+ */
6004
+ function getCheckCommand() {
6005
+ const override = process.env.HMA_CHECK_COMMAND?.trim();
6006
+ if (override)
6007
+ return override;
6008
+ return `${CLI_PREFIX} check`;
6009
+ }
6010
+ /**
6011
+ * Resolve the "full project scan" hint command string.
6012
+ *
6013
+ * Precedence:
6014
+ * 1. HMA_FULL_SCAN_HINT env var (full command string, e.g. "opena2a review")
6015
+ * 2. `${CLI_PREFIX} secure <dir>` — default.
6016
+ */
6017
+ function getFullScanHint() {
6018
+ const override = process.env.HMA_FULL_SCAN_HINT?.trim();
6019
+ if (override)
6020
+ return override;
6021
+ return `${CLI_PREFIX} secure <dir>`;
6022
+ }
6023
+ /**
6024
+ * Print the standard 3-line next-steps footer shown after every `check`
6025
+ * invocation. Lines:
6026
+ * 1. How to force a fresh local scan of *this* target.
6027
+ * 2. How to run the full project scan (respects HMA_FULL_SCAN_HINT so that
6028
+ * sibling CLIs like opena2a can redirect users to their own flagship
6029
+ * command instead of `hackmyagent secure <dir>`).
6030
+ * 3. Discoverability: the other target syntaxes `check` accepts.
6031
+ *
6032
+ * Suppressed in --ci so machine-readable output stays clean.
6033
+ */
6034
+ function printCheckNextSteps(target) {
6035
+ if (globalCiMode)
6036
+ return;
6037
+ console.log();
6038
+ console.log(` ${colors.dim}Run a fresh local scan: ${getCheckCommand()} ${target} --rescan${RESET()}`);
6039
+ console.log(` ${colors.dim}Full project scan: ${getFullScanHint()}${RESET()}`);
6040
+ console.log(` ${colors.dim}Also accepts: pip:<pkg> · <owner>/<repo> · ./<dir> · @publisher/skill${RESET()}`);
5941
6041
  console.log();
5942
6042
  }
5943
6043
  /**
@@ -5947,6 +6047,17 @@ function displayRegistryResult(data) {
5947
6047
  async function suggestSimilarPackages(name) {
5948
6048
  const controller = new AbortController();
5949
6049
  const timeout = setTimeout(() => controller.abort(), 5000);
6050
+ // Simple Levenshtein distance for filtering relevant suggestions
6051
+ function levenshtein(a, b) {
6052
+ const m = a.length, n = b.length;
6053
+ const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_, j) => i === 0 ? j : j === 0 ? i : 0));
6054
+ for (let i = 1; i <= m; i++)
6055
+ for (let j = 1; j <= n; j++)
6056
+ dp[i][j] = a[i - 1] === b[j - 1]
6057
+ ? dp[i - 1][j - 1]
6058
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
6059
+ return dp[m][n];
6060
+ }
5950
6061
  try {
5951
6062
  // Build search queries: the name itself, plus the unscoped name for scoped packages
5952
6063
  const queries = [name];
@@ -5955,11 +6066,9 @@ async function suggestSimilarPackages(name) {
5955
6066
  queries.push(scopeMatch[1]);
5956
6067
  }
5957
6068
  const seen = new Set();
5958
- const suggestions = [];
6069
+ const candidates = [];
5959
6070
  for (const query of queries) {
5960
- if (suggestions.length >= 3)
5961
- break;
5962
- const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=5`;
6071
+ const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=10`;
5963
6072
  const res = await fetch(url, { signal: controller.signal });
5964
6073
  if (!res.ok)
5965
6074
  continue;
@@ -5971,12 +6080,20 @@ async function suggestSimilarPackages(name) {
5971
6080
  if (pkg === name || seen.has(pkg))
5972
6081
  continue;
5973
6082
  seen.add(pkg);
5974
- suggestions.push(pkg);
5975
- if (suggestions.length >= 3)
5976
- break;
6083
+ // Compare unscoped names for better matching
6084
+ const unscopedInput = name.replace(/^@[^/]+\//, '');
6085
+ const unscopedPkg = pkg.replace(/^@[^/]+\//, '');
6086
+ const dist = levenshtein(unscopedInput.toLowerCase(), unscopedPkg.toLowerCase());
6087
+ // Only suggest if reasonably similar (distance < half the input length + 3)
6088
+ const maxDist = Math.floor(unscopedInput.length / 2) + 3;
6089
+ if (dist <= maxDist) {
6090
+ candidates.push({ name: pkg, distance: dist });
6091
+ }
5977
6092
  }
5978
6093
  }
5979
- return suggestions;
6094
+ // Sort by edit distance and return top 3
6095
+ candidates.sort((a, b) => a.distance - b.distance);
6096
+ return candidates.slice(0, 3).map(c => c.name);
5980
6097
  }
5981
6098
  finally {
5982
6099
  clearTimeout(timeout);
@@ -5989,8 +6106,8 @@ async function suggestSimilarPackages(name) {
5989
6106
  async function checkGitHubRepo(target, options) {
5990
6107
  const { org, repo, cloneUrl } = parseGitHubTarget(target);
5991
6108
  const displayName = `${org}/${repo}`;
5992
- // Step 1: Check registry for existing trust data
5993
- if (!options.offline) {
6109
+ // Step 1: Check registry for existing trust data (unless --rescan forces a fresh scan)
6110
+ if (!options.offline && !options.rescan) {
5994
6111
  const registryData = await queryRegistry(displayName);
5995
6112
  if (registryData?.found && !isScanStale(registryData.lastScannedAt)) {
5996
6113
  if (options.json) {
@@ -6007,6 +6124,9 @@ async function checkGitHubRepo(target, options) {
6007
6124
  }
6008
6125
  }
6009
6126
  }
6127
+ else if (options.rescan && !options.json && !globalCiMode) {
6128
+ console.error(`Forcing fresh local scan (--rescan)...`);
6129
+ }
6010
6130
  // Step 2: Clone and scan
6011
6131
  const { mkdtemp, rm } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
6012
6132
  const { tmpdir } = await Promise.resolve().then(() => __importStar(require('node:os')));
@@ -6095,8 +6215,7 @@ async function checkGitHubRepo(target, options) {
6095
6215
  queuePendingScan(displayName, result);
6096
6216
  }
6097
6217
  }
6098
- console.log(`\n Full project scan: ${CLI_PREFIX} secure <dir>`);
6099
- console.log();
6218
+ printCheckNextSteps(displayName);
6100
6219
  if (critical.length > 0 || high.length > 0)
6101
6220
  process.exit(1);
6102
6221
  }
@@ -6110,7 +6229,7 @@ async function checkGitHubRepo(target, options) {
6110
6229
  console.error(`Error: Cloning "${displayName}" timed out (120s). The repo may be too large.`);
6111
6230
  console.error(`\nTry cloning manually and scanning the local path:`);
6112
6231
  console.error(` git clone --depth 1 ${cloneUrl}`);
6113
- console.error(` ${CLI_PREFIX} check ./${repo}/`);
6232
+ console.error(` ${getCheckCommand()} ./${repo}/`);
6114
6233
  }
6115
6234
  else {
6116
6235
  console.error(`Error: ${message}`);
@@ -6239,8 +6358,9 @@ async function checkPyPiPackage(target, options) {
6239
6358
  else {
6240
6359
  console.log(`\n ${colors.green}No security issues found.${RESET()}`);
6241
6360
  }
6242
- console.log(`\n Full project scan: ${CLI_PREFIX} secure <dir>`);
6243
- console.log();
6361
+ // Pass the original target (with pip: / pypi: prefix preserved) so the
6362
+ // rescan hint stays runnable — `hackmyagent check requests` would try npm.
6363
+ printCheckNextSteps(target);
6244
6364
  if (critical.length > 0 || high.length > 0)
6245
6365
  process.exit(1);
6246
6366
  }
@@ -6399,8 +6519,7 @@ async function checkRawUrl(url, options) {
6399
6519
  queuePendingScan(displayName, result);
6400
6520
  }
6401
6521
  }
6402
- console.log(`\n Full project scan: ${CLI_PREFIX} secure <dir>`);
6403
- console.log();
6522
+ printCheckNextSteps(displayName);
6404
6523
  if (critical.length > 0 || high.length > 0)
6405
6524
  process.exit(1);
6406
6525
  }
@@ -6413,7 +6532,7 @@ async function checkRawUrl(url, options) {
6413
6532
  else if (message.includes('timeout') || message.includes('Timeout')) {
6414
6533
  console.error(`Error: Fetching "${url}" timed out. The target may be too large.`);
6415
6534
  console.error(`\nTry downloading manually and scanning the local path:`);
6416
- console.error(` ${CLI_PREFIX} check ./downloaded-dir/`);
6535
+ console.error(` ${getCheckCommand()} ./downloaded-dir/`);
6417
6536
  }
6418
6537
  else {
6419
6538
  console.error(`Error scanning URL: ${message}`);
@@ -6425,8 +6544,8 @@ async function checkRawUrl(url, options) {
6425
6544
  }
6426
6545
  }
6427
6546
  async function checkNpmPackage(name, options) {
6428
- // Step 1: Check registry for existing trust data
6429
- if (!options.offline) {
6547
+ // Step 1: Check registry for existing trust data (unless --rescan forces a fresh scan)
6548
+ if (!options.offline && !options.rescan) {
6430
6549
  const registryData = await queryRegistry(name);
6431
6550
  if (registryData?.found && !isScanStale(registryData.lastScannedAt)) {
6432
6551
  // Fresh data in registry — show it
@@ -6445,6 +6564,9 @@ async function checkNpmPackage(name, options) {
6445
6564
  }
6446
6565
  }
6447
6566
  }
6567
+ else if (options.rescan && !options.json && !globalCiMode) {
6568
+ console.error(`Forcing fresh local scan (--rescan)...`);
6569
+ }
6448
6570
  // Step 2: Download and scan
6449
6571
  const { mkdtemp, rm } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
6450
6572
  const { tmpdir } = await Promise.resolve().then(() => __importStar(require('node:os')));
@@ -6537,8 +6659,7 @@ async function checkNpmPackage(name, options) {
6537
6659
  queuePendingScan(name, result);
6538
6660
  }
6539
6661
  }
6540
- console.log(`\n Full project scan: ${CLI_PREFIX} secure <dir>`);
6541
- console.log();
6662
+ printCheckNextSteps(name);
6542
6663
  if (critical.length > 0 || high.length > 0)
6543
6664
  process.exit(1);
6544
6665
  }