faucet_cli/cli.rs
1//! Argument parser shared by `main.rs` and the integration tests.
2
3use clap::{Parser, Subcommand};
4use std::path::PathBuf;
5
6/// `faucet` — config-driven runner for faucet-stream pipelines.
7#[derive(Debug, Parser)]
8#[command(name = "faucet", version, about, long_about = None)]
9pub struct Cli {
10 /// Override the global log level (also honors `FAUCET_LOG`).
11 #[arg(long, global = true, env = "FAUCET_LOG", default_value = "info")]
12 pub log_level: String,
13
14 #[command(subcommand)]
15 pub command: Command,
16}
17
18/// Top-level subcommands.
19#[derive(Debug, Subcommand)]
20pub enum Command {
21 /// Execute a pipeline config end-to-end.
22 Run(RunArgs),
23 /// Replay a bounded historical window of a pipeline: chunk --from/--to
24 /// into window units, run them with bounded parallelism, and record
25 /// durable, resumable progress. Exits non-zero if any unit fails.
26 Backfill(BackfillArgs),
27 /// Bulk-snapshot a database table, then stream CDC from a position captured
28 /// before the snapshot (a true mirror with `write_mode: upsert`).
29 /// Long-running when `replication.continuous` is true (Ctrl-C / SIGTERM to stop).
30 Replicate(ReplicateArgs),
31 /// Connect to a config's source, enumerate the datasets behind it
32 /// (tables / collections / indices / prefixes), and emit a ready-to-run
33 /// config with one matrix row per dataset.
34 Discover(DiscoverArgs),
35 /// Parse + validate a pipeline config without running it.
36 Validate(ValidateArgs),
37 /// Print the JSON Schema for a specific connector.
38 Schema(SchemaArgs),
39 /// List every compiled-in source, sink, and transform with a one-line
40 /// description (`--available` lists the whole connector registry instead).
41 List(ListArgs),
42 /// Search the connector registry index for connectors by name / keyword.
43 Search(SearchArgs),
44 /// Score each connector's conformance to the faucet SDK contract and print
45 /// its maturity tier (Stable / Experimental / Beta / Draft) + capabilities.
46 Conformance(ConformanceArgs),
47 /// Show how to install or enable a connector from the registry index
48 /// (prints the recipe; never executes anything).
49 Install(InstallArgs),
50 /// Run only the source side and print records to stdout (uses the stdout sink).
51 Preview(PreviewArgs),
52 /// Read-only preview of what a config would do: resolved pipeline, inferred
53 /// output schema, sink schema delta, lineage, and target sinks — zero writes.
54 Plan(PlanArgs),
55 /// Watch a config and re-run a sample offline on every save, printing a
56 /// live diff of the output. Requires the `cli-dev` build feature.
57 #[cfg(feature = "cli-dev")]
58 Dev(DevArgs),
59 /// Scaffold a starter `pipeline.yaml` to disk.
60 Init(InitArgs),
61 /// Scaffold a new artifact — currently a third-party connector crate.
62 New(NewArgs),
63 /// Probe every connector in a config (auth / network / permissions) and
64 /// print a green/red checklist. Exits non-zero if any probe fails.
65 Doctor(DoctorArgs),
66 /// Run fixture-based offline pipeline tests from one or more spec files.
67 /// No real source or sink is touched. Exits non-zero if any case fails.
68 Test(TestArgs),
69 /// Inspect, replay, or discard dead-letter-queue envelopes written by a
70 /// pipeline's `dlq:` sink.
71 Dlq(DlqArgs),
72 /// Validate a config's `contract:` block and print a summary, or export
73 /// it in a machine-readable format (`--export`).
74 #[cfg(feature = "contract")]
75 Contract(ContractArgs),
76 /// Validate a config's `masking:` block and print which rules apply to
77 /// each destination sink.
78 #[cfg(feature = "masking")]
79 Masking(MaskingArgs),
80 /// Run a pipeline on a cron schedule (long-running; Ctrl-C / SIGTERM to stop).
81 #[cfg(feature = "schedule")]
82 Schedule(ScheduleArgs),
83 /// Run a long-running HTTP control plane (submit / poll / cancel pipeline runs).
84 #[cfg(feature = "serve")]
85 Serve(ServeArgs),
86 /// Send a synthetic notification through a config's `notifications:` rules
87 /// to validate channel setup end-to-end (no pipeline runs).
88 #[cfg(feature = "notify")]
89 Notify(NotifyArgs),
90 /// Browse the Data Movement Catalog accumulated by a config's `catalog:`
91 /// store — datasets, schema timelines, volume/freshness, lineage.
92 #[cfg(feature = "catalog")]
93 Catalog(CatalogArgs),
94}
95
96/// `faucet catalog` arguments.
97#[cfg(feature = "catalog")]
98#[derive(Debug, Parser)]
99pub struct CatalogArgs {
100 #[command(subcommand)]
101 pub command: CatalogCommand,
102}
103
104/// `faucet catalog` subcommands.
105#[cfg(feature = "catalog")]
106#[derive(Debug, Subcommand)]
107pub enum CatalogCommand {
108 /// List every catalogued dataset (newest activity first).
109 Datasets(CatalogDatasetsArgs),
110 /// Show one dataset's detail: schema timeline, volume points, edges.
111 Show(CatalogShowArgs),
112 /// Print the dataset lineage graph (optionally rooted at a dataset).
113 Lineage(CatalogLineageArgs),
114}
115
116/// Shared config-loading flags for the `faucet catalog` subcommands.
117#[cfg(feature = "catalog")]
118#[derive(Debug, Parser)]
119pub struct CatalogConfigArgs {
120 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
121 /// `catalog:` block naming the store. If omitted, auto-discover
122 /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
123 #[arg(long)]
124 pub config: Option<PathBuf>,
125 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
126 /// Defaults to `.env` in cwd if present.
127 #[arg(long, conflicts_with = "no_env_file")]
128 pub env_file: Option<PathBuf>,
129 /// Skip auto-loading `.env` from cwd.
130 #[arg(long)]
131 pub no_env_file: bool,
132 /// Select a named overlay from the config's `profiles:` block.
133 /// Overrides the `FAUCET_PROFILE` env var.
134 #[arg(long, env = "FAUCET_PROFILE")]
135 pub profile: Option<String>,
136 /// Emit machine-readable JSON instead of the human summary.
137 #[arg(long)]
138 pub json: bool,
139}
140
141/// `faucet catalog datasets` arguments.
142#[cfg(feature = "catalog")]
143#[derive(Debug, Parser)]
144pub struct CatalogDatasetsArgs {
145 #[command(flatten)]
146 pub common: CatalogConfigArgs,
147 /// Only datasets of this connector kind (e.g. `postgres`, `csv`).
148 #[arg(long)]
149 pub kind: Option<String>,
150 /// Case-insensitive substring match on the dataset URI.
151 #[arg(long)]
152 pub q: Option<String>,
153 /// Max datasets to list.
154 #[arg(long, default_value_t = 100)]
155 pub limit: usize,
156}
157
158/// `faucet catalog show <id>` arguments.
159#[cfg(feature = "catalog")]
160#[derive(Debug, Parser)]
161pub struct CatalogShowArgs {
162 /// Dataset id (from `faucet catalog datasets`), or a unique prefix of one.
163 pub id: String,
164 #[command(flatten)]
165 pub common: CatalogConfigArgs,
166}
167
168/// `faucet catalog lineage` arguments.
169#[cfg(feature = "catalog")]
170#[derive(Debug, Parser)]
171pub struct CatalogLineageArgs {
172 #[command(flatten)]
173 pub common: CatalogConfigArgs,
174 /// Dataset id to root the graph at (whole graph when omitted).
175 #[arg(long)]
176 pub root: Option<String>,
177 /// BFS hop bound around --root.
178 #[arg(long, default_value_t = 5)]
179 pub depth: u32,
180}
181
182/// `faucet notify test` arguments.
183#[cfg(feature = "notify")]
184#[derive(Debug, Parser)]
185pub struct NotifyArgs {
186 #[command(subcommand)]
187 pub command: NotifyCommand,
188}
189
190/// `faucet notify` subcommands.
191#[cfg(feature = "notify")]
192#[derive(Debug, Subcommand)]
193pub enum NotifyCommand {
194 /// Fire one synthetic event at every matching rule in the config.
195 Test(NotifyTestArgs),
196}
197
198/// `faucet notify test <config>` arguments.
199#[cfg(feature = "notify")]
200#[derive(Debug, Parser)]
201pub struct NotifyTestArgs {
202 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
203 /// `notifications:` block. If omitted, auto-discover in cwd.
204 pub config: Option<PathBuf>,
205 /// Which event to synthesize (defaults to `run_failure`).
206 #[arg(long, default_value = "run_failure")]
207 pub event: String,
208 /// Path to a `.env` file for `${env:VAR}` interpolation.
209 #[arg(long, conflicts_with = "no_env_file")]
210 pub env_file: Option<PathBuf>,
211 /// Disable `.env` auto-discovery.
212 #[arg(long)]
213 pub no_env_file: bool,
214}
215
216/// `faucet test` arguments.
217#[derive(Debug, Parser)]
218pub struct TestArgs {
219 /// One or more test-spec files (`.yaml`, `.yml`, or `.json`), e.g.
220 /// `faucet test tests/*.yaml`.
221 #[arg(required = true)]
222 pub specs: Vec<PathBuf>,
223 /// Run only cases whose name contains this substring.
224 #[arg(long)]
225 pub filter: Option<String>,
226 /// Emit a machine-readable JSON report instead of the human checklist.
227 #[arg(long)]
228 pub json: bool,
229 /// Default `${now.*}` clock for cases without their own `clock:` field
230 /// (RFC3339 like `2026-01-31T00:00:00Z`, or a date `2026-01-31`).
231 /// Defaults to process start (UTC).
232 #[arg(long)]
233 pub clock: Option<String>,
234 /// Path to a `.env` file to load for `${env:VAR}` interpolation in
235 /// referenced pipeline configs. Defaults to `.env` in cwd if present.
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 each referenced config's `profiles:` block.
242 /// Overrides the `FAUCET_PROFILE` env var.
243 #[arg(long, env = "FAUCET_PROFILE")]
244 pub profile: Option<String>,
245 /// Resolve `${vault:…}` / `${aws-sm:…}` / … secret directives in
246 /// referenced configs (requires network + credentials). By default tests
247 /// load configs offline and leave secret directives unresolved — safe
248 /// because the real source/sink configs holding them are never used.
249 #[arg(long)]
250 pub resolve_secrets: bool,
251}
252
253/// `faucet dlq` arguments.
254#[derive(Debug, Parser)]
255pub struct DlqArgs {
256 #[command(subcommand)]
257 pub command: DlqCommand,
258}
259
260/// `faucet dlq` subcommands.
261#[derive(Debug, Subcommand)]
262pub enum DlqCommand {
263 /// Read a DLQ location back and print a per-reason / per-error-kind
264 /// breakdown plus a sample of quarantined records.
265 Inspect(DlqInspectArgs),
266 /// Re-feed quarantined records through a pipeline config (transforms →
267 /// quality → contract → sink). Rows that fail again land in a *fresh* DLQ.
268 Replay(DlqReplayArgs),
269 /// Remove processed envelopes from a DLQ location (archive by default,
270 /// or `--delete`), filtered by reason and/or age.
271 Discard(DlqDiscardArgs),
272}
273
274/// `faucet dlq inspect <location>` arguments.
275#[derive(Debug, Parser)]
276pub struct DlqInspectArgs {
277 /// DLQ location: a `.jsonl` file, a directory of `*.jsonl` files, or a glob.
278 pub location: String,
279 /// Only include envelopes with this DLQ reason (`partial` / `dlq_all` /
280 /// `quality` / `schema_drift` / `contract`).
281 #[arg(long)]
282 pub reason: Option<String>,
283 /// Number of sample records to show. Default: 5.
284 #[arg(long, default_value_t = 5)]
285 pub limit: usize,
286 /// Key for a DLQ sealed at rest by the jsonl sink's `encryption` block.
287 /// Repeat the flag to also try older (rotated) keys. Requires a build
288 /// with the `encryption` feature.
289 #[arg(long = "encryption-key")]
290 pub encryption_key: Vec<String>,
291 /// Emit a machine-readable JSON summary instead of the human report.
292 #[arg(long)]
293 pub json: bool,
294}
295
296/// `faucet dlq replay <config> --from <location>` arguments.
297#[derive(Debug, Parser)]
298pub struct DlqReplayArgs {
299 /// Path to the pipeline config whose sink / transforms / quality / contract
300 /// the replayed records flow through. If omitted, auto-discover in cwd.
301 pub config: Option<PathBuf>,
302 /// DLQ location to replay from: a `.jsonl` file, a directory, or a glob.
303 #[arg(long)]
304 pub from: String,
305 /// Only replay envelopes with this DLQ reason.
306 #[arg(long)]
307 pub reason: Option<String>,
308 /// Where replayed rows that fail *again* are quarantined. Defaults to a
309 /// `replay-failed.jsonl` sibling of the source (never the source itself).
310 #[arg(long)]
311 pub failed_dlq: Option<String>,
312 /// Which root row of the config to replay through. Defaults to the first root.
313 #[arg(long)]
314 pub row: Option<String>,
315 /// Report what would be replayed without writing to the sink.
316 #[arg(long)]
317 pub dry_run: bool,
318 /// Key for a DLQ sealed at rest by the jsonl sink's `encryption` block.
319 /// Repeat the flag to also try older (rotated) keys. Requires a build
320 /// with the `encryption` feature.
321 #[arg(long = "encryption-key")]
322 pub encryption_key: Vec<String>,
323 /// (Replay picks up the config's own dlq `encryption` block automatically
324 /// when no key is passed.)
325 /// Emit a machine-readable JSON result instead of the human summary.
326 #[arg(long)]
327 pub json: bool,
328 /// Path to a `.env` file for `${env:VAR}` interpolation in the config.
329 #[arg(long, conflicts_with = "no_env_file")]
330 pub env_file: Option<PathBuf>,
331 /// Skip auto-loading `.env` from cwd.
332 #[arg(long)]
333 pub no_env_file: bool,
334 /// Select a named overlay from the config's `profiles:` block.
335 #[arg(long, env = "FAUCET_PROFILE")]
336 pub profile: Option<String>,
337}
338
339/// `faucet dlq discard <location>` arguments.
340#[derive(Debug, Parser)]
341pub struct DlqDiscardArgs {
342 /// DLQ location: a `.jsonl` file, a directory of `*.jsonl` files, or a glob.
343 pub location: String,
344 /// Only discard envelopes with this DLQ reason.
345 #[arg(long)]
346 pub reason: Option<String>,
347 /// Only discard envelopes older than this: an RFC3339 timestamp
348 /// (`2026-06-01T00:00:00Z`) or a relative age (`7d`, `24h`, `30m`).
349 #[arg(long)]
350 pub before: Option<String>,
351 /// Permanently delete matching envelopes instead of archiving them to a
352 /// `<file>.archived.jsonl` sibling.
353 #[arg(long)]
354 pub delete: bool,
355 /// Key for a DLQ sealed at rest by the jsonl sink's `encryption` block.
356 /// Repeat the flag to also try older (rotated) keys. Requires a build
357 /// with the `encryption` feature.
358 #[arg(long = "encryption-key")]
359 pub encryption_key: Vec<String>,
360 /// Emit a machine-readable JSON result instead of the human summary.
361 #[arg(long)]
362 pub json: bool,
363}
364
365/// `faucet doctor` arguments.
366#[derive(Debug, Parser)]
367pub struct DoctorArgs {
368 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
369 /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
370 pub config: Option<PathBuf>,
371 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
372 /// Defaults to `.env` in cwd if present.
373 #[arg(long, conflicts_with = "no_env_file")]
374 pub env_file: Option<PathBuf>,
375 /// Skip auto-loading `.env` from cwd.
376 #[arg(long)]
377 pub no_env_file: bool,
378 /// Per-probe timeout in seconds.
379 #[arg(long, default_value_t = 10)]
380 pub timeout_secs: u64,
381 /// Emit machine-readable JSON instead of the human checklist.
382 #[arg(long)]
383 pub json: bool,
384 /// Select a named overlay from the config's `profiles:` block and deep-merge
385 /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
386 #[arg(long, env = "FAUCET_PROFILE")]
387 pub profile: Option<String>,
388}
389
390/// `faucet contract` arguments.
391#[cfg(feature = "contract")]
392#[derive(Debug, Parser)]
393pub struct ContractArgs {
394 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
395 /// `pipeline.contract:` block. If omitted, auto-discover
396 /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
397 pub config: Option<PathBuf>,
398 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
399 /// Defaults to `.env` in cwd if present.
400 #[arg(long, conflicts_with = "no_env_file")]
401 pub env_file: Option<PathBuf>,
402 /// Skip auto-loading `.env` from cwd.
403 #[arg(long)]
404 pub no_env_file: bool,
405 /// Select a named overlay from the config's `profiles:` block and deep-merge
406 /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
407 #[arg(long, env = "FAUCET_PROFILE")]
408 pub profile: Option<String>,
409 /// Export the contract in a machine-readable format instead of the
410 /// human summary: the canonical contract JSON, a standalone JSON Schema,
411 /// or an OpenLineage schema facet.
412 #[arg(long, value_enum)]
413 pub export: Option<ContractExportFormat>,
414}
415
416/// Arguments for `faucet masking`.
417#[cfg(feature = "masking")]
418#[derive(Debug, Parser)]
419pub struct MaskingArgs {
420 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
421 /// `pipeline.masking:` block. If omitted, auto-discover
422 /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
423 pub config: Option<PathBuf>,
424 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
425 /// Defaults to `.env` in cwd if present.
426 #[arg(long, conflicts_with = "no_env_file")]
427 pub env_file: Option<PathBuf>,
428 /// Skip auto-loading `.env` from cwd.
429 #[arg(long)]
430 pub no_env_file: bool,
431 /// Select a named overlay from the config's `profiles:` block and deep-merge
432 /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
433 #[arg(long, env = "FAUCET_PROFILE")]
434 pub profile: Option<String>,
435}
436
437/// Export format for `faucet contract --export`.
438#[cfg(feature = "contract")]
439#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
440pub enum ContractExportFormat {
441 /// The canonical contract document as JSON.
442 Contract,
443 /// A standalone JSON Schema (draft 2020-12) for the promised records.
444 JsonSchema,
445 /// An OpenLineage `SchemaDatasetFacet` JSON document.
446 Openlineage,
447}
448
449/// `faucet schedule` arguments.
450#[cfg(feature = "schedule")]
451#[derive(Debug, Parser)]
452pub struct ScheduleArgs {
453 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a `schedule:`
454 /// block. If omitted, auto-discover `faucet.yaml` / `.yml` / `.json` in cwd.
455 pub config: Option<PathBuf>,
456 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
457 /// Defaults to `.env` in cwd if present.
458 #[arg(long, conflicts_with = "no_env_file")]
459 pub env_file: Option<PathBuf>,
460 /// Skip auto-loading `.env` from cwd.
461 #[arg(long)]
462 pub no_env_file: bool,
463 /// Run exactly one pipeline run immediately, then exit (ignores cron timing).
464 /// Useful for platform-driven invocation (k8s CronJob / systemd OnCalendar).
465 #[arg(long)]
466 pub once: bool,
467 /// Select a named overlay from the config's `profiles:` block and deep-merge
468 /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
469 #[arg(long, env = "FAUCET_PROFILE")]
470 pub profile: Option<String>,
471}
472
473/// `faucet serve` arguments.
474#[cfg(feature = "serve")]
475#[derive(Debug, Clone, Parser)]
476pub struct ServeArgs {
477 /// Bind address. Defaults to loopback; set 0.0.0.0:PORT to expose externally.
478 #[arg(long, env = "FAUCET_SERVE_LISTEN", default_value = "127.0.0.1:8080")]
479 pub listen: String,
480 /// Bearer token required on /v1/* requests. Prefer the env var (avoids `ps` leakage).
481 #[arg(long, env = "FAUCET_SERVE_AUTH_TOKEN", conflicts_with = "no_auth")]
482 pub auth_token: Option<String>,
483 /// Explicitly disable authentication. Required if no token is set, so an
484 /// unauthenticated server is never accidental.
485 #[arg(long)]
486 pub no_auth: bool,
487 /// Path to an RBAC auth config (YAML/JSON) defining principals — each a
488 /// `{ name, token, role }` where role is `viewer` / `operator` / `admin`.
489 /// Enables role-based access control + an audit log. Mutually exclusive with
490 /// `--auth-token` / `--no-auth`.
491 #[arg(long, conflicts_with_all = ["auth_token", "no_auth"])]
492 pub auth_config: Option<std::path::PathBuf>,
493 /// Max pipeline runs executing at once. Default: min(16, cpu count).
494 #[arg(long)]
495 pub max_concurrent_runs: Option<usize>,
496 /// Max queued (not-yet-running) runs before POST /v1/runs returns 429.
497 /// Default: 8 × max-concurrent-runs.
498 #[arg(long)]
499 pub max_queued_runs: Option<usize>,
500 /// Workspace-default config merged under every submitted run.
501 #[arg(long)]
502 pub default_config: Option<std::path::PathBuf>,
503 /// Run-history backend URL: omitted = in-memory; postgres://… ; sqlite:… .
504 #[arg(long)]
505 pub history: Option<String>,
506 /// CORS allow-list origin (repeatable). Omitted = CORS disabled.
507 #[arg(long)]
508 pub cors_origin: Vec<String>,
509 /// Max POST /v1/runs body size in bytes (413 on exceed).
510 #[arg(long, default_value_t = 1_048_576)]
511 pub body_limit_bytes: usize,
512 /// SIGTERM/SIGINT drain window in seconds.
513 #[arg(long, default_value_t = 60)]
514 pub shutdown_grace_secs: u64,
515 /// Retain terminal run records this long (seconds).
516 #[arg(long, default_value_t = 604_800)]
517 pub retain_terminal_runs_secs: u64,
518 /// Idempotency-key replay window (seconds).
519 #[arg(long, default_value_t = 86_400)]
520 pub idempotency_retention_secs: u64,
521 /// Run-ownership lease TTL in seconds (multi-instance orphan fencing). A run
522 /// is owned by the instance executing it and its lease is heartbeated at
523 /// ~⅓ of this interval; only a run whose lease has expired (owner presumed
524 /// dead) is recovered as failed. Make this comfortably larger than expected
525 /// GC/IO stalls so a healthy-but-slow instance is never falsely reclaimed.
526 /// Only relevant with a persistent (postgres/sqlite) history backend.
527 #[arg(long, default_value_t = 30)]
528 pub lease_ttl_secs: u64,
529 /// Per-probe timeout for `doctor_first` preflight (seconds).
530 #[arg(long, default_value_t = 10)]
531 pub probe_timeout_secs: u64,
532 /// Path to a `.env` file loaded for the server's own startup interpolation.
533 #[arg(long, conflicts_with = "no_env_file")]
534 pub env_file: Option<std::path::PathBuf>,
535 /// Skip auto-loading `.env` from cwd at startup.
536 #[arg(long)]
537 pub no_env_file: bool,
538 /// Disable serving the embedded web console (only meaningful in a build that
539 /// includes the `serve-ui` feature; the API is unaffected).
540 #[arg(long)]
541 pub no_ui: bool,
542 /// Enable clustered execution: run a claim loop that pulls Pending runs from
543 /// the shared history DB so N instances pull-balance and fail over. Requires
544 /// a postgres/sqlite --history backend.
545 #[arg(long)]
546 pub cluster: bool,
547 /// Claim-loop poll interval (seconds) in cluster mode. Also the
548 /// cross-instance cancel-propagation lag. Must be > 0.
549 #[arg(long, default_value_t = 2)]
550 pub cluster_poll_secs: u64,
551 /// Max failover re-runs of an orphaned run before it is marked Failed
552 /// (poison). Must be > 0.
553 #[arg(long, default_value_t = 3)]
554 pub cluster_max_attempts: u32,
555 /// Path to a triggers file (YAML/JSON) defining event-driven pipeline
556 /// triggers (object-arrival / webhook / queue-depth). Requires a build with
557 /// the `triggers` feature. See `faucet schema triggers`.
558 #[arg(long)]
559 pub triggers: Option<std::path::PathBuf>,
560}
561
562/// `faucet run` arguments.
563#[derive(Debug, Parser)]
564pub struct RunArgs {
565 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config.
566 /// If omitted (and `--from-env` is not set), auto-discover
567 /// `faucet.yaml` / `faucet.yml` / `faucet.json` in the current directory.
568 /// Mutually exclusive with `--from-env`.
569 #[arg(conflicts_with = "from_env")]
570 pub config: Option<PathBuf>,
571 /// Build the pipeline entirely from `FAUCET_*` environment variables —
572 /// no YAML required. See `cli/README.md` for the variable schema.
573 #[arg(long)]
574 pub from_env: bool,
575 /// Path to a `.env` file to load before reading variables. Works in both
576 /// YAML mode (for `${env:VAR}` interpolation) and `--from-env` mode.
577 /// When omitted, `.env` in the current directory is auto-loaded if present.
578 /// Existing process-env values always win over file-supplied ones.
579 #[arg(long, conflicts_with = "no_env_file")]
580 pub env_file: Option<PathBuf>,
581 /// Skip auto-loading `.env` from the current directory.
582 #[arg(long)]
583 pub no_env_file: bool,
584 /// Stop after fetching from the source — write nothing to the sink.
585 #[arg(long)]
586 pub dry_run: bool,
587 /// Stop after writing this many records to the sink. Default: unlimited.
588 #[arg(long)]
589 pub limit: Option<usize>,
590 /// Override the state-store directory (file backend only).
591 #[arg(long)]
592 pub state_path: Option<PathBuf>,
593 /// Override the `${now.*}` interpolation clock (RFC3339 like
594 /// `2026-01-31T00:00:00Z`, or a date `2026-01-31`). Default: process start (UTC).
595 /// Use for backfills.
596 #[arg(long)]
597 pub clock: Option<String>,
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 /// Not applicable in `--from-env` mode (no config file to compose).
601 #[arg(long, env = "FAUCET_PROFILE")]
602 pub profile: Option<String>,
603 /// Show a live full-screen terminal UI (per-invocation throughput, errors,
604 /// DLQ counts, bookmark age) while the pipeline runs. Requires a binary
605 /// built with the `cli-tui` feature and a real terminal on stdout —
606 /// on a non-TTY (CI, pipes) the run proceeds normally with a notice.
607 /// Press `q` to cancel cooperatively (in-flight work flushes at the next
608 /// page boundary).
609 #[arg(long)]
610 pub tui: bool,
611}
612
613/// `faucet backfill` arguments.
614#[derive(Debug, Parser)]
615pub struct BackfillArgs {
616 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
617 /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
618 pub config: Option<PathBuf>,
619 /// Window start (inclusive): RFC3339 (`2026-06-01T00:00:00Z`) or a date
620 /// (`2026-06-01`, midnight in --timezone). Requires --to.
621 #[arg(long, requires = "to", conflicts_with = "from_bookmark")]
622 pub from: Option<String>,
623 /// Window end (exclusive): RFC3339 or a date.
624 #[arg(long, requires = "from", conflicts_with = "from_bookmark")]
625 pub to: Option<String>,
626 /// Chunk the range into windows of this duration (`45s`, `30m`, `6h`,
627 /// `1d`, `1w`) so each chunk is an independent, resumable unit. Defaults
628 /// to the config's `backfill.window`; omitted = one unit for the whole
629 /// range.
630 #[arg(long)]
631 pub window: Option<String>,
632 /// Replay from this explicit bookmark value instead of a wall-clock
633 /// range (seeded into the backfill's scoped state key; the source's own
634 /// incremental logic reads forward from it). JSON or a bare string.
635 #[arg(long)]
636 pub from_bookmark: Option<String>,
637 /// Upper bookmark bound: records whose --bookmark-field orders after
638 /// this value are dropped before the sink.
639 #[arg(long, requires_all = ["from_bookmark", "bookmark_field"])]
640 pub to_bookmark: Option<String>,
641 /// Record field the --to-bookmark bound applies to.
642 #[arg(long)]
643 pub bookmark_field: Option<String>,
644 /// Max concurrently-running window units. Defaults to the config's
645 /// `backfill.concurrency`, else 1 (sequential).
646 #[arg(long)]
647 pub concurrency: Option<usize>,
648 /// IANA timezone for date boundaries and `${now.*}` rendering. Defaults
649 /// to the config's `backfill.timezone`, else UTC.
650 #[arg(long)]
651 pub timezone: Option<String>,
652 /// Root row of the config to backfill. Defaults to the only root.
653 #[arg(long)]
654 pub row: Option<String>,
655 /// Redirect writes to this named sink template under `pipeline.sinks`
656 /// (backfill into a staging table first).
657 #[arg(long)]
658 pub into: Option<String>,
659 /// Print the planned units without running anything.
660 #[arg(long)]
661 pub dry_run: bool,
662 /// Continue a previously-interrupted backfill of the same range: skip
663 /// units already done, re-run failed and pending ones.
664 #[arg(long, conflicts_with = "restart")]
665 pub resume: bool,
666 /// Discard a previous progress marker for this range and start over.
667 #[arg(long)]
668 pub restart: bool,
669 /// Emit a machine-readable JSON report instead of the human summary.
670 #[arg(long)]
671 pub json: bool,
672 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
673 /// Defaults to `.env` in cwd if present.
674 #[arg(long, conflicts_with = "no_env_file")]
675 pub env_file: Option<PathBuf>,
676 /// Skip auto-loading `.env` from cwd.
677 #[arg(long)]
678 pub no_env_file: bool,
679 /// Select a named overlay from the config's `profiles:` block.
680 /// Overrides the `FAUCET_PROFILE` env var.
681 #[arg(long, env = "FAUCET_PROFILE")]
682 pub profile: Option<String>,
683}
684
685/// `faucet replicate` arguments.
686#[derive(Debug, Parser)]
687pub struct ReplicateArgs {
688 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
689 /// `replication:` block. If omitted, auto-discover
690 /// `faucet.yaml` / `.yml` / `.json` in cwd.
691 pub config: Option<PathBuf>,
692 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
693 /// Defaults to `.env` in cwd if present.
694 #[arg(long, conflicts_with = "no_env_file")]
695 pub env_file: Option<PathBuf>,
696 /// Skip auto-loading `.env` from cwd.
697 #[arg(long)]
698 pub no_env_file: bool,
699 /// Select a named overlay from the config's `profiles:` block and deep-merge
700 /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
701 #[arg(long, env = "FAUCET_PROFILE")]
702 pub profile: Option<String>,
703}
704
705/// `faucet discover` arguments.
706#[derive(Debug, Parser)]
707pub struct DiscoverArgs {
708 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config whose source
709 /// points at the system to introspect. If omitted, auto-discover
710 /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
711 pub config: Option<PathBuf>,
712 /// Which source template to introspect (an entry under `pipeline.sources`).
713 /// Defaults to `default` (the legacy singular `pipeline.source`).
714 #[arg(long)]
715 pub source: Option<String>,
716 /// Only include datasets whose name matches this `*`-wildcard pattern
717 /// (repeatable; no patterns = include everything).
718 #[arg(long)]
719 pub include: Vec<String>,
720 /// Exclude datasets whose name matches this `*`-wildcard pattern
721 /// (repeatable; applied after --include).
722 #[arg(long)]
723 pub exclude: Vec<String>,
724 /// Write the generated config to this file instead of stdout.
725 #[arg(long, short = 'o')]
726 pub output: Option<PathBuf>,
727 /// Overwrite the --output file if it already exists.
728 #[arg(long)]
729 pub force: bool,
730 /// Emit the discovered datasets as machine-readable JSON instead of a
731 /// generated config.
732 #[arg(long)]
733 pub json: bool,
734 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
735 /// Defaults to `.env` in cwd if present.
736 #[arg(long, conflicts_with = "no_env_file")]
737 pub env_file: Option<PathBuf>,
738 /// Skip auto-loading `.env` from cwd.
739 #[arg(long)]
740 pub no_env_file: bool,
741 /// Select a named overlay from the config's `profiles:` block.
742 /// Overrides the `FAUCET_PROFILE` env var.
743 #[arg(long, env = "FAUCET_PROFILE")]
744 pub profile: Option<String>,
745}
746
747/// `faucet validate` arguments.
748#[derive(Debug, Parser)]
749pub struct ValidateArgs {
750 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
751 /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
752 pub config: Option<PathBuf>,
753 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
754 /// Defaults to `.env` in cwd if present.
755 #[arg(long, conflicts_with = "no_env_file")]
756 pub env_file: Option<PathBuf>,
757 /// Skip auto-loading `.env` from cwd.
758 #[arg(long)]
759 pub no_env_file: bool,
760 /// Validate grammar and structure only — skip fetching from secrets
761 /// managers (no network / credentials needed).
762 #[arg(long)]
763 pub no_secrets: bool,
764 /// Select a named overlay from the config's `profiles:` block and deep-merge
765 /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
766 #[arg(long, env = "FAUCET_PROFILE")]
767 pub profile: Option<String>,
768 /// Print the fully-composed config (after extends/!include/profile, before
769 /// `${...}` interpolation) and exit. For debugging composition precedence.
770 /// `--no-secrets` is redundant here (no interpolation or secret fetch occurs).
771 #[arg(long)]
772 pub show_composed: bool,
773}
774
775/// `faucet schema` arguments.
776#[derive(Debug, Parser)]
777pub struct SchemaArgs {
778 #[command(subcommand)]
779 pub target: SchemaTarget,
780}
781
782/// Schema subcommand target — which connector or system component to describe.
783#[derive(Debug, Subcommand)]
784pub enum SchemaTarget {
785 /// Composed JSON Schema for the **entire** `faucet.yaml` / `faucet.json`
786 /// config document (top-level grammar + per-connector `type` discrimination).
787 /// Point an editor at it with a `# yaml-language-server: $schema=…` header.
788 Config,
789 /// JSON Schema for a source connector config.
790 Source {
791 /// Connector name (e.g. `rest`, `graphql`, `postgres`).
792 name: String,
793 },
794 /// JSON Schema for a sink connector config.
795 Sink {
796 /// Connector name (e.g. `jsonl`, `bigquery`, `postgres`).
797 name: String,
798 },
799 /// JSON Schema for a transform's inline config.
800 Transform {
801 /// Transform name (e.g. `flatten`, `keys_case`, `cast`).
802 /// Run `faucet list` to see what is compiled in.
803 name: String,
804 },
805 /// JSON Schema for the DLQ (Dead Letter Queue) specification.
806 Dlq,
807 /// JSON Schema for the `replication:` (snapshot→CDC) block.
808 Replication,
809 /// JSON Schema for the `backfill:` (window replay defaults) block.
810 Backfill,
811 /// JSON Schema for the top-level `execution:` block.
812 Execution,
813 /// JSON Schema for the top-level `resilience:` block.
814 Resilience,
815 /// JSON Schema for the top-level `sla:` (freshness/volume SLA) block.
816 Sla,
817 /// JSON Schema for the `quality:` block.
818 #[cfg(feature = "quality")]
819 Quality,
820 /// JSON Schema for the `contract:` block.
821 #[cfg(feature = "contract")]
822 Contract,
823 /// JSON Schema for the `masking:` (PII masking) block.
824 #[cfg(feature = "masking")]
825 Masking,
826 /// JSON Schema for the `faucet test` spec file.
827 Test,
828 /// Grammar reference for secrets-manager interpolation directives.
829 Secrets,
830 /// JSON Schema for the `schedule:` block.
831 #[cfg(feature = "schedule")]
832 Schedule,
833 /// JSON Schema for the `lineage:` (OpenLineage) block.
834 #[cfg(feature = "lineage")]
835 Lineage,
836 /// JSON Schema for the `--triggers` file (event-driven pipeline triggers).
837 #[cfg(feature = "triggers")]
838 Triggers,
839 /// JSON Schema for the `notifications:` (incident-routing) block.
840 #[cfg(feature = "notify")]
841 Notifications,
842 /// JSON Schema for the `catalog:` (Data Movement Catalog store) block.
843 #[cfg(feature = "catalog")]
844 Catalog,
845}
846
847/// `faucet preview` arguments.
848#[derive(Debug, Parser)]
849pub struct PreviewArgs {
850 /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
851 /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
852 pub config: Option<PathBuf>,
853 /// Stop after this many records. Default: 10.
854 #[arg(long, default_value_t = 10)]
855 pub limit: usize,
856 /// Path to a `.env` file to load for `${env:VAR}` interpolation.
857 /// Defaults to `.env` in cwd if present.
858 #[arg(long, conflicts_with = "no_env_file")]
859 pub env_file: Option<PathBuf>,
860 /// Skip auto-loading `.env` from cwd.
861 #[arg(long)]
862 pub no_env_file: bool,
863 /// Select a named overlay from the config's `profiles:` block and deep-merge
864 /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
865 #[arg(long, env = "FAUCET_PROFILE")]
866 pub profile: Option<String>,
867}
868
869/// `faucet init` arguments.
870#[derive(Debug, Parser)]
871pub struct InitArgs {
872 /// Name written into the generated file's `name:` field. Defaults to
873 /// `my-pipeline` when omitted.
874 pub name: Option<String>,
875 /// Source connector kind to scaffold (e.g. `rest`, `postgres`, `s3`).
876 /// Defaults to `rest`. Run `faucet list` to see what is compiled in.
877 #[arg(long)]
878 pub source: Option<String>,
879 /// Sink connector kind to scaffold (e.g. `jsonl`, `bigquery`).
880 /// Defaults to `jsonl`. Run `faucet list` to see what is compiled in.
881 #[arg(long)]
882 pub sink: Option<String>,
883 /// Output file path. Defaults to `pipeline.yaml`.
884 #[arg(long, short = 'o', default_value = "pipeline.yaml")]
885 pub output: PathBuf,
886 /// Overwrite the output file if it already exists.
887 #[arg(long)]
888 pub force: bool,
889 /// Prompt for the source and sink kinds interactively instead of using
890 /// `--source` / `--sink`. Requires the `cli-interactive` build feature
891 /// and a TTY on stdin; falls back to the arg-driven path otherwise.
892 #[arg(long)]
893 pub interactive: bool,
894 /// Name of the template under which to register the scaffolded source
895 /// and sink. The generated config uses `pipeline.sources.<TEMPLATE>` and
896 /// `pipeline.sinks.<TEMPLATE>`. Defaults to `default` so a matrix row
897 /// without a `ref:` field still resolves through the new schema.
898 #[arg(long, default_value = "default")]
899 pub template: String,
900 /// (singer only) Run `<executable> --discover` to fetch the tap's catalog,
901 /// write it next to the output, and scaffold the config with the discovered
902 /// streams listed. Requires `--source singer` and `--executable`.
903 #[arg(long)]
904 pub discover: bool,
905 /// (singer only) The Singer tap executable to discover with (used by
906 /// `--discover`), e.g. `tap-github` or `/opt/taps/tap-csv`.
907 #[arg(long)]
908 pub executable: Option<String>,
909 /// (singer only) The target stream to emit. When given with `--discover`,
910 /// the written catalog marks this stream — and any inferable parent
911 /// streams (e.g. a parent-keyed tap's parent) — `selected`, and the
912 /// scaffolded config's `stream:` is set to it. Most DB / SDK taps sync
913 /// nothing unless a stream is selected in the catalog.
914 #[arg(long)]
915 pub stream: Option<String>,
916}
917
918/// `faucet plan` arguments.
919#[derive(Debug, Parser)]
920pub struct PlanArgs {
921 /// Path to a `.yaml`/`.yml`/`.json` config (auto-discovered if omitted).
922 pub config: Option<PathBuf>,
923 /// Which row to plan (default: the first root row).
924 #[arg(long)]
925 pub row: Option<String>,
926 /// Offline sample of input records (`.jsonl` or a `.json` array) to preview
927 /// the output schema, volume, and sink delta through — no source is touched.
928 #[arg(long)]
929 pub sample: Option<PathBuf>,
930 /// Pull a capped, read-only sample from the real source instead of a
931 /// fixture (bounded by `--limit`; no bookmark is advanced).
932 #[arg(long)]
933 pub live: bool,
934 /// Cap for `--live` sampling.
935 #[arg(long, default_value_t = 10)]
936 pub limit: usize,
937 /// Emit the plan as JSON.
938 #[arg(long)]
939 pub json: bool,
940 /// Resolve secrets-manager directives (needs network/credentials). Off by
941 /// default so `plan` works offline like `faucet test`.
942 #[arg(long)]
943 pub resolve_secrets: bool,
944 /// Select a `profiles:` overlay.
945 #[arg(long, env = "FAUCET_PROFILE")]
946 pub profile: Option<String>,
947}
948
949/// `faucet dev` arguments.
950#[derive(Debug, Parser)]
951pub struct DevArgs {
952 /// Path to the `.yaml`/`.yml`/`.json` config to watch.
953 pub config: PathBuf,
954 /// Which row to run (default: the first root row).
955 #[arg(long)]
956 pub row: Option<String>,
957 /// Offline sample of input records (`.jsonl` or `.json` array). Required
958 /// for the offline loop.
959 #[arg(long)]
960 pub sample: Option<PathBuf>,
961 /// (reserved) pull a capped read-only sample from the real source.
962 #[arg(long)]
963 pub live: bool,
964 /// Cap for `--live` sampling.
965 #[arg(long, default_value_t = 10)]
966 pub limit: usize,
967 /// Run once and exit instead of watching (also the non-TTY fallback).
968 #[arg(long)]
969 pub once: bool,
970 /// Debounce window between re-runs, in milliseconds.
971 #[arg(long, default_value_t = 300)]
972 pub debounce_ms: u64,
973 /// Select a `profiles:` overlay.
974 #[arg(long, env = "FAUCET_PROFILE")]
975 pub profile: Option<String>,
976}
977
978/// `faucet list` arguments.
979#[derive(Debug, Parser)]
980pub struct ListArgs {
981 /// List every connector in the registry index (not just the compiled-in
982 /// ones), marking which are already in this binary.
983 #[arg(long)]
984 pub available: bool,
985 /// Read a custom registry index instead of the built-in one.
986 #[arg(long)]
987 pub index: Option<PathBuf>,
988}
989
990/// `faucet conformance` arguments.
991#[derive(Debug, Parser)]
992pub struct ConformanceArgs {
993 /// Only score the connector with this system name (e.g. `postgres`); prints
994 /// a detailed scorecard. Omit to score every compiled-in connector.
995 pub name: Option<String>,
996 /// Restrict to `source` or `sink`.
997 #[arg(long)]
998 pub kind: Option<String>,
999 /// Emit the full scorecards as JSON.
1000 #[arg(long)]
1001 pub json: bool,
1002}
1003
1004/// `faucet search` arguments.
1005#[derive(Debug, Parser)]
1006pub struct SearchArgs {
1007 /// Term to match against connector name / description / keywords / crate.
1008 pub term: String,
1009 /// Read a custom registry index instead of the built-in one.
1010 #[arg(long)]
1011 pub index: Option<PathBuf>,
1012 /// Emit matches as JSON.
1013 #[arg(long)]
1014 pub json: bool,
1015}
1016
1017/// `faucet install` arguments.
1018#[derive(Debug, Parser)]
1019pub struct InstallArgs {
1020 /// Connector system name (e.g. `kafka`).
1021 pub name: String,
1022 /// Disambiguate when a name exists as both a source and a sink.
1023 #[arg(long)]
1024 pub kind: Option<String>,
1025 /// Read a custom registry index instead of the built-in one.
1026 #[arg(long)]
1027 pub index: Option<PathBuf>,
1028}
1029
1030/// `faucet new` arguments.
1031#[derive(Debug, Parser)]
1032pub struct NewArgs {
1033 #[command(subcommand)]
1034 pub target: NewTarget,
1035}
1036
1037/// What `faucet new` scaffolds.
1038#[derive(Debug, Subcommand)]
1039pub enum NewTarget {
1040 /// Scaffold a ready-to-build `faucet-source-<name>` / `faucet-sink-<name>`
1041 /// connector crate following every repo convention.
1042 Connector(NewConnectorArgs),
1043}
1044
1045/// `faucet new connector` arguments.
1046#[derive(Debug, Parser)]
1047pub struct NewConnectorArgs {
1048 /// Connector system name (lowercase, e.g. `acme` or `acme-widgets`). Becomes
1049 /// the crate name `faucet-<kind>-<name>` and the YAML `type:` value.
1050 pub name: String,
1051 /// Whether to scaffold a `source` or a `sink`.
1052 #[arg(long)]
1053 pub kind: String,
1054 /// Also scaffold a `faucet-common-<name>` crate for config shared between a
1055 /// source/sink pair.
1056 #[arg(long)]
1057 pub common: bool,
1058 /// Directory to write the new crate(s) into. Defaults to the current dir.
1059 #[arg(long, short = 'o', default_value = ".")]
1060 pub output: PathBuf,
1061 /// Overwrite any existing files.
1062 #[arg(long)]
1063 pub force: bool,
1064}