@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.
Files changed (97) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +45 -0
  3. package/cursor-hooks/README.md +119 -0
  4. package/cursor-hooks/context-inject.sh +118 -0
  5. package/cursor-hooks/hooks.json +39 -0
  6. package/cursor-hooks/save-file-edit.sh +130 -0
  7. package/cursor-hooks/save-observation.sh +116 -0
  8. package/cursor-hooks/save-shell-execution.sh +121 -0
  9. package/cursor-hooks/session-summary.sh +142 -0
  10. package/dist/capture.d.ts +111 -0
  11. package/dist/capture.d.ts.map +1 -0
  12. package/dist/capture.integration.d.ts +2 -0
  13. package/dist/capture.integration.d.ts.map +1 -0
  14. package/dist/capture.integration.js +67 -0
  15. package/dist/capture.integration.js.map +1 -0
  16. package/dist/capture.js +264 -0
  17. package/dist/capture.js.map +1 -0
  18. package/dist/client-summarizer.d.ts +59 -0
  19. package/dist/client-summarizer.d.ts.map +1 -0
  20. package/dist/client-summarizer.js +211 -0
  21. package/dist/client-summarizer.js.map +1 -0
  22. package/dist/client-summarizer.test.d.ts +2 -0
  23. package/dist/client-summarizer.test.d.ts.map +1 -0
  24. package/dist/client-summarizer.test.js +127 -0
  25. package/dist/client-summarizer.test.js.map +1 -0
  26. package/dist/config.d.ts +13 -0
  27. package/dist/config.d.ts.map +1 -0
  28. package/dist/config.js +131 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/config.test.d.ts +2 -0
  31. package/dist/config.test.d.ts.map +1 -0
  32. package/dist/config.test.js +182 -0
  33. package/dist/config.test.js.map +1 -0
  34. package/dist/cursor-hooks.d.ts +46 -0
  35. package/dist/cursor-hooks.d.ts.map +1 -0
  36. package/dist/cursor-hooks.js +251 -0
  37. package/dist/cursor-hooks.js.map +1 -0
  38. package/dist/cursor-rules.d.ts +42 -0
  39. package/dist/cursor-rules.d.ts.map +1 -0
  40. package/dist/cursor-rules.js +229 -0
  41. package/dist/cursor-rules.js.map +1 -0
  42. package/dist/cursor-rules.test.d.ts +2 -0
  43. package/dist/cursor-rules.test.d.ts.map +1 -0
  44. package/dist/cursor-rules.test.js +55 -0
  45. package/dist/cursor-rules.test.js.map +1 -0
  46. package/dist/dedup.d.ts +22 -0
  47. package/dist/dedup.d.ts.map +1 -0
  48. package/dist/dedup.js +60 -0
  49. package/dist/dedup.js.map +1 -0
  50. package/dist/dedup.test.d.ts +2 -0
  51. package/dist/dedup.test.d.ts.map +1 -0
  52. package/dist/dedup.test.js +83 -0
  53. package/dist/dedup.test.js.map +1 -0
  54. package/dist/hooks/index.d.ts +52 -0
  55. package/dist/hooks/index.d.ts.map +1 -0
  56. package/dist/hooks/index.js +136 -0
  57. package/dist/hooks/index.js.map +1 -0
  58. package/dist/hooks.test.d.ts +2 -0
  59. package/dist/hooks.test.d.ts.map +1 -0
  60. package/dist/hooks.test.js +94 -0
  61. package/dist/hooks.test.js.map +1 -0
  62. package/dist/index.d.ts +9 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +9 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/strip-private.d.ts +12 -0
  67. package/dist/strip-private.d.ts.map +1 -0
  68. package/dist/strip-private.js +28 -0
  69. package/dist/strip-private.js.map +1 -0
  70. package/dist/strip-private.test.d.ts +2 -0
  71. package/dist/strip-private.test.d.ts.map +1 -0
  72. package/dist/strip-private.test.js +37 -0
  73. package/dist/strip-private.test.js.map +1 -0
  74. package/dist/utils.d.ts +3 -0
  75. package/dist/utils.d.ts.map +1 -0
  76. package/dist/utils.js +11 -0
  77. package/dist/utils.js.map +1 -0
  78. package/package.json +28 -0
  79. package/src/capture.integration.ts +98 -0
  80. package/src/capture.ts +352 -0
  81. package/src/client-summarizer.test.ts +144 -0
  82. package/src/client-summarizer.ts +338 -0
  83. package/src/config.test.ts +211 -0
  84. package/src/config.ts +157 -0
  85. package/src/cursor-hooks.ts +309 -0
  86. package/src/cursor-rules.test.ts +63 -0
  87. package/src/cursor-rules.ts +313 -0
  88. package/src/dedup.test.ts +84 -0
  89. package/src/dedup.ts +67 -0
  90. package/src/hooks/index.ts +226 -0
  91. package/src/hooks.test.ts +111 -0
  92. package/src/index.ts +32 -0
  93. package/src/strip-private.test.ts +57 -0
  94. package/src/strip-private.ts +34 -0
  95. package/src/utils.ts +10 -0
  96. package/tsconfig.json +12 -0
  97. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,4 @@
1
+
2
+ > @memoire-ai/collector@0.1.0 build /Users/dekai/Documents/esprit/Memoire/packages/collector
3
+ > tsc
4
+
@@ -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