Skip to main content

apr_cli/
extended_commands.rs

1
2/// Extended CLI commands (analysis, profiling, QA, benchmarks, and advanced tools).
3///
4/// Flattened into `Commands` via `#[command(flatten)]` so all subcommands remain
5/// top-level from the user's perspective (e.g., `apr chat`, `apr profile`).
6#[derive(Subcommand, Debug)]
7pub enum ExtendedCommands {
8    /// Interactive chat with language model
9    Chat {
10        /// Path to .apr model file
11        #[arg(value_name = "FILE")]
12        file: PathBuf,
13        /// Sampling temperature (0 = greedy, higher = more random)
14        #[arg(long, default_value = "0.7")]
15        temperature: f32,
16        /// Nucleus sampling threshold
17        #[arg(long, default_value = "0.9")]
18        top_p: f32,
19        /// Maximum tokens to generate per response
20        #[arg(long, default_value = "512")]
21        max_tokens: usize,
22        /// System prompt to set model behavior
23        #[arg(long)]
24        system: Option<String>,
25        /// Show inspection info (top-k probs, tokens/sec)
26        #[arg(long)]
27        inspect: bool,
28        /// Disable GPU acceleration (use CPU)
29        #[arg(long)]
30        no_gpu: bool,
31        /// Force GPU acceleration (requires CUDA)
32        #[arg(long)]
33        gpu: bool,
34        /// Enable inference tracing (APR-TRACE-001)
35        #[arg(long)]
36        trace: bool,
37        /// Trace specific steps only (comma-separated)
38        #[arg(long, value_delimiter = ',')]
39        trace_steps: Option<Vec<String>>,
40        /// Verbose tracing
41        #[arg(long)]
42        trace_verbose: bool,
43        /// Save trace output to JSON file
44        #[arg(long, value_name = "FILE")]
45        trace_output: Option<PathBuf>,
46        /// Trace detail level (none, basic, layer, payload)
47        #[arg(long, value_name = "LEVEL", default_value = "basic")]
48        trace_level: String,
49        /// Enable inline Roofline profiling (PMAT-SHOWCASE-METHODOLOGY-001)
50        #[arg(long)]
51        profile: bool,
52    },
53    /// Benchmark throughput (spec H12: >= 10 tok/s)
54    Bench {
55        /// Path to model file
56        #[arg(value_name = "FILE")]
57        file: PathBuf,
58        /// Number of warmup iterations
59        #[arg(long, default_value = "3")]
60        warmup: usize,
61        /// Number of measurement iterations
62        #[arg(long, default_value = "5")]
63        iterations: usize,
64        /// Max tokens to generate per iteration
65        #[arg(long, default_value = "32")]
66        max_tokens: usize,
67        /// Test prompt
68        #[arg(long)]
69        prompt: Option<String>,
70        /// Use realizar for fast inference (vs aprender baseline)
71        #[arg(long)]
72        fast: bool,
73        /// Benchmark specific brick
74        #[arg(long)]
75        brick: Option<String>,
76    },
77    /// Evaluate model perplexity (spec H13: PPL <= 20) or classification metrics
78    Eval {
79        /// Path to model file or checkpoint directory
80        #[arg(value_name = "FILE")]
81        file: PathBuf,
82        /// Dataset: wikitext-2, lambada, or custom
83        #[arg(long, default_value = "wikitext-2")]
84        dataset: String,
85        /// Custom text (when dataset=custom)
86        #[arg(long)]
87        text: Option<String>,
88        /// Maximum tokens to evaluate
89        #[arg(long, default_value = "512")]
90        max_tokens: usize,
91        /// Perplexity threshold for pass/fail
92        #[arg(long, default_value = "20.0")]
93        threshold: f32,
94        /// Task type: omit for perplexity, "classify" for classification eval
95        #[arg(long)]
96        task: Option<String>,
97        /// Test data file (JSONL) for classification evaluation
98        #[arg(long, value_name = "FILE")]
99        data: Option<PathBuf>,
100        /// Model size hint: "0.5B", "tiny" (for classification eval)
101        #[arg(long)]
102        model_size: Option<String>,
103        /// Number of output classes (default: 5)
104        #[arg(long, default_value = "5")]
105        num_classes: usize,
106        /// Generate HuggingFace model card (README.md) in checkpoint dir
107        #[arg(long)]
108        generate_card: bool,
109        /// Device for inference: "cpu" (default) or "cuda" (GPU-accelerated, ALB-089)
110        #[arg(long, default_value = "cpu")]
111        device: String,
112        /// Number of samples per problem for pass@k (ALB-088, default: 1)
113        #[arg(long, default_value = "1")]
114        samples: usize,
115        /// Sampling temperature (0.0 = greedy, 0.8 = standard for pass@k>1)
116        #[arg(long, default_value = "0.0")]
117        temperature: f32,
118    },
119    /// Deep profiling with Roofline analysis
120    Profile {
121        /// Path to model file
122        #[arg(value_name = "FILE")]
123        file: PathBuf,
124        /// Layer-by-layer granular analysis
125        #[arg(long)]
126        granular: bool,
127        /// Output format (human, json, flamegraph)
128        #[arg(long, default_value = "human")]
129        format: String,
130        /// Focus on specific operation
131        #[arg(long)]
132        focus: Option<String>,
133        /// Detect naive implementations
134        #[arg(long)]
135        detect_naive: bool,
136        /// GFLOPS threshold for naive detection
137        #[arg(long, default_value = "10.0")]
138        threshold: f64,
139        /// Compare against HuggingFace baseline
140        #[arg(long)]
141        compare_hf: Option<String>,
142        /// Measure energy consumption (requires RAPL)
143        #[arg(long)]
144        energy: bool,
145        /// Compute performance grade (vs Ollama baseline)
146        #[arg(long)]
147        perf_grade: bool,
148        /// Show call graph
149        #[arg(long)]
150        callgraph: bool,
151        /// Exit non-zero if naive implementation detected
152        #[arg(long)]
153        fail_on_naive: bool,
154        /// Output file path for flamegraph SVG (GH-174, PMAT-182)
155        #[arg(long, short = 'o')]
156        output: Option<PathBuf>,
157
158        // PMAT-192: CI Assertion Mode (GH-180)
159        /// Enable CI mode with assertion checks (exits 1 on failure)
160        #[arg(long)]
161        ci: bool,
162        /// Minimum throughput in tok/s (CI assertion, exits 1 if below)
163        #[arg(long)]
164        assert_throughput: Option<f64>,
165        /// Maximum p99 latency in ms (CI assertion, exits 1 if above)
166        #[arg(long)]
167        assert_p99: Option<f64>,
168        /// Maximum p50 latency in ms (CI assertion, exits 1 if above)
169        #[arg(long)]
170        assert_p50: Option<f64>,
171        /// Warmup passes before measurement (default: 3)
172        #[arg(long, default_value = "3")]
173        warmup: usize,
174        /// Measurement passes (default: 10)
175        #[arg(long, default_value = "10")]
176        measure: usize,
177        /// Number of tokens to generate per measurement pass (default: 32)
178        #[arg(long, default_value = "32")]
179        tokens: usize,
180        /// Compare against Ollama baseline (runs ollama for comparison)
181        #[arg(long)]
182        ollama: bool,
183        /// Disable GPU (force CPU-only profiling)
184        #[arg(long)]
185        no_gpu: bool,
186        /// Compare against another model format (F-PROFILE-011)
187        #[arg(long, value_name = "FILE")]
188        compare: Option<PathBuf>,
189    },
190    /// Falsifiable QA checklist for model releases
191    Qa {
192        /// Path to model file
193        #[arg(value_name = "FILE")]
194        file: PathBuf,
195        /// Minimum throughput threshold in tok/s
196        #[arg(long, value_name = "TPS")]
197        assert_tps: Option<f64>,
198        /// Minimum speedup vs Ollama
199        #[arg(long, value_name = "SPEEDUP")]
200        assert_speedup: Option<f64>,
201        /// Minimum GPU vs CPU speedup (F-PERF-042)
202        #[arg(long, value_name = "SPEEDUP")]
203        assert_gpu_speedup: Option<f64>,
204        /// Skip golden output test
205        #[arg(long)]
206        skip_golden: bool,
207        /// Skip throughput benchmark
208        #[arg(long)]
209        skip_throughput: bool,
210        /// Skip Ollama parity comparison
211        #[arg(long)]
212        skip_ollama: bool,
213        /// Skip GPU vs CPU speedup test (F-PERF-042)
214        #[arg(long)]
215        skip_gpu_speedup: bool,
216        /// Skip tensor contract validation (PMAT-235)
217        #[arg(long)]
218        skip_contract: bool,
219        /// Skip cross-format parity test (F-QUAL-032)
220        #[arg(long)]
221        skip_format_parity: bool,
222        /// Skip PTX parity validation (GH-219)
223        #[arg(long)]
224        skip_ptx_parity: bool,
225        /// SafeTensors model path for cross-format parity test (F-QUAL-032)
226        #[arg(long, value_name = "PATH")]
227        safetensors_path: Option<PathBuf>,
228        /// Number of benchmark iterations
229        #[arg(long, default_value = "10")]
230        iterations: usize,
231        /// Number of warmup iterations
232        #[arg(long, default_value = "3")]
233        warmup: usize,
234        /// Maximum tokens to generate
235        #[arg(long, default_value = "32")]
236        max_tokens: usize,
237        /// Output as JSON (for CI integration)
238        #[arg(long)]
239        json: bool,
240        /// Verbose output
241        #[arg(short, long)]
242        verbose: bool,
243        /// Minimum number of gates that must execute (fail if fewer)
244        #[arg(long, value_name = "N")]
245        min_executed: Option<usize>,
246        /// Previous QA report for regression detection
247        #[arg(long, value_name = "FILE")]
248        previous_report: Option<PathBuf>,
249        /// Maximum allowed performance regression ratio (default: 0.10 = 10%)
250        #[arg(long, value_name = "RATIO")]
251        regression_threshold: Option<f64>,
252        /// Skip GPU state isolation test
253        #[arg(long)]
254        skip_gpu_state: bool,
255        /// Skip metadata plausibility validation (Bug 210, GH-222)
256        #[arg(long)]
257        skip_metadata: bool,
258        /// Skip GPU capability match gate (GH-280)
259        #[arg(long)]
260        skip_capability: bool,
261        /// Assert classifier head presence and shape (F-CLASS-004)
262        #[arg(long)]
263        assert_classifier_head: bool,
264    },
265    /// GPU/CPU parity check (PMAT-232: genchi genbutsu — see where GPU diverges)
266    Parity {
267        /// Path to GGUF model file
268        #[arg(value_name = "FILE")]
269        file: PathBuf,
270        /// Prompt text (default: "What is 2+2?")
271        #[arg(short, long, default_value = "What is 2+2?")]
272        prompt: String,
273        /// Assert parity (exit non-zero on divergence)
274        #[arg(long)]
275        assert: bool,
276    },
277    /// Model-to-PTX source mapping (Mieruka: make GPU kernel dispatch visible)
278    #[command(name = "ptx-map")]
279    PtxMap {
280        /// Path to GGUF model file
281        #[arg(value_name = "FILE")]
282        file: PathBuf,
283        /// Filter to specific kernel (e.g., --kernel Q4KGemv)
284        #[arg(long)]
285        kernel: Option<String>,
286        /// Reverse lookup: kernel name -> which layers/steps use it
287        #[arg(long)]
288        reverse: Option<String>,
289        /// Output as JSON
290        #[arg(long)]
291        json: bool,
292        /// Full PTX snippets and detailed analysis
293        #[arg(short, long)]
294        verbose: bool,
295        /// Show batched prefill kernel variants instead of decode
296        #[arg(long)]
297        prefill: bool,
298    },
299    /// PTX analysis and bug detection (trueno-explain: register pressure, roofline, 15+ bug detectors)
300    #[command(name = "ptx")]
301    Ptx {
302        /// Path to a PTX source file
303        #[arg(value_name = "FILE")]
304        file: Option<PathBuf>,
305        /// Analyze a named kernel from trueno-gpu
306        #[arg(long, short)]
307        kernel: Option<String>,
308        /// Strict mode (no performance whitelist)
309        #[arg(long)]
310        strict: bool,
311        /// Show only bug analysis (skip register/memory/roofline)
312        #[arg(long)]
313        bugs: bool,
314        /// Output as JSON
315        #[arg(long)]
316        json: bool,
317        /// Verbose output (include PTX source listing)
318        #[arg(short, long)]
319        verbose: bool,
320    },
321    /// ML tuning: LoRA/QLoRA configuration, memory planning, and HPO (GH-176, SPEC-TUNE-2026-001)
322    #[cfg(feature = "training")]
323    Tune {
324        /// Path to model file (optional if using --model)
325        #[arg(value_name = "FILE")]
326        file: Option<PathBuf>,
327        /// Tuning method: auto, full, lora, qlora
328        #[arg(long, short = 'm', default_value = "auto")]
329        method: String,
330        /// LoRA rank (default: auto-selected)
331        #[arg(long, short = 'r')]
332        rank: Option<u32>,
333        /// Available VRAM in GB
334        #[arg(long, default_value = "16.0")]
335        vram: f64,
336        /// Only plan configuration, don't train
337        #[arg(long)]
338        plan: bool,
339        /// Model size for planning (e.g., "7B", "1.5B")
340        #[arg(long, value_name = "SIZE")]
341        model: Option<String>,
342        /// Freeze base model weights
343        #[arg(long)]
344        freeze_base: bool,
345        /// Training data file (JSONL format)
346        #[arg(long, value_name = "FILE")]
347        train_data: Option<PathBuf>,
348        /// Output as JSON (for CI integration)
349        #[arg(long)]
350        json: bool,
351        /// Task type for HPO: classify (SPEC-TUNE-2026-001)
352        #[arg(long)]
353        task: Option<String>,
354        /// Number of HPO trials (default: 10)
355        #[arg(long, default_value = "10")]
356        budget: usize,
357        /// HPO search strategy: tpe, grid, random
358        #[arg(long, default_value = "tpe")]
359        strategy: String,
360        /// HPO scheduler: asha, median, none
361        #[arg(long, default_value = "asha")]
362        scheduler: String,
363        /// Scout mode: 1 epoch per trial for fast exploration
364        #[arg(long)]
365        scout: bool,
366        /// Training data file for HPO (JSONL format)
367        #[arg(long, value_name = "FILE")]
368        data: Option<PathBuf>,
369        /// Number of output classes for classification
370        #[arg(long, default_value = "5")]
371        num_classes: usize,
372        /// Model size hint for HPO (e.g., "0.5B", "1.5B")
373        #[arg(long)]
374        model_size: Option<String>,
375        /// Warm-start from scout phase results directory
376        #[arg(long, value_name = "DIR")]
377        from_scout: Option<PathBuf>,
378        /// Maximum epochs per trial (full mode, default: 20)
379        #[arg(long, default_value = "20")]
380        max_epochs: usize,
381        /// Maximum wall-clock time (e.g., "8h", "30m")
382        #[arg(long)]
383        time_limit: Option<String>,
384    },
385    /// Attach live TUI to a running training session
386    #[cfg(feature = "training")]
387    Monitor {
388        /// Experiment output directory (same as finetune -o)
389        #[arg(value_name = "DIR")]
390        dir: Option<PathBuf>,
391        /// Refresh interval in milliseconds
392        #[arg(long, default_value = "500")]
393        refresh_ms: u64,
394        /// Compact display mode
395        #[arg(long)]
396        compact: bool,
397        /// Output JSON lines instead of TUI (for LLM agents and CI)
398        #[arg(long)]
399        json: bool,
400        /// Output format: tui (default), json, text
401        #[arg(long, default_value = "tui")]
402        format: String,
403    },
404    /// List, show, and compare training experiment runs
405    #[cfg(feature = "training")]
406    Runs {
407        #[command(subcommand)]
408        command: RunsCommands,
409    },
410    /// Interactive experiment browser (TUI with loss curves)
411    #[cfg(feature = "training")]
412    Experiment {
413        #[command(subcommand)]
414        command: ExperimentCommands,
415    },
416    /// ComputeBrick pipeline monitor (cbtop)
417    Cbtop {
418        /// Model name (e.g., qwen2.5-coder-1.5b)
419        #[arg(long)]
420        model: Option<String>,
421        /// Attach to running realizar process
422        #[arg(long)]
423        attach: Option<String>,
424        /// Path to GGUF model file for real profiling
425        #[arg(long, value_name = "MODEL")]
426        model_path: Option<PathBuf>,
427        /// Run in headless mode (no TUI, for CI/automation)
428        #[arg(long)]
429        headless: bool,
430        /// Output JSON format (requires --headless)
431        #[arg(long)]
432        json: bool,
433        /// Output file path (requires --headless)
434        #[arg(long, value_name = "FILE")]
435        output: Option<PathBuf>,
436        /// CI mode: exit with code 1 if thresholds not met
437        #[arg(long)]
438        ci: bool,
439        /// Minimum throughput threshold in tok/s (for --ci)
440        #[arg(long, value_name = "TOK_S")]
441        throughput: Option<f64>,
442        /// Minimum brick score threshold 0-100 (for --ci)
443        #[arg(long, value_name = "SCORE")]
444        brick_score: Option<u32>,
445        /// Number of warmup iterations before measurement
446        #[arg(long, default_value = "10")]
447        warmup: usize,
448        /// Number of measurement iterations
449        #[arg(long, default_value = "100")]
450        iterations: usize,
451        /// PAR-100: Enable speculative decoding benchmark
452        #[arg(long)]
453        speculative: bool,
454        /// PAR-100: Number of tokens to draft speculatively (default: 4)
455        #[arg(long, default_value = "4")]
456        speculation_k: usize,
457        /// PAR-099: Path to draft model for speculative decoding
458        #[arg(long, value_name = "DRAFT_MODEL")]
459        draft_model: Option<PathBuf>,
460        /// PAR-102: Number of concurrent requests
461        #[arg(long, default_value = "1")]
462        concurrent: usize,
463        /// Use simulated data (for CI testing only)
464        #[arg(long)]
465        simulated: bool,
466    },
467    /// Export for probar visual testing
468    Probar {
469        /// Path to .apr model file
470        #[arg(value_name = "FILE")]
471        file: PathBuf,
472        /// Output directory for test artifacts
473        #[arg(short, long, default_value = "./probar-export")]
474        output: PathBuf,
475        /// Export format: json, png, or both
476        #[arg(long, default_value = "both")]
477        format: String,
478        /// Golden reference directory for comparison
479        #[arg(long)]
480        golden: Option<PathBuf>,
481        /// Filter layers by name pattern
482        #[arg(long)]
483        layer: Option<String>,
484    },
485    /// Compare APR model against HuggingFace source
486    #[command(name = "compare-hf")]
487    CompareHf {
488        /// Path to .apr model file
489        #[arg(value_name = "FILE")]
490        file: PathBuf,
491        /// HuggingFace repo ID (e.g., openai/whisper-tiny)
492        #[arg(long)]
493        hf: String,
494        /// Filter tensors by name pattern
495        #[arg(long)]
496        tensor: Option<String>,
497        /// Comparison threshold (default: 1e-5)
498        #[arg(long, default_value = "1e-5")]
499        threshold: f64,
500        /// Output as JSON
501        #[arg(long)]
502        json: bool,
503    },
504    /// Format-aware binary forensics (10X better than xxd)
505    Hex {
506        /// Path to model file (APR, GGUF, or SafeTensors)
507        #[arg(value_name = "FILE")]
508        file: PathBuf,
509        /// Filter tensors by name pattern
510        #[arg(long)]
511        tensor: Option<String>,
512        /// Limit bytes/values to display
513        #[arg(long, default_value = "64")]
514        limit: usize,
515        /// Show tensor statistics
516        #[arg(long)]
517        stats: bool,
518        /// List tensor names only
519        #[arg(long)]
520        list: bool,
521        /// Output as JSON
522        #[arg(long)]
523        json: bool,
524        /// Annotated file header (magic, version, tensor count, metadata)
525        #[arg(long)]
526        header: bool,
527        /// Q4K/Q6K/Q8_0 super-block structure with field annotations
528        #[arg(long)]
529        blocks: bool,
530        /// Value histogram + entropy + kurtosis analysis
531        #[arg(long)]
532        distribution: bool,
533        /// Layout contract verification overlay per tensor
534        #[arg(long)]
535        contract: bool,
536        /// Per-region byte entropy analysis
537        #[arg(long)]
538        entropy: bool,
539        /// Raw bytes (like xxd but format-aware, with ASCII column)
540        #[arg(long)]
541        raw: bool,
542        /// Start at byte offset (supports 0x prefix for hex)
543        #[arg(long, default_value = "0")]
544        offset: String,
545        /// Bytes per row for raw output (default: 16)
546        #[arg(long, default_value = "16")]
547        width: usize,
548        /// Slice range for partial tensor reads (e.g., 0:3 for first 3 elements)
549        #[arg(long)]
550        slice: Option<String>,
551    },
552    /// Model architecture tree view
553    Tree {
554        /// Path to .apr model file
555        #[arg(value_name = "FILE")]
556        file: PathBuf,
557        /// Filter by component pattern
558        #[arg(long)]
559        filter: Option<String>,
560        /// Output format: ascii, dot, mermaid, json
561        #[arg(long, default_value = "ascii")]
562        format: String,
563        /// Show tensor sizes
564        #[arg(long)]
565        sizes: bool,
566        /// Maximum tree depth
567        #[arg(long)]
568        depth: Option<usize>,
569    },
570    /// Data flow visualization
571    Flow {
572        /// Path to .apr model file
573        #[arg(value_name = "FILE")]
574        file: PathBuf,
575        /// Filter by layer pattern
576        #[arg(long)]
577        layer: Option<String>,
578        /// Component to visualize: full, encoder, decoder, etc.
579        #[arg(long, default_value = "full")]
580        component: String,
581        /// Verbose output with statistics
582        #[arg(short, long)]
583        verbose: bool,
584        /// Output as JSON
585        #[arg(long)]
586        json: bool,
587    },
588    /// Cross-subcommand smoke test (does every tool handle this model?)
589    Qualify {
590        /// Path to model file (APR, GGUF, or SafeTensors)
591        #[arg(value_name = "FILE")]
592        file: PathBuf,
593        /// Testing tier: smoke (Phase 1), standard (+contracts), full (+playbook)
594        #[arg(long, default_value = "smoke")]
595        tier: String,
596        /// Timeout per gate in seconds
597        #[arg(long, default_value = "120")]
598        timeout: u64,
599        /// Output as JSON
600        #[arg(long)]
601        json: bool,
602        /// Show subcommand output (disable stdout suppression)
603        #[arg(short, long)]
604        verbose: bool,
605        /// Skip specific gates (comma-separated)
606        #[arg(long, value_delimiter = ',')]
607        skip: Option<Vec<String>>,
608    },
609    /// Training pipeline (plan/apply) — forjar-style pre-flight validation
610    #[cfg(feature = "training")]
611    Train {
612        #[command(subcommand)]
613        command: TrainCommands,
614    },
615    /// Tokenizer training pipeline (plan/apply) — BPE vocabulary learning
616    Tokenize {
617        #[command(subcommand)]
618        command: TokenizeCommands,
619    },
620    /// Data quality pipeline (audit, split, balance) — powered by alimentar
621    Data {
622        #[command(subcommand)]
623        command: DataCommands,
624    },
625    /// Pipeline orchestration (plan/apply/status) — wraps forjar DAG engine
626    Pipeline {
627        #[command(subcommand)]
628        command: PipelineCommands,
629    },
630    /// Automated Five Whys diagnosis on a training checkpoint
631    Diagnose {
632        /// Path to checkpoint directory
633        #[arg(value_name = "CHECKPOINT_DIR")]
634        checkpoint_dir: PathBuf,
635        /// Test data file (JSONL) for evaluation
636        #[arg(long, value_name = "FILE")]
637        data: Option<PathBuf>,
638        /// Model size hint: "0.5B", "tiny"
639        #[arg(long)]
640        model_size: Option<String>,
641        /// Number of output classes (default: 5)
642        #[arg(long, default_value = "5")]
643        num_classes: usize,
644    },
645    /// Publishing, conversion, and analysis tools
646    #[command(flatten)]
647    Tools(ToolCommands),
648}
649
650#[cfg(feature = "training")]
651/// Subcommands for `apr runs` — experiment run management (ALB-050/051)
652#[derive(Subcommand, Debug)]
653pub enum RunsCommands {
654    /// List all training experiment runs (with inline loss sparklines)
655    Ls {
656        /// Directory to scan for experiments (default: current dir)
657        #[arg(long, value_name = "DIR")]
658        dir: Option<PathBuf>,
659        /// Read from global experiment registry (~/.entrenar/experiments.db)
660        #[arg(long)]
661        global: bool,
662        /// Filter by status: running, completed, failed, all
663        #[arg(long, default_value = "all")]
664        status: String,
665        /// Output as JSON
666        #[arg(long)]
667        json: bool,
668        /// Maximum number of runs to show
669        #[arg(long, default_value = "50")]
670        limit: usize,
671    },
672    /// Show detailed metrics for a specific run (with braille loss curve)
673    Show {
674        /// Run ID
675        #[arg(value_name = "RUN_ID")]
676        run_id: String,
677        /// Directory containing experiment DB
678        #[arg(long, value_name = "DIR")]
679        dir: Option<PathBuf>,
680        /// Read from global registry
681        #[arg(long)]
682        global: bool,
683        /// Output as JSON
684        #[arg(long)]
685        json: bool,
686    },
687    /// Compare two runs side-by-side (loss curves, config diff, metrics)
688    Diff {
689        /// First run ID
690        #[arg(value_name = "RUN_A")]
691        run_a: String,
692        /// Second run ID
693        #[arg(value_name = "RUN_B")]
694        run_b: String,
695        /// Directory containing experiment DB
696        #[arg(long, value_name = "DIR")]
697        dir: Option<PathBuf>,
698        /// Read from global registry
699        #[arg(long)]
700        global: bool,
701        /// Output as JSON
702        #[arg(long)]
703        json: bool,
704    },
705}
706
707#[cfg(feature = "training")]
708/// Subcommands for `apr experiment` — interactive experiment browser (ALB-024)
709#[derive(Subcommand, Debug)]
710pub enum ExperimentCommands {
711    /// Browse experiment history with interactive TUI (loss curves, params)
712    View {
713        /// Path to experiment database file
714        #[arg(long, value_name = "FILE")]
715        db: Option<PathBuf>,
716        /// Read from global experiment registry (~/.entrenar/experiments.db)
717        #[arg(long)]
718        global: bool,
719        /// Output as JSON (non-interactive)
720        #[arg(long)]
721        json: bool,
722    },
723}