gdelt 0.1.0

CLI for GDELT Project - optimized for agentic usage with local data caching
//! Schema introspection command handlers.
//!
//! These commands help AI agents understand the CLI structure.

use crate::cli::args::{GlobalArgs, SchemaCommand};
use crate::cli::output::OutputWriter;
use crate::error::{self, ExitStatus, GdeltError, Result};
use serde_json::json;

/// Handle schema commands
pub async fn handle_schema(cmd: SchemaCommand, global: &GlobalArgs) -> Result<ExitStatus> {
    match cmd {
        SchemaCommand::Commands => list_commands(global),
        SchemaCommand::Command(args) => describe_command(&args.command, global),
        SchemaCommand::Output(args) => describe_output(&args.command, global),
        SchemaCommand::Codes => list_exit_codes(global),
        SchemaCommand::Tools => list_mcp_tools(global),
    }
}

fn list_commands(global: &GlobalArgs) -> Result<ExitStatus> {
    let output = OutputWriter::new(global);

    let commands = json!({
        "commands": [
            {
                "name": "doc",
                "description": "DOC 2.0 API - Search news articles and documents",
                "subcommands": [
                    {"name": "search", "description": "Search for articles"},
                    {"name": "timeline", "description": "Get timeline data (volume/tone over time)"},
                    {"name": "wordcloud", "description": "Generate word cloud data"},
                    {"name": "themes", "description": "List available GKG themes"}
                ]
            },
            {
                "name": "geo",
                "description": "GEO 2.0 API - Geographic search and mapping",
                "subcommands": [
                    {"name": "search", "description": "Search with geographic context"},
                    {"name": "points", "description": "Get point data for locations"},
                    {"name": "heatmap", "description": "Generate heatmap data"},
                    {"name": "aggregate", "description": "Country/region aggregation"}
                ]
            },
            {
                "name": "tv",
                "description": "TV 2.0 API - Television news search",
                "subcommands": [
                    {"name": "search", "description": "Search TV captions"},
                    {"name": "clips", "description": "Get video clips"},
                    {"name": "timeline", "description": "TV coverage timeline"},
                    {"name": "stations", "description": "List stations"}
                ]
            },
            {
                "name": "tvai",
                "description": "TV AI API - AI-enhanced TV analysis",
                "subcommands": [
                    {"name": "search", "description": "Search AI-analyzed content"},
                    {"name": "concepts", "description": "Search by visual concepts"},
                    {"name": "visual", "description": "Search by visual entities"}
                ]
            },
            {
                "name": "data",
                "description": "Download and manage historical GDELT data",
                "subcommands": [
                    {"name": "download events", "description": "Download events data"},
                    {"name": "download gkg", "description": "Download GKG data"},
                    {"name": "download mentions", "description": "Download mentions data"},
                    {"name": "sync", "description": "Sync to latest data"},
                    {"name": "status", "description": "Show download status"},
                    {"name": "list", "description": "List available date ranges"},
                    {"name": "delete", "description": "Delete local data"}
                ]
            },
            {
                "name": "events",
                "description": "Query local events database",
                "subcommands": [
                    {"name": "query", "description": "Query events with filters"},
                    {"name": "lookup", "description": "Lookup event by ID"},
                    {"name": "actors", "description": "Query by actor"},
                    {"name": "countries", "description": "Query by country"},
                    {"name": "codes list", "description": "List CAMEO codes"},
                    {"name": "codes search", "description": "Search codes by description"},
                    {"name": "codes describe", "description": "Describe a specific code"}
                ]
            },
            {
                "name": "gkg",
                "description": "Query local Global Knowledge Graph",
                "subcommands": [
                    {"name": "query", "description": "Query GKG records"},
                    {"name": "themes", "description": "Query by themes"},
                    {"name": "persons", "description": "Query person mentions"},
                    {"name": "organizations", "description": "Query organization mentions"},
                    {"name": "locations", "description": "Query location mentions"}
                ]
            },
            {
                "name": "db",
                "description": "Database management",
                "subcommands": [
                    {"name": "stats", "description": "Show database statistics"},
                    {"name": "vacuum", "description": "Optimize database"},
                    {"name": "export", "description": "Export data to file"},
                    {"name": "import", "description": "Import data from file"},
                    {"name": "query", "description": "Run raw SQL query"}
                ]
            },
            {
                "name": "analytics",
                "description": "Built-in analytics",
                "subcommands": [
                    {"name": "trends", "description": "Trend analysis"},
                    {"name": "entities", "description": "Entity extraction"},
                    {"name": "sentiment", "description": "Sentiment analysis"},
                    {"name": "compare", "description": "Compare topics (A vs B)"},
                    {"name": "report daily", "description": "Generate daily report"},
                    {"name": "report weekly", "description": "Generate weekly report"},
                    {"name": "report custom", "description": "Generate custom report"}
                ]
            },
            {
                "name": "config",
                "description": "Configuration management",
                "subcommands": [
                    {"name": "show", "description": "Show current configuration"},
                    {"name": "get", "description": "Get a configuration value"},
                    {"name": "set", "description": "Set a configuration value"},
                    {"name": "reset", "description": "Reset configuration to defaults"},
                    {"name": "validate", "description": "Validate configuration file"}
                ]
            },
            {
                "name": "schema",
                "description": "Schema introspection for agents",
                "subcommands": [
                    {"name": "commands", "description": "List all commands"},
                    {"name": "command", "description": "Get schema for a specific command"},
                    {"name": "output", "description": "Get output schema for a command"},
                    {"name": "codes", "description": "List all exit codes"},
                    {"name": "tools", "description": "List MCP tool definitions"}
                ]
            },
            {
                "name": "serve",
                "description": "Start MCP server (foreground)"
            },
            {
                "name": "daemon",
                "description": "Daemon management",
                "subcommands": [
                    {"name": "start", "description": "Start the daemon"},
                    {"name": "stop", "description": "Stop the daemon"},
                    {"name": "restart", "description": "Restart the daemon"},
                    {"name": "status", "description": "Show daemon status"},
                    {"name": "logs", "description": "View daemon logs"},
                    {"name": "install", "description": "Install as system service"},
                    {"name": "uninstall", "description": "Uninstall system service"}
                ]
            },
            {
                "name": "skill",
                "description": "Claude Code skill management",
                "subcommands": [
                    {"name": "install", "description": "Install Claude Code skill"},
                    {"name": "uninstall", "description": "Uninstall skill"},
                    {"name": "update", "description": "Update skill files"},
                    {"name": "status", "description": "Show skill status"}
                ]
            },
            {
                "name": "completions",
                "description": "Generate shell completions"
            }
        ]
    });

    output.write_value(&commands)?;
    Ok(ExitStatus::Success)
}

fn describe_command(command: &str, global: &GlobalArgs) -> Result<ExitStatus> {
    let output = OutputWriter::new(global);

    let schema = match command {
        "doc search" => json!({
            "command": "doc search",
            "description": "Search for news articles using GDELT DOC 2.0 API",
            "arguments": [
                {"name": "query", "type": "string", "required": true, "description": "Search query (supports GDELT query syntax)"}
            ],
            "options": [
                {"name": "--timespan", "short": "-t", "type": "string", "default": "24h", "description": "Time span to search (e.g., 24h, 7d, 3m)"},
                {"name": "--start", "type": "string", "description": "Start datetime (YYYYMMDDHHMMSS)"},
                {"name": "--end", "type": "string", "description": "End datetime (YYYYMMDDHHMMSS)"},
                {"name": "--max-records", "short": "-n", "type": "integer", "default": 75, "description": "Maximum records to return"},
                {"name": "--sort", "short": "-s", "type": "enum", "values": ["date-desc", "date-asc", "tone-desc", "tone-asc", "hybrid-rel"], "default": "hybrid-rel"},
                {"name": "--lang", "short": "-l", "type": "string", "description": "Filter by source language (ISO 639-1)"},
                {"name": "--country", "short": "-c", "type": "string", "description": "Filter by source country (FIPS code)"},
                {"name": "--domain", "short": "-d", "type": "string", "description": "Filter by domain"},
                {"name": "--theme", "type": "string", "description": "Filter by GKG theme"},
                {"name": "--tone-min", "type": "float", "description": "Minimum tone score (-10 to 10)"},
                {"name": "--tone-max", "type": "float", "description": "Maximum tone score (-10 to 10)"}
            ],
            "examples": [
                "gdelt doc search 'climate change'",
                "gdelt doc search 'ukraine' --timespan 7d --lang en",
                "gdelt doc search 'election' --country US --sort date-desc"
            ]
        }),
        "events query" => json!({
            "command": "events query",
            "description": "Query events from local database",
            "options": [
                {"name": "--actor", "short": "-a", "type": "string", "description": "Filter by actor code"},
                {"name": "--event-code", "short": "-e", "type": "string", "description": "Filter by event code (supports wildcards like '14*')"},
                {"name": "--quad-class", "type": "integer", "description": "Filter by quad class (1-4)"},
                {"name": "--country", "short": "-c", "type": "string", "description": "Filter by country"},
                {"name": "--start", "type": "string", "description": "Start date (YYYY-MM-DD)"},
                {"name": "--end", "type": "string", "description": "End date (YYYY-MM-DD)"},
                {"name": "--goldstein-min", "type": "float", "description": "Minimum Goldstein scale (-10 to 10)"},
                {"name": "--goldstein-max", "type": "float", "description": "Maximum Goldstein scale"},
                {"name": "--limit", "short": "-n", "type": "integer", "default": 100},
                {"name": "--offset", "type": "integer", "default": 0}
            ],
            "examples": [
                "gdelt events query --country US --start 2024-01-01",
                "gdelt events query --event-code '14*' --limit 50",
                "gdelt events query --actor GOV --quad-class 4"
            ]
        }),
        "db query" => json!({
            "command": "db query",
            "description": "Run raw SQL query on local database",
            "arguments": [
                {"name": "query", "type": "string", "required": true, "description": "SQL query to execute"}
            ],
            "examples": [
                "gdelt db query 'SELECT COUNT(*) FROM events'",
                "gdelt db query 'SELECT actor1_code, COUNT(*) as cnt FROM events GROUP BY actor1_code ORDER BY cnt DESC LIMIT 10'"
            ]
        }),
        _ => {
            return Err(GdeltError::NotFound(format!("Unknown command: {}", command)));
        }
    };

    output.write_value(&schema)?;
    Ok(ExitStatus::Success)
}

fn describe_output(command: &str, global: &GlobalArgs) -> Result<ExitStatus> {
    let output = OutputWriter::new(global);

    let schema = match command {
        "doc search" => json!({
            "command": "doc search",
            "output_schema": {
                "type": "object",
                "properties": {
                    "articles": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "url": {"type": "string"},
                                "title": {"type": "string"},
                                "seendate": {"type": "string"},
                                "domain": {"type": "string"},
                                "language": {"type": "string"},
                                "sourcecountry": {"type": "string"},
                                "tone": {"type": "number"}
                            }
                        }
                    }
                }
            }
        }),
        "events query" => json!({
            "command": "events query",
            "output_schema": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "global_event_id": {"type": "integer"},
                        "sql_date": {"type": "integer"},
                        "actor1_code": {"type": "string"},
                        "actor1_name": {"type": "string"},
                        "actor2_code": {"type": "string"},
                        "actor2_name": {"type": "string"},
                        "event_code": {"type": "string"},
                        "quad_class": {"type": "integer"},
                        "goldstein_scale": {"type": "number"},
                        "num_mentions": {"type": "integer"},
                        "avg_tone": {"type": "number"},
                        "action_geo_fullname": {"type": "string"},
                        "source_url": {"type": "string"}
                    }
                }
            }
        }),
        "db stats" => json!({
            "command": "db stats",
            "output_schema": {
                "type": "object",
                "properties": {
                    "events_count": {"type": "integer"},
                    "gkg_count": {"type": "integer"},
                    "mentions_count": {"type": "integer"},
                    "events_date_range": {"type": "array", "items": {"type": "integer"}},
                    "file_size_bytes": {"type": "integer"}
                }
            }
        }),
        _ => {
            return Err(GdeltError::NotFound(format!("Unknown command: {}", command)));
        }
    };

    output.write_value(&schema)?;
    Ok(ExitStatus::Success)
}

fn list_exit_codes(global: &GlobalArgs) -> Result<ExitStatus> {
    let output = OutputWriter::new(global);
    let codes = error::all_exit_codes();
    output.write_value(&serde_json::to_value(codes)?)?;
    Ok(ExitStatus::Success)
}

fn list_mcp_tools(global: &GlobalArgs) -> Result<ExitStatus> {
    let output = OutputWriter::new(global);

    let tools = json!({
        "tools": [
            {
                "name": "gdelt_doc_search",
                "description": "Search GDELT news articles",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "query": {"type": "string", "description": "Search query"},
                        "timespan": {"type": "string", "description": "Time span (e.g., 24h, 7d)"},
                        "max_records": {"type": "integer", "description": "Maximum results"},
                        "lang": {"type": "string", "description": "Language filter"},
                        "country": {"type": "string", "description": "Country filter"}
                    },
                    "required": ["query"]
                }
            },
            {
                "name": "gdelt_events_query",
                "description": "Query local GDELT events database",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "actor": {"type": "string", "description": "Actor code filter"},
                        "event_code": {"type": "string", "description": "Event code filter"},
                        "country": {"type": "string", "description": "Country filter"},
                        "start_date": {"type": "string", "description": "Start date (YYYY-MM-DD)"},
                        "end_date": {"type": "string", "description": "End date (YYYY-MM-DD)"},
                        "limit": {"type": "integer", "description": "Maximum results"}
                    }
                }
            },
            {
                "name": "gdelt_geo_search",
                "description": "Geographic search for GDELT data",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "query": {"type": "string", "description": "Search query"},
                        "timespan": {"type": "string", "description": "Time span"},
                        "country": {"type": "string", "description": "Country filter"},
                        "max_points": {"type": "integer", "description": "Maximum points"}
                    },
                    "required": ["query"]
                }
            },
            {
                "name": "gdelt_db_query",
                "description": "Run SQL query on local GDELT database",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "sql": {"type": "string", "description": "SQL query"}
                    },
                    "required": ["sql"]
                }
            }
        ]
    });

    output.write_value(&tools)?;
    Ok(ExitStatus::Success)
}