feat(#1985): subscription --list to audit watched repos

This commit is contained in:
damocles 2026-06-24 23:19:13 +02:00 committed by mara
commit fce9bb8c11
3 changed files with 37 additions and 3 deletions

View file

@ -81,6 +81,7 @@ hive-forge ci-log --run 51 # print a CI run's job step logs (r
hive-forge ci-rerun --pr 42 # re-run CI without an empty commit (dispatches a fresh run; --run n / --branch name also work)
hive-forge subscription --watch # subscribe to repo notifications
hive-forge subscription --unwatch # unsubscribe
hive-forge subscription --list # list every repo you watch (audit the notification firehose)
hive-forge -r internal/knowledge clone # clone with creds auto-injected
hive-forge -r internal/knowledge pr-create --agit --topic foo --title "..." # open PR via AGit (no fork)
```

View file

@ -142,7 +142,7 @@ enum Verb {
/// Print the unified diff for a PR.
#[command(hide = true)]
Diff(verbs::diff::Args),
/// Get or set this user's watch subscription on a repo.
/// Get/set this user's watch subscription on a repo, or --list all watched repos.
Subscription(verbs::subscription::Args),
/// List timeline events on an issue or PR (closes, label adds,
/// assignments, commit refs, pushes, etc.) — the audit trail

View file

@ -1,5 +1,7 @@
//! `subscription [--watch|--ignore|--unwatch] [repo]` — get or set
//! the current user's watch subscription for a repo.
//! `subscription [--watch|--ignore|--unwatch|--list] [repo]` — get or
//! set the current user's watch subscription for a repo, or list every
//! repo the user watches (`--list`, for auditing the notification
//! firehose).
use anyhow::Result;
use clap::Args as ClapArgs;
@ -9,6 +11,12 @@ use crate::client::Client;
use crate::verbs::print_json;
#[derive(ClapArgs)]
#[allow(
clippy::struct_excessive_bools,
reason = "mutually-exclusive clap action flags (group = \"action\"); an \
enum would drop the --watch/--ignore/--unwatch/--list flag \
ergonomics agents already use"
)]
pub struct Args {
/// Subscribe (watch the repo).
#[arg(long, group = "action")]
@ -19,9 +27,34 @@ pub struct Args {
/// Unsubscribe (clear watch + ignore).
#[arg(long, group = "action")]
unwatch: bool,
/// List every repo the current user watches (ignores `[repo]`).
#[arg(long, group = "action")]
list: bool,
}
pub fn run(client: &Client, args: Args) -> Result<()> {
if args.list {
// List every repo the authed user watches, so an agent can audit
// its notification firehose and decide what to `--unwatch`. Not
// repo-scoped. Paginated, with a generous page cap so a
// misbehaving server can't spin us forever.
const MAX_PAGES: u32 = 50;
const PAGE: u32 = 50;
let mut watching: Vec<String> = Vec::new();
for page in 1..=MAX_PAGES {
let v = client.get_json(&format!("/user/subscriptions?page={page}&limit={PAGE}"))?;
let arr = v.as_array().cloned().unwrap_or_default();
let n = arr.len();
watching.extend(
arr.iter()
.filter_map(|r| r["full_name"].as_str().map(str::to_owned)),
);
if n < PAGE as usize {
break;
}
}
return print_json(&json!({ "watching": watching }));
}
let repo = client.repo();
if args.unwatch {
client.delete(&format!("/repos/{repo}/subscription"), None)?;