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    /// Generate a shell tab-completion script (bash / zsh / fish / powershell /
147    /// elvish). For registry- and config-aware *dynamic* completion, enable the
148    /// `COMPLETE` hook instead, e.g. `source <(COMPLETE=zsh faucet)`.
149    Completions(CompletionsArgs),
150    /// Upgrade a config written against an older `faucet` grammar to the current
151    /// shape (e.g. pre-`pipeline:` top-level source/sink, legacy inline auth).
152    /// Idempotent; rewrites in place unless `--check` / `--stdout`.
153    Migrate(MigrateArgs),
154    /// Canonicalize a config: stable key order, normalized style. Idempotent;
155    /// rewrites in place unless `--check` / `--stdout`. Comments are not
156    /// preserved (the config is parsed and re-serialized).
157    Fmt(FmtArgs),
158    /// Explain, in plain English, what a pipeline config does — source →
159    /// transforms → sink, matrix expansion, replication, delivery guarantee,
160    /// and state store. Read-only and fully offline (no source is touched).
161    Explain(ExplainArgs),
162    /// Show recent run history recorded in a config's `catalog:` store —
163    /// status, duration, throughput, and bookmark. Read-only; requires the
164    /// `catalog` build feature.
165    #[cfg(feature = "catalog")]
166    History(HistoryArgs),
167}
168
169/// `faucet migrate` arguments.
170#[derive(Debug, Args)]
171pub struct MigrateArgs {
172    /// Config file to migrate. Auto-discovered (`faucet.yaml` → `.yml` →
173    /// `.json`) when omitted.
174    #[arg(value_hint = clap::ValueHint::FilePath)]
175    pub config: Option<PathBuf>,
176    /// Report whether a migration is needed without writing (exits non-zero if
177    /// the config is not current). For CI / pre-upgrade checks.
178    #[arg(long)]
179    pub check: bool,
180    /// Write the migrated config to stdout instead of rewriting the file.
181    #[arg(long, conflicts_with = "check")]
182    pub stdout: bool,
183}
184
185/// `faucet fmt` arguments.
186#[derive(Debug, Args)]
187pub struct FmtArgs {
188    /// Config file(s) to format. Auto-discovered (`faucet.yaml` → `.yml` →
189    /// `.json`) when none are given.
190    #[arg(value_hint = clap::ValueHint::FilePath)]
191    pub configs: Vec<PathBuf>,
192    /// Report whether each file is already canonical without writing (exits
193    /// non-zero and prints a unified diff for any file that is not). For CI.
194    #[arg(long)]
195    pub check: bool,
196    /// Write the formatted result to stdout instead of rewriting the file(s).
197    #[arg(long, conflicts_with = "check")]
198    pub stdout: bool,
199}
200
201/// `faucet explain` arguments.
202#[derive(Debug, Args)]
203pub struct ExplainArgs {
204    /// Path to a `.yaml`/`.yml`/`.json` config (auto-discovered if omitted).
205    pub config: Option<PathBuf>,
206    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
207    /// Defaults to `.env` in cwd if present.
208    #[arg(long, conflicts_with = "no_env_file")]
209    pub env_file: Option<PathBuf>,
210    /// Skip auto-loading `.env` from cwd.
211    #[arg(long)]
212    pub no_env_file: bool,
213    /// Select a named overlay from the config's `profiles:` block.
214    #[arg(long, env = "FAUCET_PROFILE")]
215    pub profile: Option<String>,
216    /// Emit the narration as structured JSON instead of prose.
217    #[arg(long)]
218    pub json: bool,
219    /// Narrate every matrix row instead of summarizing a large matrix.
220    #[arg(long)]
221    pub rows: bool,
222}
223
224/// `faucet history` arguments.
225#[cfg(feature = "catalog")]
226#[derive(Debug, Args)]
227pub struct HistoryArgs {
228    /// Path to a config carrying a `catalog:` block (auto-discovered if omitted).
229    pub config: Option<PathBuf>,
230    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
231    #[arg(long, conflicts_with = "no_env_file")]
232    pub env_file: Option<PathBuf>,
233    /// Skip auto-loading `.env` from cwd.
234    #[arg(long)]
235    pub no_env_file: bool,
236    /// Select a named overlay from the config's `profiles:` block.
237    #[arg(long, env = "FAUCET_PROFILE")]
238    pub profile: Option<String>,
239    /// Maximum number of runs to show, newest first.
240    #[arg(long, default_value_t = 20)]
241    pub limit: usize,
242    /// Show only runs that contain an invocation for this matrix row id.
243    #[arg(long)]
244    pub row: Option<String>,
245    /// Emit the history as JSON instead of a table.
246    #[arg(long)]
247    pub json: bool,
248}
249
250/// `faucet completions` arguments.
251#[derive(Debug, Args)]
252pub struct CompletionsArgs {
253    /// Target shell.
254    pub shell: clap_complete::aot::Shell,
255}
256
257/// `faucet catalog` arguments.
258#[cfg(feature = "catalog")]
259#[derive(Debug, Parser)]
260pub struct CatalogArgs {
261    #[command(subcommand)]
262    pub command: CatalogCommand,
263}
264
265/// `faucet catalog` subcommands.
266#[cfg(feature = "catalog")]
267#[derive(Debug, Subcommand)]
268pub enum CatalogCommand {
269    /// List every catalogued dataset (newest activity first).
270    Datasets(CatalogDatasetsArgs),
271    /// Show one dataset's detail: schema timeline, volume points, edges.
272    Show(CatalogShowArgs),
273    /// Print the dataset lineage graph (optionally rooted at a dataset).
274    Lineage(CatalogLineageArgs),
275}
276
277/// Shared config-loading flags for the `faucet catalog` subcommands.
278#[cfg(feature = "catalog")]
279#[derive(Debug, Parser)]
280pub struct CatalogConfigArgs {
281    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
282    /// `catalog:` block naming the store. If omitted, auto-discover
283    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
284    #[arg(long)]
285    pub config: Option<PathBuf>,
286    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
287    /// Defaults to `.env` in cwd if present.
288    #[arg(long, conflicts_with = "no_env_file")]
289    pub env_file: Option<PathBuf>,
290    /// Skip auto-loading `.env` from cwd.
291    #[arg(long)]
292    pub no_env_file: bool,
293    /// Select a named overlay from the config's `profiles:` block.
294    /// Overrides the `FAUCET_PROFILE` env var.
295    #[arg(long, env = "FAUCET_PROFILE")]
296    pub profile: Option<String>,
297    /// Emit machine-readable JSON instead of the human summary.
298    #[arg(long)]
299    pub json: bool,
300}
301
302/// `faucet catalog datasets` arguments.
303#[cfg(feature = "catalog")]
304#[derive(Debug, Parser)]
305pub struct CatalogDatasetsArgs {
306    #[command(flatten)]
307    pub common: CatalogConfigArgs,
308    /// Only datasets of this connector kind (e.g. `postgres`, `csv`).
309    #[arg(long)]
310    pub kind: Option<String>,
311    /// Case-insensitive substring match on the dataset URI.
312    #[arg(long)]
313    pub q: Option<String>,
314    /// Max datasets to list.
315    #[arg(long, default_value_t = 100)]
316    pub limit: usize,
317}
318
319/// `faucet catalog show <id>` arguments.
320#[cfg(feature = "catalog")]
321#[derive(Debug, Parser)]
322pub struct CatalogShowArgs {
323    /// Dataset id (from `faucet catalog datasets`), or a unique prefix of one.
324    pub id: String,
325    #[command(flatten)]
326    pub common: CatalogConfigArgs,
327}
328
329/// `faucet catalog lineage` arguments.
330#[cfg(feature = "catalog")]
331#[derive(Debug, Parser)]
332pub struct CatalogLineageArgs {
333    #[command(flatten)]
334    pub common: CatalogConfigArgs,
335    /// Dataset id to root the graph at (whole graph when omitted).
336    #[arg(long)]
337    pub root: Option<String>,
338    /// BFS hop bound around --root.
339    #[arg(long, default_value_t = 5)]
340    pub depth: u32,
341}
342
343/// `faucet notify test` arguments.
344#[cfg(feature = "notify")]
345#[derive(Debug, Parser)]
346pub struct NotifyArgs {
347    #[command(subcommand)]
348    pub command: NotifyCommand,
349}
350
351/// `faucet notify` subcommands.
352#[cfg(feature = "notify")]
353#[derive(Debug, Subcommand)]
354pub enum NotifyCommand {
355    /// Fire one synthetic event at every matching rule in the config.
356    Test(NotifyTestArgs),
357}
358
359/// `faucet notify test <config>` arguments.
360#[cfg(feature = "notify")]
361#[derive(Debug, Parser)]
362pub struct NotifyTestArgs {
363    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
364    /// `notifications:` block. If omitted, auto-discover in cwd.
365    pub config: Option<PathBuf>,
366    /// Which event to synthesize (defaults to `run_failure`).
367    #[arg(long, default_value = "run_failure")]
368    pub event: String,
369    /// Path to a `.env` file for `${env:VAR}` interpolation.
370    #[arg(long, conflicts_with = "no_env_file")]
371    pub env_file: Option<PathBuf>,
372    /// Disable `.env` auto-discovery.
373    #[arg(long)]
374    pub no_env_file: bool,
375}
376
377/// `faucet test` arguments.
378#[derive(Debug, Parser)]
379pub struct TestArgs {
380    /// One or more test-spec files (`.yaml`, `.yml`, or `.json`), e.g.
381    /// `faucet test tests/*.yaml`.
382    #[arg(required = true)]
383    pub specs: Vec<PathBuf>,
384    /// Run only cases whose name contains this substring.
385    #[arg(long)]
386    pub filter: Option<String>,
387    /// Emit a machine-readable JSON report instead of the human checklist.
388    #[arg(long)]
389    pub json: bool,
390    /// Default `${now.*}` clock for cases without their own `clock:` field
391    /// (RFC3339 like `2026-01-31T00:00:00Z`, or a date `2026-01-31`).
392    /// Defaults to process start (UTC).
393    #[arg(long)]
394    pub clock: Option<String>,
395    /// Path to a `.env` file to load for `${env:VAR}` interpolation in
396    /// referenced pipeline configs. Defaults to `.env` in cwd if present.
397    #[arg(long, conflicts_with = "no_env_file")]
398    pub env_file: Option<PathBuf>,
399    /// Skip auto-loading `.env` from cwd.
400    #[arg(long)]
401    pub no_env_file: bool,
402    /// Select a named overlay from each referenced config's `profiles:` block.
403    /// Overrides the `FAUCET_PROFILE` env var.
404    #[arg(long, env = "FAUCET_PROFILE")]
405    pub profile: Option<String>,
406    /// Resolve `${vault:…}` / `${aws-sm:…}` / … secret directives in
407    /// referenced configs (requires network + credentials). By default tests
408    /// load configs offline and leave secret directives unresolved — safe
409    /// because the real source/sink configs holding them are never used.
410    #[arg(long)]
411    pub resolve_secrets: bool,
412}
413
414/// `faucet dlq` arguments.
415#[derive(Debug, Parser)]
416pub struct DlqArgs {
417    #[command(subcommand)]
418    pub command: DlqCommand,
419}
420
421/// `faucet dlq` subcommands.
422#[derive(Debug, Subcommand)]
423pub enum DlqCommand {
424    /// Read a DLQ location back and print a per-reason / per-error-kind
425    /// breakdown plus a sample of quarantined records.
426    Inspect(DlqInspectArgs),
427    /// Re-feed quarantined records through a pipeline config (transforms →
428    /// quality → contract → sink). Rows that fail again land in a *fresh* DLQ.
429    Replay(DlqReplayArgs),
430    /// Remove processed envelopes from a DLQ location (archive by default,
431    /// or `--delete`), filtered by reason and/or age.
432    Discard(DlqDiscardArgs),
433}
434
435/// `faucet dlq inspect <location>` arguments.
436#[derive(Debug, Parser)]
437pub struct DlqInspectArgs {
438    /// DLQ location: a `.jsonl` file, a directory of `*.jsonl` files, or a glob.
439    pub location: String,
440    /// Only include envelopes with this DLQ reason (`partial` / `dlq_all` /
441    /// `quality` / `schema_drift` / `contract`).
442    #[arg(long)]
443    pub reason: Option<String>,
444    /// Number of sample records to show. Default: 5.
445    #[arg(long, default_value_t = 5)]
446    pub limit: usize,
447    /// Key for a DLQ sealed at rest by the jsonl sink's `encryption` block.
448    /// Repeat the flag to also try older (rotated) keys. Requires a build
449    /// with the `encryption` feature.
450    #[arg(long = "encryption-key")]
451    pub encryption_key: Vec<String>,
452    /// Emit a machine-readable JSON summary instead of the human report.
453    #[arg(long)]
454    pub json: bool,
455}
456
457/// `faucet dlq replay <config> --from <location>` arguments.
458#[derive(Debug, Parser)]
459pub struct DlqReplayArgs {
460    /// Path to the pipeline config whose sink / transforms / quality / contract
461    /// the replayed records flow through. If omitted, auto-discover in cwd.
462    pub config: Option<PathBuf>,
463    /// DLQ location to replay from: a `.jsonl` file, a directory, or a glob.
464    #[arg(long)]
465    pub from: String,
466    /// Only replay envelopes with this DLQ reason.
467    #[arg(long)]
468    pub reason: Option<String>,
469    /// Where replayed rows that fail *again* are quarantined. Defaults to a
470    /// `replay-failed.jsonl` sibling of the source (never the source itself).
471    #[arg(long)]
472    pub failed_dlq: Option<String>,
473    /// Which root row of the config to replay through. Defaults to the first root.
474    #[arg(long)]
475    pub row: Option<String>,
476    /// Report what would be replayed without writing to the sink.
477    #[arg(long)]
478    pub dry_run: bool,
479    /// Key for a DLQ sealed at rest by the jsonl sink's `encryption` block.
480    /// Repeat the flag to also try older (rotated) keys. Requires a build
481    /// with the `encryption` feature.
482    #[arg(long = "encryption-key")]
483    pub encryption_key: Vec<String>,
484    /// (Replay picks up the config's own dlq `encryption` block automatically
485    /// when no key is passed.)
486    /// Emit a machine-readable JSON result instead of the human summary.
487    #[arg(long)]
488    pub json: bool,
489    /// Path to a `.env` file for `${env:VAR}` interpolation in the config.
490    #[arg(long, conflicts_with = "no_env_file")]
491    pub env_file: Option<PathBuf>,
492    /// Skip auto-loading `.env` from cwd.
493    #[arg(long)]
494    pub no_env_file: bool,
495    /// Select a named overlay from the config's `profiles:` block.
496    #[arg(long, env = "FAUCET_PROFILE")]
497    pub profile: Option<String>,
498}
499
500/// `faucet dlq discard <location>` arguments.
501#[derive(Debug, Parser)]
502pub struct DlqDiscardArgs {
503    /// DLQ location: a `.jsonl` file, a directory of `*.jsonl` files, or a glob.
504    pub location: String,
505    /// Only discard envelopes with this DLQ reason.
506    #[arg(long)]
507    pub reason: Option<String>,
508    /// Only discard envelopes older than this: an RFC3339 timestamp
509    /// (`2026-06-01T00:00:00Z`) or a relative age (`7d`, `24h`, `30m`).
510    #[arg(long)]
511    pub before: Option<String>,
512    /// Permanently delete matching envelopes instead of archiving them to a
513    /// `<file>.archived.jsonl` sibling.
514    #[arg(long)]
515    pub delete: bool,
516    /// Key for a DLQ sealed at rest by the jsonl sink's `encryption` block.
517    /// Repeat the flag to also try older (rotated) keys. Requires a build
518    /// with the `encryption` feature.
519    #[arg(long = "encryption-key")]
520    pub encryption_key: Vec<String>,
521    /// Emit a machine-readable JSON result instead of the human summary.
522    #[arg(long)]
523    pub json: bool,
524}
525
526/// `faucet doctor` arguments.
527#[derive(Debug, Parser)]
528pub struct DoctorArgs {
529    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
530    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
531    pub config: Option<PathBuf>,
532    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
533    /// Defaults to `.env` in cwd if present.
534    #[arg(long, conflicts_with = "no_env_file")]
535    pub env_file: Option<PathBuf>,
536    /// Skip auto-loading `.env` from cwd.
537    #[arg(long)]
538    pub no_env_file: bool,
539    /// Per-probe timeout in seconds.
540    #[arg(long, default_value_t = 10)]
541    pub timeout_secs: u64,
542    /// Emit machine-readable JSON instead of the human checklist.
543    #[arg(long)]
544    pub json: bool,
545    /// Run only the offline static config lints (no network probes): dangling /
546    /// unreferenced `auth:` providers, unused `vars:`, and no-op sink
547    /// `batch_size: 0`. Fast and credential-free — ideal for CI. Exits non-zero
548    /// on any lint *error* (warnings don't fail).
549    #[arg(long)]
550    pub offline: bool,
551    /// Select a named overlay from the config's `profiles:` block and deep-merge
552    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
553    #[arg(long, env = "FAUCET_PROFILE")]
554    pub profile: Option<String>,
555}
556
557/// `faucet contract` arguments.
558#[cfg(feature = "contract")]
559#[derive(Debug, Parser)]
560pub struct ContractArgs {
561    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
562    /// `pipeline.contract:` block. If omitted, auto-discover
563    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
564    pub config: Option<PathBuf>,
565    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
566    /// Defaults to `.env` in cwd if present.
567    #[arg(long, conflicts_with = "no_env_file")]
568    pub env_file: Option<PathBuf>,
569    /// Skip auto-loading `.env` from cwd.
570    #[arg(long)]
571    pub no_env_file: bool,
572    /// Select a named overlay from the config's `profiles:` block and deep-merge
573    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
574    #[arg(long, env = "FAUCET_PROFILE")]
575    pub profile: Option<String>,
576    /// Export the contract in a machine-readable format instead of the
577    /// human summary: the canonical contract JSON, a standalone JSON Schema,
578    /// or an OpenLineage schema facet.
579    #[arg(long, value_enum)]
580    pub export: Option<ContractExportFormat>,
581}
582
583/// Arguments for `faucet masking`.
584#[cfg(feature = "masking")]
585#[derive(Debug, Parser)]
586pub struct MaskingArgs {
587    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
588    /// `pipeline.masking:` block. If omitted, auto-discover
589    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
590    pub config: Option<PathBuf>,
591    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
592    /// Defaults to `.env` in cwd if present.
593    #[arg(long, conflicts_with = "no_env_file")]
594    pub env_file: Option<PathBuf>,
595    /// Skip auto-loading `.env` from cwd.
596    #[arg(long)]
597    pub no_env_file: bool,
598    /// Select a named overlay from the config's `profiles:` block and deep-merge
599    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
600    #[arg(long, env = "FAUCET_PROFILE")]
601    pub profile: Option<String>,
602}
603
604/// Export format for `faucet contract --export`.
605#[cfg(feature = "contract")]
606#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
607pub enum ContractExportFormat {
608    /// The canonical contract document as JSON.
609    Contract,
610    /// A standalone JSON Schema (draft 2020-12) for the promised records.
611    JsonSchema,
612    /// An OpenLineage `SchemaDatasetFacet` JSON document.
613    Openlineage,
614}
615
616/// `faucet schedule` arguments.
617#[cfg(feature = "schedule")]
618#[derive(Debug, Parser)]
619pub struct ScheduleArgs {
620    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a `schedule:`
621    /// block. If omitted, auto-discover `faucet.yaml` / `.yml` / `.json` in cwd.
622    pub config: Option<PathBuf>,
623    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
624    /// Defaults to `.env` in cwd if present.
625    #[arg(long, conflicts_with = "no_env_file")]
626    pub env_file: Option<PathBuf>,
627    /// Skip auto-loading `.env` from cwd.
628    #[arg(long)]
629    pub no_env_file: bool,
630    /// Run exactly one pipeline run immediately, then exit (ignores cron timing).
631    /// Useful for platform-driven invocation (k8s CronJob / systemd OnCalendar).
632    #[arg(long)]
633    pub once: bool,
634    /// Select a named overlay from the config's `profiles:` block and deep-merge
635    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
636    #[arg(long, env = "FAUCET_PROFILE")]
637    pub profile: Option<String>,
638}
639
640/// `faucet serve` arguments.
641#[cfg(feature = "serve")]
642#[derive(Debug, Clone, Parser)]
643pub struct ServeArgs {
644    /// Bind address. Defaults to loopback; set 0.0.0.0:PORT to expose externally.
645    #[arg(long, env = "FAUCET_SERVE_LISTEN", default_value = "127.0.0.1:8080")]
646    pub listen: String,
647    /// Bearer token required on /v1/* requests. Prefer the env var (avoids `ps` leakage).
648    #[arg(long, env = "FAUCET_SERVE_AUTH_TOKEN", conflicts_with = "no_auth")]
649    pub auth_token: Option<String>,
650    /// Explicitly disable authentication. Required if no token is set, so an
651    /// unauthenticated server is never accidental.
652    #[arg(long)]
653    pub no_auth: bool,
654    /// Path to an RBAC auth config (YAML/JSON) defining principals — each a
655    /// `{ name, token, role }` where role is `viewer` / `operator` / `admin`.
656    /// Enables role-based access control + an audit log. Mutually exclusive with
657    /// `--auth-token` / `--no-auth`.
658    #[arg(long, conflicts_with_all = ["auth_token", "no_auth"])]
659    pub auth_config: Option<std::path::PathBuf>,
660    /// Max pipeline runs executing at once. Default: min(16, cpu count).
661    #[arg(long)]
662    pub max_concurrent_runs: Option<usize>,
663    /// Max queued (not-yet-running) runs before POST /v1/runs returns 429.
664    /// Default: 8 × max-concurrent-runs.
665    #[arg(long)]
666    pub max_queued_runs: Option<usize>,
667    /// Workspace-default config merged under every submitted run.
668    #[arg(long)]
669    pub default_config: Option<std::path::PathBuf>,
670    /// Run-history backend URL: omitted = in-memory; postgres://… ; sqlite:… .
671    #[arg(long)]
672    pub history: Option<String>,
673    /// CORS allow-list origin (repeatable). Omitted = CORS disabled.
674    #[arg(long)]
675    pub cors_origin: Vec<String>,
676    /// Max POST /v1/runs body size in bytes (413 on exceed).
677    #[arg(long, default_value_t = 1_048_576)]
678    pub body_limit_bytes: usize,
679    /// SIGTERM/SIGINT drain window in seconds.
680    #[arg(long, default_value_t = 60)]
681    pub shutdown_grace_secs: u64,
682    /// Retain terminal run records this long (seconds).
683    #[arg(long, default_value_t = 604_800)]
684    pub retain_terminal_runs_secs: u64,
685    /// Idempotency-key replay window (seconds).
686    #[arg(long, default_value_t = 86_400)]
687    pub idempotency_retention_secs: u64,
688    /// Run-ownership lease TTL in seconds (multi-instance orphan fencing). A run
689    /// is owned by the instance executing it and its lease is heartbeated at
690    /// ~⅓ of this interval; only a run whose lease has expired (owner presumed
691    /// dead) is recovered as failed. Make this comfortably larger than expected
692    /// GC/IO stalls so a healthy-but-slow instance is never falsely reclaimed.
693    /// Only relevant with a persistent (postgres/sqlite) history backend.
694    #[arg(long, default_value_t = 30)]
695    pub lease_ttl_secs: u64,
696    /// Per-probe timeout for `doctor_first` preflight (seconds).
697    #[arg(long, default_value_t = 10)]
698    pub probe_timeout_secs: u64,
699    /// Path to a `.env` file loaded for the server's own startup interpolation.
700    #[arg(long, conflicts_with = "no_env_file")]
701    pub env_file: Option<std::path::PathBuf>,
702    /// Skip auto-loading `.env` from cwd at startup.
703    #[arg(long)]
704    pub no_env_file: bool,
705    /// Disable serving the embedded web console (only meaningful in a build that
706    /// includes the `serve-ui` feature; the API is unaffected).
707    #[arg(long)]
708    pub no_ui: bool,
709    /// Enable clustered execution: run a claim loop that pulls Pending runs from
710    /// the shared history DB so N instances pull-balance and fail over. Requires
711    /// a postgres/sqlite --history backend.
712    #[arg(long)]
713    pub cluster: bool,
714    /// Claim-loop poll interval (seconds) in cluster mode. Also the
715    /// cross-instance cancel-propagation lag. Must be > 0.
716    #[arg(long, default_value_t = 2)]
717    pub cluster_poll_secs: u64,
718    /// Max failover re-runs of an orphaned run before it is marked Failed
719    /// (poison). Must be > 0.
720    #[arg(long, default_value_t = 3)]
721    pub cluster_max_attempts: u32,
722    /// Path to a triggers file (YAML/JSON) defining event-driven pipeline
723    /// triggers (object-arrival / webhook / queue-depth). Requires a build with
724    /// the `triggers` feature. See `faucet schema triggers`.
725    #[arg(long)]
726    pub triggers: Option<std::path::PathBuf>,
727    /// Mount the MCP (Model Context Protocol) endpoint at `/mcp`, exposing
728    /// faucet as agent tool calls. Effective only in a build with the `mcp`
729    /// feature; the endpoint inherits serve's bearer-auth + RBAC + audit.
730    #[arg(long)]
731    pub mcp: bool,
732    /// Allow the MCP endpoint's *mutating* tools (`run_pipeline`). Off by
733    /// default: only read-only tools are exposed. A caller still needs the
734    /// `RunWrite` RBAC scope. Only meaningful together with `--mcp`.
735    #[arg(long)]
736    pub mcp_allow_mutations: bool,
737}
738
739/// `faucet mcp` arguments — run an MCP server over stdio (#420).
740#[cfg(feature = "mcp")]
741#[derive(Debug, Clone, Parser)]
742pub struct McpArgs {
743    /// Allow mutating tools (`run_pipeline`). Off by default — only read-only
744    /// tools (list / schema / scaffold / validate / preview) are exposed.
745    /// stdio is local-trust: there is no bearer/RBAC layer, so enable this only
746    /// for a trusted local agent.
747    #[arg(long)]
748    pub allow_mutations: bool,
749    /// Optional `.env` file to load before starting (for `${env:…}` in configs
750    /// passed to `validate`/`preview`/`run_pipeline`).
751    #[arg(long, conflicts_with = "no_env_file")]
752    pub env_file: Option<std::path::PathBuf>,
753    /// Skip auto-loading `.env` from cwd at startup.
754    #[arg(long)]
755    pub no_env_file: bool,
756}
757
758/// `faucet run` arguments.
759#[derive(Debug, Parser)]
760pub struct RunArgs {
761    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config.
762    /// If omitted (and `--from-env` is not set), auto-discover
763    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in the current directory.
764    /// Mutually exclusive with `--from-env`.
765    #[arg(conflicts_with = "from_env")]
766    pub config: Option<PathBuf>,
767    /// Build the pipeline entirely from `FAUCET_*` environment variables —
768    /// no YAML required. See `cli/README.md` for the variable schema.
769    #[arg(long)]
770    pub from_env: bool,
771    /// Path to a `.env` file to load before reading variables. Works in both
772    /// YAML mode (for `${env:VAR}` interpolation) and `--from-env` mode.
773    /// When omitted, `.env` in the current directory is auto-loaded if present.
774    /// Existing process-env values always win over file-supplied ones.
775    #[arg(long, conflicts_with = "no_env_file")]
776    pub env_file: Option<PathBuf>,
777    /// Skip auto-loading `.env` from the current directory.
778    #[arg(long)]
779    pub no_env_file: bool,
780    /// Stop after fetching from the source — write nothing to the sink.
781    #[arg(long)]
782    pub dry_run: bool,
783    /// Stop after writing this many records to the sink. Default: unlimited.
784    #[arg(long)]
785    pub limit: Option<usize>,
786    /// Override the state-store directory (file backend only).
787    #[arg(long)]
788    pub state_path: Option<PathBuf>,
789    /// Override the `${now.*}` interpolation clock (RFC3339 like
790    /// `2026-01-31T00:00:00Z`, or a date `2026-01-31`). Default: process start (UTC).
791    /// Use for backfills.
792    #[arg(long)]
793    pub clock: Option<String>,
794    /// Select a named overlay from the config's `profiles:` block and deep-merge
795    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
796    /// Not applicable in `--from-env` mode (no config file to compose).
797    #[arg(long, env = "FAUCET_PROFILE")]
798    pub profile: Option<String>,
799    /// Show a live full-screen terminal UI (per-invocation throughput, errors,
800    /// DLQ counts, bookmark age) while the pipeline runs. Requires a binary
801    /// built with the `cli-tui` feature and a real terminal on stdout —
802    /// on a non-TTY (CI, pipes) the run proceeds normally with a notice.
803    /// Press `q` to cancel cooperatively (in-flight work flushes at the next
804    /// page boundary).
805    #[arg(long)]
806    pub tui: bool,
807
808    /// Suppress the inline live progress line (records in/out, rows/s, pages,
809    /// elapsed) that `faucet run` shows on an interactive terminal. The
810    /// progress line is already auto-disabled on a non-TTY stdout (CI, pipes)
811    /// and when `--tui` is used; `--quiet` turns it off explicitly, keeping
812    /// only the periodic log output.
813    #[arg(long)]
814    pub quiet: bool,
815
816    /// Format for the end-of-run summary: `text` (default, human — written to
817    /// **stderr** so stdout stays clean for the sink), `json` (a single
818    /// machine-readable document on **stdout**), or `ndjson` (one JSON object
819    /// per matrix row on **stdout**). With `json`/`ndjson`, stdout carries only
820    /// the summary — logs stay on stderr — so `faucet run` is scriptable.
821    #[arg(long, value_enum, default_value_t = RunOutput::Text)]
822    pub output: RunOutput,
823
824    /// Runtime matrix-row selection (`--select`/`--only`/`--skip`/`--status`/
825    /// `--tag`/`--include-parents`).
826    #[command(flatten)]
827    pub selection: SelectionArgs,
828}
829
830/// Format for `faucet run`'s end-of-run summary.
831#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, clap::ValueEnum)]
832pub enum RunOutput {
833    /// Human-readable one-line summary (default).
834    #[default]
835    Text,
836    /// A single machine-readable JSON document with per-row + total stats.
837    Json,
838    /// One JSON object per matrix row (newline-delimited) for streaming consumers.
839    Ndjson,
840}
841
842/// `faucet backfill` arguments.
843#[derive(Debug, Parser)]
844pub struct BackfillArgs {
845    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
846    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
847    pub config: Option<PathBuf>,
848    /// Window start (inclusive): RFC3339 (`2026-06-01T00:00:00Z`) or a date
849    /// (`2026-06-01`, midnight in --timezone). Requires --to.
850    #[arg(long, requires = "to", conflicts_with = "from_bookmark")]
851    pub from: Option<String>,
852    /// Window end (exclusive): RFC3339 or a date.
853    #[arg(long, requires = "from", conflicts_with = "from_bookmark")]
854    pub to: Option<String>,
855    /// Chunk the range into windows of this duration (`45s`, `30m`, `6h`,
856    /// `1d`, `1w`) so each chunk is an independent, resumable unit. Defaults
857    /// to the config's `backfill.window`; omitted = one unit for the whole
858    /// range.
859    #[arg(long)]
860    pub window: Option<String>,
861    /// Replay from this explicit bookmark value instead of a wall-clock
862    /// range (seeded into the backfill's scoped state key; the source's own
863    /// incremental logic reads forward from it). JSON or a bare string.
864    #[arg(long)]
865    pub from_bookmark: Option<String>,
866    /// Upper bookmark bound: records whose --bookmark-field orders after
867    /// this value are dropped before the sink.
868    #[arg(long, requires_all = ["from_bookmark", "bookmark_field"])]
869    pub to_bookmark: Option<String>,
870    /// Record field the --to-bookmark bound applies to.
871    #[arg(long)]
872    pub bookmark_field: Option<String>,
873    /// Max concurrently-running window units. Defaults to the config's
874    /// `backfill.concurrency`, else 1 (sequential).
875    #[arg(long)]
876    pub concurrency: Option<usize>,
877    /// IANA timezone for date boundaries and `${now.*}` rendering. Defaults
878    /// to the config's `backfill.timezone`, else UTC.
879    #[arg(long)]
880    pub timezone: Option<String>,
881    /// Root row of the config to backfill. Defaults to the only root.
882    #[arg(long)]
883    pub row: Option<String>,
884    /// Redirect writes to this named sink template under `pipeline.sinks`
885    /// (backfill into a staging table first).
886    #[arg(long)]
887    pub into: Option<String>,
888    /// Print the planned units without running anything.
889    #[arg(long)]
890    pub dry_run: bool,
891    /// Continue a previously-interrupted backfill of the same range: skip
892    /// units already done, re-run failed and pending ones.
893    #[arg(long, conflicts_with = "restart")]
894    pub resume: bool,
895    /// Discard a previous progress marker for this range and start over.
896    #[arg(long)]
897    pub restart: bool,
898    /// Emit a machine-readable JSON report instead of the human summary.
899    #[arg(long)]
900    pub json: bool,
901    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
902    /// Defaults to `.env` in cwd if present.
903    #[arg(long, conflicts_with = "no_env_file")]
904    pub env_file: Option<PathBuf>,
905    /// Skip auto-loading `.env` from cwd.
906    #[arg(long)]
907    pub no_env_file: bool,
908    /// Select a named overlay from the config's `profiles:` block.
909    /// Overrides the `FAUCET_PROFILE` env var.
910    #[arg(long, env = "FAUCET_PROFILE")]
911    pub profile: Option<String>,
912}
913
914/// `faucet replicate` arguments.
915#[derive(Debug, Parser)]
916pub struct ReplicateArgs {
917    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
918    /// `replication:` block. If omitted, auto-discover
919    /// `faucet.yaml` / `.yml` / `.json` in cwd.
920    pub config: Option<PathBuf>,
921    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
922    /// Defaults to `.env` in cwd if present.
923    #[arg(long, conflicts_with = "no_env_file")]
924    pub env_file: Option<PathBuf>,
925    /// Skip auto-loading `.env` from cwd.
926    #[arg(long)]
927    pub no_env_file: bool,
928    /// Select a named overlay from the config's `profiles:` block and deep-merge
929    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
930    #[arg(long, env = "FAUCET_PROFILE")]
931    pub profile: Option<String>,
932}
933
934/// `faucet discover` arguments.
935#[derive(Debug, Parser)]
936pub struct DiscoverArgs {
937    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config whose source
938    /// points at the system to introspect. If omitted, auto-discover
939    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
940    pub config: Option<PathBuf>,
941    /// Which source template to introspect (an entry under `pipeline.sources`).
942    /// Defaults to `default` (the legacy singular `pipeline.source`).
943    #[arg(long)]
944    pub source: Option<String>,
945    /// Only include datasets whose name matches this `*`-wildcard pattern
946    /// (repeatable; no patterns = include everything).
947    #[arg(long)]
948    pub include: Vec<String>,
949    /// Exclude datasets whose name matches this `*`-wildcard pattern
950    /// (repeatable; applied after --include).
951    #[arg(long)]
952    pub exclude: Vec<String>,
953    /// Write the generated config to this file instead of stdout.
954    #[arg(long, short = 'o')]
955    pub output: Option<PathBuf>,
956    /// Overwrite the --output file if it already exists.
957    #[arg(long)]
958    pub force: bool,
959    /// Emit the discovered datasets as machine-readable JSON instead of a
960    /// generated config.
961    #[arg(long)]
962    pub json: bool,
963    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
964    /// Defaults to `.env` in cwd if present.
965    #[arg(long, conflicts_with = "no_env_file")]
966    pub env_file: Option<PathBuf>,
967    /// Skip auto-loading `.env` from cwd.
968    #[arg(long)]
969    pub no_env_file: bool,
970    /// Select a named overlay from the config's `profiles:` block.
971    /// Overrides the `FAUCET_PROFILE` env var.
972    #[arg(long, env = "FAUCET_PROFILE")]
973    pub profile: Option<String>,
974}
975
976/// `faucet validate` arguments.
977#[derive(Debug, Parser)]
978pub struct ValidateArgs {
979    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
980    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
981    pub config: Option<PathBuf>,
982    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
983    /// Defaults to `.env` in cwd if present.
984    #[arg(long, conflicts_with = "no_env_file")]
985    pub env_file: Option<PathBuf>,
986    /// Skip auto-loading `.env` from cwd.
987    #[arg(long)]
988    pub no_env_file: bool,
989    /// Validate grammar and structure only — skip fetching from secrets
990    /// managers (no network / credentials needed).
991    #[arg(long)]
992    pub no_secrets: bool,
993    /// Select a named overlay from the config's `profiles:` block and deep-merge
994    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
995    #[arg(long, env = "FAUCET_PROFILE")]
996    pub profile: Option<String>,
997    /// Print the fully-composed config (after extends/!include/profile, before
998    /// `${...}` interpolation) and exit. For debugging composition precedence.
999    /// `--no-secrets` is redundant here (no interpolation or secret fetch occurs).
1000    #[arg(long)]
1001    pub show_composed: bool,
1002
1003    /// Runtime matrix-row selection — `validate` reports each row's resolved
1004    /// status/tags and whether the selection would run or skip it.
1005    #[command(flatten)]
1006    pub selection: SelectionArgs,
1007}
1008
1009/// `faucet schema` arguments.
1010#[derive(Debug, Parser)]
1011pub struct SchemaArgs {
1012    #[command(subcommand)]
1013    pub target: SchemaTarget,
1014}
1015
1016/// Schema subcommand target — which connector or system component to describe.
1017#[derive(Debug, Subcommand)]
1018pub enum SchemaTarget {
1019    /// Composed JSON Schema for the **entire** `faucet.yaml` / `faucet.json`
1020    /// config document (top-level grammar + per-connector `type` discrimination).
1021    /// Point an editor at it with a `# yaml-language-server: $schema=…` header.
1022    Config,
1023    /// JSON Schema for a source connector config.
1024    Source {
1025        /// Connector name (e.g. `rest`, `graphql`, `postgres`).
1026        #[arg(add = ArgValueCandidates::new(completions::source_kind_candidates))]
1027        name: String,
1028    },
1029    /// JSON Schema for a sink connector config.
1030    Sink {
1031        /// Connector name (e.g. `jsonl`, `bigquery`, `postgres`).
1032        #[arg(add = ArgValueCandidates::new(completions::sink_kind_candidates))]
1033        name: String,
1034    },
1035    /// JSON Schema for a transform's inline config.
1036    Transform {
1037        /// Transform name (e.g. `flatten`, `keys_case`, `cast`).
1038        /// Run `faucet list` to see what is compiled in.
1039        #[arg(add = ArgValueCandidates::new(completions::transform_candidates))]
1040        name: String,
1041    },
1042    /// JSON Schema for the DLQ (Dead Letter Queue) specification.
1043    Dlq,
1044    /// JSON Schema for the `replication:` (snapshot→CDC) block.
1045    Replication,
1046    /// JSON Schema for the `backfill:` (window replay defaults) block.
1047    Backfill,
1048    /// JSON Schema for the top-level `execution:` block.
1049    Execution,
1050    /// JSON Schema for the top-level `resilience:` block.
1051    Resilience,
1052    /// JSON Schema for the top-level `sla:` (freshness/volume SLA) block.
1053    Sla,
1054    /// JSON Schema for the `quality:` block.
1055    #[cfg(feature = "quality")]
1056    Quality,
1057    /// JSON Schema for the `contract:` block.
1058    #[cfg(feature = "contract")]
1059    Contract,
1060    /// JSON Schema for the `masking:` (PII masking) block.
1061    #[cfg(feature = "masking")]
1062    Masking,
1063    /// JSON Schema for the `faucet test` spec file.
1064    Test,
1065    /// Grammar reference for secrets-manager interpolation directives.
1066    Secrets,
1067    /// JSON Schema for the `schedule:` block.
1068    #[cfg(feature = "schedule")]
1069    Schedule,
1070    /// JSON Schema for the `lineage:` (OpenLineage) block.
1071    #[cfg(feature = "lineage")]
1072    Lineage,
1073    /// JSON Schema for the `--triggers` file (event-driven pipeline triggers).
1074    #[cfg(feature = "triggers")]
1075    Triggers,
1076    /// JSON Schema for the `notifications:` (incident-routing) block.
1077    #[cfg(feature = "notify")]
1078    Notifications,
1079    /// JSON Schema for the `catalog:` (Data Movement Catalog store) block.
1080    #[cfg(feature = "catalog")]
1081    Catalog,
1082}
1083
1084/// `faucet preview` arguments.
1085#[derive(Debug, Parser)]
1086pub struct PreviewArgs {
1087    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
1088    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
1089    pub config: Option<PathBuf>,
1090    /// Stop after this many records. Default: 10.
1091    #[arg(long, default_value_t = 10)]
1092    pub limit: usize,
1093    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
1094    /// Defaults to `.env` in cwd if present.
1095    #[arg(long, conflicts_with = "no_env_file")]
1096    pub env_file: Option<PathBuf>,
1097    /// Skip auto-loading `.env` from cwd.
1098    #[arg(long)]
1099    pub no_env_file: bool,
1100    /// Select a named overlay from the config's `profiles:` block and deep-merge
1101    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
1102    #[arg(long, env = "FAUCET_PROFILE")]
1103    pub profile: Option<String>,
1104
1105    /// Runtime matrix-row selection — `preview` previews the first root row of
1106    /// the selected run set.
1107    #[command(flatten)]
1108    pub selection: SelectionArgs,
1109}
1110
1111/// `faucet init` arguments.
1112#[derive(Debug, Parser)]
1113pub struct InitArgs {
1114    /// Name written into the generated file's `name:` field. Defaults to
1115    /// `my-pipeline` when omitted.
1116    pub name: Option<String>,
1117    /// Source connector kind to scaffold (e.g. `rest`, `postgres`, `s3`).
1118    /// Defaults to `rest`. Run `faucet list` to see what is compiled in.
1119    #[arg(long)]
1120    pub source: Option<String>,
1121    /// Sink connector kind to scaffold (e.g. `jsonl`, `bigquery`).
1122    /// Defaults to `jsonl`. Run `faucet list` to see what is compiled in.
1123    #[arg(long)]
1124    pub sink: Option<String>,
1125    /// Output file path. Defaults to `pipeline.yaml`.
1126    #[arg(long, short = 'o', default_value = "pipeline.yaml")]
1127    pub output: PathBuf,
1128    /// Overwrite the output file if it already exists.
1129    #[arg(long)]
1130    pub force: bool,
1131    /// Prompt for the source and sink kinds interactively instead of using
1132    /// `--source` / `--sink`. Requires the `cli-interactive` build feature
1133    /// and a TTY on stdin; falls back to the arg-driven path otherwise.
1134    #[arg(long)]
1135    pub interactive: bool,
1136    /// Name of the template under which to register the scaffolded source
1137    /// and sink. The generated config uses `pipeline.sources.<TEMPLATE>` and
1138    /// `pipeline.sinks.<TEMPLATE>`. Defaults to `default` so a matrix row
1139    /// without a `ref:` field still resolves through the new schema.
1140    #[arg(long, default_value = "default")]
1141    pub template: String,
1142    /// (singer only) Run `<executable> --discover` to fetch the tap's catalog,
1143    /// write it next to the output, and scaffold the config with the discovered
1144    /// streams listed. Requires `--source singer` and `--executable`.
1145    #[arg(long)]
1146    pub discover: bool,
1147    /// (singer only) The Singer tap executable to discover with (used by
1148    /// `--discover`), e.g. `tap-github` or `/opt/taps/tap-csv`.
1149    #[arg(long)]
1150    pub executable: Option<String>,
1151    /// (singer only) The target stream to emit. When given with `--discover`,
1152    /// the written catalog marks this stream — and any inferable parent
1153    /// streams (e.g. a parent-keyed tap's parent) — `selected`, and the
1154    /// scaffolded config's `stream:` is set to it. Most DB / SDK taps sync
1155    /// nothing unless a stream is selected in the catalog.
1156    #[arg(long)]
1157    pub stream: Option<String>,
1158}
1159
1160/// `faucet plan` arguments.
1161#[derive(Debug, Parser)]
1162pub struct PlanArgs {
1163    /// Path to a `.yaml`/`.yml`/`.json` config (auto-discovered if omitted).
1164    pub config: Option<PathBuf>,
1165    /// Which row to plan (default: the first root row).
1166    #[arg(long)]
1167    pub row: Option<String>,
1168    /// Offline sample of input records (`.jsonl` or a `.json` array) to preview
1169    /// the output schema, volume, and sink delta through — no source is touched.
1170    #[arg(long)]
1171    pub sample: Option<PathBuf>,
1172    /// Pull a capped, read-only sample from the real source instead of a
1173    /// fixture (bounded by `--limit`; no bookmark is advanced).
1174    #[arg(long)]
1175    pub live: bool,
1176    /// Cap for `--live` sampling.
1177    #[arg(long, default_value_t = 10)]
1178    pub limit: usize,
1179    /// Emit the plan as JSON.
1180    #[arg(long)]
1181    pub json: bool,
1182    /// Show a `terraform plan`-style diff of the current config against the last
1183    /// recorded run, instead of the resolved-pipeline preview (#374). Requires a
1184    /// `catalog:` block. Resolves secrets so the diff matches what `run` records.
1185    #[arg(long)]
1186    pub diff: bool,
1187    /// Resolve secrets-manager directives (needs network/credentials). Off by
1188    /// default so `plan` works offline like `faucet test`. Implied by `--diff`.
1189    #[arg(long)]
1190    pub resolve_secrets: bool,
1191    /// Select a `profiles:` overlay.
1192    #[arg(long, env = "FAUCET_PROFILE")]
1193    pub profile: Option<String>,
1194}
1195
1196/// `faucet dev` arguments.
1197#[derive(Debug, Parser)]
1198pub struct DevArgs {
1199    /// Path to the `.yaml`/`.yml`/`.json` config to watch.
1200    pub config: PathBuf,
1201    /// Which row to run (default: the first root row).
1202    #[arg(long)]
1203    pub row: Option<String>,
1204    /// Offline sample of input records (`.jsonl` or `.json` array). Required
1205    /// for the offline loop.
1206    #[arg(long)]
1207    pub sample: Option<PathBuf>,
1208    /// (reserved) pull a capped read-only sample from the real source.
1209    #[arg(long)]
1210    pub live: bool,
1211    /// Cap for `--live` sampling.
1212    #[arg(long, default_value_t = 10)]
1213    pub limit: usize,
1214    /// Run once and exit instead of watching (also the non-TTY fallback).
1215    #[arg(long)]
1216    pub once: bool,
1217    /// Debounce window between re-runs, in milliseconds.
1218    #[arg(long, default_value_t = 300)]
1219    pub debounce_ms: u64,
1220    /// Select a `profiles:` overlay.
1221    #[arg(long, env = "FAUCET_PROFILE")]
1222    pub profile: Option<String>,
1223}
1224
1225/// `faucet list` arguments.
1226#[derive(Debug, Parser)]
1227pub struct ListArgs {
1228    /// List every connector in the registry index (not just the compiled-in
1229    /// ones), marking which are already in this binary.
1230    #[arg(long)]
1231    pub available: bool,
1232    /// Read a custom registry index instead of the built-in one.
1233    #[arg(long)]
1234    pub index: Option<PathBuf>,
1235}
1236
1237/// `faucet conformance` arguments.
1238#[derive(Debug, Parser)]
1239pub struct ConformanceArgs {
1240    /// Only score the connector with this system name (e.g. `postgres`); prints
1241    /// a detailed scorecard. Omit to score every compiled-in connector.
1242    pub name: Option<String>,
1243    /// Restrict to `source` or `sink`.
1244    #[arg(long)]
1245    pub kind: Option<String>,
1246    /// Score every compiled-in connector (the default when no NAME is given;
1247    /// accepted explicitly for clarity in CI).
1248    #[arg(long)]
1249    pub all: bool,
1250    /// Emit the full scorecards as JSON.
1251    #[arg(long)]
1252    pub json: bool,
1253    /// Fail (exit non-zero) if any scored connector is below this maturity tier
1254    /// — an opt-in CI gate. One of `stable` / `experimental` / `beta` / `draft`.
1255    #[arg(long, value_name = "TIER")]
1256    pub min_tier: Option<String>,
1257}
1258
1259/// `faucet search` arguments.
1260#[derive(Debug, Parser)]
1261pub struct SearchArgs {
1262    /// Term to match against connector name / description / keywords / crate.
1263    pub term: String,
1264    /// Read a custom registry index instead of the built-in one.
1265    #[arg(long)]
1266    pub index: Option<PathBuf>,
1267    /// Emit matches as JSON.
1268    #[arg(long)]
1269    pub json: bool,
1270}
1271
1272/// `faucet install` arguments.
1273#[derive(Debug, Parser)]
1274pub struct InstallArgs {
1275    /// Connector system name (e.g. `kafka`).
1276    pub name: String,
1277    /// Disambiguate when a name exists as both a source and a sink.
1278    #[arg(long)]
1279    pub kind: Option<String>,
1280    /// Read a custom registry index instead of the built-in one.
1281    #[arg(long)]
1282    pub index: Option<PathBuf>,
1283}
1284
1285/// `faucet new` arguments.
1286#[derive(Debug, Parser)]
1287pub struct NewArgs {
1288    #[command(subcommand)]
1289    pub target: NewTarget,
1290}
1291
1292/// What `faucet new` scaffolds.
1293#[derive(Debug, Subcommand)]
1294pub enum NewTarget {
1295    /// Scaffold a ready-to-build `faucet-source-<name>` / `faucet-sink-<name>`
1296    /// connector crate following every repo convention.
1297    Connector(NewConnectorArgs),
1298}
1299
1300/// `faucet new connector` arguments.
1301#[derive(Debug, Parser)]
1302pub struct NewConnectorArgs {
1303    /// Connector system name (lowercase, e.g. `acme` or `acme-widgets`). Becomes
1304    /// the crate name `faucet-<kind>-<name>` and the YAML `type:` value.
1305    pub name: String,
1306    /// Whether to scaffold a `source` or a `sink`.
1307    #[arg(long)]
1308    pub kind: String,
1309    /// Also scaffold a `faucet-common-<name>` crate for config shared between a
1310    /// source/sink pair.
1311    #[arg(long)]
1312    pub common: bool,
1313    /// Directory to write the new crate(s) into. Defaults to the current dir.
1314    #[arg(long, short = 'o', default_value = ".")]
1315    pub output: PathBuf,
1316    /// Overwrite any existing files.
1317    #[arg(long)]
1318    pub force: bool,
1319}