memplex 3.2.2__tar.gz → 3.2.3__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.
- {memplex-3.2.2 → memplex-3.2.3}/PKG-INFO +1 -1
- {memplex-3.2.2 → memplex-3.2.3}/README.md +35 -29
- {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/.claude-plugin/plugin.json +1 -1
- {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/agent_installer.py +27 -3
- {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/cli.py +58 -80
- {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/mcp_server.py +1 -1
- {memplex-3.2.2 → memplex-3.2.3}/memplex.egg-info/PKG-INFO +1 -1
- {memplex-3.2.2 → memplex-3.2.3}/pyproject.toml +1 -1
- {memplex-3.2.2 → memplex-3.2.3}/tests/test_agent_hot_paths.py +62 -0
- {memplex-3.2.2 → memplex-3.2.3}/tests/test_hooks.py +31 -8
- {memplex-3.2.2 → memplex-3.2.3}/tests/test_install_scripts.py +68 -0
- {memplex-3.2.2 → memplex-3.2.3}/LICENSE +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/__main__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/.mcp.json +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/hooks/hooks.json +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/scripts/hook-runner.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/skills/mem-explore/SKILL.md +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/skills/mem-manage/SKILL.md +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/skills/mem-search/SKILL.md +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/skills/mem-write/SKILL.md +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/agent_runtime.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/claude_skill.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/http_api.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/base.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/benchmark_cli.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/evaluator.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/loader.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/locomo.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/memory_eval.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/memory_metrics.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/metrics.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/nq_trivia.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/popqa_hotpot.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/compaction.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/config.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/associator/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/associator/domain_classifier.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/associator/entity_aligner.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/associator/ref_linker.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/associator/term_mapper.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/dictionaries/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/engine.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/extractors/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/extractors/docx.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/extractors/image.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/extractors/markdown.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/extractors/pdf.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/extractors/vision_mapper.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/handlers/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/handlers/clipboard.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/handlers/file_handler.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/handlers/url_handler.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/hooks/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/hooks/collector.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/hooks/hook_event.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/core/hooks/registry.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/enhancer.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/fallback_chain.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/injection_guard.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/provider.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/providers/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/providers/anthropic.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/providers/local.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/providers/rule_based.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/sanitizer.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/logging_utils.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/metrics.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/models/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/models/feedback.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/models/graph.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/models/memory.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/models/misc.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/models/paragraph.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/models/search.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/models/source.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/models/task.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/processing/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/processing/graph_builder.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/processing/merger/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/processing/merger/confidence_calculator.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/processing/merger/conflict_resolver.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/retrieval/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/retrieval/dedup.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/retrieval/embedding.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/retrieval/reranker.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/service.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/base.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/changelog.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/feedback.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/lite/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/lite/store.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/vector.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/wiki/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/wiki/community.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/wiki/compiler.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/wiki/generator.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/wiki/search.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex/worker.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex.egg-info/SOURCES.txt +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex.egg-info/dependency_links.txt +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex.egg-info/entry_points.txt +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex.egg-info/requires.txt +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/memplex.egg-info/top_level.txt +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/setup.cfg +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/tests/test_agent_runtime.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/tests/test_associators.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/tests/test_config.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/tests/test_core_engine.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/tests/test_graph_builder.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/tests/test_llm.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/tests/test_models.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/tests/test_service.py +0 -0
- {memplex-3.2.2 → memplex-3.2.3}/tests/test_storage.py +0 -0
|
@@ -13,61 +13,67 @@
|
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
|
-
###
|
|
16
|
+
### One-Command Agent Setup
|
|
17
|
+
|
|
18
|
+
No source checkout is required. The npm entrypoint matches the common
|
|
19
|
+
`npx <tool> setup` pattern used by modern CLIs:
|
|
17
20
|
|
|
18
|
-
**Option 1: Local plugin (for development)**
|
|
19
21
|
```bash
|
|
20
|
-
|
|
21
|
-
cd plugin
|
|
22
|
-
/plugin install ./plugin
|
|
22
|
+
npx memplex setup
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Install into a specific local agent:
|
|
26
|
+
|
|
26
27
|
```bash
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
npx memplex setup --agent codex --project-path "$PWD"
|
|
29
|
+
npx memplex setup --agent claude-code --project-path "$PWD"
|
|
30
|
+
npx memplex setup --agent openclaw --project-path "$PWD"
|
|
31
|
+
npx memplex setup --agent hermes --project-path "$PWD"
|
|
29
32
|
```
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
Install every supported agent config on this machine:
|
|
35
|
+
|
|
32
36
|
```bash
|
|
33
|
-
|
|
37
|
+
npx memplex setup --agent all --project-path "$PWD"
|
|
34
38
|
```
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
Uninstall:
|
|
37
41
|
|
|
38
42
|
```bash
|
|
39
|
-
|
|
40
|
-
cd memplex
|
|
41
|
-
pip install -e .
|
|
43
|
+
npx memplex uninstall --agent all
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
The npm wrapper creates a persistent Python environment at
|
|
47
|
+
`~/.local/share/memplex/agent-venv`, installs `memplex==3.2.3`, detects local
|
|
48
|
+
Codex, Claude Code, OpenClaw, and Hermes config directories/commands, then
|
|
49
|
+
registers Memplex with each detected agent. It uses `uv` when available and
|
|
50
|
+
falls back to `python -m venv` plus `pip`.
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
into a detected local agent with:
|
|
52
|
+
Python-first users can use a persistent tool install:
|
|
48
53
|
|
|
49
54
|
```bash
|
|
50
|
-
|
|
55
|
+
uv tool install memplex==3.2.3
|
|
56
|
+
memplex setup
|
|
51
57
|
```
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
environment at `~/.local/share/memplex/agent-venv`, detects local Codex, Claude
|
|
55
|
-
Code, OpenClaw, and Hermes config directories/commands, then registers Memplex
|
|
56
|
-
with each detected agent. It uses `uv` when available and falls back to
|
|
57
|
-
`python -m venv` plus `pip`.
|
|
59
|
+
The raw hosted installer remains available for shell-only environments:
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
```bash
|
|
62
|
+
curl -fsSL https://raw.githubusercontent.com/articultur/memplex/main/scripts/install-agent.sh | bash
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Claude Code Plugin
|
|
60
66
|
|
|
61
67
|
```bash
|
|
62
|
-
|
|
63
|
-
bash -s -- --agent hermes --package memplex --project-path "$PWD" --user-id "$USER"
|
|
68
|
+
memplex setup --agent claude-code
|
|
64
69
|
```
|
|
65
70
|
|
|
66
|
-
|
|
71
|
+
### From Source
|
|
67
72
|
|
|
68
73
|
```bash
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
git clone https://github.com/articultur/memplex.git
|
|
75
|
+
cd memplex
|
|
76
|
+
pip install -e .
|
|
71
77
|
```
|
|
72
78
|
|
|
73
79
|
Uninstall:
|
|
@@ -80,9 +80,33 @@ def uninstall_agent(
|
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
def _expand_agents(agent: str) -> list[str]:
|
|
83
|
-
|
|
83
|
+
requested = (agent or "auto").strip().lower()
|
|
84
|
+
if requested == "all":
|
|
84
85
|
return ["codex", "claude-code", "openclaw", "hermes"]
|
|
85
|
-
|
|
86
|
+
if requested == "auto":
|
|
87
|
+
detected = _detect_agents()
|
|
88
|
+
if not detected:
|
|
89
|
+
raise ValueError(
|
|
90
|
+
"No supported local agents detected. Re-run with "
|
|
91
|
+
"--agent codex|claude-code|openclaw|hermes|all."
|
|
92
|
+
)
|
|
93
|
+
return detected
|
|
94
|
+
return [get_agent_manifest(requested)["name"]]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _detect_agents() -> list[str]:
|
|
98
|
+
detected: list[str] = []
|
|
99
|
+
checks = [
|
|
100
|
+
("codex", "CODEX_HOME", ".codex", "codex"),
|
|
101
|
+
("claude-code", "CLAUDE_CONFIG_DIR", ".claude", "claude"),
|
|
102
|
+
("openclaw", "OPENCLAW_CONFIG_DIR", ".openclaw", "openclaw"),
|
|
103
|
+
("hermes", "HERMES_CONFIG_DIR", ".hermes", "hermes"),
|
|
104
|
+
]
|
|
105
|
+
for name, env_name, default_name, command in checks:
|
|
106
|
+
root = Path(os.environ.get(env_name, Path.home() / default_name)).expanduser()
|
|
107
|
+
if root.exists() or shutil.which(command):
|
|
108
|
+
detected.append(name)
|
|
109
|
+
return detected
|
|
86
110
|
|
|
87
111
|
|
|
88
112
|
def _install_one(
|
|
@@ -925,4 +949,4 @@ def _package_version() -> str:
|
|
|
925
949
|
try:
|
|
926
950
|
return pkg_version("memplex")
|
|
927
951
|
except Exception:
|
|
928
|
-
return "3.2.
|
|
952
|
+
return "3.2.3"
|
|
@@ -13,9 +13,11 @@ Usage::
|
|
|
13
13
|
memplex compact --scope project
|
|
14
14
|
memplex health
|
|
15
15
|
memplex stats
|
|
16
|
+
memplex setup # Install into detected local agents
|
|
17
|
+
memplex install --agent codex
|
|
18
|
+
memplex uninstall --agent openclaw
|
|
16
19
|
memplex agent install --agent all
|
|
17
20
|
memplex agent uninstall --agent openclaw
|
|
18
|
-
memplex setup # Install as Claude Code plugin
|
|
19
21
|
memplex unsetup # Uninstall Claude Code plugin
|
|
20
22
|
|
|
21
23
|
Global options::
|
|
@@ -395,80 +397,25 @@ def _get_marketplace_dir() -> Path:
|
|
|
395
397
|
|
|
396
398
|
|
|
397
399
|
def cmd_setup(args: argparse.Namespace) -> int:
|
|
398
|
-
"""Install Memplex
|
|
399
|
-
|
|
400
|
-
return cmd_unsetup(args)
|
|
401
|
-
|
|
402
|
-
market_dir = _get_marketplace_dir()
|
|
403
|
-
plugin_target = market_dir / "plugin"
|
|
404
|
-
|
|
405
|
-
print("Memplex Plugin Setup")
|
|
406
|
-
print("=" * 40)
|
|
407
|
-
|
|
408
|
-
# 1. Check Python dependencies
|
|
409
|
-
print("\n[1/4] Checking dependencies...")
|
|
410
|
-
try:
|
|
411
|
-
import numpy # noqa: F401
|
|
412
|
-
import yaml # noqa: F401
|
|
413
|
-
|
|
414
|
-
print(" Core dependencies: OK")
|
|
415
|
-
except ImportError as e:
|
|
416
|
-
print(f" Missing dependency: {e}")
|
|
417
|
-
print(" Run: pip install memplex[embedding]")
|
|
418
|
-
return 1
|
|
419
|
-
|
|
420
|
-
# 2. Find and copy plugin directory
|
|
421
|
-
print("\n[2/4] Installing plugin files...")
|
|
422
|
-
try:
|
|
423
|
-
source = _get_plugin_source_dir()
|
|
424
|
-
except FileNotFoundError as e:
|
|
425
|
-
print(f" Error: {e}")
|
|
426
|
-
return 1
|
|
427
|
-
|
|
428
|
-
if plugin_target.exists():
|
|
429
|
-
shutil.rmtree(plugin_target)
|
|
430
|
-
|
|
431
|
-
def _ignore_patterns(_dir, files):
|
|
432
|
-
return [f for f in files if f == "__pycache__" or f.endswith(".pyc")]
|
|
433
|
-
|
|
434
|
-
shutil.copytree(source, plugin_target, symlinks=False, ignore=_ignore_patterns)
|
|
435
|
-
print(f" Installed to: {plugin_target}")
|
|
436
|
-
|
|
437
|
-
# 3. Write marketplace.json
|
|
438
|
-
print("\n[3/4] Registering with Claude Code...")
|
|
439
|
-
market_json = market_dir / "marketplace.json"
|
|
440
|
-
market_dir.mkdir(parents=True, exist_ok=True)
|
|
441
|
-
market_json.write_text(_MARKETPLACE_JSON.strip() + "\n")
|
|
442
|
-
print(f" Marketplace: {market_json}")
|
|
443
|
-
|
|
444
|
-
# 4. Write install marker
|
|
445
|
-
print("\n[4/4] Writing install marker...")
|
|
446
|
-
marker = market_dir / ".install-version"
|
|
447
|
-
from importlib.metadata import version as pkg_version
|
|
400
|
+
"""Install or uninstall Memplex in local agent hosts."""
|
|
401
|
+
from memplex.adapters.agent_installer import install_agent, uninstall_agent
|
|
448
402
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
{
|
|
456
|
-
"version": ver,
|
|
457
|
-
"installedAt": __import__("datetime").datetime.now().isoformat(),
|
|
458
|
-
},
|
|
459
|
-
indent=2,
|
|
403
|
+
should_uninstall = getattr(args, "uninstall", False) or args.command == "uninstall"
|
|
404
|
+
if should_uninstall:
|
|
405
|
+
result = uninstall_agent(
|
|
406
|
+
args.agent,
|
|
407
|
+
target_dir=getattr(args, "target_dir", None),
|
|
408
|
+
dry_run=getattr(args, "dry_run", False),
|
|
460
409
|
)
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
print(
|
|
470
|
-
print(f" - Manifest: {plugin_target}/../.claude-plugin/plugin.json")
|
|
471
|
-
print("\nRestart Claude Code to activate the plugin.")
|
|
410
|
+
else:
|
|
411
|
+
result = install_agent(
|
|
412
|
+
args.agent,
|
|
413
|
+
target_dir=getattr(args, "target_dir", None),
|
|
414
|
+
user_id=getattr(args, "user_id", None),
|
|
415
|
+
project_path=getattr(args, "project_path", None),
|
|
416
|
+
dry_run=getattr(args, "dry_run", False),
|
|
417
|
+
)
|
|
418
|
+
print(_fmt(_dataclass_to_dict(result), args.output))
|
|
472
419
|
return 0
|
|
473
420
|
|
|
474
421
|
|
|
@@ -579,7 +526,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
579
526
|
p_agent_install.add_argument(
|
|
580
527
|
"--agent",
|
|
581
528
|
default="all",
|
|
582
|
-
help="Agent id: codex | claude-code | openclaw | hermes | all",
|
|
529
|
+
help="Agent id: auto | codex | claude-code | openclaw | hermes | all",
|
|
583
530
|
)
|
|
584
531
|
p_agent_install.add_argument(
|
|
585
532
|
"--target-dir",
|
|
@@ -598,7 +545,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
598
545
|
p_agent_uninstall.add_argument(
|
|
599
546
|
"--agent",
|
|
600
547
|
default="all",
|
|
601
|
-
help="Agent id: codex | claude-code | openclaw | hermes | all",
|
|
548
|
+
help="Agent id: auto | codex | claude-code | openclaw | hermes | all",
|
|
602
549
|
)
|
|
603
550
|
p_agent_uninstall.add_argument(
|
|
604
551
|
"--target-dir",
|
|
@@ -629,11 +576,39 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
629
576
|
p_agent_capture.add_argument("--assistant-message", required=True)
|
|
630
577
|
p_agent_capture.add_argument("--next-prompt-hint", default=None)
|
|
631
578
|
|
|
632
|
-
# -- setup --
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
579
|
+
# -- setup / install / uninstall --
|
|
580
|
+
def add_setup_parser(name: str, *, uninstall: bool = False):
|
|
581
|
+
help_text = (
|
|
582
|
+
"Uninstall Memplex from local agent hosts"
|
|
583
|
+
if uninstall
|
|
584
|
+
else "Set up Memplex in detected local agent hosts"
|
|
585
|
+
)
|
|
586
|
+
p_setup = sub.add_parser(name, help=help_text)
|
|
587
|
+
p_setup.add_argument(
|
|
588
|
+
"--agent",
|
|
589
|
+
default="auto",
|
|
590
|
+
help="Agent id: auto | codex | claude-code | openclaw | hermes | all",
|
|
591
|
+
)
|
|
592
|
+
p_setup.add_argument(
|
|
593
|
+
"--target-dir",
|
|
594
|
+
default=None,
|
|
595
|
+
help="Override the selected agent config root directory",
|
|
596
|
+
)
|
|
597
|
+
p_setup.add_argument("--user-id", default=None)
|
|
598
|
+
p_setup.add_argument("--project-path", default=None)
|
|
599
|
+
p_setup.add_argument(
|
|
600
|
+
"--dry-run", action="store_true", help="Show planned files without writing"
|
|
601
|
+
)
|
|
602
|
+
if not uninstall:
|
|
603
|
+
p_setup.add_argument(
|
|
604
|
+
"--uninstall", action="store_true", help="Uninstall instead of install"
|
|
605
|
+
)
|
|
606
|
+
return p_setup
|
|
607
|
+
|
|
608
|
+
add_setup_parser("setup")
|
|
609
|
+
add_setup_parser("install")
|
|
610
|
+
add_setup_parser("stepup")
|
|
611
|
+
add_setup_parser("uninstall", uninstall=True)
|
|
637
612
|
|
|
638
613
|
# -- unsetup --
|
|
639
614
|
sub.add_parser("unsetup", help="Uninstall Memplex Claude Code plugin")
|
|
@@ -671,6 +646,9 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
671
646
|
"stats": cmd_stats,
|
|
672
647
|
"agent": cmd_agent,
|
|
673
648
|
"setup": cmd_setup,
|
|
649
|
+
"install": cmd_setup,
|
|
650
|
+
"stepup": cmd_setup,
|
|
651
|
+
"uninstall": cmd_setup,
|
|
674
652
|
"unsetup": cmd_unsetup,
|
|
675
653
|
}
|
|
676
654
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "memplex"
|
|
7
|
-
version = "3.2.
|
|
7
|
+
version = "3.2.3"
|
|
8
8
|
description = "Memplex - Memory Complex: multi-agent knowledge graph memory system with 3-layer retrieval"
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
dependencies = [
|
|
@@ -51,6 +51,68 @@ def test_codex_hot_path_uses_managed_mcp_block(tmp_path):
|
|
|
51
51
|
assert 'args = ["-m", "memplex.adapters.mcp_server"]' in config
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def test_top_level_setup_auto_detects_agent_without_nested_command(tmp_path):
|
|
55
|
+
home = tmp_path / "home"
|
|
56
|
+
(home / ".codex").mkdir(parents=True)
|
|
57
|
+
result = _run_memplex(
|
|
58
|
+
[
|
|
59
|
+
"--output",
|
|
60
|
+
"json",
|
|
61
|
+
"setup",
|
|
62
|
+
"--dry-run",
|
|
63
|
+
"--project-path",
|
|
64
|
+
str(PROJECT_ROOT),
|
|
65
|
+
],
|
|
66
|
+
env={**os.environ, "HOME": str(home), "PATH": "/usr/bin:/bin"},
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
assert result.returncode == 0, result.stderr
|
|
70
|
+
payload = json.loads(result.stdout)
|
|
71
|
+
assert payload[0]["agent"] == "codex"
|
|
72
|
+
assert payload[0]["action"] == "install"
|
|
73
|
+
assert payload[0]["status"] == "planned"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_top_level_install_and_stepup_aliases(tmp_path):
|
|
77
|
+
for command in ("install", "stepup"):
|
|
78
|
+
result = _run_memplex(
|
|
79
|
+
[
|
|
80
|
+
"--output",
|
|
81
|
+
"json",
|
|
82
|
+
command,
|
|
83
|
+
"--agent",
|
|
84
|
+
"codex",
|
|
85
|
+
"--target-dir",
|
|
86
|
+
str(tmp_path / command),
|
|
87
|
+
"--dry-run",
|
|
88
|
+
]
|
|
89
|
+
)
|
|
90
|
+
assert result.returncode == 0, result.stderr
|
|
91
|
+
payload = json.loads(result.stdout)
|
|
92
|
+
assert payload[0]["agent"] == "codex"
|
|
93
|
+
assert payload[0]["action"] == "install"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_top_level_uninstall_alias(tmp_path):
|
|
97
|
+
result = _run_memplex(
|
|
98
|
+
[
|
|
99
|
+
"--output",
|
|
100
|
+
"json",
|
|
101
|
+
"uninstall",
|
|
102
|
+
"--agent",
|
|
103
|
+
"codex",
|
|
104
|
+
"--target-dir",
|
|
105
|
+
str(tmp_path / "codex"),
|
|
106
|
+
"--dry-run",
|
|
107
|
+
]
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
assert result.returncode == 0, result.stderr
|
|
111
|
+
payload = json.loads(result.stdout)
|
|
112
|
+
assert payload[0]["agent"] == "codex"
|
|
113
|
+
assert payload[0]["action"] == "uninstall"
|
|
114
|
+
|
|
115
|
+
|
|
54
116
|
def test_mcp_hot_path_initializes_over_stdio(tmp_path):
|
|
55
117
|
codex_home = tmp_path / "codex"
|
|
56
118
|
install = _run_memplex(
|
|
@@ -255,7 +255,7 @@ class TestMCPServerProtocol:
|
|
|
255
255
|
result = mcp_server._handle_initialize({})
|
|
256
256
|
assert result["protocolVersion"] == "2024-11-05"
|
|
257
257
|
assert result["serverInfo"]["name"] == "memplex"
|
|
258
|
-
assert result["serverInfo"]["version"] == "3.2.
|
|
258
|
+
assert result["serverInfo"]["version"] == "3.2.3"
|
|
259
259
|
assert "tools" in result["capabilities"]
|
|
260
260
|
|
|
261
261
|
def test_tools_list_returns_definitions(self, mcp_server):
|
|
@@ -1136,14 +1136,25 @@ class TestCLI:
|
|
|
1136
1136
|
|
|
1137
1137
|
def test_setup_command(self, tmp_path):
|
|
1138
1138
|
r = subprocess.run(
|
|
1139
|
-
[
|
|
1139
|
+
[
|
|
1140
|
+
sys.executable,
|
|
1141
|
+
"-m",
|
|
1142
|
+
"memplex",
|
|
1143
|
+
"--output",
|
|
1144
|
+
"json",
|
|
1145
|
+
"setup",
|
|
1146
|
+
"--agent",
|
|
1147
|
+
"claude-code",
|
|
1148
|
+
],
|
|
1140
1149
|
capture_output=True,
|
|
1141
1150
|
text=True,
|
|
1142
1151
|
timeout=30,
|
|
1143
1152
|
env={**os.environ, "CLAUDE_CONFIG_DIR": str(tmp_path)},
|
|
1144
1153
|
)
|
|
1145
1154
|
assert r.returncode == 0
|
|
1146
|
-
|
|
1155
|
+
data = json.loads(r.stdout)
|
|
1156
|
+
assert data[0]["agent"] == "claude-code"
|
|
1157
|
+
assert data[0]["status"] == "installed"
|
|
1147
1158
|
|
|
1148
1159
|
market_dir = tmp_path / "plugins" / "marketplaces" / "articultur"
|
|
1149
1160
|
assert (market_dir / "marketplace.json").exists()
|
|
@@ -1156,7 +1167,7 @@ class TestCLI:
|
|
|
1156
1167
|
def test_unsetup_command(self, tmp_path):
|
|
1157
1168
|
# Setup first
|
|
1158
1169
|
subprocess.run(
|
|
1159
|
-
[sys.executable, "-m", "memplex", "setup"],
|
|
1170
|
+
[sys.executable, "-m", "memplex", "setup", "--agent", "claude-code"],
|
|
1160
1171
|
capture_output=True,
|
|
1161
1172
|
text=True,
|
|
1162
1173
|
timeout=30,
|
|
@@ -1177,21 +1188,33 @@ class TestCLI:
|
|
|
1177
1188
|
|
|
1178
1189
|
def test_setup_uninstall_flag(self, tmp_path):
|
|
1179
1190
|
subprocess.run(
|
|
1180
|
-
[sys.executable, "-m", "memplex", "setup"],
|
|
1191
|
+
[sys.executable, "-m", "memplex", "setup", "--agent", "claude-code"],
|
|
1181
1192
|
capture_output=True,
|
|
1182
1193
|
text=True,
|
|
1183
1194
|
timeout=30,
|
|
1184
1195
|
env={**os.environ, "CLAUDE_CONFIG_DIR": str(tmp_path)},
|
|
1185
1196
|
)
|
|
1186
1197
|
r = subprocess.run(
|
|
1187
|
-
[
|
|
1198
|
+
[
|
|
1199
|
+
sys.executable,
|
|
1200
|
+
"-m",
|
|
1201
|
+
"memplex",
|
|
1202
|
+
"--output",
|
|
1203
|
+
"json",
|
|
1204
|
+
"setup",
|
|
1205
|
+
"--uninstall",
|
|
1206
|
+
"--agent",
|
|
1207
|
+
"claude-code",
|
|
1208
|
+
],
|
|
1188
1209
|
capture_output=True,
|
|
1189
1210
|
text=True,
|
|
1190
1211
|
timeout=30,
|
|
1191
1212
|
env={**os.environ, "CLAUDE_CONFIG_DIR": str(tmp_path)},
|
|
1192
1213
|
)
|
|
1193
1214
|
assert r.returncode == 0
|
|
1194
|
-
|
|
1215
|
+
data = json.loads(r.stdout)
|
|
1216
|
+
assert data[0]["agent"] == "claude-code"
|
|
1217
|
+
assert data[0]["status"] == "uninstalled"
|
|
1195
1218
|
|
|
1196
1219
|
def test_unsetup_when_not_installed(self, tmp_path):
|
|
1197
1220
|
r = subprocess.run(
|
|
@@ -1270,7 +1293,7 @@ class TestPluginConfig:
|
|
|
1270
1293
|
Path(PROJECT_ROOT / "plugin" / ".claude-plugin" / "plugin.json").read_text()
|
|
1271
1294
|
)
|
|
1272
1295
|
assert data["name"] == "memplex"
|
|
1273
|
-
assert data["version"] == "3.2.
|
|
1296
|
+
assert data["version"] == "3.2.3"
|
|
1274
1297
|
assert "repository" in data
|
|
1275
1298
|
|
|
1276
1299
|
def test_hooks_json_valid(self):
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import shutil
|
|
6
7
|
import subprocess
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
@@ -13,6 +14,9 @@ NPM_AGENT_PACKAGE = PROJECT_ROOT / "npm" / "agent-installer" / "package.json"
|
|
|
13
14
|
NPM_AGENT_BIN = PROJECT_ROOT / "npm" / "agent-installer" / "bin" / "memplex-install-agent.js"
|
|
14
15
|
NPM_PACKAGE = PROJECT_ROOT / "npm" / "hermes-installer" / "package.json"
|
|
15
16
|
NPM_BIN = PROJECT_ROOT / "npm" / "hermes-installer" / "bin" / "memplex-install-hermes.js"
|
|
17
|
+
NPM_MEMPLEX_PACKAGE = PROJECT_ROOT / "npm" / "memplex" / "package.json"
|
|
18
|
+
NPM_MEMPLEX_BIN = PROJECT_ROOT / "npm" / "memplex" / "bin" / "memplex.js"
|
|
19
|
+
NODE_BIN = shutil.which("node") or "node"
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
def test_installer_shell_syntax():
|
|
@@ -155,8 +159,17 @@ def test_agent_installer_all_uses_transactional_cli_path(tmp_path):
|
|
|
155
159
|
|
|
156
160
|
|
|
157
161
|
def test_npm_hermes_installer_package_shape():
|
|
162
|
+
memplex_package = json.loads(NPM_MEMPLEX_PACKAGE.read_text())
|
|
163
|
+
assert memplex_package["name"] == "memplex"
|
|
164
|
+
assert memplex_package["version"] == "3.2.3"
|
|
165
|
+
assert memplex_package["bin"]["memplex"] == "bin/memplex.js"
|
|
166
|
+
memplex_script = NPM_MEMPLEX_BIN.read_text()
|
|
167
|
+
assert "npx memplex setup" in memplex_script
|
|
168
|
+
assert "memplex==3.2.3" in memplex_script
|
|
169
|
+
|
|
158
170
|
agent_package = json.loads(NPM_AGENT_PACKAGE.read_text())
|
|
159
171
|
assert agent_package["name"] == "@articultur/memplex-agent-installer"
|
|
172
|
+
assert agent_package["version"] == "0.2.0"
|
|
160
173
|
assert agent_package["bin"]["memplex-install-agent"] == "bin/memplex-install-agent.js"
|
|
161
174
|
agent_script = NPM_AGENT_BIN.read_text()
|
|
162
175
|
assert "MEMPLEX_INSTALL_SCRIPT_URL" in agent_script
|
|
@@ -164,8 +177,63 @@ def test_npm_hermes_installer_package_shape():
|
|
|
164
177
|
|
|
165
178
|
package = json.loads(NPM_PACKAGE.read_text())
|
|
166
179
|
assert package["name"] == "@articultur/memplex-hermes-installer"
|
|
180
|
+
assert package["version"] == "0.2.0"
|
|
167
181
|
assert package["bin"]["memplex-install-hermes"] == "bin/memplex-install-hermes.js"
|
|
168
182
|
script = NPM_BIN.read_text()
|
|
169
183
|
assert "MEMPLEX_INSTALL_SCRIPT_URL" in script
|
|
170
184
|
assert "install-agent.sh" in script
|
|
171
185
|
assert '"--agent", "hermes"' in script
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_npm_memplex_setup_runs_hosted_installer_dry_run(tmp_path):
|
|
189
|
+
result = subprocess.run(
|
|
190
|
+
[
|
|
191
|
+
NODE_BIN,
|
|
192
|
+
str(NPM_MEMPLEX_BIN),
|
|
193
|
+
"setup",
|
|
194
|
+
"--agent",
|
|
195
|
+
"codex",
|
|
196
|
+
"--dry-run",
|
|
197
|
+
"--venv-dir",
|
|
198
|
+
str(tmp_path / "venv"),
|
|
199
|
+
"--project-path",
|
|
200
|
+
"/repo/a",
|
|
201
|
+
],
|
|
202
|
+
capture_output=True,
|
|
203
|
+
text=True,
|
|
204
|
+
timeout=10,
|
|
205
|
+
env={
|
|
206
|
+
"HOME": str(tmp_path / "home"),
|
|
207
|
+
"PATH": "/usr/bin:/bin",
|
|
208
|
+
"MEMPLEX_INSTALL_SCRIPT_URL": f"file://{AGENT_INSTALLER}",
|
|
209
|
+
},
|
|
210
|
+
)
|
|
211
|
+
assert result.returncode == 0, result.stderr
|
|
212
|
+
assert "memplex==3.2.3" in result.stdout
|
|
213
|
+
assert "-m memplex agent install --agent codex" in result.stdout
|
|
214
|
+
assert "--project-path /repo/a" in result.stdout
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def test_npm_memplex_uninstall_aliases_hosted_installer(tmp_path):
|
|
218
|
+
result = subprocess.run(
|
|
219
|
+
[
|
|
220
|
+
NODE_BIN,
|
|
221
|
+
str(NPM_MEMPLEX_BIN),
|
|
222
|
+
"uninstall",
|
|
223
|
+
"--agent",
|
|
224
|
+
"codex",
|
|
225
|
+
"--dry-run",
|
|
226
|
+
"--venv-dir",
|
|
227
|
+
str(tmp_path / "venv"),
|
|
228
|
+
],
|
|
229
|
+
capture_output=True,
|
|
230
|
+
text=True,
|
|
231
|
+
timeout=10,
|
|
232
|
+
env={
|
|
233
|
+
"HOME": str(tmp_path / "home"),
|
|
234
|
+
"PATH": "/usr/bin:/bin",
|
|
235
|
+
"MEMPLEX_INSTALL_SCRIPT_URL": f"file://{AGENT_INSTALLER}",
|
|
236
|
+
},
|
|
237
|
+
)
|
|
238
|
+
assert result.returncode == 0, result.stderr
|
|
239
|
+
assert "-m memplex agent uninstall --agent codex" in result.stdout
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|