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}