chat-recall 0.5.5 → 0.5.6

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.
@@ -33,6 +33,32 @@ fi
33
33
  CHAT_RECALL_BIN="${CHAT_RECALL_BIN:-$(command -v chat-recall || true)}"
34
34
  if [[ -z "$CHAT_RECALL_BIN" ]] || [[ ! -x "$CHAT_RECALL_BIN" ]]; then exit 0; fi
35
35
 
36
+ # Run a command under a time limit, on any platform.
37
+ #
38
+ # `timeout` is GNU coreutils and macOS does not ship it. Both hooks that used it
39
+ # swallowed the failure with `|| true`, so on every Mac the command never ran at
40
+ # all — silently, while `chat-recall doctor` reported the hook installed and
41
+ # healthy. Homebrew's coreutils installs it as `gtimeout`, so prefer whichever
42
+ # exists and fall back to a plain shell watchdog when neither does.
43
+ run_limited() {
44
+ local secs="$1"; shift
45
+ if command -v timeout >/dev/null 2>&1; then
46
+ timeout "$secs" "$@"
47
+ elif command -v gtimeout >/dev/null 2>&1; then
48
+ gtimeout "$secs" "$@"
49
+ else
50
+ # Watchdog: run in the background, kill it if it outlives the budget.
51
+ "$@" &
52
+ local cmd_pid=$!
53
+ ( sleep "$secs"; kill -TERM "$cmd_pid" 2>/dev/null || true ) &
54
+ local watch_pid=$!
55
+ wait "$cmd_pid" 2>/dev/null
56
+ local rc=$?
57
+ kill -TERM "$watch_pid" 2>/dev/null || true
58
+ return $rc
59
+ fi
60
+ }
61
+
36
62
  TIMEOUT="${CHAT_RECALL_ESCALATE_TIMEOUT:-60}"
37
63
  DELAY="${CHAT_RECALL_ESCALATE_DELAY:-20}"
38
64
 
@@ -42,9 +68,9 @@ DELAY="${CHAT_RECALL_ESCALATE_DELAY:-20}"
42
68
  sleep "$DELAY"
43
69
  cd "$CWD" 2>/dev/null || true
44
70
  if [[ -n "$SESSION_ID" ]]; then
45
- timeout "$TIMEOUT" "$CHAT_RECALL_BIN" escalate "$SESSION_ID" >/dev/null 2>&1 || true
71
+ run_limited "$TIMEOUT" "$CHAT_RECALL_BIN" escalate "$SESSION_ID" >/dev/null 2>&1 || true
46
72
  else
47
- timeout "$TIMEOUT" "$CHAT_RECALL_BIN" escalate --latest >/dev/null 2>&1 || true
73
+ run_limited "$TIMEOUT" "$CHAT_RECALL_BIN" escalate --latest >/dev/null 2>&1 || true
48
74
  fi
49
75
  ) </dev/null >/dev/null 2>&1 &
50
76
 
@@ -109,7 +109,7 @@ extract_facts() {
109
109
  "tool_uses": $tools,
110
110
  "first_topic": "$(echo "$first_topic" | jq -Rs '.' 2>/dev/null || echo "\"$first_topic\"")",
111
111
  "decisions": "$(echo "$decisions" | jq -Rs '.' 2>/dev/null || echo "\"\"")",
112
- "saved_at": "$(date -Iseconds)"
112
+ "saved_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
113
113
  }
114
114
  EOF
115
115
  }
@@ -176,7 +176,12 @@ do_save() {
176
176
  fi
177
177
 
178
178
  # Track this save
179
- echo "{\"session_id\": \"$SESSION_ID\", \"saved_at\": \"$(date -Iseconds)\", \"precompact\": $precompact}" > "${STATE_FILE}"
179
+ # `date -Iseconds` is GNU-only: BSD date (macOS) rejects -I and prints an
180
+ # empty string, so this field was blank on every Mac. -u +format works on
181
+ # both. saved_at_epoch is written alongside it because reading the ISO value
182
+ # BACK needed `date -d`, which BSD also lacks — storing the number means
183
+ # nothing has to parse a date at all.
184
+ echo "{\"session_id\": \"$SESSION_ID\", \"saved_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\", \"saved_at_epoch\": $(date +%s), \"precompact\": $precompact}" > "${STATE_FILE}"
180
185
 
181
186
  if [[ "$precompact" == "true" ]]; then
182
187
  log "[PRECOMPACT] Emergency save for $SESSION_ID"
@@ -193,17 +198,18 @@ should_save() {
193
198
  return 0
194
199
  fi
195
200
 
196
- # Check last save time
197
- local last_save
198
- last_save=$(jq -r '.saved_at // empty' "${STATE_FILE}" 2>/dev/null || echo "")
199
-
200
- if [[ -z "$last_save" ]]; then
201
+ # Read the epoch we stored. The old code kept only an ISO string and parsed
202
+ # it with `date -d`, which does not exist on BSD date — so on macOS this
203
+ # always resolved to 0, the throttle never engaged, and the hook re-scanned
204
+ # the entire transcript on EVERY Stop event.
205
+ local last_save_epoch
206
+ last_save_epoch=$(jq -r '.saved_at_epoch // empty' "${STATE_FILE}" 2>/dev/null || echo "")
207
+
208
+ if [[ -z "$last_save_epoch" ]]; then
209
+ # A state file written before saved_at_epoch existed. Treat it as due
210
+ # rather than trying to parse its timestamp portably.
201
211
  return 0
202
212
  fi
203
-
204
- # Don't save more than once per 30 seconds
205
- local last_save_epoch
206
- last_save_epoch=$(date -d "$last_save" +%s 2>/dev/null || echo 0)
207
213
  local now_epoch
208
214
  now_epoch=$(date +%s)
209
215
 
@@ -27,7 +27,33 @@ CHAT_RECALL_BIN="${CHAT_RECALL_BIN:-$(command -v chat-recall || true)}"
27
27
  if [[ -z "$CHAT_RECALL_BIN" ]] || [[ ! -x "$CHAT_RECALL_BIN" ]]; then exit 0; fi
28
28
 
29
29
  TIMEOUT="${CHAT_RECALL_WAKEUP_TIMEOUT:-10}"
30
- OUT="$(timeout "$TIMEOUT" "$CHAT_RECALL_BIN" memory wake-up -p "$PROJECT" 2>/dev/null || true)"
30
+ # Run a command under a time limit, on any platform.
31
+ #
32
+ # `timeout` is GNU coreutils and macOS does not ship it. Both hooks that used it
33
+ # swallowed the failure with `|| true`, so on every Mac the command never ran at
34
+ # all — silently, while `chat-recall doctor` reported the hook installed and
35
+ # healthy. Homebrew's coreutils installs it as `gtimeout`, so prefer whichever
36
+ # exists and fall back to a plain shell watchdog when neither does.
37
+ run_limited() {
38
+ local secs="$1"; shift
39
+ if command -v timeout >/dev/null 2>&1; then
40
+ timeout "$secs" "$@"
41
+ elif command -v gtimeout >/dev/null 2>&1; then
42
+ gtimeout "$secs" "$@"
43
+ else
44
+ # Watchdog: run in the background, kill it if it outlives the budget.
45
+ "$@" &
46
+ local cmd_pid=$!
47
+ ( sleep "$secs"; kill -TERM "$cmd_pid" 2>/dev/null || true ) &
48
+ local watch_pid=$!
49
+ wait "$cmd_pid" 2>/dev/null
50
+ local rc=$?
51
+ kill -TERM "$watch_pid" 2>/dev/null || true
52
+ return $rc
53
+ fi
54
+ }
55
+
56
+ OUT="$(run_limited "$TIMEOUT" "$CHAT_RECALL_BIN" memory wake-up -p "$PROJECT" 2>/dev/null || true)"
31
57
  [[ -z "$OUT" ]] && exit 0
32
58
 
33
59
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-recall",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/munhq/chat-recall.git"
@@ -11,6 +11,9 @@
11
11
  },
12
12
  "description": "Local-first memory for AI coding tools \u2014 indexes Claude Code / Gemini CLI / OpenCode / Codex sessions, exposes them over MCP, optionally syncs redacted history to a chat-recall server",
13
13
  "type": "module",
14
+ "engines": {
15
+ "node": ">=22.5.0"
16
+ },
14
17
  "license": "Elastic-2.0",
15
18
  "bin": {
16
19
  "chat-recall": "./dist/cli.js",
@@ -25,9 +28,10 @@
25
28
  "LICENSE"
26
29
  ],
27
30
  "scripts": {
28
- "build": "tsc -b && node scripts/bundle.mjs && node scripts/postbuild.mjs",
31
+ "build": "tsc -b && tsc -p tsconfig.daemon.json && node scripts/bundle.mjs && node scripts/postbuild.mjs",
29
32
  "build:types": "tsc -b",
30
- "auto-indexer": "tsx auto-indexer/indexer.ts"
33
+ "auto-indexer": "tsx auto-indexer/indexer.ts",
34
+ "typecheck:daemon": "tsc -p tsconfig.daemon.json"
31
35
  },
32
36
  "dependencies": {
33
37
  "@modelcontextprotocol/sdk": "^1.0.0",