@memoire-ai/collector 0.1.0
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.
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +45 -0
- package/cursor-hooks/README.md +119 -0
- package/cursor-hooks/context-inject.sh +118 -0
- package/cursor-hooks/hooks.json +39 -0
- package/cursor-hooks/save-file-edit.sh +130 -0
- package/cursor-hooks/save-observation.sh +116 -0
- package/cursor-hooks/save-shell-execution.sh +121 -0
- package/cursor-hooks/session-summary.sh +142 -0
- package/dist/capture.d.ts +111 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.integration.d.ts +2 -0
- package/dist/capture.integration.d.ts.map +1 -0
- package/dist/capture.integration.js +67 -0
- package/dist/capture.integration.js.map +1 -0
- package/dist/capture.js +264 -0
- package/dist/capture.js.map +1 -0
- package/dist/client-summarizer.d.ts +59 -0
- package/dist/client-summarizer.d.ts.map +1 -0
- package/dist/client-summarizer.js +211 -0
- package/dist/client-summarizer.js.map +1 -0
- package/dist/client-summarizer.test.d.ts +2 -0
- package/dist/client-summarizer.test.d.ts.map +1 -0
- package/dist/client-summarizer.test.js +127 -0
- package/dist/client-summarizer.test.js.map +1 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +131 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +182 -0
- package/dist/config.test.js.map +1 -0
- package/dist/cursor-hooks.d.ts +46 -0
- package/dist/cursor-hooks.d.ts.map +1 -0
- package/dist/cursor-hooks.js +251 -0
- package/dist/cursor-hooks.js.map +1 -0
- package/dist/cursor-rules.d.ts +42 -0
- package/dist/cursor-rules.d.ts.map +1 -0
- package/dist/cursor-rules.js +229 -0
- package/dist/cursor-rules.js.map +1 -0
- package/dist/cursor-rules.test.d.ts +2 -0
- package/dist/cursor-rules.test.d.ts.map +1 -0
- package/dist/cursor-rules.test.js +55 -0
- package/dist/cursor-rules.test.js.map +1 -0
- package/dist/dedup.d.ts +22 -0
- package/dist/dedup.d.ts.map +1 -0
- package/dist/dedup.js +60 -0
- package/dist/dedup.js.map +1 -0
- package/dist/dedup.test.d.ts +2 -0
- package/dist/dedup.test.d.ts.map +1 -0
- package/dist/dedup.test.js +83 -0
- package/dist/dedup.test.js.map +1 -0
- package/dist/hooks/index.d.ts +52 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +136 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks.test.d.ts +2 -0
- package/dist/hooks.test.d.ts.map +1 -0
- package/dist/hooks.test.js +94 -0
- package/dist/hooks.test.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/strip-private.d.ts +12 -0
- package/dist/strip-private.d.ts.map +1 -0
- package/dist/strip-private.js +28 -0
- package/dist/strip-private.js.map +1 -0
- package/dist/strip-private.test.d.ts +2 -0
- package/dist/strip-private.test.d.ts.map +1 -0
- package/dist/strip-private.test.js +37 -0
- package/dist/strip-private.test.js.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +11 -0
- package/dist/utils.js.map +1 -0
- package/package.json +28 -0
- package/src/capture.integration.ts +98 -0
- package/src/capture.ts +352 -0
- package/src/client-summarizer.test.ts +144 -0
- package/src/client-summarizer.ts +338 -0
- package/src/config.test.ts +211 -0
- package/src/config.ts +157 -0
- package/src/cursor-hooks.ts +309 -0
- package/src/cursor-rules.test.ts +63 -0
- package/src/cursor-rules.ts +313 -0
- package/src/dedup.test.ts +84 -0
- package/src/dedup.ts +67 -0
- package/src/hooks/index.ts +226 -0
- package/src/hooks.test.ts +111 -0
- package/src/index.ts +32 -0
- package/src/strip-private.test.ts +57 -0
- package/src/strip-private.ts +34 -0
- package/src/utils.ts +10 -0
- package/tsconfig.json +12 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
> @memoire/collector@0.1.0 test /Users/dekai/Documents/esprit/Memoire/packages/collector
|
|
3
|
+
> node --test dist/*.test.js
|
|
4
|
+
|
|
5
|
+
✔ ClientSummarizer parses Anthropic responses (32.023959ms)
|
|
6
|
+
✔ ClientSummarizer parses native Claude JSON output (0.262209ms)
|
|
7
|
+
✔ ClientSummarizer parses native Codex JSONL output (0.177791ms)
|
|
8
|
+
✔ ClientSummarizer returns null on command failure (0.117208ms)
|
|
9
|
+
✔ ClientSummarizer truncates summary to 180 chars (0.123583ms)
|
|
10
|
+
✔ ClientSummarizer limits concepts to 12 (0.120959ms)
|
|
11
|
+
✔ ClientSummarizer handles session_summary details (0.110875ms)
|
|
12
|
+
✔ readMemoireConfig loads the shared config file (0.912834ms)
|
|
13
|
+
✔ createCollectorFromConfig instantiates a collector from the shared config (1.790458ms)
|
|
14
|
+
✔ createCollectorFromConfig enables client-side summarization from config (0.491209ms)
|
|
15
|
+
✔ createCollectorFromConfig does not enable summarizer without anthropic key (6.794375ms)
|
|
16
|
+
✔ createCollectorFromConfig uses native Claude mode when requested and CLI is available (0.768958ms)
|
|
17
|
+
✔ createCollectorFromConfig uses native Codex mode when requested and CLI is available (0.618792ms)
|
|
18
|
+
✔ buildCursorRulesContent compacts oversized resume content into the token budget (3.015416ms)
|
|
19
|
+
✔ buildCursorRulesContent keeps structured cursor rules bounded too (0.223375ms)
|
|
20
|
+
✔ ContentDedup allows first occurrence (0.450042ms)
|
|
21
|
+
✔ ContentDedup blocks duplicate within window (0.075958ms)
|
|
22
|
+
✔ ContentDedup allows different content (0.059166ms)
|
|
23
|
+
✔ ContentDedup allows content after window expires (84.420875ms)
|
|
24
|
+
✔ ContentDedup disabled when windowMs is 0 (0.141833ms)
|
|
25
|
+
✔ ContentDedup distinguishes same content with different keys (0.0685ms)
|
|
26
|
+
✔ ContentDedup.destroy cleans up (0.068042ms)
|
|
27
|
+
✔ hooks send user prompts as prompt events with request framing (0.740208ms)
|
|
28
|
+
✔ hooks classify decisions, attempts, conventions, and branch events (0.918125ms)
|
|
29
|
+
✔ inferToolUseEventType respects hints and failure signals (0.165041ms)
|
|
30
|
+
✔ stripPrivateTags removes single private block (4.070708ms)
|
|
31
|
+
✔ stripPrivateTags removes multiple private blocks (0.078666ms)
|
|
32
|
+
✔ stripPrivateTags handles multiline private content (0.046791ms)
|
|
33
|
+
✔ stripPrivateTags is case-insensitive (0.04375ms)
|
|
34
|
+
✔ stripPrivateTags returns content unchanged when no tags present (0.041875ms)
|
|
35
|
+
✔ stripPrivateTags returns empty string for fully private content (0.221834ms)
|
|
36
|
+
✔ stripPrivateFromOptions strips strings and string arrays (1.733667ms)
|
|
37
|
+
✔ stripPrivateFromOptions returns undefined for undefined input (0.047375ms)
|
|
38
|
+
ℹ tests 33
|
|
39
|
+
ℹ suites 0
|
|
40
|
+
ℹ pass 33
|
|
41
|
+
ℹ fail 0
|
|
42
|
+
ℹ cancelled 0
|
|
43
|
+
ℹ skipped 0
|
|
44
|
+
ℹ todo 0
|
|
45
|
+
ℹ duration_ms 167.269875
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Memoire Cursor Hooks
|
|
2
|
+
|
|
3
|
+
Automatic context injection for [Cursor IDE](https://cursor.com) using Cursor's hooks system.
|
|
4
|
+
|
|
5
|
+
This implements the **3-point injection** approach (inspired by claude-mem):
|
|
6
|
+
|
|
7
|
+
| Hook | When | What it does |
|
|
8
|
+
|------|------|-------------|
|
|
9
|
+
| `beforeSubmitPrompt` | Before every prompt | Fetches latest project context from Memoire and writes `.cursor/rules/memoire-context.mdc` so Cursor auto-includes it |
|
|
10
|
+
| `stop` | Session ends | Sends a session summary event to Memoire and refreshes the context file |
|
|
11
|
+
| `afterMCPExecution` | After MCP tool calls | Records tool usage as observations in Memoire |
|
|
12
|
+
|
|
13
|
+
## Quick Setup
|
|
14
|
+
|
|
15
|
+
### Option A: Programmatic (recommended)
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { installCursorHooks } from '@memoire/collector';
|
|
19
|
+
|
|
20
|
+
installCursorHooks('/path/to/project', 'http://127.0.0.1:3100', 'your-api-key');
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Option B: Manual
|
|
24
|
+
|
|
25
|
+
1. **Copy hooks config** to your project root:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
cp hooks.json <project-root>/.cursor/hooks.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
2. **Copy hook scripts** to your project:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
mkdir -p <project-root>/.cursor/hooks
|
|
35
|
+
cp context-inject.sh session-summary.sh save-observation.sh <project-root>/.cursor/hooks/
|
|
36
|
+
chmod +x <project-root>/.cursor/hooks/*.sh
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
3. **Configure credentials** — create `~/.memoire/config`:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
MEMOIRE_API_URL=http://127.0.0.1:3100
|
|
43
|
+
MEMOIRE_API_KEY=your-api-key
|
|
44
|
+
MEMOIRE_ORG_ID=your-org-id
|
|
45
|
+
MEMOIRE_PROJECT_ID=your-project-id
|
|
46
|
+
MEMOIRE_USER_ID=your-user-id
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
4. **Restart Cursor** to pick up the hooks.
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
Scripts read configuration from environment variables first, then fall back to `~/.memoire/config`.
|
|
54
|
+
|
|
55
|
+
| Variable | Description | Default |
|
|
56
|
+
|----------|-------------|---------|
|
|
57
|
+
| `MEMOIRE_API_URL` | Memoire API base URL | `http://127.0.0.1:3100` |
|
|
58
|
+
| `MEMOIRE_API_KEY` | API key for authentication | — |
|
|
59
|
+
| `MEMOIRE_ORG_ID` | Organization ID | — |
|
|
60
|
+
| `MEMOIRE_PROJECT_ID` | Project ID | — |
|
|
61
|
+
| `MEMOIRE_USER_ID` | User ID | — |
|
|
62
|
+
| `MEMOIRE_SESSION_ID` | Session ID (auto-generated) | `cursor-<timestamp>-<pid>` |
|
|
63
|
+
| `MEMOIRE_CLIENT` | Client identifier | `cursor` |
|
|
64
|
+
| `MEMOIRE_CONFIG_FILE` | Path to config file | `~/.memoire/config` |
|
|
65
|
+
|
|
66
|
+
## Config File Format
|
|
67
|
+
|
|
68
|
+
The config file uses simple `KEY=VALUE` format (one per line):
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
# Memoire configuration
|
|
72
|
+
MEMOIRE_API_URL=http://127.0.0.1:3100
|
|
73
|
+
MEMOIRE_API_KEY=mem_abc123
|
|
74
|
+
MEMOIRE_ORG_ID=550e8400-e29b-41d4-a716-446655440000
|
|
75
|
+
MEMOIRE_PROJECT_ID=6ba7b810-9dad-11d1-80b4-00c04fd430c8
|
|
76
|
+
MEMOIRE_USER_ID=7c9e6679-7425-40de-944b-e07fc1f90ae7
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Values may optionally be quoted with single or double quotes. Comments start with `#`.
|
|
80
|
+
|
|
81
|
+
## Design Principles
|
|
82
|
+
|
|
83
|
+
- **Idempotent** — safe to run multiple times; uses atomic file writes
|
|
84
|
+
- **Fail-silent** — all scripts exit 0 on any error; never blocks the IDE
|
|
85
|
+
- **Fast** — 3-second timeout on all API calls
|
|
86
|
+
- **POSIX sh** — no bash-isms; works on macOS, Linux, WSL
|
|
87
|
+
- **No dependencies** — only uses `curl`, `sed`, `cat`, `mkdir`, `mv`
|
|
88
|
+
|
|
89
|
+
## How It Works
|
|
90
|
+
|
|
91
|
+
### beforeSubmitPrompt (context-inject.sh)
|
|
92
|
+
|
|
93
|
+
1. Reads config from env / `~/.memoire/config`
|
|
94
|
+
2. Calls `POST /v1/context/assemble` with profile + facts + recent activity
|
|
95
|
+
3. Writes the assembled context to `.cursor/rules/memoire-context.mdc`
|
|
96
|
+
4. Cursor's `alwaysApply: true` frontmatter auto-includes it in every prompt
|
|
97
|
+
|
|
98
|
+
### stop (session-summary.sh)
|
|
99
|
+
|
|
100
|
+
1. Sends a `session_summary` event to `POST /v1/events`
|
|
101
|
+
2. Fetches the latest dashboard summary from `POST /v1/dashboard/summary`
|
|
102
|
+
3. Updates `.cursor/rules/memoire-context.mdc` with the summary
|
|
103
|
+
|
|
104
|
+
### afterMCPExecution (save-observation.sh)
|
|
105
|
+
|
|
106
|
+
1. Receives tool name and status as arguments
|
|
107
|
+
2. Classifies as `observation` (success) or `attempt` (error)
|
|
108
|
+
3. Sends event to `POST /v1/events` with MCP tool metadata
|
|
109
|
+
|
|
110
|
+
## .gitignore
|
|
111
|
+
|
|
112
|
+
Add to your `.gitignore`:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
# Memoire auto-generated context (per-user, not committed)
|
|
116
|
+
.cursor/rules/memoire-context.mdc
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The hook scripts themselves (`.cursor/hooks/`) _can_ be committed so the whole team benefits.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Memoire context injection hook for Cursor IDE
|
|
3
|
+
# Called via beforeSubmitPrompt — fetches latest project context
|
|
4
|
+
# and writes .cursor/rules/memoire-context.mdc so Cursor auto-includes it.
|
|
5
|
+
#
|
|
6
|
+
# Usage: sh context-inject.sh ["prompt text"]
|
|
7
|
+
#
|
|
8
|
+
# Environment:
|
|
9
|
+
# MEMOIRE_API_URL - API base URL (default: http://127.0.0.1:3100)
|
|
10
|
+
# MEMOIRE_API_KEY - API key for authentication
|
|
11
|
+
# MEMOIRE_ORG_ID - Organization ID
|
|
12
|
+
# MEMOIRE_PROJECT_ID - Project ID
|
|
13
|
+
# MEMOIRE_USER_ID - User ID
|
|
14
|
+
#
|
|
15
|
+
# Falls back to ~/.memoire/config for any unset values.
|
|
16
|
+
# Fails silently — never blocks the IDE.
|
|
17
|
+
|
|
18
|
+
set -e
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# Config loading
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
CONFIG_FILE="${MEMOIRE_CONFIG_FILE:-$HOME/.memoire/config}"
|
|
25
|
+
|
|
26
|
+
load_config() {
|
|
27
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
28
|
+
while IFS='=' read -r key value; do
|
|
29
|
+
# skip comments and blank lines
|
|
30
|
+
case "$key" in
|
|
31
|
+
''|\#*) continue ;;
|
|
32
|
+
esac
|
|
33
|
+
# strip surrounding whitespace / quotes
|
|
34
|
+
value=$(printf '%s' "$value" | sed "s/^[[:space:]]*[\"']\\{0,1\\}//;s/[\"']\\{0,1\\}[[:space:]]*$//")
|
|
35
|
+
case "$key" in
|
|
36
|
+
MEMOIRE_API_URL) [ -z "$MEMOIRE_API_URL" ] && MEMOIRE_API_URL="$value" ;;
|
|
37
|
+
MEMOIRE_API_KEY) [ -z "$MEMOIRE_API_KEY" ] && MEMOIRE_API_KEY="$value" ;;
|
|
38
|
+
MEMOIRE_ORG_ID) [ -z "$MEMOIRE_ORG_ID" ] && MEMOIRE_ORG_ID="$value" ;;
|
|
39
|
+
MEMOIRE_PROJECT_ID) [ -z "$MEMOIRE_PROJECT_ID" ] && MEMOIRE_PROJECT_ID="$value" ;;
|
|
40
|
+
MEMOIRE_USER_ID) [ -z "$MEMOIRE_USER_ID" ] && MEMOIRE_USER_ID="$value" ;;
|
|
41
|
+
esac
|
|
42
|
+
done < "$CONFIG_FILE"
|
|
43
|
+
fi
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
load_config
|
|
47
|
+
|
|
48
|
+
API_URL="${MEMOIRE_API_URL:-http://127.0.0.1:3100}"
|
|
49
|
+
API_KEY="${MEMOIRE_API_KEY:-}"
|
|
50
|
+
ORG_ID="${MEMOIRE_ORG_ID:-}"
|
|
51
|
+
PROJECT_ID="${MEMOIRE_PROJECT_ID:-}"
|
|
52
|
+
USER_ID="${MEMOIRE_USER_ID:-}"
|
|
53
|
+
|
|
54
|
+
# Bail if required IDs are missing
|
|
55
|
+
if [ -z "$ORG_ID" ] || [ -z "$PROJECT_ID" ] || [ -z "$USER_ID" ]; then
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Fetch assembled context from Memoire API
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
AUTH_HEADER=""
|
|
64
|
+
if [ -n "$API_KEY" ]; then
|
|
65
|
+
AUTH_HEADER="Authorization: Bearer ${API_KEY}"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
RESPONSE=$(curl -sf --max-time 3 \
|
|
69
|
+
-X POST \
|
|
70
|
+
-H "Content-Type: application/json" \
|
|
71
|
+
${AUTH_HEADER:+-H "$AUTH_HEADER"} \
|
|
72
|
+
-d "{
|
|
73
|
+
\"org_id\": \"${ORG_ID}\",
|
|
74
|
+
\"project_id\": \"${PROJECT_ID}\",
|
|
75
|
+
\"viewer_user_id\": \"${USER_ID}\",
|
|
76
|
+
\"sections\": [\"profile\", \"facts\", \"recent_activity\"],
|
|
77
|
+
\"max_tokens\": 2000
|
|
78
|
+
}" \
|
|
79
|
+
"${API_URL}/v1/context/assemble" 2>/dev/null) || exit 0
|
|
80
|
+
|
|
81
|
+
# Extract context_block from JSON response (lightweight — no jq dependency)
|
|
82
|
+
# The assemble endpoint returns { context_block: "..." }
|
|
83
|
+
CONTEXT_BLOCK=$(printf '%s' "$RESPONSE" | sed -n 's/.*"context_block"[[:space:]]*:[[:space:]]*"//p' | sed 's/".*//' | sed 's/\\n/\
|
|
84
|
+
/g; s/\\t/\t/g; s/\\"/"/g; s/\\\\/\\/g')
|
|
85
|
+
|
|
86
|
+
if [ -z "$CONTEXT_BLOCK" ]; then
|
|
87
|
+
exit 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# Write .cursor/rules/memoire-context.mdc
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
RULES_DIR=".cursor/rules"
|
|
95
|
+
RULES_FILE="${RULES_DIR}/memoire-context.mdc"
|
|
96
|
+
TEMP_FILE="${RULES_FILE}.tmp"
|
|
97
|
+
|
|
98
|
+
mkdir -p "$RULES_DIR"
|
|
99
|
+
|
|
100
|
+
# Build the MDC file with frontmatter
|
|
101
|
+
cat > "$TEMP_FILE" << 'FRONTMATTER'
|
|
102
|
+
---
|
|
103
|
+
alwaysApply: true
|
|
104
|
+
description: "Memoire shared memory context (auto-updated by hook)"
|
|
105
|
+
---
|
|
106
|
+
FRONTMATTER
|
|
107
|
+
|
|
108
|
+
printf '\n# Memoire Context\n\n' >> "$TEMP_FILE"
|
|
109
|
+
printf '> Auto-injected by Memoire beforeSubmitPrompt hook.\n' >> "$TEMP_FILE"
|
|
110
|
+
printf '> Use Memoire MCP tools for deeper queries.\n\n' >> "$TEMP_FILE"
|
|
111
|
+
printf '%s\n' "$CONTEXT_BLOCK" >> "$TEMP_FILE"
|
|
112
|
+
printf '\n---\n' >> "$TEMP_FILE"
|
|
113
|
+
printf '*Use Memoire MCP tools (`search_context`, `project_profile`, `oracle_research`) for detailed queries.*\n' >> "$TEMP_FILE"
|
|
114
|
+
|
|
115
|
+
# Atomic write
|
|
116
|
+
mv "$TEMP_FILE" "$RULES_FILE"
|
|
117
|
+
|
|
118
|
+
exit 0
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"beforeSubmitPrompt": [
|
|
4
|
+
{
|
|
5
|
+
"command": "sh .cursor/hooks/context-inject.sh \"$PROMPT\"",
|
|
6
|
+
"description": "Inject Memoire project context into Cursor rules",
|
|
7
|
+
"blocking": true
|
|
8
|
+
}
|
|
9
|
+
],
|
|
10
|
+
"stop": [
|
|
11
|
+
{
|
|
12
|
+
"command": "sh .cursor/hooks/session-summary.sh",
|
|
13
|
+
"description": "Generate Memoire session summary",
|
|
14
|
+
"blocking": false
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"afterMCPExecution": [
|
|
18
|
+
{
|
|
19
|
+
"command": "sh .cursor/hooks/save-observation.sh \"$TOOL_NAME\" \"$TOOL_STATUS\"",
|
|
20
|
+
"description": "Track MCP tool calls in Memoire",
|
|
21
|
+
"blocking": false
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"afterShellExecution": [
|
|
25
|
+
{
|
|
26
|
+
"command": "sh .cursor/hooks/save-shell-execution.sh \"$COMMAND\" \"$EXIT_CODE\"",
|
|
27
|
+
"description": "Track shell commands in Memoire",
|
|
28
|
+
"blocking": false
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"afterFileEdit": [
|
|
32
|
+
{
|
|
33
|
+
"command": "sh .cursor/hooks/save-file-edit.sh \"$FILE_PATH\"",
|
|
34
|
+
"description": "Track file edits in Memoire",
|
|
35
|
+
"blocking": false
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Memoire file edit hook for Cursor IDE
|
|
3
|
+
# Called via afterFileEdit — records file modifications as observation events
|
|
4
|
+
# so Memoire can track which files the developer is working on.
|
|
5
|
+
#
|
|
6
|
+
# Usage: sh save-file-edit.sh ["file_path"]
|
|
7
|
+
#
|
|
8
|
+
# Arguments:
|
|
9
|
+
# $1 - Path of the file that was edited
|
|
10
|
+
#
|
|
11
|
+
# Environment:
|
|
12
|
+
# MEMOIRE_API_URL - API base URL (default: http://127.0.0.1:3100)
|
|
13
|
+
# MEMOIRE_API_KEY - API key for authentication
|
|
14
|
+
# MEMOIRE_ORG_ID - Organization ID
|
|
15
|
+
# MEMOIRE_PROJECT_ID - Project ID
|
|
16
|
+
# MEMOIRE_USER_ID - User ID
|
|
17
|
+
# MEMOIRE_SESSION_ID - Session ID (auto-generated if unset)
|
|
18
|
+
#
|
|
19
|
+
# Falls back to ~/.memoire/config for any unset values.
|
|
20
|
+
# Fails silently — never blocks the IDE.
|
|
21
|
+
|
|
22
|
+
set -e
|
|
23
|
+
|
|
24
|
+
FILE_PATH="${1:-unknown}"
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Config loading
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
CONFIG_FILE="${MEMOIRE_CONFIG_FILE:-$HOME/.memoire/config}"
|
|
31
|
+
|
|
32
|
+
load_config() {
|
|
33
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
34
|
+
while IFS='=' read -r key value; do
|
|
35
|
+
case "$key" in
|
|
36
|
+
''|\#*) continue ;;
|
|
37
|
+
esac
|
|
38
|
+
value=$(printf '%s' "$value" | sed "s/^[[:space:]]*[\"']\\{0,1\\}//;s/[\"']\\{0,1\\}[[:space:]]*$//")
|
|
39
|
+
case "$key" in
|
|
40
|
+
MEMOIRE_API_URL) [ -z "$MEMOIRE_API_URL" ] && MEMOIRE_API_URL="$value" ;;
|
|
41
|
+
MEMOIRE_API_KEY) [ -z "$MEMOIRE_API_KEY" ] && MEMOIRE_API_KEY="$value" ;;
|
|
42
|
+
MEMOIRE_ORG_ID) [ -z "$MEMOIRE_ORG_ID" ] && MEMOIRE_ORG_ID="$value" ;;
|
|
43
|
+
MEMOIRE_PROJECT_ID) [ -z "$MEMOIRE_PROJECT_ID" ] && MEMOIRE_PROJECT_ID="$value" ;;
|
|
44
|
+
MEMOIRE_USER_ID) [ -z "$MEMOIRE_USER_ID" ] && MEMOIRE_USER_ID="$value" ;;
|
|
45
|
+
MEMOIRE_SESSION_ID) [ -z "$MEMOIRE_SESSION_ID" ] && MEMOIRE_SESSION_ID="$value" ;;
|
|
46
|
+
MEMOIRE_CLIENT) [ -z "$MEMOIRE_CLIENT" ] && MEMOIRE_CLIENT="$value" ;;
|
|
47
|
+
esac
|
|
48
|
+
done < "$CONFIG_FILE"
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
load_config
|
|
53
|
+
|
|
54
|
+
API_URL="${MEMOIRE_API_URL:-http://127.0.0.1:3100}"
|
|
55
|
+
API_KEY="${MEMOIRE_API_KEY:-}"
|
|
56
|
+
ORG_ID="${MEMOIRE_ORG_ID:-}"
|
|
57
|
+
PROJECT_ID="${MEMOIRE_PROJECT_ID:-}"
|
|
58
|
+
USER_ID="${MEMOIRE_USER_ID:-}"
|
|
59
|
+
SESSION_ID="${MEMOIRE_SESSION_ID:-cursor-$(date +%s)-$$}"
|
|
60
|
+
CLIENT="${MEMOIRE_CLIENT:-cursor}"
|
|
61
|
+
|
|
62
|
+
if [ -z "$ORG_ID" ] || [ -z "$PROJECT_ID" ] || [ -z "$USER_ID" ]; then
|
|
63
|
+
exit 0
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# Extract file metadata for richer concepts
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
FILE_EXT=$(printf '%s' "$FILE_PATH" | sed 's/.*\.//' | tr '[:upper:]' '[:lower:]')
|
|
71
|
+
FILE_NAME=$(basename "$FILE_PATH")
|
|
72
|
+
|
|
73
|
+
# Map extension to language concept
|
|
74
|
+
LANG_CONCEPT="file"
|
|
75
|
+
case "$FILE_EXT" in
|
|
76
|
+
ts|tsx) LANG_CONCEPT="typescript" ;;
|
|
77
|
+
js|jsx) LANG_CONCEPT="javascript" ;;
|
|
78
|
+
py) LANG_CONCEPT="python" ;;
|
|
79
|
+
rs) LANG_CONCEPT="rust" ;;
|
|
80
|
+
go) LANG_CONCEPT="go" ;;
|
|
81
|
+
java) LANG_CONCEPT="java" ;;
|
|
82
|
+
rb) LANG_CONCEPT="ruby" ;;
|
|
83
|
+
css|scss) LANG_CONCEPT="css" ;;
|
|
84
|
+
html) LANG_CONCEPT="html" ;;
|
|
85
|
+
md) LANG_CONCEPT="markdown" ;;
|
|
86
|
+
json) LANG_CONCEPT="config" ;;
|
|
87
|
+
yaml|yml) LANG_CONCEPT="config" ;;
|
|
88
|
+
toml) LANG_CONCEPT="config" ;;
|
|
89
|
+
esac
|
|
90
|
+
|
|
91
|
+
CONTENT="File edited: ${FILE_PATH}"
|
|
92
|
+
EVENT_TYPE="observation"
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# Send event to Memoire API
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
AUTH_HEADER=""
|
|
99
|
+
if [ -n "$API_KEY" ]; then
|
|
100
|
+
AUTH_HEADER="Authorization: Bearer ${API_KEY}"
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
104
|
+
IDEMPOTENCY_KEY="edit-${FILE_NAME}-${SESSION_ID}-$(date +%s)"
|
|
105
|
+
|
|
106
|
+
SAFE_PATH=$(printf '%s' "$FILE_PATH" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
107
|
+
|
|
108
|
+
curl -sf --max-time 3 \
|
|
109
|
+
-X POST \
|
|
110
|
+
-H "Content-Type: application/json" \
|
|
111
|
+
${AUTH_HEADER:+-H "$AUTH_HEADER"} \
|
|
112
|
+
-d "{
|
|
113
|
+
\"events\": [{
|
|
114
|
+
\"org_id\": \"${ORG_ID}\",
|
|
115
|
+
\"project_id\": \"${PROJECT_ID}\",
|
|
116
|
+
\"user_id\": \"${USER_ID}\",
|
|
117
|
+
\"session_id\": \"${SESSION_ID}\",
|
|
118
|
+
\"client\": \"${CLIENT}\",
|
|
119
|
+
\"event_type\": \"${EVENT_TYPE}\",
|
|
120
|
+
\"content\": \"${CONTENT}\",
|
|
121
|
+
\"files_modified\": [\"${SAFE_PATH}\"],
|
|
122
|
+
\"concepts\": [\"file_edit\", \"${LANG_CONCEPT}\"],
|
|
123
|
+
\"created_at\": \"${TIMESTAMP}\",
|
|
124
|
+
\"idempotency_key\": \"${IDEMPOTENCY_KEY}\",
|
|
125
|
+
\"private\": false
|
|
126
|
+
}]
|
|
127
|
+
}" \
|
|
128
|
+
"${API_URL}/v1/events" >/dev/null 2>&1 || true
|
|
129
|
+
|
|
130
|
+
exit 0
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Memoire MCP observation hook for Cursor IDE
|
|
3
|
+
# Called via afterMCPExecution — records tool usage as an observation event
|
|
4
|
+
# so Memoire can build a richer picture of the development session.
|
|
5
|
+
#
|
|
6
|
+
# Usage: sh save-observation.sh ["tool_name"] ["status"]
|
|
7
|
+
#
|
|
8
|
+
# Arguments:
|
|
9
|
+
# $1 - MCP tool name (e.g. "search_context", "read_file")
|
|
10
|
+
# $2 - Execution status ("success" or "error")
|
|
11
|
+
#
|
|
12
|
+
# Environment:
|
|
13
|
+
# MEMOIRE_API_URL - API base URL (default: http://127.0.0.1:3100)
|
|
14
|
+
# MEMOIRE_API_KEY - API key for authentication
|
|
15
|
+
# MEMOIRE_ORG_ID - Organization ID
|
|
16
|
+
# MEMOIRE_PROJECT_ID - Project ID
|
|
17
|
+
# MEMOIRE_USER_ID - User ID
|
|
18
|
+
# MEMOIRE_SESSION_ID - Session ID (auto-generated if unset)
|
|
19
|
+
#
|
|
20
|
+
# Falls back to ~/.memoire/config for any unset values.
|
|
21
|
+
# Fails silently — never blocks the IDE.
|
|
22
|
+
|
|
23
|
+
set -e
|
|
24
|
+
|
|
25
|
+
TOOL_NAME="${1:-unknown}"
|
|
26
|
+
TOOL_STATUS="${2:-success}"
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# Config loading
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
CONFIG_FILE="${MEMOIRE_CONFIG_FILE:-$HOME/.memoire/config}"
|
|
33
|
+
|
|
34
|
+
load_config() {
|
|
35
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
36
|
+
while IFS='=' read -r key value; do
|
|
37
|
+
case "$key" in
|
|
38
|
+
''|\#*) continue ;;
|
|
39
|
+
esac
|
|
40
|
+
value=$(printf '%s' "$value" | sed "s/^[[:space:]]*[\"']\\{0,1\\}//;s/[\"']\\{0,1\\}[[:space:]]*$//")
|
|
41
|
+
case "$key" in
|
|
42
|
+
MEMOIRE_API_URL) [ -z "$MEMOIRE_API_URL" ] && MEMOIRE_API_URL="$value" ;;
|
|
43
|
+
MEMOIRE_API_KEY) [ -z "$MEMOIRE_API_KEY" ] && MEMOIRE_API_KEY="$value" ;;
|
|
44
|
+
MEMOIRE_ORG_ID) [ -z "$MEMOIRE_ORG_ID" ] && MEMOIRE_ORG_ID="$value" ;;
|
|
45
|
+
MEMOIRE_PROJECT_ID) [ -z "$MEMOIRE_PROJECT_ID" ] && MEMOIRE_PROJECT_ID="$value" ;;
|
|
46
|
+
MEMOIRE_USER_ID) [ -z "$MEMOIRE_USER_ID" ] && MEMOIRE_USER_ID="$value" ;;
|
|
47
|
+
MEMOIRE_SESSION_ID) [ -z "$MEMOIRE_SESSION_ID" ] && MEMOIRE_SESSION_ID="$value" ;;
|
|
48
|
+
MEMOIRE_CLIENT) [ -z "$MEMOIRE_CLIENT" ] && MEMOIRE_CLIENT="$value" ;;
|
|
49
|
+
esac
|
|
50
|
+
done < "$CONFIG_FILE"
|
|
51
|
+
fi
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
load_config
|
|
55
|
+
|
|
56
|
+
API_URL="${MEMOIRE_API_URL:-http://127.0.0.1:3100}"
|
|
57
|
+
API_KEY="${MEMOIRE_API_KEY:-}"
|
|
58
|
+
ORG_ID="${MEMOIRE_ORG_ID:-}"
|
|
59
|
+
PROJECT_ID="${MEMOIRE_PROJECT_ID:-}"
|
|
60
|
+
USER_ID="${MEMOIRE_USER_ID:-}"
|
|
61
|
+
SESSION_ID="${MEMOIRE_SESSION_ID:-cursor-$(date +%s)-$$}"
|
|
62
|
+
CLIENT="${MEMOIRE_CLIENT:-cursor}"
|
|
63
|
+
|
|
64
|
+
if [ -z "$ORG_ID" ] || [ -z "$PROJECT_ID" ] || [ -z "$USER_ID" ]; then
|
|
65
|
+
exit 0
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# Determine event type based on tool status
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
if [ "$TOOL_STATUS" = "error" ]; then
|
|
73
|
+
EVENT_TYPE="attempt"
|
|
74
|
+
CONTENT="MCP tool ${TOOL_NAME} failed"
|
|
75
|
+
else
|
|
76
|
+
EVENT_TYPE="observation"
|
|
77
|
+
CONTENT="MCP tool ${TOOL_NAME} executed successfully"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# Send event to Memoire API
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
AUTH_HEADER=""
|
|
85
|
+
if [ -n "$API_KEY" ]; then
|
|
86
|
+
AUTH_HEADER="Authorization: Bearer ${API_KEY}"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
90
|
+
IDEMPOTENCY_KEY="mcp-${TOOL_NAME}-${SESSION_ID}-$(date +%s)"
|
|
91
|
+
|
|
92
|
+
# Escape tool name for safe JSON embedding
|
|
93
|
+
SAFE_TOOL_NAME=$(printf '%s' "$TOOL_NAME" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
94
|
+
|
|
95
|
+
curl -sf --max-time 3 \
|
|
96
|
+
-X POST \
|
|
97
|
+
-H "Content-Type: application/json" \
|
|
98
|
+
${AUTH_HEADER:+-H "$AUTH_HEADER"} \
|
|
99
|
+
-d "{
|
|
100
|
+
\"events\": [{
|
|
101
|
+
\"org_id\": \"${ORG_ID}\",
|
|
102
|
+
\"project_id\": \"${PROJECT_ID}\",
|
|
103
|
+
\"user_id\": \"${USER_ID}\",
|
|
104
|
+
\"session_id\": \"${SESSION_ID}\",
|
|
105
|
+
\"client\": \"${CLIENT}\",
|
|
106
|
+
\"event_type\": \"${EVENT_TYPE}\",
|
|
107
|
+
\"content\": \"${CONTENT}\",
|
|
108
|
+
\"concepts\": [\"mcp\", \"${SAFE_TOOL_NAME}\"],
|
|
109
|
+
\"created_at\": \"${TIMESTAMP}\",
|
|
110
|
+
\"idempotency_key\": \"${IDEMPOTENCY_KEY}\",
|
|
111
|
+
\"private\": false
|
|
112
|
+
}]
|
|
113
|
+
}" \
|
|
114
|
+
"${API_URL}/v1/events" >/dev/null 2>&1 || true
|
|
115
|
+
|
|
116
|
+
exit 0
|