zynk 1.5.0

Portable protocol and helper CLI for multi-agent collaboration.
mod agent_ergonomics;
mod assign;
mod audit;
mod compose;
mod custody;
mod dashboard;
mod dashboard_live;
mod dashboard_write;
mod db;
mod db_dashboard;
mod decide;
mod decision;
mod herdr_orchestration;
mod overlay;
mod profile;
mod read_model;
mod report;
mod reveal;
mod send_herdr;
mod status;
mod timestamp;
mod work_event;

use clap::{Args, CommandFactory, Parser, Subcommand};

#[derive(Parser)]
#[command(
    name = "zynk",
    version,
    about = "Portable multi-agent collaboration helper CLI."
)]
struct Cli {
    #[command(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
#[allow(clippy::large_enum_variant)]
enum Commands {
    /// List Herdr workspaces, tabs, panes, agents, and statuses.
    List(herdr_orchestration::ListArgs),
    /// Compose a protocol message.
    Compose(compose::ComposeArgs),
    /// Send a composed protocol message.
    Send(SendCommand),
    /// Write an ADR 022 rolling status file.
    Status(status::StatusArgs),
    /// Append an ADR 023 audit record.
    #[command(
        long_about = "Append an audit record (ADR 023). Use it for manual proof, corrections, recovery, and non-zynk-send events — for example recording a message delivered over a non-herdr transport, or correcting a chain. It is not for a message already sent with zynk send herdr --session-id: that send records its audit and corpus automatically, so auditing it again would double-record one message. (The audited-send integrity gap also prints a zynk audit … --payload-file … command; running that is the intended recovery path.)"
    )]
    Audit(audit::AuditArgs),
    /// Render a point-in-time dashboard snapshot.
    Dashboard(dashboard::DashboardArgs),
    /// Manage the live-state SQLite database.
    Db(db::DbArgs),
    /// Record a work-telemetry event (ADR 033 M1).
    Report(report::ReportArgs),
    /// Record a typed operator decision (ADR 033 D4 / M2a).
    Decide(decide::DecideArgs),
    /// Reveal a retained payload (ADR 034 D8): proof-before-disclosure un-redaction.
    Reveal(reveal::RevealArgs),
    /// Assign a participant overlay (ADR 036): actor-kind / role / integrity trait.
    Assign(assign::AssignArgs),
    /// Herdr runtime orchestration commands (not protocol/audit state).
    Herdr(herdr_orchestration::HerdrArgs),
    /// Print the current agent's live Herdr context (ADR 040, read-only).
    Whoami(agent_ergonomics::WhoamiArgs),
    /// Alias for `whoami` (ADR 040, read-only).
    Who(agent_ergonomics::WhoamiArgs),
    /// Reply to a message by mid (ADR 040): routing inferred, judgment explicit.
    Reply(agent_ergonomics::ReplyArgs),
    /// List messages addressed to me (ADR 040, read-only).
    Inbox(agent_ergonomics::InboxArgs),
    /// Show a message's re= thread (ADR 040, read-only).
    Thread(agent_ergonomics::ThreadArgs),
    /// Read-only diagnostic of the zynk/Herdr environment (ADR 040).
    Doctor(agent_ergonomics::DoctorArgs),
}

#[derive(Args)]
struct SendCommand {
    #[command(subcommand)]
    transport: SendTransport,
}

#[derive(Subcommand)]
#[allow(clippy::large_enum_variant)]
enum SendTransport {
    /// Send via Herdr with receiver-verified delivery (ADR 041): paste (pane send-text),
    /// one Enter (pane send-keys), then records `sent` only after the rendered `[herdr
    /// ...]` header marker is verified on the receiver pane; never `sent` on a transport
    /// exit alone. TUI-independent (no input-box scraping). Multiline bodies preserved.
    Herdr(send_herdr::SendHerdrArgs),
    /// Send to a named live agent (ADR 040): resolves name -> pane live, composes send herdr.
    Agent(agent_ergonomics::SendAgentArgs),
}

#[derive(Debug)]
pub struct CliError {
    code: i32,
    message: String,
}

impl CliError {
    pub fn usage(message: impl Into<String>) -> Self {
        Self {
            code: 2,
            message: message.into(),
        }
    }

    pub fn failure(message: impl Into<String>) -> Self {
        Self {
            code: 1,
            message: message.into(),
        }
    }

    pub fn with_code(code: i32, message: impl Into<String>) -> Self {
        Self {
            code,
            message: message.into(),
        }
    }

    // Used by in-source unit tests (resolver error assertions); dead in the
    // integration-test bin build where #[cfg(test)] units are not compiled.
    #[allow(dead_code)]
    pub(crate) fn message(&self) -> &str {
        &self.message
    }
}

pub type CliResult<T> = Result<T, CliError>;

fn run() -> i32 {
    let cli = Cli::parse();
    let result = match cli.command {
        Some(Commands::List(args)) => herdr_orchestration::run_list(args),
        Some(Commands::Compose(args)) => compose::run(args),
        Some(Commands::Send(command)) => match command.transport {
            SendTransport::Herdr(args) => send_herdr::run(args),
            SendTransport::Agent(args) => agent_ergonomics::run_send_agent(args),
        },
        Some(Commands::Status(args)) => status::run(args),
        Some(Commands::Audit(args)) => audit::run(args),
        Some(Commands::Dashboard(args)) => dashboard::run(args),
        Some(Commands::Db(args)) => db::run(args),
        Some(Commands::Report(args)) => report::run(args),
        Some(Commands::Decide(args)) => decide::run(args),
        Some(Commands::Reveal(args)) => reveal::run(args),
        Some(Commands::Assign(args)) => assign::run(args),
        Some(Commands::Herdr(args)) => herdr_orchestration::run_herdr(args),
        Some(Commands::Whoami(args)) => agent_ergonomics::run_whoami(args),
        Some(Commands::Who(args)) => agent_ergonomics::run_whoami(args),
        Some(Commands::Reply(args)) => agent_ergonomics::run_reply(args),
        Some(Commands::Inbox(args)) => agent_ergonomics::run_inbox(args),
        Some(Commands::Thread(args)) => agent_ergonomics::run_thread(args),
        Some(Commands::Doctor(args)) => agent_ergonomics::run_doctor(args),
        None => {
            let mut command = Cli::command();
            let _ = command.print_help();
            println!();
            return 2;
        }
    };

    match result {
        Ok(()) => 0,
        Err(error) => {
            eprintln!("error: {}", error.message);
            error.code
        }
    }
}

fn main() {
    std::process::exit(run());
}