1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
mod docs;
mod init;
mod proc;
mod project;
mod run;
mod settings;
mod test;
mod tools;
mod update;
mod update_check;
use anyhow::Result;
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "cfasim", about = "CFA Simulator CLI")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Initialize a new cfasim project
Init {
/// Target directory (name is derived from directory name; defaults to current directory if provided without a value)
#[arg(long, default_missing_value = ".")]
dir: Option<String>,
/// Template: python or rust (skips interactive prompt)
#[arg(long)]
template: Option<init::Template>,
/// Use local templates instead of downloading from GitHub
#[arg(long)]
local: bool,
},
/// Update cfasim to the latest release
Update,
/// Check your system for the tools needed to develop cfasim projects
Tools {
/// Skip network-bound update checks (faster, works offline)
#[arg(long)]
offline: bool,
},
/// List cfasim-ui components and charts (LLM-friendly with --json)
Docs {
/// Emit the raw JSON directory instead of the human-readable listing
#[arg(long)]
json: bool,
},
/// Run tests for the current cfasim project (unit and e2e by default)
Test {
/// Run only unit tests (`uv run pytest` or `cargo test`)
#[arg(long)]
unit: bool,
/// Run only end-to-end tests (`pnpm test:e2e`)
#[arg(long)]
e2e: bool,
},
/// Start the Vite dev server for the current cfasim project
Run {
/// Extra arguments forwarded to `vite` (prefix with `--`, e.g. `cfasim run -- --port 5174`)
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
let is_init = matches!(cli.command, Commands::Init { .. });
if !matches!(cli.command, Commands::Update) {
settings::prompt_for_updates_if_first_run();
}
let result = match cli.command {
Commands::Init {
dir,
template,
local,
} => init::run(dir, template, local).map_err(|e| anyhow::anyhow!("{e}")),
Commands::Update => update::run(),
Commands::Tools { offline } => tools::run(offline),
Commands::Docs { json } => docs::run(json),
Commands::Test { unit, e2e } => test::run(unit, e2e),
Commands::Run { args } => run::run(args),
};
if is_init {
update_check::maybe_print_update_hint();
}
result
}