node-app-build 6.5.0

Mini app developer CLI: scaffold, validate, package node-app-* Debian packages
//! `node-app harness` — agent-drivable live full-stack (regtest bitcoind +
//! alice/bob Lightning nodes) with channel/payment probes.
//!
//! Layered on `node-app dev --agent`: bring-up calls `commands::dev::run`
//! in-process; this module only adds the bitcoind lifecycle, the probe
//! surface, and the `harness-state.json` index.

use anyhow::Result;
use clap::{Subcommand, ValueEnum};

pub mod bitcoind;
pub mod probes;
pub mod state;

#[derive(Debug, Clone, Copy, ValueEnum, Default)]
pub enum BitcoindMode {
    /// Run bitcoind in a Docker container (self-contained; default).
    #[default]
    Docker,
    /// Attach to an already-running bitcoind/Polar on 127.0.0.1:18443.
    External,
}

#[derive(Subcommand, Debug)]
pub enum HarnessAction {
    /// Bring up regtest bitcoind + alice/bob, onboard + peer-seed, leave running.
    Up {
        /// Path to the monorepo checkout (defaults to current dir).
        #[arg(long, default_value = ".")]
        monorepo_path: std::path::PathBuf,
        /// bitcoind source: docker (default) or external.
        #[arg(long, value_enum, default_value_t = BitcoindMode::Docker)]
        bitcoind: BitcoindMode,
        /// Also fund alice and open a usable alice→bob channel.
        #[arg(long)]
        with_channel: bool,
        /// Wipe existing harness/instance data before bringing up.
        #[arg(long)]
        clean: bool,
    },
    /// Health + peers + channels + balances for both nodes (JSON).
    Status,
    /// Mine N regtest blocks.
    Mine { blocks: u32 },
    /// Fund a node on-chain: send BTC + mine confirmations.
    Fund { node: String, btc: f64 },
    /// Connect + open a channel and wait until usable.
    ChannelOpen { from: String, to: String, sats: u64 },
    /// Invoice on `to`, pay from `from`, return settlement (JSON).
    Pay { from: String, to: String, sats: u64 },
    /// Drive a cross-node L402 paid call: `from` calls `route` on its own daemon
    /// which proxies to `to` via L402HttpClient.  Prints settlement JSON.
    L402 {
        from: String,
        to: String,
        route: String,
        /// JSON payload body (pass as a quoted JSON string on the CLI).
        payload: String,
    },
    /// Invoke any capability on a node (JSON payload).
    Invoke { node: String, capability: String, payload: String },
    /// Tail a node's captured daemon log.
    Logs { node: String, #[arg(long)] tail: bool },
    /// Stop daemons (+ bitcoind container). --clean also wipes data dirs.
    Down { #[arg(long)] clean: bool },
}

pub fn run(action: HarnessAction) -> Result<()> {
    match action {
        HarnessAction::Up { monorepo_path, bitcoind, with_channel, clean } => {
            probes::up(monorepo_path, bitcoind, with_channel, clean)
        }
        HarnessAction::Status => probes::status(),
        HarnessAction::Mine { blocks } => probes::mine(blocks),
        HarnessAction::Fund { node, btc } => probes::fund(&node, btc),
        HarnessAction::ChannelOpen { from, to, sats } => probes::channel_open(&from, &to, sats),
        HarnessAction::Pay { from, to, sats } => probes::pay(&from, &to, sats),
        HarnessAction::L402 { from, to, route, payload } => {
            probes::l402(&from, &to, &route, &payload)
        }
        HarnessAction::Invoke { node, capability, payload } => {
            probes::invoke(&node, &capability, &payload)
        }
        HarnessAction::Logs { node, tail } => probes::logs(&node, tail),
        HarnessAction::Down { clean } => probes::down(clean),
    }
}