opencode-ralph-loop-cli 0.1.0

Scaffolder CLI for OpenCode Ralph Loop plugin — one command setup
Documentation
pub mod cli;
pub mod commands;
pub mod config;
pub mod error;
pub mod fs_atomic;
pub mod hash;
pub mod manifest;
pub mod output;
pub mod templates;

use clap::Parser;

use crate::cli::{Cli, Commands};
use crate::error::CliError;

const JSON_OUTPUT_SCHEMA: &str = r#"{
  "$schema": "https://json-schema.org/draft-07/schema#",
  "title": "opencode-ralph-loop-cli output",
  "description": "JSON/NDJSON output schema for opencode-ralph-loop-cli v1",
  "type": "object",
  "required": ["status", "command", "files", "summary", "cli_version", "schema_version", "duration_ms", "plugin_version"],
  "properties": {
    "status":         { "type": "string", "description": "ok | drift | error:<msg>" },
    "command":        { "type": "string" },
    "cli_version":    { "type": "string" },
    "schema_version": { "type": "integer" },
    "plugin_version": { "type": "string" },
    "duration_ms":    { "type": "integer" },
    "files": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["path", "action", "sha256", "size_bytes"],
        "properties": {
          "path":            { "type": "string" },
          "action":          { "type": "string", "enum": ["created","updated","skipped","modified","missing","removed"] },
          "sha256":          { "type": "string" },
          "expected_sha256": { "type": ["string", "null"] },
          "size_bytes":      { "type": "integer" }
        }
      }
    },
    "summary": {
      "type": "object",
      "properties": {
        "created":  { "type": "integer" },
        "updated":  { "type": "integer" },
        "skipped":  { "type": "integer" },
        "modified": { "type": "integer" },
        "missing":  { "type": "integer" },
        "removed":  { "type": "integer" }
      }
    }
  }
}"#;

pub fn run() -> i32 {
    init_tracing();

    // SIGINT → exit code 130 (per PRD)
    ctrlc::set_handler(|| {
        std::process::exit(130);
    })
    .ok();

    let cli = Cli::parse();

    // --json-schema: print JSON output schema and exit
    if cli.json_schema {
        println!("{}", JSON_OUTPUT_SCHEMA);
        return 0;
    }

    // Load XDG config (silently falls back to defaults)
    let _config = crate::config::Config::load(cli.config.as_ref());

    let result = dispatch(cli);

    match result {
        Ok(()) => 0,
        Err(e) => {
            eprintln!("ERROR: {e}");
            e.exit_code()
        }
    }
}

fn dispatch(cli: Cli) -> Result<(), CliError> {
    match cli.command {
        Commands::Init(args) => commands::init::run(&args, &cli.output),
        Commands::Check(args) => commands::check::run(&args, &cli.output),
        Commands::Uninstall(args) => commands::uninstall::run(&args, &cli.output),
        Commands::List => commands::list::run(&cli.output),
        Commands::Doctor => {
            let path = std::env::current_dir().map_err(|e| CliError::io("current directory", e))?;
            commands::doctor::run(&path, &cli.output)
        }
        Commands::Completions { shell } => {
            commands::completions::run(shell);
            Ok(())
        }
    }
}

fn init_tracing() {
    use tracing_subscriber::{EnvFilter, fmt};
    fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .with_writer(std::io::stderr)
        .init();
}