1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
use clap::{Parser, Subcommand, ValueEnum};
use clap_complete::Shell;
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(
name = "svccat",
version,
about = "Service catalog drift detection for multi-service repositories",
long_about = "\
svccat reads your declared service manifest and compares it against what \
actually exists in the repo, flagging drift before it becomes toil.\n\n\
Typical workflow:\n \
svccat check # inspect drift in the current repo\n \
svccat check --fail-on-drift # gate CI on zero drift\n \
svccat graph # emit a Mermaid diagram for docs\n \
svccat export --format json # machine-readable catalog snapshot"
)]
pub struct Cli {
/// Repository root (defaults to current directory)
#[arg(short, long, global = true)]
pub root: Option<PathBuf>,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Check declared services against the repo and report drift
Check {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Output format
#[arg(short, long, value_enum, default_value_t = OutputFormat::Terminal)]
format: OutputFormat,
/// Exit with code 1 when drift is detected (useful in CI)
#[arg(long)]
fail_on_drift: bool,
/// Ping each service URL and report reachability
#[arg(long)]
ping: bool,
/// Glob patterns to exclude from discovery (repeatable, e.g. --ignore "examples/*")
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
/// Only check services owned by this team (matches the `team:` field)
#[arg(long, value_name = "TEAM")]
team: Option<String>,
/// Compare against the manifest at this git ref; show only new / resolved drift
///
/// Example: --since HEAD~1 or --since main
#[arg(long, value_name = "GIT_REF")]
since: Option<String>,
/// Exit with code 1 when --since reveals new drift items (ignores pre-existing drift)
#[arg(long, requires = "since")]
fail_on_new_drift: bool,
/// Maximum directory depth to scan for services (default: 1 - direct children of each
/// discovery path). Use 2 to also detect services nested one level deeper, e.g.
/// services/team/auth-service.
#[arg(long, value_name = "N", default_value_t = 1)]
depth: u32,
/// Only report drift absent from this saved baseline snapshot (JSON from svccat export).
///
/// Generate a baseline with: svccat export --format json > baseline.json
/// Then gate on regressions only: svccat check --baseline baseline.json --fail-on-drift
#[arg(long, value_name = "FILE")]
baseline: Option<PathBuf>,
/// Write output to this file instead of stdout
#[arg(short, long, value_name = "FILE")]
output: Option<PathBuf>,
},
/// Generate a Mermaid or Markdown view of the service catalog
Graph {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Output format
#[arg(short, long, value_enum, default_value_t = GraphFormat::Mermaid)]
format: GraphFormat,
/// Only include services owned by this team; cross-team depends_on targets
/// are shown as external nodes
#[arg(long, value_name = "TEAM")]
team: Option<String>,
/// Only include services whose name contains this substring (case-insensitive)
#[arg(long, value_name = "PATTERN")]
filter: Option<String>,
/// Write output to this file instead of stdout
#[arg(short, long, value_name = "FILE")]
output: Option<PathBuf>,
},
/// Export the full service catalog with drift summary
Export {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Export format
#[arg(short, long, value_enum, default_value_t = ExportFormat::Json)]
format: ExportFormat,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
/// Maximum directory depth to scan for services (default: 1)
#[arg(long, value_name = "N", default_value_t = 1)]
depth: u32,
/// Only export services that changed since this git ref (new or modified fields)
///
/// Example: --since HEAD~1 or --since main
#[arg(long, value_name = "GIT_REF")]
since: Option<String>,
},
/// Scaffold a services.yaml by auto-discovering services in the repo
Init {
/// Where to write the manifest (default: services.yaml in repo root)
#[arg(short, long)]
output: Option<PathBuf>,
/// Overwrite an existing manifest file
#[arg(long)]
force: bool,
},
/// Compare two svccat export JSON snapshots and show what changed
///
/// Generate snapshots with: svccat export --format json > snapshot.json
Diff {
/// Path to the older snapshot (JSON)
before: std::path::PathBuf,
/// Path to the newer snapshot (JSON)
after: std::path::PathBuf,
/// Output format
#[arg(short, long, value_enum, default_value_t = DiffFormat::Terminal)]
format: DiffFormat,
},
/// Watch the manifest and service directories for changes and re-run drift checks
///
/// Continuously monitors the manifest file and service directories.
/// Re-runs drift analysis whenever a file change is detected and prints
/// a timestamped report. Press Ctrl-C to stop.
Watch {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Exit with code 1 when drift is detected on the first check
#[arg(long)]
fail_on_drift: bool,
/// Only watch services owned by this team
#[arg(long, value_name = "TEAM")]
team: Option<String>,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
/// Maximum directory depth to scan for services (default: 1)
#[arg(long, value_name = "N", default_value_t = 1)]
depth: u32,
/// On each check, compare against the manifest at this git ref and show only new drift
///
/// Example: --since HEAD~1 or --since main
#[arg(long, value_name = "GIT_REF")]
since: Option<String>,
/// Send a native OS desktop notification when the drift count changes
#[arg(long)]
notify: bool,
/// Also re-run every N seconds regardless of file-system events
///
/// Useful in Docker containers, network mounts, or other environments
/// where inotify is unreliable.
#[arg(long, value_name = "N")]
interval: Option<u64>,
},
/// Generate a full ownership and drift report
///
/// Outputs a per-team breakdown of every service with language, platform, oncall,
/// and drift status, plus a dependency summary and full drift details.
Report {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Output format
#[arg(short, long, value_enum, default_value_t = ReportFormat::Markdown)]
format: ReportFormat,
/// Write output to this file instead of stdout
#[arg(short, long, value_name = "FILE")]
output: Option<PathBuf>,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
/// Show drift evolution across the last N git commits
///
/// Requires a git repository. Loads the manifest at each of the last N commits
/// and emits a Markdown table of errors/warnings over time.
#[arg(long, value_name = "N")]
history: Option<usize>,
/// Emit a Shields.io badge Markdown snippet reflecting current drift status.
///
/// Prints a Markdown image link you can paste into your README:
/// [](…)
#[arg(long)]
badge: bool,
},
/// Validate the manifest for structural issues
///
/// Checks for duplicate service names, blank names, self-referential
/// depends_on entries, duplicate depends_on entries, and unknown manifest versions.
Lint {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
},
/// Import services from an external catalog format and merge into services.yaml
///
/// Supported sources:
/// backstage Walk the repo for catalog-info.yaml files (Backstage format)
/// and generate service entries from every Kind: Component found.
/// docker-compose Parse services from docker-compose.yml / compose.yaml
/// openapi Walk the repo for openapi.yaml / swagger.yaml spec files
Import {
/// Source catalog format to import from
#[arg(value_enum)]
from: ImportSource,
/// Where to write the merged manifest (default: services.yaml in repo root)
#[arg(short, long)]
output: Option<PathBuf>,
/// Overwrite an existing manifest instead of merging
#[arg(long)]
force: bool,
},
/// Auto-remediate simple drift in the manifest
///
/// Adds undeclared services found in the repo and, with --prune, removes
/// declared services whose directories are no longer present.
Fix {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Also remove declared services whose directories are missing from the repo
#[arg(long)]
prune: bool,
/// Preview changes without writing anything
#[arg(long)]
dry_run: bool,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
/// Maximum directory depth to scan for services (default: 1)
#[arg(long, value_name = "N", default_value_t = 1)]
depth: u32,
},
/// Install a git hook that runs svccat check on commit or push
///
/// Writes a shell script to .git/hooks/<hook> that runs `svccat check`.
/// The hook file must not already exist.
InstallHooks {
/// Which git hook to install
#[arg(long, value_enum, default_value_t = HookKind::PreCommit)]
hook: HookKind,
/// Pass --fail-on-drift to svccat check in the hook script
#[arg(long, default_value_t = true)]
fail_on_drift: bool,
},
/// Run a full audit: lint + drift + optional URL ping, with a health score
///
/// Combines `svccat lint`, `svccat check`, and optionally `svccat check --ping`
/// into a single pass, emitting a scored summary. Exits with code 1 when
/// drift errors or lint errors are present.
Audit {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Ping each service URL and include reachability in the score
#[arg(long)]
ping: bool,
/// Estimate deployment costs based on declared platforms
#[arg(long)]
cost_estimate: bool,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
/// Maximum directory depth to scan for services (default: 1)
#[arg(long, value_name = "N", default_value_t = 1)]
depth: u32,
/// Output format
#[arg(short, long, value_enum, default_value_t = AuditFormat::Terminal)]
format: AuditFormat,
},
/// Enforce metadata policies defined in .svccat/policy.yaml
///
/// Policies declare which fields are `required` (errors) or `recommended`
/// (warnings) for every service in the manifest.
///
/// Example policy file (.svccat/policy.yaml):
/// required:
/// - team
/// - oncall
/// recommended:
/// - language
/// - docs
Policy {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Output format
#[arg(short, long, value_enum, default_value_t = PolicyFormat::Terminal)]
format: PolicyFormat,
/// Exit with code 1 when policy errors are present
#[arg(long)]
fail_on_violations: bool,
},
/// Manage named snapshots of the service catalog
///
/// Snapshots are saved to .svccat/snapshots/ and can be compared with
/// `svccat diff` by using the snapshot file path.
///
/// Commands:
/// save <name> - Save the current catalog as a named snapshot
/// list - List all saved snapshots
/// delete <name> - Remove a snapshot
Snapshot {
#[command(subcommand)]
action: SnapshotAction,
},
/// Run lint + drift + policy in a single CI-friendly pass
///
/// Combines `svccat lint`, `svccat check`, and `svccat policy` (when a
/// policy file exists) into one command. Exits with code 1 on any errors.
Ci {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
/// Maximum directory depth to scan for services (default: 1)
#[arg(long, value_name = "N", default_value_t = 1)]
depth: u32,
/// Output format
#[arg(short, long, value_enum, default_value_t = CiFormat::Terminal)]
format: CiFormat,
/// Watch the manifest and service directories; re-run on every change
#[arg(long)]
watch: bool,
/// Also re-run every N seconds regardless of file-system events
#[arg(long, value_name = "N")]
interval: Option<u64>,
},
/// Search the service catalog
///
/// Supports `field:value` syntax (e.g. `team:payments`) and plain
/// substring matching against all fields.
///
/// Searchable fields: name, language, platform, url, role, team, oncall,
/// docs, ci, path, tags, depends_on.
Search {
/// Search query, e.g. `team:platform` or `auth`
query: String,
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Write output to this file instead of stdout
#[arg(short, long, value_name = "FILE")]
output: Option<PathBuf>,
},
/// Analyse inter-service dependencies declared in the manifest
///
/// Detects undeclared dependency targets (services referenced in
/// `depends_on` that are not in the manifest) and circular dependency
/// chains. Exits with code 1 when errors are found.
Deps {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Output format
#[arg(short, long, value_enum, default_value_t = DepsFormat::Terminal)]
format: DepsFormat,
/// Write output to this file instead of stdout
#[arg(short, long, value_name = "FILE")]
output: Option<PathBuf>,
},
/// Add or remove tags on a service entry in the manifest
///
/// Modifies the manifest file in-place.
Tag {
#[command(subcommand)]
action: TagAction,
},
/// Score every service on completeness, drift health, and policy compliance
///
/// Each service receives three dimension scores (0-100):
/// completeness - how many optional metadata fields are populated
/// drift - 100 when clean, reduced by drift errors
/// policy - 100 when no policy errors are present
///
/// A composite total is computed as: completeness*40% + drift*40% + policy*20%.
Scorecard {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
/// Maximum directory depth to scan for services (default: 1)
#[arg(long, value_name = "N", default_value_t = 1)]
depth: u32,
/// Output format
#[arg(short, long, value_enum, default_value_t = ScorecardFormat::Terminal)]
format: ScorecardFormat,
/// Write output to this file instead of stdout
#[arg(short, long, value_name = "FILE")]
output: Option<PathBuf>,
},
/// Fire a webhook manually or configure automatic webhook notifications
///
/// When invoked without a subcommand, runs the current drift check and fires
/// the configured webhook if thresholds are met.
///
/// Configure a webhook URL in `svccat.toml`:
/// [webhook]
/// url = "https://hooks.slack.com/services/..."
/// on_errors = true
/// on_warnings = false
Webhook {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
/// Maximum directory depth to scan for services (default: 1)
#[arg(long, value_name = "N", default_value_t = 1)]
depth: u32,
/// Override the webhook URL from svccat.toml
#[arg(long, value_name = "URL")]
url: Option<String>,
},
/// Show field-coverage statistics for the service manifest
///
/// Displays how many services have each metadata field set, with a
/// percentage and ASCII bar chart per field, plus an overall health score.
Stats {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
},
/// Serve a live HTML drift report over HTTP
///
/// Starts a local HTTP server that renders the drift report as HTML on
/// every request, so you always see up-to-date information.
/// Browse to http://localhost:<port> (default 7777).
Serve {
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// TCP port to listen on (default: 7777)
#[arg(short, long, default_value_t = 7777)]
port: u16,
/// Auto-refresh the browser page every N seconds (0 = disabled)
#[arg(long, value_name = "N", default_value_t = 0)]
refresh: u32,
},
/// Manage multiple service repositories as a workspace
///
/// Check drift across multiple repositories, with aggregated reporting
/// and cross-repo dependency analysis. Requires a workspace configuration
/// with a [workspace] section in svccat.toml.
Workspace {
#[command(subcommand)]
action: WorkspaceAction,
},
/// Run a self-contained demo against a temporary sample monorepo
///
/// Creates a small sample catalog (with deliberate drift) in a temp directory
/// and walks through `check`, `graph`, and `stats` so you can see svccat in
/// action with zero setup. Afterwards, run `svccat init` in your own repo.
Demo {
/// Keep the generated sample directory instead of deleting it
#[arg(long)]
keep: bool,
},
/// Print shell completion script to stdout
///
/// Source the output to enable tab completion, e.g.:
/// source <(svccat completions bash)
/// svccat completions zsh > ~/.zfunc/_svccat
Completions {
/// Shell to generate completions for
shell: Shell,
},
}
#[derive(Subcommand, Debug)]
pub enum WorkspaceAction {
/// Check declared services against multiple repositories and report drift
Check {
/// Path to workspace config file (default: svccat.toml)
#[arg(long, default_value = "svccat.toml")]
config: PathBuf,
/// Filter to specific repositories (comma-separated names)
#[arg(long, value_name = "REPOS")]
filter: Option<String>,
/// Output format
#[arg(short, long, value_enum, default_value_t = OutputFormat::Terminal)]
format: OutputFormat,
/// Exit with code 1 when drift is detected (useful in CI)
#[arg(long)]
fail_on_drift: bool,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
/// Maximum directory depth to scan for services (default: 1)
#[arg(long, value_name = "N", default_value_t = 1)]
depth: u32,
/// Write output to this file instead of stdout
#[arg(short, long, value_name = "FILE")]
output: Option<PathBuf>,
},
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum OutputFormat {
Terminal,
/// One line per service: status icon, name, and drift summary
Compact,
Json,
Sarif,
Markdown,
Junit,
GithubAnnotation,
/// Comma-separated values: service, severity, kind, message, detail
Csv,
/// Slack Block Kit JSON payload for posting to a channel or webhook
Slack,
/// Microsoft Teams Adaptive Card JSON payload
Teams,
/// Datadog Events API JSON payload
Datadog,
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum GraphFormat {
Mermaid,
Markdown,
/// Graphviz DOT digraph (pipe to `dot -Tsvg` to render)
Dot,
/// PlantUML component diagram (paste at plantuml.com or pipe to plantuml)
Plantuml,
/// Interactive self-contained HTML file with a D3.js force-directed graph
Html,
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum ReportFormat {
Markdown,
Html,
/// Machine-readable JSON: manifest summary + per-team service breakdown
Json,
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum ExportFormat {
Json,
Markdown,
/// Comma-separated values: name, language, platform, role, url, team, oncall
Csv,
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum ImportSource {
/// Backstage catalog-info.yaml files (Kind: Component)
Backstage,
/// Docker Compose services (docker-compose.yml or compose.yaml)
DockerCompose,
/// OpenAPI / Swagger spec files (openapi.yaml, swagger.yaml)
Openapi,
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum HookKind {
/// Run on every git commit (pre-commit hook)
PreCommit,
/// Run before git push (pre-push hook)
PrePush,
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum DiffFormat {
/// Coloured terminal output (default)
Terminal,
/// GitHub-flavoured Markdown table
Markdown,
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum AuditFormat {
/// Coloured terminal output with bar chart (default)
Terminal,
/// Machine-readable JSON object
Json,
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum PolicyFormat {
/// Coloured terminal output (default)
Terminal,
/// Machine-readable JSON array of violations
Json,
}
#[derive(Subcommand, Debug)]
pub enum SnapshotAction {
/// Save the current catalog state as a named snapshot
Save {
/// Name for this snapshot (e.g. v1.0, pre-migration)
name: String,
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
/// Maximum directory depth to scan for services (default: 1)
#[arg(long, value_name = "N", default_value_t = 1)]
depth: u32,
},
/// List all saved snapshots
List,
/// Delete a named snapshot
Delete {
/// Name of the snapshot to delete
name: String,
},
/// Compare two named snapshots against each other
Compare {
/// Name of the older ("before") snapshot
before: String,
/// Name of the newer ("after") snapshot
after: String,
/// Output format
#[arg(short, long, value_enum, default_value_t = DiffFormat::Terminal)]
format: DiffFormat,
},
/// Compare a named snapshot against the current catalog state
Diff {
/// Name of the snapshot to compare against
name: String,
/// Glob patterns to exclude from discovery (repeatable)
#[arg(long, value_name = "PATTERN")]
ignore: Vec<String>,
/// Maximum directory depth to scan for services (default: 1)
#[arg(long, value_name = "N", default_value_t = 1)]
depth: u32,
/// Output format
#[arg(short, long, value_enum, default_value_t = DiffFormat::Terminal)]
format: DiffFormat,
},
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum CiFormat {
/// Coloured terminal output (default)
Terminal,
/// Machine-readable JSON
Json,
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum DepsFormat {
/// Coloured terminal output (default)
Terminal,
/// Mermaid flowchart
Mermaid,
/// Machine-readable JSON
Json,
}
#[derive(Debug, Clone, ValueEnum, PartialEq)]
pub enum ScorecardFormat {
/// Coloured table output (default)
Terminal,
/// Machine-readable JSON
Json,
/// GitHub-flavoured Markdown table
Markdown,
}
#[derive(Subcommand, Debug)]
pub enum TagAction {
/// Add a tag to a service
Add {
/// Service name
service: String,
/// Tag to add
tag: String,
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
},
/// Remove a tag from a service
Remove {
/// Service name
service: String,
/// Tag to remove
tag: String,
/// Path to the manifest file (auto-detected if omitted)
#[arg(short, long)]
manifest: Option<PathBuf>,
},
}