feat: add configurable output directory with --output flag (#41)

* feat: add configurable output directory with --output flag

Add --output CLI flag to specify custom output directory for session
folders containing audit logs, prompts, agent logs, and deliverables.

Changes:
- Add --output <path> CLI flag parsing
- Update generateAuditPath() to use custom path when provided
- Add consolidateOutputs() to copy deliverables to session folder
- Update Docker examples with volume mounts for output directories
- Default remains ./audit-logs/ when --output is not specified

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add configurable output directory with --output flag

Add --output CLI flag to specify custom output directory for session
folders containing audit logs, prompts, agent logs, and deliverables.

Changes:
- Add --output <path> CLI flag parsing
- Store outputPath in Session interface for persistence
- Update generateAuditPath() to use custom path when provided
- Pass outputPath through pre-recon and checkpoint-manager
- Add consolidateOutputs() to copy deliverables to session folder
- Update Docker examples with volume mount instructions
- Default remains ./audit-logs/ when --output is not specified

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: add gitkeep and fix formatting

* fix: correct docker run command formatting in README

Remove invalid inline comments after backslash continuations in docker
run commands. Comments cannot appear after backslash line continuations
in shell scripts, as the backslash escapes the newline character.

Reorganized comments to appear on separate lines before or after the
command block for better clarity and proper shell syntax.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
ezl-keygraph
2026-01-08 23:50:42 +05:30
committed by GitHub
parent dd18f4629b
commit 264b16991a
9 changed files with 102 additions and 25 deletions

View File

@@ -15,13 +15,14 @@ npm install
### Running the Penetration Testing Agent ### Running the Penetration Testing Agent
```bash ```bash
shannon <WEB_URL> <REPO_PATH> --config <CONFIG_FILE> shannon <WEB_URL> <REPO_PATH> [--config <CONFIG_FILE>] [--output <OUTPUT_DIR>]
``` ```
Example: Example:
```bash ```bash
shannon "https://example.com" "/path/to/local/repo" shannon "https://example.com" "/path/to/local/repo"
shannon "https://juice-shop.herokuapp.com" "/home/user/juice-shop" --config juice-shop-config.yaml shannon "https://juice-shop.herokuapp.com" "/home/user/juice-shop" --config juice-shop-config.yaml
shannon "https://example.com" "/path/to/repo" --output /path/to/reports
``` ```
### Alternative Execution ### Alternative Execution
@@ -194,10 +195,11 @@ The agent implements a sophisticated checkpoint system using git:
The agent implements a crash-safe, self-healing audit system (v3.0) with the following guarantees: The agent implements a crash-safe, self-healing audit system (v3.0) with the following guarantees:
**Architecture:** **Architecture:**
- **audit-logs/**: Centralized metrics and forensic logs (source of truth) - **audit-logs/** (or custom `--output` path): Centralized metrics and forensic logs (source of truth)
- `{hostname}_{sessionId}/session.json` - Comprehensive metrics with attempt-level detail - `{hostname}_{sessionId}/session.json` - Comprehensive metrics with attempt-level detail
- `{hostname}_{sessionId}/prompts/` - Exact prompts used for reproducibility - `{hostname}_{sessionId}/prompts/` - Exact prompts used for reproducibility
- `{hostname}_{sessionId}/agents/` - Turn-by-turn execution logs - `{hostname}_{sessionId}/agents/` - Turn-by-turn execution logs
- `{hostname}_{sessionId}/deliverables/` - Security reports and findings
- **.shannon-store.json**: Minimal orchestration state (completedAgents, checkpoints) - **.shannon-store.json**: Minimal orchestration state (completedAgents, checkpoints)
**Crash Safety:** **Crash Safety:**
@@ -287,13 +289,15 @@ dist/ # Compiled JavaScript output
└── ... # Other compiled files └── ... # Other compiled files
package.json # Node.js dependencies package.json # Node.js dependencies
.shannon-store.json # Orchestration state (minimal) .shannon-store.json # Orchestration state (minimal)
audit-logs/ # Centralized audit data (v3.0) audit-logs/ # Centralized audit data (default, or use --output)
└── {hostname}_{sessionId}/ └── {hostname}_{sessionId}/
├── session.json # Comprehensive metrics ├── session.json # Comprehensive metrics
├── prompts/ # Prompt snapshots ├── prompts/ # Prompt snapshots
│ └── {agent}.md │ └── {agent}.md
── agents/ # Agent execution logs ── agents/ # Agent execution logs
└── {timestamp}_{agent}_attempt-{N}.log └── {timestamp}_{agent}_attempt-{N}.log
└── deliverables/ # Security reports and findings
└── ...
configs/ # Configuration files configs/ # Configuration files
├── config-schema.json # JSON Schema validation ├── config-schema.json # JSON Schema validation
├── example-config.yaml # Template configuration ├── example-config.yaml # Template configuration

View File

@@ -169,10 +169,15 @@ docker run --rm -it \
-e CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 \ -e CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 \
-v "$(pwd)/repos:/app/repos" \ -v "$(pwd)/repos:/app/repos" \
-v "$(pwd)/configs:/app/configs" \ -v "$(pwd)/configs:/app/configs" \
# Comment below line if using custom output directory
-v "$(pwd)/audit-logs:/app/audit-logs" \
shannon:latest \ shannon:latest \
"https://your-app.com/" \ "https://your-app.com/" \
"/app/repos/your-app" \ "/app/repos/your-app" \
--config /app/configs/example-config.yaml --config /app/configs/example-config.yaml
# Optional: uncomment below for custom output directory
# -v "$(pwd)/reports:/app/reports" \
# --output /app/reports
``` ```
**With Anthropic API Key:** **With Anthropic API Key:**
@@ -186,10 +191,15 @@ docker run --rm -it \
-e CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 \ -e CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 \
-v "$(pwd)/repos:/app/repos" \ -v "$(pwd)/repos:/app/repos" \
-v "$(pwd)/configs:/app/configs" \ -v "$(pwd)/configs:/app/configs" \
# Comment below line if using custom output directory
-v "$(pwd)/audit-logs:/app/audit-logs" \
shannon:latest \ shannon:latest \
"https://your-app.com/" \ "https://your-app.com/" \
"/app/repos/your-app" \ "/app/repos/your-app" \
--config /app/configs/example-config.yaml --config /app/configs/example-config.yaml
# Optional: uncomment below for custom output directory
# -v "$(pwd)/reports:/app/reports" \
# --output /app/reports
``` ```
#### Platform-Specific Instructions #### Platform-Specific Instructions
@@ -217,10 +227,15 @@ docker run --rm -it \
-e CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 \ -e CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 \
-v "$(pwd)/repos:/app/repos" \ -v "$(pwd)/repos:/app/repos" \
-v "$(pwd)/configs:/app/configs" \ -v "$(pwd)/configs:/app/configs" \
# Comment below line if using custom output directory
-v "$(pwd)/audit-logs:/app/audit-logs" \
shannon:latest \ shannon:latest \
"http://host.docker.internal:3000" \ "http://host.docker.internal:3000" \
"/app/repos/your-app" \ "/app/repos/your-app" \
--config /app/configs/example-config.yaml --config /app/configs/example-config.yaml
# Optional: uncomment below for custom output directory
# -v "$(pwd)/reports:/app/reports" \
# --output /app/reports
``` ```
### Configuration (Optional) ### Configuration (Optional)
@@ -281,7 +296,7 @@ docker run --rm shannon:latest --status
### Output and Results ### Output and Results
All analysis results are saved to the `deliverables/` directory: All results are saved to `./audit-logs/` by default. Use `--output <path>` to specify a custom directory. If using `--output`, ensure that path is mounted to an accessible host directory (e.g., `-v "$(pwd)/custom-directory:/app/reports"`).
- **Pre-reconnaissance reports** - External scan results - **Pre-reconnaissance reports** - External scan results
- **Vulnerability assessments** - Potential vulnerabilities from thorough code analysis and network mapping - **Vulnerability assessments** - Potential vulnerabilities from thorough code analysis and network mapping

0
audit-logs/.gitkeep Normal file
View File

View File

@@ -26,6 +26,7 @@ export interface SessionMetadata {
id: string; id: string;
webUrl: string; webUrl: string;
repoPath?: string; repoPath?: string;
outputPath?: string;
[key: string]: unknown; [key: string]: unknown;
} }
@@ -40,10 +41,12 @@ export function generateSessionIdentifier(sessionMetadata: SessionMetadata): str
/** /**
* Generate path to audit log directory for a session * Generate path to audit log directory for a session
* Uses custom outputPath if provided, otherwise defaults to AUDIT_LOGS_DIR
*/ */
export function generateAuditPath(sessionMetadata: SessionMetadata): string { export function generateAuditPath(sessionMetadata: SessionMetadata): string {
const sessionIdentifier = generateSessionIdentifier(sessionMetadata); const sessionIdentifier = generateSessionIdentifier(sessionMetadata);
return path.join(AUDIT_LOGS_DIR, sessionIdentifier); const baseDir = sessionMetadata.outputPath || AUDIT_LOGS_DIR;
return path.join(baseDir, sessionIdentifier);
} }
/** /**

View File

@@ -293,7 +293,7 @@ const runSingleAgent = async (
AGENTS[agentName]!.displayName, AGENTS[agentName]!.displayName,
agentName, agentName,
getAgentColor(agentName), getAgentColor(agentName),
{ id: currentSession.id, webUrl: currentSession.webUrl, repoPath: currentSession.repoPath } { id: currentSession.id, webUrl: currentSession.webUrl, repoPath: currentSession.repoPath, ...(currentSession.outputPath && { outputPath: currentSession.outputPath }) }
); );
if (!result.success) { if (!result.success) {
@@ -727,7 +727,8 @@ export const rollbackTo = async (targetAgent: string, session: Session): Promise
const sessionMetadata: SessionMetadata = { const sessionMetadata: SessionMetadata = {
id: session.id, id: session.id,
webUrl: session.webUrl, webUrl: session.webUrl,
repoPath: session.repoPath repoPath: session.repoPath,
...(session.outputPath && { outputPath: session.outputPath })
}; };
const auditSession = new AuditSession(sessionMetadata); const auditSession = new AuditSession(sessionMetadata);
await auditSession.initialize(); await auditSession.initialize();

View File

@@ -33,6 +33,9 @@ export function showHelp(): void {
console.log( console.log(
' --config <file> YAML configuration file for authentication and testing parameters' ' --config <file> YAML configuration file for authentication and testing parameters'
); );
console.log(
' --output <path> Custom output directory for session folder (default: ./audit-logs/)'
);
console.log( console.log(
' --pipeline-testing Use minimal prompts for fast pipeline testing (creates minimal deliverables)' ' --pipeline-testing Use minimal prompts for fast pipeline testing (creates minimal deliverables)'
); );
@@ -55,6 +58,7 @@ export function showHelp(): void {
console.log(' # Normal mode - create new session'); console.log(' # Normal mode - create new session');
console.log(' shannon "https://example.com" "/path/to/local/repo"'); console.log(' shannon "https://example.com" "/path/to/local/repo"');
console.log(' shannon "https://example.com" "/path/to/local/repo" --config auth.yaml'); console.log(' shannon "https://example.com" "/path/to/local/repo" --config auth.yaml');
console.log(' shannon "https://example.com" "/path/to/local/repo" --output /path/to/reports');
console.log( console.log(
' shannon "https://example.com" "/path/to/local/repo" --setup-only # Setup only\n' ' shannon "https://example.com" "/path/to/local/repo" --setup-only # Setup only\n'
); );

View File

@@ -137,7 +137,8 @@ async function runPreReconWave1(
variables: PromptVariables, variables: PromptVariables,
config: DistributedConfig | null, config: DistributedConfig | null,
pipelineTestingMode: boolean = false, pipelineTestingMode: boolean = false,
sessionId: string | null = null sessionId: string | null = null,
outputPath: string | null = null
): Promise<Wave1Results> { ): Promise<Wave1Results> {
console.log(chalk.blue(' → Launching Wave 1 operations in parallel...')); console.log(chalk.blue(' → Launching Wave 1 operations in parallel...'));
@@ -155,7 +156,7 @@ async function runPreReconWave1(
AGENTS['pre-recon'].displayName, AGENTS['pre-recon'].displayName,
'pre-recon', // Agent name for snapshot creation 'pre-recon', // Agent name for snapshot creation
chalk.cyan, chalk.cyan,
{ id: sessionId!, webUrl, repoPath: sourceDir } // Session metadata for audit logging (STANDARD: use 'id' field) { id: sessionId!, webUrl, repoPath: sourceDir, ...(outputPath && { outputPath }) } // Session metadata for audit logging (STANDARD: use 'id' field)
) )
); );
const [codeAnalysis] = await Promise.all(operations); const [codeAnalysis] = await Promise.all(operations);
@@ -178,7 +179,7 @@ async function runPreReconWave1(
AGENTS['pre-recon'].displayName, AGENTS['pre-recon'].displayName,
'pre-recon', // Agent name for snapshot creation 'pre-recon', // Agent name for snapshot creation
chalk.cyan, chalk.cyan,
{ id: sessionId!, webUrl, repoPath: sourceDir } // Session metadata for audit logging (STANDARD: use 'id' field) { id: sessionId!, webUrl, repoPath: sourceDir, ...(outputPath && { outputPath }) } // Session metadata for audit logging (STANDARD: use 'id' field)
) )
); );
} }
@@ -357,13 +358,14 @@ export async function executePreReconPhase(
config: DistributedConfig | null, config: DistributedConfig | null,
toolAvailability: ToolAvailability, toolAvailability: ToolAvailability,
pipelineTestingMode: boolean, pipelineTestingMode: boolean,
sessionId: string | null = null sessionId: string | null = null,
outputPath: string | null = null
): Promise<PreReconResult> { ): Promise<PreReconResult> {
console.log(chalk.yellow.bold('\n🔍 PHASE 1: PRE-RECONNAISSANCE')); console.log(chalk.yellow.bold('\n🔍 PHASE 1: PRE-RECONNAISSANCE'));
const timer = new Timer('phase-1-pre-recon'); const timer = new Timer('phase-1-pre-recon');
console.log(chalk.yellow('Wave 1: Initial footprinting...')); console.log(chalk.yellow('Wave 1: Initial footprinting...'));
const wave1Results = await runPreReconWave1(webUrl, sourceDir, variables, config, pipelineTestingMode, sessionId); const wave1Results = await runPreReconWave1(webUrl, sourceDir, variables, config, pipelineTestingMode, sessionId, outputPath);
console.log(chalk.green(' ✅ Wave 1 operations completed')); console.log(chalk.green(' ✅ Wave 1 operations completed'));
console.log(chalk.yellow('Wave 2: Additional scanning...')); console.log(chalk.yellow('Wave 2: Additional scanning...'));

View File

@@ -41,6 +41,7 @@ export interface Session {
repoPath: string; repoPath: string;
configFile: string | null; configFile: string | null;
targetRepo: string; targetRepo: string;
outputPath: string | null;
status: 'in-progress' | 'completed' | 'failed'; status: 'in-progress' | 'completed' | 'failed';
completedAgents: AgentName[]; completedAgents: AgentName[];
failedAgents: AgentName[]; failedAgents: AgentName[];
@@ -265,7 +266,8 @@ export const createSession = async (
webUrl: string, webUrl: string,
repoPath: string, repoPath: string,
configFile: string | null = null, configFile: string | null = null,
targetRepo: string | null = null targetRepo: string | null = null,
outputPath: string | null = null
): Promise<Session> => { ): Promise<Session> => {
// Use targetRepo if provided, otherwise use repoPath // Use targetRepo if provided, otherwise use repoPath
const resolvedTargetRepo = targetRepo || repoPath; const resolvedTargetRepo = targetRepo || repoPath;
@@ -279,9 +281,12 @@ export const createSession = async (
console.log(chalk.blue(`📝 Reusing existing session: ${existingSession.id.substring(0, 8)}...`)); console.log(chalk.blue(`📝 Reusing existing session: ${existingSession.id.substring(0, 8)}...`));
console.log(chalk.gray(` Progress: ${existingSession.completedAgents.length}/${Object.keys(AGENTS).length} agents completed`)); console.log(chalk.gray(` Progress: ${existingSession.completedAgents.length}/${Object.keys(AGENTS).length} agents completed`));
// Update last activity timestamp // Update last activity timestamp and outputPath if provided
await updateSession(existingSession.id, { lastActivity: new Date().toISOString() }); await updateSession(existingSession.id, {
return existingSession; lastActivity: new Date().toISOString(),
...(outputPath && { outputPath })
});
return { ...existingSession, ...(outputPath && { outputPath }) };
} }
// If completed, create a new session (allows re-running after completion) // If completed, create a new session (allows re-running after completion)
@@ -298,6 +303,7 @@ export const createSession = async (
repoPath, repoPath,
configFile, configFile,
targetRepo: resolvedTargetRepo, targetRepo: resolvedTargetRepo,
outputPath,
status: 'in-progress', status: 'in-progress',
completedAgents: [], completedAgents: [],
failedAgents: [], failedAgents: [],

View File

@@ -80,6 +80,27 @@ interface MainResult {
// Configure zx to disable timeouts (let tools run as long as needed) // Configure zx to disable timeouts (let tools run as long as needed)
$.timeout = 0; $.timeout = 0;
/**
* Consolidate deliverables from target repo into the session folder
* Copies deliverables directory from source repo to session audit path
*/
async function consolidateOutputs(sourceDir: string, sessionPath: string): Promise<void> {
const srcDeliverables = path.join(sourceDir, 'deliverables');
const destDeliverables = path.join(sessionPath, 'deliverables');
try {
if (await fs.pathExists(srcDeliverables)) {
await fs.copy(srcDeliverables, destDeliverables, { overwrite: true });
console.log(chalk.gray(`📄 Deliverables copied to session folder`));
} else {
console.log(chalk.yellow(`⚠️ No deliverables directory found at ${srcDeliverables}`));
}
} catch (error) {
const err = error as Error;
console.log(chalk.yellow(`⚠️ Failed to consolidate deliverables: ${err.message}`));
}
}
// Setup graceful cleanup on process signals // Setup graceful cleanup on process signals
process.on('SIGINT', async () => { process.on('SIGINT', async () => {
console.log(chalk.yellow('\n⚠ Received SIGINT, cleaning up...')); console.log(chalk.yellow('\n⚠ Received SIGINT, cleaning up...'));
@@ -99,7 +120,8 @@ async function main(
repoPath: string, repoPath: string,
configPath: string | null = null, configPath: string | null = null,
pipelineTestingMode: boolean = false, pipelineTestingMode: boolean = false,
disableLoader: boolean = false disableLoader: boolean = false,
outputPath: string | null = null
): Promise<MainResult> { ): Promise<MainResult> {
// Set global flag for loader control // Set global flag for loader control
global.SHANNON_DISABLE_LOADER = disableLoader; global.SHANNON_DISABLE_LOADER = disableLoader;
@@ -116,6 +138,9 @@ async function main(
if (configPath) { if (configPath) {
console.log(chalk.cyan(`⚙️ Config: ${configPath}`)); console.log(chalk.cyan(`⚙️ Config: ${configPath}`));
} }
if (outputPath) {
console.log(chalk.cyan(`📂 Output: ${outputPath}`));
}
console.log(chalk.gray('─'.repeat(60))); console.log(chalk.gray('─'.repeat(60)));
// Parse configuration if provided // Parse configuration if provided
@@ -166,7 +191,7 @@ async function main(
const variables: PromptVariables = { webUrl, repoPath, sourceDir }; const variables: PromptVariables = { webUrl, repoPath, sourceDir };
// Create session for tracking (in normal mode) // Create session for tracking (in normal mode)
const session: Session = await createSession(webUrl, repoPath, configPath, sourceDir); const session: Session = await createSession(webUrl, repoPath, configPath, sourceDir, outputPath);
console.log(chalk.blue(`📝 Session created: ${session.id.substring(0, 8)}...`)); console.log(chalk.blue(`📝 Session created: ${session.id.substring(0, 8)}...`));
// If setup-only mode, exit after session creation // If setup-only mode, exit after session creation
@@ -243,7 +268,8 @@ async function main(
distributedConfig, distributedConfig,
toolAvailability, toolAvailability,
pipelineTestingMode, pipelineTestingMode,
session.id // Pass session ID for logging session.id, // Pass session ID for logging
outputPath // Pass output path for audit logging
); );
timingResults.phases['pre-recon'] = preReconDuration; timingResults.phases['pre-recon'] = preReconDuration;
await updateSessionProgress('pre-recon'); await updateSessionProgress('pre-recon');
@@ -262,7 +288,7 @@ async function main(
AGENTS['recon'].displayName, AGENTS['recon'].displayName,
'recon', // Agent name for snapshot creation 'recon', // Agent name for snapshot creation
chalk.cyan, chalk.cyan,
{ id: session.id, webUrl, repoPath: sourceDir } // Session metadata for audit logging (STANDARD: use 'id' field) { id: session.id, webUrl, repoPath: sourceDir, ...(outputPath && { outputPath }) } // Session metadata for audit logging (STANDARD: use 'id' field)
); );
const reconDuration = reconTimer.stop(); const reconDuration = reconTimer.stop();
timingResults.phases['recon'] = reconDuration; timingResults.phases['recon'] = reconDuration;
@@ -345,7 +371,7 @@ async function main(
'Executive Summary and Report Cleanup', 'Executive Summary and Report Cleanup',
'report', // Agent name for snapshot creation 'report', // Agent name for snapshot creation
chalk.cyan, chalk.cyan,
{ id: session.id, webUrl, repoPath: sourceDir } // Session metadata for audit logging (STANDARD: use 'id' field) { id: session.id, webUrl, repoPath: sourceDir, ...(outputPath && { outputPath }) } // Session metadata for audit logging (STANDARD: use 'id' field)
); );
const reportDuration = reportTimer.stop(); const reportDuration = reportTimer.stop();
@@ -380,7 +406,11 @@ async function main(
console.log(chalk.gray('─'.repeat(60))); console.log(chalk.gray('─'.repeat(60)));
// Calculate audit logs path // Calculate audit logs path
const auditLogsPath = generateAuditPath({ id: session.id, webUrl: session.webUrl, repoPath: session.repoPath }); const auditLogsPath = generateAuditPath({ id: session.id, webUrl: session.webUrl, repoPath: session.repoPath, ...(outputPath && { outputPath }) });
// Consolidate deliverables into the session folder
await consolidateOutputs(sourceDir, auditLogsPath);
console.log(chalk.green(`\n📂 All outputs consolidated: ${auditLogsPath}`));
// Return final report path and audit logs path for clickable output // Return final report path and audit logs path for clickable output
return { return {
@@ -398,6 +428,7 @@ if (args[0] && args[0].includes('shannon')) {
// Parse flags and arguments // Parse flags and arguments
let configPath: string | null = null; let configPath: string | null = null;
let outputPath: string | null = null;
let pipelineTestingMode = false; let pipelineTestingMode = false;
let disableLoader = false; let disableLoader = false;
const nonFlagArgs: string[] = []; const nonFlagArgs: string[] = [];
@@ -413,6 +444,14 @@ for (let i = 0; i < args.length; i++) {
console.log(chalk.red('❌ --config flag requires a file path')); console.log(chalk.red('❌ --config flag requires a file path'));
process.exit(1); process.exit(1);
} }
} else if (args[i] === '--output') {
if (i + 1 < args.length) {
outputPath = path.resolve(args[i + 1]!);
i++; // Skip the next argument
} else {
console.log(chalk.red('❌ --output flag requires a directory path'));
process.exit(1);
}
} else if (args[i] === '--pipeline-testing') { } else if (args[i] === '--pipeline-testing') {
pipelineTestingMode = true; pipelineTestingMode = true;
} else if (args[i] === '--disable-loader') { } else if (args[i] === '--disable-loader') {
@@ -494,6 +533,9 @@ console.log(chalk.green('✅ Input validation passed:'));
console.log(chalk.gray(` Target Web URL: ${webUrl}`)); console.log(chalk.gray(` Target Web URL: ${webUrl}`));
console.log(chalk.gray(` Target Repository: ${repoPathValidation.path}\n`)); console.log(chalk.gray(` Target Repository: ${repoPathValidation.path}\n`));
console.log(chalk.gray(` Config Path: ${configPath}\n`)); console.log(chalk.gray(` Config Path: ${configPath}\n`));
if (outputPath) {
console.log(chalk.gray(` Output Path: ${outputPath}\n`));
}
if (pipelineTestingMode) { if (pipelineTestingMode) {
console.log(chalk.yellow('⚡ PIPELINE TESTING MODE ENABLED - Using minimal test prompts for fast pipeline validation\n')); console.log(chalk.yellow('⚡ PIPELINE TESTING MODE ENABLED - Using minimal test prompts for fast pipeline validation\n'));
} }
@@ -502,7 +544,7 @@ if (disableLoader) {
} }
try { try {
const result = await main(webUrl!, repoPathValidation.path!, configPath, pipelineTestingMode, disableLoader); const result = await main(webUrl!, repoPathValidation.path!, configPath, pipelineTestingMode, disableLoader, outputPath);
console.log(chalk.green.bold('\n📄 FINAL REPORT AVAILABLE:')); console.log(chalk.green.bold('\n📄 FINAL REPORT AVAILABLE:'));
console.log(chalk.cyan(result.reportPath)); console.log(chalk.cyan(result.reportPath));
console.log(chalk.green.bold('\n📂 AUDIT LOGS AVAILABLE:')); console.log(chalk.green.bold('\n📂 AUDIT LOGS AVAILABLE:'));