diff --git a/.gitignore b/.gitignore index 2ebb565c..f665de99 100644 --- a/.gitignore +++ b/.gitignore @@ -133,7 +133,5 @@ org-templates/**/.auth-token !/org-templates/molecule-dev /org-templates/molecule-dev/* !/org-templates/molecule-dev/system-prompt.md -/plugins/* -# Exception: molecule-medo lives here until it gets its own standalone repo. -!/plugins/molecule-medo/ +/plugins/ /workspace-configs-templates/ diff --git a/plugins/molecule-medo/plugin.yaml b/plugins/molecule-medo/plugin.yaml deleted file mode 100644 index 74adce13..00000000 --- a/plugins/molecule-medo/plugin.yaml +++ /dev/null @@ -1,6 +0,0 @@ -name: molecule-medo -version: 0.1.0 -description: Baidu MeDo no-code AI platform integration (hackathon / China-region) -author: Molecule AI -tags: [hackathon, baidu, medo, china] -runtimes: [claude_code, deepagents, langgraph] diff --git a/plugins/molecule-medo/skills/medo-tools/SKILL.md b/plugins/molecule-medo/skills/medo-tools/SKILL.md deleted file mode 100644 index a8fdd8c8..00000000 --- a/plugins/molecule-medo/skills/medo-tools/SKILL.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: MeDo Tools -description: > - Create, update, and publish applications on Baidu MeDo (摩搭), a no-code AI - application builder. Used in the Molecule AI hackathon integration (May 2026). -tags: [hackathon, baidu, medo, china, no-code] -examples: - - "Create a chatbot app on MeDo called 'Customer Support'" - - "Update the content of my MeDo app abc123" - - "Publish my MeDo app to production" ---- - -# MeDo Tools - -Provides three tools for interacting with the Baidu MeDo no-code platform: - -- **create_medo_app** — Scaffold a new application from a template (blank, chatbot, form, dashboard). -- **update_medo_app** — Push content or configuration changes to an existing application. -- **publish_medo_app** — Publish a draft application to production or staging. - -## Setup - -Set `MEDO_API_KEY` as a workspace secret. Optionally override the base URL via `MEDO_BASE_URL` -(default: `https://api.moda.baidu.com/v1`). - -When `MEDO_API_KEY` is absent the tools run in mock mode and return stub responses — safe for -local development and testing. diff --git a/plugins/molecule-medo/skills/medo-tools/scripts/medo.py b/plugins/molecule-medo/skills/medo-tools/scripts/medo.py deleted file mode 100644 index ddf53271..00000000 --- a/plugins/molecule-medo/skills/medo-tools/scripts/medo.py +++ /dev/null @@ -1,106 +0,0 @@ -"""MeDo tools — Baidu MeDo no-code AI platform integration. - -MeDo (摩搭, moda.baidu.com) is Baidu's no-code AI application builder used in -the Molecule AI hackathon integration (May 2026). Three core operations: - create_medo_app — scaffold a new application from a template - update_medo_app — push content / config changes to an existing app - publish_medo_app — publish a draft app to a target environment - -Authentication: set MEDO_API_KEY as a workspace secret. -Override base URL via MEDO_BASE_URL (default: https://api.moda.baidu.com/v1). - -Mock backend: when MEDO_API_KEY is absent the tools return a predictable stub -response — safe for unit tests and local development. -TODO: swap _mock_http_post for a real httpx.AsyncClient call once keys are live. -""" - -import logging -import os - -from langchain_core.tools import tool - -logger = logging.getLogger(__name__) - -MEDO_BASE_URL = os.environ.get("MEDO_BASE_URL", "https://api.moda.baidu.com/v1") -MEDO_API_KEY = os.environ.get("MEDO_API_KEY", "") - -_VALID_TEMPLATES = ("blank", "chatbot", "form", "dashboard") -_VALID_ENVS = ("production", "staging") - - -async def _mock_http_post(path: str, payload: dict) -> dict: - """Stub HTTP call. TODO: replace with real httpx.AsyncClient once MEDO_API_KEY is live.""" - return {"status": "ok", "mock": True, "path": path, "payload_keys": list(payload.keys())} - - -@tool -async def create_medo_app(name: str, template: str = "blank", description: str = "") -> dict: - """Create a new MeDo application. - - Args: - name: Application name (required). - template: Starting template — blank | chatbot | form | dashboard (default: blank). - description: Short description of the application. - - Returns: - dict with 'app_id' and 'status' on success, 'error' key on failure. - """ - if not name: - return {"error": "name is required"} - if template not in _VALID_TEMPLATES: - return {"error": f"template must be one of: {', '.join(_VALID_TEMPLATES)}"} - try: - result = await _mock_http_post("/apps", {"name": name, "template": template, "description": description}) - logger.info("MeDo create_app: name=%s template=%s → %s", name, template, result) - return result - except Exception as exc: - logger.exception("MeDo create_app failed") - return {"error": str(exc)} - - -@tool -async def update_medo_app(app_id: str, content: dict) -> dict: - """Push content or configuration changes to an existing MeDo application. - - Args: - app_id: The MeDo application ID returned by create_medo_app. - content: Dict of fields to update (e.g. {"title": "...", "nodes": [...]}). - - Returns: - dict with 'status' on success, 'error' key on failure. - """ - if not app_id: - return {"error": "app_id is required"} - if not content: - return {"error": "content must be a non-empty dict"} - try: - result = await _mock_http_post(f"/apps/{app_id}", content) - logger.info("MeDo update_app: app_id=%s keys=%s → %s", app_id, list(content.keys()), result) - return result - except Exception as exc: - logger.exception("MeDo update_app failed") - return {"error": str(exc)} - - -@tool -async def publish_medo_app(app_id: str, environment: str = "production") -> dict: - """Publish a MeDo application to a target environment. - - Args: - app_id: The MeDo application ID to publish. - environment: Target — production | staging (default: production). - - Returns: - dict with 'status' on success, 'error' key on failure. - """ - if not app_id: - return {"error": "app_id is required"} - if environment not in _VALID_ENVS: - return {"error": f"environment must be one of: {', '.join(_VALID_ENVS)}"} - try: - result = await _mock_http_post(f"/apps/{app_id}/publish", {"environment": environment}) - logger.info("MeDo publish_app: app_id=%s env=%s → %s", app_id, environment, result) - return result - except Exception as exc: - logger.exception("MeDo publish_app failed") - return {"error": str(exc)} diff --git a/plugins/molecule-medo/tests/conftest.py b/plugins/molecule-medo/tests/conftest.py deleted file mode 100644 index 413c2298..00000000 --- a/plugins/molecule-medo/tests/conftest.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Minimal conftest for molecule-medo plugin tests. - -langchain_core is a declared dependency of workspace-template (>=0.3.0) and -is expected to be present in the test environment. If it is absent, mock it -so the @tool decorator in medo.py is a no-op and the tests can still run. -""" - -import sys -from types import ModuleType - - -def _mock_langchain_if_missing(): - if "langchain_core" not in sys.modules: - lc_mod = ModuleType("langchain_core") - lc_tools_mod = ModuleType("langchain_core.tools") - lc_tools_mod.tool = lambda f: f # @tool becomes identity decorator - sys.modules["langchain_core"] = lc_mod - sys.modules["langchain_core.tools"] = lc_tools_mod - - -_mock_langchain_if_missing() diff --git a/plugins/molecule-medo/tests/test_medo.py b/plugins/molecule-medo/tests/test_medo.py deleted file mode 100644 index 301e8d7b..00000000 --- a/plugins/molecule-medo/tests/test_medo.py +++ /dev/null @@ -1,85 +0,0 @@ -"""Tests for plugins/molecule-medo/skills/medo-tools/scripts/medo.py. - -All tests exercise the mock backend (no MEDO_API_KEY required). - -NOTE: @tool is a LangChain decorator that returns a StructuredTool rather than -the raw async function. conftest.py mocks langchain_core.tools.tool as an -identity decorator so that calling the functions directly (without .ainvoke()) -works in tests — matching the original test approach. -""" - -import importlib.util -import sys -from pathlib import Path - -import pytest - -# plugin root: plugins/molecule-medo/ -_PLUGIN_ROOT = Path(__file__).resolve().parents[1] -_MEDO_PATH = _PLUGIN_ROOT / "skills" / "medo-tools" / "scripts" / "medo.py" - - -def _load_medo(): - spec = importlib.util.spec_from_file_location("medo_plugin_tools", _MEDO_PATH) - mod = importlib.util.module_from_spec(spec) - sys.modules["medo_plugin_tools"] = mod # register before exec to handle self-refs - spec.loader.exec_module(mod) - return mod - - -@pytest.fixture() -def medo(monkeypatch): - monkeypatch.delenv("MEDO_API_KEY", raising=False) - monkeypatch.delenv("MEDO_BASE_URL", raising=False) - return _load_medo() - - -class TestCreateMedoApp: - @pytest.mark.asyncio - async def test_requires_name(self, medo): - result = await medo.create_medo_app(name="") - assert "error" in result - - @pytest.mark.asyncio - async def test_rejects_unknown_template(self, medo): - result = await medo.create_medo_app(name="app", template="unknown") - assert "error" in result and "template" in result["error"] - - @pytest.mark.asyncio - async def test_mock_success(self, medo): - result = await medo.create_medo_app(name="my-app", template="chatbot") - assert result.get("mock") is True and result.get("status") == "ok" - - -class TestUpdateMedoApp: - @pytest.mark.asyncio - async def test_requires_app_id(self, medo): - result = await medo.update_medo_app(app_id="", content={"title": "x"}) - assert "error" in result - - @pytest.mark.asyncio - async def test_requires_non_empty_content(self, medo): - result = await medo.update_medo_app(app_id="abc", content={}) - assert "error" in result - - @pytest.mark.asyncio - async def test_mock_success(self, medo): - result = await medo.update_medo_app(app_id="abc", content={"title": "v2"}) - assert result.get("mock") is True and "abc" in result.get("path", "") - - -class TestPublishMedoApp: - @pytest.mark.asyncio - async def test_requires_app_id(self, medo): - result = await medo.publish_medo_app(app_id="") - assert "error" in result - - @pytest.mark.asyncio - async def test_rejects_invalid_environment(self, medo): - result = await medo.publish_medo_app(app_id="abc", environment="dev") - assert "error" in result and "environment" in result["error"] - - @pytest.mark.asyncio - async def test_mock_success(self, medo): - result = await medo.publish_medo_app(app_id="abc") - assert result.get("mock") is True and result.get("status") == "ok"