Skip to main content

faucet_cli/
cli.rs

1//! Argument parser shared by `main.rs` and the integration tests.
2
3use crate::commands::completions;
4use clap::{Args, Parser, Subcommand};
5use clap_complete::engine::ArgValueCandidates;
6use std::path::PathBuf;
7
8/// Runtime matrix-row selection flags, shared by `run`/`validate`/`preview`/
9/// `plan` via `#[command(flatten)]`. Implements the selection model of
10/// #370 (identity), #371 (status), #376 (tags), and #377 (include_parents).
11#[derive(Debug, Args, Default, Clone)]
12pub struct SelectionArgs {
13    /// Run only matrix rows whose id exactly matches. Repeatable and/or
14    /// comma-joined (`--select people --select time_off` or `--select a,b`).
15    /// Force-includes by name, bypassing the `status` gate. (#370)
16    #[arg(long = "select", value_delimiter = ',', env = "FAUCET_SELECT",
17          add = ArgValueCandidates::new(completions::matrix_id_candidates))]
18    pub select: Vec<String>,
19
20    /// Like `--select` but glob-matched against row ids (`--only 'timeoff_*'`).
21    /// Also bypasses the `status` gate. Repeatable / comma-joined. (#370)
22    #[arg(long = "only", value_delimiter = ',',
23          add = ArgValueCandidates::new(completions::matrix_id_candidates))]
24    pub only: Vec<String>,
25
26    /// Remove matching rows (exact id or glob) from the run set, applied last.
27    /// A `mandatory` row is removable only by an exact `--skip <id>`. (#370)
28    #[arg(long = "skip", value_delimiter = ',', env = "FAUCET_SKIP",
29          add = ArgValueCandidates::new(completions::matrix_id_candidates))]
30    pub skip: Vec<String>,
31
32    /// Additively include a readiness tier beyond the default
33    /// `{mandatory, active}` set: `available` / `draft` / `archived`.
34    /// Repeatable / comma-joined. (#371)
35    #[arg(long = "status", value_delimiter = ',', env = "FAUCET_STATUS",
36          add = ArgValueCandidates::new(completions::status_candidates))]
37    pub status: Vec<String>,
38
39    /// Narrow the eligible set to rows carrying any listed tag (union).
40    /// Cannot resurrect a non-eligible row — raise `--status` for that.
41    /// Repeatable / comma-joined. (#376)
42    #[arg(long = "tag", value_delimiter = ',', env = "FAUCET_TAGS",
43          add = ArgValueCandidates::new(completions::tag_candidates))]
44    pub tags: Vec<String>,
45
46    /// How a selected row's `parent:` / `depends_on:` ancestors are resolved
47    /// when not independently selected: `off` (default, strict — error on a
48    /// missing ancestor), `eligible`, or `all`. Overrides
49    /// `selection.include_parents` in the config. (#377)
50    #[arg(long = "include-parents", env = "FAUCET_INCLUDE_PARENTS")]
51    pub include_parents: Option<String>,
52}
53
54/// `faucet` — config-driven runner for faucet-stream pipelines.
55#[derive(Debug, Parser)]
56#[command(name = "faucet", version, about, long_about = None)]
57pub struct Cli {
58    /// Override the global log level (also honors `FAUCET_LOG`).
59    #[arg(long, global = true, env = "FAUCET_LOG", default_value = "info")]
60    pub log_level: String,
61
62    #[command(subcommand)]
63    pub command: Command,
64}
65
66/// Top-level subcommands.
67#[derive(Debug, Subcommand)]
68pub enum Command {
69    /// Execute a pipeline config end-to-end.
70    Run(RunArgs),
71    /// Replay a bounded historical window of a pipeline: chunk --from/--to
72    /// into window units, run them with bounded parallelism, and record
73    /// durable, resumable progress. Exits non-zero if any unit fails.
74    Backfill(BackfillArgs),
75    /// Bulk-snapshot a database table, then stream CDC from a position captured
76    /// before the snapshot (a true mirror with `write_mode: upsert`).
77    /// Long-running when `replication.continuous` is true (Ctrl-C / SIGTERM to stop).
78    Replicate(ReplicateArgs),
79    /// Connect to a config's source, enumerate the datasets behind it
80    /// (tables / collections / indices / prefixes), and emit a ready-to-run
81    /// config with one matrix row per dataset.
82    Discover(DiscoverArgs),
83    /// Parse + validate a pipeline config without running it.
84    Validate(ValidateArgs),
85    /// Print the JSON Schema for a specific connector.
86    Schema(SchemaArgs),
87    /// List every compiled-in source, sink, and transform with a one-line
88    /// description (`--available` lists the whole connector registry instead).
89    List(ListArgs),
90    /// Search the connector registry index for connectors by name / keyword.
91    Search(SearchArgs),
92    /// Score each connector's conformance to the faucet SDK contract and print
93    /// its maturity tier (Stable / Experimental / Beta / Draft) + capabilities.
94    Conformance(ConformanceArgs),
95    /// Show how to install or enable a connector from the registry index
96    /// (prints the recipe; never executes anything).
97    Install(InstallArgs),
98    /// Run only the source side and print records to stdout (uses the stdout sink).
99    Preview(PreviewArgs),
100    /// Read-only preview of what a config would do: resolved pipeline, inferred
101    /// output schema, sink schema delta, lineage, and target sinks — zero writes.
102    Plan(PlanArgs),
103    /// Watch a config and re-run a sample offline on every save, printing a
104    /// live diff of the output. Requires the `cli-dev` build feature.
105    #[cfg(feature = "cli-dev")]
106    Dev(DevArgs),
107    /// Scaffold a starter `pipeline.yaml` to disk.
108    Init(InitArgs),
109    /// Scaffold a new artifact — currently a third-party connector crate.
110    New(NewArgs),
111    /// Probe every connector in a config (auth / network / permissions) and
112    /// print a green/red checklist. Exits non-zero if any probe fails.
113    Doctor(DoctorArgs),
114    /// Run fixture-based offline pipeline tests from one or more spec files.
115    /// No real source or sink is touched. Exits non-zero if any case fails.
116    Test(TestArgs),
117    /// Inspect, replay, or discard dead-letter-queue envelopes written by a
118    /// pipeline's `dlq:` sink.
119    Dlq(DlqArgs),
120    /// Validate a config's `contract:` block and print a summary, or export
121    /// it in a machine-readable format (`--export`).
122    #[cfg(feature = "contract")]
123    Contract(ContractArgs),
124    /// Validate a config's `masking:` block and print which rules apply to
125    /// each destination sink.
126    #[cfg(feature = "masking")]
127    Masking(MaskingArgs),
128    /// Run a pipeline on a cron schedule (long-running; Ctrl-C / SIGTERM to stop).
129    #[cfg(feature = "schedule")]
130    Schedule(ScheduleArgs),
131    /// Run a long-running HTTP control plane (submit / poll / cancel pipeline runs).
132    #[cfg(feature = "serve")]
133    Serve(ServeArgs),
134    /// Run an MCP (Model Context Protocol) server over stdio, exposing faucet's
135    /// introspection surfaces as agent tool calls (for Claude Desktop / Code).
136    #[cfg(feature = "mcp")]
137    Mcp(McpArgs),
138    /// Send a synthetic notification through a config's `notifications:` rules
139    /// to validate channel setup end-to-end (no pipeline runs).
140    #[cfg(feature = "notify")]
141    Notify(NotifyArgs),
142    /// Browse the Data Movement Catalog accumulated by a config's `catalog:`
143    /// store — datasets, schema timelines, volume/freshness, lineage.
144    #[cfg(feature = "catalog")]
145    Catalog(CatalogArgs),
146    /// Register a parameterized config once, then trigger runs by id + params.
147    /// The registry is shared with `faucet serve` — point both at the same
148    /// store URL and templates registered here are triggerable over HTTP.
149    #[cfg(feature = "templates")]
150    Template(TemplateArgs),
151    /// Generate a shell tab-completion script (bash / zsh / fish / powershell /
152    /// elvish). For registry- and config-aware *dynamic* completion, enable the
153    /// `COMPLETE` hook instead, e.g. `source <(COMPLETE=zsh faucet)`.
154    Completions(CompletionsArgs),
155    /// Upgrade a config written against an older `faucet` grammar to the current
156    /// shape (e.g. pre-`pipeline:` top-level source/sink, legacy inline auth).
157    /// Idempotent; rewrites in place unless `--check` / `--stdout`.
158    Migrate(MigrateArgs),
159    /// Canonicalize a config: stable key order, normalized style. Idempotent;
160    /// rewrites in place unless `--check` / `--stdout`. Comments are not
161    /// preserved (the config is parsed and re-serialized).
162    Fmt(FmtArgs),
163    /// Explain, in plain English, what a pipeline config does — source →
164    /// transforms → sink, matrix expansion, replication, delivery guarantee,
165    /// and state store. Read-only and fully offline (no source is touched).
166    Explain(ExplainArgs),
167    /// Show recent run history recorded in a config's `catalog:` store —
168    /// status, duration, throughput, and bookmark. Read-only; requires the
169    /// `catalog` build feature.
170    #[cfg(feature = "catalog")]
171    History(HistoryArgs),
172}
173
174/// `faucet migrate` arguments.
175#[derive(Debug, Args)]
176pub struct MigrateArgs {
177    /// Config file to migrate. Auto-discovered (`faucet.yaml` → `.yml` →
178    /// `.json`) when omitted.
179    #[arg(value_hint = clap::ValueHint::FilePath)]
180    pub config: Option<PathBuf>,
181    /// Report whether a migration is needed without writing (exits non-zero if
182    /// the config is not current). For CI / pre-upgrade checks.
183    #[arg(long)]
184    pub check: bool,
185    /// Write the migrated config to stdout instead of rewriting the file.
186    #[arg(long, conflicts_with = "check")]
187    pub stdout: bool,
188}
189
190/// `faucet fmt` arguments.
191#[derive(Debug, Args)]
192pub struct FmtArgs {
193    /// Config file(s) to format. Auto-discovered (`faucet.yaml` → `.yml` →
194    /// `.json`) when none are given.
195    #[arg(value_hint = clap::ValueHint::FilePath)]
196    pub configs: Vec<PathBuf>,
197    /// Report whether each file is already canonical without writing (exits
198    /// non-zero and prints a unified diff for any file that is not). For CI.
199    #[arg(long)]
200    pub check: bool,
201    /// Write the formatted result to stdout instead of rewriting the file(s).
202    #[arg(long, conflicts_with = "check")]
203    pub stdout: bool,
204}
205
206/// `faucet explain` arguments.
207#[derive(Debug, Args)]
208pub struct ExplainArgs {
209    /// Path to a `.yaml`/`.yml`/`.json` config (auto-discovered if omitted).
210    pub config: Option<PathBuf>,
211    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
212    /// Defaults to `.env` in cwd if present.
213    #[arg(long, conflicts_with = "no_env_file")]
214    pub env_file: Option<PathBuf>,
215    /// Skip auto-loading `.env` from cwd.
216    #[arg(long)]
217    pub no_env_file: bool,
218    /// Select a named overlay from the config's `profiles:` block.
219    #[arg(long, env = "FAUCET_PROFILE")]
220    pub profile: Option<String>,
221    /// Emit the narration as structured JSON instead of prose.
222    #[arg(long)]
223    pub json: bool,
224    /// Narrate every matrix row instead of summarizing a large matrix.
225    #[arg(long)]
226    pub rows: bool,
227}
228
229/// `faucet history` arguments.
230#[cfg(feature = "catalog")]
231#[derive(Debug, Args)]
232pub struct HistoryArgs {
233    /// Path to a config carrying a `catalog:` block (auto-discovered if omitted).
234    pub config: Option<PathBuf>,
235    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
236    #[arg(long, conflicts_with = "no_env_file")]
237    pub env_file: Option<PathBuf>,
238    /// Skip auto-loading `.env` from cwd.
239    #[arg(long)]
240    pub no_env_file: bool,
241    /// Select a named overlay from the config's `profiles:` block.
242    #[arg(long, env = "FAUCET_PROFILE")]
243    pub profile: Option<String>,
244    /// Maximum number of runs to show, newest first.
245    #[arg(long, default_value_t = 20)]
246    pub limit: usize,
247    /// Show only runs that contain an invocation for this matrix row id.
248    #[arg(long)]
249    pub row: Option<String>,
250    /// Emit the history as JSON instead of a table.
251    #[arg(long)]
252    pub json: bool,
253}
254
255/// `faucet completions` arguments.
256#[derive(Debug, Args)]
257pub struct CompletionsArgs {
258    /// Target shell.
259    pub shell: clap_complete::aot::Shell,
260}
261
262/// `faucet catalog` arguments.
263#[cfg(feature = "catalog")]
264#[derive(Debug, Parser)]
265pub struct CatalogArgs {
266    #[command(subcommand)]
267    pub command: CatalogCommand,
268}
269
270/// `faucet catalog` subcommands.
271#[cfg(feature = "catalog")]
272#[derive(Debug, Subcommand)]
273pub enum CatalogCommand {
274    /// List every catalogued dataset (newest activity first).
275    Datasets(CatalogDatasetsArgs),
276    /// Show one dataset's detail: schema timeline, volume points, edges.
277    Show(CatalogShowArgs),
278    /// Print the dataset lineage graph (optionally rooted at a dataset).
279    Lineage(CatalogLineageArgs),
280}
281
282/// Shared config-loading flags for the `faucet catalog` subcommands.
283#[cfg(feature = "catalog")]
284#[derive(Debug, Parser)]
285pub struct CatalogConfigArgs {
286    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
287    /// `catalog:` block naming the store. If omitted, auto-discover
288    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
289    #[arg(long)]
290    pub config: Option<PathBuf>,
291    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
292    /// Defaults to `.env` in cwd if present.
293    #[arg(long, conflicts_with = "no_env_file")]
294    pub env_file: Option<PathBuf>,
295    /// Skip auto-loading `.env` from cwd.
296    #[arg(long)]
297    pub no_env_file: bool,
298    /// Select a named overlay from the config's `profiles:` block.
299    /// Overrides the `FAUCET_PROFILE` env var.
300    #[arg(long, env = "FAUCET_PROFILE")]
301    pub profile: Option<String>,
302    /// Emit machine-readable JSON instead of the human summary.
303    #[arg(long)]
304    pub json: bool,
305}
306
307/// `faucet catalog datasets` arguments.
308#[cfg(feature = "catalog")]
309#[derive(Debug, Parser)]
310pub struct CatalogDatasetsArgs {
311    #[command(flatten)]
312    pub common: CatalogConfigArgs,
313    /// Only datasets of this connector kind (e.g. `postgres`, `csv`).
314    #[arg(long)]
315    pub kind: Option<String>,
316    /// Case-insensitive substring match on the dataset URI.
317    #[arg(long)]
318    pub q: Option<String>,
319    /// Max datasets to list.
320    #[arg(long, default_value_t = 100)]
321    pub limit: usize,
322}
323
324/// `faucet catalog show <id>` arguments.
325#[cfg(feature = "catalog")]
326#[derive(Debug, Parser)]
327pub struct CatalogShowArgs {
328    /// Dataset id (from `faucet catalog datasets`), or a unique prefix of one.
329    pub id: String,
330    #[command(flatten)]
331    pub common: CatalogConfigArgs,
332}
333
334/// `faucet catalog lineage` arguments.
335#[cfg(feature = "catalog")]
336#[derive(Debug, Parser)]
337pub struct CatalogLineageArgs {
338    #[command(flatten)]
339    pub common: CatalogConfigArgs,
340    /// Dataset id to root the graph at (whole graph when omitted).
341    #[arg(long)]
342    pub root: Option<String>,
343    /// BFS hop bound around --root.
344    #[arg(long, default_value_t = 5)]
345    pub depth: u32,
346}
347
348/// `faucet template` arguments (#444).
349#[cfg(feature = "templates")]
350#[derive(Debug, Parser)]
351pub struct TemplateArgs {
352    #[command(subcommand)]
353    pub command: TemplateCommand,
354}
355
356/// `faucet template` subcommands.
357#[cfg(feature = "templates")]
358#[derive(Debug, Subcommand)]
359pub enum TemplateCommand {
360    /// Validate a config and register it as a new template version.
361    Register(TemplateRegisterArgs),
362    /// List registered templates (newest version of each, plus its release state).
363    List(TemplateListArgs),
364    /// Show one template: its params, config body, and versions.
365    Show(TemplateShowArgs),
366    /// Make a version live — what unpinned runs will use. The one action that
367    /// moves existing callers; registering a build never does.
368    Launch(TemplateLaunchArgs),
369    /// Re-launch the previously launched version.
370    Rollback(TemplateRollbackArgs),
371    /// Retire a template (or revive one with `--undo`).
372    Deprecate(TemplateDeprecateArgs),
373    /// Point a named environment channel (`prod`, `staging`, …) at a version.
374    Promote(TemplatePromoteArgs),
375    /// Delete one version, or every version, of a template.
376    Delete(TemplateDeleteArgs),
377    /// Materialize a template with the given params and run it locally.
378    Run(TemplateRunArgs),
379}
380
381/// Where the template registry lives — shared by every `faucet template`
382/// subcommand.
383#[cfg(feature = "templates")]
384#[derive(Debug, Parser)]
385pub struct TemplateStoreArgs {
386    /// Registry store URL: `sqlite:<path>`, a `postgres://…` URL, or `memory`
387    /// (process-lifetime only — useful for a smoke test). Point
388    /// `faucet serve --history` at the same URL to trigger these templates over
389    /// HTTP. SQL backends need the matching `serve-history-sqlite` /
390    /// `serve-history-postgres` build feature.
391    #[arg(long, env = "FAUCET_TEMPLATE_STORE")]
392    pub store: String,
393    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
394    /// Defaults to `.env` in cwd if present.
395    #[arg(long, conflicts_with = "no_env_file")]
396    pub env_file: Option<PathBuf>,
397    /// Skip auto-loading `.env` from cwd.
398    #[arg(long)]
399    pub no_env_file: bool,
400    /// Emit machine-readable JSON instead of the human summary.
401    #[arg(long)]
402    pub json: bool,
403}
404
405/// `faucet template register <config>` arguments.
406#[cfg(feature = "templates")]
407#[derive(Debug, Parser)]
408pub struct TemplateRegisterArgs {
409    /// Path to the `.yaml`, `.yml`, or `.json` config to register. Stored
410    /// verbatim, so `${env:…}` / `${vault:…}` stay unresolved and are resolved
411    /// when a run is triggered.
412    #[arg(value_hint = clap::ValueHint::FilePath)]
413    pub config: PathBuf,
414    /// Registry id. Derived from the config's `name:` when omitted.
415    #[arg(long)]
416    pub id: Option<String>,
417    /// Free-text description shown by `list` / `show`.
418    #[arg(long)]
419    pub description: Option<String>,
420    /// Point a named channel at the newly registered version, e.g.
421    /// `--tag dev --tag test`. The version number itself always auto-increments;
422    /// channels come from a fixed set (`dev`, `test`, `staging`, `pre-prod`,
423    /// `canary`, `stable`, `prod`, `previous`). `latest` is derived and always
424    /// names the newest version, so it cannot be assigned.
425    #[arg(long = "tag", value_name = "CHANNEL")]
426    pub tag: Vec<String>,
427    /// Launch the new version immediately, making it the one unpinned runs use.
428    /// Without this the version is registered but inert — a new build never moves
429    /// existing callers until you launch it.
430    #[arg(long)]
431    pub launch: bool,
432    #[command(flatten)]
433    pub common: TemplateStoreArgs,
434}
435
436/// `faucet template promote <id>` arguments.
437#[cfg(feature = "templates")]
438#[derive(Debug, Parser)]
439pub struct TemplatePromoteArgs {
440    /// Template id.
441    pub id: String,
442    /// Channel to move: `dev`, `test`, `staging`, `pre-prod`, `canary`, or
443    /// `prod`. The derived channels (`stable`, `previous`, `newest`) cannot be
444    /// promoted — `stable` moves with `faucet template launch`.
445    #[arg(long = "tag", value_name = "CHANNEL")]
446    pub tag: String,
447    /// What to point it at: a version number, or another channel whose current
448    /// target should be copied (`--tag prod --version pre-prod`). Defaults to
449    /// `stable`, the currently launched version.
450    #[arg(long, default_value = "stable")]
451    pub version: String,
452    #[command(flatten)]
453    pub common: TemplateStoreArgs,
454}
455
456/// `faucet template launch <id>` arguments.
457#[cfg(feature = "templates")]
458#[derive(Debug, Parser)]
459pub struct TemplateLaunchArgs {
460    /// Template id.
461    pub id: String,
462    /// Which version to make live: a number, or a channel whose current target to
463    /// copy (`--version pre-prod` launches whatever passed pre-prod). Defaults to
464    /// `newest` — launching what you just registered is the common case.
465    #[arg(long, default_value = "newest")]
466    pub version: String,
467    #[command(flatten)]
468    pub common: TemplateStoreArgs,
469}
470
471/// `faucet template rollback <id>` arguments.
472#[cfg(feature = "templates")]
473#[derive(Debug, Parser)]
474pub struct TemplateRollbackArgs {
475    /// Template id.
476    pub id: String,
477    #[command(flatten)]
478    pub common: TemplateStoreArgs,
479}
480
481/// `faucet template deprecate <id>` arguments.
482#[cfg(feature = "templates")]
483#[derive(Debug, Parser)]
484pub struct TemplateDeprecateArgs {
485    /// Template id.
486    pub id: String,
487    /// Why it is being retired — shown to anyone who triggers it.
488    #[arg(long)]
489    pub reason: Option<String>,
490    /// Revive a deprecated template instead of retiring it.
491    #[arg(long)]
492    pub undo: bool,
493    #[command(flatten)]
494    pub common: TemplateStoreArgs,
495}
496
497/// `faucet template list` arguments.
498#[cfg(feature = "templates")]
499#[derive(Debug, Parser)]
500pub struct TemplateListArgs {
501    #[command(flatten)]
502    pub common: TemplateStoreArgs,
503}
504
505/// `faucet template show <id>` arguments.
506#[cfg(feature = "templates")]
507#[derive(Debug, Parser)]
508pub struct TemplateShowArgs {
509    /// Template id.
510    pub id: String,
511    /// Version to show: a number, or a named channel (`stable` — the default,
512    /// i.e. the launched version — `newest`, `previous`, `prod`, `dev`, …).
513    #[arg(long, default_value = "stable")]
514    pub version: String,
515    /// Print ONLY the pure template config — comments stripped, re-emitted as
516    /// canonical YAML — so it pipes cleanly to a file. Suppresses the metadata
517    /// report. Ignored with `--json`.
518    #[arg(long)]
519    pub clean: bool,
520    #[command(flatten)]
521    pub common: TemplateStoreArgs,
522}
523
524/// `faucet template delete <id>` arguments.
525#[cfg(feature = "templates")]
526#[derive(Debug, Parser)]
527pub struct TemplateDeleteArgs {
528    /// Template id.
529    pub id: String,
530    /// Delete only this version — a number, or a named channel (`latest`,
531    /// `prod`, …) resolved to the version it points at. Omitted = delete every
532    /// version of the template.
533    #[arg(long)]
534    pub version: Option<String>,
535    #[command(flatten)]
536    pub common: TemplateStoreArgs,
537}
538
539/// `faucet template run <id>` arguments.
540#[cfg(feature = "templates")]
541#[derive(Debug, Parser)]
542pub struct TemplateRunArgs {
543    /// Template id.
544    pub id: String,
545    /// Version to run: a number, or a named channel. Defaults to `stable` — the
546    /// launched version — so an unpinned run never picks up a build that has not
547    /// been launched. Use `newest` to run the most recent build regardless.
548    #[arg(long, default_value = "stable")]
549    pub version: String,
550    /// Supply a declared param: `--param tenant_id=acme`. Repeatable.
551    #[arg(long = "param", value_name = "NAME=VALUE")]
552    pub param: Vec<String>,
553    /// Override an environment variable for this materialization only:
554    /// `--param-env REGION=eu`, or bare `--param-env TOKEN` to take it from the
555    /// caller's environment. Repeatable.
556    #[arg(long = "param-env", value_name = "NAME[=VALUE]")]
557    pub param_env: Vec<String>,
558    /// Materialize and validate without running (prints the resolved config).
559    #[arg(long)]
560    pub dry_run: bool,
561    /// Stop after writing this many records to the sink.
562    #[arg(long)]
563    pub limit: Option<usize>,
564    #[command(flatten)]
565    pub common: TemplateStoreArgs,
566}
567
568/// `faucet notify test` arguments.
569#[cfg(feature = "notify")]
570#[derive(Debug, Parser)]
571pub struct NotifyArgs {
572    #[command(subcommand)]
573    pub command: NotifyCommand,
574}
575
576/// `faucet notify` subcommands.
577#[cfg(feature = "notify")]
578#[derive(Debug, Subcommand)]
579pub enum NotifyCommand {
580    /// Fire one synthetic event at every matching rule in the config.
581    Test(NotifyTestArgs),
582}
583
584/// `faucet notify test <config>` arguments.
585#[cfg(feature = "notify")]
586#[derive(Debug, Parser)]
587pub struct NotifyTestArgs {
588    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
589    /// `notifications:` block. If omitted, auto-discover in cwd.
590    pub config: Option<PathBuf>,
591    /// Which event to synthesize (defaults to `run_failure`).
592    #[arg(long, default_value = "run_failure")]
593    pub event: String,
594    /// Path to a `.env` file for `${env:VAR}` interpolation.
595    #[arg(long, conflicts_with = "no_env_file")]
596    pub env_file: Option<PathBuf>,
597    /// Disable `.env` auto-discovery.
598    #[arg(long)]
599    pub no_env_file: bool,
600}
601
602/// `faucet test` arguments.
603#[derive(Debug, Parser)]
604pub struct TestArgs {
605    /// One or more test-spec files (`.yaml`, `.yml`, or `.json`), e.g.
606    /// `faucet test tests/*.yaml`.
607    #[arg(required = true)]
608    pub specs: Vec<PathBuf>,
609    /// Run only cases whose name contains this substring.
610    #[arg(long)]
611    pub filter: Option<String>,
612    /// Emit a machine-readable JSON report instead of the human checklist.
613    #[arg(long)]
614    pub json: bool,
615    /// Default `${now.*}` clock for cases without their own `clock:` field
616    /// (RFC3339 like `2026-01-31T00:00:00Z`, or a date `2026-01-31`).
617    /// Defaults to process start (UTC).
618    #[arg(long)]
619    pub clock: Option<String>,
620    /// Path to a `.env` file to load for `${env:VAR}` interpolation in
621    /// referenced pipeline configs. Defaults to `.env` in cwd if present.
622    #[arg(long, conflicts_with = "no_env_file")]
623    pub env_file: Option<PathBuf>,
624    /// Skip auto-loading `.env` from cwd.
625    #[arg(long)]
626    pub no_env_file: bool,
627    /// Select a named overlay from each referenced config's `profiles:` block.
628    /// Overrides the `FAUCET_PROFILE` env var.
629    #[arg(long, env = "FAUCET_PROFILE")]
630    pub profile: Option<String>,
631    /// Resolve `${vault:…}` / `${aws-sm:…}` / … secret directives in
632    /// referenced configs (requires network + credentials). By default tests
633    /// load configs offline and leave secret directives unresolved — safe
634    /// because the real source/sink configs holding them are never used.
635    #[arg(long)]
636    pub resolve_secrets: bool,
637}
638
639/// `faucet dlq` arguments.
640#[derive(Debug, Parser)]
641pub struct DlqArgs {
642    #[command(subcommand)]
643    pub command: DlqCommand,
644}
645
646/// `faucet dlq` subcommands.
647#[derive(Debug, Subcommand)]
648pub enum DlqCommand {
649    /// Read a DLQ location back and print a per-reason / per-error-kind
650    /// breakdown plus a sample of quarantined records.
651    Inspect(DlqInspectArgs),
652    /// Re-feed quarantined records through a pipeline config (transforms →
653    /// quality → contract → sink). Rows that fail again land in a *fresh* DLQ.
654    Replay(DlqReplayArgs),
655    /// Remove processed envelopes from a DLQ location (archive by default,
656    /// or `--delete`), filtered by reason and/or age.
657    Discard(DlqDiscardArgs),
658}
659
660/// `faucet dlq inspect <location>` arguments.
661#[derive(Debug, Parser)]
662pub struct DlqInspectArgs {
663    /// DLQ location: a `.jsonl` file, a directory of `*.jsonl` files, or a glob.
664    pub location: String,
665    /// Only include envelopes with this DLQ reason (`partial` / `dlq_all` /
666    /// `quality` / `schema_drift` / `contract`).
667    #[arg(long)]
668    pub reason: Option<String>,
669    /// Number of sample records to show. Default: 5.
670    #[arg(long, default_value_t = 5)]
671    pub limit: usize,
672    /// Key for a DLQ sealed at rest by the jsonl sink's `encryption` block.
673    /// Repeat the flag to also try older (rotated) keys. Requires a build
674    /// with the `encryption` feature.
675    #[arg(long = "encryption-key")]
676    pub encryption_key: Vec<String>,
677    /// Emit a machine-readable JSON summary instead of the human report.
678    #[arg(long)]
679    pub json: bool,
680}
681
682/// `faucet dlq replay <config> --from <location>` arguments.
683#[derive(Debug, Parser)]
684pub struct DlqReplayArgs {
685    /// Path to the pipeline config whose sink / transforms / quality / contract
686    /// the replayed records flow through. If omitted, auto-discover in cwd.
687    pub config: Option<PathBuf>,
688    /// DLQ location to replay from: a `.jsonl` file, a directory, or a glob.
689    #[arg(long)]
690    pub from: String,
691    /// Only replay envelopes with this DLQ reason.
692    #[arg(long)]
693    pub reason: Option<String>,
694    /// Where replayed rows that fail *again* are quarantined. Defaults to a
695    /// `replay-failed.jsonl` sibling of the source (never the source itself).
696    #[arg(long)]
697    pub failed_dlq: Option<String>,
698    /// Which root row of the config to replay through. Defaults to the first root.
699    #[arg(long)]
700    pub row: Option<String>,
701    /// Report what would be replayed without writing to the sink.
702    #[arg(long)]
703    pub dry_run: bool,
704    /// Key for a DLQ sealed at rest by the jsonl sink's `encryption` block.
705    /// Repeat the flag to also try older (rotated) keys. Requires a build
706    /// with the `encryption` feature.
707    #[arg(long = "encryption-key")]
708    pub encryption_key: Vec<String>,
709    /// (Replay picks up the config's own dlq `encryption` block automatically
710    /// when no key is passed.)
711    /// Emit a machine-readable JSON result instead of the human summary.
712    #[arg(long)]
713    pub json: bool,
714    /// Path to a `.env` file for `${env:VAR}` interpolation in the config.
715    #[arg(long, conflicts_with = "no_env_file")]
716    pub env_file: Option<PathBuf>,
717    /// Skip auto-loading `.env` from cwd.
718    #[arg(long)]
719    pub no_env_file: bool,
720    /// Select a named overlay from the config's `profiles:` block.
721    #[arg(long, env = "FAUCET_PROFILE")]
722    pub profile: Option<String>,
723}
724
725/// `faucet dlq discard <location>` arguments.
726#[derive(Debug, Parser)]
727pub struct DlqDiscardArgs {
728    /// DLQ location: a `.jsonl` file, a directory of `*.jsonl` files, or a glob.
729    pub location: String,
730    /// Only discard envelopes with this DLQ reason.
731    #[arg(long)]
732    pub reason: Option<String>,
733    /// Only discard envelopes older than this: an RFC3339 timestamp
734    /// (`2026-06-01T00:00:00Z`) or a relative age (`7d`, `24h`, `30m`).
735    #[arg(long)]
736    pub before: Option<String>,
737    /// Permanently delete matching envelopes instead of archiving them to a
738    /// `<file>.archived.jsonl` sibling.
739    #[arg(long)]
740    pub delete: bool,
741    /// Key for a DLQ sealed at rest by the jsonl sink's `encryption` block.
742    /// Repeat the flag to also try older (rotated) keys. Requires a build
743    /// with the `encryption` feature.
744    #[arg(long = "encryption-key")]
745    pub encryption_key: Vec<String>,
746    /// Emit a machine-readable JSON result instead of the human summary.
747    #[arg(long)]
748    pub json: bool,
749}
750
751/// `faucet doctor` arguments.
752#[derive(Debug, Parser)]
753pub struct DoctorArgs {
754    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
755    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
756    pub config: Option<PathBuf>,
757    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
758    /// Defaults to `.env` in cwd if present.
759    #[arg(long, conflicts_with = "no_env_file")]
760    pub env_file: Option<PathBuf>,
761    /// Skip auto-loading `.env` from cwd.
762    #[arg(long)]
763    pub no_env_file: bool,
764    /// Per-probe timeout in seconds.
765    #[arg(long, default_value_t = 10)]
766    pub timeout_secs: u64,
767    /// Emit machine-readable JSON instead of the human checklist.
768    #[arg(long)]
769    pub json: bool,
770    /// Run only the offline static config lints (no network probes): dangling /
771    /// unreferenced `auth:` providers, unused `vars:`, and no-op sink
772    /// `batch_size: 0`. Fast and credential-free — ideal for CI. Exits non-zero
773    /// on any lint *error* (warnings don't fail).
774    #[arg(long)]
775    pub offline: bool,
776    /// Select a named overlay from the config's `profiles:` block and deep-merge
777    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
778    #[arg(long, env = "FAUCET_PROFILE")]
779    pub profile: Option<String>,
780}
781
782/// `faucet contract` arguments.
783#[cfg(feature = "contract")]
784#[derive(Debug, Parser)]
785pub struct ContractArgs {
786    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
787    /// `pipeline.contract:` block. If omitted, auto-discover
788    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
789    pub config: Option<PathBuf>,
790    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
791    /// Defaults to `.env` in cwd if present.
792    #[arg(long, conflicts_with = "no_env_file")]
793    pub env_file: Option<PathBuf>,
794    /// Skip auto-loading `.env` from cwd.
795    #[arg(long)]
796    pub no_env_file: bool,
797    /// Select a named overlay from the config's `profiles:` block and deep-merge
798    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
799    #[arg(long, env = "FAUCET_PROFILE")]
800    pub profile: Option<String>,
801    /// Export the contract in a machine-readable format instead of the
802    /// human summary: the canonical contract JSON, a standalone JSON Schema,
803    /// or an OpenLineage schema facet.
804    #[arg(long, value_enum)]
805    pub export: Option<ContractExportFormat>,
806}
807
808/// Arguments for `faucet masking`.
809#[cfg(feature = "masking")]
810#[derive(Debug, Parser)]
811pub struct MaskingArgs {
812    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
813    /// `pipeline.masking:` block. If omitted, auto-discover
814    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
815    pub config: Option<PathBuf>,
816    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
817    /// Defaults to `.env` in cwd if present.
818    #[arg(long, conflicts_with = "no_env_file")]
819    pub env_file: Option<PathBuf>,
820    /// Skip auto-loading `.env` from cwd.
821    #[arg(long)]
822    pub no_env_file: bool,
823    /// Select a named overlay from the config's `profiles:` block and deep-merge
824    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
825    #[arg(long, env = "FAUCET_PROFILE")]
826    pub profile: Option<String>,
827}
828
829/// Export format for `faucet contract --export`.
830#[cfg(feature = "contract")]
831#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
832pub enum ContractExportFormat {
833    /// The canonical contract document as JSON.
834    Contract,
835    /// A standalone JSON Schema (draft 2020-12) for the promised records.
836    JsonSchema,
837    /// An OpenLineage `SchemaDatasetFacet` JSON document.
838    Openlineage,
839}
840
841/// `faucet schedule` arguments.
842#[cfg(feature = "schedule")]
843#[derive(Debug, Parser)]
844pub struct ScheduleArgs {
845    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a `schedule:`
846    /// block. If omitted, auto-discover `faucet.yaml` / `.yml` / `.json` in cwd.
847    pub config: Option<PathBuf>,
848    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
849    /// Defaults to `.env` in cwd if present.
850    #[arg(long, conflicts_with = "no_env_file")]
851    pub env_file: Option<PathBuf>,
852    /// Skip auto-loading `.env` from cwd.
853    #[arg(long)]
854    pub no_env_file: bool,
855    /// Run exactly one pipeline run immediately, then exit (ignores cron timing).
856    /// Useful for platform-driven invocation (k8s CronJob / systemd OnCalendar).
857    #[arg(long)]
858    pub once: bool,
859    /// Select a named overlay from the config's `profiles:` block and deep-merge
860    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
861    #[arg(long, env = "FAUCET_PROFILE")]
862    pub profile: Option<String>,
863}
864
865/// `faucet serve` arguments.
866#[cfg(feature = "serve")]
867#[derive(Debug, Clone, Parser)]
868pub struct ServeArgs {
869    /// Bind address. Defaults to loopback; set 0.0.0.0:PORT to expose externally.
870    #[arg(long, env = "FAUCET_SERVE_LISTEN", default_value = "127.0.0.1:8080")]
871    pub listen: String,
872    /// Bearer token required on /v1/* requests. Prefer the env var (avoids `ps` leakage).
873    #[arg(long, env = "FAUCET_SERVE_AUTH_TOKEN", conflicts_with = "no_auth")]
874    pub auth_token: Option<String>,
875    /// Explicitly disable authentication. Required if no token is set, so an
876    /// unauthenticated server is never accidental.
877    #[arg(long)]
878    pub no_auth: bool,
879    /// Path to an RBAC auth config (YAML/JSON) defining principals — each a
880    /// `{ name, token, role }` where role is `viewer` / `operator` / `admin`.
881    /// Enables role-based access control + an audit log. Mutually exclusive with
882    /// `--auth-token` / `--no-auth`.
883    #[arg(long, conflicts_with_all = ["auth_token", "no_auth"])]
884    pub auth_config: Option<std::path::PathBuf>,
885    /// Max pipeline runs executing at once. Default: min(16, cpu count).
886    #[arg(long)]
887    pub max_concurrent_runs: Option<usize>,
888    /// Max queued (not-yet-running) runs before POST /v1/runs returns 429.
889    /// Default: 8 × max-concurrent-runs.
890    #[arg(long)]
891    pub max_queued_runs: Option<usize>,
892    /// Workspace-default config merged under every submitted run.
893    #[arg(long)]
894    pub default_config: Option<std::path::PathBuf>,
895    /// Run-history backend URL: omitted = in-memory; postgres://… ; sqlite:… .
896    #[arg(long)]
897    pub history: Option<String>,
898    /// CORS allow-list origin (repeatable). Omitted = CORS disabled.
899    #[arg(long)]
900    pub cors_origin: Vec<String>,
901    /// Max POST /v1/runs body size in bytes (413 on exceed).
902    #[arg(long, default_value_t = 1_048_576)]
903    pub body_limit_bytes: usize,
904    /// SIGTERM/SIGINT drain window in seconds.
905    #[arg(long, default_value_t = 60)]
906    pub shutdown_grace_secs: u64,
907    /// Retain terminal run records this long (seconds).
908    #[arg(long, default_value_t = 604_800)]
909    pub retain_terminal_runs_secs: u64,
910    /// Idempotency-key replay window (seconds).
911    #[arg(long, default_value_t = 86_400)]
912    pub idempotency_retention_secs: u64,
913    /// How long persisted run logs are kept (seconds, #529), independent of run
914    /// records. Requires a persistent `--history` backend; `0` disables durable
915    /// log persistence (ephemeral SSE only). Default: 7 days.
916    #[arg(long, default_value_t = 604_800)]
917    pub log_retention_secs: u64,
918    /// Per-run cap on persisted log lines (#529). Past it a truncation marker is
919    /// recorded and further lines are dropped.
920    #[arg(long, default_value_t = 100_000)]
921    pub log_max_lines_per_run: usize,
922    /// Run-ownership lease TTL in seconds (multi-instance orphan fencing). A run
923    /// is owned by the instance executing it and its lease is heartbeated at
924    /// ~⅓ of this interval; only a run whose lease has expired (owner presumed
925    /// dead) is recovered as failed. Make this comfortably larger than expected
926    /// GC/IO stalls so a healthy-but-slow instance is never falsely reclaimed.
927    /// Only relevant with a persistent (postgres/sqlite) history backend.
928    #[arg(long, default_value_t = 30)]
929    pub lease_ttl_secs: u64,
930    /// Per-probe timeout for `doctor_first` preflight (seconds).
931    #[arg(long, default_value_t = 10)]
932    pub probe_timeout_secs: u64,
933    /// Path to a `.env` file loaded for the server's own startup interpolation.
934    #[arg(long, conflicts_with = "no_env_file")]
935    pub env_file: Option<std::path::PathBuf>,
936    /// Skip auto-loading `.env` from cwd at startup.
937    #[arg(long)]
938    pub no_env_file: bool,
939    /// Disable serving the embedded web console (only meaningful in a build that
940    /// includes the `serve-ui` feature; the API is unaffected).
941    #[arg(long)]
942    pub no_ui: bool,
943    /// Enable clustered execution: run a claim loop that pulls Pending runs from
944    /// the shared history DB so N instances pull-balance and fail over. Requires
945    /// a postgres/sqlite --history backend.
946    #[arg(long)]
947    pub cluster: bool,
948    /// Claim-loop poll interval (seconds) in cluster mode. Also the
949    /// cross-instance cancel-propagation lag. Must be > 0.
950    #[arg(long, default_value_t = 2)]
951    pub cluster_poll_secs: u64,
952    /// Max failover re-runs of an orphaned run before it is marked Failed
953    /// (poison). Must be > 0.
954    #[arg(long, default_value_t = 3)]
955    pub cluster_max_attempts: u32,
956    /// Path to a triggers file (YAML/JSON) defining event-driven pipeline
957    /// triggers (object-arrival / webhook / queue-depth). Requires a build with
958    /// the `triggers` feature. See `faucet schema triggers`.
959    #[arg(long)]
960    pub triggers: Option<std::path::PathBuf>,
961    /// Restrict per-run completion callbacks (`callback` on a submit) to these
962    /// hosts. Repeatable. When unset, any host is permitted **except**
963    /// link-local / cloud-metadata addresses, which are always refused unless
964    /// named here. See the HTTP API reference for the egress posture.
965    #[arg(long = "callback-allow-host")]
966    pub callback_allow_host: Vec<String>,
967    /// Mount the MCP (Model Context Protocol) endpoint at `/mcp`, exposing
968    /// faucet as agent tool calls. Effective only in a build with the `mcp`
969    /// feature; the endpoint inherits serve's bearer-auth + RBAC + audit.
970    #[arg(long)]
971    pub mcp: bool,
972    /// Allow the MCP endpoint's *mutating* tools (`run_pipeline`). Off by
973    /// default: only read-only tools are exposed. A caller still needs the
974    /// `RunWrite` RBAC scope. Only meaningful together with `--mcp`.
975    #[arg(long)]
976    pub mcp_allow_mutations: bool,
977}
978
979/// `faucet mcp` arguments — run an MCP server over stdio (#420).
980#[cfg(feature = "mcp")]
981#[derive(Debug, Clone, Parser)]
982pub struct McpArgs {
983    /// Allow mutating tools (`run_pipeline`). Off by default — only read-only
984    /// tools (list / schema / scaffold / validate / preview) are exposed.
985    /// stdio is local-trust: there is no bearer/RBAC layer, so enable this only
986    /// for a trusted local agent.
987    #[arg(long)]
988    pub allow_mutations: bool,
989    /// Optional `.env` file to load before starting (for `${env:…}` in configs
990    /// passed to `validate`/`preview`/`run_pipeline`).
991    #[arg(long, conflicts_with = "no_env_file")]
992    pub env_file: Option<std::path::PathBuf>,
993    /// Skip auto-loading `.env` from cwd at startup.
994    #[arg(long)]
995    pub no_env_file: bool,
996    /// Pipeline-template registry to expose (#444): `sqlite:<path>`, a
997    /// `postgres://…` URL, or `memory`. Enables the `list_templates` /
998    /// `get_template` tools (plus `register_template` / `run_template` with
999    /// `--allow-mutations`). Omitted = no template tools are advertised.
1000    #[cfg(feature = "templates")]
1001    #[arg(long, env = "FAUCET_TEMPLATE_STORE")]
1002    pub template_store: Option<String>,
1003}
1004
1005/// `faucet run` arguments.
1006///
1007/// `Default` is derived so callers that execute an already-loaded config through
1008/// `commands::run::execute` (notably `faucet template run`) can build a
1009/// plain-run argument set without restating every flag.
1010#[derive(Debug, Parser, Default)]
1011pub struct RunArgs {
1012    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config.
1013    /// If omitted (and `--from-env` is not set), auto-discover
1014    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in the current directory.
1015    /// Mutually exclusive with `--from-env`.
1016    #[arg(conflicts_with = "from_env")]
1017    pub config: Option<PathBuf>,
1018    /// Build the pipeline entirely from `FAUCET_*` environment variables —
1019    /// no YAML required. See `cli/README.md` for the variable schema.
1020    #[arg(long)]
1021    pub from_env: bool,
1022    /// Path to a `.env` file to load before reading variables. Works in both
1023    /// YAML mode (for `${env:VAR}` interpolation) and `--from-env` mode.
1024    /// When omitted, `.env` in the current directory is auto-loaded if present.
1025    /// Existing process-env values always win over file-supplied ones.
1026    #[arg(long, conflicts_with = "no_env_file")]
1027    pub env_file: Option<PathBuf>,
1028    /// Skip auto-loading `.env` from the current directory.
1029    #[arg(long)]
1030    pub no_env_file: bool,
1031    /// Stop after fetching from the source — write nothing to the sink.
1032    #[arg(long)]
1033    pub dry_run: bool,
1034    /// Stop after writing this many records to the sink. Default: unlimited.
1035    #[arg(long)]
1036    pub limit: Option<usize>,
1037    /// Override the state-store directory (file backend only).
1038    #[arg(long)]
1039    pub state_path: Option<PathBuf>,
1040    /// Override the `${now.*}` interpolation clock (RFC3339 like
1041    /// `2026-01-31T00:00:00Z`, or a date `2026-01-31`). Default: process start (UTC).
1042    /// Use for backfills.
1043    #[arg(long)]
1044    pub clock: Option<String>,
1045    /// Select a named overlay from the config's `profiles:` block and deep-merge
1046    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
1047    /// Not applicable in `--from-env` mode (no config file to compose).
1048    #[arg(long, env = "FAUCET_PROFILE")]
1049    pub profile: Option<String>,
1050    /// Show a live full-screen terminal UI (per-invocation throughput, errors,
1051    /// DLQ counts, bookmark age) while the pipeline runs. Requires a binary
1052    /// built with the `cli-tui` feature and a real terminal on stdout —
1053    /// on a non-TTY (CI, pipes) the run proceeds normally with a notice.
1054    /// Press `q` to cancel cooperatively (in-flight work flushes at the next
1055    /// page boundary).
1056    #[arg(long)]
1057    pub tui: bool,
1058
1059    /// Suppress the inline live progress line (records in/out, rows/s, pages,
1060    /// elapsed) that `faucet run` shows on an interactive terminal. The
1061    /// progress line is already auto-disabled on a non-TTY stdout (CI, pipes)
1062    /// and when `--tui` is used; `--quiet` turns it off explicitly, keeping
1063    /// only the periodic log output.
1064    #[arg(long)]
1065    pub quiet: bool,
1066
1067    /// Format for the end-of-run summary: `text` (default, human — written to
1068    /// **stderr** so stdout stays clean for the sink), `json` (a single
1069    /// machine-readable document on **stdout**), or `ndjson` (one JSON object
1070    /// per matrix row on **stdout**). With `json`/`ndjson`, stdout carries only
1071    /// the summary — logs stay on stderr — so `faucet run` is scriptable.
1072    #[arg(long, value_enum, default_value_t = RunOutput::Text)]
1073    pub output: RunOutput,
1074
1075    /// Supply a value for a `params:` entry declared by the config (#444):
1076    /// `--param tenant_id=acme`. Repeatable. Values are coerced to the declared
1077    /// type, so `--param page=50` satisfies a `type: int` param. A param with a
1078    /// `default` needs no flag; a `required` one errors when unsupplied.
1079    #[arg(long = "param", value_name = "NAME=VALUE")]
1080    pub param: Vec<String>,
1081
1082    /// Override an environment variable for this run's `${env:VAR}` resolution
1083    /// only (#444): `--param-env REGION=eu` sets it, bare `--param-env TOKEN`
1084    /// takes the value from the caller's environment. Repeatable. The process
1085    /// environment is not modified.
1086    #[arg(long = "param-env", value_name = "NAME[=VALUE]")]
1087    pub param_env: Vec<String>,
1088
1089    /// Runtime matrix-row selection (`--select`/`--only`/`--skip`/`--status`/
1090    /// `--tag`/`--include-parents`).
1091    #[command(flatten)]
1092    pub selection: SelectionArgs,
1093}
1094
1095/// Format for `faucet run`'s end-of-run summary.
1096#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, clap::ValueEnum)]
1097pub enum RunOutput {
1098    /// Human-readable one-line summary (default).
1099    #[default]
1100    Text,
1101    /// A single machine-readable JSON document with per-row + total stats.
1102    Json,
1103    /// One JSON object per matrix row (newline-delimited) for streaming consumers.
1104    Ndjson,
1105}
1106
1107/// `faucet backfill` arguments.
1108#[derive(Debug, Parser)]
1109pub struct BackfillArgs {
1110    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
1111    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
1112    pub config: Option<PathBuf>,
1113    /// Window start (inclusive): RFC3339 (`2026-06-01T00:00:00Z`) or a date
1114    /// (`2026-06-01`, midnight in --timezone). Requires --to.
1115    #[arg(long, requires = "to", conflicts_with = "from_bookmark")]
1116    pub from: Option<String>,
1117    /// Window end (exclusive): RFC3339 or a date.
1118    #[arg(long, requires = "from", conflicts_with = "from_bookmark")]
1119    pub to: Option<String>,
1120    /// Chunk the range into windows of this duration (`45s`, `30m`, `6h`,
1121    /// `1d`, `1w`) so each chunk is an independent, resumable unit. Defaults
1122    /// to the config's `backfill.window`; omitted = one unit for the whole
1123    /// range.
1124    #[arg(long)]
1125    pub window: Option<String>,
1126    /// Replay from this explicit bookmark value instead of a wall-clock
1127    /// range (seeded into the backfill's scoped state key; the source's own
1128    /// incremental logic reads forward from it). JSON or a bare string.
1129    #[arg(long)]
1130    pub from_bookmark: Option<String>,
1131    /// Upper bookmark bound: records whose --bookmark-field orders after
1132    /// this value are dropped before the sink.
1133    #[arg(long, requires_all = ["from_bookmark", "bookmark_field"])]
1134    pub to_bookmark: Option<String>,
1135    /// Record field the --to-bookmark bound applies to.
1136    #[arg(long)]
1137    pub bookmark_field: Option<String>,
1138    /// Max concurrently-running window units. Defaults to the config's
1139    /// `backfill.concurrency`, else 1 (sequential).
1140    #[arg(long)]
1141    pub concurrency: Option<usize>,
1142    /// IANA timezone for date boundaries and `${now.*}` rendering. Defaults
1143    /// to the config's `backfill.timezone`, else UTC.
1144    #[arg(long)]
1145    pub timezone: Option<String>,
1146    /// Root row of the config to backfill. Defaults to the only root.
1147    #[arg(long)]
1148    pub row: Option<String>,
1149    /// Redirect writes to this named sink template under `pipeline.sinks`
1150    /// (backfill into a staging table first).
1151    #[arg(long)]
1152    pub into: Option<String>,
1153    /// Print the planned units without running anything.
1154    #[arg(long)]
1155    pub dry_run: bool,
1156    /// Continue a previously-interrupted backfill of the same range: skip
1157    /// units already done, re-run failed and pending ones.
1158    #[arg(long, conflicts_with = "restart")]
1159    pub resume: bool,
1160    /// Discard a previous progress marker for this range and start over.
1161    #[arg(long)]
1162    pub restart: bool,
1163    /// Emit a machine-readable JSON report instead of the human summary.
1164    #[arg(long)]
1165    pub json: bool,
1166    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
1167    /// Defaults to `.env` in cwd if present.
1168    #[arg(long, conflicts_with = "no_env_file")]
1169    pub env_file: Option<PathBuf>,
1170    /// Skip auto-loading `.env` from cwd.
1171    #[arg(long)]
1172    pub no_env_file: bool,
1173    /// Select a named overlay from the config's `profiles:` block.
1174    /// Overrides the `FAUCET_PROFILE` env var.
1175    #[arg(long, env = "FAUCET_PROFILE")]
1176    pub profile: Option<String>,
1177}
1178
1179/// `faucet replicate` arguments.
1180#[derive(Debug, Parser)]
1181pub struct ReplicateArgs {
1182    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
1183    /// `replication:` block. If omitted, auto-discover
1184    /// `faucet.yaml` / `.yml` / `.json` in cwd.
1185    pub config: Option<PathBuf>,
1186    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
1187    /// Defaults to `.env` in cwd if present.
1188    #[arg(long, conflicts_with = "no_env_file")]
1189    pub env_file: Option<PathBuf>,
1190    /// Skip auto-loading `.env` from cwd.
1191    #[arg(long)]
1192    pub no_env_file: bool,
1193    /// Select a named overlay from the config's `profiles:` block and deep-merge
1194    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
1195    #[arg(long, env = "FAUCET_PROFILE")]
1196    pub profile: Option<String>,
1197}
1198
1199/// `faucet discover` arguments.
1200#[derive(Debug, Parser)]
1201pub struct DiscoverArgs {
1202    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config whose source
1203    /// points at the system to introspect. If omitted, auto-discover
1204    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
1205    pub config: Option<PathBuf>,
1206    /// Which source template to introspect (an entry under `pipeline.sources`).
1207    /// Defaults to `default` (the legacy singular `pipeline.source`).
1208    #[arg(long)]
1209    pub source: Option<String>,
1210    /// Only include datasets whose name matches this `*`-wildcard pattern
1211    /// (repeatable; no patterns = include everything).
1212    #[arg(long)]
1213    pub include: Vec<String>,
1214    /// Exclude datasets whose name matches this `*`-wildcard pattern
1215    /// (repeatable; applied after --include).
1216    #[arg(long)]
1217    pub exclude: Vec<String>,
1218    /// Write the generated config to this file instead of stdout.
1219    #[arg(long, short = 'o')]
1220    pub output: Option<PathBuf>,
1221    /// Overwrite the --output file if it already exists.
1222    #[arg(long)]
1223    pub force: bool,
1224    /// Emit the discovered datasets as machine-readable JSON instead of a
1225    /// generated config.
1226    #[arg(long)]
1227    pub json: bool,
1228    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
1229    /// Defaults to `.env` in cwd if present.
1230    #[arg(long, conflicts_with = "no_env_file")]
1231    pub env_file: Option<PathBuf>,
1232    /// Skip auto-loading `.env` from cwd.
1233    #[arg(long)]
1234    pub no_env_file: bool,
1235    /// Select a named overlay from the config's `profiles:` block.
1236    /// Overrides the `FAUCET_PROFILE` env var.
1237    #[arg(long, env = "FAUCET_PROFILE")]
1238    pub profile: Option<String>,
1239}
1240
1241/// `faucet validate` arguments.
1242#[derive(Debug, Parser)]
1243pub struct ValidateArgs {
1244    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
1245    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
1246    pub config: Option<PathBuf>,
1247    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
1248    /// Defaults to `.env` in cwd if present.
1249    #[arg(long, conflicts_with = "no_env_file")]
1250    pub env_file: Option<PathBuf>,
1251    /// Skip auto-loading `.env` from cwd.
1252    #[arg(long)]
1253    pub no_env_file: bool,
1254    /// Validate grammar and structure only — skip fetching from secrets
1255    /// managers (no network / credentials needed).
1256    #[arg(long)]
1257    pub no_secrets: bool,
1258    /// Select a named overlay from the config's `profiles:` block and deep-merge
1259    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
1260    #[arg(long, env = "FAUCET_PROFILE")]
1261    pub profile: Option<String>,
1262    /// Print the fully-composed config (after extends/!include/profile, before
1263    /// `${...}` interpolation) and exit. For debugging composition precedence.
1264    /// `--no-secrets` is redundant here (no interpolation or secret fetch occurs).
1265    #[arg(long)]
1266    pub show_composed: bool,
1267
1268    /// Supply a value for a declared `params:` entry (#444), e.g.
1269    /// `--param tenant_id=acme`. Repeatable. Without any `--param`, a `required`
1270    /// param is validated against a type-shaped placeholder — so a
1271    /// parameterized config validates in CI without inventing real values.
1272    /// Passing at least one `--param` switches to strict binding, checking that
1273    /// every required param is supplied and every value has the declared type.
1274    #[arg(long = "param", value_name = "NAME=VALUE")]
1275    pub param: Vec<String>,
1276
1277    /// Override an environment variable for this validation only:
1278    /// `--param-env REGION=eu`, or bare `--param-env TOKEN` to take it from the
1279    /// caller's environment. Repeatable.
1280    #[arg(long = "param-env", value_name = "NAME[=VALUE]")]
1281    pub param_env: Vec<String>,
1282
1283    /// Runtime matrix-row selection — `validate` reports each row's resolved
1284    /// status/tags and whether the selection would run or skip it.
1285    #[command(flatten)]
1286    pub selection: SelectionArgs,
1287
1288    /// Emit a structured JSON validation summary instead of the prose report,
1289    /// so CI can assert on it programmatically. Suppresses the human lines.
1290    #[arg(long)]
1291    pub json: bool,
1292}
1293
1294/// `faucet schema` arguments.
1295#[derive(Debug, Parser)]
1296pub struct SchemaArgs {
1297    #[command(subcommand)]
1298    pub target: Option<SchemaTarget>,
1299    /// List every valid schema target and exit, instead of printing a schema.
1300    #[arg(long)]
1301    pub list: bool,
1302}
1303
1304/// Schema subcommand target — which connector or system component to describe.
1305#[derive(Debug, Subcommand)]
1306pub enum SchemaTarget {
1307    /// Composed JSON Schema for the **entire** `faucet.yaml` / `faucet.json`
1308    /// config document (top-level grammar + per-connector `type` discrimination).
1309    /// Point an editor at it with a `# yaml-language-server: $schema=…` header.
1310    Config,
1311    /// JSON Schema for a source connector config.
1312    Source {
1313        /// Connector name (e.g. `rest`, `graphql`, `postgres`).
1314        #[arg(add = ArgValueCandidates::new(completions::source_kind_candidates))]
1315        name: String,
1316    },
1317    /// JSON Schema for a sink connector config.
1318    Sink {
1319        /// Connector name (e.g. `jsonl`, `bigquery`, `postgres`).
1320        #[arg(add = ArgValueCandidates::new(completions::sink_kind_candidates))]
1321        name: String,
1322    },
1323    /// JSON Schema for a transform's inline config.
1324    Transform {
1325        /// Transform name (e.g. `flatten`, `keys_case`, `cast`).
1326        /// Run `faucet list` to see what is compiled in.
1327        #[arg(add = ArgValueCandidates::new(completions::transform_candidates))]
1328        name: String,
1329    },
1330    /// JSON Schema for the DLQ (Dead Letter Queue) specification.
1331    Dlq,
1332    /// JSON Schema for the `replication:` (snapshot→CDC) block.
1333    Replication,
1334    /// JSON Schema for the `backfill:` (window replay defaults) block.
1335    Backfill,
1336    /// JSON Schema for the `partition:` (range partitioning) block.
1337    Partition,
1338    /// JSON Schema for the top-level `execution:` block.
1339    Execution,
1340    /// JSON Schema for the top-level `resilience:` block.
1341    Resilience,
1342    /// JSON Schema for the top-level `sla:` (freshness/volume SLA) block.
1343    Sla,
1344    /// JSON Schema for the `quality:` block.
1345    #[cfg(feature = "quality")]
1346    Quality,
1347    /// JSON Schema for the `contract:` block.
1348    #[cfg(feature = "contract")]
1349    Contract,
1350    /// JSON Schema for the `masking:` (PII masking) block.
1351    #[cfg(feature = "masking")]
1352    Masking,
1353    /// JSON Schema for the `faucet test` spec file.
1354    Test,
1355    /// Grammar reference for secrets-manager interpolation directives.
1356    Secrets,
1357    /// JSON Schema for the `schedule:` block.
1358    #[cfg(feature = "schedule")]
1359    Schedule,
1360    /// JSON Schema for the `lineage:` (OpenLineage) block.
1361    #[cfg(feature = "lineage")]
1362    Lineage,
1363    /// JSON Schema for the `--triggers` file (event-driven pipeline triggers).
1364    #[cfg(feature = "triggers")]
1365    Triggers,
1366    /// JSON Schema for the `notifications:` (incident-routing) block.
1367    #[cfg(feature = "notify")]
1368    Notifications,
1369    /// JSON Schema for the `catalog:` (Data Movement Catalog store) block.
1370    #[cfg(feature = "catalog")]
1371    Catalog,
1372    /// JSON Schema for one entry of the `params:` (typed run parameters) block.
1373    /// A config's `params:` maps names to entries of this shape; values are
1374    /// supplied per run via `--param` or a template trigger.
1375    Params,
1376}
1377
1378/// `faucet preview` arguments.
1379#[derive(Debug, Parser)]
1380pub struct PreviewArgs {
1381    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
1382    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
1383    pub config: Option<PathBuf>,
1384    /// Stop after this many records. Default: 10.
1385    #[arg(long, default_value_t = 10)]
1386    pub limit: usize,
1387    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
1388    /// Defaults to `.env` in cwd if present.
1389    #[arg(long, conflicts_with = "no_env_file")]
1390    pub env_file: Option<PathBuf>,
1391    /// Skip auto-loading `.env` from cwd.
1392    #[arg(long)]
1393    pub no_env_file: bool,
1394    /// Select a named overlay from the config's `profiles:` block and deep-merge
1395    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
1396    #[arg(long, env = "FAUCET_PROFILE")]
1397    pub profile: Option<String>,
1398
1399    /// Runtime matrix-row selection — `preview` previews the first root row of
1400    /// the selected run set.
1401    #[command(flatten)]
1402    pub selection: SelectionArgs,
1403}
1404
1405/// `faucet init` arguments.
1406#[derive(Debug, Parser)]
1407pub struct InitArgs {
1408    /// Name written into the generated file's `name:` field. Defaults to
1409    /// `my-pipeline` when omitted.
1410    pub name: Option<String>,
1411    /// Source connector kind to scaffold (e.g. `rest`, `postgres`, `s3`).
1412    /// Defaults to `rest`. Run `faucet list` to see what is compiled in.
1413    #[arg(long)]
1414    pub source: Option<String>,
1415    /// Sink connector kind to scaffold (e.g. `jsonl`, `bigquery`).
1416    /// Defaults to `jsonl`. Run `faucet list` to see what is compiled in.
1417    #[arg(long)]
1418    pub sink: Option<String>,
1419    /// Output file path. Defaults to `pipeline.yaml`.
1420    #[arg(long, short = 'o', default_value = "pipeline.yaml")]
1421    pub output: PathBuf,
1422    /// Overwrite the output file if it already exists.
1423    #[arg(long)]
1424    pub force: bool,
1425    /// Prompt for the source and sink kinds interactively instead of using
1426    /// `--source` / `--sink`. Requires the `cli-interactive` build feature
1427    /// and a TTY on stdin; falls back to the arg-driven path otherwise.
1428    #[arg(long)]
1429    pub interactive: bool,
1430    /// Name of the template under which to register the scaffolded source
1431    /// and sink. The generated config uses `pipeline.sources.<TEMPLATE>` and
1432    /// `pipeline.sinks.<TEMPLATE>`. Defaults to `default` so a matrix row
1433    /// without a `ref:` field still resolves through the new schema.
1434    #[arg(long, default_value = "default")]
1435    pub template: String,
1436    /// (singer only) Run `<executable> --discover` to fetch the tap's catalog,
1437    /// write it next to the output, and scaffold the config with the discovered
1438    /// streams listed. Requires `--source singer` and `--executable`.
1439    #[arg(long)]
1440    pub discover: bool,
1441    /// (singer only) The Singer tap executable to discover with (used by
1442    /// `--discover`), e.g. `tap-github` or `/opt/taps/tap-csv`.
1443    #[arg(long)]
1444    pub executable: Option<String>,
1445    /// (singer only) The target stream to emit. When given with `--discover`,
1446    /// the written catalog marks this stream — and any inferable parent
1447    /// streams (e.g. a parent-keyed tap's parent) — `selected`, and the
1448    /// scaffolded config's `stream:` is set to it. Most DB / SDK taps sync
1449    /// nothing unless a stream is selected in the catalog.
1450    #[arg(long)]
1451    pub stream: Option<String>,
1452}
1453
1454/// `faucet plan` arguments.
1455#[derive(Debug, Parser)]
1456pub struct PlanArgs {
1457    /// Path to a `.yaml`/`.yml`/`.json` config (auto-discovered if omitted).
1458    pub config: Option<PathBuf>,
1459    /// Which row to plan (default: the first root row).
1460    #[arg(long)]
1461    pub row: Option<String>,
1462    /// Offline sample of input records (`.jsonl` or a `.json` array) to preview
1463    /// the output schema, volume, and sink delta through — no source is touched.
1464    #[arg(long)]
1465    pub sample: Option<PathBuf>,
1466    /// Pull a capped, read-only sample from the real source instead of a
1467    /// fixture (bounded by `--limit`; no bookmark is advanced).
1468    #[arg(long)]
1469    pub live: bool,
1470    /// Cap for `--live` sampling.
1471    #[arg(long, default_value_t = 10)]
1472    pub limit: usize,
1473    /// Emit the plan as JSON.
1474    #[arg(long)]
1475    pub json: bool,
1476    /// Show a `terraform plan`-style diff of the current config against the last
1477    /// recorded run, instead of the resolved-pipeline preview (#374). Requires a
1478    /// `catalog:` block. Resolves secrets so the diff matches what `run` records.
1479    #[arg(long)]
1480    pub diff: bool,
1481    /// Resolve secrets-manager directives (needs network/credentials). Off by
1482    /// default so `plan` works offline like `faucet test`. Implied by `--diff`.
1483    #[arg(long)]
1484    pub resolve_secrets: bool,
1485    /// Select a `profiles:` overlay.
1486    #[arg(long, env = "FAUCET_PROFILE")]
1487    pub profile: Option<String>,
1488}
1489
1490/// `faucet dev` arguments.
1491#[derive(Debug, Parser)]
1492pub struct DevArgs {
1493    /// Path to the `.yaml`/`.yml`/`.json` config to watch.
1494    pub config: PathBuf,
1495    /// Which row to run (default: the first root row).
1496    #[arg(long)]
1497    pub row: Option<String>,
1498    /// Offline sample of input records (`.jsonl` or `.json` array). Required
1499    /// for the offline loop.
1500    #[arg(long)]
1501    pub sample: Option<PathBuf>,
1502    /// (reserved) pull a capped read-only sample from the real source.
1503    #[arg(long)]
1504    pub live: bool,
1505    /// Cap for `--live` sampling.
1506    #[arg(long, default_value_t = 10)]
1507    pub limit: usize,
1508    /// Run once and exit instead of watching (also the non-TTY fallback).
1509    #[arg(long)]
1510    pub once: bool,
1511    /// Debounce window between re-runs, in milliseconds.
1512    #[arg(long, default_value_t = 300)]
1513    pub debounce_ms: u64,
1514    /// Select a `profiles:` overlay.
1515    #[arg(long, env = "FAUCET_PROFILE")]
1516    pub profile: Option<String>,
1517}
1518
1519/// `faucet list` arguments.
1520#[derive(Debug, Parser)]
1521pub struct ListArgs {
1522    /// List every connector in the registry index (not just the compiled-in
1523    /// ones), marking which are already in this binary.
1524    #[arg(long)]
1525    pub available: bool,
1526    /// Read a custom registry index instead of the built-in one.
1527    #[arg(long)]
1528    pub index: Option<PathBuf>,
1529    /// Emit the listing as JSON instead of the human-readable columns.
1530    #[arg(long)]
1531    pub json: bool,
1532}
1533
1534/// `faucet conformance` arguments.
1535#[derive(Debug, Parser)]
1536pub struct ConformanceArgs {
1537    /// Only score the connector with this system name (e.g. `postgres`); prints
1538    /// a detailed scorecard. Omit to score every compiled-in connector.
1539    pub name: Option<String>,
1540    /// Restrict to `source` or `sink`.
1541    #[arg(long)]
1542    pub kind: Option<String>,
1543    /// Score every compiled-in connector (the default when no NAME is given;
1544    /// accepted explicitly for clarity in CI).
1545    #[arg(long)]
1546    pub all: bool,
1547    /// Emit the full scorecards as JSON.
1548    #[arg(long)]
1549    pub json: bool,
1550    /// Fail (exit non-zero) if any scored connector is below this maturity tier
1551    /// — an opt-in CI gate. One of `stable` / `experimental` / `beta` / `draft`.
1552    #[arg(long, value_name = "TIER")]
1553    pub min_tier: Option<String>,
1554    /// Print the connector capability matrix (Markdown) derived from the
1555    /// registry allowlists and exit — the generated source for the docs-site
1556    /// capability matrix. Ignores the scoring flags.
1557    #[arg(long)]
1558    pub matrix: bool,
1559}
1560
1561/// `faucet search` arguments.
1562#[derive(Debug, Parser)]
1563pub struct SearchArgs {
1564    /// Term to match against connector name / description / keywords / crate.
1565    pub term: String,
1566    /// Read a custom registry index instead of the built-in one.
1567    #[arg(long)]
1568    pub index: Option<PathBuf>,
1569    /// Emit matches as JSON.
1570    #[arg(long)]
1571    pub json: bool,
1572}
1573
1574/// `faucet install` arguments.
1575#[derive(Debug, Parser)]
1576pub struct InstallArgs {
1577    /// Connector system name (e.g. `kafka`).
1578    pub name: String,
1579    /// Disambiguate when a name exists as both a source and a sink.
1580    #[arg(long)]
1581    pub kind: Option<String>,
1582    /// Read a custom registry index instead of the built-in one.
1583    #[arg(long)]
1584    pub index: Option<PathBuf>,
1585}
1586
1587/// `faucet new` arguments.
1588#[derive(Debug, Parser)]
1589pub struct NewArgs {
1590    #[command(subcommand)]
1591    pub target: NewTarget,
1592}
1593
1594/// What `faucet new` scaffolds.
1595#[derive(Debug, Subcommand)]
1596pub enum NewTarget {
1597    /// Scaffold a ready-to-build `faucet-source-<name>` / `faucet-sink-<name>`
1598    /// connector crate following every repo convention.
1599    Connector(NewConnectorArgs),
1600}
1601
1602/// `faucet new connector` arguments.
1603#[derive(Debug, Parser)]
1604pub struct NewConnectorArgs {
1605    /// Connector system name (lowercase, e.g. `acme` or `acme-widgets`). Becomes
1606    /// the crate name `faucet-<kind>-<name>` and the YAML `type:` value.
1607    pub name: String,
1608    /// Whether to scaffold a `source` or a `sink`.
1609    #[arg(long)]
1610    pub kind: String,
1611    /// Also scaffold a `faucet-common-<name>` crate for config shared between a
1612    /// source/sink pair.
1613    #[arg(long)]
1614    pub common: bool,
1615    /// Directory to write the new crate(s) into. Defaults to the current dir.
1616    #[arg(long, short = 'o', default_value = ".")]
1617    pub output: PathBuf,
1618    /// Overwrite any existing files.
1619    #[arg(long)]
1620    pub force: bool,
1621}