mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-02-12 17:22:50 +00:00
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:
14
CLAUDE.md
14
CLAUDE.md
@@ -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
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -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
0
audit-logs/.gitkeep
Normal 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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...'));
|
||||||
|
|||||||
@@ -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: [],
|
||||||
|
|||||||
@@ -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:'));
|
||||||
|
|||||||
Reference in New Issue
Block a user