diff --git a/hive-forge/src/main.rs b/hive-forge/src/main.rs index 033a9103..9aab6341 100644 --- a/hive-forge/src/main.rs +++ b/hive-forge/src/main.rs @@ -91,6 +91,12 @@ enum Verb { /// Add a collaborator to the active repo (`-r`/`HIVE_FORGE_REPO`) /// with a permission level. Companion to `repo-create`. RepoAddCollaborator(verbs::repo_add_collaborator::Args), + /// List the active repo's full label set (project-wide), optionally + /// filtered by a name substring. Unlike `labels ` (which lists + /// an issue/PR's labels), this shows every label defined on the repo — + /// the valid names + descriptions for triage / labelling. `--json` + /// emits the full label objects (id, name, color, description). + RepoLabels(verbs::repo_labels::Args), /// Triage lint queries (unassigned / no-reviewer / stale-branches / assignments). Lint(verbs::lint::Args), /// List issues / PRs with filters (`--kind`, `--state`, `--assignee`, @@ -163,6 +169,7 @@ fn main() -> Result<()> { Verb::Clone(a) => verbs::clone::run(&client, a), Verb::RepoCreate(a) => verbs::repo_create::run(&client, a), Verb::RepoAddCollaborator(a) => verbs::repo_add_collaborator::run(&client, a), + Verb::RepoLabels(a) => verbs::repo_labels::run(&client, a), Verb::Lint(a) => verbs::lint::run(&client, a), Verb::List(a) => verbs::list::run(&client, a), Verb::Milestone(a) => verbs::milestone::run(&client, a), diff --git a/hive-forge/src/verbs/mod.rs b/hive-forge/src/verbs/mod.rs index 9aaa43b0..e56f99ff 100644 --- a/hive-forge/src/verbs/mod.rs +++ b/hive-forge/src/verbs/mod.rs @@ -31,6 +31,7 @@ pub mod pr_reviews; pub mod pr_status; pub mod repo_add_collaborator; pub mod repo_create; +pub mod repo_labels; pub mod subscription; pub mod timeline; pub mod tree_sha; diff --git a/hive-forge/src/verbs/repo_labels.rs b/hive-forge/src/verbs/repo_labels.rs new file mode 100644 index 00000000..0681ad00 --- /dev/null +++ b/hive-forge/src/verbs/repo_labels.rs @@ -0,0 +1,52 @@ +//! `repo-labels [pattern]` — list the active repo's full label set +//! (project-wide), not the labels on one issue/PR (that's `labels +//! `). Useful for triage / labelling to discover the valid label +//! names + what each means before applying them. + +use anyhow::Result; +use clap::Args as ClapArgs; +use serde_json::{Value, json}; + +use crate::client::Client; +use crate::verbs::print_json; + +#[derive(ClapArgs)] +pub struct Args { + /// Substring pattern to filter label names (case-sensitive). + pattern: Option, +} + +pub fn run(client: &Client, args: Args) -> Result<()> { + let repo = client.repo(); + // Repos can carry more than one page of labels; paginate so the list + // is complete rather than capped at the first page. + let labels = client.get_json_all(&format!("/repos/{repo}/labels"), 10)?; + let filtered: Vec<&Value> = labels + .iter() + .filter(|l| { + let name = l.get("name").and_then(Value::as_str).unwrap_or_default(); + args.pattern.as_deref().is_none_or(|p| name.contains(p)) + }) + .collect(); + + if client.json_mode() { + // Full label objects (id, name, color, description) as a JSON array. + print_json(&json!(filtered))?; + } else { + // One label per line: `name`, plus its description (tab-separated) + // when set, so triage can see what each label means at a glance. + for l in filtered { + let name = l.get("name").and_then(Value::as_str).unwrap_or_default(); + let desc = l + .get("description") + .and_then(Value::as_str) + .unwrap_or_default(); + if desc.is_empty() { + println!("{name}"); + } else { + println!("{name}\t{desc}"); + } + } + } + Ok(()) +}