mod api;
mod auth;
mod auth_context;
mod capabilities;
mod character;
mod config;
mod game_session;
mod inventory;
mod login_ui;
mod play;
mod terminal;
mod trace_setup;
use clap::{Parser, Subcommand};
use play::PlayArgs;
const ABOUT: &str = "Flatland3 — terminal play client and control-plane CLI";
const LONG_ABOUT: &str = "\
Play a server-authoritative MMORPG in the terminal, or manage accounts via the control plane.
Default (no subcommand): launch the Ratatui play client (requires login + existing character).
Use `flatland3 capabilities` for a full list of commands and environment variables.";
const AFTER_HELP: &str = "\
EXAMPLES:
flatland3 auth register --email you@example.com --password '…' --name You
flatland3 auth login --email you@example.com --password '…'
flatland3 character create MadSin
flatland3 character list
flatland3 --name MadSin
flatland3 auth token create --name ci --scope play,read
flatland3 capabilities Print capabilities reference
LOCAL DEV (monorepo only):
make dev-watch Control plane + embedded gateway (auto-rebuild)
make dev-play NAME=MadSin Client (character must already exist)
cargo run -p flatland-dev -- --bots 4 Embedded server + TUI (no CP)
SERVICES (local dev):
make db-up && cargo run -p flatland-control-plane-bin # HTTP :7380
cargo run -p flatland-server # game :7373
cargo run -p flatland3 # play
See plans/05-cli-and-developer-guide.md for HTTP API and curl examples.";
#[derive(Debug, Parser)]
#[command(
name = "flatland3",
version,
about = ABOUT,
long_about = LONG_ABOUT,
after_help = AFTER_HELP
)]
struct Cli {
#[arg(long, global = true, help = "JSON response envelope for subcommands")]
json: bool,
#[command(subcommand)]
command: Option<Command>,
#[command(flatten)]
play: PlayArgs,
}
#[derive(Debug, Subcommand)]
enum Command {
Capabilities,
Keys,
Auth {
#[command(subcommand)]
command: auth::AuthCommand,
},
Character {
#[command(subcommand)]
command: character::CharacterCommand,
},
Inventory {
#[arg(long)]
character_id: Option<uuid::Uuid>,
},
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let _ = dotenvy::dotenv();
let cli = Cli::parse();
if cli.command.is_none() {
trace_setup::init_for_play()?;
} else {
trace_setup::init()?;
}
match cli.command {
Some(Command::Capabilities) => {
capabilities::print();
Ok(())
}
Some(Command::Keys) => {
print!("{}", flatland_tui::format_keybindings_plain());
Ok(())
}
Some(Command::Auth { command }) => auth::run(command).await,
Some(Command::Character { command }) => character::run(command, cli.json).await,
Some(Command::Inventory { character_id }) => inventory::run(character_id, cli.json).await,
None => {
login_ui::run_if_needed().await?;
let opts = game_session::prepare_remote_play(
cli.play.name.as_deref(),
cli.play.server,
)
.await?;
let auto_reconnect = play::auto_reconnect_enabled(cli.play.no_reconnect);
play::run_with_options(opts, auto_reconnect).await
}
}
}