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
This commit is contained in:
parent
4d57e93196
commit
91b7bc3ec1
5 changed files with 129 additions and 29 deletions
11
CHANGELOG.md
11
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/),
|
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).
|
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
|
## [0.1.0] - 2026-02-22
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ systemctl start argus
|
||||||
|
|
||||||
| Command | Description |
|
| 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 list [--pending]` | List notifications |
|
||||||
| `argus notif act <id> <action>` | Mark as acted |
|
| `argus notif act <id> <action>` | Mark as acted |
|
||||||
| `argus notif dismiss <id> [reason]` | Dismiss |
|
| `argus notif dismiss <id> [reason]` | Dismiss |
|
||||||
|
|
@ -84,7 +84,8 @@ Environment variables:
|
||||||
|----------|---------|-------------|
|
|----------|---------|-------------|
|
||||||
| `ARGUS_DB` | `~/.argus/argus.db` | Database path |
|
| `ARGUS_DB` | `~/.argus/argus.db` | Database path |
|
||||||
| `ARGUS_PORT` | `8100` | Dashboard port (datasette runs on port+1) |
|
| `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
|
## Architecture
|
||||||
|
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.1.0
|
0.2.0
|
||||||
|
|
|
||||||
127
bin/argus
127
bin/argus
|
|
@ -19,7 +19,7 @@ from typing import Optional
|
||||||
# Configuration
|
# Configuration
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
VERSION = "0.1.0"
|
VERSION = "0.2.0"
|
||||||
DEFAULT_DB_PATH = Path.home() / ".argus" / "argus.db"
|
DEFAULT_DB_PATH = Path.home() / ".argus" / "argus.db"
|
||||||
DEFAULT_PORT = 8100 # Dashboard port; datasette runs on port+1
|
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}")
|
print(f"✓ Initialized database at {db_path}")
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# GitHub API
|
# Backend Configuration
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def get_gh_token() -> Optional[str]:
|
DEFAULT_BACKEND = "gh" # gh | tea
|
||||||
return os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN")
|
|
||||||
|
def get_backend() -> str:
|
||||||
|
return os.environ.get("ARGUS_BACKEND", DEFAULT_BACKEND)
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# GitHub API (gh backend)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
def gh_api(endpoint: str) -> dict:
|
def gh_api(endpoint: str) -> dict:
|
||||||
"""Call GitHub API via gh CLI."""
|
"""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)
|
print(f"Error parsing GitHub API response", file=sys.stderr)
|
||||||
sys.exit(1)
|
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
|
# Commands: Core
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
@ -152,11 +243,15 @@ def cmd_status(args) -> None:
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def cmd_notif_pull(args) -> None:
|
def cmd_notif_pull(args) -> None:
|
||||||
"""Pull notifications from GitHub."""
|
"""Pull notifications from GitHub/Forgejo."""
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
|
|
||||||
# Fetch notifications from GitHub
|
# Determine backend
|
||||||
notifications = gh_api("notifications")
|
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
|
new_count = 0
|
||||||
for notif in notifications:
|
for notif in notifications:
|
||||||
|
|
@ -171,25 +266,16 @@ def cmd_notif_pull(args) -> None:
|
||||||
if existing:
|
if existing:
|
||||||
continue
|
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:
|
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
|
new_count += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
conn.execute("""
|
conn.execute("""
|
||||||
INSERT INTO notifications (id, repo_id, reason, title, url, seen_at)
|
INSERT INTO notifications (id, repo_id, reason, title, url, seen_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
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
|
new_count += 1
|
||||||
|
|
||||||
if not args.dry_run:
|
if not args.dry_run:
|
||||||
|
|
@ -540,8 +626,9 @@ def main():
|
||||||
notif_parser = subparsers.add_parser("notif", help="Notification management")
|
notif_parser = subparsers.add_parser("notif", help="Notification management")
|
||||||
notif_sub = notif_parser.add_subparsers(dest="notif_cmd")
|
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("--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 = notif_sub.add_parser("list", help="List notifications")
|
||||||
list_parser.add_argument("--pending", action="store_true", help="Show only pending")
|
list_parser.add_argument("--pending", action="store_true", help="Show only pending")
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,11 @@ argus status # Summary: pending, activity, escalations
|
||||||
### Notifications
|
### Notifications
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
argus notif pull [--dry-run] # Fetch from GitHub API
|
argus notif pull [--backend gh|tea] [--dry-run] # Fetch notifications
|
||||||
argus notif list [--pending] # List notifications
|
argus notif list [--pending] # List notifications
|
||||||
argus notif act <id> <action> # Mark as acted
|
argus notif act <id> <action> # Mark as acted
|
||||||
argus notif dismiss <id> [reason] # Dismiss
|
argus notif dismiss <id> [reason] # Dismiss
|
||||||
argus notif audit [--stale-hours] # Auto-escalate stale
|
argus notif audit [--stale-hours] # Auto-escalate stale
|
||||||
```
|
```
|
||||||
|
|
||||||
### Activity Logging
|
### Activity Logging
|
||||||
|
|
@ -70,7 +70,8 @@ argus serve [--port 8100] [--dashboard-port 8101]
|
||||||
|----------|---------|-------------|
|
|----------|---------|-------------|
|
||||||
| `ARGUS_DB` | `~/.argus/argus.db` | Database path |
|
| `ARGUS_DB` | `~/.argus/argus.db` | Database path |
|
||||||
| `ARGUS_PORT` | `8100` | Dashboard port (datasette on port+1) |
|
| `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
|
## Workflow
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue