ouija 0.1.0-alpha.239

Cross-machine AI session daemon — bridges Claude Code sessions via tmux injection and Nostr P2P
use std::net::SocketAddr;

use axum::Router;
use axum::routing::{delete, get, post};
use tokio::net::TcpListener;

use crate::state::SharedState;
use crate::{admin, api, hooks};

/// Start the HTTP server on the configured port.
pub async fn run(state: SharedState) -> anyhow::Result<()> {
    let port = state.config.port;
    let name = state.config.name.clone();

    let app = Router::new()
        .route("/", get(admin::dashboard))
        .route("/admin", get(admin::dashboard))
        .route("/api/status", get(api::status))
        .route("/api/ticket", get(api::ticket))
        .route("/api/register", post(api::register))
        .route("/api/send", post(api::send_msg))
        .route("/api/inject", post(api::inject))
        .route("/api/rename", post(api::rename))
        .route(
            "/api/session-identities/claim",
            post(api::claim_local_identity),
        )
        .route(
            "/api/session-identities/dormant",
            get(api::dormant_identities),
        )
        .route(
            "/api/session-identities/dormant/{id}",
            get(api::dormant_identity),
        )
        .route("/api/remove", post(api::remove))
        .route("/api/sessions/update", post(api::update_session))
        .route("/api/sessions/bulk-update", post(api::bulk_update_sessions))
        .route("/api/connect", post(api::connect))
        .route("/api/nodes", get(api::nodes))
        .route("/api/nodes/disconnect", post(api::disconnect_node))
        .route("/api/regenerate-ticket", post(api::regenerate_ticket))
        .route(
            "/api/settings",
            get(api::get_settings).post(api::update_settings),
        )
        .route("/api/relays", get(api::get_relays).post(api::update_relays))
        .route(
            "/api/tasks",
            get(api::list_tasks)
                .post(api::create_task)
                .delete(api::delete_task),
        )
        .route("/api/tasks/enable", post(api::enable_task))
        .route("/api/tasks/disable", post(api::disable_task))
        .route("/api/tasks/trigger", post(api::trigger_task))
        .route("/api/task-runs", get(api::list_task_runs))
        .route(
            "/api/humans",
            get(api::list_humans)
                .post(api::add_human)
                .delete(api::remove_human),
        )
        .route("/api/sessions/{name}", get(api::get_session))
        .route("/api/sessions/kill", post(api::kill_session))
        .route("/api/sessions/recover-lease", post(api::recover_lease))
        .route("/api/sessions/prune-stale", post(api::prune_stale_sessions))
        .route("/api/sessions/start", post(api::start_session))
        .route("/api/sessions/restart", post(api::restart_session))
        .route(
            "/api/pane/{pane}/block-interactive",
            get(api::get_block_interactive).delete(api::clear_block_interactive),
        )
        .route(
            "/api/pane/{pane}/pending-replies",
            get(api::get_pending_replies),
        )
        .route(
            "/api/pane/{pane}/pending-replies/{from}",
            delete(api::delete_pending_reply),
        )
        .route("/api/pane/{pane}/stopped", post(api::session_stopped))
        .route("/api/pane/{pane}/active", post(api::session_active))
        .route("/api/session/{id}/ready", post(api::session_ready))
        .route(
            "/api/backend-session/{id}/ready",
            post(api::backend_session_ready),
        )
        .route(
            "/api/backend-identities/resolve",
            post(api::resolve_backend_identity),
        )
        .route(
            "/api/backend-identities/bind",
            post(api::bind_backend_identity),
        )
        .route(
            "/api/backend-identities/recover",
            post(api::recover_backend_identity),
        )
        .route(
            "/api/backend-identities/repair",
            post(api::repair_backend_identity),
        )
        .route("/api/clear-reminder", post(api::clear_reminder))
        .route("/api/projects", get(api::list_projects))
        .route("/api/hooks/session-start", post(hooks::session_start))
        .route("/api/hooks/session-end", post(hooks::session_end))
        .route("/api/hooks/stop", post(hooks::hook_stop))
        .route("/api/hooks/prompt-submit", post(hooks::prompt_submit))
        .route("/api/hooks/pre-tool-use", post(hooks::pre_tool_use))
        .route("/api/hooks/post-compact", post(hooks::post_compact))
        .route("/api/sessions/{session_id}/compact", post(api::compact))
        .with_state(state);

    let addr = format!("127.0.0.1:{port}");
    let listener = TcpListener::bind(&addr).await?;
    println!("ouija daemon '{name}' listening on http://localhost:{port}");
    tracing::info!("ouija daemon '{name}' listening on {addr}");
    // `into_make_service_with_connect_info::<SocketAddr>()` enables the
    // `ConnectInfo<SocketAddr>` extractor — handlers can read the peer
    // address and port of the TCP connection (issue #14 diagnostic).
    axum::serve(
        listener,
        app.into_make_service_with_connect_info::<SocketAddr>(),
    )
    .await?;

    Ok(())
}