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.
Files changed (120) hide show
  1. {memplex-3.2.2 → memplex-3.2.3}/PKG-INFO +1 -1
  2. {memplex-3.2.2 → memplex-3.2.3}/README.md +35 -29
  3. {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/.claude-plugin/plugin.json +1 -1
  4. {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/agent_installer.py +27 -3
  5. {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/cli.py +58 -80
  6. {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/mcp_server.py +1 -1
  7. {memplex-3.2.2 → memplex-3.2.3}/memplex.egg-info/PKG-INFO +1 -1
  8. {memplex-3.2.2 → memplex-3.2.3}/pyproject.toml +1 -1
  9. {memplex-3.2.2 → memplex-3.2.3}/tests/test_agent_hot_paths.py +62 -0
  10. {memplex-3.2.2 → memplex-3.2.3}/tests/test_hooks.py +31 -8
  11. {memplex-3.2.2 → memplex-3.2.3}/tests/test_install_scripts.py +68 -0
  12. {memplex-3.2.2 → memplex-3.2.3}/LICENSE +0 -0
  13. {memplex-3.2.2 → memplex-3.2.3}/memplex/__init__.py +0 -0
  14. {memplex-3.2.2 → memplex-3.2.3}/memplex/__main__.py +0 -0
  15. {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/.mcp.json +0 -0
  16. {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/__init__.py +0 -0
  17. {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/hooks/hooks.json +0 -0
  18. {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/scripts/hook-runner.py +0 -0
  19. {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/skills/mem-explore/SKILL.md +0 -0
  20. {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/skills/mem-manage/SKILL.md +0 -0
  21. {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/skills/mem-search/SKILL.md +0 -0
  22. {memplex-3.2.2 → memplex-3.2.3}/memplex/_plugin/skills/mem-write/SKILL.md +0 -0
  23. {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/__init__.py +0 -0
  24. {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/agent_runtime.py +0 -0
  25. {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/claude_skill.py +0 -0
  26. {memplex-3.2.2 → memplex-3.2.3}/memplex/adapters/http_api.py +0 -0
  27. {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/__init__.py +0 -0
  28. {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/base.py +0 -0
  29. {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/benchmark_cli.py +0 -0
  30. {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/evaluator.py +0 -0
  31. {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/loader.py +0 -0
  32. {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/locomo.py +0 -0
  33. {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/memory_eval.py +0 -0
  34. {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/memory_metrics.py +0 -0
  35. {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/metrics.py +0 -0
  36. {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/nq_trivia.py +0 -0
  37. {memplex-3.2.2 → memplex-3.2.3}/memplex/benchmarks/popqa_hotpot.py +0 -0
  38. {memplex-3.2.2 → memplex-3.2.3}/memplex/compaction.py +0 -0
  39. {memplex-3.2.2 → memplex-3.2.3}/memplex/config.py +0 -0
  40. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/__init__.py +0 -0
  41. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/associator/__init__.py +0 -0
  42. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/associator/domain_classifier.py +0 -0
  43. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/associator/entity_aligner.py +0 -0
  44. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/associator/ref_linker.py +0 -0
  45. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/associator/term_mapper.py +0 -0
  46. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/dictionaries/__init__.py +0 -0
  47. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/engine.py +0 -0
  48. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/extractors/__init__.py +0 -0
  49. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/extractors/docx.py +0 -0
  50. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/extractors/image.py +0 -0
  51. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/extractors/markdown.py +0 -0
  52. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/extractors/pdf.py +0 -0
  53. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/extractors/vision_mapper.py +0 -0
  54. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/handlers/__init__.py +0 -0
  55. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/handlers/clipboard.py +0 -0
  56. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/handlers/file_handler.py +0 -0
  57. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/handlers/url_handler.py +0 -0
  58. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/hooks/__init__.py +0 -0
  59. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/hooks/collector.py +0 -0
  60. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/hooks/hook_event.py +0 -0
  61. {memplex-3.2.2 → memplex-3.2.3}/memplex/core/hooks/registry.py +0 -0
  62. {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/__init__.py +0 -0
  63. {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/enhancer.py +0 -0
  64. {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/fallback_chain.py +0 -0
  65. {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/injection_guard.py +0 -0
  66. {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/provider.py +0 -0
  67. {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/providers/__init__.py +0 -0
  68. {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/providers/anthropic.py +0 -0
  69. {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/providers/local.py +0 -0
  70. {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/providers/rule_based.py +0 -0
  71. {memplex-3.2.2 → memplex-3.2.3}/memplex/llm/sanitizer.py +0 -0
  72. {memplex-3.2.2 → memplex-3.2.3}/memplex/logging_utils.py +0 -0
  73. {memplex-3.2.2 → memplex-3.2.3}/memplex/metrics.py +0 -0
  74. {memplex-3.2.2 → memplex-3.2.3}/memplex/models/__init__.py +0 -0
  75. {memplex-3.2.2 → memplex-3.2.3}/memplex/models/feedback.py +0 -0
  76. {memplex-3.2.2 → memplex-3.2.3}/memplex/models/graph.py +0 -0
  77. {memplex-3.2.2 → memplex-3.2.3}/memplex/models/memory.py +0 -0
  78. {memplex-3.2.2 → memplex-3.2.3}/memplex/models/misc.py +0 -0
  79. {memplex-3.2.2 → memplex-3.2.3}/memplex/models/paragraph.py +0 -0
  80. {memplex-3.2.2 → memplex-3.2.3}/memplex/models/search.py +0 -0
  81. {memplex-3.2.2 → memplex-3.2.3}/memplex/models/source.py +0 -0
  82. {memplex-3.2.2 → memplex-3.2.3}/memplex/models/task.py +0 -0
  83. {memplex-3.2.2 → memplex-3.2.3}/memplex/processing/__init__.py +0 -0
  84. {memplex-3.2.2 → memplex-3.2.3}/memplex/processing/graph_builder.py +0 -0
  85. {memplex-3.2.2 → memplex-3.2.3}/memplex/processing/merger/__init__.py +0 -0
  86. {memplex-3.2.2 → memplex-3.2.3}/memplex/processing/merger/confidence_calculator.py +0 -0
  87. {memplex-3.2.2 → memplex-3.2.3}/memplex/processing/merger/conflict_resolver.py +0 -0
  88. {memplex-3.2.2 → memplex-3.2.3}/memplex/retrieval/__init__.py +0 -0
  89. {memplex-3.2.2 → memplex-3.2.3}/memplex/retrieval/dedup.py +0 -0
  90. {memplex-3.2.2 → memplex-3.2.3}/memplex/retrieval/embedding.py +0 -0
  91. {memplex-3.2.2 → memplex-3.2.3}/memplex/retrieval/reranker.py +0 -0
  92. {memplex-3.2.2 → memplex-3.2.3}/memplex/service.py +0 -0
  93. {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/__init__.py +0 -0
  94. {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/base.py +0 -0
  95. {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/changelog.py +0 -0
  96. {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/feedback.py +0 -0
  97. {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/lite/__init__.py +0 -0
  98. {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/lite/store.py +0 -0
  99. {memplex-3.2.2 → memplex-3.2.3}/memplex/storage/vector.py +0 -0
  100. {memplex-3.2.2 → memplex-3.2.3}/memplex/wiki/__init__.py +0 -0
  101. {memplex-3.2.2 → memplex-3.2.3}/memplex/wiki/community.py +0 -0
  102. {memplex-3.2.2 → memplex-3.2.3}/memplex/wiki/compiler.py +0 -0
  103. {memplex-3.2.2 → memplex-3.2.3}/memplex/wiki/generator.py +0 -0
  104. {memplex-3.2.2 → memplex-3.2.3}/memplex/wiki/search.py +0 -0
  105. {memplex-3.2.2 → memplex-3.2.3}/memplex/worker.py +0 -0
  106. {memplex-3.2.2 → memplex-3.2.3}/memplex.egg-info/SOURCES.txt +0 -0
  107. {memplex-3.2.2 → memplex-3.2.3}/memplex.egg-info/dependency_links.txt +0 -0
  108. {memplex-3.2.2 → memplex-3.2.3}/memplex.egg-info/entry_points.txt +0 -0
  109. {memplex-3.2.2 → memplex-3.2.3}/memplex.egg-info/requires.txt +0 -0
  110. {memplex-3.2.2 → memplex-3.2.3}/memplex.egg-info/top_level.txt +0 -0
  111. {memplex-3.2.2 → memplex-3.2.3}/setup.cfg +0 -0
  112. {memplex-3.2.2 → memplex-3.2.3}/tests/test_agent_runtime.py +0 -0
  113. {memplex-3.2.2 → memplex-3.2.3}/tests/test_associators.py +0 -0
  114. {memplex-3.2.2 → memplex-3.2.3}/tests/test_config.py +0 -0
  115. {memplex-3.2.2 → memplex-3.2.3}/tests/test_core_engine.py +0 -0
  116. {memplex-3.2.2 → memplex-3.2.3}/tests/test_graph_builder.py +0 -0
  117. {memplex-3.2.2 → memplex-3.2.3}/tests/test_llm.py +0 -0
  118. {memplex-3.2.2 → memplex-3.2.3}/tests/test_models.py +0 -0
  119. {memplex-3.2.2 → memplex-3.2.3}/tests/test_service.py +0 -0
  120. {memplex-3.2.2 → memplex-3.2.3}/tests/test_storage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memplex
3
- Version: 3.2.2
3
+ Version: 3.2.3
4
4
  Summary: Memplex - Memory Complex: multi-agent knowledge graph memory system with 3-layer retrieval
5
5
  Requires-Python: >=3.11
6
6
  License-File: LICENSE
@@ -13,61 +13,67 @@
13
13
 
14
14
  ## Installation
15
15
 
16
- ### Claude Code Plugin
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
- # Clone and install locally
21
- cd plugin
22
- /plugin install ./plugin
22
+ npx memplex setup
23
23
  ```
24
24
 
25
- **Option 2: Via marketplace**
25
+ Install into a specific local agent:
26
+
26
27
  ```bash
27
- /plugin marketplace add ./marketplace.json
28
- /plugin install memplex
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
- **Option 3: pip install**
34
+ Install every supported agent config on this machine:
35
+
32
36
  ```bash
33
- pip install memplex
37
+ npx memplex setup --agent all --project-path "$PWD"
34
38
  ```
35
39
 
36
- ### From Source
40
+ Uninstall:
37
41
 
38
42
  ```bash
39
- git clone https://github.com/articultur/memplex.git
40
- cd memplex
41
- pip install -e .
43
+ npx memplex uninstall --agent all
42
44
  ```
43
45
 
44
- ### Agent One-Command Install
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
- For a machine that does not have this repository checked out, install Memplex
47
- into a detected local agent with:
52
+ Python-first users can use a persistent tool install:
48
53
 
49
54
  ```bash
50
- curl -fsSL https://raw.githubusercontent.com/articultur/memplex/main/scripts/install-agent.sh | bash
55
+ uv tool install memplex==3.2.3
56
+ memplex setup
51
57
  ```
52
58
 
53
- By default the script installs Memplex from GitHub into a persistent Python
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
- Install a specific agent:
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
- curl -fsSL https://raw.githubusercontent.com/articultur/memplex/main/scripts/install-agent.sh | \
63
- bash -s -- --agent hermes --package memplex --project-path "$PWD" --user-id "$USER"
68
+ memplex setup --agent claude-code
64
69
  ```
65
70
 
66
- Install every supported agent config on this machine:
71
+ ### From Source
67
72
 
68
73
  ```bash
69
- curl -fsSL https://raw.githubusercontent.com/articultur/memplex/main/scripts/install-agent.sh | \
70
- bash -s -- --agent all --project-path "$PWD"
74
+ git clone https://github.com/articultur/memplex.git
75
+ cd memplex
76
+ pip install -e .
71
77
  ```
72
78
 
73
79
  Uninstall:
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memplex",
3
- "version": "3.2.2",
3
+ "version": "3.2.3",
4
4
  "description": "Multi-agent memory system -- persistent knowledge graph with 3-layer retrieval, compaction, and wiki",
5
5
  "author": {
6
6
  "name": "articultur"
@@ -80,9 +80,33 @@ def uninstall_agent(
80
80
 
81
81
 
82
82
  def _expand_agents(agent: str) -> list[str]:
83
- if (agent or "").strip().lower() == "all":
83
+ requested = (agent or "auto").strip().lower()
84
+ if requested == "all":
84
85
  return ["codex", "claude-code", "openclaw", "hermes"]
85
- return [get_agent_manifest(agent)["name"]]
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.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 as a Claude Code plugin."""
399
- if getattr(args, "uninstall", False):
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
- try:
450
- ver = pkg_version("memplex")
451
- except Exception:
452
- ver = "3.2.2"
453
- marker.write_text(
454
- json.dumps(
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
- print(f" Version: {ver}")
463
-
464
- print("\n" + "=" * 40)
465
- print("Memplex plugin installed successfully!")
466
- print("\nWhat was configured:")
467
- print(f" - Hooks: {plugin_target}/hooks/hooks.json")
468
- print(f" - MCP: {plugin_target}/.mcp.json")
469
- print(f" - Skills: {plugin_target}/skills/*/SKILL.md")
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
- p_setup = sub.add_parser("setup", help="Install Memplex as a Claude Code plugin")
634
- p_setup.add_argument(
635
- "--uninstall", action="store_true", help="Uninstall the plugin"
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.2",
334
+ "version": "3.2.3",
335
335
  },
336
336
  }
337
337
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memplex
3
- Version: 3.2.2
3
+ Version: 3.2.3
4
4
  Summary: Memplex - Memory Complex: multi-agent knowledge graph memory system with 3-layer retrieval
5
5
  Requires-Python: >=3.11
6
6
  License-File: LICENSE
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memplex"
7
- version = "3.2.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.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
- [sys.executable, "-m", "memplex", "setup"],
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
- assert "Memplex plugin installed successfully" in r.stdout
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
- [sys.executable, "-m", "memplex", "setup", "--uninstall"],
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
- assert "Memplex plugin uninstalled" in r.stdout
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.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