Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions meshmind/cli/plugin_stubs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Bundled demo plugins shipped with MeshMind."""
7 changes: 7 additions & 0 deletions meshmind/cli/plugin_stubs/demo_review/plugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
id: demo_review
name: Demo Review
version: "1.0.0"
capabilities:
- code_review
- review
- analysis
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Review code changes with emphasis on correctness, regressions, and missing tests.
State concrete findings first and keep summaries brief.
7 changes: 7 additions & 0 deletions meshmind/cli/plugin_stubs/demo_security/plugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
id: demo_security
name: Demo Security
version: "1.0.0"
capabilities:
- security
- threat_modeling
- analysis
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Review systems for security flaws, unsafe configuration, data exposure, and privilege boundaries.
Prioritize exploitable issues and concrete mitigations.
1 change: 1 addition & 0 deletions meshmind/cli/presets/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Packaged ``meshmind init`` preset templates."""
37 changes: 37 additions & 0 deletions meshmind/cli/presets/code.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
mesh:
name: "{{mesh_name}}"
discovery: "mdns"

defaults:
model: "gemma3:1b"
timeout: 120
retries: 1

nodes:
{{mesh_name}}-reviewer:
type: "specialist"
port: 8401
plugin: "demo_review"
system_prompt: |
You review code changes with a focus on bugs, regressions, and missing tests.
capabilities:
- "code_review"
- "review"
- "analysis"

{{mesh_name}}-security:
type: "specialist"
port: 8402
plugin: "demo_security"
system_prompt: |
You analyze applications for security risks, unsafe defaults, and hardening gaps.
capabilities:
- "security"
- "threat_modeling"
- "analysis"

{{mesh_name}}-coordinator:
type: "coordinator"
port: 8403
ui: true
ui_port: 8080
22 changes: 21 additions & 1 deletion meshmind/cli/repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import asyncio
import logging
from pathlib import Path
from typing import TYPE_CHECKING

import click
Expand Down Expand Up @@ -50,7 +51,25 @@ async def _read_line(prompt: str) -> str | None:
return None


async def run_mesh_repl(mesh: Mesh, *, base_url: str, stop: asyncio.Event) -> None:
def _append_history(history_path: Path | None, line: str) -> None:
"""Persist one entered line if history storage is configured."""
if history_path is None or not line:
return
try:
history_path.parent.mkdir(parents=True, exist_ok=True)
with history_path.open("a", encoding="utf-8") as f:
f.write(line + "\n")
except OSError:
logger.debug("Could not write REPL history to %s", history_path, exc_info=True)


async def run_mesh_repl(
mesh: Mesh,
*,
base_url: str,
stop: asyncio.Event,
repl_history_path: Path | None = None,
) -> None:
"""Block until the user types `stop` / `exit` or EOF; sets ``stop`` when exiting."""
click.echo()
click.echo(
Expand All @@ -68,6 +87,7 @@ async def run_mesh_repl(mesh: Mesh, *, base_url: str, stop: asyncio.Event) -> No
line = line.strip()
if not line:
continue
_append_history(repl_history_path, line)

parts = line.split(None, 1)
cmd = parts[0].lower() if parts else ""
Expand Down
72 changes: 42 additions & 30 deletions meshmind/plugins/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,39 @@

from __future__ import annotations

import importlib
import logging
from pathlib import Path
from typing import Any
import importlib
import logging
import os
from pathlib import Path
from typing import Any

import yaml

logger = logging.getLogger(__name__)

PLUGIN_DIR = Path.home() / ".meshmind" / "plugins"


def discover_plugins() -> list[dict[str, Any]]:
"""Discover installed plugins in ~/.meshmind/plugins/.

Returns list of plugin metadata dicts.
"""
plugins: list[dict[str, Any]] = []

if not PLUGIN_DIR.exists():
return plugins

for plugin_path in PLUGIN_DIR.iterdir():
logger = logging.getLogger(__name__)

DEFAULT_PLUGIN_DIR = Path.home() / ".meshmind" / "plugins"


def get_plugin_dir() -> Path:
"""Return the active plugin root, honoring ``MESHMIND_PLUGIN_DIR``."""
raw = os.environ.get("MESHMIND_PLUGIN_DIR", "").strip()
if raw:
return Path(raw).expanduser()
return DEFAULT_PLUGIN_DIR


def discover_plugins() -> list[dict[str, Any]]:
"""Discover installed plugins in ~/.meshmind/plugins/.

Returns list of plugin metadata dicts.
"""
plugins: list[dict[str, Any]] = []
plugin_dir = get_plugin_dir()

if not plugin_dir.exists():
return plugins

for plugin_path in plugin_dir.iterdir():
if not plugin_path.is_dir():
continue

Expand All @@ -65,12 +75,14 @@ def load_plugin(name: str) -> dict[str, Any]:
1. ~/.meshmind/plugins/<name>/
2. Installed Python packages named meshmind_<name>

Returns config dict with: system_prompt, functions, function_handlers, capabilities.
"""
# Try local plugin directory first
plugin_path = PLUGIN_DIR / name
if plugin_path.exists():
return _load_plugin_from_dir(plugin_path)
Returns config dict with: system_prompt, functions, function_handlers, capabilities.
"""
plugin_dir = get_plugin_dir()

# Try local plugin directory first
plugin_path = plugin_dir / name
if plugin_path.exists():
return _load_plugin_from_dir(plugin_path)

# Try installed Python package (plugin names are sanitized to alphanumeric + underscore)
sanitized = "".join(c if c.isalnum() or c == "_" else "_" for c in name)
Expand All @@ -83,9 +95,9 @@ def load_plugin(name: str) -> dict[str, Any]:
except ImportError:
pass

raise FileNotFoundError(
f"Plugin '{name}' not found in {PLUGIN_DIR} or as a Python package"
)
raise FileNotFoundError(
f"Plugin '{name}' not found in {plugin_dir} or as a Python package"
)


def _load_plugin_from_dir(plugin_path: Path) -> dict[str, Any]:
Expand Down Expand Up @@ -153,4 +165,4 @@ def _load_plugin_from_dir(plugin_path: Path) -> dict[str, Any]:
return config


__all__ = ["PLUGIN_DIR", "discover_plugins", "load_plugin"]
__all__ = ["DEFAULT_PLUGIN_DIR", "discover_plugins", "get_plugin_dir", "load_plugin"]
35 changes: 21 additions & 14 deletions meshmind/sdk/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@
from pathlib import Path
from typing import Any, Callable

from meshmind.config.parser import load_mesh_config
from meshmind.core.node import MeshNode
from meshmind.core.protocol import NodeInfo
from meshmind.knowledge.loader import load_knowledge_files
from meshmind.config.parser import load_mesh_config
from meshmind.core.node import MeshNode
from meshmind.core.protocol import NodeInfo
from meshmind.knowledge.loader import load_knowledge_files
from meshmind.plugins.merge import apply_plugin_to_nodedef

try:
from meshmind.sdk.node_def import NodeDef
Expand All @@ -41,8 +42,9 @@ class NodeDef:
capabilities: list[str] = field(default_factory=list)
knowledge_files: list[str] = field(default_factory=list)
knowledge_domains: list[str] = field(default_factory=list)
ui: bool = False
ui_port: int = 8080
ui: bool = False
ui_port: int = 8080
plugin: str = ""

def to_node_info(self) -> NodeInfo:
return NodeInfo(
Expand Down Expand Up @@ -159,19 +161,24 @@ def from_yaml(cls, path: str | Path) -> "Mesh":
system_prompt=node_def.system_prompt,
capabilities=list(node_def.capabilities),
knowledge_files=list(node_def.knowledge),
knowledge_domains=list(node_def.knowledge_domains),
ui=node_def.ui,
ui_port=node_def.ui_port,
)
mesh._node_defs.append(nd)
knowledge_domains=list(node_def.knowledge_domains),
ui=node_def.ui,
ui_port=node_def.ui_port,
plugin=node_def.plugin,
)
if nd.plugin:
apply_plugin_to_nodedef(nd, nd.plugin)
mesh._node_defs.append(nd)

return mesh

def add_node(self, node_def: NodeDef) -> None:
"""Add a specialist node to the mesh."""
if not node_def.model:
node_def.model = self.default_model
self._node_defs.append(node_def)
if not node_def.model:
node_def.model = self.default_model
if node_def.plugin:
apply_plugin_to_nodedef(node_def, node_def.plugin)
self._node_defs.append(node_def)

def add_coordinator(
self,
Expand Down
7 changes: 4 additions & 3 deletions meshmind/sdk/node_def.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ class NodeDef:
knowledge_files: list[str] = field(default_factory=list)
functions: list[dict] = field(default_factory=list)
function_handlers: dict = field(default_factory=dict)
knowledge_domains: list[str] = field(default_factory=list)
ui: bool = False
ui_port: int = 8080
knowledge_domains: list[str] = field(default_factory=list)
ui: bool = False
ui_port: int = 8080
plugin: str = ""

def to_node_info(self) -> NodeInfo:
"""Convert to a NodeInfo for the core layer."""
Expand Down
13 changes: 8 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ include = ["meshmind*"]

[tool.setuptools.package-data]
meshmind = [
"knowledge/*.json",
"ui/templates/*.html",
"ui/static/**/*",
"cli/templates/*.tmpl",
]
"knowledge/*.json",
"ui/templates/*.html",
"ui/static/**/*",
"cli/templates/*.tmpl",
"cli/presets/*.yaml",
"cli/plugin_stubs/*/plugin.yaml",
"cli/plugin_stubs/*/prompts/*.txt",
]
1 change: 1 addition & 0 deletions test-mesh/.meshmind/repl_history
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hi
37 changes: 37 additions & 0 deletions test-mesh/meshmind.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
mesh:
name: "test-mesh"
discovery: "mdns"

defaults:
model: "gemma3:1b"
timeout: 120
retries: 1

nodes:
test-mesh-reviewer:
type: "specialist"
port: 8401
plugin: "demo_review"
system_prompt: |
You review code changes with a focus on bugs, regressions, and missing tests.
capabilities:
- "code_review"
- "review"
- "analysis"

test-mesh-security:
type: "specialist"
port: 8402
plugin: "demo_security"
system_prompt: |
You analyze applications for security risks, unsafe defaults, and hardening gaps.
capabilities:
- "security"
- "threat_modeling"
- "analysis"

test-mesh-coordinator:
type: "coordinator"
port: 8403
ui: true
ui_port: 8080
Loading