Skip to main content

logicaffeine_cli/
cli.rs

1//! LOGOS CLI (largo) — argument parsing and dispatch.
2//!
3//! This module defines the `largo` command-line surface: the [`Cli`] parser,
4//! the [`Commands`] enum, and [`run_cli`], which dispatches each subcommand
5//! to its handler in [`crate::commands`].
6//!
7//! # Architecture
8//!
9//! The CLI is built on [`clap`] for argument parsing with derive macros.
10//! Each command variant in [`Commands`] maps to a handler function in a
11//! dedicated module under `commands/` that performs the actual work.
12//!
13//! # Examples
14//!
15//! ```bash
16//! # Create a new project
17//! largo new my_project
18//!
19//! # Build and run
20//! cd my_project
21//! largo run
22//!
23//! # Publish to registry
24//! largo login
25//! largo publish
26//! ```
27
28use clap::{Parser, Subcommand};
29use std::path::PathBuf;
30
31use crate::commands;
32use crate::ui::{self, ColorMode};
33
34/// Command-line interface for the LOGOS build tool.
35///
36/// The `Cli` struct is the top-level argument parser for `largo`. It delegates
37/// to the [`Commands`] enum for subcommand handling.
38///
39/// # Usage
40///
41/// Typically invoked via [`run_cli`] which parses arguments and dispatches
42/// to the appropriate handler:
43///
44/// ```no_run
45/// use logicaffeine_cli::cli::run_cli;
46///
47/// if let Err(e) = run_cli() {
48///     eprintln!("Error: {}", e);
49///     std::process::exit(1);
50/// }
51/// ```
52/// The version string, flavor-stamped: the full build (Z3 verification
53/// statically linked) reports `X.Y.Z (full)` so installs are diagnosable.
54#[cfg(feature = "verification")]
55const LARGO_VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (full)");
56/// The lean build reports the bare version.
57#[cfg(not(feature = "verification"))]
58const LARGO_VERSION: &str = env!("CARGO_PKG_VERSION");
59
60#[derive(Parser)]
61#[command(name = "largo")]
62#[command(about = "The LOGOS build tool", long_about = None)]
63#[command(version = LARGO_VERSION)]
64#[command(arg_required_else_help = true)]
65#[command(styles = ui::CLAP_STYLES)]
66pub struct Cli {
67    /// The subcommand to execute.
68    #[command(subcommand)]
69    pub command: Commands,
70
71    /// Suppress informational output (errors still print).
72    #[arg(long, short, global = true)]
73    pub quiet: bool,
74
75    /// Increase output verbosity (repeatable).
76    #[arg(long, short, global = true, action = clap::ArgAction::Count)]
77    pub verbose: u8,
78
79    /// When to use terminal colors.
80    #[arg(long, global = true, value_enum, default_value = "auto", value_name = "WHEN")]
81    pub color: ColorMode,
82}
83
84/// Available CLI subcommands.
85///
86/// Each variant represents a distinct operation that `largo` can perform.
87/// Commands are grouped into three categories:
88///
89/// ## Project Management
90/// - [`New`][Commands::New] - Create a new project in a new directory
91/// - [`Init`][Commands::Init] - Initialize a project in the current directory
92///
93/// ## Build & Run
94/// - [`Build`][Commands::Build] - Compile the project
95/// - [`Run`][Commands::Run] - Build and execute
96/// - [`Check`][Commands::Check] - Type-check without building
97/// - [`Verify`][Commands::Verify] - Run Z3 static verification
98///
99/// ## Package Registry
100/// - [`Publish`][Commands::Publish] - Upload package to registry
101/// - [`Login`][Commands::Login] - Authenticate with registry
102/// - [`Logout`][Commands::Logout] - Remove stored credentials
103#[derive(Subcommand)]
104pub enum Commands {
105    /// Create a new LOGOS project in a new directory.
106    ///
107    /// Scaffolds a complete project structure including:
108    /// - `Largo.toml` manifest file
109    /// - `src/main.lg` entry point with a "Hello, world!" example
110    /// - `.gitignore` configured for LOGOS projects
111    ///
112    /// # Example
113    ///
114    /// ```bash
115    /// largo new my_project
116    /// cd my_project
117    /// largo run
118    /// ```
119    #[command(after_help = "Examples:\n  largo new hello\n  cd hello\n  largo run")]
120    New {
121        /// The project name, used for the directory and package name.
122        name: String,
123    },
124
125    /// Initialize a LOGOS project in the current directory.
126    ///
127    /// Similar to [`New`][Commands::New] but works in an existing directory.
128    /// Creates the manifest and source structure without creating a new folder.
129    ///
130    /// # Example
131    ///
132    /// ```bash
133    /// mkdir my_project && cd my_project
134    /// largo init
135    /// ```
136    #[command(after_help = "Examples:\n  mkdir app && cd app\n  largo init\n  largo init --name my_app")]
137    Init {
138        /// Project name. If omitted, uses the current directory name.
139        #[arg(long)]
140        name: Option<String>,
141    },
142
143    /// Build the current project.
144    ///
145    /// Compiles the LOGOS source to Rust, then invokes `cargo build` on the
146    /// generated code. The resulting binary is placed in `target/debug/` or
147    /// `target/release/` depending on the mode.
148    ///
149    /// # Verification
150    ///
151    /// When `--verify` is passed, the build process includes Z3 static
152    /// verification of logical constraints. This requires:
153    /// - A Pro+ license (via `--license` or `LOGOS_LICENSE` env var)
154    /// - The `verification` feature enabled at build time
155    ///
156    /// # Example
157    ///
158    /// ```bash
159    /// largo build              # Debug build
160    /// largo build --release    # Release build with optimizations
161    /// largo build --verify     # Build with Z3 verification
162    /// ```
163    #[command(after_help = "Examples:\n  largo build\n  largo build --release\n  largo build --emit wasm\n  largo build --lib --target aarch64-unknown-linux-gnu")]
164    Build {
165        /// Build with optimizations enabled.
166        #[arg(long, short)]
167        release: bool,
168
169        /// Run Z3 static verification after compilation.
170        /// Requires a Pro+ license.
171        #[arg(long)]
172        verify: bool,
173
174        /// License key for verification.
175        /// Can also be set via the `LOGOS_LICENSE` environment variable.
176        #[arg(long)]
177        license: Option<String>,
178
179        /// Build as a library instead of an executable.
180        /// Generates `lib.rs` with `crate-type = ["cdylib"]` instead of a binary.
181        #[arg(long)]
182        lib: bool,
183
184        /// Target triple for cross-compilation.
185        /// Use "wasm" as shorthand for "wasm32-unknown-unknown".
186        #[arg(long)]
187        target: Option<String>,
188
189        /// Pre-build every `is exported for native` function into the AOT-native
190        /// tier bundle (a cached cdylib per function) under `.logos-native/`.
191        #[arg(long)]
192        native_functions: bool,
193
194        /// Emit target. `wasm` compiles the project DIRECTLY to a self-contained `.wasm` module via the
195        /// built-in backend (no rustc / cargo / wasm-bindgen — milliseconds), written to
196        /// `target/<name>.wasm`. `wasm-linked` additionally links the real `logicaffeine_base::BigInt`
197        /// runtime (via `rust-lld`) so overflowing integer arithmetic computes the exact big number
198        /// instead of wrapping — needs the Rust toolchain + a wasm32 `base` build. Omit for the default
199        /// rustc-based Rust build.
200        #[arg(long)]
201        emit: Option<String>,
202    },
203
204    /// Run Z3 static verification without building.
205    ///
206    /// Performs formal verification of logical constraints in the project
207    /// using the Z3 SMT solver. This catches logical errors that would be
208    /// impossible to detect through testing alone.
209    ///
210    /// Requires a Pro+ license.
211    ///
212    /// # Example
213    ///
214    /// ```bash
215    /// largo verify --license sub_xxxxx
216    /// # Or with environment variable:
217    /// export LOGOS_LICENSE=sub_xxxxx
218    /// largo verify
219    /// ```
220    #[command(after_help = "Examples:\n  largo verify --license sub_xxxxx\n  LOGOS_LICENSE=sub_xxxxx largo verify")]
221    Verify {
222        /// License key for verification.
223        /// Can also be set via the `LOGOS_LICENSE` environment variable.
224        #[arg(long)]
225        license: Option<String>,
226    },
227
228    /// Build and run the current project.
229    ///
230    /// Equivalent to `largo build` followed by executing the resulting binary.
231    /// The exit code of the built program is propagated.
232    ///
233    /// With `--interpret`, skips Rust compilation and uses the tree-walking
234    /// interpreter for sub-second feedback during development.
235    ///
236    /// # Example
237    ///
238    /// ```bash
239    /// largo run              # Debug mode (compile to Rust)
240    /// largo run --release    # Release mode
241    /// largo run --interpret  # Interpret directly (no compilation)
242    /// ```
243    #[command(after_help = "Examples:\n  largo run\n  largo run --release\n  largo run --interpret\n  largo run --emit wasm\n  largo run -- input.txt --program-flag")]
244    Run {
245        /// Build with optimizations enabled.
246        #[arg(long, short)]
247        release: bool,
248
249        /// Run using the interpreter instead of compiling to Rust.
250        /// Provides sub-second feedback but lacks full Rust performance.
251        #[arg(long, short)]
252        interpret: bool,
253
254        /// `wasm` compiles DIRECTLY to a `.wasm` (built-in backend, no rustc) and runs it via the
255        /// emitted host shim (node). `wasm-linked` links the real `BigInt` runtime first (exact
256        /// arbitrary-precision integers; needs the Rust toolchain). Compile-and-run in one step.
257        #[arg(long, conflicts_with = "interpret")]
258        emit: Option<String>,
259
260        /// Arguments to pass to the program.
261        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
262        args: Vec<String>,
263    },
264
265    /// Check the project for errors without producing a binary.
266    ///
267    /// Parses and type-checks the LOGOS source without invoking the full
268    /// build pipeline. Useful for quick validation during development.
269    ///
270    /// # Example
271    ///
272    /// ```bash
273    /// largo check
274    /// ```
275    #[command(after_help = "Examples:\n  largo check\n  largo check --quiet")]
276    Check {
277        /// Also run rustc's analysis over the generated code (the same deep
278        /// pass the IDE's flycheck uses) and translate its findings to LOGOS.
279        #[arg(long)]
280        deep: bool,
281    },
282
283    /// Report which optimizations actually FIRE when compiling a LOGOS file.
284    ///
285    /// Compiles the file on the AOT, run-path, and VM-compile paths with the
286    /// firing trace on, and lists the optimizations that genuinely changed the
287    /// program (not merely the ones that are enabled). Useful for understanding
288    /// and auditing what the compiler did to a given program.
289    ///
290    /// # Example
291    ///
292    /// ```bash
293    /// largo opts src/main.lg
294    /// largo opts src/main.lg --json
295    /// ```
296    #[command(after_help = "Examples:\n  largo opts src/main.lg\n  largo opts src/main.lg --json")]
297    Opts {
298        /// The `.lg` source file to analyze.
299        file: PathBuf,
300
301        /// Emit the fired optimizations as JSON (keyword list).
302        #[arg(long)]
303        json: bool,
304    },
305
306    /// Publish the package to the LOGOS registry.
307    ///
308    /// Packages the project as a tarball and uploads it to the specified
309    /// registry. Requires authentication via `largo login`.
310    ///
311    /// # Pre-flight Checks
312    ///
313    /// Before publishing, the command verifies:
314    /// - The entry point exists
315    /// - No uncommitted git changes (unless `--allow-dirty`)
316    /// - Valid authentication token
317    ///
318    /// # Example
319    ///
320    /// ```bash
321    /// largo publish              # Publish to default registry
322    /// largo publish --dry-run    # Validate without uploading
323    /// ```
324    #[command(after_help = "Examples:\n  largo publish --dry-run\n  largo publish\n  largo publish --allow-dirty")]
325    Publish {
326        /// Registry URL. Defaults to `registry.logicaffeine.com`.
327        #[arg(long)]
328        registry: Option<String>,
329
330        /// Perform all validation without actually uploading.
331        /// Useful for testing the publish process.
332        #[arg(long)]
333        dry_run: bool,
334
335        /// Allow publishing with uncommitted git changes.
336        /// By default, publishing requires a clean working directory.
337        #[arg(long)]
338        allow_dirty: bool,
339    },
340
341    /// Authenticate with the package registry.
342    ///
343    /// Stores an API token for the specified registry. The token is saved
344    /// in `~/.config/logos/credentials.toml` with restricted permissions.
345    ///
346    /// # Token Acquisition
347    ///
348    /// Tokens can be obtained from the registry's web interface:
349    /// 1. Visit `{registry}/auth/github` to authenticate
350    /// 2. Generate an API token from your profile
351    /// 3. Provide it via `--token` or interactive prompt
352    ///
353    /// # Example
354    ///
355    /// ```bash
356    /// largo login                       # Interactive prompt
357    /// largo login --token tok_xxxxx     # Non-interactive
358    /// ```
359    #[command(after_help = "Examples:\n  largo login\n  largo login --token lgr_xxxxx")]
360    Login {
361        /// Registry URL. Defaults to `registry.logicaffeine.com`.
362        #[arg(long)]
363        registry: Option<String>,
364
365        /// API token. If omitted, prompts for input on stdin.
366        #[arg(long)]
367        token: Option<String>,
368    },
369
370    /// Remove stored credentials for a registry.
371    ///
372    /// Deletes the authentication token from the local credentials file.
373    ///
374    /// # Example
375    ///
376    /// ```bash
377    /// largo logout
378    /// ```
379    #[command(after_help = "Examples:\n  largo logout\n  largo logout --registry https://registry.example.com")]
380    Logout {
381        /// Registry URL. Defaults to `registry.logicaffeine.com`.
382        #[arg(long)]
383        registry: Option<String>,
384    },
385
386    /// Diagnose the environment largo runs in.
387    ///
388    /// Checks the Rust toolchain (needed by `build`/`run`), the wasm32
389    /// target, node (for `--emit wasm`), the verification flavor, registry
390    /// reachability and credentials, update freshness, and — inside a
391    /// project — manifest health. Degradations are warnings; only a broken
392    /// project fails. Works offline.
393    #[command(after_help = "Examples:\n  largo doctor\n  largo doctor --registry https://registry.example.com")]
394    Doctor {
395        /// Registry URL to probe (defaults to the LOGOS registry).
396        #[arg(long)]
397        registry: Option<String>,
398    },
399
400    /// Start the interactive LOGOS REPL.
401    ///
402    /// Two modes in one session: imperative statements against a
403    /// persistent interpreter session (`logos>`), and English→FOL logic
404    /// mode with discourse-aware anaphora (`logic>`). Type `:help` inside
405    /// for the meta-commands (`:mode`, `:format`, `:readings`, `:vars`,
406    /// `:save`, …). Works on a pipe too — no terminal required.
407    #[command(after_help = "Examples:\n  largo repl\n  largo repl --logic\n  largo repl --logic --format latex\n  largo repl --load session.lg\n  printf 'Let x be 5.\\nShow x.\\n' | largo repl")]
408    Repl {
409        /// Start in logic mode (English → FOL).
410        #[arg(long)]
411        logic: bool,
412
413        /// Initial logic output format.
414        #[arg(long, value_enum)]
415        format: Option<crate::commands::logic::LogicFormat>,
416
417        /// Load a saved session/program on startup.
418        #[arg(long)]
419        load: Option<PathBuf>,
420    },
421
422    /// Solve a DIMACS CNF with the certified SAT engine.
423    ///
424    /// The SAT Competition interface as a largo verb: prints
425    /// `s SATISFIABLE` with a `v` model or `s UNSATISFIABLE`, optionally
426    /// exporting a DRAT/DPR/SR refutation for external checkers
427    /// (drat-trim). Exit codes follow the competition convention:
428    /// 10 = SAT, 20 = UNSAT, 1 = error.
429    #[command(after_help = "Examples:\n  largo sat instance.cnf\n  largo sat instance.cnf --proof refutation.drat\n  largo sat instance.cnf --stats")]
430    Sat {
431        /// The DIMACS CNF file to solve.
432        file: PathBuf,
433
434        /// Write the UNSAT certificate here (DRAT; DPR/SR for symmetry routes).
435        #[arg(long)]
436        proof: Option<PathBuf>,
437
438        /// Print solver statistics to stderr.
439        #[arg(long)]
440        stats: bool,
441    },
442
443    /// Prove the theorems in a LOGOS source file (kernel-certified).
444    ///
445    /// Runs `## Theory` developments (formal Axiom/Theorem declarations,
446    /// proved in citation order) and English `## Theorem` blocks
447    /// (Given/Prove/Proof) through the proof engine. Every ✓ is certified
448    /// by the type-theory kernel — a mere derivation never counts.
449    #[command(after_help = "Examples:\n  largo prove                 # prove the project entry\n  largo prove geometry.lg\n  largo prove socrates.lg --trace\n  largo prove tarski.lg --json")]
450    Prove {
451        /// The source file (defaults to the project entry).
452        file: Option<PathBuf>,
453
454        /// Show the rendered derivation tree under each proved theorem.
455        #[arg(long)]
456        trace: bool,
457
458        /// Emit machine-readable JSON results.
459        #[arg(long, conflicts_with = "trace")]
460        json: bool,
461    },
462
463    /// Translate English to First-Order Logic.
464    ///
465    /// Compiles a natural-language sentence to formal logic — the LOGOS
466    /// logic mode from the terminal. Reads the sentence inline, from
467    /// `--file`, or from piped stdin. Prints bare FOL on stdout, so output
468    /// pipes cleanly into other tools.
469    #[command(after_help = "Examples:\n  largo logic \"Every woman loves a man.\"\n  largo logic \"Every woman loves a man.\" --all-readings\n  largo logic \"It might rain.\" --format kripke\n  echo \"Socrates is mortal.\" | largo logic\n  printf 'A farmer owns a donkey.\\nHe feeds it.' | largo logic --discourse")]
470    Logic {
471        /// The English sentence to translate.
472        sentence: Option<String>,
473
474        /// Read the sentence (or discourse) from a file.
475        #[arg(long, short, conflicts_with = "sentence")]
476        file: Option<PathBuf>,
477
478        /// Output format for the logical form.
479        #[arg(long, value_enum, default_value = "unicode")]
480        format: crate::commands::logic::LogicFormat,
481
482        /// Show every reading (quantifier scopes + parse forest), numbered.
483        #[arg(long, conflicts_with = "discourse")]
484        all_readings: bool,
485
486        /// Enrich with scalar implicature (pragmatic strengthening).
487        #[arg(long)]
488        pragmatic: bool,
489
490        /// Treat each input line as one sentence of a discourse with
491        /// shared anaphora context.
492        #[arg(long)]
493        discourse: bool,
494    },
495
496    /// Generate documentation from the project's `##` blocks.
497    ///
498    /// Renders a markdown reference from the literate structure of the
499    /// entry file: `## To` signatures, type definitions, notes, examples,
500    /// and formal blocks — in source order. `## Main` is omitted.
501    #[command(after_help = "Examples:\n  largo doc\n  largo doc --out book")]
502    Doc {
503        /// Output directory (defaults to `target/doc`).
504        #[arg(long)]
505        out: Option<PathBuf>,
506    },
507
508    /// Add a dependency to Largo.toml.
509    ///
510    /// Accepts `name` (any version), `name@version`, or `logos:name` (the
511    /// registry URI form). `--path` and `--git` record local and git
512    /// dependencies. Edits preserve the manifest's comments and formatting.
513    #[command(after_help = "Examples:\n  largo add math_utils\n  largo add [email protected]\n  largo add logos:std\n  largo add local_lib --path ../local_lib\n  largo add remote --git https://example.com/remote.git")]
514    Add {
515        /// The dependency: `name`, `name@version`, or `logos:name`.
516        spec: String,
517
518        /// Use a local path dependency.
519        #[arg(long, conflicts_with = "git")]
520        path: Option<String>,
521
522        /// Use a git dependency.
523        #[arg(long, conflicts_with = "path")]
524        git: Option<String>,
525    },
526
527    /// Remove a dependency from Largo.toml.
528    ///
529    /// Deletes the named entry from `[dependencies]`, leaving the rest of
530    /// the manifest byte-identical.
531    #[command(after_help = "Examples:\n  largo remove math_utils")]
532    Remove {
533        /// The dependency name to remove.
534        name: String,
535    },
536
537    /// Format LOGOS source files.
538    ///
539    /// Applies the canonical style (4-space indentation, no tabs, no
540    /// trailing whitespace) — the same rules the language server uses.
541    /// Without paths, formats the whole project; with paths, exactly those
542    /// files. `--check` writes nothing and exits 1 if anything would change.
543    #[command(after_help = "Examples:\n  largo fmt\n  largo fmt src/main.lg\n  largo fmt --check    # CI gate, writes nothing")]
544    Fmt {
545        /// Specific files to format (defaults to all project sources).
546        paths: Vec<PathBuf>,
547
548        /// Check only: list files that need formatting, exit 1 if any.
549        #[arg(long)]
550        check: bool,
551    },
552
553    /// Emit compiled code without building a binary.
554    ///
555    /// Prints the generated Rust or C translation of the program, or writes
556    /// a self-contained `.wasm` module (built-in backend, no rustc) with its
557    /// Node.js host shim. Without FILE, uses the current project's entry;
558    /// with FILE, works on any standalone `.lg`/`.md` source.
559    #[command(after_help = "Examples:\n  largo emit rust\n  largo emit rust -o generated.rs\n  largo emit c standalone.lg\n  largo emit wasm\n  largo emit wasm-linked -o dist/app.wasm")]
560    Emit {
561        /// What to emit.
562        #[arg(value_enum)]
563        target: crate::commands::emit::EmitTarget,
564
565        /// A standalone source file (defaults to the project entry).
566        file: Option<PathBuf>,
567
568        /// Write to this path instead of stdout (rust/c) or the default
569        /// module path (wasm).
570        #[arg(long, short)]
571        output: Option<PathBuf>,
572    },
573
574    /// Remove build artifacts.
575    ///
576    /// Deletes the project's `target/` directory. With `--all`, also removes
577    /// the `.logos-native/` compiled-function bundle cache produced by
578    /// `largo build --native-functions`.
579    #[command(after_help = "Examples:\n  largo clean\n  largo clean --all")]
580    Clean {
581        /// Also remove the `.logos-native/` bundle cache.
582        #[arg(long)]
583        all: bool,
584    },
585
586    /// Generate shell completions for largo.
587    ///
588    /// Writes a completion script for the given shell to stdout. Source it
589    /// from your shell's configuration to get tab completion for every
590    /// largo command and flag.
591    #[command(after_help = "Examples:\n  largo completions bash > ~/.local/share/bash-completion/completions/largo\n  largo completions zsh > ~/.zfunc/_largo\n  largo completions fish > ~/.config/fish/completions/largo.fish")]
592    Completions {
593        /// The shell to generate completions for.
594        #[arg(value_enum)]
595        shell: clap_complete::Shell,
596    },
597
598    /// Reserved for the LOGOS test framework (coming in a future release).
599    #[command(hide = true)]
600    Test {
601        /// Ignored; the verb is reserved.
602        #[arg(trailing_var_arg = true, allow_hyphen_values = true, hide = true)]
603        args: Vec<String>,
604    },
605}
606
607/// Parse CLI arguments and execute the corresponding command.
608///
609/// This is the main entry point for the `largo` CLI. It parses command-line
610/// arguments using [`clap`], then dispatches to the appropriate handler
611/// function based on the subcommand.
612///
613/// # Errors
614///
615/// Returns an error if:
616/// - The project structure is invalid (missing `Largo.toml`)
617/// - File system operations fail
618/// - Build or compilation fails
619/// - Registry operations fail (authentication, network, etc.)
620///
621/// # Example
622///
623/// ```no_run
624/// use logicaffeine_cli::cli::run_cli;
625///
626/// fn main() {
627///     if let Err(e) = run_cli() {
628///         eprintln!("Error: {}", e);
629///         std::process::exit(1);
630///     }
631/// }
632/// ```
633pub fn run_cli() -> Result<(), Box<dyn std::error::Error>> {
634    let cli = Cli::parse();
635    ui::init(cli.color, cli.quiet, cli.verbose);
636
637    match cli.command {
638        Commands::New { name } => commands::new::cmd_new(&name),
639        Commands::Init { name } => commands::new::cmd_init(name.as_deref()),
640        Commands::Build { release, verify, license, lib, target, native_functions, emit } => {
641            commands::build::cmd_build(release, verify, license, lib, target, native_functions, emit)
642        }
643        Commands::Run { emit: Some(e), args, .. } if e == "wasm" => commands::run::cmd_run_wasm(&args, false),
644        Commands::Run { emit: Some(e), args, .. } if e == "wasm-linked" => commands::run::cmd_run_wasm(&args, true),
645        Commands::Run { emit: Some(e), .. } => {
646            Err(format!("unknown --emit target '{e}' (expected 'wasm' or 'wasm-linked')").into())
647        }
648        Commands::Run { interpret, args, .. } if interpret => commands::run::cmd_run_interpret(&args),
649        Commands::Run { release, args, .. } => commands::run::cmd_run(release, &args),
650        Commands::Check { deep } => commands::check::cmd_check(deep),
651        Commands::Opts { file, json } => commands::opts::cmd_opts(&file, json),
652        Commands::Verify { license } => commands::verify::cmd_verify(license),
653        Commands::Publish { registry, dry_run, allow_dirty } => {
654            commands::publish::cmd_publish(registry.as_deref(), dry_run, allow_dirty)
655        }
656        Commands::Login { registry, token } => commands::publish::cmd_login(registry.as_deref(), token),
657        Commands::Logout { registry } => commands::publish::cmd_logout(registry.as_deref()),
658        Commands::Doctor { registry } => commands::doctor::cmd_doctor(registry),
659        Commands::Repl { logic, format, load } => crate::repl::cmd_repl(logic, format, load),
660        Commands::Sat { file, proof, stats } => commands::sat::cmd_sat(file, proof, stats),
661        Commands::Prove { file, trace, json } => commands::prove::cmd_prove(file, trace, json),
662        Commands::Logic { sentence, file, format, all_readings, pragmatic, discourse } => {
663            commands::logic::cmd_logic(sentence, file, format, all_readings, pragmatic, discourse)
664        }
665        Commands::Doc { out } => commands::doc::cmd_doc(out),
666        Commands::Add { spec, path, git } => commands::deps::cmd_add(spec, path, git),
667        Commands::Remove { name } => commands::deps::cmd_remove(name),
668        Commands::Fmt { paths, check } => commands::fmt::cmd_fmt(paths, check),
669        Commands::Emit { target, file, output } => commands::emit::cmd_emit(target, file, output),
670        Commands::Clean { all } => commands::clean::cmd_clean(all),
671        Commands::Completions { shell } => commands::completions::cmd_completions(shell),
672        Commands::Test { .. } => Err(ui::CliError::with_hint(
673            "`largo test` is reserved for the LOGOS test framework (coming in a future release)",
674            "run `largo check` to validate your project today",
675        )
676        .exit_code(ui::EXIT_USAGE)
677        .into()),
678    }
679}