Address review of PR #1 (Valentin): replace the linear config walk-through and single-section schema. - weekly-activity config becomes a menu loop: Current sources (masked) -> Add a new source (pick kind, repeatable) / Remove a source / Exit; the file is written only when something changed - new versioned config layout (version = 2): [[kind.accounts]] arrays so several accounts or instances per provider coexist; optional display labels; strict unknown-key/type validation kept (errors carry account position, e.g. [github.accounts[1]]) - legacy v1 files (no version key, one section per source) migrate transparently in memory on load; saving from the wizard persists v2; the file itself is never rewritten by load_config - non-anonymous Launchpad accounts now authenticate at config time: constructing LaunchpadSource runs launchpadlib's browser OAuth and the token lands in the system keyring before any report; an explicit credentials_file stays supported as an additive opt-out - report aggregates every configured account in stable order with failure isolation intact; headings add the account label when it disambiguates (source_label shared between aggregate and wizard) - GitHub accounts gain optional token_env resolved lazily against the environment before falling back to GITHUB_TOKEN / `gh auth token`
173 lines
6.9 KiB
Markdown
173 lines
6.9 KiB
Markdown
# weekly-activity
|
|
|
|
Aggregate weekly activity across development platforms (Launchpad, GitHub, GitLab, Debian BTS) into a single text report.
|
|
|
|
## Usage
|
|
|
|
```bash
|
|
# Interactive setup: pick sources, store identities/tokens once
|
|
uv run weekly-activity config
|
|
|
|
# Aggregated report across every configured source
|
|
uv run weekly-activity report # bare `weekly-activity` does the same
|
|
```
|
|
|
|
### Single sources (ad-hoc, no config needed)
|
|
|
|
|
|
```bash
|
|
# Launchpad (uses system keyring for OAuth; --anonymous for public data only)
|
|
uv run weekly-activity launchpad <username>
|
|
uv run weekly-activity launchpad --anonymous <username>
|
|
|
|
# GitHub (uses GITHUB_TOKEN env var or `gh auth token`; anonymous works but rate-limited)
|
|
uv run weekly-activity github <username>
|
|
uv run weekly-activity github --token ghp_xxx <username>
|
|
|
|
# GitLab (gitlab.com by default; any self-hosted instance via --url, e.g. Debian Salsa;
|
|
# uses GITLAB_TOKEN / SALSA_TOKEN env vars, anonymous works but rate-limited)
|
|
uv run weekly-activity gitlab <username>
|
|
uv run weekly-activity gitlab --url https://salsa.debian.org <username>
|
|
uv run weekly-activity gitlab --url https://salsa.debian.org --token glpat-xxx <username>
|
|
|
|
# Debian BTS (bugs.debian.org; identify by the email used on bugs)
|
|
uv run weekly-activity bts <email>
|
|
|
|
# Custom date range (ISO dates, UTC)
|
|
uv run weekly-activity launchpad --since 2025-08-01 --until 2025-08-15 <username>
|
|
uv run weekly-activity github --since 2025-08-01 --until 2025-08-15 <username>
|
|
uv run weekly-activity gitlab --since 2025-08-01 --until 2025-08-15 <username>
|
|
uv run weekly-activity bts --since 2025-08-01 --until 2025-08-15 <email>
|
|
```
|
|
|
|
### GitLab instances
|
|
|
|
Any GitLab instance works via `--url`. Token discovery order:
|
|
|
|
1. `--token` flag
|
|
2. env var named by `--token-env`
|
|
3. `<HOST>_TOKEN` derived from the instance host (e.g. `SALSA_TOKEN` for `salsa.debian.org`)
|
|
4. `GITLAB_TOKEN`
|
|
|
|
## What it reports
|
|
|
|
| Section | Launchpad | GitHub | GitLab | Debian BTS |
|
|
|---|---|---|---|---|
|
|
| Merge proposals / PRs authored | `getMergeProposals` (created in range) | `/search/issues` (`author:` + `type:pr`) | `/merge_requests` (`author_id`, created range) | — |
|
|
| Review requests received | `getRequestedReviews` (filtered by `date_review_requested`) | `/search/issues` (`reviewed-by:` + `type:pr`) | — | — |
|
|
| Reviews performed | `getRequestedReviews` (filtered by `date_reviewed`) | (same query) | events (`action=approved`) | — |
|
|
| Issues / Bugs | `searchTasks` (reporter, commenter, assignee) | `/search/issues` (author, commenter, assignee) | `/issues` (`author_id`, `assignee_id`) | `get_bugs` (`submitter`) + `get_status` (created in range) |
|
|
| Bugs owned, updated | — | — | — | `get_bugs` (`owner`) + `get_status` (`log_modified`) |
|
|
| Repositories modified | `git_repositories.getRepositories` (`modified_since_date`) | `/users/{user}/repos` (filtered by `pushed_at`) | — (covered by push events) | — |
|
|
| Commits pushed | — (not in Launchpad REST API) | `/repos/{owner}/{repo}/commits` (per active repo) | events (`action=pushed`) | — |
|
|
|
|
### Default time window
|
|
|
|
Rolling 7 days ending today (inclusive). Override with `--since` / `--until` (ISO dates, UTC).
|
|
|
|
## Configuration
|
|
|
|
`weekly-activity config` opens a small menu-driven editor: it lists your
|
|
current sources (secrets masked), then lets you **Add a new source**
|
|
(choose the kind, repeat as often as you like), **Remove a source**, or
|
|
**Exit** (writes the file only when something changed). Several accounts
|
|
of the same provider can live side by side. Tokens are stored as provided
|
|
(plaintext), but the file is created with owner-only permissions (`0600`)
|
|
and secrets are never echoed back. Use `--config <path>` (also accepted by
|
|
`report`) to keep configs elsewhere.
|
|
|
|
Default location: `${XDG_CONFIG_HOME:-~/.config}/weekly-activity.toml`.
|
|
|
|
```toml
|
|
version = 2
|
|
|
|
[[launchpad.accounts]]
|
|
username = "jane"
|
|
mode = "credentials" # "anonymous" (public data only) | "credentials"
|
|
# credentials_file = "~/lp-creds.json" # omit -> system keyring
|
|
|
|
[[github.accounts]]
|
|
name = "personal" # optional display label
|
|
username = "octocat"
|
|
token = "" # empty -> $token_env / GITHUB_TOKEN / `gh auth token`
|
|
|
|
[[github.accounts]]
|
|
name = "work"
|
|
username = "acme-jane"
|
|
token_env = "WORK_GH_TOKEN"
|
|
|
|
[[gitlab.accounts]]
|
|
name = "salsa"
|
|
url = "https://salsa.debian.org"
|
|
username = "jane"
|
|
token_env = "SALSA_TOKEN"
|
|
|
|
[[bts.accounts]]
|
|
email = "jane@example.org"
|
|
```
|
|
|
|
An absent section disables that source; an empty one is not written.
|
|
When a non-anonymous Launchpad account is added without a credentials
|
|
file, the wizard runs launchpadlib's OAuth right there — a browser opens
|
|
once and the token is kept in your system keyring, so `report` never has
|
|
to authenticate.
|
|
|
|
Legacy configs without the `version` key (one single-account section per
|
|
source) keep working: they are migrated transparently when loaded and
|
|
rewritten in the v2 shape the next time the wizard saves.
|
|
|
|
|
|
## Architecture
|
|
|
|
```
|
|
src/weekly_activity/
|
|
model.py ActivityReport, Section, last_week_window
|
|
report.py format_report() — source-agnostic text renderer
|
|
cli.py subcommand dispatch (launchpad | github | gitlab | bts | report | config)
|
|
aggregate.py collect_specs()/format_combined() — one text report across sources
|
|
config.py ActivityConfig schema + TOML load/save (tomllib read, hand-written)
|
|
wizard.py interactive `config` onboarding
|
|
sources/
|
|
__init__.py ActivitySource protocol
|
|
launchpad.py LaunchpadSource (launchpadlib)
|
|
github.py GitHubSource (httpx)
|
|
gitlab.py GitLabSource (httpx, REST v4 — gitlab.com or self-hosted)
|
|
debian_bts.py DebianBtsSource (python-debianbts SOAP)
|
|
```
|
|
|
|
Each source implements `ActivitySource` — a `name` and a `collect(username, since, until) -> ActivityReport` method. Sources keep their own dataclasses internally; the shared contract is `Section` (title + pre-formatted lines) and `ActivityReport` (metadata + sections + warnings). The report formatter (`report.py`) is source-agnostic.
|
|
|
|
Per-query failures (rate limits, server timeouts) are caught per-section — a failing endpoint records a warning and degrades to an empty section rather than killing the whole report.
|
|
|
|
## Development
|
|
|
|
```bash
|
|
uv sync # install deps + dev tooling
|
|
uv run ruff format src # format
|
|
uv run ruff check src # lint
|
|
uv run ty check # type check
|
|
```
|
|
|
|
## CI / releases
|
|
|
|
Gitea Actions (`.gitea/workflows/ci.yml`) runs two jobs:
|
|
|
|
- **check** — format, lint, and type check on every push to `main`
|
|
- **release** — on `v*` tags: builds a standalone Linux executable with
|
|
PyInstaller, smoke-tests it, creates a Gitea release, and uploads the
|
|
binary as an asset
|
|
|
|
Cut a release with:
|
|
|
|
```bash
|
|
git tag vX.Y.Z && git push origin vX.Y.Z
|
|
```
|
|
|
|
## Dependencies
|
|
|
|
- **launchpadlib** — Launchpad REST API client
|
|
- **httpx** — GitHub & GitLab REST API client
|
|
- **python-debianbts** — Debian BTS SOAP client
|
|
- **ruff** — format + lint (dev)
|
|
- **ty** — type check (dev)
|