@remnic/plugin-claude-code 1.0.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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "remnic",
3
+ "description": "Universal memory for AI agents — automatic recall, observation, and cross-agent knowledge sharing",
4
+ "version": "1.0.0",
5
+ "author": "Joshua Warren",
6
+ "homepage": "https://github.com/joshuaswarren/remnic",
7
+ "repository": "https://github.com/joshuaswarren/remnic"
8
+ }
package/.mcp.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "mcpServers": {
3
+ "remnic": {
4
+ "type": "http",
5
+ "url": "http://localhost:4318/mcp",
6
+ "headers": {
7
+ "Authorization": "Bearer {{REMNIC_TOKEN}}",
8
+ "X-Engram-Client-Id": "claude-code"
9
+ }
10
+ }
11
+ }
12
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Joshua Warren
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: engram:memory-review
3
+ description: Review and curate memory suggestions from Engram's review queue
4
+ ---
5
+
6
+ You are a memory review agent for Engram. Your job is to review memory suggestions
7
+ that have been queued for human review and help the user decide which to keep, edit,
8
+ or dismiss.
9
+
10
+ ## Workflow
11
+
12
+ 1. Call `engram_review_queue_list` to get pending review items
13
+ 2. For each item, present:
14
+ - The suggested memory content
15
+ - Its source (which conversation/tool produced it)
16
+ - Its category and confidence score
17
+ 3. Ask the user to approve, edit, or dismiss each item
18
+ 4. For approved items, call `engram_suggestion_submit` with the final content
19
+ 5. Summarize what was kept and what was dismissed
20
+
21
+ ## Guidelines
22
+
23
+ - Group similar suggestions together
24
+ - Flag potential duplicates with existing memories
25
+ - Suggest edits for vague or overly specific memories
26
+ - Prioritize actionable preferences and decisions over transient observations
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env bash
2
+ # Remnic PostToolUse hook for Claude Code.
3
+ # Observes file edits (Write/Edit/MultiEdit) by sending transcript
4
+ # delta to the observe endpoint. Runs in background, never blocks.
5
+
6
+ set -euo pipefail
7
+
8
+ ensure_migrated() {
9
+ if [ -f "${HOME}/.remnic/.migrated-from-engram" ]; then
10
+ return 0
11
+ fi
12
+ if [ ! -d "${HOME}/.engram" ] && [ ! -f "${HOME}/.config/engram/config.json" ]; then
13
+ return 0
14
+ fi
15
+ if command -v remnic >/dev/null 2>&1; then
16
+ remnic migrate >/dev/null 2>&1 || true
17
+ elif command -v engram >/dev/null 2>&1; then
18
+ engram migrate >/dev/null 2>&1 || true
19
+ fi
20
+ }
21
+
22
+ ensure_migrated
23
+
24
+ REMNIC_HOST="${REMNIC_HOST:-${ENGRAM_HOST:-127.0.0.1}}"
25
+ REMNIC_PORT="${REMNIC_PORT:-${ENGRAM_PORT:-4318}}"
26
+ REMNIC_URL="http://${REMNIC_HOST}:${REMNIC_PORT}/engram/v1/observe"
27
+
28
+ LOG="${HOME}/.remnic/logs/remnic-post-tool-observe.log"
29
+ mkdir -p "$(dirname "$LOG")"
30
+ log() { echo "$(date '+%F %T') [post-tool] $*" >> "$LOG"; }
31
+
32
+ # Read token
33
+ REMNIC_TOKEN=""
34
+ for TOKEN_FILE in "${HOME}/.remnic/tokens.json" "${HOME}/.engram/tokens.json"; do
35
+ [ ! -f "$TOKEN_FILE" ] && continue
36
+ REMNIC_TOKEN="$(node -e "
37
+ const fs = require('fs');
38
+ const tokenFile = process.argv[1];
39
+ const store = JSON.parse(fs.readFileSync(tokenFile, 'utf8'));
40
+ const tokens = store.tokens || [];
41
+ const cc = tokens.find(t => t.connector === 'claude-code');
42
+ const oc = tokens.find(t => t.connector === 'openclaw');
43
+ let tok = (cc && cc.token) || (oc && oc.token) || '';
44
+ if (!tok) { tok = store['claude-code'] || store['openclaw'] || ''; }
45
+ process.stdout.write(tok);
46
+ " "$TOKEN_FILE" 2>/dev/null || echo "")"
47
+ [ -n "$REMNIC_TOKEN" ] && break
48
+ done
49
+ [ -z "$REMNIC_TOKEN" ] && REMNIC_TOKEN="${OPENCLAW_REMNIC_ACCESS_TOKEN:-${OPENCLAW_ENGRAM_ACCESS_TOKEN:-}}"
50
+
51
+ INPUT="$(cat)"
52
+
53
+ # Return immediately — never block the tool
54
+ echo '{"continue":true}'
55
+
56
+ [ -z "$REMNIC_TOKEN" ] && exit 0
57
+
58
+ SESSION_ID="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.session_id||'')" "$INPUT" 2>/dev/null || echo "")"
59
+ TRANSCRIPT_PATH="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.transcript_path||'')" "$INPUT" 2>/dev/null || echo "")"
60
+ CWD="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.cwd||'')" "$INPUT" 2>/dev/null || echo "")"
61
+ TOOL_NAME="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.tool_name||'')" "$INPUT" 2>/dev/null || echo "")"
62
+ PROJECT_NAME="$(basename "$CWD" 2>/dev/null || echo "unknown")"
63
+
64
+ [ -z "$SESSION_ID" ] && exit 0
65
+ { [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; } && exit 0
66
+
67
+ LEGACY_CURSOR_FILE="/tmp/engram-cursor-${SESSION_ID}"
68
+ CURSOR_FILE="/tmp/remnic-cursor-${SESSION_ID}"
69
+ LEGACY_LOCK_DIR="/tmp/engram-lock-${SESSION_ID}.d"
70
+ LOCK_DIR="/tmp/remnic-lock-${SESSION_ID}.d"
71
+
72
+ if [ ! -f "$CURSOR_FILE" ] && { [ -f "$LEGACY_CURSOR_FILE" ] || [ -d "$LEGACY_LOCK_DIR" ]; }; then
73
+ CURSOR_FILE="$LEGACY_CURSOR_FILE"
74
+ LOCK_DIR="$LEGACY_LOCK_DIR"
75
+ fi
76
+
77
+ (
78
+ # Acquire exclusive lock
79
+ ACQUIRED=0
80
+ for _i in $(seq 1 50); do
81
+ if mkdir "$LOCK_DIR" 2>/dev/null; then ACQUIRED=1; break; fi
82
+ sleep 0.1
83
+ done
84
+ trap 'rmdir "$LOCK_DIR" 2>/dev/null' EXIT INT TERM
85
+ [ "$ACQUIRED" -eq 0 ] && exit 0
86
+
87
+ LAST_COUNT=0
88
+ [ -f "$CURSOR_FILE" ] && LAST_COUNT="$(cat "$CURSOR_FILE" 2>/dev/null || echo 0)"
89
+
90
+ PAYLOAD="$(node -e "
91
+ const fs = require('fs');
92
+ const path = process.argv[1];
93
+ const sessionId = process.argv[2];
94
+ const lastCount = parseInt(process.argv[3], 10) || 0;
95
+
96
+ const lines = fs.readFileSync(path, 'utf8').split('\n').filter(Boolean);
97
+ const messages = [];
98
+ for (const line of lines) {
99
+ try {
100
+ const entry = JSON.parse(line);
101
+ if (entry.type !== 'user' && entry.type !== 'assistant') continue;
102
+ const msg = entry.message;
103
+ if (!msg || typeof msg !== 'object') continue;
104
+ const role = msg.role;
105
+ if (role !== 'user' && role !== 'assistant') continue;
106
+ let text = '';
107
+ if (typeof msg.content === 'string') text = msg.content.trim();
108
+ else if (Array.isArray(msg.content)) {
109
+ text = msg.content
110
+ .filter(b => b.type === 'text' && b.text)
111
+ .map(b => b.text.trim())
112
+ .join('\n').trim();
113
+ }
114
+ if (text) messages.push({ role, content: text });
115
+ } catch {}
116
+ }
117
+
118
+ const newMessages = messages.slice(lastCount);
119
+ if (!newMessages.length) {
120
+ process.stdout.write('CURSOR:' + messages.length);
121
+ } else {
122
+ process.stdout.write(JSON.stringify({
123
+ sessionKey: sessionId,
124
+ messages: newMessages,
125
+ __total__: messages.length
126
+ }));
127
+ }
128
+ " "$TRANSCRIPT_PATH" "$SESSION_ID" "$LAST_COUNT" 2>/dev/null)"
129
+
130
+ [ -z "$PAYLOAD" ] && { log "parse failed for $SESSION_ID"; exit 0; }
131
+
132
+ if echo "$PAYLOAD" | grep -q "^CURSOR:"; then
133
+ echo "${PAYLOAD#CURSOR:}" > "$CURSOR_FILE"
134
+ exit 0
135
+ fi
136
+
137
+ TOTAL="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(String(d.__total__||0))" "$PAYLOAD" 2>/dev/null || echo 0)"
138
+ MSG_COUNT="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(String((d.messages||[]).length))" "$PAYLOAD" 2>/dev/null || echo "?")"
139
+ CLEAN="$(node -e "const d=JSON.parse(process.argv[1]); delete d.__total__; process.stdout.write(JSON.stringify(d))" "$PAYLOAD" 2>/dev/null)"
140
+
141
+ [ -z "$CLEAN" ] && exit 0
142
+
143
+ log "observing $MSG_COUNT new messages (cursor $LAST_COUNT->$TOTAL) project=$PROJECT_NAME tool=$TOOL_NAME"
144
+
145
+ RAW="$(curl -s -w "\n%{http_code}" --max-time 120 \
146
+ -X POST "$REMNIC_URL" \
147
+ -H "Authorization: Bearer ${REMNIC_TOKEN}" \
148
+ -H "Content-Type: application/json" \
149
+ -H "X-Engram-Client-Id: claude-code" \
150
+ -d "$CLEAN" 2>/dev/null)"
151
+ CURL_EXIT=$?
152
+ HTTP_STATUS="$(echo "$RAW" | tail -1)"
153
+
154
+ if [ $CURL_EXIT -eq 0 ] && [[ "$HTTP_STATUS" =~ ^2 ]]; then
155
+ log "observe OK for $SESSION_ID"
156
+ echo "$TOTAL" > "$CURSOR_FILE"
157
+ else
158
+ log "observe failed (curl=$CURL_EXIT http=$HTTP_STATUS) — cursor not advanced"
159
+ fi
160
+ ) >> "$LOG" 2>&1 &
161
+
162
+ disown $!
163
+ exit 0
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ # Remnic session cleanup for Claude Code.
3
+ # Removes cursor and lock files for the session.
4
+ #
5
+ # NOTE: Claude Code does not support a Stop/SessionEnd hook event.
6
+ # This script is provided for manual cleanup or future hook support.
7
+ # Temp files in /tmp/ are cleaned by the OS on reboot.
8
+
9
+ INPUT="$(cat)"
10
+ SESSION_ID="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.session_id||'')" "$INPUT" 2>/dev/null || echo "")"
11
+
12
+ echo '{"continue":true}'
13
+
14
+ [ -z "$SESSION_ID" ] && exit 0
15
+
16
+ rm -f "/tmp/remnic-cursor-${SESSION_ID}" 2>/dev/null
17
+ rmdir "/tmp/remnic-lock-${SESSION_ID}.d" 2>/dev/null
18
+ rm -f "/tmp/engram-cursor-${SESSION_ID}" 2>/dev/null
19
+ rmdir "/tmp/engram-lock-${SESSION_ID}.d" 2>/dev/null
20
+
21
+ exit 0
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env bash
2
+ # Remnic SessionStart hook for Claude Code.
3
+ # Recalls project context and user preferences at session start.
4
+ # Tries auto mode (45s) then falls back to minimal mode (20s).
5
+ # Starts daemon if not running.
6
+
7
+ set -euo pipefail
8
+
9
+ ensure_migrated() {
10
+ if [ -f "${HOME}/.remnic/.migrated-from-engram" ]; then
11
+ return 0
12
+ fi
13
+ if [ ! -d "${HOME}/.engram" ] && [ ! -f "${HOME}/.config/engram/config.json" ]; then
14
+ return 0
15
+ fi
16
+ if command -v remnic >/dev/null 2>&1; then
17
+ remnic migrate >/dev/null 2>&1 || true
18
+ elif command -v engram >/dev/null 2>&1; then
19
+ engram migrate >/dev/null 2>&1 || true
20
+ fi
21
+ }
22
+
23
+ ensure_migrated
24
+
25
+ REMNIC_HOST="${REMNIC_HOST:-${ENGRAM_HOST:-127.0.0.1}}"
26
+ REMNIC_PORT="${REMNIC_PORT:-${ENGRAM_PORT:-4318}}"
27
+ REMNIC_URL="http://${REMNIC_HOST}:${REMNIC_PORT}/engram/v1/recall"
28
+ REMNIC_HEALTH_URL="http://${REMNIC_HOST}:${REMNIC_PORT}/engram/v1/health"
29
+ TOKEN_FILES=("${HOME}/.remnic/tokens.json" "${HOME}/.engram/tokens.json")
30
+
31
+ LOG="${HOME}/.remnic/logs/remnic-session-recall.log"
32
+ mkdir -p "$(dirname "$LOG")"
33
+ log() { echo "$(date '+%F %T') [session-start] $*" >> "$LOG"; }
34
+
35
+ # Read token from per-plugin token store
36
+ REMNIC_TOKEN=""
37
+ for TOKEN_FILE in "${TOKEN_FILES[@]}"; do
38
+ [ ! -f "$TOKEN_FILE" ] && continue
39
+ REMNIC_TOKEN="$(node -e "
40
+ const store = JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'));
41
+ const tokens = store.tokens || [];
42
+ const cc = tokens.find(t => t.connector === 'claude-code');
43
+ const oc = tokens.find(t => t.connector === 'openclaw');
44
+ let tok = (cc && cc.token) || (oc && oc.token) || '';
45
+ if (!tok) { tok = store['claude-code'] || store['openclaw'] || ''; }
46
+ process.stdout.write(tok);
47
+ " "$TOKEN_FILE" 2>/dev/null || echo "")"
48
+ [ -n "$REMNIC_TOKEN" ] && break
49
+ done
50
+
51
+ # Fallback to env var
52
+ [ -z "$REMNIC_TOKEN" ] && REMNIC_TOKEN="${OPENCLAW_REMNIC_ACCESS_TOKEN:-${OPENCLAW_ENGRAM_ACCESS_TOKEN:-}}"
53
+
54
+ INPUT="$(cat)"
55
+ SESSION_ID="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.session_id||'')" "$INPUT" 2>/dev/null || echo "")"
56
+ CWD="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.cwd||'')" "$INPUT" 2>/dev/null || echo "")"
57
+ PROJECT_NAME="$(basename "$CWD" 2>/dev/null || echo "unknown")"
58
+
59
+ log "session=$SESSION_ID project=$PROJECT_NAME"
60
+
61
+ # Health check — start daemon if not running
62
+ if ! curl -sf --max-time 2 "$REMNIC_HEALTH_URL" >/dev/null 2>&1; then
63
+ log "daemon not responding, attempting start..."
64
+ if command -v remnic >/dev/null 2>&1; then
65
+ remnic daemon start >/dev/null 2>&1 &
66
+ elif command -v engram >/dev/null 2>&1; then
67
+ engram daemon start >/dev/null 2>&1 &
68
+ fi
69
+ sleep 2
70
+ if ! curl -sf --max-time 2 "$REMNIC_HEALTH_URL" >/dev/null 2>&1; then
71
+ log "daemon still not responding after start attempt"
72
+ echo '{"continue":true,"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"[Remnic: daemon not running — start with: remnic daemon start]"}}'
73
+ exit 0
74
+ fi
75
+ fi
76
+
77
+ if [ -z "$REMNIC_TOKEN" ]; then
78
+ log "skipping: no token found"
79
+ echo '{"continue":true,"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"[Remnic: no auth token — run: remnic connectors install claude-code]"}}'
80
+ exit 0
81
+ fi
82
+
83
+ QUERY="Starting a new coding session in project: ${PROJECT_NAME}. Recall relevant memories, preferences, decisions, patterns, and context about this project and the user."
84
+
85
+ REQUEST_BODY="$(node -e "process.stdout.write(JSON.stringify({
86
+ query: process.argv[1],
87
+ sessionKey: process.argv[2],
88
+ topK: 12,
89
+ mode: 'auto'
90
+ }))" "$QUERY" "$SESSION_ID" 2>/dev/null)"
91
+
92
+ [ -z "$REQUEST_BODY" ] && echo '{"continue":true}' && exit 0
93
+
94
+ log "attempting full recall (auto mode)..."
95
+ RAW="$(curl -s -w "\n%{http_code}" --max-time 45 \
96
+ -X POST "$REMNIC_URL" \
97
+ -H "Authorization: Bearer ${REMNIC_TOKEN}" \
98
+ -H "Content-Type: application/json" \
99
+ -H "X-Engram-Client-Id: claude-code" \
100
+ -d "$REQUEST_BODY" 2>/dev/null)"
101
+ CURL_EXIT=$?
102
+ HTTP_STATUS="$(echo "$RAW" | tail -1)"
103
+ RESPONSE="$(echo "$RAW" | sed '$d')"
104
+
105
+ if [ $CURL_EXIT -ne 0 ] || ! [[ "$HTTP_STATUS" =~ ^2 ]] || [ -z "$RESPONSE" ]; then
106
+ log "full recall failed (curl=$CURL_EXIT http=$HTTP_STATUS) — falling back to minimal"
107
+ MINIMAL_BODY="$(node -e "process.stdout.write(JSON.stringify({
108
+ query: process.argv[1],
109
+ sessionKey: process.argv[2],
110
+ topK: 8,
111
+ mode: 'minimal'
112
+ }))" "$QUERY" "$SESSION_ID" 2>/dev/null)"
113
+ RAW="$(curl -s -w "\n%{http_code}" --max-time 20 \
114
+ -X POST "$REMNIC_URL" \
115
+ -H "Authorization: Bearer ${REMNIC_TOKEN}" \
116
+ -H "Content-Type: application/json" \
117
+ -H "X-Engram-Client-Id: claude-code" \
118
+ -d "${MINIMAL_BODY:-$REQUEST_BODY}" 2>/dev/null)"
119
+ CURL_EXIT=$?
120
+ HTTP_STATUS="$(echo "$RAW" | tail -1)"
121
+ RESPONSE="$(echo "$RAW" | sed '$d')"
122
+ [[ "$CURL_EXIT" -eq 0 && "$HTTP_STATUS" =~ ^2 ]] && log "minimal recall succeeded" || { log "minimal recall also failed"; CURL_EXIT=1; }
123
+ fi
124
+
125
+ if [ $CURL_EXIT -eq 0 ] && [[ "$HTTP_STATUS" =~ ^2 ]] && [ -n "$RESPONSE" ]; then
126
+ CONTEXT="$(node -e "
127
+ const d = JSON.parse(process.argv[1]);
128
+ const ctx = d.context || '';
129
+ const count = d.count || 0;
130
+ const mode = d.mode || '';
131
+ if (ctx) {
132
+ const label = '[Remnic Memory Recall — ' + count + ' memories' + (mode ? ', ' + mode + ' mode' : '') + ']';
133
+ process.stdout.write(label + '\n\n' + ctx);
134
+ } else {
135
+ process.stdout.write('[Remnic: no relevant memories found for this session]');
136
+ }
137
+ " "$RESPONSE" 2>/dev/null || echo "[Remnic: recall parse error]")"
138
+ log "recall complete: $(echo "$CONTEXT" | head -1)"
139
+ else
140
+ CONTEXT="[Remnic: server unreachable — continuing without memory recall]"
141
+ log "$CONTEXT"
142
+ fi
143
+
144
+ node -e "
145
+ const context = process.argv[1];
146
+ process.stdout.write(JSON.stringify({
147
+ continue: true,
148
+ hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext: context }
149
+ }));
150
+ " "$CONTEXT"
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env bash
2
+ # Remnic UserPromptSubmit hook for Claude Code.
3
+ # Recalls per-prompt context using the user's message as query.
4
+ # Skips short prompts (<4 words). Minimal mode, 20s timeout.
5
+
6
+ set -euo pipefail
7
+
8
+ ensure_migrated() {
9
+ if [ -f "${HOME}/.remnic/.migrated-from-engram" ]; then
10
+ return 0
11
+ fi
12
+ if [ ! -d "${HOME}/.engram" ] && [ ! -f "${HOME}/.config/engram/config.json" ]; then
13
+ return 0
14
+ fi
15
+ if command -v remnic >/dev/null 2>&1; then
16
+ remnic migrate >/dev/null 2>&1 || true
17
+ elif command -v engram >/dev/null 2>&1; then
18
+ engram migrate >/dev/null 2>&1 || true
19
+ fi
20
+ }
21
+
22
+ ensure_migrated
23
+
24
+ REMNIC_HOST="${REMNIC_HOST:-${ENGRAM_HOST:-127.0.0.1}}"
25
+ REMNIC_PORT="${REMNIC_PORT:-${ENGRAM_PORT:-4318}}"
26
+ REMNIC_URL="http://${REMNIC_HOST}:${REMNIC_PORT}/engram/v1/recall"
27
+
28
+ LOG="${HOME}/.remnic/logs/remnic-user-prompt-recall.log"
29
+ mkdir -p "$(dirname "$LOG")"
30
+ log() { echo "$(date '+%F %T') [user-prompt] $*" >> "$LOG"; }
31
+
32
+ # Read token
33
+ REMNIC_TOKEN=""
34
+ for TOKEN_FILE in "${HOME}/.remnic/tokens.json" "${HOME}/.engram/tokens.json"; do
35
+ [ ! -f "$TOKEN_FILE" ] && continue
36
+ REMNIC_TOKEN="$(node -e "
37
+ const fs = require('fs');
38
+ const tokenFile = process.argv[1];
39
+ const store = JSON.parse(fs.readFileSync(tokenFile, 'utf8'));
40
+ const tokens = store.tokens || [];
41
+ const cc = tokens.find(t => t.connector === 'claude-code');
42
+ const oc = tokens.find(t => t.connector === 'openclaw');
43
+ let tok = (cc && cc.token) || (oc && oc.token) || '';
44
+ if (!tok) { tok = store['claude-code'] || store['openclaw'] || ''; }
45
+ process.stdout.write(tok);
46
+ " "$TOKEN_FILE" 2>/dev/null || echo "")"
47
+ [ -n "$REMNIC_TOKEN" ] && break
48
+ done
49
+ [ -z "$REMNIC_TOKEN" ] && REMNIC_TOKEN="${OPENCLAW_REMNIC_ACCESS_TOKEN:-${OPENCLAW_ENGRAM_ACCESS_TOKEN:-}}"
50
+
51
+ INPUT="$(cat)"
52
+
53
+ if [ -z "$REMNIC_TOKEN" ]; then
54
+ echo '{"continue":true}'
55
+ exit 0
56
+ fi
57
+
58
+ SESSION_ID="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.session_id||'')" "$INPUT" 2>/dev/null || echo "")"
59
+ PROMPT="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.prompt||'')" "$INPUT" 2>/dev/null || echo "")"
60
+
61
+ # Skip very short prompts
62
+ WORD_COUNT="$(echo "$PROMPT" | wc -w | tr -d ' ')"
63
+ if [ "$WORD_COUNT" -lt 4 ]; then
64
+ echo '{"continue":true}'
65
+ exit 0
66
+ fi
67
+
68
+ log "session=$SESSION_ID words=$WORD_COUNT"
69
+
70
+ REQUEST_BODY="$(node -e "process.stdout.write(JSON.stringify({
71
+ query: process.argv[1],
72
+ sessionKey: process.argv[2],
73
+ topK: 8,
74
+ mode: 'minimal'
75
+ }))" "$PROMPT" "$SESSION_ID" 2>/dev/null)"
76
+
77
+ [ -z "$REQUEST_BODY" ] && echo '{"continue":true}' && exit 0
78
+
79
+ RAW="$(curl -s -w "\n%{http_code}" --max-time 20 \
80
+ -X POST "$REMNIC_URL" \
81
+ -H "Authorization: Bearer ${REMNIC_TOKEN}" \
82
+ -H "Content-Type: application/json" \
83
+ -H "X-Engram-Client-Id: claude-code" \
84
+ -d "$REQUEST_BODY" 2>/dev/null)"
85
+ CURL_EXIT=$?
86
+ HTTP_STATUS="$(echo "$RAW" | tail -1)"
87
+ RESPONSE="$(echo "$RAW" | sed '$d')"
88
+
89
+ if [ $CURL_EXIT -ne 0 ] || ! [[ "$HTTP_STATUS" =~ ^2 ]] || [ -z "$RESPONSE" ]; then
90
+ log "recall failed (curl=$CURL_EXIT http=$HTTP_STATUS)"
91
+ echo '{"continue":true}'
92
+ exit 0
93
+ fi
94
+
95
+ node -e "
96
+ const d = JSON.parse(process.argv[1]);
97
+ const ctx = d.context || '';
98
+ const count = d.count || 0;
99
+ if (!ctx || count === 0) {
100
+ process.stdout.write(JSON.stringify({continue: true}));
101
+ } else {
102
+ process.stdout.write(JSON.stringify({
103
+ continue: true,
104
+ hookSpecificOutput: {
105
+ hookEventName: 'UserPromptSubmit',
106
+ additionalContext: '<remnic-memory count=\"' + count + '\">\n' + ctx + '\n</remnic-memory>'
107
+ }
108
+ }));
109
+ }
110
+ " "$RESPONSE" 2>/dev/null || echo '{"continue":true}'
111
+
112
+ COUNT="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(String(d.count||0))" "$RESPONSE" 2>/dev/null || echo "?")"
113
+ log "done: ${COUNT} memories injected"
@@ -0,0 +1,40 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "*",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "./hooks/bin/session-start.sh",
10
+ "timeout": 45000
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "PostToolUse": [
16
+ {
17
+ "matcher": "Write|Edit|MultiEdit",
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "./hooks/bin/post-tool-observe.sh",
22
+ "timeout": 10000
23
+ }
24
+ ]
25
+ }
26
+ ],
27
+ "UserPromptSubmit": [
28
+ {
29
+ "matcher": "*",
30
+ "hooks": [
31
+ {
32
+ "type": "command",
33
+ "command": "./hooks/bin/user-prompt-recall.sh",
34
+ "timeout": 20000
35
+ }
36
+ ]
37
+ }
38
+ ]
39
+ }
40
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@remnic/plugin-claude-code",
3
+ "version": "1.0.0",
4
+ "description": "Remnic memory plugin for Claude Code — hooks, skills, MCP integration",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/joshuaswarren/remnic.git",
10
+ "directory": "packages/plugin-claude-code"
11
+ },
12
+ "keywords": [
13
+ "remnic",
14
+ "memory",
15
+ "claude-code",
16
+ "plugin",
17
+ "ai-agent"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public",
21
+ "provenance": true
22
+ },
23
+ "files": [
24
+ ".claude-plugin",
25
+ "hooks",
26
+ "skills",
27
+ "agents",
28
+ ".mcp.json",
29
+ "settings.json"
30
+ ]
31
+ }
package/settings.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "mcp": {
4
+ "remnic": {
5
+ "allow": ["remnic_*", "engram_*"]
6
+ }
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: remnic-entities
3
+ description: Browse entities in the Remnic knowledge graph and surface their facts and relationships. Trigger phrases include "tell me about the entity", "look up", "what do we know about".
4
+ allowed-tools:
5
+ - remnic_entity_get
6
+ ---
7
+
8
+ ## When to use
9
+
10
+ Use when the user names a specific project, person, service, or concept and asks what is known about it. Entities are the structured graph view of memory — facts and relationships rather than free text.
11
+
12
+ Triggers:
13
+
14
+ - "Tell me about the entity …"
15
+ - "Look up …"
16
+ - `/remnic:entities <name>` slash command invocation.
17
+ - Any reference to a named thing that could have linked facts.
18
+
19
+ ## Inputs
20
+
21
+ - `name` (required) — canonical entity name, ideally as the user wrote it.
22
+ - Optional: entity type hint (project, person, service, tool).
23
+
24
+ ## Procedure
25
+
26
+ 1. Extract the entity name from the user's message. Preserve capitalization.
27
+ 2. Call `remnic_entity_get` with that name.
28
+ 3. If found, present facts, relationships, and a last-updated timestamp in a compact block.
29
+ 4. If missing, say so and offer to create it via `remnic-remember` with the user's permission.
30
+ 5. If multiple candidates match, list them briefly and ask which the user meant.
31
+
32
+ ## Efficiency plan
33
+
34
+ - Do not fetch speculatively for every proper noun — only when the user asks or the task depends on it.
35
+ - Cache the entity payload within the current turn.
36
+ - Pair with `remnic-recall` when unstructured context would round out the view.
37
+
38
+ ## Pitfalls and fixes
39
+
40
+ - **Pitfall:** Guessing entity names. **Fix:** Use the user's wording; disambiguate rather than inventing.
41
+ - **Pitfall:** Dumping the full payload. **Fix:** Summarize into facts, relations, last-updated.
42
+ - **Pitfall:** Treating missing entities as a bug. **Fix:** Missing just means the graph has not captured it yet.
43
+
44
+ ## Verification checklist
45
+
46
+ - [ ] `remnic_entity_get` was called with a user-supplied or user-confirmed name.
47
+ - [ ] Output shows facts, relationships, and timestamps in a compact view.
48
+ - [ ] Missing entities are acknowledged plainly with an offer to create.
49
+ - [ ] Canonical `remnic_entity_get` was used over legacy `engram_entity_get`.
50
+
51
+ > Tool names: canonical name is `remnic_entity_get`. The legacy `engram_entity_get` alias remains accepted during v1.x.
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: remnic-memory-workflow
3
+ description: Shared memory workflow for Claude Code agents connected to Remnic — recall before acting, observe during work, remember at the end. Trigger phrases include "what do you remember about", "save this for later", "any context from last time".
4
+ disable-model-invocation: true
5
+ allowed-tools:
6
+ - remnic_recall
7
+ - remnic_memory_store
8
+ - remnic_lcm_search
9
+ - remnic_entity_get
10
+ - remnic_observe
11
+ ---
12
+
13
+ ## When to use
14
+
15
+ Use this skill as the default playbook whenever Claude Code picks up a task that could benefit from prior context, or when the user explicitly asks the agent to remember or recall something. The individual `remnic-recall`, `remnic-remember`, `remnic-search`, `remnic-entities`, and `remnic-status` skills implement the detailed steps.
16
+
17
+ Triggers:
18
+
19
+ - New ticket, branch, or task begins.
20
+ - "What do you remember about …"
21
+ - "Save this for later."
22
+ - Long-running turn produces a durable outcome worth capturing.
23
+
24
+ ## Inputs
25
+
26
+ - Current user request (natural language).
27
+ - Optional: active project path, ticket number, branch name.
28
+ - Optional: topic keywords surfaced earlier in the session.
29
+
30
+ ## Procedure
31
+
32
+ 1. **Recall first.** Call `remnic_recall` with a concise natural-language query built from the user's request. Pull 3–8 results and filter for relevance.
33
+ 2. **Mention relevant memories briefly** to the user when they change the plan; otherwise use them as quiet context.
34
+ 3. **Observe during work.** For significant tool results (Write/Edit/MultiEdit, Bash exits, test output) call `remnic_observe` so Remnic keeps its ambient context fresh.
35
+ 4. **Deep search on demand.** If `remnic_recall` missed something the user insists exists, fall back to `remnic_lcm_search` with a more literal phrase.
36
+ 5. **Browse entities.** When the user names a project, person, or concept, call `remnic_entity_get` to pull facts and relations.
37
+ 6. **Remember at the end.** Before ending the turn, store durable decisions, preferences, and findings via `remnic_memory_store`.
38
+
39
+ ## Efficiency plan
40
+
41
+ - One broad recall beats several narrow ones.
42
+ - Skip recall for trivially local tasks (formatting, arithmetic, mechanical refactors).
43
+ - Reuse recall results within the same turn.
44
+ - Store each memory once; update rather than duplicate.
45
+
46
+ ## Pitfalls and fixes
47
+
48
+ - **Pitfall:** Storing transient state. **Fix:** Only store facts with durable value.
49
+ - **Pitfall:** Leaking secrets. **Fix:** Redact credentials and tokens before calling `remnic_memory_store`.
50
+ - **Pitfall:** Flooding the user with recalled context. **Fix:** Summarize in 1–3 bullet points.
51
+ - **Pitfall:** Forgetting the final write. **Fix:** Make "remember the decision" the last step of any non-trivial turn.
52
+
53
+ ## Verification checklist
54
+
55
+ - [ ] Recall was attempted before the agent committed to a plan.
56
+ - [ ] User-facing mentions of recalled context were concise and relevant.
57
+ - [ ] Durable decisions and findings were stored via `remnic_memory_store`.
58
+ - [ ] No secrets, credentials, or transient state were written.
59
+ - [ ] Canonical `remnic_*` tool names were used over legacy aliases.
60
+
61
+ > Tool names: this skill uses the canonical `remnic_*` MCP tools. Legacy `engram_*` aliases remain accepted during v1.x for backward compatibility.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: remnic-recall
3
+ description: Search Remnic memories by natural-language query. Trigger phrases include "what do you remember about", "recall anything on", "have we discussed".
4
+ allowed-tools:
5
+ - remnic_recall
6
+ ---
7
+
8
+ ## When to use
9
+
10
+ Use when the user or the current task needs prior context from Remnic. This is the default first step for any non-trivial Claude Code turn that could benefit from memory.
11
+
12
+ Triggers:
13
+
14
+ - "What do you remember about …"
15
+ - "Have we talked about …"
16
+ - `/remnic:recall <query>` slash command invocation.
17
+ - A new task begins and the agent wants background.
18
+
19
+ ## Inputs
20
+
21
+ - `query` (required) — natural-language question or topic string.
22
+ - Optional: caller-supplied budget hint (brief, deep).
23
+
24
+ ## Procedure
25
+
26
+ 1. Build a concise natural-language query from the user's message. Prefer the user's own wording.
27
+ 2. Call `remnic_recall` with that query; request 3–8 results unless the caller specified otherwise.
28
+ 3. Filter results for topical relevance.
29
+ 4. Present 1–5 bullet points summarizing the relevant memories, attributed when useful.
30
+ 5. If nothing relevant came back, say so plainly and suggest `remnic-remember` if there is something worth storing now.
31
+
32
+ ## Efficiency plan
33
+
34
+ - One broad recall beats several narrow ones.
35
+ - Reuse recall results within the same turn — do not re-query the same topic.
36
+ - Skip recall for trivially local tasks.
37
+
38
+ ## Pitfalls and fixes
39
+
40
+ - **Pitfall:** Quoting irrelevant recalls just because they came back. **Fix:** Filter for relevance before surfacing.
41
+ - **Pitfall:** Over-narrowing the query. **Fix:** Start broad; refine only when the first pass was noisy.
42
+ - **Pitfall:** Showing raw memory payloads. **Fix:** Summarize in the user's own terms.
43
+
44
+ ## Verification checklist
45
+
46
+ - [ ] `remnic_recall` was called with a natural-language query.
47
+ - [ ] Results were filtered for relevance before being shown.
48
+ - [ ] Summary is ≤ 5 bullets unless the user asked for more.
49
+ - [ ] Canonical `remnic_recall` was used over legacy `engram_recall`.
50
+
51
+ > Tool names: canonical name is `remnic_recall`. The legacy `engram_recall` alias remains accepted during v1.x.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: remnic-remember
3
+ description: Store a durable memory in Remnic so every connected agent can recall it. Trigger phrases include "remember this", "save this for later", "add a note that".
4
+ disable-model-invocation: true
5
+ allowed-tools:
6
+ - remnic_memory_store
7
+ ---
8
+
9
+ ## When to use
10
+
11
+ Use when the user explicitly asks Claude Code to remember something, or when a turn produces a durable decision, preference, or finding worth storing for later sessions.
12
+
13
+ Triggers:
14
+
15
+ - "Remember that …"
16
+ - "Save this for later."
17
+ - `/remnic:remember <text>` slash command invocation.
18
+ - End-of-turn consolidation after a non-trivial conclusion.
19
+
20
+ ## Inputs
21
+
22
+ - `content` (required) — the statement to store, in the user's own words where possible.
23
+ - Optional: category hint (preference, decision, fact, procedure).
24
+ - Optional: related entity or project name.
25
+
26
+ ## Procedure
27
+
28
+ 1. Confirm the content is durable — it should still be useful days or weeks from now.
29
+ 2. Strip any secrets, credentials, or transient context.
30
+ 3. Keep the user's voice; rephrase only when needed for clarity.
31
+ 4. Call `remnic_memory_store` with the text as `content`. Include category or entity hints when the tool accepts them.
32
+ 5. Confirm to the user in one line what was stored.
33
+ 6. Mention that the memory is available across connected agents (Claude Code, Codex, Hermes, OpenClaw).
34
+
35
+ ## Efficiency plan
36
+
37
+ - Batch related facts into a single memory rather than writing many tiny ones.
38
+ - If a prior memory on the same topic exists, update it rather than duplicating.
39
+ - Do not store anything easily re-derived from source code or docs.
40
+
41
+ ## Pitfalls and fixes
42
+
43
+ - **Pitfall:** Storing transient state. **Fix:** Only commit facts that will still matter tomorrow.
44
+ - **Pitfall:** Leaking secrets. **Fix:** Redact before calling the tool.
45
+ - **Pitfall:** Duplicates on the same topic. **Fix:** Recall first; prefer update.
46
+ - **Pitfall:** Vague entries. **Fix:** Be specific — who, what, when, why.
47
+
48
+ ## Verification checklist
49
+
50
+ - [ ] Content is durable and specific.
51
+ - [ ] No secrets or PII were included.
52
+ - [ ] `remnic_memory_store` was called with the final content.
53
+ - [ ] User received a one-line confirmation.
54
+ - [ ] Canonical `remnic_memory_store` was used over legacy `engram_memory_store`.
55
+
56
+ > Tool names: canonical name is `remnic_memory_store`. The legacy `engram_memory_store` alias remains accepted during v1.x.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: remnic-search
3
+ description: Run a deep full-text search across every Remnic memory. Trigger phrases include "search memories for", "find anything about", "deep search".
4
+ allowed-tools:
5
+ - remnic_lcm_search
6
+ ---
7
+
8
+ ## When to use
9
+
10
+ Use when `remnic-recall` did not surface something the user insists exists, or when the task genuinely needs exhaustive coverage rather than a ranked semantic summary.
11
+
12
+ Triggers:
13
+
14
+ - "Search memories for …"
15
+ - "Deep search on …"
16
+ - `/remnic:search <query>` slash command invocation.
17
+ - Follow-up after `remnic-recall` returned nothing useful.
18
+
19
+ ## Inputs
20
+
21
+ - `query` (required) — literal phrase or keyword string; this is full-text, not semantic.
22
+ - Optional: date range, category filter, entity constraint.
23
+
24
+ ## Procedure
25
+
26
+ 1. Pick the most literal phrase the user expects to match.
27
+ 2. Call `remnic_lcm_search` with that phrase.
28
+ 3. Group results by date or category when the tool returns enough metadata.
29
+ 4. Present the top 5–10 matches with dates and short excerpts.
30
+ 5. If still nothing, say so plainly and offer `remnic-remember` for capturing the content going forward.
31
+
32
+ ## Efficiency plan
33
+
34
+ - Use the most specific phrase available.
35
+ - One targeted search beats several broad ones.
36
+ - Do not re-run a deep search for the same topic in the same turn after recall already covered it.
37
+
38
+ ## Pitfalls and fixes
39
+
40
+ - **Pitfall:** Using `remnic_lcm_search` as the default. **Fix:** Start with `remnic_recall`; escalate only when recall fails.
41
+ - **Pitfall:** Pasting huge result dumps. **Fix:** Show top matches with excerpts.
42
+ - **Pitfall:** Over-broad queries. **Fix:** Require at least one distinctive keyword.
43
+
44
+ ## Verification checklist
45
+
46
+ - [ ] `remnic_lcm_search` was called only after recall fell short or exhaustive matching was required.
47
+ - [ ] Results were grouped by relevance or date when possible.
48
+ - [ ] Output shows excerpts, not raw payloads.
49
+ - [ ] Canonical `remnic_lcm_search` was used over legacy `engram_lcm_search`.
50
+
51
+ > Tool names: canonical name is `remnic_lcm_search`. The legacy `engram_lcm_search` alias remains accepted during v1.x.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: remnic-status
3
+ description: Check the health of the Remnic daemon, stores, and connected clients. Trigger phrases include "is remnic running", "check memory status", "daemon health".
4
+ ---
5
+
6
+ ## When to use
7
+
8
+ Use when the user asks whether Remnic is running, when recall or store calls start failing, or when diagnosing cross-agent memory issues from Claude Code.
9
+
10
+ Triggers:
11
+
12
+ - "Is Remnic running?"
13
+ - "Check memory status."
14
+ - `/remnic:status` slash command invocation.
15
+ - Recall/store tools returned connection errors in this turn.
16
+
17
+ ## Inputs
18
+
19
+ - Optional: specific component to check (daemon, HTTP server, MCP server, store backend).
20
+
21
+ ## Procedure
22
+
23
+ 1. Check the Remnic health endpoint via the MCP bridge or run `remnic daemon status` in a shell.
24
+ 2. Report, in a compact block:
25
+ - Daemon running state (PID if known).
26
+ - Listening port(s).
27
+ - Memory store path.
28
+ - Connected clients or plugins, if exposed.
29
+ 3. If the daemon is not running, suggest `remnic daemon start` and mention the log path.
30
+ 4. If recall/store tools were erroring earlier in the turn, correlate the health state with those errors in one sentence.
31
+
32
+ ## Efficiency plan
33
+
34
+ - One health call per turn is enough; do not poll.
35
+ - Skip the check for trivially local tasks.
36
+ - Reuse the health payload for downstream troubleshooting within the same turn.
37
+
38
+ ## Pitfalls and fixes
39
+
40
+ - **Pitfall:** Running `remnic daemon status` on a host where the daemon lives in a container. **Fix:** Prefer the MCP health endpoint, or run the CLI inside the container.
41
+ - **Pitfall:** Reporting "down" from a single failed tool call. **Fix:** Confirm with the health endpoint before claiming an outage.
42
+ - **Pitfall:** Forgetting the log path. **Fix:** Always include a pointer to logs on failure.
43
+
44
+ ## Verification checklist
45
+
46
+ - [ ] Health was checked via the endpoint or CLI, not guessed.
47
+ - [ ] Daemon state, port, store path, and clients were reported concisely.
48
+ - [ ] If unhealthy, the user got a concrete next step.
49
+ - [ ] Canonical `remnic daemon` wording was used over legacy `engram daemon` where the CLI has been renamed.
50
+
51
+ > CLI names: canonical CLI is `remnic daemon`. The legacy `engram daemon` invocation remains accepted during v1.x.