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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
use std::path::PathBuf;
use clap::{ArgAction, Parser, Subcommand};
use crate::commands::show::ShowFormat;
#[derive(Parser)]
#[command(
name = "bv",
about = "A fast, project-scoped tool manager for bioinformatics",
version,
propagate_version = true
)]
pub struct Cli {
#[arg(short, long, global = true)]
pub verbose: bool,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
/// Add one or more tools to bv.toml (and pull their images).
Add {
/// Tool identifiers, optionally with version (e.g. `blast blast@2.15.0 hmmer`).
#[arg(required = true)]
tools: Vec<String>,
/// Registry URL or local path. Overrides BV_REGISTRY env var and bv.toml.
#[arg(long, env = "BV_REGISTRY")]
registry: Option<String>,
/// Skip hardware requirement checks (useful on dev Macs for GPU tools).
#[arg(long)]
ignore_hardware: bool,
/// Allow adding tools that are marked `experimental` in the registry.
#[arg(long)]
allow_experimental: bool,
/// Container backend: `docker`, `apptainer`, or `auto` (default).
#[arg(long, env = "BV_BACKEND")]
backend: Option<String>,
/// Refuse to add tools that do not carry a Sigstore image signature.
#[arg(long)]
require_signed: bool,
/// Maximum concurrent image pulls. Defaults to min(8, num_cpus). Tune
/// higher on fast networks; lower on metered/throttled connections.
#[arg(long, env = "BV_JOBS")]
jobs: Option<usize>,
},
/// Verify a tool manifest against the conformance test suite.
///
/// With no tool argument, walks every tool in the registry and prints a
/// summary table. Useful as a one-shot registry health check.
Conformance {
/// Tool identifier. Omit to walk the entire registry.
tool: Option<String>,
/// Registry URL or local path. Overrides BV_REGISTRY env var.
#[arg(long, env = "BV_REGISTRY")]
registry: Option<String>,
/// Container backend: `docker`, `apptainer`, or `auto`.
#[arg(long, env = "BV_BACKEND")]
backend: Option<String>,
/// In walk mode: only run tools whose id contains this substring.
#[arg(long)]
filter: Option<String>,
/// In walk mode: skip tools that require a GPU.
#[arg(long)]
skip_gpu: bool,
/// In walk mode: skip tools that require reference data.
#[arg(long)]
skip_reference_data: bool,
/// In walk mode: skip tools marked `deprecated = true` in their manifest.
#[arg(long)]
skip_deprecated: bool,
/// In walk mode: number of tools to check concurrently. Each runs in
/// its own blocking task; gated by a semaphore. Tune up on machines
/// with fast network + plenty of disk (8xH100 boxes love `--jobs 16`).
#[arg(long, default_value = "4")]
jobs: usize,
},
/// Search the registry for tools.
Search {
/// Search query (matches tool id, description, and I/O types).
query: String,
/// Tier filter: `all`, `core`, `community`, or `experimental`.
/// Default shows core and community tools.
#[arg(long)]
tier: Option<String>,
/// Registry URL or local path. Overrides BV_REGISTRY env var.
#[arg(long, env = "BV_REGISTRY")]
registry: Option<String>,
/// Maximum number of results to show.
#[arg(long, default_value = "20")]
limit: usize,
},
/// Update installed tools to their latest version in the registry.
///
/// With no arguments, updates all tools declared in bv.toml.
/// Specify tool ids to update only those tools.
///
/// bv update # update all tools
/// bv update colabfold # update one tool
Update {
/// Tool identifiers to update. Omit to update all tools in bv.toml.
tools: Vec<String>,
/// Registry URL or local path. Overrides BV_REGISTRY env var and bv.toml.
#[arg(long, env = "BV_REGISTRY")]
registry: Option<String>,
/// Skip hardware requirement checks.
#[arg(long)]
ignore_hardware: bool,
/// Container backend: `docker`, `apptainer`, or `auto` (default).
#[arg(long, env = "BV_BACKEND")]
backend: Option<String>,
/// Maximum concurrent image pulls. Defaults to min(8, num_cpus).
#[arg(long, env = "BV_JOBS")]
jobs: Option<usize>,
},
/// Remove a tool from bv.toml and bv.lock.
Remove {
/// Tool identifier.
tool: String,
},
/// Run a tool or binary inside its container.
///
/// bv flags (e.g. --backend) must come before the tool/binary name.
/// Everything after the name is forwarded verbatim to the container,
/// including flags like --help and --version.
///
/// bv run blastn -query foo.fa -db nr (binary routing)
/// bv run blast -- blastn -query foo.fa (name the tool explicitly)
/// bv run --backend apptainer blastn -version (bv flag before name)
#[command(disable_help_flag = true, disable_version_flag = true)]
Run {
/// Print help for `bv run` (use before the tool/binary name).
#[arg(short = 'h', action = ArgAction::Help, exclusive = true)]
help: Option<bool>,
/// Tool id or exposed binary name.
tool: String,
/// Container backend: `docker`, `apptainer`, or `auto` (default).
#[arg(long, env = "BV_BACKEND")]
backend: Option<String>,
/// Arguments forwarded verbatim to the container entrypoint.
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
/// List tools installed in this project.
List {
/// Show binary routing table instead of tool list.
#[arg(long)]
binaries: bool,
/// Show per-tool layer breakdown (factored tools only).
#[arg(long)]
layers: bool,
},
/// Show which tools include a given conda package.
///
/// For factored tools, names the exact layer and its digest.
/// For legacy (squashed) images, reports that the package provenance
/// is not tracked; the package may still be present.
///
/// Example: `bv why openssl`
Why {
/// Conda package name to search for (case-insensitive substring match).
package: String,
},
/// Show typed I/O schema and metadata for a tool.
Show {
/// Tool identifier.
tool: String,
/// Output format.
#[arg(long, value_enum)]
format: Option<ShowFormat>,
},
/// Show detailed lockfile information about an installed tool.
Info {
/// Tool identifier.
tool: String,
},
/// Resolve bv.toml and write (or check) bv.lock.
Lock {
/// Exit 1 if bv.lock would change; useful in CI.
#[arg(long)]
check: bool,
/// Registry URL or local path. Overrides BV_REGISTRY env var and bv.toml.
#[arg(long, env = "BV_REGISTRY")]
registry: Option<String>,
},
/// Pull every image in bv.lock, making the project runnable offline.
Sync {
/// Fail if bv.toml and bv.lock are not consistent with each other.
#[arg(long)]
frozen: bool,
/// Registry URL (used only for drift detection; optional).
#[arg(long, env = "BV_REGISTRY")]
registry: Option<String>,
/// Container backend: `docker`, `apptainer`, or `auto` (default).
#[arg(long, env = "BV_BACKEND")]
backend: Option<String>,
/// Maximum concurrent image pulls. Defaults to min(8, num_cpus).
#[arg(long, env = "BV_JOBS")]
jobs: Option<usize>,
},
/// Check that the environment is correctly configured.
Doctor,
/// Reference data management.
#[command(subcommand)]
Data(DataCommands),
/// Local cache management.
#[command(subcommand)]
Cache(CacheCommands),
/// Run a command with bv-managed binaries on PATH (for scripts and CI).
///
/// By default, mirrors `uv run`: re-locks if bv.toml is newer than bv.lock,
/// pulls any missing images, and regenerates shims if needed before exec.
/// Pass `--no-sync` (or set `BV_EXEC_NO_SYNC=1`) to skip all of that for a
/// fast inner loop.
#[command(disable_help_flag = true, disable_version_flag = true)]
Exec {
/// Print help for `bv exec`.
#[arg(short = 'h', action = ArgAction::Help, exclusive = true)]
help: Option<bool>,
/// Skip the auto-sync (lock-staleness check, missing-image pulls,
/// shim regeneration). Equivalent to `BV_EXEC_NO_SYNC=1`.
#[arg(long)]
no_sync: bool,
/// Command to run.
command: String,
/// Arguments forwarded to the command.
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
/// Spawn an interactive subshell with bv-managed binaries on PATH.
Shell {
/// Shell to spawn (defaults to $SHELL).
#[arg(long)]
shell: Option<String>,
},
/// Export this project's tools to another package manager's format.
///
/// One-way escape hatch: emits a conda env YAML approximating the bv
/// project so a hesitant lab can `conda env create -f environment.yml`.
/// Tools sourced from biocontainers are mapped to bioconda specs; custom
/// OCI images are listed in a comment block with no spec line.
Export {
/// Output format. Only `conda` is supported today.
#[arg(long, default_value = "conda")]
format: String,
/// Write to a file instead of stdout.
#[arg(long, short = 'o')]
output: Option<PathBuf>,
},
/// Build and publish a tool to bv-registry (opens a PR).
Publish {
/// Local directory or GitHub repo, e.g. `./my-tool` or `github:user/repo` or `github:user/repo@v2.0`.
source: String,
/// Tool name override (also settable in bv-publish.toml).
#[arg(long)]
tool_name: Option<String>,
/// Version override (also settable in bv-publish.toml).
#[arg(long)]
tool_version: Option<String>,
/// Skip all interactive prompts; requires bv-publish.toml or explicit flags.
#[arg(long)]
non_interactive: bool,
/// Build the image but do not push to GHCR.
#[arg(long)]
no_push: bool,
/// Push the image but do not open a PR.
#[arg(long)]
no_pr: bool,
/// GitHub PAT with `repo` and `write:packages` scopes. Reads GITHUB_TOKEN env var.
#[arg(long, env = "GITHUB_TOKEN")]
github_token: Option<String>,
/// GHCR token override. Falls back to --github-token when absent.
#[arg(long, env = "GHCR_TOKEN")]
ghcr_token: Option<String>,
/// Target registry repository in `owner/repo` form.
#[arg(long, default_value = "tejasprabhune/bv-registry")]
registry_repo: String,
/// GHCR namespace to push the image into. Defaults to your own GitHub username
/// (e.g. `ghcr.io/<you>/<tool>:<ver>`), so you don't need org permissions to publish.
/// Override with e.g. `--push-to bv-registry` if you have write access to that org.
#[arg(long)]
push_to: Option<String>,
/// Docker build platform.
#[arg(long, default_value = "amd64")]
platform: String,
},
}
#[derive(Subcommand)]
pub enum DataCommands {
/// Download one or more reference datasets from the registry.
Fetch {
/// Dataset identifiers, optionally with version (e.g. `pdbaa pdbaa@2024_01`).
#[arg(required = true)]
datasets: Vec<String>,
/// Registry URL or local path. Overrides BV_REGISTRY env var and bv.toml.
#[arg(long, env = "BV_REGISTRY")]
registry: Option<String>,
/// Skip the size confirmation prompt.
#[arg(long, short = 'y')]
yes: bool,
},
/// List reference datasets present in the local cache.
List,
/// Walk the registry and HEAD-check every dataset's source URLs.
///
/// Reports declared vs actual size, plus a PASS/FAIL/MISMATCH per dataset.
/// Cheap (no downloads); use as a registry health check before fetching.
Verify {
/// Registry URL or local path. Overrides BV_REGISTRY env var.
#[arg(long, env = "BV_REGISTRY")]
registry: Option<String>,
/// Only verify datasets whose id contains this substring.
#[arg(long)]
filter: Option<String>,
/// Number of HEAD requests to issue concurrently.
#[arg(long, default_value = "8")]
jobs: usize,
/// Treat declared sizes that disagree with Content-Length by more
/// than this fraction as MISMATCH. Default 0.20 (20%).
#[arg(long, default_value = "0.20")]
size_tolerance: f64,
},
}
#[derive(Subcommand)]
pub enum CacheCommands {
/// Show cache footprint broken down by category.
Size,
/// List entries in the bv-managed cache.
List,
/// Remove cache entries not reachable from any known lockfile.
Prune {
/// Print what would be removed without removing it.
#[arg(long)]
dry_run: bool,
/// Skip the confirmation prompt.
#[arg(long, short = 'y')]
yes: bool,
/// Remove every bv-managed entry (still skips files bv does not own).
#[arg(long)]
all: bool,
/// Keep the most recently used N versions of each tool. Defaults to keeping
/// only entries reachable from the discovered lockfiles.
#[arg(long)]
keep_recent: Option<usize>,
},
}