1use std::collections::BTreeMap;
22use std::path::PathBuf;
23
24use clap::{Args, Parser, Subcommand, ValueEnum};
25use serde::{Deserialize, Serialize};
26pub use tokmd_tool_schema::ToolSchemaFormat;
27pub use tokmd_types::{
28 AnalysisFormat, ChildIncludeMode, ChildrenMode, ConfigMode, ExportFormat, RedactMode,
29 TableFormat,
30};
31
32#[derive(Parser, Debug)]
36#[command(name = "tokmd", version, about, long_about = None)]
37pub struct Cli {
38 #[command(flatten)]
39 pub global: GlobalArgs,
40
41 #[command(flatten)]
43 pub lang: CliLangArgs,
44
45 #[command(subcommand)]
46 pub command: Option<Commands>,
47
48 #[arg(long, visible_alias = "view", global = true)]
50 pub profile: Option<String>,
51}
52
53#[derive(Args, Debug, Clone, Default)]
54pub struct GlobalArgs {
55 #[arg(
61 long = "exclude",
62 visible_alias = "ignore",
63 value_name = "PATTERN",
64 global = true
65 )]
66 pub excluded: Vec<String>,
67
68 #[arg(long, value_enum, default_value_t = ConfigMode::Auto)]
70 pub config: ConfigMode,
71
72 #[arg(long)]
74 pub hidden: bool,
75
76 #[arg(long)]
80 pub no_ignore: bool,
81
82 #[arg(long)]
84 pub no_ignore_parent: bool,
85
86 #[arg(long)]
88 pub no_ignore_dot: bool,
89
90 #[arg(long, visible_alias = "no-ignore-git")]
92 pub no_ignore_vcs: bool,
93
94 #[arg(long)]
96 pub treat_doc_strings_as_comments: bool,
97
98 #[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count)]
100 pub verbose: u8,
101
102 #[arg(long, global = true)]
104 pub no_progress: bool,
105}
106
107#[derive(Subcommand, Debug, Clone)]
108pub enum Commands {
109 Lang(CliLangArgs),
111
112 Module(CliModuleArgs),
114
115 Export(CliExportArgs),
117
118 Analyze(CliAnalyzeArgs),
120
121 Badge(BadgeArgs),
123
124 Init(InitArgs),
126
127 Completions(CompletionsArgs),
129
130 Run(RunArgs),
132
133 Diff(DiffArgs),
135
136 Context(CliContextArgs),
138
139 CheckIgnore(CliCheckIgnoreArgs),
141
142 Tools(ToolsArgs),
144
145 Gate(CliGateArgs),
147
148 Cockpit(CockpitArgs),
150
151 Baseline(BaselineArgs),
153
154 Handoff(HandoffArgs),
156
157 Sensor(SensorArgs),
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize, Default)]
162pub struct UserConfig {
163 pub profiles: BTreeMap<String, Profile>,
164 pub repos: BTreeMap<String, String>, }
166
167#[derive(Debug, Clone, Serialize, Deserialize, Default)]
168pub struct Profile {
169 pub format: Option<String>, pub top: Option<usize>,
172
173 pub files: Option<bool>,
175
176 pub module_roots: Option<Vec<String>>,
178 pub module_depth: Option<usize>,
179 pub min_code: Option<usize>,
180 pub max_rows: Option<usize>,
181 pub redact: Option<RedactMode>,
182 pub meta: Option<bool>,
183
184 pub children: Option<String>,
186}
187
188#[derive(Args, Debug, Clone)]
189pub struct RunArgs {
190 #[arg(value_name = "PATH", default_value = ".")]
192 pub paths: Vec<PathBuf>,
193
194 #[arg(long)]
196 pub output_dir: Option<PathBuf>,
197
198 #[arg(long)]
200 pub name: Option<String>,
201
202 #[arg(long, value_enum)]
204 pub analysis: Option<AnalysisPreset>,
205
206 #[arg(long, value_enum)]
208 pub redact: Option<RedactMode>,
209}
210
211#[derive(Args, Debug, Clone)]
212pub struct DiffArgs {
213 #[arg(long)]
215 pub from: Option<String>,
216
217 #[arg(long)]
219 pub to: Option<String>,
220
221 #[arg(value_name = "REF", num_args = 2)]
223 pub refs: Vec<String>,
224
225 #[arg(long, value_enum, default_value_t = DiffFormat::Md)]
227 pub format: DiffFormat,
228
229 #[arg(long)]
231 pub compact: bool,
232
233 #[arg(long, value_enum, default_value_t = ColorMode::Auto)]
235 pub color: ColorMode,
236}
237
238#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
239#[serde(rename_all = "kebab-case")]
240pub enum DiffFormat {
241 #[default]
243 Md,
244 Json,
246}
247
248#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
249#[serde(rename_all = "kebab-case")]
250pub enum ColorMode {
251 #[default]
253 Auto,
254 Always,
256 Never,
258}
259
260#[derive(Args, Debug, Clone)]
261pub struct CompletionsArgs {
262 #[arg(value_enum)]
264 pub shell: Shell,
265}
266
267#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
268#[serde(rename_all = "kebab-case")]
269pub enum Shell {
270 Bash,
271 Elvish,
272 Fish,
273 Powershell,
274 Zsh,
275}
276
277#[derive(Args, Debug, Clone, Default)]
278pub struct CliLangArgs {
279 #[arg(value_name = "PATH")]
281 pub paths: Option<Vec<PathBuf>>,
282
283 #[arg(long, value_enum)]
285 pub format: Option<TableFormat>,
286
287 #[arg(long)]
290 pub top: Option<usize>,
291
292 #[arg(long)]
294 pub files: bool,
295
296 #[arg(long, value_enum)]
298 pub children: Option<ChildrenMode>,
299}
300
301#[derive(Args, Debug, Clone)]
302pub struct CliModuleArgs {
303 #[arg(value_name = "PATH")]
305 pub paths: Option<Vec<PathBuf>>,
306
307 #[arg(long, value_enum)]
309 pub format: Option<TableFormat>,
310
311 #[arg(long)]
314 pub top: Option<usize>,
315
316 #[arg(long, value_delimiter = ',')]
321 pub module_roots: Option<Vec<String>>,
322
323 #[arg(long)]
329 pub module_depth: Option<usize>,
330
331 #[arg(long, value_enum)]
333 pub children: Option<ChildIncludeMode>,
334}
335
336#[derive(Args, Debug, Clone)]
337pub struct CliExportArgs {
338 #[arg(value_name = "PATH")]
340 pub paths: Option<Vec<PathBuf>>,
341
342 #[arg(long, value_enum)]
344 pub format: Option<ExportFormat>,
345
346 #[arg(long, value_name = "PATH", visible_alias = "out")]
348 pub output: Option<PathBuf>,
349
350 #[arg(long, value_delimiter = ',')]
352 pub module_roots: Option<Vec<String>>,
353
354 #[arg(long)]
356 pub module_depth: Option<usize>,
357
358 #[arg(long, value_enum)]
360 pub children: Option<ChildIncludeMode>,
361
362 #[arg(long)]
364 pub min_code: Option<usize>,
365
366 #[arg(long)]
368 pub max_rows: Option<usize>,
369
370 #[arg(long, action = clap::ArgAction::Set)]
372 pub meta: Option<bool>,
373
374 #[arg(long, value_enum)]
376 pub redact: Option<RedactMode>,
377
378 #[arg(long, value_name = "PATH")]
380 pub strip_prefix: Option<PathBuf>,
381}
382
383#[derive(Args, Debug, Clone)]
384pub struct CliAnalyzeArgs {
385 #[arg(value_name = "INPUT", default_value = ".")]
387 pub inputs: Vec<PathBuf>,
388
389 #[arg(long, value_enum)]
391 pub preset: Option<AnalysisPreset>,
392
393 #[arg(long, value_enum)]
395 pub format: Option<AnalysisFormat>,
396
397 #[arg(long)]
399 pub window: Option<usize>,
400
401 #[arg(long, action = clap::ArgAction::SetTrue, conflicts_with = "no_git")]
403 pub git: bool,
404
405 #[arg(long = "no-git", action = clap::ArgAction::SetTrue, conflicts_with = "git")]
407 pub no_git: bool,
408
409 #[arg(long)]
411 pub output_dir: Option<PathBuf>,
412
413 #[arg(long)]
415 pub max_files: Option<usize>,
416
417 #[arg(long)]
419 pub max_bytes: Option<u64>,
420
421 #[arg(long)]
423 pub max_file_bytes: Option<u64>,
424
425 #[arg(long)]
427 pub max_commits: Option<usize>,
428
429 #[arg(long)]
431 pub max_commit_files: Option<usize>,
432
433 #[arg(long, value_enum)]
435 pub granularity: Option<ImportGranularity>,
436
437 #[arg(long)]
439 pub detail_functions: bool,
440
441 #[arg(long)]
443 pub near_dup: bool,
444
445 #[arg(long, default_value = "0.80")]
447 pub near_dup_threshold: f64,
448
449 #[arg(long, default_value = "2000")]
451 pub near_dup_max_files: usize,
452
453 #[arg(long, value_enum)]
455 pub near_dup_scope: Option<NearDupScope>,
456
457 #[arg(long, default_value = "10000")]
459 pub near_dup_max_pairs: usize,
460
461 #[arg(long, value_name = "GLOB")]
463 pub near_dup_exclude: Vec<String>,
464
465 #[arg(long, value_name = "KEY")]
467 pub explain: Option<String>,
468}
469
470#[derive(Args, Debug, Clone)]
471pub struct BadgeArgs {
472 #[arg(value_name = "INPUT", default_value = ".")]
474 pub inputs: Vec<PathBuf>,
475
476 #[arg(long, value_enum)]
478 pub metric: BadgeMetric,
479
480 #[arg(long, value_enum)]
482 pub preset: Option<AnalysisPreset>,
483
484 #[arg(long, action = clap::ArgAction::SetTrue, conflicts_with = "no_git")]
486 pub git: bool,
487
488 #[arg(long = "no-git", action = clap::ArgAction::SetTrue, conflicts_with = "git")]
490 pub no_git: bool,
491
492 #[arg(long)]
494 pub max_commits: Option<usize>,
495
496 #[arg(long)]
498 pub max_commit_files: Option<usize>,
499
500 #[arg(long, visible_alias = "out")]
502 pub output: Option<PathBuf>,
503}
504
505#[derive(Args, Debug, Clone)]
506pub struct InitArgs {
507 #[arg(long, value_name = "DIR", default_value = ".")]
509 pub dir: PathBuf,
510
511 #[arg(long)]
513 pub force: bool,
514
515 #[arg(long)]
517 pub print: bool,
518
519 #[arg(long, value_enum, default_value_t = InitProfile::Default)]
521 pub template: InitProfile,
522
523 #[arg(long)]
525 pub non_interactive: bool,
526}
527
528#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
529#[serde(rename_all = "kebab-case")]
530pub enum AnalysisPreset {
531 Receipt,
532 Health,
533 Risk,
534 Supply,
535 Architecture,
536 Topics,
537 Security,
538 Identity,
539 Git,
540 Deep,
541 Fun,
542}
543
544#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
545#[serde(rename_all = "kebab-case")]
546pub enum ImportGranularity {
547 Module,
548 File,
549}
550
551#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
552#[serde(rename_all = "kebab-case")]
553pub enum BadgeMetric {
554 Lines,
555 Tokens,
556 Bytes,
557 Doc,
558 Blank,
559 Hotspot,
560}
561
562#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
563#[serde(rename_all = "kebab-case")]
564pub enum InitProfile {
565 Default,
566 Rust,
567 Node,
568 Mono,
569 Python,
570 Go,
571 Cpp,
572}
573
574#[derive(Args, Debug, Clone)]
575pub struct CliContextArgs {
576 #[arg(value_name = "PATH")]
578 pub paths: Option<Vec<PathBuf>>,
579
580 #[arg(long, default_value = "128k")]
582 pub budget: String,
583
584 #[arg(long, value_enum, default_value_t = ContextStrategy::Greedy)]
586 pub strategy: ContextStrategy,
587
588 #[arg(long, value_enum, default_value_t = ValueMetric::Code)]
590 pub rank_by: ValueMetric,
591
592 #[arg(long = "mode", value_enum, default_value_t = ContextOutput::List)]
594 pub output_mode: ContextOutput,
595
596 #[arg(long)]
598 pub compress: bool,
599
600 #[arg(long)]
602 pub no_smart_exclude: bool,
603
604 #[arg(long, value_delimiter = ',')]
606 pub module_roots: Option<Vec<String>>,
607
608 #[arg(long)]
610 pub module_depth: Option<usize>,
611
612 #[arg(long)]
614 pub git: bool,
615
616 #[arg(long = "no-git")]
618 pub no_git: bool,
619
620 #[arg(long, default_value = "1000")]
622 pub max_commits: usize,
623
624 #[arg(long, default_value = "100")]
626 pub max_commit_files: usize,
627
628 #[arg(long, value_name = "PATH", visible_alias = "out")]
630 pub output: Option<PathBuf>,
631
632 #[arg(long)]
634 pub force: bool,
635
636 #[arg(long, value_name = "DIR", conflicts_with = "output")]
638 pub bundle_dir: Option<PathBuf>,
639
640 #[arg(long, default_value = "10485760")]
642 pub max_output_bytes: u64,
643
644 #[arg(long, value_name = "PATH")]
646 pub log: Option<PathBuf>,
647
648 #[arg(long, default_value = "0.15")]
650 pub max_file_pct: f64,
651
652 #[arg(long)]
654 pub max_file_tokens: Option<usize>,
655
656 #[arg(long)]
658 pub require_git_scores: bool,
659}
660
661#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
662#[serde(rename_all = "kebab-case")]
663pub enum ContextStrategy {
664 #[default]
666 Greedy,
667 Spread,
669}
670
671#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
672#[serde(rename_all = "kebab-case")]
673pub enum ValueMetric {
674 #[default]
676 Code,
677 Tokens,
679 Churn,
681 Hotspot,
683}
684
685#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
686#[serde(rename_all = "kebab-case")]
687pub enum ContextOutput {
688 #[default]
690 List,
691 Bundle,
693 Json,
695}
696
697#[derive(Args, Debug, Clone)]
698pub struct CliCheckIgnoreArgs {
699 #[arg(value_name = "PATH", required = true)]
701 pub paths: Vec<PathBuf>,
702
703 #[arg(long, short = 'v')]
705 pub verbose: bool,
706}
707
708#[derive(Args, Debug, Clone)]
709pub struct ToolsArgs {
710 #[arg(long, value_enum, default_value_t = ToolSchemaFormat::Jsonschema)]
712 pub format: ToolSchemaFormat,
713
714 #[arg(long)]
716 pub pretty: bool,
717}
718
719#[derive(Args, Debug, Clone)]
720pub struct CliGateArgs {
721 #[arg(value_name = "INPUT")]
723 pub input: Option<PathBuf>,
724
725 #[arg(long)]
727 pub policy: Option<PathBuf>,
728
729 #[arg(long, value_name = "PATH")]
734 pub baseline: Option<PathBuf>,
735
736 #[arg(long, value_name = "PATH")]
741 pub ratchet_config: Option<PathBuf>,
742
743 #[arg(long, value_enum)]
745 pub preset: Option<AnalysisPreset>,
746
747 #[arg(long, value_enum, default_value_t = GateFormat::Text)]
749 pub format: GateFormat,
750
751 #[arg(long)]
753 pub fail_fast: bool,
754}
755
756#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
757#[serde(rename_all = "kebab-case")]
758pub enum GateFormat {
759 #[default]
761 Text,
762 Json,
764}
765
766#[derive(Args, Debug, Clone)]
767pub struct CockpitArgs {
768 #[arg(long, default_value = "main")]
770 pub base: String,
771
772 #[arg(long, default_value = "HEAD")]
774 pub head: String,
775
776 #[arg(long, value_enum, default_value_t = CockpitFormat::Json)]
778 pub format: CockpitFormat,
779
780 #[arg(long, value_name = "PATH")]
782 pub output: Option<std::path::PathBuf>,
783
784 #[arg(long, value_name = "DIR")]
786 pub artifacts_dir: Option<std::path::PathBuf>,
787
788 #[arg(long, value_name = "PATH")]
793 pub baseline: Option<std::path::PathBuf>,
794
795 #[arg(long, value_enum, default_value_t = DiffRangeMode::TwoDot)]
797 pub diff_range: DiffRangeMode,
798
799 #[arg(long)]
806 pub sensor_mode: bool,
807}
808
809#[derive(Args, Debug, Clone)]
810pub struct BaselineArgs {
811 #[arg(default_value = ".")]
813 pub path: PathBuf,
814
815 #[arg(long, default_value = ".tokmd/baseline.json")]
817 pub output: PathBuf,
818
819 #[arg(long)]
821 pub determinism: bool,
822
823 #[arg(long, short)]
825 pub force: bool,
826}
827
828#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
829#[serde(rename_all = "kebab-case")]
830pub enum CockpitFormat {
831 #[default]
833 Json,
834 Md,
836 Sections,
838}
839
840#[derive(Args, Debug, Clone)]
841pub struct HandoffArgs {
842 #[arg(value_name = "PATH")]
844 pub paths: Option<Vec<PathBuf>>,
845
846 #[arg(long, default_value = ".handoff")]
848 pub out_dir: PathBuf,
849
850 #[arg(long, default_value = "128k")]
852 pub budget: String,
853
854 #[arg(long, value_enum, default_value_t = ContextStrategy::Greedy)]
856 pub strategy: ContextStrategy,
857
858 #[arg(long, value_enum, default_value_t = ValueMetric::Hotspot)]
860 pub rank_by: ValueMetric,
861
862 #[arg(long, value_enum, default_value_t = HandoffPreset::Risk)]
864 pub preset: HandoffPreset,
865
866 #[arg(long, value_delimiter = ',')]
868 pub module_roots: Option<Vec<String>>,
869
870 #[arg(long)]
872 pub module_depth: Option<usize>,
873
874 #[arg(long)]
876 pub force: bool,
877
878 #[arg(long)]
880 pub compress: bool,
881
882 #[arg(long)]
884 pub no_smart_exclude: bool,
885
886 #[arg(long = "no-git")]
888 pub no_git: bool,
889
890 #[arg(long, default_value = "1000")]
892 pub max_commits: usize,
893
894 #[arg(long, default_value = "100")]
896 pub max_commit_files: usize,
897
898 #[arg(long, default_value = "0.15")]
900 pub max_file_pct: f64,
901
902 #[arg(long)]
904 pub max_file_tokens: Option<usize>,
905}
906
907#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
908#[serde(rename_all = "kebab-case")]
909pub enum HandoffPreset {
910 Minimal,
912 Standard,
914 #[default]
916 Risk,
917 Deep,
919}
920
921#[derive(Args, Debug, Clone, Serialize, Deserialize)]
922pub struct SensorArgs {
923 #[arg(long, default_value = "main")]
925 pub base: String,
926
927 #[arg(long, default_value = "HEAD")]
929 pub head: String,
930
931 #[arg(
933 long,
934 value_name = "PATH",
935 default_value = "artifacts/tokmd/report.json"
936 )]
937 pub output: std::path::PathBuf,
938
939 #[arg(long, value_enum, default_value_t = SensorFormat::Json)]
941 pub format: SensorFormat,
942}
943
944#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
945#[serde(rename_all = "kebab-case")]
946pub enum SensorFormat {
947 #[default]
949 Json,
950 Md,
952}
953
954#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
955#[serde(rename_all = "kebab-case")]
956pub enum NearDupScope {
957 #[default]
959 Module,
960 Lang,
962 Global,
964}
965
966#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
967#[serde(rename_all = "kebab-case")]
968pub enum DiffRangeMode {
969 #[default]
971 TwoDot,
972 ThreeDot,
974}
975
976pub use tokmd_settings::{
981 AnalyzeConfig, BadgeConfig, ContextConfig, ExportConfig, GateConfig, GateRule, ModuleConfig,
982 RatchetRuleConfig, ScanConfig, TomlConfig, TomlResult, ViewProfile,
983};
984
985impl From<&GlobalArgs> for tokmd_settings::ScanOptions {
990 fn from(g: &GlobalArgs) -> Self {
991 Self {
992 excluded: g.excluded.clone(),
993 config: g.config,
994 hidden: g.hidden,
995 no_ignore: g.no_ignore,
996 no_ignore_parent: g.no_ignore_parent,
997 no_ignore_dot: g.no_ignore_dot,
998 no_ignore_vcs: g.no_ignore_vcs,
999 treat_doc_strings_as_comments: g.treat_doc_strings_as_comments,
1000 }
1001 }
1002}
1003
1004impl From<GlobalArgs> for tokmd_settings::ScanOptions {
1005 fn from(g: GlobalArgs) -> Self {
1006 Self::from(&g)
1007 }
1008}