1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use crate::model::Severity;
#[derive(Debug, Parser)]
#[command(
name = "reptr",
version,
about = "Local-first pentest report generator",
long_about = "Write your pentest findings as Markdown files. Run `reptr build`. \
Get a polished HTML, JSON, DOCX, and PDF report. \
No Docker, no database, no SaaS."
)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
/// Scaffold a new engagement directory.
New {
/// Engagement slug (also used as directory name).
name: String,
},
/// Add a new finding, stub or otherwise.
Add {
#[command(subcommand)]
what: AddTarget,
},
/// Parse, validate, and render the engagement in `path` (default: cwd).
Build {
#[arg(default_value = ".")]
path: PathBuf,
},
/// Build once, then auto-rebuild whenever findings, templates, or config change.
Watch {
#[arg(default_value = ".")]
path: PathBuf,
},
/// Summarise findings across every engagement directory under `path`.
Stats {
#[arg(default_value = ".")]
path: PathBuf,
/// Output format.
#[arg(long, value_enum, default_value_t = StatsFormat::Text)]
format: StatsFormat,
},
/// Manage and inspect the finding-template library.
Library {
#[command(subcommand)]
action: LibraryAction,
},
/// Build the engagement, diff findings against the previous build, and emit a delta report.
///
/// On the first run `reptr retest` establishes a baseline (same as `reptr build`).
/// On every subsequent run it shows which findings are new, resolved, regressed, or changed
/// and writes `output/<slug>-retest.{html,json}`.
Retest {
#[arg(default_value = ".")]
path: PathBuf,
},
}
#[derive(Debug, Subcommand)]
pub enum LibraryAction {
/// List every template in the library directory.
List {
/// Engagement root (default: cwd) — used to resolve [library].path.
#[arg(default_value = ".")]
path: PathBuf,
},
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum StatsFormat {
Text,
Json,
}
#[derive(Debug, Subcommand)]
pub enum AddTarget {
/// Add a finding stub at `findings/NNN-<slug>.md`.
Finding {
/// Finding title (optional when --from is supplied; falls back to the template's title).
title: Option<String>,
/// Severity — one of critical|high|medium|low|info. Ignored when --from is set
/// and the library template defines its own severity.
#[arg(long, value_enum, default_value_t = SeverityArg::Medium)]
severity: SeverityArg,
/// Import from a finding-library template by name (e.g. `xss-stored` or `web/xss-stored`).
#[arg(long)]
from: Option<String>,
/// Engagement root (default: cwd).
#[arg(long, default_value = ".")]
path: PathBuf,
},
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum SeverityArg {
Critical,
High,
Medium,
Low,
Info,
}
impl From<SeverityArg> for Severity {
fn from(value: SeverityArg) -> Self {
match value {
SeverityArg::Critical => Severity::Critical,
SeverityArg::High => Severity::High,
SeverityArg::Medium => Severity::Medium,
SeverityArg::Low => Severity::Low,
SeverityArg::Info => Severity::Info,
}
}
}