hull-core 0.1.0b1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. hull_core-0.1.0b1/.github/CODEOWNERS +2 -0
  2. hull_core-0.1.0b1/.github/ISSUE_TEMPLATE/bug_report.md +34 -0
  3. hull_core-0.1.0b1/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  4. hull_core-0.1.0b1/.github/PULL_REQUEST_TEMPLATE.md +25 -0
  5. hull_core-0.1.0b1/.github/repo-settings.json +12 -0
  6. hull_core-0.1.0b1/.github/rulesets/main.json +64 -0
  7. hull_core-0.1.0b1/.github/workflows/cd.yml +388 -0
  8. hull_core-0.1.0b1/.github/workflows/ci.yml +294 -0
  9. hull_core-0.1.0b1/.github/workflows/codeql.yml +30 -0
  10. hull_core-0.1.0b1/.gitignore +24 -0
  11. hull_core-0.1.0b1/.mise.toml +53 -0
  12. hull_core-0.1.0b1/.pre-commit-config.yaml +123 -0
  13. hull_core-0.1.0b1/.python-version +1 -0
  14. hull_core-0.1.0b1/.repo-bootstrap.toml +15 -0
  15. hull_core-0.1.0b1/.skret.yaml +8 -0
  16. hull_core-0.1.0b1/AGENTS.md +28 -0
  17. hull_core-0.1.0b1/CHANGELOG.md +3 -0
  18. hull_core-0.1.0b1/CODE_OF_CONDUCT.md +46 -0
  19. hull_core-0.1.0b1/CONTRIBUTING.md +21 -0
  20. hull_core-0.1.0b1/LICENSE +201 -0
  21. hull_core-0.1.0b1/PKG-INFO +135 -0
  22. hull_core-0.1.0b1/README.md +86 -0
  23. hull_core-0.1.0b1/SECURITY.md +15 -0
  24. hull_core-0.1.0b1/packages/core-py/README.md +23 -0
  25. hull_core-0.1.0b1/packages/core-py/src/hull_core/__init__.py +36 -0
  26. hull_core-0.1.0b1/packages/core-py/src/hull_core/auth/__init__.py +33 -0
  27. hull_core-0.1.0b1/packages/core-py/src/hull_core/auth/asgi.py +75 -0
  28. hull_core-0.1.0b1/packages/core-py/src/hull_core/auth/context.py +47 -0
  29. hull_core-0.1.0b1/packages/core-py/src/hull_core/auth/middleware.py +105 -0
  30. hull_core-0.1.0b1/packages/core-py/src/hull_core/auth/tokens.py +83 -0
  31. hull_core-0.1.0b1/packages/core-py/src/hull_core/auth/users.py +131 -0
  32. hull_core-0.1.0b1/packages/core-py/src/hull_core/cli.py +107 -0
  33. hull_core-0.1.0b1/packages/core-py/src/hull_core/config/__init__.py +39 -0
  34. hull_core-0.1.0b1/packages/core-py/src/hull_core/config/models.py +59 -0
  35. hull_core-0.1.0b1/packages/core-py/src/hull_core/config/settings.py +132 -0
  36. hull_core-0.1.0b1/packages/core-py/src/hull_core/crypto/__init__.py +5 -0
  37. hull_core-0.1.0b1/packages/core-py/src/hull_core/crypto/timing.py +16 -0
  38. hull_core-0.1.0b1/packages/core-py/src/hull_core/http/__init__.py +23 -0
  39. hull_core-0.1.0b1/packages/core-py/src/hull_core/http/ssrf.py +297 -0
  40. hull_core-0.1.0b1/packages/core-py/src/hull_core/lifecycle/__init__.py +5 -0
  41. hull_core-0.1.0b1/packages/core-py/src/hull_core/lifecycle/lock.py +278 -0
  42. hull_core-0.1.0b1/packages/core-py/src/hull_core/limits/__init__.py +5 -0
  43. hull_core-0.1.0b1/packages/core-py/src/hull_core/limits/limiter.py +34 -0
  44. hull_core-0.1.0b1/packages/core-py/src/hull_core/providers/__init__.py +5 -0
  45. hull_core-0.1.0b1/packages/core-py/src/hull_core/providers/openai_spec.py +147 -0
  46. hull_core-0.1.0b1/packages/core-py/src/hull_core/server/__init__.py +6 -0
  47. hull_core-0.1.0b1/packages/core-py/src/hull_core/server/app.py +78 -0
  48. hull_core-0.1.0b1/packages/core-py/src/hull_core/server/run.py +57 -0
  49. hull_core-0.1.0b1/packages/core-py/src/hull_core/storage/__init__.py +5 -0
  50. hull_core-0.1.0b1/packages/core-py/src/hull_core/storage/sqlite.py +69 -0
  51. hull_core-0.1.0b1/packages/core-py/tests/auth/test_authenticator.py +106 -0
  52. hull_core-0.1.0b1/packages/core-py/tests/auth/test_middleware.py +81 -0
  53. hull_core-0.1.0b1/packages/core-py/tests/auth/test_tokens.py +31 -0
  54. hull_core-0.1.0b1/packages/core-py/tests/auth/test_users.py +150 -0
  55. hull_core-0.1.0b1/packages/core-py/tests/config/test_models.py +52 -0
  56. hull_core-0.1.0b1/packages/core-py/tests/config/test_settings.py +89 -0
  57. hull_core-0.1.0b1/packages/core-py/tests/http/test_ssrf.py +203 -0
  58. hull_core-0.1.0b1/packages/core-py/tests/lifecycle/test_lock.py +401 -0
  59. hull_core-0.1.0b1/packages/core-py/tests/limits/test_limiter.py +28 -0
  60. hull_core-0.1.0b1/packages/core-py/tests/providers/test_openai_spec.py +264 -0
  61. hull_core-0.1.0b1/packages/core-py/tests/server/test_app.py +159 -0
  62. hull_core-0.1.0b1/packages/core-py/tests/server/test_run.py +111 -0
  63. hull_core-0.1.0b1/packages/core-py/tests/storage/test_sqlite.py +47 -0
  64. hull_core-0.1.0b1/packages/core-py/tests/test_cli.py +69 -0
  65. hull_core-0.1.0b1/packages/core-py/tests/test_crypto.py +42 -0
  66. hull_core-0.1.0b1/packages/embedding-daemon/README.md +16 -0
  67. hull_core-0.1.0b1/packages/embedding-daemon/src/hull_embedding_daemon/__init__.py +6 -0
  68. hull_core-0.1.0b1/packages/embedding-daemon/src/hull_embedding_daemon/__main__.py +53 -0
  69. hull_core-0.1.0b1/packages/embedding-daemon/src/hull_embedding_daemon/_extra.py +45 -0
  70. hull_core-0.1.0b1/packages/embedding-daemon/src/hull_embedding_daemon/api.py +82 -0
  71. hull_core-0.1.0b1/packages/embedding-daemon/src/hull_embedding_daemon/backends/__init__.py +0 -0
  72. hull_core-0.1.0b1/packages/embedding-daemon/src/hull_embedding_daemon/backends/gguf.py +17 -0
  73. hull_core-0.1.0b1/packages/embedding-daemon/src/hull_embedding_daemon/backends/onnx.py +17 -0
  74. hull_core-0.1.0b1/packages/embedding-daemon/tests/test_api.py +89 -0
  75. hull_core-0.1.0b1/packages/embedding-daemon/tests/test_extra_guard.py +80 -0
  76. hull_core-0.1.0b1/packages/embedding-daemon/tests/test_gguf.py +26 -0
  77. hull_core-0.1.0b1/packages/embedding-daemon/tests/test_main.py +59 -0
  78. hull_core-0.1.0b1/packages/embedding-daemon/tests/test_onnx.py +26 -0
  79. hull_core-0.1.0b1/packages/embedding-daemon/tests/test_version.py +26 -0
  80. hull_core-0.1.0b1/packages/web/README.md +30 -0
  81. hull_core-0.1.0b1/packages/web/scripts/measure_identity_ab.py +243 -0
  82. hull_core-0.1.0b1/packages/web/src/hull_web/__init__.py +39 -0
  83. hull_core-0.1.0b1/packages/web/src/hull_web/_extra.py +51 -0
  84. hull_core-0.1.0b1/packages/web/src/hull_web/adapters/__init__.py +10 -0
  85. hull_core-0.1.0b1/packages/web/src/hull_web/adapters/mangadex.py +403 -0
  86. hull_core-0.1.0b1/packages/web/src/hull_web/browsers/__init__.py +19 -0
  87. hull_core-0.1.0b1/packages/web/src/hull_web/browsers/browserless.py +66 -0
  88. hull_core-0.1.0b1/packages/web/src/hull_web/browsers/cf_render.py +83 -0
  89. hull_core-0.1.0b1/packages/web/src/hull_web/browsers/interact.py +132 -0
  90. hull_core-0.1.0b1/packages/web/src/hull_web/browsers/invisible.py +76 -0
  91. hull_core-0.1.0b1/packages/web/src/hull_web/browsers/patchright.py +73 -0
  92. hull_core-0.1.0b1/packages/web/src/hull_web/browsers/protocol.py +27 -0
  93. hull_core-0.1.0b1/packages/web/src/hull_web/fingerprint.py +209 -0
  94. hull_core-0.1.0b1/packages/web/src/hull_web/http/__init__.py +13 -0
  95. hull_core-0.1.0b1/packages/web/src/hull_web/http/client.py +233 -0
  96. hull_core-0.1.0b1/packages/web/src/hull_web/http/url.py +137 -0
  97. hull_core-0.1.0b1/packages/web/src/hull_web/py.typed +0 -0
  98. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/__init__.py +19 -0
  99. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/agent.py +391 -0
  100. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/base.py +31 -0
  101. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/cache.py +117 -0
  102. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/robots.py +110 -0
  103. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/selector_inference.py +212 -0
  104. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/state.py +37 -0
  105. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/strategies/__init__.py +22 -0
  106. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/strategies/api_direct.py +80 -0
  107. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/strategies/basic_http.py +101 -0
  108. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/strategies/captcha.py +295 -0
  109. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/strategies/headless.py +106 -0
  110. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/strategies/invisible_browser.py +32 -0
  111. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/strategies/patchright_browser.py +194 -0
  112. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/strategies/remote_render.py +49 -0
  113. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/strategies/tls_spoof.py +97 -0
  114. hull_core-0.1.0b1/packages/web/src/hull_web/scraper/utils.py +190 -0
  115. hull_core-0.1.0b1/packages/web/src/hull_web/search/__init__.py +7 -0
  116. hull_core-0.1.0b1/packages/web/src/hull_web/search/client.py +294 -0
  117. hull_core-0.1.0b1/packages/web/src/hull_web/search/models.py +33 -0
  118. hull_core-0.1.0b1/packages/web/src/hull_web/search/runner.py +1448 -0
  119. hull_core-0.1.0b1/packages/web/tests/__init__.py +0 -0
  120. hull_core-0.1.0b1/packages/web/tests/conftest.py +34 -0
  121. hull_core-0.1.0b1/packages/web/tests/test_adapters/__init__.py +0 -0
  122. hull_core-0.1.0b1/packages/web/tests/test_adapters/test_mangadex.py +961 -0
  123. hull_core-0.1.0b1/packages/web/tests/test_browsers/__init__.py +0 -0
  124. hull_core-0.1.0b1/packages/web/tests/test_browsers/test_browserless.py +77 -0
  125. hull_core-0.1.0b1/packages/web/tests/test_browsers/test_interact.py +55 -0
  126. hull_core-0.1.0b1/packages/web/tests/test_browsers/test_invisible.py +130 -0
  127. hull_core-0.1.0b1/packages/web/tests/test_browsers/test_patchright.py +109 -0
  128. hull_core-0.1.0b1/packages/web/tests/test_browsers/test_protocol.py +84 -0
  129. hull_core-0.1.0b1/packages/web/tests/test_extra_guard.py +65 -0
  130. hull_core-0.1.0b1/packages/web/tests/test_http/__init__.py +0 -0
  131. hull_core-0.1.0b1/packages/web/tests/test_http/test_client.py +463 -0
  132. hull_core-0.1.0b1/packages/web/tests/test_http/test_ssrf_bypass.py +23 -0
  133. hull_core-0.1.0b1/packages/web/tests/test_http/test_ssrf_whitelist.py +74 -0
  134. hull_core-0.1.0b1/packages/web/tests/test_http/test_url.py +334 -0
  135. hull_core-0.1.0b1/packages/web/tests/test_scraper/__init__.py +0 -0
  136. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_agent.py +372 -0
  137. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_base.py +102 -0
  138. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_cache.py +242 -0
  139. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_identity.py +127 -0
  140. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_robots.py +120 -0
  141. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_selector_inference.py +358 -0
  142. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_state.py +101 -0
  143. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_strategies/__init__.py +0 -0
  144. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_strategies/test_api_direct.py +282 -0
  145. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_strategies/test_basic_http.py +185 -0
  146. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_strategies/test_browser_ssrf.py +113 -0
  147. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_strategies/test_captcha.py +558 -0
  148. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_strategies/test_headless.py +328 -0
  149. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_strategies/test_identity_wiring.py +143 -0
  150. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_strategies/test_patchright_strategy.py +361 -0
  151. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_strategies/test_remote_render.py +68 -0
  152. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_strategies/test_ssrf_protection.py +37 -0
  153. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_strategies/test_tls_spoof.py +251 -0
  154. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_utils.py +310 -0
  155. hull_core-0.1.0b1/packages/web/tests/test_scraper/test_validate_under_rendered.py +106 -0
  156. hull_core-0.1.0b1/packages/web/tests/test_search/__init__.py +0 -0
  157. hull_core-0.1.0b1/packages/web/tests/test_search/conftest.py +61 -0
  158. hull_core-0.1.0b1/packages/web/tests/test_search/test_build_filtered_query.py +137 -0
  159. hull_core-0.1.0b1/packages/web/tests/test_search/test_client.py +777 -0
  160. hull_core-0.1.0b1/packages/web/tests/test_search/test_models.py +98 -0
  161. hull_core-0.1.0b1/packages/web/tests/test_search/test_runner.py +1572 -0
  162. hull_core-0.1.0b1/packages/web/tests/test_search/test_runner_security.py +54 -0
  163. hull_core-0.1.0b1/packages/web/tests/test_search/test_searxng_lock.py +121 -0
  164. hull_core-0.1.0b1/packages/web/tests/test_search/test_write_secure_text.py +52 -0
  165. hull_core-0.1.0b1/pyproject.toml +159 -0
  166. hull_core-0.1.0b1/renovate.json +44 -0
  167. hull_core-0.1.0b1/scripts/enforce-commit.sh +8 -0
  168. hull_core-0.1.0b1/scripts/preserve-diacritics.py +287 -0
  169. hull_core-0.1.0b1/scripts/repo-bootstrap/_lib.py +542 -0
  170. hull_core-0.1.0b1/scripts/repo-bootstrap/verify_readme_sync.py +511 -0
  171. hull_core-0.1.0b1/tests/test_extra_modules_sync.py +44 -0
  172. hull_core-0.1.0b1/tests/test_packaging.py +159 -0
  173. hull_core-0.1.0b1/uv.lock +4172 -0
@@ -0,0 +1,2 @@
1
+ # Default code owner — required reviewer for all PRs
2
+ * @n24q02m
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+ ---
8
+
9
+ **Describe the bug**
10
+ A clear and concise description of what the bug is.
11
+
12
+ **To Reproduce**
13
+ Steps to reproduce the behavior:
14
+
15
+ 1. Go to '...'
16
+ 2. Click on '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Screenshots**
24
+ If applicable, add screenshots to help explain your problem.
25
+
26
+ **Environment (please complete the following information):**
27
+
28
+ - OS: [e.g. macOS, Linux, Windows]
29
+ - Go version: [output of `go version`]
30
+ - hull-core version: [output of `hull-core --version`]
31
+ - Installation method: [e.g. go install, Docker, brew, scoop, apt, direct binary]
32
+
33
+ **Additional context**
34
+ Add any other context about the problem here.
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+ ---
8
+
9
+ **Is your feature request related to a problem? Please describe.**
10
+ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11
+
12
+ **Describe the solution you'd like**
13
+ A clear and concise description of what you want to happen.
14
+
15
+ **Describe alternatives you've considered**
16
+ A clear and concise description of any alternative solutions or features you've considered.
17
+
18
+ **Additional context**
19
+ Add any other context or screenshots about the feature request here.
@@ -0,0 +1,25 @@
1
+ ## Description
2
+ <!-- Describe what this PR does and why -->
3
+
4
+ ## Changes
5
+ <!-- List the key changes -->
6
+ -
7
+
8
+ ## Type of Change
9
+ - [ ] Bug fix (non-breaking change which fixes an issue)
10
+ - [ ] New feature (non-breaking change which adds functionality)
11
+ - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
12
+
13
+ ## Testing
14
+ - [ ] Existing tests pass
15
+ - [ ] Added new tests for the changes
16
+ - [ ] Tested manually
17
+
18
+ ## Screenshots
19
+ <!-- If applicable, add screenshots to help explain your changes -->
20
+
21
+ ## Checklist
22
+ - [ ] My code follows the project's coding standards
23
+ - [ ] I have commented my code where necessary
24
+ - [ ] I have updated documentation if needed
25
+ - [ ] My changes generate no new warnings
@@ -0,0 +1,12 @@
1
+ {
2
+ "schema_version": 1,
3
+ "tier": 2,
4
+ "description": "hull is the shared monorepo core for the wet, crg and mnemo self-hosted stack",
5
+ "homepage": "",
6
+ "topics": [
7
+ "mcp",
8
+ "model-context-protocol",
9
+ "python",
10
+ "self-hosted"
11
+ ]
12
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "Main Branch Rules",
3
+ "target": "branch",
4
+ "source_type": "Repository",
5
+ "source": "n24q02m/hull",
6
+ "enforcement": "active",
7
+ "bypass_actors": [
8
+ {
9
+ "actor_id": 5,
10
+ "actor_type": "RepositoryRole",
11
+ "bypass_mode": "always"
12
+ },
13
+ {
14
+ "actor_id": 3200052,
15
+ "actor_type": "Integration",
16
+ "bypass_mode": "always"
17
+ }
18
+ ],
19
+ "conditions": {
20
+ "ref_name": {
21
+ "exclude": [],
22
+ "include": [
23
+ "~DEFAULT_BRANCH"
24
+ ]
25
+ }
26
+ },
27
+ "rules": [
28
+ {
29
+ "type": "deletion"
30
+ },
31
+ {
32
+ "type": "non_fast_forward"
33
+ },
34
+ {
35
+ "type": "required_linear_history"
36
+ },
37
+ {
38
+ "type": "pull_request",
39
+ "parameters": {
40
+ "required_approving_review_count": 0,
41
+ "dismiss_stale_reviews_on_push": true,
42
+ "require_code_owner_review": false,
43
+ "require_last_push_approval": false,
44
+ "required_review_thread_resolution": false,
45
+ "require_extra_approval_for_unattributed_changes": true,
46
+ "allowed_merge_methods": [
47
+ "squash"
48
+ ]
49
+ }
50
+ },
51
+ {
52
+ "type": "code_scanning",
53
+ "parameters": {
54
+ "code_scanning_tools": [
55
+ {
56
+ "tool": "CodeQL",
57
+ "security_alerts_threshold": "high_or_higher",
58
+ "alerts_threshold": "errors"
59
+ }
60
+ ]
61
+ }
62
+ }
63
+ ]
64
+ }
@@ -0,0 +1,388 @@
1
+ # =============================================================================
2
+ # CD Template: Python. Publish steps are opt-in per repo via
3
+ # vars.PUBLISH_PYPI / vars.PUBLISH_DOCKER / vars.PUBLISH_MCP_REGISTRY.
4
+ # Managed by n24q02m repo-bootstrap; local changes may be overwritten.
5
+ #
6
+ # Pipeline: PSR v10 -> PyPI -> Docker multi-arch (amd64+arm64) -> MCP Registry
7
+ # CUSTOMIZE: Search for "# CUSTOMIZE:" comments and adjust for your repo.
8
+ # =============================================================================
9
+ name: CD
10
+
11
+ on:
12
+ workflow_dispatch:
13
+ inputs:
14
+ release_type:
15
+ description: "Release type"
16
+ required: true
17
+ type: choice
18
+ options:
19
+ - beta
20
+ - stable
21
+ publish_existing_tag:
22
+ description: "Recovery only: publish an already-created tag (e.g. v3.1.0), skipping semantic-release. Use when the auto-computed version's tag name is permanently reserved by a GitHub immutable release."
23
+ required: false
24
+ type: string
25
+ default: ""
26
+
27
+ permissions:
28
+ contents: read
29
+
30
+ env:
31
+ # Release lane: dispatch-only (release_type=beta, then stable); a push never releases.
32
+ RELEASE_TYPE: ${{ inputs.release_type }}
33
+ DOCKERHUB_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/hull-core
34
+ GHCR_IMAGE: ghcr.io/${{ github.repository }}
35
+
36
+ concurrency:
37
+ group: release
38
+ cancel-in-progress: false
39
+
40
+ jobs:
41
+ # ============================================================================
42
+ # README registry metadata: the package carries the repo's README to PyPI, so
43
+ # a manifest that names a file which is not there publishes an empty project
44
+ # page. Runs before the release rather than beside it -- a PyPI upload cannot
45
+ # be undone, only yanked.
46
+ # ============================================================================
47
+ readme-sync:
48
+ name: Verify README registry metadata
49
+ runs-on: ubuntu-latest
50
+ steps:
51
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
52
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
53
+ with:
54
+ python-version: "3.13"
55
+ - name: Verify README registry metadata
56
+ run: python scripts/repo-bootstrap/verify_readme_sync.py --repo-root=.
57
+
58
+ # ============================================================================
59
+ # Release: PSR v10
60
+ # ============================================================================
61
+ release:
62
+ name: Semantic Release
63
+ runs-on: ubuntu-latest
64
+ outputs:
65
+ released: ${{ steps.release.outputs.released || steps.forced.outputs.released }}
66
+ tag: ${{ steps.release.outputs.tag || steps.forced.outputs.tag }}
67
+ version: ${{ steps.release.outputs.version || steps.forced.outputs.version }}
68
+ is_prerelease: ${{ steps.release.outputs.is_prerelease || steps.forced.outputs.is_prerelease }}
69
+ steps:
70
+ - name: Harden Runner
71
+ uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
72
+ with:
73
+ egress-policy: audit
74
+
75
+ - name: Generate GitHub App Token
76
+ id: app-token
77
+ uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
78
+ with:
79
+ app-id: ${{ vars.CI_APP_ID }}
80
+ private-key: ${{ secrets.CI_APP_KEY }}
81
+
82
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
83
+ with:
84
+ token: ${{ steps.app-token.outputs.token }}
85
+ fetch-depth: 0
86
+
87
+ # Version bump runs on the fork (orphan-tag + registry-collision guards).
88
+ # The publish step below stays upstream — the fork does not replace it.
89
+ - uses: n24q02m/better-semantic-release@087b84e8d2ba75bdec350924d3bf8247088e0b1a # v1.4.0
90
+ id: release
91
+ if: inputs.publish_existing_tag == ''
92
+ with:
93
+ github_token: ${{ steps.app-token.outputs.token }}
94
+ prerelease: ${{ env.RELEASE_TYPE == 'beta' }}
95
+ prerelease_token: beta
96
+
97
+ - uses: python-semantic-release/publish-action@5a5718ce47b892ef699f2972dae122297771d641 # v10.6.1
98
+ if: inputs.publish_existing_tag == '' && steps.release.outputs.released == 'true'
99
+ with:
100
+ github_token: ${{ steps.app-token.outputs.token }}
101
+ tag: ${{ steps.release.outputs.tag }}
102
+
103
+ - name: Use existing tag (immutable-release recovery)
104
+ id: forced
105
+ if: inputs.publish_existing_tag != ''
106
+ run: |
107
+ TAG="${{ inputs.publish_existing_tag }}"
108
+ VERSION="${TAG#v}"
109
+ {
110
+ echo "released=true"
111
+ echo "tag=$TAG"
112
+ echo "version=$VERSION"
113
+ echo "is_prerelease=false"
114
+ } >> "$GITHUB_OUTPUT"
115
+
116
+ # ============================================================================
117
+ # Notify Downstream
118
+ # ============================================================================
119
+ # A core/shared repo cutting a stable release has to open a tracking issue in
120
+ # every repo that pins it, or downstream pins drift silently. The dispatch goes
121
+ # to one hub repo that fans the issues out; `n24q02m/n24q02m` is that hub as a
122
+ # literal, copied from the repo this template was measured against rather than
123
+ # derived from `n24q02m` -- the hub happening to share the owner's name is not
124
+ # a rule this template gets to assume.
125
+ #
126
+ # Stable only: `is_prerelease != 'true'`. A beta cutting downstream issues would
127
+ # file the same issue again at stable.
128
+ #
129
+ # New repositories and repositories already using the gate require
130
+ # NOTIFY_DOWNSTREAM, because this template also lands on non-core Python repos.
131
+ # During retrofit, an existing ungated notify-downstream job stays ungated:
132
+ # silently adding an unset-variable gate would stop a live release behavior.
133
+ # apply.py detects only that existing job block; it does not infer core status
134
+ # from a repository name.
135
+ notify-downstream:
136
+ name: Notify Downstream
137
+ needs: [release]
138
+ if: >-
139
+ needs.release.outputs.released == 'true'
140
+ && needs.release.outputs.is_prerelease != 'true'
141
+ && vars.NOTIFY_DOWNSTREAM == 'true'
142
+ runs-on: ubuntu-latest
143
+ steps:
144
+ - name: Harden Runner
145
+ uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
146
+ with:
147
+ egress-policy: audit
148
+
149
+ - name: Generate GitHub App Token
150
+ id: app-token
151
+ uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
152
+ with:
153
+ app-id: ${{ vars.CI_APP_ID }}
154
+ private-key: ${{ secrets.CI_APP_KEY }}
155
+ # Without owner/repositories the token is scoped to THIS repo only
156
+ # and the dispatch 403s ("Resource not accessible by integration").
157
+ # Scope to the target repo, nothing more.
158
+ owner: n24q02m
159
+ repositories: n24q02m
160
+
161
+ - name: Dispatch core-update
162
+ env:
163
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
164
+ run: |
165
+ gh api repos/n24q02m/n24q02m/dispatches \
166
+ -f event_type=core-update \
167
+ -f 'client_payload[core]=hull-core' \
168
+ -f 'client_payload[version]=${{ needs.release.outputs.version }}' \
169
+ -f 'client_payload[tag]=${{ needs.release.outputs.tag }}' \
170
+ -f 'client_payload[release_url]=https://github.com/${{ github.repository }}/releases/tag/${{ needs.release.outputs.tag }}'
171
+
172
+ # ============================================================================
173
+ # Publish to PyPI
174
+ # ============================================================================
175
+ publish-pypi:
176
+ name: Publish to PyPI
177
+ needs: [readme-sync, release]
178
+ if: needs.release.outputs.released == 'true' && vars.PUBLISH_PYPI == 'true'
179
+ runs-on: ubuntu-latest
180
+ environment: pypi
181
+ permissions:
182
+ contents: read
183
+ id-token: write
184
+ steps:
185
+ - name: Harden Runner
186
+ uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
187
+ with:
188
+ egress-policy: audit
189
+
190
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
191
+ with:
192
+ ref: ${{ needs.release.outputs.tag }}
193
+ - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
194
+ - run: uv build
195
+ - name: Publish to PyPI
196
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
197
+
198
+ # ============================================================================
199
+ # Build Docker (multi-arch: amd64 + arm64)
200
+ # ============================================================================
201
+ build-docker:
202
+ name: Build Docker (${{ matrix.platform }})
203
+ # readme-sync, because this job pushes an image to a registry and
204
+ # merge-docker then publishes ./README.md as the Docker Hub description.
205
+ # The gate is only a gate if it runs BEFORE the push.
206
+ needs: [readme-sync, release]
207
+ if: needs.release.outputs.released == 'true' && vars.PUBLISH_DOCKER == 'true'
208
+ permissions:
209
+ contents: read
210
+ packages: write
211
+ strategy:
212
+ fail-fast: false
213
+ matrix:
214
+ include:
215
+ - platform: linux/amd64
216
+ runner: ubuntu-latest
217
+ artifact: linux-amd64
218
+ - platform: linux/arm64
219
+ runner: ubuntu-24.04-arm
220
+ artifact: linux-arm64
221
+ runs-on: ${{ matrix.runner }}
222
+ steps:
223
+ - name: Harden Runner
224
+ uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
225
+ with:
226
+ egress-policy: audit
227
+
228
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
229
+ with:
230
+ ref: ${{ needs.release.outputs.tag }}
231
+ - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
232
+ - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
233
+ with:
234
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
235
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
236
+ - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
237
+ with:
238
+ registry: ghcr.io
239
+ username: ${{ github.actor }}
240
+ password: ${{ secrets.GITHUB_TOKEN }}
241
+ - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
242
+ id: build
243
+ with:
244
+ context: .
245
+ platforms: ${{ matrix.platform }}
246
+ outputs: type=image,"name=${{ env.DOCKERHUB_IMAGE }},${{ env.GHCR_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
247
+ cache-from: type=gha,scope=${{ github.ref_name }}-${{ matrix.artifact }}
248
+ cache-to: type=gha,mode=max,scope=${{ github.ref_name }}-${{ matrix.artifact }}
249
+ - run: |
250
+ mkdir -p ${{ runner.temp }}/digests
251
+ echo "${{ steps.build.outputs.digest }}" > "${{ runner.temp }}/digests/${{ matrix.artifact }}"
252
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
253
+ with:
254
+ name: digest-${{ matrix.artifact }}
255
+ path: ${{ runner.temp }}/digests/*
256
+ retention-days: 1
257
+
258
+ # ============================================================================
259
+ # Merge Docker Manifests
260
+ # ============================================================================
261
+ merge-docker:
262
+ name: Merge Docker Manifests
263
+ needs: [release, build-docker]
264
+ if: needs.release.outputs.released == 'true' && vars.PUBLISH_DOCKER == 'true'
265
+ runs-on: ubuntu-latest
266
+ permissions:
267
+ contents: read
268
+ packages: write
269
+ steps:
270
+ - name: Harden Runner
271
+ uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
272
+ with:
273
+ egress-policy: audit
274
+
275
+ - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
276
+ with:
277
+ pattern: digest-*
278
+ path: ${{ runner.temp }}/digests
279
+ merge-multiple: true
280
+ - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
281
+ with:
282
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
283
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
284
+ - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
285
+ with:
286
+ registry: ghcr.io
287
+ username: ${{ github.actor }}
288
+ password: ${{ secrets.GITHUB_TOKEN }}
289
+ - name: Create and push manifests
290
+ env:
291
+ VERSION: ${{ needs.release.outputs.version }}
292
+ IS_PRERELEASE: ${{ needs.release.outputs.is_prerelease }}
293
+ run: |
294
+ if [ "$IS_PRERELEASE" = "true" ]; then
295
+ TAGS="beta ${VERSION}"
296
+ else
297
+ MAJOR=$(echo "$VERSION" | cut -d. -f1)
298
+ MINOR=$(echo "$VERSION" | cut -d. -f1-2)
299
+ TAGS="latest ${VERSION} ${MINOR} ${MAJOR}"
300
+ fi
301
+
302
+ for IMAGE in "$DOCKERHUB_IMAGE" "$GHCR_IMAGE"; do
303
+ tag_args=()
304
+ for TAG in $TAGS; do
305
+ tag_args+=("-t" "${IMAGE}:${TAG}")
306
+ done
307
+
308
+ digest_args=()
309
+ for digest_file in "${{ runner.temp }}/digests/"*; do
310
+ while IFS= read -r digest; do
311
+ if [ -n "$digest" ]; then
312
+ digest_args+=("${IMAGE}@${digest}")
313
+ fi
314
+ done < "$digest_file"
315
+ done
316
+
317
+ docker buildx imagetools create "${tag_args[@]}" "${digest_args[@]}"
318
+ done
319
+
320
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
321
+ with:
322
+ sparse-checkout: README.md
323
+ - uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0
324
+ with:
325
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
326
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
327
+ # CUSTOMIZE: DockerHub repository name
328
+ repository: ${{ env.DOCKERHUB_IMAGE }}
329
+ short-description: ${{ github.event.repository.description }}
330
+ readme-filepath: ./README.md
331
+
332
+ # ============================================================================
333
+ # Publish to MCP Registry (stable only)
334
+ # ============================================================================
335
+ publish-mcp-registry:
336
+ name: Publish to MCP Registry
337
+ needs: [release, publish-pypi, merge-docker]
338
+ if: >-
339
+ needs.release.outputs.released == 'true'
340
+ && needs.release.outputs.is_prerelease != 'true'
341
+ && vars.PUBLISH_PYPI == 'true'
342
+ && vars.PUBLISH_DOCKER == 'true'
343
+ && vars.PUBLISH_MCP_REGISTRY == 'true'
344
+ runs-on: ubuntu-latest
345
+ permissions:
346
+ contents: read
347
+ id-token: write
348
+ steps:
349
+ - name: Harden Runner
350
+ uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
351
+ with:
352
+ egress-policy: audit
353
+
354
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
355
+ with:
356
+ ref: ${{ needs.release.outputs.tag }}
357
+
358
+ - name: Update server.json version
359
+ run: |
360
+ VERSION="${{ needs.release.outputs.version }}"
361
+ jq --arg v "$VERSION" '
362
+ .version = $v |
363
+ .packages = [.packages[] |
364
+ if .registryType == "oci" then del(.version)
365
+ else .version = $v
366
+ end
367
+ ]
368
+ ' server.json > server.json.tmp && mv server.json.tmp server.json
369
+
370
+ - name: Install MCP Publisher
371
+ run: |
372
+ curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
373
+
374
+ - name: Login to MCP Registry
375
+ run: ./mcp-publisher login github-oidc
376
+
377
+ - name: Publish to MCP Registry
378
+ run: |
379
+ set +e
380
+ out=$(./mcp-publisher publish 2>&1)
381
+ rc=$?
382
+ echo "$out"
383
+ set -e
384
+ if [ $rc -ne 0 ] && echo "$out" | grep -q "cannot publish duplicate version"; then
385
+ echo "duplicate version already in registry - idempotent recovery, treating as success"
386
+ exit 0
387
+ fi
388
+ exit $rc