github-star-lists 0.1.1 → 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 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
- Create a local config:
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
- Preview directly. This does not write to GitHub:
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
- Most users should start with `wizard`. The flags below are useful for automation or repeatable workflows.
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 default config
189
+ 3. the packaged `general` preset
145
190
 
146
191
  Use a specific config:
147
192
 
@@ -7,112 +7,70 @@
7
7
  },
8
8
  "lists": [
9
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"]
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": "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"]
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": "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"]
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": "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"]
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": "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"]
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": "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"]
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": "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"]
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": "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"]
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": "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"]
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": "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"]
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": "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"]
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.1.1",
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
  ],
@@ -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
+ }
@@ -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 DEFAULT_CONFIG_PATH = resolve(PACKAGE_ROOT, "config/star-lists.json");
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 limitArg = process.argv.find((arg) => arg.startsWith("--limit="));
29
- const limit = limitArg ? Number(limitArg.slice("--limit=".length)) : Infinity;
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
- writeFileSync(target, readFileSync(DEFAULT_CONFIG_PATH, "utf8"));
99
- console.log(`Wrote ${target}`);
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) return envToken;
256
+ if (envToken) {
257
+ authToken = envToken;
258
+ return authToken;
259
+ }
222
260
 
223
261
  try {
224
- return execFileSync("gh", ["auth", "token"], { encoding: "utf8" }).trim();
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
- progress.start("Loading viewer lists");
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.viewer.lists.nodes.map((list) => [list.name, list]));
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.viewer.lists.nodes, progress);
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 ${token}`,
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 ${token}`,
1177
+ authorization: `bearer ${getToken()}`,
887
1178
  "content-type": "application/json",
888
1179
  "user-agent": "git-star-organizer"
889
1180
  },