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.
Files changed (30) hide show
  1. package/dist/.integrity-manifest.json +1 -1
  2. package/dist/checker/skill-identifier.d.ts.map +1 -1
  3. package/dist/checker/skill-identifier.js +9 -1
  4. package/dist/checker/skill-identifier.js.map +1 -1
  5. package/dist/cli.js +202 -28
  6. package/dist/cli.js.map +1 -1
  7. package/dist/nanomind-core/inference/tme-classifier.d.ts.map +1 -1
  8. package/dist/nanomind-core/inference/tme-classifier.js +4 -5
  9. package/dist/nanomind-core/inference/tme-classifier.js.map +1 -1
  10. package/dist/nanomind-core/telemetry/auto-update.d.ts +27 -0
  11. package/dist/nanomind-core/telemetry/auto-update.d.ts.map +1 -0
  12. package/dist/nanomind-core/telemetry/auto-update.js +129 -0
  13. package/dist/nanomind-core/telemetry/auto-update.js.map +1 -0
  14. package/dist/nanomind-core/telemetry/client.d.ts +66 -0
  15. package/dist/nanomind-core/telemetry/client.d.ts.map +1 -0
  16. package/dist/nanomind-core/telemetry/client.js +123 -0
  17. package/dist/nanomind-core/telemetry/client.js.map +1 -0
  18. package/dist/nanomind-core/telemetry/config.d.ts +33 -0
  19. package/dist/nanomind-core/telemetry/config.d.ts.map +1 -0
  20. package/dist/nanomind-core/telemetry/config.js +119 -0
  21. package/dist/nanomind-core/telemetry/config.js.map +1 -0
  22. package/dist/nanomind-core/telemetry/index.d.ts +15 -0
  23. package/dist/nanomind-core/telemetry/index.d.ts.map +1 -0
  24. package/dist/nanomind-core/telemetry/index.js +27 -0
  25. package/dist/nanomind-core/telemetry/index.js.map +1 -0
  26. package/dist/wild/scorer.js +2 -2
  27. package/dist/wild/scorer.js.map +1 -1
  28. package/dist/wild/types.d.ts +1 -1
  29. package/dist/wild/types.d.ts.map +1 -1
  30. 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 JSON to stdout synchronously with retry for pipe backpressure.
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 writeJsonStdout(data) {
53
+ function writeLargeStdout(text) {
54
54
  const fs = require('fs');
55
- const buf = Buffer.from(JSON.stringify(data, null, 2) + '\n');
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 spin-wait briefly then retry
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, '-V, --version', 'Output the version number')
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(`NanoMind: ${nmResult.compiledArtifacts} artifact(s) compiled\n`);
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 result = await (0, index_1.checkSkill)(skill, {
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 + NanoMind + behavioral simulation + adaptive attacks (~30s per artifact)')
1801
- .option('--static-only', 'Disable NanoMind and simulation (static checks only, fast, deterministic)')
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 + NanoMind + behavioral simulation + adaptive attacks\n`);
1911
+ console.log(`Analysis: static + semantic + behavioral simulation + adaptive attacks\n`);
1902
1912
  }
1903
1913
  else if (nanomindAvailable) {
1904
- console.log(`Analysis: static + NanoMind (enhanced accuracy)\n`);
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
- console.log(output);
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
- console.log(output);
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 fixable issues
2302
- const remainingFixable = issues.filter((f) => f.fixable && !f.fixed);
2303
- if (remainingFixable.length > 0) {
2304
- console.log(`${colors.yellow}${remainingFixable.length} more issue${remainingFixable.length === 1 ? '' : 's'} can be auto-fixed.${RESET()} Run \`${CLI_PREFIX} secure --fix\` again.\n`);
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
- console.log(output);
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: NanoMind + SOUL governance simulation (~15s)')
4578
- .option('--static-only', 'Disable NanoMind (static governance checks only)')
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 + NanoMind semantic (deep)\n`);
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 NanoMind semantic analysis\n`);
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 using NanoMind')
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 === 'poor') {
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);