feat: add Codex plugin support with hooks, commands, and documentation
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "mempalace",
|
||||||
|
"interface": {
|
||||||
|
"displayName": "MemPalace"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "mempalace",
|
||||||
|
"source": {
|
||||||
|
"source": "local",
|
||||||
|
"path": "./.codex-plugin"
|
||||||
|
},
|
||||||
|
"policy": {
|
||||||
|
"installation": "AVAILABLE",
|
||||||
|
"authentication": "NONE"
|
||||||
|
},
|
||||||
|
"category": "Coding"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"mempalace": {
|
||||||
|
"command": "python3",
|
||||||
|
"args": [
|
||||||
|
"-m",
|
||||||
|
"mempalace.mcp_server"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"commands": [],
|
"commands": [],
|
||||||
"mcp": {
|
"mcpServers": {
|
||||||
"mempalace": {
|
"mempalace": {
|
||||||
"command": "python3",
|
"command": "python3",
|
||||||
"args": [
|
"args": [
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# MemPalace - Codex CLI Plugin
|
||||||
|
|
||||||
|
Give your AI a persistent memory -- mine projects and conversations into a searchable palace backed by ChromaDB, with 19 MCP tools, auto-save hooks, and guided skills.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Python 3.10+
|
||||||
|
- Codex CLI installed and configured
|
||||||
|
- `pip install mempalace`
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Local Install
|
||||||
|
|
||||||
|
1. Copy or symlink the `.codex-plugin` directory into your project root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp -r .codex-plugin /path/to/your/project/.codex-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Verify the plugin is detected:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
codex --plugins
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Initialize your palace:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
codex /init
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git Install
|
||||||
|
|
||||||
|
1. Clone the MemPalace repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/milla-jovovich/mempalace.git
|
||||||
|
cd mempalace
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install the Python package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
3. The `.codex-plugin` directory is already in the repo root. Codex CLI will detect it automatically when you run Codex from inside the repository.
|
||||||
|
|
||||||
|
4. Initialize your palace:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
codex /init
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Skills
|
||||||
|
|
||||||
|
| Skill | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `/help` | Show available commands and usage tips |
|
||||||
|
| `/init` | Initialize a new memory palace |
|
||||||
|
| `/search` | Semantic search across all mined memories |
|
||||||
|
| `/mine` | Mine a project or conversation into your palace |
|
||||||
|
| `/status` | Show palace status, room counts, and health |
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
The plugin includes an auto-save hook that runs on session stop, automatically preserving conversation context into your palace.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- Repository: https://github.com/milla-jovovich/mempalace
|
||||||
|
- Issues: https://github.com/milla-jovovich/mempalace/issues
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"SessionStart": [
|
||||||
|
{
|
||||||
|
"matcher": "*",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "./hooks/mempal-session-start-hook.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Stop": [
|
||||||
|
{
|
||||||
|
"matcher": "*",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "./hooks/mempal-stop-hook.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PreCompact": [
|
||||||
|
{
|
||||||
|
"matcher": "*",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "./hooks/mempal-precompact-hook.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
PLUGIN_ROOT="${CODEX_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||||
|
|
||||||
|
# Capture stdin (hook input from Codex)
|
||||||
|
INPUT_FILE=$(mktemp 2>/dev/null || echo "/tmp/mempal-precompact-hook-$$.json")
|
||||||
|
cat > "$INPUT_FILE"
|
||||||
|
|
||||||
|
# Pipe to Python CLI with codex harness
|
||||||
|
cat "$INPUT_FILE" | python3 -m mempalace hook run --hook precompact --harness codex
|
||||||
|
EXIT_CODE=$?
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -f "$INPUT_FILE" 2>/dev/null
|
||||||
|
exit $EXIT_CODE
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
PLUGIN_ROOT="${CODEX_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||||
|
|
||||||
|
# Capture stdin (hook input from Codex)
|
||||||
|
INPUT_FILE=$(mktemp 2>/dev/null || echo "/tmp/mempal-session-start-hook-$$.json")
|
||||||
|
cat > "$INPUT_FILE"
|
||||||
|
|
||||||
|
# Pipe to Python CLI with codex harness
|
||||||
|
cat "$INPUT_FILE" | python3 -m mempalace hook run --hook session-start --harness codex
|
||||||
|
EXIT_CODE=$?
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -f "$INPUT_FILE" 2>/dev/null
|
||||||
|
exit $EXIT_CODE
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
PLUGIN_ROOT="${CODEX_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||||
|
|
||||||
|
# Capture stdin (hook input from Codex)
|
||||||
|
INPUT_FILE=$(mktemp 2>/dev/null || echo "/tmp/mempal-stop-hook-$$.json")
|
||||||
|
cat > "$INPUT_FILE"
|
||||||
|
|
||||||
|
# Pipe to Python CLI with codex harness
|
||||||
|
cat "$INPUT_FILE" | python3 -m mempalace hook run --hook stop --harness codex
|
||||||
|
EXIT_CODE=$?
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -f "$INPUT_FILE" 2>/dev/null
|
||||||
|
exit $EXIT_CODE
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "mempalace",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.",
|
||||||
|
"author": { "name": "milla-jovovich" },
|
||||||
|
"homepage": "https://github.com/milla-jovovich/mempalace",
|
||||||
|
"repository": "https://github.com/milla-jovovich/mempalace",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": ["memory", "ai", "rag", "mcp", "chromadb", "palace", "search"],
|
||||||
|
"skills": "./skills/",
|
||||||
|
"hooks": "./hooks.json",
|
||||||
|
"mcpServers": {
|
||||||
|
"mempalace": {
|
||||||
|
"command": "python3",
|
||||||
|
"args": ["-m", "mempalace.mcp_server"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"displayName": "MemPalace",
|
||||||
|
"shortDescription": "AI memory system for Codex",
|
||||||
|
"longDescription": "Give your AI a persistent memory — mine projects and conversations into a searchable palace backed by ChromaDB, with 19 MCP tools, auto-save hooks, and guided skills.",
|
||||||
|
"developerName": "milla-jovovich",
|
||||||
|
"category": "Coding",
|
||||||
|
"capabilities": ["Interactive", "Read", "Write"],
|
||||||
|
"websiteURL": "https://github.com/milla-jovovich/mempalace",
|
||||||
|
"privacyPolicyURL": "https://github.com/milla-jovovich/mempalace",
|
||||||
|
"termsOfServiceURL": "https://github.com/milla-jovovich/mempalace",
|
||||||
|
"defaultPrompt": [
|
||||||
|
"Search my memories for recent decisions",
|
||||||
|
"Mine this project into my memory palace",
|
||||||
|
"Show my palace status and room counts"
|
||||||
|
],
|
||||||
|
"brandColor": "#7C3AED"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: help
|
||||||
|
description: Show MemPalace help — available commands, usage tips, and getting started guidance.
|
||||||
|
allowed-tools: Bash, Read
|
||||||
|
---
|
||||||
|
|
||||||
|
# MemPalace Help
|
||||||
|
|
||||||
|
Run the following command and follow the returned instructions step by step:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mempalace instructions help
|
||||||
|
```
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: init
|
||||||
|
description: Initialize a new MemPalace — guided setup for your AI memory palace with ChromaDB backend.
|
||||||
|
allowed-tools: Bash, Read, Write, Edit
|
||||||
|
---
|
||||||
|
|
||||||
|
# MemPalace Init
|
||||||
|
|
||||||
|
Run the following command and follow the returned instructions step by step:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mempalace instructions init
|
||||||
|
```
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: mine
|
||||||
|
description: Mine a project or conversation into your MemPalace — extract and store memories for later retrieval.
|
||||||
|
allowed-tools: Bash, Read, Glob, Grep
|
||||||
|
---
|
||||||
|
|
||||||
|
# MemPalace Mine
|
||||||
|
|
||||||
|
Run the following command and follow the returned instructions step by step:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mempalace instructions mine
|
||||||
|
```
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: search
|
||||||
|
description: Search your MemPalace — semantic search across all mined memories, projects, and conversations.
|
||||||
|
allowed-tools: Bash, Read
|
||||||
|
---
|
||||||
|
|
||||||
|
# MemPalace Search
|
||||||
|
|
||||||
|
Run the following command and follow the returned instructions step by step:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mempalace instructions search
|
||||||
|
```
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: status
|
||||||
|
description: Show MemPalace status — room counts, storage usage, and palace health.
|
||||||
|
allowed-tools: Bash, Read
|
||||||
|
---
|
||||||
|
|
||||||
|
# MemPalace Status
|
||||||
|
|
||||||
|
Run the following command and follow the returned instructions step by step:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mempalace instructions status
|
||||||
|
```
|
||||||
@@ -32,11 +32,15 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
jq --arg v "${{ steps.version.outputs.version }}" '.plugins[0].version = $v' .claude-plugin/marketplace.json > tmp.json && mv tmp.json .claude-plugin/marketplace.json
|
jq --arg v "${{ steps.version.outputs.version }}" '.plugins[0].version = $v' .claude-plugin/marketplace.json > tmp.json && mv tmp.json .claude-plugin/marketplace.json
|
||||||
|
|
||||||
|
- name: Sync codex plugin.json
|
||||||
|
run: |
|
||||||
|
jq --arg v "${{ steps.version.outputs.version }}" '.version = $v' .codex-plugin/plugin.json > tmp.json && mv tmp.json .codex-plugin/plugin.json
|
||||||
|
|
||||||
- name: Commit and push
|
- name: Commit and push
|
||||||
run: |
|
run: |
|
||||||
git config user.name "github-actions[bot]"
|
git config user.name "github-actions[bot]"
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
git add mempalace/version.py .claude-plugin/plugin.json .claude-plugin/marketplace.json
|
git add mempalace/version.py .claude-plugin/plugin.json .claude-plugin/marketplace.json .codex-plugin/plugin.json
|
||||||
if ! git diff --staged --quiet; then
|
if ! git diff --staged --quiet; then
|
||||||
git commit -m "chore: bump version to ${{ steps.version.outputs.version }}"
|
git commit -m "chore: bump version to ${{ steps.version.outputs.version }}"
|
||||||
git push
|
git push
|
||||||
|
|||||||
+2
-2
@@ -475,13 +475,13 @@ def main():
|
|||||||
p_hook_run.add_argument(
|
p_hook_run.add_argument(
|
||||||
"--hook",
|
"--hook",
|
||||||
required=True,
|
required=True,
|
||||||
choices=["stop", "precompact"],
|
choices=["session-start", "stop", "precompact"],
|
||||||
help="Hook name to run",
|
help="Hook name to run",
|
||||||
)
|
)
|
||||||
p_hook_run.add_argument(
|
p_hook_run.add_argument(
|
||||||
"--harness",
|
"--harness",
|
||||||
required=True,
|
required=True,
|
||||||
choices=["claude-code"],
|
choices=["claude-code", "codex"],
|
||||||
help="Harness type (determines stdin JSON format)",
|
help="Harness type (determines stdin JSON format)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+28
-3
@@ -1,9 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
Hook logic for MemPalace — Python implementation of stop and precompact hooks.
|
Hook logic for MemPalace — Python implementation of session-start, stop, and precompact hooks.
|
||||||
|
|
||||||
Reads JSON from stdin, outputs JSON to stdout.
|
Reads JSON from stdin, outputs JSON to stdout.
|
||||||
Supported hooks: stop, precompact
|
Supported hooks: session-start, stop, precompact
|
||||||
Supported harnesses: claude-code (extensible to cursor, gemini, etc.)
|
Supported harnesses: claude-code, codex (extensible to cursor, gemini, etc.)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@@ -105,10 +105,20 @@ def _parse_claude_code_input(data: dict) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_codex_input(data: dict) -> dict:
|
||||||
|
"""Parse stdin JSON for the codex harness."""
|
||||||
|
return {
|
||||||
|
"session_id": _sanitize_session_id(str(data.get("session_id", "unknown"))),
|
||||||
|
"stop_hook_active": data.get("stop_hook_active", False),
|
||||||
|
"transcript_path": str(data.get("transcript_path", "")),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _parse_harness_input(data: dict, harness: str) -> dict:
|
def _parse_harness_input(data: dict, harness: str) -> dict:
|
||||||
"""Parse stdin JSON according to the harness type."""
|
"""Parse stdin JSON according to the harness type."""
|
||||||
parsers = {
|
parsers = {
|
||||||
"claude-code": _parse_claude_code_input,
|
"claude-code": _parse_claude_code_input,
|
||||||
|
"codex": _parse_codex_input,
|
||||||
}
|
}
|
||||||
parser = parsers.get(harness)
|
parser = parsers.get(harness)
|
||||||
if parser is None:
|
if parser is None:
|
||||||
@@ -163,6 +173,20 @@ def hook_stop(data: dict, harness: str):
|
|||||||
_output({})
|
_output({})
|
||||||
|
|
||||||
|
|
||||||
|
def hook_session_start(data: dict, harness: str):
|
||||||
|
"""Session start hook: initialize session tracking state."""
|
||||||
|
parsed = _parse_harness_input(data, harness)
|
||||||
|
session_id = parsed["session_id"]
|
||||||
|
|
||||||
|
_log(f"SESSION START for session {session_id}")
|
||||||
|
|
||||||
|
# Initialize session state directory
|
||||||
|
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Pass through — no blocking on session start
|
||||||
|
_output({})
|
||||||
|
|
||||||
|
|
||||||
def hook_precompact(data: dict, harness: str):
|
def hook_precompact(data: dict, harness: str):
|
||||||
"""Precompact hook: always block with comprehensive save instruction."""
|
"""Precompact hook: always block with comprehensive save instruction."""
|
||||||
parsed = _parse_harness_input(data, harness)
|
parsed = _parse_harness_input(data, harness)
|
||||||
@@ -196,6 +220,7 @@ def run_hook(hook_name: str, harness: str):
|
|||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
hooks = {
|
hooks = {
|
||||||
|
"session-start": hook_session_start,
|
||||||
"stop": hook_stop,
|
"stop": hook_stop,
|
||||||
"precompact": hook_precompact,
|
"precompact": hook_precompact,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user