git-repokit 0.9.10__py3-none-any.whl
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.
- git_repokit-0.9.10.dist-info/METADATA +422 -0
- git_repokit-0.9.10.dist-info/RECORD +174 -0
- git_repokit-0.9.10.dist-info/WHEEL +5 -0
- git_repokit-0.9.10.dist-info/entry_points.txt +2 -0
- git_repokit-0.9.10.dist-info/licenses/LICENSE +9 -0
- git_repokit-0.9.10.dist-info/top_level.txt +1 -0
- repokit/__init__.py +8 -0
- repokit/__main__.py +12 -0
- repokit/__version__.py +4 -0
- repokit/_vendor/__init__.py +0 -0
- repokit/_vendor/ailib/__init__.py +1 -0
- repokit/_vendor/ailib/analyzer.py +177 -0
- repokit/_vendor/ailib/backend.py +95 -0
- repokit/_vendor/ailib/cache.py +97 -0
- repokit/_vendor/ailib/parsers.py +52 -0
- repokit/_vendor/ailib/transports/__init__.py +16 -0
- repokit/_vendor/ailib/transports/cli.py +312 -0
- repokit/_vendor/ailib/transports/openai_compat.py +383 -0
- repokit/_vendor/ailib/transports/prompt_file.py +41 -0
- repokit/_vendor/ailib/types.py +224 -0
- repokit/auth_integration.py +244 -0
- repokit/badges.py +183 -0
- repokit/branch_utils.py +364 -0
- repokit/cli.py +2997 -0
- repokit/common_kit.py +202 -0
- repokit/config.py +686 -0
- repokit/defaults.py +220 -0
- repokit/directory_analyzer.py +1244 -0
- repokit/directory_profiles.py +423 -0
- repokit/git_config.py +370 -0
- repokit/github_create.py +142 -0
- repokit/github_setup.py +248 -0
- repokit/guardrails.py +707 -0
- repokit/history_cleaner.py +449 -0
- repokit/history_protection.py +434 -0
- repokit/hooks/pre-commit-guardrails +122 -0
- repokit/hooks/pre-commit-template +28 -0
- repokit/layers.py +510 -0
- repokit/licenses/0bsd.txt +14 -0
- repokit/licenses/LICENSES.md +27 -0
- repokit/licenses/__init__.py +142 -0
- repokit/licenses/agpl-3.0.txt +661 -0
- repokit/licenses/all-rights-reserved.txt +10 -0
- repokit/licenses/apache-2.0.txt +201 -0
- repokit/licenses/bsd-2-clause.txt +24 -0
- repokit/licenses/bsd-3-clause.txt +28 -0
- repokit/licenses/bsl-1.0.txt +23 -0
- repokit/licenses/cc-by-4.0.txt +395 -0
- repokit/licenses/cc-by-sa-4.0.txt +427 -0
- repokit/licenses/cc0-1.0.txt +121 -0
- repokit/licenses/epl-2.0.txt +277 -0
- repokit/licenses/gpl-2.0.txt +339 -0
- repokit/licenses/gpl-3.0.txt +674 -0
- repokit/licenses/isc.txt +15 -0
- repokit/licenses/lgpl-2.1.txt +504 -0
- repokit/licenses/lgpl-3.0.txt +165 -0
- repokit/licenses/mit.txt +21 -0
- repokit/licenses/mpl-2.0.txt +373 -0
- repokit/licenses/unlicense.txt +24 -0
- repokit/readme_blocks.py +112 -0
- repokit/remote_integration.py +627 -0
- repokit/repo_manager.py +2796 -0
- repokit/template_engine.py +391 -0
- repokit/templates/ai/claude/CLAUDE-nested.md.template +25 -0
- repokit/templates/ai/claude/CLAUDE.md.template +98 -0
- repokit/templates/ai/claude/instructions/step1_context_rebuilder.md +33 -0
- repokit/templates/ai/claude/instructions/step2_dev_workflow_process.md +67 -0
- repokit/templates/ai/claude/instructions/step3_context_bridge.md +52 -0
- repokit/templates/common/CHANGELOG.md.template +15 -0
- repokit/templates/common/CONTRIBUTING.md.template +31 -0
- repokit/templates/common/README.md.template +30 -0
- repokit/templates/common/gitattributes.template +17 -0
- repokit/templates/common/gitignore.template +141 -0
- repokit/templates/extras/FUNDING.yml.template +13 -0
- repokit/templates/extras/ROADMAP.md.template +17 -0
- repokit/templates/extras/dependabot.yml.template +22 -0
- repokit/templates/extras/one-offs-README.md.template +28 -0
- repokit/templates/extras/platform-support.md.template +11 -0
- repokit/templates/extras/pull_request_template.md.template +25 -0
- repokit/templates/extras/release-template.md.template +34 -0
- repokit/templates/extras/settings.json.template +11 -0
- repokit/templates/extras/stale.yml.template +14 -0
- repokit/templates/github/CODEOWNERS.template +11 -0
- repokit/templates/github/ISSUE_TEMPLATE/bug-report.md.template +29 -0
- repokit/templates/github/ISSUE_TEMPLATE/feature-request.md.template +19 -0
- repokit/templates/github/issues/notes.md.template +18 -0
- repokit/templates/github/issues/roadmap.md.template +15 -0
- repokit/templates/hooks/pre-commit.template +64 -0
- repokit/templates/layers/archetypes/any/claude-plugin/.claude-plugin/marketplace.json +20 -0
- repokit/templates/layers/archetypes/any/claude-plugin/plugin/.claude-plugin/plugin.json +15 -0
- repokit/templates/layers/archetypes/any/claude-plugin/plugin/README.md +28 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.github/workflows/ci-c-cpp.yml +24 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.repokit-common.toml.rkappend +4 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/launch.json +29 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/tasks.json +26 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakeLists.txt +47 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakePresets.json +23 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/cmake/GenerateStamp.cmake +45 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/src/main.cpp +27 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/tests/test_version.cpp +20 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/vcpkg.json +5 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/.vscode/launch.json +27 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/Makefile +10 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/build.bat +7 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/src/__RK_SNAKE__.c +26 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/launch.json +17 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/tasks.json +20 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.sln +19 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.vcxproj +41 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/src/main.cpp +26 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/.repokit-common.toml +28 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/.vscode/launch.json +34 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/hooks.json +15 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/scripts/session-start.mjs +23 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/tests/manifest.test.js +30 -0
- repokit/templates/layers/archetypes/js-ts/node/.repokit-common.toml.rkappend +6 -0
- repokit/templates/layers/archetypes/js-ts/node/package.json +13 -0
- repokit/templates/layers/archetypes/js-ts/node-package/.github/workflows/release.yml +39 -0
- repokit/templates/layers/archetypes/js-ts/node-package/package.json +32 -0
- repokit/templates/layers/archetypes/js-ts/node-package/src/index.ts +13 -0
- repokit/templates/layers/archetypes/js-ts/node-package/tests/index.test.ts +11 -0
- repokit/templates/layers/archetypes/js-ts/script/.vscode/launch.json +34 -0
- repokit/templates/layers/archetypes/js-ts/script/__RK_PROJECT__.js +37 -0
- repokit/templates/layers/archetypes/js-ts/script/tests/version.test.js +26 -0
- repokit/templates/layers/archetypes/js-ts/ts/package.json +18 -0
- repokit/templates/layers/archetypes/js-ts/ts/tsconfig.json +15 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.github/workflows/ci-python.yml +44 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.repokit-common.toml +21 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.vscode/launch.json +33 -0
- repokit/templates/layers/archetypes/python/claude-plugin/VERSION +1 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/hooks.json +15 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/run-hook.mjs +42 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/session_start.py +31 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/tests/test_manifest.py +42 -0
- repokit/templates/layers/archetypes/python/pypi/.github/workflows/release.yml +63 -0
- repokit/templates/layers/archetypes/python/pypi/.vscode/launch.json +43 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__init__.py +5 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__main__.py +6 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/_version.py +96 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/cli.py +18 -0
- repokit/templates/layers/archetypes/python/pypi/pyproject.toml +71 -0
- repokit/templates/layers/archetypes/python/pypi/tests/conftest.py +11 -0
- repokit/templates/layers/archetypes/python/pypi/tests/test_version.py +50 -0
- repokit/templates/layers/archetypes/python/script/.github/workflows/ci-python.yml +43 -0
- repokit/templates/layers/archetypes/python/script/.repokit-common.toml +14 -0
- repokit/templates/layers/archetypes/python/script/.vscode/launch.json +33 -0
- repokit/templates/layers/archetypes/python/script/VERSION +1 -0
- repokit/templates/layers/archetypes/python/script/__RK_PROJECT__.py +40 -0
- repokit/templates/layers/archetypes/python/script/tests/test_version.py +25 -0
- repokit/templates/layers/archetypes/python/setuppy/requirements.txt +8 -0
- repokit/templates/layers/archetypes/python/setuppy/setup.py +23 -0
- repokit/templates/layers/languages/c-cpp/.gitignore.rkappend +20 -0
- repokit/templates/layers/languages/c-cpp/.repokit-common.toml +7 -0
- repokit/templates/layers/languages/c-cpp/include/__RK_SNAKE__/version.h +20 -0
- repokit/templates/layers/languages/c-cpp/src/app.rc +25 -0
- repokit/templates/layers/languages/js-ts/.editorconfig +15 -0
- repokit/templates/layers/languages/js-ts/.github/workflows/ci-js-ts.yml +41 -0
- repokit/templates/layers/languages/js-ts/.gitignore.rkappend +13 -0
- repokit/templates/layers/languages/js-ts/.repokit-common.toml +19 -0
- repokit/templates/layers/languages/js-ts/.vscode/launch.json +24 -0
- repokit/templates/layers/languages/js-ts/VERSION +1 -0
- repokit/templates/layers/languages/python/.github/workflows/ci-python.yml +76 -0
- repokit/templates/layers/languages/python/.gitignore.rkappend +15 -0
- repokit/templates/layers/languages/python/.vscode/launch.json +33 -0
- repokit/templates/layers/manifest.toml +302 -0
- repokit/templates/vault/CLAUDE.md +29 -0
- repokit/templates/vault/claude/_maps/MOC-project.md +15 -0
- repokit/templates/vault/claude/_oracle/backlinks.md +3 -0
- repokit/templates/vault/claude/_oracle/concepts.md +12 -0
- repokit/templates/vault/claude/_oracle/manifest.md +10 -0
- repokit/templates/vault/claude/_todo/ACTIVE-WORK.md +18 -0
- repokit/templates/vault/claude/_todo/OPEN-LOOPS.md +14 -0
- repokit/utils.py +316 -0
- repokit/vault.py +248 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Authentication handler for RepoKit.
|
|
4
|
+
|
|
5
|
+
Manages authentication with remote services like GitHub and GitLab.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import subprocess
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, Any, Optional, Tuple
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AuthenticationHandler:
|
|
17
|
+
"""
|
|
18
|
+
Handles authentication with remote services.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, verbose: int = 0):
|
|
22
|
+
"""
|
|
23
|
+
Initialize the authentication handler.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
verbose: Verbosity level (0=normal, 1=info, 2=debug)
|
|
27
|
+
"""
|
|
28
|
+
self.verbose = verbose
|
|
29
|
+
self.logger = logging.getLogger("repokit.auth")
|
|
30
|
+
|
|
31
|
+
# Default locations for credentials
|
|
32
|
+
self.user_home = Path.home()
|
|
33
|
+
self.default_creds_path = self.user_home / ".repokit" / "credentials.json"
|
|
34
|
+
|
|
35
|
+
# Initialize with empty credentials
|
|
36
|
+
self.credentials = {}
|
|
37
|
+
|
|
38
|
+
def load_credentials(self, credentials_file: Optional[str] = None) -> bool:
|
|
39
|
+
"""
|
|
40
|
+
Load credentials from a file.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
credentials_file: Path to credentials file (or None to use default)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
True if loaded successfully, False otherwise
|
|
47
|
+
"""
|
|
48
|
+
if credentials_file:
|
|
49
|
+
creds_path = Path(credentials_file)
|
|
50
|
+
elif self.default_creds_path.exists():
|
|
51
|
+
creds_path = self.default_creds_path
|
|
52
|
+
else:
|
|
53
|
+
if self.verbose >= 1:
|
|
54
|
+
self.logger.info("No credentials file found")
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
with open(creds_path, "r") as f:
|
|
59
|
+
self.credentials = json.load(f)
|
|
60
|
+
|
|
61
|
+
if self.verbose >= 1:
|
|
62
|
+
self.logger.info(f"Loaded credentials from {creds_path}")
|
|
63
|
+
return True
|
|
64
|
+
except Exception as e:
|
|
65
|
+
self.logger.error(f"Error loading credentials: {str(e)}")
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
def get_token(
|
|
69
|
+
self, service: str, token_command: Optional[str] = None
|
|
70
|
+
) -> Optional[str]:
|
|
71
|
+
"""
|
|
72
|
+
Get authentication token for a service.
|
|
73
|
+
|
|
74
|
+
Tries multiple sources in this order:
|
|
75
|
+
1. Command provided token command
|
|
76
|
+
2. Environment variable
|
|
77
|
+
3. Credentials file
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
service: Service name ('github' or 'gitlab')
|
|
81
|
+
token_command: Optional command to retrieve token from password manager
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Authentication token or None if not found
|
|
85
|
+
"""
|
|
86
|
+
# Standardize service name
|
|
87
|
+
service = service.lower()
|
|
88
|
+
|
|
89
|
+
# 1. Try token command if provided
|
|
90
|
+
if token_command:
|
|
91
|
+
try:
|
|
92
|
+
if self.verbose >= 2:
|
|
93
|
+
self.logger.debug(f"Running token command: {token_command}")
|
|
94
|
+
result = subprocess.run(
|
|
95
|
+
token_command,
|
|
96
|
+
shell=True,
|
|
97
|
+
check=True,
|
|
98
|
+
capture_output=True,
|
|
99
|
+
text=True,
|
|
100
|
+
)
|
|
101
|
+
token = result.stdout.strip()
|
|
102
|
+
if token:
|
|
103
|
+
if self.verbose >= 1:
|
|
104
|
+
self.logger.info(f"Retrieved {service} token using command")
|
|
105
|
+
return token
|
|
106
|
+
except Exception as e:
|
|
107
|
+
self.logger.error(f"Failed to get token from command: {str(e)}")
|
|
108
|
+
|
|
109
|
+
# 2. Try environment variable
|
|
110
|
+
env_var = f"{service.upper()}_TOKEN"
|
|
111
|
+
token = os.environ.get(env_var)
|
|
112
|
+
if token:
|
|
113
|
+
if self.verbose >= 1:
|
|
114
|
+
self.logger.info(f"Using {service} token from environment variable")
|
|
115
|
+
return token
|
|
116
|
+
|
|
117
|
+
# 3. Try credentials file
|
|
118
|
+
if service in self.credentials and "token" in self.credentials[service]:
|
|
119
|
+
if self.verbose >= 1:
|
|
120
|
+
self.logger.info(f"Using {service} token from credentials file")
|
|
121
|
+
return self.credentials[service]["token"]
|
|
122
|
+
|
|
123
|
+
# Token not found
|
|
124
|
+
self.logger.warning(f"No {service} token found")
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
def get_organization(self, service: str) -> Optional[str]:
|
|
128
|
+
"""
|
|
129
|
+
Get organization or group for a service.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
service: Service name ('github' or 'gitlab')
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Organization/group name or None if not specified
|
|
136
|
+
"""
|
|
137
|
+
# Standardize service name
|
|
138
|
+
service = service.lower()
|
|
139
|
+
|
|
140
|
+
# Try environment variable first
|
|
141
|
+
env_var = f"{service.upper()}_ORGANIZATION"
|
|
142
|
+
if service == "gitlab":
|
|
143
|
+
env_var = "GITLAB_GROUP" # GitLab uses "group" terminology
|
|
144
|
+
|
|
145
|
+
org = os.environ.get(env_var)
|
|
146
|
+
if org:
|
|
147
|
+
return org
|
|
148
|
+
|
|
149
|
+
# Try credentials file
|
|
150
|
+
if service in self.credentials:
|
|
151
|
+
if service == "github" and "organization" in self.credentials[service]:
|
|
152
|
+
return self.credentials[service]["organization"]
|
|
153
|
+
elif service == "gitlab" and "group" in self.credentials[service]:
|
|
154
|
+
return self.credentials[service]["group"]
|
|
155
|
+
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
def get_api_url(self, service: str) -> str:
|
|
159
|
+
"""
|
|
160
|
+
Get API URL for a service.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
service: Service name ('github' or 'gitlab')
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
API URL for the service
|
|
167
|
+
"""
|
|
168
|
+
if service.lower() == "github":
|
|
169
|
+
return os.environ.get("GITHUB_API_URL", "https://api.github.com")
|
|
170
|
+
elif service.lower() == "gitlab":
|
|
171
|
+
return os.environ.get("GITLAB_API_URL", "https://gitlab.com/api/v4")
|
|
172
|
+
else:
|
|
173
|
+
return ""
|
|
174
|
+
|
|
175
|
+
def store_credentials(
|
|
176
|
+
self,
|
|
177
|
+
service: str,
|
|
178
|
+
token: str,
|
|
179
|
+
organization: Optional[str] = None,
|
|
180
|
+
file_path: Optional[str] = None,
|
|
181
|
+
) -> bool:
|
|
182
|
+
"""
|
|
183
|
+
Store credentials in the credentials file.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
service: Service name ('github' or 'gitlab')
|
|
187
|
+
token: Authentication token
|
|
188
|
+
organization: Organization or group name (optional)
|
|
189
|
+
file_path: Path to credentials file (or None to use default)
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
True if stored successfully, False otherwise
|
|
193
|
+
"""
|
|
194
|
+
# Standardize service name
|
|
195
|
+
service = service.lower()
|
|
196
|
+
|
|
197
|
+
# Load existing credentials if file exists
|
|
198
|
+
if file_path:
|
|
199
|
+
creds_path = Path(file_path)
|
|
200
|
+
else:
|
|
201
|
+
creds_path = self.default_creds_path
|
|
202
|
+
# Ensure directory exists
|
|
203
|
+
creds_path.parent.mkdir(parents=True, exist_ok=True)
|
|
204
|
+
|
|
205
|
+
# Load existing credentials if file exists
|
|
206
|
+
existing_creds = {}
|
|
207
|
+
if creds_path.exists():
|
|
208
|
+
try:
|
|
209
|
+
with open(creds_path, "r") as f:
|
|
210
|
+
existing_creds = json.load(f)
|
|
211
|
+
except Exception as e:
|
|
212
|
+
self.logger.error(f"Error loading existing credentials: {str(e)}")
|
|
213
|
+
# Continue with empty credentials
|
|
214
|
+
|
|
215
|
+
# Update credentials
|
|
216
|
+
service_creds = existing_creds.get(service, {})
|
|
217
|
+
service_creds["token"] = token
|
|
218
|
+
|
|
219
|
+
if organization:
|
|
220
|
+
if service == "github":
|
|
221
|
+
service_creds["organization"] = organization
|
|
222
|
+
elif service == "gitlab":
|
|
223
|
+
service_creds["group"] = organization
|
|
224
|
+
|
|
225
|
+
existing_creds[service] = service_creds
|
|
226
|
+
|
|
227
|
+
# Save credentials
|
|
228
|
+
try:
|
|
229
|
+
with open(creds_path, "w") as f:
|
|
230
|
+
json.dump(existing_creds, f, indent=2)
|
|
231
|
+
|
|
232
|
+
# Set restrictive permissions
|
|
233
|
+
os.chmod(creds_path, 0o600)
|
|
234
|
+
|
|
235
|
+
if self.verbose >= 1:
|
|
236
|
+
self.logger.info(f"Stored {service} credentials in {creds_path}")
|
|
237
|
+
|
|
238
|
+
# Update in-memory credentials
|
|
239
|
+
self.credentials = existing_creds
|
|
240
|
+
|
|
241
|
+
return True
|
|
242
|
+
except Exception as e:
|
|
243
|
+
self.logger.error(f"Error storing credentials: {str(e)}")
|
|
244
|
+
return False
|
repokit/badges.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""README badges in the house's shape (0.9.7).
|
|
2
|
+
|
|
3
|
+
Measured on 2026-10-25 across the 107 house projects under C:\\code
|
|
4
|
+
(tests/one-offs/thinking/p4-measure-badges/) and settled with djdarcy; the
|
|
5
|
+
design is notes/2026-10-25__00-02-16__both_readme-badge-sets__the-house-badges-and-how-repokit-picks-them.md
|
|
6
|
+
in the vault.
|
|
7
|
+
|
|
8
|
+
- TYPES is the catalogue: each badge's commonest house form, filled per
|
|
9
|
+
project, inline (`[](link)`) as the house writes them.
|
|
10
|
+
- TEMPLATES are ordered lists. The general ones follow djdarcy's slots:
|
|
11
|
+
where to get it (PyPI, else the release), when it was released, package
|
|
12
|
+
downloads, runtime, other package locations, licence, discussions,
|
|
13
|
+
ghstats, platform last (the commitment that the tools work everywhere).
|
|
14
|
+
ComfyUI keeps its own order: the Registry is where a node comes from.
|
|
15
|
+
- CI is a filler: in when it lands on a row that already exists, out when
|
|
16
|
+
it would start a row by itself (ROW badges per row).
|
|
17
|
+
- The licence's colour is its house colour (GPL/AGPL green, MIT yellow,
|
|
18
|
+
others orange), switched to the next of green, yellow, orange when it would
|
|
19
|
+
equal a neighbour's colour.
|
|
20
|
+
|
|
21
|
+
A person's own templates (`badge_templates`) and badge types
|
|
22
|
+
(`badge_types`, each naming `after` to take its place in the order) come
|
|
23
|
+
from the global config; `--badges NAME|a,b,c` or the config's `badges`
|
|
24
|
+
picks one.
|
|
25
|
+
"""
|
|
26
|
+
from typing import Dict, List, Optional, Sequence
|
|
27
|
+
|
|
28
|
+
ROW = 7 # shields badges per row of a GitHub README (to be checked on a render)
|
|
29
|
+
|
|
30
|
+
# id -> (alt, image, link, colour). {owner} {repo} {dist} {python} {node}
|
|
31
|
+
# {license_label} {license_url} {gist_user} {gist} {pages} are filled per
|
|
32
|
+
# project; {colour} is the badge's resolved colour.
|
|
33
|
+
TYPES: Dict[str, tuple] = {
|
|
34
|
+
"pypi-version": ("PyPI", "https://img.shields.io/pypi/v/{dist}?color={colour}",
|
|
35
|
+
"https://pypi.org/project/{dist}/", "green"),
|
|
36
|
+
"release-date": ("Release Date", "https://img.shields.io/github/release-date/{owner}/{repo}?color={colour}",
|
|
37
|
+
"https://github.com/{owner}/{repo}/releases", "green"),
|
|
38
|
+
"downloads": ("PyPI Downloads",
|
|
39
|
+
"https://static.pepy.tech/personalized-badge/{dist}?period=total&units=international_system"
|
|
40
|
+
"&left_color=black&right_color={colour}&left_text=downloads",
|
|
41
|
+
"https://pypistats.org/packages/{dist}", "green"),
|
|
42
|
+
"release-version": ("GitHub release",
|
|
43
|
+
"https://img.shields.io/github/v/release/{owner}/{repo}?include_prereleases&label=version",
|
|
44
|
+
"https://github.com/{owner}/{repo}/releases", "blue"),
|
|
45
|
+
"ci": ("CI", "https://github.com/{owner}/{repo}/actions/workflows/{ci}/badge.svg",
|
|
46
|
+
"https://github.com/{owner}/{repo}/actions/workflows/{ci}", "default"),
|
|
47
|
+
"python": ("Python {python}+", "https://img.shields.io/badge/python-{python}+-{colour}.svg",
|
|
48
|
+
"https://www.python.org/downloads/", "blue"),
|
|
49
|
+
"node": ("Node {node}+", "https://img.shields.io/badge/node-{node}+-{colour}.svg",
|
|
50
|
+
"https://nodejs.org/en/download", "blue"),
|
|
51
|
+
"comfyui-registry": ("ComfyUI Registry", "https://img.shields.io/badge/ComfyUI-Registry-{colour}.svg",
|
|
52
|
+
"https://registry.comfy.org/publishers/{gist_user}/nodes/{repo_lower}", "green"),
|
|
53
|
+
"license": ("License: {license_label}", "https://img.shields.io/badge/license-{license_badge}-{colour}.svg",
|
|
54
|
+
"{license_url}", "contrast"),
|
|
55
|
+
"discussions": ("GitHub Discussions", "https://img.shields.io/github/discussions/{owner}/{repo}",
|
|
56
|
+
"https://github.com/{owner}/{repo}/discussions", "blue"),
|
|
57
|
+
"installs": ("Installs",
|
|
58
|
+
"https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/{gist_user}/{gist}/raw/installs.json",
|
|
59
|
+
"https://{pages}.github.io/{repo}/stats/#installs", "blue"),
|
|
60
|
+
"views": ("Views",
|
|
61
|
+
"https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/{gist_user}/{gist}/raw/views.json",
|
|
62
|
+
"https://{pages}.github.io/{repo}/stats/#views", "blue"),
|
|
63
|
+
"platform": ("Platform", "https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-{colour}.svg",
|
|
64
|
+
"{platform_link}", "lightgrey"),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# The default order: djdarcy's slots, CI after the release slots. A badge type
|
|
68
|
+
# a person adds names its place with `after`.
|
|
69
|
+
ORDER = ["pypi-version", "release-date", "downloads", "release-version", "ci", "python", "node",
|
|
70
|
+
"comfyui-registry", "license", "discussions", "installs", "views", "platform"]
|
|
71
|
+
|
|
72
|
+
TEMPLATES: Dict[str, List[str]] = {
|
|
73
|
+
"pypi": ["pypi-version", "release-date", "downloads", "ci", "runtime", "license", "discussions",
|
|
74
|
+
"installs", "platform"],
|
|
75
|
+
"github": ["release-version", "ci", "runtime", "license", "discussions", "installs", "platform"],
|
|
76
|
+
# its own order: the Registry is where a node comes from (djdarcy, 2026-10-25)
|
|
77
|
+
"comfyui": ["runtime", "comfyui-registry", "release-version", "license", "installs", "views"],
|
|
78
|
+
"minimal": ["runtime", "license"],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# The default template for a preset
|
|
82
|
+
PRESET_TEMPLATE = {"python/pypi": "pypi", "python/comfyui": "comfyui"}
|
|
83
|
+
|
|
84
|
+
# licence id -> (badge text, the licence's page, house colour)
|
|
85
|
+
LICENSES = {
|
|
86
|
+
"gpl-3.0": ("GPL v3", "https://www.gnu.org/licenses/gpl-3.0.html", "green"),
|
|
87
|
+
"gpl-3.0-or-later": ("GPL v3", "https://www.gnu.org/licenses/gpl-3.0.html", "green"),
|
|
88
|
+
"agpl-3.0": ("AGPL v3", "https://www.gnu.org/licenses/agpl-3.0.html", "green"),
|
|
89
|
+
"agpl-3.0-or-later": ("AGPL v3", "https://www.gnu.org/licenses/agpl-3.0.html", "green"),
|
|
90
|
+
"lgpl-3.0": ("LGPL v3", "https://www.gnu.org/licenses/lgpl-3.0.html", "green"),
|
|
91
|
+
"lgpl-3.0-or-later": ("LGPL v3", "https://www.gnu.org/licenses/lgpl-3.0.html", "green"),
|
|
92
|
+
"gpl-2.0": ("GPL v2", "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html", "green"),
|
|
93
|
+
"mit": ("MIT", "https://opensource.org/licenses/MIT", "yellow"),
|
|
94
|
+
"apache-2.0": ("Apache 2.0", "https://opensource.org/licenses/Apache-2.0", "orange"),
|
|
95
|
+
}
|
|
96
|
+
CONTRAST = ["green", "yellow", "orange"]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class BadgeError(ValueError):
|
|
100
|
+
"""A template or badge name the catalogue does not know."""
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _shields_text(text: str) -> str:
|
|
104
|
+
"""A static shields badge's text: '-' doubled, '_' doubled, spaces escaped."""
|
|
105
|
+
return text.replace("-", "--").replace("_", "__").replace(" ", "%20")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def resolve(request, archetype: Optional[str], templates: Optional[dict] = None,
|
|
109
|
+
types: Optional[dict] = None) -> List[str]:
|
|
110
|
+
"""The badge ids for a request: a template name, a list of ids, or None
|
|
111
|
+
(the preset's default template)."""
|
|
112
|
+
templates = {**TEMPLATES, **(templates or {})}
|
|
113
|
+
known = set(TYPES) | set(types or {}) | {"runtime"}
|
|
114
|
+
if request is None or request == "":
|
|
115
|
+
request = PRESET_TEMPLATE.get(archetype or "", "github")
|
|
116
|
+
if isinstance(request, str) and "," not in request:
|
|
117
|
+
if request not in templates:
|
|
118
|
+
if request in known:
|
|
119
|
+
return [request]
|
|
120
|
+
raise BadgeError(f"unknown badge template {request!r}; known: {', '.join(sorted(templates))}")
|
|
121
|
+
names = list(templates[request])
|
|
122
|
+
else:
|
|
123
|
+
names = [n.strip() for n in (request.split(",") if isinstance(request, str) else request) if n.strip()]
|
|
124
|
+
order = ORDER + [t for t in (types or {})]
|
|
125
|
+
names = sorted(dict.fromkeys(names), key=lambda n: order.index(n) if n in order else len(order))
|
|
126
|
+
unknown = [n for n in names if n not in known]
|
|
127
|
+
if unknown:
|
|
128
|
+
raise BadgeError(f"unknown badges {', '.join(unknown)}; known: {', '.join(sorted(known))}")
|
|
129
|
+
return names
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def render(names: Sequence[str], values: Dict[str, str], ci: str = "auto",
|
|
133
|
+
types: Optional[dict] = None) -> str:
|
|
134
|
+
"""The badge lines, one per line, inline. `values` fills the tokens; a
|
|
135
|
+
badge whose required value is missing (no CI file, no runtime floor) is
|
|
136
|
+
left out. `ci` is auto, on or off."""
|
|
137
|
+
catalogue = dict(TYPES)
|
|
138
|
+
for tid, spec in (types or {}).items(): # a person's own badge lines
|
|
139
|
+
catalogue[tid] = (spec.get("alt", tid), spec["image"], spec.get("link", ""), spec.get("colour", "default"))
|
|
140
|
+
picked = []
|
|
141
|
+
for n in names:
|
|
142
|
+
if n == "runtime":
|
|
143
|
+
n = "python" if values.get("python") else "node" if values.get("node") else None
|
|
144
|
+
if not n:
|
|
145
|
+
continue
|
|
146
|
+
if n == "ci" and not values.get("ci"):
|
|
147
|
+
continue
|
|
148
|
+
if n == "license" and not values.get("license_badge"):
|
|
149
|
+
continue
|
|
150
|
+
if n in ("python",) and not values.get("python"):
|
|
151
|
+
continue
|
|
152
|
+
if n in ("node",) and not values.get("node"):
|
|
153
|
+
continue
|
|
154
|
+
picked.append(n)
|
|
155
|
+
if "ci" in picked and ci != "on":
|
|
156
|
+
others = len(picked) - 1
|
|
157
|
+
if ci == "off" or (others and others % ROW == 0):
|
|
158
|
+
picked.remove("ci")
|
|
159
|
+
colours = [catalogue[n][3] for n in picked]
|
|
160
|
+
for i, n in enumerate(picked):
|
|
161
|
+
if colours[i] != "contrast":
|
|
162
|
+
continue
|
|
163
|
+
neighbours = {colours[j] for j in (i - 1, i + 1) if 0 <= j < len(picked)}
|
|
164
|
+
neighbours |= {"blue"} if "default" in neighbours else set() # shields' default reads blue
|
|
165
|
+
house = values.get("license_colour") or "orange"
|
|
166
|
+
colours[i] = house if house not in neighbours else next(
|
|
167
|
+
(c for c in CONTRAST if c not in neighbours), house)
|
|
168
|
+
lines = []
|
|
169
|
+
for n, colour in zip(picked, colours):
|
|
170
|
+
alt, image, link, _ = catalogue[n]
|
|
171
|
+
fill = {**values, "colour": colour}
|
|
172
|
+
lines.append(f"[})]({link.format(**fill)})")
|
|
173
|
+
return "\n".join(lines)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def license_values(lid: Optional[str], spdx: Optional[str]) -> Dict[str, str]:
|
|
177
|
+
"""The licence badge's text, link and house colour; empty when the project
|
|
178
|
+
has no catalogue licence (a file, none, or all rights reserved)."""
|
|
179
|
+
if not lid or not spdx or spdx.startswith("LicenseRef"):
|
|
180
|
+
return {}
|
|
181
|
+
label, url, colour = LICENSES.get(lid, (spdx, "LICENSE", "orange"))
|
|
182
|
+
return {"license_label": label, "license_badge": _shields_text(label), "license_url": url,
|
|
183
|
+
"license_colour": colour}
|