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