soliplex-plumber 0.1__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.
- soliplex_plumber-0.1/.cache/.gitignore +1 -0
- soliplex_plumber-0.1/.cache/1341065934833703470 +4 -0
- soliplex_plumber-0.1/.cache/3476900567878811119 +4 -0
- soliplex_plumber-0.1/.cache/3541202890535126406 +45 -0
- soliplex_plumber-0.1/.cache/6805557853694729310 +45 -0
- soliplex_plumber-0.1/.github/workflows/docs.yaml +66 -0
- soliplex_plumber-0.1/.github/workflows/gitleaks.yaml +55 -0
- soliplex_plumber-0.1/.github/workflows/issue-opened.yaml +22 -0
- soliplex_plumber-0.1/.github/workflows/pr-opened.yaml +22 -0
- soliplex_plumber-0.1/.github/workflows/pypi.yaml +30 -0
- soliplex_plumber-0.1/.github/workflows/python-lint.yaml +65 -0
- soliplex_plumber-0.1/.github/workflows/python-test.yaml +66 -0
- soliplex_plumber-0.1/.github/workflows/trufflehog.yaml +44 -0
- soliplex_plumber-0.1/.gitignore +33 -0
- soliplex_plumber-0.1/.gitleaks.toml +16 -0
- soliplex_plumber-0.1/.pre-commit-config.yaml +46 -0
- soliplex_plumber-0.1/LICENSE +21 -0
- soliplex_plumber-0.1/PKG-INFO +38 -0
- soliplex_plumber-0.1/README.md +25 -0
- soliplex_plumber-0.1/docs/index.md +29 -0
- soliplex_plumber-0.1/docs/reference/api.md +28 -0
- soliplex_plumber-0.1/pyproject.toml +103 -0
- soliplex_plumber-0.1/src/soliplex_plumber/rooms.py +213 -0
- soliplex_plumber-0.1/tests/unit/test_rooms.py +250 -0
- soliplex_plumber-0.1/uv.lock +668 -0
- soliplex_plumber-0.1/zensical.toml +99 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": "\n<!doctype html>\n<html lang=\"en\" class=\"no-js\">\n <head>\n \n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n \n <meta name=\"description\" content=\"Stdlib-only tools for reading and modifying the configuration of an existing Soliplex stack -- the shared dependency for skill projects that wire rooms.\n\">\n \n \n <meta name=\"author\" content=\"Soliplex\">\n \n \n <link rel=\"canonical\" href=\"https://soliplex.github.io/soliplex-plumber/reference/api/\">\n \n \n <link rel=\"prev\" href=\"../..\">\n \n \n \n \n \n \n <link rel=\"icon\" href=\"../../assets/images/favicon.png\">\n <meta name=\"generator\" content=\"zensical-0.0.45\">\n \n \n \n <title>API reference - Soliplex Plumber</title>\n \n \n \n \n \n \n <link rel=\"stylesheet\" href=\"../../assets/stylesheets/modern/main.fba56155.min.css\">\n \n \n \n \n <link rel=\"stylesheet\" href=\"../../assets/stylesheets/modern/palette.dfe2e883.min.css\">\n \n \n\n\n \n \n \n \n \n \n \n \n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback\">\n <style>:root{--md-text-font:\"Inter\";--md-code-font:\"JetBrains Mono\"}</style>\n \n \n \n <script>__md_scope=new URL(\"../..\",location),__md_scope.pathname.endsWith(\"/\")||(__md_scope=new URL(__md_scope.pathname+\"/\",location)),__md_hash=e=>[...e].reduce(((e,t)=>(e<<5)-e+t.charCodeAt(0)),0),__md_get=(e,t=localStorage,_=__md_scope)=>JSON.parse(t.getItem(_.pathname+\".\"+e)),__md_set=(e,t,_=localStorage,a=__md_scope)=>{try{_.setItem(a.pathname+\".\"+e,JSON.stringify(t))}catch(e){}},document.documentElement.setAttribute(\"data-platform\",navigator.platform)</script>\n \n \n\n \n \n </head>\n \n \n \n \n \n \n \n \n \n <body dir=\"ltr\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\">\n \n \n <input class=\"md-toggle\" data-md-toggle=\"drawer\" type=\"checkbox\" id=\"__drawer\" autocomplete=\"off\">\n <input class=\"md-toggle\" data-md-toggle=\"search\" type=\"checkbox\" id=\"__search\" autocomplete=\"off\">\n <label class=\"md-overlay\" for=\"__drawer\" aria-label=\"Navigation\"></label>\n <div data-md-component=\"skip\">\n \n \n <a href=\"#api-reference\" class=\"md-skip\">\n Skip to content\n </a>\n \n </div>\n <div data-md-component=\"announce\">\n \n </div>\n \n \n \n\n \n\n<header class=\"md-header md-header--shadow\" data-md-component=\"header\">\n <nav class=\"md-header__inner md-grid\" aria-label=\"Header\">\n <a href=\"../..\" title=\"Soliplex Plumber\" class=\"md-header__button md-logo\" aria-label=\"Soliplex Plumber\" data-md-component=\"logo\">\n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-book-open\" viewBox=\"0 0 24 24\"><path d=\"M12 7v14M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>\n\n </a>\n <label class=\"md-header__button md-icon\" for=\"__drawer\" aria-label=\"Navigation\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-menu\" viewBox=\"0 0 24 24\"><path d=\"M4 5h16M4 12h16M4 19h16\"/></svg>\n </label>\n <div class=\"md-header__title\" data-md-component=\"header-title\">\n <div class=\"md-header__ellipsis\">\n <div class=\"md-header__topic\">\n <span class=\"md-ellipsis\">\n Soliplex Plumber\n </span>\n </div>\n <div class=\"md-header__topic\" data-md-component=\"header-topic\">\n <span class=\"md-ellipsis\">\n \n API reference\n \n </span>\n </div>\n </div>\n </div>\n \n \n <form class=\"md-header__option\" data-md-component=\"palette\">\n \n \n \n \n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to dark mode\" type=\"radio\" name=\"__palette\" id=\"__palette_0\">\n \n <label class=\"md-header__button md-icon\" title=\"Switch to dark mode\" for=\"__palette_1\" hidden>\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-sun\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\"/></svg>\n </label>\n \n \n \n \n \n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"slate\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to light mode\" type=\"radio\" name=\"__palette\" id=\"__palette_1\">\n \n <label class=\"md-header__button md-icon\" title=\"Switch to light mode\" for=\"__palette_0\" hidden>\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-moon\" viewBox=\"0 0 24 24\"><path d=\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\"/></svg>\n </label>\n \n \n</form>\n \n \n \n <script>var palette=__md_get(\"__palette\");if(palette&&palette.color){if(\"(prefers-color-scheme)\"===palette.color.media){var media=matchMedia(\"(prefers-color-scheme: light)\"),input=document.querySelector(media.matches?\"[data-md-color-media='(prefers-color-scheme: light)']\":\"[data-md-color-media='(prefers-color-scheme: dark)']\");palette.color.media=input.getAttribute(\"data-md-color-media\"),palette.color.scheme=input.getAttribute(\"data-md-color-scheme\"),palette.color.primary=input.getAttribute(\"data-md-color-primary\"),palette.color.accent=input.getAttribute(\"data-md-color-accent\")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute(\"data-md-color-\"+key,value)}</script>\n \n \n \n \n \n <label class=\"md-header__button md-icon\" for=\"__search\" aria-label=\"Search\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-search\" viewBox=\"0 0 24 24\"><path d=\"m21 21-4.34-4.34\"/><circle cx=\"11\" cy=\"11\" r=\"8\"/></svg>\n </label>\n <div class=\"md-search\" data-md-component=\"search\" role=\"dialog\" aria-label=\"Search\">\n <button type=\"button\" class=\"md-search__button\">\n Search\n </button>\n</div>\n \n \n <div class=\"md-header__source\">\n \n <a href=\"https://github.com/soliplex/soliplex-plumber\" title=\"Go to repository\" class=\"md-source\" data-md-component=\"source\">\n <div class=\"md-source__icon md-icon\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc.--><path fill=\"currentColor\" d=\"M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4\"/></svg>\n </div>\n <div class=\"md-source__repository\">\n soliplex/soliplex-plumber\n </div>\n</a>\n \n </div>\n </nav>\n \n</header>\n \n <div class=\"md-container\" data-md-component=\"container\">\n \n \n \n \n \n \n <main class=\"md-main\" data-md-component=\"main\">\n <div class=\"md-main__inner md-grid\">\n \n \n \n <div class=\"md-sidebar md-sidebar--primary\" data-md-component=\"sidebar\" data-md-type=\"navigation\" >\n <div class=\"md-sidebar__scrollwrap\">\n <div class=\"md-sidebar__inner\">\n \n\n\n\n<nav class=\"md-nav md-nav--primary\" aria-label=\"Navigation\" data-md-level=\"0\">\n <label class=\"md-nav__title\" for=\"__drawer\">\n <a href=\"../..\" title=\"Soliplex Plumber\" class=\"md-nav__button md-logo\" aria-label=\"Soliplex Plumber\" data-md-component=\"logo\">\n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-book-open\" viewBox=\"0 0 24 24\"><path d=\"M12 7v14M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>\n\n </a>\n Soliplex Plumber\n </label>\n \n <div class=\"md-nav__source\">\n <a href=\"https://github.com/soliplex/soliplex-plumber\" title=\"Go to repository\" class=\"md-source\" data-md-component=\"source\">\n <div class=\"md-source__icon md-icon\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc.--><path fill=\"currentColor\" d=\"M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4\"/></svg>\n </div>\n <div class=\"md-source__repository\">\n soliplex/soliplex-plumber\n </div>\n</a>\n </div>\n \n <ul class=\"md-nav__list\" data-md-scrollfix>\n \n \n \n \n \n \n \n <li class=\"md-nav__item\">\n <a href=\"../..\" class=\"md-nav__link\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n Home\n\n \n </span>\n \n \n\n </a>\n </li>\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <li class=\"md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested\">\n \n \n \n <input class=\"md-nav__toggle md-toggle \" type=\"checkbox\" id=\"__nav_2\" checked>\n \n \n <label class=\"md-nav__link\" for=\"__nav_2\" id=\"__nav_2_label\" tabindex=\"\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n API reference\n\n \n </span>\n \n \n\n <span class=\"md-nav__icon md-icon\"></span>\n </label>\n \n <nav class=\"md-nav\" data-md-level=\"1\" aria-labelledby=\"__nav_2_label\" aria-expanded=\"true\">\n <label class=\"md-nav__title\" for=\"__nav_2\">\n <span class=\"md-nav__icon md-icon\"></span>\n \n \n API reference\n\n </label>\n <ul class=\"md-nav__list\" data-md-scrollfix>\n \n \n \n \n \n \n \n \n \n <li class=\"md-nav__item md-nav__item--active\">\n \n \n \n \n \n \n \n <label class=\"md-nav__link md-nav__link--active\" for=\"__toc\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n API reference\n\n \n </span>\n \n \n\n <span class=\"md-nav__icon md-icon\"></span>\n </label>\n \n <a href=\"././\" class=\"md-nav__link md-nav__link--active\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n API reference\n\n \n </span>\n \n \n\n </a>\n \n \n\n\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\n \n \n \n \n \n \n <label class=\"md-nav__title\" for=\"__toc\">\n <span class=\"md-nav__icon md-icon\"></span>\n On this page\n </label>\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\n \n <li class=\"md-nav__item\">\n <a href=\"#rooms-add-a-room-to-a-soliplex-stack\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n <code>rooms</code> — add a room to a Soliplex stack\n </span>\n </span>\n </a>\n \n</li>\n \n </ul>\n \n</nav>\n \n </li>\n \n\n \n \n </ul>\n </nav>\n \n </li>\n \n\n \n </ul>\n</nav>\n </div>\n </div>\n </div>\n \n \n \n <div class=\"md-sidebar md-sidebar--secondary\" data-md-component=\"sidebar\" data-md-type=\"toc\" >\n <div class=\"md-sidebar__scrollwrap\">\n \n \n \n \n \n \n \n <input class=\"md-nav__toggle md-toggle\" type=\"checkbox\" id=\"__toc\">\n <div class=\"md-sidebar-button__wrapper\">\n <label class=\"md-sidebar-button\" for=\"__toc\"></label>\n </div>\n \n \n <div class=\"md-sidebar__inner\">\n \n\n\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\n \n \n \n \n \n \n <label class=\"md-nav__title\" for=\"__toc\">\n <span class=\"md-nav__icon md-icon\"></span>\n On this page\n </label>\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\n \n <li class=\"md-nav__item\">\n <a href=\"#rooms-add-a-room-to-a-soliplex-stack\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n <code>rooms</code> — add a room to a Soliplex stack\n </span>\n </span>\n </a>\n \n</li>\n \n </ul>\n \n</nav>\n </div>\n </div>\n </div>\n \n \n \n <div class=\"md-content\" data-md-component=\"content\">\n \n \n\n\n\n \n\n\n <nav class=\"md-path\" aria-label=\"Navigation\" >\n <ol class=\"md-path__list\">\n \n \n \n \n <li class=\"md-path__item\">\n <a href=\"../..\" class=\"md-path__link\">\n \n \n <span class=\"md-ellipsis\">\n Home\n </span>\n\n </a>\n </li>\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <li class=\"md-path__item\">\n <a href=\"././\" class=\"md-path__link\">\n \n \n <span class=\"md-ellipsis\">\n API reference\n </span>\n\n </a>\n </li>\n \n \n\n \n </ol>\n </nav>\n\n \n <article class=\"md-content__inner md-typeset\">\n \n \n \n \n\n\n<h1 id=\"api-reference\">API reference<a class=\"headerlink\" href=\"#api-reference\" title=\"Permanent link\">¶</a></h1>\n<div class=\"admonition note\">\n<p class=\"admonition-title\">Implemented</p>\n<p>This is the library's public surface. Every member below is implemented\nand covered by the test suite (100% branch coverage); the modules under\n<code>src/soliplex_plumber/</code> are the source of truth.</p>\n</div>\n<p>There is no re-exporting package <code>__init__</code> — <strong>client code imports the\nsubmodule and uses its members by dotted name</strong> (e.g. <code>from soliplex_plumber\nimport rooms</code> then <code>rooms.install_room(...)</code>).</p>\n<h2 id=\"rooms-add-a-room-to-a-soliplex-stack\"><code>rooms</code> — add a room to a Soliplex stack<a class=\"headerlink\" href=\"#rooms-add-a-room-to-a-soliplex-stack\" title=\"Permanent link\">¶</a></h2>\n<p>Generic, template-agnostic, stdlib-only logic for wiring a room into a generated\nstack. The shared core behind both the <code>soliplex-template</code> skill's <code>add_room.py</code>\nand the <code>soliplex-concierge</code> installer; it edits <code>installation.yaml</code> line-based so\ncomments and layout are preserved.</p>\n<table>\n<thead>\n<tr>\n<th>Member</th>\n<th>Purpose</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>validate_room_id(room_id)</code></td>\n<td>enforce the room-id / path-segment rule (<code>ROOM_ID_RE</code>); raises <code>AddRoomError</code></td>\n</tr>\n<tr>\n<td><code>resolve_project(project_dir)</code></td>\n<td>resolve + verify the stack root (has <code>COMPOSE_FILE</code> and <code>INSTALLATION_FILE</code>)</td>\n</tr>\n<tr>\n<td><code>resolve_package_name(project, override)</code></td>\n<td>the stack's own package (inferred from <code>src/<pkg>/tools.py</code>) or <code>DEFAULT_PACKAGE_NAME</code></td>\n</tr>\n<tr>\n<td><code>add_room_path(text, room_id) -> (text, action)</code></td>\n<td>ensure <code>room_paths</code> loads the room; action is <code>ADDED</code> / <code>UNCHANGED</code> / <code>COVERED</code></td>\n</tr>\n<tr>\n<td><code>install_room(project, room_id, *, config_text, prompt_text=None, force=False, dry_run=False)</code></td>\n<td>write the room dir + config (+ optional prompt) and apply the <code>room_paths</code> edit</td>\n</tr>\n<tr>\n<td><code>RoomInstalled(config_path, path_action)</code></td>\n<td>the <code>install_room</code> outcome (alias <code>RoomInstall</code> kept for back-compat)</td>\n</tr>\n<tr>\n<td><code>AddRoomError</code></td>\n<td>user-facing error with message-factory classmethods</td>\n</tr>\n<tr>\n<td><code>ADDED</code> / <code>UNCHANGED</code> / <code>COVERED</code> / <code>ROOMS_PARENT_ENTRY</code></td>\n<td>the <code>room_paths</code> action constants and the <code>./rooms</code> auto-discovery entry</td>\n</tr>\n</tbody>\n</table>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n </article>\n </div>\n \n \n <script>var tabs=__md_get(\"__tabs\");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(\".tabbed-set\")){var labels=set.querySelector(\".tabbed-labels\");for(var tab of tabs)for(var label of labels.getElementsByTagName(\"label\"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>\n\n<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith(\"__tabbed_\"))</script>\n </div>\n \n <button type=\"button\" class=\"md-top md-icon\" data-md-component=\"top\" hidden>\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-circle-arrow-up\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m16 12-4-4-4 4M12 16V8\"/></svg>\n Back to top\n</button>\n \n </main>\n \n <footer class=\"md-footer\">\n \n \n \n <nav class=\"md-footer__inner md-grid\" aria-label=\"Footer\" >\n \n \n <a href=\"../..\" class=\"md-footer__link md-footer__link--prev\" aria-label=\"Previous: Home\">\n <div class=\"md-footer__button md-icon\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-arrow-left\" viewBox=\"0 0 24 24\"><path d=\"m12 19-7-7 7-7M19 12H5\"/></svg>\n </div>\n <div class=\"md-footer__title\">\n <span class=\"md-footer__direction\">\n Previous\n </span>\n <div class=\"md-ellipsis\">\n Home\n </div>\n </div>\n </a>\n \n \n </nav>\n \n \n <div class=\"md-footer-meta md-typeset\">\n <div class=\"md-footer-meta__inner md-grid\">\n <div class=\"md-copyright\">\n \n <div class=\"md-copyright__highlight\">\n Copyright © Soliplex\n\n </div>\n \n \n Made with\n <a href=\"https://zensical.org/\" target=\"_blank\" rel=\"noopener\">\n Zensical\n </a>\n \n</div>\n \n </div>\n </div>\n</footer>\n \n </div>\n <div class=\"md-dialog\" data-md-component=\"dialog\">\n <div class=\"md-dialog__inner md-typeset\"></div>\n </div>\n \n \n \n \n \n <script id=\"__config\" type=\"application/json\">{\"annotate\":null,\"base\":\"../..\",\"features\":[\"announce.dismiss\",\"content.code.annotate\",\"content.code.copy\",\"content.code.select\",\"content.tabs.link\",\"navigation.footer\",\"navigation.indexes\",\"navigation.instant\",\"navigation.instant.prefetch\",\"navigation.path\",\"navigation.sections\",\"navigation.top\",\"navigation.tracking\",\"search.highlight\"],\"search\":\"../../assets/javascripts/workers/search.e2d2d235.min.js\",\"tags\":null,\"translations\":{\"clipboard.copied\":\"Copied to clipboard\",\"clipboard.copy\":\"Copy to clipboard\",\"search.result.more.one\":\"1 more on this page\",\"search.result.more.other\":\"# more on this page\",\"search.result.none\":\"No matching documents\",\"search.result.one\":\"1 matching document\",\"search.result.other\":\"# matching documents\",\"search.result.placeholder\":\"Type to start searching\",\"search.result.term.missing\":\"Missing\",\"select.version\":\"Select version\"},\"version\":null}</script>\n \n \n <script src=\"../../assets/javascripts/bundle.6e5f0216.min.js\"></script>\n \n \n </body>\n</html>",
|
|
3
|
+
"hash": 1485270945752863816
|
|
4
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": "\n<!doctype html>\n<html lang=\"en\" class=\"no-js\">\n <head>\n \n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n \n <meta name=\"description\" content=\"Stdlib-only tools for reading and modifying the configuration of an existing Soliplex stack -- the shared dependency for skill projects that wire rooms.\n\">\n \n \n <meta name=\"author\" content=\"Soliplex\">\n \n \n <link rel=\"canonical\" href=\"https://soliplex.github.io/soliplex-plumber/\">\n \n \n \n <link rel=\"next\" href=\"./reference/api/\">\n \n \n \n \n \n <link rel=\"icon\" href=\"./assets/images/favicon.png\">\n <meta name=\"generator\" content=\"zensical-0.0.45\">\n \n \n \n <title>Soliplex Plumber - Soliplex Plumber</title>\n \n \n \n \n \n \n <link rel=\"stylesheet\" href=\"./assets/stylesheets/modern/main.fba56155.min.css\">\n \n \n \n \n <link rel=\"stylesheet\" href=\"./assets/stylesheets/modern/palette.dfe2e883.min.css\">\n \n \n\n\n \n \n \n \n \n \n \n \n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,500,500i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback\">\n <style>:root{--md-text-font:\"Inter\";--md-code-font:\"JetBrains Mono\"}</style>\n \n \n \n <script>__md_scope=new URL(\".\",location),__md_scope.pathname.endsWith(\"/\")||(__md_scope=new URL(__md_scope.pathname+\"/\",location)),__md_hash=e=>[...e].reduce(((e,t)=>(e<<5)-e+t.charCodeAt(0)),0),__md_get=(e,t=localStorage,_=__md_scope)=>JSON.parse(t.getItem(_.pathname+\".\"+e)),__md_set=(e,t,_=localStorage,a=__md_scope)=>{try{_.setItem(a.pathname+\".\"+e,JSON.stringify(t))}catch(e){}},document.documentElement.setAttribute(\"data-platform\",navigator.platform)</script>\n \n \n\n \n \n </head>\n \n \n \n \n \n \n \n \n \n <body dir=\"ltr\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\">\n \n \n <input class=\"md-toggle\" data-md-toggle=\"drawer\" type=\"checkbox\" id=\"__drawer\" autocomplete=\"off\">\n <input class=\"md-toggle\" data-md-toggle=\"search\" type=\"checkbox\" id=\"__search\" autocomplete=\"off\">\n <label class=\"md-overlay\" for=\"__drawer\" aria-label=\"Navigation\"></label>\n <div data-md-component=\"skip\">\n \n \n <a href=\"#soliplex-plumber\" class=\"md-skip\">\n Skip to content\n </a>\n \n </div>\n <div data-md-component=\"announce\">\n \n </div>\n \n \n \n\n \n\n<header class=\"md-header md-header--shadow\" data-md-component=\"header\">\n <nav class=\"md-header__inner md-grid\" aria-label=\"Header\">\n <a href=\"\" title=\"Soliplex Plumber\" class=\"md-header__button md-logo\" aria-label=\"Soliplex Plumber\" data-md-component=\"logo\">\n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-book-open\" viewBox=\"0 0 24 24\"><path d=\"M12 7v14M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>\n\n </a>\n <label class=\"md-header__button md-icon\" for=\"__drawer\" aria-label=\"Navigation\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-menu\" viewBox=\"0 0 24 24\"><path d=\"M4 5h16M4 12h16M4 19h16\"/></svg>\n </label>\n <div class=\"md-header__title\" data-md-component=\"header-title\">\n <div class=\"md-header__ellipsis\">\n <div class=\"md-header__topic\">\n <span class=\"md-ellipsis\">\n Soliplex Plumber\n </span>\n </div>\n <div class=\"md-header__topic\" data-md-component=\"header-topic\">\n <span class=\"md-ellipsis\">\n \n Soliplex Plumber\n \n </span>\n </div>\n </div>\n </div>\n \n \n <form class=\"md-header__option\" data-md-component=\"palette\">\n \n \n \n \n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"default\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to dark mode\" type=\"radio\" name=\"__palette\" id=\"__palette_0\">\n \n <label class=\"md-header__button md-icon\" title=\"Switch to dark mode\" for=\"__palette_1\" hidden>\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-sun\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\"/></svg>\n </label>\n \n \n \n \n \n <input class=\"md-option\" data-md-color-media=\"none\" data-md-color-scheme=\"slate\" data-md-color-primary=\"indigo\" data-md-color-accent=\"indigo\" aria-label=\"Switch to light mode\" type=\"radio\" name=\"__palette\" id=\"__palette_1\">\n \n <label class=\"md-header__button md-icon\" title=\"Switch to light mode\" for=\"__palette_0\" hidden>\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-moon\" viewBox=\"0 0 24 24\"><path d=\"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401\"/></svg>\n </label>\n \n \n</form>\n \n \n \n <script>var palette=__md_get(\"__palette\");if(palette&&palette.color){if(\"(prefers-color-scheme)\"===palette.color.media){var media=matchMedia(\"(prefers-color-scheme: light)\"),input=document.querySelector(media.matches?\"[data-md-color-media='(prefers-color-scheme: light)']\":\"[data-md-color-media='(prefers-color-scheme: dark)']\");palette.color.media=input.getAttribute(\"data-md-color-media\"),palette.color.scheme=input.getAttribute(\"data-md-color-scheme\"),palette.color.primary=input.getAttribute(\"data-md-color-primary\"),palette.color.accent=input.getAttribute(\"data-md-color-accent\")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute(\"data-md-color-\"+key,value)}</script>\n \n \n \n \n \n <label class=\"md-header__button md-icon\" for=\"__search\" aria-label=\"Search\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-search\" viewBox=\"0 0 24 24\"><path d=\"m21 21-4.34-4.34\"/><circle cx=\"11\" cy=\"11\" r=\"8\"/></svg>\n </label>\n <div class=\"md-search\" data-md-component=\"search\" role=\"dialog\" aria-label=\"Search\">\n <button type=\"button\" class=\"md-search__button\">\n Search\n </button>\n</div>\n \n \n <div class=\"md-header__source\">\n \n <a href=\"https://github.com/soliplex/soliplex-plumber\" title=\"Go to repository\" class=\"md-source\" data-md-component=\"source\">\n <div class=\"md-source__icon md-icon\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc.--><path fill=\"currentColor\" d=\"M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4\"/></svg>\n </div>\n <div class=\"md-source__repository\">\n soliplex/soliplex-plumber\n </div>\n</a>\n \n </div>\n </nav>\n \n</header>\n \n <div class=\"md-container\" data-md-component=\"container\">\n \n \n \n \n \n \n <main class=\"md-main\" data-md-component=\"main\">\n <div class=\"md-main__inner md-grid\">\n \n \n \n <div class=\"md-sidebar md-sidebar--primary\" data-md-component=\"sidebar\" data-md-type=\"navigation\" >\n <div class=\"md-sidebar__scrollwrap\">\n <div class=\"md-sidebar__inner\">\n \n\n\n\n<nav class=\"md-nav md-nav--primary\" aria-label=\"Navigation\" data-md-level=\"0\">\n <label class=\"md-nav__title\" for=\"__drawer\">\n <a href=\"\" title=\"Soliplex Plumber\" class=\"md-nav__button md-logo\" aria-label=\"Soliplex Plumber\" data-md-component=\"logo\">\n \n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-book-open\" viewBox=\"0 0 24 24\"><path d=\"M12 7v14M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>\n\n </a>\n Soliplex Plumber\n </label>\n \n <div class=\"md-nav__source\">\n <a href=\"https://github.com/soliplex/soliplex-plumber\" title=\"Go to repository\" class=\"md-source\" data-md-component=\"source\">\n <div class=\"md-source__icon md-icon\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><!--! Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2026 Fonticons, Inc.--><path fill=\"currentColor\" d=\"M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4\"/></svg>\n </div>\n <div class=\"md-source__repository\">\n soliplex/soliplex-plumber\n </div>\n</a>\n </div>\n \n <ul class=\"md-nav__list\" data-md-scrollfix>\n \n \n \n \n \n \n \n \n \n <li class=\"md-nav__item md-nav__item--active\">\n \n \n \n \n \n \n \n <label class=\"md-nav__link md-nav__link--active\" for=\"__toc\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n Home\n\n \n </span>\n \n \n\n <span class=\"md-nav__icon md-icon\"></span>\n </label>\n \n <a href=\"\" class=\"md-nav__link md-nav__link--active\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n Home\n\n \n </span>\n \n \n\n </a>\n \n \n\n\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\n \n \n \n \n \n \n <label class=\"md-nav__title\" for=\"__toc\">\n <span class=\"md-nav__icon md-icon\"></span>\n On this page\n </label>\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\n \n <li class=\"md-nav__item\">\n <a href=\"#what-it-provides\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n What it provides\n </span>\n </span>\n </a>\n \n</li>\n \n </ul>\n \n</nav>\n \n </li>\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n <li class=\"md-nav__item md-nav__item--section md-nav__item--nested\">\n \n \n \n <input class=\"md-nav__toggle md-toggle \" type=\"checkbox\" id=\"__nav_2\" >\n \n \n <label class=\"md-nav__link\" for=\"__nav_2\" id=\"__nav_2_label\" tabindex=\"\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n API reference\n\n \n </span>\n \n \n\n <span class=\"md-nav__icon md-icon\"></span>\n </label>\n \n <nav class=\"md-nav\" data-md-level=\"1\" aria-labelledby=\"__nav_2_label\" aria-expanded=\"false\">\n <label class=\"md-nav__title\" for=\"__nav_2\">\n <span class=\"md-nav__icon md-icon\"></span>\n \n \n API reference\n\n </label>\n <ul class=\"md-nav__list\" data-md-scrollfix>\n \n \n \n \n \n \n \n <li class=\"md-nav__item\">\n <a href=\"./reference/api/\" class=\"md-nav__link\">\n \n \n \n <span class=\"md-ellipsis\">\n \n \n API reference\n\n \n </span>\n \n \n\n </a>\n </li>\n \n\n \n \n </ul>\n </nav>\n \n </li>\n \n\n \n </ul>\n</nav>\n </div>\n </div>\n </div>\n \n \n \n <div class=\"md-sidebar md-sidebar--secondary\" data-md-component=\"sidebar\" data-md-type=\"toc\" >\n <div class=\"md-sidebar__scrollwrap\">\n \n \n \n \n \n \n \n <input class=\"md-nav__toggle md-toggle\" type=\"checkbox\" id=\"__toc\">\n <div class=\"md-sidebar-button__wrapper\">\n <label class=\"md-sidebar-button\" for=\"__toc\"></label>\n </div>\n \n \n <div class=\"md-sidebar__inner\">\n \n\n\n<nav class=\"md-nav md-nav--secondary\" aria-label=\"On this page\">\n \n \n \n \n \n \n <label class=\"md-nav__title\" for=\"__toc\">\n <span class=\"md-nav__icon md-icon\"></span>\n On this page\n </label>\n <ul class=\"md-nav__list\" data-md-component=\"toc\" data-md-scrollfix>\n \n <li class=\"md-nav__item\">\n <a href=\"#what-it-provides\" class=\"md-nav__link\">\n <span class=\"md-ellipsis\">\n <span class=\"md-typeset\">\n What it provides\n </span>\n </span>\n </a>\n \n</li>\n \n </ul>\n \n</nav>\n </div>\n </div>\n </div>\n \n \n \n <div class=\"md-content\" data-md-component=\"content\">\n \n \n\n\n\n \n\n\n \n <article class=\"md-content__inner md-typeset\">\n \n \n \n \n\n\n<h1 id=\"soliplex-plumber\">Soliplex Plumber<a class=\"headerlink\" href=\"#soliplex-plumber\" title=\"Permanent link\">¶</a></h1>\n<p><code>soliplex-plumber</code> is the shared, <strong>stdlib-only</strong> library for reading and\nmodifying the configuration of an <em>existing</em> Soliplex stack. It is the common\ndependency for the skill projects that operate on a generated stack -- the\n<code>soliplex-template</code> skill's <code>add_room.py</code> and the <code>soliplex-concierge</code>\ninstaller -- so the stack-wiring rules live in one place.</p>\n<div class=\"admonition note\">\n<p class=\"admonition-title\">Status</p>\n<p>The library is implemented and tested (100% branch coverage). It does pure\nfilesystem work -- no Docker, no running backend, stdlib only.</p>\n</div>\n<h2 id=\"what-it-provides\">What it provides<a class=\"headerlink\" href=\"#what-it-provides\" title=\"Permanent link\">¶</a></h2>\n<ul>\n<li><strong><code>rooms</code></strong> -- generic, template-agnostic logic for adding a room to a stack:\n resolve and validate the stack root, infer its package, and ensure\n <code>installation.yaml</code>'s <code>room_paths</code> loads the room (editing line-based, so\n comments and layout are preserved). See the\n <a href=\"reference/api/\">API reference</a>.</li>\n</ul>\n<p>There is no re-exporting package <code>__init__</code>; client code imports the submodule\nand uses its members by dotted name:</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">soliplex_plumber</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">rooms</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a>\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a><span class=\"n\">project</span> <span class=\"o\">=</span> <span class=\"n\">rooms</span><span class=\"o\">.</span><span class=\"n\">resolve_project</span><span class=\"p\">(</span><span class=\"s2\">"/path/to/stack"</span><span class=\"p\">)</span>\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a><span class=\"n\">installed</span> <span class=\"o\">=</span> <span class=\"n\">rooms</span><span class=\"o\">.</span><span class=\"n\">install_room</span><span class=\"p\">(</span><span class=\"n\">project</span><span class=\"p\">,</span> <span class=\"s2\">"handbook"</span><span class=\"p\">,</span> <span class=\"n\">config_text</span><span class=\"o\">=</span><span class=\"n\">cfg</span><span class=\"p\">)</span>\n</span></code></pre></div>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n </article>\n </div>\n \n \n <script>var tabs=__md_get(\"__tabs\");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(\".tabbed-set\")){var labels=set.querySelector(\".tabbed-labels\");for(var tab of tabs)for(var label of labels.getElementsByTagName(\"label\"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>\n\n<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith(\"__tabbed_\"))</script>\n </div>\n \n <button type=\"button\" class=\"md-top md-icon\" data-md-component=\"top\" hidden>\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-circle-arrow-up\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m16 12-4-4-4 4M12 16V8\"/></svg>\n Back to top\n</button>\n \n </main>\n \n <footer class=\"md-footer\">\n \n \n \n <nav class=\"md-footer__inner md-grid\" aria-label=\"Footer\" >\n \n \n \n <a href=\"./reference/api/\" class=\"md-footer__link md-footer__link--next\" aria-label=\"Next: API reference\">\n <div class=\"md-footer__title\">\n <span class=\"md-footer__direction\">\n Next\n </span>\n <div class=\"md-ellipsis\">\n API reference\n </div>\n </div>\n <div class=\"md-footer__button md-icon\">\n \n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" class=\"lucide lucide-arrow-right\" viewBox=\"0 0 24 24\"><path d=\"M5 12h14M12 5l7 7-7 7\"/></svg>\n </div>\n </a>\n \n </nav>\n \n \n <div class=\"md-footer-meta md-typeset\">\n <div class=\"md-footer-meta__inner md-grid\">\n <div class=\"md-copyright\">\n \n <div class=\"md-copyright__highlight\">\n Copyright © Soliplex\n\n </div>\n \n \n Made with\n <a href=\"https://zensical.org/\" target=\"_blank\" rel=\"noopener\">\n Zensical\n </a>\n \n</div>\n \n </div>\n </div>\n</footer>\n \n </div>\n <div class=\"md-dialog\" data-md-component=\"dialog\">\n <div class=\"md-dialog__inner md-typeset\"></div>\n </div>\n \n \n \n \n \n <script id=\"__config\" type=\"application/json\">{\"annotate\":null,\"base\":\".\",\"features\":[\"announce.dismiss\",\"content.code.annotate\",\"content.code.copy\",\"content.code.select\",\"content.tabs.link\",\"navigation.footer\",\"navigation.indexes\",\"navigation.instant\",\"navigation.instant.prefetch\",\"navigation.path\",\"navigation.sections\",\"navigation.top\",\"navigation.tracking\",\"search.highlight\"],\"search\":\"./assets/javascripts/workers/search.e2d2d235.min.js\",\"tags\":null,\"translations\":{\"clipboard.copied\":\"Copied to clipboard\",\"clipboard.copy\":\"Copy to clipboard\",\"search.result.more.one\":\"1 more on this page\",\"search.result.more.other\":\"# more on this page\",\"search.result.none\":\"No matching documents\",\"search.result.one\":\"1 matching document\",\"search.result.other\":\"# matching documents\",\"search.result.placeholder\":\"Type to start searching\",\"search.result.term.missing\":\"Missing\",\"select.version\":\"Select version\"},\"version\":null}</script>\n \n \n <script src=\"./assets/javascripts/bundle.6e5f0216.min.js\"></script>\n \n \n </body>\n</html>",
|
|
3
|
+
"hash": 2188577422303779309
|
|
4
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": {
|
|
3
|
+
"meta": {},
|
|
4
|
+
"content": "<h1 id=\"soliplex-plumber\">Soliplex Plumber<a class=\"headerlink\" href=\"#soliplex-plumber\" title=\"Permanent link\">¶</a></h1>\n<p><code>soliplex-plumber</code> is the shared, <strong>stdlib-only</strong> library for reading and\nmodifying the configuration of an <em>existing</em> Soliplex stack. It is the common\ndependency for the skill projects that operate on a generated stack -- the\n<code>soliplex-template</code> skill's <code>add_room.py</code> and the <code>soliplex-concierge</code>\ninstaller -- so the stack-wiring rules live in one place.</p>\n<div class=\"admonition note\">\n<p class=\"admonition-title\">Status</p>\n<p>The library is implemented and tested (100% branch coverage). It does pure\nfilesystem work -- no Docker, no running backend, stdlib only.</p>\n</div>\n<h2 id=\"what-it-provides\">What it provides<a class=\"headerlink\" href=\"#what-it-provides\" title=\"Permanent link\">¶</a></h2>\n<ul>\n<li><strong><code>rooms</code></strong> -- generic, template-agnostic logic for adding a room to a stack:\n resolve and validate the stack root, infer its package, and ensure\n <code>installation.yaml</code>'s <code>room_paths</code> loads the room (editing line-based, so\n comments and layout are preserved). See the\n <a href=\"reference/api/\">API reference</a>.</li>\n</ul>\n<p>There is no re-exporting package <code>__init__</code>; client code imports the submodule\nand uses its members by dotted name:</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">soliplex_plumber</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">rooms</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a>\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a><span class=\"n\">project</span> <span class=\"o\">=</span> <span class=\"n\">rooms</span><span class=\"o\">.</span><span class=\"n\">resolve_project</span><span class=\"p\">(</span><span class=\"s2\">"/path/to/stack"</span><span class=\"p\">)</span>\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a><span class=\"n\">installed</span> <span class=\"o\">=</span> <span class=\"n\">rooms</span><span class=\"o\">.</span><span class=\"n\">install_room</span><span class=\"p\">(</span><span class=\"n\">project</span><span class=\"p\">,</span> <span class=\"s2\">"handbook"</span><span class=\"p\">,</span> <span class=\"n\">config_text</span><span class=\"o\">=</span><span class=\"n\">cfg</span><span class=\"p\">)</span>\n</span></code></pre></div>",
|
|
5
|
+
"search": [
|
|
6
|
+
{
|
|
7
|
+
"location": null,
|
|
8
|
+
"level": 1,
|
|
9
|
+
"title": "Soliplex Plumber",
|
|
10
|
+
"text": "<p><code>soliplex-plumber</code> is the shared, stdlib-only library for reading and modifying the configuration of an existing Soliplex stack. It is the common dependency for the skill projects that operate on a generated stack -- the <code>soliplex-template</code> skill's <code>add_room.py</code> and the <code>soliplex-concierge</code> installer -- so the stack-wiring rules live in one place.</p> <p>Status</p> <p>The library is implemented and tested (100% branch coverage). It does pure filesystem work -- no Docker, no running backend, stdlib only.</p>",
|
|
11
|
+
"path": [],
|
|
12
|
+
"tags": []
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"location": "what-it-provides",
|
|
16
|
+
"level": 2,
|
|
17
|
+
"title": "What it provides",
|
|
18
|
+
"text": "<ul> <li><code>rooms</code> -- generic, template-agnostic logic for adding a room to a stack: resolve and validate the stack root, infer its package, and ensure <code>installation.yaml</code>'s <code>room_paths</code> loads the room (editing line-based, so comments and layout are preserved). See the API reference.</li> </ul> <p>There is no re-exporting package <code>__init__</code>; client code imports the submodule and uses its members by dotted name:</p> <pre><code>from soliplex_plumber import rooms\n\nproject = rooms.resolve_project(\"/path/to/stack\")\ninstalled = rooms.install_room(project, \"handbook\", config_text=cfg)\n</code></pre>",
|
|
19
|
+
"path": [],
|
|
20
|
+
"tags": []
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"title": "Soliplex Plumber",
|
|
24
|
+
"toc": [
|
|
25
|
+
{
|
|
26
|
+
"title": "Soliplex Plumber",
|
|
27
|
+
"content": "Soliplex Plumber",
|
|
28
|
+
"id": "soliplex-plumber",
|
|
29
|
+
"url": "#soliplex-plumber",
|
|
30
|
+
"children": [
|
|
31
|
+
{
|
|
32
|
+
"title": "What it provides",
|
|
33
|
+
"content": "What it provides",
|
|
34
|
+
"id": "what-it-provides",
|
|
35
|
+
"url": "#what-it-provides",
|
|
36
|
+
"children": [],
|
|
37
|
+
"level": 2
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"level": 1
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"hash": 7823077601700005752
|
|
45
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": {
|
|
3
|
+
"meta": {},
|
|
4
|
+
"content": "<h1 id=\"api-reference\">API reference<a class=\"headerlink\" href=\"#api-reference\" title=\"Permanent link\">¶</a></h1>\n<div class=\"admonition note\">\n<p class=\"admonition-title\">Implemented</p>\n<p>This is the library's public surface. Every member below is implemented\nand covered by the test suite (100% branch coverage); the modules under\n<code>src/soliplex_plumber/</code> are the source of truth.</p>\n</div>\n<p>There is no re-exporting package <code>__init__</code> — <strong>client code imports the\nsubmodule and uses its members by dotted name</strong> (e.g. <code>from soliplex_plumber\nimport rooms</code> then <code>rooms.install_room(...)</code>).</p>\n<h2 id=\"rooms-add-a-room-to-a-soliplex-stack\"><code>rooms</code> — add a room to a Soliplex stack<a class=\"headerlink\" href=\"#rooms-add-a-room-to-a-soliplex-stack\" title=\"Permanent link\">¶</a></h2>\n<p>Generic, template-agnostic, stdlib-only logic for wiring a room into a generated\nstack. The shared core behind both the <code>soliplex-template</code> skill's <code>add_room.py</code>\nand the <code>soliplex-concierge</code> installer; it edits <code>installation.yaml</code> line-based so\ncomments and layout are preserved.</p>\n<table>\n<thead>\n<tr>\n<th>Member</th>\n<th>Purpose</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>validate_room_id(room_id)</code></td>\n<td>enforce the room-id / path-segment rule (<code>ROOM_ID_RE</code>); raises <code>AddRoomError</code></td>\n</tr>\n<tr>\n<td><code>resolve_project(project_dir)</code></td>\n<td>resolve + verify the stack root (has <code>COMPOSE_FILE</code> and <code>INSTALLATION_FILE</code>)</td>\n</tr>\n<tr>\n<td><code>resolve_package_name(project, override)</code></td>\n<td>the stack's own package (inferred from <code>src/<pkg>/tools.py</code>) or <code>DEFAULT_PACKAGE_NAME</code></td>\n</tr>\n<tr>\n<td><code>add_room_path(text, room_id) -> (text, action)</code></td>\n<td>ensure <code>room_paths</code> loads the room; action is <code>ADDED</code> / <code>UNCHANGED</code> / <code>COVERED</code></td>\n</tr>\n<tr>\n<td><code>install_room(project, room_id, *, config_text, prompt_text=None, force=False, dry_run=False)</code></td>\n<td>write the room dir + config (+ optional prompt) and apply the <code>room_paths</code> edit</td>\n</tr>\n<tr>\n<td><code>RoomInstalled(config_path, path_action)</code></td>\n<td>the <code>install_room</code> outcome (alias <code>RoomInstall</code> kept for back-compat)</td>\n</tr>\n<tr>\n<td><code>AddRoomError</code></td>\n<td>user-facing error with message-factory classmethods</td>\n</tr>\n<tr>\n<td><code>ADDED</code> / <code>UNCHANGED</code> / <code>COVERED</code> / <code>ROOMS_PARENT_ENTRY</code></td>\n<td>the <code>room_paths</code> action constants and the <code>./rooms</code> auto-discovery entry</td>\n</tr>\n</tbody>\n</table>",
|
|
5
|
+
"search": [
|
|
6
|
+
{
|
|
7
|
+
"location": null,
|
|
8
|
+
"level": 1,
|
|
9
|
+
"title": "API reference",
|
|
10
|
+
"text": "<p>Implemented</p> <p>This is the library's public surface. Every member below is implemented and covered by the test suite (100% branch coverage); the modules under <code>src/soliplex_plumber/</code> are the source of truth.</p> <p>There is no re-exporting package <code>__init__</code> — client code imports the submodule and uses its members by dotted name (e.g. <code>from soliplex_plumber import rooms</code> then <code>rooms.install_room(...)</code>).</p>",
|
|
11
|
+
"path": [],
|
|
12
|
+
"tags": []
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"location": "rooms-add-a-room-to-a-soliplex-stack",
|
|
16
|
+
"level": 2,
|
|
17
|
+
"title": "<code>rooms</code> — add a room to a Soliplex stack",
|
|
18
|
+
"text": "<p>Generic, template-agnostic, stdlib-only logic for wiring a room into a generated stack. The shared core behind both the <code>soliplex-template</code> skill's <code>add_room.py</code> and the <code>soliplex-concierge</code> installer; it edits <code>installation.yaml</code> line-based so comments and layout are preserved.</p> Member Purpose <code>validate_room_id(room_id)</code> enforce the room-id / path-segment rule (<code>ROOM_ID_RE</code>); raises <code>AddRoomError</code> <code>resolve_project(project_dir)</code> resolve + verify the stack root (has <code>COMPOSE_FILE</code> and <code>INSTALLATION_FILE</code>) <code>resolve_package_name(project, override)</code> the stack's own package (inferred from <code>src/<pkg>/tools.py</code>) or <code>DEFAULT_PACKAGE_NAME</code> <code>add_room_path(text, room_id) -> (text, action)</code> ensure <code>room_paths</code> loads the room; action is <code>ADDED</code> / <code>UNCHANGED</code> / <code>COVERED</code> <code>install_room(project, room_id, *, config_text, prompt_text=None, force=False, dry_run=False)</code> write the room dir + config (+ optional prompt) and apply the <code>room_paths</code> edit <code>RoomInstalled(config_path, path_action)</code> the <code>install_room</code> outcome (alias <code>RoomInstall</code> kept for back-compat) <code>AddRoomError</code> user-facing error with message-factory classmethods <code>ADDED</code> / <code>UNCHANGED</code> / <code>COVERED</code> / <code>ROOMS_PARENT_ENTRY</code> the <code>room_paths</code> action constants and the <code>./rooms</code> auto-discovery entry",
|
|
19
|
+
"path": [],
|
|
20
|
+
"tags": []
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"title": "API reference",
|
|
24
|
+
"toc": [
|
|
25
|
+
{
|
|
26
|
+
"title": "API reference",
|
|
27
|
+
"content": "API reference",
|
|
28
|
+
"id": "api-reference",
|
|
29
|
+
"url": "#api-reference",
|
|
30
|
+
"children": [
|
|
31
|
+
{
|
|
32
|
+
"title": "rooms — add a room to a Soliplex stack",
|
|
33
|
+
"content": "<code>rooms</code> — add a room to a Soliplex stack",
|
|
34
|
+
"id": "rooms-add-a-room-to-a-soliplex-stack",
|
|
35
|
+
"url": "#rooms-add-a-room-to-a-soliplex-stack",
|
|
36
|
+
"children": [],
|
|
37
|
+
"level": 2
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"level": 1
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"hash": 10928872380341298869
|
|
45
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: Publish docs
|
|
2
|
+
|
|
3
|
+
# Builds the Zensical docs site (sources under docs/, config in zensical.toml)
|
|
4
|
+
# and publishes the generated site/ to the 'gh-pages' branch. Point the
|
|
5
|
+
# repository's Pages "Source" at the 'gh-pages' branch (Settings -> Pages) --
|
|
6
|
+
# a one-time manual step.
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
branches: [main]
|
|
11
|
+
paths:
|
|
12
|
+
- 'docs/**'
|
|
13
|
+
- 'zensical.toml'
|
|
14
|
+
- '.github/workflows/docs.yaml'
|
|
15
|
+
workflow_dispatch:
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
contents: write
|
|
19
|
+
|
|
20
|
+
# Allow one publish at a time; let an in-progress run finish.
|
|
21
|
+
concurrency:
|
|
22
|
+
group: publish-docs
|
|
23
|
+
cancel-in-progress: false
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
publish-docs:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
timeout-minutes: 10
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
|
|
32
|
+
- name: Install uv
|
|
33
|
+
uses: astral-sh/setup-uv@v6
|
|
34
|
+
|
|
35
|
+
- name: Set up Python
|
|
36
|
+
uses: actions/setup-python@v6
|
|
37
|
+
with:
|
|
38
|
+
python-version-file: "pyproject.toml"
|
|
39
|
+
|
|
40
|
+
# The 'docs' group provides zensical (pinned via uv.lock).
|
|
41
|
+
- name: Install dependencies
|
|
42
|
+
run: uv sync --frozen --group docs
|
|
43
|
+
|
|
44
|
+
- name: Build site
|
|
45
|
+
run: uv run zensical build --clean
|
|
46
|
+
|
|
47
|
+
- name: Publish to gh-pages
|
|
48
|
+
uses: peaceiris/actions-gh-pages@v4
|
|
49
|
+
with:
|
|
50
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
51
|
+
publish_dir: ./site
|
|
52
|
+
publish_branch: gh-pages
|
|
53
|
+
|
|
54
|
+
- name: Notify Slack on failure
|
|
55
|
+
if: failure()
|
|
56
|
+
uses: slackapi/slack-github-action@v2.1.0
|
|
57
|
+
with:
|
|
58
|
+
webhook: ${{ secrets.SLACK_NOTIFY_URL }}
|
|
59
|
+
webhook-type: incoming-webhook
|
|
60
|
+
payload: |
|
|
61
|
+
{
|
|
62
|
+
"channel": "#soliplex",
|
|
63
|
+
"username": "soliplex",
|
|
64
|
+
"text": ":x: ${{ github.workflow }} failed on ${{ github.head_ref }}:\n${{ github.event.head_commit.message }}\nhttps://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
|
|
65
|
+
"icon_emoji": ":ghost:"
|
|
66
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: Gitleaks Secret Scan
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- '**'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
gitleaks:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
timeout-minutes: 10
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout code
|
|
19
|
+
uses: actions/checkout@v6
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
|
|
23
|
+
- name: Run Gitleaks
|
|
24
|
+
id: gitleaks
|
|
25
|
+
uses: docker://ghcr.io/gitleaks/gitleaks:latest
|
|
26
|
+
continue-on-error: true
|
|
27
|
+
with:
|
|
28
|
+
args: detect --source=/github/workspace --config=/github/workspace/.gitleaks.toml --no-git --verbose --redact --report-format json --report-path /github/workspace/gitleaks-report.json
|
|
29
|
+
|
|
30
|
+
- name: Parse Gitleaks report
|
|
31
|
+
id: parse-report
|
|
32
|
+
if: steps.gitleaks.outcome == 'failure'
|
|
33
|
+
run: |
|
|
34
|
+
if [ -f gitleaks-report.json ]; then
|
|
35
|
+
FILES=$(jq -r '[.[].File] | unique | .[]' gitleaks-report.json | head -10 | tr '\n' ', ' | sed 's/,$//')
|
|
36
|
+
echo "matched_files=$FILES" >> $GITHUB_OUTPUT
|
|
37
|
+
else
|
|
38
|
+
echo "matched_files=unknown" >> $GITHUB_OUTPUT
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
- name: Fail if secrets found
|
|
42
|
+
if: steps.gitleaks.outcome == 'failure'
|
|
43
|
+
run: exit 1
|
|
44
|
+
|
|
45
|
+
- name: Notify Slack on failure
|
|
46
|
+
if: failure()
|
|
47
|
+
uses: rtCamp/action-slack-notify@v2
|
|
48
|
+
env:
|
|
49
|
+
SLACK_WEBHOOK: ${{ secrets.SLACK_NOTIFY_URL }}
|
|
50
|
+
SLACK_CHANNEL: soliplex
|
|
51
|
+
SLACK_USERNAME: gitleaks-ci
|
|
52
|
+
SLACK_ICON_EMOJI: ':rotating_light:'
|
|
53
|
+
SLACK_COLOR: danger
|
|
54
|
+
SLACK_TITLE: Gitleaks Secret Scan Failed
|
|
55
|
+
SLACK_MESSAGE: 'Potential secrets detected on ${{ github.ref_name }} in: ${{ steps.parse-report.outputs.matched_files }}\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n $GITHUB_OUTPUT'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Notify Slack on New issue
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issues:
|
|
5
|
+
types: [opened]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
notify:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- name: Send Slack notification when new issue is opened
|
|
12
|
+
uses: slackapi/slack-github-action@v2.1.0
|
|
13
|
+
with:
|
|
14
|
+
webhook: ${{ secrets.SLACK_NOTIFY_URL }}
|
|
15
|
+
webhook-type: incoming-webhook
|
|
16
|
+
payload: |
|
|
17
|
+
{
|
|
18
|
+
"channel": "#soliplex",
|
|
19
|
+
"username": "soliplex",
|
|
20
|
+
"text": ":bell: New issue by *${{ github.event.issue.user.login }}*:\n*${{ github.event.issue.title }}*\n${{ github.event.issue.html_url }}",
|
|
21
|
+
"icon_emoji": ":ghost:"
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Notify Slack on New PR
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
notify:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- name: Send Slack notification when new PR is opened
|
|
12
|
+
uses: slackapi/slack-github-action@v2.1.0
|
|
13
|
+
with:
|
|
14
|
+
webhook: ${{ secrets.SLACK_NOTIFY_URL }}
|
|
15
|
+
webhook-type: incoming-webhook
|
|
16
|
+
payload: |
|
|
17
|
+
{
|
|
18
|
+
"channel": "#soliplex",
|
|
19
|
+
"username": "soliplex",
|
|
20
|
+
"text": ":bell: New Pull Request by *${{ github.event.pull_request.user.login }}*:\n*${{ github.event.pull_request.title }}*\n${{ github.event.pull_request.html_url }}",
|
|
21
|
+
"icon_emoji": ":ghost:"
|
|
22
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Build & publish soliplex-plumber to pypi
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
# Mint a short-lived OIDC token for PyPI trusted publishing (no API token).
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
# Must match the Environment name on the PyPI trusted publisher.
|
|
16
|
+
environment:
|
|
17
|
+
name: pypi
|
|
18
|
+
url: https://pypi.org/p/soliplex-plumber
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v6
|
|
23
|
+
- name: Set up Python
|
|
24
|
+
uses: actions/setup-python@v6
|
|
25
|
+
with:
|
|
26
|
+
python-version-file: "pyproject.toml"
|
|
27
|
+
- name: Build soliplex-plumber
|
|
28
|
+
run: uv build --sdist --wheel
|
|
29
|
+
- name: Publish soliplex-plumber
|
|
30
|
+
run: uv publish --trusted-publishing always
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: Python linting
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
paths:
|
|
8
|
+
- 'pyproject.toml'
|
|
9
|
+
- 'src/soliplex_plumber/**'
|
|
10
|
+
- 'tests/**'
|
|
11
|
+
- '.github/workflows/python-lint.yaml'
|
|
12
|
+
- 'uv.lock'
|
|
13
|
+
pull_request:
|
|
14
|
+
branches:
|
|
15
|
+
- main
|
|
16
|
+
paths:
|
|
17
|
+
- 'pyproject.toml'
|
|
18
|
+
- 'src/soliplex_plumber/**'
|
|
19
|
+
- 'tests/**'
|
|
20
|
+
- '.github/workflows/python-lint.yaml'
|
|
21
|
+
- 'uv.lock'
|
|
22
|
+
workflow_dispatch:
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
lint-soliplex-plumber:
|
|
26
|
+
runs-on: ${{ matrix.os }}
|
|
27
|
+
timeout-minutes: 15
|
|
28
|
+
strategy:
|
|
29
|
+
fail-fast: true
|
|
30
|
+
matrix:
|
|
31
|
+
os: [ubuntu-latest]
|
|
32
|
+
python-version: ["3.13"]
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
|
|
36
|
+
- name: Install uv
|
|
37
|
+
uses: astral-sh/setup-uv@v6
|
|
38
|
+
|
|
39
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
40
|
+
uses: actions/setup-python@v6
|
|
41
|
+
with:
|
|
42
|
+
python-version: ${{ matrix.python-version }}
|
|
43
|
+
|
|
44
|
+
- name: Install dependencies
|
|
45
|
+
run: uv sync --frozen --group dev
|
|
46
|
+
|
|
47
|
+
- name: Check formatting
|
|
48
|
+
run: uv run ruff format --check
|
|
49
|
+
|
|
50
|
+
- name: Run ruff linter
|
|
51
|
+
run: uv run ruff check
|
|
52
|
+
|
|
53
|
+
- name: Notify Slack on failure
|
|
54
|
+
if: failure()
|
|
55
|
+
uses: slackapi/slack-github-action@v2.1.0
|
|
56
|
+
with:
|
|
57
|
+
webhook: ${{ secrets.SLACK_NOTIFY_URL }}
|
|
58
|
+
webhook-type: incoming-webhook
|
|
59
|
+
payload: |
|
|
60
|
+
{
|
|
61
|
+
"channel": "#soliplex",
|
|
62
|
+
"username": "soliplex",
|
|
63
|
+
"text": ":x: ${{ github.workflow }} failed on ${{ github.head_ref }}:\n${{ github.event.head_commit.message }}\nhttps://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
|
|
64
|
+
"icon_emoji": ":ghost:"
|
|
65
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: Python tests/coverage
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
paths:
|
|
8
|
+
- 'pyproject.toml'
|
|
9
|
+
- 'src/soliplex_plumber/**'
|
|
10
|
+
- 'tests/**'
|
|
11
|
+
- '.github/workflows/python-test.yaml'
|
|
12
|
+
- 'uv.lock'
|
|
13
|
+
pull_request:
|
|
14
|
+
branches:
|
|
15
|
+
- main
|
|
16
|
+
paths:
|
|
17
|
+
- 'pyproject.toml'
|
|
18
|
+
- 'src/soliplex_plumber/**'
|
|
19
|
+
- 'tests/**'
|
|
20
|
+
- '.github/workflows/python-test.yaml'
|
|
21
|
+
- 'uv.lock'
|
|
22
|
+
workflow_dispatch:
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
test-soliplex-plumber:
|
|
26
|
+
runs-on: ${{ matrix.os }}
|
|
27
|
+
timeout-minutes: 15
|
|
28
|
+
strategy:
|
|
29
|
+
fail-fast: true
|
|
30
|
+
matrix:
|
|
31
|
+
os: [ubuntu-latest]
|
|
32
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
33
|
+
include:
|
|
34
|
+
# Only enforce coverage on Python >= 3.13.
|
|
35
|
+
- python-version: "3.12"
|
|
36
|
+
pytest-args: "--no-cov"
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
|
|
40
|
+
- name: Install uv
|
|
41
|
+
uses: astral-sh/setup-uv@v6
|
|
42
|
+
|
|
43
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
44
|
+
uses: actions/setup-python@v6
|
|
45
|
+
with:
|
|
46
|
+
python-version: ${{ matrix.python-version }}
|
|
47
|
+
|
|
48
|
+
- name: Install dependencies
|
|
49
|
+
run: uv sync --frozen --group dev
|
|
50
|
+
|
|
51
|
+
- name: Run unit tests with coverage
|
|
52
|
+
run: uv run pytest ${{ matrix.pytest-args }}
|
|
53
|
+
|
|
54
|
+
- name: Notify Slack on failure
|
|
55
|
+
if: failure()
|
|
56
|
+
uses: slackapi/slack-github-action@v2.1.0
|
|
57
|
+
with:
|
|
58
|
+
webhook: ${{ secrets.SLACK_NOTIFY_URL }}
|
|
59
|
+
webhook-type: incoming-webhook
|
|
60
|
+
payload: |
|
|
61
|
+
{
|
|
62
|
+
"channel": "#soliplex",
|
|
63
|
+
"username": "soliplex",
|
|
64
|
+
"text": ":x: ${{ github.workflow }} failed on ${{ github.head_ref }}:\n${{ github.event.head_commit.message }}\nhttps://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
|
|
65
|
+
"icon_emoji": ":ghost:"
|
|
66
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: TruffleHog Secrets Scan
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- '**'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
trufflehog:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
timeout-minutes: 10
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout code
|
|
19
|
+
uses: actions/checkout@v6
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
- name: TruffleHog scan
|
|
23
|
+
uses: trufflesecurity/trufflehog@main
|
|
24
|
+
with:
|
|
25
|
+
extra_args: --only-verified
|
|
26
|
+
|
|
27
|
+
notify-failure:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
needs: [trufflehog]
|
|
30
|
+
if: failure()
|
|
31
|
+
timeout-minutes: 2
|
|
32
|
+
steps:
|
|
33
|
+
- name: Notify Slack on failure
|
|
34
|
+
uses: slackapi/slack-github-action@v2.1.1
|
|
35
|
+
with:
|
|
36
|
+
webhook: ${{ secrets.SLACK_NOTIFY_URL }}
|
|
37
|
+
webhook-type: incoming-webhook
|
|
38
|
+
payload: |
|
|
39
|
+
{
|
|
40
|
+
"channel": "#soliplex",
|
|
41
|
+
"username": "soliplex",
|
|
42
|
+
"text": ":rotating_light: ${{ github.workflow }} failed on ${{ github.head_ref || github.ref_name }}:\n${{ github.event.head_commit.message }}\nhttps://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
|
|
43
|
+
"icon_emoji": ":ghost:"
|
|
44
|
+
}
|