@runhalo/cli 0.1.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.
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Halo CLI - Child Safety Compliance Scanner
4
+ * Usage: runhalo scan <path> [options]
5
+ */
6
+ import { HaloEngine, Violation, ScanResult, EngineConfig } from '@runhalo/engine';
7
+ type OutputFormat = 'json' | 'sarif' | 'text';
8
+ interface CLIOptions {
9
+ format: OutputFormat;
10
+ include: string[];
11
+ exclude: string[];
12
+ rules: string[];
13
+ severity: string[];
14
+ output: string;
15
+ verbose: boolean;
16
+ ethicalPreview: boolean;
17
+ }
18
+ /**
19
+ * Format violations as SARIF output
20
+ */
21
+ declare function formatSARIF(results: ScanResult[], rules: any[]): string;
22
+ /**
23
+ * Format violations as JSON output
24
+ */
25
+ declare function formatJSON(results: ScanResult[]): string;
26
+ /**
27
+ * Format violations as human-readable text
28
+ */
29
+ declare function formatText(results: ScanResult[], verbose?: boolean, fileCount?: number): string;
30
+ /**
31
+ * Create a Halo engine instance
32
+ */
33
+ declare function createEngine(config?: EngineConfig): HaloEngine;
34
+ /**
35
+ * Scan a single file
36
+ */
37
+ declare function scanFile(filePath: string, content?: string): Violation[];
38
+ /**
39
+ * Scan a directory
40
+ */
41
+ declare function scanDirectory(dirPath: string, config?: EngineConfig): Promise<ScanResult[]>;
42
+ /**
43
+ * Main scan function
44
+ */
45
+ declare function scan(paths: string[], options: CLIOptions): Promise<number>;
46
+ export { scan, scanFile, scanDirectory, createEngine, formatSARIF, formatJSON, formatText };
package/dist/index.js ADDED
@@ -0,0 +1,632 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * Halo CLI - Child Safety Compliance Scanner
5
+ * Usage: runhalo scan <path> [options]
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.scan = scan;
42
+ exports.scanFile = scanFile;
43
+ exports.scanDirectory = scanDirectory;
44
+ exports.createEngine = createEngine;
45
+ exports.formatSARIF = formatSARIF;
46
+ exports.formatJSON = formatJSON;
47
+ exports.formatText = formatText;
48
+ const commander_1 = require("commander");
49
+ const glob_1 = require("glob");
50
+ const fs = __importStar(require("fs"));
51
+ const path = __importStar(require("path"));
52
+ const engine_1 = require("@runhalo/engine");
53
+ // CLI configuration
54
+ const program = new commander_1.Command();
55
+ /**
56
+ * Get default file patterns to scan
57
+ */
58
+ function getDefaultPatterns() {
59
+ return [
60
+ '**/*.ts',
61
+ '**/*.js',
62
+ '**/*.tsx',
63
+ '**/*.jsx',
64
+ '**/*.py',
65
+ '**/*.swift',
66
+ '**/*.java',
67
+ '**/*.kt',
68
+ '**/*.sql',
69
+ '**/*.html',
70
+ '**/*.vue',
71
+ '**/*.svelte',
72
+ // Added Phase B: languages previously missing (C++, C#, PHP, QML)
73
+ '**/*.php',
74
+ '**/*.cpp',
75
+ '**/*.h',
76
+ '**/*.hpp',
77
+ '**/*.cs',
78
+ '**/*.qml'
79
+ ];
80
+ }
81
+ /**
82
+ * Get default exclusion patterns
83
+ */
84
+ function getDefaultExcludePatterns() {
85
+ return [
86
+ // Package and build artifacts
87
+ 'node_modules/**',
88
+ 'dist/**',
89
+ 'build/**',
90
+ '.git/**',
91
+ 'coverage/**',
92
+ '.next/**',
93
+ '.svelte-kit/**',
94
+ '.nuxt/**',
95
+ // Minified and bundled files (major FP source — vendor code is not your code)
96
+ '**/*.min.js',
97
+ '**/*.min.mjs',
98
+ '**/*.bundle.js',
99
+ '**/*.chunk.js',
100
+ // Vendor and static asset directories
101
+ '**/vendor/**',
102
+ '**/static/js/**',
103
+ '**/public/js/**',
104
+ '**/assets/js/**',
105
+ // Test files (compliance scans shouldn't flag test code)
106
+ '**/test/**',
107
+ '**/tests/**',
108
+ '**/__tests__/**',
109
+ '**/__mocks__/**',
110
+ '**/spec/**',
111
+ '**/fixtures/**',
112
+ '**/testing/**',
113
+ '**/testdata/**',
114
+ '**/*.test.ts',
115
+ '**/*.test.js',
116
+ '**/*.test.tsx',
117
+ '**/*.test.jsx',
118
+ '**/*.spec.ts',
119
+ '**/*.spec.js',
120
+ '**/*.spec.tsx',
121
+ '**/*.spec.jsx',
122
+ '**/test_*.py',
123
+ '**/conftest.py',
124
+ '**/*_test.py',
125
+ '**/*_test.go',
126
+ // Lock files
127
+ '**/package-lock.json',
128
+ '**/yarn.lock',
129
+ '**/pnpm-lock.yaml'
130
+ ];
131
+ }
132
+ /**
133
+ * Format violations as SARIF output
134
+ */
135
+ function formatSARIF(results, rules) {
136
+ const sarif = {
137
+ version: '2.1.0',
138
+ $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
139
+ runs: [{
140
+ tool: {
141
+ driver: {
142
+ name: 'Halo',
143
+ version: '1.0.0',
144
+ informationUri: 'https://runhalo.dev',
145
+ rules: rules.map(r => ({
146
+ id: r.id,
147
+ name: r.name,
148
+ shortDescription: { text: r.description },
149
+ helpUri: `https://runhalo.dev/rules/${r.id}`,
150
+ defaultConfiguration: { level: r.severity }
151
+ }))
152
+ }
153
+ },
154
+ results: results.flatMap(result => result.violations.map(v => ({
155
+ ruleId: v.ruleId,
156
+ level: v.severity === 'critical' || v.severity === 'high' ? 'error' : 'warning',
157
+ message: { text: v.message },
158
+ locations: [{
159
+ physicalLocation: {
160
+ artifactLocation: {
161
+ uri: result.filePath,
162
+ uriBaseId: 'SRCROOT'
163
+ },
164
+ region: {
165
+ startLine: v.line,
166
+ startColumn: v.column
167
+ }
168
+ }
169
+ }]
170
+ })))
171
+ }]
172
+ };
173
+ return JSON.stringify(sarif, null, 2);
174
+ }
175
+ /**
176
+ * Format violations as JSON output
177
+ */
178
+ function formatJSON(results) {
179
+ const output = {
180
+ version: '1.0.0',
181
+ scannedAt: new Date().toISOString(),
182
+ totalFiles: results.length,
183
+ totalViolations: results.reduce((sum, r) => sum + r.violations.length, 0),
184
+ results: results
185
+ };
186
+ return JSON.stringify(output, null, 2);
187
+ }
188
+ // ANSI color codes for terminal output
189
+ const colors = {
190
+ reset: '\x1b[0m',
191
+ bold: '\x1b[1m',
192
+ dim: '\x1b[2m',
193
+ red: '\x1b[31m',
194
+ yellow: '\x1b[33m',
195
+ blue: '\x1b[34m',
196
+ cyan: '\x1b[36m',
197
+ white: '\x1b[37m',
198
+ bgRed: '\x1b[41m',
199
+ bgYellow: '\x1b[43m',
200
+ bgBlue: '\x1b[44m',
201
+ magenta: '\x1b[35m',
202
+ };
203
+ // Detect if color should be used (respect NO_COLOR env and pipe detection)
204
+ const useColor = !process.env.NO_COLOR && process.stdout.isTTY !== false;
205
+ function c(color, text) {
206
+ return useColor ? `${color}${text}${colors.reset}` : text;
207
+ }
208
+ /**
209
+ * Format violations as human-readable text
210
+ */
211
+ function formatText(results, verbose = false, fileCount = 0) {
212
+ let output = '';
213
+ let totalViolations = 0;
214
+ let criticalCount = 0;
215
+ let highCount = 0;
216
+ let mediumCount = 0;
217
+ let lowCount = 0;
218
+ let filesWithViolations = 0;
219
+ for (const result of results) {
220
+ if (result.violations.length === 0)
221
+ continue;
222
+ filesWithViolations++;
223
+ totalViolations += result.violations.length;
224
+ // Count by severity
225
+ for (const v of result.violations) {
226
+ switch (v.severity) {
227
+ case 'critical':
228
+ criticalCount++;
229
+ break;
230
+ case 'high':
231
+ highCount++;
232
+ break;
233
+ case 'medium':
234
+ mediumCount++;
235
+ break;
236
+ case 'low':
237
+ lowCount++;
238
+ break;
239
+ }
240
+ }
241
+ output += `\n${c(colors.bold, result.filePath)}\n`;
242
+ for (const violation of result.violations) {
243
+ // Severity tag with color
244
+ let severityTag;
245
+ switch (violation.severity) {
246
+ case 'critical':
247
+ severityTag = c(colors.red + colors.bold, 'CRITICAL');
248
+ break;
249
+ case 'high':
250
+ severityTag = c(colors.yellow + colors.bold, 'HIGH');
251
+ break;
252
+ case 'medium':
253
+ severityTag = c(colors.blue, 'MEDIUM');
254
+ break;
255
+ default:
256
+ severityTag = c(colors.dim, 'LOW');
257
+ }
258
+ // Always show line:column (developer-standard format)
259
+ const location = c(colors.dim, `${violation.line}:${violation.column}`);
260
+ output += ` ${location} ${severityTag} ${c(colors.cyan, violation.ruleId)}\n`;
261
+ output += ` ${c(colors.dim, '│')} ${violation.message}\n`;
262
+ if (verbose) {
263
+ output += ` ${c(colors.dim, '│')} ${c(colors.magenta, '💡')} ${violation.fixSuggestion}\n`;
264
+ if (violation.penalty) {
265
+ output += ` ${c(colors.dim, '│')} ${c(colors.red, '⚠')} Penalty: ${violation.penalty}\n`;
266
+ }
267
+ }
268
+ output += '\n';
269
+ }
270
+ }
271
+ if (totalViolations === 0) {
272
+ const scannedMsg = fileCount > 0 ? ` (${fileCount} files scanned)` : '';
273
+ return `${c(colors.bold, '✅ No COPPA issues detected!')}${scannedMsg}\n`;
274
+ }
275
+ // Summary header
276
+ let header = `\n${c(colors.bold, `⚠ Found ${totalViolations} issue(s)`)}`;
277
+ header += ` across ${filesWithViolations} file(s)`;
278
+ if (fileCount > 0) {
279
+ header += ` (${fileCount} files scanned)`;
280
+ }
281
+ header += '\n';
282
+ // Severity breakdown
283
+ const parts = [];
284
+ if (criticalCount > 0)
285
+ parts.push(c(colors.red + colors.bold, `${criticalCount} critical`));
286
+ if (highCount > 0)
287
+ parts.push(c(colors.yellow + colors.bold, `${highCount} high`));
288
+ if (mediumCount > 0)
289
+ parts.push(c(colors.blue, `${mediumCount} medium`));
290
+ if (lowCount > 0)
291
+ parts.push(c(colors.dim, `${lowCount} low`));
292
+ header += ` ${parts.join(c(colors.dim, ' · '))}\n`;
293
+ return header + output;
294
+ }
295
+ /**
296
+ * Load .haloignore from a directory (walks up to find it)
297
+ */
298
+ function loadHaloignore(startDir) {
299
+ let dir = path.resolve(startDir);
300
+ const root = path.parse(dir).root;
301
+ while (dir !== root) {
302
+ const ignorePath = path.join(dir, '.haloignore');
303
+ if (fs.existsSync(ignorePath)) {
304
+ try {
305
+ const content = fs.readFileSync(ignorePath, 'utf-8');
306
+ return (0, engine_1.parseHaloignore)(content);
307
+ }
308
+ catch {
309
+ return undefined;
310
+ }
311
+ }
312
+ dir = path.dirname(dir);
313
+ }
314
+ return undefined;
315
+ }
316
+ /**
317
+ * Create a Halo engine instance
318
+ */
319
+ function createEngine(config) {
320
+ return new engine_1.HaloEngine(config);
321
+ }
322
+ /**
323
+ * Scan a single file
324
+ */
325
+ function scanFile(filePath, content) {
326
+ const engine = new engine_1.HaloEngine();
327
+ const fileContent = content || fs.readFileSync(filePath, 'utf-8');
328
+ return engine.scanFile(filePath, fileContent);
329
+ }
330
+ /**
331
+ * Scan a directory
332
+ */
333
+ async function scanDirectory(dirPath, config) {
334
+ const engine = new engine_1.HaloEngine(config);
335
+ const results = [];
336
+ const stats = fs.statSync(dirPath);
337
+ if (stats.isDirectory()) {
338
+ const patterns = getDefaultPatterns();
339
+ const excludes = getDefaultExcludePatterns();
340
+ let allFiles = [];
341
+ for (const pattern of patterns) {
342
+ const fullPattern = path.join(dirPath, pattern);
343
+ const files = await (0, glob_1.glob)(fullPattern, {
344
+ ignore: excludes,
345
+ absolute: true
346
+ });
347
+ allFiles.push(...files);
348
+ }
349
+ const uniqueFiles = [...new Set(allFiles)];
350
+ for (const filePath of uniqueFiles) {
351
+ try {
352
+ const content = fs.readFileSync(filePath, 'utf-8');
353
+ const violations = engine.scanFile(filePath, content);
354
+ results.push({
355
+ filePath,
356
+ violations,
357
+ scannedAt: new Date().toISOString(),
358
+ totalViolations: violations.length,
359
+ suppressedCount: 0
360
+ });
361
+ }
362
+ catch (err) {
363
+ // Skip files that can't be read
364
+ }
365
+ }
366
+ }
367
+ else {
368
+ const content = fs.readFileSync(dirPath, 'utf-8');
369
+ const violations = engine.scanFile(dirPath, content);
370
+ results.push({
371
+ filePath: dirPath,
372
+ violations,
373
+ scannedAt: new Date().toISOString(),
374
+ totalViolations: violations.length,
375
+ suppressedCount: 0
376
+ });
377
+ }
378
+ return results;
379
+ }
380
+ /**
381
+ * Main scan function
382
+ */
383
+ async function scan(paths, options) {
384
+ // Validate paths exist
385
+ const scanRoot = paths[0] || '.';
386
+ if (!fs.existsSync(scanRoot)) {
387
+ console.error(`❌ Path not found: ${scanRoot}`);
388
+ return 3;
389
+ }
390
+ // Load .haloignore from the first scan path
391
+ let ignoreConfig;
392
+ try {
393
+ ignoreConfig = loadHaloignore(fs.statSync(scanRoot).isDirectory() ? scanRoot : path.dirname(scanRoot));
394
+ }
395
+ catch {
396
+ // Ignore errors loading .haloignore
397
+ }
398
+ if (options.verbose && ignoreConfig) {
399
+ console.error('📋 Loaded .haloignore configuration');
400
+ }
401
+ // Auto-detect project domains from package.json to reduce ext-017 FPs
402
+ const projectDomains = [];
403
+ try {
404
+ const projectRoot = fs.statSync(scanRoot).isDirectory() ? scanRoot : path.dirname(scanRoot);
405
+ const pkgPath = path.join(projectRoot, 'package.json');
406
+ if (fs.existsSync(pkgPath)) {
407
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
408
+ // Hosting platforms that should never be treated as "own domain"
409
+ const hostingPlatforms = ['github.com', 'github.io', 'gitlab.com', 'bitbucket.org', 'npmjs.com', 'npmjs.org', 'herokuapp.com', 'vercel.app', 'netlify.app'];
410
+ // Extract domains from homepage, repository, and name
411
+ if (pkg.homepage) {
412
+ try {
413
+ const url = new URL(pkg.homepage);
414
+ if (!hostingPlatforms.includes(url.hostname)) {
415
+ projectDomains.push(url.hostname);
416
+ }
417
+ }
418
+ catch { /* not a URL */ }
419
+ }
420
+ if (typeof pkg.repository === 'string' && pkg.repository.includes('github.com')) {
421
+ // Extract org/repo name as potential domain hint
422
+ const repoMatch = pkg.repository.match(/github\.com[/:]([\w-]+)\/([\w-]+)/);
423
+ if (repoMatch) {
424
+ projectDomains.push(`${repoMatch[2]}.com`);
425
+ projectDomains.push(`${repoMatch[2]}.org`);
426
+ projectDomains.push(`${repoMatch[2]}.io`);
427
+ }
428
+ }
429
+ else if (pkg.repository?.url) {
430
+ const repoMatch = pkg.repository.url.match(/github\.com[/:]([\w-]+)\/([\w-]+)/);
431
+ if (repoMatch) {
432
+ projectDomains.push(`${repoMatch[2]}.com`);
433
+ projectDomains.push(`${repoMatch[2]}.org`);
434
+ projectDomains.push(`${repoMatch[2]}.io`);
435
+ }
436
+ }
437
+ // Use package name as domain hint
438
+ if (pkg.name) {
439
+ const cleanName = pkg.name.replace(/^@[\w-]+\//, '').replace(/-/g, '');
440
+ if (cleanName.length >= 3) {
441
+ projectDomains.push(`${cleanName}.com`);
442
+ projectDomains.push(`${cleanName}.org`);
443
+ projectDomains.push(`${cleanName}.io`);
444
+ }
445
+ }
446
+ }
447
+ }
448
+ catch {
449
+ // Ignore errors detecting project domains
450
+ }
451
+ if (options.verbose && projectDomains.length > 0) {
452
+ console.error(`🏠 Detected project domains: ${[...new Set(projectDomains)].join(', ')}`);
453
+ }
454
+ const engine = new engine_1.HaloEngine({
455
+ includePatterns: options.include,
456
+ excludePatterns: options.exclude,
457
+ rules: options.rules.length > 0 ? options.rules : undefined,
458
+ severityFilter: options.severity.length > 0 ? options.severity : undefined,
459
+ ignoreConfig,
460
+ projectDomains: projectDomains.length > 0 ? [...new Set(projectDomains)] : undefined,
461
+ ethical: options.ethicalPreview
462
+ });
463
+ const results = [];
464
+ let fileCount = 0;
465
+ // Collect all files to scan
466
+ const allFiles = [];
467
+ for (const scanPath of paths) {
468
+ const stats = fs.statSync(scanPath);
469
+ if (stats.isDirectory()) {
470
+ const patterns = options.include.length > 0
471
+ ? options.include
472
+ : getDefaultPatterns();
473
+ const excludes = options.exclude.length > 0
474
+ ? options.exclude
475
+ : getDefaultExcludePatterns();
476
+ for (const pattern of patterns) {
477
+ const fullPattern = path.join(scanPath, pattern);
478
+ const files = await (0, glob_1.glob)(fullPattern, {
479
+ ignore: excludes,
480
+ absolute: true
481
+ });
482
+ allFiles.push(...files);
483
+ }
484
+ }
485
+ else if (stats.isFile()) {
486
+ allFiles.push(scanPath);
487
+ }
488
+ }
489
+ // Deduplicate files
490
+ let uniqueFiles = [...new Set(allFiles)];
491
+ // Belt-and-suspenders: filter out test files that slip through glob exclusion
492
+ // (e.g., Django's tests.py files which aren't *inside* a tests/ directory)
493
+ const isTestFile = (filePath) => {
494
+ const normalized = filePath.replace(/\\/g, '/');
495
+ const segments = normalized.split('/');
496
+ // Check directory segments
497
+ if (segments.some(s => s === 'test' || s === 'tests' || s === '__tests__' ||
498
+ s === '__mocks__' || s === 'spec' || s === 'fixtures' ||
499
+ s === 'testing' || s === 'testdata'))
500
+ return true;
501
+ // Check file name patterns
502
+ const fileName = segments[segments.length - 1] || '';
503
+ if (/\.(?:test|spec)\.[jt]sx?$/.test(fileName))
504
+ return true;
505
+ if (/^test_.*\.py$/.test(fileName))
506
+ return true;
507
+ if (/^tests\.py$/.test(fileName))
508
+ return true;
509
+ if (/^conftest\.py$/.test(fileName))
510
+ return true;
511
+ if (/_test\.py$/.test(fileName))
512
+ return true;
513
+ if (/_test\.go$/.test(fileName))
514
+ return true;
515
+ return false;
516
+ };
517
+ const beforeFilter = uniqueFiles.length;
518
+ uniqueFiles = uniqueFiles.filter(f => !isTestFile(f));
519
+ const filteredCount = beforeFilter - uniqueFiles.length;
520
+ if (options.verbose) {
521
+ if (filteredCount > 0) {
522
+ console.error(`🧪 Excluded ${filteredCount} test files`);
523
+ }
524
+ console.error(`🔍 Scanning ${uniqueFiles.length} files...`);
525
+ }
526
+ // Max file size: 1MB (skip large/binary files)
527
+ const MAX_FILE_SIZE = 1024 * 1024;
528
+ // Scan each file
529
+ for (const filePath of uniqueFiles) {
530
+ try {
531
+ // Skip files that are too large (likely not source code)
532
+ const stat = fs.statSync(filePath);
533
+ if (stat.size > MAX_FILE_SIZE) {
534
+ if (options.verbose) {
535
+ console.error(`⏭️ Skipping large file: ${filePath} (${(stat.size / 1024).toFixed(0)}KB)`);
536
+ }
537
+ continue;
538
+ }
539
+ const content = fs.readFileSync(filePath, 'utf-8');
540
+ // Skip binary files (check for null bytes in first 512 chars)
541
+ if (content.substring(0, 512).includes('\0')) {
542
+ continue;
543
+ }
544
+ const violations = engine.scanFile(filePath, content);
545
+ if (violations.length > 0) {
546
+ results.push({
547
+ filePath,
548
+ violations,
549
+ scannedAt: new Date().toISOString(),
550
+ totalViolations: violations.length,
551
+ suppressedCount: 0
552
+ });
553
+ }
554
+ fileCount++;
555
+ }
556
+ catch (err) {
557
+ if (options.verbose) {
558
+ console.error(`⚠️ Error reading ${filePath}:`, err);
559
+ }
560
+ }
561
+ }
562
+ // Format output
563
+ let output;
564
+ switch (options.format) {
565
+ case 'sarif':
566
+ output = formatSARIF(results, engine.getRules());
567
+ break;
568
+ case 'json':
569
+ output = formatJSON(results);
570
+ break;
571
+ default:
572
+ output = formatText(results, options.verbose, fileCount);
573
+ }
574
+ // Write output (only one path — no duplication)
575
+ if (options.output) {
576
+ fs.writeFileSync(options.output, output);
577
+ console.error(`📄 Results written to ${options.output}`);
578
+ }
579
+ else {
580
+ process.stdout.write(output);
581
+ }
582
+ // Return exit code based on violations
583
+ const hasCriticalOrHigh = results.some(r => r.violations.some(v => v.severity === 'critical' || v.severity === 'high'));
584
+ if (hasCriticalOrHigh) {
585
+ return 2; // Critical violations found
586
+ }
587
+ if (results.length > 0) {
588
+ return 1; // Violations found (medium/low only)
589
+ }
590
+ return 0; // No violations
591
+ }
592
+ // CLI setup
593
+ program
594
+ .name('runhalo')
595
+ .description('Halo - Child Safety Compliance Scanner for COPPA 2.0')
596
+ .version('1.0.0');
597
+ program
598
+ .command('scan')
599
+ .description('Scan files or directories for COPPA violations')
600
+ .argument('[paths...]', 'Paths to scan (default: current directory)', ['.'])
601
+ .option('-f, --format <format>', 'Output format: json, sarif, text', 'text')
602
+ .option('-i, --include <patterns...>', 'File patterns to include')
603
+ .option('-e, --exclude <patterns...>', 'File patterns to exclude')
604
+ .option('-r, --rules <ruleIds...>', 'Specific rules to run (e.g., coppa-auth-001)')
605
+ .option('-s, --severity <levels...>', 'Filter by severity: critical, high, medium, low')
606
+ .option('-o, --output <file>', 'Output file path')
607
+ .option('--ethical-preview', 'Enable experimental ethical design rules (Sprint 5 preview)')
608
+ .option('-v, --verbose', 'Verbose output')
609
+ .action(async (paths, options) => {
610
+ try {
611
+ const exitCode = await scan(paths, {
612
+ format: options.format || 'text',
613
+ include: options.include || [],
614
+ exclude: options.exclude || [],
615
+ rules: options.rules || [],
616
+ severity: options.severity || [],
617
+ output: options.output || '',
618
+ verbose: options.verbose || false,
619
+ ethicalPreview: options.ethicalPreview || false
620
+ });
621
+ process.exit(exitCode);
622
+ }
623
+ catch (error) {
624
+ console.error('❌ Error:', error instanceof Error ? error.message : error);
625
+ process.exit(3); // Fatal error
626
+ }
627
+ });
628
+ // Run CLI
629
+ if (require.main === module) {
630
+ program.parse();
631
+ }
632
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@runhalo/cli",
3
+ "version": "0.1.0",
4
+ "description": "Halo CLI — scan your codebase for COPPA 2.0 privacy risks",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "runhalo": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist/index.js",
12
+ "dist/index.js.map",
13
+ "dist/index.d.ts"
14
+ ],
15
+ "scripts": {
16
+ "prebuild": "cd ../engine && npm run build",
17
+ "build": "tsc",
18
+ "test": "jest --config jest.config.cjs",
19
+ "scan": "node dist/index.js scan"
20
+ },
21
+ "keywords": [
22
+ "coppa",
23
+ "privacy",
24
+ "child-safety",
25
+ "cli",
26
+ "scanner",
27
+ "linter"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/runhalo/halo.git",
32
+ "directory": "packages/cli"
33
+ },
34
+ "homepage": "https://runhalo.dev",
35
+ "bugs": {
36
+ "url": "https://github.com/runhalo/halo/issues"
37
+ },
38
+ "author": "Mindful Media <hello@mindfulmedia.org> (https://mindfulmedia.org)",
39
+ "license": "Apache-2.0",
40
+ "dependencies": {
41
+ "@runhalo/engine": "^0.1.0",
42
+ "commander": "^11.1.0",
43
+ "glob": "^10.3.10"
44
+ },
45
+ "devDependencies": {
46
+ "@types/jest": "^29.5.14",
47
+ "@types/node": "^20.10.0",
48
+ "jest": "^29.7.0",
49
+ "ts-jest": "^29.4.6",
50
+ "typescript": "^5.3.3"
51
+ }
52
+ }