diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06fd692..3392fe6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,6 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install and test - working-directory: hermes/keeperhub run: | python -m pip install --upgrade pip pip install -e ".[dev]" diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index 0bfb192..5c46102 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -1,20 +1,20 @@ name: release-pypi -# Publishes hermes-plugin-keeperhub (hermes/keeperhub) to PyPI via trusted -# publishing (OIDC). Triggered by a tag, e.g.: -# git tag py-hermes-v1.0.0 && git push origin py-hermes-v1.0.0 +# Publishes hermes-plugin-keeperhub to PyPI via trusted publishing (OIDC). +# Triggered by a version tag, e.g.: +# git tag v1.0.0 && git push origin v1.0.0 # # ONE-TIME SETUP (PyPI supports pending publishers — do this BEFORE first release): # On pypi.org: Your projects -> Publishing -> Add a pending publisher -> # PyPI project name: hermes-plugin-keeperhub -# Owner: KeeperHub Repository: agent-plugins +# Owner: KeeperHub Repository: hermes-plugin-keeperhub # Workflow name: release-pypi.yml Environment: pypi # Create the matching `pypi` environment under repo Settings -> Environments. on: push: tags: - - "py-hermes-v*" + - "v*" workflow_dispatch: permissions: @@ -26,9 +26,9 @@ concurrency: jobs: publish: - # Only publish from a py-hermes-v* tag. A manual workflow_dispatch (which can - # run from any branch) must not push a release to PyPI. - if: startsWith(github.ref, 'refs/tags/py-hermes-v') + # Only publish from a v* tag. A manual workflow_dispatch (which can run from + # any branch) must not push a release to PyPI. + if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest environment: pypi permissions: @@ -39,23 +39,19 @@ jobs: with: python-version: "3.12" - name: Verify tag matches pyproject version - working-directory: hermes/keeperhub env: REF_NAME: ${{ github.ref_name }} run: | - tag="${REF_NAME#py-hermes-v}" + tag="${REF_NAME#v}" ver="$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")" echo "tag=$tag pyproject=$ver" if [ "$tag" != "$ver" ]; then - echo "::error::tag py-hermes-v$tag does not match pyproject version $ver" + echo "::error::tag v$tag does not match pyproject version $ver" exit 1 fi - name: Build sdist and wheel - working-directory: hermes/keeperhub run: | python -m pip install --upgrade build python -m build - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: hermes/keeperhub/dist diff --git a/README.md b/README.md index 8e15546..a6bdc0e 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,80 @@ -# KeeperHub agent plugins +# hermes-plugin-keeperhub -Framework plugins that connect AI agent frameworks to -[KeeperHub](https://keeperhub.com) on-chain workflow automation. Each plugin -ships from its own subdirectory and publishes to that framework's registry. +A [Hermes](https://github.com/NousResearch/hermes-agent) agent plugin for +[KeeperHub](https://keeperhub.com) — gives your agent `kh_*` tools to manage and +run on-chain automation workflows, browse templates and protocol actions, and +(opt-in) execute transactions, all over the KeeperHub MCP API. -| Plugin | Framework | Path | Registry | -| --- | --- | --- | --- | -| `hermes-plugin-keeperhub` | [Hermes](https://github.com/NousResearch/hermes) | [`hermes/keeperhub`](./hermes/keeperhub) | PyPI | +## Install -Looking for the framework-agnostic MCP client foundation these build on? See -[`KeeperHub/mcp`](https://github.com/KeeperHub/mcp). For Claude Code, see -[`KeeperHub/claude-plugins`](https://github.com/KeeperHub/claude-plugins). +**Recommended — Hermes plugin manager (no pip needed):** -## Releasing +```bash +hermes plugins install KeeperHub/hermes-plugin-keeperhub --enable +``` + +**Or via pip / PyPI:** + +```bash +pip install hermes-plugin-keeperhub +``` +then enable it in your Hermes profile `~/.hermes/config.yaml`: +```yaml +plugins: + enabled: + - keeperhub +``` -Each plugin is versioned and tagged independently. Hermes / KeeperHub: +Either way, set your KeeperHub **organization** API key (prefix `kh_`, from +Settings → API Keys → Organisation) and restart Hermes: ```bash -git tag py-hermes-v1.0.0 && git push origin py-hermes-v1.0.0 +export KH_API_KEY="kh_..." ``` -publishes `hermes/keeperhub` to PyPI via OIDC trusted publishing -(`.github/workflows/release-pypi.yml`). +The plugin's only dependency is `httpx`, which ships with Hermes — so the +clone-based install needs nothing extra. + +## Safety: read-only by default + +By default the plugin registers **read-only** tools (list/get/search workflows, +executions, templates, integrations, action schemas, status). Tools that change +organization state or move funds on-chain are **withheld** until you opt in: + +```bash +export KEEPERHUB_ENABLE_WRITES=true +``` + +The gate is structural — withheld tools are never registered, so the agent can +neither call nor be delegated a tool that does not exist. + +| Mode | Tools | +| --- | --- | +| Default (read-only) | `kh_list_workflows`, `kh_get_workflow`, `kh_search_org_workflows`, `kh_search_workflows_marketplace`, `kh_get_execution_status`, `kh_get_execution_logs`, `kh_get_direct_execution_status`, `kh_search_templates`, `kh_get_template`, `kh_search_plugins`, `kh_get_plugin`, `kh_list_action_schemas`, `kh_search_protocol_actions`, `kh_list_integrations`, `kh_get_wallet_integration`, `kh_ai_generate_workflow`, `kh_tools_documentation`, `kh_status` | +| `KEEPERHUB_ENABLE_WRITES=true` adds | `kh_create_workflow`, `kh_update_workflow`, `kh_delete_workflow`, `kh_execute_workflow`, `kh_deploy_template`, `kh_call_workflow`, `kh_execute_protocol_action`, `kh_execute_transfer`, `kh_execute_contract_call`, `kh_execute_check_and_execute` | + +## Try it + +Ask your agent things like: + +- "List my KeeperHub workflows" +- "Show me workflow ``" +- "What action schemas and chains does KeeperHub support?" +- "Check my KeeperHub connection status" → runs `kh_status` + +## Configuration + +| Env var | Required | Description | +| --- | --- | --- | +| `KH_API_KEY` | yes | KeeperHub organization API key (`kh_…`). | +| `KEEPERHUB_ENABLE_WRITES` | no | Set to `true`/`1`/`yes`/`on` to register write/exec tools. Default off. | + +## Development + +```bash +pip install -e ".[dev]" +pytest -q +``` ## License diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a2bf78e --- /dev/null +++ b/__init__.py @@ -0,0 +1,21 @@ +"""Directory-plugin entry point (no-pip install path). + +When installed via `hermes plugins install KeeperHub/hermes-plugin-keeperhub`, +Hermes clones this repo into ~/.hermes/plugins/keeperhub/ and loads THIS file as +the plugin module, reading `register` off it. The relative import resolves the +bundled `keeperhub_plugin` subpackage from the cloned directory — no pip, no +sys.path entry required. (The pip/PyPI path uses the `keeperhub_plugin.entry` +console entry point instead and never imports this file.) +""" + +try: + # Hermes directory-load: this file is executed with a synthetic parent + # package (hermes_plugins.keeperhub) + __path__, so the relative import + # resolves the bundled subpackage from the cloned directory — no pip. + from .keeperhub_plugin.entry import register +except ImportError: + # Imported standalone (e.g. a test collector, or keeperhub_plugin already on + # sys.path) where there is no parent package for the relative form. + from keeperhub_plugin.entry import register + +__all__ = ["register"] diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..cecb773 --- /dev/null +++ b/conftest.py @@ -0,0 +1,6 @@ +# The repo-root ./__init__.py is the Hermes directory-plugin entry point — a +# relative-import module that Hermes loads with a synthetic parent package +# (hermes_plugins.keeperhub). It is NOT a test module; if pytest collects/imports +# it directly the relative import raises "attempted relative import with no known +# parent package". Exclude it from collection. +collect_ignore = ["__init__.py"] diff --git a/hermes/keeperhub/LICENSE b/hermes/keeperhub/LICENSE deleted file mode 100644 index 4b9d3ea..0000000 --- a/hermes/keeperhub/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2026 KeeperHub - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/hermes/keeperhub/README.md b/hermes/keeperhub/README.md deleted file mode 100644 index 9de1c2b..0000000 --- a/hermes/keeperhub/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# hermes-plugin-keeperhub - -A [Hermes](https://github.com/NousResearch/hermes) agent plugin for -[KeeperHub](https://keeperhub.com) — give your agent `kh_*` tools to manage and -run on-chain automation workflows, browse templates and protocol actions, and -(opt-in) execute transactions, all over the KeeperHub MCP API. - -## Install - -```bash -pip install hermes-plugin-keeperhub -``` - -Set your KeeperHub **organization** API key (prefix `kh_`, from Settings → API -Keys → Organisation): - -```bash -export KH_API_KEY="kh_..." -``` - -Then enable the plugin in your Hermes profile `config.yaml`: - -```yaml -plugins: - enabled: - - keeperhub -``` - -Hermes discovers the plugin through its `hermes_agent.plugins` entry point on -the next gateway start — no directory copying required. - -## Safety: read-only by default - -By default the plugin registers **read-only** tools (list/get/search workflows, -executions, templates, integrations, action schemas, status). Tools that change -organization state or move funds on-chain are **withheld** until you opt in: - -```bash -export KEEPERHUB_ENABLE_WRITES=true -``` - -The gate is structural — withheld tools are never registered, so the agent can -neither call nor be delegated a tool that does not exist. - -| Mode | Tools | -| --- | --- | -| Default (read-only) | `kh_list_workflows`, `kh_get_workflow`, `kh_search_org_workflows`, `kh_search_workflows_marketplace`, `kh_get_execution_status`, `kh_get_execution_logs`, `kh_get_direct_execution_status`, `kh_search_templates`, `kh_get_template`, `kh_search_plugins`, `kh_get_plugin`, `kh_list_action_schemas`, `kh_search_protocol_actions`, `kh_list_integrations`, `kh_get_wallet_integration`, `kh_ai_generate_workflow`, `kh_tools_documentation`, `kh_status` | -| `KEEPERHUB_ENABLE_WRITES=true` adds | `kh_create_workflow`, `kh_update_workflow`, `kh_delete_workflow`, `kh_execute_workflow`, `kh_deploy_template`, `kh_call_workflow`, `kh_execute_protocol_action`, `kh_execute_transfer`, `kh_execute_contract_call`, `kh_execute_check_and_execute` | - -## Try it - -Once enabled, ask your agent things like: - -- "List my KeeperHub workflows" -- "Show me workflow ``" -- "What action schemas and chains does KeeperHub support?" -- "Check my KeeperHub connection status" → runs `kh_status` - -## Configuration - -| Env var | Required | Description | -| --- | --- | --- | -| `KH_API_KEY` | yes | KeeperHub organization API key (`kh_…`). | -| `KEEPERHUB_ENABLE_WRITES` | no | Set to `true`/`1`/`yes`/`on` to register write/exec tools. Default off. | - -## Development - -```bash -pip install -e ".[dev]" -pytest -q -``` - -## License - -[Apache-2.0](./LICENSE) diff --git a/hermes/keeperhub/__init__.py b/hermes/keeperhub/__init__.py deleted file mode 100644 index bc15bcf..0000000 --- a/hermes/keeperhub/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Directory-layout entry for ~/.hermes/plugins/keeperhub — re-export register.""" - -from keeperhub_plugin.entry import register - -__all__ = ["register"] diff --git a/hermes/keeperhub/keeperhub_plugin/__init__.py b/keeperhub_plugin/__init__.py similarity index 100% rename from hermes/keeperhub/keeperhub_plugin/__init__.py rename to keeperhub_plugin/__init__.py diff --git a/hermes/keeperhub/keeperhub_plugin/client.py b/keeperhub_plugin/client.py similarity index 100% rename from hermes/keeperhub/keeperhub_plugin/client.py rename to keeperhub_plugin/client.py diff --git a/hermes/keeperhub/keeperhub_plugin/config.py b/keeperhub_plugin/config.py similarity index 100% rename from hermes/keeperhub/keeperhub_plugin/config.py rename to keeperhub_plugin/config.py diff --git a/hermes/keeperhub/keeperhub_plugin/entry.py b/keeperhub_plugin/entry.py similarity index 82% rename from hermes/keeperhub/keeperhub_plugin/entry.py rename to keeperhub_plugin/entry.py index 27e7635..e737b65 100644 --- a/hermes/keeperhub/keeperhub_plugin/entry.py +++ b/keeperhub_plugin/entry.py @@ -13,16 +13,16 @@ import os from typing import Any -from keeperhub_plugin.tools_execution import register_execution_tools -from keeperhub_plugin.tools_generate import register_generate_tools -from keeperhub_plugin.tools_integrations import register_integration_tools -from keeperhub_plugin.tools_marketplace import register_marketplace_tools -from keeperhub_plugin.tools_plugins import register_plugin_tools -from keeperhub_plugin.tools_protocol import register_protocol_tools -from keeperhub_plugin.tools_direct import register_direct_tools -from keeperhub_plugin.tools_status import register_status_tool -from keeperhub_plugin.tools_templates import register_template_tools -from keeperhub_plugin.tools_workflows import register_workflow_tools +from .tools_execution import register_execution_tools +from .tools_generate import register_generate_tools +from .tools_integrations import register_integration_tools +from .tools_marketplace import register_marketplace_tools +from .tools_plugins import register_plugin_tools +from .tools_protocol import register_protocol_tools +from .tools_direct import register_direct_tools +from .tools_status import register_status_tool +from .tools_templates import register_template_tools +from .tools_workflows import register_workflow_tools logger = logging.getLogger(__name__) diff --git a/hermes/keeperhub/keeperhub_plugin/tools_direct.py b/keeperhub_plugin/tools_direct.py similarity index 98% rename from hermes/keeperhub/keeperhub_plugin/tools_direct.py rename to keeperhub_plugin/tools_direct.py index 46b6f61..c0da470 100644 --- a/hermes/keeperhub/keeperhub_plugin/tools_direct.py +++ b/keeperhub_plugin/tools_direct.py @@ -5,7 +5,7 @@ import json from typing import Any -from keeperhub_plugin.tools_shared import TOOLSET, as_record, compact, fenced_json, run_mcp +from .tools_shared import TOOLSET, as_record, compact, fenced_json, run_mcp _WRITE_WARNING = ( "⚠️ Submits an on-chain transaction using your KeeperHub wallet. Verify all parameters before invoking." diff --git a/hermes/keeperhub/keeperhub_plugin/tools_execution.py b/keeperhub_plugin/tools_execution.py similarity index 96% rename from hermes/keeperhub/keeperhub_plugin/tools_execution.py rename to keeperhub_plugin/tools_execution.py index e38fa2e..a665ac7 100644 --- a/hermes/keeperhub/keeperhub_plugin/tools_execution.py +++ b/keeperhub_plugin/tools_execution.py @@ -4,7 +4,7 @@ from typing import Any -from keeperhub_plugin.tools_shared import TOOLSET, as_record, fenced_json, run_mcp +from .tools_shared import TOOLSET, as_record, fenced_json, run_mcp def register_execution_tools(ctx: Any) -> None: diff --git a/hermes/keeperhub/keeperhub_plugin/tools_generate.py b/keeperhub_plugin/tools_generate.py similarity index 96% rename from hermes/keeperhub/keeperhub_plugin/tools_generate.py rename to keeperhub_plugin/tools_generate.py index e5e5bf5..a3e77b5 100644 --- a/hermes/keeperhub/keeperhub_plugin/tools_generate.py +++ b/keeperhub_plugin/tools_generate.py @@ -4,7 +4,7 @@ from typing import Any -from keeperhub_plugin.tools_shared import TOOLSET, as_record, err_json, fenced_json, run_mcp +from .tools_shared import TOOLSET, as_record, err_json, fenced_json, run_mcp def _fmt_ai_generate(result: Any) -> str: diff --git a/hermes/keeperhub/keeperhub_plugin/tools_integrations.py b/keeperhub_plugin/tools_integrations.py similarity index 97% rename from hermes/keeperhub/keeperhub_plugin/tools_integrations.py rename to keeperhub_plugin/tools_integrations.py index 50fe407..19f4b2f 100644 --- a/hermes/keeperhub/keeperhub_plugin/tools_integrations.py +++ b/keeperhub_plugin/tools_integrations.py @@ -4,7 +4,7 @@ from typing import Any -from keeperhub_plugin.tools_shared import TOOLSET, as_record, run_mcp +from .tools_shared import TOOLSET, as_record, run_mcp def register_integration_tools(ctx: Any) -> None: diff --git a/hermes/keeperhub/keeperhub_plugin/tools_marketplace.py b/keeperhub_plugin/tools_marketplace.py similarity index 97% rename from hermes/keeperhub/keeperhub_plugin/tools_marketplace.py rename to keeperhub_plugin/tools_marketplace.py index 072a8c5..7937f88 100644 --- a/hermes/keeperhub/keeperhub_plugin/tools_marketplace.py +++ b/keeperhub_plugin/tools_marketplace.py @@ -5,9 +5,9 @@ import logging from typing import Any -from keeperhub_plugin.client import get_client -from keeperhub_plugin.config import resolve_api_key -from keeperhub_plugin.tools_shared import ( +from .client import get_client +from .config import resolve_api_key +from .tools_shared import ( TOOLSET, NOT_FOUND_RE, as_record, diff --git a/hermes/keeperhub/keeperhub_plugin/tools_plugins.py b/keeperhub_plugin/tools_plugins.py similarity index 98% rename from hermes/keeperhub/keeperhub_plugin/tools_plugins.py rename to keeperhub_plugin/tools_plugins.py index 52ebae5..f85dfc4 100644 --- a/hermes/keeperhub/keeperhub_plugin/tools_plugins.py +++ b/keeperhub_plugin/tools_plugins.py @@ -4,7 +4,7 @@ from typing import Any -from keeperhub_plugin.tools_shared import TOOLSET, as_record, compact, fenced_json, run_mcp +from .tools_shared import TOOLSET, as_record, compact, fenced_json, run_mcp _PLUGIN_CATEGORIES = ("web3", "discord", "sendgrid", "system", "triggers") diff --git a/hermes/keeperhub/keeperhub_plugin/tools_protocol.py b/keeperhub_plugin/tools_protocol.py similarity index 97% rename from hermes/keeperhub/keeperhub_plugin/tools_protocol.py rename to keeperhub_plugin/tools_protocol.py index 9729055..c210aad 100644 --- a/hermes/keeperhub/keeperhub_plugin/tools_protocol.py +++ b/keeperhub_plugin/tools_protocol.py @@ -4,7 +4,7 @@ from typing import Any -from keeperhub_plugin.tools_shared import TOOLSET, as_record, compact, fenced_json, run_mcp +from .tools_shared import TOOLSET, as_record, compact, fenced_json, run_mcp _PROTOCOLS = ("aave", "morpho", "chronicle", "chainlink", "uniswap", "compound", "lido", "maker") diff --git a/hermes/keeperhub/keeperhub_plugin/tools_shared.py b/keeperhub_plugin/tools_shared.py similarity index 94% rename from hermes/keeperhub/keeperhub_plugin/tools_shared.py rename to keeperhub_plugin/tools_shared.py index 45e0f55..dc2bf85 100644 --- a/hermes/keeperhub/keeperhub_plugin/tools_shared.py +++ b/keeperhub_plugin/tools_shared.py @@ -7,8 +7,8 @@ import re from typing import Any, Callable -from keeperhub_plugin.client import KeeperHubClient, get_client -from keeperhub_plugin.config import resolve_api_key +from .client import KeeperHubClient, get_client +from .config import resolve_api_key logger = logging.getLogger(__name__) diff --git a/hermes/keeperhub/keeperhub_plugin/tools_status.py b/keeperhub_plugin/tools_status.py similarity index 92% rename from hermes/keeperhub/keeperhub_plugin/tools_status.py rename to keeperhub_plugin/tools_status.py index f09bff7..96133e9 100644 --- a/hermes/keeperhub/keeperhub_plugin/tools_status.py +++ b/keeperhub_plugin/tools_status.py @@ -4,9 +4,9 @@ from typing import Any -from keeperhub_plugin.client import get_client -from keeperhub_plugin.config import SUPPORTED_ENV_VARS, is_likely_valid_api_key, resolve_api_key -from keeperhub_plugin.tools_shared import TOOLSET, ok_json +from .client import get_client +from .config import SUPPORTED_ENV_VARS, is_likely_valid_api_key, resolve_api_key +from .tools_shared import TOOLSET, ok_json def register_status_tool(ctx: Any) -> None: diff --git a/hermes/keeperhub/keeperhub_plugin/tools_templates.py b/keeperhub_plugin/tools_templates.py similarity index 98% rename from hermes/keeperhub/keeperhub_plugin/tools_templates.py rename to keeperhub_plugin/tools_templates.py index f576678..dd74783 100644 --- a/hermes/keeperhub/keeperhub_plugin/tools_templates.py +++ b/keeperhub_plugin/tools_templates.py @@ -4,7 +4,7 @@ from typing import Any -from keeperhub_plugin.tools_shared import TOOLSET, as_record, compact, run_mcp +from .tools_shared import TOOLSET, as_record, compact, run_mcp def _schema( diff --git a/hermes/keeperhub/keeperhub_plugin/tools_workflows.py b/keeperhub_plugin/tools_workflows.py similarity index 99% rename from hermes/keeperhub/keeperhub_plugin/tools_workflows.py rename to keeperhub_plugin/tools_workflows.py index 10e009f..88ecc25 100644 --- a/hermes/keeperhub/keeperhub_plugin/tools_workflows.py +++ b/keeperhub_plugin/tools_workflows.py @@ -4,7 +4,7 @@ from typing import Any -from keeperhub_plugin.tools_shared import ( +from .tools_shared import ( TOOLSET, as_record, compact, diff --git a/hermes/keeperhub/plugin.yaml b/plugin.yaml similarity index 100% rename from hermes/keeperhub/plugin.yaml rename to plugin.yaml diff --git a/hermes/keeperhub/pyproject.toml b/pyproject.toml similarity index 75% rename from hermes/keeperhub/pyproject.toml rename to pyproject.toml index 4169f56..0d849d7 100644 --- a/hermes/keeperhub/pyproject.toml +++ b/pyproject.toml @@ -20,8 +20,8 @@ dev = ["pytest>=8"] [project.urls] Homepage = "https://keeperhub.com" Documentation = "https://docs.keeperhub.com" -Repository = "https://github.com/KeeperHub/agent-plugins" -Issues = "https://github.com/KeeperHub/agent-plugins/issues" +Repository = "https://github.com/KeeperHub/hermes-plugin-keeperhub" +Issues = "https://github.com/KeeperHub/hermes-plugin-keeperhub/issues" # Hermes discovers pip-installed plugins via this entry-point group. The value # MUST be a MODULE path (no ":register" suffix): Hermes' loader resolves the @@ -39,3 +39,8 @@ include = ["keeperhub_plugin*"] [tool.pytest.ini_options] testpaths = ["tests"] pythonpath = ["."] +# importlib mode imports test files by path and does NOT walk up into the repo +# root as a package — required because the root __init__.py is the Hermes +# directory-plugin entry (a relative-import module) and must not be imported by +# the test collector. +addopts = "--import-mode=importlib" diff --git a/hermes/keeperhub/tests/conftest.py b/tests/conftest.py similarity index 100% rename from hermes/keeperhub/tests/conftest.py rename to tests/conftest.py diff --git a/hermes/keeperhub/tests/test_client.py b/tests/test_client.py similarity index 100% rename from hermes/keeperhub/tests/test_client.py rename to tests/test_client.py diff --git a/hermes/keeperhub/tests/test_marketplace_fallback.py b/tests/test_marketplace_fallback.py similarity index 100% rename from hermes/keeperhub/tests/test_marketplace_fallback.py rename to tests/test_marketplace_fallback.py diff --git a/hermes/keeperhub/tests/test_write_gate.py b/tests/test_write_gate.py similarity index 100% rename from hermes/keeperhub/tests/test_write_gate.py rename to tests/test_write_gate.py