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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
//! Command line. `scan` accepts keep-going and package filters.
use std::path::PathBuf;
use clap::{ArgAction, Parser, Subcommand, ValueEnum};
/// Prioritize vulnerabilities from lockfiles using OSV, EPSS, and CISA KEV.
#[derive(Debug, Parser)]
#[command(name = "patchsense", version, about, arg_required_else_help = true)]
pub struct Cli {
/// Subcommand.
#[command(subcommand)]
pub command: Command,
}
/// PatchSense commands.
#[derive(Debug, Subcommand)]
pub enum Command {
/// Scan lockfiles and print matching advisories.
Scan(ScanArgs),
/// Inspect or delete the local SQLite cache.
Cache(CacheArgs),
/// Write an example config, agent instructions, or MCP snippets.
Init(InitArgs),
/// Create or update a pull-request comment from a JSON report.
Comment(CommentArgs),
/// Run the MCP server on stdio.
Mcp,
}
/// Arguments for `patchsense cache`.
#[derive(Debug, Parser)]
pub struct CacheArgs {
/// Directory that contains `cache.db`. Overrides `PATCHSENSE_CACHE_DIR`.
///
/// Global so `cache info --cache-dir DIR` is accepted as well as
/// `cache --cache-dir DIR info`.
#[arg(long, global = true)]
pub cache_dir: Option<PathBuf>,
/// Cache subcommand.
#[command(subcommand)]
pub command: CacheCommand,
}
/// Arguments for `patchsense comment`.
#[derive(Debug, Parser)]
pub struct CommentArgs {
/// JSON report from `patchsense scan --format json`.
#[arg(long, value_name = "FILE")]
pub report: PathBuf,
/// `auto`, `github`, or `gitlab`. `auto` follows `GITHUB_ACTIONS` or `GITLAB_CI`.
#[arg(long, value_name = "PROVIDER", default_value = "auto")]
pub provider: String,
/// Findings included in the comment table. Defaults to 20.
#[arg(long, value_name = "N")]
pub top: Option<usize>,
/// Print the markdown and do not call the API.
#[arg(long)]
pub dry_run: bool,
}
/// Arguments for `patchsense init`.
#[derive(Debug, Parser)]
pub struct InitArgs {
/// Replace an existing `patchsense.toml`.
#[arg(long)]
pub force: bool,
/// Write the instruction block for `all` (the default), `codex`, `claude`, or `cursor`.
///
/// `--agent` with no value means `all`.
#[arg(
long,
num_args = 0..=1,
default_missing_value = "all",
value_name = "AGENT"
)]
pub agent: Option<String>,
/// Print MCP snippets for Cursor, Claude Code, and Codex. Does not write a config file.
#[arg(long)]
pub print_mcp_config: bool,
}
/// `patchsense cache` subcommands.
#[derive(Debug, Subcommand)]
pub enum CacheCommand {
/// Delete `cache.db`.
Clear,
/// Print the cache path, schema version, and row counts.
Info,
}
/// Arguments for `patchsense scan`.
#[derive(Debug, Parser)]
pub struct ScanArgs {
/// Directory or lockfile to scan. Defaults to the current directory.
pub path: Option<PathBuf>,
/// Continue after a lockfile fails to parse. This is the default.
#[arg(long, action = ArgAction::SetTrue, overrides_with = "no_keep_going")]
pub keep_going: bool,
/// Stop at the first lockfile that cannot be read or parsed.
#[arg(long, action = ArgAction::SetTrue, overrides_with = "keep_going")]
pub no_keep_going: bool,
/// Ecosystems to scan, comma-separated (`npm,pypi`). Repeatable.
#[arg(long, value_delimiter = ',', value_name = "LIST")]
pub ecosystem: Vec<String>,
/// Skip dependencies the parser marked as dev.
#[arg(long)]
pub no_dev: bool,
/// Use only the local cache. Do not call OSV.
#[arg(long)]
pub offline: bool,
/// With `--offline`, exit 2 when an EPSS row or the KEV catalog is past its TTL.
///
/// Without this flag, those rows are used and the report warns that they are stale.
#[arg(long)]
pub strict_cache: bool,
/// Do not read or write the cache.
#[arg(long)]
pub no_cache: bool,
/// Directory that contains `cache.db`. Overrides `PATCHSENSE_CACHE_DIR`.
#[arg(long)]
pub cache_dir: Option<PathBuf>,
/// Path to `patchsense.toml`. Overrides discovery and `PATCHSENSE_CONFIG`.
#[arg(long, value_name = "FILE")]
pub config: Option<PathBuf>,
/// Exit when a finding reaches this bar: critical, review, any, never, or 0-100.
#[arg(long, value_name = "WHEN")]
pub fail_on: Option<String>,
/// Extra paths to skip. Repeatable. Added to `scan.exclude` from the config file.
#[arg(long, action = ArgAction::Append)]
pub exclude: Vec<String>,
/// Hide findings below this score. Does not change the exit code.
#[arg(long, value_name = "N")]
pub min_score: Option<u32>,
/// Do not color the table. Also honored when `NO_COLOR` is set.
#[arg(long)]
pub no_color: bool,
/// Report format. Repeatable. Paired with `--output` in the same order.
///
/// With no `--format`, a TTY gets `table` and a pipe or file gets `json`.
#[arg(
short = 'f',
long = "format",
value_enum,
action = ArgAction::Append,
value_name = "FORMAT"
)]
pub format: Vec<ReportFormat>,
/// Write the paired `--format` to this file instead of stdout. Repeatable.
#[arg(short = 'o', long = "output", action = ArgAction::Append, value_name = "FILE")]
pub output: Vec<PathBuf>,
/// Ask a local coding agent to explain the top findings.
#[arg(long)]
pub explain: bool,
/// Agent for `--explain`: `auto`, `codex`, `claude`, or `cursor`.
#[arg(long, value_name = "AGENT")]
pub agent: Option<String>,
/// How many findings to send to the agent. Defaults to the config value, usually 10.
#[arg(long, value_name = "N")]
pub explain_top: Option<u32>,
}
/// One scan report format.
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum ReportFormat {
/// Priority table.
Table,
/// PRD JSON report.
Json,
/// SARIF 2.1.0.
Sarif,
/// Markdown comment body.
Markdown,
/// GitLab dependency scanning report.
Gitlab,
}