feat(mem0-plugin): complete all Tier 1-8 spec items, add scheduled dream and bash error detection
- Add /mem0:dream --schedule support via Claude Code /schedule command with local launchd/cron fallback for non-cloud users - Add /mem0:digest --schedule and file output to ~/.mem0/weekly-digest.md with digest-history.log for trend tracking - Add on_post_commit.sh PostToolUse hook: interactive prompt after git commit asking user to save change as memory with category suggestion - Add on_bash_output.sh PostToolUse hook: detects stack traces and error patterns in bash command output, injects mem0 search rubric for prior occurrences (complements existing user prompt error detection) - Fix session-end report: now shows per-category breakdown "wrote 3 memories (decision 2, anti_pattern 1)" matching spec - Wire new hooks into all 3 platforms (Claude Code, Cursor, Codex) - Add post_commit and bash_error telemetry events - Add CTO demo testing guide with tier-by-tier manual test steps
This commit is contained in:
@@ -34,6 +34,26 @@
|
|||||||
"timeout": 3
|
"timeout": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CODEX_PLUGIN_ROOT}/scripts/on_post_commit.sh",
|
||||||
|
"timeout": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CODEX_PLUGIN_ROOT}/scripts/on_bash_output.sh",
|
||||||
|
"timeout": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Stop": [
|
"Stop": [
|
||||||
|
|||||||
@@ -17,6 +17,16 @@
|
|||||||
"command": "${CURSOR_PLUGIN_ROOT}/scripts/on_post_tool_use_cursor.sh",
|
"command": "${CURSOR_PLUGIN_ROOT}/scripts/on_post_tool_use_cursor.sh",
|
||||||
"matcher": "mcp__mem0__",
|
"matcher": "mcp__mem0__",
|
||||||
"timeout": 3
|
"timeout": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "${CURSOR_PLUGIN_ROOT}/scripts/on_post_commit.sh",
|
||||||
|
"matcher": "Bash",
|
||||||
|
"timeout": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "${CURSOR_PLUGIN_ROOT}/scripts/on_bash_output.sh",
|
||||||
|
"matcher": "Bash",
|
||||||
|
"timeout": 5
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"preCompact": [
|
"preCompact": [
|
||||||
|
|||||||
@@ -66,6 +66,26 @@
|
|||||||
"timeout": 3
|
"timeout": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_post_commit.sh",
|
||||||
|
"timeout": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on_bash_output.sh",
|
||||||
|
"timeout": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"PreCompact": [
|
"PreCompact": [
|
||||||
|
|||||||
Executable
+104
@@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Hook: PostToolUse (matcher: Bash)
|
||||||
|
#
|
||||||
|
# Scans bash command output for stack traces and error patterns.
|
||||||
|
# When found, injects a search rubric telling the agent to check mem0
|
||||||
|
# for prior occurrences of the same error.
|
||||||
|
#
|
||||||
|
# This complements on_user_prompt.sh (which catches errors in the user's
|
||||||
|
# typed message). This hook catches errors in COMMAND OUTPUT — e.g.,
|
||||||
|
# when `npm test` or `python script.py` fails with a traceback.
|
||||||
|
#
|
||||||
|
# Input: JSON on stdin with tool_name, tool_input, tool_result
|
||||||
|
# Output: Context injected into Claude's next response (exit 0)
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
INPUT=$(cat)
|
||||||
|
|
||||||
|
TOOL_RESULT=$(echo "$INPUT" | jq -r '.tool_result // ""' 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
# Skip short output (< 50 chars unlikely to contain a real stack trace)
|
||||||
|
if [ ${#TOOL_RESULT} -lt 50 ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Skip if this is a git commit (handled by on_post_commit.sh)
|
||||||
|
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
|
||||||
|
case "$COMMAND" in
|
||||||
|
*"git commit"*|*"git merge"*|*"git rebase"*)
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Detect stack traces and error patterns in command output
|
||||||
|
HAS_ERROR=""
|
||||||
|
if echo "$TOOL_RESULT" | grep -qiE '(Traceback \(most recent|Error:|Exception:|panic:|FAILED|fatal:|FAIL:| at .+\.[a-z]+:[0-9]+|error\[E[0-9]+\])'; then
|
||||||
|
HAS_ERROR="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$HAS_ERROR" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
. "$SCRIPT_DIR/_identity.sh" 2>/dev/null || true
|
||||||
|
|
||||||
|
if [ -z "${MEM0_API_KEY:-}" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract error class/message (first matching line)
|
||||||
|
ERROR_LINE=$(echo "$TOOL_RESULT" | grep -iE '(Error:|Exception:|panic:|FAIL:|fatal:)' | head -1 | sed 's/^[[:space:]]*//' | cut -c1-120)
|
||||||
|
|
||||||
|
# Extract file paths from stack trace frames
|
||||||
|
TRACE_FILES=$(echo "$TOOL_RESULT" | grep -oE '([a-zA-Z0-9_./-]+\.(py|ts|tsx|js|jsx|rs|go|rb|java|sh))(:[0-9]+)?' | head -5 | sort -u)
|
||||||
|
|
||||||
|
# Build file list for display
|
||||||
|
FILE_DISPLAY=""
|
||||||
|
if [ -n "$TRACE_FILES" ]; then
|
||||||
|
FILE_DISPLAY=$(echo "$TRACE_FILES" | sed 's/^/ - /')
|
||||||
|
fi
|
||||||
|
|
||||||
|
USER_ID="$MEM0_RESOLVED_USER_ID"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
## Error detected in command output
|
||||||
|
|
||||||
|
\`$COMMAND\` produced an error:
|
||||||
|
> $ERROR_LINE
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$FILE_DISPLAY" ]; then
|
||||||
|
cat <<EOF
|
||||||
|
**Files in stack trace:**
|
||||||
|
$FILE_DISPLAY
|
||||||
|
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
Search mem0 for prior occurrences — this error may have been seen before:
|
||||||
|
- \`search_memories(query="$(echo "$ERROR_LINE" | cut -c1-60)", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "anti_pattern"}}]})\`
|
||||||
|
- \`search_memories(query="$(echo "$ERROR_LINE" | cut -c1-60)", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata": {"type": "bug_fix"}}]})\`
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$TRACE_FILES" ]; then
|
||||||
|
FIRST_FILE=$(echo "$TRACE_FILES" | head -1 | sed 's/:[0-9]*//')
|
||||||
|
cat <<EOF
|
||||||
|
- \`search_memories(query="$FIRST_FILE", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}]})\`
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
If mem0 returns relevant context, use it to debug faster.
|
||||||
|
If you solve this, store the fix as an \`anti_pattern\` or \`bug_fix\` memory for next time.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Telemetry
|
||||||
|
python3 "$SCRIPT_DIR/telemetry.py" bash_error --error_detected 2>/dev/null &
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+93
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Hook: PostToolUse (matcher: Bash)
|
||||||
|
#
|
||||||
|
# Fires AFTER a Bash tool call completes. When a git commit/merge/rebase
|
||||||
|
# just succeeded, surfaces 1-3 relevant memories from the changed files
|
||||||
|
# and prompts Claude to ask the user if this change should be stored as
|
||||||
|
# a learning.
|
||||||
|
#
|
||||||
|
# Input: JSON on stdin with tool_name, tool_input, tool_result
|
||||||
|
# Output: Context injected into Claude's next response (exit 0)
|
||||||
|
#
|
||||||
|
# This implements Spec #28 — interactive pre-commit memory check.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
INPUT=$(cat)
|
||||||
|
|
||||||
|
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ -z "$COMMAND" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only trigger on git commit/merge/rebase commands
|
||||||
|
case "$COMMAND" in
|
||||||
|
*"git commit"*|*"git merge"*|*"git rebase"*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Check if the command actually succeeded (look for commit hash in output)
|
||||||
|
TOOL_RESULT=$(echo "$INPUT" | jq -r '.tool_result // ""' 2>/dev/null || echo "")
|
||||||
|
if echo "$TOOL_RESULT" | grep -qiE '(error|fatal|abort|nothing to commit|no changes)'; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
. "$SCRIPT_DIR/_identity.sh" 2>/dev/null || true
|
||||||
|
|
||||||
|
# No API key — skip interactive prompt
|
||||||
|
if [ -z "${MEM0_API_KEY:-}" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract changed files from commit
|
||||||
|
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD 2>/dev/null | head -10 || echo "")
|
||||||
|
if [ -z "$CHANGED_FILES" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the commit message
|
||||||
|
COMMIT_MSG=$(git log -1 --format='%s' HEAD 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
# Build file list for display
|
||||||
|
FILE_LIST=$(echo "$CHANGED_FILES" | sed 's/^/ - /' | head -5)
|
||||||
|
FILE_COUNT=$(echo "$CHANGED_FILES" | wc -l | tr -d ' ')
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
## Commit captured — save as memory?
|
||||||
|
|
||||||
|
**Commit:** $COMMIT_MSG
|
||||||
|
**Files changed ($FILE_COUNT):**
|
||||||
|
$FILE_LIST
|
||||||
|
|
||||||
|
This commit may contain learnings worth preserving. Ask the user:
|
||||||
|
|
||||||
|
> **Save this change as a memory?** I can store it as one of:
|
||||||
|
> - \`decision\` — if an architectural or design choice was made
|
||||||
|
> - \`task_learning\` — if a strategy or approach worked well
|
||||||
|
> - \`anti_pattern\` — if this fixes a bug or avoids a known pitfall
|
||||||
|
> - \`convention\` — if this establishes a new pattern
|
||||||
|
>
|
||||||
|
> Type the category, or **skip** to move on.
|
||||||
|
|
||||||
|
If the user provides a category (or says yes/sure/go ahead):
|
||||||
|
1. Summarize the commit: what changed and why (from the diff + commit message)
|
||||||
|
2. Call \`add_memory\` with:
|
||||||
|
- \`messages=[{"role": "user", "content": "<your summary>"}]\`
|
||||||
|
- \`user_id="$MEM0_RESOLVED_USER_ID"\`
|
||||||
|
- \`app_id="$MEM0_PROJECT_ID"\`
|
||||||
|
- \`metadata={"type": "<chosen_category>", "branch": "$MEM0_BRANCH", "confidence": 0.8, "files": [<changed files>], "source": "post-commit"}\`
|
||||||
|
- \`infer=False\`
|
||||||
|
|
||||||
|
If the user says skip/no/nothing: proceed normally. Do NOT ask again for the same commit.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Telemetry
|
||||||
|
python3 "$SCRIPT_DIR/telemetry.py" post_commit --files_count="$FILE_COUNT" 2>/dev/null &
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -100,7 +100,12 @@ def report() -> str:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
parts = []
|
parts = []
|
||||||
parts.append(f"Session: wrote {adds} memories, retrieved {searches}")
|
category_counts = stats.get("category_counts", {})
|
||||||
|
if category_counts:
|
||||||
|
breakdown = ", ".join(f"{c} {n}" for c, n in sorted(category_counts.items(), key=lambda x: -x[1]))
|
||||||
|
parts.append(f"Session: wrote {adds} memories ({breakdown}), retrieved {searches}")
|
||||||
|
else:
|
||||||
|
parts.append(f"Session: wrote {adds} memories, retrieved {searches}")
|
||||||
if categories:
|
if categories:
|
||||||
parts.append(f"Categories touched: {', '.join(categories)}")
|
parts.append(f"Categories touched: {', '.join(categories)}")
|
||||||
|
|
||||||
|
|||||||
@@ -140,6 +140,11 @@ def main() -> int:
|
|||||||
properties["source_detail"] = arg.split("=", 1)[1]
|
properties["source_detail"] = arg.split("=", 1)[1]
|
||||||
elif arg.startswith("--tool="):
|
elif arg.startswith("--tool="):
|
||||||
properties["tool"] = arg.split("=", 1)[1]
|
properties["tool"] = arg.split("=", 1)[1]
|
||||||
|
elif arg.startswith("--files_count="):
|
||||||
|
try:
|
||||||
|
properties["files_count"] = int(arg.split("=", 1)[1])
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
emit(event_type, properties)
|
emit(event_type, properties)
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -65,7 +65,55 @@ Top categories: <top 3 by count>
|
|||||||
<2-3 sentence summary of the most important decisions, learnings, or patterns stored this week>
|
<2-3 sentence summary of the most important decisions, learnings, or patterns stored this week>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 4: Empty state
|
### Step 4: Write digest to file
|
||||||
|
|
||||||
|
After displaying, write the digest to `~/.mem0/weekly-digest.md` for persistence
|
||||||
|
and external consumption (email, Slack, etc.):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.mem0
|
||||||
|
```
|
||||||
|
|
||||||
|
Write the full digest output (same markdown shown in terminal) to `~/.mem0/weekly-digest.md`
|
||||||
|
using the Write tool. **Overwrite** the file each time — it always contains the latest digest.
|
||||||
|
|
||||||
|
Also append a one-line summary to `~/.mem0/digest-history.log` for trend tracking:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "<YYYY-MM-DD> | <project_id> | +<new_count> memories | top: <top_category>" >> ~/.mem0/digest-history.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Print at the end:
|
||||||
|
```
|
||||||
|
Digest saved to ~/.mem0/weekly-digest.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Schedule recurring digests
|
||||||
|
|
||||||
|
When invoked with `--schedule` (e.g., `/mem0:digest --schedule weekly`), register
|
||||||
|
a cloud routine via Claude Code's `/schedule` command:
|
||||||
|
|
||||||
|
```
|
||||||
|
/schedule <frequency> /mem0:digest
|
||||||
|
```
|
||||||
|
|
||||||
|
For example:
|
||||||
|
- `/schedule weekly on Monday 9am /mem0:digest` — digest every Monday morning
|
||||||
|
- `/schedule daily at 8am /mem0:digest` — daily digest
|
||||||
|
|
||||||
|
Print:
|
||||||
|
```
|
||||||
|
Digest scheduled: <frequency>
|
||||||
|
Manage at: https://claude.ai/code/routines
|
||||||
|
```
|
||||||
|
|
||||||
|
If `/schedule` is unavailable, print a cron one-liner the user can install manually:
|
||||||
|
```bash
|
||||||
|
# macOS/Linux — weekly Monday 9am
|
||||||
|
(crontab -l 2>/dev/null; echo "0 9 * * 1 cd PROJECT_DIR && claude -p '/mem0:digest' >> /tmp/mem0-digest.log 2>&1") | crontab -
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Empty state
|
||||||
|
|
||||||
If no memories in the last 7 days:
|
If no memories in the last 7 days:
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -235,6 +235,110 @@ In auto mode:
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: Claude Code does not have native cron/scheduling. To run dream periodically,
|
## Scheduling recurring dreams
|
||||||
use an external scheduler (cron, launchd) calling `claude -p "/mem0:dream --auto"`,
|
|
||||||
or run it manually at the start of each week.
|
When invoked with `--schedule` (e.g., `/mem0:dream --schedule weekly`), register a
|
||||||
|
cloud routine via Claude Code's built-in `/schedule` command so the dream runs
|
||||||
|
automatically without any local cron or launchd setup.
|
||||||
|
|
||||||
|
### Step S1: Parse schedule frequency
|
||||||
|
|
||||||
|
Accept natural-language frequency after `--schedule`:
|
||||||
|
|
||||||
|
| User input | Cron equivalent | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `weekly` or `--schedule weekly` | Every Sunday 3:00 AM local | Default weekly consolidation |
|
||||||
|
| `daily` | Every day 3:00 AM local | For high-volume projects |
|
||||||
|
| `biweekly` | Every other Sunday 3:00 AM local | Lower frequency option |
|
||||||
|
| Custom (e.g., `"every Monday 9am"`) | Pass verbatim to `/schedule` | Let Claude Code resolve it |
|
||||||
|
|
||||||
|
### Step S2: Create the routine
|
||||||
|
|
||||||
|
Use Claude Code's `/schedule` command to create a cloud routine. The routine runs
|
||||||
|
`/mem0:dream --auto` on the specified schedule against the current repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
/schedule <frequency> /mem0:dream --auto
|
||||||
|
```
|
||||||
|
|
||||||
|
For example:
|
||||||
|
- `/schedule weekly /mem0:dream --auto` — runs every week
|
||||||
|
- `/schedule daily at 3am /mem0:dream --auto` — runs every day at 3 AM
|
||||||
|
- `/schedule every Monday 9am /mem0:dream --auto` — runs every Monday at 9 AM
|
||||||
|
|
||||||
|
The `/schedule` command handles all the cloud infrastructure: repository cloning,
|
||||||
|
environment setup, and cron scheduling. The routine runs as a full Claude Code
|
||||||
|
cloud session with access to the mem0 MCP tools.
|
||||||
|
|
||||||
|
### Step S3: Confirm to user
|
||||||
|
|
||||||
|
After the routine is created, print:
|
||||||
|
|
||||||
|
```
|
||||||
|
Dream scheduled: <frequency>
|
||||||
|
Routine name: mem0-dream-<project_id>
|
||||||
|
Next run: <next scheduled time>
|
||||||
|
|
||||||
|
Manage at: https://claude.ai/code/routines
|
||||||
|
Edit: /schedule list → /schedule update
|
||||||
|
Cancel: /schedule list → delete the routine
|
||||||
|
```
|
||||||
|
|
||||||
|
### Managing scheduled dreams
|
||||||
|
|
||||||
|
| Action | Command |
|
||||||
|
|---|---|
|
||||||
|
| List all routines | `/schedule list` |
|
||||||
|
| Run dream now | `/schedule run` (select the dream routine) |
|
||||||
|
| Change frequency | `/schedule update` (select the dream routine) |
|
||||||
|
| Pause | Toggle off at claude.ai/code/routines |
|
||||||
|
| Delete | Delete at claude.ai/code/routines or `/schedule update` |
|
||||||
|
|
||||||
|
### Fallback for non-cloud users
|
||||||
|
|
||||||
|
If `/schedule` is unavailable (API key auth, no claude.ai subscription), fall back
|
||||||
|
to local options:
|
||||||
|
|
||||||
|
1. **macOS launchd plist** — generate and install:
|
||||||
|
```bash
|
||||||
|
cat > ~/Library/LaunchAgents/com.mem0.dream.plist << 'PLIST'
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key><string>com.mem0.dream</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>claude</string>
|
||||||
|
<string>-p</string>
|
||||||
|
<string>/mem0:dream --auto</string>
|
||||||
|
<string>--allowedTools</string>
|
||||||
|
<string>mcp__mem0__*</string>
|
||||||
|
</array>
|
||||||
|
<key>StartCalendarInterval</key>
|
||||||
|
<dict>
|
||||||
|
<key>Weekday</key><integer>0</integer>
|
||||||
|
<key>Hour</key><integer>3</integer>
|
||||||
|
<key>Minute</key><integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>StandardOutPath</key><string>/tmp/mem0-dream.log</string>
|
||||||
|
<key>StandardErrorPath</key><string>/tmp/mem0-dream.err</string>
|
||||||
|
<key>WorkingDirectory</key><string>PROJECT_DIR</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
PLIST
|
||||||
|
launchctl load ~/Library/LaunchAgents/com.mem0.dream.plist
|
||||||
|
```
|
||||||
|
Replace `PROJECT_DIR` with the actual project path.
|
||||||
|
|
||||||
|
2. **Linux cron** — add entry:
|
||||||
|
```bash
|
||||||
|
(crontab -l 2>/dev/null; echo "0 3 * * 0 cd PROJECT_DIR && claude -p '/mem0:dream --auto' >> /tmp/mem0-dream.log 2>&1") | crontab -
|
||||||
|
```
|
||||||
|
|
||||||
|
Print which method was used and how to verify:
|
||||||
|
```
|
||||||
|
Dream scheduled (local: launchd/cron): weekly Sundays 3am
|
||||||
|
Verify: launchctl list | grep mem0 # macOS
|
||||||
|
crontab -l | grep mem0 # Linux
|
||||||
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user