88const { Command } = require ( 'commander' ) ;
99const { yellow, red, cyan } = require ( 'colorette' ) ;
1010const pkg = require ( '../package.json' ) ;
11- const { getMaskedConfig, loadConfig, CONFIG_FILE } = require ( '../lib/config' ) ;
11+ const { getMaskedConfig, loadConfig, setAnalyticsEnabled , CONFIG_FILE } = require ( '../lib/config' ) ;
1212const { CLIProfileCommand } = require ( '../profiler' ) ;
1313const { CLIFullScanCommand } = require ( '../scanner' ) ;
14+ const { AnalyticsService } = require ( '../lib/services/analytics' ) ;
1415
15- const program = new Command ( ) ;
16-
17- program
18- . name ( 'secureflow' )
19- . description ( 'AI-powered security analysis CLI tool with intelligent file discovery' )
20- . version ( pkg . version ) ;
21-
22- program
23- . command ( 'scan' )
24- . description ( 'Perform full security scan of project using AI analysis' )
25- . argument ( '[path]' , 'Path to project directory (defaults to current directory)' , '.' )
26- . option ( '--model <model>' , 'AI model to use for analysis' )
27- . option ( '--format <format>' , 'Output format: text|json|defectdojo' , 'text' )
28- . option ( '--output <file>' , 'Save results to file' )
29- . option ( '--defectdojo' , 'Export results in DefectDojo import format (same as --format defectdojo)' )
30- . option ( '--defectdojo-url <url>' , 'DefectDojo instance URL (e.g., https://defectdojo.example.com)' )
31- . option ( '--defectdojo-token <token>' , 'DefectDojo API token for authentication' )
32- . option ( '--defectdojo-product-id <id>' , 'DefectDojo product ID to submit findings to' )
33- . option ( '--defectdojo-engagement-id <id>' , 'DefectDojo engagement ID (optional - will create if not provided)' )
34- . option ( '--defectdojo-test-title <title>' , 'Title for the DefectDojo test (defaults to "SecureFlow Scan")' )
35- . action ( async ( projectPath , options ) => {
16+ // Initialize analytics for CLI
17+ const analytics = AnalyticsService . getInstance ( ) ;
18+
19+ // Main async function to ensure proper initialization order
20+ async function main ( ) {
21+ const config = loadConfig ( ) ;
22+
23+ // Initialize analytics FIRST if enabled by user (default: true)
24+ const analyticsEnabled = config . analytics ?. enabled !== false ;
25+
26+ if ( analyticsEnabled ) {
27+ try {
28+ await analytics . initializeForCLI ( {
29+ cli_version : pkg . version
30+ } ) ;
31+ } catch ( err ) {
32+ // Silently fail - analytics should not disrupt CLI
33+ }
34+ }
35+
36+ // NOW set up the program after analytics is ready
37+ const program = new Command ( ) ;
38+
39+ program
40+ . name ( 'secureflow' )
41+ . description ( 'AI-powered security analysis CLI tool with intelligent file discovery' )
42+ . version ( pkg . version )
43+ . option ( '--disable-analytics' , 'Disable analytics for this session' )
44+ . option ( '--enable-analytics' , 'Enable analytics for this session' ) ;
45+
46+ // Handle analytics flags
47+ program . hook ( 'preAction' , ( thisCommand , actionCommand ) => {
48+ const opts = thisCommand . opts ( ) ;
49+ if ( opts . disableAnalytics ) {
50+ analytics . initialized = false ; // Disable for this session
51+ }
52+ } ) ;
53+
54+ program
55+ . command ( 'scan' )
56+ . description ( 'Perform full security scan of project using AI analysis' )
57+ . argument ( '[path]' , 'Path to project directory (defaults to current directory)' , '.' )
58+ . option ( '--model <model>' , 'AI model to use for analysis' )
59+ . option ( '--format <format>' , 'Output format: text|json|defectdojo' , 'text' )
60+ . option ( '--output <file>' , 'Save results to file' )
61+ . option ( '--defectdojo' , 'Export results in DefectDojo import format (same as --format defectdojo)' )
62+ . option ( '--defectdojo-url <url>' , 'DefectDojo instance URL (e.g., https://defectdojo.example.com)' )
63+ . option ( '--defectdojo-token <token>' , 'DefectDojo API token for authentication' )
64+ . option ( '--defectdojo-product-id <id>' , 'DefectDojo product ID to submit findings to' )
65+ . option ( '--defectdojo-engagement-id <id>' , 'DefectDojo engagement ID (optional - will create if not provided)' )
66+ . option ( '--defectdojo-test-title <title>' , 'Title for the DefectDojo test (defaults to "SecureFlow Scan")' )
67+ . action ( async ( projectPath , options ) => {
3668 try {
69+ // Load config to get actual model being used
70+ const config = loadConfig ( ) ;
71+ const actualModel = options . model || config . model ;
72+
73+ // Track scan command usage with actual model
74+ await analytics . trackEvent ( 'CLI Command: Scan' , {
75+ ai_model : actualModel ,
76+ ai_provider : config . provider ,
77+ output_format : options . format || 'text' ,
78+ has_defectdojo : ! ! options . defectdojo
79+ } ) ;
80+
3781 // Handle --defectdojo flag
3882 let outputFormat = options . format ;
3983 if ( options . defectdojo ) {
@@ -63,15 +107,26 @@ program
63107 }
64108 } ) ;
65109
66- program
67- . command ( 'profile' )
68- . description ( 'Profile project to identify application types and technologies' )
69- . argument ( '[path]' , 'Path to project directory (defaults to current directory)' , '.' )
70- . option ( '--model <model>' , 'AI model to use for analysis' )
71- . option ( '--format <format>' , 'Output format: text|json' , 'text' )
72- . option ( '--output <file>' , 'Save results to file' )
73- . action ( async ( projectPath , options ) => {
110+ program
111+ . command ( 'profile' )
112+ . description ( 'Profile project to identify application types and technologies' )
113+ . argument ( '[path]' , 'Path to project directory (defaults to current directory)' , '.' )
114+ . option ( '--model <model>' , 'AI model to use for analysis' )
115+ . option ( '--format <format>' , 'Output format: text|json' , 'text' )
116+ . option ( '--output <file>' , 'Save results to file' )
117+ . action ( async ( projectPath , options ) => {
74118 try {
119+ // Load config to get actual model being used
120+ const config = loadConfig ( ) ;
121+ const actualModel = options . model || config . model ;
122+
123+ // Track profile command usage with actual model
124+ await analytics . trackEvent ( 'CLI Command: Profile' , {
125+ ai_model : actualModel ,
126+ ai_provider : config . provider ,
127+ output_format : options . format || 'text'
128+ } ) ;
129+
75130 const profileCommand = new CLIProfileCommand ( {
76131 selectedModel : options . model
77132 } ) ;
@@ -86,12 +141,12 @@ program
86141 }
87142 } ) ;
88143
89- program
90- . command ( 'config' )
91- . description ( 'Show CLI configuration (masked by default)' )
92- . option ( '--show' , 'Show configuration summary' , false )
93- . option ( '--raw' , 'Do not mask secrets (use with caution)' , false )
94- . action ( ( opts ) => {
144+ program
145+ . command ( 'config' )
146+ . description ( 'Show CLI configuration (masked by default)' )
147+ . option ( '--show' , 'Show configuration summary' , false )
148+ . option ( '--raw' , 'Do not mask secrets (use with caution)' , false )
149+ . action ( ( opts ) => {
95150 if ( ! opts . show ) {
96151 console . log ( 'Use --show to display the configuration.' ) ;
97152 console . log ( `Config file path: ${ CONFIG_FILE } ` ) ;
@@ -105,14 +160,60 @@ program
105160 process . exitCode = 0 ;
106161 } ) ;
107162
108- program
109- . command ( 'helpall' )
110- . description ( 'Show help for all commands' )
111- . action ( ( ) => {
112- program . commands . forEach ( ( c ) => c . outputHelp ( ) ) ;
113- } ) ;
163+ program
164+ . command ( 'analytics <action>' )
165+ . description ( 'Manage analytics preferences (enable|disable|status)' )
166+ . action ( ( action ) => {
167+ const cfg = loadConfig ( ) ;
168+ const currentStatus = cfg . analytics ?. enabled !== false ? 'enabled' : 'disabled' ;
169+
170+ if ( action === 'status' ) {
171+ console . log ( cyan ( 'Analytics Status:' ) , currentStatus ) ;
172+ console . log ( '\nAnalytics help improve SecureFlow by collecting anonymous usage metrics.' ) ;
173+ console . log ( 'No personal information, code, or file paths are collected.' ) ;
174+ console . log ( '\nCommands:' ) ;
175+ console . log ( ' secureflow analytics enable - Enable analytics permanently' ) ;
176+ console . log ( ' secureflow analytics disable - Disable analytics permanently' ) ;
177+ console . log ( ' secureflow analytics status - Show current status' ) ;
178+ } else if ( action === 'enable' ) {
179+ if ( setAnalyticsEnabled ( true ) ) {
180+ console . log ( cyan ( '✓ Analytics enabled' ) ) ;
181+ console . log ( 'Thank you for helping improve SecureFlow!' ) ;
182+ } else {
183+ console . error ( red ( '✗ Failed to update analytics preference' ) ) ;
184+ }
185+ } else if ( action === 'disable' ) {
186+ if ( setAnalyticsEnabled ( false ) ) {
187+ console . log ( cyan ( '✓ Analytics disabled' ) ) ;
188+ console . log ( 'Analytics have been turned off.' ) ;
189+ } else {
190+ console . error ( red ( '✗ Failed to update analytics preference' ) ) ;
191+ }
192+ } else {
193+ console . error ( red ( `Unknown action: ${ action } ` ) ) ;
194+ console . log ( 'Valid actions: enable, disable, status' ) ;
195+ process . exitCode = 1 ;
196+ }
197+ } ) ;
198+
199+ program
200+ . command ( 'helpall' )
201+ . description ( 'Show help for all commands' )
202+ . action ( async ( ) => {
203+ // Track help command usage
204+ await analytics . trackEvent ( 'CLI Command: Help' , { } ) ;
205+ program . commands . forEach ( ( c ) => c . outputHelp ( ) ) ;
206+ } ) ;
207+
208+ // Parse CLI arguments
209+ await program . parseAsync ( process . argv ) ;
210+
211+ // Quick shutdown for fast exit (fire and forget)
212+ analytics . shutdown ( true ) . catch ( ( ) => { } ) ;
213+ }
114214
115- program . parseAsync ( process . argv ) . catch ( ( err ) => {
215+ // Run the main function
216+ main ( ) . catch ( ( err ) => {
116217 console . error ( red ( '[secureflow] fatal error' ) ) ;
117218 console . error ( err ?. stack || err ?. message || String ( err ) ) ;
118219 process . exit ( 1 ) ;
0 commit comments