Commands

Add in a main.rs file that will be used to run the counter-app CLI:

cd counter-app
touch src/main.rs

Now in the main.rs file add the use statements:

#![allow(unused)]
fn main() {
use clap::{Args, Parser, Subcommand};
use counter_app::App;
use essential_app_utils::compile::compile_pint_project;
use essential_types::PredicateAddress;
use std::path::PathBuf;
}

Using the cli crate clap add two commands:

#![allow(unused)]
fn main() {
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Command,
}

#[derive(Subcommand)]
enum Command {
    ReadCount {
        #[command(flatten)]
        server: Shared,
    },
    IncrementCount {
        #[command(flatten)]
        server: Shared,
    },
}

#[derive(Args)]
pub struct Shared {
    /// The address of the server to connect to.
    pub server: String,
    /// The directory containing the pint files.
    pub pint_directory: PathBuf,
}
}

The command ReadCount with become read-count <SERVER> <PINT_DIRECTORY> which will read the current count from the server.
The command IncrementCount with become increment-count <SERVER> <PINT_DIRECTORY> which will increment the current count on the server.
Both commands take the same arguments so we use a Shared struct to hold the arguments.

Add the main function to run the CLI:

#[tokio::main]
async fn main() {
    let args = Cli::parse();
    if let Err(err) = run(args).await {
        eprintln!("Command failed because: {}", err);
    }
}

This is fairly simple and just handles errors in a nice way for the user.

For both commands we need to compile the pint project to get the address of the predicate and create a new App.
Add this helper function:

#![allow(unused)]
fn main() {
async fn create_app(pint_directory: PathBuf, server: String) -> Result<App, anyhow::Error> {
    let counter = compile_pint_project(pint_directory).await?;
    let contract_address = essential_hash::contract_addr::from_contract(&counter);
    let predicate_address = essential_hash::content_addr(&counter.predicates[0]);
    let predicate_address = PredicateAddress {
        contract: contract_address,
        predicate: predicate_address,
    };
    let app = App::new(server, predicate_address)?;
    Ok(app)
}
}

The core of the cli is the run function.
It should handle each command and use the App to complete the actions like:

#![allow(unused)]
fn main() {
async fn run(cli: Cli) -> anyhow::Result<()> {
    let Cli { command } = cli;
    match command {
        Command::ReadCount {
            server: Shared {
                server,
                pint_directory,
            },
        } => {
            let app = create_app(pint_directory, server).await?;
            let count = app.read_count().await?;
            println!("Current count is: {}", count);
        }
        Command::IncrementCount {
            server: Shared {
                server,
                pint_directory,
            },
        } => {
            let app = create_app(pint_directory, server).await?;
            let new_count = app.increment().await?;
            println!("Incremented count to: {}", new_count);
        }
    }
    Ok(())
}
}
Check your `main.rs` matches this.
use clap::{Args, Parser, Subcommand};
use counter_app::App;
use essential_app_utils::compile::compile_pint_project;
use essential_types::PredicateAddress;
use std::path::PathBuf;

#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Command,
}

#[derive(Subcommand)]
enum Command {
    ReadCount {
        #[command(flatten)]
        server: Shared,
    },
    IncrementCount {
        #[command(flatten)]
        server: Shared,
    },
}

#[derive(Args)]
pub struct Shared {
    /// The address of the server to connect to.
    pub server: String,
    /// The directory containing the pint files.
    pub pint_directory: PathBuf,
}

#[tokio::main]
async fn main() {
    let args = Cli::parse();
    if let Err(err) = run(args).await {
        eprintln!("Command failed because: {}", err);
    }
}

async fn run(cli: Cli) -> anyhow::Result<()> {
    let Cli { command } = cli;
    match command {
        Command::ReadCount {
            server: Shared {
                server,
                pint_directory,
            },
        } => {
            let app = create_app(pint_directory, server).await?;
            let count = app.read_count().await?;
            println!("Current count is: {}", count);
        }
        Command::IncrementCount {
            server: Shared {
                server,
                pint_directory,
            },
        } => {
            let app = create_app(pint_directory, server).await?;
            let new_count = app.increment().await?;
            println!("Incremented count to: {}", new_count);
        }
    }
    Ok(())
}

async fn create_app(pint_directory: PathBuf, server: String) -> Result<App, anyhow::Error> {
    let counter = compile_pint_project(pint_directory).await?;
    let contract_address = essential_hash::contract_addr::from_contract(&counter);
    let predicate_address = essential_hash::content_addr(&counter.predicates[0]);
    let predicate_address = PredicateAddress {
        contract: contract_address,
        predicate: predicate_address,
    };
    let app = App::new(server, predicate_address)?;
    Ok(app)
}