apr_cli/commands_enum.rs
1
2/// Compute backends `--backend` accepts on `apr run` / `apr chat`.
3///
4/// The flag used to be a free-form `String`: `--backend banana` printed
5/// `Backend override: banana` and then quietly ran the default backend. That is
6/// the exact failure the `--backend cuda` guard in `dispatch.rs` exists to
7/// prevent — a run whose throughput number was taken through a backend the
8/// caller did not ask for — so a typo must be rejected by the parser, not
9/// echoed back.
10pub const BACKEND_VALUES: [&str; 3] = ["cuda", "cpu", "wgpu"];
11
12/// The ONE `--backend` declaration, flattened into every command that offers the
13/// inference backend override (`apr run`, `apr chat`, `apr serve run`).
14///
15/// #2583: the `value_parser` above was applied by hand at each site, so
16/// `apr serve run` — added later — declared a bare
17/// `#[arg(long, value_name = "BACKEND")]` and accepted ANY string:
18/// `apr serve run --backend nonsense` parsed, `ServerConfig.backend` carried
19/// `"nonsense"`, and the server came up on whatever backend it would have picked
20/// anyway. Copying the `value_parser` to that third site would have left the same
21/// hand-copy hazard for the fourth (cf. #2585, where two `apr` bin targets were
22/// duplicated by hand and had already diverged), so the declaration itself is
23/// shared: a new consumer writes `#[command(flatten)] backend: BackendArg` and
24/// cannot express the unvalidated form.
25#[derive(clap::Args, Debug, Clone, Default, PartialEq, Eq)]
26pub struct BackendArg {
27 /// Compute backend override (cuda, cpu, wgpu)
28 #[arg(long, value_name = "BACKEND", value_parser = BACKEND_VALUES)]
29 pub backend: Option<String>,
30}
31
32/// Backends `apr finetune --gpu-backend` accepts.
33///
34/// #2583 follow-up: this site is the SAME silent-wrong-backend defect as
35/// `apr serve run --backend`, one command over. It was declared
36/// `#[arg(long, default_value = "auto")] gpu_backend: String` with no
37/// `value_parser`, and `gpu_backend_notice()` (commands/finetune.rs:265)
38/// dispatches on it with a catch-all `_ =>` arm that means "auto". So
39/// `--gpu-backend cudaa` parsed, printed `auto → …`, and trained on whatever
40/// backend auto-selection picked — the typo was indistinguishable from not
41/// passing the flag at all.
42///
43/// It is deliberately NOT `BackendArg`: the domains genuinely differ. `auto` is
44/// a valid selection here and is absent from `BACKEND_VALUES`; `cpu` is not a
45/// selectable training backend and is present there. Sharing the *declaration*
46/// across two different value sets would have to widen both, re-admitting
47/// `--backend auto` on `apr run` (unvalidated again, by a different route) and
48/// `--gpu-backend cpu` on `apr finetune` (silently the `_ =>` auto arm again).
49/// What is shared instead is the *invariant*, enforced by
50/// `test_every_backend_arg_advertises_its_values_2583` over the built clap tree.
51pub const FINETUNE_GPU_BACKEND_VALUES: [&str; 3] = ["auto", "cuda", "wgpu"];
52
53/// Provider backends `apr distill --backend` accepts.
54///
55/// Unlike the two above this one was NOT silently accepted: `distill::run()`
56/// (commands/distill.rs:464-503) matches on it before any I/O and returns
57/// `ValidationFailed` naming both valid values, covered by
58/// `distill_run_unknown_backend_errors`. Wiring the same list into clap is
59/// defence in depth and makes `--help` advertise the set, but it closes a
60/// help-text gap, not a silent-wrong-backend defect. The runtime check stays.
61pub const DISTILL_BACKEND_VALUES: [&str; 2] = ["fixture", "cuda"];
62
63/// Trace detail levels `--trace-level` accepts.
64///
65/// Each value is dispatched on by string equality in `run_entry.rs`; an
66/// unrecognised value silently selected "no extra trace output at all" while
67/// printing `Trace level: <typo>` as though it had taken effect.
68pub const TRACE_LEVEL_VALUES: [&str; 5] = ["none", "basic", "layer", "payload", "chrome"];
69
70/// Output formats `apr run -f/--format` accepts.
71pub const RUN_FORMAT_VALUES: [&str; 4] = ["text", "json", "srt", "vtt"];
72
73/// Output format for `apr code` non-interactive mode (PMAT-CODE-OUTPUT-FORMAT-001).
74/// Mirrors Claude Code's `claude -p --output-format <fmt>` parity row.
75#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum, Default)]
76pub enum CodeOutputFormat {
77 /// Plain assistant text on stdout (default; existing behavior).
78 #[default]
79 Text,
80 /// Structured JSON envelope: `{type:"result", subtype:"success", result, session_id, duration_ms}`.
81 Json,
82}
83
84/// Input format for `apr code` non-interactive mode (PMAT-CODE-INPUT-FORMAT-001).
85/// `--input-format json` reads `{"role":"user","content":"..."}` from stdin instead
86/// of treating stdin as raw prompt text.
87#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum, Default)]
88pub enum CodeInputFormat {
89 /// Raw prompt text from positional args or stdin (default; existing behavior).
90 #[default]
91 Text,
92 /// JSON message envelope on stdin: `{"role":"user","content":"..."}`.
93 Json,
94}
95
96#[derive(Subcommand, Debug)]
97pub enum Commands {
98 /// Run model directly (auto-download, cache, execute)
99 Run {
100 /// Model source: local path, hf://org/repo, or URL
101 #[arg(value_name = "SOURCE")]
102 source: String,
103 /// Text prompt (positional): `apr run model.gguf "What is 2+2?"`
104 #[arg(value_name = "PROMPT")]
105 positional_prompt: Option<String>,
106 /// Input file (audio, text, etc.)
107 #[arg(short, long)]
108 input: Option<PathBuf>,
109 /// Text prompt for generation (for LLM models)
110 #[arg(short, long)]
111 prompt: Option<String>,
112 /// Maximum tokens to generate (default: 32)
113 #[arg(short = 'n', long, default_value = "32")]
114 max_tokens: usize,
115 /// Enable streaming output
116 #[arg(long)]
117 stream: bool,
118 /// Language code (for ASR models)
119 #[arg(short, long)]
120 language: Option<String>,
121 /// Task (transcribe, translate)
122 #[arg(short, long)]
123 task: Option<String>,
124 /// Output format (text, json, srt, vtt)
125 #[arg(short = 'f', long, default_value = "text", value_parser = RUN_FORMAT_VALUES)]
126 format: String,
127 /// Disable GPU acceleration (force CPU-only inference)
128 #[arg(long, alias = "cpu", conflicts_with = "gpu")]
129 no_gpu: bool,
130 /// Force GPU acceleration
131 #[arg(long, conflicts_with = "no_gpu")]
132 gpu: bool,
133 /// Offline mode: block all network access (Sovereign AI compliance)
134 #[arg(long)]
135 offline: bool,
136 /// Benchmark mode: output performance metrics (tok/s, latency)
137 #[arg(long)]
138 benchmark: bool,
139 /// Enable inference tracing (APR-TRACE-001)
140 #[arg(long)]
141 trace: bool,
142 /// Trace specific steps only (comma-separated)
143 #[arg(long, value_delimiter = ',')]
144 trace_steps: Option<Vec<String>>,
145 /// Verbose tracing (show tensor values)
146 #[arg(long)]
147 trace_verbose: bool,
148 /// Save trace output to JSON file
149 #[arg(long, value_name = "FILE")]
150 trace_output: Option<PathBuf>,
151 /// Trace detail level (none, basic, layer, payload, chrome)
152 /// "chrome" outputs chrome://tracing JSON integrating layer trace + brick profile.
153 /// F-CLIPARITY-01 / PMAT-386 / paiml/aprender#574
154 #[arg(long, value_name = "LEVEL", default_value = "basic", value_parser = TRACE_LEVEL_VALUES)]
155 trace_level: String,
156 /// Shorthand for --trace --trace-level payload (tensor value inspection)
157 #[arg(long)]
158 trace_payload: bool,
159 /// Enable inline Roofline profiling (PMAT-SHOWCASE-METHODOLOGY-001)
160 #[arg(long)]
161 profile: bool,
162 /// Apply chat template for Instruct models (GAP-UX-001)
163 ///
164 /// Wraps prompt in ChatML format for Qwen2, LLaMA, Mistral Instruct models.
165 /// Format: <|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n
166 #[arg(long)]
167 chat: bool,
168 /// Sampling temperature (0.0 = greedy, default: 0.0)
169 #[arg(long, default_value = "0.0")]
170 temperature: f32,
171 /// Top-k sampling (default: 1 = greedy)
172 #[arg(long, default_value = "1")]
173 top_k: usize,
174 /// Top-p nucleus sampling (0.0 = disabled). When set with --top-k, applies top-k first then top-p.
175 /// F-CLIPARITY-01 / PMAT-381 / paiml/aprender#569
176 #[arg(long)]
177 top_p: Option<f32>,
178 /// RNG seed for deterministic sampling (default: 299792458, matching Candle)
179 /// F-CLIPARITY-01 / PMAT-382 / paiml/aprender#570
180 #[arg(long, default_value = "299792458")]
181 seed: u64,
182 /// Repetition penalty (1.0 = no penalty, >1.0 penalizes repeats)
183 /// F-CLIPARITY-01 / PMAT-383 / paiml/aprender#571
184 #[arg(long, default_value = "1.0")]
185 repeat_penalty: f32,
186 /// Context window for repetition penalty (number of recent tokens to check)
187 /// F-CLIPARITY-01 / PMAT-384 / paiml/aprender#571
188 #[arg(long, default_value = "64")]
189 repeat_last_n: usize,
190 /// Process prompt tokens one-by-one instead of batched prefill.
191 /// Useful for debugging prefill correctness (comparing per-token attention).
192 /// F-CLIPARITY-01 / PMAT-385 / paiml/aprender#572
193 #[arg(long)]
194 split_prompt: bool,
195 /// Batch mode: read prompts from JSONL, output results as JSONL.
196 /// Model loads once, processes all prompts sequentially.
197 /// Each input line: {"prompt": "...", "task_id": "..."}
198 /// Chat template is applied automatically.
199 #[arg(long, value_name = "FILE")]
200 batch_jsonl: Option<PathBuf>,
201 /// Show verbose output (model loading, backend info)
202 #[arg(short, long)]
203 verbose: bool,
204 // PMAT-488 / #2583: shared `--backend` declaration (see `BackendArg`).
205 #[command(flatten)]
206 backend: BackendArg,
207 },
208 /// Inference server (plan/run)
209 Serve {
210 #[command(subcommand)]
211 command: ServeCommands,
212 },
213 /// Inspect model metadata, vocab, and structure
214 Inspect {
215 /// Path to .apr model file
216 #[arg(value_name = "FILE")]
217 file: PathBuf,
218 /// Show vocabulary details
219 #[arg(long)]
220 vocab: bool,
221 /// Show filter/security details
222 #[arg(long)]
223 filters: bool,
224 /// Show weight statistics
225 #[arg(long)]
226 weights: bool,
227 /// Output as JSON
228 #[arg(long)]
229 json: bool,
230 /// Emit a 0-100 model quality score block.
231 ///
232 /// Per SPEC-SHIP-TWO-001 §84 P3-A (AC-SHIP2-007 quality
233 /// threshold ≥ 90). The score aggregates: physics checks
234 /// (no NaN/Inf, no all-zero tensors), structural
235 /// completeness (architecture / hidden_size / num_layers
236 /// metadata present), provenance (license + data_source +
237 /// data_license non-empty), HF identity (hf_architecture
238 /// stamped per PMAT-690 P0-K), and tokenizer presence
239 /// (has_vocab + embedded merges). A ship-blocking model
240 /// MUST score ≥ 90 by this rubric.
241 #[arg(long)]
242 quality: bool,
243 },
244 /// Simple debugging output ("drama" mode available), or a debug subcommand
245 ///
246 /// `apr debug model.apr` dumps the file. `apr debug embed-viz --model M`
247 /// projects the model's token-embedding table to 2-D — the producer
248 /// `apr embed-viz-lint` reads (aprender#2377 finding 3).
249 Debug {
250 /// Path to .apr model file (omit only when using a subcommand)
251 #[arg(value_name = "FILE")]
252 file: Option<PathBuf>,
253 /// Debug subcommand, e.g. `embed-viz`
254 #[command(subcommand)]
255 action: Option<DebugCommands>,
256 /// Theatrical "drama" mode output
257 #[arg(long)]
258 drama: bool,
259 /// Show hex dump
260 #[arg(long)]
261 hex: bool,
262 /// Extract ASCII strings
263 #[arg(long)]
264 strings: bool,
265 /// Limit output lines
266 #[arg(long, default_value = "256")]
267 limit: usize,
268 },
269 /// Validate model integrity and quality
270 Validate {
271 /// Path to .apr model file
272 #[arg(value_name = "FILE")]
273 file: PathBuf,
274 /// Show 100-point quality assessment
275 #[arg(long)]
276 quality: bool,
277 /// Strict validation (fail on warnings)
278 #[arg(long)]
279 strict: bool,
280 /// Minimum score to pass (0-100)
281 #[arg(long)]
282 min_score: Option<u8>,
283 },
284 /// Validate a publish manifest (FALSIFY-PM-001..006).
285 ///
286 /// Contract: `contracts/publish-manifest-v1.yaml`
287 /// Spec: SPEC-SHIP-TWO-001 §12.3 AC-EX-004
288 ValidateManifest {
289 /// Path to manifest YAML
290 #[arg(value_name = "MANIFEST")]
291 file: PathBuf,
292 /// Optional local .apr artifact to discharge FALSIFY-PM-002 (sha256 match)
293 #[arg(long, value_name = "APR_FILE")]
294 artifact: Option<PathBuf>,
295 /// Discharge FALSIFY-PM-003 via network: HTTP HEAD + streaming sha256.
296 /// Default is DEFERRED (offline-safe). Ignored when --offline is set.
297 /// Closes F-PUBLISH-EXTRA-001::dogfood_ex05 (no Python in ex-05).
298 #[arg(long)]
299 live: bool,
300 },
301 /// Compare two models
302 Diff {
303 /// First model file
304 #[arg(value_name = "FILE1")]
305 file1: PathBuf,
306 /// Second model file
307 #[arg(value_name = "FILE2")]
308 file2: PathBuf,
309 /// Show weight-level differences
310 #[arg(long)]
311 weights: bool,
312 /// Compare actual tensor values with statistical analysis
313 #[arg(long)]
314 values: bool,
315 /// Filter tensors by name pattern (for --values)
316 #[arg(long)]
317 filter: Option<String>,
318 /// Maximum number of tensors to compare (for --values)
319 #[arg(long, default_value = "10")]
320 limit: usize,
321 /// Account for transpose when comparing (GGUF col-major vs APR row-major)
322 #[arg(long)]
323 transpose_aware: bool,
324 /// Output as JSON
325 #[arg(long)]
326 json: bool,
327 /// CRUX-B-20: per-tensor quant roundtrip error report (RMSE / cosine / max_abs).
328 /// FILE1 is the reference (fp16/fp32/bf16); FILE2 is the quantized variant.
329 #[arg(long)]
330 quant_roundtrip: bool,
331 /// CRUX-B-20: cosine threshold for the quant-roundtrip exit-code gate.
332 /// Any tensor with cosine < threshold makes the command exit non-zero.
333 #[arg(long, default_value = "0.95",
334 value_parser = crate::commands::threshold_arg::parse_cosine_f32)]
335 threshold: f32,
336 /// CRUX-B-20: suppress the threshold exit-code gate (still emits the report).
337 #[arg(long)]
338 no_threshold: bool,
339 },
340 /// List tensor names and shapes
341 Tensors {
342 /// Path to .apr model file
343 #[arg(value_name = "FILE")]
344 file: PathBuf,
345 /// Show tensor statistics (mean, std, min, max)
346 #[arg(long)]
347 stats: bool,
348 /// Filter tensors by name pattern
349 #[arg(long)]
350 filter: Option<String>,
351 /// Limit number of tensors shown (0 = unlimited)
352 #[arg(long, default_value = "0")]
353 limit: usize,
354 /// Output as JSON
355 #[arg(long)]
356 json: bool,
357 },
358 /// Layer-by-layer trace analysis
359 Trace {
360 /// Path to .apr model file
361 #[arg(value_name = "FILE")]
362 file: PathBuf,
363 /// Filter layers by name pattern
364 #[arg(long)]
365 layer: Option<String>,
366 /// Compare with reference model
367 #[arg(long)]
368 reference: Option<PathBuf>,
369 /// Output as JSON
370 #[arg(long)]
371 json: bool,
372 /// Verbose output with per-layer stats
373 #[arg(short, long)]
374 verbose: bool,
375 /// Trace payload through model
376 #[arg(long)]
377 payload: bool,
378 /// Diff mode
379 #[arg(long)]
380 diff: bool,
381 /// Interactive mode
382 #[arg(long)]
383 interactive: bool,
384 /// Save per-stage F32 tensors during trace for SHIP-007 layer-0
385 /// element-wise diff. Comma-separated stage names from
386 /// `apr-cli-trace-save-tensor-v1.yaml` (e.g.
387 /// `embedding,qkv_matmul,attention`). Pass `all` to save every
388 /// stage. Output goes to `--save-tensor-dir` if provided,
389 /// else `<file_dir>/trace-tensors/<run_id>/`.
390 #[arg(long, value_name = "STAGES")]
391 save_tensor: Option<String>,
392 /// Output directory for `--save-tensor` (default: sibling
393 /// `trace-tensors/<run_id>/`).
394 #[arg(long, value_name = "DIR")]
395 save_tensor_dir: Option<PathBuf>,
396 /// Layer-id range for `--save-tensor` (default: 0..1, i.e.
397 /// layer 0 only). Format: `START..END` (Rust range syntax,
398 /// END exclusive).
399 #[arg(long, value_name = "RANGE", default_value = "0..1")]
400 save_tensor_layers: String,
401 },
402 /// Check for best practices and conventions
403 Lint {
404 /// Path to .apr model file
405 #[arg(value_name = "FILE")]
406 file: PathBuf,
407 /// Fail on warnings as well as errors.
408 ///
409 /// By default only ERROR-level findings fail the run. Every real model
410 /// carries advisory metadata warnings (missing license, model_card,
411 /// provenance), so gating on warnings meant `apr lint` could not exit 0
412 /// on anything and its exit code told you nothing.
413 #[arg(long)]
414 strict: bool,
415 },
416 /// Evaluate a BeatBenchmark contract against a measured value (PMAT-741)
417 #[command(name = "beat-run")]
418 BeatRun {
419 /// Path to a beat-benchmark contract YAML (e.g. contracts/beat-sklearn-iris-v1.yaml)
420 #[arg(value_name = "CONTRACT")]
421 contract: PathBuf,
422 /// Measured metric value; when given, emit a WON/REGRESSED verdict and
423 /// exit non-zero on regression. Omit to just report the pinned baseline.
424 #[arg(long, value_name = "VALUE")]
425 measured: Option<f64>,
426 },
427 /// Emit a SHA-256 manifest of input files (CRUX-G-05)
428 Manifest {
429 /// Files to include in the manifest (one entry per file)
430 #[arg(value_name = "FILES", num_args = 1..)]
431 files: Vec<PathBuf>,
432 /// Output JSON manifest path
433 #[arg(short, long, value_name = "MAN_JSON")]
434 output: PathBuf,
435 },
436 /// Explain errors, architecture, tensors, and kernel dispatch
437 Explain {
438 /// Error code, model file path, or family name (auto-detected)
439 #[arg(value_name = "CODE_OR_FILE")]
440 code_or_file: Option<String>,
441 /// Path to .apr model file (optional context for --tensor)
442 #[arg(short, long)]
443 file: Option<PathBuf>,
444 /// Explain a specific tensor
445 #[arg(long)]
446 tensor: Option<String>,
447 /// Explain kernel dispatch pipeline for architecture
448 #[arg(long)]
449 kernel: bool,
450 /// Output as JSON
451 #[arg(long)]
452 json: bool,
453 /// Show kernel contract details and proof obligations
454 #[arg(short, long)]
455 verbose: bool,
456 /// Show per-kernel proof status from contract tests
457 #[arg(long)]
458 proof_status: bool,
459 },
460 /// Manage canary tests for regression
461 Canary {
462 #[command(subcommand)]
463 command: CanaryCommands,
464 },
465 /// Export model to other formats
466 Export {
467 /// Path to .apr model file
468 #[arg(value_name = "FILE", required_unless_present = "list_formats")]
469 file: Option<PathBuf>,
470 /// Output format (safetensors, gguf, mlx, onnx, openvino, coreml)
471 #[arg(long, default_value = "safetensors")]
472 format: String,
473 /// Output file/directory path
474 #[arg(short, long)]
475 output: Option<PathBuf>,
476 /// Apply quantization during export (int8, int4, fp16)
477 #[arg(long)]
478 quantize: Option<String>,
479 /// List all supported export formats
480 #[arg(long)]
481 list_formats: bool,
482 /// Batch export to multiple formats (comma-separated: gguf,mlx,safetensors)
483 #[arg(long)]
484 batch: Option<String>,
485 /// Output in JSON format
486 #[arg(long)]
487 json: bool,
488 /// Plan mode (validate inputs, show export plan, no execution)
489 #[arg(long)]
490 plan: bool,
491 /// #2392: Overwrite an existing output file (refused without it)
492 #[arg(short, long)]
493 force: bool,
494 },
495 /// Import from external formats (hf://org/repo, local files, URLs)
496 Import {
497 /// Source: hf://org/repo, local file, or URL
498 #[arg(value_name = "SOURCE")]
499 source: String,
500 /// Output .apr file path (default: derived from source name)
501 #[arg(short, long)]
502 output: Option<PathBuf>,
503 /// Model architecture (whisper, llama, bert, qwen2, qwen3, gpt2, starcoder, gpt-neox, opt, phi, gemma, falcon, mamba, t5, auto)
504 #[arg(long, default_value = "auto")]
505 arch: String,
506 /// Quantization (int8, int4, fp16)
507 #[arg(long)]
508 quantize: Option<String>,
509 /// Strict mode: reject unverified architectures and fail on validation errors
510 #[arg(long)]
511 strict: bool,
512 /// Preserve Q4K quantization for fused kernel inference (GGUF only)
513 /// Uses realizar's Q4K converter instead of dequantizing to F32
514 #[arg(long)]
515 preserve_q4k: bool,
516 /// PMAT-232: External tokenizer.json for weights-only GGUF files.
517 /// Required if the GGUF has no embedded tokenizer vocabulary.
518 #[arg(long)]
519 tokenizer: Option<PathBuf>,
520 /// F-GT-001: Enforce provenance chain. Rejects pre-baked GGUF imports
521 /// (only SafeTensors sources allowed). Ensures single-provenance testing.
522 #[arg(long)]
523 enforce_provenance: bool,
524 /// GH-223: Allow import without config.json (default: error).
525 /// Without config.json, hyperparameters like rope_theta are inferred from
526 /// tensor shapes and may be wrong, producing garbage output.
527 #[arg(long)]
528 allow_no_config: bool,
529 },
530 /// Download and cache model OR HuggingFace dataset (Ollama-like UX)
531 Pull {
532 /// Model reference (alias, hf:// URI, or org/repo) OR "dataset"
533 /// asset-type discriminator. When this value is the literal
534 /// string "dataset", the next positional `repo` is the
535 /// HuggingFace dataset repo and dataset-pull semantics apply.
536 #[arg(value_name = "MODEL_OR_ASSET_TYPE")]
537 model_ref: String,
538 /// Dataset repository (used only when model_ref == "dataset").
539 /// Per `apr-cli-pull-dataset-v1.yaml`.
540 #[arg(value_name = "REPO")]
541 repo: Option<String>,
542 /// Force re-download even if cached
543 #[arg(long)]
544 force: bool,
545 /// Verify an already-cached model by re-hashing every file against the
546 /// BLAKE3 checksums recorded in `.apr-manifest.json` at download time.
547 ///
548 /// `apr pull` has always RECORDED those hashes and never checked them:
549 /// the only integrity check in the tree compares file SIZE. Size cannot
550 /// see a same-length corruption (a 7.1 GB SafeTensors blob was found
551 /// with 27 of 339 tensors zeroed, byte-length exactly correct). This
552 /// costs O(bytes) by design, which is why it is opt-in. Performs no
553 /// network I/O.
554 #[arg(long)]
555 verify: bool,
556 /// CRUX-A-01: resolve short name to canonical URL and exit without
557 /// performing any network I/O.
558 #[arg(long)]
559 dry_run: bool,
560 /// CRUX-A-03: pin to a specific branch, tag, or git SHA on the remote
561 /// (HuggingFace Hub). Defaults to "main" when omitted.
562 #[arg(long, value_name = "REV")]
563 revision: Option<String>,
564 /// CRUX-A-20: offline mode — forbid any outbound network I/O.
565 /// Equivalent to APR_OFFLINE=1 or HF_HUB_OFFLINE=1 in the environment.
566 #[arg(long)]
567 offline: bool,
568 /// (dataset mode) Glob pattern for shard selection. May be passed
569 /// multiple times; matches are unioned. fnmatch-compatible
570 /// (`*`, `?`, `[a-z]`). No-match is fail-fast.
571 #[arg(long, value_name = "GLOB")]
572 include: Vec<String>,
573 /// (dataset mode) Output directory. Default:
574 /// `~/.cache/aprender/datasets/<repo>/`.
575 #[arg(short = 'o', long)]
576 output: Option<PathBuf>,
577 },
578 /// Registry operations (CRUX-A-01): inspect alias map, etc.
579 Registry {
580 #[command(subcommand)]
581 command: crate::commands::registry::RegistryCommands,
582 },
583 /// List cached models
584 #[command(name = "list", alias = "ls")]
585 List,
586 /// Remove model from cache
587 #[command(name = "rm", alias = "remove")]
588 Rm {
589 /// Model reference to remove
590 #[arg(value_name = "MODEL")]
591 model_ref: String,
592 },
593 /// Convert/optimize model
594 Convert {
595 /// Path to .apr model file
596 #[arg(value_name = "FILE")]
597 file: PathBuf,
598 /// Quantize to format (int8, int4, fp16, q4k)
599 #[arg(long)]
600 quantize: Option<String>,
601 /// Compress output (none, zstd, zstd-max, lz4)
602 #[arg(long)]
603 compress: Option<String>,
604 /// Output file path
605 #[arg(short, long)]
606 output: PathBuf,
607 /// Force overwrite existing files
608 #[arg(short, long)]
609 force: bool,
610 },
611 /// Stamp provenance fields (license, data_source, data_license) onto an existing .apr file
612 ///
613 /// SHIP-009 full-discharge enabler — patches the three provenance fields on
614 /// a pre-built APR v2 artifact (e.g., the shipped MODEL-1 teacher whose
615 /// fields are all (missing) because it was built before GATE-APR-PROV-001..003
616 /// shipped). Tensor bytes and header flags are preserved verbatim.
617 Stamp {
618 /// Path to input .apr model file
619 #[arg(value_name = "FILE")]
620 file: PathBuf,
621 /// SPDX license identifier (e.g., Apache-2.0)
622 #[arg(long)]
623 license: Option<String>,
624 /// Training-data source (e.g., huggingface.co/Qwen/Qwen2.5-Coder-7B-Instruct)
625 #[arg(long = "data-source")]
626 data_source: Option<String>,
627 /// SPDX license for data_source (e.g., Apache-2.0)
628 #[arg(long = "data-license")]
629 data_license: Option<String>,
630 /// HuggingFace class name (e.g., Qwen2ForCausalLM, LlamaForCausalLM).
631 ///
632 /// PMAT-690 P0-K extension (SPEC §86): patch the upstream
633 /// `architectures[0]` stamp on a pre-P0-K APR so downstream
634 /// consumers (apr inspect --quality, apr pretrain --init,
635 /// apr export → llama-cli) see the correct HF identity.
636 #[arg(long = "hf-architecture")]
637 hf_architecture: Option<String>,
638 /// HuggingFace model_type slug (e.g., qwen2, llama).
639 ///
640 /// PMAT-690 P0-K extension (SPEC §86).
641 #[arg(long = "hf-model-type")]
642 hf_model_type: Option<String>,
643 /// Lowercase architecture family slug (e.g., qwen2, llama).
644 ///
645 /// PMAT-690 P0-K extension (SPEC §86). This is the field
646 /// `apr pretrain --init` reads for arch dispatch — without
647 /// patching it, pre-P0-K checkpoints with the P0-H "LlamaForCausalLM"
648 /// fallback in this field cannot be loaded as Qwen2 inits.
649 #[arg(long)]
650 architecture: Option<String>,
651 /// Directory containing tokenizer files (vocab.json + merges.txt
652 /// OR tokenizer.json). When provided, embeds the vocabulary +
653 /// BPE merges into the APR's `custom.tokenizer.vocabulary` /
654 /// `custom.tokenizer.merges` JSON metadata AND sets the
655 /// HAS_VOCAB header flag.
656 ///
657 /// PMAT-690 P3-C-prep defect 1 fix (2026-05-17): pre-P0-K APRs
658 /// trained from inits without embedded tokenizers fail `apr run`
659 /// with PMAT-172. This flag lets the §86 salvage recipe embed
660 /// the tokenizer post-hoc so the artifact is self-contained
661 /// for inference (the apr binary's headline use case).
662 #[arg(long = "tokenizer", value_name = "DIR")]
663 tokenizer_dir: Option<PathBuf>,
664 /// Output file path
665 #[arg(short, long)]
666 output: PathBuf,
667 /// Force overwrite existing files
668 #[arg(short, long)]
669 force: bool,
670 },
671 /// Compile model into standalone executable (APR-SPEC §4.16)
672 Compile {
673 /// Input .apr model file
674 #[arg(value_name = "FILE", required_unless_present = "list_targets")]
675 file: Option<PathBuf>,
676 /// Output binary path (default: derived from model name)
677 #[arg(short, long)]
678 output: Option<PathBuf>,
679 /// Target triple (e.g., x86_64-unknown-linux-musl)
680 #[arg(long)]
681 target: Option<String>,
682 /// Quantize weights before embedding (int8, int4, fp16)
683 #[arg(long)]
684 quantize: Option<String>,
685 /// Release mode (optimized)
686 #[arg(long)]
687 release: bool,
688 /// Strip debug symbols
689 #[arg(long)]
690 strip: bool,
691 /// Enable LTO (Link-Time Optimization)
692 #[arg(long)]
693 lto: bool,
694 /// List available compilation targets
695 #[arg(long)]
696 list_targets: bool,
697 },
698 /// Merge multiple models
699 Merge {
700 /// Model files to merge
701 #[arg(value_name = "FILES", num_args = 2..)]
702 files: Vec<PathBuf>,
703 /// Merge strategy (average, weighted, slerp, ties, dare)
704 #[arg(long, default_value = "average")]
705 strategy: String,
706 /// Output file path (optional in --plan mode)
707 #[arg(short, long, required_unless_present = "plan")]
708 output: Option<PathBuf>,
709 /// Weights for weighted merge (comma-separated, e.g., "0.7,0.3")
710 #[arg(long, value_delimiter = ',')]
711 weights: Option<Vec<f32>>,
712 /// Base model for TIES/DARE (task vectors computed as delta from base)
713 #[arg(long)]
714 base_model: Option<PathBuf>,
715 /// DARE drop probability (default: 0.9)
716 #[arg(long, default_value = "0.9")]
717 drop_rate: f32,
718 /// TIES trim density threshold (default: 0.2)
719 #[arg(long, default_value = "0.2")]
720 density: f32,
721 /// RNG seed for DARE (default: 42)
722 #[arg(long, default_value = "42")]
723 seed: u64,
724 /// Plan mode (validate inputs, show merge plan, no execution)
725 #[arg(long)]
726 plan: bool,
727 /// #2392: Overwrite an existing output file (refused without it)
728 #[arg(short, long)]
729 force: bool,
730 },
731 /// Quantize model weights (GH-243)
732 Quantize {
733 /// Input model file
734 #[arg(value_name = "FILE")]
735 file: PathBuf,
736 /// Quantization scheme: int8, int4, fp16, q4k
737 #[arg(long, short = 's', default_value = "int4")]
738 scheme: String,
739 /// Output file path (required unless --plan)
740 #[arg(short, long)]
741 output: Option<PathBuf>,
742 /// Output format override (apr, gguf, safetensors)
743 #[arg(long)]
744 format: Option<String>,
745 /// Batch quantization (comma-separated schemes)
746 #[arg(long)]
747 batch: Option<String>,
748 /// Plan mode (estimate only, no execution)
749 #[arg(long)]
750 plan: bool,
751 /// Force overwrite existing files
752 #[arg(short, long)]
753 force: bool,
754 },
755 /// Model optimization commands (fine-tune, prune, distill)
756 #[command(flatten)]
757 ModelOps(ModelOpsCommands),
758 /// Start the MCP (Model Context Protocol) server over stdio
759 ///
760 /// Exposes `apr` as MCP tools for Claude Code, Cursor, Cline, and other
761 /// MCP clients. Configure via `.mcp.json` with `{"command":"apr","args":["mcp"]}`.
762 Mcp {},
763 /// Interactive terminal UI
764 Tui {
765 /// Path to .apr model file
766 #[arg(value_name = "FILE")]
767 file: Option<PathBuf>,
768 },
769 /// Model self-test: 10-stage pipeline integrity check (APR-TRACE-001)
770 Check {
771 /// Path to model file
772 #[arg(value_name = "FILE")]
773 file: PathBuf,
774 /// Disable GPU acceleration
775 #[arg(long)]
776 no_gpu: bool,
777 /// Output as JSON
778 #[arg(long)]
779 json: bool,
780 },
781 /// GPU status and VRAM reservation management (GPU-SHARE-001)
782 #[cfg(feature = "training")]
783 Gpu {
784 /// Show reservations as JSON
785 #[arg(long)]
786 json: bool,
787 },
788 /// Sovereign AI coding assistant — all inference local via realizar (PMAT-182)
789
790 Code {
791 /// Path to local GGUF/APR model file (prefers .apr format)
792 #[arg(long)]
793 model: Option<PathBuf>,
794
795 /// Project directory (loads APR.md/CLAUDE.md from this path)
796 #[arg(long, default_value = ".")]
797 project: PathBuf,
798
799 /// Resume previous session (optionally by ID)
800 #[arg(long)]
801 resume: Option<Option<String>>,
802
803 /// Agent manifest (advanced — overrides defaults)
804 #[arg(long)]
805 manifest: Option<PathBuf>,
806
807 /// Initial prompt (non-interactive: print response and exit)
808 #[arg(short, long)]
809 print: bool,
810
811 /// Prompt text (positional, for -p mode).
812 ///
813 /// NOT `trailing_var_arg`: that made clap absorb everything after the
814 /// first prompt word into the prompt, so `apr code -p "hi" --model X`
815 /// silently discarded `--model` and ran whatever auto-discovery found
816 /// — a wrong-model execution with no diagnostic — and typo'd flags
817 /// produced no parse error at all. Options are now parsed in any
818 /// position; a prompt that genuinely starts with `-` needs `--`.
819 prompt: Vec<String>,
820
821 /// Max turns before stopping
822 #[arg(long, default_value = "50")]
823 max_turns: u32,
824
825 /// Emit a `ccpa-trace.jsonl` describing the run to this path.
826 /// Format mirrors the schema at
827 /// <https://github.com/paiml/claude-code-parity-apr/blob/main/contracts/claude-code-parity-apr-v1.yaml>
828 /// (`§ trace_schema`). Used by `ccpa measure` to score apr-code
829 /// against canonical Claude Code reference fixtures.
830 #[arg(long)]
831 emit_trace: Option<PathBuf>,
832
833 /// Output format for non-interactive (`-p`) mode (PMAT-CODE-OUTPUT-FORMAT-001).
834 /// `text` (default): plain assistant text.
835 /// `json`: structured `{type:"result", subtype:"success", result, session_id, duration_ms}`
836 /// envelope matching Claude Code's `claude -p --output-format json` shape.
837 #[arg(long, value_enum, default_value_t = CodeOutputFormat::Text)]
838 output_format: CodeOutputFormat,
839
840 /// Input format for non-interactive stdin (PMAT-CODE-INPUT-FORMAT-001).
841 /// `text` (default): treat stdin as raw prompt text.
842 /// `json`: parse `{"role":"user","content":"..."}` from stdin and use `content`
843 /// as the prompt. Matches Claude Code's `claude -p --input-format json` shape.
844 #[arg(long, value_enum, default_value_t = CodeInputFormat::Text)]
845 input_format: CodeInputFormat,
846 },
847 /// Extended analysis, profiling, QA, and visualization commands
848 #[command(flatten)]
849 Extended(ExtendedCommands),
850
851 /// Monorepo management (publish, shims, audit, archive) [dev-only]
852 #[cfg(feature = "dev")]
853 #[command(subcommand)]
854 Mono(crate::commands::mono::MonoCommands),
855
856 /// RAG pipeline: index, query, transcribe (was the `trueno-rag` binary)
857 #[command(subcommand)]
858 Rag(aprender_rag_cli::Commands),
859
860 /// zram device management (was the `trueno-zram` binary)
861 #[command(subcommand)]
862 Zram(aprender_zram_cli::Commands),
863
864 /// Discrete-event simulation: run, render, validate, verify, emc-check
865 /// (was the `simular` binary)
866 // disable_help_subcommand: simular's `Commands` carries an explicit `Help`
867 // variant. Standalone that is fine -- its own `Cli` sets
868 // disable_help_subcommand = true, so clap does not also generate one. That
869 // attribute lives on `Cli`, which apr never constructs: it embeds the
870 // `Commands` enum directly, so clap's auto-`help` came back and collided:
871 // Command sim: command name `help` is duplicated
872 // clap's duplicate check is #[cfg(debug_assertions)], so a release build
873 // would have SHIPPED the ambiguity instead of panicking.
874 #[command(subcommand, disable_help_subcommand = true)]
875 Sim(simular::cli::Commands),
876
877 /// Compute-graph profiling: profile, bench, roofline, doctor
878 /// (was the `aprender-cgp` binary)
879 #[command(subcommand)]
880 Cgp(cgp::cli::Commands),
881
882 /// Provable-contracts: validate, lint, score, kani, proof-status
883 /// (the `pv` binary keeps shipping under its own name; this is the
884 /// in-apr route to the same commands)
885 #[command(subcommand)]
886 Pv(aprender_contracts_cli::cli::Commands),
887}
888
889/// Subcommands for `apr debug` (aprender#2377 finding 3).
890///
891/// `embed-viz` is the PRODUCER for `apr embed-viz-lint`: CRUX-F-18 shipped the
892/// lint with help pointing at `apr debug embed-viz`, which did not exist, so
893/// its schema / row-count / determinism gates had never run on real data.
894#[derive(Subcommand, Debug)]
895pub enum DebugCommands {
896 /// Project a model's token-embedding table to 2-D and write the
897 /// `token_id,token_str,x,y` CSV `apr embed-viz-lint` reads.
898 ///
899 /// Reads the real embedding tensor (GGUF / APR / SafeTensors, dequantising
900 /// as needed). `--projection umap` is REFUSED with a non-zero exit rather
901 /// than labelling a different algorithm's output "umap".
902 EmbedViz {
903 /// Model file holding the embedding table
904 #[arg(long, value_name = "FILE")]
905 model: PathBuf,
906 /// Embedding tensor name (default: auto-detect the known names)
907 #[arg(long, value_name = "NAME")]
908 tensor: Option<String>,
909 /// Projection method: exact `pca`, seeded `random`, or `umap` (refused)
910 #[arg(long, value_enum, default_value_t = EmbedProjection::Pca)]
911 projection: EmbedProjection,
912 /// Seed pinning the random projection, so a rerun is byte-identical
913 #[arg(long, value_name = "N", default_value_t = 0)]
914 seed: u64,
915 /// Project only the first N vocabulary rows (default: all)
916 #[arg(long, value_name = "N")]
917 limit: Option<usize>,
918 /// Token text, one per line, for the `token_str` column. Without it apr
919 /// reads the GGUF vocabulary, or writes `<unresolved>`
920 #[arg(long, value_name = "FILE")]
921 tokens: Option<PathBuf>,
922 /// Write the CSV here instead of stdout
923 #[arg(short, long, value_name = "FILE")]
924 output: Option<PathBuf>,
925 /// Overwrite an existing --output file (refused without it)
926 #[arg(short, long)]
927 force: bool,
928 },
929}