github-weekly-reporter 0.1.0 → 0.2.0
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.
- package/README.md +86 -115
- package/dist/cli/commands/deploy.d.ts.map +1 -1
- package/dist/cli/commands/deploy.js +9 -3
- package/dist/cli/commands/deploy.js.map +1 -1
- package/dist/cli/commands/fetch.d.ts +3 -0
- package/dist/cli/commands/fetch.d.ts.map +1 -0
- package/dist/cli/commands/fetch.js +148 -0
- package/dist/cli/commands/fetch.js.map +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +59 -80
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/render.d.ts +3 -0
- package/dist/cli/commands/render.d.ts.map +1 -0
- package/dist/cli/commands/render.js +172 -0
- package/dist/cli/commands/render.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +3 -0
- package/dist/cli/commands/setup.d.ts.map +1 -0
- package/dist/cli/commands/setup.js +709 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/index.js +6 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/collector/clean-body.d.ts +2 -0
- package/dist/collector/clean-body.d.ts.map +1 -0
- package/dist/collector/clean-body.js +27 -0
- package/dist/collector/clean-body.js.map +1 -0
- package/dist/collector/date-range.d.ts +3 -2
- package/dist/collector/date-range.d.ts.map +1 -1
- package/dist/collector/date-range.js +125 -5
- package/dist/collector/date-range.js.map +1 -1
- package/dist/collector/fetch-contributions.d.ts +11 -1
- package/dist/collector/fetch-contributions.d.ts.map +1 -1
- package/dist/collector/fetch-contributions.js +15 -3
- package/dist/collector/fetch-contributions.js.map +1 -1
- package/dist/collector/fetch-events.d.ts +6 -0
- package/dist/collector/fetch-events.d.ts.map +1 -0
- package/dist/collector/fetch-events.js +106 -0
- package/dist/collector/fetch-events.js.map +1 -0
- package/dist/collector/fetch-repo-prs.d.ts +7 -0
- package/dist/collector/fetch-repo-prs.d.ts.map +1 -0
- package/dist/collector/fetch-repo-prs.js +62 -0
- package/dist/collector/fetch-repo-prs.js.map +1 -0
- package/dist/collector/queries.d.ts +1 -4
- package/dist/collector/queries.d.ts.map +1 -1
- package/dist/collector/queries.js +7 -59
- package/dist/collector/queries.js.map +1 -1
- package/dist/deployer/index-page.d.ts +22 -2
- package/dist/deployer/index-page.d.ts.map +1 -1
- package/dist/deployer/index-page.js +424 -33
- package/dist/deployer/index-page.js.map +1 -1
- package/dist/deployer/week.d.ts +1 -1
- package/dist/deployer/week.d.ts.map +1 -1
- package/dist/deployer/week.js +17 -6
- package/dist/deployer/week.js.map +1 -1
- package/dist/i18n/fonts.d.ts +8 -0
- package/dist/i18n/fonts.d.ts.map +1 -0
- package/dist/i18n/fonts.js +44 -0
- package/dist/i18n/fonts.js.map +1 -0
- package/dist/i18n/index.d.ts +28 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +259 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/llm/index.d.ts +2 -1
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +77 -5
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/preprocess.d.ts +3 -0
- package/dist/llm/preprocess.d.ts.map +1 -0
- package/dist/llm/preprocess.js +92 -0
- package/dist/llm/preprocess.js.map +1 -0
- package/dist/llm/prompt.d.ts.map +1 -1
- package/dist/llm/prompt.js +109 -29
- package/dist/llm/prompt.js.map +1 -1
- package/dist/llm/providers/anthropic.js +1 -1
- package/dist/llm/providers/anthropic.js.map +1 -1
- package/dist/llm/providers/grok.d.ts +3 -0
- package/dist/llm/providers/grok.d.ts.map +1 -0
- package/dist/llm/providers/grok.js +18 -0
- package/dist/llm/providers/grok.js.map +1 -0
- package/dist/llm/providers/groq.d.ts +3 -0
- package/dist/llm/providers/groq.d.ts.map +1 -0
- package/dist/llm/providers/groq.js +18 -0
- package/dist/llm/providers/groq.js.map +1 -0
- package/dist/llm/providers/openai.js +1 -1
- package/dist/llm/providers/openai.js.map +1 -1
- package/dist/llm/providers/openrouter.d.ts +3 -0
- package/dist/llm/providers/openrouter.d.ts.map +1 -0
- package/dist/llm/providers/openrouter.js +18 -0
- package/dist/llm/providers/openrouter.js.map +1 -0
- package/dist/llm/types.d.ts +5 -2
- package/dist/llm/types.d.ts.map +1 -1
- package/dist/renderer/helpers.d.ts +6 -1
- package/dist/renderer/helpers.d.ts.map +1 -1
- package/dist/renderer/helpers.js +54 -17
- package/dist/renderer/helpers.js.map +1 -1
- package/dist/renderer/index.d.ts +11 -2
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +41 -19
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/og-image.d.ts +15 -0
- package/dist/renderer/og-image.d.ts.map +1 -0
- package/dist/renderer/og-image.js +152 -0
- package/dist/renderer/og-image.js.map +1 -0
- package/dist/renderer/themes.d.ts +2 -17
- package/dist/renderer/themes.d.ts.map +1 -1
- package/dist/renderer/themes.js +371 -147
- package/dist/renderer/themes.js.map +1 -1
- package/dist/types.d.ts +103 -11
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -2
- package/src/renderer/templates/partials/footer.hbs +3 -1
- package/src/renderer/templates/partials/header.hbs +10 -7
- package/src/renderer/templates/partials/highlights.hbs +24 -0
- package/src/renderer/templates/partials/overview.hbs +3 -0
- package/src/renderer/templates/partials/summaries.hbs +73 -0
- package/src/renderer/templates/report.hbs +100 -15
- package/dist/cli/config.d.ts +0 -11
- package/dist/cli/config.d.ts.map +0 -1
- package/dist/cli/config.js +0 -16
- package/dist/cli/config.js.map +0 -1
- package/dist/cli/config.test.d.ts +0 -2
- package/dist/cli/config.test.d.ts.map +0 -1
- package/dist/cli/config.test.js +0 -32
- package/dist/cli/config.test.js.map +0 -1
- package/dist/collector/aggregate.test.d.ts +0 -2
- package/dist/collector/aggregate.test.d.ts.map +0 -1
- package/dist/collector/aggregate.test.js +0 -88
- package/dist/collector/aggregate.test.js.map +0 -1
- package/dist/collector/date-range.test.d.ts +0 -2
- package/dist/collector/date-range.test.d.ts.map +0 -1
- package/dist/collector/date-range.test.js +0 -25
- package/dist/collector/date-range.test.js.map +0 -1
- package/dist/collector/fetch-issues.d.ts +0 -5
- package/dist/collector/fetch-issues.d.ts.map +0 -1
- package/dist/collector/fetch-issues.js +0 -31
- package/dist/collector/fetch-issues.js.map +0 -1
- package/dist/collector/fetch-languages.d.ts +0 -4
- package/dist/collector/fetch-languages.d.ts.map +0 -1
- package/dist/collector/fetch-languages.js +0 -42
- package/dist/collector/fetch-languages.js.map +0 -1
- package/dist/collector/fetch-pull-requests.d.ts +0 -5
- package/dist/collector/fetch-pull-requests.d.ts.map +0 -1
- package/dist/collector/fetch-pull-requests.js +0 -31
- package/dist/collector/fetch-pull-requests.js.map +0 -1
- package/dist/collector/index.d.ts +0 -3
- package/dist/collector/index.d.ts.map +0 -1
- package/dist/collector/index.js +0 -50
- package/dist/collector/index.js.map +0 -1
- package/dist/deployer/index-page.test.d.ts +0 -2
- package/dist/deployer/index-page.test.d.ts.map +0 -1
- package/dist/deployer/index-page.test.js +0 -29
- package/dist/deployer/index-page.test.js.map +0 -1
- package/dist/deployer/week.test.d.ts +0 -2
- package/dist/deployer/week.test.d.ts.map +0 -1
- package/dist/deployer/week.test.js +0 -21
- package/dist/deployer/week.test.js.map +0 -1
- package/dist/llm/llm.test.d.ts +0 -2
- package/dist/llm/llm.test.d.ts.map +0 -1
- package/dist/llm/llm.test.js +0 -24
- package/dist/llm/llm.test.js.map +0 -1
- package/dist/llm/prompt.test.d.ts +0 -2
- package/dist/llm/prompt.test.d.ts.map +0 -1
- package/dist/llm/prompt.test.js +0 -48
- package/dist/llm/prompt.test.js.map +0 -1
- package/dist/renderer/renderer.test.d.ts +0 -2
- package/dist/renderer/renderer.test.d.ts.map +0 -1
- package/dist/renderer/renderer.test.js +0 -111
- package/dist/renderer/renderer.test.js.map +0 -1
- package/src/renderer/templates/partials/heatmap.hbs +0 -11
- package/src/renderer/templates/partials/languages.hbs +0 -19
- package/src/renderer/templates/partials/narrative.hbs +0 -10
- package/src/renderer/templates/partials/repositories.hbs +0 -25
- package/src/renderer/templates/partials/stats.hbs +0 -8
package/README.md
CHANGED
|
@@ -1,148 +1,119 @@
|
|
|
1
1
|
# GitHub Weekly Reporter
|
|
2
2
|
|
|
3
|
-
Generate beautiful weekly GitHub activity reports with
|
|
3
|
+
Generate beautiful weekly GitHub activity reports with AI-powered narratives.
|
|
4
4
|
|
|
5
|
-
Collect your commits, pull requests, issues, and code reviews from the past week, render them as a polished static HTML page, and deploy to GitHub Pages.
|
|
5
|
+
Collect your commits, pull requests, issues, and code reviews from the past week, render them as a polished static HTML page, and deploy to GitHub Pages automatically.
|
|
6
6
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npx github-weekly-reporter generate \
|
|
11
|
-
-t $GITHUB_TOKEN \
|
|
12
|
-
-u your-username
|
|
13
|
-
|
|
14
|
-
npx github-weekly-reporter deploy \
|
|
15
|
-
-d ./report \
|
|
16
|
-
-r https://github.com/your-username/weekly-report.git
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Features
|
|
20
|
-
|
|
21
|
-
- Weekly stats: commits, PRs opened/merged, issues, reviews
|
|
22
|
-
- Top repositories by activity
|
|
23
|
-
- Language breakdown (CSS-only chart)
|
|
24
|
-
- 7-day contribution heatmap
|
|
25
|
-
- AI-generated narrative summary (optional, supports OpenAI / Anthropic / Gemini)
|
|
26
|
-
- Light and dark themes
|
|
27
|
-
- Self-contained HTML (no external requests, no JavaScript required)
|
|
28
|
-
- Deploys to GitHub Pages with weekly archive
|
|
29
|
-
|
|
30
|
-
## Installation
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
npm install -g github-weekly-reporter
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Or run directly with npx:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npx github-weekly-reporter <command>
|
|
40
|
-
```
|
|
7
|
+
## Prerequisites
|
|
41
8
|
|
|
42
|
-
|
|
9
|
+
Before running setup, have these ready:
|
|
43
10
|
|
|
44
|
-
|
|
11
|
+
1. **GitHub fine-grained PAT** with `All repositories` access and these permissions (all Read & Write):
|
|
12
|
+
`Administration`, `Contents`, `Actions`, `Secrets`, `Pages`, `Workflows`
|
|
13
|
+
([Create one](https://github.com/settings/personal-access-tokens/new))
|
|
45
14
|
|
|
46
|
-
|
|
15
|
+
2. **LLM API key** from one of the supported providers.
|
|
16
|
+
| Provider | Free Tier | Get API Key |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| OpenRouter | Yes | https://openrouter.ai/settings/keys |
|
|
19
|
+
| Groq | Yes | https://console.groq.com/keys |
|
|
20
|
+
| Google Gemini | Yes | https://aistudio.google.com/apikey |
|
|
21
|
+
| OpenAI | No | https://platform.openai.com/api-keys |
|
|
22
|
+
| Anthropic | No | https://console.anthropic.com/settings/keys |
|
|
23
|
+
| Grok (xAI) | No | https://console.x.ai |
|
|
47
24
|
|
|
48
|
-
|
|
49
|
-
github-weekly-reporter generate [options]
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
| Option | Env Variable | Config Key | Description |
|
|
53
|
-
|---|---|---|---|
|
|
54
|
-
| `-t, --token` | `GITHUB_TOKEN` | - | GitHub personal access token (required) |
|
|
55
|
-
| `-u, --username` | `GITHUB_USERNAME` | `username` | GitHub username (required) |
|
|
56
|
-
| `-o, --output` | - | `output` | Output directory (default: `./report`) |
|
|
57
|
-
| `--theme` | - | `theme` | `default` or `dark` |
|
|
58
|
-
| `--llm-provider` | `LLM_PROVIDER` | `llm.provider` | `openai`, `anthropic`, or `gemini` |
|
|
59
|
-
| `--llm-api-key` | `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` | - | LLM API key |
|
|
60
|
-
| `--llm-model` | `LLM_MODEL` | `llm.model` | Model name (e.g. `gpt-4o-mini`) |
|
|
25
|
+
3. **LLM model name** for your chosen provider:
|
|
61
26
|
|
|
62
|
-
|
|
27
|
+
| Provider | Models |
|
|
28
|
+
|---|---|
|
|
29
|
+
| OpenRouter | https://openrouter.ai/models |
|
|
30
|
+
| Groq | https://console.groq.com/docs/models |
|
|
31
|
+
| Google Gemini | https://ai.google.dev/gemini-api/docs/models |
|
|
32
|
+
| OpenAI | https://platform.openai.com/docs/models |
|
|
33
|
+
| Anthropic | https://docs.anthropic.com/en/docs/about-claude/models |
|
|
34
|
+
| Grok (xAI) | https://docs.x.ai/docs/models |
|
|
63
35
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
Push generated report files to the `gh-pages` branch.
|
|
36
|
+
## Quick Start
|
|
67
37
|
|
|
68
38
|
```bash
|
|
69
|
-
github-weekly-reporter
|
|
39
|
+
npx github-weekly-reporter setup
|
|
70
40
|
```
|
|
71
41
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
42
|
+
The setup command will walk you through everything:
|
|
43
|
+
- Creates a repository
|
|
44
|
+
- Adds workflow files (daily fetch + weekly report)
|
|
45
|
+
- Configures secrets (PAT and LLM API key)
|
|
46
|
+
- Enables GitHub Pages
|
|
47
|
+
- Triggers your first weekly report
|
|
76
48
|
|
|
77
|
-
|
|
49
|
+
See [Manual Setup](docs/manual-setup.md) if you prefer to configure everything yourself.
|
|
78
50
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
```toml
|
|
82
|
-
username = "your-username"
|
|
83
|
-
theme = "dark"
|
|
84
|
-
output = "./report"
|
|
51
|
+
## Features
|
|
85
52
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
53
|
+
- Weekly stats: commits, PRs opened/merged, issues, reviews
|
|
54
|
+
- Top repositories by activity
|
|
55
|
+
- Language breakdown (CSS-only chart)
|
|
56
|
+
- 7-day contribution heatmap
|
|
57
|
+
- AI-generated narrative summary (6 providers, free tiers available)
|
|
58
|
+
- Dark theme with responsive design
|
|
59
|
+
- Self-contained HTML (no external requests, no JavaScript required)
|
|
60
|
+
- SEO optimized (OG images, JSON-LD, sitemap)
|
|
61
|
+
- Deploys to GitHub Pages with weekly archive
|
|
62
|
+
- Internationalization (10 languages)
|
|
90
63
|
|
|
91
|
-
|
|
64
|
+
## LLM Providers
|
|
92
65
|
|
|
93
|
-
|
|
66
|
+
AI narratives are required for report generation. Six providers are supported, including free tiers:
|
|
94
67
|
|
|
95
|
-
|
|
|
68
|
+
| Provider | Free Tier | Env Variable |
|
|
96
69
|
|---|---|---|
|
|
97
|
-
|
|
|
98
|
-
|
|
|
99
|
-
|
|
|
100
|
-
|
|
|
101
|
-
|
|
|
102
|
-
|
|
|
103
|
-
|
|
104
|
-
## Themes
|
|
70
|
+
| **OpenRouter** | Yes (25+ free models) | `OPENROUTER_API_KEY` |
|
|
71
|
+
| **Groq** | Yes (generous) | `GROQ_API_KEY` |
|
|
72
|
+
| **Google Gemini** | Yes | `GEMINI_API_KEY` |
|
|
73
|
+
| OpenAI | No | `OPENAI_API_KEY` |
|
|
74
|
+
| Anthropic | No | `ANTHROPIC_API_KEY` |
|
|
75
|
+
| Grok (xAI) | No | `GROK_API_KEY` |
|
|
105
76
|
|
|
106
|
-
|
|
77
|
+
OpenRouter and Groq are recommended for free usage. Both offer high-quality models at no cost. For model selection, see each provider's documentation:
|
|
107
78
|
|
|
108
|
-
-
|
|
109
|
-
-
|
|
79
|
+
- OpenRouter: https://openrouter.ai/models
|
|
80
|
+
- Groq: https://console.groq.com/docs/models
|
|
81
|
+
- Gemini: https://ai.google.dev/gemini-api/docs/models
|
|
82
|
+
- OpenAI: https://platform.openai.com/docs/models
|
|
83
|
+
- Anthropic: https://docs.anthropic.com/en/docs/about-claude/models
|
|
84
|
+
- Grok: https://docs.x.ai/docs/models
|
|
110
85
|
|
|
111
|
-
|
|
86
|
+
If the LLM call fails, the workflow will stop with an error. Make sure your API key and model name are correct.
|
|
112
87
|
|
|
113
|
-
|
|
88
|
+
## Generating a Report Manually
|
|
114
89
|
|
|
115
|
-
|
|
116
|
-
|---|---|---|
|
|
117
|
-
| OpenAI | `OPENAI_API_KEY` | `gpt-4o-mini` |
|
|
118
|
-
| Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514` |
|
|
119
|
-
| Google Gemini | `GEMINI_API_KEY` | `gemini-2.0-flash` |
|
|
90
|
+
After setup, daily fetches run automatically. To generate a weekly report anytime:
|
|
120
91
|
|
|
121
|
-
|
|
92
|
+
1. Go to your repository's **Actions** tab
|
|
93
|
+
2. Click **Weekly Report**
|
|
94
|
+
3. Click **Run workflow**
|
|
122
95
|
|
|
123
|
-
|
|
96
|
+
The report will be built and deployed to GitHub Pages within a few minutes.
|
|
124
97
|
|
|
125
|
-
|
|
126
|
-
name: Weekly Report
|
|
127
|
-
on:
|
|
128
|
-
schedule:
|
|
129
|
-
- cron: '0 9 * * 1' # Every Monday 9:00 UTC
|
|
130
|
-
workflow_dispatch:
|
|
98
|
+
## Supported Languages
|
|
131
99
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
100
|
+
| Code | Language |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `en` | English |
|
|
103
|
+
| `ja` | Japanese |
|
|
104
|
+
| `zh-CN` | Chinese (Simplified) |
|
|
105
|
+
| `zh-TW` | Chinese (Traditional) |
|
|
106
|
+
| `ko` | Korean |
|
|
107
|
+
| `es` | Spanish |
|
|
108
|
+
| `fr` | French |
|
|
109
|
+
| `de` | German |
|
|
110
|
+
| `pt` | Portuguese |
|
|
111
|
+
| `ru` | Russian |
|
|
112
|
+
|
|
113
|
+
## Documentation
|
|
114
|
+
|
|
115
|
+
- [Manual Setup](docs/manual-setup.md) - step-by-step guide without the setup command
|
|
116
|
+
- [CLI Reference](docs/cli-reference.md) - all commands and environment variables
|
|
146
117
|
|
|
147
118
|
## URL Structure
|
|
148
119
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/deploy.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/deploy.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiDpC,eAAO,MAAM,cAAc,GAAI,SAAS,OAAO,KAAG,IAuBjD,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// deploy command: push generated report directory to gh-pages branch
|
|
2
2
|
import { deploy } from "../../deployer/index.js";
|
|
3
3
|
import { getWeekId } from "../../deployer/week.js";
|
|
4
|
+
import { parseLocalDate } from "../../collector/date-range.js";
|
|
4
5
|
const env = (key) => process.env[key];
|
|
5
6
|
const buildRepoUrl = (repo) => {
|
|
6
7
|
const repoSlug = repo ?? env("GITHUB_REPOSITORY");
|
|
@@ -23,7 +24,7 @@ const buildRepoUrl = (repo) => {
|
|
|
23
24
|
return `https://github.com/${repoSlug}.git`;
|
|
24
25
|
};
|
|
25
26
|
const run = async (options) => {
|
|
26
|
-
const weekId = getWeekId();
|
|
27
|
+
const weekId = getWeekId(options.date, options.timezone);
|
|
27
28
|
console.log(`Deploying ${options.directory} to gh-pages...`);
|
|
28
29
|
await deploy({
|
|
29
30
|
repoUrl: options.repoUrl,
|
|
@@ -36,14 +37,19 @@ export const registerDeploy = (program) => {
|
|
|
36
37
|
program
|
|
37
38
|
.command("deploy")
|
|
38
39
|
.description("Deploy generated report to GitHub Pages (gh-pages branch)")
|
|
39
|
-
.option("-d, --directory <dir>", "Directory containing generated
|
|
40
|
+
.option("-d, --directory <dir>", "Directory containing generated HTML files (env: OUTPUT_DIR, default: ./output)")
|
|
40
41
|
.option("-r, --repo <slug>", "Repository (owner/repo or full URL, env: GITHUB_REPOSITORY)")
|
|
42
|
+
.option("--timezone <tz>", "IANA timezone (env: TIMEZONE, default: UTC)")
|
|
43
|
+
.option("--date <date>", "Date within the target week (YYYY-MM-DD, default: today)")
|
|
41
44
|
.action(async (opts) => {
|
|
42
45
|
try {
|
|
46
|
+
const timezone = opts.timezone ?? env("TIMEZONE") ?? "UTC";
|
|
43
47
|
const repoUrl = buildRepoUrl(opts.repo);
|
|
44
48
|
await run({
|
|
45
|
-
directory: opts.directory,
|
|
49
|
+
directory: opts.directory ?? env("OUTPUT_DIR") ?? "./output",
|
|
46
50
|
repoUrl,
|
|
51
|
+
timezone,
|
|
52
|
+
date: opts.date ? parseLocalDate(opts.date, timezone) : undefined,
|
|
47
53
|
});
|
|
48
54
|
}
|
|
49
55
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../../src/cli/commands/deploy.ts"],"names":[],"mappings":"AAAA,qEAAqE;AAGrE,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../../src/cli/commands/deploy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/fetch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmJpC,eAAO,MAAM,aAAa,GAAI,SAAS,OAAO,KAAG,IA4BhD,CAAC"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// fetch commands: daily-fetch (events) and weekly-fetch (full data)
|
|
2
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { parse as parseYaml, stringify as toYaml } from "yaml";
|
|
5
|
+
import { graphql } from "@octokit/graphql";
|
|
6
|
+
import { buildWeeklyRange, toISODate, parseLocalDate } from "../../collector/date-range.js";
|
|
7
|
+
import { fetchEvents, dedupeEvents } from "../../collector/fetch-events.js";
|
|
8
|
+
import { fetchContributions } from "../../collector/fetch-contributions.js";
|
|
9
|
+
import { fetchPRsByRefs } from "../../collector/fetch-repo-prs.js";
|
|
10
|
+
import { aggregateRepositories } from "../../collector/aggregate.js";
|
|
11
|
+
import { getWeekId } from "../../deployer/week.js";
|
|
12
|
+
const env = (key) => process.env[key];
|
|
13
|
+
const resolveBaseOptions = (cli) => {
|
|
14
|
+
const token = cli.token ?? env("GITHUB_TOKEN");
|
|
15
|
+
if (!token)
|
|
16
|
+
throw new Error("GitHub token required. Pass --token or set GITHUB_TOKEN.");
|
|
17
|
+
const username = cli.username ?? env("GITHUB_USERNAME");
|
|
18
|
+
if (!username)
|
|
19
|
+
throw new Error("GitHub username required. Pass --username or set GITHUB_USERNAME.");
|
|
20
|
+
const timezone = cli.timezone ?? env("TIMEZONE") ?? "UTC";
|
|
21
|
+
const date = cli.date ? parseLocalDate(cli.date, timezone) : undefined;
|
|
22
|
+
return { token, username, dataDir: cli.dataDir ?? env("DATA_DIR") ?? "./data", date, timezone };
|
|
23
|
+
};
|
|
24
|
+
const tryReadYaml = async (path) => {
|
|
25
|
+
try {
|
|
26
|
+
const raw = await readFile(path, "utf-8");
|
|
27
|
+
return parseYaml(raw);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
// Extract PR references from events
|
|
34
|
+
const extractPRRefs = (events) => {
|
|
35
|
+
const refs = [];
|
|
36
|
+
events.forEach((e) => {
|
|
37
|
+
const p = e.payload;
|
|
38
|
+
if (p.kind === "pull_request" && p.number > 0) {
|
|
39
|
+
refs.push({ repo: e.repo, number: p.number });
|
|
40
|
+
}
|
|
41
|
+
if (p.kind === "review" && p.prNumber > 0) {
|
|
42
|
+
refs.push({ repo: e.repo, number: p.prNumber });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return refs;
|
|
46
|
+
};
|
|
47
|
+
// daily-fetch: accumulate events
|
|
48
|
+
const runDailyFetch = async (options) => {
|
|
49
|
+
const weekId = getWeekId(options.date, options.timezone);
|
|
50
|
+
const range = buildWeeklyRange(options.date, options.timezone);
|
|
51
|
+
const reportDir = join(options.dataDir, weekId.path);
|
|
52
|
+
await mkdir(reportDir, { recursive: true });
|
|
53
|
+
console.log(`Fetching events for ${options.username} (${weekId.path})...`);
|
|
54
|
+
const newEvents = await fetchEvents(options.token, options.username, range);
|
|
55
|
+
console.log(`Fetched ${newEvents.length} events.`);
|
|
56
|
+
const eventsPath = join(reportDir, "events.yaml");
|
|
57
|
+
const existing = await tryReadYaml(eventsPath) ?? [];
|
|
58
|
+
const merged = dedupeEvents([...existing, ...newEvents]);
|
|
59
|
+
await writeFile(eventsPath, toYaml(merged, { lineWidth: 120 }), "utf-8");
|
|
60
|
+
console.log(`Events accumulated: ${merged.length} total (${eventsPath})`);
|
|
61
|
+
};
|
|
62
|
+
// weekly-fetch: use accumulated events to find PRs, fetch each individually
|
|
63
|
+
const runWeeklyFetch = async (options) => {
|
|
64
|
+
const weekId = getWeekId(options.date, options.timezone);
|
|
65
|
+
const range = buildWeeklyRange(options.date, options.timezone);
|
|
66
|
+
const reportDir = join(options.dataDir, weekId.path);
|
|
67
|
+
await mkdir(reportDir, { recursive: true });
|
|
68
|
+
// Load accumulated events
|
|
69
|
+
const eventsPath = join(reportDir, "events.yaml");
|
|
70
|
+
const events = await tryReadYaml(eventsPath) ?? [];
|
|
71
|
+
console.log(`Loaded ${events.length} accumulated events.`);
|
|
72
|
+
// Extract PR refs from events
|
|
73
|
+
const prRefs = extractPRRefs(events);
|
|
74
|
+
console.log(`Found ${prRefs.length} PR references (${new Set(prRefs.map((r) => `${r.repo}#${r.number}`)).size} unique).`);
|
|
75
|
+
// Fetch individual PRs
|
|
76
|
+
console.log("Fetching PRs...");
|
|
77
|
+
const pullRequests = await fetchPRsByRefs(options.token, prRefs);
|
|
78
|
+
console.log(`Fetched ${pullRequests.length} PRs.`);
|
|
79
|
+
// Fetch contributions (GraphQL)
|
|
80
|
+
console.log("Fetching contribution stats...");
|
|
81
|
+
const gql = graphql.defaults({ headers: { authorization: `token ${options.token}` } });
|
|
82
|
+
const contributions = await fetchContributions(gql, options.username, range, options.timezone);
|
|
83
|
+
const repositories = aggregateRepositories(pullRequests, []);
|
|
84
|
+
const totalAdditions = pullRequests.reduce((sum, pr) => sum + pr.additions, 0);
|
|
85
|
+
const totalDeletions = pullRequests.reduce((sum, pr) => sum + pr.deletions, 0);
|
|
86
|
+
const githubData = {
|
|
87
|
+
username: contributions.username,
|
|
88
|
+
avatarUrl: contributions.avatarUrl,
|
|
89
|
+
profile: contributions.profile,
|
|
90
|
+
dateRange: {
|
|
91
|
+
from: toISODate(range.from, options.timezone),
|
|
92
|
+
to: toISODate(range.to, options.timezone),
|
|
93
|
+
},
|
|
94
|
+
stats: {
|
|
95
|
+
totalCommits: contributions.totalCommits,
|
|
96
|
+
totalAdditions,
|
|
97
|
+
totalDeletions,
|
|
98
|
+
prsOpened: pullRequests.filter((pr) => pr.author.toLowerCase() === options.username.toLowerCase()).length,
|
|
99
|
+
prsMerged: pullRequests.filter((pr) => pr.state === "merged" && pr.author.toLowerCase() === options.username.toLowerCase()).length,
|
|
100
|
+
prsReviewed: contributions.prsReviewed,
|
|
101
|
+
issuesOpened: 0,
|
|
102
|
+
issuesClosed: 0,
|
|
103
|
+
},
|
|
104
|
+
dailyCommits: contributions.dailyCommits,
|
|
105
|
+
repositories,
|
|
106
|
+
pullRequests,
|
|
107
|
+
issues: [],
|
|
108
|
+
events,
|
|
109
|
+
externalContributions: [],
|
|
110
|
+
};
|
|
111
|
+
const dataPath = join(reportDir, "github-data.yaml");
|
|
112
|
+
await writeFile(dataPath, toYaml(githubData, { lineWidth: 120 }), "utf-8");
|
|
113
|
+
console.log(`GitHub data written to ${dataPath}`);
|
|
114
|
+
console.log(`Total: ${pullRequests.length} PRs`);
|
|
115
|
+
};
|
|
116
|
+
const baseOptions = (cmd) => cmd
|
|
117
|
+
.option("-t, --token <token>", "GitHub token (env: GITHUB_TOKEN)")
|
|
118
|
+
.option("-u, --username <username>", "GitHub username (env: GITHUB_USERNAME)")
|
|
119
|
+
.option("--data-dir <dir>", "Data directory (env: DATA_DIR, default: ./data)")
|
|
120
|
+
.option("--timezone <tz>", "IANA timezone (env: TIMEZONE, default: UTC)")
|
|
121
|
+
.option("--date <date>", "Date within the target week (YYYY-MM-DD, default: today)");
|
|
122
|
+
export const registerFetch = (program) => {
|
|
123
|
+
baseOptions(program
|
|
124
|
+
.command("daily-fetch")
|
|
125
|
+
.description("Fetch today's GitHub events and accumulate (run daily via cron)")).action(async (opts) => {
|
|
126
|
+
try {
|
|
127
|
+
const options = resolveBaseOptions(opts);
|
|
128
|
+
await runDailyFetch(options);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
baseOptions(program
|
|
136
|
+
.command("weekly-fetch")
|
|
137
|
+
.description("Build full weekly data from accumulated events + individual PR fetches")).action(async (opts) => {
|
|
138
|
+
try {
|
|
139
|
+
const options = resolveBaseOptions(opts);
|
|
140
|
+
await runWeeklyFetch(options);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
//# sourceMappingURL=fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../../src/cli/commands/fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuFpC,eAAO,MAAM,gBAAgB,GAAI,SAAS,OAAO,KAAG,IAoBnD,CAAC"}
|
|
@@ -1,100 +1,79 @@
|
|
|
1
|
-
// generate command:
|
|
2
|
-
import {
|
|
1
|
+
// generate command: read github-data.yaml and generate LLM content
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { generateNarrative } from "../../llm/index.js";
|
|
7
|
-
import { renderIndexPage } from "../../deployer/index-page.js";
|
|
4
|
+
import { parse as parseYaml, stringify as toYaml } from "yaml";
|
|
5
|
+
import { generateContent } from "../../llm/index.js";
|
|
8
6
|
import { getWeekId } from "../../deployer/week.js";
|
|
9
|
-
import {
|
|
7
|
+
import { parseLocalDate } from "../../collector/date-range.js";
|
|
10
8
|
const env = (key) => process.env[key];
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
?? env
|
|
27
|
-
const llmModel = cli.llmModel ?? env("LLM_MODEL")
|
|
9
|
+
const resolveOptions = (cli) => {
|
|
10
|
+
const llmProvider = (cli.llmProvider ?? env("LLM_PROVIDER"));
|
|
11
|
+
if (!llmProvider)
|
|
12
|
+
throw new Error("LLM provider required. Pass --llm-provider or set LLM_PROVIDER.");
|
|
13
|
+
const providerKeyMap = {
|
|
14
|
+
openai: "OPENAI_API_KEY",
|
|
15
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
16
|
+
gemini: "GEMINI_API_KEY",
|
|
17
|
+
openrouter: "OPENROUTER_API_KEY",
|
|
18
|
+
groq: "GROQ_API_KEY",
|
|
19
|
+
grok: "GROK_API_KEY",
|
|
20
|
+
};
|
|
21
|
+
const envVarName = providerKeyMap[llmProvider];
|
|
22
|
+
const llmApiKey = cli.llmApiKey ?? (envVarName ? env(envVarName) : undefined);
|
|
23
|
+
if (!llmApiKey)
|
|
24
|
+
throw new Error(`LLM API key required. Pass --llm-api-key or set ${envVarName ?? "the provider's API key env var"}.`);
|
|
25
|
+
const llmModel = cli.llmModel ?? env("LLM_MODEL");
|
|
26
|
+
if (!llmModel)
|
|
27
|
+
throw new Error("LLM model required. Pass --llm-model or set LLM_MODEL.");
|
|
28
|
+
const language = (cli.language ?? env("LANGUAGE") ?? "en");
|
|
29
|
+
const timezone = cli.timezone ?? env("TIMEZONE") ?? "UTC";
|
|
30
|
+
const date = cli.date ? parseLocalDate(cli.date, timezone) : undefined;
|
|
28
31
|
return {
|
|
29
|
-
|
|
30
|
-
username,
|
|
31
|
-
output: cli.output ?? config.output ?? "./report",
|
|
32
|
-
theme: (cli.theme ?? config.theme ?? "default"),
|
|
32
|
+
dataDir: cli.dataDir ?? env("DATA_DIR") ?? "./data",
|
|
33
33
|
llmProvider,
|
|
34
34
|
llmApiKey,
|
|
35
35
|
llmModel,
|
|
36
|
+
language,
|
|
37
|
+
timezone,
|
|
38
|
+
date,
|
|
36
39
|
};
|
|
37
40
|
};
|
|
38
|
-
const listReportDirs = async (dir) => {
|
|
39
|
-
const paths = [];
|
|
40
|
-
let entries = [];
|
|
41
|
-
try {
|
|
42
|
-
entries = await readdir(dir);
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return paths;
|
|
46
|
-
}
|
|
47
|
-
for (const year of entries.filter((n) => /^\d{4}$/.test(n))) {
|
|
48
|
-
const weeks = await readdir(join(dir, year));
|
|
49
|
-
weeks
|
|
50
|
-
.filter((n) => /^W\d{2}$/.test(n))
|
|
51
|
-
.forEach((w) => paths.push(`${year}/${w}`));
|
|
52
|
-
}
|
|
53
|
-
return paths;
|
|
54
|
-
};
|
|
55
41
|
const run = async (options) => {
|
|
56
|
-
const weekId = getWeekId();
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
42
|
+
const weekId = getWeekId(options.date, options.timezone);
|
|
43
|
+
const dataDir = join(options.dataDir, weekId.path);
|
|
44
|
+
const dataPath = join(dataDir, "github-data.yaml");
|
|
45
|
+
console.log(`Reading ${dataPath}...`);
|
|
46
|
+
const raw = await readFile(dataPath, "utf-8");
|
|
47
|
+
const data = parseYaml(raw);
|
|
48
|
+
console.log(`Generating AI content (${options.llmProvider}/${options.llmModel}, lang: ${options.language})...`);
|
|
49
|
+
const aiContent = await generateContent({ ...data, language: options.language }, {
|
|
50
|
+
provider: options.llmProvider,
|
|
51
|
+
apiKey: options.llmApiKey,
|
|
52
|
+
model: options.llmModel,
|
|
53
|
+
language: options.language,
|
|
54
|
+
});
|
|
55
|
+
if (!aiContent) {
|
|
56
|
+
console.error("LLM returned no content.");
|
|
57
|
+
process.exit(1);
|
|
66
58
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const reportDir = join(options.output, weekId.path);
|
|
71
|
-
await mkdir(reportDir, { recursive: true });
|
|
72
|
-
const reportPath = join(reportDir, "index.html");
|
|
73
|
-
await writeFile(reportPath, html, "utf-8");
|
|
74
|
-
console.log(`Report written to ${reportPath}`);
|
|
75
|
-
// Write index page
|
|
76
|
-
const allReports = await listReportDirs(options.output);
|
|
77
|
-
if (!allReports.includes(weekId.path))
|
|
78
|
-
allReports.push(weekId.path);
|
|
79
|
-
const indexHtml = renderIndexPage(allReports, options.theme);
|
|
80
|
-
const indexPath = join(options.output, "index.html");
|
|
81
|
-
await writeFile(indexPath, indexHtml, "utf-8");
|
|
82
|
-
console.log(`Index written to ${indexPath}`);
|
|
59
|
+
const llmDataPath = join(dataDir, "llm-data.yaml");
|
|
60
|
+
await writeFile(llmDataPath, toYaml(aiContent, { lineWidth: 120 }), "utf-8");
|
|
61
|
+
console.log(`LLM data written to ${llmDataPath}`);
|
|
83
62
|
};
|
|
84
63
|
export const registerGenerate = (program) => {
|
|
85
64
|
program
|
|
86
65
|
.command("generate")
|
|
87
|
-
.description("
|
|
88
|
-
.option("-
|
|
89
|
-
.option("-
|
|
90
|
-
.option("-
|
|
91
|
-
.option("--
|
|
92
|
-
.option("--
|
|
93
|
-
.option("--
|
|
94
|
-
.option("--
|
|
66
|
+
.description("Generate AI content from fetched GitHub data")
|
|
67
|
+
.option("--data-dir <dir>", "Data directory (env: DATA_DIR, default: ./data)")
|
|
68
|
+
.option("--llm-provider <provider>", "LLM provider (env: LLM_PROVIDER)")
|
|
69
|
+
.option("--llm-api-key <key>", "LLM API key (env: OPENROUTER_API_KEY / GROQ_API_KEY / GEMINI_API_KEY / OPENAI_API_KEY / ANTHROPIC_API_KEY / GROK_API_KEY)")
|
|
70
|
+
.option("--llm-model <model>", "LLM model name (env: LLM_MODEL)")
|
|
71
|
+
.option("--language <lang>", "Report language: en, ja, zh-CN, zh-TW, ko, es, fr, de, pt, ru (env: LANGUAGE, default: en)")
|
|
72
|
+
.option("--timezone <tz>", "IANA timezone (env: TIMEZONE, default: UTC)")
|
|
73
|
+
.option("--date <date>", "Date within the target week (YYYY-MM-DD, default: today)")
|
|
95
74
|
.action(async (opts) => {
|
|
96
75
|
try {
|
|
97
|
-
const options =
|
|
76
|
+
const options = resolveOptions(opts);
|
|
98
77
|
await run(options);
|
|
99
78
|
}
|
|
100
79
|
catch (error) {
|