@remnic/plugin-claude-code 9.3.572 → 9.3.573
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remnic",
|
|
3
3
|
"description": "Universal memory for AI agents — automatic recall, observation, and cross-agent knowledge sharing",
|
|
4
|
-
"version": "9.3.
|
|
4
|
+
"version": "9.3.573",
|
|
5
5
|
"author": "Joshua Warren",
|
|
6
6
|
"homepage": "https://github.com/joshuaswarren/remnic",
|
|
7
7
|
"repository": "https://github.com/joshuaswarren/remnic"
|
|
@@ -61,31 +61,147 @@ CWD="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.cwd|
|
|
|
61
61
|
TOOL_NAME="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.tool_name||'')" "$INPUT" 2>/dev/null || echo "")"
|
|
62
62
|
PROJECT_NAME="$(basename "$CWD" 2>/dev/null || echo "unknown")"
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
case "$SESSION_ID" in
|
|
65
|
+
""|*[!A-Za-z0-9._-]*)
|
|
66
|
+
log "invalid session id: $SESSION_ID"
|
|
67
|
+
exit 0
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
65
70
|
{ [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; } && exit 0
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
STATE_HOME="${XDG_STATE_HOME:-${HOME}/.local/state}"
|
|
73
|
+
STATE_DIR="${STATE_HOME}/remnic/hooks"
|
|
74
|
+
|
|
75
|
+
mkdir -p "$STATE_DIR" 2>/dev/null || exit 0
|
|
76
|
+
if ! node - "$STATE_DIR" <<'NODE'
|
|
77
|
+
const fs = require('fs');
|
|
78
|
+
const stateDir = process.argv[2];
|
|
79
|
+
try {
|
|
80
|
+
const info = fs.lstatSync(stateDir);
|
|
81
|
+
if (info.isSymbolicLink() || !info.isDirectory()) process.exit(1);
|
|
82
|
+
if (typeof process.getuid === 'function' && info.uid !== process.getuid()) process.exit(1);
|
|
83
|
+
if ((info.mode & 0o077) !== 0) fs.chmodSync(stateDir, 0o700);
|
|
84
|
+
} catch {
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
NODE
|
|
88
|
+
then
|
|
89
|
+
log "unsafe state directory $STATE_DIR"
|
|
90
|
+
exit 0
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
CURSOR_FILE="${STATE_DIR}/remnic-cursor-${SESSION_ID}"
|
|
94
|
+
LOCK_DIR="${STATE_DIR}/remnic-lock-${SESSION_ID}.d"
|
|
95
|
+
LEGACY_CURSOR_FILE="${STATE_DIR}/engram-cursor-${SESSION_ID}"
|
|
96
|
+
LEGACY_LOCK_DIR="${STATE_DIR}/engram-lock-${SESSION_ID}.d"
|
|
71
97
|
|
|
72
98
|
if [ ! -f "$CURSOR_FILE" ] && { [ -f "$LEGACY_CURSOR_FILE" ] || [ -d "$LEGACY_LOCK_DIR" ]; }; then
|
|
73
99
|
CURSOR_FILE="$LEGACY_CURSOR_FILE"
|
|
74
100
|
LOCK_DIR="$LEGACY_LOCK_DIR"
|
|
75
101
|
fi
|
|
76
102
|
|
|
103
|
+
validate_cursor_file() {
|
|
104
|
+
node - "$CURSOR_FILE" <<'NODE'
|
|
105
|
+
const fs = require('fs');
|
|
106
|
+
const cursorFile = process.argv[2];
|
|
107
|
+
try {
|
|
108
|
+
const info = fs.lstatSync(cursorFile);
|
|
109
|
+
if (info.isSymbolicLink() || !info.isFile()) process.exit(1);
|
|
110
|
+
if (typeof process.getuid === 'function' && info.uid !== process.getuid()) process.exit(1);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (error && error.code === 'ENOENT') process.exit(0);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
NODE
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
read_cursor_file() {
|
|
119
|
+
validate_cursor_file || {
|
|
120
|
+
log "unsafe cursor file $CURSOR_FILE"
|
|
121
|
+
return 1
|
|
122
|
+
}
|
|
123
|
+
[ -f "$CURSOR_FILE" ] && cat "$CURSOR_FILE" 2>/dev/null || echo 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
write_cursor_file() {
|
|
127
|
+
NEW_CURSOR_VALUE="$1"
|
|
128
|
+
validate_cursor_file || {
|
|
129
|
+
log "refusing unsafe cursor file $CURSOR_FILE"
|
|
130
|
+
return 1
|
|
131
|
+
}
|
|
132
|
+
TEMP_CURSOR="$(mktemp "${CURSOR_FILE}.tmp.XXXXXX" 2>/dev/null)" || return 1
|
|
133
|
+
if ! printf '%s\n' "$NEW_CURSOR_VALUE" > "$TEMP_CURSOR"; then
|
|
134
|
+
rm -f "$TEMP_CURSOR"
|
|
135
|
+
return 1
|
|
136
|
+
fi
|
|
137
|
+
chmod 600 "$TEMP_CURSOR" 2>/dev/null || true
|
|
138
|
+
mv -f "$TEMP_CURSOR" "$CURSOR_FILE"
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
migrate_tmp_cursor_file() {
|
|
142
|
+
for TMP_CURSOR_FILE in "/tmp/remnic-cursor-${SESSION_ID}" "/tmp/engram-cursor-${SESSION_ID}"; do
|
|
143
|
+
[ ! -e "$TMP_CURSOR_FILE" ] && continue
|
|
144
|
+
TMP_CURSOR_VALUE="$(node - "$TMP_CURSOR_FILE" <<'NODE'
|
|
145
|
+
const fs = require('fs');
|
|
146
|
+
const cursorFile = process.argv[2];
|
|
147
|
+
try {
|
|
148
|
+
const info = fs.lstatSync(cursorFile);
|
|
149
|
+
if (info.isSymbolicLink() || !info.isFile()) process.exit(1);
|
|
150
|
+
if (typeof process.getuid === 'function' && info.uid !== process.getuid()) process.exit(1);
|
|
151
|
+
const value = fs.readFileSync(cursorFile, 'utf8').trim();
|
|
152
|
+
if (!/^\d+$/.test(value)) process.exit(1);
|
|
153
|
+
process.stdout.write(value);
|
|
154
|
+
} catch {
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
NODE
|
|
158
|
+
)" || continue
|
|
159
|
+
CURRENT_CURSOR_VALUE=""
|
|
160
|
+
if validate_cursor_file; then
|
|
161
|
+
CURRENT_CURSOR_VALUE="$([ -f "$CURSOR_FILE" ] && cat "$CURSOR_FILE" 2>/dev/null || echo "")"
|
|
162
|
+
fi
|
|
163
|
+
case "$CURRENT_CURSOR_VALUE" in
|
|
164
|
+
""|*[!0-9]*) CURRENT_CURSOR_VALUE="-1" ;;
|
|
165
|
+
esac
|
|
166
|
+
if [ "$TMP_CURSOR_VALUE" -gt "$CURRENT_CURSOR_VALUE" ]; then
|
|
167
|
+
write_cursor_file "$TMP_CURSOR_VALUE" || continue
|
|
168
|
+
fi
|
|
169
|
+
rm -f "$TMP_CURSOR_FILE" 2>/dev/null
|
|
170
|
+
done
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
remove_stale_lock_dir() {
|
|
174
|
+
node - "$LOCK_DIR" <<'NODE'
|
|
175
|
+
const fs = require('fs');
|
|
176
|
+
const lockDir = process.argv[2];
|
|
177
|
+
try {
|
|
178
|
+
const info = fs.lstatSync(lockDir);
|
|
179
|
+
if (info.isSymbolicLink() || !info.isDirectory()) process.exit(1);
|
|
180
|
+
if (typeof process.getuid === 'function' && info.uid !== process.getuid()) process.exit(1);
|
|
181
|
+
if (Date.now() - info.mtimeMs < 10 * 60 * 1000) process.exit(0);
|
|
182
|
+
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (error && error.code === 'ENOENT') process.exit(0);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
NODE
|
|
188
|
+
}
|
|
189
|
+
|
|
77
190
|
(
|
|
78
191
|
# Acquire exclusive lock
|
|
79
192
|
ACQUIRED=0
|
|
80
193
|
for _i in $(seq 1 50); do
|
|
81
194
|
if mkdir "$LOCK_DIR" 2>/dev/null; then ACQUIRED=1; break; fi
|
|
195
|
+
[ "$_i" -eq 1 ] && remove_stale_lock_dir >/dev/null 2>&1
|
|
82
196
|
sleep 0.1
|
|
83
197
|
done
|
|
84
198
|
trap 'rmdir "$LOCK_DIR" 2>/dev/null' EXIT INT TERM
|
|
85
199
|
[ "$ACQUIRED" -eq 0 ] && exit 0
|
|
86
200
|
|
|
201
|
+
migrate_tmp_cursor_file
|
|
202
|
+
|
|
87
203
|
LAST_COUNT=0
|
|
88
|
-
|
|
204
|
+
LAST_COUNT="$(read_cursor_file)" || exit 0
|
|
89
205
|
|
|
90
206
|
PAYLOAD="$(node -e "
|
|
91
207
|
const fs = require('fs');
|
|
@@ -130,7 +246,7 @@ fi
|
|
|
130
246
|
[ -z "$PAYLOAD" ] && { log "parse failed for $SESSION_ID"; exit 0; }
|
|
131
247
|
|
|
132
248
|
if echo "$PAYLOAD" | grep -q "^CURSOR:"; then
|
|
133
|
-
|
|
249
|
+
write_cursor_file "${PAYLOAD#CURSOR:}" || log "cursor write failed for $SESSION_ID"
|
|
134
250
|
exit 0
|
|
135
251
|
fi
|
|
136
252
|
|
|
@@ -153,7 +269,7 @@ fi
|
|
|
153
269
|
|
|
154
270
|
if [ $CURL_EXIT -eq 0 ] && [[ "$HTTP_STATUS" =~ ^2 ]]; then
|
|
155
271
|
log "observe OK for $SESSION_ID"
|
|
156
|
-
|
|
272
|
+
write_cursor_file "$TOTAL" || log "cursor write failed for $SESSION_ID"
|
|
157
273
|
else
|
|
158
274
|
log "observe failed (curl=$CURL_EXIT http=$HTTP_STATUS) — cursor not advanced"
|
|
159
275
|
fi
|
package/hooks/bin/session-end.sh
CHANGED
|
@@ -1,21 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Remnic session cleanup for Claude Code.
|
|
3
|
-
# Removes cursor and lock files for the session.
|
|
3
|
+
# Removes private cursor and lock files for the session.
|
|
4
4
|
#
|
|
5
5
|
# NOTE: Claude Code does not support a Stop/SessionEnd hook event.
|
|
6
6
|
# This script is provided for manual cleanup or future hook support.
|
|
7
|
-
#
|
|
7
|
+
# Private state files live under ${XDG_STATE_HOME:-$HOME/.local/state}/remnic/hooks.
|
|
8
8
|
|
|
9
9
|
INPUT="$(cat)"
|
|
10
10
|
SESSION_ID="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.session_id||'')" "$INPUT" 2>/dev/null || echo "")"
|
|
11
11
|
|
|
12
12
|
echo '{"continue":true}'
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
case "$SESSION_ID" in
|
|
15
|
+
""|*[!A-Za-z0-9._-]*)
|
|
16
|
+
exit 0
|
|
17
|
+
;;
|
|
18
|
+
esac
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
STATE_HOME="${XDG_STATE_HOME:-${HOME}/.local/state}"
|
|
21
|
+
STATE_DIR="${STATE_HOME}/remnic/hooks"
|
|
22
|
+
CURSOR_FILE="${STATE_DIR}/remnic-cursor-${SESSION_ID}"
|
|
23
|
+
LOCK_DIR="${STATE_DIR}/remnic-lock-${SESSION_ID}.d"
|
|
24
|
+
LEGACY_CURSOR_FILE="${STATE_DIR}/engram-cursor-${SESSION_ID}"
|
|
25
|
+
LEGACY_LOCK_DIR="${STATE_DIR}/engram-lock-${SESSION_ID}.d"
|
|
26
|
+
|
|
27
|
+
if [ -d "$STATE_DIR" ] && [ ! -L "$STATE_DIR" ]; then
|
|
28
|
+
if [ -e "$CURSOR_FILE" ] && [ ! -L "$CURSOR_FILE" ]; then
|
|
29
|
+
rm -f "$CURSOR_FILE" 2>/dev/null
|
|
30
|
+
fi
|
|
31
|
+
rmdir "$LOCK_DIR" 2>/dev/null
|
|
32
|
+
if [ -e "$LEGACY_CURSOR_FILE" ] && [ ! -L "$LEGACY_CURSOR_FILE" ]; then
|
|
33
|
+
rm -f "$LEGACY_CURSOR_FILE" 2>/dev/null
|
|
34
|
+
fi
|
|
35
|
+
rmdir "$LEGACY_LOCK_DIR" 2>/dev/null
|
|
36
|
+
fi
|
|
20
37
|
|
|
21
38
|
exit 0
|