Skip to main content

degenbot_cli_core/
lib.rs

1//! `degenbot-cli-core` — the clap-free semantics home for the degenbot console
2//! (ADR-051 D1/D2).
3//!
4//! The console has two first-class front ends: the pure-Rust argv facade
5//! (`degenbot-cli`, clap) and the Python passthrough (`src/degenbot/_cli.py`).
6//! Both map argv into the SAME command model declared here, so command
7//! semantics — prompt policy, dry-run text, exit-code mapping — exist exactly
8//! once. This crate is deliberately **pyo3-free** (so the pure-Rust consumer can
9//! depend on it) and **clap-free/indicatif-free** (argv spelling and progress
10//! rendering are the facade's job; asserted by `just check-cli-core-purity`).
11//!
12//! # Shape
13//!
14//! - [`Command`] + the per-group enums: the constructor-parsed command model.
15//! - [`Command::execute`] / [`run_with_cancel`]: the execution entries,
16//!   returning a typed [`CommandReport`].
17//! - [`CliError`] → [`ExitCode`]: declared at exactly one `From` site; the
18//!   workspace `exit = "deny"` lint stands, so `run` returns codes and never
19//!   aborts the process. The typed fleet boot refusal (FF-T1) maps to
20//!   `EX_CONFIG` 78 (lifted out of `DegenbotCLI.invoke`).
21//! - [`PromptPlan`] + [`Prompter`]: interactive policy is declared data, ported
22//!   verbatim from the click handlers and audited, never redesigned (D4).
23//! - [`CancelHandle`]: the cooperative cancel carrier the updater arms thread
24//!   into `run_pool_update` / `run_aave_update` (the facade owns the SIGINT
25//!   policy, ADR-051 D7).
26//!
27//! # Groups
28//!
29//! - `database` ([`DatabaseCommand`]): the template group; ports
30//!   `src/degenbot/cli/database.py` arm for arm.
31//! - `exchange` ([`ExchangeCommand`]): the 34 Python click verbs collapse to
32//!   one data-driven command resolving `(chain, name)` through the
33//!   `degenbot-uniswap` deployments registry (ADR-051 D5).
34//! - `pool` ([`PoolCommand`]): the `degenbot-pool-updater` chunk loop +
35//!   on-chain-truth verify.
36//! - `aave` ([`AaveCommand`]): the `degenbot-aave` market run + row flips.
37//! - `fleet` ([`FleetCommand`]): the live cordon posture over the
38//!   operator command channel (ADR-051 D6).
39//! - `path` ([`PathCommand`]): live add-path / bounded discovery over
40//!   the same operator command channel.
41
42pub mod aave;
43pub mod block;
44pub mod cancel;
45pub mod command;
46pub mod context;
47pub mod database;
48pub mod error;
49pub mod exchange;
50pub mod fleet;
51pub mod operator;
52pub mod path;
53pub mod pool;
54pub mod prompt;
55pub mod report;
56
57pub use aave::{resolve_aave_deployment, AaveCommand, AaveDeployment, AAVE_DEPLOYMENTS};
58pub use block::{
59    parse_to_block, resolve_chain_selector, resolve_to_block, BlockTag, ToBlockSpec,
60    DEFAULT_CHUNK_SIZE, DEFAULT_TO_BLOCK, DEFAULT_VERIFY_ALL_INTERVAL,
61};
62pub use cancel::CancelHandle;
63pub use command::{Command, DatabaseCommand};
64pub use context::CliContext;
65pub use database::database_backup_path;
66pub use error::{CliError, ExitCode};
67pub use exchange::{
68    resolve_deployment, ExchangeCommand, ExchangeDeployment, PoolManagerDeployment,
69    RETIRED_EXCHANGES,
70};
71pub use fleet::FleetCommand;
72pub use operator::{
73    parse_hop_token, parse_sim_intake_floor, render_json_sorted, resolve_socket,
74    validate_posture_patch, PathDirection, PathFamily, PathStep, PosturePatchEntry,
75    PosturePatchValue, WireRequest, WireResponse, FLEET_POSTURE_THRESHOLD_KEYS,
76    SIM_INTAKE_FLOOR_RESTORE, SOCKET_DEFAULT, SOCKET_ENV,
77};
78pub use path::PathCommand;
79pub use pool::{PoolCommand, PoolFamily};
80pub use prompt::{PromptPlan, Prompter};
81pub use report::{
82    schema_state_label, AavePositionLine, AaveReport, AaveUpdateEntry, AaveUpdateOutcome,
83    ActivateOutcome, CommandOutcome, CommandReport, CutoverOutcome, DatabaseReport,
84    DeactivateOutcome, DryRunKind, ExchangeReport, FleetReport, PathReport, PoolReport,
85};
86
87/// The ONE execution entry: run `command` against `ctx`, asking `prompter` when
88/// the command's [`PromptPlan`] requires it.
89///
90/// Returns a [`CommandOutcome`] carrying the typed [`CommandReport`] (on
91/// success) and the [`ExitCode`] derived at the single `From<&CliError>` site.
92/// A fresh [`CancelHandle`] is created (no SIGINT wiring); use
93/// [`run_with_cancel`] to share the facade's handle.
94#[must_use]
95pub fn run(command: &Command, ctx: &CliContext<'_>, prompter: &dyn Prompter) -> CommandOutcome {
96    run_with_cancel(command, ctx, prompter, &CancelHandle::new())
97}
98
99/// The cancellable execution entry (ADR-051 D7).
100#[must_use]
101pub fn run_with_cancel(
102    command: &Command,
103    ctx: &CliContext<'_>,
104    prompter: &dyn Prompter,
105    cancel: &CancelHandle,
106) -> CommandOutcome {
107    let result = command.execute_with_cancel(ctx, prompter, cancel);
108    let exit_code = match &result {
109        Ok(_) => ExitCode::Success,
110        Err(err) => ExitCode::from(err),
111    };
112    CommandOutcome::new(result, exit_code)
113}