Skip to main content

hematite/
lib.rs

1pub mod agent;
2
3/// Serializes tests that call `std::env::set_current_dir` to prevent race conditions
4/// in parallel test runs. Any test that mutates the process-wide cwd must hold this
5/// lock for the duration of the directory change.
6#[cfg(test)]
7pub(crate) static TEST_CWD_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
8
9pub mod memory;
10pub mod runtime;
11pub mod telemetry;
12pub mod tools;
13pub mod ui;
14
15pub const HEMATITE_VERSION: &str = env!("CARGO_PKG_VERSION");
16pub const HEMATITE_AUTHOR: &str = "Ocean Bennett";
17pub const HEMATITE_REPOSITORY_URL: &str = "https://github.com/undergroundrap/hematite-cli";
18pub const HEMATITE_SHORT_DESCRIPTION: &str =
19    "Local-first AI coding harness — Senior SysAdmin, Network Admin, Data Analyst, and Software Engineer in your terminal.";
20const HEMATITE_GIT_COMMIT_SHORT_RAW: &str = env!("HEMATITE_GIT_COMMIT_SHORT");
21const HEMATITE_GIT_EXACT_TAG_RAW: &str = env!("HEMATITE_GIT_EXACT_TAG");
22const HEMATITE_GIT_DIRTY_RAW: &str = env!("HEMATITE_GIT_DIRTY");
23
24pub fn hematite_git_commit_short() -> Option<&'static str> {
25    #[allow(clippy::const_is_empty)]
26    (!HEMATITE_GIT_COMMIT_SHORT_RAW.is_empty()).then_some(HEMATITE_GIT_COMMIT_SHORT_RAW)
27}
28
29pub fn hematite_git_exact_tag() -> Option<&'static str> {
30    #[allow(clippy::const_is_empty)]
31    (!HEMATITE_GIT_EXACT_TAG_RAW.is_empty()).then_some(HEMATITE_GIT_EXACT_TAG_RAW)
32}
33
34pub fn hematite_git_dirty() -> bool {
35    HEMATITE_GIT_DIRTY_RAW.eq_ignore_ascii_case("true")
36}
37
38pub fn hematite_build_descriptor() -> String {
39    let release_tag = format!("v{}", HEMATITE_VERSION);
40    let exact_release = matches!(hematite_git_exact_tag(), Some(tag) if tag == release_tag);
41
42    if exact_release && !hematite_git_dirty() {
43        "release".to_string()
44    } else {
45        match (hematite_git_commit_short(), hematite_git_dirty()) {
46            (Some(commit), true) => format!("dev+{}-dirty", commit),
47            (Some(commit), false) => format!("dev+{}", commit),
48            (None, true) => "dev-dirty".to_string(),
49            (None, false) => "dev".to_string(),
50        }
51    }
52}
53
54pub fn hematite_version() -> String {
55    format!("v{}", HEMATITE_VERSION)
56}
57
58pub fn hematite_version_display() -> String {
59    format!("v{} [{}]", HEMATITE_VERSION, hematite_build_descriptor())
60}
61
62pub fn hematite_version_report() -> String {
63    let mut lines = vec![
64        format!("Hematite v{}", HEMATITE_VERSION),
65        format!("Build: {}", hematite_build_descriptor()),
66    ];
67    if let Some(commit) = hematite_git_commit_short() {
68        lines.push(format!("Commit: {}", commit));
69    }
70    lines.push(format!(
71        "Built from a dirty worktree: {}",
72        if hematite_git_dirty() { "yes" } else { "no" }
73    ));
74    lines.push(format!(
75        "Exact release tag at build time: {}",
76        hematite_git_exact_tag().unwrap_or("none")
77    ));
78    lines.join("\n")
79}
80
81pub fn hematite_about_report() -> String {
82    [
83        format!("Hematite v{}", HEMATITE_VERSION),
84        format!("Build: {}", hematite_build_descriptor()),
85        format!("Created and maintained by {}", HEMATITE_AUTHOR),
86        HEMATITE_SHORT_DESCRIPTION.to_string(),
87        format!("Repo: {}", HEMATITE_REPOSITORY_URL),
88    ]
89    .join("\n")
90}
91
92pub fn hematite_identity_answer() -> String {
93    format!(
94        "Hematite was created and is maintained by {}.\n\n{}\n\nThe running assistant uses a local model runtime, but Hematite itself is the local harness: the TUI, tool use, file editing, workflow control, host inspection, data analysis sandbox, voice integration, and workstation-assistant architecture.\n\nRepo: {}",
95        HEMATITE_AUTHOR, HEMATITE_SHORT_DESCRIPTION, HEMATITE_REPOSITORY_URL
96    )
97}
98
99// Standard imports for library users
100pub use agent::config::HematiteConfig;
101pub use agent::conversation::ConversationManager;
102pub use agent::inference::InferenceEngine;
103
104use clap::Parser;
105
106#[derive(Parser, Debug, Clone)]
107#[command(
108    author,
109    version,
110    about = "Hematite CLI - SysAdmin, Network Admin, Data Analyst, and Software Engineer in your terminal",
111    long_about = None
112)]
113pub struct CliCockpit {
114    #[arg(long, help = "Bypasses the high-risk modal (Danger mode)")]
115    pub yolo: bool,
116
117    #[arg(
118        long,
119        default_value_t = 3,
120        help = "Sets max parallel workers (default 3)"
121    )]
122    pub swarm_size: usize,
123
124    #[arg(
125        long,
126        help = "Forces the Vigil Brief Mode for concise, high-speed output"
127    )]
128    pub brief: bool,
129
130    #[arg(
131        long,
132        help = "Pass a custom salt to reroll the deterministic species hash"
133    )]
134    pub reroll: Option<String>,
135
136    #[arg(
137        long,
138        help = "Rusty Mode: Enables the Rusty personality system, snark, and companion features"
139    )]
140    pub rusty: bool,
141
142    #[arg(long, help = "Show Rusty stats and exit")]
143    pub stats: bool,
144
145    #[arg(
146        long,
147        help = "Skip the blocking splash screen and enter the TUI immediately"
148    )]
149    pub no_splash: bool,
150
151    #[arg(
152        long,
153        help = "Optional model ID for simple tasks (overrides auto-detect)"
154    )]
155    pub fast_model: Option<String>,
156
157    #[arg(
158        long,
159        help = "Optional model ID for complex tasks (overrides auto-detect)"
160    )]
161    pub think_model: Option<String>,
162
163    #[arg(
164        long,
165        default_value = "http://localhost:1234/v1",
166        help = "The base URL for the OpenAI-compatible API"
167    )]
168    pub url: String,
169
170    // ── MCP Server ────────────────────────────────────────────────────────────
171    #[arg(
172        long,
173        help_heading = "MCP Server",
174        help = "Run as an MCP stdio server — exposes inspect_host to Claude Desktop, OpenClaw, Cursor, and any MCP-capable agent"
175    )]
176    pub mcp_server: bool,
177
178    #[arg(
179        long,
180        help_heading = "MCP Server",
181        help = "Enable edge redaction in MCP server mode — strips usernames, MACs, serial numbers, hostnames, and credentials before responses leave the machine"
182    )]
183    pub edge_redact: bool,
184
185    #[arg(
186        long,
187        help_heading = "MCP Server",
188        help = "Enable semantic edge redaction — routes inspect_host output through the local model for privacy-safe summarization before any data leaves the machine. Implies --edge-redact."
189    )]
190    pub semantic_redact: bool,
191
192    #[arg(
193        long,
194        help_heading = "MCP Server",
195        help = "Endpoint for --semantic-redact (default: same as --url). Point at a dedicated compact model on a different port."
196    )]
197    pub semantic_url: Option<String>,
198
199    #[arg(
200        long,
201        help_heading = "MCP Server",
202        help = "Model ID for --semantic-redact (e.g. bonsai-8b). Required when multiple models are loaded."
203    )]
204    pub semantic_model: Option<String>,
205
206    // ── Headless Reports ──────────────────────────────────────────────────────
207    #[arg(
208        long,
209        help_heading = "Headless Reports",
210        help = "Run a headless diagnostic report and print to stdout — no TUI launched. Pipe to a file: hematite --report > health.md"
211    )]
212    pub report: bool,
213
214    #[arg(
215        long,
216        help_heading = "Headless Reports",
217        default_value = "md",
218        help = "Output format: md (default), json, or html (self-contained, double-clickable)"
219    )]
220    pub report_format: String,
221
222    #[arg(
223        long,
224        help_heading = "Headless Reports",
225        help = "Staged triage — health_report then targeted follow-up inspections. Saves to .hematite/reports/. Add --open to launch."
226    )]
227    pub diagnose: bool,
228
229    #[arg(
230        long,
231        help_heading = "Headless Reports",
232        default_missing_value = "default",
233        num_args = 0..=1,
234        value_name = "PRESET",
235        help = "IT-first-look triage. Optional preset: network, security, performance, storage, apps. Plain --triage runs health+security+connectivity+identity+updates."
236    )]
237    pub triage: Option<String>,
238
239    #[arg(
240        long,
241        help_heading = "Headless Reports",
242        value_name = "ISSUE",
243        help = "Targeted fix plan — keyword-matches your issue to the right inspect_host topics and saves a step-by-step plan. Example: hematite --fix \"PC running slow\""
244    )]
245    pub fix: Option<String>,
246
247    #[arg(
248        long,
249        help_heading = "Headless Reports",
250        help = "Open the saved report file immediately after writing (browser for HTML, editor for Markdown)"
251    )]
252    pub open: bool,
253
254    #[arg(
255        long,
256        help_heading = "Headless Reports",
257        help = "With --fix: preview which topics would be inspected without running any checks"
258    )]
259    pub dry_run: bool,
260
261    #[arg(
262        long,
263        help_heading = "Headless Reports",
264        help = "With --fix: offer to run safe auto-fixes after generating the plan (DNS flush, service restarts, clock sync, etc.)"
265    )]
266    pub execute: bool,
267
268    #[arg(
269        long,
270        help_heading = "Headless Reports",
271        help = "With --fix --execute: skip the Y/n prompt and apply auto-fixes immediately. Use in scripts and scheduled tasks."
272    )]
273    pub yes: bool,
274
275    #[arg(
276        long,
277        help_heading = "Headless Reports",
278        help = "Suppress output when the result is healthy (exit 0). Only prints when issues are found (exit 1). Use in scheduled tasks and scripts."
279    )]
280    pub quiet: bool,
281
282    #[arg(
283        long,
284        help_heading = "Headless Reports",
285        help = "Maintenance sweep — checks every safe auto-fix topic, skips what is healthy, runs what needs fixing, and verifies each fix resolved. No model required."
286    )]
287    pub fix_all: bool,
288
289    #[arg(
290        long,
291        help_heading = "Headless Reports",
292        value_name = "LABEL",
293        help = "With --fix-all: run only the named fix from the sweep. Example: hematite --fix-all --only \"Flush DNS Cache\". Use --fix-all --list to see all fix labels."
294    )]
295    pub only: Option<String>,
296
297    #[arg(
298        long,
299        help_heading = "Headless Reports",
300        help = "Copy output to clipboard after the command completes. Works with --triage, --diagnose, --fix, --fix-all, --inspect, and --query."
301    )]
302    pub clipboard: bool,
303
304    #[arg(
305        long,
306        help_heading = "Headless Reports",
307        help = "Show a native desktop notification when the command finishes. On alert pattern match with --watch, fires a notification instead of only ringing the bell. Windows 10/11 only."
308    )]
309    pub notify: bool,
310
311    #[arg(
312        long,
313        help_heading = "Headless Reports",
314        value_name = "PATH",
315        help = "Save report output to an explicit file path instead of the auto-dated .hematite/reports/ directory. Works with --triage, --diagnose, --fix, --fix-all, and --inspect."
316    )]
317    pub output: Option<String>,
318
319    #[arg(
320        long,
321        help_heading = "Headless Reports",
322        default_missing_value = "weekly",
323        num_args = 0..=1,
324        value_name = "CADENCE",
325        help = "Register a Windows scheduled task for --triage. CADENCE: weekly (default), daily, remove, status. Combine with --fix-all to schedule the maintenance sweep instead."
326    )]
327    pub schedule: Option<String>,
328
329    // ── Modelless Inspection ──────────────────────────────────────────────────
330    #[arg(
331        long,
332        help_heading = "Modelless Inspection",
333        help = "List all 128 available inspect_host topics by category. No model or TUI required."
334    )]
335    pub inventory: bool,
336
337    #[arg(
338        long,
339        help_heading = "Modelless Inspection",
340        value_name = "TOPIC[,TOPIC2,...]",
341        help = "Run any inspect_host topic directly to stdout. Comma-separate for multiple topics. Example: hematite --inspect wifi,latency,dns_cache"
342    )]
343    pub inspect: Option<String>,
344
345    #[arg(
346        long,
347        help_heading = "Modelless Inspection",
348        value_name = "QUERY",
349        help = "Natural-language query routed to the right inspect_host topics. Example: hematite --query \"why is my PC slow\""
350    )]
351    pub query: Option<String>,
352
353    #[arg(
354        long,
355        help_heading = "Modelless Inspection",
356        value_name = "TOPIC[,TOPIC2,...]",
357        help = "Continuously poll topic(s) every N seconds (see --watch-interval). Press Ctrl+C to stop. Example: hematite --watch resource_load,thermal"
358    )]
359    pub watch: Option<String>,
360
361    #[arg(
362        long,
363        help_heading = "Modelless Inspection",
364        value_name = "SECONDS",
365        default_value = "5",
366        help = "Polling interval in seconds for --watch (default: 5)"
367    )]
368    pub watch_interval: u64,
369
370    #[arg(
371        long,
372        help_heading = "Modelless Inspection",
373        value_name = "N",
374        help = "With --watch: stop after N poll cycles instead of running until Ctrl+C. Example: hematite --watch resource_load --count 5"
375    )]
376    pub count: Option<u64>,
377
378    #[arg(
379        long,
380        help_heading = "Modelless Inspection",
381        value_name = "TOPIC[,TOPIC2,...]",
382        help = "Take two snapshots separated by --diff-after seconds and show a colored diff. Example: hematite --diff processes --diff-after 60"
383    )]
384    pub diff: Option<String>,
385
386    #[arg(
387        long,
388        help_heading = "Modelless Inspection",
389        value_name = "SECONDS",
390        default_value = "30",
391        help = "Seconds between snapshots for --diff (default: 30)"
392    )]
393    pub diff_after: u64,
394
395    #[arg(
396        long,
397        help_heading = "Modelless Inspection",
398        value_name = "PATTERN",
399        help = "With --watch: silent heartbeat when pattern is absent, bell + full output on match. Example: hematite --watch thermal --alert throttl"
400    )]
401    pub alert: Option<String>,
402
403    #[arg(
404        long,
405        help_heading = "Modelless Inspection",
406        value_name = "PATTERN",
407        help = "With --watch or --inspect: filter output to only lines containing PATTERN. Case-insensitive. Example: hematite --watch resource_load --field cpu"
408    )]
409    pub field: Option<String>,
410
411    #[arg(
412        long,
413        help_heading = "Modelless Inspection",
414        value_name = "NAME",
415        help = "With --inspect: save output to .hematite/snapshots/<name>.txt instead of printing. Example: hematite --inspect thermal --snapshot before-update"
416    )]
417    pub snapshot: Option<String>,
418
419    #[arg(
420        long,
421        help_heading = "Modelless Inspection",
422        value_name = "NAME",
423        help = "With --diff: load snapshot A from .hematite/snapshots/<name>.txt instead of running a live capture. Example: hematite --diff thermal --from before-update"
424    )]
425    pub from: Option<String>,
426
427    #[arg(
428        long,
429        help_heading = "Modelless Inspection",
430        help = "List saved snapshots in .hematite/snapshots/ with timestamps and sizes"
431    )]
432    pub snapshots: bool,
433
434    #[arg(
435        long,
436        help_heading = "Modelless Inspection",
437        value_name = "NAME1,NAME2",
438        help = "Diff two saved snapshots against each other without a live run. Example: hematite --compare before-update,after-update"
439    )]
440    pub compare: Option<String>,
441
442    #[arg(
443        long,
444        help_heading = "Modelless Inspection",
445        value_name = "NAME",
446        help = "Start a change audit session — takes a baseline snapshot of key system topics. Example: hematite --audit-start pre-patch"
447    )]
448    pub audit_start: Option<String>,
449
450    #[arg(
451        long,
452        help_heading = "Modelless Inspection",
453        value_name = "NAME",
454        help = "End a change audit session — re-runs the baseline topics and generates a diff report. Example: hematite --audit-end pre-patch"
455    )]
456    pub audit_end: Option<String>,
457
458    #[arg(
459        long,
460        help_heading = "Modelless Inspection",
461        value_name = "TOPIC[,TOPIC2,...]",
462        help = "Topics to capture for --audit-start (default: services,startup_items,ports,scheduled_tasks,shares,firewall_rules,processes,connections)"
463    )]
464    pub audit_topics: Option<String>,
465
466    #[arg(
467        long,
468        help_heading = "Modelless Inspection",
469        value_name = "TOPIC:PATTERN",
470        help = "Add a persistent alert rule. Format: TOPIC:PATTERN (e.g. thermal:throttl). Add --alert-rule-label to name it. Add --alert-rule-negate to fire when pattern is absent."
471    )]
472    pub alert_rule_add: Option<String>,
473
474    #[arg(
475        long,
476        help_heading = "Modelless Inspection",
477        value_name = "NAME",
478        help = "Label for the alert rule being added with --alert-rule-add."
479    )]
480    pub alert_rule_label: Option<String>,
481
482    #[arg(
483        long,
484        help_heading = "Modelless Inspection",
485        help = "With --alert-rule-add: fire when pattern is ABSENT (e.g. alert if antivirus is not running)."
486    )]
487    pub alert_rule_negate: bool,
488
489    #[arg(
490        long,
491        help_heading = "Modelless Inspection",
492        help = "List all saved alert rules."
493    )]
494    pub alert_rules: bool,
495
496    #[arg(
497        long,
498        help_heading = "Modelless Inspection",
499        value_name = "ID",
500        help = "Remove alert rule by ID (see --alert-rules for IDs)."
501    )]
502    pub alert_rule_remove: Option<u64>,
503
504    #[arg(
505        long,
506        help_heading = "Modelless Inspection",
507        help = "Evaluate all saved alert rules against live machine data and fire toast notifications for matches. Add --schedule hourly|daily to automate."
508    )]
509    pub alert_rule_run: bool,
510
511    #[arg(
512        long,
513        help_heading = "Modelless Inspection",
514        help = "Take today's timeline snapshot (health_report, startup_items, ports, services). Skips if already captured today. Add --schedule daily to register a Task Scheduler task."
515    )]
516    pub timeline_capture: bool,
517
518    #[arg(
519        long,
520        help_heading = "Modelless Inspection",
521        help = "Show the machine state timeline — all captured daily entries with date, health grade, and summary."
522    )]
523    pub timeline: bool,
524
525    #[arg(
526        long,
527        help_heading = "Modelless Inspection",
528        value_name = "DATE or DATE1,DATE2",
529        help = "Diff timeline entries. Single date diffs against the previous entry; two dates diff each other. Example: hematite --timeline-diff 2025-05-10"
530    )]
531    pub timeline_diff: Option<String>,
532
533    #[arg(
534        long,
535        help_heading = "Modelless Inspection",
536        help = "Show an ASCII health grade trend chart from all captured timeline entries. Renders a bar chart, sparkline, and trajectory summary."
537    )]
538    pub timeline_trend: bool,
539
540    #[arg(
541        long,
542        help_heading = "Modelless Inspection",
543        value_name = "SYMPTOM",
544        help = "Symptom-driven root-cause diagnosis — describe the problem in plain English. Runs all relevant topics and returns ranked probable causes with evidence. No model required. Example: hematite --diagnose-why \"PC is slow and freezing\""
545    )]
546    pub diagnose_why: Option<String>,
547
548    #[arg(
549        long,
550        help_heading = "Headless Reports",
551        value_name = "FILE",
552        help = "Statistical profiler — loads CSV/TSV/JSON/SQLite and prints a real computed column profile. No model required. Example: hematite --analyze data.csv"
553    )]
554    pub analyze: Option<String>,
555
556    #[arg(
557        long,
558        help_heading = "Headless Reports",
559        value_name = "EXPR",
560        help = "Evaluate a math or science expression locally — no model, no cloud. Supports arithmetic, trig, stats, physical constants, and percentages. Examples: hematite --compute \"sqrt(2)*pi\", hematite --compute \"15% of 89.99\", hematite --compute \"N_A * k_B\""
561    )]
562    pub compute: Option<String>,
563
564    #[arg(
565        long,
566        help_heading = "Headless Reports",
567        value_name = "EXPR",
568        help = "Unit conversion — instant, no model, no cloud. 15 categories: length, mass, time, area, volume, speed, force, pressure, energy, power, data, angle, frequency, illuminance, fuel economy, temperature. Examples: hematite --convert '5 km to miles'  '100 f to c'  '1 atm to Pa'  '60 mph to km/h'  '1 GiB to MB'  '1 kcal to J'  'list' (show all units)"
569    )]
570    pub convert: Option<String>,
571
572    #[arg(
573        long,
574        help_heading = "Headless Reports",
575        value_name = "FILE",
576        help = "Run a SQL query against a local data file (CSV, TSV, JSON, SQLite). The file is loaded as a table named 'data'. Pair with --sql to provide the query. Example: hematite --query-data employees.csv --sql \"SELECT department, COUNT(*) FROM data GROUP BY department\""
577    )]
578    pub query_data: Option<String>,
579
580    #[arg(
581        long,
582        help_heading = "Headless Reports",
583        value_name = "QUERY",
584        help = "SQL query to run against the file specified by --query-data. The table is always named 'data'. Example: --sql \"SELECT AVG(salary) FROM data WHERE department='Engineering'\""
585    )]
586    pub sql: Option<String>,
587
588    #[arg(
589        long,
590        help_heading = "Headless Reports",
591        value_name = "FILE",
592        help = "Generate a chart from a data file — no model, no cloud. Supports CSV, TSV, JSON, and SQLite. Uses matplotlib when available; falls back to a pure-Python SVG generator. Example: hematite --plot data.csv --plot-type histogram --plot-x age"
593    )]
594    pub plot: Option<String>,
595
596    #[arg(
597        long,
598        help_heading = "Headless Reports",
599        value_name = "TYPE",
600        default_value = "histogram",
601        help = "Chart type for --plot: histogram, scatter, line, or bar. Default: histogram."
602    )]
603    pub plot_type: Option<String>,
604
605    #[arg(
606        long,
607        help_heading = "Headless Reports",
608        value_name = "COLUMN",
609        help = "X-axis column name for --plot. Auto-detected from numeric columns if omitted."
610    )]
611    pub plot_x: Option<String>,
612
613    #[arg(
614        long,
615        help_heading = "Headless Reports",
616        value_name = "COLUMN",
617        help = "Y-axis column name for --plot (scatter/line charts). Auto-detected if omitted."
618    )]
619    pub plot_y: Option<String>,
620
621    #[arg(
622        long,
623        help_heading = "Headless Reports",
624        value_name = "TEXT",
625        help = "Chart title for --plot (auto-generated if omitted)."
626    )]
627    pub plot_title: Option<String>,
628
629    #[arg(
630        long,
631        help_heading = "Headless Reports",
632        value_name = "FILE",
633        help = "Output SVG file path for --plot (default: <input>_plot.svg)."
634    )]
635    pub plot_output: Option<String>,
636
637    #[arg(
638        long,
639        help_heading = "Headless Reports",
640        value_name = "QUERY",
641        help = "Monte Carlo simulation — instant, no model. Modes: 'pi N' (estimate π), 'birthday N', 'dice 2d6 1000', 'ruin P START GOAL N', 'walk WALKS STEPS'. Example: hematite --simulate 'pi 1000000'  'dice 2d6+3 5000'  'birthday 30'"
642    )]
643    pub simulate: Option<String>,
644
645    #[arg(
646        long,
647        help_heading = "Data Analysis",
648        value_name = "FILE",
649        help = "Discrete Fourier Transform on a numeric column — finds dominant frequencies. Use --fourier-col COL, --fourier-top N, --fourier-rate Hz. Example: hematite --fourier signal.csv --fourier-col value --fourier-top 10 --fourier-rate 44100"
650    )]
651    pub fourier: Option<String>,
652
653    #[arg(
654        long,
655        help_heading = "Data Analysis",
656        value_name = "COL",
657        help = "Column to analyze with --fourier (auto-detected if omitted)."
658    )]
659    pub fourier_col: Option<String>,
660
661    #[arg(
662        long,
663        help_heading = "Data Analysis",
664        value_name = "N",
665        help = "Number of top frequency components to report for --fourier (default 10)."
666    )]
667    pub fourier_top: Option<usize>,
668
669    #[arg(
670        long,
671        help_heading = "Data Analysis",
672        value_name = "HZ",
673        help = "Sample rate in Hz for --fourier (default 1.0 — reports normalized frequencies)."
674    )]
675    pub fourier_rate: Option<f64>,
676
677    #[arg(
678        long,
679        help_heading = "Data Analysis",
680        value_name = "FILE",
681        help = "k-Means clustering on numeric columns. Use --cluster-k N (default 3), --cluster-cols COL1,COL2,..., --cluster-output FILE. Example: hematite --cluster data.csv --cluster-k 4 --cluster-cols height,weight --cluster-output labeled.csv"
682    )]
683    pub cluster: Option<String>,
684
685    #[arg(
686        long,
687        help_heading = "Data Analysis",
688        value_name = "N",
689        help = "Number of clusters for --cluster (default 3)."
690    )]
691    pub cluster_k: Option<usize>,
692
693    #[arg(
694        long,
695        help_heading = "Data Analysis",
696        value_name = "COL1,COL2,...",
697        help = "Feature columns for --cluster, comma-separated (default: all numeric)."
698    )]
699    pub cluster_cols: Option<String>,
700
701    #[arg(
702        long,
703        help_heading = "Data Analysis",
704        value_name = "FILE",
705        help = "Output CSV with cluster labels appended for --cluster."
706    )]
707    pub cluster_output: Option<String>,
708
709    #[arg(
710        long,
711        help_heading = "Data Analysis",
712        value_name = "FILE",
713        help = "Normalize/standardize numeric columns. Use --normalize-method minmax|zscore|robust, --normalize-cols COL1,COL2,..., --normalize-output FILE. Example: hematite --normalize data.csv --normalize-method zscore --normalize-output scaled.csv"
714    )]
715    pub normalize: Option<String>,
716
717    #[arg(
718        long,
719        help_heading = "Data Analysis",
720        value_name = "METHOD",
721        help = "Normalization method: minmax (default), zscore, robust."
722    )]
723    pub normalize_method: Option<String>,
724
725    #[arg(
726        long,
727        help_heading = "Data Analysis",
728        value_name = "COL1,COL2,...",
729        help = "Columns to normalize for --normalize, comma-separated (default: all numeric)."
730    )]
731    pub normalize_cols: Option<String>,
732
733    #[arg(
734        long,
735        help_heading = "Data Analysis",
736        value_name = "FILE",
737        help = "Output CSV with normalized values for --normalize."
738    )]
739    pub normalize_output: Option<String>,
740
741    #[arg(
742        long,
743        help_heading = "Data Analysis",
744        value_name = "FILE",
745        help = "Run PCA on a CSV/TSV file. Reports eigenvalues, variance explained per component, and top loadings. Example: hematite --pca data.csv --pca-components 3"
746    )]
747    pub pca: Option<String>,
748
749    #[arg(
750        long,
751        help_heading = "Data Analysis",
752        value_name = "N",
753        help = "Number of principal components to compute for --pca (default: 3)."
754    )]
755    pub pca_components: Option<usize>,
756
757    #[arg(
758        long,
759        help_heading = "Data Analysis",
760        value_name = "COL1,COL2,...",
761        help = "Columns to include in --pca, comma-separated (default: all numeric)."
762    )]
763    pub pca_cols: Option<String>,
764
765    #[arg(
766        long,
767        help_heading = "Data Analysis",
768        value_name = "FILE",
769        help = "Output CSV with projected coordinates for --pca."
770    )]
771    pub pca_output: Option<String>,
772
773    #[arg(
774        long,
775        help_heading = "Math & Science",
776        value_name = "QUERY",
777        help = "Graph theory — parse an edge list and run BFS/DFS/Dijkstra/components/topo-sort. Example: hematite --graph 'shortest A D\\nA B 2\\nB D 3'"
778    )]
779    pub graph: Option<String>,
780
781    #[arg(
782        long,
783        help_heading = "Math & Science",
784        value_name = "QUERY",
785        help = "Symbolic calculus — differentiate, integrate, simplify, or evaluate. Example: hematite --symbolic 'diff x^3 + sin(x)'  or  'integrate 3*x^2'  or  'x^2+1 at x=5'"
786    )]
787    pub symbolic: Option<String>,
788
789    #[arg(
790        long,
791        help_heading = "Math & Science",
792        value_name = "QUERY",
793        help = "Financial math — NPV, IRR, loan amortization, compound interest, bond pricing, Black-Scholes. Example: hematite --finance 'loan 200000 6.5% 30'  or  'bs 100 100 5% 20% 1 call'"
794    )]
795    pub finance: Option<String>,
796
797    #[arg(
798        long,
799        help_heading = "Math & Science",
800        value_name = "QUERY",
801        help = "Propositional logic — truth table, SAT, tautology, CNF/DNF, equivalence, simplify. Example: hematite --logic 'A and (B or not C)'  or  'equiv A->B ; not A or B'"
802    )]
803    pub logic: Option<String>,
804
805    #[arg(
806        long,
807        help_heading = "Math & Science",
808        value_name = "QUERY",
809        help = "Signal processing (DSP) — DFT, convolution, cross-correlation, moving average, FIR filter design, waveform generation. No model, no cloud. Example: hematite --signal 'dft 1,0,-1,0'  or  'lowpass 0.1 31 hamming'  or  'gen sine 2 64'"
810    )]
811    pub signal: Option<String>,
812
813    #[arg(
814        long,
815        help_heading = "Math & Science",
816        value_name = "QUERY",
817        help = "Interpolation & curve fitting — linear, cubic spline, Lagrange polynomial, nearest-neighbor, with ASCII curve preview. Example: hematite --interpolate 'spline 0,0 1,1 2,4 3,9 at 1.5'  or  'linear 0,0 10,100 at 3,7'"
818    )]
819    pub interpolate: Option<String>,
820
821    #[arg(
822        long,
823        help_heading = "Math & Science",
824        value_name = "QUERY",
825        help = "Unit conversion — 14 categories, 130+ units. Length, mass, temperature, energy, digital storage, pressure, angle, and more. Example: hematite --units '100 km to miles'  or  '98.6 f to c'  or  '5 kg'  or  'list length'"
826    )]
827    pub units: Option<String>,
828
829    #[arg(
830        long,
831        help_heading = "Math & Science",
832        value_name = "QUERY",
833        help = "ODE solver — solves ordinary differential equations using Euler, RK4, or adaptive RK45. Preset models: logistic, exponential, Lotka-Volterra, SIR. Example: hematite --ode 'dy/dt = -y  y0=1  t=5'  or  'logistic r=1 K=100 y0=5 t=10'"
834    )]
835    pub ode: Option<String>,
836
837    #[arg(
838        long,
839        help_heading = "Math & Science",
840        value_name = "QUERY",
841        help = "Numerical optimization — minimize/maximize 1D/2D functions, gradient descent, root finding. No model. Example: hematite --optimize 'min x^2-4*x+3 a=0 b=5'  or  'max sin(x) a=0 b=6.28'  or  'root x^3-2 a=0 b=2'"
842    )]
843    pub optimize: Option<String>,
844
845    #[arg(
846        long,
847        help_heading = "Data Analysis",
848        value_name = "DATA",
849        help = "Statistical hypothesis test — t-tests, chi-square, ANOVA, Mann-Whitney, Pearson, proportion z-test, confidence intervals. Provide comma-separated numbers or 'successes,n' for proportions. Example: hematite --hypothesis '2.1,2.8,3.2,2.5' --hypothesis-test one-t --hypothesis-mu 2.0"
850    )]
851    pub hypothesis: Option<String>,
852
853    #[arg(
854        long,
855        help_heading = "Data Analysis",
856        value_name = "TYPE",
857        help = "Test type for --hypothesis. Options: one-t two-t paired chi2 anova mannwhitney pearson proportion prop2 ci. Default: one-t."
858    )]
859    pub hypothesis_test: Option<String>,
860
861    #[arg(
862        long,
863        help_heading = "Data Analysis",
864        value_name = "DATA",
865        help = "Second group data for --hypothesis (two-t, paired, mannwhitney, pearson, prop2). Comma-separated numbers or 'successes,n'."
866    )]
867    pub hypothesis_group2: Option<String>,
868
869    #[arg(
870        long,
871        help_heading = "Data Analysis",
872        value_name = "ALPHA",
873        help = "Significance level for --hypothesis (default: 0.05)."
874    )]
875    pub hypothesis_alpha: Option<f64>,
876
877    #[arg(
878        long,
879        help_heading = "Data Analysis",
880        value_name = "MU",
881        help = "Null hypothesis mean or proportion for --hypothesis one-t or proportion tests (default: 0.0)."
882    )]
883    pub hypothesis_mu: Option<f64>,
884
885    #[arg(
886        long,
887        help_heading = "Data Analysis",
888        value_name = "FILE",
889        help = "Classification (k-NN or Naive Bayes) — train on labeled CSV, LOO cross-validate, predict new samples. Example: hematite --classify data.csv --classify-label species --classify-k 3"
890    )]
891    pub classify: Option<String>,
892    #[arg(
893        long,
894        value_name = "COL",
895        help = "Label column for --classify (default: last column)."
896    )]
897    pub classify_label: Option<String>,
898    #[arg(
899        long,
900        value_name = "COL1,COL2,...",
901        help = "Feature columns for --classify (default: all except label)."
902    )]
903    pub classify_cols: Option<String>,
904    #[arg(
905        long,
906        value_name = "V1,V2,...",
907        help = "Predict class for this comma-separated feature vector."
908    )]
909    pub classify_predict: Option<String>,
910    #[arg(
911        long,
912        value_name = "N",
913        help = "k neighbors for --classify k-NN (default: 3)."
914    )]
915    pub classify_k: Option<usize>,
916    #[arg(
917        long,
918        value_name = "METHOD",
919        help = "Algorithm for --classify: knn (default) or nb (Naive Bayes)."
920    )]
921    pub classify_method: Option<String>,
922
923    #[arg(
924        long,
925        help_heading = "Data Analysis",
926        value_name = "FILE",
927        help = "Polynomial curve fit — fit a degree-N polynomial to two CSV columns, compute R², RMSE, ASCII scatter+curve plot, and residual plot. Example: hematite --polyfit data.csv --polyfit-x age --polyfit-y salary --polyfit-degree 2"
928    )]
929    pub polyfit: Option<String>,
930    #[arg(
931        long,
932        value_name = "COL",
933        help = "X column for --polyfit (default: first column)."
934    )]
935    pub polyfit_x: Option<String>,
936    #[arg(
937        long,
938        value_name = "COL",
939        help = "Y column for --polyfit (default: last column)."
940    )]
941    pub polyfit_y: Option<String>,
942    #[arg(
943        long,
944        value_name = "N",
945        help = "Polynomial degree for --polyfit (default: 1 = linear, max: 10)."
946    )]
947    pub polyfit_degree: Option<usize>,
948    #[arg(
949        long,
950        value_name = "X1,X2,...",
951        help = "Predict y values for these x values with --polyfit."
952    )]
953    pub polyfit_predict: Option<String>,
954
955    #[arg(
956        long,
957        help_heading = "Math & Science",
958        value_name = "QUERY",
959        help = "Probability distribution calculator — instant, no model, no cloud. Distributions: normal, binomial, poisson, t (Student's), chi2, exponential, uniform, geometric. Operations: pdf/pmf, cdf, quantile, table, all. Examples: hematite --probability 'normal mean=0 sd=1 x=1.96' | hematite --probability 'binomial n=10 p=0.3 k=4' | hematite --probability 'poisson lambda=3 all' | hematite --probability 't df=9 x=2.262 cdf'"
960    )]
961    pub probability: Option<String>,
962
963    #[arg(
964        long,
965        help_heading = "Math & Science",
966        value_name = "QUERY",
967        help = "Bitwise calculator — instant, no model, no cloud. Inspect any integer in decimal/hex/binary/octal with full bit breakdown (popcount, parity, leading/trailing zeros, two's complement, byte decomposition). Operations: AND, OR, XOR, NOT, SHL, SHR, ROL, ROR. Also: IEEE 754 float bit-pattern analysis. Examples: hematite --bitwise '0xFF AND 0x3C' | hematite --bitwise 'NOT 0xAB' | hematite --bitwise '1 SHL 7' | hematite --bitwise 'ieee754 3.14159'"
968    )]
969    pub bitwise: Option<String>,
970
971    #[arg(
972        long,
973        help_heading = "Math & Science",
974        value_name = "QUERY",
975        help = "Set theory calculator — instant, no model, no cloud. Operations: union, intersection, difference, symmetric difference, power set, Cartesian product, subset/superset/disjoint checks. Elements can be numbers or strings. Examples: hematite --set '{1,2,3} union {3,4,5}' | hematite --set 'powerset {a,b,c}' | hematite --set 'cartesian {1,2} x {a,b,c}'"
976    )]
977    pub set: Option<String>,
978
979    #[arg(
980        long,
981        help_heading = "Math & Science",
982        value_name = "QUERY",
983        help = "Classical cipher encoder/decoder — instant, no model, no cloud. Ciphers: ROT13, Atbash, Caesar (with full brute-force table), Vigenère (encode/decode), Rail Fence (encode/decode), Columnar Transposition, Morse Code. Examples: hematite --cipher 'rot13 Hello World' | hematite --cipher 'caesar 13 Hello' | hematite --cipher 'vigenere encode KEY plaintext' | hematite --cipher 'morse encode Hello'"
984    )]
985    pub cipher: Option<String>,
986
987    #[arg(
988        long,
989        help_heading = "Math & Science",
990        value_name = "TEXT",
991        help = "Text statistics and readability analyzer — instant, no model, no cloud. Computes: character/word/sentence/paragraph counts, syllable count, average word length, Flesch Reading Ease, Flesch-Kincaid Grade Level, Gunning Fog Index, SMOG Index, Coleman-Liau Index, top-20 word frequency, letter frequency, longest words. Example: hematite --text-stats 'Paste or type any text here...'"
992    )]
993    pub text_stats: Option<String>,
994
995    #[arg(
996        long,
997        help_heading = "Math & Science",
998        value_name = "QUERY",
999        help = "String distance metrics — instant, no model, no cloud. Computes: Levenshtein, Damerau-Levenshtein, Hamming, Jaro, Jaro-Winkler, LCS similarity, longest common substring. Separate the two strings with ' vs ' or ','. Examples: hematite --levenshtein 'kitten vs sitting' | hematite --levenshtein 'hello, helo'"
1000    )]
1001    pub levenshtein: Option<String>,
1002
1003    #[arg(
1004        long,
1005        help_heading = "Math & Science",
1006        value_name = "NUMBER",
1007        help = "Number format converter — instant, no model, no cloud. Shows every representation of a number: thousands-separated decimal, scientific notation, engineering notation, SI prefix, hex/binary/octal (integers), English word form, log₁₀, ln, square root, reciprocal. Examples: hematite --number-format 1234567890 | hematite --number-format 6.022e23 | hematite --number-format 0xFF"
1008    )]
1009    pub number_format: Option<String>,
1010
1011    #[arg(
1012        long,
1013        help_heading = "Math & Science",
1014        value_name = "QUERY",
1015        help = "Sorting algorithm visualizer — instant, no model, no cloud. Shows step-by-step ASCII bar-chart visualization with comparison and swap counts. Algorithms: bubble, insertion, selection, merge, quick, heap. Pass all to compare all 6. Examples: hematite --sort-viz '5,3,8,1,9,2' | hematite --sort-viz 'bubble 5,3,8,1,9,2' | hematite --sort-viz 'merge 9,7,5,3,1'"
1016    )]
1017    pub sort_viz: Option<String>,
1018
1019    #[arg(
1020        long,
1021        help_heading = "Math & Science",
1022        value_name = "TEXT",
1023        help = "Checksum calculator — instant, no model, no cloud. Computes CRC-32, CRC-16 (CCITT), Adler-32, FNV-1a 32/64, DJB2, SDBM, XOR-8/16, and Sum-8/16/32 checksums for any string. Also shows hex dump, min/max/avg byte values. Examples: hematite --checksum 'Hello, World!' | hematite --checksum '123456789'"
1024    )]
1025    pub checksum: Option<String>,
1026
1027    #[arg(
1028        long,
1029        help_heading = "Math & Science",
1030        value_name = "VALUE",
1031        help = "Validation toolkit — instant, no model, no cloud. Validates: Luhn algorithm (credit card numbers + card network detection), ISBN-10, ISBN-13/EAN-13, IBAN (all countries), UUID format and version. Shows check digit corrections for invalid values. Examples: hematite --validate '4532015112830366' | hematite --validate '978-0-306-40615-7' | hematite --validate 'GB82WEST12345698765432'"
1032    )]
1033    pub validate: Option<String>,
1034
1035    #[arg(
1036        long,
1037        help_heading = "Headless Reports",
1038        value_name = "ELEMENT",
1039        help = "Look up a periodic table element — instant, no model, no cloud. Accepts symbol (H, Au), full name (Gold, Hydrogen), or atomic number (79). Shows atomic mass, category, period/group, electronegativity, and state at STP. Example: hematite --periodic Au"
1040    )]
1041    pub periodic: Option<String>,
1042
1043    #[arg(
1044        long,
1045        help_heading = "Headless Reports",
1046        value_name = "TARGET",
1047        help = "Compute MD5/SHA1/SHA256/SHA512 checksums of a file or text string. If TARGET is an existing file path, the file is hashed; otherwise the literal text is hashed. Pair with --hash-algo to select a single algorithm. Examples: hematite --hash installer.exe, hematite --hash \"hello world\""
1048    )]
1049    pub hash: Option<String>,
1050
1051    #[arg(
1052        long,
1053        help_heading = "Headless Reports",
1054        value_name = "ALGO",
1055        default_value = "all",
1056        help = "Hash algorithm for --hash: md5, sha1, sha256, sha512, or 'all' (default). Example: hematite --hash file.zip --hash-algo sha256"
1057    )]
1058    pub hash_algo: Option<String>,
1059
1060    #[arg(
1061        long,
1062        help_heading = "Headless Reports",
1063        value_name = "TEXT",
1064        help = "Encode text to a specified format. Pair with --codec (default: base64). Supported codecs: base64, hex, url, rot13, html, binary. Examples: hematite --encode \"hello world\", hematite --encode \"hello\" --codec hex"
1065    )]
1066    pub encode: Option<String>,
1067
1068    #[arg(
1069        long,
1070        help_heading = "Headless Reports",
1071        value_name = "TEXT",
1072        help = "Decode text from a specified format. Pair with --codec (default: base64). Supported codecs: base64, hex, url, rot13, html, binary. Examples: hematite --decode \"aGVsbG8gd29ybGQ=\", hematite --decode \"68656c6c6f\" --codec hex"
1073    )]
1074    pub decode: Option<String>,
1075
1076    #[arg(
1077        long,
1078        help_heading = "Headless Reports",
1079        value_name = "FORMAT",
1080        help = "Encoding format for --encode and --decode: base64 (default), hex, url, rot13, html, binary."
1081    )]
1082    pub codec: Option<String>,
1083
1084    #[arg(
1085        long,
1086        help_heading = "Headless Reports",
1087        value_name = "QUERY",
1088        help = "Search the built-in formula library — no model, no cloud. Pass a name, category, or keyword. Run --formula list to browse all entries. Examples: hematite --formula \"kinetic energy\", hematite --formula ohms, hematite --formula mechanics"
1089    )]
1090    pub formula: Option<String>,
1091
1092    #[arg(
1093        long,
1094        help_heading = "Headless Reports",
1095        value_name = "TYPE",
1096        help = "Generate cryptographically secure random values — no model, no cloud. Types: uuid  password  token  hex  urlsafe  pin  bytes  int  dice. Examples: hematite --random uuid, hematite --random password --length 24, hematite --random dice --random-args 2d6"
1097    )]
1098    pub random: Option<String>,
1099
1100    #[arg(
1101        long,
1102        help_heading = "Headless Reports",
1103        value_name = "N",
1104        help = "Length for --random password/token/pin/bytes generation. Default: 20 for passwords, 32 for tokens, 6 for PINs."
1105    )]
1106    pub length: Option<usize>,
1107
1108    #[arg(
1109        long,
1110        help_heading = "Headless Reports",
1111        value_name = "ARGS",
1112        help = "Extra arguments for --random: dice notation (2d6, d20), int range (1 100), or custom charset for passwords."
1113    )]
1114    pub random_args: Option<String>,
1115
1116    #[arg(
1117        long,
1118        help_heading = "Headless Reports",
1119        value_name = "FILES",
1120        help = "Row-level diff of two data files — no model, no cloud. Pass comma-separated paths: file_a.csv,file_b.csv. Supports CSV, TSV, JSON, and SQLite. Pair with --diff-key to set the key column. Example: hematite --diff-data before.csv,after.csv"
1121    )]
1122    pub diff_data: Option<String>,
1123
1124    #[arg(
1125        long,
1126        help_heading = "Headless Reports",
1127        value_name = "COLUMN",
1128        help = "Key column for --diff-data row matching. Defaults to the first column. Example: --diff-key id"
1129    )]
1130    pub diff_key: Option<String>,
1131
1132    #[arg(
1133        long,
1134        help_heading = "Headless Reports",
1135        value_name = "FILE",
1136        help = "Descriptive statistics for numeric columns in a data file — no model, no cloud. Supports CSV, TSV, JSON, SQLite. Pair with --column to focus on one column. Example: hematite --describe sales.csv --column revenue"
1137    )]
1138    pub describe: Option<String>,
1139
1140    #[arg(
1141        long,
1142        help_heading = "Headless Reports",
1143        value_name = "NAME",
1144        help = "Column name to analyze with --stats. If omitted, all numeric columns are summarized."
1145    )]
1146    pub column: Option<String>,
1147
1148    #[arg(
1149        long,
1150        help_heading = "Headless Reports",
1151        value_name = "OP",
1152        help = "Matrix operation — no model, no cloud. OP: det  inv  transpose  multiply  solve  eigenvalues  rank  trace. Pass matrix as JSON: --matrix det --matrix-a '[[1,2],[3,4]]'. Example: hematite --matrix det --matrix-a '[[1,2],[3,4]]'"
1153    )]
1154    pub matrix: Option<String>,
1155
1156    #[arg(
1157        long,
1158        value_name = "JSON",
1159        help = "Matrix A for --matrix, as a JSON array of rows: '[[1,2],[3,4]]'"
1160    )]
1161    pub matrix_a: Option<String>,
1162
1163    #[arg(
1164        long,
1165        value_name = "JSON",
1166        help = "Matrix B for --matrix multiply or solve: '[[5],[6]]'"
1167    )]
1168    pub matrix_b: Option<String>,
1169
1170    #[arg(
1171        long,
1172        help_heading = "Headless Reports",
1173        value_name = "EQUATION",
1174        help = "Solve an equation numerically — no model, no cloud. Format: 'LHS = RHS' or expression = 0. Variable defaults to x. Supports sin/cos/sqrt/log/exp/pi/e. Example: hematite --solve 'x^2 - 4 = 0'  or  --solve '2*x + 3 = 11'"
1175    )]
1176    pub solve: Option<String>,
1177
1178    #[arg(
1179        long,
1180        value_name = "VAR",
1181        help = "Variable name for --solve. Default: x. Example: --solve 't^2 = 16' --solve-var t"
1182    )]
1183    pub solve_var: Option<String>,
1184
1185    #[arg(
1186        long,
1187        value_name = "LO,HI",
1188        help = "Search range for --solve as 'lo,hi'. Default: -1000,1000. Example: --solve-range '-100,100'"
1189    )]
1190    pub solve_range: Option<String>,
1191
1192    #[arg(
1193        long,
1194        help_heading = "Headless Reports",
1195        value_name = "FILE",
1196        help = "Fit a curve to two columns of data — no model, no cloud. Tries linear, polynomial, exponential, power, and log models and ranks by R². Pair with --fit-x, --fit-y, --fit-model. Example: hematite --curve-fit data.csv --fit-x time --fit-y temperature"
1197    )]
1198    pub curve_fit: Option<String>,
1199
1200    #[arg(
1201        long,
1202        value_name = "COL",
1203        help = "X column for --curve-fit. Defaults to first numeric column."
1204    )]
1205    pub fit_x: Option<String>,
1206
1207    #[arg(
1208        long,
1209        value_name = "COL",
1210        help = "Y column for --curve-fit. Defaults to second numeric column."
1211    )]
1212    pub fit_y: Option<String>,
1213
1214    #[arg(
1215        long,
1216        value_name = "MODEL",
1217        help = "Model for --curve-fit: linear  poly2  poly3  exp  power  log  auto (default: auto, tries all)"
1218    )]
1219    pub fit_model: Option<String>,
1220
1221    #[arg(
1222        long,
1223        help_heading = "Headless Reports",
1224        value_name = "EXPR",
1225        help = "Numerically integrate an expression — no model, no cloud. Uses adaptive Simpson's rule. Pair with --from, --to, --int-var. Example: hematite --integrate 'sin(x)' --from 0 --to pi  or  --integrate 'x^2' --from 0 --to 3"
1226    )]
1227    pub integrate: Option<String>,
1228
1229    #[arg(
1230        long,
1231        value_name = "N",
1232        help = "Lower bound for --integrate. Example: --int-from 0"
1233    )]
1234    pub int_from: Option<String>,
1235
1236    #[arg(
1237        long,
1238        value_name = "N",
1239        help = "Upper bound for --integrate. Example: --int-to pi"
1240    )]
1241    pub int_to: Option<String>,
1242
1243    #[arg(
1244        long,
1245        value_name = "VAR",
1246        help = "Integration variable for --integrate. Default: x."
1247    )]
1248    pub int_var: Option<String>,
1249
1250    #[arg(
1251        long,
1252        value_name = "N",
1253        help = "Number of intervals for --integrate (default: 1000). Adaptive Simpson uses this as fallback."
1254    )]
1255    pub int_n: Option<usize>,
1256
1257    #[arg(
1258        long,
1259        help_heading = "Headless Reports",
1260        value_name = "EXPR",
1261        help = "Numerically differentiate an expression — no model, no cloud. Uses 5-point stencil. Pair with --at and optionally --order. Example: hematite --differentiate 'x^3 + 2*x' --at 2  or  --differentiate 'sin(x)' --at 'pi/2'"
1262    )]
1263    pub differentiate: Option<String>,
1264
1265    #[arg(
1266        long,
1267        value_name = "X",
1268        help = "Point at which to evaluate --differentiate or --solve. Example: --at 3.14"
1269    )]
1270    pub at: Option<String>,
1271
1272    #[arg(
1273        long,
1274        value_name = "N",
1275        help = "Derivative order for --differentiate (1st, 2nd, 3rd, 4th). Default: 1."
1276    )]
1277    pub order: Option<u8>,
1278
1279    #[arg(
1280        long,
1281        help_heading = "Headless Reports",
1282        value_name = "FILE",
1283        help = "AI-free data profile — type detection, missing values, ranges, outliers, and duplicate rows. No model, no cloud. Supports CSV, TSV, JSON, SQLite. Example: hematite --profile customers.csv"
1284    )]
1285    pub profile: Option<String>,
1286
1287    #[arg(
1288        long,
1289        help_heading = "Headless Reports",
1290        value_name = "N",
1291        help = "Prime number info — no model, no cloud. Primality test, factorization, divisors, Euler's φ, σ(n), nearest primes. Example: hematite --prime 97  or  --prime 360"
1292    )]
1293    pub prime: Option<u64>,
1294
1295    #[arg(
1296        long,
1297        help_heading = "Headless Reports",
1298        value_name = "TYPE",
1299        help = "Generate a numeric sequence — no model, no cloud. Types: arithmetic  geometric  fibonacci  prime  square  triangular  cube  power2. Pair with --seq-count, --seq-start, --seq-step. Example: hematite --sequence fibonacci --seq-count 20"
1300    )]
1301    pub sequence: Option<String>,
1302
1303    #[arg(
1304        long,
1305        value_name = "N",
1306        help = "Number of terms for --sequence (default: 10)."
1307    )]
1308    pub seq_count: Option<usize>,
1309
1310    #[arg(
1311        long,
1312        value_name = "N",
1313        help = "Starting value for --sequence (default: 1)."
1314    )]
1315    pub seq_start: Option<f64>,
1316
1317    #[arg(
1318        long,
1319        value_name = "N",
1320        help = "Step or ratio for --sequence (default: 1 for arithmetic, 2 for geometric)."
1321    )]
1322    pub seq_step: Option<f64>,
1323
1324    #[arg(
1325        long,
1326        help_heading = "Headless Reports",
1327        value_name = "N K",
1328        help = "Combinations and permutations — no model, no cloud. Computes C(n,k) and P(n,k). Pass two integers separated by a space or comma. Example: hematite --choose '10 3'  or  --choose '52,5'"
1329    )]
1330    pub choose: Option<String>,
1331
1332    #[arg(
1333        long,
1334        help_heading = "Headless Reports",
1335        value_name = "EXPR",
1336        help = "Boolean truth table — no model, no cloud. Variables are single letters (A, B, C). Operators: AND OR NOT XOR NAND NOR (or ∧ ∨ ¬ ⊕). Example: hematite --truth-table '(A AND B) OR NOT C'"
1337    )]
1338    pub truth_table: Option<String>,
1339
1340    #[arg(
1341        long,
1342        help_heading = "Headless Reports",
1343        value_name = "A,B",
1344        help = "GCD and LCM of two integers — no model, no cloud. Example: hematite --gcd '48,18'  or  --gcd '360 252'"
1345    )]
1346    pub gcd: Option<String>,
1347
1348    #[arg(
1349        long,
1350        help_heading = "Headless Reports",
1351        value_name = "N or ROMAN",
1352        help = "Roman numeral conversion — no model, no cloud. Pass a number to encode or a Roman numeral to decode. Example: hematite --roman 2024  or  --roman MMXXIV"
1353    )]
1354    pub roman: Option<String>,
1355
1356    #[arg(
1357        long,
1358        help_heading = "Headless Reports",
1359        value_name = "N",
1360        help = "Number base conversion — no model, no cloud. Pair with --base-from and --base-to. Default: --base-from 10 --base-to 2. Example: hematite --base-convert 255 --base-to 16  or  --base-convert FF --base-from 16 --base-to 10"
1361    )]
1362    pub base_convert: Option<String>,
1363
1364    #[arg(
1365        long,
1366        value_name = "N",
1367        help = "Source base for --base-convert (2–36). Default: 10."
1368    )]
1369    pub base_from: Option<u32>,
1370
1371    #[arg(
1372        long,
1373        value_name = "N",
1374        help = "Target base for --base-convert (2–36). Default: 2."
1375    )]
1376    pub base_to: Option<u32>,
1377
1378    #[arg(
1379        long,
1380        help_heading = "Headless Reports",
1381        value_name = "EXPR",
1382        help = "Date arithmetic and calendar info — no model, no cloud. Examples: hematite --date '2024-01-01 to 2024-12-31'  --date '2024-03-15 +90'  --date '2024-06-15'  --date 'unix 1700000000'"
1383    )]
1384    pub date: Option<String>,
1385
1386    #[arg(
1387        long,
1388        help_heading = "Headless Reports",
1389        value_name = "CIDR",
1390        help = "IPv4 subnet calculator — no model, no cloud. Pass a CIDR address. Example: hematite --subnet 192.168.1.0/24  or  --subnet 10.0.0.1/8"
1391    )]
1392    pub subnet: Option<String>,
1393
1394    #[arg(
1395        long,
1396        help_heading = "Headless Reports",
1397        value_name = "COLOR",
1398        help = "Color space conversion — no model, no cloud. Converts hex/RGB to HSL, HSV, CMYK, and WCAG luminance. Example: hematite --color '#ff8800'  or  --color 'rgb(255,136,0)'  or  --color '3f8'"
1399    )]
1400    pub color: Option<String>,
1401
1402    #[arg(
1403        long,
1404        help_heading = "Headless Reports",
1405        value_name = "FORMULA",
1406        help = "Molecular weight from a chemical formula — no model, no cloud. Supports nested groups: Ca(NO3)2, (NH4)2SO4. Example: hematite --mw H2O  or  --mw 'C6H12O6'"
1407    )]
1408    pub mw: Option<String>,
1409
1410    #[arg(
1411        long,
1412        help_heading = "Headless Reports",
1413        value_name = "NAME",
1414        help = "Physical constants lookup — no model, no cloud. Use 'list' to see all. Example: hematite --const c  --const planck  --const avogadro  --const list"
1415    )]
1416    pub r#const: Option<String>,
1417
1418    #[arg(
1419        long,
1420        help_heading = "Headless Reports",
1421        value_name = "QUERY",
1422        help = "Standard normal distribution — no model, no cloud. Modes: 'cdf X [mu sigma]'  'pdf X'  'inv P'  'between A B'  'table'. Example: hematite --normal 'cdf 1.96'  --normal 'inv 0.975'  --normal table"
1423    )]
1424    pub normal: Option<String>,
1425
1426    #[arg(
1427        long,
1428        help_heading = "Headless Reports",
1429        value_name = "EXPR",
1430        help = "2D/3D vector math — instant, no model. Ops: dot, cross, +, -, scalar*, mag, norm, angle, proj. Example: hematite --vectors '[1,2,3] dot [4,5,6]'  'mag [3,4]'  '[1,2,3] cross [0,0,1]'"
1431    )]
1432    pub vectors: Option<String>,
1433
1434    #[arg(
1435        long,
1436        help_heading = "Headless Reports",
1437        value_name = "QUERY",
1438        help = "Number theory — instant, no model. Ops: extgcd, crt (Chinese Remainder Theorem), mobius, modinv, modpow, cf (continued fractions), goldbach, totient, jacobi. Example: hematite --number-theory 'modpow 3 10 1000'  'crt 2 3 3 5'  'goldbach 28'  'cf 355/113'  '42'"
1439    )]
1440    pub number_theory: Option<String>,
1441
1442    #[arg(
1443        long,
1444        help_heading = "Data Analysis",
1445        value_name = "FILE",
1446        help = "Percentile/quantile report for all numeric columns (or --percentile-col COL for a specific column). Example: hematite --percentile data.csv --percentile-col salary"
1447    )]
1448    pub percentile: Option<String>,
1449
1450    #[arg(
1451        long,
1452        help_heading = "Data Analysis",
1453        value_name = "COL",
1454        help = "Column to analyze with --percentile (default: all numeric columns)."
1455    )]
1456    pub percentile_col: Option<String>,
1457
1458    #[arg(
1459        long,
1460        help_heading = "Data Analysis",
1461        value_name = "FILE",
1462        help = "Pivot table — group rows by two columns and aggregate a value column. Use --pivot-row, --pivot-col, --pivot-val, --pivot-agg (count/sum/mean/min/max). Example: hematite --pivot sales.csv --pivot-row region --pivot-col quarter --pivot-val revenue --pivot-agg sum"
1463    )]
1464    pub pivot: Option<String>,
1465
1466    #[arg(
1467        long,
1468        help_heading = "Data Analysis",
1469        value_name = "COL",
1470        help = "Row grouping column for --pivot."
1471    )]
1472    pub pivot_row: Option<String>,
1473
1474    #[arg(
1475        long,
1476        help_heading = "Data Analysis",
1477        value_name = "COL",
1478        help = "Column grouping column for --pivot."
1479    )]
1480    pub pivot_col: Option<String>,
1481
1482    #[arg(
1483        long,
1484        help_heading = "Data Analysis",
1485        value_name = "COL",
1486        help = "Value column for --pivot aggregation."
1487    )]
1488    pub pivot_val: Option<String>,
1489
1490    #[arg(
1491        long,
1492        help_heading = "Data Analysis",
1493        value_name = "AGG",
1494        help = "Aggregation for --pivot: count (default), sum, mean, min, max."
1495    )]
1496    pub pivot_agg: Option<String>,
1497
1498    #[arg(
1499        long,
1500        help_heading = "Data Analysis",
1501        value_name = "FILE",
1502        help = "Multivariate OLS linear regression from a CSV/TSV/JSON/SQLite file. Use --regression-target to specify the dependent variable and --regression-predictors for a comma-separated list of independent variables. Example: hematite --regression data.csv --regression-target price --regression-predictors sqft,bedrooms,bathrooms"
1503    )]
1504    pub regression: Option<String>,
1505
1506    #[arg(
1507        long,
1508        help_heading = "Data Analysis",
1509        value_name = "COL",
1510        help = "Target (dependent) column for --regression (auto-detected if omitted)."
1511    )]
1512    pub regression_target: Option<String>,
1513
1514    #[arg(
1515        long,
1516        help_heading = "Data Analysis",
1517        value_name = "COL1,COL2,...",
1518        help = "Predictor (independent) columns for --regression, comma-separated (auto-detected if omitted)."
1519    )]
1520    pub regression_predictors: Option<String>,
1521
1522    #[arg(
1523        long,
1524        help_heading = "Data Analysis",
1525        value_name = "FILE",
1526        help = "Detect outliers using IQR (1.5× fence) and Z-score (|z|>3) in all numeric columns or a specific column. Use --outlier-col COL and --outlier-output FILE to save clean data. Example: hematite --outliers data.csv --outlier-col salary --outlier-output clean.csv"
1527    )]
1528    pub outliers: Option<String>,
1529
1530    #[arg(
1531        long,
1532        help_heading = "Data Analysis",
1533        value_name = "COL",
1534        help = "Column to analyze for --outliers (default: all numeric columns)."
1535    )]
1536    pub outlier_col: Option<String>,
1537
1538    #[arg(
1539        long,
1540        help_heading = "Data Analysis",
1541        value_name = "FILE",
1542        help = "Save clean data (outliers removed) to this CSV path."
1543    )]
1544    pub outlier_output: Option<String>,
1545
1546    #[arg(
1547        long,
1548        help_heading = "Data Analysis",
1549        value_name = "FILE",
1550        help = "Random-sample rows from a CSV/TSV/JSON/SQLite file. Use --sample-n or --sample-frac for size; --split for train/test; --sample-output DIR to save files. Example: hematite --sample data.csv --sample-n 200 --split 0.8 --sample-output out/"
1551    )]
1552    pub sample: Option<String>,
1553
1554    #[arg(
1555        long,
1556        help_heading = "Data Analysis",
1557        value_name = "N",
1558        help = "Number of rows to sample (default 100)."
1559    )]
1560    pub sample_n: Option<usize>,
1561
1562    #[arg(
1563        long,
1564        help_heading = "Data Analysis",
1565        value_name = "FRAC",
1566        help = "Fraction of rows to sample, e.g. 0.1 for 10%."
1567    )]
1568    pub sample_frac: Option<f64>,
1569
1570    #[arg(
1571        long,
1572        help_heading = "Data Analysis",
1573        value_name = "SEED",
1574        help = "Random seed for reproducible sampling (default 42)."
1575    )]
1576    pub sample_seed: Option<u64>,
1577
1578    #[arg(
1579        long,
1580        help_heading = "Data Analysis",
1581        value_name = "FRAC",
1582        help = "Train/test split fraction, e.g. 0.8 saves 80% to train and 20% to test. Requires --sample-output."
1583    )]
1584    pub split: Option<f64>,
1585
1586    #[arg(
1587        long,
1588        help_heading = "Data Analysis",
1589        value_name = "DIR",
1590        help = "Output directory for sampled files. If omitted, prints sample to stdout."
1591    )]
1592    pub sample_output: Option<String>,
1593
1594    #[arg(
1595        long,
1596        help_heading = "Data Analysis",
1597        value_name = "FILE",
1598        help = "Compute correlation matrix for all numeric columns in a file. Use --corr-method pearson|spearman. Example: hematite --correlation data.csv --corr-method spearman"
1599    )]
1600    pub correlation: Option<String>,
1601
1602    #[arg(
1603        long,
1604        help_heading = "Data Analysis",
1605        value_name = "METHOD",
1606        help = "Correlation method: pearson (default) or spearman."
1607    )]
1608    pub corr_method: Option<String>,
1609
1610    #[arg(
1611        long,
1612        help_heading = "Data Analysis",
1613        value_name = "FILE",
1614        help = "Time-series analysis: rolling mean, trend, peaks/valleys, sparkline. Example: hematite --timeseries sales.csv --ts-date date --ts-value revenue --ts-window 7"
1615    )]
1616    pub timeseries: Option<String>,
1617
1618    #[arg(
1619        long,
1620        help_heading = "Data Analysis",
1621        value_name = "COL",
1622        help = "Date column name for --timeseries (auto-detected if omitted)."
1623    )]
1624    pub ts_date: Option<String>,
1625
1626    #[arg(
1627        long,
1628        help_heading = "Data Analysis",
1629        value_name = "COL",
1630        help = "Value column name for --timeseries (auto-detected if omitted)."
1631    )]
1632    pub ts_value: Option<String>,
1633
1634    #[arg(
1635        long,
1636        help_heading = "Data Analysis",
1637        value_name = "N",
1638        help = "Rolling window size for --timeseries (default 7)."
1639    )]
1640    pub ts_window: Option<usize>,
1641
1642    #[arg(long, hide = true)]
1643    pub pdf_extract_helper: Option<String>,
1644
1645    #[arg(long, hide = true)]
1646    pub teleported_from: Option<String>,
1647}
1648
1649#[cfg(test)]
1650mod tests {
1651    #[test]
1652    fn version_report_contains_release_version() {
1653        let report = crate::hematite_version_report();
1654        assert!(report.contains(crate::HEMATITE_VERSION));
1655        assert!(report.contains("Build:"));
1656    }
1657
1658    #[test]
1659    fn about_report_contains_author_and_repo() {
1660        let report = crate::hematite_about_report();
1661        assert!(report.contains(crate::HEMATITE_AUTHOR));
1662        assert!(report.contains(crate::HEMATITE_REPOSITORY_URL));
1663    }
1664}