feat(cli): Add global robot mode for machine-readable output
Introduces a unified robot mode that enables JSON output across all commands, designed for AI agent and script consumption. Robot mode activation (any of): - --robot flag: Explicit opt-in - GI_ROBOT=1 env var: For persistent configuration - Non-TTY stdout: Auto-detect when piped (e.g., gi list issues | jq) Implementation: - Cli::is_robot_mode(): Centralized detection logic - All command handlers receive robot_mode boolean - Errors emit structured JSON to stderr with exit codes - Success responses emit JSON to stdout Behavior changes in robot mode: - No color/emoji output (no ANSI escapes) - No progress spinners or interactive prompts - Timestamps as ISO 8601 strings (not relative "2 hours ago") - Full content (no truncation of descriptions/notes) - Structured error objects with code, message, suggestion This enables reliable parsing by Claude Code, shell scripts, and automation pipelines. The auto-detect on non-TTY means simple piping "just works" without explicit flags. Per-command --json flags remain for explicit control and override robot mode when needed for human-friendly terminal + JSON file output. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
pub mod commands;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::io::IsTerminal;
|
||||
|
||||
/// GitLab Inbox - Unified notification management
|
||||
#[derive(Parser)]
|
||||
@@ -13,11 +14,23 @@ pub struct Cli {
|
||||
#[arg(short, long, global = true)]
|
||||
pub config: Option<String>,
|
||||
|
||||
/// Machine-readable JSON output (auto-enabled when piped)
|
||||
#[arg(long, global = true, env = "GI_ROBOT")]
|
||||
pub robot: bool,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub command: Commands,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
/// Check if robot mode is active (explicit flag, env var, or non-TTY stdout)
|
||||
pub fn is_robot_mode(&self) -> bool {
|
||||
self.robot || !std::io::stdout().is_terminal()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Commands {
|
||||
/// Initialize configuration and database
|
||||
Init {
|
||||
@@ -62,7 +75,7 @@ pub enum Commands {
|
||||
/// Ingest data from GitLab
|
||||
Ingest {
|
||||
/// Resource type to ingest
|
||||
#[arg(long, value_parser = ["issues", "merge_requests"])]
|
||||
#[arg(long, value_parser = ["issues", "mrs"])]
|
||||
r#type: String,
|
||||
|
||||
/// Filter to single project
|
||||
@@ -92,8 +105,8 @@ pub enum Commands {
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
|
||||
/// Filter by state
|
||||
#[arg(long, value_parser = ["opened", "closed", "all"])]
|
||||
/// Filter by state (opened|closed|all for issues; opened|merged|closed|locked|all for MRs)
|
||||
#[arg(long)]
|
||||
state: Option<String>,
|
||||
|
||||
/// Filter by author username
|
||||
@@ -108,7 +121,7 @@ pub enum Commands {
|
||||
#[arg(long)]
|
||||
label: Option<Vec<String>>,
|
||||
|
||||
/// Filter by milestone title
|
||||
/// Filter by milestone title (issues only)
|
||||
#[arg(long)]
|
||||
milestone: Option<String>,
|
||||
|
||||
@@ -116,11 +129,11 @@ pub enum Commands {
|
||||
#[arg(long)]
|
||||
since: Option<String>,
|
||||
|
||||
/// Filter by due date (before this date, YYYY-MM-DD)
|
||||
/// Filter by due date (before this date, YYYY-MM-DD) (issues only)
|
||||
#[arg(long)]
|
||||
due_before: Option<String>,
|
||||
|
||||
/// Show only issues with a due date
|
||||
/// Show only issues with a due date (issues only)
|
||||
#[arg(long)]
|
||||
has_due_date: bool,
|
||||
|
||||
@@ -132,13 +145,33 @@ pub enum Commands {
|
||||
#[arg(long, value_parser = ["desc", "asc"], default_value = "desc")]
|
||||
order: String,
|
||||
|
||||
/// Open first matching issue in browser
|
||||
/// Open first matching item in browser
|
||||
#[arg(long)]
|
||||
open: bool,
|
||||
|
||||
/// Output as JSON
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
|
||||
/// Show only draft MRs (MRs only)
|
||||
#[arg(long, conflicts_with = "no_draft")]
|
||||
draft: bool,
|
||||
|
||||
/// Exclude draft MRs (MRs only)
|
||||
#[arg(long, conflicts_with = "draft")]
|
||||
no_draft: bool,
|
||||
|
||||
/// Filter by reviewer username (MRs only)
|
||||
#[arg(long)]
|
||||
reviewer: Option<String>,
|
||||
|
||||
/// Filter by target branch (MRs only)
|
||||
#[arg(long)]
|
||||
target_branch: Option<String>,
|
||||
|
||||
/// Filter by source branch (MRs only)
|
||||
#[arg(long)]
|
||||
source_branch: Option<String>,
|
||||
},
|
||||
|
||||
/// Count entities in local database
|
||||
@@ -164,5 +197,9 @@ pub enum Commands {
|
||||
/// Filter by project path (required if iid is ambiguous)
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
|
||||
/// Output as JSON
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user