From 91b7bc3ec119349064f8526f152c40464270a8b7 Mon Sep 17 00:00:00 2001 From: Zeus Date: Sun, 22 Feb 2026 10:47:18 +0000 Subject: [PATCH] feat: add backend flag for GitHub/Forgejo support - --backend gh|tea flag on notif pull command - ARGUS_BACKEND env var (default: gh) - gh backend: uses gh CLI for GitHub notifications - tea backend: uses tea CLI for Forgejo notifications Bump to v0.2.0 --- CHANGELOG.md | 11 +++++ README.md | 5 +- VERSION | 2 +- bin/argus | 127 ++++++++++++++++++++++++++++++++++++++++++-------- docs/SKILL.md | 13 +++--- 5 files changed, 129 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43955ba..8bcb2b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.0] - 2026-02-22 + +### Added +- Backend flag: `--backend gh|tea` for GitHub or Forgejo support +- `ARGUS_BACKEND` environment variable (default: `gh`) +- Forgejo notifications via `tea` CLI + +### Changed +- Simplified port config: single `ARGUS_PORT` (dashboard), datasette on port+1 +- Removed `ARGUS_DASHBOARD_PORT` env var + ## [0.1.0] - 2026-02-22 ### Added diff --git a/README.md b/README.md index ac3d158..18c31e4 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ systemctl start argus | Command | Description | |---------|-------------| -| `argus notif pull [--dry-run]` | Fetch from GitHub | +| `argus notif pull [--backend gh\|tea]` | Fetch from GitHub/Forgejo | | `argus notif list [--pending]` | List notifications | | `argus notif act ` | Mark as acted | | `argus notif dismiss [reason]` | Dismiss | @@ -84,7 +84,8 @@ Environment variables: |----------|---------|-------------| | `ARGUS_DB` | `~/.argus/argus.db` | Database path | | `ARGUS_PORT` | `8100` | Dashboard port (datasette runs on port+1) | -| `GH_TOKEN` | — | GitHub API token | +| `ARGUS_BACKEND` | `gh` | Backend: `gh` (GitHub) or `tea` (Forgejo) | +| `GH_TOKEN` | — | GitHub API token (for gh backend) | ## Architecture diff --git a/VERSION b/VERSION index 6e8bf73..0ea3a94 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0 +0.2.0 diff --git a/bin/argus b/bin/argus index 4a5c2ed..d5b33fd 100755 --- a/bin/argus +++ b/bin/argus @@ -19,7 +19,7 @@ from typing import Optional # Configuration # ============================================================================ -VERSION = "0.1.0" +VERSION = "0.2.0" DEFAULT_DB_PATH = Path.home() / ".argus" / "argus.db" DEFAULT_PORT = 8100 # Dashboard port; datasette runs on port+1 @@ -72,11 +72,17 @@ def init_db() -> None: print(f"✓ Initialized database at {db_path}") # ============================================================================ -# GitHub API +# Backend Configuration # ============================================================================ -def get_gh_token() -> Optional[str]: - return os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN") +DEFAULT_BACKEND = "gh" # gh | tea + +def get_backend() -> str: + return os.environ.get("ARGUS_BACKEND", DEFAULT_BACKEND) + +# ============================================================================ +# GitHub API (gh backend) +# ============================================================================ def gh_api(endpoint: str) -> dict: """Call GitHub API via gh CLI.""" @@ -95,6 +101,91 @@ def gh_api(endpoint: str) -> dict: print(f"Error parsing GitHub API response", file=sys.stderr) sys.exit(1) +def gh_fetch_notifications() -> list: + """Fetch notifications from GitHub.""" + notifications = gh_api("notifications") + results = [] + for notif in notifications: + repo_full = notif["repository"]["full_name"] + url = notif["subject"]["url"] or "" + # Convert API URL to web URL + web_url = url.replace("api.github.com/repos", "github.com") + web_url = web_url.replace("/pulls/", "/pull/") + + results.append({ + "id": notif["id"], + "repo_id": repo_full, + "reason": notif["reason"], + "title": notif["subject"]["title"], + "url": web_url, + "type": notif["subject"]["type"].lower(), + }) + return results + +# ============================================================================ +# Forgejo API (tea backend) +# ============================================================================ + +def tea_cmd(args: list) -> str: + """Run tea CLI command and return output.""" + try: + result = subprocess.run( + ["tea"] + args, + capture_output=True, + text=True, + check=True + ) + return result.stdout + except subprocess.CalledProcessError as e: + # Filter out the "NOTE: no gitea login" warning + stderr = e.stderr.strip() + if "token does not have" in stderr: + print(f"Error: {stderr}", file=sys.stderr) + print("Hint: Your tea token needs 'read:notification' scope.", file=sys.stderr) + else: + print(f"Error calling tea CLI: {stderr}", file=sys.stderr) + sys.exit(1) + +def tea_fetch_notifications() -> list: + """Fetch notifications from Forgejo via tea CLI.""" + import yaml + + output = tea_cmd(["notifications", "list", "--mine", "-o", "yaml"]) + + # Parse YAML output + try: + notifications = yaml.safe_load(output) or [] + except: + # Fallback: parse line by line if yaml fails + notifications = [] + + results = [] + for notif in notifications: + # tea notifications format differs from gh + results.append({ + "id": str(notif.get("id", "")), + "repo_id": notif.get("repository", ""), + "reason": notif.get("type", "unknown"), + "title": notif.get("title", ""), + "url": "", # tea doesn't provide URL directly + "type": notif.get("type", "").lower(), + }) + return results + +# ============================================================================ +# Unified Backend Interface +# ============================================================================ + +def fetch_notifications(backend: str) -> list: + """Fetch notifications using the specified backend.""" + if backend == "gh": + return gh_fetch_notifications() + elif backend == "tea": + return tea_fetch_notifications() + else: + print(f"Error: Unknown backend '{backend}'. Use 'gh' or 'tea'.", file=sys.stderr) + sys.exit(1) + # ============================================================================ # Commands: Core # ============================================================================ @@ -152,11 +243,15 @@ def cmd_status(args) -> None: # ============================================================================ def cmd_notif_pull(args) -> None: - """Pull notifications from GitHub.""" + """Pull notifications from GitHub/Forgejo.""" conn = get_db() - # Fetch notifications from GitHub - notifications = gh_api("notifications") + # Determine backend + backend = getattr(args, 'backend', None) or get_backend() + print(f"Using backend: {backend}") + + # Fetch notifications using the appropriate backend + notifications = fetch_notifications(backend) new_count = 0 for notif in notifications: @@ -171,25 +266,16 @@ def cmd_notif_pull(args) -> None: if existing: continue - # Extract repo info - repo_full = notif["repository"]["full_name"] - reason = notif["reason"] - title = notif["subject"]["title"] - url = notif["subject"]["url"] or "" - - # Convert API URL to web URL - web_url = url.replace("api.github.com/repos", "github.com") - web_url = web_url.replace("/pulls/", "/pull/") - if args.dry_run: - print(f"[DRY-RUN] Would add: {title} ({reason})") + print(f"[DRY-RUN] Would add: {notif['title']} ({notif['reason']})") new_count += 1 continue conn.execute(""" INSERT INTO notifications (id, repo_id, reason, title, url, seen_at) VALUES (?, ?, ?, ?, ?, ?) - """, (notif_id, repo_full, reason, title, web_url, datetime.now().isoformat())) + """, (notif_id, notif["repo_id"], notif["reason"], notif["title"], + notif["url"], datetime.now().isoformat())) new_count += 1 if not args.dry_run: @@ -540,8 +626,9 @@ def main(): notif_parser = subparsers.add_parser("notif", help="Notification management") notif_sub = notif_parser.add_subparsers(dest="notif_cmd") - pull_parser = notif_sub.add_parser("pull", help="Pull notifications from GitHub") + pull_parser = notif_sub.add_parser("pull", help="Pull notifications from GitHub/Forgejo") pull_parser.add_argument("--dry-run", action="store_true", help="Show what would be pulled") + pull_parser.add_argument("--backend", choices=["gh", "tea"], help="Backend: gh (GitHub) or tea (Forgejo)") list_parser = notif_sub.add_parser("list", help="List notifications") list_parser.add_argument("--pending", action="store_true", help="Show only pending") diff --git a/docs/SKILL.md b/docs/SKILL.md index 755ae29..f7dbe0e 100644 --- a/docs/SKILL.md +++ b/docs/SKILL.md @@ -35,11 +35,11 @@ argus status # Summary: pending, activity, escalations ### Notifications ```bash -argus notif pull [--dry-run] # Fetch from GitHub API -argus notif list [--pending] # List notifications -argus notif act # Mark as acted -argus notif dismiss [reason] # Dismiss -argus notif audit [--stale-hours] # Auto-escalate stale +argus notif pull [--backend gh|tea] [--dry-run] # Fetch notifications +argus notif list [--pending] # List notifications +argus notif act # Mark as acted +argus notif dismiss [reason] # Dismiss +argus notif audit [--stale-hours] # Auto-escalate stale ``` ### Activity Logging @@ -70,7 +70,8 @@ argus serve [--port 8100] [--dashboard-port 8101] |----------|---------|-------------| | `ARGUS_DB` | `~/.argus/argus.db` | Database path | | `ARGUS_PORT` | `8100` | Dashboard port (datasette on port+1) | -| `GH_TOKEN` | — | GitHub API token | +| `ARGUS_BACKEND` | `gh` | Backend: `gh` (GitHub) or `tea` (Forgejo) | +| `GH_TOKEN` | — | GitHub API token (for gh backend) | ## Workflow