memplex 3.2.2__tar.gz → 3.2.4__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.4}/PKG-INFO +1 -1
- {memplex-3.2.2 → memplex-3.2.4}/README.md +35 -29
- {memplex-3.2.2 → memplex-3.2.4}/memplex/_plugin/.claude-plugin/plugin.json +1 -1
- {memplex-3.2.2 → memplex-3.2.4}/memplex/adapters/agent_installer.py +38 -20
- {memplex-3.2.2 → memplex-3.2.4}/memplex/adapters/cli.py +58 -80
- {memplex-3.2.2 → memplex-3.2.4}/memplex/adapters/mcp_server.py +3 -7
- {memplex-3.2.2 → memplex-3.2.4}/memplex/compaction.py +18 -13
- {memplex-3.2.2 → memplex-3.2.4}/memplex/retrieval/reranker.py +11 -3
- {memplex-3.2.2 → memplex-3.2.4}/memplex.egg-info/PKG-INFO +1 -1
- {memplex-3.2.2 → memplex-3.2.4}/pyproject.toml +1 -1
- {memplex-3.2.2 → memplex-3.2.4}/tests/test_agent_hot_paths.py +62 -0
- {memplex-3.2.2 → memplex-3.2.4}/tests/test_agent_runtime.py +24 -0
- {memplex-3.2.2 → memplex-3.2.4}/tests/test_hooks.py +48 -51
- {memplex-3.2.2 → memplex-3.2.4}/tests/test_install_scripts.py +68 -0
- {memplex-3.2.2 → memplex-3.2.4}/LICENSE +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/__main__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/_plugin/.mcp.json +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/_plugin/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/_plugin/hooks/hooks.json +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/_plugin/scripts/hook-runner.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/_plugin/skills/mem-explore/SKILL.md +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/_plugin/skills/mem-manage/SKILL.md +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/_plugin/skills/mem-search/SKILL.md +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/_plugin/skills/mem-write/SKILL.md +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/adapters/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/adapters/agent_runtime.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/adapters/claude_skill.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/adapters/http_api.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/benchmarks/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/benchmarks/base.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/benchmarks/benchmark_cli.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/benchmarks/evaluator.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/benchmarks/loader.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/benchmarks/locomo.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/benchmarks/memory_eval.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/benchmarks/memory_metrics.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/benchmarks/metrics.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/benchmarks/nq_trivia.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/benchmarks/popqa_hotpot.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/config.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/associator/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/associator/domain_classifier.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/associator/entity_aligner.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/associator/ref_linker.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/associator/term_mapper.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/dictionaries/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/engine.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/extractors/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/extractors/docx.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/extractors/image.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/extractors/markdown.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/extractors/pdf.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/extractors/vision_mapper.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/handlers/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/handlers/clipboard.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/handlers/file_handler.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/handlers/url_handler.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/hooks/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/hooks/collector.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/hooks/hook_event.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/core/hooks/registry.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/llm/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/llm/enhancer.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/llm/fallback_chain.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/llm/injection_guard.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/llm/provider.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/llm/providers/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/llm/providers/anthropic.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/llm/providers/local.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/llm/providers/rule_based.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/llm/sanitizer.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/logging_utils.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/metrics.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/models/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/models/feedback.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/models/graph.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/models/memory.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/models/misc.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/models/paragraph.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/models/search.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/models/source.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/models/task.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/processing/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/processing/graph_builder.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/processing/merger/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/processing/merger/confidence_calculator.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/processing/merger/conflict_resolver.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/retrieval/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/retrieval/dedup.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/retrieval/embedding.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/service.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/storage/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/storage/base.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/storage/changelog.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/storage/feedback.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/storage/lite/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/storage/lite/store.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/storage/vector.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/wiki/__init__.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/wiki/community.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/wiki/compiler.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/wiki/generator.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/wiki/search.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex/worker.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex.egg-info/SOURCES.txt +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex.egg-info/dependency_links.txt +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex.egg-info/entry_points.txt +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex.egg-info/requires.txt +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/memplex.egg-info/top_level.txt +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/setup.cfg +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/tests/test_associators.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/tests/test_config.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/tests/test_core_engine.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/tests/test_graph_builder.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/tests/test_llm.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/tests/test_models.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/tests/test_service.py +0 -0
- {memplex-3.2.2 → memplex-3.2.4}/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.4`, 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.4
|
|
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(
|
|
@@ -140,8 +164,8 @@ def _install_codex(target_dir: str | Path | None, *, dry_run: bool) -> AgentInst
|
|
|
140
164
|
"[mcp_servers.memplex]",
|
|
141
165
|
f'command = "{_python_command()}"',
|
|
142
166
|
'args = ["-m", "memplex.adapters.mcp_server"]',
|
|
143
|
-
|
|
144
|
-
|
|
167
|
+
"startup_timeout_sec = 10",
|
|
168
|
+
"tool_timeout_sec = 60",
|
|
145
169
|
MANAGED_END,
|
|
146
170
|
"",
|
|
147
171
|
]
|
|
@@ -334,14 +358,10 @@ def _uninstall_openclaw(
|
|
|
334
358
|
is_managed_entry = _is_managed_openclaw_entry(memplex_entry)
|
|
335
359
|
config_changed = False
|
|
336
360
|
previous_memory_slot = (
|
|
337
|
-
memplex_entry.get("config", {})
|
|
338
|
-
.get("managed", {})
|
|
339
|
-
.get("previousMemorySlot")
|
|
361
|
+
memplex_entry.get("config", {}).get("managed", {}).get("previousMemorySlot")
|
|
340
362
|
)
|
|
341
363
|
added_allow_entry = (
|
|
342
|
-
memplex_entry.get("config", {})
|
|
343
|
-
.get("managed", {})
|
|
344
|
-
.get("addedAllowEntry", False)
|
|
364
|
+
memplex_entry.get("config", {}).get("managed", {}).get("addedAllowEntry", False)
|
|
345
365
|
)
|
|
346
366
|
if is_managed_entry and slots.get("memory") == "memplex":
|
|
347
367
|
if previous_memory_slot:
|
|
@@ -357,11 +377,7 @@ def _uninstall_openclaw(
|
|
|
357
377
|
config_changed = True
|
|
358
378
|
if config_changed and not dry_run:
|
|
359
379
|
_write_json(config_path, config)
|
|
360
|
-
if (
|
|
361
|
-
extension_dir.exists()
|
|
362
|
-
and _is_managed_openclaw_extension(extension_dir)
|
|
363
|
-
and not dry_run
|
|
364
|
-
):
|
|
380
|
+
if extension_dir.exists() and _is_managed_openclaw_extension(extension_dir) and not dry_run:
|
|
365
381
|
shutil.rmtree(extension_dir)
|
|
366
382
|
return AgentInstallResult(
|
|
367
383
|
agent="openclaw",
|
|
@@ -414,7 +430,11 @@ def _install_hermes(
|
|
|
414
430
|
agent="hermes",
|
|
415
431
|
action="install",
|
|
416
432
|
status="planned" if dry_run else "installed",
|
|
417
|
-
files=[
|
|
433
|
+
files=[
|
|
434
|
+
str(provider_path),
|
|
435
|
+
str(plugin_dir / "plugin.yaml"),
|
|
436
|
+
str(plugin_dir / "__init__.py"),
|
|
437
|
+
],
|
|
418
438
|
message="Installed Memplex Hermes memory provider plugin and descriptor.",
|
|
419
439
|
next_steps=["Restart Hermes and select the memplex memory provider."],
|
|
420
440
|
)
|
|
@@ -605,9 +625,7 @@ def compact_memories(context):
|
|
|
605
625
|
)
|
|
606
626
|
|
|
607
627
|
|
|
608
|
-
def _write_hermes_provider_plugin(
|
|
609
|
-
plugin_dir: Path, provider_config: dict[str, Any]
|
|
610
|
-
) -> None:
|
|
628
|
+
def _write_hermes_provider_plugin(plugin_dir: Path, provider_config: dict[str, Any]) -> None:
|
|
611
629
|
plugin_dir.mkdir(parents=True, exist_ok=True)
|
|
612
630
|
(plugin_dir / "plugin.yaml").write_text(
|
|
613
631
|
"\n".join(
|
|
@@ -925,4 +943,4 @@ def _package_version() -> str:
|
|
|
925
943
|
try:
|
|
926
944
|
return pkg_version("memplex")
|
|
927
945
|
except Exception:
|
|
928
|
-
return "3.2.
|
|
946
|
+
return "3.2.4"
|
|
@@ -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
|
|
|
@@ -331,7 +331,7 @@ class MCPServer:
|
|
|
331
331
|
},
|
|
332
332
|
"serverInfo": {
|
|
333
333
|
"name": "memplex",
|
|
334
|
-
"version": "3.2.
|
|
334
|
+
"version": "3.2.4",
|
|
335
335
|
},
|
|
336
336
|
}
|
|
337
337
|
|
|
@@ -353,9 +353,7 @@ class MCPServer:
|
|
|
353
353
|
"content": [
|
|
354
354
|
{
|
|
355
355
|
"type": "text",
|
|
356
|
-
"text": json.dumps(
|
|
357
|
-
result, default=str, ensure_ascii=False, indent=2
|
|
358
|
-
),
|
|
356
|
+
"text": json.dumps(result, default=str, ensure_ascii=False, indent=2),
|
|
359
357
|
}
|
|
360
358
|
],
|
|
361
359
|
}
|
|
@@ -400,9 +398,7 @@ class MCPServer:
|
|
|
400
398
|
)
|
|
401
399
|
return {
|
|
402
400
|
"total": len(result.results),
|
|
403
|
-
"scope": result.scope.value
|
|
404
|
-
if hasattr(result.scope, "value")
|
|
405
|
-
else str(result.scope),
|
|
401
|
+
"scope": result.scope.value if hasattr(result.scope, "value") else str(result.scope),
|
|
406
402
|
"latency_ms": result.latency_ms,
|
|
407
403
|
"results": [
|
|
408
404
|
{
|
|
@@ -29,7 +29,16 @@ import logging
|
|
|
29
29
|
import os
|
|
30
30
|
import time
|
|
31
31
|
from dataclasses import dataclass
|
|
32
|
-
from datetime import datetime
|
|
32
|
+
from datetime import datetime, timezone
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _ensure_aware(dt: datetime) -> datetime:
|
|
36
|
+
"""Normalize a datetime to offset-aware UTC for safe arithmetic."""
|
|
37
|
+
if dt.tzinfo is None:
|
|
38
|
+
return dt.replace(tzinfo=timezone.utc)
|
|
39
|
+
return dt
|
|
40
|
+
|
|
41
|
+
|
|
33
42
|
from pathlib import Path
|
|
34
43
|
from typing import TYPE_CHECKING, List, Optional
|
|
35
44
|
|
|
@@ -251,9 +260,7 @@ class CompactionPipeline:
|
|
|
251
260
|
skipped=False,
|
|
252
261
|
)
|
|
253
262
|
|
|
254
|
-
async def _execute_stage(
|
|
255
|
-
self, stage: str, scope: CompactionScope
|
|
256
|
-
) -> CompactionStageResult:
|
|
263
|
+
async def _execute_stage(self, stage: str, scope: CompactionScope) -> CompactionStageResult:
|
|
257
264
|
"""Dispatch to the correct stage handler."""
|
|
258
265
|
handlers = {
|
|
259
266
|
"extract": self._execute_extract,
|
|
@@ -334,9 +341,7 @@ class CompactionPipeline:
|
|
|
334
341
|
|
|
335
342
|
# Sort by weight * observation composite score
|
|
336
343
|
def _score(fv: FieldValue) -> float:
|
|
337
|
-
return fv.weight * (
|
|
338
|
-
fv.observation if fv.observation is not None else 1.0
|
|
339
|
-
)
|
|
344
|
+
return fv.weight * (fv.observation if fv.observation is not None else 1.0)
|
|
340
345
|
|
|
341
346
|
values.sort(key=_score, reverse=True)
|
|
342
347
|
for fv in values[max_values:]:
|
|
@@ -376,7 +381,7 @@ class CompactionPipeline:
|
|
|
376
381
|
|
|
377
382
|
removed = 0
|
|
378
383
|
processed = len(functions)
|
|
379
|
-
now = datetime.now()
|
|
384
|
+
now = datetime.now(timezone.utc)
|
|
380
385
|
|
|
381
386
|
for func in functions:
|
|
382
387
|
should_delete = False
|
|
@@ -394,7 +399,7 @@ class CompactionPipeline:
|
|
|
394
399
|
except (ValueError, TypeError):
|
|
395
400
|
updated = None
|
|
396
401
|
if updated is not None:
|
|
397
|
-
age_days = (now - updated).days
|
|
402
|
+
age_days = (now - _ensure_aware(updated)).days
|
|
398
403
|
if age_days > max_age_days and func.access_count < min_access:
|
|
399
404
|
should_delete = True
|
|
400
405
|
|
|
@@ -406,7 +411,7 @@ class CompactionPipeline:
|
|
|
406
411
|
review_until = datetime.fromisoformat(review_until)
|
|
407
412
|
except (ValueError, TypeError):
|
|
408
413
|
review_until = None
|
|
409
|
-
if review_until is not None and now > review_until:
|
|
414
|
+
if review_until is not None and now > _ensure_aware(review_until):
|
|
410
415
|
should_delete = True
|
|
411
416
|
elif review_until is None:
|
|
412
417
|
# No expiry set -- use TTL from creation
|
|
@@ -416,7 +421,7 @@ class CompactionPipeline:
|
|
|
416
421
|
created = datetime.fromisoformat(created)
|
|
417
422
|
except (ValueError, TypeError):
|
|
418
423
|
created = None
|
|
419
|
-
if created is not None and (now - created).days > review_ttl:
|
|
424
|
+
if created is not None and (now - _ensure_aware(created)).days > review_ttl:
|
|
420
425
|
should_delete = True
|
|
421
426
|
|
|
422
427
|
# Prune deprecated FieldValue entries (not the whole Function)
|
|
@@ -457,7 +462,7 @@ class CompactionPipeline:
|
|
|
457
462
|
|
|
458
463
|
functions = self._store.list_functions(limit=100000)
|
|
459
464
|
max_age_days = self._config.compaction.prune_max_age_days
|
|
460
|
-
now = datetime.now()
|
|
465
|
+
now = datetime.now(timezone.utc)
|
|
461
466
|
archived = 0
|
|
462
467
|
|
|
463
468
|
for func in functions:
|
|
@@ -471,7 +476,7 @@ class CompactionPipeline:
|
|
|
471
476
|
if updated is None:
|
|
472
477
|
continue
|
|
473
478
|
|
|
474
|
-
age_days = (now - updated).days
|
|
479
|
+
age_days = (now - _ensure_aware(updated)).days
|
|
475
480
|
if age_days > max_age_days and func.access_count == 0:
|
|
476
481
|
# Write to archive
|
|
477
482
|
archive_file = archive_dir / f"{func.id}.json"
|
|
@@ -23,7 +23,15 @@ from __future__ import annotations
|
|
|
23
23
|
|
|
24
24
|
import logging
|
|
25
25
|
import math
|
|
26
|
-
from datetime import datetime
|
|
26
|
+
from datetime import datetime, timezone
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _ensure_aware(dt: datetime) -> datetime:
|
|
30
|
+
if dt.tzinfo is None:
|
|
31
|
+
return dt.replace(tzinfo=timezone.utc)
|
|
32
|
+
return dt
|
|
33
|
+
|
|
34
|
+
|
|
27
35
|
from typing import TYPE_CHECKING, Dict, List, Optional
|
|
28
36
|
|
|
29
37
|
from memplex.models import SearchResult, SourceType
|
|
@@ -181,7 +189,7 @@ class Reranker:
|
|
|
181
189
|
updated_at = datetime.fromisoformat(updated_at)
|
|
182
190
|
except (ValueError, TypeError):
|
|
183
191
|
return 0.5
|
|
184
|
-
days_since = max(0, (datetime.now() - updated_at).days)
|
|
192
|
+
days_since = max(0, (datetime.now(timezone.utc) - _ensure_aware(updated_at)).days)
|
|
185
193
|
return min(1.0, math.exp(-days_since / 60))
|
|
186
194
|
|
|
187
195
|
def _source_weight(self, source_type: SourceType) -> float:
|
|
@@ -212,7 +220,7 @@ class Reranker:
|
|
|
212
220
|
except (ValueError, TypeError):
|
|
213
221
|
last_accessed = None
|
|
214
222
|
if last_accessed is not None:
|
|
215
|
-
days = max(0, (datetime.now() - last_accessed).days)
|
|
223
|
+
days = max(0, (datetime.now(timezone.utc) - _ensure_aware(last_accessed)).days)
|
|
216
224
|
recency = min(1.0, math.exp(-days / 60))
|
|
217
225
|
else:
|
|
218
226
|
recency = 0.3
|
|
@@ -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.4"
|
|
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(
|