use clap::Parser;
use std::process::ExitCode;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
mod analytics;
mod api;
mod cli;
mod config;
mod daemon;
mod data;
mod db;
mod enrich;
mod error;
mod mcp;
mod models;
mod skill;
use cli::args::{Cli, Command, OutputFormat};
use error::{ExitStatus, Result};
#[tokio::main]
async fn main() -> ExitCode {
let cli = Cli::parse();
init_logging(&cli);
match run(cli).await {
Ok(status) => status.into(),
Err(e) => {
let status = e.exit_status();
eprintln!("error: {e}");
if let Some(source) = std::error::Error::source(&e) {
eprintln!("caused by: {source}");
}
status.into()
}
}
}
fn init_logging(cli: &Cli) {
let filter = match cli.global.verbose {
0 if cli.global.quiet => EnvFilter::new("error"),
0 => EnvFilter::new("warn"),
1 => EnvFilter::new("info"),
2 => EnvFilter::new("debug"),
_ => EnvFilter::new("trace"),
};
let filter = if cli.global.debug {
EnvFilter::new("debug")
} else if cli.global.trace {
EnvFilter::new("trace")
} else {
filter
};
let subscriber = tracing_subscriber::registry().with(filter);
if cli.global.output == OutputFormat::Json {
subscriber.with(fmt::layer().json()).init();
} else {
subscriber
.with(fmt::layer().with_target(false).without_time())
.init();
}
}
async fn run(cli: Cli) -> Result<ExitStatus> {
if cli.global.help_json {
return print_help_json(&cli);
}
match cli.command {
Command::Doc(cmd) => handle_doc(cmd, &cli.global).await,
Command::Geo(cmd) => handle_geo(cmd, &cli.global).await,
Command::Tv(cmd) => handle_tv(cmd, &cli.global).await,
Command::Tvai(cmd) => handle_tvai(cmd, &cli.global).await,
Command::Data(cmd) => handle_data(cmd, &cli.global).await,
Command::Events(cmd) => handle_events(cmd, &cli.global).await,
Command::Gkg(cmd) => handle_gkg(cmd, &cli.global).await,
Command::Db(cmd) => handle_db(cmd, &cli.global).await,
Command::Serve(cmd) => handle_serve(cmd, &cli.global).await,
Command::Daemon(cmd) => handle_daemon(cmd, &cli.global).await,
Command::Skill(cmd) => handle_skill(cmd, &cli.global).await,
Command::Analytics(cmd) => handle_analytics(cmd, &cli.global).await,
Command::Config(cmd) => handle_config(cmd, &cli.global).await,
Command::Schema(cmd) => handle_schema(cmd, &cli.global).await,
Command::Enrich(cmd) => handle_enrich(cmd, &cli.global).await,
Command::Completions(cmd) => handle_completions(cmd),
}
}
fn print_help_json(_cli: &Cli) -> Result<ExitStatus> {
let help = serde_json::json!({
"name": "gdelt",
"version": env!("CARGO_PKG_VERSION"),
"description": env!("CARGO_PKG_DESCRIPTION"),
"commands": get_command_help(),
"global_options": get_global_options_help(),
"exit_codes": error::all_exit_codes(),
});
println!("{}", serde_json::to_string_pretty(&help)?);
Ok(ExitStatus::Success)
}
fn get_command_help() -> serde_json::Value {
serde_json::json!([
{
"name": "doc",
"description": "DOC 2.0 API - Search news articles and documents",
"subcommands": ["search", "timeline", "wordcloud", "themes"]
},
{
"name": "geo",
"description": "GEO 2.0 API - Geographic search and mapping",
"subcommands": ["search", "points", "heatmap", "aggregate"]
},
{
"name": "tv",
"description": "TV 2.0 API - Television news search",
"subcommands": ["search", "clips", "timeline", "stations"]
},
{
"name": "tvai",
"description": "TV AI API - AI-enhanced TV analysis",
"subcommands": ["search", "concepts", "visual"]
},
{
"name": "data",
"description": "Download and manage historical GDELT data",
"subcommands": ["download", "sync", "status", "list", "delete"]
},
{
"name": "events",
"description": "Query local events database",
"subcommands": ["query", "lookup", "actors", "countries", "codes"]
},
{
"name": "gkg",
"description": "Query local Global Knowledge Graph",
"subcommands": ["query", "themes", "persons", "organizations", "locations"]
},
{
"name": "db",
"description": "Database management",
"subcommands": ["stats", "vacuum", "export", "import", "query"]
},
{
"name": "serve",
"description": "Start MCP server (foreground)"
},
{
"name": "daemon",
"description": "Daemon management",
"subcommands": ["start", "stop", "restart", "status", "logs", "install", "uninstall"]
},
{
"name": "skill",
"description": "Claude Code skill management",
"subcommands": ["install", "uninstall", "update", "status"]
},
{
"name": "analytics",
"description": "Built-in analytics",
"subcommands": ["trends", "entities", "sentiment", "compare", "report"]
},
{
"name": "config",
"description": "Configuration management",
"subcommands": ["show", "get", "set", "reset", "validate", "path"]
},
{
"name": "schema",
"description": "Schema introspection for agents",
"subcommands": ["commands", "command", "output", "codes", "tools"]
},
{
"name": "enrich",
"description": "Enrich articles with GKG metadata and optionally fetch full text"
},
{
"name": "completions",
"description": "Generate shell completions"
}
])
}
fn get_global_options_help() -> serde_json::Value {
serde_json::json!([
{
"name": "output",
"short": "o",
"long": "output",
"type": "enum",
"values": ["auto", "json", "jsonl", "csv", "table", "markdown"],
"default": "auto",
"description": "Output format"
},
{
"name": "quiet",
"short": "q",
"long": "quiet",
"type": "bool",
"description": "Suppress non-essential output"
},
{
"name": "verbose",
"short": "v",
"long": "verbose",
"type": "count",
"description": "Increase verbosity (-v, -vv, -vvv)"
},
{
"name": "dry-run",
"long": "dry-run",
"type": "bool",
"description": "Preview without executing"
},
{
"name": "yes",
"short": "y",
"long": "yes",
"type": "bool",
"description": "Auto-confirm prompts"
},
{
"name": "no-cache",
"long": "no-cache",
"type": "bool",
"description": "Bypass cache"
},
{
"name": "cache-only",
"long": "cache-only",
"type": "bool",
"description": "Only use cached data"
},
{
"name": "timeout",
"long": "timeout",
"type": "integer",
"default": 30,
"description": "Request timeout in seconds"
},
{
"name": "help-json",
"long": "help-json",
"type": "bool",
"description": "Machine-readable help (JSON)"
}
])
}
use cli::args::{
AnalyticsCommand, ConfigCommand, DataCommand, DaemonCommand, DbCommand,
DocCommand, EnrichArgs, EventsCommand, GeoCommand, GkgCommand, GlobalArgs, SchemaCommand,
ServeArgs, SkillCommand, TvAiCommand, TvCommand, CompletionsArgs, Shell,
};
use cli::handlers;
async fn handle_doc(cmd: DocCommand, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_doc(cmd, global).await
}
async fn handle_geo(cmd: GeoCommand, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_geo(cmd, global).await
}
async fn handle_tv(cmd: TvCommand, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_tv(cmd, global).await
}
async fn handle_tvai(cmd: TvAiCommand, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_tvai(cmd, global).await
}
async fn handle_data(cmd: DataCommand, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_data(cmd, global).await
}
async fn handle_events(cmd: EventsCommand, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_events(cmd, global).await
}
async fn handle_gkg(cmd: GkgCommand, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_gkg(cmd, global).await
}
async fn handle_db(cmd: DbCommand, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_db(cmd, global).await
}
async fn handle_serve(cmd: ServeArgs, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_serve(cmd, global).await
}
async fn handle_daemon(cmd: DaemonCommand, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_daemon(cmd, global).await
}
async fn handle_skill(cmd: SkillCommand, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_skill(cmd, global).await
}
async fn handle_analytics(cmd: AnalyticsCommand, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_analytics(cmd, global).await
}
async fn handle_config(cmd: ConfigCommand, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_config(cmd, global).await
}
async fn handle_schema(cmd: SchemaCommand, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_schema(cmd, global).await
}
async fn handle_enrich(cmd: EnrichArgs, global: &GlobalArgs) -> Result<ExitStatus> {
handlers::handle_enrich(cmd, global).await
}
fn handle_completions(cmd: CompletionsArgs) -> Result<ExitStatus> {
use clap::CommandFactory;
use clap_complete::generate;
let shell = match cmd.shell {
Shell::Bash => clap_complete::Shell::Bash,
Shell::Zsh => clap_complete::Shell::Zsh,
Shell::Fish => clap_complete::Shell::Fish,
Shell::PowerShell => clap_complete::Shell::PowerShell,
};
let mut cli = Cli::command();
generate(shell, &mut cli, "gdelt", &mut std::io::stdout());
Ok(ExitStatus::Success)
}