gh-weekly-updates 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gh_weekly_updates-0.1.0/LICENSE +21 -0
- gh_weekly_updates-0.1.0/PKG-INFO +181 -0
- gh_weekly_updates-0.1.0/README.md +150 -0
- gh_weekly_updates-0.1.0/pyproject.toml +54 -0
- gh_weekly_updates-0.1.0/setup.cfg +4 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates/__init__.py +1 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates/cli.py +238 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates/collector.py +443 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates/config.py +66 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates/contributions.py +91 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates/models.py +190 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates/publisher.py +72 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates/summariser.py +120 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates.egg-info/PKG-INFO +181 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates.egg-info/SOURCES.txt +17 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates.egg-info/dependency_links.txt +1 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates.egg-info/entry_points.txt +2 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates.egg-info/requires.txt +10 -0
- gh_weekly_updates-0.1.0/src/gh_weekly_updates.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sahan Serasinghe <sahan.serasinghe@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gh-weekly-updates
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Automatically discover and summarise your weekly GitHub impact
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/sahansera/gh-weekly-updates
|
|
7
|
+
Project-URL: Repository, https://github.com/sahansera/gh-weekly-updates
|
|
8
|
+
Project-URL: Issues, https://github.com/sahansera/gh-weekly-updates/issues
|
|
9
|
+
Keywords: github,weekly,impact,summary,engineering
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: click>=8.1
|
|
22
|
+
Requires-Dist: httpx>=0.27
|
|
23
|
+
Requires-Dist: pydantic>=2.0
|
|
24
|
+
Requires-Dist: pyyaml>=6.0
|
|
25
|
+
Requires-Dist: rich>=13.0
|
|
26
|
+
Requires-Dist: openai>=1.30
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# gh-weekly-updates
|
|
33
|
+
|
|
34
|
+
> Automatically discover and summarise your weekly GitHub impact using AI.
|
|
35
|
+
|
|
36
|
+
`gh-weekly-updates` collects your GitHub activity — pull requests authored & reviewed, issues created & commented on, discussions — and generates a structured Markdown summary using [GitHub Models](https://github.com/marketplace/models).
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- **Auto-discover repos** via the GitHub GraphQL Contributions API, or provide an explicit list
|
|
41
|
+
- **Collect detailed activity**: PRs, reviews, issues, issue comments, discussions
|
|
42
|
+
- **AI-powered summarisation** via GitHub Models (`openai/gpt-4.1` by default)
|
|
43
|
+
- **Structured output**: Wins / Strategic Influence / Challenges with inline links
|
|
44
|
+
- **Push to a repo**: automatically commit summaries to a GitHub repo for sharing
|
|
45
|
+
- **Fully configurable**: org, repos, model, prompt — via YAML config or CLI flags
|
|
46
|
+
- **Custom prompts**: tailor the AI summary to your team's format
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install gh-weekly-updates
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Requirements
|
|
55
|
+
|
|
56
|
+
- Python 3.11+
|
|
57
|
+
- A GitHub personal access token (PAT) **or** the [GitHub CLI](https://cli.github.com/) (`gh`) authenticated
|
|
58
|
+
- Required scopes: `repo`, `read:org`
|
|
59
|
+
- The token also needs access to [GitHub Models](https://github.com/marketplace/models) for AI summarisation
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Authenticate with the GitHub CLI (easiest)
|
|
65
|
+
gh auth login
|
|
66
|
+
|
|
67
|
+
# Run with defaults — covers the past week
|
|
68
|
+
gh-weekly-updates
|
|
69
|
+
|
|
70
|
+
# Or specify a date range
|
|
71
|
+
gh-weekly-updates --since 2025-06-01 --until 2025-06-07
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
Create a `config.yaml` (see [config.example.yaml](config.example.yaml)):
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
# GitHub org to scope repo discovery to
|
|
80
|
+
org: my-org
|
|
81
|
+
|
|
82
|
+
# Explicit repo list (skips auto-discovery)
|
|
83
|
+
repos:
|
|
84
|
+
- my-org/api-service
|
|
85
|
+
- my-org/web-app
|
|
86
|
+
|
|
87
|
+
# GitHub Model for summarisation
|
|
88
|
+
model: openai/gpt-4.1
|
|
89
|
+
|
|
90
|
+
# Push summary to a repo under weekly-updates/
|
|
91
|
+
push_repo: my-user/my-updates
|
|
92
|
+
|
|
93
|
+
# Custom system prompt (inline)
|
|
94
|
+
prompt: |
|
|
95
|
+
You are an engineering manager writing a concise weekly summary...
|
|
96
|
+
|
|
97
|
+
# Or load prompt from a file
|
|
98
|
+
# prompt_file: my-prompt.txt
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## CLI reference
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
Usage: gh-weekly-updates [OPTIONS]
|
|
105
|
+
|
|
106
|
+
Options:
|
|
107
|
+
--config PATH Path to YAML config file. Default: ./config.yaml
|
|
108
|
+
--since TEXT Start date (ISO 8601). Default: previous Monday.
|
|
109
|
+
--until TEXT End date (ISO 8601). Default: now.
|
|
110
|
+
--user TEXT GitHub username. Default: authenticated user.
|
|
111
|
+
--repos TEXT Comma-separated list of repos (owner/name).
|
|
112
|
+
--org TEXT GitHub org to scope repo discovery to.
|
|
113
|
+
--output PATH Write summary to a file instead of stdout.
|
|
114
|
+
--push TEXT Push summary to a GitHub repo (owner/name).
|
|
115
|
+
--model TEXT GitHub Model to use. Default: openai/gpt-4.1.
|
|
116
|
+
--verbose Enable debug logging.
|
|
117
|
+
--help Show this message and exit.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Authentication
|
|
121
|
+
|
|
122
|
+
`gh-weekly-updates` resolves a GitHub token in this order:
|
|
123
|
+
|
|
124
|
+
1. `GITHUB_TOKEN` environment variable
|
|
125
|
+
2. `gh auth token` (GitHub CLI)
|
|
126
|
+
|
|
127
|
+
For GitHub Enterprise with SSO, ensure the token is authorised for your org.
|
|
128
|
+
|
|
129
|
+
## Using with GitHub Actions
|
|
130
|
+
|
|
131
|
+
You can run `gh-weekly-updates` on a schedule in a GitHub Actions workflow:
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
name: Weekly Impact Summary
|
|
135
|
+
|
|
136
|
+
on:
|
|
137
|
+
schedule:
|
|
138
|
+
- cron: '0 9 * * 1' # Every Monday at 9am UTC
|
|
139
|
+
workflow_dispatch:
|
|
140
|
+
|
|
141
|
+
jobs:
|
|
142
|
+
summarise:
|
|
143
|
+
runs-on: ubuntu-latest
|
|
144
|
+
steps:
|
|
145
|
+
- uses: actions/checkout@v4
|
|
146
|
+
|
|
147
|
+
- uses: actions/setup-python@v5
|
|
148
|
+
with:
|
|
149
|
+
python-version: '3.12'
|
|
150
|
+
|
|
151
|
+
- name: Install gh-weekly-updates
|
|
152
|
+
run: pip install gh-weekly-updates
|
|
153
|
+
|
|
154
|
+
- name: Generate summary
|
|
155
|
+
env:
|
|
156
|
+
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
|
157
|
+
run: |
|
|
158
|
+
gh-weekly-updates \
|
|
159
|
+
--config config.yaml \
|
|
160
|
+
--push my-user/my-updates
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
> **Note**: The default `GITHUB_TOKEN` provided by Actions has limited scope.
|
|
164
|
+
> Use a [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) stored as a repository secret with `repo` and `read:org` scopes, plus GitHub Models access.
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
git clone https://github.com/sahansera/gh-weekly-updates.git
|
|
170
|
+
cd gh-weekly-updates
|
|
171
|
+
python -m venv .venv && source .venv/bin/activate
|
|
172
|
+
make dev # install in editable mode with dev deps
|
|
173
|
+
make run # run with defaults
|
|
174
|
+
make lint # ruff check
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# gh-weekly-updates
|
|
2
|
+
|
|
3
|
+
> Automatically discover and summarise your weekly GitHub impact using AI.
|
|
4
|
+
|
|
5
|
+
`gh-weekly-updates` collects your GitHub activity — pull requests authored & reviewed, issues created & commented on, discussions — and generates a structured Markdown summary using [GitHub Models](https://github.com/marketplace/models).
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Auto-discover repos** via the GitHub GraphQL Contributions API, or provide an explicit list
|
|
10
|
+
- **Collect detailed activity**: PRs, reviews, issues, issue comments, discussions
|
|
11
|
+
- **AI-powered summarisation** via GitHub Models (`openai/gpt-4.1` by default)
|
|
12
|
+
- **Structured output**: Wins / Strategic Influence / Challenges with inline links
|
|
13
|
+
- **Push to a repo**: automatically commit summaries to a GitHub repo for sharing
|
|
14
|
+
- **Fully configurable**: org, repos, model, prompt — via YAML config or CLI flags
|
|
15
|
+
- **Custom prompts**: tailor the AI summary to your team's format
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install gh-weekly-updates
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Requirements
|
|
24
|
+
|
|
25
|
+
- Python 3.11+
|
|
26
|
+
- A GitHub personal access token (PAT) **or** the [GitHub CLI](https://cli.github.com/) (`gh`) authenticated
|
|
27
|
+
- Required scopes: `repo`, `read:org`
|
|
28
|
+
- The token also needs access to [GitHub Models](https://github.com/marketplace/models) for AI summarisation
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Authenticate with the GitHub CLI (easiest)
|
|
34
|
+
gh auth login
|
|
35
|
+
|
|
36
|
+
# Run with defaults — covers the past week
|
|
37
|
+
gh-weekly-updates
|
|
38
|
+
|
|
39
|
+
# Or specify a date range
|
|
40
|
+
gh-weekly-updates --since 2025-06-01 --until 2025-06-07
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
Create a `config.yaml` (see [config.example.yaml](config.example.yaml)):
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
# GitHub org to scope repo discovery to
|
|
49
|
+
org: my-org
|
|
50
|
+
|
|
51
|
+
# Explicit repo list (skips auto-discovery)
|
|
52
|
+
repos:
|
|
53
|
+
- my-org/api-service
|
|
54
|
+
- my-org/web-app
|
|
55
|
+
|
|
56
|
+
# GitHub Model for summarisation
|
|
57
|
+
model: openai/gpt-4.1
|
|
58
|
+
|
|
59
|
+
# Push summary to a repo under weekly-updates/
|
|
60
|
+
push_repo: my-user/my-updates
|
|
61
|
+
|
|
62
|
+
# Custom system prompt (inline)
|
|
63
|
+
prompt: |
|
|
64
|
+
You are an engineering manager writing a concise weekly summary...
|
|
65
|
+
|
|
66
|
+
# Or load prompt from a file
|
|
67
|
+
# prompt_file: my-prompt.txt
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## CLI reference
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
Usage: gh-weekly-updates [OPTIONS]
|
|
74
|
+
|
|
75
|
+
Options:
|
|
76
|
+
--config PATH Path to YAML config file. Default: ./config.yaml
|
|
77
|
+
--since TEXT Start date (ISO 8601). Default: previous Monday.
|
|
78
|
+
--until TEXT End date (ISO 8601). Default: now.
|
|
79
|
+
--user TEXT GitHub username. Default: authenticated user.
|
|
80
|
+
--repos TEXT Comma-separated list of repos (owner/name).
|
|
81
|
+
--org TEXT GitHub org to scope repo discovery to.
|
|
82
|
+
--output PATH Write summary to a file instead of stdout.
|
|
83
|
+
--push TEXT Push summary to a GitHub repo (owner/name).
|
|
84
|
+
--model TEXT GitHub Model to use. Default: openai/gpt-4.1.
|
|
85
|
+
--verbose Enable debug logging.
|
|
86
|
+
--help Show this message and exit.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Authentication
|
|
90
|
+
|
|
91
|
+
`gh-weekly-updates` resolves a GitHub token in this order:
|
|
92
|
+
|
|
93
|
+
1. `GITHUB_TOKEN` environment variable
|
|
94
|
+
2. `gh auth token` (GitHub CLI)
|
|
95
|
+
|
|
96
|
+
For GitHub Enterprise with SSO, ensure the token is authorised for your org.
|
|
97
|
+
|
|
98
|
+
## Using with GitHub Actions
|
|
99
|
+
|
|
100
|
+
You can run `gh-weekly-updates` on a schedule in a GitHub Actions workflow:
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
name: Weekly Impact Summary
|
|
104
|
+
|
|
105
|
+
on:
|
|
106
|
+
schedule:
|
|
107
|
+
- cron: '0 9 * * 1' # Every Monday at 9am UTC
|
|
108
|
+
workflow_dispatch:
|
|
109
|
+
|
|
110
|
+
jobs:
|
|
111
|
+
summarise:
|
|
112
|
+
runs-on: ubuntu-latest
|
|
113
|
+
steps:
|
|
114
|
+
- uses: actions/checkout@v4
|
|
115
|
+
|
|
116
|
+
- uses: actions/setup-python@v5
|
|
117
|
+
with:
|
|
118
|
+
python-version: '3.12'
|
|
119
|
+
|
|
120
|
+
- name: Install gh-weekly-updates
|
|
121
|
+
run: pip install gh-weekly-updates
|
|
122
|
+
|
|
123
|
+
- name: Generate summary
|
|
124
|
+
env:
|
|
125
|
+
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
|
126
|
+
run: |
|
|
127
|
+
gh-weekly-updates \
|
|
128
|
+
--config config.yaml \
|
|
129
|
+
--push my-user/my-updates
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
> **Note**: The default `GITHUB_TOKEN` provided by Actions has limited scope.
|
|
133
|
+
> Use a [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) stored as a repository secret with `repo` and `read:org` scopes, plus GitHub Models access.
|
|
134
|
+
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
git clone https://github.com/sahansera/gh-weekly-updates.git
|
|
139
|
+
cd gh-weekly-updates
|
|
140
|
+
python -m venv .venv && source .venv/bin/activate
|
|
141
|
+
make dev # install in editable mode with dev deps
|
|
142
|
+
make run # run with defaults
|
|
143
|
+
make lint # ruff check
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gh-weekly-updates"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Automatically discover and summarise your weekly GitHub impact"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
keywords = ["github", "weekly", "impact", "summary", "engineering"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Topic :: Software Development",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"click>=8.1",
|
|
25
|
+
"httpx>=0.27",
|
|
26
|
+
"pydantic>=2.0",
|
|
27
|
+
"pyyaml>=6.0",
|
|
28
|
+
"rich>=13.0",
|
|
29
|
+
"openai>=1.30",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"ruff>=0.4",
|
|
35
|
+
"pytest>=8.0",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/sahansera/gh-weekly-updates"
|
|
40
|
+
Repository = "https://github.com/sahansera/gh-weekly-updates"
|
|
41
|
+
Issues = "https://github.com/sahansera/gh-weekly-updates/issues"
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
gh-weekly-updates = "gh_weekly_updates.cli:main"
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
target-version = "py311"
|
|
48
|
+
line-length = 100
|
|
49
|
+
|
|
50
|
+
[tool.ruff.lint]
|
|
51
|
+
select = ["E", "F", "I", "W"]
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.packages.find]
|
|
54
|
+
where = ["src"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""gh-weekly-updates — auto-discover and summarise your weekly GitHub contributions."""
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""CLI entrypoint for gh-weekly-updates."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime, timedelta, timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
import yaml
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.logging import RichHandler
|
|
14
|
+
from rich.markdown import Markdown
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
|
|
17
|
+
from gh_weekly_updates.collector import collect_activity
|
|
18
|
+
from gh_weekly_updates.config import get_github_token, get_github_username
|
|
19
|
+
from gh_weekly_updates.contributions import discover_repos
|
|
20
|
+
from gh_weekly_updates.publisher import publish_to_repo
|
|
21
|
+
from gh_weekly_updates.summariser import DEFAULT_MODEL, summarise
|
|
22
|
+
|
|
23
|
+
console = Console()
|
|
24
|
+
|
|
25
|
+
DEFAULT_CONFIG_PATH = "config.yaml"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _load_config(path: str | None) -> dict:
|
|
29
|
+
"""Load YAML config file. Returns empty dict if not found."""
|
|
30
|
+
config_path = Path(path) if path else Path(DEFAULT_CONFIG_PATH)
|
|
31
|
+
if config_path.exists():
|
|
32
|
+
with open(config_path) as f:
|
|
33
|
+
return yaml.safe_load(f) or {}
|
|
34
|
+
return {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _default_since() -> datetime:
|
|
38
|
+
"""Return the previous Monday 00:00 UTC so we always cover at least a full week."""
|
|
39
|
+
now = datetime.now(timezone.utc)
|
|
40
|
+
today_midnight = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
41
|
+
days_since_monday = now.weekday() # Monday=0
|
|
42
|
+
# Always go back to LAST week's Monday (7 + days_since_monday days back)
|
|
43
|
+
# e.g. Tuesday → 8 days back, Monday → 7 days back
|
|
44
|
+
return today_midnight - timedelta(days=days_since_monday + 7)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _parse_date(value: str) -> datetime:
|
|
48
|
+
"""Parse an ISO-8601 date string into a timezone-aware datetime."""
|
|
49
|
+
dt = datetime.fromisoformat(value)
|
|
50
|
+
if dt.tzinfo is None:
|
|
51
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
52
|
+
return dt
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@click.command()
|
|
56
|
+
@click.option(
|
|
57
|
+
"--config",
|
|
58
|
+
"config_path",
|
|
59
|
+
default=None,
|
|
60
|
+
type=click.Path(),
|
|
61
|
+
help="Path to YAML config file. Default: ./config.yaml",
|
|
62
|
+
)
|
|
63
|
+
@click.option(
|
|
64
|
+
"--since",
|
|
65
|
+
default=None,
|
|
66
|
+
help="Start date (ISO 8601). Default: Monday of previous week.",
|
|
67
|
+
)
|
|
68
|
+
@click.option(
|
|
69
|
+
"--until",
|
|
70
|
+
default=None,
|
|
71
|
+
help="End date (ISO 8601). Default: now.",
|
|
72
|
+
)
|
|
73
|
+
@click.option(
|
|
74
|
+
"--user",
|
|
75
|
+
default=None,
|
|
76
|
+
help="GitHub username. Default: authenticated user.",
|
|
77
|
+
)
|
|
78
|
+
@click.option(
|
|
79
|
+
"--repos",
|
|
80
|
+
default=None,
|
|
81
|
+
help="Comma-separated list of repos (owner/name). Skips auto-discovery.",
|
|
82
|
+
)
|
|
83
|
+
@click.option(
|
|
84
|
+
"--output",
|
|
85
|
+
default=None,
|
|
86
|
+
type=click.Path(),
|
|
87
|
+
help="Write summary to file instead of stdout.",
|
|
88
|
+
)
|
|
89
|
+
@click.option(
|
|
90
|
+
"--org",
|
|
91
|
+
default=None,
|
|
92
|
+
help="GitHub org to scope repo discovery to.",
|
|
93
|
+
)
|
|
94
|
+
@click.option(
|
|
95
|
+
"--push",
|
|
96
|
+
"push_repo",
|
|
97
|
+
default=None,
|
|
98
|
+
help="Push summary to a GitHub repo (owner/name).",
|
|
99
|
+
)
|
|
100
|
+
@click.option(
|
|
101
|
+
"--model",
|
|
102
|
+
default=DEFAULT_MODEL,
|
|
103
|
+
show_default=True,
|
|
104
|
+
help="GitHub Model to use for summarisation.",
|
|
105
|
+
)
|
|
106
|
+
@click.option(
|
|
107
|
+
"--verbose",
|
|
108
|
+
is_flag=True,
|
|
109
|
+
help="Enable debug logging.",
|
|
110
|
+
)
|
|
111
|
+
def main(
|
|
112
|
+
config_path: str | None,
|
|
113
|
+
since: str | None,
|
|
114
|
+
until: str | None,
|
|
115
|
+
user: str | None,
|
|
116
|
+
repos: str | None,
|
|
117
|
+
org: str | None,
|
|
118
|
+
push_repo: str | None,
|
|
119
|
+
output: str | None,
|
|
120
|
+
model: str,
|
|
121
|
+
verbose: bool,
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Discover your weekly GitHub impact and generate a structured summary."""
|
|
124
|
+
# --- Load config ---
|
|
125
|
+
cfg = _load_config(config_path)
|
|
126
|
+
|
|
127
|
+
# CLI flags override config file values
|
|
128
|
+
org = org or cfg.get("org")
|
|
129
|
+
model = model if model != DEFAULT_MODEL else cfg.get("model", model)
|
|
130
|
+
push_repo = push_repo or cfg.get("push_repo")
|
|
131
|
+
|
|
132
|
+
# Custom prompt: inline in config or from a file
|
|
133
|
+
custom_prompt = cfg.get("prompt")
|
|
134
|
+
if not custom_prompt and cfg.get("prompt_file"):
|
|
135
|
+
prompt_path = Path(cfg["prompt_file"])
|
|
136
|
+
if prompt_path.exists():
|
|
137
|
+
custom_prompt = prompt_path.read_text()
|
|
138
|
+
|
|
139
|
+
# Repos: CLI --repos flag (comma-separated) > config file list
|
|
140
|
+
if repos:
|
|
141
|
+
repo_list_override = [r.strip() for r in repos.split(",") if r.strip()]
|
|
142
|
+
elif cfg.get("repos"):
|
|
143
|
+
repo_list_override = cfg["repos"]
|
|
144
|
+
else:
|
|
145
|
+
repo_list_override = None
|
|
146
|
+
|
|
147
|
+
# --- Logging ---
|
|
148
|
+
log_level = logging.DEBUG if verbose else logging.INFO
|
|
149
|
+
logging.basicConfig(
|
|
150
|
+
level=log_level,
|
|
151
|
+
format="%(message)s",
|
|
152
|
+
handlers=[RichHandler(rich_tracebacks=True, show_path=False)],
|
|
153
|
+
)
|
|
154
|
+
log = logging.getLogger("gh_weekly_updates")
|
|
155
|
+
|
|
156
|
+
# --- Auth ---
|
|
157
|
+
with console.status("[bold green]Authenticating with GitHub..."):
|
|
158
|
+
token = get_github_token()
|
|
159
|
+
|
|
160
|
+
# --- Resolve user ---
|
|
161
|
+
if not user:
|
|
162
|
+
with console.status("[bold green]Resolving GitHub username..."):
|
|
163
|
+
user = get_github_username(token)
|
|
164
|
+
console.print(f"[bold]User:[/bold] {user}")
|
|
165
|
+
if org:
|
|
166
|
+
console.print(f"[bold]Org:[/bold] {org}")
|
|
167
|
+
|
|
168
|
+
# --- Date range ---
|
|
169
|
+
since_dt = _parse_date(since) if since else _default_since()
|
|
170
|
+
until_dt = _parse_date(until) if until else datetime.now(timezone.utc)
|
|
171
|
+
|
|
172
|
+
console.print(
|
|
173
|
+
f"[bold]Period:[/bold] {since_dt.strftime('%Y-%m-%d')} → "
|
|
174
|
+
f"{until_dt.strftime('%Y-%m-%d %H:%M')} UTC"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# --- Resolve repos ---
|
|
178
|
+
if repo_list_override:
|
|
179
|
+
repo_list = repo_list_override
|
|
180
|
+
console.print(f"[bold]Using configured repos:[/bold] {len(repo_list)}")
|
|
181
|
+
else:
|
|
182
|
+
with console.status("[bold green]Discovering repos you contributed to..."):
|
|
183
|
+
repo_list = discover_repos(token, user, since_dt, until_dt, org=org)
|
|
184
|
+
|
|
185
|
+
if not repo_list:
|
|
186
|
+
console.print("[yellow]No repos found for this period. Nothing to summarise.[/yellow]")
|
|
187
|
+
sys.exit(0)
|
|
188
|
+
|
|
189
|
+
console.print(f"[bold]Repos:[/bold] {len(repo_list)}")
|
|
190
|
+
for r in repo_list:
|
|
191
|
+
console.print(f" • {r}")
|
|
192
|
+
|
|
193
|
+
# --- Collect activity ---
|
|
194
|
+
with console.status("[bold green]Collecting activity across repos..."):
|
|
195
|
+
activity = collect_activity(token, user, repo_list, since_dt, until_dt)
|
|
196
|
+
|
|
197
|
+
console.print(
|
|
198
|
+
Panel(
|
|
199
|
+
f"[bold]{activity.total_activities}[/bold] activities collected\n"
|
|
200
|
+
f" PRs authored: {len(activity.prs_authored)}\n"
|
|
201
|
+
f" PRs reviewed: {len(activity.prs_reviewed)}\n"
|
|
202
|
+
f" Issues created: {len(activity.issues_created)}\n"
|
|
203
|
+
f" Issue comments: {len(activity.issue_comments)}\n"
|
|
204
|
+
f" Discussions created: {len(activity.discussions_created)}\n"
|
|
205
|
+
f" Discussion comments: {len(activity.discussion_comments)}",
|
|
206
|
+
title="Activity Summary",
|
|
207
|
+
border_style="green",
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if activity.total_activities == 0:
|
|
212
|
+
console.print("[yellow]No activities found. Nothing to summarise.[/yellow]")
|
|
213
|
+
sys.exit(0)
|
|
214
|
+
|
|
215
|
+
# --- Summarise ---
|
|
216
|
+
with console.status("[bold green]Generating impact summary via AI..."):
|
|
217
|
+
summary = summarise(activity, token, model, custom_prompt=custom_prompt)
|
|
218
|
+
|
|
219
|
+
# --- Output ---
|
|
220
|
+
if output:
|
|
221
|
+
with open(output, "w") as f:
|
|
222
|
+
f.write(summary)
|
|
223
|
+
console.print(f"\n[bold green]Summary written to {output}[/bold green]")
|
|
224
|
+
else:
|
|
225
|
+
console.print()
|
|
226
|
+
console.print(Markdown(summary))
|
|
227
|
+
|
|
228
|
+
# --- Push to repo ---
|
|
229
|
+
if push_repo:
|
|
230
|
+
with console.status(f"[bold green]Pushing to {push_repo}..."):
|
|
231
|
+
publish_to_repo(summary, push_repo, since_dt, until_dt)
|
|
232
|
+
console.print(
|
|
233
|
+
f"\n[bold green]Pushed to https://github.com/{push_repo}/tree/main/weekly-updates[/bold green]"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
if __name__ == "__main__":
|
|
238
|
+
main()
|