diff --git a/.tmp/sitecustomize.py b/.tmp/sitecustomize.py new file mode 100644 index 0000000..07cb26a --- /dev/null +++ b/.tmp/sitecustomize.py @@ -0,0 +1,8 @@ +import tempfile + +_real_mkdir = tempfile._os.mkdir + +def _mkdir(path, mode=0o777): + return _real_mkdir(path, 0o777) + +tempfile._os.mkdir = _mkdir diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..955f2b6 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,15 @@ +include README.md +include LICENSE +include CHANGELOG.md +include CONTRIBUTING.md +include requirements.txt + +recursive-include meshmind/knowledge *.json +recursive-include meshmind/ui/template *.html +recursive-include meshmind/ui/static * +recursive-include meshmind/cli/templates *.tmpl +recursive-include meshmind/cli/presets *.yaml +recursive-include meshmind/cli/plugin_stubs *.yaml *.txt *.md + +prune meshmind.egg-info +global-exclude __pycache__ *.py[cod] diff --git a/PUBLISHING.md b/PUBLISHING.md new file mode 100644 index 0000000..7b1caf9 --- /dev/null +++ b/PUBLISHING.md @@ -0,0 +1,36 @@ +# Publishing to PyPI + +## 1) Prep + +- Make sure `pyproject.toml` has the right `project.version`. +- Update `CHANGELOG.md` (and optionally docs release notes). +- Tag the release (recommended): `git tag vX.Y.Z` + +## 2) Build + +```bash +python -m pip install -U build twine +python -m build +python -m twine check dist/* +``` + +## 3) Upload + +TestPyPI (recommended first): + +```bash +python -m twine upload -r testpypi dist/* +``` + +PyPI: + +```bash +python -m twine upload dist/* +``` + +## 4) Verify install + +```bash +python -m pip install -U meshmind +meshmind --help +``` diff --git a/README.md b/README.md index 9cc6294..9aa68a2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -
+
# MeshMind @@ -28,7 +28,7 @@ This is not distributed inference for a single giant model. MeshMind is built ar **Install (released package):** Python **3.11+** and [Ollama](https://ollama.com). Stable installs: ```bash -pip install meshmind +pip install -U meshmind ``` **Install (from source, contributors):** from the repo root: diff --git a/docs/plugins.md b/docs/plugins.md index 7e7291d..621c6ae 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -52,8 +52,8 @@ When `meshmind.yaml` sets `plugin: ""` on a node: ## Bundled demos -The repo ships stub plugins `demo_review` and `demo_security` under `meshmind/cli/plugin_stubs/`. -`meshmind init --preset code` can copy them into your plugin directory (default unless `--skip-bundled-plugins`). +The repo ships stub plugins `demo_review`, `demo_security`, `demo_documentation`, and `demo_research` under `meshmind/cli/plugin_stubs/`. +`meshmind init --preset code` can copy `demo_review` and `demo_security` into your plugin directory (default unless `--skip-bundled-plugins`). If you skipped that step or moved machines, run `meshmind plugin sync -c meshmind.yaml` before `meshmind up`. ## meshmind.yaml diff --git a/meshmind/cli/__init__.py b/meshmind/cli/__init__.py new file mode 100644 index 0000000..256192e --- /dev/null +++ b/meshmind/cli/__init__.py @@ -0,0 +1 @@ +"""MeshMind CLI package.""" diff --git a/meshmind/cli/__inti__.py b/meshmind/cli/__inti__.py deleted file mode 100644 index e69de29..0000000 diff --git a/meshmind/cli/plugin_stubs/demo_documentation/plugin.yaml b/meshmind/cli/plugin_stubs/demo_documentation/plugin.yaml new file mode 100644 index 0000000..ad1311e --- /dev/null +++ b/meshmind/cli/plugin_stubs/demo_documentation/plugin.yaml @@ -0,0 +1,13 @@ +id: demo_documentation +name: Demo Documentation +capabilities: + - documentation + - writing + - editing +functions: + - name: write_documentation + description: Generate clear documentation, user guides, or API reference. + parameters: {} + - name: improve_readability + description: Rewrite text to make it easier to understand and scan. + parameters: {} diff --git a/meshmind/cli/plugin_stubs/demo_documentation/prompts/system_prompt.txt b/meshmind/cli/plugin_stubs/demo_documentation/prompts/system_prompt.txt new file mode 100644 index 0000000..e08d807 --- /dev/null +++ b/meshmind/cli/plugin_stubs/demo_documentation/prompts/system_prompt.txt @@ -0,0 +1 @@ +You are a documentation specialist. Produce clear, concise guides, API reference, and examples with a reader-first approach. diff --git a/meshmind/cli/plugin_stubs/demo_research/plugin.yaml b/meshmind/cli/plugin_stubs/demo_research/plugin.yaml new file mode 100644 index 0000000..4a0d8a7 --- /dev/null +++ b/meshmind/cli/plugin_stubs/demo_research/plugin.yaml @@ -0,0 +1,13 @@ +id: demo_research +name: Demo Research +capabilities: + - research + - analysis + - summarization +functions: + - name: research_topic + description: Investigate a topic and summarize key findings. + parameters: {} + - name: extract_insights + description: Extract the most important insights from a body of text. + parameters: {} diff --git a/meshmind/cli/plugin_stubs/demo_research/prompts/system_prompt.txt b/meshmind/cli/plugin_stubs/demo_research/prompts/system_prompt.txt new file mode 100644 index 0000000..1bcbfdf --- /dev/null +++ b/meshmind/cli/plugin_stubs/demo_research/prompts/system_prompt.txt @@ -0,0 +1 @@ +You are a researcher assistant. Gather relevant information, analyze it critically, and summarize your findings in a useful way. diff --git a/meshmind/config/schema.py b/meshmind/config/schema.py index afa963f..9ac1a4c 100644 --- a/meshmind/config/schema.py +++ b/meshmind/config/schema.py @@ -59,6 +59,7 @@ class DefaultsConfig(BaseModel): model: str = "gemma4" timeout: int = 60 retries: int = 1 + capability_keywords: dict[str, list[str]] = Field(default_factory=dict) class PluginReference(BaseModel): diff --git a/meshmind/core/node.py b/meshmind/core/node.py index 124c1ea..6d2b68c 100644 --- a/meshmind/core/node.py +++ b/meshmind/core/node.py @@ -250,6 +250,7 @@ def __init__( registry=self.registry, ai_engine=self.ai_engine if use_ai else None, event_logger=self._log_event, + capability_keywords=config.get("capability_keywords"), ) self.discovery = MeshDiscovery( diff --git a/meshmind/core/orchestrator.py b/meshmind/core/orchestrator.py index a287fde..9b0f213 100644 --- a/meshmind/core/orchestrator.py +++ b/meshmind/core/orchestrator.py @@ -139,6 +139,7 @@ def __init__( ai_engine=None, max_retries: int = 0, event_logger=None, + capability_keywords: dict[str, list[str]] | None = None, ) -> None: self.node_id = node_id self.node_name = node_name @@ -148,6 +149,7 @@ def __init__( self.event_logger = event_logger self.traces: list[QueryTrace] = [] self._max_traces = 100 + self._capability_keywords = capability_keywords def _log_event(self, event_type: str, data: dict[str, Any]) -> None: if callable(self.event_logger): @@ -186,7 +188,7 @@ async def _classify_and_route(self, query: str) -> list[dict]: except Exception: logger.warning("AI classification failed, falling back to keywords") - return build_routing_plan(self.registry, query, self.node_id) + return build_routing_plan(self.registry, query, self.node_id, self._capability_keywords) async def process_query(self, query: str) -> tuple[str, QueryTrace]: """Process a user query by routing to specialized nodes and aggregating responses. diff --git a/meshmind/sdk/mesh.py b/meshmind/sdk/mesh.py index f9319e3..ef364ec 100644 --- a/meshmind/sdk/mesh.py +++ b/meshmind/sdk/mesh.py @@ -122,6 +122,7 @@ def __init__( manual_peers: list[dict] | None = None, default_model: str = "gemma4", dev_mode: bool | None = None, + capability_keywords: dict[str, list[str]] | None = None, ) -> None: self.name = name self.discovery = discovery @@ -138,6 +139,7 @@ def __init__( self._running = False self._event_handlers: dict[str, list[Callable]] = {} self._config_dir: Path = Path(".") + self._capability_keywords = capability_keywords or {} @classmethod def from_yaml(cls, path: str | Path) -> "Mesh": @@ -152,6 +154,7 @@ def from_yaml(cls, path: str | Path) -> "Mesh": manual_peers=peers, default_model=config.defaults.model, dev_mode=None, + capability_keywords=config.defaults.capability_keywords, ) mesh._config_dir = path.parent @@ -235,6 +238,10 @@ async def start(self) -> None: + knowledge_context ) + # Add mesh-wide capability keywords to node config + if self._capability_keywords: + config["capability_keywords"] = self._capability_keywords + node = MeshNode( node_info=node_info, config=config, diff --git a/my-project/.meshmind/repl_history b/my-project/.meshmind/repl_history new file mode 100644 index 0000000..0d7fd40 --- /dev/null +++ b/my-project/.meshmind/repl_history @@ -0,0 +1,68 @@ + +# 2026-04-20 11:27:03.556918 ++python -m venv venv-test ++.\venv-test\Scripts\Activate.ps1 ++python -m pip install --upgrade pip ++python -m pip install meshmind==0.1.1 ++meshmind --version + +# 2026-04-20 12:19:55.876072 ++ meshmind plugin install demo_documentation ++meshmind plugin install demo_research + +# 2026-04-20 12:32:16.012494 ++hi + +# 2026-04-20 12:40:28.008274 ++hiii which plugins are there + +# 2026-04-20 12:44:55.592211 ++What tools do you have available? + +# 2026-04-20 12:50:44.647563 ++Write a short guide on how to install Python packages + +# 2026-04-20 12:52:01.938719 ++hi + +# 2026-04-20 12:52:13.162441 ++ + +# 2026-04-20 12:57:08.104379 ++hi + +# 2026-04-20 12:58:16.572155 ++What tools do you have available? + +# 2026-04-20 12:58:59.034871 ++what is cg + +# 2026-04-20 12:59:10.187850 ++what is dsa + +# 2026-04-20 12:59:22.030278 ++dsa + +# 2026-04-20 13:00:39.975381 ++what other plugins are there in the meshmind + +# 2026-04-20 13:03:03.884260 ++Research the benefits of renewable energy + +# 2026-04-20 13:24:54.566450 ++Research renewable energy AND write documentation about solar panels + +# 2026-04-20 13:27:04.002920 ++hu + +# 2026-04-20 13:35:17.967465 ++hi + +# 2026-04-20 13:36:03.873971 ++Research renewable energy AND write documentation about solar panels + +# 2026-04-20 13:38:40.568903 ++write a short poem + +# 2026-04-20 14:12:39.308336 ++hi diff --git a/my-project/meshmind.yaml b/my-project/meshmind.yaml index 34316b8..7bd2cef 100644 --- a/my-project/meshmind.yaml +++ b/my-project/meshmind.yaml @@ -12,10 +12,39 @@ mesh: # - host: "192.168.1.10" # port: 8401 -defaults: - model: 'gemma3:1b' # Lower-memory Gemma model for local development + defaults: + model: 'llama3.2:latest' # Model with tool support timeout: 60 # Query timeout in seconds retries: 1 # Retry failed sub-queries once + capability_keywords: + research: + - research + - investigate + - study + - analyze + - survey + analysis: + - analysis + - analyze + - examine + - evaluate + summarization: + - summary + - summarize + - brief + - concise + documentation: + - documentation + - document + - guide + - manual + - write + - writing + writing: + - write + - writing + - compose + - draft nodes: # ------------------------------------ @@ -23,7 +52,7 @@ nodes: # ------------------------------------ assistant: type: 'specialist' - # model: "gemma3:1b" # Override default model + model: "llama3.2:latest" port: 8401 system_prompt: | You are a helpful assistant specializing in research and analysis. @@ -38,21 +67,40 @@ nodes: knowledge: - ./data/knowledge.json - # Add more specialist nodes as needed: - # writer: - # type: "specialist" - # port: 8402 - # system_prompt: "You are a technical writer..." - # capabilities: - # - "writing" - # - "documentation" + documentation_specialist: + type: 'specialist' + model: "llama3.2:latest" + port: 8402 + plugin: demo_documentation + capabilities: + - 'documentation' + - 'writing' + knowledge_domains: + - 'documentation' + system_prompt: | + You are a documentation specialist. Use the documentation plugin to generate clear guides, examples, and API reference. + + research_specialist: + type: 'specialist' + model: "llama3.2:latest" + port: 8405 + plugin: demo_research + capabilities: + - 'research' + - 'analysis' + - 'summarization' + knowledge_domains: + - 'research' + system_prompt: | + You are a research specialist. Use the research plugin to investigate topics and summarize findings. # ------------------------------------ # Coordinator - routes queries and aggregates # ------------------------------------ coordinator: type: 'coordinator' + model: 'llama3.2:latest' port: 8403 ui: true # Enable web dashboard - ui_port: 9000 # Dashboard at http://localhost:9080 + ui_port: 9000 # Dashboard at http://localhost:9000 diff --git a/pyproject.toml b/pyproject.toml index ff9412c..1e0274b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,54 +1,71 @@ -[project] -name = "meshmind" -version = "0.1.1" -description = "Orchestrate local LLMs across devices with no cloud dependency" -readme = "README.md" -requires-python = ">=3.11" -license = {text = "Apache-2.0"} -classifiers = [ - "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", -] - -dependencies = [ - "zeroconf>=0.131.0", - "ollama>=0.4.0", - "fastapi>=0.111.0", - "uvicorn>=0.30.0", - "websockets>=12.0", - "pyyaml>=6.0", - "pydantic>=2.7", - "click>=8.1", - "prompt-toolkit>=3.0.43", - "rich>=13.7.0", -] - -[project.optional-dependencies] -dev = [ - "pytest>=8.0", - "pytest-asyncio>=0.23", - "build>=1.2.0", -] -mcp = [ - "mcp>=1.2.0", -] - -[project.scripts] -meshmind = "meshmind.cli.main:cli" - -[tool.setuptools.packages.find] -include = ["meshmind*"] - -[tool.setuptools.package-data] -meshmind = [ - "knowledge/*.json", - "ui/template/*.html", - "ui/static/**/*", - "cli/templates/*.tmpl", - "cli/presets/*.yaml", - "cli/plugin_stubs/**/*.yaml", - "cli/plugin_stubs/**/*.txt", - "cli/plugin_stubs/**/*.md", -] +[build-system] +requires = ["setuptools>=69", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "meshmind" +version = "0.1.1" +description = "Orchestrate local LLMs across devices with no cloud dependency" +readme = "README.md" +requires-python = ">=3.11" +license = "Apache-2.0" +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +keywords = ["llm", "ollama", "fastapi", "local-first", "distributed-systems", "agent", "plugins"] +authors = [{name = "Nexarion Distributed AI"}] + +dependencies = [ + "zeroconf>=0.131.0", + "ollama>=0.4.0", + "fastapi>=0.111.0", + "uvicorn>=0.30.0", + "websockets>=12.0", + "pyyaml>=6.0", + "pydantic>=2.7", + "click>=8.1", + "prompt-toolkit>=3.0.43", + "rich>=13.7.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0", + "pytest-asyncio>=0.23", + "build>=1.2.0", + "twine>=5.0.0", +] +mcp = [ + "mcp>=1.2.0", +] + +[project.scripts] +meshmind = "meshmind.cli.main:cli" + +[project.urls] +Homepage = "https://github.com/Nexarion-Distributed-AI/MeshMind" +Repository = "https://github.com/Nexarion-Distributed-AI/MeshMind" +Issues = "https://github.com/Nexarion-Distributed-AI/MeshMind/issues" +Changelog = "https://github.com/Nexarion-Distributed-AI/MeshMind/blob/main/CHANGELOG.md" +Documentation = "https://github.com/Nexarion-Distributed-AI/MeshMind/tree/main/docs" + +[tool.setuptools] +include-package-data = true +license-files = ["LICENSE"] + +[tool.setuptools.packages.find] +include = ["meshmind*"] + +[tool.setuptools.package-data] +meshmind = [ + "knowledge/*.json", + "ui/template/*.html", + "ui/static/**/*", + "cli/templates/*.tmpl", + "cli/presets/*.yaml", + "cli/plugin_stubs/**/*.yaml", + "cli/plugin_stubs/**/*.txt", + "cli/plugin_stubs/**/*.md", +] diff --git a/shivam/data/knowledge.json b/shivam/data/knowledge.json new file mode 100644 index 0000000..f0073ce --- /dev/null +++ b/shivam/data/knowledge.json @@ -0,0 +1,7 @@ +{ + "project_name": "shivam", + "topics": [ + "Replace with topic areas this mesh should emphasize." + ], + "conventions": "Short conventions and style notes editors can follow when answering (tone, citation, formatting)." +} diff --git a/shivam/meshmind.yaml b/shivam/meshmind.yaml new file mode 100644 index 0000000..cf882ed --- /dev/null +++ b/shivam/meshmind.yaml @@ -0,0 +1,58 @@ +# MeshMind Configuration +# Docs: https://github.com/meshmind/meshmind +# +# Define your mesh of AI nodes below. +# Run `meshmind up` to start all nodes. +# Run `meshmind config validate` to check this file. + +mesh: + name: "shivam" + discovery: "mdns" # "mdns" (auto-detect peers) or "manual" (static list) + # peers: # Only needed for manual discovery + # - host: "192.168.1.10" + # port: 8401 + +defaults: + model: "gemma3:1b" # Default model for all nodes (any Ollama model works) + timeout: 60 # Query timeout in seconds + retries: 1 # Retry failed sub-queries once + +nodes: + # ------------------------------------ + # Specialist Node - handles a specific domain + # ------------------------------------ + shivam-assistant: + type: "specialist" + # model: "gemma3:1b" # Override default model + port: 8401 + system_prompt: | + You are a helpful assistant specializing in research and analysis. + Provide detailed, accurate, and well-structured answers. + capabilities: + - "research" + - "analysis" + - "summarization" + knowledge_domains: + - "research" + - "analysis" + knowledge: + - ./data/knowledge.json + + # Add more specialist nodes as needed: + # writer: + # type: "specialist" + # port: 8402 + # system_prompt: "You are a technical writer..." + # capabilities: + # - "writing" + # - "documentation" + + # ------------------------------------ + # Coordinator - routes queries and aggregates + # ------------------------------------ + shivam-coordinator: + type: "coordinator" + port: 8403 + ui: true # Enable web dashboard + ui_port: 8080 # Dashboard at http://localhost:8080 + diff --git a/tests/test_plugin_init_ux.py b/tests/test_plugin_init_ux.py index 467d54d..376bbc7 100644 --- a/tests/test_plugin_init_ux.py +++ b/tests/test_plugin_init_ux.py @@ -37,6 +37,8 @@ def test_iter_bundled_plugin_ids_includes_demos() -> None: ids = iter_bundled_plugin_ids() assert "demo_review" in ids assert "demo_security" in ids + assert "demo_documentation" in ids + assert "demo_research" in ids def test_install_one_bundled_plugin_copies_stub(tmp_path, monkeypatch) -> None: @@ -114,6 +116,9 @@ def test_plugin_catalog_cli_lists_bundled(args: list[str]) -> None: r = CliRunner().invoke(cli, args) assert r.exit_code == 0 assert "demo_review" in r.output + assert "demo_security" in r.output + assert "demo_documentation" in r.output + assert "demo_research" in r.output def test_apply_plugin_file_not_found_message_includes_sync_hint(tmp_path, monkeypatch) -> None: