codesummary 1.0.2 → 1.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.
package/src/cli.js CHANGED
@@ -1,392 +1,510 @@
1
- import inquirer from 'inquirer';
2
- import chalk from 'chalk';
3
- import path from 'path';
4
- import fs from 'fs-extra';
5
- import ora from 'ora';
6
-
7
- import ConfigManager from './configManager.js';
8
- import Scanner from './scanner.js';
9
- import PDFGenerator from './pdfGenerator.js';
10
- import ErrorHandler from './errorHandler.js';
11
-
12
- /**
13
- * Command Line Interface for CodeSummary
14
- * Handles user interaction and orchestrates the scanning and PDF generation process
15
- */
16
- export class CLI {
17
- constructor() {
18
- this.configManager = new ConfigManager();
19
- this.config = null;
20
- this.scanner = null;
21
- this.pdfGenerator = null;
22
- }
23
-
24
- /**
25
- * Main entry point for CLI execution
26
- * @param {Array} args - Command line arguments
27
- */
28
- async run(args = []) {
29
- try {
30
- // Parse command line arguments
31
- const options = this.parseArguments(args);
32
-
33
- // Handle special commands
34
- if (options.showConfig) {
35
- await this.showConfig();
36
- return;
37
- }
38
-
39
- if (options.resetConfig) {
40
- await this.resetConfig();
41
- return;
42
- }
43
-
44
- if (options.config) {
45
- await this.editConfig();
46
- return;
47
- }
48
-
49
- // Main scanning and PDF generation flow
50
- await this.executeMainFlow(options);
51
-
52
- } catch (error) {
53
- ErrorHandler.handleError(error, 'CLI Operation');
54
- }
55
- }
56
-
57
- /**
58
- * Parse command line arguments
59
- * @param {Array} args - Raw arguments
60
- * @returns {object} Parsed options
61
- */
62
- parseArguments(args) {
63
- const options = {
64
- output: null,
65
- showConfig: false,
66
- resetConfig: false,
67
- config: false,
68
- help: false,
69
- noInteractive: false
70
- };
71
-
72
- for (let i = 0; i < args.length; i++) {
73
- const arg = args[i];
74
-
75
- switch (arg) {
76
- case '--output':
77
- case '-o':
78
- options.output = args[++i];
79
- break;
80
- case '--show-config':
81
- options.showConfig = true;
82
- break;
83
- case '--reset-config':
84
- options.resetConfig = true;
85
- break;
86
- case 'config':
87
- options.config = true;
88
- break;
89
- case '--help':
90
- case '-h':
91
- options.help = true;
92
- break;
93
- case '--no-interactive':
94
- options.noInteractive = true;
95
- break;
96
- default:
97
- if (arg.startsWith('-')) {
98
- throw new Error(`Unknown option: ${arg}`);
99
- }
100
- }
101
- }
102
-
103
- if (options.help) {
104
- this.showHelp();
105
- process.exit(0);
106
- }
107
-
108
- return options;
109
- }
110
-
111
- /**
112
- * Execute the main scanning and PDF generation flow
113
- * @param {object} options - Parsed command line options
114
- */
115
- async executeMainFlow(options) {
116
- // Load or create configuration
117
- this.config = await this.loadConfiguration();
118
-
119
- // Initialize components
120
- this.scanner = new Scanner(this.config);
121
- this.pdfGenerator = new PDFGenerator(this.config);
122
-
123
- // Determine scan path (default: current working directory)
124
- const scanPath = process.cwd();
125
- const projectName = path.basename(scanPath);
126
-
127
- console.log(chalk.cyan(`CodeSummary - Scanning project: ${chalk.bold(projectName)}\n`));
128
-
129
- // Scan directory
130
- const spinner = ora('Scanning directory structure...').start();
131
- const filesByExtension = await this.scanner.scanDirectory(scanPath);
132
- spinner.succeed('Directory scan completed');
133
-
134
- // Check if any supported files were found
135
- if (Object.keys(filesByExtension).length === 0) {
136
- console.log(chalk.red('ERROR: No supported files found. Nothing to document.'));
137
- process.exit(1);
138
- }
139
-
140
- // Display scan summary
141
- this.scanner.displayScanSummary(filesByExtension);
142
-
143
- // Let user select extensions to include
144
- const selectedExtensions = await this.selectExtensions(filesByExtension);
145
-
146
- if (selectedExtensions.length === 0) {
147
- console.log(chalk.yellow('WARNING: No extensions selected. Exiting.'));
148
- process.exit(0);
149
- }
150
-
151
- // Check file count threshold
152
- const totalFiles = this.calculateTotalFiles(filesByExtension, selectedExtensions);
153
- await this.checkFileCountThreshold(totalFiles);
154
-
155
- // Determine output path
156
- const outputPath = this.determineOutputPath(options.output, projectName);
157
-
158
- // Ensure output directory exists
159
- await PDFGenerator.ensureOutputDirectory(path.dirname(outputPath));
160
-
161
- // Generate PDF
162
- const generationSpinner = ora('Generating PDF document...').start();
163
- const result = await this.pdfGenerator.generatePDF(
164
- filesByExtension,
165
- selectedExtensions,
166
- outputPath,
167
- projectName
168
- );
169
- generationSpinner.succeed('PDF generation completed');
170
-
171
- // Display success summary
172
- await this.displayCompletionSummary(result.outputPath, selectedExtensions, totalFiles, result.pageCount);
173
- }
174
-
175
- /**
176
- * Load configuration (with first-run setup if needed)
177
- * @returns {object} Configuration object
178
- */
179
- async loadConfiguration() {
180
- let config = await this.configManager.loadConfig();
181
-
182
- if (!config) {
183
- // First run - trigger setup wizard
184
- config = await this.configManager.runFirstTimeSetup();
185
- } else {
186
- console.log(chalk.gray(`Using configuration from ${this.configManager.configPath}`));
187
- }
188
-
189
- return config;
190
- }
191
-
192
- /**
193
- * Let user select which extensions to include
194
- * @param {object} filesByExtension - Available files by extension
195
- * @returns {Array} Selected extensions
196
- */
197
- async selectExtensions(filesByExtension) {
198
- const extensionInfo = this.scanner.getExtensionInfo(filesByExtension);
199
-
200
- const choices = extensionInfo.map(info => ({
201
- name: `${info.extension} ${info.description} (${info.count} files)`,
202
- value: info.extension,
203
- checked: true // Pre-select all detected extensions
204
- }));
205
-
206
- const { selectedExtensions } = await inquirer.prompt([{
207
- type: 'checkbox',
208
- name: 'selectedExtensions',
209
- message: 'Select file extensions to include:',
210
- choices,
211
- validate: (answer) => {
212
- if (answer.length === 0) {
213
- return 'You must select at least one extension.';
214
- }
215
- return true;
216
- }
217
- }]);
218
-
219
- return selectedExtensions;
220
- }
221
-
222
- /**
223
- * Calculate total files for selected extensions
224
- * @param {object} filesByExtension - Files by extension
225
- * @param {Array} selectedExtensions - Selected extensions
226
- * @returns {number} Total file count
227
- */
228
- calculateTotalFiles(filesByExtension, selectedExtensions) {
229
- return selectedExtensions.reduce((total, ext) => {
230
- return total + (filesByExtension[ext]?.length || 0);
231
- }, 0);
232
- }
233
-
234
- /**
235
- * Check if file count exceeds threshold and prompt user
236
- * @param {number} totalFiles - Total file count
237
- */
238
- async checkFileCountThreshold(totalFiles) {
239
- if (totalFiles > this.config.settings.maxFilesBeforePrompt) {
240
- console.log(chalk.yellow(`WARNING: Found ${totalFiles} files. Generating the PDF may take a while.`));
241
-
242
- const { shouldContinue } = await inquirer.prompt([{
243
- type: 'confirm',
244
- name: 'shouldContinue',
245
- message: 'Do you want to continue?',
246
- default: true
247
- }]);
248
-
249
- if (!shouldContinue) {
250
- console.log(chalk.gray('Operation cancelled by user.'));
251
- process.exit(0);
252
- }
253
- }
254
- }
255
-
256
- /**
257
- * Determine final output path for PDF
258
- * @param {string} overridePath - Optional override path from CLI
259
- * @param {string} projectName - Project name
260
- * @returns {string} Final output path
261
- */
262
- determineOutputPath(overridePath, projectName) {
263
- let outputDir;
264
-
265
- if (overridePath) {
266
- // Validate and sanitize override path from CLI
267
- const sanitizedPath = ErrorHandler.sanitizeInput(overridePath);
268
- ErrorHandler.validatePath(sanitizedPath, { preventTraversal: true });
269
-
270
- outputDir = path.resolve(sanitizedPath);
271
- console.log(chalk.gray(`PDF will be saved to: ${outputDir}`));
272
- } else {
273
- // Use config settings
274
- if (this.config.output.mode === 'relative') {
275
- outputDir = process.cwd();
276
- } else {
277
- outputDir = path.resolve(this.config.output.fixedPath);
278
- }
279
- }
280
-
281
- // Sanitize project name for filename
282
- const sanitizedProjectName = ErrorHandler.sanitizeInput(projectName);
283
-
284
- return PDFGenerator.generateOutputPath(sanitizedProjectName, outputDir);
285
- }
286
-
287
- /**
288
- * Display completion summary
289
- * @param {string} outputPath - Generated PDF path
290
- * @param {Array} selectedExtensions - Selected extensions
291
- * @param {number} totalFiles - Total files processed
292
- * @param {number|string} pageCount - Number of pages in PDF or 'N/A'
293
- */
294
- async displayCompletionSummary(outputPath, selectedExtensions, totalFiles, pageCount) {
295
- // Get PDF stats
296
- const stats = await fs.stat(outputPath);
297
- const fileSizeFormatted = this.formatFileSize(stats.size);
298
-
299
- console.log(chalk.green('\nSUCCESS: PDF generation completed successfully!\n'));
300
- console.log(chalk.cyan('Summary:'));
301
- console.log(chalk.gray(` Output: ${outputPath}`));
302
- console.log(chalk.gray(` Extensions: ${selectedExtensions.join(', ')}`));
303
- console.log(chalk.gray(` Total files: ${totalFiles}`));
304
- if (pageCount !== 'N/A') {
305
- console.log(chalk.gray(` Total pages: ${pageCount}`));
306
- }
307
- console.log(chalk.gray(` PDF size: ${fileSizeFormatted}`));
308
- console.log();
309
- }
310
-
311
- /**
312
- * Show current configuration
313
- */
314
- async showConfig() {
315
- const config = await this.configManager.loadConfig();
316
- if (config) {
317
- this.configManager.displayConfig(config);
318
- } else {
319
- console.log(chalk.yellow('WARNING: No configuration found. Run codesummary to set up.'));
320
- }
321
- }
322
-
323
- /**
324
- * Reset configuration
325
- */
326
- async resetConfig() {
327
- await this.configManager.resetConfig();
328
- console.log(chalk.green('SUCCESS: Configuration reset. Run codesummary to set up again.'));
329
- }
330
-
331
- /**
332
- * Edit configuration interactively
333
- */
334
- async editConfig() {
335
- let config = await this.configManager.loadConfig();
336
-
337
- if (!config) {
338
- console.log(chalk.yellow('WARNING: No configuration found. Running first-time setup...'));
339
- config = await this.configManager.runFirstTimeSetup();
340
- } else {
341
- config = await this.configManager.editConfig(config);
342
- }
343
- }
344
-
345
- /**
346
- * Format file size in human readable format
347
- * @param {number} bytes - Size in bytes
348
- * @returns {string} Formatted size
349
- */
350
- formatFileSize(bytes) {
351
- const units = ['B', 'KB', 'MB', 'GB'];
352
- let size = bytes;
353
- let unitIndex = 0;
354
-
355
- while (size >= 1024 && unitIndex < units.length - 1) {
356
- size /= 1024;
357
- unitIndex++;
358
- }
359
-
360
- return `${size.toFixed(1)} ${units[unitIndex]}`;
361
- }
362
-
363
- /**
364
- * Show help information
365
- */
366
- showHelp() {
367
- console.log(chalk.cyan('\nCodeSummary - Generate PDF documentation from source code\n'));
368
-
369
- console.log(chalk.white('Usage:'));
370
- console.log(' codesummary [options] Scan current directory and generate PDF');
371
- console.log(' codesummary config Edit configuration settings');
372
- console.log();
373
-
374
- console.log(chalk.white('Options:'));
375
- console.log(' -o, --output <path> Override output directory');
376
- console.log(' --show-config Display current configuration');
377
- console.log(' --reset-config Reset configuration to defaults');
378
- console.log(' -h, --help Show this help message');
379
- console.log();
380
-
381
- console.log(chalk.white('Examples:'));
382
- console.log(' codesummary Scan current project');
383
- console.log(' codesummary --output ./docs Save PDF to ./docs folder');
384
- console.log(' codesummary config Edit settings');
385
- console.log(' codesummary --show-config View current settings');
386
- console.log();
387
-
388
- console.log(chalk.gray('For more information, visit: https://github.com/skamoll/CodeSummary'));
389
- }
390
- }
391
-
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import path from 'path';
4
+ import fs from 'fs-extra';
5
+ import ora from 'ora';
6
+
7
+ import ConfigManager from './configManager.js';
8
+ import Scanner from './scanner.js';
9
+ import PDFGenerator from './pdfGenerator.js';
10
+ import ErrorHandler from './errorHandler.js';
11
+
12
+ /**
13
+ * Command Line Interface for CodeSummary
14
+ * Handles user interaction and orchestrates the scanning and PDF generation process
15
+ */
16
+ export class CLI {
17
+ constructor() {
18
+ this.configManager = new ConfigManager();
19
+ this.config = null;
20
+ this.scanner = null;
21
+ this.pdfGenerator = null;
22
+ }
23
+
24
+ /**
25
+ * Main entry point for CLI execution
26
+ * @param {Array} args - Command line arguments
27
+ */
28
+ async run(args = []) {
29
+ try {
30
+ // Parse command line arguments
31
+ const options = await this.parseArguments(args);
32
+
33
+ // Handle special commands
34
+ if (options.showConfig) {
35
+ await this.showConfig();
36
+ return;
37
+ }
38
+
39
+ if (options.resetConfig) {
40
+ await this.resetConfig();
41
+ return;
42
+ }
43
+
44
+ if (options.config) {
45
+ await this.editConfig();
46
+ return;
47
+ }
48
+
49
+ // Main scanning and PDF generation flow
50
+ await this.executeMainFlow(options);
51
+
52
+ } catch (error) {
53
+ ErrorHandler.handleError(error, 'CLI Operation');
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Parse command line arguments
59
+ * @param {Array} args - Raw arguments
60
+ * @returns {object} Parsed options
61
+ */
62
+ async parseArguments(args) {
63
+ const options = {
64
+ output: null,
65
+ showConfig: false,
66
+ resetConfig: false,
67
+ config: false,
68
+ help: false,
69
+ noInteractive: false,
70
+ format: 'pdf'
71
+ };
72
+
73
+ for (let i = 0; i < args.length; i++) {
74
+ const arg = args[i];
75
+
76
+ switch (arg) {
77
+ case '--output':
78
+ case '-o':
79
+ if (i + 1 >= args.length) {
80
+ throw new Error(`Option ${arg} requires a value`);
81
+ }
82
+ i++; // Move to next argument
83
+ const outputPath = args[i];
84
+
85
+ // Validate output path
86
+ if (!outputPath || outputPath.trim().length === 0) {
87
+ throw new Error(`Option ${arg} requires a non-empty path`);
88
+ }
89
+
90
+ // Sanitize and validate path
91
+ const sanitizedPath = ErrorHandler.sanitizeInput(outputPath, {
92
+ allowPath: true,
93
+ maxLength: 500,
94
+ strictMode: true
95
+ });
96
+
97
+ if (sanitizedPath !== outputPath) {
98
+ console.warn(chalk.yellow(`WARNING: Output path was sanitized: ${outputPath} -> ${sanitizedPath}`));
99
+ }
100
+
101
+ try {
102
+ ErrorHandler.validatePath(sanitizedPath, {
103
+ preventTraversal: true,
104
+ mustBeAbsolute: false
105
+ });
106
+ } catch (error) {
107
+ throw new Error(`Invalid output path: ${error.message}`);
108
+ }
109
+
110
+ options.output = sanitizedPath;
111
+ break;
112
+ case '--show-config':
113
+ options.showConfig = true;
114
+ break;
115
+ case '--reset-config':
116
+ options.resetConfig = true;
117
+ break;
118
+ case 'config':
119
+ options.config = true;
120
+ break;
121
+ case '--help':
122
+ case '-h':
123
+ options.help = true;
124
+ break;
125
+ case '--no-interactive':
126
+ options.noInteractive = true;
127
+ break;
128
+ case '--format':
129
+ case '-f':
130
+ if (i + 1 >= args.length) {
131
+ throw new Error(`Option ${arg} requires a value (pdf or rag)`);
132
+ }
133
+ i++;
134
+ const format = args[i].toLowerCase();
135
+ if (!['pdf', 'rag'].includes(format)) {
136
+ throw new Error(`Invalid format: ${format}. Use 'pdf' or 'rag'`);
137
+ }
138
+ options.format = format;
139
+ break;
140
+ default:
141
+ if (arg.startsWith('-')) {
142
+ throw new Error(`Unknown option: ${arg}`);
143
+ }
144
+ // Allow non-option arguments (for future extensibility)
145
+ break;
146
+ }
147
+ }
148
+
149
+ if (options.help) {
150
+ this.showHelp();
151
+ await ErrorHandler.safeExit(0, 'Help displayed');
152
+ }
153
+
154
+ return options;
155
+ }
156
+
157
+ /**
158
+ * Execute the main scanning and PDF generation flow
159
+ * @param {object} options - Parsed command line options
160
+ */
161
+ async executeMainFlow(options) {
162
+ // Load or create configuration
163
+ this.config = await this.loadConfiguration();
164
+
165
+ // Initialize components
166
+ this.scanner = new Scanner(this.config);
167
+ this.pdfGenerator = new PDFGenerator(this.config);
168
+
169
+ // Determine scan path (default: current working directory)
170
+ const scanPath = process.cwd();
171
+ const projectName = path.basename(scanPath);
172
+
173
+ console.log(chalk.cyan(`CodeSummary - Scanning project: ${chalk.bold(projectName)}\n`));
174
+
175
+ // Scan directory
176
+ const spinner = ora('Scanning directory structure...').start();
177
+ const filesByExtension = await this.scanner.scanDirectory(scanPath);
178
+ spinner.succeed('Directory scan completed');
179
+
180
+ // Check if any supported files were found
181
+ if (Object.keys(filesByExtension).length === 0) {
182
+ console.log(chalk.red('ERROR: No supported files found. Nothing to document.'));
183
+ await ErrorHandler.safeExit(1, 'No supported files found');
184
+ }
185
+
186
+ // Display scan summary
187
+ this.scanner.displayScanSummary(filesByExtension);
188
+
189
+ // Let user select extensions to include
190
+ const selectedExtensions = await this.selectExtensions(filesByExtension);
191
+
192
+ if (selectedExtensions.length === 0) {
193
+ console.log(chalk.yellow('WARNING: No extensions selected. Exiting.'));
194
+ await ErrorHandler.safeExit(0, 'No extensions selected');
195
+ }
196
+
197
+ // Check file count threshold
198
+ const totalFiles = this.calculateTotalFiles(filesByExtension, selectedExtensions);
199
+ await this.checkFileCountThreshold(totalFiles);
200
+
201
+ // Generate output based on format
202
+ if (options.format === 'rag') {
203
+ // Generate RAG-optimized output
204
+ const ragGenerator = await import('./ragGenerator.js');
205
+ const ragOutputPath = this.determineRagOutputPath(options.output, projectName);
206
+
207
+ // Ensure output directory exists
208
+ await fs.ensureDir(path.dirname(ragOutputPath));
209
+
210
+ const generationSpinner = ora('Generating RAG-optimized output...').start();
211
+ const result = await ragGenerator.default.generateRagOutput(
212
+ filesByExtension,
213
+ selectedExtensions,
214
+ ragOutputPath,
215
+ projectName,
216
+ scanPath
217
+ );
218
+ generationSpinner.succeed('RAG output generation completed');
219
+
220
+ // Display RAG success summary
221
+ await this.displayRagCompletionSummary(result.outputPath, selectedExtensions, totalFiles, result.totalChunks);
222
+ } else {
223
+ // Generate PDF (default behavior)
224
+ const outputPath = this.determineOutputPath(options.output, projectName);
225
+
226
+ // Ensure output directory exists
227
+ await PDFGenerator.ensureOutputDirectory(path.dirname(outputPath));
228
+
229
+ // Generate PDF
230
+ const generationSpinner = ora('Generating PDF document...').start();
231
+ const result = await this.pdfGenerator.generatePDF(
232
+ filesByExtension,
233
+ selectedExtensions,
234
+ outputPath,
235
+ projectName
236
+ );
237
+ generationSpinner.succeed('PDF generation completed');
238
+
239
+ // Display success summary
240
+ await this.displayCompletionSummary(result.outputPath, selectedExtensions, totalFiles, result.pageCount);
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Load configuration (with first-run setup if needed)
246
+ * @returns {object} Configuration object
247
+ */
248
+ async loadConfiguration() {
249
+ let config = await this.configManager.loadConfig();
250
+
251
+ if (!config) {
252
+ // First run - trigger setup wizard
253
+ config = await this.configManager.runFirstTimeSetup();
254
+ } else {
255
+ console.log(chalk.gray(`Using configuration from ${this.configManager.configPath}`));
256
+ }
257
+
258
+ return config;
259
+ }
260
+
261
+ /**
262
+ * Let user select which extensions to include
263
+ * @param {object} filesByExtension - Available files by extension
264
+ * @returns {Array} Selected extensions
265
+ */
266
+ async selectExtensions(filesByExtension) {
267
+ const extensionInfo = this.scanner.getExtensionInfo(filesByExtension);
268
+
269
+ const choices = extensionInfo.map(info => ({
270
+ name: `${info.extension} → ${info.description} (${info.count} files)`,
271
+ value: info.extension,
272
+ checked: true // Pre-select all detected extensions
273
+ }));
274
+
275
+ const { selectedExtensions } = await inquirer.prompt([{
276
+ type: 'checkbox',
277
+ name: 'selectedExtensions',
278
+ message: 'Select file extensions to include:',
279
+ choices,
280
+ validate: (answer) => {
281
+ if (answer.length === 0) {
282
+ return 'You must select at least one extension.';
283
+ }
284
+ return true;
285
+ }
286
+ }]);
287
+
288
+ return selectedExtensions;
289
+ }
290
+
291
+ /**
292
+ * Calculate total files for selected extensions
293
+ * @param {object} filesByExtension - Files by extension
294
+ * @param {Array} selectedExtensions - Selected extensions
295
+ * @returns {number} Total file count
296
+ */
297
+ calculateTotalFiles(filesByExtension, selectedExtensions) {
298
+ return selectedExtensions.reduce((total, ext) => {
299
+ return total + (filesByExtension[ext]?.length || 0);
300
+ }, 0);
301
+ }
302
+
303
+ /**
304
+ * Check if file count exceeds threshold and prompt user
305
+ * @param {number} totalFiles - Total file count
306
+ */
307
+ async checkFileCountThreshold(totalFiles) {
308
+ if (totalFiles > this.config.settings.maxFilesBeforePrompt) {
309
+ console.log(chalk.yellow(`WARNING: Found ${totalFiles} files. Generating the PDF may take a while.`));
310
+
311
+ const { shouldContinue } = await inquirer.prompt([{
312
+ type: 'confirm',
313
+ name: 'shouldContinue',
314
+ message: 'Do you want to continue?',
315
+ default: true
316
+ }]);
317
+
318
+ if (!shouldContinue) {
319
+ console.log(chalk.gray('Operation cancelled by user.'));
320
+ await ErrorHandler.safeExit(0, 'Operation cancelled by user');
321
+ }
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Determine final output path for RAG format
327
+ * @param {string} overridePath - Optional override path from CLI
328
+ * @param {string} projectName - Project name
329
+ * @returns {string} Final output path
330
+ */
331
+ determineRagOutputPath(overridePath, projectName) {
332
+ let outputDir;
333
+
334
+ if (overridePath) {
335
+ const sanitizedPath = ErrorHandler.sanitizeInput(overridePath);
336
+ ErrorHandler.validatePath(sanitizedPath, { preventTraversal: true });
337
+ outputDir = path.resolve(sanitizedPath);
338
+ } else {
339
+ if (this.config.output.mode === 'relative') {
340
+ outputDir = process.cwd();
341
+ } else {
342
+ outputDir = path.resolve(this.config.output.fixedPath);
343
+ }
344
+ }
345
+
346
+ const sanitizedProjectName = ErrorHandler.sanitizeInput(projectName);
347
+ return path.join(outputDir, `${sanitizedProjectName}_rag.json`);
348
+ }
349
+
350
+ /**
351
+ * Determine final output path for PDF
352
+ * @param {string} overridePath - Optional override path from CLI
353
+ * @param {string} projectName - Project name
354
+ * @returns {string} Final output path
355
+ */
356
+ determineOutputPath(overridePath, projectName) {
357
+ let outputDir;
358
+
359
+ if (overridePath) {
360
+ // Validate and sanitize override path from CLI
361
+ const sanitizedPath = ErrorHandler.sanitizeInput(overridePath);
362
+ ErrorHandler.validatePath(sanitizedPath, { preventTraversal: true });
363
+
364
+ outputDir = path.resolve(sanitizedPath);
365
+ console.log(chalk.gray(`PDF will be saved to: ${outputDir}`));
366
+ } else {
367
+ // Use config settings
368
+ if (this.config.output.mode === 'relative') {
369
+ outputDir = process.cwd();
370
+ } else {
371
+ outputDir = path.resolve(this.config.output.fixedPath);
372
+ }
373
+ }
374
+
375
+ // Sanitize project name for filename
376
+ const sanitizedProjectName = ErrorHandler.sanitizeInput(projectName);
377
+
378
+ return PDFGenerator.generateOutputPath(sanitizedProjectName, outputDir);
379
+ }
380
+
381
+ /**
382
+ * Display RAG completion summary
383
+ * @param {string} outputPath - Generated RAG JSON path
384
+ * @param {Array} selectedExtensions - Selected extensions
385
+ * @param {number} totalFiles - Total files processed
386
+ * @param {number} totalChunks - Number of chunks generated
387
+ */
388
+ async displayRagCompletionSummary(outputPath, selectedExtensions, totalFiles, totalChunks) {
389
+ const stats = await fs.stat(outputPath);
390
+ const fileSizeFormatted = this.formatFileSize(stats.size);
391
+
392
+ console.log(chalk.green('\nSUCCESS: RAG-optimized output generated successfully!\n'));
393
+ console.log(chalk.cyan('Summary:'));
394
+ console.log(chalk.gray(` Output: ${outputPath}`));
395
+ console.log(chalk.gray(` Extensions: ${selectedExtensions.join(', ')}`));
396
+ console.log(chalk.gray(` Total files: ${totalFiles}`));
397
+ console.log(chalk.gray(` Total chunks: ${totalChunks}`));
398
+ console.log(chalk.gray(` JSON size: ${fileSizeFormatted}`));
399
+ console.log(chalk.gray(` Ready for RAG/LLM ingestion`));
400
+ console.log();
401
+ }
402
+
403
+ /**
404
+ * Display completion summary
405
+ * @param {string} outputPath - Generated PDF path
406
+ * @param {Array} selectedExtensions - Selected extensions
407
+ * @param {number} totalFiles - Total files processed
408
+ * @param {number|string} pageCount - Number of pages in PDF or 'N/A'
409
+ */
410
+ async displayCompletionSummary(outputPath, selectedExtensions, totalFiles, pageCount) {
411
+ // Get PDF stats
412
+ const stats = await fs.stat(outputPath);
413
+ const fileSizeFormatted = this.formatFileSize(stats.size);
414
+
415
+ console.log(chalk.green('\nSUCCESS: PDF generation completed successfully!\n'));
416
+ console.log(chalk.cyan('Summary:'));
417
+ console.log(chalk.gray(` Output: ${outputPath}`));
418
+ console.log(chalk.gray(` Extensions: ${selectedExtensions.join(', ')}`));
419
+ console.log(chalk.gray(` Total files: ${totalFiles}`));
420
+ if (pageCount !== 'N/A') {
421
+ console.log(chalk.gray(` Total pages: ${pageCount}`));
422
+ }
423
+ console.log(chalk.gray(` PDF size: ${fileSizeFormatted}`));
424
+ console.log();
425
+ }
426
+
427
+ /**
428
+ * Show current configuration
429
+ */
430
+ async showConfig() {
431
+ const config = await this.configManager.loadConfig();
432
+ if (config) {
433
+ this.configManager.displayConfig(config);
434
+ } else {
435
+ console.log(chalk.yellow('WARNING: No configuration found. Run codesummary to set up.'));
436
+ }
437
+ }
438
+
439
+ /**
440
+ * Reset configuration
441
+ */
442
+ async resetConfig() {
443
+ await this.configManager.resetConfig();
444
+ console.log(chalk.green('SUCCESS: Configuration reset. Run codesummary to set up again.'));
445
+ }
446
+
447
+ /**
448
+ * Edit configuration interactively
449
+ */
450
+ async editConfig() {
451
+ let config = await this.configManager.loadConfig();
452
+
453
+ if (!config) {
454
+ console.log(chalk.yellow('WARNING: No configuration found. Running first-time setup...'));
455
+ config = await this.configManager.runFirstTimeSetup();
456
+ } else {
457
+ config = await this.configManager.editConfig(config);
458
+ }
459
+ }
460
+
461
+ /**
462
+ * Format file size in human readable format
463
+ * @param {number} bytes - Size in bytes
464
+ * @returns {string} Formatted size
465
+ */
466
+ formatFileSize(bytes) {
467
+ const units = ['B', 'KB', 'MB', 'GB'];
468
+ let size = bytes;
469
+ let unitIndex = 0;
470
+
471
+ while (size >= 1024 && unitIndex < units.length - 1) {
472
+ size /= 1024;
473
+ unitIndex++;
474
+ }
475
+
476
+ return `${size.toFixed(1)} ${units[unitIndex]}`;
477
+ }
478
+
479
+ /**
480
+ * Show help information
481
+ */
482
+ showHelp() {
483
+ console.log(chalk.cyan('\nCodeSummary - Generate PDF documentation from source code\n'));
484
+
485
+ console.log(chalk.white('Usage:'));
486
+ console.log(' codesummary [options] Scan current directory and generate PDF');
487
+ console.log(' codesummary config Edit configuration settings');
488
+ console.log();
489
+
490
+ console.log(chalk.white('Options:'));
491
+ console.log(' -o, --output <path> Override output directory');
492
+ console.log(' -f, --format <format> Output format: pdf (default) or rag');
493
+ console.log(' --show-config Display current configuration');
494
+ console.log(' --reset-config Reset configuration to defaults');
495
+ console.log(' -h, --help Show this help message');
496
+ console.log();
497
+
498
+ console.log(chalk.white('Examples:'));
499
+ console.log(' codesummary Scan current project (PDF)');
500
+ console.log(' codesummary --format rag Generate RAG-optimized JSON');
501
+ console.log(' codesummary --output ./docs Save output to ./docs folder');
502
+ console.log(' codesummary config Edit settings');
503
+ console.log(' codesummary --show-config View current settings');
504
+ console.log();
505
+
506
+ console.log(chalk.gray('For more information, visit: https://github.com/skamoll/CodeSummary'));
507
+ }
508
+ }
509
+
392
510
  export default CLI;