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//! - `config` ([`ConfigCommand`]): the read-only view of the operator file
42//!   (ADR-062 D6) — the values, the winning layer per value, and the file the
43//!   mutating arms write.
44
45pub mod aave;
46pub mod block;
47pub mod cancel;
48pub mod command;
49pub mod config;
50pub mod context;
51pub mod database;
52pub mod error;
53pub mod exchange;
54pub mod fleet;
55pub mod operator;
56pub mod path;
57pub mod pool;
58pub mod prompt;
59pub mod registrations;
60pub mod report;
61pub mod strategy;
62
63pub use aave::{resolve_aave_deployment, AaveCommand, AaveDeployment, AAVE_DEPLOYMENTS};
64pub use block::{
65    parse_to_block, resolve_chain_selector, resolve_to_block, BlockTag, ToBlockSpec,
66    DEFAULT_CHUNK_SIZE, DEFAULT_TO_BLOCK, DEFAULT_VERIFY_ALL_INTERVAL,
67};
68pub use cancel::CancelHandle;
69pub use command::{Command, DatabaseCommand};
70pub use config::ConfigCommand;
71pub use context::CliContext;
72pub use database::database_backup_path;
73pub use error::{CliError, ExitCode};
74pub use exchange::{
75    resolve_deployment, ExchangeCommand, ExchangeDeployment, PoolManagerDeployment,
76    RETIRED_EXCHANGES,
77};
78pub use fleet::FleetCommand;
79pub use operator::{
80    parse_hop_token, parse_sim_intake_floor, render_json_sorted, resolve_socket,
81    validate_posture_patch, PathDirection, PathFamily, PathStep, PosturePatchEntry,
82    PosturePatchValue, WireRequest, WireResponse, FLEET_POSTURE_THRESHOLD_KEYS,
83    SIM_INTAKE_FLOOR_RESTORE, SOCKET_DEFAULT, SOCKET_ENV,
84};
85pub use path::PathCommand;
86pub use pool::{PoolCommand, PoolFamily};
87pub use prompt::{PromptPlan, Prompter};
88pub use registrations::{ensure_supported_registrations, RegistrationReport};
89pub use report::{
90    schema_state_label, AavePositionLine, AaveReport, AaveUpdateEntry, AaveUpdateOutcome,
91    ActivateOutcome, CommandOutcome, CommandReport, ConfigReport, ConfigValue, CutoverOutcome,
92    DatabaseReport, DeactivateOutcome, DryRunKind, ExchangeActiveState, ExchangeListRow,
93    ExchangeReport, FleetReport, PathReport, PoolReport, StrategyReport,
94};
95pub use strategy::{
96    descriptor, descriptors, EndpointSummary, MutationOutcome, StrategyCommand, StrategyFacet,
97    StrategyFacetDescriptor,
98};
99
100/// The ONE execution entry: run `command` against `ctx`, asking `prompter` when
101/// the command's [`PromptPlan`] requires it.
102///
103/// Returns a [`CommandOutcome`] carrying the typed [`CommandReport`] (on
104/// success) and the [`ExitCode`] derived at the single `From<&CliError>` site.
105/// A fresh [`CancelHandle`] is created (no SIGINT wiring); use
106/// [`run_with_cancel`] to share the facade's handle.
107#[must_use]
108pub fn run(command: &Command, ctx: &CliContext<'_>, prompter: &dyn Prompter) -> CommandOutcome {
109    run_with_cancel(command, ctx, prompter, &CancelHandle::new())
110}
111
112/// The cancellable execution entry (ADR-051 D7).
113#[must_use]
114pub fn run_with_cancel(
115    command: &Command,
116    ctx: &CliContext<'_>,
117    prompter: &dyn Prompter,
118    cancel: &CancelHandle,
119) -> CommandOutcome {
120    let result = command.execute_with_cancel(ctx, prompter, cancel);
121    let exit_code = match &result {
122        Ok(_) => ExitCode::Success,
123        Err(err) => ExitCode::from(err),
124    };
125    CommandOutcome::new(result, exit_code)
126}