hackmyagent 0.12.9 → 0.13.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.
- package/dist/.integrity-manifest.json +1 -1
- package/dist/checker/skill-identifier.d.ts.map +1 -1
- package/dist/checker/skill-identifier.js +9 -1
- package/dist/checker/skill-identifier.js.map +1 -1
- package/dist/cli.js +202 -28
- package/dist/cli.js.map +1 -1
- package/dist/nanomind-core/inference/tme-classifier.d.ts.map +1 -1
- package/dist/nanomind-core/inference/tme-classifier.js +4 -5
- package/dist/nanomind-core/inference/tme-classifier.js.map +1 -1
- package/dist/nanomind-core/telemetry/auto-update.d.ts +27 -0
- package/dist/nanomind-core/telemetry/auto-update.d.ts.map +1 -0
- package/dist/nanomind-core/telemetry/auto-update.js +129 -0
- package/dist/nanomind-core/telemetry/auto-update.js.map +1 -0
- package/dist/nanomind-core/telemetry/client.d.ts +66 -0
- package/dist/nanomind-core/telemetry/client.d.ts.map +1 -0
- package/dist/nanomind-core/telemetry/client.js +123 -0
- package/dist/nanomind-core/telemetry/client.js.map +1 -0
- package/dist/nanomind-core/telemetry/config.d.ts +33 -0
- package/dist/nanomind-core/telemetry/config.d.ts.map +1 -0
- package/dist/nanomind-core/telemetry/config.js +119 -0
- package/dist/nanomind-core/telemetry/config.js.map +1 -0
- package/dist/nanomind-core/telemetry/index.d.ts +15 -0
- package/dist/nanomind-core/telemetry/index.d.ts.map +1 -0
- package/dist/nanomind-core/telemetry/index.js +27 -0
- package/dist/nanomind-core/telemetry/index.js.map +1 -0
- package/dist/wild/scorer.js +2 -2
- package/dist/wild/scorer.js.map +1 -1
- package/dist/wild/types.d.ts +1 -1
- package/dist/wild/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -45,14 +45,14 @@ const wild_1 = require("./wild");
|
|
|
45
45
|
const nemoclaw_scanner_1 = require("./hardening/nemoclaw-scanner");
|
|
46
46
|
const program = new commander_1.Command();
|
|
47
47
|
program.showHelpAfterError('(run with --help for usage)');
|
|
48
|
-
// Write
|
|
48
|
+
// Write a string to stdout synchronously with retry for pipe backpressure.
|
|
49
49
|
// process.stdout.write() is async and gets truncated when process.exit()
|
|
50
50
|
// runs before the stream flushes. fs.writeFileSync(1, ...) can fail with
|
|
51
51
|
// EAGAIN on non-blocking pipes when the buffer (64KB on macOS) fills up.
|
|
52
52
|
// This function writes in chunks with retry to handle both cases.
|
|
53
|
-
function
|
|
53
|
+
function writeLargeStdout(text) {
|
|
54
54
|
const fs = require('fs');
|
|
55
|
-
const buf = Buffer.from(
|
|
55
|
+
const buf = Buffer.from(text);
|
|
56
56
|
let offset = 0;
|
|
57
57
|
while (offset < buf.length) {
|
|
58
58
|
try {
|
|
@@ -61,13 +61,16 @@ function writeJsonStdout(data) {
|
|
|
61
61
|
}
|
|
62
62
|
catch (e) {
|
|
63
63
|
if (e && typeof e === 'object' && 'code' in e && e.code === 'EAGAIN') {
|
|
64
|
-
// Pipe buffer full
|
|
64
|
+
// Pipe buffer full -- spin-wait briefly then retry
|
|
65
65
|
continue;
|
|
66
66
|
}
|
|
67
67
|
throw e;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
function writeJsonStdout(data) {
|
|
72
|
+
writeLargeStdout(JSON.stringify(data, null, 2) + '\n');
|
|
73
|
+
}
|
|
71
74
|
// Resolve the CLI command name based on how we were invoked.
|
|
72
75
|
// When run via `opena2a scan secure`, use `opena2a scan` prefix.
|
|
73
76
|
// When run directly as `hackmyagent`, use that.
|
|
@@ -96,6 +99,10 @@ function validateRegistryUrl(url) {
|
|
|
96
99
|
}
|
|
97
100
|
return url;
|
|
98
101
|
}
|
|
102
|
+
// Global CI mode flag -- set before parse() by stripping --ci from argv.
|
|
103
|
+
// Commands that already define --ci (secure, scan-soul) use their own opts;
|
|
104
|
+
// all others can check this module-level flag.
|
|
105
|
+
let globalCiMode = false;
|
|
99
106
|
// Check for NO_COLOR env or non-TTY to disable colors by default
|
|
100
107
|
const noColorEnv = process.env.NO_COLOR !== undefined || !process.stdout.isTTY;
|
|
101
108
|
// Color codes - will be cleared if --no-color is passed
|
|
@@ -136,7 +143,7 @@ Examples:
|
|
|
136
143
|
$ hackmyagent secure --fix Fix issues automatically
|
|
137
144
|
$ hackmyagent fix-all Run all security plugins
|
|
138
145
|
$ hackmyagent scan example.com Scan external infrastructure`)
|
|
139
|
-
.version('hackmyagent ' + index_1.VERSION, '-
|
|
146
|
+
.version('hackmyagent ' + index_1.VERSION, '-v, --version', 'Output the version number')
|
|
140
147
|
.option('--no-color', 'Disable colored output (also respects NO_COLOR env)');
|
|
141
148
|
program.addHelpText('beforeAll', `
|
|
142
149
|
Quick start:
|
|
@@ -212,7 +219,7 @@ Examples:
|
|
|
212
219
|
const riskDisplay = RISK_DISPLAY[risk];
|
|
213
220
|
console.log(`\n${riskDisplay.color()}${riskDisplay.symbol} ${risk.toUpperCase()} RISK${RESET()}\n`);
|
|
214
221
|
console.log(`Path: ${resolved}`);
|
|
215
|
-
console.log(`
|
|
222
|
+
console.log(`Semantic analysis: ${nmResult.compiledArtifacts} file(s) analyzed\n`);
|
|
216
223
|
if (issues.length === 0) {
|
|
217
224
|
console.log(`${colors.green}No security issues detected.${RESET()}\n`);
|
|
218
225
|
}
|
|
@@ -232,10 +239,13 @@ Examples:
|
|
|
232
239
|
process.exit(1);
|
|
233
240
|
return;
|
|
234
241
|
}
|
|
235
|
-
// Registry lookup path (non-local identifier)
|
|
236
|
-
const
|
|
242
|
+
// Registry lookup path (non-local identifier) with 10s timeout
|
|
243
|
+
const checkPromise = (0, index_1.checkSkill)(skill, {
|
|
237
244
|
skipDnsVerification: options.offline,
|
|
238
245
|
});
|
|
246
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Timed out verifying "${skill}" (10s). The publisher may not exist or DNS is unreachable.\n` +
|
|
247
|
+
`Try: ${CLI_PREFIX.replace(' scan', '')} check ${skill} --offline`)), 10000));
|
|
248
|
+
const result = await Promise.race([checkPromise, timeoutPromise]);
|
|
239
249
|
if (options.json) {
|
|
240
250
|
writeJsonStdout(result);
|
|
241
251
|
return;
|
|
@@ -1797,8 +1807,8 @@ Examples:
|
|
|
1797
1807
|
.option('-b, --benchmark <name>', 'Run benchmark compliance check (e.g., oasb-1)')
|
|
1798
1808
|
.option('-l, --level <level>', 'Benchmark level: L1 (Essential), L2 (Standard), L3 (Hardened)', 'L1')
|
|
1799
1809
|
.option('-c, --category <name>', 'Filter to specific benchmark category')
|
|
1800
|
-
.option('--deep', 'Maximum analysis: static +
|
|
1801
|
-
.option('--static-only', 'Disable
|
|
1810
|
+
.option('--deep', 'Maximum analysis: static + semantic + behavioral simulation + adaptive attacks (~30s per file)')
|
|
1811
|
+
.option('--static-only', 'Disable semantic analysis and simulation (static checks only, fast, deterministic)')
|
|
1802
1812
|
.option('--scan-depth <depth>', 'CAAT scan depth: quick (config+creds only), standard (default), deep (+ simulation)', 'standard')
|
|
1803
1813
|
.option('--ci-publish', 'Submit scan results to registry CI endpoint (requires CI_SCAN_HMAC_SECRET env)')
|
|
1804
1814
|
.option('--publish', 'Push scan results to the OpenA2A Registry')
|
|
@@ -1898,14 +1908,13 @@ Examples:
|
|
|
1898
1908
|
// Static only -- no extra output
|
|
1899
1909
|
}
|
|
1900
1910
|
else if (nanomindAvailable && isDeep) {
|
|
1901
|
-
console.log(`Analysis: static +
|
|
1911
|
+
console.log(`Analysis: static + semantic + behavioral simulation + adaptive attacks\n`);
|
|
1902
1912
|
}
|
|
1903
1913
|
else if (nanomindAvailable) {
|
|
1904
|
-
console.log(`Analysis: static +
|
|
1914
|
+
console.log(`Analysis: static + semantic (ML-enhanced accuracy)\n`);
|
|
1905
1915
|
}
|
|
1906
1916
|
else if (isDeep) {
|
|
1907
1917
|
console.log(`Analysis: static + behavioral simulation\n`);
|
|
1908
|
-
console.log(` Tip: Install NanoMind for even better results: nanomind-daemon start\n`);
|
|
1909
1918
|
}
|
|
1910
1919
|
// Default static-only: no message needed, it's the baseline
|
|
1911
1920
|
}
|
|
@@ -2068,14 +2077,14 @@ Examples:
|
|
|
2068
2077
|
printBenchmarkReport(benchmarkResult, options.verbose ?? false);
|
|
2069
2078
|
output = '';
|
|
2070
2079
|
}
|
|
2071
|
-
// Write output
|
|
2080
|
+
// Write output (use writeLargeStdout to avoid 64KB pipe truncation)
|
|
2072
2081
|
if (output) {
|
|
2073
2082
|
if (options.output) {
|
|
2074
2083
|
require('fs').writeFileSync(options.output, output);
|
|
2075
2084
|
console.error(`Report written to ${options.output}`);
|
|
2076
2085
|
}
|
|
2077
2086
|
else {
|
|
2078
|
-
|
|
2087
|
+
writeLargeStdout(output + '\n');
|
|
2079
2088
|
}
|
|
2080
2089
|
}
|
|
2081
2090
|
// Check fail threshold
|
|
@@ -2139,7 +2148,7 @@ Examples:
|
|
|
2139
2148
|
console.error(`Report written to ${options.output}`);
|
|
2140
2149
|
}
|
|
2141
2150
|
else {
|
|
2142
|
-
|
|
2151
|
+
writeLargeStdout(output + '\n');
|
|
2143
2152
|
}
|
|
2144
2153
|
const critHigh = result.findings.filter((f) => !f.passed && !f.fixed && (f.severity === 'critical' || f.severity === 'high'));
|
|
2145
2154
|
if (critHigh.length > 0)
|
|
@@ -2298,10 +2307,10 @@ Examples:
|
|
|
2298
2307
|
console.log(`\n ${colors.yellow}${unverifiedCount} fix${unverifiedCount === 1 ? '' : 'es'} could not be verified. Review these manually.${RESET()}`);
|
|
2299
2308
|
}
|
|
2300
2309
|
console.log();
|
|
2301
|
-
// Remaining
|
|
2302
|
-
const
|
|
2303
|
-
if (
|
|
2304
|
-
console.log(`${
|
|
2310
|
+
// Remaining issues with fix guidance (not yet auto-fixed)
|
|
2311
|
+
const remainingWithFix = issues.filter((f) => !f.fixed && (f.fix || f.fixable));
|
|
2312
|
+
if (remainingWithFix.length > 0) {
|
|
2313
|
+
console.log(`${remainingWithFix.length} remaining issue${remainingWithFix.length === 1 ? '' : 's'} ${remainingWithFix.length === 1 ? 'has' : 'have'} fix guidance. Run \`${CLI_PREFIX} fix-all\` to apply all available fixes.\n`);
|
|
2305
2314
|
}
|
|
2306
2315
|
if (result.backupPath) {
|
|
2307
2316
|
console.log(`${colors.yellow}Backup created:${RESET()} ${result.backupPath}`);
|
|
@@ -2947,6 +2956,18 @@ Examples:
|
|
|
2947
2956
|
.option('-v, --verbose', 'Show detailed finding information')
|
|
2948
2957
|
.action(async (target, options) => {
|
|
2949
2958
|
try {
|
|
2959
|
+
// Detect local path confusion: user probably wants 'secure' not 'scan'
|
|
2960
|
+
const fs = require('fs');
|
|
2961
|
+
if (fs.existsSync(target) && (target === '.' || target.startsWith('./') || target.startsWith('/') || target.startsWith('..'))) {
|
|
2962
|
+
const secureCmd = CLI_PREFIX.includes('scan')
|
|
2963
|
+
? CLI_PREFIX.replace('scan', 'secure')
|
|
2964
|
+
: `${CLI_PREFIX} secure`;
|
|
2965
|
+
console.error(`\n"scan" is for external targets (hostnames/IPs).` +
|
|
2966
|
+
`\nTo scan a local project, use:\n` +
|
|
2967
|
+
`\n ${secureCmd} ${target}` +
|
|
2968
|
+
`\n`);
|
|
2969
|
+
process.exit(1);
|
|
2970
|
+
}
|
|
2950
2971
|
const timeoutMs = parseInt(options.timeout ?? '5000', 10);
|
|
2951
2972
|
const customPorts = options.ports
|
|
2952
2973
|
? options.ports.split(',').map((p) => parseInt(p.trim(), 10))
|
|
@@ -3240,14 +3261,14 @@ Examples:
|
|
|
3240
3261
|
printAttackReport(report, options.verbose ?? false);
|
|
3241
3262
|
output = '';
|
|
3242
3263
|
}
|
|
3243
|
-
// Write output
|
|
3264
|
+
// Write output (use writeLargeStdout to avoid 64KB pipe truncation)
|
|
3244
3265
|
if (output) {
|
|
3245
3266
|
if (options.output) {
|
|
3246
3267
|
require('fs').writeFileSync(options.output, output);
|
|
3247
3268
|
console.error(`Report written to ${options.output}`);
|
|
3248
3269
|
}
|
|
3249
3270
|
else {
|
|
3250
|
-
|
|
3271
|
+
writeLargeStdout(output + '\n');
|
|
3251
3272
|
}
|
|
3252
3273
|
}
|
|
3253
3274
|
// Registry reporting: only when explicitly requested via --version-id (CI) or --registry-report
|
|
@@ -4574,8 +4595,8 @@ Examples:
|
|
|
4574
4595
|
.option('--tier <tier>', 'Override agent tier detection (BASIC, TOOL-USING, AGENTIC, MULTI-AGENT)')
|
|
4575
4596
|
.option('--profile <profile>', 'Override agent profile (conversational, code-assistant, tool-agent, autonomous, orchestrator, custom)')
|
|
4576
4597
|
.option('--fail-below <score>', 'Exit 1 if score below threshold (0-100)')
|
|
4577
|
-
.option('--deep', 'Maximum analysis:
|
|
4578
|
-
.option('--static-only', 'Disable
|
|
4598
|
+
.option('--deep', 'Maximum analysis: semantic + SOUL governance simulation (~15s)')
|
|
4599
|
+
.option('--static-only', 'Disable semantic analysis (static governance checks only)')
|
|
4579
4600
|
.option('--publish', 'Push scan results to the OpenA2A Registry')
|
|
4580
4601
|
.option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)', validateRegistryUrl(process.env.REGISTRY_URL || 'https://api.oa2a.org'))
|
|
4581
4602
|
.option('--contribute', 'Share anonymized scan findings with OpenA2A Registry (overrides config)')
|
|
@@ -4647,7 +4668,7 @@ Examples:
|
|
|
4647
4668
|
process.stdout.write('\nOASB v2 Behavioral Governance Scan\n');
|
|
4648
4669
|
process.stdout.write('----------------------------------------------------\n');
|
|
4649
4670
|
if (options.deep) {
|
|
4650
|
-
process.stdout.write(`Analysis: static +
|
|
4671
|
+
process.stdout.write(`Analysis: static + semantic (ML-enhanced deep scan)\n`);
|
|
4651
4672
|
}
|
|
4652
4673
|
process.stdout.write('\n');
|
|
4653
4674
|
if (result.file) {
|
|
@@ -4713,7 +4734,7 @@ Examples:
|
|
|
4713
4734
|
}
|
|
4714
4735
|
else if (result.deepAnalysisResults && result.deepAnalysisResults.length > 0) {
|
|
4715
4736
|
const llmUpgraded = result.deepAnalysisResults.filter((e) => e.llmPassed).length;
|
|
4716
|
-
process.stdout.write(`Deep Analysis: ${llmUpgraded} control${llmUpgraded === 1 ? '' : 's'} upgraded by
|
|
4737
|
+
process.stdout.write(`Deep Analysis: ${llmUpgraded} control${llmUpgraded === 1 ? '' : 's'} upgraded by ML semantic analysis\n`);
|
|
4717
4738
|
}
|
|
4718
4739
|
else {
|
|
4719
4740
|
process.stdout.write(`Deep Analysis: all controls passed, no further analysis needed\n`);
|
|
@@ -5310,7 +5331,7 @@ program
|
|
|
5310
5331
|
program
|
|
5311
5332
|
.command('explain')
|
|
5312
5333
|
.argument('<findingId>', 'Finding ID to explain (e.g., SKILL-SEMANTIC-007 or CRED-001)')
|
|
5313
|
-
.description('Explain a security finding in plain English
|
|
5334
|
+
.description('Explain a security finding in plain English')
|
|
5314
5335
|
.action(async (findingId) => {
|
|
5315
5336
|
console.log(`Explaining finding: ${findingId}\n`);
|
|
5316
5337
|
// Try NanoMind daemon first for dynamic explanation
|
|
@@ -5486,7 +5507,7 @@ Examples:
|
|
|
5486
5507
|
}
|
|
5487
5508
|
}
|
|
5488
5509
|
// Exit with non-zero if resilience is poor
|
|
5489
|
-
if (report.resilienceRating === 'critical' || report.resilienceRating === '
|
|
5510
|
+
if (report.resilienceRating === 'critical' || report.resilienceRating === 'needs-attention') {
|
|
5490
5511
|
process.exit(1);
|
|
5491
5512
|
}
|
|
5492
5513
|
}
|
|
@@ -5539,6 +5560,153 @@ function printWildReport(report) {
|
|
|
5539
5560
|
console.log(`page content through an LLM. For static config scanning, use:${colors.reset}`);
|
|
5540
5561
|
console.log(` ${colors.cyan}npx hackmyagent secure${colors.reset}`);
|
|
5541
5562
|
}
|
|
5563
|
+
// pull-stubs: fetch pending HMA check stubs from the registry
|
|
5564
|
+
program
|
|
5565
|
+
.command('pull-stubs')
|
|
5566
|
+
.description(`Fetch pending HMA check stubs from the registry for review.
|
|
5567
|
+
|
|
5568
|
+
The ARIA pipeline discovers new attack patterns and creates stub definitions
|
|
5569
|
+
for checks that HMA doesn't yet implement. This command pulls those stubs
|
|
5570
|
+
so you can review, refine, and integrate them.
|
|
5571
|
+
|
|
5572
|
+
Requires INTERNAL_API_KEY environment variable for registry authentication.
|
|
5573
|
+
|
|
5574
|
+
Examples:
|
|
5575
|
+
$ ${CLI_PREFIX} pull-stubs
|
|
5576
|
+
$ ${CLI_PREFIX} pull-stubs --status review
|
|
5577
|
+
$ ${CLI_PREFIX} pull-stubs --json`)
|
|
5578
|
+
.option('--status <status>', 'Filter by stub status (draft, review, integrated, rejected)', 'draft')
|
|
5579
|
+
.option('--registry-url <url>', 'Registry base URL', validateRegistryUrl(process.env.REGISTRY_URL || 'https://api.oa2a.org'))
|
|
5580
|
+
.option('--json', 'Output raw JSON instead of formatted table')
|
|
5581
|
+
.action(async (opts) => {
|
|
5582
|
+
const validStatuses = ['draft', 'review', 'integrated', 'rejected'];
|
|
5583
|
+
if (!validStatuses.includes(opts.status)) {
|
|
5584
|
+
process.stderr.write(`Error: --status must be one of: ${validStatuses.join(', ')}\n`);
|
|
5585
|
+
process.stderr.write(` Got: ${opts.status}\n`);
|
|
5586
|
+
process.exit(1);
|
|
5587
|
+
}
|
|
5588
|
+
const apiKey = process.env.INTERNAL_API_KEY;
|
|
5589
|
+
if (!apiKey) {
|
|
5590
|
+
process.stderr.write('Error: INTERNAL_API_KEY environment variable is not set.\n');
|
|
5591
|
+
process.stderr.write('\nThis command requires registry authentication.\n');
|
|
5592
|
+
process.stderr.write('Set the variable and retry:\n');
|
|
5593
|
+
process.stderr.write(' export INTERNAL_API_KEY=<your-key>\n');
|
|
5594
|
+
process.stderr.write(` ${CLI_PREFIX} pull-stubs\n`);
|
|
5595
|
+
process.exit(1);
|
|
5596
|
+
}
|
|
5597
|
+
const registryUrl = validateRegistryUrl(opts.registryUrl).replace(/\/+$/, '');
|
|
5598
|
+
const endpoint = `${registryUrl}/internal/aria/hma-stubs`;
|
|
5599
|
+
let responseData;
|
|
5600
|
+
try {
|
|
5601
|
+
const controller = new AbortController();
|
|
5602
|
+
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
5603
|
+
const res = await fetch(endpoint, {
|
|
5604
|
+
headers: {
|
|
5605
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
5606
|
+
'Accept': 'application/json',
|
|
5607
|
+
},
|
|
5608
|
+
signal: controller.signal,
|
|
5609
|
+
});
|
|
5610
|
+
clearTimeout(timeout);
|
|
5611
|
+
if (!res.ok) {
|
|
5612
|
+
const body = await res.text().catch(() => '');
|
|
5613
|
+
process.stderr.write(`Error: Registry returned ${res.status} ${res.statusText}\n`);
|
|
5614
|
+
if (res.status === 401 || res.status === 403) {
|
|
5615
|
+
process.stderr.write(' Your INTERNAL_API_KEY may be invalid or expired.\n');
|
|
5616
|
+
}
|
|
5617
|
+
if (body)
|
|
5618
|
+
process.stderr.write(` ${body.slice(0, 200)}\n`);
|
|
5619
|
+
process.exit(1);
|
|
5620
|
+
}
|
|
5621
|
+
responseData = await res.json();
|
|
5622
|
+
}
|
|
5623
|
+
catch (err) {
|
|
5624
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
5625
|
+
process.stderr.write(`Error: Registry request timed out after 15s.\n`);
|
|
5626
|
+
process.stderr.write(` URL: ${endpoint}\n`);
|
|
5627
|
+
process.stderr.write(` Check your network connection and registry URL.\n`);
|
|
5628
|
+
}
|
|
5629
|
+
else {
|
|
5630
|
+
process.stderr.write(`Error: Could not reach the registry.\n`);
|
|
5631
|
+
process.stderr.write(` URL: ${endpoint}\n`);
|
|
5632
|
+
process.stderr.write(` ${err instanceof Error ? err.message : String(err)}\n`);
|
|
5633
|
+
}
|
|
5634
|
+
process.exit(1);
|
|
5635
|
+
}
|
|
5636
|
+
// Filter by status
|
|
5637
|
+
const stubs = responseData.stubs.filter(s => s.status === opts.status);
|
|
5638
|
+
if (stubs.length === 0) {
|
|
5639
|
+
if (opts.json) {
|
|
5640
|
+
writeJsonStdout({ stubs: [], total: responseData.total, filtered: 0, status: opts.status });
|
|
5641
|
+
}
|
|
5642
|
+
else {
|
|
5643
|
+
console.log(`No stubs with status "${opts.status}" found.`);
|
|
5644
|
+
if (responseData.total > 0) {
|
|
5645
|
+
console.log(` Registry has ${responseData.total} total stub(s). Try a different --status filter.`);
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
return;
|
|
5649
|
+
}
|
|
5650
|
+
// JSON output mode
|
|
5651
|
+
if (opts.json) {
|
|
5652
|
+
writeJsonStdout({ stubs, total: responseData.total, filtered: stubs.length, status: opts.status });
|
|
5653
|
+
return;
|
|
5654
|
+
}
|
|
5655
|
+
// Formatted output
|
|
5656
|
+
const severityColor = {
|
|
5657
|
+
critical: colors.brightRed,
|
|
5658
|
+
high: colors.red,
|
|
5659
|
+
medium: colors.yellow,
|
|
5660
|
+
low: colors.cyan,
|
|
5661
|
+
info: colors.dim,
|
|
5662
|
+
};
|
|
5663
|
+
console.log(`\nHMA Check Stubs (status: ${opts.status})\n`);
|
|
5664
|
+
for (const stub of stubs) {
|
|
5665
|
+
const sc = severityColor[stub.severity?.toLowerCase()] || '';
|
|
5666
|
+
console.log(`${'='.repeat(60)}`);
|
|
5667
|
+
console.log(` Check ID: ${stub.checkId}`);
|
|
5668
|
+
console.log(` Series: ${stub.series}`);
|
|
5669
|
+
console.log(` Name: ${stub.name}`);
|
|
5670
|
+
console.log(` Severity: ${sc}${stub.severity}${colors.reset}`);
|
|
5671
|
+
console.log(` ARIA ID: ${stub.ariaFindingId}`);
|
|
5672
|
+
console.log(` Status: ${stub.status}`);
|
|
5673
|
+
if (stub.description) {
|
|
5674
|
+
console.log(` Description: ${stub.description}`);
|
|
5675
|
+
}
|
|
5676
|
+
if (stub.detectionLogic) {
|
|
5677
|
+
console.log(` Detection logic:`);
|
|
5678
|
+
for (const line of stub.detectionLogic.split('\n')) {
|
|
5679
|
+
console.log(` ${line}`);
|
|
5680
|
+
}
|
|
5681
|
+
}
|
|
5682
|
+
console.log('');
|
|
5683
|
+
}
|
|
5684
|
+
// Summary
|
|
5685
|
+
console.log('='.repeat(60));
|
|
5686
|
+
console.log(`\nSummary`);
|
|
5687
|
+
console.log(` Total in registry: ${responseData.total}`);
|
|
5688
|
+
console.log(` Matching "${opts.status}": ${stubs.length}`);
|
|
5689
|
+
// By series
|
|
5690
|
+
const bySeries = {};
|
|
5691
|
+
for (const s of stubs) {
|
|
5692
|
+
bySeries[s.series] = (bySeries[s.series] || 0) + 1;
|
|
5693
|
+
}
|
|
5694
|
+
console.log(`\n By series:`);
|
|
5695
|
+
for (const [series, count] of Object.entries(bySeries).sort((a, b) => b[1] - a[1])) {
|
|
5696
|
+
console.log(` ${series}: ${count}`);
|
|
5697
|
+
}
|
|
5698
|
+
// By severity
|
|
5699
|
+
const bySeverity = {};
|
|
5700
|
+
for (const s of stubs) {
|
|
5701
|
+
bySeverity[s.severity] = (bySeverity[s.severity] || 0) + 1;
|
|
5702
|
+
}
|
|
5703
|
+
console.log(`\n By severity:`);
|
|
5704
|
+
for (const [sev, count] of Object.entries(bySeverity).sort((a, b) => b[1] - a[1])) {
|
|
5705
|
+
const sc = severityColor[sev?.toLowerCase()] || '';
|
|
5706
|
+
console.log(` ${sc}${sev}${colors.reset}: ${count}`);
|
|
5707
|
+
}
|
|
5708
|
+
console.log('');
|
|
5709
|
+
});
|
|
5542
5710
|
// create-skill: generate best-practice, secured skills from plain English
|
|
5543
5711
|
program
|
|
5544
5712
|
.command('create-skill')
|
|
@@ -5585,6 +5753,12 @@ program
|
|
|
5585
5753
|
catch {
|
|
5586
5754
|
// Integrity check itself failed -- continue (don't block on missing manifest in dev)
|
|
5587
5755
|
}
|
|
5756
|
+
// Global --ci flag: strip from argv so individual commands don't reject it.
|
|
5757
|
+
// Any command can check globalCiMode to adjust behavior.
|
|
5758
|
+
if (process.argv.includes('--ci')) {
|
|
5759
|
+
globalCiMode = true;
|
|
5760
|
+
process.argv = process.argv.filter(a => a !== '--ci');
|
|
5761
|
+
}
|
|
5588
5762
|
if (process.argv.length <= 2) {
|
|
5589
5763
|
program.outputHelp();
|
|
5590
5764
|
process.exit(0);
|