github-star-lists 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 +55 -10
- package/config/star-lists.json +44 -86
- package/package.json +10 -1
- package/presets/ai-robotics.json +118 -0
- package/presets/ai.json +40 -0
- package/presets/general.json +76 -0
- package/presets/robotics.json +40 -0
- package/presets/webdev.json +40 -0
- package/scripts/organize-stars.mjs +334 -43
package/README.md
CHANGED
|
@@ -36,13 +36,7 @@ The token needs the classic `user` scope. Add `repo` if your starred repositorie
|
|
|
36
36
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npx github-star-lists init
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Use the guided flow:
|
|
39
|
+
Most users should start with the guided flow:
|
|
46
40
|
|
|
47
41
|
```bash
|
|
48
42
|
npx github-star-lists wizard
|
|
@@ -57,7 +51,13 @@ The wizard asks you to choose:
|
|
|
57
51
|
- whether to sync existing list visibility
|
|
58
52
|
- whether to scan all stars or only the newest N
|
|
59
53
|
|
|
60
|
-
|
|
54
|
+
To create a local config first, use the general preset:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx github-star-lists init
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Then preview. This does not write to GitHub:
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
63
|
npx github-star-lists
|
|
@@ -69,7 +69,52 @@ Apply changes:
|
|
|
69
69
|
npx github-star-lists --apply
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
## Setup Your Config
|
|
73
|
+
|
|
74
|
+
`init` writes `star-lists.config.json` in the current directory. The default preset is `general`.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx github-star-lists init
|
|
78
|
+
npx github-star-lists init --preset general
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Available presets:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npx github-star-lists init --list-presets
|
|
85
|
+
npx github-star-lists init --preset ai
|
|
86
|
+
npx github-star-lists init --preset robotics
|
|
87
|
+
npx github-star-lists init --preset webdev
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If you already use GitHub Star Lists, generate config from those lists:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx github-star-lists init --from-existing-lists
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This copies list names, descriptions, and visibility. Keywords are left empty, while topics are lightly suggested from list names.
|
|
97
|
+
|
|
98
|
+
To generate a non-AI config suggestion from your starred repositories:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx github-star-lists suggest-config
|
|
102
|
+
npx github-star-lists suggest-config --limit=200
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This scans starred repo topics, languages, and description keywords, then writes:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
out/suggested-config.json
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
After reviewing that file, run with it:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npx github-star-lists --config out/suggested-config.json
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The flags below are useful for automation or repeatable workflows.
|
|
73
118
|
|
|
74
119
|
## Visibility
|
|
75
120
|
|
|
@@ -141,7 +186,7 @@ By default the CLI uses the first existing file from:
|
|
|
141
186
|
|
|
142
187
|
1. `star-lists.config.json`
|
|
143
188
|
2. `config/star-lists.json`
|
|
144
|
-
3. the packaged
|
|
189
|
+
3. the packaged `general` preset
|
|
145
190
|
|
|
146
191
|
Use a specific config:
|
|
147
192
|
|
package/config/star-lists.json
CHANGED
|
@@ -7,112 +7,70 @@
|
|
|
7
7
|
},
|
|
8
8
|
"lists": [
|
|
9
9
|
{
|
|
10
|
-
"name": "
|
|
11
|
-
"description": "AI
|
|
12
|
-
"keywords": [
|
|
13
|
-
|
|
14
|
-
"copilot", "cursor", "claude", "codex", "mcp", "multi-agent", "swe", "devin",
|
|
15
|
-
"software engineering agent", "llm app"
|
|
16
|
-
],
|
|
17
|
-
"topics": ["agents", "ai-agent", "mcp", "code-generation", "coding-agent", "developer-tools"]
|
|
10
|
+
"name": "AI / LLM",
|
|
11
|
+
"description": "AI, large language models, agents, RAG, inference, and model tooling.",
|
|
12
|
+
"keywords": ["ai", "agent", "llm", "rag", "embedding", "inference", "transformer", "chatbot", "prompt"],
|
|
13
|
+
"topics": ["ai", "llm", "rag", "agents", "machine-learning", "openai", "transformers"]
|
|
18
14
|
},
|
|
19
15
|
{
|
|
20
|
-
"name": "
|
|
21
|
-
"description": "
|
|
22
|
-
"keywords": [
|
|
23
|
-
|
|
24
|
-
"transformer", "prompt", "embedding", "inference", "ollama", "llama",
|
|
25
|
-
"vllm", "langchain", "llamaindex", "eval", "chatbot"
|
|
26
|
-
],
|
|
27
|
-
"topics": ["llm", "rag", "transformers", "langchain", "llama", "openai", "chatbot", "embeddings"]
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"name": "Vision",
|
|
31
|
-
"description": "Computer vision, image generation, video, OCR, and multimodal vision.",
|
|
32
|
-
"keywords": [
|
|
33
|
-
"vision", "computer vision", "image", "video", "ocr", "segmentation",
|
|
34
|
-
"detection", "object detection", "diffusion", "stable diffusion", "multimodal",
|
|
35
|
-
"vae", "gan", "opencv", "yolo"
|
|
36
|
-
],
|
|
37
|
-
"topics": ["computer-vision", "opencv", "image-processing", "diffusion", "ocr", "yolo", "multimodal"]
|
|
16
|
+
"name": "Frontend",
|
|
17
|
+
"description": "Frontend frameworks, UI components, design systems, and browser applications.",
|
|
18
|
+
"keywords": ["frontend", "react", "vue", "svelte", "next.js", "nextjs", "ui", "component", "css", "tailwind", "vite"],
|
|
19
|
+
"topics": ["frontend", "react", "vue", "svelte", "nextjs", "tailwindcss", "ui"]
|
|
38
20
|
},
|
|
39
21
|
{
|
|
40
|
-
"name": "
|
|
41
|
-
"description": "
|
|
42
|
-
"keywords": [
|
|
43
|
-
|
|
44
|
-
"ggml", "awq", "gptq", "bitsandbytes", "compression", "efficient inference"
|
|
45
|
-
],
|
|
46
|
-
"topics": ["quantization", "gguf", "ggml", "model-compression", "inference"]
|
|
22
|
+
"name": "Backend",
|
|
23
|
+
"description": "APIs, servers, databases, backend frameworks, and service architecture.",
|
|
24
|
+
"keywords": ["backend", "api", "server", "database", "postgres", "sqlite", "redis", "queue", "fastapi", "django", "rails", "nestjs"],
|
|
25
|
+
"topics": ["backend", "api", "server", "database", "postgresql", "sqlite", "redis"]
|
|
47
26
|
},
|
|
48
27
|
{
|
|
49
|
-
"name": "
|
|
50
|
-
"description": "
|
|
51
|
-
"keywords": ["
|
|
52
|
-
"topics": ["
|
|
28
|
+
"name": "DevOps",
|
|
29
|
+
"description": "Deployment, containers, infrastructure, CI/CD, observability, and operations.",
|
|
30
|
+
"keywords": ["devops", "docker", "kubernetes", "k8s", "terraform", "ansible", "ci", "cd", "deployment", "monitoring", "observability"],
|
|
31
|
+
"topics": ["devops", "docker", "kubernetes", "terraform", "ci-cd", "monitoring", "observability"]
|
|
53
32
|
},
|
|
54
33
|
{
|
|
55
|
-
"name": "
|
|
56
|
-
"description": "
|
|
57
|
-
"keywords": [
|
|
58
|
-
|
|
59
|
-
"bundle adjustment", "point cloud", "pointcloud", "3d reconstruction"
|
|
60
|
-
],
|
|
61
|
-
"topics": ["slam", "lidar", "mapping", "localization", "point-cloud", "visual-odometry"]
|
|
34
|
+
"name": "Data",
|
|
35
|
+
"description": "Data engineering, analytics, ETL, notebooks, visualization, and data stores.",
|
|
36
|
+
"keywords": ["data", "analytics", "etl", "pipeline", "notebook", "pandas", "duckdb", "spark", "visualization", "dashboard"],
|
|
37
|
+
"topics": ["data-engineering", "analytics", "etl", "pandas", "duckdb", "visualization"]
|
|
62
38
|
},
|
|
63
39
|
{
|
|
64
|
-
"name": "
|
|
65
|
-
"description": "
|
|
66
|
-
"keywords": [
|
|
67
|
-
|
|
68
|
-
"automation", "desktop", "app", "editor", "viewer", "formatter", "converter"
|
|
69
|
-
],
|
|
70
|
-
"topics": ["cli", "terminal", "tui", "productivity", "developer-tools", "automation"]
|
|
40
|
+
"name": "Security",
|
|
41
|
+
"description": "Security tooling, authentication, privacy, vulnerability research, and cryptography.",
|
|
42
|
+
"keywords": ["security", "auth", "oauth", "sso", "passkey", "secret", "vulnerability", "scanner", "privacy", "encryption", "cryptography"],
|
|
43
|
+
"topics": ["security", "privacy", "oauth", "cryptography", "vulnerability-scanner"]
|
|
71
44
|
},
|
|
72
45
|
{
|
|
73
|
-
"name": "
|
|
74
|
-
"description": "
|
|
75
|
-
"keywords": [
|
|
76
|
-
|
|
77
|
-
"design system", "tailwind", "css", "web app", "vite"
|
|
78
|
-
],
|
|
79
|
-
"topics": ["react", "nextjs", "vue", "svelte", "tailwindcss", "frontend", "ui"]
|
|
46
|
+
"name": "CLI / Tools",
|
|
47
|
+
"description": "Command-line tools, developer utilities, productivity apps, and workflow automation.",
|
|
48
|
+
"keywords": ["cli", "terminal", "tui", "tool", "utility", "developer tool", "productivity", "automation", "workflow", "formatter", "converter"],
|
|
49
|
+
"topics": ["cli", "terminal", "tui", "developer-tools", "productivity", "automation"]
|
|
80
50
|
},
|
|
81
51
|
{
|
|
82
|
-
"name": "
|
|
83
|
-
"description": "
|
|
84
|
-
"keywords": [
|
|
85
|
-
|
|
86
|
-
"queue", "microservice", "fastapi", "django", "rails", "spring", "nestjs"
|
|
87
|
-
],
|
|
88
|
-
"topics": ["api", "backend", "database", "postgresql", "sqlite", "redis", "server"]
|
|
52
|
+
"name": "Mobile",
|
|
53
|
+
"description": "Mobile apps, iOS, Android, React Native, Flutter, and cross-platform tooling.",
|
|
54
|
+
"keywords": ["mobile", "ios", "android", "react native", "react-native", "flutter", "swift", "kotlin"],
|
|
55
|
+
"topics": ["mobile", "ios", "android", "react-native", "flutter", "swift", "kotlin"]
|
|
89
56
|
},
|
|
90
57
|
{
|
|
91
|
-
"name": "
|
|
92
|
-
"description": "
|
|
93
|
-
"keywords": [
|
|
94
|
-
|
|
95
|
-
"deployment", "observability", "monitoring", "prometheus", "grafana"
|
|
96
|
-
],
|
|
97
|
-
"topics": ["docker", "kubernetes", "terraform", "devops", "ci-cd", "monitoring"]
|
|
58
|
+
"name": "Game",
|
|
59
|
+
"description": "Game engines, game development, graphics, simulation, and interactive experiences.",
|
|
60
|
+
"keywords": ["game", "gamedev", "unity", "unreal", "godot", "graphics", "shader", "simulation"],
|
|
61
|
+
"topics": ["game-development", "gamedev", "unity", "unreal-engine", "godot", "graphics"]
|
|
98
62
|
},
|
|
99
63
|
{
|
|
100
|
-
"name": "
|
|
101
|
-
"description": "
|
|
102
|
-
"keywords": [
|
|
103
|
-
|
|
104
|
-
"spark", "visualization", "dashboard", "chart"
|
|
105
|
-
],
|
|
106
|
-
"topics": ["data-engineering", "analytics", "etl", "pandas", "duckdb", "visualization"]
|
|
64
|
+
"name": "Robotics",
|
|
65
|
+
"description": "Robotics, ROS, autonomy, SLAM, simulation, mapping, and robot tooling.",
|
|
66
|
+
"keywords": ["robot", "robotics", "ros", "ros2", "slam", "navigation", "gazebo", "rviz", "lidar", "mapping", "autonomy"],
|
|
67
|
+
"topics": ["robotics", "ros", "ros2", "slam", "gazebo", "autonomous-robots"]
|
|
107
68
|
},
|
|
108
69
|
{
|
|
109
|
-
"name": "
|
|
110
|
-
"description": "
|
|
111
|
-
"keywords": [
|
|
112
|
-
|
|
113
|
-
"scanner", "privacy", "encryption", "cryptography", "sso"
|
|
114
|
-
],
|
|
115
|
-
"topics": ["security", "privacy", "oauth", "cryptography", "vulnerability-scanner"]
|
|
70
|
+
"name": "Uncategorized",
|
|
71
|
+
"description": "A placeholder list for repositories you want to classify manually.",
|
|
72
|
+
"keywords": [],
|
|
73
|
+
"topics": []
|
|
116
74
|
}
|
|
117
75
|
]
|
|
118
76
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-star-lists",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Organize GitHub starred repositories into Star Lists with dry-run plans, README fallback classification, and list description sync.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"scripts/",
|
|
12
12
|
"config/",
|
|
13
|
+
"presets/",
|
|
13
14
|
"README.md",
|
|
14
15
|
"LICENSE"
|
|
15
16
|
],
|
|
@@ -28,6 +29,14 @@
|
|
|
28
29
|
],
|
|
29
30
|
"author": "",
|
|
30
31
|
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/sohee-zoe/github-star-lists.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/sohee-zoe/github-star-lists/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/sohee-zoe/github-star-lists#readme",
|
|
31
40
|
"engines": {
|
|
32
41
|
"node": ">=20"
|
|
33
42
|
},
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"settings": {
|
|
3
|
+
"defaultPrivate": false,
|
|
4
|
+
"minScore": 2,
|
|
5
|
+
"maxListsPerRepo": 3,
|
|
6
|
+
"preserveExistingAssignments": true
|
|
7
|
+
},
|
|
8
|
+
"lists": [
|
|
9
|
+
{
|
|
10
|
+
"name": "Agentic Coding",
|
|
11
|
+
"description": "AI coding agents, code generation, MCP, and developer automation.",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"agent", "agentic", "ai coding", "autonomous", "codegen", "coding assistant",
|
|
14
|
+
"copilot", "cursor", "claude", "codex", "mcp", "multi-agent", "swe", "devin",
|
|
15
|
+
"software engineering agent", "llm app"
|
|
16
|
+
],
|
|
17
|
+
"topics": ["agents", "ai-agent", "mcp", "code-generation", "coding-agent", "developer-tools"]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "LLM",
|
|
21
|
+
"description": "Large language models, RAG, inference, evals, and LLM application tooling.",
|
|
22
|
+
"keywords": [
|
|
23
|
+
"llm", "large language model", "language model", "rag", "retrieval",
|
|
24
|
+
"transformer", "prompt", "embedding", "inference", "ollama", "llama",
|
|
25
|
+
"vllm", "langchain", "llamaindex", "eval", "chatbot"
|
|
26
|
+
],
|
|
27
|
+
"topics": ["llm", "rag", "transformers", "langchain", "llama", "openai", "chatbot", "embeddings"]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "Vision",
|
|
31
|
+
"description": "Computer vision, image generation, video, OCR, and multimodal vision.",
|
|
32
|
+
"keywords": [
|
|
33
|
+
"vision", "computer vision", "image", "video", "ocr", "segmentation",
|
|
34
|
+
"detection", "object detection", "diffusion", "stable diffusion", "multimodal",
|
|
35
|
+
"vae", "gan", "opencv", "yolo"
|
|
36
|
+
],
|
|
37
|
+
"topics": ["computer-vision", "opencv", "image-processing", "diffusion", "ocr", "yolo", "multimodal"]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "Quantization",
|
|
41
|
+
"description": "Model compression, quantization, GGUF, and efficient inference.",
|
|
42
|
+
"keywords": [
|
|
43
|
+
"quantization", "quantize", "quantized", "int4", "int8", "fp8", "gguf",
|
|
44
|
+
"ggml", "awq", "gptq", "bitsandbytes", "compression", "efficient inference"
|
|
45
|
+
],
|
|
46
|
+
"topics": ["quantization", "gguf", "ggml", "model-compression", "inference"]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "ROS",
|
|
50
|
+
"description": "ROS, robotics middleware, robot tooling, and autonomy systems.",
|
|
51
|
+
"keywords": ["ros", "ros2", "robot", "robotics", "navigation2", "nav2", "gazebo", "rviz", "urdf"],
|
|
52
|
+
"topics": ["ros", "ros2", "robotics", "gazebo", "rviz", "autonomous-robots"]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "SLAM",
|
|
56
|
+
"description": "SLAM, localization, mapping, LiDAR, and visual odometry.",
|
|
57
|
+
"keywords": [
|
|
58
|
+
"slam", "localization", "mapping", "lidar", "visual odometry", "odometry",
|
|
59
|
+
"bundle adjustment", "point cloud", "pointcloud", "3d reconstruction"
|
|
60
|
+
],
|
|
61
|
+
"topics": ["slam", "lidar", "mapping", "localization", "point-cloud", "visual-odometry"]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "Utilities",
|
|
65
|
+
"description": "Practical CLI, desktop, productivity, and small workflow tools.",
|
|
66
|
+
"keywords": [
|
|
67
|
+
"cli", "terminal", "tui", "utility", "tool", "productivity", "workflow",
|
|
68
|
+
"automation", "desktop", "app", "editor", "viewer", "formatter", "converter"
|
|
69
|
+
],
|
|
70
|
+
"topics": ["cli", "terminal", "tui", "productivity", "developer-tools", "automation"]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "Frontend",
|
|
74
|
+
"description": "Frontend frameworks, UI components, design systems, and web apps.",
|
|
75
|
+
"keywords": [
|
|
76
|
+
"react", "next.js", "nextjs", "vue", "svelte", "frontend", "ui", "component",
|
|
77
|
+
"design system", "tailwind", "css", "web app", "vite"
|
|
78
|
+
],
|
|
79
|
+
"topics": ["react", "nextjs", "vue", "svelte", "tailwindcss", "frontend", "ui"]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "Backend",
|
|
83
|
+
"description": "APIs, servers, databases, infrastructure services, and backend frameworks.",
|
|
84
|
+
"keywords": [
|
|
85
|
+
"api", "backend", "server", "database", "postgres", "sqlite", "redis",
|
|
86
|
+
"queue", "microservice", "fastapi", "django", "rails", "spring", "nestjs"
|
|
87
|
+
],
|
|
88
|
+
"topics": ["api", "backend", "database", "postgresql", "sqlite", "redis", "server"]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "DevOps",
|
|
92
|
+
"description": "Deployment, containers, Kubernetes, CI, observability, and operations.",
|
|
93
|
+
"keywords": [
|
|
94
|
+
"docker", "kubernetes", "k8s", "terraform", "ansible", "ci", "cd",
|
|
95
|
+
"deployment", "observability", "monitoring", "prometheus", "grafana"
|
|
96
|
+
],
|
|
97
|
+
"topics": ["docker", "kubernetes", "terraform", "devops", "ci-cd", "monitoring"]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "Data",
|
|
101
|
+
"description": "Data engineering, analytics, notebooks, ETL, and visualization.",
|
|
102
|
+
"keywords": [
|
|
103
|
+
"data", "analytics", "etl", "pipeline", "notebook", "pandas", "duckdb",
|
|
104
|
+
"spark", "visualization", "dashboard", "chart"
|
|
105
|
+
],
|
|
106
|
+
"topics": ["data-engineering", "analytics", "etl", "pandas", "duckdb", "visualization"]
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "Security",
|
|
110
|
+
"description": "Security tooling, secrets, auth, vulnerability research, and privacy.",
|
|
111
|
+
"keywords": [
|
|
112
|
+
"security", "auth", "oauth", "passkey", "secret", "vulnerability",
|
|
113
|
+
"scanner", "privacy", "encryption", "cryptography", "sso"
|
|
114
|
+
],
|
|
115
|
+
"topics": ["security", "privacy", "oauth", "cryptography", "vulnerability-scanner"]
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
package/presets/ai.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"settings": {
|
|
3
|
+
"defaultPrivate": false,
|
|
4
|
+
"minScore": 2,
|
|
5
|
+
"maxListsPerRepo": 3,
|
|
6
|
+
"preserveExistingAssignments": true
|
|
7
|
+
},
|
|
8
|
+
"lists": [
|
|
9
|
+
{
|
|
10
|
+
"name": "LLM",
|
|
11
|
+
"description": "Large language models, RAG, inference, evals, and LLM application tooling.",
|
|
12
|
+
"keywords": ["llm", "large language model", "language model", "rag", "retrieval", "transformer", "prompt", "embedding", "inference", "ollama", "llama", "vllm", "langchain", "llamaindex", "eval", "chatbot"],
|
|
13
|
+
"topics": ["llm", "rag", "transformers", "langchain", "llama", "openai", "chatbot", "embeddings"]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "AI Agents",
|
|
17
|
+
"description": "AI agents, MCP, tool use, multi-agent systems, and workflow automation.",
|
|
18
|
+
"keywords": ["agent", "agentic", "ai agent", "autonomous", "tool use", "mcp", "multi-agent", "workflow", "assistant"],
|
|
19
|
+
"topics": ["agents", "ai-agent", "mcp", "multi-agent", "automation"]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "AI Coding",
|
|
23
|
+
"description": "Coding assistants, code generation, software engineering agents, and developer automation.",
|
|
24
|
+
"keywords": ["ai coding", "codegen", "coding assistant", "copilot", "cursor", "claude", "codex", "devin", "software engineering agent"],
|
|
25
|
+
"topics": ["code-generation", "coding-agent", "developer-tools", "ai-agent"]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "Vision",
|
|
29
|
+
"description": "Computer vision, image generation, video, OCR, and multimodal systems.",
|
|
30
|
+
"keywords": ["vision", "computer vision", "image", "video", "ocr", "segmentation", "detection", "diffusion", "stable diffusion", "multimodal", "opencv", "yolo"],
|
|
31
|
+
"topics": ["computer-vision", "opencv", "image-processing", "diffusion", "ocr", "yolo", "multimodal"]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "ML Ops",
|
|
35
|
+
"description": "Training, evaluation, datasets, model serving, and machine learning infrastructure.",
|
|
36
|
+
"keywords": ["machine learning", "mlops", "training", "dataset", "eval", "benchmark", "model serving", "fine tuning", "finetune"],
|
|
37
|
+
"topics": ["machine-learning", "mlops", "datasets", "model-serving", "evaluation"]
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"settings": {
|
|
3
|
+
"defaultPrivate": false,
|
|
4
|
+
"minScore": 2,
|
|
5
|
+
"maxListsPerRepo": 3,
|
|
6
|
+
"preserveExistingAssignments": true
|
|
7
|
+
},
|
|
8
|
+
"lists": [
|
|
9
|
+
{
|
|
10
|
+
"name": "AI / LLM",
|
|
11
|
+
"description": "AI, large language models, agents, RAG, inference, and model tooling.",
|
|
12
|
+
"keywords": ["ai", "agent", "llm", "rag", "embedding", "inference", "transformer", "chatbot", "prompt"],
|
|
13
|
+
"topics": ["ai", "llm", "rag", "agents", "machine-learning", "openai", "transformers"]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "Frontend",
|
|
17
|
+
"description": "Frontend frameworks, UI components, design systems, and browser applications.",
|
|
18
|
+
"keywords": ["frontend", "react", "vue", "svelte", "next.js", "nextjs", "ui", "component", "css", "tailwind", "vite"],
|
|
19
|
+
"topics": ["frontend", "react", "vue", "svelte", "nextjs", "tailwindcss", "ui"]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "Backend",
|
|
23
|
+
"description": "APIs, servers, databases, backend frameworks, and service architecture.",
|
|
24
|
+
"keywords": ["backend", "api", "server", "database", "postgres", "sqlite", "redis", "queue", "fastapi", "django", "rails", "nestjs"],
|
|
25
|
+
"topics": ["backend", "api", "server", "database", "postgresql", "sqlite", "redis"]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "DevOps",
|
|
29
|
+
"description": "Deployment, containers, infrastructure, CI/CD, observability, and operations.",
|
|
30
|
+
"keywords": ["devops", "docker", "kubernetes", "k8s", "terraform", "ansible", "ci", "cd", "deployment", "monitoring", "observability"],
|
|
31
|
+
"topics": ["devops", "docker", "kubernetes", "terraform", "ci-cd", "monitoring", "observability"]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "Data",
|
|
35
|
+
"description": "Data engineering, analytics, ETL, notebooks, visualization, and data stores.",
|
|
36
|
+
"keywords": ["data", "analytics", "etl", "pipeline", "notebook", "pandas", "duckdb", "spark", "visualization", "dashboard"],
|
|
37
|
+
"topics": ["data-engineering", "analytics", "etl", "pandas", "duckdb", "visualization"]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "Security",
|
|
41
|
+
"description": "Security tooling, authentication, privacy, vulnerability research, and cryptography.",
|
|
42
|
+
"keywords": ["security", "auth", "oauth", "sso", "passkey", "secret", "vulnerability", "scanner", "privacy", "encryption", "cryptography"],
|
|
43
|
+
"topics": ["security", "privacy", "oauth", "cryptography", "vulnerability-scanner"]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "CLI / Tools",
|
|
47
|
+
"description": "Command-line tools, developer utilities, productivity apps, and workflow automation.",
|
|
48
|
+
"keywords": ["cli", "terminal", "tui", "tool", "utility", "developer tool", "productivity", "automation", "workflow", "formatter", "converter"],
|
|
49
|
+
"topics": ["cli", "terminal", "tui", "developer-tools", "productivity", "automation"]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "Mobile",
|
|
53
|
+
"description": "Mobile apps, iOS, Android, React Native, Flutter, and cross-platform tooling.",
|
|
54
|
+
"keywords": ["mobile", "ios", "android", "react native", "react-native", "flutter", "swift", "kotlin"],
|
|
55
|
+
"topics": ["mobile", "ios", "android", "react-native", "flutter", "swift", "kotlin"]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "Game",
|
|
59
|
+
"description": "Game engines, game development, graphics, simulation, and interactive experiences.",
|
|
60
|
+
"keywords": ["game", "gamedev", "unity", "unreal", "godot", "graphics", "shader", "simulation"],
|
|
61
|
+
"topics": ["game-development", "gamedev", "unity", "unreal-engine", "godot", "graphics"]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "Robotics",
|
|
65
|
+
"description": "Robotics, ROS, autonomy, SLAM, simulation, mapping, and robot tooling.",
|
|
66
|
+
"keywords": ["robot", "robotics", "ros", "ros2", "slam", "navigation", "gazebo", "rviz", "lidar", "mapping", "autonomy"],
|
|
67
|
+
"topics": ["robotics", "ros", "ros2", "slam", "gazebo", "autonomous-robots"]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "Uncategorized",
|
|
71
|
+
"description": "A placeholder list for repositories you want to classify manually.",
|
|
72
|
+
"keywords": [],
|
|
73
|
+
"topics": []
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"settings": {
|
|
3
|
+
"defaultPrivate": false,
|
|
4
|
+
"minScore": 2,
|
|
5
|
+
"maxListsPerRepo": 3,
|
|
6
|
+
"preserveExistingAssignments": true
|
|
7
|
+
},
|
|
8
|
+
"lists": [
|
|
9
|
+
{
|
|
10
|
+
"name": "ROS",
|
|
11
|
+
"description": "ROS, ROS 2, robotics middleware, robot tooling, and autonomy systems.",
|
|
12
|
+
"keywords": ["ros", "ros2", "robot", "robotics", "navigation2", "nav2", "gazebo", "rviz", "urdf"],
|
|
13
|
+
"topics": ["ros", "ros2", "robotics", "gazebo", "rviz", "autonomous-robots"]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "SLAM",
|
|
17
|
+
"description": "SLAM, localization, mapping, LiDAR, point clouds, and visual odometry.",
|
|
18
|
+
"keywords": ["slam", "localization", "mapping", "lidar", "visual odometry", "odometry", "bundle adjustment", "point cloud", "pointcloud", "3d reconstruction"],
|
|
19
|
+
"topics": ["slam", "lidar", "mapping", "localization", "point-cloud", "visual-odometry"]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "Simulation",
|
|
23
|
+
"description": "Robot simulation, digital twins, physics engines, and synthetic environments.",
|
|
24
|
+
"keywords": ["simulation", "simulator", "gazebo", "isaac", "mujoco", "pybullet", "physics", "digital twin"],
|
|
25
|
+
"topics": ["simulation", "gazebo", "isaac-sim", "mujoco", "pybullet"]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "Perception",
|
|
29
|
+
"description": "Robot perception, computer vision, sensors, detection, tracking, and segmentation.",
|
|
30
|
+
"keywords": ["perception", "vision", "camera", "sensor", "detection", "tracking", "segmentation", "opencv", "lidar"],
|
|
31
|
+
"topics": ["perception", "computer-vision", "opencv", "lidar", "segmentation"]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "Control",
|
|
35
|
+
"description": "Robot control, planning, kinematics, dynamics, navigation, and motion planning.",
|
|
36
|
+
"keywords": ["control", "planning", "motion planning", "kinematics", "dynamics", "navigation", "trajectory", "path planning"],
|
|
37
|
+
"topics": ["robotics", "motion-planning", "navigation", "control"]
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"settings": {
|
|
3
|
+
"defaultPrivate": false,
|
|
4
|
+
"minScore": 2,
|
|
5
|
+
"maxListsPerRepo": 3,
|
|
6
|
+
"preserveExistingAssignments": true
|
|
7
|
+
},
|
|
8
|
+
"lists": [
|
|
9
|
+
{
|
|
10
|
+
"name": "Frontend",
|
|
11
|
+
"description": "Frontend frameworks, UI components, design systems, and browser applications.",
|
|
12
|
+
"keywords": ["frontend", "react", "vue", "svelte", "next.js", "nextjs", "remix", "ui", "component", "css", "tailwind", "vite"],
|
|
13
|
+
"topics": ["frontend", "react", "vue", "svelte", "nextjs", "tailwindcss", "ui"]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "Backend",
|
|
17
|
+
"description": "APIs, servers, databases, backend frameworks, and service architecture.",
|
|
18
|
+
"keywords": ["backend", "api", "server", "database", "postgres", "sqlite", "redis", "queue", "fastapi", "django", "rails", "nestjs"],
|
|
19
|
+
"topics": ["backend", "api", "server", "database", "postgresql", "sqlite", "redis"]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "Full Stack",
|
|
23
|
+
"description": "Full-stack frameworks, application starters, auth, and product scaffolding.",
|
|
24
|
+
"keywords": ["full stack", "fullstack", "app starter", "boilerplate", "auth", "saas", "template", "monorepo"],
|
|
25
|
+
"topics": ["fullstack", "starter-template", "boilerplate", "auth", "saas"]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "DevOps",
|
|
29
|
+
"description": "Deployment, containers, infrastructure, CI/CD, observability, and operations.",
|
|
30
|
+
"keywords": ["devops", "docker", "kubernetes", "k8s", "terraform", "ci", "cd", "deployment", "monitoring", "observability"],
|
|
31
|
+
"topics": ["devops", "docker", "kubernetes", "terraform", "ci-cd", "monitoring"]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "Tooling",
|
|
35
|
+
"description": "Build tools, test tools, developer experience, CLIs, and workflow automation.",
|
|
36
|
+
"keywords": ["tooling", "build", "bundler", "test", "testing", "cli", "developer experience", "dx", "automation", "formatter", "linter"],
|
|
37
|
+
"topics": ["developer-tools", "cli", "testing", "build-tool", "automation"]
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs";
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
5
|
import { stdin as input, stdout as output } from "node:process";
|
|
6
6
|
import { dirname, resolve } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
9
9
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
-
const
|
|
10
|
+
const PRESETS_DIR = resolve(PACKAGE_ROOT, "presets");
|
|
11
|
+
const DEFAULT_PRESET = "general";
|
|
12
|
+
const DEFAULT_CONFIG_PATH = resolve(PRESETS_DIR, `${DEFAULT_PRESET}.json`);
|
|
11
13
|
|
|
12
14
|
const command = process.argv[2]?.startsWith("-") ? null : process.argv[2];
|
|
13
15
|
const args = new Set(process.argv.slice(2));
|
|
@@ -25,8 +27,9 @@ const onlyListMetadata = args.has("--only-list-metadata") || onlyListDescription
|
|
|
25
27
|
const allPrivate = args.has("--all-private");
|
|
26
28
|
const allPublic = args.has("--all-public");
|
|
27
29
|
const yes = args.has("--yes") || args.has("-y");
|
|
28
|
-
const
|
|
29
|
-
const limit =
|
|
30
|
+
const limitOption = resolveOption("--limit");
|
|
31
|
+
const limit = limitOption ? Number(limitOption) : Infinity;
|
|
32
|
+
let authToken = null;
|
|
30
33
|
|
|
31
34
|
if (args.has("--help") || args.has("-h")) {
|
|
32
35
|
printHelp();
|
|
@@ -34,7 +37,12 @@ if (args.has("--help") || args.has("-h")) {
|
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
if (command === "init") {
|
|
37
|
-
initConfig();
|
|
40
|
+
await initConfig();
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (command === "suggest-config") {
|
|
45
|
+
await suggestConfig();
|
|
38
46
|
process.exit(0);
|
|
39
47
|
}
|
|
40
48
|
|
|
@@ -43,7 +51,7 @@ if (command === "wizard") {
|
|
|
43
51
|
process.exit(status);
|
|
44
52
|
}
|
|
45
53
|
|
|
46
|
-
if (command && !["run", "wizard"].includes(command)) {
|
|
54
|
+
if (command && !["run", "wizard", "suggest-config"].includes(command)) {
|
|
47
55
|
fail(`Unknown command: ${command}\nRun with --help for usage.`);
|
|
48
56
|
}
|
|
49
57
|
|
|
@@ -70,8 +78,6 @@ const settings = {
|
|
|
70
78
|
...config.settings
|
|
71
79
|
};
|
|
72
80
|
|
|
73
|
-
const token = getToken();
|
|
74
|
-
|
|
75
81
|
function resolveOption(name) {
|
|
76
82
|
const equalsArg = process.argv.find((arg) => arg.startsWith(`${name}=`));
|
|
77
83
|
if (equalsArg) return equalsArg.slice(name.length + 1);
|
|
@@ -90,13 +96,40 @@ function findDefaultConfigPath() {
|
|
|
90
96
|
return candidates.find((candidate) => existsSync(candidate)) ?? DEFAULT_CONFIG_PATH;
|
|
91
97
|
}
|
|
92
98
|
|
|
93
|
-
function initConfig() {
|
|
99
|
+
async function initConfig() {
|
|
100
|
+
if (args.has("--list-presets")) {
|
|
101
|
+
console.log(listPresets().join("\n"));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
94
105
|
const target = resolve(process.cwd(), resolveOption("--config") ?? "star-lists.config.json");
|
|
95
106
|
if (existsSync(target) && !args.has("--force")) {
|
|
96
107
|
fail(`${target} already exists. Re-run init with --force to overwrite it.`);
|
|
97
108
|
}
|
|
98
|
-
|
|
99
|
-
|
|
109
|
+
|
|
110
|
+
if (args.has("--from-existing-lists")) {
|
|
111
|
+
await initFromExistingLists(target);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const preset = resolveOption("--preset") ?? DEFAULT_PRESET;
|
|
116
|
+
writeFileSync(target, `${JSON.stringify(readPresetConfig(preset), null, 2)}\n`);
|
|
117
|
+
console.log(`Wrote ${target} from preset "${preset}"`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function listPresets() {
|
|
121
|
+
return readdirSync(PRESETS_DIR)
|
|
122
|
+
.filter((name) => name.endsWith(".json"))
|
|
123
|
+
.map((name) => name.replace(/\.json$/, ""))
|
|
124
|
+
.sort((a, b) => a.localeCompare(b));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function readPresetConfig(preset) {
|
|
128
|
+
const available = listPresets();
|
|
129
|
+
if (!available.includes(preset)) {
|
|
130
|
+
fail(`Unknown preset: ${preset}\nAvailable presets: ${available.join(", ")}`);
|
|
131
|
+
}
|
|
132
|
+
return JSON.parse(readFileSync(resolve(PRESETS_DIR, `${preset}.json`), "utf8"));
|
|
100
133
|
}
|
|
101
134
|
|
|
102
135
|
async function runWizard() {
|
|
@@ -217,16 +250,83 @@ async function promptText(rl, question, defaultValue) {
|
|
|
217
250
|
}
|
|
218
251
|
|
|
219
252
|
function getToken() {
|
|
253
|
+
if (authToken) return authToken;
|
|
254
|
+
|
|
220
255
|
const envToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
221
|
-
if (envToken)
|
|
256
|
+
if (envToken) {
|
|
257
|
+
authToken = envToken;
|
|
258
|
+
return authToken;
|
|
259
|
+
}
|
|
222
260
|
|
|
223
261
|
try {
|
|
224
|
-
|
|
262
|
+
authToken = execFileSync("gh", ["auth", "token"], { encoding: "utf8" }).trim();
|
|
263
|
+
return authToken;
|
|
225
264
|
} catch {
|
|
226
265
|
fail("No GitHub token found. Set GITHUB_TOKEN/GH_TOKEN or run `gh auth login`.");
|
|
227
266
|
}
|
|
228
267
|
}
|
|
229
268
|
|
|
269
|
+
async function initFromExistingLists(target) {
|
|
270
|
+
const progress = createProgress({ quiet });
|
|
271
|
+
const viewer = await fetchViewerLists(progress);
|
|
272
|
+
const lists = viewer.lists.nodes.map((list) => ({
|
|
273
|
+
name: list.name,
|
|
274
|
+
description: list.description ?? "",
|
|
275
|
+
isPrivate: list.isPrivate,
|
|
276
|
+
keywords: [],
|
|
277
|
+
topics: suggestTopicsForListName(list.name)
|
|
278
|
+
}));
|
|
279
|
+
|
|
280
|
+
writeFileSync(target, `${JSON.stringify({
|
|
281
|
+
settings: {
|
|
282
|
+
defaultPrivate: false,
|
|
283
|
+
minScore: 2,
|
|
284
|
+
maxListsPerRepo: 3,
|
|
285
|
+
preserveExistingAssignments: true
|
|
286
|
+
},
|
|
287
|
+
lists
|
|
288
|
+
}, null, 2)}\n`);
|
|
289
|
+
console.log(`Wrote ${target} from ${lists.length} existing GitHub Star Lists`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function suggestConfig() {
|
|
293
|
+
if (Number.isNaN(limit) || limit <= 0) {
|
|
294
|
+
fail("--limit must be a positive number");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const progress = createProgress({ quiet });
|
|
298
|
+
const viewer = await fetchViewerLists(progress);
|
|
299
|
+
const stars = await fetchStars(limit, progress);
|
|
300
|
+
const stats = buildSuggestionStats(stars);
|
|
301
|
+
const existingLists = viewer.lists.nodes;
|
|
302
|
+
const lists = existingLists.length
|
|
303
|
+
? existingLists.map((list) => suggestListFromExisting(list, stats))
|
|
304
|
+
: suggestListsFromStats(stats);
|
|
305
|
+
|
|
306
|
+
mkdirSync(outDir, { recursive: true });
|
|
307
|
+
const jsonPath = resolve(outDir, "suggested-config.json");
|
|
308
|
+
writeFileSync(jsonPath, `${JSON.stringify({
|
|
309
|
+
settings: {
|
|
310
|
+
defaultPrivate: false,
|
|
311
|
+
minScore: 2,
|
|
312
|
+
maxListsPerRepo: 3,
|
|
313
|
+
preserveExistingAssignments: true
|
|
314
|
+
},
|
|
315
|
+
lists,
|
|
316
|
+
_meta: {
|
|
317
|
+
generatedAt: new Date().toISOString(),
|
|
318
|
+
starsScanned: stars.length,
|
|
319
|
+
existingListsScanned: existingLists.length,
|
|
320
|
+
topTopics: topEntries(stats.topics, 30),
|
|
321
|
+
topLanguages: topEntries(stats.languages, 20),
|
|
322
|
+
topDescriptionKeywords: topEntries(stats.descriptionKeywords, 40)
|
|
323
|
+
}
|
|
324
|
+
}, null, 2)}\n`);
|
|
325
|
+
|
|
326
|
+
console.log(`Wrote ${jsonPath}`);
|
|
327
|
+
console.log(`Scanned ${stars.length} starred repositories and ${existingLists.length} existing lists.`);
|
|
328
|
+
}
|
|
329
|
+
|
|
230
330
|
function resolveListPrivacy(listConfig) {
|
|
231
331
|
if (allPrivate) return true;
|
|
232
332
|
if (allPublic) return false;
|
|
@@ -271,11 +371,16 @@ function printHelp() {
|
|
|
271
371
|
console.log(`GitHub Star List Organizer
|
|
272
372
|
|
|
273
373
|
Usage:
|
|
274
|
-
github-star-lists init [--config star-lists.config.json] [--force]
|
|
374
|
+
github-star-lists init [--preset general] [--config star-lists.config.json] [--force]
|
|
375
|
+
github-star-lists init --from-existing-lists [--config star-lists.config.json] [--force]
|
|
376
|
+
github-star-lists suggest-config [--limit=200] [--out-dir out]
|
|
275
377
|
github-star-lists wizard
|
|
276
378
|
github-star-lists [run] [options]
|
|
277
379
|
|
|
278
380
|
Options:
|
|
381
|
+
--preset <name> Config preset for init. Default: general.
|
|
382
|
+
--from-existing-lists Build config from your current GitHub Star Lists.
|
|
383
|
+
--list-presets Print available preset names.
|
|
279
384
|
--apply Apply changes to GitHub. Without this, only writes a plan.
|
|
280
385
|
--config <path> Classification config path.
|
|
281
386
|
--out-dir <path> Plan output directory. Default: out
|
|
@@ -300,33 +405,9 @@ Authentication:
|
|
|
300
405
|
|
|
301
406
|
async function main() {
|
|
302
407
|
const progress = createProgress({ quiet });
|
|
303
|
-
|
|
304
|
-
const viewer = await graphql(`
|
|
305
|
-
query {
|
|
306
|
-
viewer {
|
|
307
|
-
login
|
|
308
|
-
id
|
|
309
|
-
lists(first: 100) {
|
|
310
|
-
nodes {
|
|
311
|
-
id
|
|
312
|
-
name
|
|
313
|
-
description
|
|
314
|
-
isPrivate
|
|
315
|
-
items(first: 100) {
|
|
316
|
-
nodes {
|
|
317
|
-
__typename
|
|
318
|
-
... on Repository { id nameWithOwner }
|
|
319
|
-
}
|
|
320
|
-
pageInfo { hasNextPage endCursor }
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
`);
|
|
327
|
-
progress.done("Loaded viewer lists");
|
|
408
|
+
const viewer = await fetchViewerLists(progress, { includeItems: true });
|
|
328
409
|
|
|
329
|
-
const listByName = new Map(viewer.
|
|
410
|
+
const listByName = new Map(viewer.lists.nodes.map((list) => [list.name, list]));
|
|
330
411
|
const listsToCreate = [];
|
|
331
412
|
const listDescriptionUpdates = [];
|
|
332
413
|
const listVisibilityUpdates = [];
|
|
@@ -443,7 +524,7 @@ async function main() {
|
|
|
443
524
|
return;
|
|
444
525
|
}
|
|
445
526
|
|
|
446
|
-
const existingAssignments = await fetchExistingAssignments(viewer.
|
|
527
|
+
const existingAssignments = await fetchExistingAssignments(viewer.lists.nodes, progress);
|
|
447
528
|
const stars = await fetchStars(limit, progress);
|
|
448
529
|
const changes = [];
|
|
449
530
|
const skipped = [];
|
|
@@ -568,6 +649,216 @@ async function main() {
|
|
|
568
649
|
if (!apply) console.log("Run with --apply to update GitHub Star Lists.");
|
|
569
650
|
}
|
|
570
651
|
|
|
652
|
+
async function fetchViewerLists(progress, { includeItems = false } = {}) {
|
|
653
|
+
const lists = [];
|
|
654
|
+
let cursor = null;
|
|
655
|
+
let viewerLogin = "";
|
|
656
|
+
let viewerId = "";
|
|
657
|
+
progress.start("Loading viewer lists");
|
|
658
|
+
|
|
659
|
+
do {
|
|
660
|
+
const data = await graphql(
|
|
661
|
+
`
|
|
662
|
+
query($cursor: String) {
|
|
663
|
+
viewer {
|
|
664
|
+
login
|
|
665
|
+
id
|
|
666
|
+
lists(first: 100, after: $cursor) {
|
|
667
|
+
nodes {
|
|
668
|
+
id
|
|
669
|
+
name
|
|
670
|
+
description
|
|
671
|
+
isPrivate
|
|
672
|
+
${includeItems ? `
|
|
673
|
+
items(first: 100) {
|
|
674
|
+
nodes {
|
|
675
|
+
__typename
|
|
676
|
+
... on Repository { id nameWithOwner }
|
|
677
|
+
}
|
|
678
|
+
pageInfo { hasNextPage endCursor }
|
|
679
|
+
}
|
|
680
|
+
` : ""}
|
|
681
|
+
}
|
|
682
|
+
pageInfo { hasNextPage endCursor }
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
`,
|
|
687
|
+
{ cursor }
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
viewerLogin = data.viewer.login;
|
|
691
|
+
viewerId = data.viewer.id;
|
|
692
|
+
lists.push(...data.viewer.lists.nodes);
|
|
693
|
+
cursor = data.viewer.lists.pageInfo.endCursor;
|
|
694
|
+
progress.tick(`Loading viewer lists ${lists.length}`);
|
|
695
|
+
if (!data.viewer.lists.pageInfo.hasNextPage) break;
|
|
696
|
+
} while (cursor);
|
|
697
|
+
|
|
698
|
+
progress.done(`Loaded ${lists.length} viewer lists`);
|
|
699
|
+
return {
|
|
700
|
+
login: viewerLogin,
|
|
701
|
+
id: viewerId,
|
|
702
|
+
lists: { nodes: lists }
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function buildSuggestionStats(stars) {
|
|
707
|
+
const topics = new Map();
|
|
708
|
+
const languages = new Map();
|
|
709
|
+
const descriptionKeywords = new Map();
|
|
710
|
+
|
|
711
|
+
for (const repo of stars) {
|
|
712
|
+
for (const topic of topicNames(repo)) increment(topics, topic);
|
|
713
|
+
const language = repo.primaryLanguage?.name;
|
|
714
|
+
if (language) increment(languages, language);
|
|
715
|
+
|
|
716
|
+
const text = `${repo.nameWithOwner} ${repo.description ?? ""}`;
|
|
717
|
+
for (const word of extractKeywords(text)) increment(descriptionKeywords, word);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return { topics, languages, descriptionKeywords };
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function suggestListFromExisting(list, stats) {
|
|
724
|
+
const nameTopics = suggestTopicsForListName(list.name);
|
|
725
|
+
const tokens = tokenize(list.name);
|
|
726
|
+
const matchedTopics = topEntries(stats.topics, 100)
|
|
727
|
+
.map(([topic]) => topic)
|
|
728
|
+
.filter((topic) => tokens.some((token) => topic.includes(token) || token.includes(topic)))
|
|
729
|
+
.slice(0, 12);
|
|
730
|
+
|
|
731
|
+
return {
|
|
732
|
+
name: list.name,
|
|
733
|
+
description: list.description ?? "",
|
|
734
|
+
isPrivate: list.isPrivate,
|
|
735
|
+
keywords: suggestKeywordsForListName(list.name, stats),
|
|
736
|
+
topics: unique([...nameTopics, ...matchedTopics])
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function suggestListsFromStats(stats) {
|
|
741
|
+
const general = readPresetConfig(DEFAULT_PRESET);
|
|
742
|
+
const topicSet = new Set(topEntries(stats.topics, 100).map(([topic]) => topic));
|
|
743
|
+
const keywordSet = new Set(topEntries(stats.descriptionKeywords, 100).map(([keyword]) => keyword));
|
|
744
|
+
const languageSet = new Set(topEntries(stats.languages, 50).map(([language]) => language.toLowerCase()));
|
|
745
|
+
|
|
746
|
+
return general.lists
|
|
747
|
+
.map((list) => {
|
|
748
|
+
const topics = (list.topics ?? []).filter((topic) => topicSet.has(topic));
|
|
749
|
+
const keywords = (list.keywords ?? []).filter((keyword) => {
|
|
750
|
+
const normalized = keyword.toLowerCase();
|
|
751
|
+
return keywordSet.has(normalized) || languageSet.has(normalized);
|
|
752
|
+
});
|
|
753
|
+
return {
|
|
754
|
+
...list,
|
|
755
|
+
keywords: unique(keywords).slice(0, 20),
|
|
756
|
+
topics: unique(topics).slice(0, 20)
|
|
757
|
+
};
|
|
758
|
+
})
|
|
759
|
+
.filter((list) => list.keywords.length || list.topics.length)
|
|
760
|
+
.slice(0, 12);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function suggestTopicsForListName(name) {
|
|
764
|
+
const aliases = new Map([
|
|
765
|
+
["ai", ["ai", "machine-learning"]],
|
|
766
|
+
["llm", ["llm", "rag", "embeddings"]],
|
|
767
|
+
["agent", ["agents", "ai-agent"]],
|
|
768
|
+
["agents", ["agents", "ai-agent"]],
|
|
769
|
+
["frontend", ["frontend", "react", "vue", "svelte"]],
|
|
770
|
+
["backend", ["backend", "api", "server"]],
|
|
771
|
+
["devops", ["devops", "docker", "kubernetes"]],
|
|
772
|
+
["data", ["data-engineering", "analytics"]],
|
|
773
|
+
["security", ["security", "privacy"]],
|
|
774
|
+
["cli", ["cli", "terminal", "developer-tools"]],
|
|
775
|
+
["tools", ["developer-tools", "productivity"]],
|
|
776
|
+
["mobile", ["mobile", "ios", "android"]],
|
|
777
|
+
["game", ["game-development", "gamedev"]],
|
|
778
|
+
["robotics", ["robotics", "ros", "ros2"]],
|
|
779
|
+
["ros", ["ros", "ros2", "robotics"]],
|
|
780
|
+
["slam", ["slam", "mapping", "localization"]]
|
|
781
|
+
]);
|
|
782
|
+
|
|
783
|
+
const topics = [];
|
|
784
|
+
for (const token of tokenize(name)) {
|
|
785
|
+
topics.push(...(aliases.get(token) ?? [token]));
|
|
786
|
+
}
|
|
787
|
+
return unique(topics).slice(0, 12);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function suggestKeywordsForListName(name, stats) {
|
|
791
|
+
const aliases = new Map([
|
|
792
|
+
["ai", ["ai", "machine learning"]],
|
|
793
|
+
["llm", ["llm", "rag", "embedding", "inference"]],
|
|
794
|
+
["agent", ["agent", "agentic", "mcp"]],
|
|
795
|
+
["agents", ["agent", "agentic", "mcp"]],
|
|
796
|
+
["frontend", ["frontend", "react", "vue", "svelte", "ui"]],
|
|
797
|
+
["backend", ["backend", "api", "server", "database"]],
|
|
798
|
+
["devops", ["devops", "docker", "kubernetes", "terraform"]],
|
|
799
|
+
["data", ["data", "analytics", "etl", "pipeline"]],
|
|
800
|
+
["security", ["security", "auth", "privacy", "encryption"]],
|
|
801
|
+
["cli", ["cli", "terminal", "tui"]],
|
|
802
|
+
["tools", ["tool", "utility", "automation"]],
|
|
803
|
+
["mobile", ["mobile", "ios", "android", "flutter"]],
|
|
804
|
+
["game", ["game", "gamedev", "graphics"]],
|
|
805
|
+
["robotics", ["robot", "robotics", "ros", "slam"]],
|
|
806
|
+
["ros", ["ros", "ros2", "robotics"]],
|
|
807
|
+
["slam", ["slam", "localization", "mapping"]]
|
|
808
|
+
]);
|
|
809
|
+
|
|
810
|
+
const tokens = tokenize(name);
|
|
811
|
+
const keywords = [];
|
|
812
|
+
for (const token of tokens) {
|
|
813
|
+
if (token.length >= 3) keywords.push(token);
|
|
814
|
+
keywords.push(...(aliases.get(token) ?? []));
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const frequentWords = topEntries(stats.descriptionKeywords, 100)
|
|
818
|
+
.map(([word]) => word)
|
|
819
|
+
.filter((word) => tokens.some((token) => word.includes(token) || token.includes(word)))
|
|
820
|
+
.slice(0, 10);
|
|
821
|
+
|
|
822
|
+
return unique([...keywords, ...frequentWords]).slice(0, 20);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function extractKeywords(text) {
|
|
826
|
+
const stopwords = new Set([
|
|
827
|
+
"about", "after", "also", "and", "are", "awesome", "based", "build", "built",
|
|
828
|
+
"can", "collection", "for", "from", "github", "into", "its", "library",
|
|
829
|
+
"list", "made", "new", "not", "open", "project", "repo", "repository",
|
|
830
|
+
"simple", "source", "that", "the", "this", "tool", "using", "with", "your"
|
|
831
|
+
]);
|
|
832
|
+
|
|
833
|
+
return tokenize(text)
|
|
834
|
+
.filter((word) => word.length >= 3)
|
|
835
|
+
.filter((word) => !stopwords.has(word))
|
|
836
|
+
.filter((word) => !/^\d+$/.test(word));
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function tokenize(text) {
|
|
840
|
+
return String(text)
|
|
841
|
+
.toLowerCase()
|
|
842
|
+
.replace(/[^a-z0-9+.#-]+/g, " ")
|
|
843
|
+
.split(/\s+/)
|
|
844
|
+
.map((word) => word.replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, ""))
|
|
845
|
+
.filter(Boolean);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function topEntries(map, count) {
|
|
849
|
+
return [...map.entries()]
|
|
850
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
851
|
+
.slice(0, count);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function increment(map, key) {
|
|
855
|
+
map.set(key, (map.get(key) ?? 0) + 1);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function unique(values) {
|
|
859
|
+
return [...new Set(values.filter(Boolean))];
|
|
860
|
+
}
|
|
861
|
+
|
|
571
862
|
async function applyListDescriptionUpdates(updates, progress) {
|
|
572
863
|
if (!updates.length) return;
|
|
573
864
|
progress.start(`Updating list descriptions 0/${updates.length}`);
|
|
@@ -702,7 +993,7 @@ async function fetchStars(max, progress) {
|
|
|
702
993
|
async function fetchReadme(nameWithOwner) {
|
|
703
994
|
const response = await fetch(`https://api.github.com/repos/${nameWithOwner}/readme`, {
|
|
704
995
|
headers: {
|
|
705
|
-
authorization: `bearer ${
|
|
996
|
+
authorization: `bearer ${getToken()}`,
|
|
706
997
|
accept: "application/vnd.github.raw",
|
|
707
998
|
"user-agent": "git-star-organizer"
|
|
708
999
|
}
|
|
@@ -883,7 +1174,7 @@ async function graphql(query, variables = {}) {
|
|
|
883
1174
|
const response = await fetch("https://api.github.com/graphql", {
|
|
884
1175
|
method: "POST",
|
|
885
1176
|
headers: {
|
|
886
|
-
authorization: `bearer ${
|
|
1177
|
+
authorization: `bearer ${getToken()}`,
|
|
887
1178
|
"content-type": "application/json",
|
|
888
1179
|
"user-agent": "git-star-organizer"
|
|
889
1180
|
},
|