From 12811683caf81f286db79eff2effad97a0b12d51 Mon Sep 17 00:00:00 2001 From: Taylor Eernisse Date: Tue, 3 Feb 2026 12:08:01 -0500 Subject: [PATCH] feat(cli): Add 'lore count events' command with human and robot output Extends the count command to support "events" as an entity type, displaying resource event counts broken down by event type (state, label, milestone) and entity type (issue, merge request). New functions in count.rs: - run_count_events: Creates DB connection and delegates to events_db::count_events for the actual queries - print_event_count: Human-readable table with aligned columns showing per-type breakdowns and row/column totals - print_event_count_json: Structured JSON matching the robot mode contract with ok/data envelope and per-type issue/mr/total counts JSON output structure: {"ok":true,"data":{"state_events":{"issue":N,"merge_request":N, "total":N},"label_events":{...},"milestone_events":{...},"total":N}} Updated exports in commands/mod.rs to expose the three new public functions (run_count_events, print_event_count, print_event_count_json). The "events" branch in handle_count (main.rs, committed earlier) routes to these functions before the existing entity type dispatcher. Co-Authored-By: Claude Opus 4.5 --- src/cli/commands/count.rs | 104 ++++++++++++++++++++++++++++++++++++++ src/cli/commands/mod.rs | 5 +- 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/src/cli/commands/count.rs b/src/cli/commands/count.rs index 42020df..751d91e 100644 --- a/src/cli/commands/count.rs +++ b/src/cli/commands/count.rs @@ -7,6 +7,7 @@ use serde::Serialize; use crate::Config; use crate::core::db::create_connection; use crate::core::error::Result; +use crate::core::events_db::{self, EventCounts}; use crate::core::paths::get_db_path; /// Result of count query. @@ -237,6 +238,109 @@ struct CountJsonBreakdown { locked: Option, } +/// Run the event count query. +pub fn run_count_events(config: &Config) -> Result { + let db_path = get_db_path(config.storage.db_path.as_deref()); + let conn = create_connection(&db_path)?; + events_db::count_events(&conn) +} + +/// JSON output structure for event counts. +#[derive(Serialize)] +struct EventCountJsonOutput { + ok: bool, + data: EventCountJsonData, +} + +#[derive(Serialize)] +struct EventCountJsonData { + state_events: EventTypeCounts, + label_events: EventTypeCounts, + milestone_events: EventTypeCounts, + total: usize, +} + +#[derive(Serialize)] +struct EventTypeCounts { + issue: usize, + merge_request: usize, + total: usize, +} + +/// Print event counts as JSON (robot mode). +pub fn print_event_count_json(counts: &EventCounts) { + let output = EventCountJsonOutput { + ok: true, + data: EventCountJsonData { + state_events: EventTypeCounts { + issue: counts.state_issue, + merge_request: counts.state_mr, + total: counts.state_issue + counts.state_mr, + }, + label_events: EventTypeCounts { + issue: counts.label_issue, + merge_request: counts.label_mr, + total: counts.label_issue + counts.label_mr, + }, + milestone_events: EventTypeCounts { + issue: counts.milestone_issue, + merge_request: counts.milestone_mr, + total: counts.milestone_issue + counts.milestone_mr, + }, + total: counts.total(), + }, + }; + + println!("{}", serde_json::to_string(&output).unwrap()); +} + +/// Print event counts (human-readable). +pub fn print_event_count(counts: &EventCounts) { + println!( + "{:<20} {:>8} {:>8} {:>8}", + style("Event Type").cyan().bold(), + style("Issues").bold(), + style("MRs").bold(), + style("Total").bold() + ); + + let state_total = counts.state_issue + counts.state_mr; + let label_total = counts.label_issue + counts.label_mr; + let milestone_total = counts.milestone_issue + counts.milestone_mr; + + println!( + "{:<20} {:>8} {:>8} {:>8}", + "State events", + format_number(counts.state_issue as i64), + format_number(counts.state_mr as i64), + format_number(state_total as i64) + ); + println!( + "{:<20} {:>8} {:>8} {:>8}", + "Label events", + format_number(counts.label_issue as i64), + format_number(counts.label_mr as i64), + format_number(label_total as i64) + ); + println!( + "{:<20} {:>8} {:>8} {:>8}", + "Milestone events", + format_number(counts.milestone_issue as i64), + format_number(counts.milestone_mr as i64), + format_number(milestone_total as i64) + ); + + let total_issues = counts.state_issue + counts.label_issue + counts.milestone_issue; + let total_mrs = counts.state_mr + counts.label_mr + counts.milestone_mr; + println!( + "{:<20} {:>8} {:>8} {:>8}", + style("Total").bold(), + format_number(total_issues as i64), + format_number(total_mrs as i64), + style(format_number(counts.total() as i64)).bold() + ); +} + /// Print count result as JSON (robot mode). pub fn print_count_json(result: &CountResult) { let breakdown = result.state_breakdown.as_ref().map(|b| CountJsonBreakdown { diff --git a/src/cli/commands/mod.rs b/src/cli/commands/mod.rs index cede53e..98ba1a4 100644 --- a/src/cli/commands/mod.rs +++ b/src/cli/commands/mod.rs @@ -15,7 +15,10 @@ pub mod sync; pub mod sync_status; pub use auth_test::run_auth_test; -pub use count::{print_count, print_count_json, run_count}; +pub use count::{ + print_count, print_count_json, print_event_count, print_event_count_json, run_count, + run_count_events, +}; pub use doctor::{print_doctor_results, run_doctor}; pub use embed::{print_embed, print_embed_json, run_embed}; pub use generate_docs::{print_generate_docs, print_generate_docs_json, run_generate_docs};