gh-tldr 1.0.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 +126 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +561 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# gh-tldr
|
|
2
|
+
|
|
3
|
+
Generate a TL;DR summary of your GitHub activity using Claude.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- [Node.js](https://nodejs.org/) >= 18
|
|
8
|
+
- [GitHub CLI](https://cli.github.com/) (`gh`) - authenticated
|
|
9
|
+
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (`claude`)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# macOS
|
|
13
|
+
brew install gh node
|
|
14
|
+
|
|
15
|
+
# Authenticate gh
|
|
16
|
+
gh auth login
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Clone and install
|
|
23
|
+
git clone https://github.com/moto-nrw/gh-tldr.git
|
|
24
|
+
cd gh-tldr
|
|
25
|
+
pnpm install
|
|
26
|
+
pnpm build
|
|
27
|
+
|
|
28
|
+
# Link globally
|
|
29
|
+
pnpm link --global
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or via npx (once published):
|
|
33
|
+
```bash
|
|
34
|
+
npx gh-tldr
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Interactive Mode
|
|
40
|
+
|
|
41
|
+
Run without arguments for guided prompts:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
gh-tldr
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
? GitHub username (leave empty for authenticated user)
|
|
49
|
+
? Time period › Last 24 hours / Last 7 days / Last 30 days
|
|
50
|
+
? Language › English / German
|
|
51
|
+
? Output format › Plain text / Markdown / Slack
|
|
52
|
+
? Include private repos? (y/N)
|
|
53
|
+
? Claude model (leave empty for default)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Direct Mode
|
|
57
|
+
|
|
58
|
+
Use flags for scripting:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Basic usage
|
|
62
|
+
gh-tldr --days 7 --english
|
|
63
|
+
|
|
64
|
+
# All options
|
|
65
|
+
gh-tldr [username] [options]
|
|
66
|
+
|
|
67
|
+
Options:
|
|
68
|
+
-d, --days <n> Time period in days (default: 1)
|
|
69
|
+
-e, --english Output in English (default: German)
|
|
70
|
+
-f, --format <type> Output format: plain|markdown|slack (default: slack)
|
|
71
|
+
-p, --public-only Exclude private repositories
|
|
72
|
+
-m, --model <model> Claude model (e.g., haiku, sonnet, opus)
|
|
73
|
+
-i, --interactive Force interactive mode
|
|
74
|
+
-h, --help Show help
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Examples
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Last 7 days in English
|
|
81
|
+
gh-tldr --days 7 --english
|
|
82
|
+
|
|
83
|
+
# Specific user, public repos only
|
|
84
|
+
gh-tldr yungweng --public-only
|
|
85
|
+
|
|
86
|
+
# Use Haiku model for faster results
|
|
87
|
+
gh-tldr --model haiku
|
|
88
|
+
|
|
89
|
+
# Markdown output for documentation
|
|
90
|
+
gh-tldr --days 30 --format markdown
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Output
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
tl;dr 28.12.2025
|
|
97
|
+
|
|
98
|
+
• 3 PRs created (repo-a, repo-b)
|
|
99
|
+
• 5 PRs reviewed (repo-c)
|
|
100
|
+
• 2 PRs merged (repo-a)
|
|
101
|
+
• 1 issue closed (repo-d)
|
|
102
|
+
• 12 commits (repo-a, repo-b)
|
|
103
|
+
|
|
104
|
+
Repos: org/repo-a, org/repo-b, org/repo-c, org/repo-d
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
Mainly worked on Feature X. Completed and merged PR "Add user authentication".
|
|
108
|
+
Did several code reviews for the team, including the new API endpoint.
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Run in dev mode
|
|
115
|
+
pnpm dev
|
|
116
|
+
|
|
117
|
+
# Build
|
|
118
|
+
pnpm build
|
|
119
|
+
|
|
120
|
+
# Type check
|
|
121
|
+
pnpm typecheck
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
|
|
7
|
+
// src/claude.ts
|
|
8
|
+
import { execa } from "execa";
|
|
9
|
+
var wordLimits = {
|
|
10
|
+
brief: { en: "20-40 word", de: "20-40 W\xF6rter" },
|
|
11
|
+
normal: { en: "50-80 word", de: "50-80 W\xF6rter" },
|
|
12
|
+
detailed: { en: "150-200 word", de: "150-200 W\xF6rter" }
|
|
13
|
+
};
|
|
14
|
+
function buildPrompt(lang, verbosity) {
|
|
15
|
+
const limit = wordLimits[verbosity][lang];
|
|
16
|
+
if (lang === "en") {
|
|
17
|
+
return `Based on this GitHub activity data, write a summary of what was accomplished. STRICT LIMIT: ${limit}. Mention specific PR/issue titles. Casual tone, no bullet points, no emojis.
|
|
18
|
+
|
|
19
|
+
GitHub Activity Data:`;
|
|
20
|
+
}
|
|
21
|
+
return `Basierend auf diesen GitHub-Aktivit\xE4tsdaten, schreibe eine Zusammenfassung was gemacht wurde. STRIKTES LIMIT: ${limit}. Erw\xE4hne konkret die PR/Issue-Titel. Lockerer Ton, keine Aufz\xE4hlungen, keine Emojis.
|
|
22
|
+
|
|
23
|
+
GitHub-Aktivit\xE4tsdaten:`;
|
|
24
|
+
}
|
|
25
|
+
async function generateSummaryText(activity, lang, verbosity = "normal", model) {
|
|
26
|
+
const prompt = buildPrompt(lang, verbosity);
|
|
27
|
+
const fullPrompt = `${prompt}
|
|
28
|
+
${JSON.stringify(activity, null, 2)}`;
|
|
29
|
+
const args = ["-p", "-", "--output-format", "text"];
|
|
30
|
+
if (model) {
|
|
31
|
+
args.push("--model", model);
|
|
32
|
+
}
|
|
33
|
+
const { stdout } = await execa("claude", args, {
|
|
34
|
+
input: fullPrompt
|
|
35
|
+
});
|
|
36
|
+
return stdout.trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/github.ts
|
|
40
|
+
import { execa as execa2 } from "execa";
|
|
41
|
+
function getSinceDate(days) {
|
|
42
|
+
const date = /* @__PURE__ */ new Date();
|
|
43
|
+
date.setDate(date.getDate() - days);
|
|
44
|
+
return date.toISOString();
|
|
45
|
+
}
|
|
46
|
+
function formatDate(date) {
|
|
47
|
+
const day = date.getDate().toString().padStart(2, "0");
|
|
48
|
+
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
49
|
+
const year = date.getFullYear();
|
|
50
|
+
return `${day}.${month}.${year}`;
|
|
51
|
+
}
|
|
52
|
+
async function ghApi(endpoint, params) {
|
|
53
|
+
const args = [
|
|
54
|
+
"api",
|
|
55
|
+
"-X",
|
|
56
|
+
"GET",
|
|
57
|
+
endpoint,
|
|
58
|
+
...Object.entries(params).flatMap(([key, value]) => [
|
|
59
|
+
"-f",
|
|
60
|
+
`${key}=${value}`
|
|
61
|
+
])
|
|
62
|
+
];
|
|
63
|
+
const { stdout } = await execa2("gh", args);
|
|
64
|
+
return JSON.parse(stdout);
|
|
65
|
+
}
|
|
66
|
+
async function ghApiPaginated(endpoint, params, maxPages = 10) {
|
|
67
|
+
const allItems = [];
|
|
68
|
+
let page = 1;
|
|
69
|
+
while (page <= maxPages) {
|
|
70
|
+
const result = await ghApi(endpoint, {
|
|
71
|
+
...params,
|
|
72
|
+
page: String(page),
|
|
73
|
+
per_page: "100"
|
|
74
|
+
});
|
|
75
|
+
allItems.push(...result.items);
|
|
76
|
+
if (result.items.length < 100) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
page++;
|
|
80
|
+
}
|
|
81
|
+
return allItems;
|
|
82
|
+
}
|
|
83
|
+
function parsePR(item) {
|
|
84
|
+
const urlParts = item.repository_url.split("/");
|
|
85
|
+
return {
|
|
86
|
+
repo: urlParts[urlParts.length - 1],
|
|
87
|
+
org: urlParts[urlParts.length - 2],
|
|
88
|
+
title: item.title,
|
|
89
|
+
number: item.number,
|
|
90
|
+
state: item.state,
|
|
91
|
+
url: item.html_url
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function parseIssue(item) {
|
|
95
|
+
const urlParts = item.repository_url.split("/");
|
|
96
|
+
return {
|
|
97
|
+
repo: urlParts[urlParts.length - 1],
|
|
98
|
+
org: urlParts[urlParts.length - 2],
|
|
99
|
+
title: item.title,
|
|
100
|
+
number: item.number,
|
|
101
|
+
url: item.html_url
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function parseCommit(item) {
|
|
105
|
+
return {
|
|
106
|
+
repo: item.repository.name,
|
|
107
|
+
org: item.repository.owner.login,
|
|
108
|
+
message: item.commit.message.split("\n")[0],
|
|
109
|
+
url: item.html_url,
|
|
110
|
+
date: item.commit.author.date
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async function getAuthenticatedUser() {
|
|
114
|
+
const { stdout } = await execa2("gh", ["api", "user", "--jq", ".login"]);
|
|
115
|
+
return stdout.trim();
|
|
116
|
+
}
|
|
117
|
+
async function fetchUserOrgs(username) {
|
|
118
|
+
try {
|
|
119
|
+
const authUser = await getAuthenticatedUser();
|
|
120
|
+
if (authUser === username) {
|
|
121
|
+
const { stdout } = await execa2("gh", [
|
|
122
|
+
"api",
|
|
123
|
+
"user/orgs",
|
|
124
|
+
"--jq",
|
|
125
|
+
".[].login"
|
|
126
|
+
]);
|
|
127
|
+
return stdout.trim().split("\n").filter(Boolean);
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const { stdout } = await execa2("gh", [
|
|
133
|
+
"api",
|
|
134
|
+
`users/${username}/orgs`,
|
|
135
|
+
"--jq",
|
|
136
|
+
".[].login"
|
|
137
|
+
]);
|
|
138
|
+
return stdout.trim().split("\n").filter(Boolean);
|
|
139
|
+
} catch {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function fetchReposCreatedSince(username, since, publicOnly) {
|
|
144
|
+
const sinceDate = new Date(since);
|
|
145
|
+
const repos = [];
|
|
146
|
+
try {
|
|
147
|
+
const visibility = publicOnly ? "public" : "all";
|
|
148
|
+
const { stdout } = await execa2("gh", [
|
|
149
|
+
"api",
|
|
150
|
+
`users/${username}/repos?type=${visibility}&sort=created&direction=desc&per_page=100`
|
|
151
|
+
]);
|
|
152
|
+
const userRepos = JSON.parse(stdout);
|
|
153
|
+
for (const repo of userRepos) {
|
|
154
|
+
if (new Date(repo.created_at) >= sinceDate) {
|
|
155
|
+
repos.push({ name: repo.name, org: repo.owner.login });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
}
|
|
160
|
+
const orgs = await fetchUserOrgs(username);
|
|
161
|
+
for (const org of orgs) {
|
|
162
|
+
try {
|
|
163
|
+
const { stdout } = await execa2("gh", [
|
|
164
|
+
"api",
|
|
165
|
+
`orgs/${org}/repos?sort=created&direction=desc&per_page=100`
|
|
166
|
+
]);
|
|
167
|
+
const orgRepos = JSON.parse(stdout);
|
|
168
|
+
for (const repo of orgRepos) {
|
|
169
|
+
if (new Date(repo.created_at) >= sinceDate) {
|
|
170
|
+
repos.push({ name: repo.name, org: repo.owner.login });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return repos.filter(
|
|
177
|
+
(repo, index, self) => self.findIndex((r) => r.name === repo.name && r.org === repo.org) === index
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
async function fetchGitHubActivity(username, days, publicOnly) {
|
|
181
|
+
const since = getSinceDate(days);
|
|
182
|
+
const today = formatDate(/* @__PURE__ */ new Date());
|
|
183
|
+
const visibilityFilter = publicOnly ? " is:public" : "";
|
|
184
|
+
const [
|
|
185
|
+
prsCreatedItems,
|
|
186
|
+
prsMergedItems,
|
|
187
|
+
prsReviewedItems,
|
|
188
|
+
issuesCreatedItems,
|
|
189
|
+
issuesClosedItems,
|
|
190
|
+
commitsItems,
|
|
191
|
+
repos_created
|
|
192
|
+
] = await Promise.all([
|
|
193
|
+
ghApiPaginated("search/issues", {
|
|
194
|
+
q: `author:${username} type:pr created:>=${since}${visibilityFilter}`
|
|
195
|
+
}),
|
|
196
|
+
ghApiPaginated("search/issues", {
|
|
197
|
+
q: `author:${username} type:pr merged:>=${since}${visibilityFilter}`
|
|
198
|
+
}),
|
|
199
|
+
ghApiPaginated("search/issues", {
|
|
200
|
+
q: `reviewed-by:${username} type:pr created:>=${since} -author:${username}${visibilityFilter}`
|
|
201
|
+
}),
|
|
202
|
+
ghApiPaginated("search/issues", {
|
|
203
|
+
q: `author:${username} type:issue created:>=${since}${visibilityFilter}`
|
|
204
|
+
}),
|
|
205
|
+
ghApiPaginated("search/issues", {
|
|
206
|
+
q: `author:${username} type:issue closed:>=${since}${visibilityFilter}`
|
|
207
|
+
}),
|
|
208
|
+
ghApiPaginated("search/commits", {
|
|
209
|
+
q: `author:${username} committer-date:>=${since}`
|
|
210
|
+
}),
|
|
211
|
+
fetchReposCreatedSince(username, since, publicOnly)
|
|
212
|
+
]);
|
|
213
|
+
const prs_created = prsCreatedItems.map(parsePR);
|
|
214
|
+
const prs_merged = prsMergedItems.map(parsePR);
|
|
215
|
+
const prs_reviewed = prsReviewedItems.map(parsePR);
|
|
216
|
+
const issues_created = issuesCreatedItems.map(parseIssue);
|
|
217
|
+
const issues_closed = issuesClosedItems.map(parseIssue);
|
|
218
|
+
const commits = commitsItems.map(parseCommit);
|
|
219
|
+
const allRepos = [
|
|
220
|
+
...prs_created,
|
|
221
|
+
...prs_merged,
|
|
222
|
+
...prs_reviewed,
|
|
223
|
+
...issues_created,
|
|
224
|
+
...issues_closed,
|
|
225
|
+
...commits
|
|
226
|
+
].map((item) => `${item.org}/${item.repo}`);
|
|
227
|
+
const repos_touched = [...new Set(allRepos)];
|
|
228
|
+
const periodText = days === 1 ? "last 24 hours" : days === 7 ? "last 7 days" : days === 30 ? "last 30 days" : `last ${days} days`;
|
|
229
|
+
return {
|
|
230
|
+
user: username,
|
|
231
|
+
date: today,
|
|
232
|
+
period: periodText,
|
|
233
|
+
prs_created,
|
|
234
|
+
prs_merged,
|
|
235
|
+
prs_reviewed,
|
|
236
|
+
issues_created,
|
|
237
|
+
issues_closed,
|
|
238
|
+
commits,
|
|
239
|
+
repos_created,
|
|
240
|
+
repos_touched
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/interactive.ts
|
|
245
|
+
import { confirm, input, select } from "@inquirer/prompts";
|
|
246
|
+
async function runInteractive() {
|
|
247
|
+
const username = await input({
|
|
248
|
+
message: "GitHub username (leave empty for authenticated user)",
|
|
249
|
+
default: ""
|
|
250
|
+
});
|
|
251
|
+
const days = await select({
|
|
252
|
+
message: "Time period",
|
|
253
|
+
choices: [
|
|
254
|
+
{ name: "Last 24 hours", value: 1 },
|
|
255
|
+
{ name: "Last 7 days", value: 7 },
|
|
256
|
+
{ name: "Last 30 days", value: 30 }
|
|
257
|
+
],
|
|
258
|
+
default: 1
|
|
259
|
+
});
|
|
260
|
+
const language = await select({
|
|
261
|
+
message: "Language",
|
|
262
|
+
choices: [
|
|
263
|
+
{ name: "English", value: "en" },
|
|
264
|
+
{ name: "German", value: "de" }
|
|
265
|
+
],
|
|
266
|
+
default: "en"
|
|
267
|
+
});
|
|
268
|
+
const verbosity = await select({
|
|
269
|
+
message: "Summary verbosity",
|
|
270
|
+
choices: [
|
|
271
|
+
{ name: "Brief (~30 words)", value: "brief" },
|
|
272
|
+
{ name: "Normal (~60 words)", value: "normal" },
|
|
273
|
+
{ name: "Detailed (~175 words)", value: "detailed" }
|
|
274
|
+
],
|
|
275
|
+
default: "normal"
|
|
276
|
+
});
|
|
277
|
+
const format = await select({
|
|
278
|
+
message: "Output format",
|
|
279
|
+
choices: [
|
|
280
|
+
{ name: "Plain text", value: "plain" },
|
|
281
|
+
{ name: "Markdown", value: "markdown" },
|
|
282
|
+
{ name: "Slack", value: "slack" }
|
|
283
|
+
],
|
|
284
|
+
default: "plain"
|
|
285
|
+
});
|
|
286
|
+
const includePrivate = await confirm({
|
|
287
|
+
message: "Include private repos?",
|
|
288
|
+
default: true
|
|
289
|
+
});
|
|
290
|
+
const model = await input({
|
|
291
|
+
message: "Claude model (leave empty for default)",
|
|
292
|
+
default: ""
|
|
293
|
+
});
|
|
294
|
+
return {
|
|
295
|
+
username,
|
|
296
|
+
days,
|
|
297
|
+
language,
|
|
298
|
+
verbosity,
|
|
299
|
+
format,
|
|
300
|
+
includePrivate,
|
|
301
|
+
model
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/output.ts
|
|
306
|
+
var GAP_THRESHOLD_MS = 3 * 60 * 60 * 1e3;
|
|
307
|
+
var SINGLE_COMMIT_HOURS = 0.25;
|
|
308
|
+
function calculateWorkSession(commits, lang) {
|
|
309
|
+
if (commits.length < 2) return null;
|
|
310
|
+
const timestamps = commits.map((c) => new Date(c.date).getTime()).sort((a, b) => a - b);
|
|
311
|
+
const sessions = [];
|
|
312
|
+
let currentSession = [timestamps[0]];
|
|
313
|
+
for (let i = 1; i < timestamps.length; i++) {
|
|
314
|
+
const gap = timestamps[i] - timestamps[i - 1];
|
|
315
|
+
if (gap > GAP_THRESHOLD_MS) {
|
|
316
|
+
sessions.push(currentSession);
|
|
317
|
+
currentSession = [timestamps[i]];
|
|
318
|
+
} else {
|
|
319
|
+
currentSession.push(timestamps[i]);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
sessions.push(currentSession);
|
|
323
|
+
let totalHours = 0;
|
|
324
|
+
for (const session of sessions) {
|
|
325
|
+
if (session.length === 1) {
|
|
326
|
+
totalHours += SINGLE_COMMIT_HOURS;
|
|
327
|
+
} else {
|
|
328
|
+
const duration = session[session.length - 1] - session[0];
|
|
329
|
+
totalHours += duration / (1e3 * 60 * 60);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const rounded = Math.round(totalHours * 2) / 2;
|
|
333
|
+
if (rounded < 0.5) return null;
|
|
334
|
+
const label = lang === "en" ? "Work session" : "Arbeitszeit";
|
|
335
|
+
const hoursLabel = rounded === 1 ? lang === "en" ? "hour" : "Stunde" : lang === "en" ? "hours" : "Stunden";
|
|
336
|
+
return `${label}: ~${rounded} ${hoursLabel}`;
|
|
337
|
+
}
|
|
338
|
+
function getRepoNames(items) {
|
|
339
|
+
const repos = [...new Set(items.map((i) => i.repo))];
|
|
340
|
+
return repos.join(", ");
|
|
341
|
+
}
|
|
342
|
+
function formatActivityLine(count, labelEn, labelDe, repos, lang, format) {
|
|
343
|
+
if (count === 0) return null;
|
|
344
|
+
const label = lang === "en" ? labelEn : labelDe;
|
|
345
|
+
const repoSuffix = repos ? ` (${repos})` : "";
|
|
346
|
+
if (format === "markdown") {
|
|
347
|
+
return `- **${count}** ${label}${repoSuffix}`;
|
|
348
|
+
}
|
|
349
|
+
return `\u2022 ${count} ${label}${repoSuffix}`;
|
|
350
|
+
}
|
|
351
|
+
function formatActivity(activity, format, lang) {
|
|
352
|
+
const lines = [];
|
|
353
|
+
const headerPrefix = format === "markdown" ? "## " : "";
|
|
354
|
+
lines.push(`${headerPrefix}tl;dr ${activity.date}`);
|
|
355
|
+
lines.push("");
|
|
356
|
+
const activityLines = [
|
|
357
|
+
formatActivityLine(
|
|
358
|
+
activity.prs_created.length,
|
|
359
|
+
"PRs created",
|
|
360
|
+
"PRs erstellt",
|
|
361
|
+
getRepoNames(activity.prs_created),
|
|
362
|
+
lang,
|
|
363
|
+
format
|
|
364
|
+
),
|
|
365
|
+
formatActivityLine(
|
|
366
|
+
activity.prs_reviewed.length,
|
|
367
|
+
"PRs reviewed",
|
|
368
|
+
"PRs reviewed/approved",
|
|
369
|
+
getRepoNames(activity.prs_reviewed),
|
|
370
|
+
lang,
|
|
371
|
+
format
|
|
372
|
+
),
|
|
373
|
+
formatActivityLine(
|
|
374
|
+
activity.prs_merged.length,
|
|
375
|
+
"PRs merged",
|
|
376
|
+
"PRs gemerged",
|
|
377
|
+
getRepoNames(activity.prs_merged),
|
|
378
|
+
lang,
|
|
379
|
+
format
|
|
380
|
+
),
|
|
381
|
+
formatActivityLine(
|
|
382
|
+
activity.issues_created.length,
|
|
383
|
+
"issues created",
|
|
384
|
+
"Issues erstellt",
|
|
385
|
+
getRepoNames(activity.issues_created),
|
|
386
|
+
lang,
|
|
387
|
+
format
|
|
388
|
+
),
|
|
389
|
+
formatActivityLine(
|
|
390
|
+
activity.issues_closed.length,
|
|
391
|
+
"issues closed",
|
|
392
|
+
"Issues geschlossen",
|
|
393
|
+
getRepoNames(activity.issues_closed),
|
|
394
|
+
lang,
|
|
395
|
+
format
|
|
396
|
+
),
|
|
397
|
+
formatActivityLine(
|
|
398
|
+
activity.commits.length,
|
|
399
|
+
"commits",
|
|
400
|
+
"Commits",
|
|
401
|
+
getRepoNames(activity.commits),
|
|
402
|
+
lang,
|
|
403
|
+
format
|
|
404
|
+
),
|
|
405
|
+
formatActivityLine(
|
|
406
|
+
activity.repos_created.length,
|
|
407
|
+
"new repos created",
|
|
408
|
+
"neue Repos erstellt",
|
|
409
|
+
activity.repos_created.map((r) => r.name).join(", "),
|
|
410
|
+
lang,
|
|
411
|
+
format
|
|
412
|
+
)
|
|
413
|
+
].filter(Boolean);
|
|
414
|
+
if (activityLines.length === 0) {
|
|
415
|
+
const noActivity = lang === "en" ? `No GitHub activity in the ${activity.period}.` : `Keine GitHub-Aktivit\xE4t in den ${activity.period}.`;
|
|
416
|
+
lines.push(noActivity);
|
|
417
|
+
return lines.join("\n");
|
|
418
|
+
}
|
|
419
|
+
lines.push(...activityLines);
|
|
420
|
+
const workSession = calculateWorkSession(activity.commits, lang);
|
|
421
|
+
if (workSession) {
|
|
422
|
+
lines.push(format === "markdown" ? `- ${workSession}` : `\u2022 ${workSession}`);
|
|
423
|
+
}
|
|
424
|
+
lines.push("");
|
|
425
|
+
if (activity.repos_touched.length > 0) {
|
|
426
|
+
const reposLabel = lang === "en" ? "Repos" : "Repos";
|
|
427
|
+
lines.push(`${reposLabel}: ${activity.repos_touched.join(", ")}`);
|
|
428
|
+
}
|
|
429
|
+
return lines.join("\n");
|
|
430
|
+
}
|
|
431
|
+
function hasActivity(activity) {
|
|
432
|
+
return activity.prs_created.length + activity.prs_merged.length + activity.prs_reviewed.length + activity.issues_created.length + activity.issues_closed.length > 0;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/cli.ts
|
|
436
|
+
async function checkDependencies() {
|
|
437
|
+
const { execa: execa3 } = await import("execa");
|
|
438
|
+
const deps = [
|
|
439
|
+
{ cmd: "gh", name: "GitHub CLI", installHint: "brew install gh" },
|
|
440
|
+
{
|
|
441
|
+
cmd: "claude",
|
|
442
|
+
name: "Claude Code CLI",
|
|
443
|
+
installHint: "npm install -g @anthropic-ai/claude-code"
|
|
444
|
+
}
|
|
445
|
+
];
|
|
446
|
+
const missing = [];
|
|
447
|
+
for (const dep of deps) {
|
|
448
|
+
try {
|
|
449
|
+
await execa3("which", [dep.cmd]);
|
|
450
|
+
} catch {
|
|
451
|
+
missing.push(`${dep.name} (${dep.installHint})`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (missing.length > 0) {
|
|
455
|
+
console.error(chalk.red("Missing dependencies:"));
|
|
456
|
+
for (const m of missing) {
|
|
457
|
+
console.error(` - ${m}`);
|
|
458
|
+
}
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
await execa3("gh", ["auth", "status"]);
|
|
463
|
+
} catch {
|
|
464
|
+
console.error(
|
|
465
|
+
chalk.red("GitHub CLI not authenticated. Run 'gh auth login'.")
|
|
466
|
+
);
|
|
467
|
+
process.exit(1);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function validateVerbosity(value) {
|
|
471
|
+
const valid = ["brief", "normal", "detailed"];
|
|
472
|
+
return valid.includes(value) ? value : "normal";
|
|
473
|
+
}
|
|
474
|
+
async function execute(username, days, lang, format, publicOnly, verbosity, model) {
|
|
475
|
+
const resolvedUsername = username || await getAuthenticatedUser();
|
|
476
|
+
console.error(
|
|
477
|
+
chalk.yellow(`Fetching GitHub activity for ${resolvedUsername}...`)
|
|
478
|
+
);
|
|
479
|
+
const activity = await fetchGitHubActivity(
|
|
480
|
+
resolvedUsername,
|
|
481
|
+
days,
|
|
482
|
+
publicOnly
|
|
483
|
+
);
|
|
484
|
+
if (!hasActivity(activity)) {
|
|
485
|
+
console.log("");
|
|
486
|
+
console.log(`tl;dr ${activity.date}`);
|
|
487
|
+
console.log("");
|
|
488
|
+
const noActivity = lang === "en" ? `No GitHub activity in the ${activity.period}.` : `Keine GitHub-Aktivit\xE4t in den ${activity.period}.`;
|
|
489
|
+
console.log(noActivity);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const stats = formatActivity(activity, format, lang);
|
|
493
|
+
console.error(chalk.yellow("Generating summary with Claude..."));
|
|
494
|
+
const summaryText = await generateSummaryText(
|
|
495
|
+
activity,
|
|
496
|
+
lang,
|
|
497
|
+
verbosity,
|
|
498
|
+
model
|
|
499
|
+
);
|
|
500
|
+
console.log("");
|
|
501
|
+
console.log(stats);
|
|
502
|
+
console.log("");
|
|
503
|
+
console.log("---");
|
|
504
|
+
console.log(summaryText);
|
|
505
|
+
}
|
|
506
|
+
async function run() {
|
|
507
|
+
const program = new Command();
|
|
508
|
+
program.name("gh-tldr").description("Generate a TL;DR summary of your GitHub activity").version("1.0.0").argument("[username]", "GitHub username (defaults to authenticated user)").option("-d, --days <n>", "Time period in days", "1").option("-e, --english", "Output in English (default: German)", false).option(
|
|
509
|
+
"-f, --format <type>",
|
|
510
|
+
"Output format: slack|markdown|plain",
|
|
511
|
+
"slack"
|
|
512
|
+
).option("-p, --public-only", "Exclude private repositories", false).option("-i, --interactive", "Force interactive mode", false).option(
|
|
513
|
+
"-v, --verbosity <level>",
|
|
514
|
+
"Summary verbosity: brief|normal|detailed",
|
|
515
|
+
"normal"
|
|
516
|
+
).option(
|
|
517
|
+
"-m, --model <model>",
|
|
518
|
+
"Claude model to use (e.g., sonnet, opus, haiku)"
|
|
519
|
+
).action(async (username, options) => {
|
|
520
|
+
try {
|
|
521
|
+
await checkDependencies();
|
|
522
|
+
const hasArgs = process.argv.length > 2;
|
|
523
|
+
const forceInteractive = options.interactive;
|
|
524
|
+
if (!hasArgs || forceInteractive) {
|
|
525
|
+
const answers = await runInteractive();
|
|
526
|
+
await execute(
|
|
527
|
+
answers.username,
|
|
528
|
+
answers.days,
|
|
529
|
+
answers.language,
|
|
530
|
+
answers.format,
|
|
531
|
+
!answers.includePrivate,
|
|
532
|
+
answers.verbosity,
|
|
533
|
+
answers.model || void 0
|
|
534
|
+
);
|
|
535
|
+
} else {
|
|
536
|
+
const days = parseInt(options.days, 10);
|
|
537
|
+
const lang = options.english ? "en" : "de";
|
|
538
|
+
const format = options.format;
|
|
539
|
+
await execute(
|
|
540
|
+
username || "",
|
|
541
|
+
days,
|
|
542
|
+
lang,
|
|
543
|
+
format,
|
|
544
|
+
options.publicOnly,
|
|
545
|
+
validateVerbosity(options.verbosity),
|
|
546
|
+
options.model
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
} catch (error) {
|
|
550
|
+
if (error instanceof Error) {
|
|
551
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
552
|
+
}
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
await program.parseAsync();
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/index.ts
|
|
560
|
+
run();
|
|
561
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/claude.ts","../src/github.ts","../src/interactive.ts","../src/output.ts","../src/index.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport { generateSummaryText } from \"./claude.js\";\nimport { fetchGitHubActivity, getAuthenticatedUser } from \"./github.js\";\nimport { runInteractive } from \"./interactive.js\";\nimport { formatActivity, hasActivity } from \"./output.js\";\nimport type { Language, OutputFormat, Verbosity } from \"./types.js\";\n\nasync function checkDependencies(): Promise<void> {\n\tconst { execa } = await import(\"execa\");\n\n\tconst deps = [\n\t\t{ cmd: \"gh\", name: \"GitHub CLI\", installHint: \"brew install gh\" },\n\t\t{\n\t\t\tcmd: \"claude\",\n\t\t\tname: \"Claude Code CLI\",\n\t\t\tinstallHint: \"npm install -g @anthropic-ai/claude-code\",\n\t\t},\n\t];\n\n\tconst missing: string[] = [];\n\n\tfor (const dep of deps) {\n\t\ttry {\n\t\t\tawait execa(\"which\", [dep.cmd]);\n\t\t} catch {\n\t\t\tmissing.push(`${dep.name} (${dep.installHint})`);\n\t\t}\n\t}\n\n\tif (missing.length > 0) {\n\t\tconsole.error(chalk.red(\"Missing dependencies:\"));\n\t\tfor (const m of missing) {\n\t\t\tconsole.error(` - ${m}`);\n\t\t}\n\t\tprocess.exit(1);\n\t}\n\n\t// Check gh auth\n\ttry {\n\t\tawait execa(\"gh\", [\"auth\", \"status\"]);\n\t} catch {\n\t\tconsole.error(\n\t\t\tchalk.red(\"GitHub CLI not authenticated. Run 'gh auth login'.\"),\n\t\t);\n\t\tprocess.exit(1);\n\t}\n}\n\ninterface CliOptions {\n\tdays: string;\n\tenglish: boolean;\n\tformat: OutputFormat;\n\tpublicOnly: boolean;\n\tinteractive: boolean;\n\tverbosity: string;\n\tmodel?: string;\n}\n\nfunction validateVerbosity(value: string): Verbosity {\n\tconst valid: Verbosity[] = [\"brief\", \"normal\", \"detailed\"];\n\treturn valid.includes(value as Verbosity) ? (value as Verbosity) : \"normal\";\n}\n\nasync function execute(\n\tusername: string,\n\tdays: number,\n\tlang: Language,\n\tformat: OutputFormat,\n\tpublicOnly: boolean,\n\tverbosity: Verbosity,\n\tmodel?: string,\n): Promise<void> {\n\t// Resolve username\n\tconst resolvedUsername = username || (await getAuthenticatedUser());\n\n\tconsole.error(\n\t\tchalk.yellow(`Fetching GitHub activity for ${resolvedUsername}...`),\n\t);\n\n\tconst activity = await fetchGitHubActivity(\n\t\tresolvedUsername,\n\t\tdays,\n\t\tpublicOnly,\n\t);\n\n\tif (!hasActivity(activity)) {\n\t\tconsole.log(\"\");\n\t\tconsole.log(`tl;dr ${activity.date}`);\n\t\tconsole.log(\"\");\n\t\tconst noActivity =\n\t\t\tlang === \"en\"\n\t\t\t\t? `No GitHub activity in the ${activity.period}.`\n\t\t\t\t: `Keine GitHub-Aktivität in den ${activity.period}.`;\n\t\tconsole.log(noActivity);\n\t\treturn;\n\t}\n\n\t// Format stats locally\n\tconst stats = formatActivity(activity, format, lang);\n\n\tconsole.error(chalk.yellow(\"Generating summary with Claude...\"));\n\n\t// Only ask Claude for the summary paragraph\n\tconst summaryText = await generateSummaryText(\n\t\tactivity,\n\t\tlang,\n\t\tverbosity,\n\t\tmodel,\n\t);\n\n\tconsole.log(\"\");\n\tconsole.log(stats);\n\tconsole.log(\"\");\n\tconsole.log(\"---\");\n\tconsole.log(summaryText);\n}\n\nexport async function run(): Promise<void> {\n\tconst program = new Command();\n\n\tprogram\n\t\t.name(\"gh-tldr\")\n\t\t.description(\"Generate a TL;DR summary of your GitHub activity\")\n\t\t.version(\"1.0.0\")\n\t\t.argument(\"[username]\", \"GitHub username (defaults to authenticated user)\")\n\t\t.option(\"-d, --days <n>\", \"Time period in days\", \"1\")\n\t\t.option(\"-e, --english\", \"Output in English (default: German)\", false)\n\t\t.option(\n\t\t\t\"-f, --format <type>\",\n\t\t\t\"Output format: slack|markdown|plain\",\n\t\t\t\"slack\",\n\t\t)\n\t\t.option(\"-p, --public-only\", \"Exclude private repositories\", false)\n\t\t.option(\"-i, --interactive\", \"Force interactive mode\", false)\n\t\t.option(\n\t\t\t\"-v, --verbosity <level>\",\n\t\t\t\"Summary verbosity: brief|normal|detailed\",\n\t\t\t\"normal\",\n\t\t)\n\t\t.option(\n\t\t\t\"-m, --model <model>\",\n\t\t\t\"Claude model to use (e.g., sonnet, opus, haiku)\",\n\t\t)\n\t\t.action(async (username: string | undefined, options: CliOptions) => {\n\t\t\ttry {\n\t\t\t\tawait checkDependencies();\n\n\t\t\t\t// Determine if we should run interactive mode\n\t\t\t\tconst hasArgs = process.argv.length > 2;\n\t\t\t\tconst forceInteractive = options.interactive;\n\n\t\t\t\tif (!hasArgs || forceInteractive) {\n\t\t\t\t\t// Interactive mode\n\t\t\t\t\tconst answers = await runInteractive();\n\t\t\t\t\tawait execute(\n\t\t\t\t\t\tanswers.username,\n\t\t\t\t\t\tanswers.days,\n\t\t\t\t\t\tanswers.language,\n\t\t\t\t\t\tanswers.format,\n\t\t\t\t\t\t!answers.includePrivate,\n\t\t\t\t\t\tanswers.verbosity,\n\t\t\t\t\t\tanswers.model || undefined,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\t// Direct mode\n\t\t\t\t\tconst days = parseInt(options.days, 10);\n\t\t\t\t\tconst lang: Language = options.english ? \"en\" : \"de\";\n\t\t\t\t\tconst format = options.format as OutputFormat;\n\n\t\t\t\t\tawait execute(\n\t\t\t\t\t\tusername || \"\",\n\t\t\t\t\t\tdays,\n\t\t\t\t\t\tlang,\n\t\t\t\t\t\tformat,\n\t\t\t\t\t\toptions.publicOnly,\n\t\t\t\t\t\tvalidateVerbosity(options.verbosity),\n\t\t\t\t\t\toptions.model,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof Error) {\n\t\t\t\t\tconsole.error(chalk.red(`Error: ${error.message}`));\n\t\t\t\t}\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t});\n\n\tawait program.parseAsync();\n}\n","import { execa } from \"execa\";\nimport type { GitHubActivity, Language, Verbosity } from \"./types.js\";\n\nconst wordLimits: Record<Verbosity, Record<Language, string>> = {\n\tbrief: { en: \"20-40 word\", de: \"20-40 Wörter\" },\n\tnormal: { en: \"50-80 word\", de: \"50-80 Wörter\" },\n\tdetailed: { en: \"150-200 word\", de: \"150-200 Wörter\" },\n};\n\nfunction buildPrompt(lang: Language, verbosity: Verbosity): string {\n\tconst limit = wordLimits[verbosity][lang];\n\n\tif (lang === \"en\") {\n\t\treturn `Based on this GitHub activity data, write a summary of what was accomplished. STRICT LIMIT: ${limit}. Mention specific PR/issue titles. Casual tone, no bullet points, no emojis.\n\nGitHub Activity Data:`;\n\t}\n\n\treturn `Basierend auf diesen GitHub-Aktivitätsdaten, schreibe eine Zusammenfassung was gemacht wurde. STRIKTES LIMIT: ${limit}. Erwähne konkret die PR/Issue-Titel. Lockerer Ton, keine Aufzählungen, keine Emojis.\n\nGitHub-Aktivitätsdaten:`;\n}\n\nexport async function generateSummaryText(\n\tactivity: GitHubActivity,\n\tlang: Language,\n\tverbosity: Verbosity = \"normal\",\n\tmodel?: string,\n): Promise<string> {\n\tconst prompt = buildPrompt(lang, verbosity);\n\tconst fullPrompt = `${prompt}\\n${JSON.stringify(activity, null, 2)}`;\n\n\tconst args = [\"-p\", \"-\", \"--output-format\", \"text\"];\n\tif (model) {\n\t\targs.push(\"--model\", model);\n\t}\n\n\tconst { stdout } = await execa(\"claude\", args, {\n\t\tinput: fullPrompt,\n\t});\n\n\treturn stdout.trim();\n}\n","import { execa } from \"execa\";\nimport type {\n\tCommit,\n\tGitHubActivity,\n\tIssue,\n\tPullRequest,\n\tRepoInfo,\n} from \"./types.js\";\n\nfunction getSinceDate(days: number): string {\n\tconst date = new Date();\n\tdate.setDate(date.getDate() - days);\n\treturn date.toISOString();\n}\n\nfunction formatDate(date: Date): string {\n\tconst day = date.getDate().toString().padStart(2, \"0\");\n\tconst month = (date.getMonth() + 1).toString().padStart(2, \"0\");\n\tconst year = date.getFullYear();\n\treturn `${day}.${month}.${year}`;\n}\n\nasync function ghApi<T>(\n\tendpoint: string,\n\tparams: Record<string, string>,\n): Promise<T> {\n\tconst args = [\n\t\t\"api\",\n\t\t\"-X\",\n\t\t\"GET\",\n\t\tendpoint,\n\t\t...Object.entries(params).flatMap(([key, value]) => [\n\t\t\t\"-f\",\n\t\t\t`${key}=${value}`,\n\t\t]),\n\t];\n\n\tconst { stdout } = await execa(\"gh\", args);\n\treturn JSON.parse(stdout) as T;\n}\n\nasync function ghApiPaginated<T>(\n\tendpoint: string,\n\tparams: Record<string, string>,\n\tmaxPages: number = 10,\n): Promise<T[]> {\n\tconst allItems: T[] = [];\n\tlet page = 1;\n\n\twhile (page <= maxPages) {\n\t\tconst result = await ghApi<SearchResult<T>>(endpoint, {\n\t\t\t...params,\n\t\t\tpage: String(page),\n\t\t\tper_page: \"100\",\n\t\t});\n\n\t\tallItems.push(...result.items);\n\n\t\tif (result.items.length < 100) {\n\t\t\tbreak;\n\t\t}\n\t\tpage++;\n\t}\n\n\treturn allItems;\n}\n\ninterface SearchResult<T> {\n\ttotal_count?: number;\n\titems: T[];\n}\n\ninterface RawPR {\n\trepository_url: string;\n\ttitle: string;\n\tnumber: number;\n\tstate: string;\n\thtml_url: string;\n}\n\ninterface RawCommit {\n\trepository: {\n\t\tname: string;\n\t\towner: { login: string };\n\t};\n\tcommit: {\n\t\tmessage: string;\n\t\tauthor: { date: string };\n\t};\n\thtml_url: string;\n}\n\ninterface RawRepo {\n\tname: string;\n\towner: { login: string };\n\tcreated_at: string;\n\thtml_url: string;\n}\n\nfunction parsePR(item: RawPR): PullRequest {\n\tconst urlParts = item.repository_url.split(\"/\");\n\treturn {\n\t\trepo: urlParts[urlParts.length - 1],\n\t\torg: urlParts[urlParts.length - 2],\n\t\ttitle: item.title,\n\t\tnumber: item.number,\n\t\tstate: item.state,\n\t\turl: item.html_url,\n\t};\n}\n\nfunction parseIssue(item: RawPR): Issue {\n\tconst urlParts = item.repository_url.split(\"/\");\n\treturn {\n\t\trepo: urlParts[urlParts.length - 1],\n\t\torg: urlParts[urlParts.length - 2],\n\t\ttitle: item.title,\n\t\tnumber: item.number,\n\t\turl: item.html_url,\n\t};\n}\n\nfunction parseCommit(item: RawCommit): Commit {\n\treturn {\n\t\trepo: item.repository.name,\n\t\torg: item.repository.owner.login,\n\t\tmessage: item.commit.message.split(\"\\n\")[0],\n\t\turl: item.html_url,\n\t\tdate: item.commit.author.date,\n\t};\n}\n\nexport async function getAuthenticatedUser(): Promise<string> {\n\tconst { stdout } = await execa(\"gh\", [\"api\", \"user\", \"--jq\", \".login\"]);\n\treturn stdout.trim();\n}\n\nasync function fetchUserOrgs(username: string): Promise<string[]> {\n\t// First try authenticated user's orgs (includes private memberships)\n\ttry {\n\t\tconst authUser = await getAuthenticatedUser();\n\t\tif (authUser === username) {\n\t\t\tconst { stdout } = await execa(\"gh\", [\n\t\t\t\t\"api\",\n\t\t\t\t\"user/orgs\",\n\t\t\t\t\"--jq\",\n\t\t\t\t\".[].login\",\n\t\t\t]);\n\t\t\treturn stdout.trim().split(\"\\n\").filter(Boolean);\n\t\t}\n\t} catch {\n\t\t// Fall through to public orgs\n\t}\n\n\t// Fallback to public orgs for other users\n\ttry {\n\t\tconst { stdout } = await execa(\"gh\", [\n\t\t\t\"api\",\n\t\t\t`users/${username}/orgs`,\n\t\t\t\"--jq\",\n\t\t\t\".[].login\",\n\t\t]);\n\t\treturn stdout.trim().split(\"\\n\").filter(Boolean);\n\t} catch {\n\t\treturn [];\n\t}\n}\n\nasync function fetchReposCreatedSince(\n\tusername: string,\n\tsince: string,\n\tpublicOnly: boolean,\n): Promise<RepoInfo[]> {\n\tconst sinceDate = new Date(since);\n\tconst repos: RepoInfo[] = [];\n\n\t// Fetch user's own repos\n\ttry {\n\t\tconst visibility = publicOnly ? \"public\" : \"all\";\n\t\tconst { stdout } = await execa(\"gh\", [\n\t\t\t\"api\",\n\t\t\t`users/${username}/repos?type=${visibility}&sort=created&direction=desc&per_page=100`,\n\t\t]);\n\t\tconst userRepos = JSON.parse(stdout) as RawRepo[];\n\t\tfor (const repo of userRepos) {\n\t\t\tif (new Date(repo.created_at) >= sinceDate) {\n\t\t\t\trepos.push({ name: repo.name, org: repo.owner.login });\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore errors for user repos\n\t}\n\n\t// Fetch repos from user's orgs\n\tconst orgs = await fetchUserOrgs(username);\n\tfor (const org of orgs) {\n\t\ttry {\n\t\t\tconst { stdout } = await execa(\"gh\", [\n\t\t\t\t\"api\",\n\t\t\t\t`orgs/${org}/repos?sort=created&direction=desc&per_page=100`,\n\t\t\t]);\n\t\t\tconst orgRepos = JSON.parse(stdout) as RawRepo[];\n\t\t\tfor (const repo of orgRepos) {\n\t\t\t\tif (new Date(repo.created_at) >= sinceDate) {\n\t\t\t\t\trepos.push({ name: repo.name, org: repo.owner.login });\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore errors for individual orgs\n\t\t}\n\t}\n\n\t// Deduplicate\n\treturn repos.filter(\n\t\t(repo, index, self) =>\n\t\t\tself.findIndex((r) => r.name === repo.name && r.org === repo.org) ===\n\t\t\tindex,\n\t);\n}\n\nexport async function fetchGitHubActivity(\n\tusername: string,\n\tdays: number,\n\tpublicOnly: boolean,\n): Promise<GitHubActivity> {\n\tconst since = getSinceDate(days);\n\tconst today = formatDate(new Date());\n\tconst visibilityFilter = publicOnly ? \" is:public\" : \"\";\n\n\t// Fetch all data in parallel with pagination\n\tconst [\n\t\tprsCreatedItems,\n\t\tprsMergedItems,\n\t\tprsReviewedItems,\n\t\tissuesCreatedItems,\n\t\tissuesClosedItems,\n\t\tcommitsItems,\n\t\trepos_created,\n\t] = await Promise.all([\n\t\tghApiPaginated<RawPR>(\"search/issues\", {\n\t\t\tq: `author:${username} type:pr created:>=${since}${visibilityFilter}`,\n\t\t}),\n\t\tghApiPaginated<RawPR>(\"search/issues\", {\n\t\t\tq: `author:${username} type:pr merged:>=${since}${visibilityFilter}`,\n\t\t}),\n\t\tghApiPaginated<RawPR>(\"search/issues\", {\n\t\t\tq: `reviewed-by:${username} type:pr created:>=${since} -author:${username}${visibilityFilter}`,\n\t\t}),\n\t\tghApiPaginated<RawPR>(\"search/issues\", {\n\t\t\tq: `author:${username} type:issue created:>=${since}${visibilityFilter}`,\n\t\t}),\n\t\tghApiPaginated<RawPR>(\"search/issues\", {\n\t\t\tq: `author:${username} type:issue closed:>=${since}${visibilityFilter}`,\n\t\t}),\n\t\tghApiPaginated<RawCommit>(\"search/commits\", {\n\t\t\tq: `author:${username} committer-date:>=${since}`,\n\t\t}),\n\t\tfetchReposCreatedSince(username, since, publicOnly),\n\t]);\n\n\tconst prs_created = prsCreatedItems.map(parsePR);\n\tconst prs_merged = prsMergedItems.map(parsePR);\n\tconst prs_reviewed = prsReviewedItems.map(parsePR);\n\tconst issues_created = issuesCreatedItems.map(parseIssue);\n\tconst issues_closed = issuesClosedItems.map(parseIssue);\n\tconst commits = commitsItems.map(parseCommit);\n\n\t// Extract unique repos touched\n\tconst allRepos = [\n\t\t...prs_created,\n\t\t...prs_merged,\n\t\t...prs_reviewed,\n\t\t...issues_created,\n\t\t...issues_closed,\n\t\t...commits,\n\t].map((item) => `${item.org}/${item.repo}`);\n\n\tconst repos_touched = [...new Set(allRepos)];\n\n\tconst periodText =\n\t\tdays === 1\n\t\t\t? \"last 24 hours\"\n\t\t\t: days === 7\n\t\t\t\t? \"last 7 days\"\n\t\t\t\t: days === 30\n\t\t\t\t\t? \"last 30 days\"\n\t\t\t\t\t: `last ${days} days`;\n\n\treturn {\n\t\tuser: username,\n\t\tdate: today,\n\t\tperiod: periodText,\n\t\tprs_created,\n\t\tprs_merged,\n\t\tprs_reviewed,\n\t\tissues_created,\n\t\tissues_closed,\n\t\tcommits,\n\t\trepos_created,\n\t\trepos_touched,\n\t};\n}\n","import { confirm, input, select } from \"@inquirer/prompts\";\nimport type { Language, OutputFormat, Verbosity } from \"./types.js\";\n\ninterface InteractiveOptions {\n\tusername: string;\n\tdays: number;\n\tlanguage: Language;\n\tverbosity: Verbosity;\n\tformat: OutputFormat;\n\tincludePrivate: boolean;\n\tmodel: string;\n}\n\nexport async function runInteractive(): Promise<InteractiveOptions> {\n\tconst username = await input({\n\t\tmessage: \"GitHub username (leave empty for authenticated user)\",\n\t\tdefault: \"\",\n\t});\n\n\tconst days = await select({\n\t\tmessage: \"Time period\",\n\t\tchoices: [\n\t\t\t{ name: \"Last 24 hours\", value: 1 },\n\t\t\t{ name: \"Last 7 days\", value: 7 },\n\t\t\t{ name: \"Last 30 days\", value: 30 },\n\t\t],\n\t\tdefault: 1,\n\t});\n\n\tconst language = await select({\n\t\tmessage: \"Language\",\n\t\tchoices: [\n\t\t\t{ name: \"English\", value: \"en\" as Language },\n\t\t\t{ name: \"German\", value: \"de\" as Language },\n\t\t],\n\t\tdefault: \"en\" as Language,\n\t});\n\n\tconst verbosity = await select({\n\t\tmessage: \"Summary verbosity\",\n\t\tchoices: [\n\t\t\t{ name: \"Brief (~30 words)\", value: \"brief\" as Verbosity },\n\t\t\t{ name: \"Normal (~60 words)\", value: \"normal\" as Verbosity },\n\t\t\t{ name: \"Detailed (~175 words)\", value: \"detailed\" as Verbosity },\n\t\t],\n\t\tdefault: \"normal\" as Verbosity,\n\t});\n\n\tconst format = await select({\n\t\tmessage: \"Output format\",\n\t\tchoices: [\n\t\t\t{ name: \"Plain text\", value: \"plain\" as OutputFormat },\n\t\t\t{ name: \"Markdown\", value: \"markdown\" as OutputFormat },\n\t\t\t{ name: \"Slack\", value: \"slack\" as OutputFormat },\n\t\t],\n\t\tdefault: \"plain\" as OutputFormat,\n\t});\n\n\tconst includePrivate = await confirm({\n\t\tmessage: \"Include private repos?\",\n\t\tdefault: true,\n\t});\n\n\tconst model = await input({\n\t\tmessage: \"Claude model (leave empty for default)\",\n\t\tdefault: \"\",\n\t});\n\n\treturn {\n\t\tusername,\n\t\tdays,\n\t\tlanguage,\n\t\tverbosity,\n\t\tformat,\n\t\tincludePrivate,\n\t\tmodel,\n\t};\n}\n","import type {\n\tCommit,\n\tGitHubActivity,\n\tLanguage,\n\tOutputFormat,\n} from \"./types.js\";\n\n// Gap threshold: if gap between commits > 3 hours, treat as separate session\nconst GAP_THRESHOLD_MS = 3 * 60 * 60 * 1000;\n// Minimum time to count for a single-commit session (15 minutes)\nconst SINGLE_COMMIT_HOURS = 0.25;\n\nfunction calculateWorkSession(\n\tcommits: Commit[],\n\tlang: Language,\n): string | null {\n\tif (commits.length < 2) return null;\n\n\t// Sort timestamps chronologically\n\tconst timestamps = commits\n\t\t.map((c) => new Date(c.date).getTime())\n\t\t.sort((a, b) => a - b);\n\n\t// Group commits into sessions based on gap threshold\n\tconst sessions: number[][] = [];\n\tlet currentSession = [timestamps[0]];\n\n\tfor (let i = 1; i < timestamps.length; i++) {\n\t\tconst gap = timestamps[i] - timestamps[i - 1];\n\t\tif (gap > GAP_THRESHOLD_MS) {\n\t\t\tsessions.push(currentSession);\n\t\t\tcurrentSession = [timestamps[i]];\n\t\t} else {\n\t\t\tcurrentSession.push(timestamps[i]);\n\t\t}\n\t}\n\tsessions.push(currentSession);\n\n\t// Calculate total hours across all sessions\n\tlet totalHours = 0;\n\tfor (const session of sessions) {\n\t\tif (session.length === 1) {\n\t\t\ttotalHours += SINGLE_COMMIT_HOURS;\n\t\t} else {\n\t\t\tconst duration = session[session.length - 1] - session[0];\n\t\t\ttotalHours += duration / (1000 * 60 * 60);\n\t\t}\n\t}\n\n\t// Round to nearest 0.5 hours\n\tconst rounded = Math.round(totalHours * 2) / 2;\n\n\tif (rounded < 0.5) return null;\n\n\tconst label = lang === \"en\" ? \"Work session\" : \"Arbeitszeit\";\n\tconst hoursLabel =\n\t\trounded === 1\n\t\t\t? lang === \"en\"\n\t\t\t\t? \"hour\"\n\t\t\t\t: \"Stunde\"\n\t\t\t: lang === \"en\"\n\t\t\t\t? \"hours\"\n\t\t\t\t: \"Stunden\";\n\n\treturn `${label}: ~${rounded} ${hoursLabel}`;\n}\n\nfunction getRepoNames(items: { repo: string }[]): string {\n\tconst repos = [...new Set(items.map((i) => i.repo))];\n\treturn repos.join(\", \");\n}\n\nfunction formatActivityLine(\n\tcount: number,\n\tlabelEn: string,\n\tlabelDe: string,\n\trepos: string,\n\tlang: Language,\n\tformat: OutputFormat,\n): string | null {\n\tif (count === 0) return null;\n\n\tconst label = lang === \"en\" ? labelEn : labelDe;\n\tconst repoSuffix = repos ? ` (${repos})` : \"\";\n\n\tif (format === \"markdown\") {\n\t\treturn `- **${count}** ${label}${repoSuffix}`;\n\t}\n\treturn `• ${count} ${label}${repoSuffix}`;\n}\n\nexport function formatActivity(\n\tactivity: GitHubActivity,\n\tformat: OutputFormat,\n\tlang: Language,\n): string {\n\tconst lines: string[] = [];\n\n\t// Header\n\tconst headerPrefix = format === \"markdown\" ? \"## \" : \"\";\n\tlines.push(`${headerPrefix}tl;dr ${activity.date}`);\n\tlines.push(\"\");\n\n\tconst activityLines = [\n\t\tformatActivityLine(\n\t\t\tactivity.prs_created.length,\n\t\t\t\"PRs created\",\n\t\t\t\"PRs erstellt\",\n\t\t\tgetRepoNames(activity.prs_created),\n\t\t\tlang,\n\t\t\tformat,\n\t\t),\n\t\tformatActivityLine(\n\t\t\tactivity.prs_reviewed.length,\n\t\t\t\"PRs reviewed\",\n\t\t\t\"PRs reviewed/approved\",\n\t\t\tgetRepoNames(activity.prs_reviewed),\n\t\t\tlang,\n\t\t\tformat,\n\t\t),\n\t\tformatActivityLine(\n\t\t\tactivity.prs_merged.length,\n\t\t\t\"PRs merged\",\n\t\t\t\"PRs gemerged\",\n\t\t\tgetRepoNames(activity.prs_merged),\n\t\t\tlang,\n\t\t\tformat,\n\t\t),\n\t\tformatActivityLine(\n\t\t\tactivity.issues_created.length,\n\t\t\t\"issues created\",\n\t\t\t\"Issues erstellt\",\n\t\t\tgetRepoNames(activity.issues_created),\n\t\t\tlang,\n\t\t\tformat,\n\t\t),\n\t\tformatActivityLine(\n\t\t\tactivity.issues_closed.length,\n\t\t\t\"issues closed\",\n\t\t\t\"Issues geschlossen\",\n\t\t\tgetRepoNames(activity.issues_closed),\n\t\t\tlang,\n\t\t\tformat,\n\t\t),\n\t\tformatActivityLine(\n\t\t\tactivity.commits.length,\n\t\t\t\"commits\",\n\t\t\t\"Commits\",\n\t\t\tgetRepoNames(activity.commits),\n\t\t\tlang,\n\t\t\tformat,\n\t\t),\n\t\tformatActivityLine(\n\t\t\tactivity.repos_created.length,\n\t\t\t\"new repos created\",\n\t\t\t\"neue Repos erstellt\",\n\t\t\tactivity.repos_created.map((r) => r.name).join(\", \"),\n\t\t\tlang,\n\t\t\tformat,\n\t\t),\n\t].filter(Boolean) as string[];\n\n\tif (activityLines.length === 0) {\n\t\tconst noActivity =\n\t\t\tlang === \"en\"\n\t\t\t\t? `No GitHub activity in the ${activity.period}.`\n\t\t\t\t: `Keine GitHub-Aktivität in den ${activity.period}.`;\n\t\tlines.push(noActivity);\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tlines.push(...activityLines);\n\n\t// Work session duration\n\tconst workSession = calculateWorkSession(activity.commits, lang);\n\tif (workSession) {\n\t\tlines.push(format === \"markdown\" ? `- ${workSession}` : `• ${workSession}`);\n\t}\n\n\tlines.push(\"\");\n\n\t// Repos touched\n\tif (activity.repos_touched.length > 0) {\n\t\tconst reposLabel = lang === \"en\" ? \"Repos\" : \"Repos\";\n\t\tlines.push(`${reposLabel}: ${activity.repos_touched.join(\", \")}`);\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function hasActivity(activity: GitHubActivity): boolean {\n\treturn (\n\t\tactivity.prs_created.length +\n\t\t\tactivity.prs_merged.length +\n\t\t\tactivity.prs_reviewed.length +\n\t\t\tactivity.issues_created.length +\n\t\t\tactivity.issues_closed.length >\n\t\t0\n\t);\n}\n","// gh-tldr - Generate a TL;DR summary of your GitHub activity\n// Entry point: detects mode (interactive vs direct) and runs CLI\n\nimport { run } from \"./cli.js\";\n\nrun();\n"],"mappings":"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","names":["execa","execa"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gh-tldr",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Generate a TL;DR summary of your GitHub activity",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"gh-tldr": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "tsx src/index.ts",
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"start": "node dist/index.js",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"lint": "biome check src",
|
|
20
|
+
"lint:fix": "biome check --write src",
|
|
21
|
+
"format": "biome format --write src"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"github",
|
|
25
|
+
"cli",
|
|
26
|
+
"tldr",
|
|
27
|
+
"activity",
|
|
28
|
+
"summary"
|
|
29
|
+
],
|
|
30
|
+
"author": "",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"packageManager": "pnpm@10.26.2",
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@inquirer/prompts": "^8.1.0",
|
|
38
|
+
"chalk": "^5.6.2",
|
|
39
|
+
"commander": "^14.0.2",
|
|
40
|
+
"execa": "^9.6.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@biomejs/biome": "2.3.10",
|
|
44
|
+
"@types/node": "^25.0.3",
|
|
45
|
+
"tsup": "^8.5.1",
|
|
46
|
+
"tsx": "^4.21.0",
|
|
47
|
+
"typescript": "^5.9.3"
|
|
48
|
+
}
|
|
49
|
+
}
|