github-dependents-info 2.0.2__tar.gz → 3.0.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.
- {github_dependents_info-2.0.2 → github_dependents_info-3.0.0}/PKG-INFO +66 -18
- {github_dependents_info-2.0.2 → github_dependents_info-3.0.0}/README.md +62 -16
- {github_dependents_info-2.0.2 → github_dependents_info-3.0.0}/github_dependents_info/__main__.py +66 -20
- {github_dependents_info-2.0.2 → github_dependents_info-3.0.0}/github_dependents_info/gh_dependents_info.py +227 -0
- {github_dependents_info-2.0.2 → github_dependents_info-3.0.0}/pyproject.toml +5 -3
- {github_dependents_info-2.0.2 → github_dependents_info-3.0.0}/LICENSE +0 -0
- {github_dependents_info-2.0.2 → github_dependents_info-3.0.0}/github_dependents_info/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github-dependents-info
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: Collect information about dependencies between a github repo and other repositories. Results available in JSON, markdown and badges.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -18,9 +18,11 @@ Requires-Dist: beautifulsoup4 (==4.14.3)
|
|
|
18
18
|
Requires-Dist: click (>=8.3.1,<8.4)
|
|
19
19
|
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
20
20
|
Requires-Dist: idna (>=3.11)
|
|
21
|
+
Requires-Dist: litellm (>=1.60.0,<2.0)
|
|
21
22
|
Requires-Dist: pandas (>=2.3.3,<3.0)
|
|
22
23
|
Requires-Dist: rich (>=14.2,<14.3)
|
|
23
|
-
Requires-Dist: typer
|
|
24
|
+
Requires-Dist: typer-slim (>=0.19,<0.20)
|
|
25
|
+
Requires-Dist: typer[standard] (>=0.19,<0.20)
|
|
24
26
|
Project-URL: Homepage, https://github.com/nvuillam/github-dependents-info
|
|
25
27
|
Project-URL: Repository, https://github.com/nvuillam/github-dependents-info
|
|
26
28
|
Description-Content-Type: text/markdown
|
|
@@ -60,6 +62,7 @@ This package uses GitHub HTML to collect dependents information and can:
|
|
|
60
62
|
- Output as text
|
|
61
63
|
- Output as json (including shields.io markdown badges)
|
|
62
64
|
- Generate summary markdown file
|
|
65
|
+
- Optionally add an AI-generated usage summary (via `litellm`) when an LLM API key is present
|
|
63
66
|
- Update existing markdown by inserting **Used by** badge within tags
|
|
64
67
|
- `<!-- gh-dependents-info-used-by-start -->
|
|
65
68
|
[](https://github.com/nvuillam/github-dependents-info/blob/main/docs/github-dependents-info.md)<!-- gh-dependents-info-used-by-end -->`
|
|
@@ -68,6 +71,24 @@ This package uses GitHub HTML to collect dependents information and can:
|
|
|
68
71
|
- Keep huge ecosystems manageable with pagination controls (`--max-scraped-pages`, `--pagination/--no-pagination`, `--page-size`)
|
|
69
72
|
- Fetch dependents faster thanks to asynchronous `httpx` requests and parallelized page scraping
|
|
70
73
|
|
|
74
|
+
### AI usage summary (optional)
|
|
75
|
+
|
|
76
|
+
If an LLM API key is detected in the environment (for example `OPENAI_API_KEY`), the tool will call a lightweight model (via `litellm`) to generate a short **usage summary** and include it in the generated markdown.
|
|
77
|
+
|
|
78
|
+
- Supported provider env vars (most common):
|
|
79
|
+
- OpenAI: `OPENAI_API_KEY`
|
|
80
|
+
- Azure OpenAI: `AZURE_OPENAI_API_KEY`
|
|
81
|
+
- Anthropic: `ANTHROPIC_API_KEY`
|
|
82
|
+
- Google Gemini: `GEMINI_API_KEY` (or `GOOGLE_API_KEY`)
|
|
83
|
+
- Mistral: `MISTRAL_API_KEY`
|
|
84
|
+
- Cohere: `COHERE_API_KEY`
|
|
85
|
+
- Groq: `GROQ_API_KEY`
|
|
86
|
+
|
|
87
|
+
- Disable with `--no-llm-summary` (or env var `GITHUB_DEPENDENTS_INFO_LLM_SUMMARY=false`)
|
|
88
|
+
- Override model with `--llm-model` (or env var `GITHUB_DEPENDENTS_INFO_LLM_MODEL` / `LITELLM_MODEL`)
|
|
89
|
+
- Adjust max summary length with `--llm-max-words` (or env var `GITHUB_DEPENDENTS_INFO_LLM_MAX_WORDS`)
|
|
90
|
+
- The summary is cached in `--csvdirectory` (file `llm_summary_<repo>.json`) and reused on subsequent runs
|
|
91
|
+
|
|
71
92
|
|
|
72
93
|
Badges example
|
|
73
94
|
|
|
@@ -79,6 +100,8 @@ Badges example
|
|
|
79
100
|
<details>
|
|
80
101
|
<summary>JSON output</summary>
|
|
81
102
|
|
|
103
|
+
Note: when AI usage summary is enabled and an LLM API key is present, the JSON output also includes `llm_summary`.
|
|
104
|
+
|
|
82
105
|
```json
|
|
83
106
|
{
|
|
84
107
|
"all_public_dependent_repos": [
|
|
@@ -263,22 +286,27 @@ _________________
|
|
|
263
286
|
github-dependents-info [OPTIONS]
|
|
264
287
|
```
|
|
265
288
|
|
|
266
|
-
| Parameter
|
|
267
|
-
|
|
268
|
-
| --repo
|
|
269
|
-
| -b<br/> --badgemarkdownfile
|
|
270
|
-
| -s<br/> --sort
|
|
271
|
-
| -x<br/> --minstars
|
|
272
|
-
| -m<br/> --markdownfile
|
|
273
|
-
| -d<br/> --docurl
|
|
274
|
-
| -p<br/> --mergepackages
|
|
275
|
-
| -j<br/> --json
|
|
276
|
-
| -u<br/> --owner
|
|
277
|
-
| -n<br/> --max-scraped-pages
|
|
278
|
-
| --pagination<br/> --no-pagination
|
|
279
|
-
| --page-size
|
|
280
|
-
| -
|
|
281
|
-
| --
|
|
289
|
+
| Parameter | Type | Description |
|
|
290
|
+
|-------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
291
|
+
| --repo | String | Repository. Example: `oxsecurity/megalinter` |
|
|
292
|
+
| -b<br/> --badgemarkdownfile | String | _(optional)_ Path to markdown file where to insert/update **Used by** badge <br/> (must contain tags `<!-- gh-dependents-info-used-by-start -->` … `<!-- gh-dependents-info-used-by-end -->`) |
|
|
293
|
+
| -s<br/> --sort | String | _(optional)_ Sort order: name (default) or stars |
|
|
294
|
+
| -x<br/> --minstars | String | _(optional)_ If set, filters repositories to keep only those with more than X stars |
|
|
295
|
+
| -m<br/> --markdownfile | String | _(optional)_ Output markdown file file |
|
|
296
|
+
| -d<br/> --docurl | String | _(optional)_ Hyperlink to use when clicking on badge markdown file badge. (Default: link to markdown file) |
|
|
297
|
+
| -p<br/> --mergepackages | String | _(optional)_ In case of multiple packages, merge their stats in a single one in markdown and json output |
|
|
298
|
+
| -j<br/> --json | String | _(optional)_ Output in json format |
|
|
299
|
+
| -u<br/> --owner | String | _(optional)_ If set, filters repositories to keep only those owned by the specified user/organization |
|
|
300
|
+
| -n<br/> --max-scraped-pages | Integer | _(optional)_ Maximum number of GitHub pages to scrape per package (0 uses all available pages) |
|
|
301
|
+
| --pagination<br/> --no-pagination | Boolean | _(optional)_ Enable (default) or disable pagination when writing markdown output |
|
|
302
|
+
| --page-size | Integer | _(optional)_ Number of repositories per markdown page when pagination is enabled (default: 500) |
|
|
303
|
+
| --llm-summary<br/> --no-llm-summary | Boolean | _(optional)_ Generate an AI usage summary in markdown output when an LLM API key is present (default: enabled) |
|
|
304
|
+
| --llm-model | String | _(optional)_ LiteLLM model to use for the summary. If not set, a lightweight model is selected based on the detected API key provider |
|
|
305
|
+
| --llm-max-repos | Integer | _(optional)_ Max dependent repos included in the summary prompt payload (default: 500) |
|
|
306
|
+
| --llm-max-words | Integer | _(optional)_ Max words for the generated summary (default: 300) |
|
|
307
|
+
| --llm-timeout | Float | _(optional)_ Timeout (seconds) for the summary LLM call (default: 120) |
|
|
308
|
+
| -v<br/> --version | Boolean | _(optional)_ Displays version of github-dependents-info |
|
|
309
|
+
| --verbose | Boolean | _(optional)_ Verbose output |
|
|
282
310
|
|
|
283
311
|
Badge tags example (the tool replaces everything between the markers):
|
|
284
312
|
|
|
@@ -328,6 +356,18 @@ _________________
|
|
|
328
356
|
|
|
329
357
|
github-dependents-info --repo nvuillam/npm-groovy-lint --markdownfile ./docs/package-usage.md --page-size 250 --mergepackages
|
|
330
358
|
|
|
359
|
+
- Disable AI usage summary generation
|
|
360
|
+
|
|
361
|
+
github-dependents-info --repo nvuillam/npm-groovy-lint --markdownfile ./docs/package-usage.md --no-llm-summary
|
|
362
|
+
|
|
363
|
+
- Force a specific LiteLLM model for the summary
|
|
364
|
+
|
|
365
|
+
github-dependents-info --repo nvuillam/npm-groovy-lint --markdownfile ./docs/package-usage.md --llm-model gpt-4o-mini
|
|
366
|
+
|
|
367
|
+
- Generate markdown with AI summary using `OPENAI_API_KEY`
|
|
368
|
+
|
|
369
|
+
GEMINI_API_KEY=YOUR_KEY github-dependents-info --repo nvuillam/npm-groovy-lint --markdownfile ./docs/package-usage.md --llm-model gemini-3-flash-preview
|
|
370
|
+
|
|
331
371
|
## Use as GitHub Action
|
|
332
372
|
|
|
333
373
|
Allow GitHub Actions to create Pull Requests in **Settings > Actions > General**
|
|
@@ -389,8 +429,16 @@ jobs:
|
|
|
389
429
|
# badgemarkdownfile: README.md
|
|
390
430
|
# sort: stars
|
|
391
431
|
# minstars: "0"
|
|
432
|
+
# llm-summary: "true" # set to "false" to disable AI usage summary
|
|
433
|
+
# llm-model: "" # optional: override LiteLLM model (example: gpt-4o-mini, gemini-3-flash-preview)
|
|
434
|
+
# llm-max-repos: "500" # optional: cap repos sent to the summary prompt
|
|
435
|
+
# llm-max-words: "250" # optional: cap summary length
|
|
436
|
+
# llm-timeout: "120" # optional: timeout (seconds) for the LLM call
|
|
392
437
|
env:
|
|
393
438
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
439
|
+
# To enable the AI usage summary, provide one of the supported provider API keys, for example:
|
|
440
|
+
# OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
441
|
+
# GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
394
442
|
|
|
395
443
|
# Workaround for git issues
|
|
396
444
|
- name: Prepare commit
|
|
@@ -33,6 +33,7 @@ This package uses GitHub HTML to collect dependents information and can:
|
|
|
33
33
|
- Output as text
|
|
34
34
|
- Output as json (including shields.io markdown badges)
|
|
35
35
|
- Generate summary markdown file
|
|
36
|
+
- Optionally add an AI-generated usage summary (via `litellm`) when an LLM API key is present
|
|
36
37
|
- Update existing markdown by inserting **Used by** badge within tags
|
|
37
38
|
- `<!-- gh-dependents-info-used-by-start -->
|
|
38
39
|
[](https://github.com/nvuillam/github-dependents-info/blob/main/docs/github-dependents-info.md)<!-- gh-dependents-info-used-by-end -->`
|
|
@@ -41,6 +42,24 @@ This package uses GitHub HTML to collect dependents information and can:
|
|
|
41
42
|
- Keep huge ecosystems manageable with pagination controls (`--max-scraped-pages`, `--pagination/--no-pagination`, `--page-size`)
|
|
42
43
|
- Fetch dependents faster thanks to asynchronous `httpx` requests and parallelized page scraping
|
|
43
44
|
|
|
45
|
+
### AI usage summary (optional)
|
|
46
|
+
|
|
47
|
+
If an LLM API key is detected in the environment (for example `OPENAI_API_KEY`), the tool will call a lightweight model (via `litellm`) to generate a short **usage summary** and include it in the generated markdown.
|
|
48
|
+
|
|
49
|
+
- Supported provider env vars (most common):
|
|
50
|
+
- OpenAI: `OPENAI_API_KEY`
|
|
51
|
+
- Azure OpenAI: `AZURE_OPENAI_API_KEY`
|
|
52
|
+
- Anthropic: `ANTHROPIC_API_KEY`
|
|
53
|
+
- Google Gemini: `GEMINI_API_KEY` (or `GOOGLE_API_KEY`)
|
|
54
|
+
- Mistral: `MISTRAL_API_KEY`
|
|
55
|
+
- Cohere: `COHERE_API_KEY`
|
|
56
|
+
- Groq: `GROQ_API_KEY`
|
|
57
|
+
|
|
58
|
+
- Disable with `--no-llm-summary` (or env var `GITHUB_DEPENDENTS_INFO_LLM_SUMMARY=false`)
|
|
59
|
+
- Override model with `--llm-model` (or env var `GITHUB_DEPENDENTS_INFO_LLM_MODEL` / `LITELLM_MODEL`)
|
|
60
|
+
- Adjust max summary length with `--llm-max-words` (or env var `GITHUB_DEPENDENTS_INFO_LLM_MAX_WORDS`)
|
|
61
|
+
- The summary is cached in `--csvdirectory` (file `llm_summary_<repo>.json`) and reused on subsequent runs
|
|
62
|
+
|
|
44
63
|
|
|
45
64
|
Badges example
|
|
46
65
|
|
|
@@ -52,6 +71,8 @@ Badges example
|
|
|
52
71
|
<details>
|
|
53
72
|
<summary>JSON output</summary>
|
|
54
73
|
|
|
74
|
+
Note: when AI usage summary is enabled and an LLM API key is present, the JSON output also includes `llm_summary`.
|
|
75
|
+
|
|
55
76
|
```json
|
|
56
77
|
{
|
|
57
78
|
"all_public_dependent_repos": [
|
|
@@ -236,22 +257,27 @@ _________________
|
|
|
236
257
|
github-dependents-info [OPTIONS]
|
|
237
258
|
```
|
|
238
259
|
|
|
239
|
-
| Parameter
|
|
240
|
-
|
|
241
|
-
| --repo
|
|
242
|
-
| -b<br/> --badgemarkdownfile
|
|
243
|
-
| -s<br/> --sort
|
|
244
|
-
| -x<br/> --minstars
|
|
245
|
-
| -m<br/> --markdownfile
|
|
246
|
-
| -d<br/> --docurl
|
|
247
|
-
| -p<br/> --mergepackages
|
|
248
|
-
| -j<br/> --json
|
|
249
|
-
| -u<br/> --owner
|
|
250
|
-
| -n<br/> --max-scraped-pages
|
|
251
|
-
| --pagination<br/> --no-pagination
|
|
252
|
-
| --page-size
|
|
253
|
-
| -
|
|
254
|
-
| --
|
|
260
|
+
| Parameter | Type | Description |
|
|
261
|
+
|-------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
262
|
+
| --repo | String | Repository. Example: `oxsecurity/megalinter` |
|
|
263
|
+
| -b<br/> --badgemarkdownfile | String | _(optional)_ Path to markdown file where to insert/update **Used by** badge <br/> (must contain tags `<!-- gh-dependents-info-used-by-start -->` … `<!-- gh-dependents-info-used-by-end -->`) |
|
|
264
|
+
| -s<br/> --sort | String | _(optional)_ Sort order: name (default) or stars |
|
|
265
|
+
| -x<br/> --minstars | String | _(optional)_ If set, filters repositories to keep only those with more than X stars |
|
|
266
|
+
| -m<br/> --markdownfile | String | _(optional)_ Output markdown file file |
|
|
267
|
+
| -d<br/> --docurl | String | _(optional)_ Hyperlink to use when clicking on badge markdown file badge. (Default: link to markdown file) |
|
|
268
|
+
| -p<br/> --mergepackages | String | _(optional)_ In case of multiple packages, merge their stats in a single one in markdown and json output |
|
|
269
|
+
| -j<br/> --json | String | _(optional)_ Output in json format |
|
|
270
|
+
| -u<br/> --owner | String | _(optional)_ If set, filters repositories to keep only those owned by the specified user/organization |
|
|
271
|
+
| -n<br/> --max-scraped-pages | Integer | _(optional)_ Maximum number of GitHub pages to scrape per package (0 uses all available pages) |
|
|
272
|
+
| --pagination<br/> --no-pagination | Boolean | _(optional)_ Enable (default) or disable pagination when writing markdown output |
|
|
273
|
+
| --page-size | Integer | _(optional)_ Number of repositories per markdown page when pagination is enabled (default: 500) |
|
|
274
|
+
| --llm-summary<br/> --no-llm-summary | Boolean | _(optional)_ Generate an AI usage summary in markdown output when an LLM API key is present (default: enabled) |
|
|
275
|
+
| --llm-model | String | _(optional)_ LiteLLM model to use for the summary. If not set, a lightweight model is selected based on the detected API key provider |
|
|
276
|
+
| --llm-max-repos | Integer | _(optional)_ Max dependent repos included in the summary prompt payload (default: 500) |
|
|
277
|
+
| --llm-max-words | Integer | _(optional)_ Max words for the generated summary (default: 300) |
|
|
278
|
+
| --llm-timeout | Float | _(optional)_ Timeout (seconds) for the summary LLM call (default: 120) |
|
|
279
|
+
| -v<br/> --version | Boolean | _(optional)_ Displays version of github-dependents-info |
|
|
280
|
+
| --verbose | Boolean | _(optional)_ Verbose output |
|
|
255
281
|
|
|
256
282
|
Badge tags example (the tool replaces everything between the markers):
|
|
257
283
|
|
|
@@ -301,6 +327,18 @@ _________________
|
|
|
301
327
|
|
|
302
328
|
github-dependents-info --repo nvuillam/npm-groovy-lint --markdownfile ./docs/package-usage.md --page-size 250 --mergepackages
|
|
303
329
|
|
|
330
|
+
- Disable AI usage summary generation
|
|
331
|
+
|
|
332
|
+
github-dependents-info --repo nvuillam/npm-groovy-lint --markdownfile ./docs/package-usage.md --no-llm-summary
|
|
333
|
+
|
|
334
|
+
- Force a specific LiteLLM model for the summary
|
|
335
|
+
|
|
336
|
+
github-dependents-info --repo nvuillam/npm-groovy-lint --markdownfile ./docs/package-usage.md --llm-model gpt-4o-mini
|
|
337
|
+
|
|
338
|
+
- Generate markdown with AI summary using `OPENAI_API_KEY`
|
|
339
|
+
|
|
340
|
+
GEMINI_API_KEY=YOUR_KEY github-dependents-info --repo nvuillam/npm-groovy-lint --markdownfile ./docs/package-usage.md --llm-model gemini-3-flash-preview
|
|
341
|
+
|
|
304
342
|
## Use as GitHub Action
|
|
305
343
|
|
|
306
344
|
Allow GitHub Actions to create Pull Requests in **Settings > Actions > General**
|
|
@@ -362,8 +400,16 @@ jobs:
|
|
|
362
400
|
# badgemarkdownfile: README.md
|
|
363
401
|
# sort: stars
|
|
364
402
|
# minstars: "0"
|
|
403
|
+
# llm-summary: "true" # set to "false" to disable AI usage summary
|
|
404
|
+
# llm-model: "" # optional: override LiteLLM model (example: gpt-4o-mini, gemini-3-flash-preview)
|
|
405
|
+
# llm-max-repos: "500" # optional: cap repos sent to the summary prompt
|
|
406
|
+
# llm-max-words: "250" # optional: cap summary length
|
|
407
|
+
# llm-timeout: "120" # optional: timeout (seconds) for the LLM call
|
|
365
408
|
env:
|
|
366
409
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
410
|
+
# To enable the AI usage summary, provide one of the supported provider API keys, for example:
|
|
411
|
+
# OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
412
|
+
# GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
367
413
|
|
|
368
414
|
# Workaround for git issues
|
|
369
415
|
- name: Prepare commit
|
{github_dependents_info-2.0.2 → github_dependents_info-3.0.0}/github_dependents_info/__main__.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from typing import Annotated
|
|
2
3
|
|
|
3
4
|
import typer
|
|
4
5
|
from github_dependents_info import version
|
|
@@ -86,6 +87,39 @@ def main(
|
|
|
86
87
|
True, "--pagination/--no-pagination", help="Enable pagination to split results into multiple files"
|
|
87
88
|
),
|
|
88
89
|
page_size: int = typer.Option(500, "--page-size", help="Number of results per page when pagination is enabled"),
|
|
90
|
+
llm_summary: Annotated[
|
|
91
|
+
bool | None,
|
|
92
|
+
typer.Option(
|
|
93
|
+
"--llm-summary/--no-llm-summary",
|
|
94
|
+
help=(
|
|
95
|
+
"Generate an AI usage summary in the markdown output when an LLM API key is present "
|
|
96
|
+
"(default: enabled)."
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
] = None,
|
|
100
|
+
llm_model: str = typer.Option(
|
|
101
|
+
None,
|
|
102
|
+
"--llm-model",
|
|
103
|
+
help=(
|
|
104
|
+
"LiteLLM model to use for summary generation. If not set, a lightweight model is selected "
|
|
105
|
+
"based on the API key provider."
|
|
106
|
+
),
|
|
107
|
+
),
|
|
108
|
+
llm_max_repos: int = typer.Option(
|
|
109
|
+
None,
|
|
110
|
+
"--llm-max-repos",
|
|
111
|
+
help="Max dependent repos to include in the LLM prompt payload (default: 80).",
|
|
112
|
+
),
|
|
113
|
+
llm_max_words: int = typer.Option(
|
|
114
|
+
None,
|
|
115
|
+
"--llm-max-words",
|
|
116
|
+
help="Max words for the generated summary (default: 300).",
|
|
117
|
+
),
|
|
118
|
+
llm_timeout: float = typer.Option(
|
|
119
|
+
None,
|
|
120
|
+
"--llm-timeout",
|
|
121
|
+
help="Timeout (seconds) for the LLM call (default: 120).",
|
|
122
|
+
),
|
|
89
123
|
) -> None:
|
|
90
124
|
# Init logger
|
|
91
125
|
if verbose is True:
|
|
@@ -104,26 +138,38 @@ def main(
|
|
|
104
138
|
if min_stars is None:
|
|
105
139
|
min_stars = 0
|
|
106
140
|
# Create GithubDependentsInfo instance
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
141
|
+
gh_options = {
|
|
142
|
+
"outputrepo": outputrepo,
|
|
143
|
+
"debug": verbose,
|
|
144
|
+
"overwrite_progress": overwrite,
|
|
145
|
+
"sort_key": sort_key,
|
|
146
|
+
"min_stars": min_stars,
|
|
147
|
+
"json_output": json_output,
|
|
148
|
+
"csv_directory": csv_directory,
|
|
149
|
+
"badge_markdown_file": badge_markdown_file,
|
|
150
|
+
"doc_url": doc_url,
|
|
151
|
+
"markdown_file": markdown_file,
|
|
152
|
+
"badge_color": badge_color,
|
|
153
|
+
"merge_packages": merge_packages,
|
|
154
|
+
"owner": owner,
|
|
155
|
+
"time_delay": time_delay,
|
|
156
|
+
"max_scraped_pages": max_scraped_pages,
|
|
157
|
+
"pagination": pagination,
|
|
158
|
+
"page_size": page_size,
|
|
159
|
+
}
|
|
160
|
+
# Only pass LLM options if explicitly provided, to keep env-based defaults working
|
|
161
|
+
if llm_summary is not None:
|
|
162
|
+
gh_options["llm_summary"] = llm_summary
|
|
163
|
+
if llm_model is not None:
|
|
164
|
+
gh_options["llm_model"] = llm_model
|
|
165
|
+
if llm_max_repos is not None:
|
|
166
|
+
gh_options["llm_max_repos"] = llm_max_repos
|
|
167
|
+
if llm_max_words is not None:
|
|
168
|
+
gh_options["llm_max_words"] = llm_max_words
|
|
169
|
+
if llm_timeout is not None:
|
|
170
|
+
gh_options["llm_timeout"] = llm_timeout
|
|
171
|
+
|
|
172
|
+
gh_deps_info = GithubDependentsInfo(repo, **gh_options)
|
|
127
173
|
# Collect data
|
|
128
174
|
gh_deps_info.collect()
|
|
129
175
|
# Write output markdown
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import inspect
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
4
5
|
import math
|
|
5
6
|
import os
|
|
6
7
|
import re
|
|
8
|
+
from collections import Counter
|
|
7
9
|
from pathlib import Path
|
|
8
10
|
|
|
9
11
|
import httpx
|
|
@@ -55,6 +57,22 @@ class GithubDependentsInfo:
|
|
|
55
57
|
self.http_retry_backoff = options.get("http_retry_backoff", 2.0)
|
|
56
58
|
self.http_retry_max_delay = options.get("http_retry_max_delay", 60.0)
|
|
57
59
|
|
|
60
|
+
# LLM summary options (used only when an API key is present)
|
|
61
|
+
llm_summary_env = os.getenv("GITHUB_DEPENDENTS_INFO_LLM_SUMMARY")
|
|
62
|
+
llm_summary_default = True
|
|
63
|
+
if llm_summary_env is not None:
|
|
64
|
+
llm_summary_default = llm_summary_env.strip().lower() not in {"0", "false", "no", "off"}
|
|
65
|
+
self.llm_summary_enabled = options.get("llm_summary", llm_summary_default)
|
|
66
|
+
self.llm_model = (
|
|
67
|
+
options.get("llm_model") or os.getenv("GITHUB_DEPENDENTS_INFO_LLM_MODEL") or os.getenv("LITELLM_MODEL")
|
|
68
|
+
)
|
|
69
|
+
self.llm_max_repos = int(options.get("llm_max_repos", os.getenv("GITHUB_DEPENDENTS_INFO_LLM_MAX_REPOS", 500)))
|
|
70
|
+
self.llm_max_words = int(options.get("llm_max_words", os.getenv("GITHUB_DEPENDENTS_INFO_LLM_MAX_WORDS", 300)))
|
|
71
|
+
self.llm_timeout = float(options.get("llm_timeout", os.getenv("GITHUB_DEPENDENTS_INFO_LLM_TIMEOUT", 120)))
|
|
72
|
+
self.llm_model_used: str | None = None
|
|
73
|
+
self.llm_summary: str | None = None
|
|
74
|
+
self.llm_summary_error: str | None = None
|
|
75
|
+
|
|
58
76
|
def collect(self):
|
|
59
77
|
"""Main entry point - synchronous wrapper for async collection."""
|
|
60
78
|
return asyncio.run(self.collect_async())
|
|
@@ -164,9 +182,203 @@ class GithubDependentsInfo:
|
|
|
164
182
|
self.badges["public"] = self.build_badge("Used%20by%20(public)", self.total_public_sum)
|
|
165
183
|
self.badges["private"] = self.build_badge("Used%20by%20(private)", self.total_private_sum)
|
|
166
184
|
self.badges["stars"] = self.build_badge("Used%20by%20(stars)", self.total_stars_sum)
|
|
185
|
+
|
|
186
|
+
# Optional: generate an LLM summary if an API key is present (and reuse cached summary when available)
|
|
187
|
+
await self.maybe_generate_llm_summary()
|
|
167
188
|
# Build final result
|
|
168
189
|
return self.build_result()
|
|
169
190
|
|
|
191
|
+
def _detect_llm_provider(self) -> dict | None:
|
|
192
|
+
"""Detect which provider API key is present and propose a lightweight default model."""
|
|
193
|
+
candidates: list[tuple[str, str, str]] = [
|
|
194
|
+
("openai", "OPENAI_API_KEY", "gpt-5-mini"),
|
|
195
|
+
("azure_openai", "AZURE_OPENAI_API_KEY", "gpt-5-mini"),
|
|
196
|
+
("anthropic", "ANTHROPIC_API_KEY", "claude-3-5-haiku-latest"),
|
|
197
|
+
("gemini", "GEMINI_API_KEY", "gemini-3-flash-preview"),
|
|
198
|
+
("google", "GOOGLE_API_KEY", "gemini-3-flash-preview"),
|
|
199
|
+
("mistral", "MISTRAL_API_KEY", "mistral-small-latest"),
|
|
200
|
+
("cohere", "COHERE_API_KEY", "command-r"),
|
|
201
|
+
("groq", "GROQ_API_KEY", "groq/llama-3.1-8b-instant"),
|
|
202
|
+
]
|
|
203
|
+
for provider, env_var, default_model in candidates:
|
|
204
|
+
if os.getenv(env_var):
|
|
205
|
+
return {"provider": provider, "env_var": env_var, "default_model": default_model}
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
def _llm_api_key_present(self) -> bool:
|
|
209
|
+
return self._detect_llm_provider() is not None
|
|
210
|
+
|
|
211
|
+
def _llm_summary_cache_path(self) -> Path | None:
|
|
212
|
+
if self.csv_directory is None:
|
|
213
|
+
return None
|
|
214
|
+
return self.csv_directory / f"llm_summary_{self.repo}.json".replace("/", "-")
|
|
215
|
+
|
|
216
|
+
def load_llm_summary(self) -> bool:
|
|
217
|
+
"""Load cached LLM summary from progress directory if present."""
|
|
218
|
+
cache_path = self._llm_summary_cache_path()
|
|
219
|
+
if cache_path is None or not cache_path.exists():
|
|
220
|
+
return False
|
|
221
|
+
try:
|
|
222
|
+
with open(cache_path, encoding="utf-8") as f:
|
|
223
|
+
payload = json.load(f)
|
|
224
|
+
summary = (payload.get("summary") or "").strip()
|
|
225
|
+
if summary:
|
|
226
|
+
self.llm_summary = summary
|
|
227
|
+
return True
|
|
228
|
+
except Exception as exc:
|
|
229
|
+
logging.warning("Failed to load cached LLM summary: %s", exc)
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
def save_llm_summary(self) -> None:
|
|
233
|
+
"""Persist LLM summary into progress directory if enabled."""
|
|
234
|
+
cache_path = self._llm_summary_cache_path()
|
|
235
|
+
if cache_path is None:
|
|
236
|
+
return
|
|
237
|
+
if not self.llm_summary:
|
|
238
|
+
return
|
|
239
|
+
try:
|
|
240
|
+
self.csv_directory.mkdir(parents=False, exist_ok=True)
|
|
241
|
+
payload = {
|
|
242
|
+
"repo": self.repo,
|
|
243
|
+
"model": self.llm_model_used or self.llm_model,
|
|
244
|
+
"summary": self.llm_summary,
|
|
245
|
+
}
|
|
246
|
+
with open(cache_path, "w", encoding="utf-8") as f:
|
|
247
|
+
json.dump(payload, f, indent=2)
|
|
248
|
+
except Exception as exc:
|
|
249
|
+
if self.debug:
|
|
250
|
+
logging.warning("Failed to save cached LLM summary: %s", exc)
|
|
251
|
+
|
|
252
|
+
def _prepare_llm_summary_payload(self) -> dict:
|
|
253
|
+
"""Prepare a compact data payload for the LLM prompt."""
|
|
254
|
+
repos_sorted = sorted(self.all_public_dependent_repos, key=lambda r: r.get("stars", 0), reverse=True)
|
|
255
|
+
repos_top = repos_sorted[: max(0, self.llm_max_repos)]
|
|
256
|
+
|
|
257
|
+
owners = [r.get("owner") for r in self.all_public_dependent_repos if r.get("owner")]
|
|
258
|
+
owners_counter = Counter(owners)
|
|
259
|
+
owners_top = owners_counter.most_common(25)
|
|
260
|
+
|
|
261
|
+
owner_stars: dict[str, int] = {}
|
|
262
|
+
for r in self.all_public_dependent_repos:
|
|
263
|
+
owner = r.get("owner")
|
|
264
|
+
if not owner:
|
|
265
|
+
continue
|
|
266
|
+
owner_stars[owner] = owner_stars.get(owner, 0) + int(r.get("stars", 0) or 0)
|
|
267
|
+
owners_top_by_stars = sorted(owner_stars.items(), key=lambda kv: kv[1], reverse=True)[:25]
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
"source_repo": self.repo,
|
|
271
|
+
"packages": [p.get("name") for p in self.packages] if self.packages else [self.repo],
|
|
272
|
+
"totals": {
|
|
273
|
+
"dependents_total": self.total_sum,
|
|
274
|
+
"dependents_public": self.total_public_sum,
|
|
275
|
+
"dependents_private": self.total_private_sum,
|
|
276
|
+
"public_dependents_total_stars": self.total_stars_sum,
|
|
277
|
+
},
|
|
278
|
+
"top_dependents_by_stars": [
|
|
279
|
+
{"name": r.get("name"), "stars": int(r.get("stars", 0) or 0)} for r in repos_top
|
|
280
|
+
],
|
|
281
|
+
"top_owners_by_dependent_count": [{"owner": o, "count": c} for (o, c) in owners_top],
|
|
282
|
+
"top_owners_by_total_stars": [{"owner": o, "stars": s} for (o, s) in owners_top_by_stars],
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async def maybe_generate_llm_summary(self) -> None:
|
|
286
|
+
"""Generate an LLM-based summary if possible; otherwise do nothing."""
|
|
287
|
+
if not self.llm_summary_enabled:
|
|
288
|
+
return
|
|
289
|
+
if self.llm_summary:
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
# Reuse cached summary when resuming from CSV progress
|
|
293
|
+
if self.load_llm_summary():
|
|
294
|
+
return
|
|
295
|
+
|
|
296
|
+
provider_info = self._detect_llm_provider()
|
|
297
|
+
if provider_info is None:
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
# Default model if none was provided
|
|
301
|
+
model = self.llm_model or provider_info.get("default_model") or "gpt-4o-mini"
|
|
302
|
+
self.llm_model_used = model
|
|
303
|
+
|
|
304
|
+
# Add provider prefix if missing (for LiteLLM compatibility)
|
|
305
|
+
if "/" not in model:
|
|
306
|
+
model = f"{provider_info['provider']}/{model}"
|
|
307
|
+
|
|
308
|
+
payload = self._prepare_llm_summary_payload()
|
|
309
|
+
system_prompt = (
|
|
310
|
+
"You summarize GitHub 'Used by' dependents for a package. "
|
|
311
|
+
"Write concise, factual Markdown. Do not invent data. "
|
|
312
|
+
"Use only the provided JSON data. "
|
|
313
|
+
"Do not include headings (no H1/H2/H3/H4). "
|
|
314
|
+
"Add blank lines before bullet points for readability. "
|
|
315
|
+
"Avoid any mention of pagination/navigation words like 'Page', 'Next', or 'Previous'."
|
|
316
|
+
)
|
|
317
|
+
user_prompt = (
|
|
318
|
+
"Given this JSON data, write a short summary that highlights: "
|
|
319
|
+
"(1) popular companies/organizations using the package, "
|
|
320
|
+
"(2) popular tools/ecosystems (infer from repo names), "
|
|
321
|
+
"(3) notable high-star dependent repositories. "
|
|
322
|
+
"Do not repeat data between sections (1), (2), and (3). "
|
|
323
|
+
"Format as Markdown with short sentences and bullet points. "
|
|
324
|
+
"Write in bold the names of companies/organizations and tools/ecosystems. "
|
|
325
|
+
f"Add blank lines before bullet points for readability. Keep under {self.llm_max_words} words.\n\n"
|
|
326
|
+
+ json.dumps(payload, ensure_ascii=False)
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
from litellm import acompletion # type: ignore
|
|
331
|
+
|
|
332
|
+
response = await acompletion(
|
|
333
|
+
model=model,
|
|
334
|
+
messages=[
|
|
335
|
+
{"role": "system", "content": system_prompt},
|
|
336
|
+
{"role": "user", "content": user_prompt},
|
|
337
|
+
],
|
|
338
|
+
temperature=0.2,
|
|
339
|
+
timeout=self.llm_timeout,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
content = None
|
|
343
|
+
if hasattr(response, "choices") and response.choices:
|
|
344
|
+
message = response.choices[0].message
|
|
345
|
+
content = getattr(message, "content", None)
|
|
346
|
+
if content:
|
|
347
|
+
self.llm_summary = str(content).strip()
|
|
348
|
+
self.save_llm_summary()
|
|
349
|
+
except Exception as exc:
|
|
350
|
+
self.llm_summary_error = str(exc)
|
|
351
|
+
logging.warning("Failed to generate LLM summary: %s", exc)
|
|
352
|
+
finally:
|
|
353
|
+
# LiteLLM can keep async clients around. Ensure they're closed before
|
|
354
|
+
# asyncio.run() tears down the event loop to avoid:
|
|
355
|
+
# RuntimeWarning: coroutine 'close_litellm_async_clients' was never awaited
|
|
356
|
+
await self._maybe_close_litellm_async_clients()
|
|
357
|
+
|
|
358
|
+
async def _maybe_close_litellm_async_clients(self) -> None:
|
|
359
|
+
"""Best-effort cleanup for LiteLLM async clients."""
|
|
360
|
+
try:
|
|
361
|
+
import litellm # type: ignore
|
|
362
|
+
|
|
363
|
+
close_fn = getattr(litellm, "close_litellm_async_clients", None)
|
|
364
|
+
if close_fn is None:
|
|
365
|
+
utils = getattr(litellm, "utils", None)
|
|
366
|
+
close_fn = getattr(utils, "close_litellm_async_clients", None) if utils else None
|
|
367
|
+
|
|
368
|
+
if close_fn is None:
|
|
369
|
+
return
|
|
370
|
+
|
|
371
|
+
if inspect.iscoroutinefunction(close_fn):
|
|
372
|
+
await close_fn()
|
|
373
|
+
return
|
|
374
|
+
|
|
375
|
+
result = close_fn()
|
|
376
|
+
if inspect.isawaitable(result):
|
|
377
|
+
await result
|
|
378
|
+
except Exception as exc:
|
|
379
|
+
if self.debug:
|
|
380
|
+
logging.debug("LiteLLM async client cleanup skipped: %s", exc)
|
|
381
|
+
|
|
170
382
|
def _extract_owner_repo(self, dependent_row):
|
|
171
383
|
repo_anchor = dependent_row.find("a", {"data-hovercard-type": "repository"})
|
|
172
384
|
if repo_anchor is None:
|
|
@@ -296,6 +508,8 @@ class GithubDependentsInfo:
|
|
|
296
508
|
self.total_stars_sum += (
|
|
297
509
|
package["public_dependent_stars"] if package["public_dependent_stars"] else 0
|
|
298
510
|
)
|
|
511
|
+
# Load cached summary if present
|
|
512
|
+
self.load_llm_summary()
|
|
299
513
|
return len(self.packages) > 0
|
|
300
514
|
|
|
301
515
|
# Build result
|
|
@@ -307,6 +521,7 @@ class GithubDependentsInfo:
|
|
|
307
521
|
"private_dependents_number": self.total_private_sum,
|
|
308
522
|
"public_dependents_stars": self.total_stars_sum,
|
|
309
523
|
"badges": self.badges,
|
|
524
|
+
"llm_summary": self.llm_summary,
|
|
310
525
|
}
|
|
311
526
|
if self.merge_packages is False:
|
|
312
527
|
self.result["packages"] = (self.packages,)
|
|
@@ -317,6 +532,10 @@ class GithubDependentsInfo:
|
|
|
317
532
|
if self.json_output is True:
|
|
318
533
|
print(json.dumps(self.result, indent=4))
|
|
319
534
|
else:
|
|
535
|
+
if self.llm_summary_enabled and self.llm_summary:
|
|
536
|
+
print("LLM Summary:\n")
|
|
537
|
+
print(self.llm_summary)
|
|
538
|
+
print("\n")
|
|
320
539
|
print("Total: " + str(self.total_sum))
|
|
321
540
|
print("Public: " + str(self.total_public_sum) + " (" + str(self.total_stars_sum) + " stars)")
|
|
322
541
|
print("Private: " + str(self.total_private_sum))
|
|
@@ -386,6 +605,10 @@ class GithubDependentsInfo:
|
|
|
386
605
|
# Summary table
|
|
387
606
|
self._append_summary_table(md_lines)
|
|
388
607
|
|
|
608
|
+
# Optional LLM summary
|
|
609
|
+
if self.llm_summary:
|
|
610
|
+
md_lines += ["## Summary", "", self.llm_summary.strip(), ""]
|
|
611
|
+
|
|
389
612
|
# Single dependents list
|
|
390
613
|
if self.merge_packages is True:
|
|
391
614
|
md_lines += [
|
|
@@ -478,6 +701,10 @@ class GithubDependentsInfo:
|
|
|
478
701
|
if page_num == 1:
|
|
479
702
|
self._append_summary_table(md_lines)
|
|
480
703
|
|
|
704
|
+
# Optional LLM summary (only on first page)
|
|
705
|
+
if self.llm_summary:
|
|
706
|
+
md_lines += ["## Summary", "", self.llm_summary.strip(), ""]
|
|
707
|
+
|
|
481
708
|
# Calculate start and end indices for this page
|
|
482
709
|
start_idx = (page_num - 1) * self.page_size
|
|
483
710
|
end_idx = start_idx + self.page_size
|
|
@@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "github-dependents-info"
|
|
8
|
-
version = "
|
|
8
|
+
version = "3.0.0"
|
|
9
9
|
description = "Collect information about dependencies between a github repo and other repositories. Results available in JSON, markdown and badges."
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
license = "MIT"
|
|
@@ -40,12 +40,14 @@ Repository = "https://github.com/nvuillam/github-dependents-info"
|
|
|
40
40
|
python = ">=3.10,<4.0"
|
|
41
41
|
|
|
42
42
|
click = ">=8.3.1,<8.4"
|
|
43
|
-
typer = {extras = ["
|
|
43
|
+
typer = {extras = ["standard"], version = ">=0.19,<0.20"}
|
|
44
|
+
typer-slim = ">=0.19,<0.20"
|
|
44
45
|
rich = ">=14.2,<14.3"
|
|
45
46
|
beautifulsoup4 = "4.14.3"
|
|
46
47
|
pandas = ">=2.3.3,<3.0"
|
|
47
48
|
httpx = "^0.28.1"
|
|
48
49
|
idna = ">=3.11"
|
|
50
|
+
litellm = ">=1.60.0,<2.0"
|
|
49
51
|
|
|
50
52
|
[tool.poetry.group.dev.dependencies]
|
|
51
53
|
bandit = "^1.7.5"
|
|
@@ -59,7 +61,7 @@ pydocstyle = "^6.3.0"
|
|
|
59
61
|
pylint = "^4.0.0"
|
|
60
62
|
pytest = "^9.0.0"
|
|
61
63
|
pyupgrade = "^3.4.0"
|
|
62
|
-
safety = "^3.0
|
|
64
|
+
safety = "^3.7.0"
|
|
63
65
|
coverage = "^7.3.4"
|
|
64
66
|
coverage-badge = "^1.1.0"
|
|
65
67
|
cryptography = ">=44.0.1"
|
|
File without changes
|
{github_dependents_info-2.0.2 → github_dependents_info-3.0.0}/github_dependents_info/__init__.py
RENAMED
|
File without changes
|