kosmos 5f4487f3ad feat(config): menu-driven multi-account wizard, v2 schema, config-time Launchpad OAuth
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`
2026-08-26 22:53:17 +00:00
2026-08-25 20:36:54 +02:00

weekly-activity

Aggregate weekly activity across development platforms (Launchpad, GitHub, GitLab, Debian BTS) into a single text report.

Usage

# 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)

# 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.

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

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:

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)
S
Description
No description provided
Readme
258 KiB
v0.3.0
Latest
2026-08-25 18:45:27 +00:00
Languages
Python 100%